1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2024-12-26 16:18:22 +00:00

Update Cardano to support Shelley era 1/3

Update protobuf

- Previous transactions don't need to be sent anymore, because fee is
  included in the transaction now. Thus transactions_count can be
  removed from CardanoSignTx message and the CardanoTxAck and
  CardanoTxRequest messages can be removed altogether.
- CardanoTxInputType.type is unused so remove it

Add NULL (None type) serialisation to CBOR

- Transaction metada must either have a valid structure or CBOR NULL
  must be used (if metadata is empty) - it can't be simply left out.

Add protocol_magics file

- Just to have a nicer way of representing protocol magics

Update transaction signing

- Previous transactions no longer need to be requested
- Output building is simplified, since fee doesn't need to be calculated
- Remove transaction class since it is no longer needed (only functions
  remained)
- Reorder functions so it reads top to bottom

Add protocol magic to byron address on testnet

- This has always been a part of the spec, but it hasn't been
  implemented before, because it wasn't really needed.

Update trezorlib

Update tests

- Transaction messages are no longer required
- Expected values are different since tx format changed
- Common values in test cases have been extracted

Remove unused file

- Progress was used when receiving previous transactions

Add CRC check to output address validation
This commit is contained in:
gabrielkerekes 2020-07-01 15:43:02 +02:00 committed by matejcik
parent 88aa3cf168
commit e1615e60ec
36 changed files with 736 additions and 682 deletions

View File

