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:
parent
88aa3cf168
commit
e1615e60ec
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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])
|
||||
|
@ -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)
|
||||
|
@ -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()
|
||||
|
@ -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]))
|
||||
|
@ -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)
|
11
core/src/apps/cardano/protocol_magics.py
Normal file
11
core/src/apps/cardano/protocol_magics.py
Normal 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")
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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),
|
||||
}
|
||||
|
@ -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),
|
||||
}
|
||||
|
@ -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),
|
||||
}
|
||||
|
@ -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),
|
||||
}
|
@ -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),
|
||||
}
|
||||
|
@ -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),
|
||||
}
|
@ -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]
|
||||
|
@ -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()
|
||||
|
87
core/tests/test_apps.cardano.sign_tx.py
Normal file
87
core/tests/test_apps.cardano.sign_tx.py
Normal 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()
|
@ -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()
|
||||
|
@ -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"],
|
||||
)
|
||||
|
||||
|
||||
|
@ -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()
|
||||
|
@ -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),
|
||||
}
|
||||
|
@ -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),
|
||||
}
|
||||
|
@ -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),
|
||||
}
|
||||
|
@ -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),
|
||||
}
|
@ -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),
|
||||
}
|
||||
|
@ -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),
|
||||
}
|
@ -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]
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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",
|
||||
|
Loading…
Reference in New Issue
Block a user