1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2025-08-03 04:18:17 +00:00

chore(core): decrease nem size by 1550 bytes

This commit is contained in:
grdddj 2022-09-17 18:27:15 +02:00 committed by matejcik
parent 32125ef51f
commit d182ac5b53
19 changed files with 501 additions and 514 deletions

View File

@ -1,41 +1,42 @@
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from trezor.messages import NEMAddress
from trezor.ui.layouts import show_address
from apps.common.keychain import with_slip44_keychain from apps.common.keychain import with_slip44_keychain
from apps.common.paths import address_n_to_str, validate_path
from . import CURVE, PATTERNS, SLIP44_ID from . import CURVE, PATTERNS, SLIP44_ID
from .helpers import check_path, get_network_str
from .validators import validate_network
if TYPE_CHECKING: if TYPE_CHECKING:
from apps.common.keychain import Keychain from apps.common.keychain import Keychain
from trezor.wire import Context from trezor.wire import Context
from trezor.messages import NEMGetAddress from trezor.messages import NEMGetAddress, NEMAddress
@with_slip44_keychain(*PATTERNS, slip44_id=SLIP44_ID, curve=CURVE) @with_slip44_keychain(*PATTERNS, slip44_id=SLIP44_ID, curve=CURVE)
async def get_address( async def get_address(
ctx: Context, msg: NEMGetAddress, keychain: Keychain ctx: Context, msg: NEMGetAddress, keychain: Keychain
) -> NEMAddress: ) -> NEMAddress:
validate_network(msg.network) from trezor.messages import NEMAddress
await validate_path( from trezor.ui.layouts import show_address
ctx, keychain, msg.address_n, check_path(msg.address_n, msg.network) from apps.common.paths import address_n_to_str, validate_path
) from .helpers import check_path, get_network_str
from .validators import validate_network
node = keychain.derive(msg.address_n) address_n = msg.address_n # local_cache_attribute
address = node.nem_address(msg.network) network = msg.network # local_cache_attribute
validate_network(network)
await validate_path(ctx, keychain, address_n, check_path(address_n, network))
node = keychain.derive(address_n)
address = node.nem_address(network)
if msg.show_display: if msg.show_display:
title = address_n_to_str(msg.address_n) title = address_n_to_str(address_n)
await show_address( await show_address(
ctx, ctx,
address=address, address,
case_sensitive=False, case_sensitive=False,
title=title, title=title,
network=get_network_str(msg.network), network=get_network_str(network),
) )
return NEMAddress(address=address) return NEMAddress(address=address)

View File

@ -1,8 +1,9 @@
from micropython import const from micropython import const
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from apps.common import paths from apps.common import paths
from . import SLIP44_ID
NEM_NETWORK_MAINNET = const(0x68) NEM_NETWORK_MAINNET = const(0x68)
NEM_NETWORK_TESTNET = const(0x98) NEM_NETWORK_TESTNET = const(0x98)
@ -44,6 +45,9 @@ def get_network_str(network: int) -> str:
def check_path(path: paths.Bip32Path, network: int) -> bool: def check_path(path: paths.Bip32Path, network: int) -> bool:
"""Validates that the appropriate coin_type is set for the given network.""" """Validates that the appropriate coin_type is set for the given network."""
from apps.common import paths
from . import SLIP44_ID
if len(path) < 2: if len(path) < 2:
return False return False

View File

@ -2,7 +2,7 @@ from typing import TYPE_CHECKING
from trezor.enums import ButtonRequestType from trezor.enums import ButtonRequestType
from trezor.strings import format_amount from trezor.strings import format_amount
from trezor.ui.layouts import confirm_metadata, confirm_properties from trezor.ui.layouts import confirm_metadata
from .helpers import NEM_MAX_DIVISIBILITY from .helpers import NEM_MAX_DIVISIBILITY
@ -14,8 +14,8 @@ async def require_confirm_text(ctx: Context, action: str) -> None:
await confirm_metadata( await confirm_metadata(
ctx, ctx,
"confirm_nem", "confirm_nem",
title="Confirm action", "Confirm action",
content=action, action,
hide_continue=True, hide_continue=True,
br_code=ButtonRequestType.ConfirmOutput, br_code=ButtonRequestType.ConfirmOutput,
) )
@ -25,20 +25,22 @@ async def require_confirm_fee(ctx: Context, action: str, fee: int) -> None:
await confirm_metadata( await confirm_metadata(
ctx, ctx,
"confirm_fee", "confirm_fee",
title="Confirm fee", "Confirm fee",
content=action + "\n{}", action + "\n{}",
param=f"{format_amount(fee, NEM_MAX_DIVISIBILITY)} XEM", f"{format_amount(fee, NEM_MAX_DIVISIBILITY)} XEM",
ButtonRequestType.ConfirmOutput,
hide_continue=True, hide_continue=True,
br_code=ButtonRequestType.ConfirmOutput,
) )
async def require_confirm_content(ctx: Context, headline: str, content: list) -> None: async def require_confirm_content(ctx: Context, headline: str, content: list) -> None:
from trezor.ui.layouts import confirm_properties
await confirm_properties( await confirm_properties(
ctx, ctx,
"confirm_content", "confirm_content",
title=headline, headline,
props=content, content,
) )
@ -47,9 +49,9 @@ async def require_confirm_final(ctx: Context, fee: int) -> None:
await confirm_metadata( await confirm_metadata(
ctx, ctx,
"confirm_final", "confirm_final",
title="Final confirm", "Final confirm",
content="Sign this transaction\n{}\nfor network fee?", "Sign this transaction\n{}\nfor network fee?",
param=f"and pay {format_amount(fee, NEM_MAX_DIVISIBILITY)} XEM", f"and pay {format_amount(fee, NEM_MAX_DIVISIBILITY)} XEM",
hide_continue=True, hide_continue=True,
hold=True, hold=True,
) )

View File

@ -1,7 +1,5 @@
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from .nem_mosaics import mosaics_iterator
if TYPE_CHECKING: if TYPE_CHECKING:
from trezor.messages import NEMMosaic from trezor.messages import NEMMosaic
@ -11,6 +9,8 @@ if TYPE_CHECKING:
def get_mosaic_definition( def get_mosaic_definition(
namespace_name: str, mosaic_name: str, network: int namespace_name: str, mosaic_name: str, network: int
) -> Mosaic | None: ) -> Mosaic | None:
from .nem_mosaics import mosaics_iterator
for mosaic in mosaics_iterator(): for mosaic in mosaics_iterator():
if namespace_name == mosaic.namespace and mosaic_name == mosaic.mosaic: if namespace_name == mosaic.namespace and mosaic_name == mosaic.mosaic:
if (mosaic.networks is None) or (network in mosaic.networks): if (mosaic.networks is None) or (network in mosaic.networks):

View File