@ -14,8 +14,9 @@ import "messages-common.proto";
* @next Failure
*/
message CardanoGetAddress {
repeated uint32 address_n = 1; // BIP-32-style path to derive the key from master node
optional bool show_display = 2; // optionally prompt for confirmation on trezor display
repeated uint32 address_n = 1; // BIP-32-style path to derive the key from master node
optional bool show_display = 2; // optionally prompt for confirmation on trezor display
optional uint32 protocol_magic = 3; // network's protocol magic - needed for Byron addresses on testnets
}
/**
@ -50,14 +51,15 @@ message CardanoPublicKey {
* Request: Ask device to sign Cardano transaction
* @start
* @next CardanoSignedTx
* @next CardanoTxRequest
* @next Failure
*/
message CardanoSignTx {
repeated CardanoTxInputType inputs = 1; // inputs to be used in transaction
repeated CardanoTxOutputType outputs = 2; // outputs to be used in transaction
optional uint32 transactions_count = 3; // transactions count
// optional uint32 transactions_count = 3; // left as a comment so we know to skip the id 3 in the future
optional uint32 protocol_magic = 5; // network's protocol magic
optional uint64 fee = 6; // transaction fee - added in shelley
optional uint64 ttl = 7; // transaction ttl - added in shelley
/**
* Structure representing cardano transaction input
*/
@ -65,7 +67,8 @@ message CardanoSignTx {
repeated uint32 address_n = 1; // BIP-32 path to derive the key from master node
optional bytes prev_hash = 2; // hash of previous transaction output to spend by this input
optional uint32 prev_index = 3; // index of previous output to spend
optional uint32 type = 4; // input type, defaults to 0
// left as a comment so we know to skip the id 4 in the future
// optional uint32 type = 4;
}
/**
* Structure representing cardano transaction output
@ -77,31 +80,11 @@ message CardanoSignTx {
}
}
/**
* Response: Serialised signed cardano transaction if tx_index is not specified.
* If tx_index is specified, trezor will wait for transaction
* @next CardanoTxAck
*/
message CardanoTxRequest {
optional uint32 tx_index = 1; // index of requested transaction
optional bytes tx_hash = 2; // hash of the signed transaction
optional bytes tx_body = 3; // serialised body of the signed transaction
}
/**
* Request: Reported transaction data
* @next CardanoSignedTx
* @next CardanoTxRequest
*/
message CardanoTxAck {
optional bytes transaction = 1;
}
/**
* Response: Serialised signed cardano transaction
* @end
*/
message CardanoSignedTx {
optional bytes tx_hash = 1; // hash of the signed transaction
optional bytes tx_body = 2; // serialised body of the signed transaction
optional bytes tx_hash = 1; // hash of the transaction body
optional bytes serialized_tx = 2; // serialized, signed transaction
}

View File

@ -181,13 +181,12 @@ enum MessageType {
// Cardano
// dropped Sign/VerifyMessage ids 300-302
// dropped TxRequest/TxAck ids 304 and 309 (shelley update)
MessageType_CardanoSignTx = 303 [(wire_in) = true];
MessageType_CardanoTxRequest = 304 [(wire_out) = true];
MessageType_CardanoGetPublicKey = 305 [(wire_in) = true];
MessageType_CardanoPublicKey = 306 [(wire_out) = true];
MessageType_CardanoGetAddress = 307 [(wire_in) = true];
MessageType_CardanoAddress = 308 [(wire_out) = true];
MessageType_CardanoTxAck = 309 [(wire_in) = true];
MessageType_CardanoSignedTx = 310 [(wire_out) = true];
// Ripple

View File

@ -1,11 +1,22 @@
from trezor import log
from trezor import log, wire
from trezor.crypto import base58, crc, hashlib
from apps.common import HARDENED, cbor
from apps.common.seed import remove_ed25519_prefix
from . import protocol_magics
def _encode_address_raw(address_data_encoded):
if False:
from typing import Tuple
from trezor.crypto import bip32
from . import seed
PROTOCOL_MAGIC_KEY = 2
INVALID_ADDRESS = wire.ProcessError("Invalid address")
NETWORK_MISMATCH = wire.ProcessError("Output address network mismatch!")
def _encode_address_raw(address_data_encoded: bytes) -> str:
return base58.encode(
cbor.encode(
[cbor.Tagged(24, address_data_encoded), crc.crc32(address_data_encoded)]
@ -13,13 +24,14 @@ def _encode_address_raw(address_data_encoded):
)
def derive_address_and_node(keychain, path: list):
def derive_address_and_node(
keychain: seed.Keychain, path: list, protocol_magic: int
) -> Tuple[str, bip32.HDNode]:
node = keychain.derive(path)
address_payload = None
address_attributes = {}
address_attributes = get_address_attributes(protocol_magic)
address_root = _get_address_root(node, address_payload)
address_root = _get_address_root(node, address_attributes)
address_type = 0
address_data = [address_root, address_attributes, address_type]
address_data_encoded = cbor.encode(address_data)
@ -27,28 +39,75 @@ def derive_address_and_node(keychain, path: list):
return (_encode_address_raw(address_data_encoded), node)
def is_safe_output_address(address) -> bool:
"""
Determines whether it is safe to include the address as-is as
a tx output, preventing unintended side effects (e.g. CBOR injection)
"""
def get_address_attributes(protocol_magic: int) -> dict:
# protocol magic is included in Byron addresses only on testnets
if protocol_magic == protocol_magics.MAINNET:
address_attributes = {}
else:
address_attributes = {PROTOCOL_MAGIC_KEY: cbor.encode(protocol_magic)}
return address_attributes
def validate_output_address(address: str, protocol_magic: int) -> None:
address_data_encoded = _decode_address_raw(address)
_validate_address_data_protocol_magic(address_data_encoded, protocol_magic)
def _decode_address_raw(address: str) -> bytes:
try:
address_hex = base58.decode(address)
address_unpacked = cbor.decode(address_hex)
except ValueError as e:
if __debug__:
log.exception(__name__, e)
return False
raise INVALID_ADDRESS
if not isinstance(address_unpacked, list) or len(address_unpacked) != 2:
return False
raise INVALID_ADDRESS
address_data_encoded = address_unpacked[0]
if not isinstance(address_data_encoded, bytes):
return False
raise INVALID_ADDRESS
return _encode_address_raw(address_data_encoded) == address
address_crc = address_unpacked[1]
if not isinstance(address_crc, int):
raise INVALID_ADDRESS
if address_crc != crc.crc32(address_data_encoded):
raise INVALID_ADDRESS
return address_data_encoded
def _validate_address_data_protocol_magic(
address_data_encoded: bytes, protocol_magic: int
) -> None:
"""
Determines whether the correct protocol magic (or none)
is included in the address. Addresses on mainnet don't
contain protocol magic, but addresses on the testnet do.
"""
address_data = cbor.decode(address_data_encoded)
if not isinstance(address_data, list) or len(address_data) < 2:
raise INVALID_ADDRESS
attributes = address_data[1]
if protocol_magic == protocol_magics.MAINNET:
if PROTOCOL_MAGIC_KEY in attributes:
raise NETWORK_MISMATCH
else: # testnet
if len(attributes) == 0 or PROTOCOL_MAGIC_KEY not in attributes:
raise NETWORK_MISMATCH
protocol_magic_cbor = attributes[PROTOCOL_MAGIC_KEY]
address_protocol_magic = cbor.decode(protocol_magic_cbor)
if not isinstance(address_protocol_magic, int):
raise INVALID_ADDRESS
if address_protocol_magic != protocol_magic:
raise NETWORK_MISMATCH
def validate_full_path(path: list) -> bool:
@ -74,17 +133,13 @@ def validate_full_path(path: list) -> bool:
return True
def _address_hash(data) -> bytes:
data = cbor.encode(data)
data = hashlib.sha3_256(data).digest()
res = hashlib.blake2b(data=data, outlen=28).digest()
def _address_hash(data: list) -> bytes:
cbor_data = cbor.encode(data)
sha_data_hash = hashlib.sha3_256(cbor_data).digest()
res = hashlib.blake2b(data=sha_data_hash, outlen=28).digest()
return res
def _get_address_root(node, payload):
def _get_address_root(node: bip32.HDNode, address_attributes: dict) -> bytes:
extpubkey = remove_ed25519_prefix(node.public_key()) + node.chain_code()
if payload:
payload = {1: cbor.encode(payload)}
else:
payload = {}
return _address_hash([0, [0, extpubkey], payload])
return _address_hash([0, [0, extpubkey], address_attributes])

View File

@ -1,18 +1,21 @@
from trezor import log, wire
from trezor.messages.CardanoAddress import CardanoAddress
from apps.cardano import CURVE, seed
from apps.cardano.address import derive_address_and_node, validate_full_path
from apps.common import paths
from apps.common.layout import address_n_to_str, show_address, show_qr
from . import CURVE, seed
from .address import derive_address_and_node, validate_full_path
@seed.with_keychain
async def get_address(ctx, msg, keychain: seed.Keychain):
await paths.validate_path(ctx, validate_full_path, keychain, msg.address_n, CURVE)
try:
address, _ = derive_address_and_node(keychain, msg.address_n)
address, _ = derive_address_and_node(
keychain, msg.address_n, msg.protocol_magic
)
except ValueError as e:
if __debug__:
log.exception(__name__, e)

View File

@ -4,11 +4,11 @@ from trezor import log, wire
from trezor.messages.CardanoPublicKey import CardanoPublicKey
from trezor.messages.HDNodeType import HDNodeType
from apps.cardano import CURVE, seed
from apps.cardano.address import derive_address_and_node
from apps.common import layout, paths
from apps.common.seed import remove_ed25519_prefix
from . import CURVE, seed
@seed.with_keychain
async def get_public_key(ctx, msg, keychain: seed.Keychain):
@ -34,7 +34,7 @@ async def get_public_key(ctx, msg, keychain: seed.Keychain):
def _get_public_key(keychain, derivation_path: list):
_, node = derive_address_and_node(keychain, derivation_path)
node = keychain.derive(derivation_path)
public_key = hexlify(remove_ed25519_prefix(node.public_key())).decode()
chain_code = hexlify(node.chain_code()).decode()

View File

@ -8,6 +8,8 @@ from trezor.utils import chunks
from apps.common.confirm import require_confirm, require_hold_to_confirm
from .. import protocol_magics
def format_coin_amount(amount):
return "%s %s" % (format_amount(amount, 6), "ADA")
@ -35,7 +37,7 @@ async def confirm_sending(ctx, amount, to):
await require_confirm(ctx, Paginated(pages))
async def confirm_transaction(ctx, amount, fee, network_name):
async def confirm_transaction(ctx, amount, fee, protocol_magic):
t1 = Text("Confirm transaction", ui.ICON_SEND, ui.GREEN)
t1.normal("Total amount:")
t1.bold(format_coin_amount(amount))
@ -44,6 +46,6 @@ async def confirm_transaction(ctx, amount, fee, network_name):
t2 = Text("Confirm transaction", ui.ICON_SEND, ui.GREEN)
t2.normal("Network:")
t2.bold(network_name)
t2.bold(protocol_magics.to_ui_string(protocol_magic))
await require_hold_to_confirm(ctx, Paginated([t1, t2]))

View File

@ -1,28 +0,0 @@
from trezor import ui
_progress = 0
_steps = 0
def init(total_steps, text):
global _progress, _steps
_progress = 0
_steps = total_steps
report_init(text)
report()
def advance():
global _progress
_progress += 1
report()
def report_init(text):
ui.display.clear()
ui.header(text)
def report():
p = 1000 * _progress // _steps
ui.display.loader(p, False, 18, ui.WHITE, ui.BG)

View File

@ -0,0 +1,11 @@
MAINNET = 764824073
TESTNET = 42
NAMES = {
MAINNET: "Mainnet",
TESTNET: "Testnet",
}
def to_ui_string(value: int) -> str:
return NAMES.get(value, "Unknown")

View File

@ -2,10 +2,11 @@ from storage import cache, device
from trezor import wire
from trezor.crypto import bip32
from apps.cardano import SEED_NAMESPACE
from apps.common import mnemonic
from apps.common.passphrase import get as get_passphrase
from . import SEED_NAMESPACE
if False:
from apps.common.paths import Bip32Path
from apps.common.keychain import MsgIn, MsgOut, Handler, HandlerWithKeychain

View File

@ -4,238 +4,189 @@ from trezor import log, wire
from trezor.crypto import base58, hashlib
from trezor.crypto.curve import ed25519
from trezor.messages.CardanoSignedTx import CardanoSignedTx
from trezor.messages.CardanoTxAck import CardanoTxAck
from trezor.messages.CardanoTxRequest import CardanoTxRequest
from apps.cardano import CURVE, seed
from apps.cardano.address import (
derive_address_and_node,
is_safe_output_address,
validate_full_path,
)
from apps.cardano.layout import confirm_sending, confirm_transaction, progress
from apps.common import cbor
from apps.common.paths import validate_path
from apps.common.seed import remove_ed25519_prefix
from . import CURVE, seed
from .address import (
derive_address_and_node,
get_address_attributes,
validate_full_path,
validate_output_address,
)
from .layout import confirm_sending, confirm_transaction
if False:
from typing import Dict, List, Tuple
from trezor.messages.CardanoSignTx import CardanoSignTx
from trezor.messages.CardanoTxInputType import CardanoTxInputType
from trezor.messages.CardanoTxOutputType import CardanoTxOutputType
# the maximum allowed change address. this should be large enough for normal
# use and still allow to quickly brute-force the correct bip32 path
MAX_CHANGE_ADDRESS_INDEX = const(1000000)
ACCOUNT_PREFIX_DEPTH = const(2)
ACCOUNT_PATH_INDEX = const(2)
BIP_PATH_LENGTH = const(5)
KNOWN_PROTOCOL_MAGICS = {764824073: "Mainnet", 1097911063: "Testnet"}
# we consider addresses from the external chain as possible change addresses as well
def is_change(output, inputs):
for input in inputs:
inp = input.address_n
if (
not output[:ACCOUNT_PREFIX_DEPTH] == inp[:ACCOUNT_PREFIX_DEPTH]
or not output[-2] < 2
or not output[-1] < MAX_CHANGE_ADDRESS_INDEX
):
return False
return True
async def show_tx(
ctx,
outputs: list,
outcoins: list,
fee: int,
network_name: str,
raw_inputs: list,
raw_outputs: list,
) -> None:
for index, output in enumerate(outputs):
if is_change(raw_outputs[index].address_n, raw_inputs):
continue
await confirm_sending(ctx, outcoins[index], output)
total_amount = sum(outcoins)
await confirm_transaction(ctx, total_amount, fee, network_name)
async def request_transaction(ctx, tx_req: CardanoTxRequest, index: int):
tx_req.tx_index = index
return await ctx.call(tx_req, CardanoTxAck)
LOVELACE_MAX_SUPPLY = 45_000_000_000 * 1_000_000
@seed.with_keychain
async def sign_tx(ctx, msg, keychain: seed.Keychain):
progress.init(msg.transactions_count, "Loading data")
async def sign_tx(
ctx: wire.Context, msg: CardanoSignTx, keychain: seed.Keychain
) -> CardanoSignedTx:
try:
attested = len(msg.inputs) * [False]
input_coins_sum = 0
# request transactions
tx_req = CardanoTxRequest()
for index in range(msg.transactions_count):
progress.advance()
tx_ack = await request_transaction(ctx, tx_req, index)
tx_hash = hashlib.blake2b(
data=bytes(tx_ack.transaction), outlen=32
).digest()
tx_decoded = cbor.decode(tx_ack.transaction)
for i, input in enumerate(msg.inputs):
if not attested[i] and input.prev_hash == tx_hash:
attested[i] = True
outputs = tx_decoded[1]
amount = outputs[input.prev_index][1]
input_coins_sum += amount
if not all(attested):
raise wire.ProcessError(
"No tx data sent for input " + str(attested.index(False))
)
transaction = Transaction(
msg.inputs, msg.outputs, keychain, msg.protocol_magic, input_coins_sum
)
if msg.fee > LOVELACE_MAX_SUPPLY:
raise wire.ProcessError("Fee is out of range!")
for i in msg.inputs:
await validate_path(ctx, validate_full_path, keychain, i.address_n, CURVE)
_validate_outputs(keychain, msg.outputs, msg.protocol_magic)
# display the transaction in UI
await _show_tx(ctx, keychain, msg)
# sign the transaction bundle and prepare the result
tx_body, tx_hash = transaction.serialise_tx()
tx = CardanoSignedTx(tx_body=tx_body, tx_hash=tx_hash)
serialized_tx, tx_hash = _serialize_tx(keychain, msg)
tx = CardanoSignedTx(serialized_tx=serialized_tx, tx_hash=tx_hash)
except ValueError as e:
if __debug__:
log.exception(__name__, e)
raise wire.ProcessError("Signing failed")
# display the transaction in UI
await show_tx(
ctx,
transaction.output_addresses,
transaction.outgoing_coins,
transaction.fee,
transaction.network_name,
transaction.inputs,
transaction.outputs,
)
return tx
class Transaction:
def __init__(
self,
inputs: list,
outputs: list,
keychain,
protocol_magic: int,
input_coins_sum: int,
):
self.inputs = inputs
self.outputs = outputs
self.keychain = keychain
# attributes have to be always empty in current Cardano
self.attributes = {}
def _validate_outputs(
keychain: seed.Keychain, outputs: List[CardanoTxOutputType], protocol_magic: int
) -> None:
total_amount = 0
for output in outputs:
total_amount += output.amount
if output.address_n:
continue
elif output.address is not None:
validate_output_address(output.address, protocol_magic)
else:
raise wire.ProcessError("Each output must have address or address_n field!")
self.network_name = KNOWN_PROTOCOL_MAGICS.get(protocol_magic, "Unknown")
self.protocol_magic = protocol_magic
self.input_coins_sum = input_coins_sum
if total_amount > LOVELACE_MAX_SUPPLY:
raise wire.ProcessError("Total transaction amount is out of range!")
def _process_outputs(self):
change_addresses = []
change_derivation_paths = []
output_addresses = []
outgoing_coins = []
change_coins = []
for output in self.outputs:
if output.address_n:
address, _ = derive_address_and_node(self.keychain, output.address_n)
change_addresses.append(address)
change_derivation_paths.append(output.address_n)
change_coins.append(output.amount)
else:
if output.address is None:
raise wire.ProcessError(
"Each output must have address or address_n field!"
)
if not is_safe_output_address(output.address):
raise wire.ProcessError("Invalid output address!")
def _serialize_tx(keychain: seed.Keychain, msg: CardanoSignTx) -> Tuple[bytes, bytes]:
tx_body = _build_tx_body(keychain, msg)
tx_hash = _hash_tx_body(tx_body)
outgoing_coins.append(output.amount)
output_addresses.append(output.address)
witnesses_for_cbor = _build_witnesses(
keychain, msg.inputs, tx_hash, msg.protocol_magic
)
# byron witnesses have the key 2 in Shelley
witnesses = {2: witnesses_for_cbor}
self.change_addresses = change_addresses
self.output_addresses = output_addresses
self.outgoing_coins = outgoing_coins
self.change_coins = change_coins
self.change_derivation_paths = change_derivation_paths
serialized_tx = cbor.encode([tx_body, witnesses, None])
def _build_witnesses(self, tx_aux_hash: str):
witnesses = []
for input in self.inputs:
_, node = derive_address_and_node(self.keychain, input.address_n)
message = (
b"\x01" + cbor.encode(self.protocol_magic) + b"\x58\x20" + tx_aux_hash
)
signature = ed25519.sign_ext(
node.private_key(), node.private_key_ext(), message
)
extended_public_key = (
remove_ed25519_prefix(node.public_key()) + node.chain_code()
)
witnesses.append(
[
(input.type or 0),
cbor.Tagged(24, cbor.encode([extended_public_key, signature])),
]
return serialized_tx, tx_hash
def _build_tx_body(keychain: seed.Keychain, msg: CardanoSignTx) -> Dict:
inputs_for_cbor = _build_inputs(msg.inputs)
outputs_for_cbor = _build_outputs(keychain, msg.outputs, msg.protocol_magic)
tx_body = {
0: inputs_for_cbor,
1: outputs_for_cbor,
2: msg.fee,
3: msg.ttl,
}
return tx_body
def _build_inputs(inputs: List[CardanoTxInputType]) -> List[Tuple[bytes, int]]:
return [(input.prev_hash, input.prev_index) for input in inputs]
def _build_outputs(
keychain: seed.Keychain, outputs: List[CardanoTxOutputType], protocol_magic: int
) -> List[Tuple[bytes, int]]:
result = []
for output in outputs:
amount = output.amount
if output.address_n:
address, _ = derive_address_and_node(
keychain, output.address_n, protocol_magic
)
else:
address = output.address
return witnesses
result.append((base58.decode(address), amount))
@staticmethod
def compute_fee(input_coins_sum: int, outgoing_coins: list, change_coins: list):
outgoing_coins_sum = sum(outgoing_coins)
change_coins_sum = sum(change_coins)
return result
return input_coins_sum - outgoing_coins_sum - change_coins_sum
def serialise_tx(self):
def _hash_tx_body(tx_body: Dict) -> bytes:
tx_body_cbor = cbor.encode(tx_body)
return hashlib.blake2b(data=tx_body_cbor, outlen=32).digest()
self._process_outputs()
inputs_cbor = []
for input in self.inputs:
inputs_cbor.append(
[
(input.type or 0),
cbor.Tagged(24, cbor.encode([input.prev_hash, input.prev_index])),
]
)
def _build_witnesses(
keychain: seed.Keychain,
inputs: List[CardanoTxInputType],
tx_body_hash: bytes,
protocol_magic: int,
) -> List[Tuple[bytes, bytes, bytes, bytes]]:
result = []
for input in inputs:
node = keychain.derive(input.address_n)
inputs_cbor = cbor.IndefiniteLengthArray(inputs_cbor)
outputs_cbor = []
for index, address in enumerate(self.output_addresses):
outputs_cbor.append(
[cbor.Raw(base58.decode(address)), self.outgoing_coins[index]]
)
for index, address in enumerate(self.change_addresses):
outputs_cbor.append(
[cbor.Raw(base58.decode(address)), self.change_coins[index]]
)
outputs_cbor = cbor.IndefiniteLengthArray(outputs_cbor)
tx_aux_cbor = [inputs_cbor, outputs_cbor, self.attributes]
tx_hash = hashlib.blake2b(data=cbor.encode(tx_aux_cbor), outlen=32).digest()
witnesses = self._build_witnesses(tx_hash)
tx_body = cbor.encode([tx_aux_cbor, witnesses])
self.fee = self.compute_fee(
self.input_coins_sum, self.outgoing_coins, self.change_coins
public_key = remove_ed25519_prefix(node.public_key())
signature = ed25519.sign_ext(
node.private_key(), node.private_key_ext(), tx_body_hash
)
chain_code = node.chain_code()
address_attributes = cbor.encode(get_address_attributes(protocol_magic))
return tx_body, tx_hash
result.append((public_key, signature, chain_code, address_attributes))
return result
async def _show_tx(
ctx: wire.Context, keychain: seed.Keychain, msg: CardanoSignTx
) -> None:
total_amount = 0
for output in msg.outputs:
if _should_hide_output(output.address_n, msg.inputs):
continue
total_amount += output.amount
if not output.address:
address, _ = derive_address_and_node(
keychain, output.address_n, msg.protocol_magic
)
else:
address = output.address
await confirm_sending(ctx, output.amount, address)
await confirm_transaction(ctx, total_amount, msg.fee, msg.protocol_magic)
# addresses from the same account as inputs should be hidden
def _should_hide_output(output: List[int], inputs: List[CardanoTxInputType]) -> bool:
for input in inputs:
inp = input.address_n
if (
len(output) != BIP_PATH_LENGTH
or output[: (ACCOUNT_PATH_INDEX + 1)] != inp[: (ACCOUNT_PATH_INDEX + 1)]
or output[-2] >= 2
or output[-1] >= MAX_CHANGE_ADDRESS_INDEX
):
return False
return True

View File

@ -32,6 +32,7 @@ _CBOR_VAR_FOLLOWS = const(0x1F)
_CBOR_FALSE = const(0x14)
_CBOR_TRUE = const(0x15)
_CBOR_NULL = const(0x16)
_CBOR_BREAK = const(0x1F)
_CBOR_RAW_TAG = const(0x18)
@ -67,7 +68,7 @@ def _cbor_encode(value: Value) -> Iterable[bytes]:
encoded_value = value.encode()
yield _header(_CBOR_TEXT_STRING, len(encoded_value))
yield encoded_value
elif isinstance(value, list):
elif isinstance(value, list) or isinstance(value, tuple):
# definite-length valued list
yield _header(_CBOR_ARRAY, len(value))
for x in value:
@ -91,8 +92,8 @@ def _cbor_encode(value: Value) -> Iterable[bytes]:
yield bytes([_CBOR_PRIMITIVE + _CBOR_TRUE])
else:
yield bytes([_CBOR_PRIMITIVE + _CBOR_FALSE])
elif isinstance(value, Raw):
yield value.value
elif value is None:
yield bytes([_CBOR_PRIMITIVE + _CBOR_NULL])
else:
if __debug__:
log.debug(__name__, "not implemented (encode): %s", type(value))
@ -194,6 +195,8 @@ def _cbor_decode(cbor: bytes) -> Tuple[Value, bytes]:
return (False, cbor[1:])
elif fb_aux == _CBOR_TRUE:
return (True, cbor[1:])
elif fb_aux == _CBOR_NULL:
return (None, cbor[1:])
elif fb_aux == _CBOR_BREAK:
return (cbor[0], cbor[1:])
else:
@ -217,11 +220,6 @@ class Tagged:
)
class Raw:
def __init__(self, value: Value):
self.value = value
class IndefiniteLengthArray:
def __init__(self, array: List[Value]) -> None:
self.array = array

View File

@ -17,13 +17,16 @@ class CardanoGetAddress(p.MessageType):
self,
address_n: List[int] = None,
show_display: bool = None,
protocol_magic: int = None,
) -> None:
self.address_n = address_n if address_n is not None else []
self.show_display = show_display
self.protocol_magic = protocol_magic
@classmethod
def get_fields(cls) -> Dict:
return {
1: ('address_n', p.UVarintType, p.FLAG_REPEATED),
2: ('show_display', p.BoolType, 0),
3: ('protocol_magic', p.UVarintType, 0),
}

View File

@ -20,19 +20,22 @@ class CardanoSignTx(p.MessageType):
self,
inputs: List[CardanoTxInputType] = None,
outputs: List[CardanoTxOutputType] = None,
transactions_count: int = None,
protocol_magic: int = None,
fee: int = None,
ttl: int = None,
) -> None:
self.inputs = inputs if inputs is not None else []
self.outputs = outputs if outputs is not None else []
self.transactions_count = transactions_count
self.protocol_magic = protocol_magic
self.fee = fee
self.ttl = ttl
@classmethod
def get_fields(cls) -> Dict:
return {
1: ('inputs', CardanoTxInputType, p.FLAG_REPEATED),
2: ('outputs', CardanoTxOutputType, p.FLAG_REPEATED),
3: ('transactions_count', p.UVarintType, 0),
5: ('protocol_magic', p.UVarintType, 0),
6: ('fee', p.UVarintType, 0),
7: ('ttl', p.UVarintType, 0),
}

View File

@ -16,14 +16,14 @@ class CardanoSignedTx(p.MessageType):
def __init__(
self,
tx_hash: bytes = None,
tx_body: bytes = None,
serialized_tx: bytes = None,
) -> None:
self.tx_hash = tx_hash
self.tx_body = tx_body
self.serialized_tx = serialized_tx
@classmethod
def get_fields(cls) -> Dict:
return {
1: ('tx_hash', p.BytesType, 0),
2: ('tx_body', p.BytesType, 0),
2: ('serialized_tx', p.BytesType, 0),
}

View File

@ -1,26 +0,0 @@
# Automatically generated by pb2py
# fmt: off
import protobuf as p
if __debug__:
try:
from typing import Dict, List # noqa: F401
from typing_extensions import Literal # noqa: F401
except ImportError:
pass
class CardanoTxAck(p.MessageType):
MESSAGE_WIRE_TYPE = 309
def __init__(
self,
transaction: bytes = None,
) -> None:
self.transaction = transaction
@classmethod
def get_fields(cls) -> Dict:
return {
1: ('transaction', p.BytesType, 0),
}

View File

@ -17,12 +17,10 @@ class CardanoTxInputType(p.MessageType):
address_n: List[int] = None,
prev_hash: bytes = None,
prev_index: int = None,
type: int = None,
) -> None:
self.address_n = address_n if address_n is not None else []
self.prev_hash = prev_hash
self.prev_index = prev_index
self.type = type
@classmethod
def get_fields(cls) -> Dict:
@ -30,5 +28,4 @@ class CardanoTxInputType(p.MessageType):
1: ('address_n', p.UVarintType, p.FLAG_REPEATED),
2: ('prev_hash', p.BytesType, 0),
3: ('prev_index', p.UVarintType, 0),
4: ('type', p.UVarintType, 0),
}

