feat(core/cardano): chunked serialization of signed transaction

pull/1548/head
Rafael Korbas 3 years ago committed by matejcik
parent 3cb686d452
commit a9b8b0e119

@ -220,11 +220,27 @@ message CardanoSignTx {
}
}
/**
* Response: Serialised signed cardano transaction chunk
* @next CardanoSignedTxChunkAck
*/
message CardanoSignedTxChunk {
required bytes signed_tx_chunk = 1; // serialised, signed transaction chunk
}
/**
* Request: Serialised signed cardano transaction chunk acknowledgement
* @next CardanoSignedTxChunk
* @next CardanoSignedTx
*/
message CardanoSignedTxChunkAck {
}
/**
* Response: Serialised signed cardano transaction
* @end
*/
message CardanoSignedTx {
required bytes tx_hash = 1; // hash of the transaction body
required bytes serialized_tx = 2; // serialized, signed transaction
required bytes tx_hash = 1; // hash of the transaction body
optional bytes serialized_tx = 2; // deprecated since transaction is sent in chunks now - kept for backwards compatibility
}

@ -206,6 +206,8 @@ enum MessageType {
MessageType_CardanoGetAddress = 307 [(wire_in) = true];
MessageType_CardanoAddress = 308 [(wire_out) = true];
MessageType_CardanoSignedTx = 310 [(wire_out) = true];
MessageType_CardanoSignedTxChunk = 311 [(wire_out) = true];
MessageType_CardanoSignedTxChunkAck = 312 [(wire_in) = true];
// Ripple
MessageType_RippleGetAddress = 400 [(wire_in) = true];

File diff suppressed because one or more lines are too long

@ -740,114 +740,6 @@
"error_message": "Invalid certificate path"
}
},
{
"description": "Too many tokens in output",
"parameters": {
"protocol_magic": 764824073,
"network_id": 1,
"fee": 42,
"ttl": 10,
"certificates": [],
"withdrawals": [],
"metadata": "",
"inputs": [
{
"path": "m/1852'/1815'/0'/0/0",
"prev_hash": "3b40265111d8bb3c3c608d95b3a0bf83461ace32d79336579a1939b3aad1c0b7",
"prev_index": 0
}
],
"outputs": [
{
"address": "Ae2tdPwUPEZCanmBz5g2GEwFqKTKpNJcGYPKfDxoNeKZ8bRHr8366kseiK2",
"amount": "3003112",
"token_bundle": [
{
"policy_id": "95a292ffee938be03e9bae5657982a74e9014eb4960108c9e23a5b39",
"tokens": [
{
"asset_name_bytes": "01aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"amount": "7878754"
},
{
"asset_name_bytes": "02aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"amount": "1234"
},
{
"asset_name_bytes": "03aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"amount": "1234"
},
{
"asset_name_bytes": "04aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"amount": "1234"
},
{
"asset_name_bytes": "05aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"amount": "1234"
},
{
"asset_name_bytes": "06aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"amount": "1234"
},
{
"asset_name_bytes": "07aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"amount": "1234"
},
{
"asset_name_bytes": "08aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"amount": "1234"
},
{
"asset_name_bytes": "09aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"amount": "1234"
}
]
},
{
"policy_id": "75a292ffee938be03e9bae5657982a74e9014eb4960108c9e23a5b39",
"tokens": [
{
"asset_name_bytes": "10aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"amount": "47"
},
{
"asset_name_bytes": "11aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"amount": "47"
},
{
"asset_name_bytes": "12aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"amount": "47"
},
{
"asset_name_bytes": "13aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"amount": "47"
},
{
"asset_name_bytes": "14aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"amount": "47"
},
{
"asset_name_bytes": "15aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"amount": "47"
},
{
"asset_name_bytes": "16aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"amount": "47"
},
{
"asset_name_bytes": "17aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"amount": "47"
}
]
}
]
}
]
},
"result": {
"error_message": "Maximum tx output size"
}
},
{
"description": "Repeated asset name in multiasset token group",
"parameters": {

@ -4,6 +4,8 @@ from trezor.crypto.curve import ed25519
from trezor.messages import CardanoAddressType, CardanoCertificateType
from trezor.messages.CardanoAddressParametersType import CardanoAddressParametersType
from trezor.messages.CardanoSignedTx import CardanoSignedTx
from trezor.messages.CardanoSignedTxChunk import CardanoSignedTxChunk
from trezor.messages.CardanoSignedTxChunkAck import CardanoSignedTxChunkAck
from apps.common import cbor, safety_checks
from apps.common.paths import validate_path
@ -78,13 +80,14 @@ if False:
CborizedTokenBundle = Dict[bytes, Dict[bytes, int]]
CborizedTxOutput = Tuple[bytes, Union[int, Tuple[int, CborizedTokenBundle]]]
CborizedSignedTx = Tuple[Dict, Dict, Optional[cbor.Raw]]
TxHash = bytes
METADATA_HASH_SIZE = 32
MINTING_POLICY_ID_LENGTH = 28
MAX_METADATA_LENGTH = 500
MAX_ASSET_NAME_LENGTH = 32
MAX_TX_OUTPUT_SIZE = 512
MAX_TX_CHUNK_SIZE = 256
@seed.with_keychain
@ -96,44 +99,50 @@ async def sign_tx(
validate_network_info(msg.network_id, msg.protocol_magic)
if _has_stake_pool_registration(msg):
return await _sign_stake_pool_registration_tx(ctx, msg, keychain)
else:
return await _sign_standard_tx(ctx, msg, keychain)
async def _sign_standard_tx(
ctx: wire.Context, msg: CardanoSignTx, keychain: seed.Keychain
) -> CardanoSignedTx:
try:
for i in msg.inputs:
await validate_path(
ctx, keychain, i.address_n, SCHEMA_ADDRESS.match(i.address_n)
if _has_stake_pool_registration(msg):
cborized_tx, tx_hash = await _sign_stake_pool_registration_tx(
ctx, msg, keychain
)
else:
cborized_tx, tx_hash = await _sign_ordinary_tx(ctx, msg, keychain)
_validate_outputs(keychain, msg.outputs, msg.protocol_magic, msg.network_id)
_validate_certificates(msg.certificates, msg.protocol_magic, msg.network_id)
_validate_withdrawals(msg.withdrawals)
_validate_metadata(msg.metadata)
signed_tx_chunks = cbor.encode_chunked(cborized_tx, MAX_TX_CHUNK_SIZE)
# display the transaction in UI
await _show_standard_tx(ctx, keychain, msg)
for signed_tx_chunk in signed_tx_chunks:
response = CardanoSignedTxChunk(signed_tx_chunk=signed_tx_chunk)
await ctx.call(response, CardanoSignedTxChunkAck)
# sign the transaction bundle and prepare the result
serialized_tx, tx_hash = _serialize_tx(keychain, msg)
tx = CardanoSignedTx(serialized_tx=serialized_tx, tx_hash=tx_hash)
return CardanoSignedTx(tx_hash=tx_hash, serialized_tx=None)
except ValueError as e:
if __debug__:
log.exception(__name__, e)
raise wire.ProcessError("Signing failed")
return tx
async def _sign_ordinary_tx(
ctx: wire.Context, msg: CardanoSignTx, keychain: seed.Keychain
) -> Tuple[CborizedSignedTx, TxHash]:
for i in msg.inputs:
await validate_path(
ctx, keychain, i.address_n, SCHEMA_ADDRESS.match(i.address_n)
)
_validate_outputs(keychain, msg.outputs, msg.protocol_magic, msg.network_id)
_validate_certificates(msg.certificates, msg.protocol_magic, msg.network_id)
_validate_withdrawals(msg.withdrawals)
_validate_metadata(msg.metadata)
# display the transaction in UI
await _show_standard_tx(ctx, keychain, msg)
return _cborize_signed_tx(keychain, msg)
async def _sign_stake_pool_registration_tx(
ctx: wire.Context, msg: CardanoSignTx, keychain: seed.Keychain
) -> CardanoSignedTx:
) -> Tuple[CborizedSignedTx, TxHash]:
"""
We have a separate tx signing flow for stake pool registration because it's a
transaction where the witnessable entries (i.e. inputs, withdrawals, etc.)
@ -146,26 +155,16 @@ async def _sign_stake_pool_registration_tx(
except the stake pool certificate itself and we provide a witness only to the
user's staking key in the list of pool owners.
"""
try:
_validate_stake_pool_registration_tx_structure(msg)
_validate_stake_pool_registration_tx_structure(msg)
_ensure_no_signing_inputs(msg.inputs)
_validate_outputs(keychain, msg.outputs, msg.protocol_magic, msg.network_id)
_validate_certificates(msg.certificates, msg.protocol_magic, msg.network_id)
_validate_metadata(msg.metadata)
_ensure_no_signing_inputs(msg.inputs)
_validate_outputs(keychain, msg.outputs, msg.protocol_magic, msg.network_id)
_validate_certificates(msg.certificates, msg.protocol_magic, msg.network_id)
_validate_metadata(msg.metadata)
await _show_stake_pool_registration_tx(ctx, keychain, msg)
await _show_stake_pool_registration_tx(ctx, keychain, msg)
# sign the transaction bundle and prepare the result
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")
return tx
return _cborize_signed_tx(keychain, msg)
def _has_stake_pool_registration(msg: CardanoSignTx) -> bool:
@ -221,7 +220,6 @@ def _validate_outputs(
)
_validate_token_bundle(output.token_bundle)
_validate_max_tx_output_size(keychain, output, protocol_magic, network_id)
if total_amount > LOVELACE_MAX_SUPPLY:
raise wire.ProcessError("Total transaction amount is out of range!")
@ -255,29 +253,6 @@ def _validate_token_bundle(token_bundle: List[CardanoAssetGroupType]) -> None:
seen_asset_name_bytes.add(asset_name_bytes)
def _validate_max_tx_output_size(
keychain: seed.Keychain,
output: CardanoTxOutputType,
protocol_magic: int,
network_id: int,
) -> None:
"""
This limitation is a mitigation measure to prevent sending
large (especially change) outputs containing many tokens that Trezor
would not be able to spend reliably given that
currently the full Cardano transaction is held in-memory.
Once Cardano-transaction signing is refactored to be streamed, this
limit can be lifted
"""
cborized_output = _cborize_output(keychain, output, protocol_magic, network_id)
serialized_output = cbor.encode(cborized_output)
if len(serialized_output) > MAX_TX_OUTPUT_SIZE:
raise wire.ProcessError(
"Maximum tx output size (%s bytes) exceeded!" % MAX_TX_OUTPUT_SIZE
)
def _ensure_no_signing_inputs(inputs: List[CardanoTxInputType]) -> None:
if any(i.address_n for i in inputs):
raise INVALID_STAKEPOOL_REGISTRATION_TX_INPUTS
@ -316,7 +291,9 @@ def _validate_metadata(metadata: Optional[bytes]) -> None:
raise INVALID_METADATA
def _serialize_tx(keychain: seed.Keychain, msg: CardanoSignTx) -> Tuple[bytes, bytes]:
def _cborize_signed_tx(
keychain: seed.Keychain, msg: CardanoSignTx
) -> Tuple[CborizedSignedTx, TxHash]:
tx_body = _cborize_tx_body(keychain, msg)
tx_hash = _hash_tx_body(tx_body)
@ -333,9 +310,7 @@ def _serialize_tx(keychain: seed.Keychain, msg: CardanoSignTx) -> Tuple[bytes, b
if msg.metadata:
metadata = cbor.Raw(bytes(msg.metadata))
serialized_tx = cbor.encode([tx_body, witnesses, metadata])
return serialized_tx, tx_hash
return (tx_body, witnesses, metadata), tx_hash
def _cborize_tx_body(keychain: seed.Keychain, msg: CardanoSignTx) -> Dict:
@ -462,8 +437,13 @@ def _hash_metadata(metadata: bytes) -> bytes:
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()
tx_body_cbor_chunks = cbor.encode_streamed(tx_body)
hashfn = hashlib.blake2b(outlen=32)
for chunk in tx_body_cbor_chunks:
hashfn.update(chunk)
return hashfn.digest()
def _cborize_witnesses(

@ -7,10 +7,10 @@ from micropython import const
from trezor import log, utils
from . import readers
from . import readers, writers
if False:
from typing import Any, Iterable, List, Tuple, Union
from typing import Any, List, Tuple, Union, Iterator
Value = Any
CborSequence = Union[List[Value], Tuple[Value, ...]]
@ -55,7 +55,7 @@ def _header(typ: int, l: int) -> bytes:
raise NotImplementedError("Length %d not suppported" % l)
def _cbor_encode(value: Value) -> Iterable[bytes]:
def _cbor_encode(value: Value) -> Iterator[bytes]:
if isinstance(value, int):
if value >= 0:
yield _header(_CBOR_UNSIGNED_INT, value)
@ -230,6 +230,47 @@ def encode(value: Value) -> bytes:
return b"".join(_cbor_encode(value))
def encode_streamed(value: Value) -> Iterator[bytes]:
"""
Returns the encoded value as an iterable of the individual
CBOR "chunks", removing the need to reserve a continuous
chunk of memory for the full serialized representation of the value
"""
return _cbor_encode(value)
def encode_chunked(value: Value, max_chunk_size: int) -> Iterator[bytes]:
"""
Returns the encoded value as an iterable of chunks of a given size,
removing the need to reserve a continuous chunk of memory for the
full serialized representation of the value.
"""
if max_chunk_size <= 0:
raise ValueError
chunks = encode_streamed(value)
chunk_buffer = writers.empty_bytearray(max_chunk_size)
try:
current_chunk_view = utils.BufferReader(next(chunks))
while True:
num_bytes_to_write = min(
current_chunk_view.remaining_count(),
max_chunk_size - len(chunk_buffer),
)
chunk_buffer.extend(current_chunk_view.read(num_bytes_to_write))
if len(chunk_buffer) >= max_chunk_size:
yield chunk_buffer
chunk_buffer[:] = bytes()
if current_chunk_view.remaining_count() == 0:
current_chunk_view = utils.BufferReader(next(chunks))
except StopIteration:
if len(chunk_buffer) > 0:
yield chunk_buffer
def decode(cbor: bytes) -> Value:
r = utils.BufferReader(cbor)
res = _cbor_decode(r)

@ -17,7 +17,7 @@ class CardanoSignedTx(p.MessageType):
self,
*,
tx_hash: bytes,
serialized_tx: bytes,
serialized_tx: bytes = None,
) -> None:
self.tx_hash = tx_hash
self.serialized_tx = serialized_tx
@ -26,5 +26,5 @@ class CardanoSignedTx(p.MessageType):
def get_fields(cls) -> Dict:
return {
1: ('tx_hash', p.BytesType, p.FLAG_REQUIRED),
2: ('serialized_tx', p.BytesType, p.FLAG_REQUIRED),
2: ('serialized_tx', p.BytesType, None),
}

@ -0,0 +1,27 @@
# 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 CardanoSignedTxChunk(p.MessageType):
MESSAGE_WIRE_TYPE = 311
def __init__(
self,
*,
signed_tx_chunk: bytes,
) -> None:
self.signed_tx_chunk = signed_tx_chunk
@classmethod
def get_fields(cls) -> Dict:
return {
1: ('signed_tx_chunk', p.BytesType, p.FLAG_REQUIRED),
}

@ -0,0 +1,14 @@
# 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 CardanoSignedTxChunkAck(p.MessageType):
MESSAGE_WIRE_TYPE = 312

@ -145,6 +145,8 @@ if not utils.BITCOIN_ONLY:
CardanoGetAddress: Literal[307] = 307
CardanoAddress: Literal[308] = 308
CardanoSignedTx: Literal[310] = 310
CardanoSignedTxChunk: Literal[311] = 311
CardanoSignedTxChunkAck: Literal[312] = 312
RippleGetAddress: Literal[400] = 400
RippleAddress: Literal[401] = 401
RippleSignTx: Literal[402] = 402

@ -1,3 +1,5 @@
import math
from common import *
from apps.common.cbor import (
@ -5,6 +7,8 @@ from apps.common.cbor import (
IndefiniteLengthArray,
decode,
encode,
encode_chunked,
encode_streamed,
)
class TestCardanoCbor(unittest.TestCase):
@ -90,5 +94,53 @@ class TestCardanoCbor(unittest.TestCase):
self.assertEqual(encode(value_tuple), encoded)
self.assertEqual(decode(encoded), val)
def test_encode_streamed(self):
large_dict = {i: i for i in range(100)}
encoded = encode(large_dict)
encoded_streamed = [
bytes(item) for item in encode_streamed(large_dict)
]
self.assertEqual(b''.join(encoded_streamed), encoded)
def test_encode_chunked(self):
large_dict = {i: i for i in range(100)}
encoded = encode(large_dict)
encoded_len = len(encoded)
assert encoded_len == 354
arbitrary_encoded_len_factor = 59
arbitrary_power_of_two = 64
larger_than_encoded_len = encoded_len + 1
for max_chunk_size in [
1,
10,
arbitrary_encoded_len_factor,
arbitrary_power_of_two,
encoded_len,
larger_than_encoded_len
]:
encoded_chunks = [
bytes(chunk) for chunk in encode_chunked(large_dict, max_chunk_size)
]
expected_number_of_chunks = math.ceil(len(encoded) / max_chunk_size)
self.assertEqual(len(encoded_chunks), expected_number_of_chunks)
# all chunks except the last should be of chunk_size
for i in range(len(encoded_chunks) - 1):
self.assertEqual(len(encoded_chunks[i]), max_chunk_size)
# last chunk should contain the remaining bytes or the whole chunk
remaining_bytes = len(encoded) % max_chunk_size
expected_last_chunk_size = remaining_bytes if remaining_bytes > 0 else max_chunk_size
self.assertEqual(len(encoded_chunks[-1]), expected_last_chunk_size)
self.assertEqual(b''.join(encoded_chunks), encoded)
if __name__ == '__main__':
unittest.main()

@ -17,7 +17,7 @@
from ipaddress import ip_address
from typing import List, Optional
from . import messages, tools
from . import exceptions, messages, tools
from .tools import expect
PROTOCOL_MAGICS = {"mainnet": 764824073, "testnet": 42}
@ -370,4 +370,15 @@ def sign_tx(
)
)
return response
result = bytearray()
while isinstance(response, messages.CardanoSignedTxChunk):
result.extend(response.signed_tx_chunk)
response = client.call(messages.CardanoSignedTxChunkAck())
if not isinstance(response, messages.CardanoSignedTx):
raise exceptions.TrezorException("Unexpected response")
if response.serialized_tx is not None:
result.extend(response.serialized_tx)
return messages.CardanoSignedTx(tx_hash=response.tx_hash, serialized_tx=result)

@ -17,7 +17,7 @@ class CardanoSignedTx(p.MessageType):
self,
*,
tx_hash: bytes,
serialized_tx: bytes,
serialized_tx: bytes = None,
) -> None:
self.tx_hash = tx_hash
self.serialized_tx = serialized_tx
@ -26,5 +26,5 @@ class CardanoSignedTx(p.MessageType):
def get_fields(cls) -> Dict:
return {
1: ('tx_hash', p.BytesType, p.FLAG_REQUIRED),
2: ('serialized_tx', p.BytesType, p.FLAG_REQUIRED),
2: ('serialized_tx', p.BytesType, None),
}

@ -0,0 +1,27 @@
# 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 CardanoSignedTxChunk(p.MessageType):
MESSAGE_WIRE_TYPE = 311
def __init__(
self,
*,
signed_tx_chunk: bytes,
) -> None:
self.signed_tx_chunk = signed_tx_chunk
@classmethod
def get_fields(cls) -> Dict:
return {
1: ('signed_tx_chunk', p.BytesType, p.FLAG_REQUIRED),
}

@ -0,0 +1,14 @@
# 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 CardanoSignedTxChunkAck(p.MessageType):
MESSAGE_WIRE_TYPE = 312

@ -142,6 +142,8 @@ CardanoPublicKey: Literal[306] = 306
CardanoGetAddress: Literal[307] = 307
CardanoAddress: Literal[308] = 308
CardanoSignedTx: Literal[310] = 310
CardanoSignedTxChunk: Literal[311] = 311
CardanoSignedTxChunkAck: Literal[312] = 312
RippleGetAddress: Literal[400] = 400
RippleAddress: Literal[401] = 401
RippleSignTx: Literal[402] = 402

@ -35,6 +35,8 @@ from .CardanoPoolRelayParametersType import CardanoPoolRelayParametersType
from .CardanoPublicKey import CardanoPublicKey
from .CardanoSignTx import CardanoSignTx
from .CardanoSignedTx import CardanoSignedTx
from .CardanoSignedTxChunk import CardanoSignedTxChunk
from .CardanoSignedTxChunkAck import CardanoSignedTxChunkAck
from .CardanoTokenType import CardanoTokenType
from .CardanoTxCertificateType import CardanoTxCertificateType
from .CardanoTxInputType import CardanoTxInputType

@ -98,6 +98,49 @@ def test_cardano_sign_tx_failed(client, parameters, result):
)
@parametrize_using_common_fixtures("cardano/sign_tx.chunked.json")
def test_cardano_sign_tx_with_multiple_chunks(client, parameters, result):
inputs = [cardano.create_input(i) for i in parameters["inputs"]]
outputs = [cardano.create_output(o) for o in parameters["outputs"]]
certificates = [cardano.create_certificate(c) for c in parameters["certificates"]]
withdrawals = [cardano.create_withdrawal(w) for w in parameters["withdrawals"]]
input_flow = parameters.get("input_flow", ())
expected_responses = [
messages.PassphraseRequest(),
messages.ButtonRequest(),
messages.ButtonRequest(),
]
expected_responses += [
messages.CardanoSignedTxChunk(signed_tx_chunk=bytes.fromhex(signed_tx_chunk))
for signed_tx_chunk in result["signed_tx_chunks"]
]
expected_responses += [
messages.CardanoSignedTx(tx_hash=bytes.fromhex(result["tx_hash"]))
]
with client:
client.set_input_flow(_to_device_actions(client, input_flow))
client.set_expected_responses(expected_responses)
response = cardano.sign_tx(
client=client,
inputs=inputs,
outputs=outputs,
fee=parameters["fee"],
ttl=parameters.get("ttl"),
validity_interval_start=parameters.get("validity_interval_start"),
certificates=certificates,
withdrawals=withdrawals,
metadata=bytes.fromhex(parameters["metadata"]),
protocol_magic=parameters["protocol_magic"],
network_id=parameters["network_id"],
)
assert response.tx_hash.hex() == result["tx_hash"]
assert response.serialized_tx.hex() == result["serialized_tx"]
def _to_device_actions(client, input_flow):
if not input_flow:
yield

@ -62,11 +62,11 @@
"cardano-test_sign_tx.py::test_cardano_sign_tx_failed[stake_deregistration_account_larger_than_100]": "ffae5d78193ba3e989f025b67c544ce77ac4fde9d547e4588a9fe6f90354a4c2",
"cardano-test_sign_tx.py::test_cardano_sign_tx_failed[testnet_protocol_magic_with_mainnet_network_id]": "612dad8ab8762162a186ec9279d7de0bdfc589c52b4e4f4eba0545a00f21c3f0",
"cardano-test_sign_tx.py::test_cardano_sign_tx_failed[testnet_transaction_with_mainnet_output]": "612dad8ab8762162a186ec9279d7de0bdfc589c52b4e4f4eba0545a00f21c3f0",
"cardano-test_sign_tx.py::test_cardano_sign_tx_failed[too_many_tokens_in_output]": "612dad8ab8762162a186ec9279d7de0bdfc589c52b4e4f4eba0545a00f21c3f0",
"cardano-test_sign_tx.py::test_cardano_sign_tx_failed[two_owners_with_path]": "612dad8ab8762162a186ec9279d7de0bdfc589c52b4e4f4eba0545a00f21c3f0",
"cardano-test_sign_tx.py::test_cardano_sign_tx_failed[unsupported_address_type]": "612dad8ab8762162a186ec9279d7de0bdfc589c52b4e4f4eba0545a00f21c3f0",
"cardano-test_sign_tx.py::test_cardano_sign_tx_failed[withdrawal_amount_is_too_large]": "612dad8ab8762162a186ec9279d7de0bdfc589c52b4e4f4eba0545a00f21c3f0",
"cardano-test_sign_tx.py::test_cardano_sign_tx_failed[withdrawal_has_non_staking_path]": "612dad8ab8762162a186ec9279d7de0bdfc589c52b4e4f4eba0545a00f21c3f0",
"cardano-test_sign_tx.py::test_cardano_sign_tx_with_multiple_chunks[large_transaction_to_be_-377cb9ff": "4a46dd8ac42295d853646a55ca1b85023b2235af6155be663b1de10a6c98def2",
"test_autolock.py::test_apply_auto_lock_delay": "38c720e0d29b7487060f2a0f8d256a5e5b4f735511e049c43f6ea62e560603ae",
"test_autolock.py::test_apply_auto_lock_delay_out_of_range[0]": "6badfcdd682ecaf16311749ef7a6c07c6a4d0df402427c8dd5a48d476751ed77",
"test_autolock.py::test_apply_auto_lock_delay_out_of_range[1]": "6badfcdd682ecaf16311749ef7a6c07c6a4d0df402427c8dd5a48d476751ed77",

Loading…
Cancel
Save