@ -1,15 +1,6 @@
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from trezor import ui from ..layout import require_confirm_content, require_confirm_final
from trezor.enums import NEMMosaicLevy, NEMSupplyChangeType
from trezor.ui.layouts import confirm_properties
from ..layout import (
require_confirm_content,
require_confirm_fee,
require_confirm_final,
require_confirm_text,
)
if TYPE_CHECKING: if TYPE_CHECKING:
from trezor.messages import ( from trezor.messages import (
@ -24,8 +15,14 @@ if TYPE_CHECKING:
async def ask_mosaic_creation( async def ask_mosaic_creation(
ctx: Context, common: NEMTransactionCommon, creation: NEMMosaicCreation ctx: Context, common: NEMTransactionCommon, creation: NEMMosaicCreation
) -> None: ) -> None:
await require_confirm_content(ctx, "Create mosaic", _creation_message(creation)) from ..layout import require_confirm_fee
await require_confirm_properties(ctx, creation.definition)
creation_message = [
("Create mosaic", creation.definition.mosaic),
("under namespace", creation.definition.namespace),
]
await require_confirm_content(ctx, "Create mosaic", creation_message)
await _require_confirm_properties(ctx, creation.definition)
await require_confirm_fee(ctx, "Confirm creation fee", creation.fee) await require_confirm_fee(ctx, "Confirm creation fee", creation.fee)
await require_confirm_final(ctx, common.fee) await require_confirm_final(ctx, common.fee)
@ -34,57 +31,49 @@ async def ask_mosaic_creation(
async def ask_supply_change( async def ask_supply_change(
ctx: Context, common: NEMTransactionCommon, change: NEMMosaicSupplyChange ctx: Context, common: NEMTransactionCommon, change: NEMMosaicSupplyChange
) -> None: ) -> None:
await require_confirm_content(ctx, "Supply change", _supply_message(change)) from trezor.enums import NEMSupplyChangeType
from ..layout import require_confirm_text
supply_message = [
("Modify supply for", change.mosaic),
("under namespace", change.namespace),
]
await require_confirm_content(ctx, "Supply change", supply_message)
if change.type == NEMSupplyChangeType.SupplyChange_Decrease: if change.type == NEMSupplyChangeType.SupplyChange_Decrease:
msg = "Decrease supply by " + str(change.delta) + " whole units?" action = "Decrease"
elif change.type == NEMSupplyChangeType.SupplyChange_Increase: elif change.type == NEMSupplyChangeType.SupplyChange_Increase:
msg = "Increase supply by " + str(change.delta) + " whole units?" action = "Increase"
else: else:
raise ValueError("Invalid supply change type") raise ValueError("Invalid supply change type")
await require_confirm_text(ctx, msg) await require_confirm_text(ctx, f"{action} supply by {change.delta} whole units?")
await require_confirm_final(ctx, common.fee) await require_confirm_final(ctx, common.fee)
def _creation_message(mosaic_creation: NEMMosaicCreation) -> list[tuple[str, str]]: async def _require_confirm_properties(
return [
("Create mosaic", mosaic_creation.definition.mosaic),
("under namespace", mosaic_creation.definition.namespace),
]
def _supply_message(supply_change: NEMMosaicSupplyChange) -> list[tuple[str, str]]:
return [
("Modify supply for", supply_change.mosaic),
("under namespace", supply_change.namespace),
]
async def require_confirm_properties(
ctx: Context, definition: NEMMosaicDefinition ctx: Context, definition: NEMMosaicDefinition
) -> None: ) -> None:
from trezor.enums import NEMMosaicLevy
from trezor import ui
from trezor.ui.layouts import confirm_properties
properties = [] properties = []
append = properties.append # local_cache_attribute
# description # description
if definition.description: if definition.description:
properties.append(("Description:", definition.description)) append(("Description:", definition.description))
# transferable # transferable
if definition.transferable: transferable = "Yes" if definition.transferable else "No"
transferable = "Yes" append(("Transferable?", transferable))
else:
transferable = "No"
properties.append(("Transferable?", transferable))
# mutable_supply # mutable_supply
if definition.mutable_supply: imm = "mutable" if definition.mutable_supply else "immutable"
imm = "mutable"
else:
imm = "immutable"
if definition.supply: if definition.supply:
properties.append(("Initial supply:", str(definition.supply) + "\n" + imm)) append(("Initial supply:", str(definition.supply) + "\n" + imm))
else: else:
properties.append(("Initial supply:", imm)) append(("Initial supply:", imm))
# levy # levy
if definition.levy: if definition.levy:
@ -93,24 +82,25 @@ async def require_confirm_properties(
assert definition.levy_namespace is not None assert definition.levy_namespace is not None
assert definition.levy_mosaic is not None assert definition.levy_mosaic is not None
properties.append(("Levy recipient:", definition.levy_address)) append(("Levy recipient:", definition.levy_address))
properties.append(("Levy fee:", str(definition.fee))) append(("Levy fee:", str(definition.fee)))
properties.append(("Levy divisibility:", str(definition.divisibility))) append(("Levy divisibility:", str(definition.divisibility)))
properties.append(("Levy namespace:", definition.levy_namespace)) append(("Levy namespace:", definition.levy_namespace))
properties.append(("Levy mosaic:", definition.levy_mosaic)) append(("Levy mosaic:", definition.levy_mosaic))
if definition.levy == NEMMosaicLevy.MosaicLevy_Absolute: levy_type = (
levy_type = "absolute" "absolute"
else: if definition.levy == NEMMosaicLevy.MosaicLevy_Absolute
levy_type = "percentile" else "percentile"
properties.append(("Levy type:", levy_type)) )
append(("Levy type:", levy_type))
await confirm_properties( await confirm_properties(
ctx, ctx,
"confirm_properties", "confirm_properties",
title="Confirm properties", "Confirm properties",
props=properties, properties,
icon_color=ui.ORANGE_ICON, icon_color=ui.ORANGE_ICON,
) )

View File

@ -2,6 +2,9 @@
# (by running `make templates` in `core`) # (by running `make templates` in `core`)
# do not edit manually! # do not edit manually!
# NOTE: not supplying the kwargs saves 120 bytes of code size
# `networks` needs kwarg as `levy` above is optional
from typing import Iterator from typing import Iterator
from trezor.enums import NEMMosaicLevy from trezor.enums import NEMMosaicLevy
@ -43,61 +46,65 @@ class Mosaic:
def mosaics_iterator() -> Iterator[Mosaic]: def mosaics_iterator() -> Iterator[Mosaic]:
yield Mosaic( yield Mosaic(
name="NEM", "NEM", # name
ticker=" XEM", " XEM", # ticker
namespace="nem", "nem", # namespace
mosaic="xem", "xem", # mosaic
divisibility=6, 6, # divisibility
None, # levy
) )
yield Mosaic( yield Mosaic(
name="DIMCOIN", "DIMCOIN", # name
ticker=" DIM", " DIM", # ticker
namespace="dim", "dim", # namespace
mosaic="coin", "coin", # mosaic
divisibility=6, 6, # divisibility
levy=MosaicLevy( MosaicLevy( # levy
type=NEMMosaicLevy.MosaicLevy_Percentile, NEMMosaicLevy.MosaicLevy_Percentile, # type
fee=10, 10, # fee
namespace="dim", "dim", # namespace
mosaic="coin", "coin", # mosaic
), ),
networks=(104,), (104,), # networks
) )
yield Mosaic( yield Mosaic(
name="DIM TOKEN", "DIM TOKEN", # name
ticker=" DIMTOK", " DIMTOK", # ticker
namespace="dim", "dim", # namespace
mosaic="token", "token", # mosaic
divisibility=6, 6, # divisibility
networks=(104,), None, # levy
(104,), # networks
) )
yield Mosaic( yield Mosaic(
name="Breeze Token", "Breeze Token", # name
ticker=" BREEZE", " BREEZE", # ticker
namespace="breeze", "breeze", # namespace
mosaic="breeze-token", "breeze-token", # mosaic
divisibility=0, 0, # divisibility
networks=(104,), None, # levy
(104,), # networks
) )
yield Mosaic( yield Mosaic(
name="PacNEM Game Credits", "PacNEM Game Credits", # name
ticker=" PAC:HRT", " PAC:HRT", # ticker
namespace="pacnem", "pacnem", # namespace
mosaic="heart", "heart", # mosaic
divisibility=0, 0, # divisibility
networks=(104,), None, # levy
(104,), # networks
) )
yield Mosaic( yield Mosaic(
name="PacNEM Score Tokens", "PacNEM Score Tokens", # name
ticker=" PAC:CHS", " PAC:CHS", # ticker
namespace="pacnem", "pacnem", # namespace
mosaic="cheese", "cheese", # mosaic
divisibility=6, 6, # divisibility
levy=MosaicLevy( MosaicLevy( # levy
type=NEMMosaicLevy.MosaicLevy_Percentile, NEMMosaicLevy.MosaicLevy_Percentile, # type
fee=100, 100, # fee
namespace="nem", "nem", # namespace
mosaic="xem", "xem", # mosaic
), ),
networks=(104,), (104,), # networks
) )

View File

@ -2,6 +2,9 @@
# (by running `make templates` in `core`) # (by running `make templates` in `core`)
# do not edit manually! # do not edit manually!
# NOTE: not supplying the kwargs saves 120 bytes of code size
# `networks` needs kwarg as `levy` above is optional
from typing import Iterator from typing import Iterator
from trezor.enums import NEMMosaicLevy from trezor.enums import NEMMosaicLevy
@ -44,21 +47,23 @@ class Mosaic:
def mosaics_iterator() -> Iterator[Mosaic]: def mosaics_iterator() -> Iterator[Mosaic]:
% for m in supported_on("trezor2", nem): % for m in supported_on("trezor2", nem):
yield Mosaic( yield Mosaic(
name="${m.name}", "${m.name}", # name
ticker=" ${m.ticker}", " ${m.ticker}", # ticker
namespace="${m.namespace}", "${m.namespace}", # namespace
mosaic="${m.mosaic}", "${m.mosaic}", # mosaic
divisibility=${m.divisibility}, ${m.divisibility}, # divisibility
% if "levy" in m: % if "levy" in m:
levy=MosaicLevy( MosaicLevy( # levy
type=NEMMosaicLevy.${m.levy}, NEMMosaicLevy.${m.levy}, # type
fee=${m.fee}, ${m.fee}, # fee
namespace="${m.levy_namespace}", "${m.levy_namespace}", # namespace
mosaic="${m.levy_mosaic}", "${m.levy_mosaic}", # mosaic
), ),
% else:
None, # levy
% endif % endif
% if "networks" in m: % if "networks" in m:
networks=${tuple(m.networks)}, ${tuple(m.networks)}, # networks
% endif % endif
) )
% endfor % endfor

View File

@ -1,9 +1,5 @@
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from ..helpers import (
NEM_TRANSACTION_TYPE_MOSAIC_CREATION,
NEM_TRANSACTION_TYPE_MOSAIC_SUPPLY_CHANGE,
)
from ..writers import ( from ..writers import (
serialize_tx_common, serialize_tx_common,
write_bytes_with_len, write_bytes_with_len,
@ -23,44 +19,43 @@ if TYPE_CHECKING:
def serialize_mosaic_creation( def serialize_mosaic_creation(
common: NEMTransactionCommon, creation: NEMMosaicCreation, public_key: bytes common: NEMTransactionCommon, creation: NEMMosaicCreation, public_key: bytes
) -> bytes: ) -> bytes:
from ..helpers import NEM_TRANSACTION_TYPE_MOSAIC_CREATION
w = serialize_tx_common(common, public_key, NEM_TRANSACTION_TYPE_MOSAIC_CREATION) w = serialize_tx_common(common, public_key, NEM_TRANSACTION_TYPE_MOSAIC_CREATION)
mosaics_w = bytearray() mosaics_w = bytearray()
write_bytes_with_len(mosaics_w, public_key) write_bytes_with_len(mosaics_w, public_key)
definition = creation.definition # local_cache_attribute
identifier_w = bytearray() identifier_w = bytearray()
write_bytes_with_len(identifier_w, creation.definition.namespace.encode()) write_bytes_with_len(identifier_w, definition.namespace.encode())
write_bytes_with_len(identifier_w, creation.definition.mosaic.encode()) write_bytes_with_len(identifier_w, definition.mosaic.encode())
write_bytes_with_len(mosaics_w, identifier_w) write_bytes_with_len(mosaics_w, identifier_w)
write_bytes_with_len(mosaics_w, creation.definition.description.encode()) write_bytes_with_len(mosaics_w, definition.description.encode())
write_uint32_le(mosaics_w, 4) # number of properties write_uint32_le(mosaics_w, 4) # number of properties
_write_property(mosaics_w, "divisibility", creation.definition.divisibility) _write_property(mosaics_w, "divisibility", definition.divisibility)
_write_property(mosaics_w, "initialSupply", creation.definition.supply) _write_property(mosaics_w, "initialSupply", definition.supply)
_write_property(mosaics_w, "supplyMutable", creation.definition.mutable_supply) _write_property(mosaics_w, "supplyMutable", definition.mutable_supply)
_write_property(mosaics_w, "transferable", creation.definition.transferable) _write_property(mosaics_w, "transferable", definition.transferable)
if creation.definition.levy: if definition.levy:
# all below asserts checked by nem.validators._validate_mosaic_creation # all below asserts checked by nem.validators._validate_mosaic_creation
assert creation.definition.levy_namespace is not None assert definition.levy_namespace is not None
assert creation.definition.levy_mosaic is not None assert definition.levy_mosaic is not None
assert creation.definition.levy_address is not None assert definition.levy_address is not None
assert creation.definition.fee is not None assert definition.fee is not None
levy_identifier_w = bytearray() levy_identifier_w = bytearray()
write_bytes_with_len( write_bytes_with_len(levy_identifier_w, definition.levy_namespace.encode())
levy_identifier_w, creation.definition.levy_namespace.encode() write_bytes_with_len(levy_identifier_w, definition.levy_mosaic.encode())
)
write_bytes_with_len(
levy_identifier_w, creation.definition.levy_mosaic.encode()
)
levy_w = bytearray() levy_w = bytearray()
write_uint32_le(levy_w, creation.definition.levy) write_uint32_le(levy_w, definition.levy)
write_bytes_with_len(levy_w, creation.definition.levy_address.encode()) write_bytes_with_len(levy_w, definition.levy_address.encode())
write_bytes_with_len(levy_w, levy_identifier_w) write_bytes_with_len(levy_w, levy_identifier_w)
write_uint64_le(levy_w, creation.definition.fee) write_uint64_le(levy_w, definition.fee)
write_bytes_with_len(mosaics_w, levy_w) write_bytes_with_len(mosaics_w, levy_w)
else: else:
@ -77,6 +72,8 @@ def serialize_mosaic_creation(
def serialize_mosaic_supply_change( def serialize_mosaic_supply_change(
common: NEMTransactionCommon, change: NEMMosaicSupplyChange, public_key: bytes common: NEMTransactionCommon, change: NEMMosaicSupplyChange, public_key: bytes
) -> bytes: ) -> bytes:
from ..helpers import NEM_TRANSACTION_TYPE_MOSAIC_SUPPLY_CHANGE
w = serialize_tx_common( w = serialize_tx_common(
common, public_key, NEM_TRANSACTION_TYPE_MOSAIC_SUPPLY_CHANGE common, public_key, NEM_TRANSACTION_TYPE_MOSAIC_SUPPLY_CHANGE
) )

View File

@ -1,11 +1,6 @@
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from trezor import ui
from trezor.crypto import nem from trezor.crypto import nem
from trezor.enums import ButtonRequestType, NEMModificationType
from trezor.ui.layouts import confirm_address
from ..layout import require_confirm_fee, require_confirm_final, require_confirm_text
if TYPE_CHECKING: if TYPE_CHECKING:
from trezor.messages import ( from trezor.messages import (
@ -17,6 +12,8 @@ if TYPE_CHECKING:
async def ask_multisig(ctx: Context, msg: NEMSignTx) -> None: async def ask_multisig(ctx: Context, msg: NEMSignTx) -> None:
from ..layout import require_confirm_fee
assert msg.multisig is not None # sign_tx assert msg.multisig is not None # sign_tx
assert msg.multisig.signer is not None # sign_tx assert msg.multisig.signer is not None # sign_tx
address = nem.compute_address(msg.multisig.signer, msg.transaction.network) address = nem.compute_address(msg.multisig.signer, msg.transaction.network)
@ -33,6 +30,9 @@ async def ask_aggregate_modification(
mod: NEMAggregateModification, mod: NEMAggregateModification,
multisig: bool, multisig: bool,
) -> None: ) -> None:
from trezor.enums import NEMModificationType
from ..layout import require_confirm_final, require_confirm_text
if not multisig: if not multisig:
await require_confirm_text(ctx, "Convert account to multisig account?") await require_confirm_text(ctx, "Convert account to multisig account?")
@ -55,12 +55,14 @@ async def ask_aggregate_modification(
async def _require_confirm_address(ctx: Context, action: str, address: str) -> None: async def _require_confirm_address(ctx: Context, action: str, address: str) -> None:
from trezor.enums import ButtonRequestType
from trezor.ui.layouts import confirm_address
await confirm_address( await confirm_address(
ctx, ctx,
br_type="confirm_multisig", "Confirm address",
title="Confirm address", address,
description=action, action,
address=address, "confirm_multisig",
br_code=ButtonRequestType.ConfirmOutput, ButtonRequestType.ConfirmOutput,
icon=ui.ICON_SEND,
) )

View File

@ -1,12 +1,5 @@
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from trezor.crypto import hashlib, nem
from ..helpers import (
NEM_TRANSACTION_TYPE_AGGREGATE_MODIFICATION,
NEM_TRANSACTION_TYPE_MULTISIG,
NEM_TRANSACTION_TYPE_MULTISIG_SIGNATURE,
)
from ..writers import serialize_tx_common, write_bytes_with_len, write_uint32_le from ..writers import serialize_tx_common, write_bytes_with_len, write_uint32_le
if TYPE_CHECKING: if TYPE_CHECKING:
@ -17,6 +10,8 @@ if TYPE_CHECKING:
def serialize_multisig( def serialize_multisig(
common: NEMTransactionCommon, public_key: bytes, inner: bytes common: NEMTransactionCommon, public_key: bytes, inner: bytes
) -> bytes: ) -> bytes:
from ..helpers import NEM_TRANSACTION_TYPE_MULTISIG
w = serialize_tx_common(common, public_key, NEM_TRANSACTION_TYPE_MULTISIG) w = serialize_tx_common(common, public_key, NEM_TRANSACTION_TYPE_MULTISIG)
write_bytes_with_len(w, inner) write_bytes_with_len(w, inner)
return w return w
@ -28,6 +23,9 @@ def serialize_multisig_signature(
inner: bytes, inner: bytes,
address_public_key: bytes, address_public_key: bytes,
) -> bytes: ) -> bytes:
from trezor.crypto import hashlib, nem
from ..helpers import NEM_TRANSACTION_TYPE_MULTISIG_SIGNATURE
w = serialize_tx_common(common, public_key, NEM_TRANSACTION_TYPE_MULTISIG_SIGNATURE) w = serialize_tx_common(common, public_key, NEM_TRANSACTION_TYPE_MULTISIG_SIGNATURE)
digest = hashlib.sha3_256(inner, keccak=True).digest() digest = hashlib.sha3_256(inner, keccak=True).digest()
address = nem.compute_address(address_public_key, common.network) address = nem.compute_address(address_public_key, common.network)
@ -41,6 +39,8 @@ def serialize_multisig_signature(
def serialize_aggregate_modification( def serialize_aggregate_modification(
common: NEMTransactionCommon, mod: NEMAggregateModification, public_key: bytes common: NEMTransactionCommon, mod: NEMAggregateModification, public_key: bytes
) -> bytearray: ) -> bytearray:
from ..helpers import NEM_TRANSACTION_TYPE_AGGREGATE_MODIFICATION
version = common.network << 24 | 1 version = common.network << 24 | 1
if mod.relative_change: if mod.relative_change:
version = common.network << 24 | 2 version = common.network << 24 | 2

View File

@ -1,7 +1,5 @@
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from . import layout, serialize
if TYPE_CHECKING: if TYPE_CHECKING:
from trezor.messages import NEMProvisionNamespace, NEMTransactionCommon from trezor.messages import NEMProvisionNamespace, NEMTransactionCommon
from trezor.wire import Context from trezor.wire import Context
@ -13,5 +11,7 @@ async def namespace(
common: NEMTransactionCommon, common: NEMTransactionCommon,
namespace: NEMProvisionNamespace, namespace: NEMProvisionNamespace,
) -> bytes: ) -> bytes:
from . import layout, serialize
await layout.ask_provision_namespace(ctx, common, namespace) await layout.ask_provision_namespace(ctx, common, namespace)
return serialize.serialize_provision_namespace(common, namespace, public_key) return serialize.serialize_provision_namespace(common, namespace, public_key)

View File

@ -1,7 +1,5 @@
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from ..layout import require_confirm_content, require_confirm_fee, require_confirm_final
if TYPE_CHECKING: if TYPE_CHECKING:
from trezor.messages import NEMProvisionNamespace, NEMTransactionCommon from trezor.messages import NEMProvisionNamespace, NEMTransactionCommon
from trezor.wire import Context from trezor.wire import Context
@ -10,6 +8,12 @@ if TYPE_CHECKING:
async def ask_provision_namespace( async def ask_provision_namespace(
ctx: Context, common: NEMTransactionCommon, namespace: NEMProvisionNamespace ctx: Context, common: NEMTransactionCommon, namespace: NEMProvisionNamespace
) -> None: ) -> None:
from ..layout import (
require_confirm_content,
require_confirm_fee,
require_confirm_final,
)
if namespace.parent: if namespace.parent:
content = [ content = [
("Create namespace", namespace.namespace), ("Create namespace", namespace.namespace),

View File

@ -1,13 +1,5 @@
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from ..helpers import NEM_TRANSACTION_TYPE_PROVISION_NAMESPACE
from ..writers import (
serialize_tx_common,
write_bytes_with_len,
write_uint32_le,
write_uint64_le,
)
if TYPE_CHECKING: if TYPE_CHECKING:
from trezor.messages import NEMProvisionNamespace, NEMTransactionCommon from trezor.messages import NEMProvisionNamespace, NEMTransactionCommon
@ -15,6 +7,14 @@ if TYPE_CHECKING:
def serialize_provision_namespace( def serialize_provision_namespace(
common: NEMTransactionCommon, namespace: NEMProvisionNamespace, public_key: bytes common: NEMTransactionCommon, namespace: NEMProvisionNamespace, public_key: bytes
) -> bytes: ) -> bytes:
from ..helpers import NEM_TRANSACTION_TYPE_PROVISION_NAMESPACE
from ..writers import (
serialize_tx_common,
write_bytes_with_len,
write_uint32_le,
write_uint64_le,
)
tx = serialize_tx_common( tx = serialize_tx_common(
common, public_key, NEM_TRANSACTION_TYPE_PROVISION_NAMESPACE common, public_key, NEM_TRANSACTION_TYPE_PROVISION_NAMESPACE
) )

View File

@ -1,44 +1,50 @@
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from trezor import wire from apps.common.keychain import with_slip44_keychain
from . import CURVE, PATTERNS, SLIP44_ID
if TYPE_CHECKING:
from trezor.messages import NEMSignTx, NEMSignedTx
from apps.common.keychain import Keychain
from trezor.wire import Context
@with_slip44_keychain(*PATTERNS, slip44_id=SLIP44_ID, curve=CURVE)
async def sign_tx(ctx: Context, msg: NEMSignTx, keychain: Keychain) -> NEMSignedTx:
from trezor.wire import DataError
from trezor.crypto.curve import ed25519 from trezor.crypto.curve import ed25519
from trezor.messages import NEMSignedTx from trezor.messages import NEMSignedTx
from apps.common import seed from apps.common import seed
from apps.common.keychain import with_slip44_keychain
from apps.common.paths import validate_path from apps.common.paths import validate_path
from . import CURVE, PATTERNS, SLIP44_ID, mosaic, multisig, namespace, transfer from . import mosaic, multisig, namespace, transfer
from .helpers import NEM_HASH_ALG, check_path from .helpers import NEM_HASH_ALG, check_path
from .validators import validate from .validators import validate
if TYPE_CHECKING:
from trezor.messages import NEMSignTx
from apps.common.keychain import Keychain
@with_slip44_keychain(*PATTERNS, slip44_id=SLIP44_ID, curve=CURVE)
async def sign_tx(ctx: wire.Context, msg: NEMSignTx, keychain: Keychain) -> NEMSignedTx:
validate(msg) validate(msg)
msg_multisig = msg.multisig # local_cache_attribute
transaction = msg.transaction # local_cache_attribute
await validate_path( await validate_path(
ctx, ctx,
keychain, keychain,
msg.transaction.address_n, transaction.address_n,
check_path(msg.transaction.address_n, msg.transaction.network), check_path(transaction.address_n, transaction.network),
) )
node = keychain.derive(msg.transaction.address_n) node = keychain.derive(transaction.address_n)
if msg.multisig: if msg_multisig:
if msg.multisig.signer is None: if msg_multisig.signer is None:
raise wire.DataError("No signer provided") raise DataError("No signer provided")
public_key = msg.multisig.signer public_key = msg_multisig.signer
common = msg.multisig common = msg_multisig
await multisig.ask(ctx, msg) await multisig.ask(ctx, msg)
else: else:
public_key = seed.remove_ed25519_prefix(node.public_key()) public_key = seed.remove_ed25519_prefix(node.public_key())
common = msg.transaction common = transaction
if msg.transfer: if msg.transfer:
tx = await transfer.transfer(ctx, public_key, common, msg.transfer, node) tx = await transfer.transfer(ctx, public_key, common, msg.transfer, node)
@ -54,28 +60,28 @@ async def sign_tx(ctx: wire.Context, msg: NEMSignTx, keychain: Keychain) -> NEMS
public_key, public_key,
common, common,
msg.aggregate_modification, msg.aggregate_modification,
msg.multisig is not None, msg_multisig is not None,
) )
elif msg.importance_transfer: elif msg.importance_transfer:
tx = await transfer.importance_transfer( tx = await transfer.importance_transfer(
ctx, public_key, common, msg.importance_transfer ctx, public_key, common, msg.importance_transfer
) )
else: else:
raise wire.DataError("No transaction provided") raise DataError("No transaction provided")
if msg.multisig: if msg_multisig:
# wrap transaction in multisig wrapper # wrap transaction in multisig wrapper
if msg.cosigning: if msg.cosigning:
assert msg.multisig.signer is not None assert msg_multisig.signer is not None
tx = multisig.cosign( tx = multisig.cosign(
seed.remove_ed25519_prefix(node.public_key()), seed.remove_ed25519_prefix(node.public_key()),
msg.transaction, transaction,
tx, tx,
msg.multisig.signer, msg_multisig.signer,
) )
else: else:
tx = multisig.initiate( tx = multisig.initiate(
seed.remove_ed25519_prefix(node.public_key()), msg.transaction, tx seed.remove_ed25519_prefix(node.public_key()), transaction, tx
) )
signature = ed25519.sign(node.private_key(), tx, NEM_HASH_ALG) signature = ed25519.sign(node.private_key(), tx, NEM_HASH_ALG)

View File

@ -1,22 +1,12 @@
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from trezor import ui from trezor import ui
from trezor.enums import ButtonRequestType, NEMImportanceTransferMode, NEMMosaicLevy from trezor.enums import ButtonRequestType
from trezor.strings import format_amount from trezor.strings import format_amount
from trezor.ui.layouts import (
confirm_action,
confirm_output,
confirm_properties,
confirm_text,
)
from ..helpers import ( from ..helpers import NEM_MOSAIC_AMOUNT_DIVISOR
NEM_LEVY_PERCENTILE_DIVISOR_ABSOLUTE, from ..layout import require_confirm_final
NEM_MAX_DIVISIBILITY, from ..mosaic.helpers import is_nem_xem_mosaic
NEM_MOSAIC_AMOUNT_DIVISOR,
)
from ..layout import require_confirm_final, require_confirm_text
from ..mosaic.helpers import get_mosaic_definition, is_nem_xem_mosaic
if TYPE_CHECKING: if TYPE_CHECKING:
from trezor.messages import ( from trezor.messages import (
@ -26,7 +16,6 @@ if TYPE_CHECKING:
NEMTransfer, NEMTransfer,
) )
from trezor.wire import Context from trezor.wire import Context
from ..mosaic.nem_mosaics import MosaicLevy
async def ask_transfer( async def ask_transfer(
@ -35,17 +24,45 @@ async def ask_transfer(
transfer: NEMTransfer, transfer: NEMTransfer,
encrypted: bool, encrypted: bool,
) -> None: ) -> None:
from trezor.ui.layouts import confirm_output, confirm_text
from ..helpers import NEM_MAX_DIVISIBILITY
if transfer.payload: if transfer.payload:
await _require_confirm_payload(ctx, transfer.payload, encrypted) # require_confirm_payload
await confirm_text(
ctx,
"confirm_payload",
"Confirm payload",
bytes(transfer.payload).decode(),
"Encrypted:" if encrypted else "Unencrypted:",
ButtonRequestType.ConfirmOutput,
icon_color=ui.GREEN if encrypted else ui.RED,
)
for mosaic in transfer.mosaics: for mosaic in transfer.mosaics:
await ask_transfer_mosaic(ctx, common, transfer, mosaic) await _ask_transfer_mosaic(ctx, common, transfer, mosaic)
await _require_confirm_transfer(ctx, transfer.recipient, _get_xem_amount(transfer))
# require_confirm_transfer
await confirm_output(
ctx,
transfer.recipient,
f"Send {format_amount(_get_xem_amount(transfer), NEM_MAX_DIVISIBILITY)} XEM",
ui.BOLD,
"Confirm transfer",
to_str="\nto\n",
)
await require_confirm_final(ctx, common.fee) await require_confirm_final(ctx, common.fee)
async def ask_transfer_mosaic( async def _ask_transfer_mosaic(
ctx: Context, common: NEMTransactionCommon, transfer: NEMTransfer, mosaic: NEMMosaic ctx: Context, common: NEMTransactionCommon, transfer: NEMTransfer, mosaic: NEMMosaic
) -> None: ) -> None:
from trezor.enums import NEMMosaicLevy
from trezor.ui.layouts import confirm_action, confirm_properties
from ..mosaic.helpers import get_mosaic_definition
from ..helpers import NEM_LEVY_PERCENTILE_DIVISOR_ABSOLUTE
if is_nem_xem_mosaic(mosaic): if is_nem_xem_mosaic(mosaic):
return return
@ -56,19 +73,26 @@ async def ask_transfer_mosaic(
await confirm_properties( await confirm_properties(
ctx, ctx,
"confirm_mosaic", "confirm_mosaic",
title="Confirm mosaic", "Confirm mosaic",
props=[ (
( (
"Confirm transfer of", "Confirm transfer of",
format_amount(mosaic_quantity, definition.divisibility) format_amount(mosaic_quantity, definition.divisibility)
+ definition.ticker, + definition.ticker,
), ),
("of", definition.name), ("of", definition.name),
], ),
)
levy = definition.levy # local_cache_attribute
if levy is not None:
if levy == NEMMosaicLevy.MosaicLevy_Absolute:
levy_fee = levy.fee
else:
levy_fee = (
mosaic_quantity * levy.fee // NEM_LEVY_PERCENTILE_DIVISOR_ABSOLUTE
) )
if definition.levy is not None:
levy_fee = _get_levy_fee(definition.levy, mosaic_quantity)
levy_msg = ( levy_msg = (
format_amount(levy_fee, definition.divisibility) + definition.ticker format_amount(levy_fee, definition.divisibility) + definition.ticker
) )
@ -76,19 +100,17 @@ async def ask_transfer_mosaic(
await confirm_properties( await confirm_properties(
ctx, ctx,
"confirm_mosaic_levy", "confirm_mosaic_levy",
title="Confirm mosaic", "Confirm mosaic",
props=[ (("Confirm mosaic\nlevy fee of", levy_msg),),
("Confirm mosaic\nlevy fee of", levy_msg),
],
) )
else: else:
await confirm_action( await confirm_action(
ctx, ctx,
"confirm_mosaic_unknown", "confirm_mosaic_unknown",
title="Confirm mosaic", "Confirm mosaic",
action="Unknown mosaic!", "Unknown mosaic!",
description="Divisibility and levy cannot be shown for unknown mosaics", "Divisibility and levy cannot be shown for unknown mosaics",
icon=ui.ICON_SEND, icon=ui.ICON_SEND,
icon_color=ui.RED, icon_color=ui.RED,
br_code=ButtonRequestType.ConfirmOutput, br_code=ButtonRequestType.ConfirmOutput,
@ -97,11 +119,11 @@ async def ask_transfer_mosaic(
await confirm_properties( await confirm_properties(
ctx, ctx,
"confirm_mosaic_transfer", "confirm_mosaic_transfer",
title="Confirm mosaic", "Confirm mosaic",
props=[ (
("Confirm transfer of", f"{mosaic_quantity} raw units"), ("Confirm transfer of", f"{mosaic_quantity} raw units"),
("of", f"{mosaic.namespace}.{mosaic.mosaic}"), ("of", f"{mosaic.namespace}.{mosaic.mosaic}"),
], ),
) )
@ -117,44 +139,15 @@ def _get_xem_amount(transfer: NEMTransfer) -> int:
return 0 return 0
def _get_levy_fee(levy: MosaicLevy, quantity: int) -> int:
if levy.type == NEMMosaicLevy.MosaicLevy_Absolute:
return levy.fee
else:
return quantity * levy.fee // NEM_LEVY_PERCENTILE_DIVISOR_ABSOLUTE
async def ask_importance_transfer( async def ask_importance_transfer(
ctx: Context, common: NEMTransactionCommon, imp: NEMImportanceTransfer ctx: Context, common: NEMTransactionCommon, imp: NEMImportanceTransfer
) -> None: ) -> None:
from trezor.enums import NEMImportanceTransferMode
from ..layout import require_confirm_text
if imp.mode == NEMImportanceTransferMode.ImportanceTransfer_Activate: if imp.mode == NEMImportanceTransferMode.ImportanceTransfer_Activate:
m = "Activate" m = "Activate"
else: else:
m = "Deactivate" m = "Deactivate"
await require_confirm_text(ctx, m + " remote harvesting?") await require_confirm_text(ctx, m + " remote harvesting?")
await require_confirm_final(ctx, common.fee) await require_confirm_final(ctx, common.fee)
async def _require_confirm_transfer(ctx: Context, recipient: str, value: int) -> None:
await confirm_output(
ctx,
recipient,
amount=f"Send {format_amount(value, NEM_MAX_DIVISIBILITY)} XEM",
font_amount=ui.BOLD,
title="Confirm transfer",
to_str="\nto\n",
)
async def _require_confirm_payload(
ctx: Context, payload: bytes, encrypted: bool = False
) -> None:
await confirm_text(
ctx,
"confirm_payload",
title="Confirm payload",
description="Encrypted:" if encrypted else "Unencrypted:",
data=bytes(payload).decode(),
icon_color=ui.GREEN if encrypted else ui.RED,
br_code=ButtonRequestType.ConfirmOutput,
)

View File

@ -1,19 +1,5 @@
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from trezor.crypto import random
from trezor.messages import (
NEMImportanceTransfer,
NEMMosaic,
NEMTransactionCommon,
NEMTransfer,
)
from ..helpers import (
AES_BLOCK_SIZE,
NEM_SALT_SIZE,
NEM_TRANSACTION_TYPE_IMPORTANCE_TRANSFER,
NEM_TRANSACTION_TYPE_TRANSFER,
)
from ..writers import ( from ..writers import (
serialize_tx_common, serialize_tx_common,
write_bytes_with_len, write_bytes_with_len,
@ -24,6 +10,12 @@ from ..writers import (
if TYPE_CHECKING: if TYPE_CHECKING:
from trezor.crypto import bip32 from trezor.crypto import bip32
from trezor.utils import Writer from trezor.utils import Writer
from trezor.messages import (
NEMImportanceTransfer,
NEMMosaic,
NEMTransactionCommon,
NEMTransfer,
)
def serialize_transfer( def serialize_transfer(
@ -33,11 +25,15 @@ def serialize_transfer(
payload: bytes, payload: bytes,
encrypted: bool, encrypted: bool,
) -> bytearray: ) -> bytearray:
from ..helpers import NEM_TRANSACTION_TYPE_TRANSFER
from ..writers import write_uint32_le
version = common.network << 24 | 2 if transfer.mosaics else common.network << 24 | 1
tx = serialize_tx_common( tx = serialize_tx_common(
common, common,
public_key, public_key,
NEM_TRANSACTION_TYPE_TRANSFER, NEM_TRANSACTION_TYPE_TRANSFER,
_get_version(common.network, transfer.mosaics), version,
) )
write_bytes_with_len(tx, transfer.recipient.encode()) write_bytes_with_len(tx, transfer.recipient.encode())
@ -75,6 +71,8 @@ def serialize_mosaic(w: Writer, namespace: str, mosaic: str, quantity: int) -> N
def serialize_importance_transfer( def serialize_importance_transfer(
common: NEMTransactionCommon, imp: NEMImportanceTransfer, public_key: bytes common: NEMTransactionCommon, imp: NEMImportanceTransfer, public_key: bytes
) -> bytes: ) -> bytes:
from ..helpers import NEM_TRANSACTION_TYPE_IMPORTANCE_TRANSFER
w = serialize_tx_common( w = serialize_tx_common(
common, public_key, NEM_TRANSACTION_TYPE_IMPORTANCE_TRANSFER common, public_key, NEM_TRANSACTION_TYPE_IMPORTANCE_TRANSFER
) )
@ -87,55 +85,45 @@ def serialize_importance_transfer(
def get_transfer_payload( def get_transfer_payload(
transfer: NEMTransfer, node: bip32.HDNode transfer: NEMTransfer, node: bip32.HDNode
) -> tuple[bytes, bool]: ) -> tuple[bytes, bool]:
from trezor.crypto import random
from ..helpers import (
AES_BLOCK_SIZE,
NEM_SALT_SIZE,
)
if transfer.public_key is not None: if transfer.public_key is not None:
if not transfer.payload: if not transfer.payload:
raise ValueError("Public key provided but no payload to encrypt") raise ValueError("Public key provided but no payload to encrypt")
encrypted_payload = _encrypt(node, transfer.public_key, transfer.payload)
# encrypt payload
salt = random.bytes(NEM_SALT_SIZE)
iv = random.bytes(AES_BLOCK_SIZE)
encrypted = node.nem_encrypt(transfer.public_key, iv, salt, transfer.payload)
encrypted_payload = iv + salt + encrypted
return encrypted_payload, True return encrypted_payload, True
else: else:
return transfer.payload or b"", False return transfer.payload or b"", False
def _encrypt(node: bip32.HDNode, public_key: bytes, payload: bytes) -> bytes:
salt = random.bytes(NEM_SALT_SIZE)
iv = random.bytes(AES_BLOCK_SIZE)
encrypted = node.nem_encrypt(public_key, iv, salt, payload)
return iv + salt + encrypted
def _get_version(network: int, mosaics: list[NEMMosaic] | None = None) -> int:
if mosaics:
return network << 24 | 2
return network << 24 | 1
def canonicalize_mosaics(mosaics: list[NEMMosaic]) -> list[NEMMosaic]: def canonicalize_mosaics(mosaics: list[NEMMosaic]) -> list[NEMMosaic]:
if len(mosaics) <= 1: if len(mosaics) <= 1:
return mosaics return mosaics
mosaics = merge_mosaics(mosaics) mosaics = _merge_mosaics(mosaics)
return sort_mosaics(mosaics) return sorted(mosaics, key=lambda m: (m.namespace, m.mosaic))
def are_mosaics_equal(a: NEMMosaic, b: NEMMosaic) -> bool: def _merge_mosaics(mosaics: list[NEMMosaic]) -> list[NEMMosaic]:
if a.namespace == b.namespace and a.mosaic == b.mosaic:
return True
return False
def merge_mosaics(mosaics: list[NEMMosaic]) -> list[NEMMosaic]:
if not mosaics: if not mosaics:
return [] return []
ret: list[NEMMosaic] = [] ret: list[NEMMosaic] = []
for i in mosaics: for i in mosaics:
found = False found = False
for k, y in enumerate(ret): for k, y in enumerate(ret):
if are_mosaics_equal(i, y): # are_mosaics_equal
if i.namespace == y.namespace and i.mosaic == y.mosaic:
ret[k].quantity += i.quantity ret[k].quantity += i.quantity
found = True found = True
if not found: if not found:
ret.append(i) ret.append(i)
return ret return ret
def sort_mosaics(mosaics: list[NEMMosaic]) -> list[NEMMosaic]:
return sorted(mosaics, key=lambda m: (m.namespace, m.mosaic))

View File

@ -1,72 +1,37 @@
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from trezor.crypto import nem
from trezor.enums import NEMModificationType
from trezor.wire import ProcessError from trezor.wire import ProcessError
from .helpers import (
NEM_MAX_DIVISIBILITY,
NEM_MAX_ENCRYPTED_PAYLOAD_SIZE,
NEM_MAX_PLAIN_PAYLOAD_SIZE,
NEM_MAX_SUPPLY,
NEM_NETWORK_MAINNET,
NEM_NETWORK_MIJIN,
NEM_NETWORK_TESTNET,
NEM_PUBLIC_KEY_SIZE,
)
if TYPE_CHECKING: if TYPE_CHECKING:
from trezor.messages import ( from trezor.messages import NEMSignTx, NEMTransactionCommon
NEMAggregateModification,
NEMImportanceTransfer,
NEMMosaicCreation,
NEMMosaicSupplyChange,
NEMProvisionNamespace,
NEMSignTx,
NEMTransactionCommon,
NEMTransfer,
)
def validate(msg: NEMSignTx) -> None: def validate(msg: NEMSignTx) -> None:
_validate_single_tx(msg) from trezor.crypto import nem
_validate_common(msg.transaction) from trezor.enums import NEMModificationType
from trezor.wire import ProcessError # local_cache_global
if msg.multisig: from .helpers import (
_validate_common(msg.multisig, inner=True) NEM_MAX_ENCRYPTED_PAYLOAD_SIZE,
_validate_multisig(msg.multisig, msg.transaction.network) NEM_MAX_PLAIN_PAYLOAD_SIZE,
if not msg.multisig and msg.cosigning: NEM_MAX_DIVISIBILITY,
raise ProcessError("No multisig transaction to cosign") NEM_MAX_SUPPLY,
if msg.transfer:
_validate_transfer(msg.transfer, msg.transaction.network)
if msg.provision_namespace:
_validate_provision_namespace(msg.provision_namespace, msg.transaction.network)
if msg.mosaic_creation:
_validate_mosaic_creation(msg.mosaic_creation, msg.transaction.network)
if msg.supply_change:
_validate_supply_change(msg.supply_change)
if msg.aggregate_modification:
_validate_aggregate_modification(
msg.aggregate_modification, msg.multisig is None
) )
if msg.importance_transfer:
_validate_importance_transfer(msg.importance_transfer)
validate_address = nem.validate_address # local_cache_attribute
transfer = msg.transfer # local_cache_attribute
aggregate_modification = msg.aggregate_modification # local_cache_attribute
multisig = msg.multisig # local_cache_attribute
mosaic_creation = msg.mosaic_creation # local_cache_attribute
network = msg.transaction.network # local_cache_attribute
def validate_network(network: int) -> None: # _validate_single_tx
if network not in (NEM_NETWORK_MAINNET, NEM_NETWORK_TESTNET, NEM_NETWORK_MIJIN):
raise ProcessError("Invalid NEM network")
def _validate_single_tx(msg: NEMSignTx) -> None:
# ensure exactly one transaction is provided # ensure exactly one transaction is provided
tx_count = ( tx_count = (
bool(msg.transfer) bool(transfer)
+ bool(msg.provision_namespace) + bool(msg.provision_namespace)
+ bool(msg.mosaic_creation) + bool(mosaic_creation)
+ bool(msg.supply_change) + bool(msg.supply_change)
+ bool(msg.aggregate_modification) + bool(aggregate_modification)
+ bool(msg.importance_transfer) + bool(msg.importance_transfer)
) )
if tx_count == 0: if tx_count == 0:
@ -74,16 +39,145 @@ def _validate_single_tx(msg: NEMSignTx) -> None:
if tx_count > 1: if tx_count > 1:
raise ProcessError("More than one transaction provided") raise ProcessError("More than one transaction provided")
_validate_common(msg.transaction)
if multisig:
_validate_common(multisig, True)
# _validate_multisig
if multisig.network != network:
raise ProcessError("Inner transaction network is different")
_validate_public_key(
multisig.signer, "Invalid multisig signer public key provided"
)
# END _validate_multisig
if not multisig and msg.cosigning:
raise ProcessError("No multisig transaction to cosign")
if transfer:
payload = transfer.payload # local_cache_attribute
if transfer.public_key is not None:
_validate_public_key(transfer.public_key, "Invalid recipient public key")
if not payload:
raise ProcessError("Public key provided but no payload to encrypt")
if payload:
if len(payload) > NEM_MAX_PLAIN_PAYLOAD_SIZE:
raise ProcessError("Payload too large")
if transfer.public_key and len(payload) > NEM_MAX_ENCRYPTED_PAYLOAD_SIZE:
raise ProcessError("Payload too large")
if not validate_address(transfer.recipient, network):
raise ProcessError("Invalid recipient address")
# END _validate_transfer
if msg.provision_namespace:
# _validate_provision_namespace
if not validate_address(msg.provision_namespace.sink, network):
raise ProcessError("Invalid rental sink address")
# END _validate_provision_namespace
if mosaic_creation:
# _validate_mosaic_creation
if not validate_address(mosaic_creation.sink, network):
raise ProcessError("Invalid creation sink address")
definition = mosaic_creation.definition # local_cache_attribute
supply = definition.supply # local_cache_attribute
divisibility = definition.divisibility # local_cache_attribute
if definition.name is not None:
raise ProcessError("Name not allowed in mosaic creation transactions")
if definition.ticker is not None:
raise ProcessError("Ticker not allowed in mosaic creation transactions")
if definition.networks:
raise ProcessError("Networks not allowed in mosaic creation transactions")
if supply is not None and divisibility is None:
raise ProcessError(
"Definition divisibility needs to be provided when supply is"
)
if supply is None and divisibility is not None:
raise ProcessError(
"Definition supply needs to be provided when divisibility is"
)
if definition.levy is not None:
if definition.fee is None:
raise ProcessError("No levy fee provided")
if definition.levy_address is None:
raise ProcessError("No levy address provided")
if definition.levy_namespace is None:
raise ProcessError("No levy namespace provided")
if definition.levy_mosaic is None:
raise ProcessError("No levy mosaic name provided")
if divisibility is None:
raise ProcessError("No divisibility provided")
if supply is None:
raise ProcessError("No supply provided")
if definition.mutable_supply is None:
raise ProcessError("No supply mutability provided")
if definition.transferable is None:
raise ProcessError("No mosaic transferability provided")
if definition.description is None:
raise ProcessError("No description provided")
if divisibility > NEM_MAX_DIVISIBILITY:
raise ProcessError("Invalid divisibility provided")
if supply > NEM_MAX_SUPPLY:
raise ProcessError("Invalid supply provided")
if not validate_address(definition.levy_address, network):
raise ProcessError("Invalid levy address")
# END _validate_mosaic_creation
if msg.supply_change:
# _validate_supply_change
pass
# END _validate_supply_change
if aggregate_modification:
# _validate_aggregate_modification
creation = multisig is None
if creation and not aggregate_modification.modifications:
raise ProcessError("No modifications provided")
for m in aggregate_modification.modifications:
if (
creation
and m.type == NEMModificationType.CosignatoryModification_Delete
):
raise ProcessError("Cannot remove cosignatory when converting account")
_validate_public_key(
m.public_key, "Invalid cosignatory public key provided"
)
# END _validate_aggregate_modification
if msg.importance_transfer:
# _validate_importance_transfer
_validate_public_key(
msg.importance_transfer.public_key,
"Invalid remote account public key provided",
)
# END _validate_importance_transfer
def validate_network(network: int) -> None:
from .helpers import (
NEM_NETWORK_MAINNET,
NEM_NETWORK_MIJIN,
NEM_NETWORK_TESTNET,
)
if network not in (NEM_NETWORK_MAINNET, NEM_NETWORK_TESTNET, NEM_NETWORK_MIJIN):
raise ProcessError("Invalid NEM network")
def _validate_common(common: NEMTransactionCommon, inner: bool = False) -> None: def _validate_common(common: NEMTransactionCommon, inner: bool = False) -> None:
validate_network(common.network) validate_network(common.network)
signer = common.signer # local_cache_attribute
err = None err = None
if not inner and common.signer: if not inner and signer:
raise ProcessError("Signer not allowed in outer transaction") raise ProcessError("Signer not allowed in outer transaction")
if inner and common.signer is None: if inner and signer is None:
err = "signer" err = "signer"
if err: if err:
@ -92,125 +186,14 @@ def _validate_common(common: NEMTransactionCommon, inner: bool = False) -> None:
else: else:
raise ProcessError(f"No {err} provided") raise ProcessError(f"No {err} provided")
if common.signer is not None: if signer is not None:
_validate_public_key( _validate_public_key(signer, "Invalid signer public key in inner transaction")
common.signer, "Invalid signer public key in inner transaction"
)
def _validate_public_key(public_key: bytes | None, err_msg: str) -> None: def _validate_public_key(public_key: bytes | None, err_msg: str) -> None:
from .helpers import NEM_PUBLIC_KEY_SIZE
if not public_key: if not public_key:
raise ProcessError(f"{err_msg} (none provided)") raise ProcessError(f"{err_msg} (none provided)")
if len(public_key) != NEM_PUBLIC_KEY_SIZE: if len(public_key) != NEM_PUBLIC_KEY_SIZE:
raise ProcessError(f"{err_msg} (invalid length)") raise ProcessError(f"{err_msg} (invalid length)")
def _validate_importance_transfer(importance_transfer: NEMImportanceTransfer) -> None:
_validate_public_key(
importance_transfer.public_key, "Invalid remote account public key provided"
)
def _validate_multisig(multisig: NEMTransactionCommon, network: int) -> None:
if multisig.network != network:
raise ProcessError("Inner transaction network is different")
_validate_public_key(multisig.signer, "Invalid multisig signer public key provided")
def _validate_aggregate_modification(
aggregate_modification: NEMAggregateModification, creation: bool = False
) -> None:
if creation and not aggregate_modification.modifications:
raise ProcessError("No modifications provided")
for m in aggregate_modification.modifications:
if creation and m.type == NEMModificationType.CosignatoryModification_Delete:
raise ProcessError("Cannot remove cosignatory when converting account")
_validate_public_key(m.public_key, "Invalid cosignatory public key provided")
def _validate_supply_change(supply_change: NEMMosaicSupplyChange) -> None:
pass
def _validate_mosaic_creation(mosaic_creation: NEMMosaicCreation, network: int) -> None:
if not nem.validate_address(mosaic_creation.sink, network):
raise ProcessError("Invalid creation sink address")
if mosaic_creation.definition.name is not None:
raise ProcessError("Name not allowed in mosaic creation transactions")
if mosaic_creation.definition.ticker is not None:
raise ProcessError("Ticker not allowed in mosaic creation transactions")
if mosaic_creation.definition.networks:
raise ProcessError("Networks not allowed in mosaic creation transactions")
if (
mosaic_creation.definition.supply is not None
and mosaic_creation.definition.divisibility is None
):
raise ProcessError(
"Definition divisibility needs to be provided when supply is"
)
if (
mosaic_creation.definition.supply is None
and mosaic_creation.definition.divisibility is not None
):
raise ProcessError(
"Definition supply needs to be provided when divisibility is"
)
if mosaic_creation.definition.levy is not None:
if mosaic_creation.definition.fee is None:
raise ProcessError("No levy fee provided")
if mosaic_creation.definition.levy_address is None:
raise ProcessError("No levy address provided")
if mosaic_creation.definition.levy_namespace is None:
raise ProcessError("No levy namespace provided")
if mosaic_creation.definition.levy_mosaic is None:
raise ProcessError("No levy mosaic name provided")
if mosaic_creation.definition.divisibility is None:
raise ProcessError("No divisibility provided")
if mosaic_creation.definition.supply is None:
raise ProcessError("No supply provided")
if mosaic_creation.definition.mutable_supply is None:
raise ProcessError("No supply mutability provided")
if mosaic_creation.definition.transferable is None:
raise ProcessError("No mosaic transferability provided")
if mosaic_creation.definition.description is None:
raise ProcessError("No description provided")
if mosaic_creation.definition.divisibility > NEM_MAX_DIVISIBILITY:
raise ProcessError("Invalid divisibility provided")
if mosaic_creation.definition.supply > NEM_MAX_SUPPLY:
raise ProcessError("Invalid supply provided")
if not nem.validate_address(mosaic_creation.definition.levy_address, network):
raise ProcessError("Invalid levy address")
def _validate_provision_namespace(
provision_namespace: NEMProvisionNamespace, network: int
) -> None:
if not nem.validate_address(provision_namespace.sink, network):
raise ProcessError("Invalid rental sink address")
def _validate_transfer(transfer: NEMTransfer, network: int) -> None:
if transfer.public_key is not None:
_validate_public_key(transfer.public_key, "Invalid recipient public key")
if not transfer.payload:
raise ProcessError("Public key provided but no payload to encrypt")
if transfer.payload:
if len(transfer.payload) > NEM_MAX_PLAIN_PAYLOAD_SIZE:
raise ProcessError("Payload too large")
if (
transfer.public_key
and len(transfer.payload) > NEM_MAX_ENCRYPTED_PAYLOAD_SIZE
):
raise ProcessError("Payload too large")
if not nem.validate_address(transfer.recipient, network):
raise ProcessError("Invalid recipient address")

View File

@ -5,6 +5,7 @@ if not utils.BITCOIN_ONLY:
from apps.nem.mosaic.helpers import get_mosaic_definition from apps.nem.mosaic.helpers import get_mosaic_definition
from apps.nem.transfer import * from apps.nem.transfer import *
from apps.nem.transfer.serialize import * from apps.nem.transfer.serialize import *
from apps.nem.transfer.serialize import _merge_mosaics
def get_mosaic(namespace: str, quantity: int, mosaic: str) -> NEMMosaic: def get_mosaic(namespace: str, quantity: int, mosaic: str) -> NEMMosaic:
@ -15,6 +16,11 @@ def get_mosaic(namespace: str, quantity: int, mosaic: str) -> NEMMosaic:
) )
# NOTE: copy-pasted from apps.nem.transfer.serialize.py
def sort_mosaics(mosaics: list[NEMMosaic]) -> list[NEMMosaic]:
return sorted(mosaics, key=lambda m: (m.namespace, m.mosaic))
@unittest.skipUnless(not utils.BITCOIN_ONLY, "altcoin") @unittest.skipUnless(not utils.BITCOIN_ONLY, "altcoin")
class TestNemMosaic(unittest.TestCase): class TestNemMosaic(unittest.TestCase):
@ -54,20 +60,20 @@ class TestNemMosaic(unittest.TestCase):
b = get_mosaic("abc", 1, "mosaic") b = get_mosaic("abc", 1, "mosaic")
c = get_mosaic("abc", 2, "xxx") c = get_mosaic("abc", 2, "xxx")
merged = merge_mosaics([a, b]) merged = _merge_mosaics([a, b])
self.assertEqual(merged[0].quantity, 2) self.assertEqual(merged[0].quantity, 2)
self.assertEqual(len(merged), 1) self.assertEqual(len(merged), 1)
a.quantity = 1 a.quantity = 1
b.quantity = 10 b.quantity = 10
merged = merge_mosaics([a, b]) merged = _merge_mosaics([a, b])
self.assertEqual(merged[0].quantity, 11) self.assertEqual(merged[0].quantity, 11)
a.namespace = "abcdef" a.namespace = "abcdef"
merged = merge_mosaics([a, b]) merged = _merge_mosaics([a, b])
self.assertEqual(len(merged), 2) self.assertEqual(len(merged), 2)
merged = merge_mosaics([a, b, c]) merged = _merge_mosaics([a, b, c])
self.assertEqual(len(merged), 3) self.assertEqual(len(merged), 3)
a.namespace = "abcdef" a.namespace = "abcdef"
@ -79,7 +85,7 @@ class TestNemMosaic(unittest.TestCase):
c.namespace = "abc" c.namespace = "abc"
c.mosaic = "mosaic" c.mosaic = "mosaic"
c.quantity = 3 c.quantity = 3
merged = merge_mosaics([a, b, c]) merged = _merge_mosaics([a, b, c])
self.assertEqual(merged[0].quantity, 1) self.assertEqual(merged[0].quantity, 1)
self.assertEqual(merged[1].quantity, 5) self.assertEqual(merged[1].quantity, 5)
self.assertEqual(len(merged), 2) self.assertEqual(len(merged), 2)
@ -93,7 +99,7 @@ class TestNemMosaic(unittest.TestCase):
c.namespace = "abc" c.namespace = "abc"
c.mosaic = "mosaic" c.mosaic = "mosaic"
c.quantity = 3 c.quantity = 3
merged = merge_mosaics([a, b, c]) merged = _merge_mosaics([a, b, c])
self.assertEqual(merged[0].quantity, 6) self.assertEqual(merged[0].quantity, 6)
self.assertEqual(len(merged), 1) self.assertEqual(len(merged), 1)

View File

@ -7,8 +7,7 @@ if not utils.BITCOIN_ONLY:
from apps.nem.mosaic import * from apps.nem.mosaic import *
from apps.nem.transfer import * from apps.nem.transfer import *
from apps.nem.transfer.serialize import * from apps.nem.transfer.serialize import *
from trezor.messages import NEMTransfer from trezor.messages import NEMTransfer, NEMTransactionCommon, NEMSignTx, NEMMosaic
from trezor.messages import NEMSignTx
@unittest.skipUnless(not utils.BITCOIN_ONLY, "altcoin") @unittest.skipUnless(not utils.BITCOIN_ONLY, "altcoin")