View File

@ -1,32 +0,0 @@
# Automatically generated by pb2py
# fmt: off
import protobuf as p
if __debug__:
try:
from typing import Dict, List # noqa: F401
from typing_extensions import Literal # noqa: F401
except ImportError:
pass
class CardanoTxRequest(p.MessageType):
MESSAGE_WIRE_TYPE = 304
def __init__(
self,
tx_index: int = None,
tx_hash: bytes = None,
tx_body: bytes = None,
) -> None:
self.tx_index = tx_index
self.tx_hash = tx_hash
self.tx_body = tx_body
@classmethod
def get_fields(cls) -> Dict:
return {
1: ('tx_index', p.UVarintType, 0),
2: ('tx_hash', p.BytesType, 0),
3: ('tx_body', p.BytesType, 0),
}

View File

@ -133,12 +133,10 @@ if not utils.BITCOIN_ONLY:
StellarBumpSequenceOp = 221 # type: Literal[221]
StellarSignedTx = 230 # type: Literal[230]
CardanoSignTx = 303 # type: Literal[303]
CardanoTxRequest = 304 # type: Literal[304]
CardanoGetPublicKey = 305 # type: Literal[305]
CardanoPublicKey = 306 # type: Literal[306]
CardanoGetAddress = 307 # type: Literal[307]
CardanoAddress = 308 # type: Literal[308]
CardanoTxAck = 309 # type: Literal[309]
CardanoSignedTx = 310 # type: Literal[310]
RippleGetAddress = 400 # type: Literal[400]
RippleAddress = 401 # type: Literal[401]

View File

