1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2024-11-17 21:22:10 +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
from apps.common import paths if TYPE_CHECKING:
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 trezor.crypto.curve import ed25519
from trezor.messages import NEMSignedTx
from apps.common import seed
from apps.common.keychain import with_slip44_keychain from apps.common.keychain import with_slip44_keychain
from apps.common.paths import validate_path
from . import CURVE, PATTERNS, SLIP44_ID, mosaic, multisig, namespace, transfer from . import CURVE, PATTERNS, SLIP44_ID
from .helpers import NEM_HASH_ALG, check_path
from .validators import validate
if TYPE_CHECKING: if TYPE_CHECKING:
from trezor.messages import NEMSignTx from trezor.messages import NEMSignTx, NEMSignedTx
from apps.common.keychain import Keychain from apps.common.keychain import Keychain
from trezor.wire import Context
@with_slip44_keychain(*PATTERNS, slip44_id=SLIP44_ID, curve=CURVE) @with_slip44_keychain(*PATTERNS, slip44_id=SLIP44_ID, curve=CURVE)
async def sign_tx(ctx: wire.Context, msg: NEMSignTx, keychain: Keychain) -> NEMSignedTx: async def sign_tx(ctx: Context, msg: NEMSignTx, keychain: Keychain) -> NEMSignedTx:
from trezor.wire import DataError
from trezor.crypto.curve import ed25519
from trezor.messages import NEMSignedTx
from apps.common import seed
from apps.common.paths import validate_path
from . import mosaic, multisig, namespace, transfer
from .helpers import NEM_HASH_ALG, check_path
from .validators import validate
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
from .helpers import (
NEM_MAX_ENCRYPTED_PAYLOAD_SIZE,
NEM_MAX_PLAIN_PAYLOAD_SIZE,
NEM_MAX_DIVISIBILITY,
NEM_MAX_SUPPLY,
)
if msg.multisig: validate_address = nem.validate_address # local_cache_attribute
_validate_common(msg.multisig, inner=True) transfer = msg.transfer # local_cache_attribute
_validate_multisig(msg.multisig, msg.transaction.network) aggregate_modification = msg.aggregate_modification # local_cache_attribute
if not msg.multisig and msg.cosigning: multisig = msg.multisig # local_cache_attribute
raise ProcessError("No multisig transaction to cosign") mosaic_creation = msg.mosaic_creation # local_cache_attribute
network = msg.transaction.network # local_cache_attribute
if msg.transfer: # _validate_single_tx
_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)
def validate_network(network: int) -> None:
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")