@ -4,6 +4,7 @@ from apps.common import seed
from apps.common import HARDENED
from trezor.crypto import bip32, slip39
if not utils.BITCOIN_ONLY:
from apps.cardano import protocol_magics
from apps.cardano.address import (
_get_address_root,
_address_hash,
@ -31,7 +32,7 @@ class TestCardanoAddress(unittest.TestCase):
for i, expected in enumerate(addresses):
# 44'/1815'/0'/0/i'
address, _ = derive_address_and_node(keychain, [0x80000000 | 44, 0x80000000 | 1815, 0x80000000, 0, 0x80000000 + i])
address, _ = derive_address_and_node(keychain, [0x80000000 | 44, 0x80000000 | 1815, 0x80000000, 0, 0x80000000 + i], protocol_magics.MAINNET)
self.assertEqual(expected, address)
nodes = [
@ -56,7 +57,7 @@ class TestCardanoAddress(unittest.TestCase):
]
for i, (priv, ext, pub, chain) in enumerate(nodes):
_, n = derive_address_and_node(keychain, [0x80000000 | 44, 0x80000000 | 1815, 0x80000000, 0, 0x80000000 + i])
_, n = derive_address_and_node(keychain, [0x80000000 | 44, 0x80000000 | 1815, 0x80000000, 0, 0x80000000 + i], protocol_magics.MAINNET)
self.assertEqual(hexlify(n.private_key()), priv)
self.assertEqual(hexlify(n.private_key_ext()), ext)
self.assertEqual(hexlify(seed.remove_ed25519_prefix(n.public_key())), pub)
@ -78,7 +79,7 @@ class TestCardanoAddress(unittest.TestCase):
for i, expected in enumerate(addresses):
# 44'/1815'/0'/0/i
address, _ = derive_address_and_node(keychain, [0x80000000 | 44, 0x80000000 | 1815, 0x80000000, 0, i])
address, _ = derive_address_and_node(keychain, [0x80000000 | 44, 0x80000000 | 1815, 0x80000000, 0, i], protocol_magics.MAINNET)
self.assertEqual(address, expected)
nodes = [
@ -103,7 +104,7 @@ class TestCardanoAddress(unittest.TestCase):
]
for i, (priv, ext, pub, chain) in enumerate(nodes):
_, n = derive_address_and_node(keychain, [0x80000000 | 44, 0x80000000 | 1815, 0x80000000, 0, i])
_, n = derive_address_and_node(keychain, [0x80000000 | 44, 0x80000000 | 1815, 0x80000000, 0, i], protocol_magics.MAINNET)
self.assertEqual(hexlify(n.private_key()), priv)
self.assertEqual(hexlify(n.private_key_ext()), ext)
self.assertEqual(hexlify(seed.remove_ed25519_prefix(n.public_key())), pub)
@ -119,7 +120,7 @@ class TestCardanoAddress(unittest.TestCase):
keychain = Keychain(node)
# 44'/1815'
address, _ = derive_address_and_node(keychain, [0x80000000 | 44, 0x80000000 | 1815])
address, _ = derive_address_and_node(keychain, [0x80000000 | 44, 0x80000000 | 1815], protocol_magics.MAINNET)
self.assertEqual(address, "Ae2tdPwUPEZ2FGHX3yCKPSbSgyuuTYgMxNq652zKopxT4TuWvEd8Utd92w3")
priv, ext, pub, chain = (
@ -129,7 +130,7 @@ class TestCardanoAddress(unittest.TestCase):
b"02ac67c59a8b0264724a635774ca2c242afa10d7ab70e2bf0a8f7d4bb10f1f7a"
)
_, n = derive_address_and_node(keychain, [0x80000000 | 44, 0x80000000 | 1815])
_, n = derive_address_and_node(keychain, [0x80000000 | 44, 0x80000000 | 1815], protocol_magics.MAINNET)
self.assertEqual(hexlify(n.private_key()), priv)
self.assertEqual(hexlify(n.private_key_ext()), ext)
self.assertEqual(hexlify(seed.remove_ed25519_prefix(n.public_key())), pub)
@ -170,14 +171,6 @@ class TestCardanoAddress(unittest.TestCase):
for path in correct_derivation_paths:
self.assertTrue(validate_full_path(path))
def test_get_address_root_scheme(self):
mnemonic = "all all all all all all all all all all all all"
passphrase = ""
root_node = bip32.from_mnemonic_cardano(mnemonic, passphrase)
address_root = _get_address_root(root_node, {1: b'X\x1cr,zu\x81?\xaf\xde\x9f\xf9\xe4\xd4\x90\xadH$\xe9\xf3\x88\x16\xcb\xd2)\x02M\x0c#\xde'})
self.assertEqual(address_root, b'\xb3\xbbS\xa8;uN:E=\xe8\xe5\x9c\x18\xbcn\xcf\xd0c\xba\x0e\xba\xaelL}\xba\xbb')
def test_slip39_128(self):
mnemonics = [
"extra extend academic bishop cricket bundle tofu goat apart victim "
@ -235,7 +228,7 @@ class TestCardanoAddress(unittest.TestCase):
for i, (address, priv, ext, pub, chain) in enumerate(nodes):
# 44'/1815'/0'/0/i
a, n = derive_address_and_node(keychain, [0x80000000 | 44, 0x80000000 | 1815, 0x80000000, 0, i])
a, n = derive_address_and_node(keychain, [0x80000000 | 44, 0x80000000 | 1815, 0x80000000, 0, i], protocol_magics.MAINNET)
self.assertEqual(a, address)
self.assertEqual(hexlify(n.private_key()), priv)
self.assertEqual(hexlify(n.private_key_ext()), ext)
@ -299,12 +292,31 @@ class TestCardanoAddress(unittest.TestCase):
for i, (address, priv, ext, pub, chain) in enumerate(nodes):
# 44'/1815'/0'/0/i
a, n = derive_address_and_node(keychain, [0x80000000 | 44, 0x80000000 | 1815, 0x80000000, 0, i])
a, n = derive_address_and_node(keychain, [0x80000000 | 44, 0x80000000 | 1815, 0x80000000, 0, i], protocol_magics.MAINNET)
self.assertEqual(a, address)
self.assertEqual(hexlify(n.private_key()), priv)
self.assertEqual(hexlify(n.private_key_ext()), ext)
self.assertEqual(hexlify(seed.remove_ed25519_prefix(n.public_key())), pub)
self.assertEqual(hexlify(n.chain_code()), chain)
def test_testnet_address(self):
mnemonic = "all all all all all all all all all all all all"
passphrase = ""
node = bip32.from_mnemonic_cardano(mnemonic, passphrase)
node.derive_cardano(0x80000000 | 44)
node.derive_cardano(0x80000000 | 1815)
keychain = Keychain(node)
addresses = [
"2657WMsDfac5F3zbgs9BwNWx3dhGAJERkAL93gPa68NJ2i8mbCHm2pLUHWSj8Mfea",
"2657WMsDfac6ezKWszxLFqJjSUgpg9NgxKc1koqi24sVpRaPhiwMaExk4useKn5HA",
"2657WMsDfac7hr1ioJGr6g7r6JRx4r1My8Rj91tcPTeVjJDpfBYKURrPG2zVLx2Sq",
]
for i, expected in enumerate(addresses):
# 44'/1815'/0'/0/i'
address, _ = derive_address_and_node(keychain, [0x80000000 | 44, 0x80000000 | 1815, 0x80000000, 0, i], protocol_magics.TESTNET)
self.assertEqual(expected, address)
if __name__ == '__main__':
unittest.main()

View File

@ -0,0 +1,87 @@
from common import *
from apps.common import HARDENED
from trezor.messages.CardanoTxInputType import CardanoTxInputType
if not utils.BITCOIN_ONLY:
from apps.cardano.sign_tx import _should_hide_output
@unittest.skipUnless(not utils.BITCOIN_ONLY, "altcoin")
class TestCardanoSignTransaction(unittest.TestCase):
def test_should_show_outputs(self):
outputs_to_show = [
# output is from the same address as input
(
[44 | HARDENED, 1815 | HARDENED, 0 | HARDENED, 0, 0],
[[44 | HARDENED, 1815 | HARDENED, 0 | HARDENED, 0, 0]],
),
# output is from the same account but from different addresses
(
[44 | HARDENED, 1815 | HARDENED, 0 | HARDENED, 0, 0],
[
[44 | HARDENED, 1815 | HARDENED, 0 | HARDENED, 0, 0],
[44 | HARDENED, 1815 | HARDENED, 0 | HARDENED, 0, 1],
[44 | HARDENED, 1815 | HARDENED, 0 | HARDENED, 0, 2],
],
),
# both output and input are from account 2
(
[44 | HARDENED, 1815 | HARDENED, 2 | HARDENED, 0, 0],
[
[44 | HARDENED, 1815 | HARDENED, 2 | HARDENED, 0, 0],
[44 | HARDENED, 1815 | HARDENED, 2 | HARDENED, 0, 1],
[44 | HARDENED, 1815 | HARDENED, 2 | HARDENED, 0, 2],
],
),
]
outputs_to_hide = [
# output is from different account
(
[44 | HARDENED, 1815 | HARDENED, 2 | HARDENED, 0, 0],
[[44 | HARDENED, 1815 | HARDENED, 0 | HARDENED, 0, 0]],
),
# output path is not complete
(
[44 | HARDENED, 1815 | HARDENED],
[[44 | HARDENED, 1815 | HARDENED, 0 | HARDENED, 0, 0]],
),
# output path is not complete
(
[44 | HARDENED, 1815 | HARDENED, 0 | HARDENED],
[[44 | HARDENED, 1815 | HARDENED, 0 | HARDENED, 0, 0]],
),
# one of the inputs has different account than output
(
[44 | HARDENED, 1815 | HARDENED, 0 | HARDENED, 0, 0],
[
[44 | HARDENED, 1815 | HARDENED, 0 | HARDENED, 0, 0],
[44 | HARDENED, 1815 | HARDENED, 2 | HARDENED, 0, 0],
],
),
# staking output path
(
[44 | HARDENED, 1815 | HARDENED, 0 | HARDENED, 2, 0,],
[[44 | HARDENED, 1815 | HARDENED, 0 | HARDENED, 0, 0]],
),
# output address too large
(
[44 | HARDENED, 1815 | HARDENED, 0 | HARDENED, 0, 1000001],
[[44 | HARDENED, 1815 | HARDENED, 0 | HARDENED, 0, 0]],
),
]
for output_path, input_paths in outputs_to_show:
inputs = [
CardanoTxInputType(input_path, "", 0) for input_path in input_paths
]
self.assertTrue(_should_hide_output(output_path, inputs))
for output_path, input_paths in outputs_to_hide:
inputs = [
CardanoTxInputType(input_path, "", 0) for input_path in input_paths
]
self.assertFalse(_should_hide_output(output_path, inputs))
if __name__ == "__main__":
unittest.main()

View File

@ -65,10 +65,28 @@ class TestCardanoCbor(unittest.TestCase):
# boolean
(True, 'f5'),
(False, 'f4'),
# null
(None, 'f6'),
]
for val, encoded in test_vectors:
self.assertEqual(unhexlify(encoded), encode(val))
self.assertEqual(val, decode(unhexlify(encoded)))
def test_cbor_tuples(self):
"""
Tuples should be encoded as arrays and decoded back as lists.
"""
test_vectors = [
([], '80'),
([1, 2, 3], '83010203'),
([1, [2, 3], [4, 5]], '8301820203820405'),
(list(range(1, 26)), '98190102030405060708090a0b0c0d0e0f101112131415161718181819'),
]
for val, encoded in test_vectors:
value_tuple = tuple(val)
self.assertEqual(unhexlify(encoded), encode(value_tuple))
self.assertEqual(val, decode(unhexlify(encoded)))
if __name__ == '__main__':
unittest.main()

View File

@ -17,48 +17,51 @@
from typing import List
from . import messages, tools
from .tools import expect, session
from .tools import expect
REQUIRED_FIELDS_TRANSACTION = ("inputs", "outputs", "transactions")
REQUIRED_FIELDS_INPUT = ("path", "prev_hash", "prev_index", "type")
PROTOCOL_MAGICS = {"mainnet": 764824073, "testnet": 42}
REQUIRED_FIELDS_TRANSACTION = ("inputs", "outputs")
REQUIRED_FIELDS_INPUT = ("path", "prev_hash", "prev_index")
@expect(messages.CardanoAddress, field="address")
def get_address(client, address_n, show_display=False):
def get_address(
client, address_n: List[int], protocol_magic: int, show_display=False
) -> messages.CardanoAddress:
return client.call(
messages.CardanoGetAddress(address_n=address_n, show_display=show_display)
messages.CardanoGetAddress(
address_n=address_n,
protocol_magic=protocol_magic,
show_display=show_display,
)
)
@expect(messages.CardanoPublicKey)
def get_public_key(client, address_n):
def get_public_key(client, address_n: List[int]) -> messages.CardanoPublicKey:
return client.call(messages.CardanoGetPublicKey(address_n=address_n))
@session
@expect(messages.CardanoSignedTx)
def sign_tx(
client,
inputs: List[messages.CardanoTxInputType],
outputs: List[messages.CardanoTxOutputType],
transactions: List[bytes],
protocol_magic,
):
fee: int,
ttl: int,
protocol_magic: int,
) -> messages.CardanoSignedTx:
response = client.call(
messages.CardanoSignTx(
inputs=inputs,
outputs=outputs,
transactions_count=len(transactions),
fee=fee,
ttl=ttl,
protocol_magic=protocol_magic,
)
)
while isinstance(response, messages.CardanoTxRequest):
tx_index = response.tx_index
transaction_data = bytes.fromhex(transactions[tx_index])
ack_message = messages.CardanoTxAck(transaction=transaction_data)
response = client.call(ack_message)
return response
@ -72,7 +75,6 @@ def create_input(input) -> messages.CardanoTxInputType:
address_n=tools.parse_path(path),
prev_hash=bytes.fromhex(input["prev_hash"]),
prev_index=input["prev_index"],
type=input["type"],
)

View File

@ -37,32 +37,36 @@ def cli():
required=True,
help="Transaction in JSON format",
)
@click.option("-N", "--network", type=int, default=1)
@click.option("-p", "--protocol-magic", type=int, default=1)
@with_client
def sign_tx(client, file, network):
def sign_tx(client, file, protocol_magic):
"""Sign Cardano transaction."""
transaction = json.load(file)
inputs = [cardano.create_input(input) for input in transaction["inputs"]]
outputs = [cardano.create_output(output) for output in transaction["outputs"]]
transactions = transaction["transactions"]
fee = transaction["fee"]
ttl = transaction["ttl"]
signed_transaction = cardano.sign_tx(client, inputs, outputs, transactions, network)
signed_transaction = cardano.sign_tx(
client, inputs, outputs, fee, ttl, protocol_magic
)
return {
"tx_hash": signed_transaction.tx_hash.hex(),
"tx_body": signed_transaction.tx_body.hex(),
"serialized_tx": signed_transaction.serialized_tx.hex(),
}
@cli.command()
@click.option("-n", "--address", required=True, help=PATH_HELP)
@click.option("-d", "--show-display", is_flag=True)
@click.option("-p", "--protocol-magic", type=int, default=1)
@with_client
def get_address(client, address, show_display):
def get_address(client, address, show_display, protocol_magic):
"""Get Cardano address."""
address_n = tools.parse_path(address)
return cardano.get_address(client, address_n, show_display)
return cardano.get_address(client, address_n, protocol_magic, show_display)
@cli.command()

View File

@ -17,13 +17,16 @@ class CardanoGetAddress(p.MessageType):
self,
address_n: List[int] = None,
show_display: bool = None,
protocol_magic: int = None,
) -> None:
self.address_n = address_n if address_n is not None else []
self.show_display = show_display
self.protocol_magic = protocol_magic
@classmethod
def get_fields(cls) -> Dict:
return {
1: ('address_n', p.UVarintType, p.FLAG_REPEATED),
2: ('show_display', p.BoolType, 0),
3: ('protocol_magic', p.UVarintType, 0),
}

View File

@ -20,19 +20,22 @@ class CardanoSignTx(p.MessageType):
self,
inputs: List[CardanoTxInputType] = None,
outputs: List[CardanoTxOutputType] = None,
transactions_count: int = None,
protocol_magic: int = None,
fee: int = None,
ttl: int = None,
) -> None:
self.inputs = inputs if inputs is not None else []
self.outputs = outputs if outputs is not None else []
self.transactions_count = transactions_count
self.protocol_magic = protocol_magic
self.fee = fee
self.ttl = ttl
@classmethod
def get_fields(cls) -> Dict:
return {
1: ('inputs', CardanoTxInputType, p.FLAG_REPEATED),
2: ('outputs', CardanoTxOutputType, p.FLAG_REPEATED),
3: ('transactions_count', p.UVarintType, 0),
5: ('protocol_magic', p.UVarintType, 0),
6: ('fee', p.UVarintType, 0),
7: ('ttl', p.UVarintType, 0),
}

View File

@ -16,14 +16,14 @@ class CardanoSignedTx(p.MessageType):
def __init__(
self,
tx_hash: bytes = None,
tx_body: bytes = None,
serialized_tx: bytes = None,
) -> None:
self.tx_hash = tx_hash
self.tx_body = tx_body
self.serialized_tx = serialized_tx
@classmethod
def get_fields(cls) -> Dict:
return {
1: ('tx_hash', p.BytesType, 0),
2: ('tx_body', p.BytesType, 0),
2: ('serialized_tx', p.BytesType, 0),
}

View File

@ -1,26 +0,0 @@
# Automatically generated by pb2py
# fmt: off
from .. import protobuf as p
if __debug__:
try:
from typing import Dict, List # noqa: F401
from typing_extensions import Literal # noqa: F401
except ImportError:
pass
class CardanoTxAck(p.MessageType):
MESSAGE_WIRE_TYPE = 309
def __init__(
self,
transaction: bytes = None,
) -> None:
self.transaction = transaction
@classmethod
def get_fields(cls) -> Dict:
return {
1: ('transaction', p.BytesType, 0),
}

View File

@ -17,12 +17,10 @@ class CardanoTxInputType(p.MessageType):
address_n: List[int] = None,
prev_hash: bytes = None,
prev_index: int = None,
type: int = None,
) -> None:
self.address_n = address_n if address_n is not None else []
self.prev_hash = prev_hash
self.prev_index = prev_index
self.type = type
@classmethod
def get_fields(cls) -> Dict:
@ -30,5 +28,4 @@ class CardanoTxInputType(p.MessageType):
1: ('address_n', p.UVarintType, p.FLAG_REPEATED),
2: ('prev_hash', p.BytesType, 0),
3: ('prev_index', p.UVarintType, 0),
4: ('type', p.UVarintType, 0),
}

View File

@ -1,32 +0,0 @@
# Automatically generated by pb2py
# fmt: off
from .. import protobuf as p
if __debug__:
try:
from typing import Dict, List # noqa: F401
from typing_extensions import Literal # noqa: F401
except ImportError:
pass
class CardanoTxRequest(p.MessageType):
MESSAGE_WIRE_TYPE = 304
def __init__(
self,
tx_index: int = None,
tx_hash: bytes = None,
tx_body: bytes = None,
) -> None:
self.tx_index = tx_index
self.tx_hash = tx_hash
self.tx_body = tx_body
@classmethod
def get_fields(cls) -> Dict:
return {
1: ('tx_index', p.UVarintType, 0),
2: ('tx_hash', p.BytesType, 0),
3: ('tx_body', p.BytesType, 0),
}

View File

@ -130,12 +130,10 @@ StellarManageDataOp = 220 # type: Literal[220]
StellarBumpSequenceOp = 221 # type: Literal[221]
StellarSignedTx = 230 # type: Literal[230]
CardanoSignTx = 303 # type: Literal[303]
CardanoTxRequest = 304 # type: Literal[304]
CardanoGetPublicKey = 305 # type: Literal[305]
CardanoPublicKey = 306 # type: Literal[306]
CardanoGetAddress = 307 # type: Literal[307]
CardanoAddress = 308 # type: Literal[308]
CardanoTxAck = 309 # type: Literal[309]
CardanoSignedTx = 310 # type: Literal[310]
RippleGetAddress = 400 # type: Literal[400]
RippleAddress = 401 # type: Literal[401]

View File

@ -26,10 +26,8 @@ from .CardanoGetPublicKey import CardanoGetPublicKey
from .CardanoPublicKey import CardanoPublicKey
from .CardanoSignTx import CardanoSignTx
from .CardanoSignedTx import CardanoSignedTx
from .CardanoTxAck import CardanoTxAck
from .CardanoTxInputType import CardanoTxInputType
from .CardanoTxOutputType import CardanoTxOutputType
from .CardanoTxRequest import CardanoTxRequest
from .ChangePin import ChangePin
from .ChangeWipeCode import ChangeWipeCode
from .CipherKeyValue import CipherKeyValue

View File

@ -16,7 +16,7 @@
import pytest
from trezorlib.cardano import get_address
from trezorlib.cardano import PROTOCOL_MAGICS, get_address
from trezorlib.tools import parse_path
from ..common import MNEMONIC12
@ -26,24 +26,44 @@ from ..common import MNEMONIC12
@pytest.mark.cardano
@pytest.mark.skip_t1 # T1 support is not planned
@pytest.mark.parametrize(
"path,expected_address",
"path,protocol_magic,expected_address",
[
# mainnet
(
"m/44'/1815'/0'/0/0",
PROTOCOL_MAGICS["mainnet"],
"Ae2tdPwUPEZLCq3sFv4wVYxwqjMH2nUzBVt1HFr4v87snYrtYq3d3bq2PUQ",
),
(
"m/44'/1815'/0'/0/1",
PROTOCOL_MAGICS["mainnet"],
"Ae2tdPwUPEZEY6pVJoyuNNdLp7VbMB7U7qfebeJ7XGunk5Z2eHarkcN1bHK",
),
(
"m/44'/1815'/0'/0/2",
PROTOCOL_MAGICS["mainnet"],
"Ae2tdPwUPEZ3gZD1QeUHvAqadAV59Zid6NP9VCR9BG5LLAja9YtBUgr6ttK",
),
# testnet
# data generated by code under test
(
"m/44'/1815'/0'/0/0",
PROTOCOL_MAGICS["testnet"],
"2657WMsDfac5vydkak9a7BqGrsLqBzB7K3vT55rucZKYDmVnUCf6hXAFkZSTcUx7r",
),
(
"m/44'/1815'/0'/0/1",
PROTOCOL_MAGICS["testnet"],
"2657WMsDfac61ebUDw53WUX49Dcfya8S8G7iYbhN4nP8JSFuh38T1LuFax1bUnhxA",
),
(
"m/44'/1815'/0'/0/2",
PROTOCOL_MAGICS["testnet"],
"2657WMsDfac5PMpEsxc1md3pgZKUZRZ11MUK8tjkDHBQG9b3TMBsTQc4PmmumVrcn",
),
],
)
@pytest.mark.setup_client(mnemonic=MNEMONIC12)
def test_cardano_get_address(client, path, expected_address):
# data from https://iancoleman.io/bip39/
address = get_address(client, parse_path(path))
def test_cardano_get_address(client, path, protocol_magic, expected_address):
address = get_address(client, parse_path(path), protocol_magic)
assert address == expected_address

View File

@ -16,7 +16,7 @@
import pytest
from trezorlib.cardano import get_address
from trezorlib.cardano import PROTOCOL_MAGICS, get_address
from trezorlib.tools import parse_path
from ..common import MNEMONIC_SLIP39_BASIC_20_3of6
@ -27,27 +27,48 @@ from ..common import MNEMONIC_SLIP39_BASIC_20_3of6
@pytest.mark.skip_t1 # T1 support is not planned
@pytest.mark.skip_ui
@pytest.mark.parametrize(
"path,expected_address",
"path,protocol_magic,expected_address",
[
# mainnet
(
"m/44'/1815'/0'/0/0",
PROTOCOL_MAGICS["mainnet"],
"Ae2tdPwUPEYxF9NAMNdd3v2LZoMeWp7gCZiDb6bZzFQeeVASzoP7HC4V9s6",
),
(
"m/44'/1815'/0'/0/1",
PROTOCOL_MAGICS["mainnet"],
"Ae2tdPwUPEZ1TjYcvfkWAbiHtGVxv4byEHHZoSyQXjPJ362DifCe1ykgqgy",
),
(
"m/44'/1815'/0'/0/2",
PROTOCOL_MAGICS["mainnet"],
"Ae2tdPwUPEZGXmSbda1kBNfyhRQGRcQxJFdk7mhWZXAGnapyejv2b2U3aRb",
),
# testnet
# data generated by code under test
(
"m/44'/1815'/0'/0/0",
PROTOCOL_MAGICS["testnet"],
"2657WMsDfac7SH1rhA2PWBggGAPrKyLt1r9SL9gajPxxcH15ZxuCUb4aK9mQ9w7dU",
),
(
"m/44'/1815'/0'/0/1",
PROTOCOL_MAGICS["testnet"],
"2657WMsDfac6Cmfg4Varph2qyLKGi2K9E8jrtvjHVzfSjmbTMGy5sY3HpxCKsmtDA",
),
(
"m/44'/1815'/0'/0/2",
PROTOCOL_MAGICS["testnet"],
"2657WMsDfac5ANb5Mw6Rbgdz6nvs2Tu675vGbbVSzXQbAkQuMWtqBvEeKTrHNtXY7",
),
],
)
@pytest.mark.setup_client(mnemonic=MNEMONIC_SLIP39_BASIC_20_3of6, passphrase=True)
def test_cardano_get_address(client, path, expected_address):
def test_cardano_get_address(client, path, protocol_magic, expected_address):
# enter passphrase
assert client.features.passphrase_protection is True
client.use_passphrase("TREZOR")
address = get_address(client, parse_path(path))
address = get_address(client, parse_path(path), protocol_magic)
assert address == expected_address

View File

@ -17,21 +17,42 @@
import pytest
from trezorlib import cardano, messages
from trezorlib.cardano import PROTOCOL_MAGICS
from trezorlib.exceptions import TrezorFailure
PROTOCOL_MAGICS = {"mainnet": 764824073, "testnet": 1097911063}
SAMPLE_INPUT = {
"path": "m/44'/1815'/0'/0/1",
"prev_hash": "1af8fa0b754ff99253d983894e63a2b09cbb56c833ba18c3384210163f63dcfc",
"prev_index": 0,
}
SAMPLE_INPUTS = [
{
"input": {
"path": "m/44'/1815'/0'/0/1",
"prev_hash": "1af8fa0b754ff99253d983894e63a2b09cbb56c833ba18c3384210163f63dcfc",
"prev_index": 0,
"type": 0,
},
"prev_tx": "839f8200d818582482582008abb575fac4c39d5bf80683f7f0c37e48f4e3d96e37d1f6611919a7241b456600ff9f8282d818582183581cda4da43db3fca93695e71dab839e72271204d28b9d964d306b8800a8a0001a7a6916a51a00305becffa0",
}
]
SAMPLE_OUTPUTS = {
"simple_output": {
"address": "Ae2tdPwUPEZCanmBz5g2GEwFqKTKpNJcGYPKfDxoNeKZ8bRHr8366kseiK2",
"amount": "3003112",
},
"change_output": {"path": "m/44'/1815'/0'/0/1", "amount": "1000000"},
"invalid_address": {
"address": "jsK75PTH2esX8k4Wvxenyz83LJJWToBbVmGrWUer2CHFHanLseh7r3sW5X5q",
"amount": "3003112",
},
"invalid_cbor": {
"address": "5dnY6xgRcNUSLGa4gfqef2jGAMHb7koQs9EXErXLNC1LiMPUnhn8joXhvEJpWQtN3F4ysATcBvCn5tABgL3e4hPWapPHmcK5GJMSEaET5JafgAGwSrznzL1Mqa",
"amount": "3003112",
},
"invalid_crc": {
"address": "Ae2tdPwUPEZ5YUb8sM3eS8JqKgrRLzhiu71crfuH2MFtqaYr5ACNRZR3Mbm",
"amount": "3003112",
},
"large_simple_output": {
"address": "Ae2tdPwUPEZCanmBz5g2GEwFqKTKpNJcGYPKfDxoNeKZ8bRHr8366kseiK2",
"amount": "449999999199999999",
},
"testnet_output": {
"address": "2657WMsDfac5vydkak9a7BqGrsLqBzB7K3vT55rucZKYDmVnUCf6hXAFkZSTcUx7r",
"amount": "3003112",
},
}
VALID_VECTORS = [
# Mainnet transaction without change
@ -39,68 +60,51 @@ VALID_VECTORS = [
# protocol magic
PROTOCOL_MAGICS["mainnet"],
# inputs
[SAMPLE_INPUTS[0]["input"]],
[SAMPLE_INPUT],
# outputs
[
{
"address": "Ae2tdPwUPEZCanmBz5g2GEwFqKTKpNJcGYPKfDxoNeKZ8bRHr8366kseiK2",
"amount": "3003112",
}
],
# transactions
[SAMPLE_INPUTS[0]["prev_tx"]],
[SAMPLE_OUTPUTS["simple_output"]],
# fee
42,
# ttl
10,
# tx hash
"799c65e8a2c0b1dc4232611728c09d3f3eb0d811c077f8e9798f84605ef1b23d",
# tx body
"82839f8200d81858248258201af8fa0b754ff99253d983894e63a2b09cbb56c833ba18c3384210163f63dcfc00ff9f8282d818582183581c9e1c71de652ec8b85fec296f0685ca3988781c94a2e1a5d89d92f45fa0001a0d0c25611a002dd2e8ffa0818200d818588582584089053545a6c254b0d9b1464e48d2b5fcf91d4e25c128afb1fcfc61d0843338ea26308151516f3b0e02bb1638142747863c520273ce9bd3e5cd91e1d46fe2a6355840312c01c27317415b0b8acc86aa789da877fe7e15c65b7ea4c4565d8739117f5f6d9d38bf5d058f7be809b2b9b06c1d79fc6b20f9a4d76d8c89bae333edf5680c",
"73e09bdebf98a9e0f17f86a2d11e0f14f4f8dae77cdf26ff1678e821f20c8db6",
# serialized tx
"83a400818258201af8fa0b754ff99253d983894e63a2b09cbb56c833ba18c3384210163f63dcfc00018182582b82d818582183581c9e1c71de652ec8b85fec296f0685ca3988781c94a2e1a5d89d92f45fa0001a0d0c25611a002dd2e802182a030aa1028184582089053545a6c254b0d9b1464e48d2b5fcf91d4e25c128afb1fcfc61d0843338ea5840da07ac5246e3f20ebd1276476a4ae34a019dd4b264ffc22eea3c28cb0f1a6bb1c7764adeecf56bcb0bc6196fd1dbe080f3a7ef5b49f56980fe5b2881a4fdfa00582026308151516f3b0e02bb1638142747863c520273ce9bd3e5cd91e1d46fe2a63541a0f6",
),
# Mainnet transaction with change
(
# protocol magic (mainnet)
764824073,
PROTOCOL_MAGICS["mainnet"],
# inputs
[
{
"path": "m/44'/1815'/0'/0/1",
"prev_hash": "1af8fa0b754ff99253d983894e63a2b09cbb56c833ba18c3384210163f63dcfc",
"prev_index": 0,
"type": 0,
}
],
[SAMPLE_INPUT],
# outputs
[
{
"address": "Ae2tdPwUPEZCanmBz5g2GEwFqKTKpNJcGYPKfDxoNeKZ8bRHr8366kseiK2",
"amount": "3003112",
},
{"path": "m/44'/1815'/0'/0/1", "amount": "1000000"},
],
# transactions
[SAMPLE_INPUTS[0]["prev_tx"]],
[SAMPLE_OUTPUTS["simple_output"], SAMPLE_OUTPUTS["change_output"]],
# fee
42,
# ttl
10,
# tx hash
"40bf94518f31aba7779dd99aa71fe867887bcb3e0bac2c6dc33d3f20ec74a6b1",
# tx body
"82839f8200d81858248258201af8fa0b754ff99253d983894e63a2b09cbb56c833ba18c3384210163f63dcfc00ff9f8282d818582183581c9e1c71de652ec8b85fec296f0685ca3988781c94a2e1a5d89d92f45fa0001a0d0c25611a002dd2e88282d818582183581cda4da43db3fca93695e71dab839e72271204d28b9d964d306b8800a8a0001a7a6916a51a000f4240ffa0818200d818588582584089053545a6c254b0d9b1464e48d2b5fcf91d4e25c128afb1fcfc61d0843338ea26308151516f3b0e02bb1638142747863c520273ce9bd3e5cd91e1d46fe2a63558400b47193163462023bdb72f03b2f6afc8e3645dbc9252cb70f7516da402ce3b8468e4a60929674de5862d6253315008e07b60aa189f5c455dd272ff1c84c89d0c",
"81b14b7e62972127eb33c0b1198de6430540ad3a98eec621a3194f2baac43a43",
# serialized tx
"83a400818258201af8fa0b754ff99253d983894e63a2b09cbb56c833ba18c3384210163f63dcfc00018282582b82d818582183581c9e1c71de652ec8b85fec296f0685ca3988781c94a2e1a5d89d92f45fa0001a0d0c25611a002dd2e882582b82d818582183581cda4da43db3fca93695e71dab839e72271204d28b9d964d306b8800a8a0001a7a6916a51a000f424002182a030aa1028184582089053545a6c254b0d9b1464e48d2b5fcf91d4e25c128afb1fcfc61d0843338ea5840d909b16038c4fd772a177038242e6793be39c735430b03ee924ed18026bd28d06920b5846247945f1204276e4b759aa5ac05a4a73b49ce705ab0e5e54a3a170e582026308151516f3b0e02bb1638142747863c520273ce9bd3e5cd91e1d46fe2a63541a0f6",
),
# Testnet transaction
(
# protocol magic
PROTOCOL_MAGICS["testnet"],
# inputs
[SAMPLE_INPUTS[0]["input"]],
[SAMPLE_INPUT],
# outputs
[
{
"address": "Ae2tdPwUPEZCanmBz5g2GEwFqKTKpNJcGYPKfDxoNeKZ8bRHr8366kseiK2",
"amount": "3003112",
}
],
# transactions
[SAMPLE_INPUTS[0]["prev_tx"]],
[SAMPLE_OUTPUTS["testnet_output"], SAMPLE_OUTPUTS["change_output"]],
# fee
42,
# ttl
10,
# tx hash
"799c65e8a2c0b1dc4232611728c09d3f3eb0d811c077f8e9798f84605ef1b23d",
# tx body
"82839f8200d81858248258201af8fa0b754ff99253d983894e63a2b09cbb56c833ba18c3384210163f63dcfc00ff9f8282d818582183581c9e1c71de652ec8b85fec296f0685ca3988781c94a2e1a5d89d92f45fa0001a0d0c25611a002dd2e8ffa0818200d818588582584089053545a6c254b0d9b1464e48d2b5fcf91d4e25c128afb1fcfc61d0843338ea26308151516f3b0e02bb1638142747863c520273ce9bd3e5cd91e1d46fe2a63558403594ee7e2bfe4c84f886a8336cecb7c42983ce9a057345ebb6294a436087d8db93ca78cf514c7c48edff4c8435f690a5817951e2b55d2db729875ee7cc0f7d08",
"5dd03fb44cb88061b2a1c246981bb31adfe4f57be69b58badb5ae8f448450932",
# serialized tx
"83a400818258201af8fa0b754ff99253d983894e63a2b09cbb56c833ba18c3384210163f63dcfc00018282582f82d818582583581c586b90cf80c021db288ce1c18ecfd3610acf64f8748768b0eb7335b1a10242182a001aae3129311a002dd2e882582f82d818582583581c98c3a558f39d1d993cc8770e8825c70a6d0f5a9eb243501c4526c29da10242182a001aa8566c011a000f424002182a030aa1028184582089053545a6c254b0d9b1464e48d2b5fcf91d4e25c128afb1fcfc61d0843338ea5840fc30afdd0d4a6d8581e0f6abe895994d208fd382f2b23ff1553d711477a4fedbd1f68a76e7465c4816d5477f4287f7360acf71fca3b3d5902e4448e48c447106582026308151516f3b0e02bb1638142747863c520273ce9bd3e5cd91e1d46fe2a63545a10242182af6",
),
]
@ -110,60 +114,105 @@ INVALID_VECTORS = [
# protocol magic
PROTOCOL_MAGICS["mainnet"],
# inputs
[SAMPLE_INPUTS[0]["input"]],
[SAMPLE_INPUT],
# outputs
[
{
"address": "jsK75PTH2esX8k4Wvxenyz83LJJWToBbVmGrWUer2CHFHanLseh7r3sW5X5q",
"amount": "3003112",
}
],
# transactions
[SAMPLE_INPUTS[0]["prev_tx"]],
"Invalid output address!",
),
# Output address is an invalid CBOR
(
# protocol magic
PROTOCOL_MAGICS["mainnet"],
# inputs
[SAMPLE_INPUTS[0]["input"]],
# outputs
[
{
"address": "jsK75PTH2esX8k4Wvxenyz83LJJWToBbVmGrWUer2CHFHanLseh7r3sW5X5q",
"amount": "3003112",
}
],
# transactions
[
"839f8200d818582482582008abb575fac4c39d5bf80683f7f0c37e48f4e3d96e37d1f6611919a7241b456600ff9f8282d818582183581cda4da43db3fca93695e71dab839e72271204d28b9d964d306b8800a8a0001a7a6916a51a00305becffa0"
],
"Invalid output address!",
[SAMPLE_OUTPUTS["invalid_address"]],
# fee
42,
# ttl
10,
# error message
"Invalid address",
),
# Output address is invalid CBOR
(
# protocol magic (mainnet)
764824073,
PROTOCOL_MAGICS["mainnet"],
# inputs
[
{
"path": "m/44'/1815'/0'/0/1",
"prev_hash": "1af8fa0b754ff99253d983894e63a2b09cbb56c833ba18c3384210163f63dcfc",
"prev_index": 0,
"type": 0,
}
],
[SAMPLE_INPUT],
# outputs
[
{
"address": "5dnY6xgRcNUSLGa4gfqef2jGAMHb7koQs9EXErXLNC1LiMPUnhn8joXhvEJpWQtN3F4ysATcBvCn5tABgL3e4hPWapPHmcK5GJMSEaET5JafgAGwSrznzL1Mqa",
"amount": "3003112",
}
],
# transactions
[SAMPLE_INPUTS[0]["prev_tx"]],
"Invalid output address!",
[SAMPLE_OUTPUTS["invalid_cbor"]],
# fee
42,
# ttl
10,
# error message
"Invalid address",
),
# Output address has invalid CRC
(
# protocol magic (mainnet)
PROTOCOL_MAGICS["mainnet"],
# inputs
[SAMPLE_INPUT],
# outputs
[SAMPLE_OUTPUTS["invalid_crc"]],
# fee
42,
# ttl
10,
# error message
"Invalid address",
),
# Fee is too high
(
# protocol magic (mainnet)
PROTOCOL_MAGICS["mainnet"],
# inputs
[SAMPLE_INPUT],
# outputs
[SAMPLE_OUTPUTS["simple_output"]],
# fee
45000000000000001,
# ttl
10,
# error message
"Fee is out of range!",
),
# Output total is too high
(
# protocol magic (mainnet)
PROTOCOL_MAGICS["mainnet"],
# inputs
[SAMPLE_INPUT],
# outputs
[SAMPLE_OUTPUTS["large_simple_output"], SAMPLE_OUTPUTS["change_output"]],
# fee
42,
# ttl
10,
# error message
"Total transaction amount is out of range!",
),
# Mainnet transaction with testnet output
(
# protocol magic
PROTOCOL_MAGICS["mainnet"],
# inputs
[SAMPLE_INPUT],
# outputs
[SAMPLE_OUTPUTS["testnet_output"]],
# fee
42,
# ttl
10,
# error message
"Output address network mismatch!",
),
# Testnet transaction with mainnet output
(
# protocol magic
PROTOCOL_MAGICS["testnet"],
# inputs
[SAMPLE_INPUT],
# outputs
[SAMPLE_OUTPUTS["simple_output"]],
# fee
42,
# ttl
10,
# error message
"Output address network mismatch!",
),
]
@ -172,18 +221,15 @@ INVALID_VECTORS = [
@pytest.mark.cardano
@pytest.mark.skip_t1 # T1 support is not planned
@pytest.mark.parametrize(
"protocol_magic,inputs,outputs,transactions,tx_hash,tx_body", VALID_VECTORS
"protocol_magic,inputs,outputs,fee,ttl,tx_hash,serialized_tx", VALID_VECTORS
)
def test_cardano_sign_tx(
client, protocol_magic, inputs, outputs, transactions, tx_hash, tx_body
client, protocol_magic, inputs, outputs, fee, ttl, tx_hash, serialized_tx
):
inputs = [cardano.create_input(i) for i in inputs]
outputs = [cardano.create_output(o) for o in outputs]
expected_responses = [
messages.CardanoTxRequest(tx_index=i) for i in range(len(transactions))
]
expected_responses += [
messages.ButtonRequest(code=messages.ButtonRequestType.Other),
messages.ButtonRequest(code=messages.ButtonRequestType.Other),
messages.CardanoSignedTx(),
@ -200,32 +246,27 @@ def test_cardano_sign_tx(
with client:
client.set_expected_responses(expected_responses)
client.set_input_flow(input_flow)
response = cardano.sign_tx(
client, inputs, outputs, transactions, protocol_magic
)
response = cardano.sign_tx(client, inputs, outputs, fee, ttl, protocol_magic)
assert response.tx_hash.hex() == tx_hash
assert response.tx_body.hex() == tx_body
assert response.serialized_tx.hex() == serialized_tx
@pytest.mark.altcoin
@pytest.mark.cardano
@pytest.mark.skip_t1 # T1 support is not planned
@pytest.mark.parametrize(
"protocol_magic,inputs,outputs,transactions,expected_error_message", INVALID_VECTORS
"protocol_magic,inputs,outputs,fee,ttl,expected_error_message", INVALID_VECTORS
)
def test_cardano_sign_tx_validation(
client, protocol_magic, inputs, outputs, transactions, expected_error_message
client, protocol_magic, inputs, outputs, fee, ttl, expected_error_message
):
inputs = [cardano.create_input(i) for i in inputs]
outputs = [cardano.create_output(o) for o in outputs]
expected_responses = [
messages.CardanoTxRequest(tx_index=i) for i in range(len(transactions))
]
expected_responses += [messages.Failure()]
expected_responses = [messages.Failure()]
with client:
client.set_expected_responses(expected_responses)
with pytest.raises(TrezorFailure, match=expected_error_message):
cardano.sign_tx(client, inputs, outputs, transactions, protocol_magic)
cardano.sign_tx(client, inputs, outputs, fee, ttl, protocol_magic)

View File

@ -17,22 +17,27 @@
import pytest
from trezorlib import cardano, messages
from trezorlib.cardano import PROTOCOL_MAGICS
from ..common import MNEMONIC_SLIP39_BASIC_20_3of6
PROTOCOL_MAGICS = {"mainnet": 764824073, "testnet": 1097911063}
SAMPLE_INPUT = {
"path": "m/44'/1815'/0'/0/1",
"prev_hash": "1af8fa0b754ff99253d983894e63a2b09cbb56c833ba18c3384210163f63dcfc",
"prev_index": 0,
}
SAMPLE_INPUTS = [
{
"input": {
"path": "m/44'/1815'/0'/0/1",
"prev_hash": "1af8fa0b754ff99253d983894e63a2b09cbb56c833ba18c3384210163f63dcfc",
"prev_index": 0,
"type": 0,
},
"prev_tx": "839f8200d818582482582008abb575fac4c39d5bf80683f7f0c37e48f4e3d96e37d1f6611919a7241b456600ff9f8282d818582183581cda4da43db3fca93695e71dab839e72271204d28b9d964d306b8800a8a0001a7a6916a51a00305becffa0",
}
]
SAMPLE_OUTPUTS = {
"simple_output": {
"address": "Ae2tdPwUPEZCanmBz5g2GEwFqKTKpNJcGYPKfDxoNeKZ8bRHr8366kseiK2",
"amount": "3003112",
},
"change_output": {"path": "m/44'/1815'/0'/0/1", "amount": "1000000"},
"testnet_output": {
"address": "2657WMsDfac5vydkak9a7BqGrsLqBzB7K3vT55rucZKYDmVnUCf6hXAFkZSTcUx7r",
"amount": "3003112",
},
}
VALID_VECTORS = [
# Mainnet transaction without change
@ -40,68 +45,51 @@ VALID_VECTORS = [
# protocol magic
PROTOCOL_MAGICS["mainnet"],
# inputs
[SAMPLE_INPUTS[0]["input"]],
[SAMPLE_INPUT],
# outputs
[
{
"address": "Ae2tdPwUPEZCanmBz5g2GEwFqKTKpNJcGYPKfDxoNeKZ8bRHr8366kseiK2",
"amount": "3003112",
}
],
# transactions
[SAMPLE_INPUTS[0]["prev_tx"]],
[SAMPLE_OUTPUTS["simple_output"]],
# fee
42,
# ttl
10,
# tx hash
"799c65e8a2c0b1dc4232611728c09d3f3eb0d811c077f8e9798f84605ef1b23d",
# tx body
"82839f8200d81858248258201af8fa0b754ff99253d983894e63a2b09cbb56c833ba18c3384210163f63dcfc00ff9f8282d818582183581c9e1c71de652ec8b85fec296f0685ca3988781c94a2e1a5d89d92f45fa0001a0d0c25611a002dd2e8ffa0818200d818588582584024c4fe188a39103db88818bc191fd8571eae7b284ebcbdf2462bde97b058a95c6f7a744035f4b3ddb8f861c18446169643cc3ae85e271b4b4f0eda05cf84c65b584032a773bcd60c83880de09676c45e52cc2c2189c1b46d93de596a5cf6e3e93041c22e6e5762144feb65b40e905659c9b5e51528fa6574273279c2507a2b996f0e",
"73e09bdebf98a9e0f17f86a2d11e0f14f4f8dae77cdf26ff1678e821f20c8db6",
# serialized tx
"83a400818258201af8fa0b754ff99253d983894e63a2b09cbb56c833ba18c3384210163f63dcfc00018182582b82d818582183581c9e1c71de652ec8b85fec296f0685ca3988781c94a2e1a5d89d92f45fa0001a0d0c25611a002dd2e802182a030aa1028184582024c4fe188a39103db88818bc191fd8571eae7b284ebcbdf2462bde97b058a95c584055c179ff2beca2c6a78d66de3dea5a6e3134ca3430447c9b73ede73d9b6ae524cde73db59d93a4dfccbbd42b4f4dbacbb655b27171d0f248fdd2d0dc16e0130458206f7a744035f4b3ddb8f861c18446169643cc3ae85e271b4b4f0eda05cf84c65b41a0f6",
),
# Mainnet transaction with change
(
# protocol magic (mainnet)
PROTOCOL_MAGICS["mainnet"],
# inputs
[
{
"path": "m/44'/1815'/0'/0/1",
"prev_hash": "1af8fa0b754ff99253d983894e63a2b09cbb56c833ba18c3384210163f63dcfc",
"prev_index": 0,
"type": 0,
}
],
[SAMPLE_INPUT],
# outputs
[
{
"address": "Ae2tdPwUPEZCanmBz5g2GEwFqKTKpNJcGYPKfDxoNeKZ8bRHr8366kseiK2",
"amount": "3003112",
},
{"path": "m/44'/1815'/0'/0/1", "amount": "1000000"},
],
# transactions
[SAMPLE_INPUTS[0]["prev_tx"]],
[SAMPLE_OUTPUTS["simple_output"], SAMPLE_OUTPUTS["change_output"]],
# fee
42,
# ttl
10,
# tx hash
"5a3921053daabc6a2ffc1528963352fa8ea842bd04056371effcd58256e0cd55",
# tx body
"82839f8200d81858248258201af8fa0b754ff99253d983894e63a2b09cbb56c833ba18c3384210163f63dcfc00ff9f8282d818582183581c9e1c71de652ec8b85fec296f0685ca3988781c94a2e1a5d89d92f45fa0001a0d0c25611a002dd2e88282d818582183581c2ea63b3db3a1865f59c11762a5aede800ed8f2dc0605d75df2ed7c9ca0001ae82668161a000f4240ffa0818200d818588582584024c4fe188a39103db88818bc191fd8571eae7b284ebcbdf2462bde97b058a95c6f7a744035f4b3ddb8f861c18446169643cc3ae85e271b4b4f0eda05cf84c65b5840ea38a37167d652fd35ac3517a6b3a5ec73e01a9f3b6d57d645c7727856a17a2c8d9403b497e148811cb087822c49b5ab6e14b1bc78acc21eca434c3e5147260f",
"4c43ce4c72f145b145ae7add414722735e250d048f61c4585a5becafcbffa6ae",
# serialized tx
"83a400818258201af8fa0b754ff99253d983894e63a2b09cbb56c833ba18c3384210163f63dcfc00018282582b82d818582183581c9e1c71de652ec8b85fec296f0685ca3988781c94a2e1a5d89d92f45fa0001a0d0c25611a002dd2e882582b82d818582183581c2ea63b3db3a1865f59c11762a5aede800ed8f2dc0605d75df2ed7c9ca0001ae82668161a000f424002182a030aa1028184582024c4fe188a39103db88818bc191fd8571eae7b284ebcbdf2462bde97b058a95c5840594c986290cc5cddf3c242f2d650fcbfd0705949c9990569798c29e42ca7b0d6e92a589be6962dcce9c53c63de973d84c38cf53374b5329e20973a280abec00d58206f7a744035f4b3ddb8f861c18446169643cc3ae85e271b4b4f0eda05cf84c65b41a0f6",
),
# Testnet transaction
(
# protocol magic
PROTOCOL_MAGICS["testnet"],
# inputs
[SAMPLE_INPUTS[0]["input"]],
[SAMPLE_INPUT],
# outputs
[
{
"address": "Ae2tdPwUPEZCanmBz5g2GEwFqKTKpNJcGYPKfDxoNeKZ8bRHr8366kseiK2",
"amount": "3003112",
}
],
# transactions
[SAMPLE_INPUTS[0]["prev_tx"]],
[SAMPLE_OUTPUTS["testnet_output"], SAMPLE_OUTPUTS["change_output"]],
# fee
42,
# ttl
10,
# tx hash
"799c65e8a2c0b1dc4232611728c09d3f3eb0d811c077f8e9798f84605ef1b23d",
# tx body
"82839f8200d81858248258201af8fa0b754ff99253d983894e63a2b09cbb56c833ba18c3384210163f63dcfc00ff9f8282d818582183581c9e1c71de652ec8b85fec296f0685ca3988781c94a2e1a5d89d92f45fa0001a0d0c25611a002dd2e8ffa0818200d818588582584024c4fe188a39103db88818bc191fd8571eae7b284ebcbdf2462bde97b058a95c6f7a744035f4b3ddb8f861c18446169643cc3ae85e271b4b4f0eda05cf84c65b58407aab2a707a6d295c0a93e396429721c48d2c09238e32112f2e1d14a8296ff463204240e7d9168e2dfe8276f426cd1f73f1254df434cdab7c942e2a920c8ce800",
"ac7ef9e4f51ed4d6b791cee111b240dae2f00c39c5cc1a150631eba8aa955528",
# serialized tx
"83a400818258201af8fa0b754ff99253d983894e63a2b09cbb56c833ba18c3384210163f63dcfc00018282582f82d818582583581c586b90cf80c021db288ce1c18ecfd3610acf64f8748768b0eb7335b1a10242182a001aae3129311a002dd2e882582f82d818582583581c709bfb5d9733cbdd72f520cd2c8b9f8f942da5e6cd0b6994e1803b0aa10242182a001aef14e76d1a000f424002182a030aa1028184582024c4fe188a39103db88818bc191fd8571eae7b284ebcbdf2462bde97b058a95c5840cfd68676454ad8bed8575dcb8ee91824c0f836da4f07a54112088b12c6b89be0c8f729d4e3fb1df0de10f049a66dea372f3e2888cabb6110d538a0e9a06fbb0758206f7a744035f4b3ddb8f861c18446169643cc3ae85e271b4b4f0eda05cf84c65b45a10242182af6",
),
]
@ -111,19 +99,16 @@ VALID_VECTORS = [
@pytest.mark.skip_t1 # T1 support is not planned
@pytest.mark.setup_client(mnemonic=MNEMONIC_SLIP39_BASIC_20_3of6, passphrase=True)
@pytest.mark.parametrize(
"protocol_magic,inputs,outputs,transactions,tx_hash,tx_body", VALID_VECTORS
"protocol_magic,inputs,outputs,fee,ttl,tx_hash,serialized_tx", VALID_VECTORS
)
def test_cardano_sign_tx(
client, protocol_magic, inputs, outputs, transactions, tx_hash, tx_body
client, protocol_magic, inputs, outputs, fee, ttl, tx_hash, serialized_tx
):
inputs = [cardano.create_input(i) for i in inputs]
outputs = [cardano.create_output(o) for o in outputs]
expected_responses = [messages.PassphraseRequest()]
expected_responses += [
messages.CardanoTxRequest(tx_index=i) for i in range(len(transactions))
]
expected_responses += [
expected_responses = [
messages.PassphraseRequest(),
messages.ButtonRequest(code=messages.ButtonRequestType.Other),
messages.ButtonRequest(code=messages.ButtonRequestType.Other),
messages.CardanoSignedTx(),
@ -141,8 +126,6 @@ def test_cardano_sign_tx(
with client:
client.set_expected_responses(expected_responses)
client.set_input_flow(input_flow)
response = cardano.sign_tx(
client, inputs, outputs, transactions, protocol_magic
)
response = cardano.sign_tx(client, inputs, outputs, fee, ttl, protocol_magic)
assert response.tx_hash.hex() == tx_hash
assert response.tx_body.hex() == tx_body
assert response.serialized_tx.hex() == serialized_tx

View File

@ -27,22 +27,29 @@
"test_msg_binance_sign_tx.py::test_binance_sign_message[message0-expected_response0]": "d41ee5e01a50f0f96fd7881db1750fab31cfe62c25b4eabbc092cc3daa039c7f",
"test_msg_binance_sign_tx.py::test_binance_sign_message[message1-expected_response1]": "7b8bbe5ba7d7b07c95065608fb1cf9aeafcb3f9671835a6e5e5a6997ff4ff99b",
"test_msg_binance_sign_tx.py::test_binance_sign_message[message2-expected_response2]": "813ad1b802dee1ace4dfa378edd840dbcea57c1a1b8eed67134def024c40a6e9",
"test_msg_cardano_get_address.py::test_cardano_get_address[m-44'-1815'-0'-0-0-Ae2tdPwUPEZLCq3sFv4wVYx": "5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586",
"test_msg_cardano_get_address.py::test_cardano_get_address[m-44'-1815'-0'-0-1-Ae2tdPwUPEZEY6pVJoyuNNd": "5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586",
"test_msg_cardano_get_address.py::test_cardano_get_address[m-44'-1815'-0'-0-2-Ae2tdPwUPEZ3gZD1QeUHvAq": "5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586",
"test_msg_cardano_get_address.py::test_cardano_get_address[m-44'-1815'-0'-0-0-42-2657WMsDfac5vydkak9a": "5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586",
"test_msg_cardano_get_address.py::test_cardano_get_address[m-44'-1815'-0'-0-0-764824073-Ae2tdPwUPEZLC": "5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586",
"test_msg_cardano_get_address.py::test_cardano_get_address[m-44'-1815'-0'-0-1-42-2657WMsDfac61ebUDw53": "5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586",
"test_msg_cardano_get_address.py::test_cardano_get_address[m-44'-1815'-0'-0-1-764824073-Ae2tdPwUPEZEY": "5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586",
"test_msg_cardano_get_address.py::test_cardano_get_address[m-44'-1815'-0'-0-2-42-2657WMsDfac5PMpEsxc1": "5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586",
"test_msg_cardano_get_address.py::test_cardano_get_address[m-44'-1815'-0'-0-2-764824073-Ae2tdPwUPEZ3g": "5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586",
"test_msg_cardano_get_public_key.py::test_cardano_get_public_key[m-44'-1815'-0'-c0fce1839f1a84c4e7702": "5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586",
"test_msg_cardano_get_public_key.py::test_cardano_get_public_key[m-44'-1815'-1'-ea5dde31b9f551e08a5b6": "5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586",
"test_msg_cardano_get_public_key.py::test_cardano_get_public_key[m-44'-1815'-2'-076338cee5ab3dae19f06": "5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586",
"test_msg_cardano_get_public_key.py::test_cardano_get_public_key[m-44'-1815'-3'-5f769380dc6fd17a4e0f2": "5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586",
"test_msg_cardano_sign_transaction.py::test_cardano_sign_tx[1097911063-inputs2-outputs2-transactions2": "fdac7a4eb74827fc2318c3646206c60a10ddfe244c089f5a798e7652f69d313e",
"test_msg_cardano_sign_transaction.py::test_cardano_sign_tx[764824073-inputs0-outputs0-transactions0-": "b1f022d81fb324a136a8401232b7112bca2b5212b98578a189d3486911d469cc",
"test_msg_cardano_sign_transaction.py::test_cardano_sign_tx[764824073-inputs1-outputs1-transactions1-": "c5f547077924a76527e8cf58abf437e27059386bff775499d21d52105d60ed44",
"test_msg_cardano_sign_transaction.py::test_cardano_sign_tx_validation[764824073-inputs0-outputs0-tra": "f504163122424398b008ec86cbd219e543eea7889d52651e0e69f707b4a14649",
"test_msg_cardano_sign_transaction.py::test_cardano_sign_tx_validation[764824073-inputs1-outputs1-tra": "f504163122424398b008ec86cbd219e543eea7889d52651e0e69f707b4a14649",
"test_msg_cardano_sign_transaction.py::test_cardano_sign_tx_validation[764824073-inputs2-outputs2-tra": "f504163122424398b008ec86cbd219e543eea7889d52651e0e69f707b4a14649",
"test_msg_cardano_sign_tx_slip39_basic.py::test_cardano_sign_tx[1097911063-inputs2-outputs2-transacti": "32edff90a2fecf74bf7e3e46794a48924082c069b9d1e57adb531749ac71e4af",
"test_msg_cardano_sign_tx_slip39_basic.py::test_cardano_sign_tx[764824073-inputs0-outputs0-transactio": "85fb1f1bf90131d6cd04e58b2303bf8d3b9a641b1f418852bc27c4432d427976",
"test_msg_cardano_sign_tx_slip39_basic.py::test_cardano_sign_tx[764824073-inputs1-outputs1-transactio": "15f55a8fc499364aa239c319100072d16ae5dc1ee9dd0db3cd1795363fde679a",
"test_msg_cardano_sign_transaction.py::test_cardano_sign_tx[42-inputs2-outputs2-42-10-5dd03fb44cb8806": "418f782a5d37c227f2d82f144bcee661d65187e07a421f64e6a5465daf544e7d",
"test_msg_cardano_sign_transaction.py::test_cardano_sign_tx[764824073-inputs0-outputs0-42-10-73e09bde": "63dc0880175285dc1b3dcadb3ae66439f28b1c18fdaf50245cd84b6c8cb3d0e0",
"test_msg_cardano_sign_transaction.py::test_cardano_sign_tx[764824073-inputs1-outputs1-42-10-81b14b7e": "63dc0880175285dc1b3dcadb3ae66439f28b1c18fdaf50245cd84b6c8cb3d0e0",
"test_msg_cardano_sign_transaction.py::test_cardano_sign_tx_validation[42-inputs6-outputs6-42-10-Outp": "5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586",
"test_msg_cardano_sign_transaction.py::test_cardano_sign_tx_validation[764824073-inputs0-outputs0-42-": "5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586",
"test_msg_cardano_sign_transaction.py::test_cardano_sign_tx_validation[764824073-inputs1-outputs1-42-": "5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586",
"test_msg_cardano_sign_transaction.py::test_cardano_sign_tx_validation[764824073-inputs2-outputs2-42-": "5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586",
"test_msg_cardano_sign_transaction.py::test_cardano_sign_tx_validation[764824073-inputs3-outputs3-450": "5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586",
"test_msg_cardano_sign_transaction.py::test_cardano_sign_tx_validation[764824073-inputs4-outputs4-42-": "5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586",
"test_msg_cardano_sign_transaction.py::test_cardano_sign_tx_validation[764824073-inputs5-outputs5-42-": "5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586",
"test_msg_cardano_sign_tx_slip39_basic.py::test_cardano_sign_tx[42-inputs2-outputs2-42-10-ac7ef9e4f51": "ba354313a87bff3e5079da58c74cdc265a9be8e9a80006e76ea68d0cdb869665",
"test_msg_cardano_sign_tx_slip39_basic.py::test_cardano_sign_tx[764824073-inputs0-outputs0-42-10-73e0": "37b670a523fca62a6d6d997541722ed070e1acf58fe618bc040e907272aa0f16",
"test_msg_cardano_sign_tx_slip39_basic.py::test_cardano_sign_tx[764824073-inputs1-outputs1-42-10-4c43": "37b670a523fca62a6d6d997541722ed070e1acf58fe618bc040e907272aa0f16",
"test_msg_change_wipe_code_t2.py::test_set_pin_to_wipe_code": "9e11b251c03ef09127da79d92f8483c4db438c7303328774790d45e3f6fb8c96",
"test_msg_change_wipe_code_t2.py::test_set_remove_wipe_code": "d280ed129a2ea4781af9e35542aa31ecf63da75fc6812ed3bd05107809f836a4",
"test_msg_change_wipe_code_t2.py::test_set_wipe_code_mismatch": "32c808f00bada2059f933f3515337e494c837bdf65e4ea918b457d1c9f4cb42a",