1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2025-03-08 11:16:09 +00:00

Merge pull request #263 from trezor/style

Automatic code style enforcement
This commit is contained in:
Jan Pochyla 2018-07-10 13:19:27 +02:00 committed by GitHub
commit 9122587825
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
127 changed files with 6763 additions and 2568 deletions

16
.flake8
View File

@ -1,16 +0,0 @@
[flake8]
ignore =
# E221: multiple spaces before operator
E221,
# E241: multiple spaces after ':'
E241,
# E402: module level import not at top of file
E402,
# E501: line too long
E501,
# E741 ambiguous variable name
E741,
# F403: star import used, unable to detect undefined names
F403,
# F405: name may be undefined, or defined from star imports
F405

View File

@ -23,16 +23,20 @@ matrix:
addons:
apt:
sources:
- deadsnakes
packages:
- build-essential
- gcc-multilib
- libusb-1.0-0-dev
- libudev-dev
- python3
- python3-pip
- python3.6
- python3.6-dev
- python3.6-venv
before_install:
- pip3 install --user pipenv
- python3.6 -m ensurepip --user
- python3.6 -m pip install --user pipenv
install:
- curl -LO "https://github.com/google/protobuf/releases/download/v${PROTOBUF_VERSION}/protoc-${PROTOBUF_VERSION}-linux-x86_64.zip"
@ -46,6 +50,8 @@ before_script:
- test "$GOAL" != "stm32" || export PATH=$PWD/$TOOLCHAIN_LONGVER/bin:$PATH
script:
- test "$GOAL" != "src" || pipenv run make style
- test "$GOAL" != "stm32" || pipenv run make build_cross
- test "$GOAL" != "stm32" || pipenv run make build_boardloader
- test "$GOAL" != "stm32" || pipenv run make build_bootloader
@ -57,8 +63,6 @@ script:
- test "$GOAL" != "unix" || pipenv run make test
- test "$GOAL" != "unix" || pipenv run make test_emu
# - test "$GOAL" != "src" || make style
notifications:
webhooks:
urls:

View File

@ -71,12 +71,18 @@ test_emu: ## run selected device tests from python-trezor
cd tests ; ./run_tests_device_emu.sh $(TESTOPTS)
pylint: ## run pylint on application sources and tests
pylint -E $(shell find src -name *.py)
pylint -E $(shell find tests -name *.py)
pylint -E $(shell find src tests -name *.py)
style: ## run code style check on application sources and tests
flake8 $(shell find src -name *.py)
flake8 $(shell find tests -name *.py)
isort --check-only $(shell find src -name *.py ! -path 'src/trezor/messages/*')
black --check $(shell find src -name *.py ! -path 'src/trezor/messages/*')
isort:
isort $(shell find src -name *.py ! -path 'src/trezor/messages/*')
black:
black $(shell find src -name *.py ! -path 'src/trezor/messages/*')
## build commands:

View File

@ -5,6 +5,12 @@ verify_ssl = true
[packages]
trezor = {git = "https://github.com/trezor/python-trezor", editable = true, ref = "master"}
protobuf = "==3.4.0"
pytest = "*"
mock = "*"
protobuf = "==3.4.0"
isort = "*"
flake8 = "*"
black = "*"
[pipenv]
allow_prereleases = true

69
Pipfile.lock generated
View File

@ -1,7 +1,7 @@
{
"_meta": {
"hash": {
"sha256": "b4eae29eaeaf82e3804fd1c8bf192feeadd4c2c6e6de3f647e999c0c915ada66"
"sha256": "5f20a0fc83eb2b29a6c724b4792d3e648eb3284ec15679aac24bca993f5af2cf"
},
"pipfile-spec": 6,
"requires": {},
@ -14,6 +14,13 @@
]
},
"default": {
"appdirs": {
"hashes": [
"sha256:9e5896d1372858f8dd3344faf4e5014d21849c756c8d5701f78f8a103b372d92",
"sha256:d8b24664561d0d34ddfaec54636d502d7cea6e29c3eaf68f3df6180863e2166e"
],
"version": "==1.4.3"
},
"atomicwrites": {
"hashes": [
"sha256:240831ea22da9ab882b551b31d4225591e5e447a68c5e188db5b89ca1d487585",
@ -28,6 +35,14 @@
],
"version": "==18.1.0"
},
"black": {
"hashes": [
"sha256:22158b89c1a6b4eb333a1e65e791a3f8b998cf3b11ae094adb2570f31f769a44",
"sha256:4b475bbd528acce094c503a3d2dbc2d05a4075f6d0ef7d9e7514518e14cc5191"
],
"index": "pypi",
"version": "==18.6b4"
},
"certifi": {
"hashes": [
"sha256:13e698f54293db9f89122b0581843a782ad0934a4fe0172d2a980ba77fc61bb7",
@ -56,6 +71,14 @@
],
"version": "==0.13"
},
"flake8": {
"hashes": [
"sha256:7253265f7abd8b313e3892944044a365e3f4ac3fcdcfb4298f55ee9ddf188ba0",
"sha256:c7841163e2b576d435799169b78703ad6ac1bbb0f199994fc05f700b2a90ea37"
],
"index": "pypi",
"version": "==3.5.0"
},
"idna": {
"hashes": [
"sha256:156a6814fb5ac1fc6850fb002e0852d56c0c8d2531923a51032d1b70760e186e",
@ -63,12 +86,28 @@
],
"version": "==2.7"
},
"isort": {
"hashes": [
"sha256:1153601da39a25b14ddc54955dbbacbb6b2d19135386699e2ad58517953b34af",
"sha256:b9c40e9750f3d77e6e4d441d8b0266cf555e7cdabdcff33c4fd06366ca761ef8",
"sha256:ec9ef8f4a9bc6f71eec99e1806bfa2de401650d996c59330782b89a5555c1497"
],
"index": "pypi",
"version": "==4.3.4"
},
"libusb1": {
"hashes": [
"sha256:8c930d9c1d037d9c83924c82608aa6a1adcaa01ca0e4a23ee0e8e18d7eee670d"
],
"version": "==1.6.4"
},
"mccabe": {
"hashes": [
"sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42",
"sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"
],
"version": "==0.6.1"
},
"mnemonic": {
"hashes": [
"sha256:02a7306a792370f4a0c106c2cf1ce5a0c84b9dbd7e71c6792fdb9ad88a727f1d"
@ -147,6 +186,20 @@
],
"version": "==1.1.2"
},
"pycodestyle": {
"hashes": [
"sha256:682256a5b318149ca0d2a9185d365d8864a768a28db66a84a2ea946bcc426766",
"sha256:6c4245ade1edfad79c3446fadfc96b0de2759662dc29d07d80a6f27ad1ca6ba9"
],
"version": "==2.3.1"
},
"pyflakes": {
"hashes": [
"sha256:08bd6a50edf8cffa9fa09a463063c425ecaaf10d1eb0335a7e8b1401aef89e6f",
"sha256:8d616a382f243dbf19b54743f280b80198be0bca3a5396f1d2e1fca6223e8805"
],
"version": "==1.6.0"
},
"pytest": {
"hashes": [
"sha256:0453c8676c2bee6feb0434748b068d5510273a916295fd61d306c4f22fbfd752",
@ -169,19 +222,17 @@
],
"version": "==1.11.0"
},
"toml": {
"hashes": [
"sha256:8e86bd6ce8cc11b9620cb637466453d94f5d57ad86f17e98a98d1f73e3baab2d"
],
"version": "==0.9.4"
},
"trezor": {
"editable": true,
"git": "https://github.com/trezor/python-trezor",
"ref": "master"
},
"typing": {
"hashes": [
"sha256:3a887b021a77b292e151afb75323dea88a7bc1b3dfa92176cff8e44c8b68bddf",
"sha256:b2c689d54e1144bbcfd191b0832980a21c2dbcf7b5ff7a66248a60c90e951eb8",
"sha256:d400a9344254803a2368533e4533a4200d21eb7b6b729c173bc38201a74db3f2"
],
"version": "==3.6.4"
},
"urllib3": {
"hashes": [
"sha256:a68ac5e15e76e7e5dd2b8f94007233e01effe3e50e8daddf69acfd81cb686baf",

View File

@ -1,4 +0,0 @@
[pytest]
addopts = --pyargs trezorlib.tests.device_tests
xfail_strict = true
run_xfail = lisk

35
setup.cfg Normal file
View File

@ -0,0 +1,35 @@
[flake8]
ignore =
# E203 whitespace before ':'
E203,
# E221: multiple spaces before operator
E221,
# E241: multiple spaces after ':'
E241,
# E402: module level import not at top of file
E402,
# E501: line too long
E501,
# E741 ambiguous variable name
E741,
# F403: star import used, unable to detect undefined names
F403,
# F405: name may be undefined, or defined from star imports
F405,
# W503: line break before binary operator
W503
[isort]
multi_line_output = 3
include_trailing_comma = True
force_grid_wrap = 0
combine_as_imports = True
line_length = 88
not_skip=__init__.py
forced_separate = apps
known_standard_library = micropython,ubinascii,ustruct,uctypes,utime,utimeq,trezorio,trezorui,trezorutils,trezorconfig
[tool:pytest]
addopts = --pyargs trezorlib.tests.device_tests
xfail_strict = true
run_xfail =

View File

@ -9,42 +9,29 @@ def length(address_type):
return 4
def addrtype_bytes(address_type: int):
if address_type <= 0xFF:
return bytes([address_type])
if address_type <= 0xFFFF:
return bytes([(address_type >> 8), (address_type & 0xFF)])
if address_type <= 0xFFFFFF:
return bytes([(address_type >> 16), (address_type >> 8), (address_type & 0xFF)])
# else
return bytes([(address_type >> 24), (address_type >> 16), (address_type >> 8), (address_type & 0xFF)])
def tobytes(address_type: int):
return address_type.to_bytes(length(address_type), "big")
def check(address_type, raw_address):
if address_type <= 0xFF:
return address_type == raw_address[0]
if address_type <= 0xFFFF:
return address_type == (raw_address[0] << 8) | raw_address[1]
if address_type <= 0xFFFFFF:
return address_type == (raw_address[0] << 16) | (raw_address[1] << 8) | raw_address[2]
# else
return address_type == (raw_address[0] << 24) | (raw_address[1] << 16) | (raw_address[2] << 8) | raw_address[3]
return raw_address.startswith(tobytes(address_type))
def strip(address_type, raw_address):
if not check(address_type, raw_address):
raise ValueError('Invalid address')
l = length(address_type)
return raw_address[l:]
raise ValueError("Invalid address")
return raw_address[length(address_type) :]
def split(coin, raw_address):
for f in ('address_type',
'address_type_p2sh',
'address_type_p2wpkh',
'address_type_p2wsh'):
for f in (
"address_type",
"address_type_p2sh",
"address_type_p2wpkh",
"address_type_p2wsh",
):
at = getattr(coin, f)
if at is not None and check(at, raw_address):
l = length(at)
return raw_address[:l], raw_address[l:]
raise ValueError('Invalid address')
raise ValueError("Invalid address")

View File

@ -1,4 +1,5 @@
from trezor.crypto import hashlib, hmac, random
from apps.common import storage
_cached_seed = None
@ -52,6 +53,6 @@ def set_passphrase(passphrase):
def clear(skip_passphrase: bool = False):
set_seed(None)
if skip_passphrase:
set_passphrase('')
set_passphrase("")
else:
set_passphrase(None)

View File

@ -1,7 +1,7 @@
from trezor.crypto.base58 import groestl512d_32, sha256d_32
class CoinInfo:
class CoinInfo:
def __init__(
self,
coin_name: str,
@ -37,7 +37,7 @@ class CoinInfo:
self.version_group_id = version_group_id
self.bip115 = bip115
self.curve_name = curve_name
if curve_name == 'secp256k1-groestl':
if curve_name == "secp256k1-groestl":
self.b58_hash = groestl512d_32
self.sign_hash_double = False
else:
@ -45,8 +45,9 @@ class CoinInfo:
self.sign_hash_double = True
# the following list is generated using tools/codegen/gen_coins.py
# generated using tools/codegen/gen_coins.py
# do not edit manually!
# fmt: off
COINS = [
CoinInfo(
coin_name='Bitcoin',

View File

@ -19,11 +19,11 @@ def by_address_type(address_type):
for c in COINS:
if c.address_type == address_type:
return c
raise ValueError('Unknown coin address type %d' % address_type)
raise ValueError("Unknown coin address type %d" % address_type)
def by_slip44(slip44):
for c in COINS:
if c.slip44 == slip44:
return c
raise ValueError('Unknown coin slip44 index %d' % slip44)
raise ValueError("Unknown coin slip44 index %d" % slip44)

View File

@ -21,7 +21,7 @@ async def hold_to_confirm(ctx, content, code=None, *args, **kwargs):
code = ButtonRequestType.Other
await ctx.call(ButtonRequest(code=code), MessageType.ButtonAck)
dialog = HoldToConfirmDialog(content, 'Hold to confirm', *args, **kwargs)
dialog = HoldToConfirmDialog(content, "Hold to confirm", *args, **kwargs)
return await ctx.wait(dialog) == CONFIRMED
@ -29,10 +29,10 @@ async def hold_to_confirm(ctx, content, code=None, *args, **kwargs):
async def require_confirm(*args, **kwargs):
confirmed = await confirm(*args, **kwargs)
if not confirmed:
raise wire.ActionCancelled('Cancelled')
raise wire.ActionCancelled("Cancelled")
async def require_hold_to_confirm(*args, **kwargs):
confirmed = await hold_to_confirm(*args, **kwargs)
if not confirmed:
raise wire.ActionCancelled('Cancelled')
raise wire.ActionCancelled("Cancelled")

View File

@ -1,5 +1,5 @@
from micropython import const
from apps.common.confirm import confirm
from trezor import ui
from trezor.messages import ButtonRequestType
from trezor.ui.container import Container
@ -7,17 +7,16 @@ from trezor.ui.qr import Qr
from trezor.ui.text import Text
from trezor.utils import chunks
from apps.common.confirm import confirm
async def show_address(ctx, address: str):
lines = split_address(address)
text = Text('Confirm address', ui.ICON_RECEIVE, icon_color=ui.GREEN)
text = Text("Confirm address", ui.ICON_RECEIVE, icon_color=ui.GREEN)
text.mono(*lines)
return await confirm(
ctx,
text,
code=ButtonRequestType.Address,
cancel='QR',
cancel_style=ui.BTN_KEY)
ctx, text, code=ButtonRequestType.Address, cancel="QR", cancel_style=ui.BTN_KEY
)
async def show_qr(ctx, address: str):
@ -26,14 +25,15 @@ async def show_qr(ctx, address: str):
qr_coef = const(4)
qr = Qr(address, (qr_x, qr_y), qr_coef)
text = Text('Confirm address', ui.ICON_RECEIVE, icon_color=ui.GREEN)
text = Text("Confirm address", ui.ICON_RECEIVE, icon_color=ui.GREEN)
content = Container(qr, text)
return await confirm(
ctx,
content,
code=ButtonRequestType.Address,
cancel='Address',
cancel_style=ui.BTN_KEY)
cancel="Address",
cancel_style=ui.BTN_KEY,
)
def split_address(address: str):

View File

@ -1,28 +1,29 @@
from trezor import ui, wire
from trezor.messages import ButtonRequestType, MessageType
from trezor.messages import ButtonRequestType, MessageType, PassphraseSourceType
from trezor.messages.ButtonRequest import ButtonRequest
from trezor.messages import PassphraseSourceType
from trezor.messages.PassphraseRequest import PassphraseRequest
from trezor.messages.PassphraseStateRequest import PassphraseStateRequest
from trezor.ui.entry_select import DEVICE, EntrySelector
from trezor.ui.passphrase import CANCELLED, PassphraseKeyboard
from trezor.ui.text import Text
from apps.common import storage
from apps.common.cache import get_state
@ui.layout
async def request_passphrase_entry(ctx):
text = Text('Enter passphrase', ui.ICON_CONFIG)
text.normal('Where to enter your', 'passphrase?')
text = Text("Enter passphrase", ui.ICON_CONFIG)
text.normal("Where to enter your", "passphrase?")
text.render()
ack = await ctx.call(
ButtonRequest(code=ButtonRequestType.PassphraseType),
MessageType.ButtonAck,
MessageType.Cancel)
MessageType.Cancel,
)
if ack.MESSAGE_WIRE_TYPE == MessageType.Cancel:
raise wire.ActionCancelled('Passphrase cancelled')
raise wire.ActionCancelled("Passphrase cancelled")
selector = EntrySelector(text)
return await ctx.wait(selector)
@ -31,28 +32,30 @@ async def request_passphrase_entry(ctx):
@ui.layout
async def request_passphrase_ack(ctx, on_device):
if not on_device:
text = Text('Passphrase entry', ui.ICON_CONFIG)
text.normal('Please, type passphrase', 'on connected host.')
text = Text("Passphrase entry", ui.ICON_CONFIG)
text.normal("Please, type passphrase", "on connected host.")
text.render()
req = PassphraseRequest(on_device=on_device)
ack = await ctx.call(req, MessageType.PassphraseAck, MessageType.Cancel)
if ack.MESSAGE_WIRE_TYPE == MessageType.Cancel:
raise wire.ActionCancelled('Passphrase cancelled')
raise wire.ActionCancelled("Passphrase cancelled")
if on_device:
if ack.passphrase is not None:
raise wire.ProcessError('Passphrase provided when it should not be')
keyboard = PassphraseKeyboard('Enter passphrase')
raise wire.ProcessError("Passphrase provided when it should not be")
keyboard = PassphraseKeyboard("Enter passphrase")
passphrase = await ctx.wait(keyboard)
if passphrase == CANCELLED:
raise wire.ActionCancelled('Passphrase cancelled')
raise wire.ActionCancelled("Passphrase cancelled")
else:
if ack.passphrase is None:
raise wire.ProcessError('Passphrase not provided')
raise wire.ProcessError("Passphrase not provided")
passphrase = ack.passphrase
req = PassphraseStateRequest(state=get_state(prev_state=ack.state, passphrase=passphrase))
req = PassphraseStateRequest(
state=get_state(prev_state=ack.state, passphrase=passphrase)
)
ack = await ctx.call(req, MessageType.PassphraseStateAck, MessageType.Cancel)
return passphrase
@ -71,4 +74,4 @@ async def protect_by_passphrase(ctx):
if storage.has_passphrase():
return await request_passphrase(ctx)
else:
return ''
return ""

View File

@ -11,32 +11,31 @@ class PinCancelled(Exception):
@ui.layout
async def request_pin(label=None, cancellable: bool=True) -> str:
async def request_pin(label=None, cancellable: bool = True) -> str:
def onchange():
c = dialog.cancel
if matrix.pin:
back = res.load(ui.ICON_BACK)
if c.content is not back:
c.normal_style = ui.BTN_CLEAR['normal']
c.normal_style = ui.BTN_CLEAR["normal"]
c.content = back
c.enable()
c.taint()
else:
lock = res.load(ui.ICON_LOCK)
if not cancellable and c.content:
c.content = ''
c.content = ""
c.disable()
c.taint()
elif c.content is not lock:
c.normal_style = ui.BTN_CANCEL['normal']
c.normal_style = ui.BTN_CANCEL["normal"]
c.content = lock
c.enable()
c.taint()
c.render()
if label is None:
label = 'Enter your PIN'
label = "Enter your PIN"
matrix = PinMatrix(label)
matrix.onchange = onchange
dialog = ConfirmDialog(matrix)
@ -54,7 +53,7 @@ async def request_pin(label=None, cancellable: bool=True) -> str:
if result == CONFIRMED:
return matrix.pin
elif matrix.pin: # reset
matrix.change('')
matrix.change("")
continue
else: # cancel
raise PinCancelled()

View File

@ -1,12 +1,15 @@
from trezor import wire
from trezor.crypto import bip32, bip39
from apps.common import cache, storage
from apps.common.request_passphrase import protect_by_passphrase
_DEFAULT_CURVE = 'secp256k1'
_DEFAULT_CURVE = "secp256k1"
async def derive_node(ctx: wire.Context, path: list, curve_name: str = _DEFAULT_CURVE) -> bip32.HDNode:
async def derive_node(
ctx: wire.Context, path: list, curve_name: str = _DEFAULT_CURVE
) -> bip32.HDNode:
seed = await _get_cached_seed(ctx)
node = bip32.from_seed(seed, curve_name)
node.derive_path(path)
@ -15,7 +18,7 @@ async def derive_node(ctx: wire.Context, path: list, curve_name: str = _DEFAULT_
async def _get_cached_seed(ctx: wire.Context) -> bytes:
if not storage.is_initialized():
raise wire.ProcessError('Device is not initialized')
raise wire.ProcessError("Device is not initialized")
if cache.get_seed() is None:
passphrase = await _get_cached_passphrase(ctx)
seed = bip39.seed(storage.get_mnemonic(), passphrase)
@ -30,11 +33,13 @@ async def _get_cached_passphrase(ctx: wire.Context) -> str:
return cache.get_passphrase()
def derive_node_without_passphrase(path: list, curve_name: str = _DEFAULT_CURVE) -> bip32.HDNode:
def derive_node_without_passphrase(
path: list, curve_name: str = _DEFAULT_CURVE
) -> bip32.HDNode:
if not storage.is_initialized():
raise Exception('Device is not initialized')
raise Exception("Device is not initialized")
seed = bip39.seed(storage.get_mnemonic(), '')
seed = bip39.seed(storage.get_mnemonic(), "")
node = bip32.from_seed(seed, curve_name)
node.derive_path(path)
return node

View File

@ -1,8 +1,10 @@
from ubinascii import hexlify
from trezor import ui
from trezor.crypto.hashlib import sha256
from trezor.ui.text import TEXT_MARGIN_LEFT
from trezor.utils import HashWriter, chunks, split_words
from ubinascii import hexlify
from apps.wallet.sign_tx.signing import write_varint
@ -19,7 +21,6 @@ def message_digest(coin, message):
def split_message(message):
def measure(s):
return ui.display.text_width(s, ui.NORMAL)

View File

@ -1,13 +1,16 @@
from micropython import const
from ubinascii import hexlify
from trezor import config
from trezor.crypto import random
from apps.common import cache
HOMESCREEN_MAXSIZE = 16384
_STORAGE_VERSION = b'\x01'
_STORAGE_VERSION = b"\x01"
# fmt: off
_APP = const(0x01) # app namespace
_DEVICE_ID = const(0x00) # bytes
_VERSION = const(0x01) # int
@ -22,6 +25,7 @@ _U2F_COUNTER = const(0x09) # int
_PASSPHRASE_SOURCE = const(0x0A) # int
_UNFINISHED_BACKUP = const(0x0B) # bool (0x01 or empty)
_AUTOLOCK_DELAY_MS = const(0x0C) # int
# fmt: on
def _new_device_id() -> str:
@ -60,9 +64,9 @@ def load_mnemonic(mnemonic: str, needs_backup: bool) -> None:
config.set(_APP, _MNEMONIC, mnemonic.encode())
config.set(_APP, _VERSION, _STORAGE_VERSION)
if needs_backup:
config.set(_APP, _NEEDS_BACKUP, b'\x01')
config.set(_APP, _NEEDS_BACKUP, b"\x01")
else:
config.set(_APP, _NEEDS_BACKUP, b'')
config.set(_APP, _NEEDS_BACKUP, b"")
def needs_backup() -> bool:
@ -70,7 +74,7 @@ def needs_backup() -> bool:
def set_backed_up() -> None:
config.set(_APP, _NEEDS_BACKUP, b'')
config.set(_APP, _NEEDS_BACKUP, b"")
def unfinished_backup() -> bool:
@ -79,34 +83,39 @@ def unfinished_backup() -> bool:
def set_unfinished_backup(state: bool) -> None:
if state:
config.set(_APP, _UNFINISHED_BACKUP, b'\x01')
config.set(_APP, _UNFINISHED_BACKUP, b"\x01")
else:
config.set(_APP, _UNFINISHED_BACKUP, b'')
config.set(_APP, _UNFINISHED_BACKUP, b"")
def get_passphrase_source() -> int:
b = config.get(_APP, _PASSPHRASE_SOURCE)
if b == b'\x01':
if b == b"\x01":
return 1
elif b == b'\x02':
elif b == b"\x02":
return 2
else:
return 0
def load_settings(label: str=None, use_passphrase: bool=None, homescreen: bytes=None, passphrase_source: int=None) -> None:
def load_settings(
label: str = None,
use_passphrase: bool = None,
homescreen: bytes = None,
passphrase_source: int = None,
) -> None:
if label is not None:
config.set(_APP, _LABEL, label.encode(), True) # public
if use_passphrase is True:
config.set(_APP, _USE_PASSPHRASE, b'\x01')
config.set(_APP, _USE_PASSPHRASE, b"\x01")
if use_passphrase is False:
config.set(_APP, _USE_PASSPHRASE, b'')
config.set(_APP, _USE_PASSPHRASE, b"")
if homescreen is not None:
if homescreen[:8] == b'TOIf\x90\x00\x90\x00':
if homescreen[:8] == b"TOIf\x90\x00\x90\x00":
if len(homescreen) <= HOMESCREEN_MAXSIZE:
config.set(_APP, _HOMESCREEN, homescreen, True) # public
else:
config.set(_APP, _HOMESCREEN, b'', True) # public
config.set(_APP, _HOMESCREEN, b"", True) # public
if passphrase_source is not None:
if passphrase_source in [0, 1, 2]:
config.set(_APP, _PASSPHRASE_SOURCE, bytes([passphrase_source]))
@ -117,7 +126,7 @@ def get_flags() -> int:
if b is None:
return 0
else:
return int.from_bytes(b, 'big')
return int.from_bytes(b, "big")
def set_flags(flags: int) -> None:
@ -125,10 +134,10 @@ def set_flags(flags: int) -> None:
if b is None:
b = 0
else:
b = int.from_bytes(b, 'big')
b = int.from_bytes(b, "big")
flags = (flags | b) & 0xFFFFFFFF
if flags != b:
config.set(_APP, _FLAGS, flags.to_bytes(4, 'big'))
config.set(_APP, _FLAGS, flags.to_bytes(4, "big"))
def get_autolock_delay_ms() -> int:
@ -136,13 +145,13 @@ def get_autolock_delay_ms() -> int:
if b is None:
return 10 * 60 * 1000
else:
return int.from_bytes(b, 'big')
return int.from_bytes(b, "big")
def set_autolock_delay_ms(delay_ms: int) -> None:
if delay_ms < 60 * 1000:
delay_ms = 60 * 1000
config.set(_APP, _AUTOLOCK_DELAY_MS, delay_ms.to_bytes(4, 'big'))
config.set(_APP, _AUTOLOCK_DELAY_MS, delay_ms.to_bytes(4, "big"))
def next_u2f_counter() -> int:
@ -150,13 +159,13 @@ def next_u2f_counter() -> int:
if b is None:
b = 0
else:
b = int.from_bytes(b, 'big') + 1
b = int.from_bytes(b, "big") + 1
set_u2f_counter(b)
return b
def set_u2f_counter(cntr: int):
config.set(_APP, _U2F_COUNTER, cntr.to_bytes(4, 'big'))
config.set(_APP, _U2F_COUNTER, cntr.to_bytes(4, "big"))
def wipe():

View File

@ -1,6 +1,7 @@
if not __debug__:
from trezor.utils import halt
halt('debug mode inactive')
halt("debug mode inactive")
if __debug__:
from trezor import loop
@ -33,12 +34,16 @@ if __debug__:
m.reset_word_pos = reset_word_index
m.reset_entropy = reset_internal_entropy
if reset_current_words:
m.reset_word = ' '.join(reset_current_words)
m.reset_word = " ".join(reset_current_words)
return m
def boot():
# wipe storage when debug build is used
storage.wipe()
register(MessageType.DebugLinkDecision, protobuf_workflow, dispatch_DebugLinkDecision)
register(MessageType.DebugLinkGetState, protobuf_workflow, dispatch_DebugLinkGetState)
register(
MessageType.DebugLinkDecision, protobuf_workflow, dispatch_DebugLinkDecision
)
register(
MessageType.DebugLinkGetState, protobuf_workflow, dispatch_DebugLinkGetState
)

View File

@ -1,25 +1,33 @@
from trezor.wire import register, protobuf_workflow
from trezor.messages.MessageType import EthereumGetAddress, EthereumSignTx
from trezor.messages.MessageType import EthereumSignMessage, EthereumVerifyMessage
from trezor.messages.MessageType import (
EthereumGetAddress,
EthereumSignMessage,
EthereumSignTx,
EthereumVerifyMessage,
)
from trezor.wire import protobuf_workflow, register
def dispatch_EthereumGetAddress(*args, **kwargs):
from .get_address import ethereum_get_address
return ethereum_get_address(*args, **kwargs)
def dispatch_EthereumSignTx(*args, **kwargs):
from .sign_tx import ethereum_sign_tx
return ethereum_sign_tx(*args, **kwargs)
def dispatch_EthereumSignMessage(*args, **kwargs):
from .sign_message import ethereum_sign_message
return ethereum_sign_message(*args, **kwargs)
def dispatch_EthereumVerifyMessage(*args, **kwargs):
from .verify_message import ethereum_verify_message
return ethereum_verify_message(*args, **kwargs)

View File

@ -37,18 +37,18 @@ def _ethereum_address_hex(address, network=None):
hx = hexlify(address).decode()
prefix = str(network.chain_id) + '0x' if rskip60 else ''
prefix = str(network.chain_id) + "0x" if rskip60 else ""
hs = sha3_256(prefix + hx).digest(True)
h = ''
h = ""
for i in range(20):
l = hx[i * 2]
if hs[i] & 0x80 and l >= 'a' and l <= 'f':
if hs[i] & 0x80 and l >= "a" and l <= "f":
l = l.upper()
h += l
l = hx[i * 2 + 1]
if hs[i] & 0x08 and l >= 'a' and l <= 'f':
if hs[i] & 0x08 and l >= "a" and l <= "f":
l = l.upper()
h += l
return '0x' + h
return "0x" + h

View File

@ -1,9 +1,9 @@
from ubinascii import hexlify
from trezor import ui
from trezor.utils import chunks, format_amount
from trezor.messages import ButtonRequestType
from trezor.ui.text import Text
from trezor.utils import chunks, format_amount
from apps.common.confirm import require_confirm, require_hold_to_confirm
from apps.ethereum import networks, tokens
@ -14,21 +14,23 @@ async def require_confirm_tx(ctx, to, value, chain_id, token=None, tx_type=None)
if to:
to_str = _ethereum_address_hex(to, networks.by_chain_id(chain_id))
else:
to_str = 'new contract?'
text = Text('Confirm sending', ui.ICON_SEND, icon_color=ui.GREEN)
to_str = "new contract?"
text = Text("Confirm sending", ui.ICON_SEND, icon_color=ui.GREEN)
text.bold(format_ethereum_amount(value, token, chain_id, tx_type))
text.normal('to')
text.normal("to")
text.mono(*split_address(to_str))
# we use SignTx, not ConfirmOutput, for compatibility with T1
await require_confirm(ctx, text, ButtonRequestType.SignTx)
async def require_confirm_fee(ctx, spending, gas_price, gas_limit, chain_id, token=None, tx_type=None):
text = Text('Confirm transaction', ui.ICON_SEND, icon_color=ui.GREEN)
async def require_confirm_fee(
ctx, spending, gas_price, gas_limit, chain_id, token=None, tx_type=None
):
text = Text("Confirm transaction", ui.ICON_SEND, icon_color=ui.GREEN)
text.bold(format_ethereum_amount(spending, token, chain_id, tx_type))
text.normal('Gas price:')
text.normal("Gas price:")
text.bold(format_ethereum_amount(gas_price, None, chain_id, tx_type))
text.normal('Maximum fee:')
text.normal("Maximum fee:")
text.bold(format_ethereum_amount(gas_price * gas_limit, None, chain_id, tx_type))
await require_hold_to_confirm(ctx, text, ButtonRequestType.SignTx)
@ -40,9 +42,9 @@ def split_data(data):
async def require_confirm_data(ctx, data, data_total):
data_str = hexlify(data[:36]).decode()
if data_total > 36:
data_str = data_str[:-2] + '..'
text = Text('Confirm data', ui.ICON_SEND, icon_color=ui.GREEN)
text.bold('Size: %d bytes' % data_total)
data_str = data_str[:-2] + ".."
text = Text("Confirm data", ui.ICON_SEND, icon_color=ui.GREEN)
text.bold("Size: %d bytes" % data_total)
text.mono(*split_data(data_str))
# we use SignTx, not ConfirmOutput, for compatibility with T1
await require_confirm(ctx, text, ButtonRequestType.SignTx)
@ -55,7 +57,7 @@ def split_address(address):
def format_ethereum_amount(value: int, token, chain_id: int, tx_type=None):
if token:
if token is tokens.UNKNOWN_TOKEN:
return 'Unknown token value'
return "Unknown token value"
suffix = token[2]
decimals = token[3]
else:
@ -63,7 +65,7 @@ def format_ethereum_amount(value: int, token, chain_id: int, tx_type=None):
decimals = 18
if value <= 1e9:
suffix = 'Wei ' + suffix
suffix = "Wei " + suffix
decimals = 0
return '%s %s' % (format_amount(value, decimals), suffix)
return "%s %s" % (format_amount(value, decimals), suffix)

View File

@ -1,12 +1,28 @@
class NetworkInfo:
def shortcut_by_chain_id(chain_id, tx_type=None):
if tx_type in [1, 6] and chain_id in [1, 3]:
return "WAN"
else:
n = by_chain_id(chain_id)
return n.shortcut if n is not None else "UNKN"
def by_chain_id(chain_id):
for n in NETWORKS:
if n.chain_id == chain_id:
return n
return None
def by_slip44(slip44):
for n in NETWORKS:
if n.slip44 == slip44:
return n
return None
class NetworkInfo:
def __init__(
self,
chain_id: int,
slip44: int,
shortcut: str,
name: str,
rskip60: bool
self, chain_id: int, slip44: int, shortcut: str, name: str, rskip60: bool
):
self.chain_id = chain_id
self.slip44 = slip44
@ -15,8 +31,9 @@ class NetworkInfo:
self.rskip60 = rskip60
# the following list is generated using tools/codegen/gen_eth_networks.py
# generated using tools/codegen/gen_eth_networks.py
# do not edit manually!
# fmt: off
NETWORKS = [
NetworkInfo(
chain_id=1,
@ -124,25 +141,3 @@ NETWORKS = [
rskip60=False,
),
]
def shortcut_by_chain_id(chain_id, tx_type=None):
if tx_type in [1, 6] and chain_id in [1, 3]:
return 'WAN'
else:
n = by_chain_id(chain_id)
return n.shortcut if n is not None else 'UNKN'
def by_chain_id(chain_id):
for n in NETWORKS:
if n.chain_id == chain_id:
return n
return None
def by_slip44(slip44):
for n in NETWORKS:
if n.slip44 == slip44:
return n
return None

View File

@ -1,9 +1,9 @@
from trezor import ui
from trezor.crypto.curve import secp256k1
from trezor.crypto.hashlib import sha3_256
from trezor.utils import HashWriter
from trezor.messages.EthereumMessageSignature import EthereumMessageSignature
from trezor.ui.text import Text
from trezor.crypto.curve import secp256k1
from trezor.utils import HashWriter
from apps.common import seed
from apps.common.confirm import require_confirm
from apps.common.signverify import split_message
@ -12,7 +12,7 @@ from apps.common.signverify import split_message
def message_digest(message):
h = HashWriter(sha3_256)
signed_message_header = '\x19Ethereum Signed Message:\n'
signed_message_header = "\x19Ethereum Signed Message:\n"
h.extend(signed_message_header)
h.extend(str(len(message)))
h.extend(message)
@ -37,6 +37,6 @@ async def ethereum_sign_message(ctx, msg):
async def require_confirm_sign_message(ctx, message):
message = split_message(message)
text = Text('Sign ETH message')
text = Text("Sign ETH message")
text.normal(*message)
await require_confirm(ctx, text)

View File

@ -1,10 +1,15 @@
from trezor import wire
from trezor.crypto import rlp
from trezor.messages.EthereumSignTx import EthereumSignTx
from trezor.messages.EthereumTxRequest import EthereumTxRequest
from trezor.utils import HashWriter
from trezor.crypto import rlp
from apps.ethereum import tokens
from apps.ethereum.layout import require_confirm_tx, require_confirm_data, require_confirm_fee
from apps.ethereum.layout import (
require_confirm_data,
require_confirm_fee,
require_confirm_tx,
)
# maximum supported chain id
MAX_CHAIN_ID = 2147483630
@ -21,21 +26,32 @@ async def ethereum_sign_tx(ctx, msg):
# detect ERC - 20 token
token = None
recipient = msg.to
value = int.from_bytes(msg.value, 'big')
if len(msg.to) == 20 and \
len(msg.value) == 0 and \
data_total == 68 and \
len(msg.data_initial_chunk) == 68 and \
msg.data_initial_chunk[:16] == b'\xa9\x05\x9c\xbb\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00':
value = int.from_bytes(msg.value, "big")
if (
len(msg.to) == 20
and len(msg.value) == 0
and data_total == 68
and len(msg.data_initial_chunk) == 68
and msg.data_initial_chunk[:16]
== b"\xa9\x05\x9c\xbb\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
):
token = tokens.token_by_chain_address(msg.chain_id, msg.to)
recipient = msg.data_initial_chunk[16:36]
value = int.from_bytes(msg.data_initial_chunk[36:68], 'big')
value = int.from_bytes(msg.data_initial_chunk[36:68], "big")
await require_confirm_tx(ctx, recipient, value, msg.chain_id, token, msg.tx_type)
if token is None and msg.data_length > 0:
await require_confirm_data(ctx, msg.data_initial_chunk, data_total)
await require_confirm_fee(ctx, value, int.from_bytes(msg.gas_price, 'big'), int.from_bytes(msg.gas_limit, 'big'), msg.chain_id, token, msg.tx_type)
await require_confirm_fee(
ctx,
value,
int.from_bytes(msg.gas_price, "big"),
int.from_bytes(msg.gas_limit, "big"),
msg.chain_id,
token,
msg.tx_type,
)
data = bytearray()
data += msg.data_initial_chunk
@ -92,6 +108,7 @@ def get_total_length(msg: EthereumSignTx, data_total: int) -> int:
async def send_request_chunk(ctx, data_left: int):
from trezor.messages.MessageType import EthereumTxAck
# TODO: layoutProgress ?
req = EthereumTxRequest()
if data_left <= 1024:
@ -124,24 +141,24 @@ async def send_signature(ctx, msg: EthereumSignTx, digest):
def check(msg: EthereumSignTx):
if msg.tx_type not in [1, 6, None]:
raise wire.DataError('tx_type out of bounds')
raise wire.DataError("tx_type out of bounds")
if msg.chain_id < 0 or msg.chain_id > MAX_CHAIN_ID:
raise wire.DataError('chain_id out of bounds')
raise wire.DataError("chain_id out of bounds")
if msg.data_length > 0:
if not msg.data_initial_chunk:
raise wire.DataError('Data length provided, but no initial chunk')
raise wire.DataError("Data length provided, but no initial chunk")
# Our encoding only supports transactions up to 2^24 bytes. To
# prevent exceeding the limit we use a stricter limit on data length.
if msg.data_length > 16000000:
raise wire.DataError('Data length exceeds limit')
raise wire.DataError("Data length exceeds limit")
if len(msg.data_initial_chunk) > msg.data_length:
raise wire.DataError('Invalid size of initial chunk')
raise wire.DataError("Invalid size of initial chunk")
# safety checks
if not check_gas(msg) or not check_to(msg):
raise wire.DataError('Safety check failed')
raise wire.DataError("Safety check failed")
def check_gas(msg: EthereumSignTx) -> bool:
@ -154,7 +171,7 @@ def check_gas(msg: EthereumSignTx) -> bool:
def check_to(msg: EthereumTxRequest) -> bool:
if msg.to == b'':
if msg.to == b"":
if msg.data_length == 0:
# sending transaction to address 0 (contract creation) without a data field
return False
@ -166,15 +183,15 @@ def check_to(msg: EthereumTxRequest) -> bool:
def sanitize(msg):
if msg.value is None:
msg.value = b''
msg.value = b""
if msg.data_initial_chunk is None:
msg.data_initial_chunk = b''
msg.data_initial_chunk = b""
if msg.data_length is None:
msg.data_length = 0
if msg.to is None:
msg.to = b''
msg.to = b""
if msg.nonce is None:
msg.nonce = b''
msg.nonce = b""
if msg.chain_id is None:
msg.chain_id = 0
return msg

File diff suppressed because it is too large Load Diff

View File

@ -1,9 +1,10 @@
from ubinascii import hexlify
from trezor.crypto.curve import secp256k1
from trezor.crypto.hashlib import sha3_256
from trezor import ui
from trezor.ui.text import Text
from trezor.messages.Success import Success
from trezor.ui.text import Text
from apps.common.confirm import require_confirm
from apps.common.display_address import split_address
from apps.common.signverify import split_message
@ -17,25 +18,25 @@ async def ethereum_verify_message(ctx, msg):
pubkey = secp256k1.verify_recover(sig, digest)
if not pubkey:
raise ValueError('Invalid signature')
raise ValueError("Invalid signature")
pkh = sha3_256(pubkey[1:]).digest(True)[-20:]
if msg.address != pkh:
raise ValueError('Invalid signature')
raise ValueError("Invalid signature")
address = '0x' + hexlify(msg.address).decode()
address = "0x" + hexlify(msg.address).decode()
await require_confirm_verify_message(ctx, address, msg.message)
return Success(message='Message verified')
return Success(message="Message verified")
async def require_confirm_verify_message(ctx, address, message):
text = Text('Confirm address')
text = Text("Confirm address")
text.mono(*split_address(address))
await require_confirm(ctx, text)
text = Text('Verify message')
text = Text("Verify message")
text.mono(*split_message(message))
await require_confirm(ctx, text)

View File

@ -1,22 +1,13 @@
from micropython import const
import uctypes
import ustruct
import utime
from micropython import const
from trezor import log
from trezor import loop
from trezor import io
from trezor import ui
from trezor import utils
from trezor import workflow
from trezor.crypto import der
from trezor.crypto import hashlib
from trezor.crypto import hmac
from trezor.crypto import random
from trezor import io, log, loop, ui, utils, workflow
from trezor.crypto import der, hashlib, hmac, random
from trezor.crypto.curve import nist256p1
from apps.common import storage, HARDENED
from apps.common import HARDENED, storage
_HID_RPT_SIZE = const(64)
_CID_BROADCAST = const(0xffffffff) # broadcast channel id
@ -27,17 +18,17 @@ _TYPE_INIT = const(0x80) # initial frame identifier
_TYPE_CONT = const(0x00) # continuation frame identifier
# types of cmd
_CMD_PING = const(0x81) # echo data through local processor only
_CMD_MSG = const(0x83) # send U2F message frame
_CMD_LOCK = const(0x84) # send lock channel command
_CMD_INIT = const(0x86) # channel initialization
_CMD_WINK = const(0x88) # send device identification wink
_CMD_PING = const(0x81) # echo data through local processor only
_CMD_MSG = const(0x83) # send U2F message frame
_CMD_LOCK = const(0x84) # send lock channel command
_CMD_INIT = const(0x86) # channel initialization
_CMD_WINK = const(0x88) # send device identification wink
_CMD_ERROR = const(0xbf) # error response
# types for the msg cmd
_MSG_REGISTER = const(0x01) # registration command
_MSG_REGISTER = const(0x01) # registration command
_MSG_AUTHENTICATE = const(0x02) # authenticate/sign command
_MSG_VERSION = const(0x03) # read version string command
_MSG_VERSION = const(0x03) # read version string command
# hid error codes
_ERR_NONE = const(0x00) # no error
@ -61,8 +52,8 @@ _SW_INS_NOT_SUPPORTED = const(0x6d00)
_SW_CLA_NOT_SUPPORTED = const(0x6e00)
# init response
_CAPFLAG_WINK = const(0x01) # device supports _CMD_WINK
_U2FHID_IF_VERSION = const(2) # interface version
_CAPFLAG_WINK = const(0x01) # device supports _CMD_WINK
_U2FHID_IF_VERSION = const(2) # interface version
# register response
_U2F_KEY_PATH = const(0x80553246)
@ -72,18 +63,18 @@ _U2F_ATT_CERT = b"0\x82\x01\x180\x81\xc0\x02\t\x00\xb1\xd9\x8fBdr\xd3,0\n\x06\x0
_BOGUS_APPID = b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
# authentication control byte
_AUTH_ENFORCE = const(0x03) # enforce user presence and sign
_AUTH_ENFORCE = const(0x03) # enforce user presence and sign
_AUTH_CHECK_ONLY = const(0x07) # check only
_AUTH_FLAG_TUP = const(0x01) # test of user presence set
_AUTH_FLAG_TUP = const(0x01) # test of user presence set
# common raw message format (ISO7816-4:2005 mapping)
_APDU_CLA = const(0) # uint8_t cla; // Class - reserved
_APDU_INS = const(1) # uint8_t ins; // U2F instruction
_APDU_P1 = const(2) # uint8_t p1; // U2F parameter 1
_APDU_P2 = const(3) # uint8_t p2; // U2F parameter 2
_APDU_LC1 = const(4) # uint8_t lc1; // Length field, set to zero
_APDU_LC2 = const(5) # uint8_t lc2; // Length field, MSB
_APDU_LC3 = const(6) # uint8_t lc3; // Length field, LSB
_APDU_CLA = const(0) # uint8_t cla; // Class - reserved
_APDU_INS = const(1) # uint8_t ins; // U2F instruction
_APDU_P1 = const(2) # uint8_t p1; // U2F parameter 1
_APDU_P2 = const(3) # uint8_t p2; // U2F parameter 2
_APDU_LC1 = const(4) # uint8_t lc1; // Length field, set to zero
_APDU_LC2 = const(5) # uint8_t lc2; // Length field, MSB
_APDU_LC3 = const(6) # uint8_t lc3; // Length field, LSB
_APDU_DATA = const(7) # uint8_t data[1]; // Data field
@ -94,10 +85,10 @@ def frame_init() -> dict:
# uint8_t bcntl; // Message byte count - low part
# uint8_t data[HID_RPT_SIZE - 7]; // Data payload
return {
'cid': 0 | uctypes.UINT32,
'cmd': 4 | uctypes.UINT8,
'bcnt': 5 | uctypes.UINT16,
'data': (7 | uctypes.ARRAY, (_HID_RPT_SIZE - 7) | uctypes.UINT8),
"cid": 0 | uctypes.UINT32,
"cmd": 4 | uctypes.UINT8,
"bcnt": 5 | uctypes.UINT16,
"data": (7 | uctypes.ARRAY, (_HID_RPT_SIZE - 7) | uctypes.UINT8),
}
@ -106,9 +97,9 @@ def frame_cont() -> dict:
# uint8_t seq; // Sequence number - b7 cleared
# uint8_t data[HID_RPT_SIZE - 5]; // Data payload
return {
'cid': 0 | uctypes.UINT32,
'seq': 4 | uctypes.UINT8,
'data': (5 | uctypes.ARRAY, (_HID_RPT_SIZE - 5) | uctypes.UINT8),
"cid": 0 | uctypes.UINT32,
"seq": 4 | uctypes.UINT8,
"data": (5 | uctypes.ARRAY, (_HID_RPT_SIZE - 5) | uctypes.UINT8),
}
@ -121,13 +112,13 @@ def resp_cmd_init() -> dict:
# uint8_t versionBuild; // Build version number
# uint8_t capFlags; // Capabilities flags
return {
'nonce': (0 | uctypes.ARRAY, 8 | uctypes.UINT8),
'cid': 8 | uctypes.UINT32,
'versionInterface': 12 | uctypes.UINT8,
'versionMajor': 13 | uctypes.UINT8,
'versionMinor': 14 | uctypes.UINT8,
'versionBuild': 15 | uctypes.UINT8,
'capFlags': 16 | uctypes.UINT8,
"nonce": (0 | uctypes.ARRAY, 8 | uctypes.UINT8),
"cid": 8 | uctypes.UINT32,
"versionInterface": 12 | uctypes.UINT8,
"versionMajor": 13 | uctypes.UINT8,
"versionMinor": 14 | uctypes.UINT8,
"versionBuild": 15 | uctypes.UINT8,
"capFlags": 16 | uctypes.UINT8,
}
@ -143,13 +134,13 @@ def resp_cmd_register(khlen: int, certlen: int, siglen: int) -> dict:
# uint8_t sig[siglen]; // Registration signature
# uint16_t status;
return {
'registerId': 0 | uctypes.UINT8,
'pubKey': (1 | uctypes.ARRAY, 65 | uctypes.UINT8),
'keyHandleLen': 66 | uctypes.UINT8,
'keyHandle': (67 | uctypes.ARRAY, khlen | uctypes.UINT8),
'cert': (cert_ofs | uctypes.ARRAY, certlen | uctypes.UINT8),
'sig': (sig_ofs | uctypes.ARRAY, siglen | uctypes.UINT8),
'status': status_ofs | uctypes.UINT16,
"registerId": 0 | uctypes.UINT8,
"pubKey": (1 | uctypes.ARRAY, 65 | uctypes.UINT8),
"keyHandleLen": 66 | uctypes.UINT8,
"keyHandle": (67 | uctypes.ARRAY, khlen | uctypes.UINT8),
"cert": (cert_ofs | uctypes.ARRAY, certlen | uctypes.UINT8),
"sig": (sig_ofs | uctypes.ARRAY, siglen | uctypes.UINT8),
"status": status_ofs | uctypes.UINT16,
}
@ -163,10 +154,10 @@ def req_cmd_authenticate(khlen: int) -> dict:
# uint8_t keyHandleLen; // Length of key handle
# uint8_t keyHandle[khlen]; // Key handle
return {
'chal': (0 | uctypes.ARRAY, 32 | uctypes.UINT8),
'appId': (32 | uctypes.ARRAY, 32 | uctypes.UINT8),
'keyHandleLen': 64 | uctypes.UINT8,
'keyHandle': (65 | uctypes.ARRAY, khlen | uctypes.UINT8),
"chal": (0 | uctypes.ARRAY, 32 | uctypes.UINT8),
"appId": (32 | uctypes.ARRAY, 32 | uctypes.UINT8),
"keyHandleLen": 64 | uctypes.UINT8,
"keyHandle": (65 | uctypes.ARRAY, khlen | uctypes.UINT8),
}
@ -177,17 +168,17 @@ def resp_cmd_authenticate(siglen: int) -> dict:
# uint8_t sig[siglen]; // Signature
# uint16_t status;
return {
'flags': 0 | uctypes.UINT8,
'ctr': 1 | uctypes.UINT32,
'sig': (5 | uctypes.ARRAY, siglen | uctypes.UINT8),
'status': status_ofs | uctypes.UINT16,
"flags": 0 | uctypes.UINT8,
"ctr": 1 | uctypes.UINT32,
"sig": (5 | uctypes.ARRAY, siglen | uctypes.UINT8),
"status": status_ofs | uctypes.UINT16,
}
def overlay_struct(buf, desc):
desc_size = uctypes.sizeof(desc, uctypes.BIG_ENDIAN)
if desc_size > len(buf):
raise ValueError('desc is too big (%d > %d)' % (desc_size, len(buf)))
raise ValueError("desc is too big (%d > %d)" % (desc_size, len(buf)))
return uctypes.struct(uctypes.addressof(buf), desc, uctypes.BIG_ENDIAN)
@ -198,8 +189,9 @@ def make_struct(desc):
class Msg:
def __init__(self, cid: int, cla: int, ins: int, p1: int, p2: int, lc: int, data: bytes) -> None:
def __init__(
self, cid: int, cla: int, ins: int, p1: int, p2: int, lc: int, data: bytes
) -> None:
self.cid = cid
self.cla = cla
self.ins = ins
@ -210,7 +202,6 @@ class Msg:
class Cmd:
def __init__(self, cid: int, cmd: int, data: bytes) -> None:
self.cid = cid
self.cmd = cmd
@ -221,10 +212,12 @@ class Cmd:
ins = self.data[_APDU_INS]
p1 = self.data[_APDU_P1]
p2 = self.data[_APDU_P2]
lc = (self.data[_APDU_LC1] << 16) + \
(self.data[_APDU_LC2] << 8) + \
(self.data[_APDU_LC3])
data = self.data[_APDU_DATA:_APDU_DATA + lc]
lc = (
(self.data[_APDU_LC1] << 16)
+ (self.data[_APDU_LC2] << 8)
+ (self.data[_APDU_LC3])
)
data = self.data[_APDU_DATA : _APDU_DATA + lc]
return Msg(self.cid, cla, ins, p1, p2, lc, data)
@ -244,7 +237,7 @@ async def read_cmd(iface: io.HID) -> Cmd:
if ifrm.cmd & _TYPE_MASK == _TYPE_CONT:
# unexpected cont packet, abort current msg
if __debug__:
log.warning(__name__, '_TYPE_CONT')
log.warning(__name__, "_TYPE_CONT")
return None
if datalen < bcnt:
@ -262,13 +255,13 @@ async def read_cmd(iface: io.HID) -> Cmd:
if cfrm.seq == _CMD_INIT:
# _CMD_INIT frame, cancels current channel
ifrm = overlay_struct(buf, desc_init)
data = ifrm.data[:ifrm.bcnt]
data = ifrm.data[: ifrm.bcnt]
break
if cfrm.cid != ifrm.cid:
# cont frame for a different channel, reply with BUSY and skip
if __debug__:
log.warning(__name__, '_ERR_CHANNEL_BUSY')
log.warning(__name__, "_ERR_CHANNEL_BUSY")
await send_cmd(cmd_error(cfrm.cid, _ERR_CHANNEL_BUSY), iface)
continue
@ -276,7 +269,7 @@ async def read_cmd(iface: io.HID) -> Cmd:
# cont frame for this channel, but incorrect seq number, abort
# current msg
if __debug__:
log.warning(__name__, '_ERR_INVALID_SEQ')
log.warning(__name__, "_ERR_INVALID_SEQ")
await send_cmd(cmd_error(cfrm.cid, _ERR_INVALID_SEQ), iface)
return None
@ -339,7 +332,6 @@ _CONFIRM_TIMEOUT_MS = const(10 * 1000)
class ConfirmState:
def __init__(self) -> None:
self.reset()
@ -391,19 +383,20 @@ class ConfirmState:
from trezor.ui.text import Text
if bytes(self.app_id) == _BOGUS_APPID:
text = Text('U2F mismatch', ui.ICON_WRONG, icon_color=ui.RED)
text.normal('Another U2F device', 'was used to register', 'in this application.')
text = Text("U2F mismatch", ui.ICON_WRONG, icon_color=ui.RED)
text.normal(
"Another U2F device", "was used to register", "in this application."
)
text.render()
await loop.sleep(3 * 1000 * 1000)
self.confirmed = True
else:
content = ConfirmContent(self.action, self.app_id)
dialog = ConfirmDialog(content, )
dialog = ConfirmDialog(content)
self.confirmed = await dialog == CONFIRMED
class ConfirmContent(ui.Widget):
def __init__(self, action: int, app_id: bytes) -> None:
self.action = action
self.app_id = app_id
@ -420,25 +413,30 @@ class ConfirmContent(ui.Widget):
if app_id == _BOGUS_APPID:
# TODO: display a warning dialog for bogus app ids
name = 'Another U2F device'
icon = res.load('apps/fido_u2f/res/u2f_generic.toif') # TODO: warning icon
name = "Another U2F device"
icon = res.load("apps/fido_u2f/res/u2f_generic.toif") # TODO: warning icon
elif app_id in knownapps.knownapps:
name = knownapps.knownapps[app_id]
try:
icon = res.load('apps/fido_u2f/res/u2f_%s.toif' % name.lower().replace(' ', '_'))
icon = res.load(
"apps/fido_u2f/res/u2f_%s.toif" % name.lower().replace(" ", "_")
)
except Exception:
icon = res.load('apps/fido_u2f/res/u2f_generic.toif')
icon = res.load("apps/fido_u2f/res/u2f_generic.toif")
else:
name = '%s...%s' % (hexlify(app_id[:4]).decode(), hexlify(app_id[-4:]).decode())
icon = res.load('apps/fido_u2f/res/u2f_generic.toif')
name = "%s...%s" % (
hexlify(app_id[:4]).decode(),
hexlify(app_id[-4:]).decode(),
)
icon = res.load("apps/fido_u2f/res/u2f_generic.toif")
self.app_name = name
self.app_icon = icon
def render(self) -> None:
if self.action == _CONFIRM_REGISTER:
header = 'U2F Register'
header = "U2F Register"
else:
header = 'U2F Authenticate'
header = "U2F Authenticate"
ui.header(header, ui.ICON_DEFAULT, ui.GREEN, ui.BG, ui.GREEN)
ui.display.image((ui.WIDTH - 64) // 2, 64, self.app_icon)
ui.display.text_center(ui.WIDTH // 2, 168, self.app_name, ui.MONO, ui.FG, ui.BG)
@ -450,46 +448,46 @@ def dispatch_cmd(req: Cmd, state: ConfirmState) -> Cmd:
if m.cla != 0:
if __debug__:
log.warning(__name__, '_SW_CLA_NOT_SUPPORTED')
log.warning(__name__, "_SW_CLA_NOT_SUPPORTED")
return msg_error(req.cid, _SW_CLA_NOT_SUPPORTED)
if m.lc + _APDU_DATA > len(req.data):
if __debug__:
log.warning(__name__, '_SW_WRONG_LENGTH')
log.warning(__name__, "_SW_WRONG_LENGTH")
return msg_error(req.cid, _SW_WRONG_LENGTH)
if m.ins == _MSG_REGISTER:
if __debug__:
log.debug(__name__, '_MSG_REGISTER')
log.debug(__name__, "_MSG_REGISTER")
return msg_register(m, state)
elif m.ins == _MSG_AUTHENTICATE:
if __debug__:
log.debug(__name__, '_MSG_AUTHENTICATE')
log.debug(__name__, "_MSG_AUTHENTICATE")
return msg_authenticate(m, state)
elif m.ins == _MSG_VERSION:
if __debug__:
log.debug(__name__, '_MSG_VERSION')
log.debug(__name__, "_MSG_VERSION")
return msg_version(m)
else:
if __debug__:
log.warning(__name__, '_SW_INS_NOT_SUPPORTED: %d', m.ins)
log.warning(__name__, "_SW_INS_NOT_SUPPORTED: %d", m.ins)
return msg_error(req.cid, _SW_INS_NOT_SUPPORTED)
elif req.cmd == _CMD_INIT:
if __debug__:
log.debug(__name__, '_CMD_INIT')
log.debug(__name__, "_CMD_INIT")
return cmd_init(req)
elif req.cmd == _CMD_PING:
if __debug__:
log.debug(__name__, '_CMD_PING')
log.debug(__name__, "_CMD_PING")
return req
elif req.cmd == _CMD_WINK:
if __debug__:
log.debug(__name__, '_CMD_WINK')
log.debug(__name__, "_CMD_WINK")
return req
else:
if __debug__:
log.warning(__name__, '_ERR_INVALID_CMD: %d', req.cmd)
log.warning(__name__, "_ERR_INVALID_CMD: %d", req.cmd)
return cmd_error(req.cid, _ERR_INVALID_CMD)
@ -519,13 +517,13 @@ def msg_register(req: Msg, state: ConfirmState) -> Cmd:
if not storage.is_initialized():
if __debug__:
log.warning(__name__, 'not initialized')
log.warning(__name__, "not initialized")
return msg_error(req.cid, _SW_CONDITIONS_NOT_SATISFIED)
# check length of input data
if len(req.data) != 64:
if __debug__:
log.warning(__name__, '_SW_WRONG_LENGTH req.data')
log.warning(__name__, "_SW_WRONG_LENGTH req.data")
return msg_error(req.cid, _SW_WRONG_LENGTH)
# parse challenge and app_id
@ -541,12 +539,12 @@ def msg_register(req: Msg, state: ConfirmState) -> Cmd:
# wait for a button or continue
if not state.confirmed:
if __debug__:
log.info(__name__, 'waiting for button')
log.info(__name__, "waiting for button")
return msg_error(req.cid, _SW_CONDITIONS_NOT_SATISFIED)
# sign the registration challenge and return
if __debug__:
log.info(__name__, 'signing register')
log.info(__name__, "signing register")
buf = msg_register_sign(chal, app_id)
state.reset()
@ -563,11 +561,11 @@ def msg_register_sign(challenge: bytes, app_id: bytes) -> bytes:
nodepath = [_U2F_KEY_PATH] + keypath
# prepare signing key from random path, compute decompressed public key
node = seed.derive_node_without_passphrase(nodepath, 'nist256p1')
node = seed.derive_node_without_passphrase(nodepath, "nist256p1")
pubkey = nist256p1.publickey(node.private_key(), False)
# first half of keyhandle is keypath
keybuf = ustruct.pack('>8L', *keypath)
keybuf = ustruct.pack(">8L", *keypath)
# second half of keyhandle is a hmac of app_id and keypath
keybase = hmac.Hmac(node.private_key(), app_id, hashlib.sha256)
@ -576,12 +574,12 @@ def msg_register_sign(challenge: bytes, app_id: bytes) -> bytes:
# hash the request data together with keyhandle and pubkey
dig = hashlib.sha256()
dig.update(b'\x00') # uint8_t reserved;
dig.update(app_id) # uint8_t appId[32];
dig.update(b"\x00") # uint8_t reserved;
dig.update(app_id) # uint8_t appId[32];
dig.update(challenge) # uint8_t chal[32];
dig.update(keybuf) # uint8_t keyHandle[64];
dig.update(keybuf) # uint8_t keyHandle[64];
dig.update(keybase)
dig.update(pubkey) # uint8_t pubKey[65];
dig.update(pubkey) # uint8_t pubKey[65];
dig = dig.digest()
# sign the digest and convert to der
@ -589,8 +587,9 @@ def msg_register_sign(challenge: bytes, app_id: bytes) -> bytes:
sig = der.encode_seq((sig[1:33], sig[33:]))
# pack to a response
buf, resp = make_struct(resp_cmd_register(
len(keybuf) + len(keybase), len(_U2F_ATT_CERT), len(sig)))
buf, resp = make_struct(
resp_cmd_register(len(keybuf) + len(keybase), len(_U2F_ATT_CERT), len(sig))
)
resp.registerId = _U2F_REGISTER_ID
utils.memcpy(resp.pubKey, 0, pubkey, 0, len(pubkey))
resp.keyHandleLen = len(keybuf) + len(keybase)
@ -608,20 +607,20 @@ def msg_authenticate(req: Msg, state: ConfirmState) -> Cmd:
if not storage.is_initialized():
if __debug__:
log.warning(__name__, 'not initialized')
log.warning(__name__, "not initialized")
return msg_error(req.cid, _SW_CONDITIONS_NOT_SATISFIED)
# we need at least keyHandleLen
if len(req.data) <= _REQ_CMD_AUTHENTICATE_KHLEN:
if __debug__:
log.warning(__name__, '_SW_WRONG_LENGTH req.data')
log.warning(__name__, "_SW_WRONG_LENGTH req.data")
return msg_error(req.cid, _SW_WRONG_LENGTH)
# check keyHandleLen
khlen = req.data[_REQ_CMD_AUTHENTICATE_KHLEN]
if khlen != 64:
if __debug__:
log.warning(__name__, '_SW_WRONG_LENGTH khlen')
log.warning(__name__, "_SW_WRONG_LENGTH khlen")
return msg_error(req.cid, _SW_WRONG_LENGTH)
auth = overlay_struct(req.data, req_cmd_authenticate(khlen))
@ -635,13 +634,13 @@ def msg_authenticate(req: Msg, state: ConfirmState) -> Cmd:
# if _AUTH_CHECK_ONLY is requested, return, because keyhandle has been checked already
if req.p1 == _AUTH_CHECK_ONLY:
if __debug__:
log.info(__name__, '_AUTH_CHECK_ONLY')
log.info(__name__, "_AUTH_CHECK_ONLY")
return msg_error(req.cid, _SW_CONDITIONS_NOT_SATISFIED)
# from now on, only _AUTH_ENFORCE is supported
if req.p1 != _AUTH_ENFORCE:
if __debug__:
log.info(__name__, '_AUTH_ENFORCE')
log.info(__name__, "_AUTH_ENFORCE")
return msg_error(req.cid, _SW_WRONG_DATA)
# check equality with last request
@ -653,12 +652,12 @@ def msg_authenticate(req: Msg, state: ConfirmState) -> Cmd:
# wait for a button or continue
if not state.confirmed:
if __debug__:
log.info(__name__, 'waiting for button')
log.info(__name__, "waiting for button")
return msg_error(req.cid, _SW_CONDITIONS_NOT_SATISFIED)
# sign the authentication challenge and return
if __debug__:
log.info(__name__, 'signing authentication')
log.info(__name__, "signing authentication")
buf = msg_authenticate_sign(auth.chal, auth.appId, node.private_key())
state.reset()
@ -671,18 +670,18 @@ def msg_authenticate_genkey(app_id: bytes, keyhandle: bytes):
# unpack the keypath from the first half of keyhandle
keybuf = keyhandle[:32]
keypath = ustruct.unpack('>8L', keybuf)
keypath = ustruct.unpack(">8L", keybuf)
# check high bit for hardened keys
for i in keypath:
if not i & HARDENED:
if __debug__:
log.warning(__name__, 'invalid key path')
log.warning(__name__, "invalid key path")
return None
# derive the signing key
nodepath = [_U2F_KEY_PATH] + list(keypath)
node = seed.derive_node_without_passphrase(nodepath, 'nist256p1')
node = seed.derive_node_without_passphrase(nodepath, "nist256p1")
# second half of keyhandle is a hmac of app_id and keypath
keybase = hmac.Hmac(node.private_key(), app_id, hashlib.sha256)
@ -692,7 +691,7 @@ def msg_authenticate_genkey(app_id: bytes, keyhandle: bytes):
# verify the hmac
if keybase != keyhandle[32:]:
if __debug__:
log.warning(__name__, 'invalid key handle')
log.warning(__name__, "invalid key handle")
return None
return node
@ -703,13 +702,13 @@ def msg_authenticate_sign(challenge: bytes, app_id: bytes, privkey: bytes) -> by
# get next counter
ctr = storage.next_u2f_counter()
ctrbuf = ustruct.pack('>L', ctr)
ctrbuf = ustruct.pack(">L", ctr)
# hash input data together with counter
dig = hashlib.sha256()
dig.update(app_id) # uint8_t appId[32];
dig.update(flags) # uint8_t flags;
dig.update(ctrbuf) # uint8_t ctr[4];
dig.update(app_id) # uint8_t appId[32];
dig.update(flags) # uint8_t flags;
dig.update(ctrbuf) # uint8_t ctr[4];
dig.update(challenge) # uint8_t chal[32];
dig = dig.digest()
@ -730,12 +729,12 @@ def msg_authenticate_sign(challenge: bytes, app_id: bytes, privkey: bytes) -> by
def msg_version(req: Msg) -> Cmd:
if req.data:
return msg_error(req.cid, _SW_WRONG_LENGTH)
return Cmd(req.cid, _CMD_MSG, b'U2F_V2\x90\x00') # includes _SW_NO_ERROR
return Cmd(req.cid, _CMD_MSG, b"U2F_V2\x90\x00") # includes _SW_NO_ERROR
def msg_error(cid: int, code: int) -> Cmd:
return Cmd(cid, _CMD_MSG, ustruct.pack('>H', code))
return Cmd(cid, _CMD_MSG, ustruct.pack(">H", code))
def cmd_error(cid: int, code: int) -> Cmd:
return Cmd(cid, _CMD_ERROR, ustruct.pack('>B', code))
return Cmd(cid, _CMD_ERROR, ustruct.pack(">B", code))

View File

@ -1,19 +1,25 @@
from trezor.crypto import hashlib
knownapps = {
hashlib.sha256(b'https://account.gandi.net/api/u2f/trusted_facets.json').digest(): 'Gandi',
hashlib.sha256(b'https://api-9dcf9b83.duosecurity.com').digest(): 'Duo',
hashlib.sha256(b'https://bitbucket.org').digest(): 'Bitbucket',
hashlib.sha256(b'https://dashboard.stripe.com').digest(): 'Stripe',
hashlib.sha256(b'https://demo.yubico.com').digest(): 'Yubico U2F Demo',
hashlib.sha256(b'https://github.com/u2f/trusted_facets').digest(): 'GitHub',
hashlib.sha256(b'https://gitlab.com').digest(): 'GitLab',
hashlib.sha256(b'https://keepersecurity.com').digest(): 'Keeper',
hashlib.sha256(b'https://slushpool.com/static/security/u2f.json').digest(): 'Slush Pool',
hashlib.sha256(b'https://u2f.bin.coffee').digest(): 'u2f.bin.coffee checker',
hashlib.sha256(b'https://vault.bitwarden.com/app-id.json').digest(): 'bitwarden',
hashlib.sha256(b'https://www.bitfinex.com').digest(): 'Bitfinex',
hashlib.sha256(b'https://www.dropbox.com/u2f-app-id.json').digest(): 'Dropbox',
hashlib.sha256(b'https://www.fastmail.com').digest(): 'FastMail',
hashlib.sha256(b'https://www.gstatic.com/securitykey/origins.json').digest(): 'Google',
hashlib.sha256(
b"https://account.gandi.net/api/u2f/trusted_facets.json"
).digest(): "Gandi",
hashlib.sha256(b"https://api-9dcf9b83.duosecurity.com").digest(): "Duo",
hashlib.sha256(b"https://bitbucket.org").digest(): "Bitbucket",
hashlib.sha256(b"https://dashboard.stripe.com").digest(): "Stripe",
hashlib.sha256(b"https://demo.yubico.com").digest(): "Yubico U2F Demo",
hashlib.sha256(b"https://github.com/u2f/trusted_facets").digest(): "GitHub",
hashlib.sha256(b"https://gitlab.com").digest(): "GitLab",
hashlib.sha256(b"https://keepersecurity.com").digest(): "Keeper",
hashlib.sha256(
b"https://slushpool.com/static/security/u2f.json"
).digest(): "Slush Pool",
hashlib.sha256(b"https://u2f.bin.coffee").digest(): "u2f.bin.coffee checker",
hashlib.sha256(b"https://vault.bitwarden.com/app-id.json").digest(): "bitwarden",
hashlib.sha256(b"https://www.bitfinex.com").digest(): "Bitfinex",
hashlib.sha256(b"https://www.dropbox.com/u2f-app-id.json").digest(): "Dropbox",
hashlib.sha256(b"https://www.fastmail.com").digest(): "FastMail",
hashlib.sha256(
b"https://www.gstatic.com/securitykey/origins.json"
).digest(): "Google",
}

View File

@ -1,23 +1,23 @@
from trezor import config, utils, wire
from trezor.wire import register, protobuf_workflow
from trezor.messages import MessageType
from trezor.messages.Features import Features
from trezor.messages.Success import Success
from trezor.wire import protobuf_workflow, register
from apps.common import storage, cache
from apps.common import cache, storage
def get_features():
f = Features()
f.vendor = 'trezor.io'
f.language = 'english'
f.major_version = utils.symbol('VERSION_MAJOR')
f.minor_version = utils.symbol('VERSION_MINOR')
f.patch_version = utils.symbol('VERSION_PATCH')
f.revision = utils.symbol('GITREV')
f.vendor = "trezor.io"
f.language = "english"
f.major_version = utils.symbol("VERSION_MAJOR")
f.minor_version = utils.symbol("VERSION_MINOR")
f.patch_version = utils.symbol("VERSION_PATCH")
f.revision = utils.symbol("GITREV")
f.model = utils.model()
if f.model == 'EMU':
f.model = 'T' # emulator currently emulates model T
if f.model == "EMU":
f.model = "T" # emulator currently emulates model T
f.device_id = storage.get_device_id()
f.label = storage.get_label()
f.initialized = storage.is_initialized()
@ -42,12 +42,12 @@ async def handle_GetFeatures(ctx, msg):
async def handle_Cancel(ctx, msg):
raise wire.ActionCancelled('Cancelled')
raise wire.ActionCancelled("Cancelled")
async def handle_ClearSession(ctx, msg):
cache.clear()
return Success(message='Session cleared')
return Success(message="Session cleared")
async def handle_Ping(ctx, msg):
@ -55,10 +55,11 @@ async def handle_Ping(ctx, msg):
from apps.common.confirm import require_confirm
from trezor.messages.ButtonRequestType import ProtectCall
from trezor.ui.text import Text
from trezor import ui
await require_confirm(ctx, Text('Confirm'), ProtectCall)
await require_confirm(ctx, Text("Confirm"), ProtectCall)
if msg.passphrase_protection:
from apps.common.request_passphrase import protect_by_passphrase
await protect_by_passphrase(ctx)
return Success(message=msg.message)

View File

@ -1,5 +1,6 @@
from trezor import ui, res, config
from trezor import config, res, ui
from trezor.ui.swipe import Swipe, degrees
from apps.common import storage
@ -13,26 +14,32 @@ async def homescreen():
def display_homescreen():
if not storage.is_initialized():
label = 'Go to trezor.io/start'
label = "Go to trezor.io/start"
image = None
else:
label = storage.get_label() or 'My TREZOR'
label = storage.get_label() or "My TREZOR"
image = storage.get_homescreen()
if not image:
image = res.load('apps/homescreen/res/bg.toif')
image = res.load("apps/homescreen/res/bg.toif")
if storage.is_initialized() and storage.unfinished_backup():
ui.display.bar(0, 0, ui.WIDTH, 30, ui.RED)
ui.display.text_center(ui.WIDTH // 2, 22, 'BACKUP FAILED!', ui.BOLD, ui.WHITE, ui.RED)
ui.display.text_center(
ui.WIDTH // 2, 22, "BACKUP FAILED!", ui.BOLD, ui.WHITE, ui.RED
)
ui.display.bar(0, 30, ui.WIDTH, ui.HEIGHT - 30, ui.BG)
elif storage.is_initialized() and storage.needs_backup():
ui.display.bar(0, 0, ui.WIDTH, 30, ui.YELLOW)
ui.display.text_center(ui.WIDTH // 2, 22, 'NEEDS BACKUP!', ui.BOLD, ui.BLACK, ui.YELLOW)
ui.display.text_center(
ui.WIDTH // 2, 22, "NEEDS BACKUP!", ui.BOLD, ui.BLACK, ui.YELLOW
)
ui.display.bar(0, 30, ui.WIDTH, ui.HEIGHT - 30, ui.BG)
elif storage.is_initialized() and not config.has_pin():
ui.display.bar(0, 0, ui.WIDTH, 30, ui.YELLOW)
ui.display.text_center(ui.WIDTH // 2, 22, 'PIN NOT SET!', ui.BOLD, ui.BLACK, ui.YELLOW)
ui.display.text_center(
ui.WIDTH // 2, 22, "PIN NOT SET!", ui.BOLD, ui.BLACK, ui.YELLOW
)
ui.display.bar(0, 30, ui.WIDTH, ui.HEIGHT - 30, ui.BG)
else:
ui.display.bar(0, 0, ui.WIDTH, ui.HEIGHT, ui.BG)

View File

@ -1,30 +1,40 @@
from trezor.wire import register, protobuf_workflow
from trezor.messages.MessageType import \
LiskGetAddress, LiskSignTx, LiskGetPublicKey, LiskSignMessage, LiskVerifyMessage
from trezor.messages.MessageType import (
LiskGetAddress,
LiskGetPublicKey,
LiskSignMessage,
LiskSignTx,
LiskVerifyMessage,
)
from trezor.wire import protobuf_workflow, register
def dispatch_LiskGetAddress(*args, **kwargs):
from .get_address import layout_lisk_get_address
return layout_lisk_get_address(*args, **kwargs)
def dispatch_LiskGetPublicKey(*args, **kwargs):
from .get_public_key import lisk_get_public_key
return lisk_get_public_key(*args, **kwargs)
def dispatch_LiskSignTx(*args, **kwargs):
from .sign_tx import lisk_sign_tx
return lisk_sign_tx(*args, **kwargs)
def dispatch_LiskSignMessage(*args, **kwargs):
from .sign_message import lisk_sign_message
return lisk_sign_message(*args, **kwargs)
def dispatch_LiskVerifyMessage(*args, **kwargs):
from .verify_message import lisk_verify_message
return lisk_verify_message(*args, **kwargs)

View File

@ -1,9 +1,10 @@
from apps.common import seed
from apps.common.display_address import show_address, show_qr
from trezor.messages.LiskAddress import LiskAddress
from .helpers import LISK_CURVE, get_address_from_public_key
from apps.common import seed
from apps.common.display_address import show_address, show_qr
async def layout_lisk_get_address(ctx, msg):
address_n = msg.address_n or ()

View File

@ -1,9 +1,10 @@
from apps.common import seed
from apps.wallet.get_public_key import _show_pubkey
from trezor.messages.LiskPublicKey import LiskPublicKey
from .helpers import LISK_CURVE
from apps.common import seed
from apps.wallet.get_public_key import _show_pubkey
async def lisk_get_public_key(ctx, msg):
address_n = msg.address_n or ()

View File

@ -1,18 +1,18 @@
from trezor.crypto.hashlib import sha256
LISK_CURVE = 'ed25519'
LISK_CURVE = "ed25519"
def get_address_from_public_key(pubkey):
pubkeyhash = sha256(pubkey).digest()
address = int.from_bytes(pubkeyhash[:8], 'little')
return str(address) + 'L'
address = int.from_bytes(pubkeyhash[:8], "little")
return str(address) + "L"
def get_votes_count(votes):
plus, minus = 0, 0
for vote in votes:
if vote.startswith('+'):
if vote.startswith("+"):
plus += 1
else:
minus += 1
@ -23,11 +23,11 @@ def get_vote_tx_text(votes):
plus, minus = get_votes_count(votes)
text = []
if plus > 0:
text.append(_text_with_plural('Add', plus))
text.append(_text_with_plural("Add", plus))
if minus > 0:
text.append(_text_with_plural('Remove', minus))
text.append(_text_with_plural("Remove", minus))
return text
def _text_with_plural(txt, value):
return '%s %s %s' % (txt, value, ('votes' if value != 1 else 'vote'))
return "%s %s %s" % (txt, value, ("votes" if value != 1 else "vote"))

View File

@ -1,5 +1,3 @@
from apps.common.confirm import require_confirm, require_hold_to_confirm
from apps.wallet.get_public_key import _show_pubkey
from trezor import ui
from trezor.messages import ButtonRequestType
from trezor.ui.text import Text
@ -7,25 +5,28 @@ from trezor.utils import chunks
from .helpers import get_vote_tx_text
from apps.common.confirm import require_confirm, require_hold_to_confirm
from apps.wallet.get_public_key import _show_pubkey
async def require_confirm_tx(ctx, to, value):
text = Text('Confirm sending', ui.ICON_SEND, icon_color=ui.GREEN)
text = Text("Confirm sending", ui.ICON_SEND, icon_color=ui.GREEN)
text.bold(format_amount(value))
text.normal('to')
text.normal("to")
text.mono(*split_address(to))
return await require_confirm(ctx, text, ButtonRequestType.SignTx)
async def require_confirm_delegate_registration(ctx, delegate_name):
text = Text('Confirm transaction', ui.ICON_SEND, icon_color=ui.GREEN)
text.normal('Do you really want to')
text.normal('register a delegate?')
text = Text("Confirm transaction", ui.ICON_SEND, icon_color=ui.GREEN)
text.normal("Do you really want to")
text.normal("register a delegate?")
text.bold(*chunks(delegate_name, 20))
return await require_confirm(ctx, text, ButtonRequestType.SignTx)
async def require_confirm_vote_tx(ctx, votes):
text = Text('Confirm transaction', ui.ICON_SEND, icon_color=ui.GREEN)
text = Text("Confirm transaction", ui.ICON_SEND, icon_color=ui.GREEN)
text.normal(*get_vote_tx_text(votes))
return await require_confirm(ctx, text, ButtonRequestType.SignTx)
@ -35,23 +36,23 @@ async def require_confirm_public_key(ctx, public_key):
async def require_confirm_multisig(ctx, multisignature):
text = Text('Confirm transaction', ui.ICON_SEND, icon_color=ui.GREEN)
text.normal('Keys group length: %s' % len(multisignature.keys_group))
text.normal('Life time: %s' % multisignature.life_time)
text.normal('Min: %s' % multisignature.min)
text = Text("Confirm transaction", ui.ICON_SEND, icon_color=ui.GREEN)
text.normal("Keys group length: %s" % len(multisignature.keys_group))
text.normal("Life time: %s" % multisignature.life_time)
text.normal("Min: %s" % multisignature.min)
return await require_confirm(ctx, text, ButtonRequestType.SignTx)
async def require_confirm_fee(ctx, value, fee):
text = Text('Confirm transaction', ui.ICON_SEND, icon_color=ui.GREEN)
text = Text("Confirm transaction", ui.ICON_SEND, icon_color=ui.GREEN)
text.bold(format_amount(value))
text.normal('fee:')
text.normal("fee:")
text.bold(format_amount(fee))
await require_hold_to_confirm(ctx, text, ButtonRequestType.ConfirmOutput)
def format_amount(value):
return '%s LSK' % (int(value) / 100000000)
return "%s LSK" % (int(value) / 100000000)
def split_address(address):

View File

@ -1,8 +1,3 @@
from apps.common import seed
from apps.common.confirm import require_confirm
from apps.common.signverify import split_message
from apps.wallet.sign_tx.signing import write_varint
from trezor import ui
from trezor.crypto.curve import ed25519
from trezor.crypto.hashlib import sha256
from trezor.messages.LiskMessageSignature import LiskMessageSignature
@ -11,10 +6,15 @@ from trezor.utils import HashWriter
from .helpers import LISK_CURVE
from apps.common import seed
from apps.common.confirm import require_confirm
from apps.common.signverify import split_message
from apps.wallet.sign_tx.signing import write_varint
def message_digest(message):
h = HashWriter(sha256)
signed_message_header = 'Lisk Signed Message:\n'
signed_message_header = "Lisk Signed Message:\n"
write_varint(h, len(signed_message_header))
h.extend(signed_message_header)
write_varint(h, len(message))
@ -39,6 +39,6 @@ async def lisk_sign_message(ctx, msg):
async def require_confirm_sign_message(ctx, message):
text = Text('Sign Lisk message')
text = Text("Sign Lisk message")
text.normal(*split_message(message))
await require_confirm(ctx, text)

View File

@ -1,5 +1,5 @@
import ustruct
from apps.common import seed
from trezor import wire
from trezor.crypto.curve import ed25519
from trezor.crypto.hashlib import sha256
@ -10,6 +10,8 @@ from trezor.utils import HashWriter
from . import layout
from .helpers import LISK_CURVE, get_address_from_public_key
from apps.common import seed
async def lisk_sign_tx(ctx, msg):
pubkey, seckey = await _get_keys(ctx, msg)
@ -18,7 +20,7 @@ async def lisk_sign_tx(ctx, msg):
try:
await _require_confirm_by_type(ctx, transaction)
except AttributeError:
raise wire.DataError('The transaction has invalid asset data field')
raise wire.DataError("The transaction has invalid asset data field")
await layout.require_confirm_fee(ctx, transaction.amount, transaction.fee)
@ -59,48 +61,59 @@ async def _require_confirm_by_type(ctx, transaction):
if transaction.type == LiskTransactionType.Transfer:
return await layout.require_confirm_tx(
ctx, transaction.recipient_id, transaction.amount)
ctx, transaction.recipient_id, transaction.amount
)
if transaction.type == LiskTransactionType.RegisterDelegate:
return await layout.require_confirm_delegate_registration(
ctx, transaction.asset.delegate.username)
ctx, transaction.asset.delegate.username
)
if transaction.type == LiskTransactionType.CastVotes:
return await layout.require_confirm_vote_tx(
ctx, transaction.asset.votes)
return await layout.require_confirm_vote_tx(ctx, transaction.asset.votes)
if transaction.type == LiskTransactionType.RegisterSecondPassphrase:
return await layout.require_confirm_public_key(
ctx, transaction.asset.signature.public_key)
ctx, transaction.asset.signature.public_key
)
if transaction.type == LiskTransactionType.RegisterMultisignatureAccount:
return await layout.require_confirm_multisig(
ctx, transaction.asset.multisignature)
ctx, transaction.asset.multisignature
)
raise wire.DataError('Invalid transaction type')
raise wire.DataError("Invalid transaction type")
def _get_transaction_bytes(tx):
# Required transaction parameters
t_type = ustruct.pack('<b', tx.type)
t_timestamp = ustruct.pack('<i', tx.timestamp)
t_type = ustruct.pack("<b", tx.type)
t_timestamp = ustruct.pack("<i", tx.timestamp)
t_sender_public_key = tx.sender_public_key
t_requester_public_key = tx.requester_public_key or b''
t_requester_public_key = tx.requester_public_key or b""
if not tx.recipient_id:
# Value can be empty string
t_recipient_id = ustruct.pack('>Q', 0)
t_recipient_id = ustruct.pack(">Q", 0)
else:
# Lisk uses big-endian for recipient_id, string -> int -> bytes
t_recipient_id = ustruct.pack('>Q', int(tx.recipient_id[:-1]))
t_recipient_id = ustruct.pack(">Q", int(tx.recipient_id[:-1]))
t_amount = ustruct.pack('<Q', tx.amount)
t_amount = ustruct.pack("<Q", tx.amount)
t_asset = _get_asset_data_bytes(tx)
t_signature = tx.signature or b''
t_signature = tx.signature or b""
return (t_type, t_timestamp, t_sender_public_key, t_requester_public_key,
t_recipient_id, t_amount, t_asset, t_signature)
return (
t_type,
t_timestamp,
t_sender_public_key,
t_requester_public_key,
t_recipient_id,
t_amount,
t_asset,
t_signature,
)
def _get_asset_data_bytes(msg):
@ -108,24 +121,24 @@ def _get_asset_data_bytes(msg):
if msg.type == LiskTransactionType.Transfer:
# Transfer transaction have optional data field
if msg.asset.data is not None:
return bytes(msg.asset.data, 'utf8')
return bytes(msg.asset.data, "utf8")
else:
return b''
return b""
if msg.type == LiskTransactionType.RegisterDelegate:
return bytes(msg.asset.delegate.username, 'utf8')
return bytes(msg.asset.delegate.username, "utf8")
if msg.type == LiskTransactionType.CastVotes:
return bytes(''.join(msg.asset.votes), 'utf8')
return bytes("".join(msg.asset.votes), "utf8")
if msg.type == LiskTransactionType.RegisterSecondPassphrase:
return msg.asset.signature.public_key
if msg.type == LiskTransactionType.RegisterMultisignatureAccount:
data = b''
data += ustruct.pack('<b', msg.asset.multisignature.min)
data += ustruct.pack('<b', msg.asset.multisignature.life_time)
data += bytes(''.join(msg.asset.multisignature.keys_group), 'utf8')
data = b""
data += ustruct.pack("<b", msg.asset.multisignature.min)
data += ustruct.pack("<b", msg.asset.multisignature.life_time)
data += bytes("".join(msg.asset.multisignature.keys_group), "utf8")
return data
raise wire.DataError('Invalid transaction type')
raise wire.DataError("Invalid transaction type")

View File

@ -1,4 +1,3 @@
from apps.wallet.verify_message import require_confirm_verify_message
from trezor import wire
from trezor.crypto.curve import ed25519
from trezor.messages.Success import Success
@ -6,14 +5,16 @@ from trezor.messages.Success import Success
from .helpers import get_address_from_public_key
from .sign_message import message_digest
from apps.wallet.verify_message import require_confirm_verify_message
async def lisk_verify_message(ctx, msg):
digest = message_digest(msg.message)
verified = ed25519.verify(msg.public_key, msg.signature, digest)
if not verified:
raise wire.ProcessError('Invalid signature')
raise wire.ProcessError("Invalid signature")
address = get_address_from_public_key(msg.public_key)
await require_confirm_verify_message(ctx, address, msg.message)
return Success(message='Message verified')
return Success(message="Message verified")

View File

@ -1,50 +1,68 @@
from trezor.wire import register, protobuf_workflow
from trezor.messages.MessageType import \
LoadDevice, ResetDevice, BackupDevice, WipeDevice, RecoveryDevice, ApplySettings, ApplyFlags, ChangePin, SetU2FCounter
from trezor.messages.MessageType import (
ApplyFlags,
ApplySettings,
BackupDevice,
ChangePin,
LoadDevice,
RecoveryDevice,
ResetDevice,
SetU2FCounter,
WipeDevice,
)
from trezor.wire import protobuf_workflow, register
def dispatch_LoadDevice(*args, **kwargs):
from .load_device import load_device
return load_device(*args, **kwargs)
def dispatch_ResetDevice(*args, **kwargs):
from .reset_device import reset_device
return reset_device(*args, **kwargs)
def dispatch_BackupDevice(*args, **kwargs):
from .backup_device import backup_device
return backup_device(*args, **kwargs)
def dispatch_WipeDevice(*args, **kwargs):
from .wipe_device import wipe_device
return wipe_device(*args, **kwargs)
def dispatch_RecoveryDevice(*args, **kwargs):
from .recovery_device import recovery_device
return recovery_device(*args, **kwargs)
def dispatch_ApplySettings(*args, **kwargs):
from .apply_settings import apply_settings
return apply_settings(*args, **kwargs)
def dispatch_ApplyFlags(*args, **kwargs):
from .apply_flags import apply_flags
return apply_flags(*args, **kwargs)
def dispatch_ChangePin(*args, **kwargs):
from .change_pin import change_pin
return change_pin(*args, **kwargs)
def dispatch_SetU2FCounter(*args, **kwargs):
from .set_u2f_counter import set_u2f_counter
return set_u2f_counter(*args, **kwargs)

View File

@ -1,7 +1,8 @@
from trezor.messages.Success import Success
from apps.common import storage
async def apply_flags(ctx, msg):
storage.set_flags(msg.flags)
return Success(message='Flags applied')
return Success(message="Flags applied")

View File

@ -2,17 +2,23 @@ from trezor import ui, wire
from trezor.messages import ButtonRequestType, PassphraseSourceType
from trezor.messages.Success import Success
from trezor.ui.text import Text
from apps.common import storage
from apps.common.confirm import require_confirm
async def apply_settings(ctx, msg):
if msg.homescreen is None and msg.label is None and msg.use_passphrase is None and msg.passphrase_source is None:
raise wire.ProcessError('No setting provided')
if (
msg.homescreen is None
and msg.label is None
and msg.use_passphrase is None
and msg.passphrase_source is None
):
raise wire.ProcessError("No setting provided")
if msg.homescreen is not None:
if len(msg.homescreen) > storage.HOMESCREEN_MAXSIZE:
raise wire.DataError('Homescreen is too complex')
raise wire.DataError("Homescreen is too complex")
await require_confirm_change_homescreen(ctx)
if msg.label is not None:
@ -24,43 +30,45 @@ async def apply_settings(ctx, msg):
if msg.passphrase_source is not None:
await require_confirm_change_passphrase_source(ctx, msg.passphrase_source)
storage.load_settings(label=msg.label,
use_passphrase=msg.use_passphrase,
homescreen=msg.homescreen,
passphrase_source=msg.passphrase_source)
storage.load_settings(
label=msg.label,
use_passphrase=msg.use_passphrase,
homescreen=msg.homescreen,
passphrase_source=msg.passphrase_source,
)
return Success(message='Settings applied')
return Success(message="Settings applied")
async def require_confirm_change_homescreen(ctx):
text = Text('Change homescreen', ui.ICON_CONFIG)
text.normal('Do you really want to', 'change homescreen?')
text = Text("Change homescreen", ui.ICON_CONFIG)
text.normal("Do you really want to", "change homescreen?")
await require_confirm(ctx, text, code=ButtonRequestType.ProtectCall)
async def require_confirm_change_label(ctx, label):
text = Text('Change label', ui.ICON_CONFIG)
text.normal('Do you really want to', 'change label to')
text.bold('%s?' % label)
text = Text("Change label", ui.ICON_CONFIG)
text.normal("Do you really want to", "change label to")
text.bold("%s?" % label)
await require_confirm(ctx, text, code=ButtonRequestType.ProtectCall)
async def require_confirm_change_passphrase(ctx, use):
text = Text('Enable passphrase' if use else 'Disable passphrase', ui.ICON_CONFIG)
text.normal('Do you really want to')
text.normal('enable passphrase' if use else 'disable passphrase')
text.normal('encryption?')
text = Text("Enable passphrase" if use else "Disable passphrase", ui.ICON_CONFIG)
text.normal("Do you really want to")
text.normal("enable passphrase" if use else "disable passphrase")
text.normal("encryption?")
await require_confirm(ctx, text, code=ButtonRequestType.ProtectCall)
async def require_confirm_change_passphrase_source(ctx, source):
if source == PassphraseSourceType.DEVICE:
desc = 'ON DEVICE'
desc = "ON DEVICE"
elif source == PassphraseSourceType.HOST:
desc = 'ON HOST'
desc = "ON HOST"
else:
desc = 'ASK'
text = Text('Passphrase source', ui.ICON_CONFIG)
text.normal('Do you really want to', 'change the passphrase', 'source to')
text.bold('ALWAYS %s?' % desc)
desc = "ASK"
text = Text("Passphrase source", ui.ICON_CONFIG)
text.normal("Do you really want to", "change the passphrase", "source to")
text.bold("ALWAYS %s?" % desc)
await require_confirm(ctx, text, code=ButtonRequestType.ProtectCall)

View File

@ -1,5 +1,6 @@
from trezor import wire
from trezor.messages.Success import Success
from apps.common import storage
from apps.management.reset_device import (
check_mnemonic,
@ -11,9 +12,9 @@ from apps.management.reset_device import (
async def backup_device(ctx, msg):
if not storage.is_initialized():
raise wire.ProcessError('Device is not initialized')
raise wire.ProcessError("Device is not initialized")
if not storage.needs_backup():
raise wire.ProcessError('Seed already backed up')
raise wire.ProcessError("Seed already backed up")
mnemonic = storage.get_mnemonic()
@ -32,4 +33,4 @@ async def backup_device(ctx, msg):
storage.set_unfinished_backup(False)
return Success(message='Seed successfully backed up')
return Success(message="Seed successfully backed up")

View File

@ -1,10 +1,10 @@
from trezor import config, loop, ui, wire
from trezor.messages import MessageType
from trezor.messages import ButtonRequestType
from trezor.messages import ButtonRequestType, MessageType
from trezor.messages.ButtonRequest import ButtonRequest
from trezor.messages.Success import Success
from trezor.pin import pin_to_int, show_pin_timeout
from trezor.ui.text import Text
from apps.common.confirm import require_confirm
from apps.common.request_pin import PinCancelled, request_pin
@ -18,52 +18,52 @@ async def change_pin(ctx, msg):
if config.has_pin():
curpin = await request_pin_ack(ctx)
if not config.check_pin(pin_to_int(curpin), show_pin_timeout):
raise wire.PinInvalid('PIN invalid')
raise wire.PinInvalid("PIN invalid")
else:
curpin = ''
curpin = ""
# get new pin
if not msg.remove:
newpin = await request_pin_confirm(ctx)
else:
newpin = ''
newpin = ""
# write into storage
if not config.change_pin(pin_to_int(curpin), pin_to_int(newpin), show_pin_timeout):
raise wire.PinInvalid('PIN invalid')
raise wire.PinInvalid("PIN invalid")
if newpin:
return Success(message='PIN changed')
return Success(message="PIN changed")
else:
return Success(message='PIN removed')
return Success(message="PIN removed")
def require_confirm_change_pin(ctx, msg):
has_pin = config.has_pin()
if msg.remove and has_pin: # removing pin
text = Text('Remove PIN', ui.ICON_CONFIG)
text.normal('Do you really want to')
text.bold('remove current PIN?')
text = Text("Remove PIN", ui.ICON_CONFIG)
text.normal("Do you really want to")
text.bold("remove current PIN?")
return require_confirm(ctx, text)
if not msg.remove and has_pin: # changing pin
text = Text('Remove PIN', ui.ICON_CONFIG)
text.normal('Do you really want to')
text.bold('change current PIN?')
text = Text("Remove PIN", ui.ICON_CONFIG)
text.normal("Do you really want to")
text.bold("change current PIN?")
return require_confirm(ctx, text)
if not msg.remove and not has_pin: # setting new pin
text = Text('Remove PIN', ui.ICON_CONFIG)
text.normal('Do you really want to')
text.bold('set new PIN?')
text = Text("Remove PIN", ui.ICON_CONFIG)
text.normal("Do you really want to")
text.bold("set new PIN?")
return require_confirm(ctx, text)
async def request_pin_confirm(ctx, *args, **kwargs):
while True:
pin1 = await request_pin_ack(ctx, 'Enter new PIN', *args, **kwargs)
pin2 = await request_pin_ack(ctx, 'Re-enter new PIN', *args, **kwargs)
pin1 = await request_pin_ack(ctx, "Enter new PIN", *args, **kwargs)
pin2 = await request_pin_ack(ctx, "Re-enter new PIN", *args, **kwargs)
if pin1 == pin2:
return pin1
await pin_mismatch()
@ -71,17 +71,19 @@ async def request_pin_confirm(ctx, *args, **kwargs):
async def request_pin_ack(ctx, *args, **kwargs):
try:
await ctx.call(ButtonRequest(code=ButtonRequestType.Other), MessageType.ButtonAck)
await ctx.call(
ButtonRequest(code=ButtonRequestType.Other), MessageType.ButtonAck
)
return await ctx.wait(request_pin(*args, **kwargs))
except PinCancelled:
raise wire.ActionCancelled('Cancelled')
raise wire.ActionCancelled("Cancelled")
@ui.layout
async def pin_mismatch():
text = Text('PIN mismatch', ui.ICON_WRONG, icon_color=ui.RED)
text.normal('Entered PINs do not', 'match each other.')
text.normal('')
text.normal('Please, try again...')
text = Text("PIN mismatch", ui.ICON_WRONG, icon_color=ui.RED)
text.normal("Entered PINs do not", "match each other.")
text.normal("")
text.normal("Please, try again...")
text.render()
await loop.sleep(3 * 1000 * 1000)

View File

@ -1,8 +1,9 @@
from trezor import config, ui, wire
from trezor import config, wire
from trezor.crypto import bip39
from trezor.messages.Success import Success
from trezor.pin import pin_to_int
from trezor.ui.text import Text
from apps.common import storage
from apps.common.confirm import require_confirm
@ -10,24 +11,22 @@ from apps.common.confirm import require_confirm
async def load_device(ctx, msg):
if storage.is_initialized():
raise wire.UnexpectedMessage('Already initialized')
raise wire.UnexpectedMessage("Already initialized")
if msg.node is not None:
raise wire.ProcessError('LoadDevice.node is not supported')
raise wire.ProcessError("LoadDevice.node is not supported")
if not msg.skip_checksum and not bip39.check(msg.mnemonic):
raise wire.ProcessError('Mnemonic is not valid')
raise wire.ProcessError("Mnemonic is not valid")
text = Text('Loading seed')
text.bold('Loading private seed', 'is not recommended.')
text.normal('Continue only if you', 'know what you are doing!')
text = Text("Loading seed")
text.bold("Loading private seed", "is not recommended.")
text.normal("Continue only if you", "know what you are doing!")
await require_confirm(ctx, text)
storage.load_mnemonic(
mnemonic=msg.mnemonic, needs_backup=True)
storage.load_settings(
use_passphrase=msg.passphrase_protection, label=msg.label)
storage.load_mnemonic(mnemonic=msg.mnemonic, needs_backup=True)
storage.load_settings(use_passphrase=msg.passphrase_protection, label=msg.label)
if msg.pin:
config.change_pin(pin_to_int(''), pin_to_int(msg.pin), None)
config.change_pin(pin_to_int(""), pin_to_int(msg.pin), None)
return Success(message='Device loaded')
return Success(message="Device loaded")

View File

@ -1,20 +1,21 @@
from trezor import config, ui, wire
from trezor.crypto import bip39
from trezor.messages.ButtonRequest import ButtonRequest
from trezor.messages.ButtonRequestType import MnemonicWordCount, MnemonicInput
from trezor.messages.Success import Success
from trezor.messages.ButtonRequestType import MnemonicInput, MnemonicWordCount
from trezor.messages.MessageType import ButtonAck
from trezor.messages.Success import Success
from trezor.pin import pin_to_int
from trezor.ui.mnemonic import MnemonicKeyboard
from trezor.ui.text import Text
from trezor.ui.word_select import WordSelector
from trezor.utils import format_ordinal
from apps.common import storage
from apps.management.change_pin import request_pin_confirm
async def recovery_device(ctx, msg):
'''
"""
Recover BIP39 seed into empty device.
1. Ask for the number of words in recovered seed.
@ -22,9 +23,9 @@ async def recovery_device(ctx, msg):
3. Optionally check the seed validity.
4. Optionally ask for the PIN, with confirmation.
5. Save into storage.
'''
"""
if not msg.dry_run and storage.is_initialized():
raise wire.UnexpectedMessage('Already initialized')
raise wire.UnexpectedMessage("Already initialized")
# ask for the number of words
wordcount = await request_wordcount(ctx)
@ -35,7 +36,7 @@ async def recovery_device(ctx, msg):
# check mnemonic validity
if msg.enforce_wordlist or msg.dry_run:
if not bip39.check(mnemonic):
raise wire.ProcessError('Mnemonic is not valid')
raise wire.ProcessError("Mnemonic is not valid")
# ask for pin repeatedly
if msg.pin_protection:
@ -44,25 +45,27 @@ async def recovery_device(ctx, msg):
# save into storage
if not msg.dry_run:
if msg.pin_protection:
config.change_pin(pin_to_int(''), pin_to_int(newpin), None)
storage.load_settings(
label=msg.label, use_passphrase=msg.passphrase_protection)
storage.load_mnemonic(
mnemonic=mnemonic, needs_backup=False)
return Success(message='Device recovered')
config.change_pin(pin_to_int(""), pin_to_int(newpin), None)
storage.load_settings(label=msg.label, use_passphrase=msg.passphrase_protection)
storage.load_mnemonic(mnemonic=mnemonic, needs_backup=False)
return Success(message="Device recovered")
else:
if storage.get_mnemonic() == mnemonic:
return Success(message='The seed is valid and matches the one in the device')
return Success(
message="The seed is valid and matches the one in the device"
)
else:
raise wire.ProcessError('The seed is valid but does not match the one in the device')
raise wire.ProcessError(
"The seed is valid but does not match the one in the device"
)
@ui.layout
async def request_wordcount(ctx):
await ctx.call(ButtonRequest(code=MnemonicWordCount), ButtonAck)
text = Text('Device recovery', ui.ICON_RECOVERY)
text.normal('Number of words?')
text = Text("Device recovery", ui.ICON_RECOVERY)
text.normal("Number of words?")
count = await ctx.wait(WordSelector(text))
return count
@ -75,8 +78,8 @@ async def request_mnemonic(ctx, count: int) -> str:
words = []
board = MnemonicKeyboard()
for i in range(count):
board.prompt = 'Type the %s word:' % format_ordinal(i + 1)
board.prompt = "Type the %s word:" % format_ordinal(i + 1)
word = await ctx.wait(board)
words.append(word)
return ' '.join(words)
return " ".join(words)

View File

@ -1,18 +1,19 @@
from micropython import const
from trezor import config, ui, wire
from ubinascii import hexlify
from trezor import config, ui, wire, workflow
from trezor.crypto import bip39, hashlib, random
from trezor.messages import ButtonRequestType, MessageType
from trezor.messages.ButtonRequest import ButtonRequest
from trezor.messages.EntropyRequest import EntropyRequest
from trezor.messages.Success import Success
from trezor import workflow
from trezor.pin import pin_to_int
from trezor.ui.confirm import HoldToConfirmDialog
from trezor.ui.mnemonic import MnemonicKeyboard
from trezor.ui.scroll import Scrollpage, animate_swipe, paginate
from trezor.ui.text import Text
from trezor.utils import chunks, format_ordinal
from ubinascii import hexlify
from apps.common import storage
from apps.common.confirm import require_confirm
from apps.management.change_pin import request_pin_confirm
@ -24,15 +25,15 @@ if __debug__:
async def reset_device(ctx, msg):
# validate parameters and device state
if msg.strength not in (128, 192, 256):
raise wire.ProcessError('Invalid strength (has to be 128, 192 or 256 bits)')
raise wire.ProcessError("Invalid strength (has to be 128, 192 or 256 bits)")
if storage.is_initialized():
raise wire.UnexpectedMessage('Already initialized')
raise wire.UnexpectedMessage("Already initialized")
# request new PIN
if msg.pin_protection:
newpin = await request_pin_confirm(ctx)
else:
newpin = ''
newpin = ""
# generate and display internal entropy
internal_ent = random.bytes(32)
@ -57,14 +58,12 @@ async def reset_device(ctx, msg):
await show_wrong_entry(ctx)
# write PIN into storage
if not config.change_pin(pin_to_int(''), pin_to_int(newpin), None):
raise wire.ProcessError('Could not change PIN')
if not config.change_pin(pin_to_int(""), pin_to_int(newpin), None):
raise wire.ProcessError("Could not change PIN")
# write settings and mnemonic into storage
storage.load_settings(
label=msg.label, use_passphrase=msg.passphrase_protection)
storage.load_mnemonic(
mnemonic=mnemonic, needs_backup=msg.skip_backup)
storage.load_settings(label=msg.label, use_passphrase=msg.passphrase_protection)
storage.load_mnemonic(mnemonic=mnemonic, needs_backup=msg.skip_backup)
# show success message. if we skipped backup, it's possible that homescreen
# is still running, uninterrupted. restart it to pick up new label.
@ -73,78 +72,64 @@ async def reset_device(ctx, msg):
else:
workflow.restartdefault()
return Success(message='Initialized')
return Success(message="Initialized")
def generate_mnemonic(strength: int,
int_entropy: bytes,
ext_entropy: bytes) -> bytes:
def generate_mnemonic(strength: int, int_entropy: bytes, ext_entropy: bytes) -> bytes:
ehash = hashlib.sha256()
ehash.update(int_entropy)
ehash.update(ext_entropy)
entropy = ehash.digest()
mnemonic = bip39.from_data(entropy[:strength // 8])
mnemonic = bip39.from_data(entropy[: strength // 8])
return mnemonic
async def show_warning(ctx):
text = Text('Backup your seed', ui.ICON_NOCOPY)
text = Text("Backup your seed", ui.ICON_NOCOPY)
text.normal(
'Never make a digital',
'copy of your recovery',
'seed and never upload',
'it online!')
"Never make a digital",
"copy of your recovery",
"seed and never upload",
"it online!",
)
await require_confirm(
ctx,
text,
ButtonRequestType.ResetDevice,
confirm='I understand',
cancel=None)
ctx, text, ButtonRequestType.ResetDevice, confirm="I understand", cancel=None
)
async def show_wrong_entry(ctx):
text = Text('Wrong entry!', ui.ICON_WRONG, icon_color=ui.RED)
text.normal(
'You have entered',
'wrong seed word.',
'Please check again.')
text = Text("Wrong entry!", ui.ICON_WRONG, icon_color=ui.RED)
text.normal("You have entered", "wrong seed word.", "Please check again.")
await require_confirm(
ctx,
text,
ButtonRequestType.ResetDevice,
confirm='Check again',
cancel=None)
ctx, text, ButtonRequestType.ResetDevice, confirm="Check again", cancel=None
)
async def show_success(ctx):
text = Text('Backup is done!', ui.ICON_CONFIRM, icon_color=ui.GREEN)
text = Text("Backup is done!", ui.ICON_CONFIRM, icon_color=ui.GREEN)
text.normal(
'Never make a digital',
'copy of your recovery',
'seed and never upload',
'it online!')
"Never make a digital",
"copy of your recovery",
"seed and never upload",
"it online!",
)
await require_confirm(
ctx,
text,
ButtonRequestType.ResetDevice,
confirm='Finish setup',
cancel=None)
ctx, text, ButtonRequestType.ResetDevice, confirm="Finish setup", cancel=None
)
async def show_entropy(ctx, entropy: bytes):
entropy_str = hexlify(entropy).decode()
lines = chunks(entropy_str, 16)
text = Text('Internal entropy', ui.ICON_RESET)
text = Text("Internal entropy", ui.ICON_RESET)
text.mono(*lines)
await require_confirm(
ctx,
text,
ButtonRequestType.ResetDevice)
await require_confirm(ctx, text, ButtonRequestType.ResetDevice)
async def show_mnemonic(ctx, mnemonic: str):
await ctx.call(
ButtonRequest(code=ButtonRequestType.ResetDevice), MessageType.ButtonAck)
ButtonRequest(code=ButtonRequestType.ResetDevice), MessageType.ButtonAck
)
first_page = const(0)
words_per_page = const(4)
words = list(enumerate(mnemonic.split()))
@ -158,8 +143,8 @@ async def show_mnemonic_page(page: int, page_count: int, pages: list):
if __debug__:
debug.reset_current_words = [word for _, word in pages[page]]
lines = ['%2d. %s' % (wi + 1, word) for wi, word in pages[page]]
text = Text('Recovery seed', ui.ICON_RESET)
lines = ["%2d. %s" % (wi + 1, word) for wi, word in pages[page]]
text = Text("Recovery seed", ui.ICON_RESET)
text.mono(*lines)
content = Scrollpage(text, page, page_count)
@ -191,6 +176,6 @@ async def check_word(ctx, words: list, index: int):
if __debug__:
debug.reset_word_index = index
keyboard = MnemonicKeyboard('Type the %s word:' % format_ordinal(index + 1))
keyboard = MnemonicKeyboard("Type the %s word:" % format_ordinal(index + 1))
result = await ctx.wait(keyboard)
return result == words[index]

View File

@ -2,19 +2,20 @@ from trezor import ui, wire
from trezor.messages import ButtonRequestType
from trezor.messages.Success import Success
from trezor.ui.text import Text
from apps.common import storage
from apps.common.confirm import require_confirm
async def set_u2f_counter(ctx, msg):
if msg.u2f_counter is None:
raise wire.ProcessError('No value provided')
raise wire.ProcessError("No value provided")
text = Text('Set U2F counter', ui.ICON_CONFIG)
text.normal('Do you really want to', 'set the U2F counter')
text.bold('to %d?' % msg.u2f_counter)
text = Text("Set U2F counter", ui.ICON_CONFIG)
text.normal("Do you really want to", "set the U2F counter")
text.bold("to %d?" % msg.u2f_counter)
await require_confirm(ctx, text, code=ButtonRequestType.ProtectCall)
storage.set_u2f_counter(msg.u2f_counter)
return Success(message='U2F counter set')
return Success(message="U2F counter set")

View File

@ -2,21 +2,25 @@ from trezor import ui
from trezor.messages import ButtonRequestType
from trezor.messages.Success import Success
from trezor.ui.text import Text
from apps.common import storage
from apps.common.confirm import require_hold_to_confirm
async def wipe_device(ctx, msg):
text = Text('Wipe device', ui.ICON_WIPE, icon_color=ui.RED)
text.normal('Do you really want to', 'wipe the device?', '')
text.bold('All data will be lost.')
text = Text("Wipe device", ui.ICON_WIPE, icon_color=ui.RED)
text.normal("Do you really want to", "wipe the device?", "")
text.bold("All data will be lost.")
await require_hold_to_confirm(ctx, text,
await require_hold_to_confirm(
ctx,
text,
code=ButtonRequestType.WipeDevice,
button_style=ui.BTN_CANCEL,
loader_style=ui.LDR_DANGER)
loader_style=ui.LDR_DANGER,
)
storage.wipe()
return Success(message='Device wiped')
return Success(message="Device wiped")

View File

@ -1,14 +1,16 @@
from trezor.wire import register, protobuf_workflow
from trezor.messages.MessageType import NEMGetAddress, NEMSignTx
from trezor.wire import protobuf_workflow, register
def dispatch_NemGetAddress(*args, **kwargs):
from .get_address import get_address
return get_address(*args, **kwargs)
def dispatch_NemSignTx(*args, **kwargs):
from .signing import sign_tx
return sign_tx(*args, **kwargs)

View File

@ -3,14 +3,14 @@ from trezor.messages import ButtonRequestType
from trezor.messages.NEMAddress import NEMAddress
from trezor.ui.text import Text
from .helpers import NEM_CURVE, get_network_str
from .layout import split_address
from .validators import validate_network
from apps.common import seed
from apps.common.confirm import confirm
from apps.common.display_address import show_qr
from .layout import split_address
from .helpers import get_network_str, NEM_CURVE
from .validators import validate_network
async def get_address(ctx, msg):
network = validate_network(msg.network)
@ -30,7 +30,9 @@ async def get_address(ctx, msg):
async def _show_address(ctx, address: str, network: int):
lines = split_address(address)
text = Text('Confirm address', ui.ICON_RECEIVE, icon_color=ui.GREEN)
text.normal('%s network' % get_network_str(network))
text = Text("Confirm address", ui.ICON_RECEIVE, icon_color=ui.GREEN)
text.normal("%s network" % get_network_str(network))
text.mono(*lines)
return await confirm(ctx, text, code=ButtonRequestType.Address, cancel='QR', cancel_style=ui.BTN_KEY)
return await confirm(
ctx, text, code=ButtonRequestType.Address, cancel="QR", cancel_style=ui.BTN_KEY
)

View File

@ -3,7 +3,7 @@ from micropython import const
NEM_NETWORK_MAINNET = const(0x68)
NEM_NETWORK_TESTNET = const(0x98)
NEM_NETWORK_MIJIN = const(0x60)
NEM_CURVE = 'ed25519-keccak'
NEM_CURVE = "ed25519-keccak"
NEM_TRANSACTION_TYPE_TRANSFER = const(0x0101)
NEM_TRANSACTION_TYPE_IMPORTANCE_TRANSFER = const(0x0801)
@ -19,7 +19,7 @@ NEM_MAX_SUPPLY = const(9000000000)
NEM_SALT_SIZE = const(32)
AES_BLOCK_SIZE = const(16)
NEM_HASH_ALG = 'keccak'
NEM_HASH_ALG = "keccak"
NEM_PUBLIC_KEY_SIZE = const(32) # ed25519 public key
NEM_LEVY_PERCENTILE_DIVISOR_ABSOLUTE = const(10000)
NEM_MOSAIC_AMOUNT_DIVISOR = const(1000000)
@ -30,8 +30,8 @@ NEM_MAX_ENCRYPTED_PAYLOAD_SIZE = const(960)
def get_network_str(network: int) -> str:
if network == NEM_NETWORK_MAINNET:
return 'Mainnet'
return "Mainnet"
elif network == NEM_NETWORK_TESTNET:
return 'Testnet'
return "Testnet"
elif network == NEM_NETWORK_MIJIN:
return 'Mijin'
return "Mijin"

View File

@ -3,22 +3,24 @@ from trezor.messages import ButtonRequestType
from trezor.ui.text import Text
from trezor.utils import chunks, format_amount, split_words
from apps.common.confirm import require_confirm, require_hold_to_confirm
from .helpers import NEM_MAX_DIVISIBILITY
from apps.common.confirm import require_confirm, require_hold_to_confirm
async def require_confirm_text(ctx, action: str):
words = split_words(action, 18)
await require_confirm_content(ctx, 'Confirm action', words)
await require_confirm_content(ctx, "Confirm action", words)
async def require_confirm_fee(ctx, action: str, fee: int):
content = (
ui.NORMAL, action,
ui.BOLD, '%s XEM' % format_amount(fee, NEM_MAX_DIVISIBILITY),
ui.NORMAL,
action,
ui.BOLD,
"%s XEM" % format_amount(fee, NEM_MAX_DIVISIBILITY),
)
await require_confirm_content(ctx, 'Confirm fee', content)
await require_confirm_content(ctx, "Confirm fee", content)
async def require_confirm_content(ctx, headline: str, content: list):
@ -28,10 +30,10 @@ async def require_confirm_content(ctx, headline: str, content: list):
async def require_confirm_final(ctx, fee: int):
text = Text('Final confirm', ui.ICON_SEND, icon_color=ui.GREEN)
text.normal('Sign this transaction')
text.bold('and pay %s XEM' % format_amount(fee, NEM_MAX_DIVISIBILITY))
text.normal('for network fee?')
text = Text("Final confirm", ui.ICON_SEND, icon_color=ui.GREEN)
text.normal("Sign this transaction")
text.bold("and pay %s XEM" % format_amount(fee, NEM_MAX_DIVISIBILITY))
text.normal("for network fee?")
# we use SignTx, not ConfirmOutput, for compatibility with T1
await require_hold_to_confirm(ctx, text, ButtonRequestType.SignTx)
@ -42,5 +44,5 @@ def split_address(address: str):
def trim(payload: str, length: int) -> str:
if len(payload) > length:
return payload[:length] + '..'
return payload[:length] + ".."
return payload

View File

@ -1,15 +1,19 @@
from trezor.messages.NEMTransactionCommon import NEMTransactionCommon
from trezor.messages.NEMMosaicCreation import NEMMosaicCreation
from trezor.messages.NEMMosaicSupplyChange import NEMMosaicSupplyChange
from trezor.messages.NEMTransactionCommon import NEMTransactionCommon
from . import layout, serialize
async def mosaic_creation(ctx, public_key: bytes, common: NEMTransactionCommon, creation: NEMMosaicCreation) -> bytearray:
async def mosaic_creation(
ctx, public_key: bytes, common: NEMTransactionCommon, creation: NEMMosaicCreation
) -> bytearray:
await layout.ask_mosaic_creation(ctx, common, creation)
return serialize.serialize_mosaic_creation(common, creation, public_key)
async def supply_change(ctx, public_key: bytes, common: NEMTransactionCommon, change: NEMMosaicSupplyChange) -> bytearray:
async def supply_change(
ctx, public_key: bytes, common: NEMTransactionCommon, change: NEMMosaicSupplyChange
) -> bytearray:
await layout.ask_supply_change(ctx, common, change)
return serialize.serialize_mosaic_supply_change(common, change, public_key)

View File

@ -1,51 +1,78 @@
from micropython import const
from trezor import ui
from trezor.messages import (NEMMosaicCreation, NEMMosaicDefinition,
NEMMosaicLevy, NEMMosaicSupplyChange,
NEMSupplyChangeType, NEMTransactionCommon)
from trezor.messages import (
NEMMosaicCreation,
NEMMosaicDefinition,
NEMMosaicLevy,
NEMMosaicSupplyChange,
NEMSupplyChangeType,
NEMTransactionCommon,
)
from trezor.ui.confirm import ConfirmDialog
from trezor.ui.scroll import Scrollpage, animate_swipe, paginate
from trezor.ui.text import Text
from trezor.utils import split_words
from ..layout import (require_confirm_content, require_confirm_fee,
require_confirm_final, require_confirm_text,
split_address, trim)
from ..layout import (
require_confirm_content,
require_confirm_fee,
require_confirm_final,
require_confirm_text,
split_address,
trim,
)
async def ask_mosaic_creation(ctx, common: NEMTransactionCommon, creation: NEMMosaicCreation):
await require_confirm_content(ctx, 'Create mosaic', _creation_message(creation))
async def ask_mosaic_creation(
ctx, common: NEMTransactionCommon, creation: NEMMosaicCreation
):
await require_confirm_content(ctx, "Create mosaic", _creation_message(creation))
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)
async def ask_supply_change(ctx, common: NEMTransactionCommon, change: NEMMosaicSupplyChange):
await require_confirm_content(ctx, 'Supply change', _supply_message(change))
async def ask_supply_change(
ctx, common: NEMTransactionCommon, change: NEMMosaicSupplyChange
):
await require_confirm_content(ctx, "Supply change", _supply_message(change))
if change.type == NEMSupplyChangeType.SupplyChange_Decrease:
msg = 'Decrease supply by ' + str(change.delta) + ' whole units?'
msg = "Decrease supply by " + str(change.delta) + " whole units?"
elif change.type == NEMSupplyChangeType.SupplyChange_Increase:
msg = 'Increase supply by ' + str(change.delta) + ' whole units?'
msg = "Increase supply by " + str(change.delta) + " whole units?"
else:
raise ValueError('Invalid supply change type')
raise ValueError("Invalid supply change type")
await require_confirm_text(ctx, msg)
await require_confirm_final(ctx, common.fee)
def _creation_message(mosaic_creation):
return [ui.NORMAL, 'Create mosaic',
ui.BOLD, mosaic_creation.definition.mosaic,
ui.NORMAL, 'under namespace',
ui.BOLD, mosaic_creation.definition.namespace]
return [
ui.NORMAL,
"Create mosaic",
ui.BOLD,
mosaic_creation.definition.mosaic,
ui.NORMAL,
"under namespace",
ui.BOLD,
mosaic_creation.definition.namespace,
]
def _supply_message(supply_change):
return [ui.NORMAL, 'Modify supply for',
ui.BOLD, supply_change.mosaic,
ui.NORMAL, 'under namespace',
ui.BOLD, supply_change.namespace]
return [
ui.NORMAL,
"Modify supply for",
ui.BOLD,
supply_change.mosaic,
ui.NORMAL,
"under namespace",
ui.BOLD,
supply_change.namespace,
]
async def _require_confirm_properties(ctx, definition: NEMMosaicDefinition):
@ -70,64 +97,64 @@ def _get_mosaic_properties(definition: NEMMosaicDefinition):
# description
if definition.description:
t = Text('Confirm properties', ui.ICON_SEND)
t.bold('Description:')
t = Text("Confirm properties", ui.ICON_SEND)
t.bold("Description:")
t.normal(*split_words(trim(definition.description, 70), 22))
properties.append(t)
# transferable
if definition.transferable:
transferable = 'Yes'
transferable = "Yes"
else:
transferable = 'No'
t = Text('Confirm properties', ui.ICON_SEND)
t.bold('Transferable?')
transferable = "No"
t = Text("Confirm properties", ui.ICON_SEND)
t.bold("Transferable?")
t.normal(transferable)
properties.append(t)
# mutable_supply
if definition.mutable_supply:
imm = 'mutable'
imm = "mutable"
else:
imm = 'immutable'
imm = "immutable"
if definition.supply:
t = Text('Confirm properties', ui.ICON_SEND)
t.bold('Initial supply:')
t = Text("Confirm properties", ui.ICON_SEND)
t.bold("Initial supply:")
t.normal(str(definition.supply), imm)
else:
t = Text('Confirm properties', ui.ICON_SEND)
t.bold('Initial supply:')
t = Text("Confirm properties", ui.ICON_SEND)
t.bold("Initial supply:")
t.normal(imm)
properties.append(t)
# levy
if definition.levy:
t = Text('Confirm properties', ui.ICON_SEND)
t.bold('Levy recipient:')
t = Text("Confirm properties", ui.ICON_SEND)
t.bold("Levy recipient:")
t.mono(*split_address(definition.levy_address))
properties.append(t)
t = Text('Confirm properties', ui.ICON_SEND)
t.bold('Levy fee:')
t = Text("Confirm properties", ui.ICON_SEND)
t.bold("Levy fee:")
t.normal(str(definition.fee))
t.bold('Levy divisibility:')
t.bold("Levy divisibility:")
t.normal(str(definition.divisibility))
properties.append(t)
t = Text('Confirm properties', ui.ICON_SEND)
t.bold('Levy namespace:')
t = Text("Confirm properties", ui.ICON_SEND)
t.bold("Levy namespace:")
t.normal(definition.levy_namespace)
t.bold('Levy mosaic:')
t.bold("Levy mosaic:")
t.normal(definition.levy_mosaic)
properties.append(t)
if definition.levy == NEMMosaicLevy.MosaicLevy_Absolute:
levy_type = 'absolute'
levy_type = "absolute"
else:
levy_type = 'percentile'
t = Text('Confirm properties', ui.ICON_SEND)
t.bold('Levy type:')
levy_type = "percentile"
t = Text("Confirm properties", ui.ICON_SEND)
t.bold("Levy type:")
t.normal(levy_type)
properties.append(t)

View File

@ -2,36 +2,58 @@ from trezor.messages.NEMMosaicCreation import NEMMosaicCreation
from trezor.messages.NEMMosaicSupplyChange import NEMMosaicSupplyChange
from trezor.messages.NEMTransactionCommon import NEMTransactionCommon
from ..helpers import (NEM_TRANSACTION_TYPE_MOSAIC_CREATION,
NEM_TRANSACTION_TYPE_MOSAIC_SUPPLY_CHANGE)
from ..writers import (write_bytes_with_length, write_common, write_uint32,
write_uint64)
from ..helpers import (
NEM_TRANSACTION_TYPE_MOSAIC_CREATION,
NEM_TRANSACTION_TYPE_MOSAIC_SUPPLY_CHANGE,
)
from ..writers import write_bytes_with_length, write_common, write_uint32, write_uint64
def serialize_mosaic_creation(common: NEMTransactionCommon, creation: NEMMosaicCreation, public_key: bytes):
w = write_common(common, bytearray(public_key), NEM_TRANSACTION_TYPE_MOSAIC_CREATION)
def serialize_mosaic_creation(
common: NEMTransactionCommon, creation: NEMMosaicCreation, public_key: bytes
):
w = write_common(
common, bytearray(public_key), NEM_TRANSACTION_TYPE_MOSAIC_CREATION
)
mosaics_w = bytearray()
write_bytes_with_length(mosaics_w, bytearray(public_key))
identifier_length = 4 + len(creation.definition.namespace) + 4 + len(creation.definition.mosaic)
identifier_length = (
4 + len(creation.definition.namespace) + 4 + len(creation.definition.mosaic)
)
write_uint32(mosaics_w, identifier_length)
write_bytes_with_length(mosaics_w, bytearray(creation.definition.namespace))
write_bytes_with_length(mosaics_w, bytearray(creation.definition.mosaic))
write_bytes_with_length(mosaics_w, bytearray(creation.definition.description))
write_uint32(mosaics_w, 4) # number of properties
_write_property(mosaics_w, 'divisibility', creation.definition.divisibility)
_write_property(mosaics_w, 'initialSupply', creation.definition.supply)
_write_property(mosaics_w, 'supplyMutable', creation.definition.mutable_supply)
_write_property(mosaics_w, 'transferable', creation.definition.transferable)
_write_property(mosaics_w, "divisibility", creation.definition.divisibility)
_write_property(mosaics_w, "initialSupply", creation.definition.supply)
_write_property(mosaics_w, "supplyMutable", creation.definition.mutable_supply)
_write_property(mosaics_w, "transferable", creation.definition.transferable)
if creation.definition.levy:
levy_identifier_length = 4 + len(creation.definition.levy_namespace) + 4 + len(creation.definition.levy_mosaic)
write_uint32(mosaics_w, 4 + 4 + len(creation.definition.levy_address) + 4 + levy_identifier_length + 8)
levy_identifier_length = (
4
+ len(creation.definition.levy_namespace)
+ 4
+ len(creation.definition.levy_mosaic)
)
write_uint32(
mosaics_w,
4
+ 4
+ len(creation.definition.levy_address)
+ 4
+ levy_identifier_length
+ 8,
)
write_uint32(mosaics_w, creation.definition.levy)
write_bytes_with_length(mosaics_w, bytearray(creation.definition.levy_address))
write_uint32(mosaics_w, levy_identifier_length)
write_bytes_with_length(mosaics_w, bytearray(creation.definition.levy_namespace))
write_bytes_with_length(
mosaics_w, bytearray(creation.definition.levy_namespace)
)
write_bytes_with_length(mosaics_w, bytearray(creation.definition.levy_mosaic))
write_uint64(mosaics_w, creation.definition.fee)
else:
@ -46,8 +68,12 @@ def serialize_mosaic_creation(common: NEMTransactionCommon, creation: NEMMosaicC
return w
def serialize_mosaic_supply_change(common: NEMTransactionCommon, change: NEMMosaicSupplyChange, public_key: bytes):
w = write_common(common, bytearray(public_key), NEM_TRANSACTION_TYPE_MOSAIC_SUPPLY_CHANGE)
def serialize_mosaic_supply_change(
common: NEMTransactionCommon, change: NEMMosaicSupplyChange, public_key: bytes
):
w = write_common(
common, bytearray(public_key), NEM_TRANSACTION_TYPE_MOSAIC_SUPPLY_CHANGE
)
identifier_length = 4 + len(change.namespace) + 4 + len(change.mosaic)
write_uint32(w, identifier_length)
@ -61,19 +87,19 @@ def serialize_mosaic_supply_change(common: NEMTransactionCommon, change: NEMMosa
def _write_property(w: bytearray, name: str, value):
if value is None:
if name in ('divisibility', 'initialSupply'):
if name in ("divisibility", "initialSupply"):
value = 0
elif name in ('supplyMutable', 'transferable'):
elif name in ("supplyMutable", "transferable"):
value = False
if type(value) == bool:
if value:
value = 'true'
value = "true"
else:
value = 'false'
value = "false"
elif type(value) == int:
value = str(value)
elif type(value) != str:
raise ValueError('Incompatible value type')
raise ValueError("Incompatible value type")
write_uint32(w, 4 + len(name) + 4 + len(value))
write_bytes_with_length(w, bytearray(name))
write_bytes_with_length(w, bytearray(value))

View File

@ -13,15 +13,19 @@ def initiate(public_key, common: NEMTransactionCommon, inner_tx: bytes) -> bytes
return serialize.serialize_multisig(common, public_key, inner_tx)
def cosign(public_key, common: NEMTransactionCommon, inner_tx: bytes, signer: bytes) -> bytes:
def cosign(
public_key, common: NEMTransactionCommon, inner_tx: bytes, signer: bytes
) -> bytes:
return serialize.serialize_multisig_signature(common, public_key, inner_tx, signer)
async def aggregate_modification(ctx,
public_key: bytes,
common: NEMTransactionCommon,
aggr: NEMAggregateModification,
multisig: bool):
async def aggregate_modification(
ctx,
public_key: bytes,
common: NEMTransactionCommon,
aggr: NEMAggregateModification,
multisig: bool,
):
await layout.ask_aggregate_modification(ctx, common, aggr, multisig)
w = serialize.serialize_aggregate_modification(common, aggr, public_key)

View File

@ -1,48 +1,58 @@
from trezor import ui
from trezor.crypto import nem
from trezor.messages import (ButtonRequestType, NEMAggregateModification,
NEMModificationType, NEMSignTx,
NEMTransactionCommon)
from trezor.messages import (
ButtonRequestType,
NEMAggregateModification,
NEMModificationType,
NEMSignTx,
NEMTransactionCommon,
)
from trezor.ui.text import Text
from ..layout import (require_confirm, require_confirm_fee,
require_confirm_final, require_confirm_text,
split_address)
from ..layout import (
require_confirm,
require_confirm_fee,
require_confirm_final,
require_confirm_text,
split_address,
)
async def ask_multisig(ctx, msg: NEMSignTx):
address = nem.compute_address(msg.multisig.signer, msg.transaction.network)
if msg.cosigning:
await _require_confirm_address(ctx, 'Cosign transaction for', address)
await _require_confirm_address(ctx, "Cosign transaction for", address)
else:
await _require_confirm_address(ctx, 'Initiate transaction for', address)
await require_confirm_fee(ctx, 'Confirm multisig fee', msg.transaction.fee)
await _require_confirm_address(ctx, "Initiate transaction for", address)
await require_confirm_fee(ctx, "Confirm multisig fee", msg.transaction.fee)
async def ask_aggregate_modification(ctx, common: NEMTransactionCommon, mod: NEMAggregateModification, multisig: bool):
async def ask_aggregate_modification(
ctx, common: NEMTransactionCommon, mod: NEMAggregateModification, multisig: bool
):
if not multisig:
await require_confirm_text(ctx, 'Convert account to multisig account?')
await require_confirm_text(ctx, "Convert account to multisig account?")
for m in mod.modifications:
if m.type == NEMModificationType.CosignatoryModification_Add:
action = 'Add'
action = "Add"
else:
action = 'Remove'
action = "Remove"
address = nem.compute_address(m.public_key, common.network)
await _require_confirm_address(ctx, action + ' cosignatory', address)
await _require_confirm_address(ctx, action + " cosignatory", address)
if mod.relative_change:
if multisig:
action = 'Modify the number of cosignatories by '
action = "Modify the number of cosignatories by "
else:
action = 'Set minimum cosignatories to '
await require_confirm_text(ctx, action + str(mod.relative_change) + '?')
action = "Set minimum cosignatories to "
await require_confirm_text(ctx, action + str(mod.relative_change) + "?")
await require_confirm_final(ctx, common.fee)
async def _require_confirm_address(ctx, action: str, address: str):
text = Text('Confirm address', ui.ICON_SEND, icon_color=ui.GREEN)
text = Text("Confirm address", ui.ICON_SEND, icon_color=ui.GREEN)
text.normal(action)
text.mono(*split_address(address))
await require_confirm(ctx, text, ButtonRequestType.ConfirmOutput)

View File

@ -2,9 +2,11 @@ from trezor.crypto import hashlib, nem
from trezor.messages.NEMAggregateModification import NEMAggregateModification
from trezor.messages.NEMTransactionCommon import NEMTransactionCommon
from ..helpers import (NEM_TRANSACTION_TYPE_AGGREGATE_MODIFICATION,
NEM_TRANSACTION_TYPE_MULTISIG,
NEM_TRANSACTION_TYPE_MULTISIG_SIGNATURE)
from ..helpers import (
NEM_TRANSACTION_TYPE_AGGREGATE_MODIFICATION,
NEM_TRANSACTION_TYPE_MULTISIG,
NEM_TRANSACTION_TYPE_MULTISIG_SIGNATURE,
)
from ..writers import write_bytes_with_length, write_common, write_uint32
@ -14,10 +16,16 @@ def serialize_multisig(common: NEMTransactionCommon, public_key: bytes, inner: b
return w
def serialize_multisig_signature(common: NEMTransactionCommon, public_key: bytes,
inner: bytes, address_public_key: bytes):
def serialize_multisig_signature(
common: NEMTransactionCommon,
public_key: bytes,
inner: bytes,
address_public_key: bytes,
):
address = nem.compute_address(address_public_key, common.network)
w = write_common(common, bytearray(public_key), NEM_TRANSACTION_TYPE_MULTISIG_SIGNATURE)
w = write_common(
common, bytearray(public_key), NEM_TRANSACTION_TYPE_MULTISIG_SIGNATURE
)
digest = hashlib.sha3_256(inner).digest(True)
write_uint32(w, 4 + len(digest))
@ -26,20 +34,26 @@ def serialize_multisig_signature(common: NEMTransactionCommon, public_key: bytes
return w
def serialize_aggregate_modification(common: NEMTransactionCommon, mod: NEMAggregateModification, public_key: bytes):
def serialize_aggregate_modification(
common: NEMTransactionCommon, mod: NEMAggregateModification, public_key: bytes
):
version = common.network << 24 | 1
if mod.relative_change:
version = common.network << 24 | 2
w = write_common(common,
bytearray(public_key),
NEM_TRANSACTION_TYPE_AGGREGATE_MODIFICATION,
version)
w = write_common(
common,
bytearray(public_key),
NEM_TRANSACTION_TYPE_AGGREGATE_MODIFICATION,
version,
)
write_uint32(w, len(mod.modifications))
return w
def serialize_cosignatory_modification(w: bytearray, type: int, cosignatory_pubkey: bytes):
def serialize_cosignatory_modification(
w: bytearray, type: int, cosignatory_pubkey: bytes
):
write_uint32(w, 4 + 4 + len(cosignatory_pubkey))
write_uint32(w, type)
write_bytes_with_length(w, bytearray(cosignatory_pubkey))

View File

@ -1,9 +1,14 @@
from trezor.messages.NEMTransactionCommon import NEMTransactionCommon
from trezor.messages.NEMProvisionNamespace import NEMProvisionNamespace
from trezor.messages.NEMTransactionCommon import NEMTransactionCommon
from . import layout, serialize
async def namespace(ctx, public_key: bytes, common: NEMTransactionCommon, namespace: NEMProvisionNamespace) -> bytearray:
async def namespace(
ctx,
public_key: bytes,
common: NEMTransactionCommon,
namespace: NEMProvisionNamespace,
) -> bytearray:
await layout.ask_provision_namespace(ctx, common, namespace)
return serialize.serialize_provision_namespace(common, namespace, public_key)

View File

@ -1,22 +1,28 @@
from trezor import ui
from trezor.messages import NEMProvisionNamespace, NEMTransactionCommon
from ..layout import (require_confirm_content, require_confirm_fee,
require_confirm_final)
from ..layout import require_confirm_content, require_confirm_fee, require_confirm_final
async def ask_provision_namespace(ctx, common: NEMTransactionCommon, namespace: NEMProvisionNamespace):
async def ask_provision_namespace(
ctx, common: NEMTransactionCommon, namespace: NEMProvisionNamespace
):
if namespace.parent:
content = (ui.NORMAL, 'Create namespace',
ui.BOLD, namespace.namespace,
ui.NORMAL, 'under namespace',
ui.BOLD, namespace.parent)
await require_confirm_content(ctx, 'Confirm namespace', content)
content = (
ui.NORMAL,
"Create namespace",
ui.BOLD,
namespace.namespace,
ui.NORMAL,
"under namespace",
ui.BOLD,
namespace.parent,
)
await require_confirm_content(ctx, "Confirm namespace", content)
else:
content = (ui.NORMAL, 'Create namespace',
ui.BOLD, namespace.namespace)
await require_confirm_content(ctx, 'Confirm namespace', content)
content = (ui.NORMAL, "Create namespace", ui.BOLD, namespace.namespace)
await require_confirm_content(ctx, "Confirm namespace", content)
await require_confirm_fee(ctx, 'Confirm rental fee', namespace.fee)
await require_confirm_fee(ctx, "Confirm rental fee", namespace.fee)
await require_confirm_final(ctx, common.fee)

View File

@ -5,10 +5,12 @@ from ..helpers import NEM_TRANSACTION_TYPE_PROVISION_NAMESPACE
from ..writers import write_bytes_with_length, write_common, write_uint32, write_uint64
def serialize_provision_namespace(common: NEMTransactionCommon, namespace: NEMProvisionNamespace, public_key: bytes) -> bytearray:
tx = write_common(common,
bytearray(public_key),
NEM_TRANSACTION_TYPE_PROVISION_NAMESPACE)
def serialize_provision_namespace(
common: NEMTransactionCommon, namespace: NEMProvisionNamespace, public_key: bytes
) -> bytearray:
tx = write_common(
common, bytearray(public_key), NEM_TRANSACTION_TYPE_PROVISION_NAMESPACE
)
write_bytes_with_length(tx, bytearray(namespace.sink))
write_uint64(tx, namespace.fee)

View File

@ -1,12 +1,13 @@
from trezor.crypto.curve import ed25519
from trezor.messages.NEMSignedTx import NEMSignedTx
from trezor.messages.NEMSignTx import NEMSignTx
from apps.common import seed
from . import mosaic, multisig, namespace, transfer
from .helpers import NEM_CURVE, NEM_HASH_ALG
from .validators import validate
from apps.common import seed
async def sign_tx(ctx, msg: NEMSignTx):
validate(msg)
@ -29,16 +30,26 @@ async def sign_tx(ctx, msg: NEMSignTx):
elif msg.supply_change:
tx = await mosaic.supply_change(ctx, public_key, common, msg.supply_change)
elif msg.aggregate_modification:
tx = await multisig.aggregate_modification(ctx, public_key, common, msg.aggregate_modification, msg.multisig is not None)
tx = await multisig.aggregate_modification(
ctx,
public_key,
common,
msg.aggregate_modification,
msg.multisig is not None,
)
elif msg.importance_transfer:
tx = await transfer.importance_transfer(ctx, public_key, common, msg.importance_transfer)
tx = await transfer.importance_transfer(
ctx, public_key, common, msg.importance_transfer
)
else:
raise ValueError('No transaction provided')
raise ValueError("No transaction provided")
if msg.multisig:
# wrap transaction in multisig wrapper
if msg.cosigning:
tx = multisig.cosign(_get_public_key(node), msg.transaction, tx, msg.multisig.signer)
tx = multisig.cosign(
_get_public_key(node), msg.transaction, tx, msg.multisig.signer
)
else:
tx = multisig.initiate(_get_public_key(node), msg.transaction, tx)

View File

@ -1,11 +1,13 @@
from trezor.messages.NEMTransfer import NEMTransfer
from trezor.messages.NEMTransactionCommon import NEMTransactionCommon
from trezor.messages.NEMImportanceTransfer import NEMImportanceTransfer
from trezor.messages.NEMTransactionCommon import NEMTransactionCommon
from trezor.messages.NEMTransfer import NEMTransfer
from . import layout, serialize
async def transfer(ctx, public_key: bytes, common: NEMTransactionCommon, transfer: NEMTransfer, node):
async def transfer(
ctx, public_key: bytes, common: NEMTransactionCommon, transfer: NEMTransfer, node
):
transfer.mosaics = serialize.canonicalize_mosaics(transfer.mosaics)
payload, encrypted = serialize.get_transfer_payload(transfer, node)
@ -17,6 +19,8 @@ async def transfer(ctx, public_key: bytes, common: NEMTransactionCommon, transfe
return w
async def importance_transfer(ctx, public_key: bytes, common: NEMTransactionCommon, imp: NEMImportanceTransfer):
async def importance_transfer(
ctx, public_key: bytes, common: NEMTransactionCommon, imp: NEMImportanceTransfer
):
await layout.ask_importance_transfer(ctx, common, imp)
return serialize.serialize_importance_transfer(common, imp, public_key)

View File

@ -1,19 +1,34 @@
from trezor import ui
from trezor.messages import (ButtonRequestType, NEMImportanceTransfer,
NEMImportanceTransferMode, NEMMosaic,
NEMMosaicLevy, NEMTransactionCommon, NEMTransfer)
from trezor.messages import (
ButtonRequestType,
NEMImportanceTransfer,
NEMImportanceTransferMode,
NEMMosaic,
NEMMosaicLevy,
NEMTransactionCommon,
NEMTransfer,
)
from trezor.ui.text import Text
from trezor.utils import format_amount, split_words
from apps.common.confirm import require_confirm
from ..helpers import (NEM_LEVY_PERCENTILE_DIVISOR_ABSOLUTE,
NEM_MAX_DIVISIBILITY, NEM_MOSAIC_AMOUNT_DIVISOR)
from ..helpers import (
NEM_LEVY_PERCENTILE_DIVISOR_ABSOLUTE,
NEM_MAX_DIVISIBILITY,
NEM_MOSAIC_AMOUNT_DIVISOR,
)
from ..layout import require_confirm_final, require_confirm_text, split_address
from ..mosaic.helpers import get_mosaic_definition, is_nem_xem_mosaic
from apps.common.confirm import require_confirm
async def ask_transfer(ctx, common: NEMTransactionCommon, transfer: NEMTransfer, payload: bytes, encrypted: bool):
async def ask_transfer(
ctx,
common: NEMTransactionCommon,
transfer: NEMTransfer,
payload: bytes,
encrypted: bool,
):
if payload:
await _require_confirm_payload(ctx, transfer.payload, encrypted)
for mosaic in transfer.mosaics:
@ -22,7 +37,9 @@ async def ask_transfer(ctx, common: NEMTransactionCommon, transfer: NEMTransfer,
await require_confirm_final(ctx, common.fee)
async def ask_transfer_mosaic(ctx, common: NEMTransactionCommon, transfer: NEMTransfer, mosaic: NEMMosaic):
async def ask_transfer_mosaic(
ctx, common: NEMTransactionCommon, transfer: NEMTransfer, mosaic: NEMMosaic
):
if is_nem_xem_mosaic(mosaic.namespace, mosaic.mosaic):
return
@ -30,31 +47,38 @@ async def ask_transfer_mosaic(ctx, common: NEMTransactionCommon, transfer: NEMTr
mosaic_quantity = mosaic.quantity * transfer.amount / NEM_MOSAIC_AMOUNT_DIVISOR
if definition:
msg = Text('Confirm mosaic', ui.ICON_SEND, icon_color=ui.GREEN)
msg.normal('Confirm transfer of')
msg.bold(format_amount(mosaic_quantity, definition['divisibility']) + definition['ticker'])
msg.normal('of')
msg.bold(definition['name'])
msg = Text("Confirm mosaic", ui.ICON_SEND, icon_color=ui.GREEN)
msg.normal("Confirm transfer of")
msg.bold(
format_amount(mosaic_quantity, definition["divisibility"])
+ definition["ticker"]
)
msg.normal("of")
msg.bold(definition["name"])
await require_confirm(ctx, msg, ButtonRequestType.ConfirmOutput)
if 'levy' in definition and 'fee' in definition:
if "levy" in definition and "fee" in definition:
levy_msg = _get_levy_msg(definition, mosaic_quantity, common.network)
msg = Text('Confirm mosaic', ui.ICON_SEND, icon_color=ui.GREEN)
msg.normal('Confirm mosaic', 'levy fee of')
msg = Text("Confirm mosaic", ui.ICON_SEND, icon_color=ui.GREEN)
msg.normal("Confirm mosaic", "levy fee of")
msg.bold(levy_msg)
await require_confirm(ctx, msg, ButtonRequestType.ConfirmOutput)
else:
msg = Text('Confirm mosaic', ui.ICON_SEND, icon_color=ui.RED)
msg.bold('Unknown mosaic!')
msg.normal(*split_words('Divisibility and levy cannot be shown for unknown mosaics', 22))
msg = Text("Confirm mosaic", ui.ICON_SEND, icon_color=ui.RED)
msg.bold("Unknown mosaic!")
msg.normal(
*split_words(
"Divisibility and levy cannot be shown for unknown mosaics", 22
)
)
await require_confirm(ctx, msg, ButtonRequestType.ConfirmOutput)
msg = Text('Confirm mosaic', ui.ICON_SEND, icon_color=ui.GREEN)
msg.normal('Confirm transfer of')
msg.bold('%s raw units' % mosaic_quantity)
msg.normal('of')
msg.bold('%s.%s' % (mosaic.namespace, mosaic.mosaic))
msg = Text("Confirm mosaic", ui.ICON_SEND, icon_color=ui.GREEN)
msg.normal("Confirm transfer of")
msg.bold("%s raw units" % mosaic_quantity)
msg.normal("of")
msg.bold("%s.%s" % (mosaic.namespace, mosaic.mosaic))
await require_confirm(ctx, msg, ButtonRequestType.ConfirmOutput)
@ -72,47 +96,50 @@ def _get_xem_amount(transfer: NEMTransfer):
def _get_levy_msg(mosaic_definition, quantity: int, network: int) -> str:
levy_definition = get_mosaic_definition(
mosaic_definition['levy_namespace'],
mosaic_definition['levy_mosaic'],
network)
if mosaic_definition['levy'] == NEMMosaicLevy.MosaicLevy_Absolute:
levy_fee = mosaic_definition['fee']
mosaic_definition["levy_namespace"], mosaic_definition["levy_mosaic"], network
)
if mosaic_definition["levy"] == NEMMosaicLevy.MosaicLevy_Absolute:
levy_fee = mosaic_definition["fee"]
else:
levy_fee = quantity * mosaic_definition['fee'] / NEM_LEVY_PERCENTILE_DIVISOR_ABSOLUTE
return format_amount(
levy_fee,
levy_definition['divisibility']
) + levy_definition['ticker']
levy_fee = (
quantity * mosaic_definition["fee"] / NEM_LEVY_PERCENTILE_DIVISOR_ABSOLUTE
)
return (
format_amount(levy_fee, levy_definition["divisibility"])
+ levy_definition["ticker"]
)
async def ask_importance_transfer(ctx, common: NEMTransactionCommon, imp: NEMImportanceTransfer):
async def ask_importance_transfer(
ctx, common: NEMTransactionCommon, imp: NEMImportanceTransfer
):
if imp.mode == NEMImportanceTransferMode.ImportanceTransfer_Activate:
m = 'Activate'
m = "Activate"
else:
m = 'Deactivate'
await require_confirm_text(ctx, m + ' remote harvesting?')
m = "Deactivate"
await require_confirm_text(ctx, m + " remote harvesting?")
await require_confirm_final(ctx, common.fee)
async def _require_confirm_transfer(ctx, recipient, value):
text = Text('Confirm transfer', ui.ICON_SEND, icon_color=ui.GREEN)
text.bold('Send %s XEM' % format_amount(value, NEM_MAX_DIVISIBILITY))
text.normal('to')
text = Text("Confirm transfer", ui.ICON_SEND, icon_color=ui.GREEN)
text.bold("Send %s XEM" % format_amount(value, NEM_MAX_DIVISIBILITY))
text.normal("to")
text.mono(*split_address(recipient))
await require_confirm(ctx, text, ButtonRequestType.ConfirmOutput)
async def _require_confirm_payload(ctx, payload: bytes, encrypt=False):
payload = str(payload, 'utf-8')
payload = str(payload, "utf-8")
if len(payload) > 48:
payload = payload[:48] + '..'
payload = payload[:48] + ".."
if encrypt:
text = Text('Confirm payload', ui.ICON_SEND, icon_color=ui.GREEN)
text.bold('Encrypted:')
text = Text("Confirm payload", ui.ICON_SEND, icon_color=ui.GREEN)
text.bold("Encrypted:")
text.normal(*split_words(payload, 22))
else:
text = Text('Confirm payload', ui.ICON_SEND, icon_color=ui.RED)
text.bold('Unencrypted:')
text = Text("Confirm payload", ui.ICON_SEND, icon_color=ui.RED)
text.bold("Unencrypted:")
text.normal(*split_words(payload, 22))
await require_confirm(ctx, text, ButtonRequestType.ConfirmOutput)

View File

@ -4,20 +4,28 @@ from trezor.messages.NEMMosaic import NEMMosaic
from trezor.messages.NEMTransactionCommon import NEMTransactionCommon
from trezor.messages.NEMTransfer import NEMTransfer
from ..helpers import (AES_BLOCK_SIZE, NEM_SALT_SIZE,
NEM_TRANSACTION_TYPE_IMPORTANCE_TRANSFER,
NEM_TRANSACTION_TYPE_TRANSFER)
from ..helpers import (
AES_BLOCK_SIZE,
NEM_SALT_SIZE,
NEM_TRANSACTION_TYPE_IMPORTANCE_TRANSFER,
NEM_TRANSACTION_TYPE_TRANSFER,
)
from ..writers import write_bytes_with_length, write_common, write_uint32, write_uint64
def serialize_transfer(common: NEMTransactionCommon,
transfer: NEMTransfer,
public_key: bytes,
payload: bytes = None,
encrypted: bool = False) -> bytearray:
tx = write_common(common, bytearray(public_key),
NEM_TRANSACTION_TYPE_TRANSFER,
_get_version(common.network, transfer.mosaics))
def serialize_transfer(
common: NEMTransactionCommon,
transfer: NEMTransfer,
public_key: bytes,
payload: bytes = None,
encrypted: bool = False,
) -> bytearray:
tx = write_common(
common,
bytearray(public_key),
NEM_TRANSACTION_TYPE_TRANSFER,
_get_version(common.network, transfer.mosaics),
)
write_bytes_with_length(tx, bytearray(transfer.recipient))
write_uint64(tx, transfer.amount)
@ -49,11 +57,12 @@ def serialize_mosaic(w: bytearray, namespace: str, mosaic: str, quantity: int):
write_uint64(w, quantity)
def serialize_importance_transfer(common: NEMTransactionCommon,
imp: NEMImportanceTransfer,
public_key: bytes) -> bytearray:
w = write_common(common, bytearray(public_key),
NEM_TRANSACTION_TYPE_IMPORTANCE_TRANSFER)
def serialize_importance_transfer(
common: NEMTransactionCommon, imp: NEMImportanceTransfer, public_key: bytes
) -> bytearray:
w = write_common(
common, bytearray(public_key), NEM_TRANSACTION_TYPE_IMPORTANCE_TRANSFER
)
write_uint32(w, imp.mode)
write_bytes_with_length(w, bytearray(imp.public_key))
@ -65,7 +74,7 @@ def get_transfer_payload(transfer: NEMTransfer, node) -> [bytes, bool]:
encrypted = False
if transfer.public_key is not None:
if payload is None:
raise ValueError('Public key provided but no payload to encrypt')
raise ValueError("Public key provided but no payload to encrypt")
payload = _encrypt(node, transfer.public_key, transfer.payload)
encrypted = True

View File

@ -1,23 +1,32 @@
from trezor.crypto import nem
from trezor.messages import NEMModificationType, NEMSupplyChangeType
from trezor.messages.NEMSignTx import (
NEMAggregateModification,
NEMImportanceTransfer,
NEMMosaicCreation,
NEMMosaicSupplyChange,
NEMProvisionNamespace,
NEMSignTx,
NEMTransactionCommon,
NEMTransfer,
)
from trezor.wire import ProcessError
from trezor.messages import NEMModificationType
from trezor.messages import NEMSupplyChangeType
from trezor.messages.NEMSignTx import (NEMAggregateModification,
NEMImportanceTransfer,
NEMMosaicCreation,
NEMMosaicSupplyChange,
NEMProvisionNamespace, NEMSignTx,
NEMTransactionCommon, NEMTransfer)
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)
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,
)
def validate(msg: NEMSignTx):
if msg.transaction is None:
raise ProcessError('No common provided')
raise ProcessError("No common provided")
_validate_single_tx(msg)
_validate_common(msg.transaction)
@ -26,7 +35,7 @@ def validate(msg: NEMSignTx):
_validate_common(msg.multisig, True)
_validate_multisig(msg.multisig, msg.transaction.network)
if not msg.multisig and msg.cosigning:
raise ProcessError('No multisig transaction to cosign')
raise ProcessError("No multisig transaction to cosign")
if msg.transfer:
_validate_transfer(msg.transfer, msg.transaction.network)
@ -37,7 +46,9 @@ def validate(msg: NEMSignTx):
if msg.supply_change:
_validate_supply_change(msg.supply_change)
if msg.aggregate_modification:
_validate_aggregate_modification(msg.aggregate_modification, msg.multisig is None)
_validate_aggregate_modification(
msg.aggregate_modification, msg.multisig is None
)
if msg.importance_transfer:
_validate_importance_transfer(msg.importance_transfer)
@ -46,23 +57,24 @@ def validate_network(network: int) -> int:
if network is None:
return NEM_NETWORK_MAINNET
if network not in (NEM_NETWORK_MAINNET, NEM_NETWORK_TESTNET, NEM_NETWORK_MIJIN):
raise ProcessError('Invalid NEM network')
raise ProcessError("Invalid NEM network")
return network
def _validate_single_tx(msg: NEMSignTx):
# ensure exactly one transaction is provided
tx_count = \
bool(msg.transfer) + \
bool(msg.provision_namespace) + \
bool(msg.mosaic_creation) + \
bool(msg.supply_change) + \
bool(msg.aggregate_modification) + \
bool(msg.importance_transfer)
tx_count = (
bool(msg.transfer)
+ bool(msg.provision_namespace)
+ bool(msg.mosaic_creation)
+ bool(msg.supply_change)
+ bool(msg.aggregate_modification)
+ bool(msg.importance_transfer)
)
if tx_count == 0:
raise ProcessError('No transaction provided')
raise ProcessError("No transaction provided")
if tx_count > 1:
raise ProcessError('More than one transaction provided')
raise ProcessError("More than one transaction provided")
def _validate_common(common: NEMTransactionCommon, inner: bool = False):
@ -70,174 +82,196 @@ def _validate_common(common: NEMTransactionCommon, inner: bool = False):
err = None
if common.timestamp is None:
err = 'timestamp'
err = "timestamp"
if common.fee is None:
err = 'fee'
err = "fee"
if common.deadline is None:
err = 'deadline'
err = "deadline"
if not inner and common.signer:
raise ProcessError('Signer not allowed in outer transaction')
raise ProcessError("Signer not allowed in outer transaction")
if inner and common.signer is None:
err = 'signer'
err = "signer"
if err:
if inner:
raise ProcessError('No %s provided in inner transaction' % err)
raise ProcessError("No %s provided in inner transaction" % err)
else:
raise ProcessError('No %s provided' % err)
raise ProcessError("No %s provided" % err)
if common.signer is not None:
_validate_public_key(common.signer, 'Invalid signer public key in inner transaction')
_validate_public_key(
common.signer, "Invalid signer public key in inner transaction"
)
def _validate_public_key(public_key: bytes, err_msg: str):
if not public_key:
raise ProcessError('%s (none provided)' % err_msg)
raise ProcessError("%s (none provided)" % err_msg)
if len(public_key) != NEM_PUBLIC_KEY_SIZE:
raise ProcessError('%s (invalid length)' % err_msg)
raise ProcessError("%s (invalid length)" % err_msg)
def _validate_importance_transfer(importance_transfer: NEMImportanceTransfer):
if importance_transfer.mode is None:
raise ProcessError('No mode provided')
_validate_public_key(importance_transfer.public_key, 'Invalid remote account public key provided')
raise ProcessError("No mode provided")
_validate_public_key(
importance_transfer.public_key, "Invalid remote account public key provided"
)
def _validate_multisig(multisig: NEMTransactionCommon, network: int):
if multisig.network != network:
raise ProcessError('Inner transaction network is different')
_validate_public_key(multisig.signer, 'Invalid multisig signer public key provided')
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):
aggregate_modification: NEMAggregateModification, creation: bool = False
):
if creation and not aggregate_modification.modifications:
raise ProcessError('No modifications provided')
raise ProcessError("No modifications provided")
for m in aggregate_modification.modifications:
if not m.type:
raise ProcessError('No modification type provided')
raise ProcessError("No modification type provided")
if m.type not in (
NEMModificationType.CosignatoryModification_Add,
NEMModificationType.CosignatoryModification_Delete
NEMModificationType.CosignatoryModification_Delete,
):
raise ProcessError('Unknown aggregate modification')
raise ProcessError("Unknown aggregate modification")
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')
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):
if supply_change.namespace is None:
raise ProcessError('No namespace provided')
raise ProcessError("No namespace provided")
if supply_change.mosaic is None:
raise ProcessError('No mosaic provided')
raise ProcessError("No mosaic provided")
if supply_change.type is None:
raise ProcessError('No type provided')
elif supply_change.type not in [NEMSupplyChangeType.SupplyChange_Decrease, NEMSupplyChangeType.SupplyChange_Increase]:
raise ProcessError('Invalid supply change type')
raise ProcessError("No type provided")
elif supply_change.type not in [
NEMSupplyChangeType.SupplyChange_Decrease,
NEMSupplyChangeType.SupplyChange_Increase,
]:
raise ProcessError("Invalid supply change type")
if supply_change.delta is None:
raise ProcessError('No delta provided')
raise ProcessError("No delta provided")
def _validate_mosaic_creation(mosaic_creation: NEMMosaicCreation, network: int):
if mosaic_creation.definition is None:
raise ProcessError('No mosaic definition provided')
raise ProcessError("No mosaic definition provided")
if mosaic_creation.sink is None:
raise ProcessError('No creation sink provided')
raise ProcessError("No creation sink provided")
if mosaic_creation.fee is None:
raise ProcessError('No creation sink fee provided')
raise ProcessError("No creation sink fee provided")
if not nem.validate_address(mosaic_creation.sink, network):
raise ProcessError('Invalid creation sink address')
raise ProcessError("Invalid creation sink address")
if mosaic_creation.definition.name is not None:
raise ProcessError('Name not allowed in mosaic creation transactions')
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')
raise ProcessError("Ticker not allowed in mosaic creation transactions")
if mosaic_creation.definition.networks:
raise ProcessError('Networks not allowed in mosaic creation transactions')
raise ProcessError("Networks not allowed in mosaic creation transactions")
if mosaic_creation.definition.namespace is None:
raise ProcessError('No mosaic namespace provided')
raise ProcessError("No mosaic namespace provided")
if mosaic_creation.definition.mosaic is None:
raise ProcessError('No mosaic name provided')
raise ProcessError("No mosaic name provided")
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.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')
raise ProcessError("No levy fee provided")
if mosaic_creation.definition.levy_address is None:
raise ProcessError('No levy address provided')
raise ProcessError("No levy address provided")
if mosaic_creation.definition.levy_namespace is None:
raise ProcessError('No levy namespace provided')
raise ProcessError("No levy namespace provided")
if mosaic_creation.definition.levy_mosaic is None:
raise ProcessError('No levy mosaic name provided')
raise ProcessError("No levy mosaic name provided")
if mosaic_creation.definition.divisibility is None:
raise ProcessError('No divisibility provided')
raise ProcessError("No divisibility provided")
if mosaic_creation.definition.supply is None:
raise ProcessError('No supply provided')
raise ProcessError("No supply provided")
if mosaic_creation.definition.mutable_supply is None:
raise ProcessError('No supply mutability provided')
raise ProcessError("No supply mutability provided")
if mosaic_creation.definition.transferable is None:
raise ProcessError('No mosaic transferability provided')
raise ProcessError("No mosaic transferability provided")
if mosaic_creation.definition.description is None:
raise ProcessError('No description provided')
raise ProcessError("No description provided")
if mosaic_creation.definition.divisibility > NEM_MAX_DIVISIBILITY:
raise ProcessError('Invalid divisibility provided')
raise ProcessError("Invalid divisibility provided")
if mosaic_creation.definition.supply > NEM_MAX_SUPPLY:
raise ProcessError('Invalid supply provided')
raise ProcessError("Invalid supply provided")
if not nem.validate_address(mosaic_creation.definition.levy_address, network):
raise ProcessError('Invalid levy address')
raise ProcessError("Invalid levy address")
def _validate_provision_namespace(provision_namespace: NEMProvisionNamespace, network: int):
def _validate_provision_namespace(
provision_namespace: NEMProvisionNamespace, network: int
):
if provision_namespace.namespace is None:
raise ProcessError('No namespace provided')
raise ProcessError("No namespace provided")
if provision_namespace.sink is None:
raise ProcessError('No rental sink provided')
raise ProcessError("No rental sink provided")
if provision_namespace.fee is None:
raise ProcessError('No rental sink fee provided')
raise ProcessError("No rental sink fee provided")
if not nem.validate_address(provision_namespace.sink, network):
raise ProcessError('Invalid rental sink address')
raise ProcessError("Invalid rental sink address")
def _validate_transfer(transfer: NEMTransfer, network: int):
if transfer.recipient is None:
raise ProcessError('No recipient provided')
raise ProcessError("No recipient provided")
if transfer.amount is None:
raise ProcessError('No amount provided')
raise ProcessError("No amount provided")
if transfer.public_key is not None:
_validate_public_key(transfer.public_key, 'Invalid recipient public key')
_validate_public_key(transfer.public_key, "Invalid recipient public key")
if transfer.payload is None:
raise ProcessError('Public key provided but no payload to encrypt')
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')
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')
raise ProcessError("Invalid recipient address")
for m in transfer.mosaics:
if m.namespace is None:
raise ProcessError('No mosaic namespace provided')
raise ProcessError("No mosaic namespace provided")
if m.mosaic is None:
raise ProcessError('No mosaic name provided')
raise ProcessError("No mosaic name provided")
if m.quantity is None:
raise ProcessError('No mosaic quantity provided')
raise ProcessError("No mosaic quantity provided")

View File

@ -28,10 +28,12 @@ def write_bytes_with_length(w, buf: bytearray):
write_bytes(w, buf)
def write_common(common: NEMTransactionCommon,
public_key: bytearray,
transaction_type: int,
version: int = None) -> bytearray:
def write_common(
common: NEMTransactionCommon,
public_key: bytearray,
transaction_type: int,
version: int = None,
) -> bytearray:
ret = bytearray()
write_uint32(ret, transaction_type)

View File

@ -1,56 +1,68 @@
from trezor.wire import register, protobuf_workflow
from trezor.messages.MessageType import \
GetPublicKey, GetAddress, \
GetEntropy, \
SignTx, \
SignMessage, VerifyMessage, \
SignIdentity, \
GetECDHSessionKey, \
CipherKeyValue
from trezor.messages.MessageType import (
CipherKeyValue,
GetAddress,
GetECDHSessionKey,
GetEntropy,
GetPublicKey,
SignIdentity,
SignMessage,
SignTx,
VerifyMessage,
)
from trezor.wire import protobuf_workflow, register
def dispatch_GetPublicKey(*args, **kwargs):
from .get_public_key import get_public_key
return get_public_key(*args, **kwargs)
def dispatch_GetAddress(*args, **kwargs):
from .get_address import get_address
return get_address(*args, **kwargs)
def dispatch_GetEntropy(*args, **kwargs):
from .get_entropy import get_entropy
return get_entropy(*args, **kwargs)
def dispatch_SignTx(*args, **kwargs):
from .sign_tx import sign_tx
return sign_tx(*args, **kwargs)
def dispatch_SignMessage(*args, **kwargs):
from .sign_message import sign_message
return sign_message(*args, **kwargs)
def dispatch_VerifyMessage(*args, **kwargs):
from .verify_message import verify_message
return verify_message(*args, **kwargs)
def dispatch_SignIdentity(*args, **kwargs):
from .sign_identity import sign_identity
return sign_identity(*args, **kwargs)
def dispatch_GetECDHSessionKey(*args, **kwargs):
from .ecdh import get_ecdh_session_key
return get_ecdh_session_key(*args, **kwargs)
def dispatch_CipherKeyValue(*args, **kwargs):
from .cipher_key_value import cipher_key_value
return cipher_key_value(*args, **kwargs)

View File

@ -1,25 +1,25 @@
from trezor import ui, wire
from trezor import wire
from trezor.crypto import hmac
from trezor.crypto.aes import AES_CBC_Decrypt, AES_CBC_Encrypt
from trezor.crypto.hashlib import sha512
from trezor.messages.CipheredKeyValue import CipheredKeyValue
from trezor.ui.text import Text, TEXT_MARGIN_LEFT
from trezor.utils import split_words
from trezor.ui.text import Text
from apps.common import seed
from apps.common.confirm import require_confirm
async def cipher_key_value(ctx, msg):
if len(msg.value) % 16 > 0:
raise wire.DataError('Value length must be a multiple of 16')
raise wire.DataError("Value length must be a multiple of 16")
encrypt = msg.encrypt
decrypt = not msg.encrypt
if (encrypt and msg.ask_on_encrypt) or (decrypt and msg.ask_on_decrypt):
if encrypt:
title = 'Encrypt value'
title = "Encrypt value"
else:
title = 'Decrypt value'
title = "Decrypt value"
text = Text(title)
text.normal(msg.key)
await require_confirm(ctx, text)
@ -31,8 +31,8 @@ async def cipher_key_value(ctx, msg):
def compute_cipher_key_value(msg, seckey: bytes) -> bytes:
data = msg.key
data += 'E1' if msg.ask_on_encrypt else 'E0'
data += 'D1' if msg.ask_on_decrypt else 'D0'
data += "E1" if msg.ask_on_encrypt else "E0"
data += "D1" if msg.ask_on_decrypt else "D0"
data = hmac.new(seckey, data, sha512).digest()
key = data[:32]
if msg.iv and len(msg.iv) == 16:

View File

@ -1,18 +1,21 @@
from trezor import ui
from ustruct import pack, unpack
from trezor.crypto.hashlib import sha256
from trezor.messages.ECDHSessionKey import ECDHSessionKey
from ustruct import pack, unpack
from trezor.utils import chunks
from apps.common.confirm import require_confirm
from apps.wallet.sign_identity import serialize_identity, serialize_identity_without_proto
from trezor.ui.text import Text
from trezor.utils import chunks
from apps.common import seed, HARDENED
from apps.common import HARDENED, seed
from apps.common.confirm import require_confirm
from apps.wallet.sign_identity import (
serialize_identity,
serialize_identity_without_proto,
)
async def get_ecdh_session_key(ctx, msg):
if msg.ecdsa_curve_name is None:
msg.ecdsa_curve_name = 'secp256k1'
msg.ecdsa_curve_name = "secp256k1"
identity = serialize_identity(msg.identity)
@ -21,42 +24,47 @@ async def get_ecdh_session_key(ctx, msg):
address_n = get_ecdh_path(identity, msg.identity.index or 0)
node = await seed.derive_node(ctx, address_n, msg.ecdsa_curve_name)
session_key = ecdh(seckey=node.private_key(),
peer_public_key=msg.peer_public_key,
curve=msg.ecdsa_curve_name)
session_key = ecdh(
seckey=node.private_key(),
peer_public_key=msg.peer_public_key,
curve=msg.ecdsa_curve_name,
)
return ECDHSessionKey(session_key=session_key)
async def require_confirm_ecdh_session_key(ctx, identity):
lines = chunks(serialize_identity_without_proto(identity), 18)
proto = identity.proto.upper() if identity.proto else 'identity'
text = Text('Decrypt %s' % proto)
proto = identity.proto.upper() if identity.proto else "identity"
text = Text("Decrypt %s" % proto)
text.mono(*lines)
await require_confirm(ctx, text)
def get_ecdh_path(identity: str, index: int):
identity_hash = sha256(pack('<I', index) + identity).digest()
identity_hash = sha256(pack("<I", index) + identity).digest()
address_n = (17, ) + unpack('<IIII', identity_hash[:16])
address_n = (17,) + unpack("<IIII", identity_hash[:16])
address_n = [HARDENED | x for x in address_n]
return address_n
def ecdh(seckey: bytes, peer_public_key: bytes, curve: str) -> bytes:
if curve == 'secp256k1':
if curve == "secp256k1":
from trezor.crypto.curve import secp256k1
session_key = secp256k1.multiply(seckey, peer_public_key)
elif curve == 'nist256p1':
elif curve == "nist256p1":
from trezor.crypto.curve import nist256p1
session_key = nist256p1.multiply(seckey, peer_public_key)
elif curve == 'curve25519':
elif curve == "curve25519":
from trezor.crypto.curve import curve25519
if peer_public_key[0] != 0x40:
raise ValueError('Curve25519 public key should start with 0x40')
session_key = b'\x04' + curve25519.multiply(seckey, peer_public_key[1:])
raise ValueError("Curve25519 public key should start with 0x40")
session_key = b"\x04" + curve25519.multiply(seckey, peer_public_key[1:])
else:
raise ValueError('Unsupported curve for ECDH: ' + curve)
raise ValueError("Unsupported curve for ECDH: " + curve)
return session_key

View File

@ -1,12 +1,13 @@
from trezor.messages import InputScriptType
from trezor.messages.Address import Address
from apps.common import coins, seed
from apps.common.display_address import show_qr, show_address
from apps.common.display_address import show_address, show_qr
from apps.wallet.sign_tx import addresses
async def get_address(ctx, msg):
coin_name = msg.coin_name or 'Bitcoin'
coin_name = msg.coin_name or "Bitcoin"
coin = coins.by_name(coin_name)
node = await seed.derive_node(ctx, msg.address_n, curve_name=coin.curve_name)
@ -17,7 +18,12 @@ async def get_address(ctx, msg):
while True:
if await show_address(ctx, address_short):
break
if await show_qr(ctx, address.upper() if msg.script_type == InputScriptType.SPENDWITNESS else address):
if await show_qr(
ctx,
address.upper()
if msg.script_type == InputScriptType.SPENDWITNESS
else address,
):
break
return Address(address=address)

View File

@ -1,16 +1,16 @@
from trezor import ui
from trezor.crypto import random
from trezor.messages import ButtonRequestType
from trezor.messages.Entropy import Entropy
from trezor.ui.text import Text
from apps.common.confirm import require_confirm
async def get_entropy(ctx, msg):
text = Text('Confirm entropy')
text.bold('Do you really want', 'to send entropy?')
text.normal('Continue only if you', 'know what you are doing!')
text = Text("Confirm entropy")
text.bold("Do you really want", "to send entropy?")
text.normal("Continue only if you", "know what you are doing!")
await require_confirm(ctx, text, code=ButtonRequestType.ProtectCall)
size = min(msg.size, 1024)

View File

@ -1,16 +1,18 @@
from trezor.messages.HDNodeType import HDNodeType
from trezor.messages.PublicKey import PublicKey
from ubinascii import hexlify
from trezor import ui
from trezor.messages import ButtonRequestType
from trezor.messages.HDNodeType import HDNodeType
from trezor.messages.PublicKey import PublicKey
from trezor.ui.text import Text
from trezor.utils import chunks
from apps.common import coins, seed
from apps.common.confirm import require_confirm
from ubinascii import hexlify
async def get_public_key(ctx, msg):
coin_name = msg.coin_name or 'Bitcoin'
coin_name = msg.coin_name or "Bitcoin"
coin = coins.by_name(coin_name)
curve_name = msg.ecdsa_curve_name
@ -21,13 +23,14 @@ async def get_public_key(ctx, msg):
node_xpub = node.serialize_public(coin.xpub_magic)
pubkey = node.public_key()
if pubkey[0] == 1:
pubkey = b'\x00' + pubkey[1:]
pubkey = b"\x00" + pubkey[1:]
node_type = HDNodeType(
depth=node.depth(),
child_num=node.child_num(),
fingerprint=node.fingerprint(),
chain_code=node.chain_code(),
public_key=pubkey)
public_key=pubkey,
)
if msg.show_display:
await _show_pubkey(ctx, pubkey)
@ -37,9 +40,6 @@ async def get_public_key(ctx, msg):
async def _show_pubkey(ctx, pubkey: bytes):
lines = chunks(hexlify(pubkey).decode(), 18)
text = Text('Confirm public key', ui.ICON_RECEIVE, icon_color=ui.GREEN)
text = Text("Confirm public key", ui.ICON_RECEIVE, icon_color=ui.GREEN)
text.mono(*lines)
return await require_confirm(
ctx,
text,
code=ButtonRequestType.PublicKey)
return await require_confirm(ctx, text, code=ButtonRequestType.PublicKey)

View File

@ -3,16 +3,16 @@ from ustruct import pack, unpack
from trezor import ui
from trezor.crypto.hashlib import sha256
from trezor.messages.SignedIdentity import SignedIdentity
from trezor.utils import chunks
from trezor.ui.text import Text
from trezor.utils import chunks
from apps.common import coins, seed, HARDENED
from apps.common import HARDENED, coins, seed
from apps.common.confirm import require_confirm
async def sign_identity(ctx, msg):
if msg.ecdsa_curve_name is None:
msg.ecdsa_curve_name = 'secp256k1'
msg.ecdsa_curve_name = "secp256k1"
identity = serialize_identity(msg.identity)
@ -21,25 +21,40 @@ async def sign_identity(ctx, msg):
address_n = get_identity_path(identity, msg.identity.index or 0)
node = await seed.derive_node(ctx, address_n, msg.ecdsa_curve_name)
coin = coins.by_name('Bitcoin')
if msg.ecdsa_curve_name == 'secp256k1':
coin = coins.by_name("Bitcoin")
if msg.ecdsa_curve_name == "secp256k1":
address = node.address(coin.address_type) # hardcoded bitcoin address type
else:
address = None
pubkey = node.public_key()
if pubkey[0] == 0x01:
pubkey = b'\x00' + pubkey[1:]
pubkey = b"\x00" + pubkey[1:]
seckey = node.private_key()
if msg.identity.proto == 'gpg':
if msg.identity.proto == "gpg":
signature = sign_challenge(
seckey, msg.challenge_hidden, msg.challenge_visual, 'gpg', msg.ecdsa_curve_name)
elif msg.identity.proto == 'ssh':
seckey,
msg.challenge_hidden,
msg.challenge_visual,
"gpg",
msg.ecdsa_curve_name,
)
elif msg.identity.proto == "ssh":
signature = sign_challenge(
seckey, msg.challenge_hidden, msg.challenge_visual, 'ssh', msg.ecdsa_curve_name)
seckey,
msg.challenge_hidden,
msg.challenge_visual,
"ssh",
msg.ecdsa_curve_name,
)
else:
signature = sign_challenge(
seckey, msg.challenge_hidden, msg.challenge_visual, coin, msg.ecdsa_curve_name)
seckey,
msg.challenge_hidden,
msg.challenge_visual,
coin,
msg.ecdsa_curve_name,
)
return SignedIdentity(address=address, public_key=pubkey, signature=signature)
@ -52,22 +67,22 @@ async def require_confirm_sign_identity(ctx, identity, challenge_visual):
lines.append(ui.MONO)
lines.extend(chunks(serialize_identity_without_proto(identity), 18))
proto = identity.proto.upper() if identity.proto else 'identity'
text = Text('Sign %s' % proto)
proto = identity.proto.upper() if identity.proto else "identity"
text = Text("Sign %s" % proto)
text.normal(*lines)
await require_confirm(ctx, text)
def serialize_identity(identity):
s = ''
s = ""
if identity.proto:
s += identity.proto + '://'
s += identity.proto + "://"
if identity.user:
s += identity.user + '@'
s += identity.user + "@"
if identity.host:
s += identity.host
if identity.port:
s += ':' + identity.port
s += ":" + identity.port
if identity.path:
s += identity.path
return s
@ -82,52 +97,53 @@ def serialize_identity_without_proto(identity):
def get_identity_path(identity: str, index: int):
identity_hash = sha256(pack('<I', index) + identity).digest()
identity_hash = sha256(pack("<I", index) + identity).digest()
address_n = (13, ) + unpack('<IIII', identity_hash[:16])
address_n = (13,) + unpack("<IIII", identity_hash[:16])
address_n = [HARDENED | x for x in address_n]
return address_n
def sign_challenge(seckey: bytes,
challenge_hidden: bytes,
challenge_visual: str,
sigtype,
curve: str) -> bytes:
def sign_challenge(
seckey: bytes, challenge_hidden: bytes, challenge_visual: str, sigtype, curve: str
) -> bytes:
from trezor.crypto.hashlib import sha256
if curve == 'secp256k1':
if curve == "secp256k1":
from trezor.crypto.curve import secp256k1
elif curve == 'nist256p1':
elif curve == "nist256p1":
from trezor.crypto.curve import nist256p1
elif curve == 'ed25519':
elif curve == "ed25519":
from trezor.crypto.curve import ed25519
from apps.common.signverify import message_digest
if sigtype == 'gpg':
if sigtype == "gpg":
data = challenge_hidden
elif sigtype == 'ssh':
if curve != 'ed25519':
elif sigtype == "ssh":
if curve != "ed25519":
data = sha256(challenge_hidden).digest()
else:
data = challenge_hidden
else:
# sigtype is coin
challenge = sha256(challenge_hidden).digest() + sha256(challenge_visual).digest()
challenge = (
sha256(challenge_hidden).digest() + sha256(challenge_visual).digest()
)
data = message_digest(sigtype, challenge)
if curve == 'secp256k1':
if curve == "secp256k1":
signature = secp256k1.sign(seckey, data)
elif curve == 'nist256p1':
elif curve == "nist256p1":
signature = nist256p1.sign(seckey, data)
elif curve == 'ed25519':
elif curve == "ed25519":
signature = ed25519.sign(seckey, data)
else:
raise ValueError('Unknown curve')
raise ValueError("Unknown curve")
if curve == 'ed25519':
signature = b'\x00' + signature
elif sigtype == 'gpg' or sigtype == 'ssh':
signature = b'\x00' + signature[1:]
if curve == "ed25519":
signature = b"\x00" + signature
elif sigtype == "gpg" or sigtype == "ssh":
signature = b"\x00" + signature[1:]
return signature

View File

@ -1,8 +1,9 @@
from trezor import ui, wire
from trezor import wire
from trezor.crypto.curve import secp256k1
from trezor.messages.InputScriptType import SPENDADDRESS, SPENDP2SHWITNESS, SPENDWITNESS
from trezor.messages.MessageSignature import MessageSignature
from trezor.ui.text import Text
from apps.common import coins, seed
from apps.common.confirm import require_confirm
from apps.common.signverify import message_digest, split_message
@ -12,7 +13,7 @@ from apps.wallet.sign_tx.addresses import get_address
async def sign_message(ctx, msg):
message = msg.message
address_n = msg.address_n
coin_name = msg.coin_name or 'Bitcoin'
coin_name = msg.coin_name or "Bitcoin"
script_type = msg.script_type or 0
coin = coins.by_name(coin_name)
@ -32,13 +33,13 @@ async def sign_message(ctx, msg):
elif script_type == SPENDWITNESS:
signature = bytes([signature[0] + 8]) + signature[1:]
else:
raise wire.ProcessError('Unsupported script type')
raise wire.ProcessError("Unsupported script type")
return MessageSignature(address=address, signature=signature)
async def require_confirm_sign_message(ctx, message):
message = split_message(message)
text = Text('Sign message')
text = Text("Sign message")
text.normal(*message)
await require_confirm(ctx, text)

View File

@ -1,16 +1,22 @@
from trezor import ui, wire
from trezor.messages.MessageType import TxAck
from trezor.messages.TxRequest import TxRequest
from trezor.messages.RequestType import TXFINISHED
from apps.common import seed, coins
from apps.wallet.sign_tx.helpers import UiConfirmOutput, UiConfirmTotal, UiConfirmFeeOverThreshold, UiConfirmForeignAddress
from trezor.messages.TxRequest import TxRequest
from apps.common import coins, seed
from apps.wallet.sign_tx.helpers import (
UiConfirmFeeOverThreshold,
UiConfirmForeignAddress,
UiConfirmOutput,
UiConfirmTotal,
)
@ui.layout
async def sign_tx(ctx, msg):
from apps.wallet.sign_tx import layout, progress, signing
coin_name = msg.coin_name or 'Bitcoin'
coin_name = msg.coin_name or "Bitcoin"
coin = coins.by_name(coin_name)
# TODO: rework this so we don't have to pass root to signing.sign_tx
root = await seed.derive_node(ctx, [], curve_name=coin.curve_name)
@ -46,5 +52,5 @@ async def sign_tx(ctx, msg):
elif isinstance(req, UiConfirmForeignAddress):
res = await layout.confirm_foreign_address(ctx, req.address_n, req.coin)
else:
raise TypeError('Invalid signing instruction')
raise TypeError("Invalid signing instruction")
return req

View File

@ -1,16 +1,18 @@
from micropython import const
from trezor.crypto.hashlib import sha256, ripemd160
from trezor.crypto import base58, bech32, cashaddr
from trezor.crypto.hashlib import ripemd160, sha256
from trezor.messages import FailureType, InputScriptType
from trezor.utils import ensure
from trezor.messages import FailureType
from trezor.messages import InputScriptType
from apps.common import address_type
from apps.common.coininfo import CoinInfo
from apps.common.address_type import addrtype_bytes
from apps.wallet.sign_tx.scripts import output_script_multisig, sha256_ripemd160_digest, output_script_native_p2wpkh_or_p2wsh
from apps.wallet.sign_tx.multisig import multisig_pubkey_index, multisig_get_pubkeys
from apps.wallet.sign_tx.multisig import multisig_get_pubkeys, multisig_pubkey_index
from apps.wallet.sign_tx.scripts import (
output_script_multisig,
output_script_native_p2wpkh_or_p2wsh,
sha256_ripemd160_digest,
)
# supported witness version for bech32 addresses
_BECH32_WITVER = const(0x00)
@ -20,18 +22,23 @@ class AddressError(Exception):
pass
def get_address(script_type: InputScriptType, coin: CoinInfo, node, multisig=None) -> str:
def get_address(
script_type: InputScriptType, coin: CoinInfo, node, multisig=None
) -> str:
if script_type == InputScriptType.SPENDADDRESS or script_type == InputScriptType.SPENDMULTISIG:
if (
script_type == InputScriptType.SPENDADDRESS
or script_type == InputScriptType.SPENDMULTISIG
):
if multisig: # p2sh multisig
pubkey = node.public_key()
index = multisig_pubkey_index(multisig, pubkey)
if index is None:
raise AddressError(FailureType.ProcessError,
'Public key not found')
raise AddressError(FailureType.ProcessError, "Public key not found")
if coin.address_type_p2sh is None:
raise AddressError(FailureType.ProcessError,
'Multisig not enabled on this coin')
raise AddressError(
FailureType.ProcessError, "Multisig not enabled on this coin"
)
pubkeys = multisig_get_pubkeys(multisig)
address = address_multisig_p2sh(pubkeys, multisig.m, coin)
@ -39,8 +46,7 @@ def get_address(script_type: InputScriptType, coin: CoinInfo, node, multisig=Non
address = address_to_cashaddr(address, coin)
return address
if script_type == InputScriptType.SPENDMULTISIG:
raise AddressError(FailureType.ProcessError,
'Multisig details required')
raise AddressError(FailureType.ProcessError, "Multisig details required")
# p2pkh
address = node.address(coin.address_type)
@ -50,8 +56,9 @@ def get_address(script_type: InputScriptType, coin: CoinInfo, node, multisig=Non
elif script_type == InputScriptType.SPENDWITNESS: # native p2wpkh or native p2wsh
if not coin.segwit or not coin.bech32_prefix:
raise AddressError(FailureType.ProcessError,
'Segwit not enabled on this coin')
raise AddressError(
FailureType.ProcessError, "Segwit not enabled on this coin"
)
# native p2wsh multisig
if multisig is not None:
pubkeys = multisig_get_pubkeys(multisig)
@ -60,10 +67,13 @@ def get_address(script_type: InputScriptType, coin: CoinInfo, node, multisig=Non
# native p2wpkh
return address_p2wpkh(node.public_key(), coin.bech32_prefix)
elif script_type == InputScriptType.SPENDP2SHWITNESS: # p2wpkh or p2wsh nested in p2sh
elif (
script_type == InputScriptType.SPENDP2SHWITNESS
): # p2wpkh or p2wsh nested in p2sh
if not coin.segwit or coin.address_type_p2sh is None:
raise AddressError(FailureType.ProcessError,
'Segwit not enabled on this coin')
raise AddressError(
FailureType.ProcessError, "Segwit not enabled on this coin"
)
# p2wsh multisig nested in p2sh
if multisig is not None:
pubkeys = multisig_get_pubkeys(multisig)
@ -73,14 +83,14 @@ def get_address(script_type: InputScriptType, coin: CoinInfo, node, multisig=Non
return address_p2wpkh_in_p2sh(node.public_key(), coin)
else:
raise AddressError(FailureType.ProcessError,
'Invalid script type')
raise AddressError(FailureType.ProcessError, "Invalid script type")
def address_multisig_p2sh(pubkeys: bytes, m: int, coin: CoinInfo):
if coin.address_type_p2sh is None:
raise AddressError(FailureType.ProcessError,
'Multisig not enabled on this coin')
raise AddressError(
FailureType.ProcessError, "Multisig not enabled on this coin"
)
redeem_script = output_script_multisig(pubkeys, m)
redeem_script_hash = sha256_ripemd160_digest(redeem_script)
return address_p2sh(redeem_script_hash, coin)
@ -88,8 +98,9 @@ def address_multisig_p2sh(pubkeys: bytes, m: int, coin: CoinInfo):
def address_multisig_p2wsh_in_p2sh(pubkeys: bytes, m: int, coin: CoinInfo):
if coin.address_type_p2sh is None:
raise AddressError(FailureType.ProcessError,
'Multisig not enabled on this coin')
raise AddressError(
FailureType.ProcessError, "Multisig not enabled on this coin"
)
witness_script = output_script_multisig(pubkeys, m)
witness_script_hash = sha256(witness_script).digest()
return address_p2wsh_in_p2sh(witness_script_hash, coin)
@ -97,20 +108,21 @@ def address_multisig_p2wsh_in_p2sh(pubkeys: bytes, m: int, coin: CoinInfo):
def address_multisig_p2wsh(pubkeys: bytes, m: int, hrp: str):
if not hrp:
raise AddressError(FailureType.ProcessError,
'Multisig not enabled on this coin')
raise AddressError(
FailureType.ProcessError, "Multisig not enabled on this coin"
)
witness_script = output_script_multisig(pubkeys, m)
witness_script_hash = sha256(witness_script).digest()
return address_p2wsh(witness_script_hash, hrp)
def address_pkh(pubkey: bytes, coin: CoinInfo) -> str:
s = addrtype_bytes(coin.address_type) + sha256_ripemd160_digest(pubkey)
s = address_type.tobytes(coin.address_type) + sha256_ripemd160_digest(pubkey)
return base58.encode_check(bytes(s), coin.b58_hash)
def address_p2sh(redeem_script_hash: bytes, coin: CoinInfo) -> str:
s = addrtype_bytes(coin.address_type_p2sh) + redeem_script_hash
s = address_type.tobytes(coin.address_type_p2sh) + redeem_script_hash
return base58.encode_check(bytes(s), coin.b58_hash)
@ -131,24 +143,21 @@ def address_p2wpkh(pubkey: bytes, hrp: str) -> str:
pubkeyhash = ecdsa_hash_pubkey(pubkey)
address = bech32.encode(hrp, _BECH32_WITVER, pubkeyhash)
if address is None:
raise AddressError(FailureType.ProcessError,
'Invalid address')
raise AddressError(FailureType.ProcessError, "Invalid address")
return address
def address_p2wsh(witness_script_hash: bytes, hrp: str) -> str:
address = bech32.encode(hrp, _BECH32_WITVER, witness_script_hash)
if address is None:
raise AddressError(FailureType.ProcessError,
'Invalid address')
raise AddressError(FailureType.ProcessError, "Invalid address")
return address
def decode_bech32_address(prefix: str, address: str) -> bytes:
witver, raw = bech32.decode(prefix, address)
if witver != _BECH32_WITVER:
raise AddressError(FailureType.ProcessError,
'Invalid address witness program')
raise AddressError(FailureType.ProcessError, "Invalid address witness program")
return bytes(raw)
@ -160,7 +169,7 @@ def address_to_cashaddr(address: str, coin: CoinInfo) -> str:
elif version == coin.address_type_p2sh:
version = cashaddr.ADDRESS_TYPE_P2SH
else:
raise ValueError('Unknown cashaddr address type')
raise ValueError("Unknown cashaddr address type")
return cashaddr.encode(coin.cashaddr_prefix, version, data)
@ -168,7 +177,7 @@ def ecdsa_hash_pubkey(pubkey: bytes) -> bytes:
if pubkey[0] == 0x04:
ensure(len(pubkey) == 65) # uncompressed format
elif pubkey[0] == 0x00:
ensure(len(pubkey) == 1) # point at infinity
ensure(len(pubkey) == 1) # point at infinity
else:
ensure(len(pubkey) == 33) # compresssed format
h = sha256(pubkey).digest()
@ -177,7 +186,9 @@ def ecdsa_hash_pubkey(pubkey: bytes) -> bytes:
def address_short(coin: CoinInfo, address: str) -> str:
if coin.cashaddr_prefix is not None and address.startswith(coin.cashaddr_prefix + ':'):
return address[len(coin.cashaddr_prefix) + 1:]
if coin.cashaddr_prefix is not None and address.startswith(
coin.cashaddr_prefix + ":"
):
return address[len(coin.cashaddr_prefix) + 1 :]
else:
return address

View File

@ -1,11 +1,17 @@
from trezor.messages.TxOutputType import TxOutputType
from trezor.messages.TxOutputBinType import TxOutputBinType
from trezor.messages.TxInputType import TxInputType
from trezor.messages.SignTx import SignTx
from trezor.messages.TxRequest import TxRequest
from trezor.messages.TransactionType import TransactionType
from trezor.messages.RequestType import TXINPUT, TXOUTPUT, TXMETA, TXEXTRADATA, TXFINISHED
from trezor.messages import InputScriptType
from trezor.messages.RequestType import (
TXEXTRADATA,
TXFINISHED,
TXINPUT,
TXMETA,
TXOUTPUT,
)
from trezor.messages.SignTx import SignTx
from trezor.messages.TransactionType import TransactionType
from trezor.messages.TxInputType import TxInputType
from trezor.messages.TxOutputBinType import TxOutputBinType
from trezor.messages.TxOutputType import TxOutputType
from trezor.messages.TxRequest import TxRequest
from apps.common.coininfo import CoinInfo
@ -14,14 +20,12 @@ from apps.common.coininfo import CoinInfo
class UiConfirmOutput:
def __init__(self, output: TxOutputType, coin: CoinInfo):
self.output = output
self.coin = coin
class UiConfirmTotal:
def __init__(self, spending: int, fee: int, coin: CoinInfo):
self.spending = spending
self.fee = fee
@ -29,14 +33,12 @@ class UiConfirmTotal:
class UiConfirmFeeOverThreshold:
def __init__(self, fee: int, coin: CoinInfo):
self.fee = fee
self.coin = coin
class UiConfirmForeignAddress:
def __init__(self, address_n: list, coin: CoinInfo):
self.address_n = address_n
self.coin = coin
@ -58,7 +60,7 @@ def confirm_foreign_address(address_n: list, coin: CoinInfo):
return (yield UiConfirmForeignAddress(address_n, coin))
def request_tx_meta(tx_req: TxRequest, tx_hash: bytes=None):
def request_tx_meta(tx_req: TxRequest, tx_hash: bytes = None):
tx_req.request_type = TXMETA
tx_req.details.tx_hash = tx_hash
tx_req.details.request_index = None
@ -67,7 +69,9 @@ def request_tx_meta(tx_req: TxRequest, tx_hash: bytes=None):
return sanitize_tx_meta(ack.tx)
def request_tx_extra_data(tx_req: TxRequest, offset: int, size: int, tx_hash: bytes=None):
def request_tx_extra_data(
tx_req: TxRequest, offset: int, size: int, tx_hash: bytes = None
):
tx_req.request_type = TXEXTRADATA
tx_req.details.extra_data_offset = offset
tx_req.details.extra_data_len = size
@ -78,7 +82,7 @@ def request_tx_extra_data(tx_req: TxRequest, offset: int, size: int, tx_hash: by
return ack.tx.extra_data
def request_tx_input(tx_req: TxRequest, i: int, tx_hash: bytes=None):
def request_tx_input(tx_req: TxRequest, i: int, tx_hash: bytes = None):
tx_req.request_type = TXINPUT
tx_req.details.request_index = i
tx_req.details.tx_hash = tx_hash
@ -87,7 +91,7 @@ def request_tx_input(tx_req: TxRequest, i: int, tx_hash: bytes=None):
return sanitize_tx_input(ack.tx)
def request_tx_output(tx_req: TxRequest, i: int, tx_hash: bytes=None):
def request_tx_output(tx_req: TxRequest, i: int, tx_hash: bytes = None):
tx_req.request_type = TXOUTPUT
tx_req.details.request_index = i
tx_req.details.tx_hash = tx_hash
@ -115,7 +119,7 @@ def sanitize_sign_tx(tx: SignTx) -> SignTx:
tx.lock_time = tx.lock_time if tx.lock_time is not None else 0
tx.inputs_count = tx.inputs_count if tx.inputs_count is not None else 0
tx.outputs_count = tx.outputs_count if tx.outputs_count is not None else 0
tx.coin_name = tx.coin_name if tx.coin_name is not None else 'Bitcoin'
tx.coin_name = tx.coin_name if tx.coin_name is not None else "Bitcoin"
tx.expiry = tx.expiry if tx.expiry is not None else 0
tx.overwintered = tx.overwintered if tx.overwintered is not None else False
return tx

View File

@ -1,16 +1,16 @@
from ubinascii import hexlify
from trezor import ui
from trezor.utils import chunks, format_amount
from trezor.messages import ButtonRequestType, OutputScriptType
from trezor.ui.text import Text
from trezor.messages import ButtonRequestType
from trezor.messages import OutputScriptType
from apps.common.confirm import confirm
from apps.common.confirm import hold_to_confirm
from trezor.utils import chunks, format_amount
from apps.common.confirm import confirm, hold_to_confirm
from apps.wallet.sign_tx import addresses
def format_coin_amount(amount, coin):
return '%s %s' % (format_amount(amount, 8), coin.coin_shortcut)
return "%s %s" % (format_amount(amount, 8), coin.coin_shortcut)
def split_address(address):
@ -25,39 +25,36 @@ async def confirm_output(ctx, output, coin):
if output.script_type == OutputScriptType.PAYTOOPRETURN:
data = hexlify(output.op_return_data).decode()
if len(data) >= 18 * 5:
data = data[:(18 * 5 - 3)] + '...'
text = Text('OP_RETURN', ui.ICON_SEND, icon_color=ui.GREEN)
data = data[: (18 * 5 - 3)] + "..."
text = Text("OP_RETURN", ui.ICON_SEND, icon_color=ui.GREEN)
text.mono(*split_op_return(data))
else:
address = output.address
address_short = addresses.address_short(coin, address)
text = Text('Confirm sending', ui.ICON_SEND, icon_color=ui.GREEN)
text.normal(format_coin_amount(output.amount, coin) + ' to')
text = Text("Confirm sending", ui.ICON_SEND, icon_color=ui.GREEN)
text.normal(format_coin_amount(output.amount, coin) + " to")
text.mono(*split_address(address_short))
return await confirm(ctx, text, ButtonRequestType.ConfirmOutput)
async def confirm_total(ctx, spending, fee, coin):
text = Text('Confirm transaction', ui.ICON_SEND, icon_color=ui.GREEN)
text.normal('Total amount:')
text = Text("Confirm transaction", ui.ICON_SEND, icon_color=ui.GREEN)
text.normal("Total amount:")
text.bold(format_coin_amount(spending, coin))
text.normal('including fee:')
text.normal("including fee:")
text.bold(format_coin_amount(fee, coin))
return await hold_to_confirm(ctx, text, ButtonRequestType.SignTx)
async def confirm_feeoverthreshold(ctx, fee, coin):
text = Text('High fee', ui.ICON_SEND, icon_color=ui.GREEN)
text.normal('The fee of')
text = Text("High fee", ui.ICON_SEND, icon_color=ui.GREEN)
text.normal("The fee of")
text.bold(format_coin_amount(fee, coin))
text.normal('is unexpectedly high.', 'Continue?')
text.normal("is unexpectedly high.", "Continue?")
return await confirm(ctx, text, ButtonRequestType.FeeOverThreshold)
async def confirm_foreign_address(ctx, address_n, coin):
text = Text('Confirm sending', ui.ICON_SEND, icon_color=ui.RED)
text.normal(
'Trying to spend',
'coins from another chain.',
'Continue?')
text = Text("Confirm sending", ui.ICON_SEND, icon_color=ui.RED)
text.normal("Trying to spend", "coins from another chain.", "Continue?")
return await confirm(ctx, text, ButtonRequestType.SignTx)

View File

@ -1,10 +1,9 @@
from trezor.crypto.hashlib import sha256
from trezor.crypto import bip32
from trezor.utils import HashWriter
from trezor.messages.MultisigRedeemScriptType import MultisigRedeemScriptType
from trezor.messages.HDNodePathType import HDNodePathType
from trezor.crypto.hashlib import sha256
from trezor.messages import FailureType
from trezor.messages.HDNodePathType import HDNodePathType
from trezor.messages.MultisigRedeemScriptType import MultisigRedeemScriptType
from trezor.utils import HashWriter
from apps.wallet.sign_tx.writers import write_bytes, write_uint32
@ -41,12 +40,12 @@ def multisig_fingerprint(multisig: MultisigRedeemScriptType) -> bytes:
n = len(pubkeys)
if n < 1 or n > 15 or m < 1 or m > 15:
raise MultisigError(FailureType.DataError, 'Invalid multisig parameters')
raise MultisigError(FailureType.DataError, "Invalid multisig parameters")
for hd in pubkeys:
d = hd.node
if len(d.public_key) != 33 or len(d.chain_code) != 32:
raise MultisigError(FailureType.DataError, 'Invalid multisig parameters')
raise MultisigError(FailureType.DataError, "Invalid multisig parameters")
# casting to bytes(), sorting on bytearray() is not supported in MicroPython
pubkeys = sorted(pubkeys, key=lambda hd: bytes(hd.node.public_key))
@ -69,8 +68,7 @@ def multisig_pubkey_index(multisig: MultisigRedeemScriptType, pubkey: bytes) ->
for i, hd in enumerate(multisig.pubkeys):
if multisig_get_pubkey(hd) == pubkey:
return i
raise MultisigError(FailureType.DataError,
'Pubkey not found in multisig script')
raise MultisigError(FailureType.DataError, "Pubkey not found in multisig script")
def multisig_get_pubkey(hd: HDNodePathType) -> bytes:
@ -81,7 +79,8 @@ def multisig_get_pubkey(hd: HDNodePathType) -> bytes:
fingerprint=n.fingerprint,
child_num=n.child_num,
chain_code=n.chain_code,
public_key=n.public_key)
public_key=n.public_key,
)
for i in p:
node.derive(i, True)
return node.public_key()

View File

@ -1,17 +1,24 @@
from micropython import const
from trezor.crypto.hashlib import blake2b
from trezor.messages import FailureType, InputScriptType
from trezor.messages.SignTx import SignTx
from trezor.messages.TxInputType import TxInputType
from trezor.messages.TxOutputBinType import TxOutputBinType
from trezor.messages import InputScriptType, FailureType
from trezor.utils import HashWriter
from apps.common.coininfo import CoinInfo
from apps.wallet.sign_tx.writers import write_bytes, write_bytes_rev, write_uint32, write_uint64, write_varint, write_tx_output, get_tx_hash
from apps.wallet.sign_tx.scripts import output_script_p2pkh, output_script_multisig
from apps.wallet.sign_tx.multisig import multisig_get_pubkeys
from apps.wallet.sign_tx.scripts import output_script_multisig, output_script_p2pkh
from apps.wallet.sign_tx.writers import (
get_tx_hash,
write_bytes,
write_bytes_rev,
write_tx_output,
write_uint32,
write_uint64,
write_varint,
)
OVERWINTERED = const(0x80000000)
@ -21,11 +28,10 @@ class Zip143Error(ValueError):
class Zip143:
def __init__(self):
self.h_prevouts = HashWriter(blake2b, outlen=32, personal=b'ZcashPrevoutHash')
self.h_sequence = HashWriter(blake2b, outlen=32, personal=b'ZcashSequencHash')
self.h_outputs = HashWriter(blake2b, outlen=32, personal=b'ZcashOutputsHash')
self.h_prevouts = HashWriter(blake2b, outlen=32, personal=b"ZcashPrevoutHash")
self.h_sequence = HashWriter(blake2b, outlen=32, personal=b"ZcashSequencHash")
self.h_outputs = HashWriter(blake2b, outlen=32, personal=b"ZcashOutputsHash")
def add_prevouts(self, txi: TxInputType):
write_bytes_rev(self.h_prevouts, txi.prev_hash)
@ -46,31 +52,42 @@ class Zip143:
def get_outputs_hash(self) -> bytes:
return get_tx_hash(self.h_outputs)
def preimage_hash(self, coin: CoinInfo, tx: SignTx, txi: TxInputType, pubkeyhash: bytes, sighash: int) -> bytes:
h_preimage = HashWriter(blake2b, outlen=32, personal=b'ZcashSigHash\x19\x1b\xa8\x5b') # BRANCH_ID = 0x5ba81b19
def preimage_hash(
self,
coin: CoinInfo,
tx: SignTx,
txi: TxInputType,
pubkeyhash: bytes,
sighash: int,
) -> bytes:
h_preimage = HashWriter(
blake2b, outlen=32, personal=b"ZcashSigHash\x19\x1b\xa8\x5b"
) # BRANCH_ID = 0x5ba81b19
assert tx.overwintered
write_uint32(h_preimage, tx.version | OVERWINTERED) # 1. nVersion | fOverwintered
write_uint32(h_preimage, coin.version_group_id) # 2. nVersionGroupId
write_uint32(
h_preimage, tx.version | OVERWINTERED
) # 1. nVersion | fOverwintered
write_uint32(h_preimage, coin.version_group_id) # 2. nVersionGroupId
write_bytes(h_preimage, bytearray(self.get_prevouts_hash())) # 3. hashPrevouts
write_bytes(h_preimage, bytearray(self.get_sequence_hash())) # 4. hashSequence
write_bytes(h_preimage, bytearray(self.get_outputs_hash())) # 5. hashOutputs
write_bytes(h_preimage, b'\x00' * 32) # 6. hashJoinSplits
write_uint32(h_preimage, tx.lock_time) # 7. nLockTime
write_uint32(h_preimage, tx.expiry) # 8. expiryHeight
write_uint32(h_preimage, sighash) # 9. nHashType
write_bytes(h_preimage, bytearray(self.get_outputs_hash())) # 5. hashOutputs
write_bytes(h_preimage, b"\x00" * 32) # 6. hashJoinSplits
write_uint32(h_preimage, tx.lock_time) # 7. nLockTime
write_uint32(h_preimage, tx.expiry) # 8. expiryHeight
write_uint32(h_preimage, sighash) # 9. nHashType
write_bytes_rev(h_preimage, txi.prev_hash) # 10a. outpoint
write_bytes_rev(h_preimage, txi.prev_hash) # 10a. outpoint
write_uint32(h_preimage, txi.prev_index)
script_code = self.derive_script_code(txi, pubkeyhash) # 10b. scriptCode
script_code = self.derive_script_code(txi, pubkeyhash) # 10b. scriptCode
write_varint(h_preimage, len(script_code))
write_bytes(h_preimage, script_code)
write_uint64(h_preimage, txi.amount) # 10c. value
write_uint64(h_preimage, txi.amount) # 10c. value
write_uint32(h_preimage, txi.sequence) # 10d. nSequence
write_uint32(h_preimage, txi.sequence) # 10d. nSequence
return get_tx_hash(h_preimage)
@ -79,12 +96,16 @@ class Zip143:
def derive_script_code(self, txi: TxInputType, pubkeyhash: bytes) -> bytearray:
if txi.multisig:
return output_script_multisig(multisig_get_pubkeys(txi.multisig), txi.multisig.m)
return output_script_multisig(
multisig_get_pubkeys(txi.multisig), txi.multisig.m
)
p2pkh = txi.script_type == InputScriptType.SPENDADDRESS
if p2pkh:
return output_script_p2pkh(pubkeyhash)
else:
raise Zip143Error(FailureType.DataError,
'Unknown input script type for zip143 script code')
raise Zip143Error(
FailureType.DataError,
"Unknown input script type for zip143 script code",
)

View File

@ -20,7 +20,7 @@ def advance():
def report_init():
ui.display.clear()
ui.header('Signing transaction')
ui.header("Signing transaction")
def report():

View File

@ -1,7 +1,14 @@
from trezor.crypto.hashlib import ripemd160, sha256
from trezor.messages.MultisigRedeemScriptType import MultisigRedeemScriptType
from apps.wallet.sign_tx.multisig import multisig_get_pubkeys
from apps.wallet.sign_tx.writers import bytearray_with_cap, write_bytes, write_varint, write_op_push, write_scriptnum
from apps.wallet.sign_tx.writers import (
bytearray_with_cap,
write_bytes,
write_op_push,
write_scriptnum,
write_varint,
)
class ScriptsError(ValueError):
@ -13,7 +20,9 @@ class ScriptsError(ValueError):
# https://github.com/bitcoin/bips/blob/master/bip-0016.mediawiki
def input_script_p2pkh_or_p2sh(pubkey: bytes, signature: bytes, sighash: int) -> bytearray:
def input_script_p2pkh_or_p2sh(
pubkey: bytes, signature: bytes, sighash: int
) -> bytearray:
w = bytearray_with_cap(5 + len(signature) + 1 + 5 + len(pubkey))
append_signature(w, signature, sighash)
append_pubkey(w, pubkey)
@ -42,7 +51,9 @@ def output_script_p2sh(scripthash: bytes) -> bytearray:
return s
def script_replay_protection_bip115(block_hash: bytes, block_height: int) -> bytearray:
def script_replay_protection_bip115(
block_hash: bytes, block_height: bytes
) -> bytearray:
if block_hash is None or block_height is None:
return bytearray()
assert len(block_hash) == 32
@ -116,7 +127,7 @@ def input_script_p2wsh_in_p2sh(script_hash: bytes) -> bytearray:
# Signature is moved to the witness.
if len(script_hash) != 32:
raise ScriptsError('Redeem script hash should be 32 bytes long')
raise ScriptsError("Redeem script hash should be 32 bytes long")
w = bytearray_with_cap(3 + len(script_hash))
w.append(0x22) # length of the data
@ -138,10 +149,15 @@ def witness_p2wpkh(signature: bytes, pubkey: bytes, sighash: int):
return w
def witness_p2wsh(multisig: MultisigRedeemScriptType, signature: bytes, signature_index: int, sighash: int):
def witness_p2wsh(
multisig: MultisigRedeemScriptType,
signature: bytes,
signature_index: int,
sighash: int,
):
signatures = multisig.signatures # other signatures
if len(signatures[signature_index]) > 0:
raise ScriptsError('Invalid multisig parameters')
raise ScriptsError("Invalid multisig parameters")
signatures[signature_index] = signature # our signature
# filter empty
@ -171,10 +187,15 @@ def witness_p2wsh(multisig: MultisigRedeemScriptType, signature: bytes, signatur
# Used either as P2SH, P2WSH, or P2WSH nested in P2SH.
def input_script_multisig(multisig: MultisigRedeemScriptType, signature: bytes, signature_index: int, sighash: int):
def input_script_multisig(
multisig: MultisigRedeemScriptType,
signature: bytes,
signature_index: int,
sighash: int,
):
signatures = multisig.signatures # other signatures
if len(signatures[signature_index]) > 0:
raise ScriptsError('Invalid multisig parameters')
raise ScriptsError("Invalid multisig parameters")
signatures[signature_index] = signature # our signature
w = bytearray()
@ -199,10 +220,10 @@ def input_script_multisig(multisig: MultisigRedeemScriptType, signature: bytes,
def output_script_multisig(pubkeys, m: int) -> bytearray:
n = len(pubkeys)
if n < 1 or n > 15 or m < 1 or m > 15:
raise ScriptsError('Invalid multisig parameters')
raise ScriptsError("Invalid multisig parameters")
for pubkey in pubkeys:
if len(pubkey) != 33:
raise ScriptsError('Invalid multisig parameters')
raise ScriptsError("Invalid multisig parameters")
w = bytearray()
w.append(0x50 + m) # numbers 1 to 16 are pushed as 0x50 + value

View File

@ -1,14 +1,22 @@
from trezor.crypto.hashlib import sha256
from trezor.messages import FailureType, InputScriptType
from trezor.messages.SignTx import SignTx
from trezor.messages.TxInputType import TxInputType
from trezor.messages.TxOutputBinType import TxOutputBinType
from trezor.messages import InputScriptType, FailureType
from trezor.utils import HashWriter
from apps.common.coininfo import CoinInfo
from apps.wallet.sign_tx.writers import write_bytes, write_bytes_rev, write_uint32, write_uint64, write_varint, write_tx_output, get_tx_hash
from apps.wallet.sign_tx.scripts import output_script_p2pkh, output_script_multisig
from apps.wallet.sign_tx.multisig import multisig_get_pubkeys
from apps.wallet.sign_tx.scripts import output_script_multisig, output_script_p2pkh
from apps.wallet.sign_tx.writers import (
get_tx_hash,
write_bytes,
write_bytes_rev,
write_tx_output,
write_uint32,
write_uint64,
write_varint,
)
class Bip143Error(ValueError):
@ -16,7 +24,6 @@ class Bip143Error(ValueError):
class Bip143:
def __init__(self):
self.h_prevouts = HashWriter(sha256)
self.h_sequence = HashWriter(sha256)
@ -41,27 +48,34 @@ class Bip143:
def get_outputs_hash(self, coin: CoinInfo) -> bytes:
return get_tx_hash(self.h_outputs, double=coin.sign_hash_double)
def preimage_hash(self, coin: CoinInfo, tx: SignTx, txi: TxInputType, pubkeyhash: bytes, sighash: int) -> bytes:
def preimage_hash(
self,
coin: CoinInfo,
tx: SignTx,
txi: TxInputType,
pubkeyhash: bytes,
sighash: int,
) -> bytes:
h_preimage = HashWriter(sha256)
assert not tx.overwintered
write_uint32(h_preimage, tx.version) # nVersion
write_uint32(h_preimage, tx.version) # nVersion
write_bytes(h_preimage, bytearray(self.get_prevouts_hash(coin))) # hashPrevouts
write_bytes(h_preimage, bytearray(self.get_sequence_hash(coin))) # hashSequence
write_bytes_rev(h_preimage, txi.prev_hash) # outpoint
write_uint32(h_preimage, txi.prev_index) # outpoint
write_bytes_rev(h_preimage, txi.prev_hash) # outpoint
write_uint32(h_preimage, txi.prev_index) # outpoint
script_code = self.derive_script_code(txi, pubkeyhash) # scriptCode
script_code = self.derive_script_code(txi, pubkeyhash) # scriptCode
write_varint(h_preimage, len(script_code))
write_bytes(h_preimage, script_code)
write_uint64(h_preimage, txi.amount) # amount
write_uint32(h_preimage, txi.sequence) # nSequence
write_bytes(h_preimage, bytearray(self.get_outputs_hash(coin))) # hashOutputs
write_uint32(h_preimage, tx.lock_time) # nLockTime
write_uint32(h_preimage, sighash) # nHashType
write_uint64(h_preimage, txi.amount) # amount
write_uint32(h_preimage, txi.sequence) # nSequence
write_bytes(h_preimage, bytearray(self.get_outputs_hash(coin))) # hashOutputs
write_uint32(h_preimage, tx.lock_time) # nLockTime
write_uint32(h_preimage, sighash) # nHashType
return get_tx_hash(h_preimage, double=coin.sign_hash_double)
@ -70,16 +84,22 @@ class Bip143:
def derive_script_code(self, txi: TxInputType, pubkeyhash: bytes) -> bytearray:
if txi.multisig:
return output_script_multisig(multisig_get_pubkeys(txi.multisig), txi.multisig.m)
return output_script_multisig(
multisig_get_pubkeys(txi.multisig), txi.multisig.m
)
p2pkh = (txi.script_type == InputScriptType.SPENDWITNESS or
txi.script_type == InputScriptType.SPENDP2SHWITNESS or
txi.script_type == InputScriptType.SPENDADDRESS)
p2pkh = (
txi.script_type == InputScriptType.SPENDWITNESS
or txi.script_type == InputScriptType.SPENDP2SHWITNESS
or txi.script_type == InputScriptType.SPENDADDRESS
)
if p2pkh:
# for p2wpkh in p2sh or native p2wpkh
# the scriptCode is a classic p2pkh
return output_script_p2pkh(pubkeyhash)
else:
raise Bip143Error(FailureType.DataError,
'Unknown input script type for bip143 script code')
raise Bip143Error(
FailureType.DataError,
"Unknown input script type for bip143 script code",
)

View File

@ -1,26 +1,28 @@
from micropython import const
from trezor.crypto import base58, bip32, der, cashaddr
from trezor.crypto import base58, bip32, cashaddr, der
from trezor.crypto.curve import secp256k1
from trezor.crypto.hashlib import sha256
from trezor.utils import HashWriter
from trezor.messages import OutputScriptType
from trezor.messages.TxRequestDetailsType import TxRequestDetailsType
from trezor.messages.TxRequestSerializedType import TxRequestSerializedType
from trezor.utils import HashWriter
from apps.common import address_type, coins
from apps.common.coininfo import CoinInfo
from apps.wallet.sign_tx import progress
from apps.wallet.sign_tx.addresses import *
from apps.wallet.sign_tx.helpers import *
from apps.wallet.sign_tx.multisig import *
from apps.wallet.sign_tx.overwinter_zip143 import ( # noqa:F401
OVERWINTERED,
Zip143,
Zip143Error,
)
from apps.wallet.sign_tx.scripts import *
from apps.wallet.sign_tx.segwit_bip143 import Bip143, Bip143Error # noqa:F401
from apps.wallet.sign_tx.overwinter_zip143 import Zip143, Zip143Error, OVERWINTERED # noqa:F401
from apps.wallet.sign_tx.tx_weight_calculator import *
from apps.wallet.sign_tx.writers import *
from apps.wallet.sign_tx import progress
# the number of bip32 levels used in a wallet (chain and address)
_BIP32_WALLET_DEPTH = const(2)
@ -92,35 +94,40 @@ async def check_tx_fee(tx: SignTx, root: bip32.HDNode):
if txi.multisig:
multifp.add(txi.multisig)
if txi.script_type in (InputScriptType.SPENDWITNESS,
InputScriptType.SPENDP2SHWITNESS):
if txi.script_type in (
InputScriptType.SPENDWITNESS,
InputScriptType.SPENDP2SHWITNESS,
):
if not coin.segwit:
raise SigningError(FailureType.DataError,
'Segwit not enabled on this coin')
raise SigningError(
FailureType.DataError, "Segwit not enabled on this coin"
)
if not txi.amount:
raise SigningError(FailureType.DataError,
'Segwit input without amount')
raise SigningError(FailureType.DataError, "Segwit input without amount")
segwit[i] = True
segwit_in += txi.amount
total_in += txi.amount
elif txi.script_type in (InputScriptType.SPENDADDRESS,
InputScriptType.SPENDMULTISIG):
elif txi.script_type in (
InputScriptType.SPENDADDRESS,
InputScriptType.SPENDMULTISIG,
):
if coin.force_bip143 or tx.overwintered:
if not txi.amount:
raise SigningError(FailureType.DataError,
'BIP/ZIP 143 input without amount')
raise SigningError(
FailureType.DataError, "BIP/ZIP 143 input without amount"
)
segwit[i] = False
segwit_in += txi.amount
total_in += txi.amount
else:
segwit[i] = False
total_in += await get_prevtx_output_value(
coin, tx_req, txi.prev_hash, txi.prev_index)
coin, tx_req, txi.prev_hash, txi.prev_index
)
else:
raise SigningError(FailureType.DataError,
'Wrong input script type')
raise SigningError(FailureType.DataError, "Wrong input script type")
for o in range(tx.outputs_count):
# STAGE_REQUEST_3_OUTPUT
@ -133,8 +140,7 @@ async def check_tx_fee(tx: SignTx, root: bip32.HDNode):
# output is change and does not need confirmation
change_out = txo.amount
elif not await confirm_output(txo, coin):
raise SigningError(FailureType.ActionCancelled,
'Output cancelled')
raise SigningError(FailureType.ActionCancelled, "Output cancelled")
write_tx_output(h_first, txo_bin)
hash143.add_output(txo_bin)
@ -142,18 +148,15 @@ async def check_tx_fee(tx: SignTx, root: bip32.HDNode):
fee = total_in - total_out
if fee < 0:
raise SigningError(FailureType.NotEnoughFunds,
'Not enough funds')
raise SigningError(FailureType.NotEnoughFunds, "Not enough funds")
# fee > (coin.maxfee per byte * tx size)
if fee > (coin.maxfee_kb / 1000) * (weight.get_total() / 4):
if not await confirm_feeoverthreshold(fee, coin):
raise SigningError(FailureType.ActionCancelled,
'Signing cancelled')
raise SigningError(FailureType.ActionCancelled, "Signing cancelled")
if not await confirm_total(total_out - change_out, fee, coin):
raise SigningError(FailureType.ActionCancelled,
'Total cancelled')
raise SigningError(FailureType.ActionCancelled, "Total cancelled")
return h_first, hash143, segwit, total_in, wallet_path
@ -189,11 +192,14 @@ async def sign_tx(tx: SignTx, root: bip32.HDNode):
# STAGE_REQUEST_SEGWIT_INPUT
txi_sign = await request_tx_input(tx_req, i_sign)
is_segwit = (txi_sign.script_type == InputScriptType.SPENDWITNESS or
txi_sign.script_type == InputScriptType.SPENDP2SHWITNESS)
is_segwit = (
txi_sign.script_type == InputScriptType.SPENDWITNESS
or txi_sign.script_type == InputScriptType.SPENDP2SHWITNESS
)
if not is_segwit:
raise SigningError(FailureType.ProcessError,
'Transaction has changed during signing')
raise SigningError(
FailureType.ProcessError, "Transaction has changed during signing"
)
input_check_wallet_path(txi_sign, wallet_path)
key_sign = node_derive(root, txi_sign.address_n)
@ -201,7 +207,8 @@ async def sign_tx(tx: SignTx, root: bip32.HDNode):
txi_sign.script_sig = input_derive_script(coin, txi_sign, key_sign_pub)
w_txi = bytearray_with_cap(
7 + len(txi_sign.prev_hash) + 4 + len(txi_sign.script_sig) + 4)
7 + len(txi_sign.prev_hash) + 4 + len(txi_sign.script_sig) + 4
)
if i_sign == 0: # serializing first input => prepend headers
write_bytes(w_txi, get_tx_header(coin, tx, True))
write_tx_input(w_txi, txi_sign)
@ -213,17 +220,21 @@ async def sign_tx(tx: SignTx, root: bip32.HDNode):
txi_sign = await request_tx_input(tx_req, i_sign)
input_check_wallet_path(txi_sign, wallet_path)
is_bip143 = (txi_sign.script_type == InputScriptType.SPENDADDRESS or
txi_sign.script_type == InputScriptType.SPENDMULTISIG)
is_bip143 = (
txi_sign.script_type == InputScriptType.SPENDADDRESS
or txi_sign.script_type == InputScriptType.SPENDMULTISIG
)
if not is_bip143 or txi_sign.amount > authorized_in:
raise SigningError(FailureType.ProcessError,
'Transaction has changed during signing')
raise SigningError(
FailureType.ProcessError, "Transaction has changed during signing"
)
authorized_in -= txi_sign.amount
key_sign = node_derive(root, txi_sign.address_n)
key_sign_pub = key_sign.public_key()
hash143_hash = hash143.preimage_hash(
coin, tx, txi_sign, ecdsa_hash_pubkey(key_sign_pub), get_hash_type(coin))
coin, tx, txi_sign, ecdsa_hash_pubkey(key_sign_pub), get_hash_type(coin)
)
# if multisig, check if singing with a key that is included in multisig
if txi_sign.multisig:
@ -235,9 +246,11 @@ async def sign_tx(tx: SignTx, root: bip32.HDNode):
# serialize input with correct signature
txi_sign.script_sig = input_derive_script(
coin, txi_sign, key_sign_pub, signature)
coin, txi_sign, key_sign_pub, signature
)
w_txi_sign = bytearray_with_cap(
5 + len(txi_sign.prev_hash) + 4 + len(txi_sign.script_sig) + 4)
5 + len(txi_sign.prev_hash) + 4 + len(txi_sign.script_sig) + 4
)
if i_sign == 0: # serializing first input => prepend headers
write_bytes(w_txi_sign, get_tx_header(coin, tx))
write_tx_input(w_txi_sign, txi_sign)
@ -252,10 +265,12 @@ async def sign_tx(tx: SignTx, root: bip32.HDNode):
h_second = HashWriter(sha256)
if tx.overwintered:
write_uint32(h_sign, tx.version | OVERWINTERED) # nVersion | fOverwintered
write_uint32(h_sign, coin.version_group_id) # nVersionGroupId
write_uint32(
h_sign, tx.version | OVERWINTERED
) # nVersion | fOverwintered
write_uint32(h_sign, coin.version_group_id) # nVersionGroupId
else:
write_uint32(h_sign, tx.version) # nVersion
write_uint32(h_sign, tx.version) # nVersion
write_varint(h_sign, tx.inputs_count)
@ -272,15 +287,21 @@ async def sign_tx(tx: SignTx, root: bip32.HDNode):
# to the previous tx's scriptPubKey (P2PKH) or a redeem script (P2SH)
if txi_sign.script_type == InputScriptType.SPENDMULTISIG:
txi_sign.script_sig = output_script_multisig(
multisig_get_pubkeys(txi_sign.multisig),
txi_sign.multisig.m)
multisig_get_pubkeys(txi_sign.multisig), txi_sign.multisig.m
)
elif txi_sign.script_type == InputScriptType.SPENDADDRESS:
txi_sign.script_sig = output_script_p2pkh(ecdsa_hash_pubkey(key_sign_pub))
txi_sign.script_sig = output_script_p2pkh(
ecdsa_hash_pubkey(key_sign_pub)
)
if coin.bip115:
txi_sign.script_sig += script_replay_protection_bip115(txi_sign.prev_block_hash_bip115, txi_sign.prev_block_height_bip115)
txi_sign.script_sig += script_replay_protection_bip115(
txi_sign.prev_block_hash_bip115,
txi_sign.prev_block_height_bip115,
)
else:
raise SigningError(FailureType.ProcessError,
'Unknown transaction type')
raise SigningError(
FailureType.ProcessError, "Unknown transaction type"
)
else:
txi.script_sig = bytes()
write_tx_input(h_sign, txi)
@ -298,29 +319,34 @@ async def sign_tx(tx: SignTx, root: bip32.HDNode):
write_uint32(h_sign, tx.lock_time)
if tx.overwintered:
write_uint32(h_sign, tx.expiry) # expiryHeight
write_varint(h_sign, 0) # nJoinSplit
write_varint(h_sign, 0) # nJoinSplit
write_uint32(h_sign, get_hash_type(coin))
# check the control digests
if get_tx_hash(h_first, False) != get_tx_hash(h_second):
raise SigningError(FailureType.ProcessError,
'Transaction has changed during signing')
raise SigningError(
FailureType.ProcessError, "Transaction has changed during signing"
)
# if multisig, check if singing with a key that is included in multisig
if txi_sign.multisig:
multisig_pubkey_index(txi_sign.multisig, key_sign_pub)
# compute the signature from the tx digest
signature = ecdsa_sign(key_sign, get_tx_hash(h_sign, double=coin.sign_hash_double))
signature = ecdsa_sign(
key_sign, get_tx_hash(h_sign, double=coin.sign_hash_double)
)
tx_ser.signature_index = i_sign
tx_ser.signature = signature
# serialize input with correct signature
txi_sign.script_sig = input_derive_script(
coin, txi_sign, key_sign_pub, signature)
coin, txi_sign, key_sign_pub, signature
)
w_txi_sign = bytearray_with_cap(
5 + len(txi_sign.prev_hash) + 4 + len(txi_sign.script_sig) + 4)
5 + len(txi_sign.prev_hash) + 4 + len(txi_sign.script_sig) + 4
)
if i_sign == 0: # serializing first input => prepend headers
write_bytes(w_txi_sign, get_tx_header(coin, tx))
write_tx_input(w_txi_sign, txi_sign)
@ -336,8 +362,7 @@ async def sign_tx(tx: SignTx, root: bip32.HDNode):
txo_bin.script_pubkey = output_derive_script(txo, coin, root)
# serialize output
w_txo_bin = bytearray_with_cap(
5 + 8 + 5 + len(txo_bin.script_pubkey) + 4)
w_txo_bin = bytearray_with_cap(5 + 8 + 5 + len(txo_bin.script_pubkey) + 4)
if o == 0: # serializing first output => prepend outputs count
write_varint(w_txo_bin, tx.outputs_count)
write_tx_output(w_txo_bin, txo_bin)
@ -357,23 +382,29 @@ async def sign_tx(tx: SignTx, root: bip32.HDNode):
txi = await request_tx_input(tx_req, i)
input_check_wallet_path(txi, wallet_path)
is_segwit = (txi.script_type == InputScriptType.SPENDWITNESS or
txi.script_type == InputScriptType.SPENDP2SHWITNESS)
is_segwit = (
txi.script_type == InputScriptType.SPENDWITNESS
or txi.script_type == InputScriptType.SPENDP2SHWITNESS
)
if not is_segwit or txi.amount > authorized_in:
raise SigningError(FailureType.ProcessError,
'Transaction has changed during signing')
raise SigningError(
FailureType.ProcessError, "Transaction has changed during signing"
)
authorized_in -= txi.amount
key_sign = node_derive(root, txi.address_n)
key_sign_pub = key_sign.public_key()
hash143_hash = hash143.preimage_hash(
coin, tx, txi, ecdsa_hash_pubkey(key_sign_pub), get_hash_type(coin))
coin, tx, txi, ecdsa_hash_pubkey(key_sign_pub), get_hash_type(coin)
)
signature = ecdsa_sign(key_sign, hash143_hash)
if txi.multisig:
# find out place of our signature based on the pubkey
signature_index = multisig_pubkey_index(txi.multisig, key_sign_pub)
witness = witness_p2wsh(txi.multisig, signature, signature_index, get_hash_type(coin))
witness = witness_p2wsh(
txi.multisig, signature, signature_index, get_hash_type(coin)
)
else:
witness = witness_p2wpkh(signature, key_sign_pub, get_hash_type(coin))
@ -390,12 +421,14 @@ async def sign_tx(tx: SignTx, root: bip32.HDNode):
write_uint32(tx_ser.serialized_tx, tx.lock_time)
if tx.overwintered:
write_uint32(tx_ser.serialized_tx, tx.expiry) # expiryHeight
write_varint(tx_ser.serialized_tx, 0) # nJoinSplit
write_varint(tx_ser.serialized_tx, 0) # nJoinSplit
await request_tx_finish(tx_req)
async def get_prevtx_output_value(coin: CoinInfo, tx_req: TxRequest, prev_hash: bytes, prev_index: int) -> int:
async def get_prevtx_output_value(
coin: CoinInfo, tx_req: TxRequest, prev_hash: bytes, prev_index: int
) -> int:
total_out = 0 # sum of output amounts
# STAGE_REQUEST_2_PREV_META
@ -405,9 +438,9 @@ async def get_prevtx_output_value(coin: CoinInfo, tx_req: TxRequest, prev_hash:
if tx.overwintered:
write_uint32(txh, tx.version | OVERWINTERED) # nVersion | fOverwintered
write_uint32(txh, coin.version_group_id) # nVersionGroupId
write_uint32(txh, coin.version_group_id) # nVersionGroupId
else:
write_uint32(txh, tx.version) # nVersion
write_uint32(txh, tx.version) # nVersion
write_varint(txh, tx.inputs_cnt)
@ -438,8 +471,7 @@ async def get_prevtx_output_value(coin: CoinInfo, tx_req: TxRequest, prev_hash:
ofs += len(data)
if get_tx_hash(txh, double=coin.sign_hash_double, reverse=True) != prev_hash:
raise SigningError(FailureType.ProcessError,
'Encountered invalid prev_hash')
raise SigningError(FailureType.ProcessError, "Encountered invalid prev_hash")
return total_out
@ -461,7 +493,7 @@ def get_tx_header(coin: CoinInfo, tx: SignTx, segwit: bool = False):
w_txi = bytearray()
if tx.overwintered:
write_uint32(w_txi, tx.version | OVERWINTERED) # nVersion | fOverwintered
write_uint32(w_txi, coin.version_group_id) # nVersionGroupId
write_uint32(w_txi, coin.version_group_id) # nVersionGroupId
else:
write_uint32(w_txi, tx.version) # nVersion
if segwit:
@ -480,33 +512,36 @@ def output_derive_script(o: TxOutputType, coin: CoinInfo, root: bip32.HDNode) ->
if o.script_type == OutputScriptType.PAYTOOPRETURN:
# op_return output
if o.amount != 0:
raise SigningError(FailureType.DataError,
'OP_RETURN output with non-zero amount')
raise SigningError(
FailureType.DataError, "OP_RETURN output with non-zero amount"
)
return output_script_paytoopreturn(o.op_return_data)
if o.address_n:
# change output
if o.address:
raise SigningError(FailureType.DataError, 'Address in change output')
raise SigningError(FailureType.DataError, "Address in change output")
o.address = get_address_for_change(o, coin, root)
else:
if not o.address:
raise SigningError(FailureType.DataError, 'Missing address')
raise SigningError(FailureType.DataError, "Missing address")
if coin.bech32_prefix and o.address.startswith(coin.bech32_prefix):
# p2wpkh or p2wsh
witprog = decode_bech32_address(coin.bech32_prefix, o.address)
return output_script_native_p2wpkh_or_p2wsh(witprog)
if coin.cashaddr_prefix is not None and o.address.startswith(coin.cashaddr_prefix + ':'):
prefix, addr = o.address.split(':')
if coin.cashaddr_prefix is not None and o.address.startswith(
coin.cashaddr_prefix + ":"
):
prefix, addr = o.address.split(":")
version, data = cashaddr.decode(prefix, addr)
if version == cashaddr.ADDRESS_TYPE_P2KH:
version = coin.address_type
elif version == cashaddr.ADDRESS_TYPE_P2SH:
version = coin.address_type_p2sh
else:
raise ValueError('Unknown cashaddr address type')
raise ValueError("Unknown cashaddr address type")
raw_address = bytes([version]) + data
else:
raw_address = base58.decode_check(o.address, coin.b58_hash)
@ -516,7 +551,9 @@ def output_derive_script(o: TxOutputType, coin: CoinInfo, root: bip32.HDNode) ->
pubkeyhash = address_type.strip(coin.address_type, raw_address)
script = output_script_p2pkh(pubkeyhash)
if coin.bip115:
script += script_replay_protection_bip115(o.block_hash_bip115, o.block_height_bip115)
script += script_replay_protection_bip115(
o.block_hash_bip115, o.block_height_bip115
)
return script
elif address_type.check(coin.address_type_p2sh, raw_address):
@ -524,10 +561,12 @@ def output_derive_script(o: TxOutputType, coin: CoinInfo, root: bip32.HDNode) ->
scripthash = address_type.strip(coin.address_type_p2sh, raw_address)
script = output_script_p2sh(scripthash)
if coin.bip115:
script += script_replay_protection_bip115(o.block_hash_bip115, o.block_height_bip115)
script += script_replay_protection_bip115(
o.block_hash_bip115, o.block_height_bip115
)
return script
raise SigningError(FailureType.DataError, 'Invalid address type')
raise SigningError(FailureType.DataError, "Invalid address type")
def get_address_for_change(o: TxOutputType, coin: CoinInfo, root: bip32.HDNode):
@ -540,33 +579,40 @@ def get_address_for_change(o: TxOutputType, coin: CoinInfo, root: bip32.HDNode):
elif o.script_type == OutputScriptType.PAYTOP2SHWITNESS:
input_script_type = InputScriptType.SPENDP2SHWITNESS
else:
raise SigningError(FailureType.DataError, 'Invalid script type')
return get_address(input_script_type, coin, node_derive(root, o.address_n), o.multisig)
raise SigningError(FailureType.DataError, "Invalid script type")
return get_address(
input_script_type, coin, node_derive(root, o.address_n), o.multisig
)
def output_is_change(o: TxOutputType, wallet_path: list, segwit_in: int) -> bool:
is_segwit = (o.script_type == OutputScriptType.PAYTOWITNESS or
o.script_type == OutputScriptType.PAYTOP2SHWITNESS)
is_segwit = (
o.script_type == OutputScriptType.PAYTOWITNESS
or o.script_type == OutputScriptType.PAYTOP2SHWITNESS
)
if is_segwit and o.amount > segwit_in:
# if the output is segwit, make sure it doesn't spend more than what the
# segwit inputs paid. this is to prevent user being tricked into
# creating ANYONECANSPEND outputs before full segwit activation.
return False
return (wallet_path is not None and
wallet_path == o.address_n[:-_BIP32_WALLET_DEPTH] and
o.address_n[-2] <= _BIP32_CHANGE_CHAIN and
o.address_n[-1] <= _BIP32_MAX_LAST_ELEMENT)
return (
wallet_path is not None
and wallet_path == o.address_n[:-_BIP32_WALLET_DEPTH]
and o.address_n[-2] <= _BIP32_CHANGE_CHAIN
and o.address_n[-1] <= _BIP32_MAX_LAST_ELEMENT
)
# Tx Inputs
# ===
def input_derive_script(coin: CoinInfo, i: TxInputType, pubkey: bytes, signature: bytes=None) -> bytes:
def input_derive_script(
coin: CoinInfo, i: TxInputType, pubkey: bytes, signature: bytes = None
) -> bytes:
if i.script_type == InputScriptType.SPENDADDRESS:
# p2pkh or p2sh
return input_script_p2pkh_or_p2sh(
pubkey, signature, get_hash_type(coin))
return input_script_p2pkh_or_p2sh(pubkey, signature, get_hash_type(coin))
if i.script_type == InputScriptType.SPENDP2SHWITNESS:
# p2wpkh or p2wsh using p2sh
@ -589,10 +635,11 @@ def input_derive_script(coin: CoinInfo, i: TxInputType, pubkey: bytes, signature
# p2sh multisig
signature_index = multisig_pubkey_index(i.multisig, pubkey)
return input_script_multisig(
i.multisig, signature, signature_index, get_hash_type(coin))
i.multisig, signature, signature_index, get_hash_type(coin)
)
else:
raise SigningError(FailureType.ProcessError, 'Invalid script type')
raise SigningError(FailureType.ProcessError, "Invalid script type")
def input_extract_wallet_path(txi: TxInputType, wallet_path: list) -> list:
@ -613,8 +660,9 @@ def input_check_wallet_path(txi: TxInputType, wallet_path: list) -> list:
return # there was a mismatch in Phase 1, ignore it now
address_n = txi.address_n[:-_BIP32_WALLET_DEPTH]
if wallet_path != address_n:
raise SigningError(FailureType.ProcessError,
'Transaction has changed during signing')
raise SigningError(
FailureType.ProcessError, "Transaction has changed during signing"
)
def node_derive(root: bip32.HDNode, address_n: list) -> bip32.HDNode:
@ -628,7 +676,9 @@ def address_n_matches_coin(address_n: list, coin: CoinInfo) -> bool:
return True # path is too short
if address_n[0] not in (44 | 0x80000000, 49 | 0x80000000, 84 | 0x80000000):
return True # path is not BIP44/49/84
return address_n[1] == (coin.slip44 | 0x80000000) # check whether coin_type matches slip44 value
return address_n[1] == (
coin.slip44 | 0x80000000
) # check whether coin_type matches slip44 value
def ecdsa_sign(node: bip32.HDNode, digest: bytes) -> bytes:
@ -638,10 +688,8 @@ def ecdsa_sign(node: bip32.HDNode, digest: bytes) -> bytes:
def is_change(
txo: TxOutputType,
wallet_path: list,
segwit_in: int,
multifp: MultisigFingerprint) -> bool:
txo: TxOutputType, wallet_path: list, segwit_in: int, multifp: MultisigFingerprint
) -> bool:
if txo.multisig:
if not multifp.matches(txo.multisig):
return False

View File

@ -34,14 +34,14 @@ _TXSIZE_WITNESSSCRIPT = const(34)
class TxWeightCalculator:
def __init__(self, inputs_count: int, outputs_count: int):
self.inputs_count = inputs_count
self.counter = 4 * (
_TXSIZE_HEADER +
_TXSIZE_FOOTER +
self.ser_length_size(inputs_count) +
self.ser_length_size(outputs_count))
_TXSIZE_HEADER
+ _TXSIZE_FOOTER
+ self.ser_length_size(inputs_count)
+ self.ser_length_size(outputs_count)
)
self.segwit = False
def add_witness_header(self):
@ -53,26 +53,31 @@ class TxWeightCalculator:
def add_input(self, i: TxInputType):
if i.multisig:
multisig_script_size = (
_TXSIZE_MULTISIGSCRIPT +
len(i.multisig.pubkeys) * (1 + _TXSIZE_PUBKEY))
multisig_script_size = _TXSIZE_MULTISIGSCRIPT + len(i.multisig.pubkeys) * (
1 + _TXSIZE_PUBKEY
)
input_script_size = (
1 + # the OP_FALSE bug in multisig
i.multisig.m * (1 + _TXSIZE_SIGNATURE) +
self.op_push_size(multisig_script_size) +
multisig_script_size)
1
+ i.multisig.m * (1 + _TXSIZE_SIGNATURE) # the OP_FALSE bug in multisig
+ self.op_push_size(multisig_script_size)
+ multisig_script_size
)
else:
input_script_size = 1 + _TXSIZE_SIGNATURE + 1 + _TXSIZE_PUBKEY
self.counter += 4 * _TXSIZE_INPUT
if (i.script_type == InputScriptType.SPENDADDRESS or
i.script_type == InputScriptType.SPENDMULTISIG):
if (
i.script_type == InputScriptType.SPENDADDRESS
or i.script_type == InputScriptType.SPENDMULTISIG
):
input_script_size += self.ser_length_size(input_script_size)
self.counter += 4 * input_script_size
elif (i.script_type == InputScriptType.SPENDWITNESS or
i.script_type == InputScriptType.SPENDP2SHWITNESS):
elif (
i.script_type == InputScriptType.SPENDWITNESS
or i.script_type == InputScriptType.SPENDP2SHWITNESS
):
self.add_witness_header()
if i.script_type == InputScriptType.SPENDP2SHWITNESS:
if i.multisig:

View File

@ -2,7 +2,6 @@ from trezor.crypto.hashlib import sha256
from trezor.messages.TxInputType import TxInputType
from trezor.messages.TxOutputBinType import TxOutputBinType
# TX Serialization
# ===
@ -131,7 +130,7 @@ def bytearray_with_cap(cap: int) -> bytearray:
# ===
def get_tx_hash(w, double: bool=False, reverse: bool=False) -> bytes:
def get_tx_hash(w, double: bool = False, reverse: bool = False) -> bytes:
d = w.get_digest()
if double:
d = sha256(d).digest()

View File

@ -1,20 +1,27 @@
from trezor import ui, wire
from trezor import wire
from trezor.crypto.curve import secp256k1
from trezor.messages.InputScriptType import SPENDADDRESS, SPENDP2SHWITNESS, SPENDWITNESS
from trezor.messages.Success import Success
from trezor.ui.text import Text
from apps.common import coins
from apps.common.confirm import require_confirm
from apps.common.display_address import split_address
from apps.common.signverify import message_digest, split_message
from apps.wallet.sign_tx.addresses import address_pkh, address_p2wpkh_in_p2sh, address_p2wpkh, address_to_cashaddr, address_short
from apps.wallet.sign_tx.addresses import (
address_p2wpkh,
address_p2wpkh_in_p2sh,
address_pkh,
address_short,
address_to_cashaddr,
)
async def verify_message(ctx, msg):
message = msg.message
address = msg.address
signature = msg.signature
coin_name = msg.coin_name or 'Bitcoin'
coin_name = msg.coin_name or "Bitcoin"
coin = coins.by_name(coin_name)
digest = message_digest(coin, message)
@ -30,12 +37,12 @@ async def verify_message(ctx, msg):
script_type = SPENDWITNESS # native segwit
signature = bytes([signature[0] - 8]) + signature[1:]
else:
raise wire.ProcessError('Invalid signature')
raise wire.ProcessError("Invalid signature")
pubkey = secp256k1.verify_recover(signature, digest)
if not pubkey:
raise wire.ProcessError('Invalid signature')
raise wire.ProcessError("Invalid signature")
if script_type == SPENDADDRESS:
addr = address_pkh(pubkey, coin)
@ -46,21 +53,21 @@ async def verify_message(ctx, msg):
elif script_type == SPENDWITNESS:
addr = address_p2wpkh(pubkey, coin.bech32_prefix)
else:
raise wire.ProcessError('Invalid signature')
raise wire.ProcessError("Invalid signature")
if addr != address:
raise wire.ProcessError('Invalid signature')
raise wire.ProcessError("Invalid signature")
await require_confirm_verify_message(ctx, address_short(coin, address), message)
return Success(message='Message verified')
return Success(message="Message verified")
async def require_confirm_verify_message(ctx, address, message):
text = Text('Confirm address')
text = Text("Confirm address")
text.mono(*split_address(address))
await require_confirm(ctx, text)
text = Text('Verify message')
text = Text("Verify message")
text.normal(*split_message(message))
await require_confirm(ctx, text)

View File

@ -1,5 +1,6 @@
from trezor import config, loop, res, ui
from trezor.pin import pin_to_int, show_pin_timeout
from apps.common.request_pin import request_pin
@ -7,7 +8,7 @@ async def bootscreen():
while True:
try:
if not config.has_pin():
config.unlock(pin_to_int(''), show_pin_timeout)
config.unlock(pin_to_int(""), show_pin_timeout)
return
await lockscreen()
label = None
@ -16,7 +17,7 @@ async def bootscreen():
if config.unlock(pin_to_int(pin), show_pin_timeout):
return
else:
label = 'Wrong PIN, enter again'
label = "Wrong PIN, enter again"
except: # noqa: E722
pass
@ -27,9 +28,9 @@ async def lockscreen():
label = storage.get_label()
image = storage.get_homescreen()
if not label:
label = 'My TREZOR'
label = "My TREZOR"
if not image:
image = res.load('apps/homescreen/res/bg.toif')
image = res.load("apps/homescreen/res/bg.toif")
await ui.backlight_slide(ui.BACKLIGHT_DIM)
@ -39,9 +40,11 @@ async def lockscreen():
ui.display.bar_radius(40, 100, 160, 40, ui.TITLE_GREY, ui.BG, 4)
ui.display.bar_radius(42, 102, 156, 36, ui.BG, ui.TITLE_GREY, 4)
ui.display.text_center(ui.WIDTH // 2, 128, 'Locked', ui.BOLD, ui.TITLE_GREY, ui.BG)
ui.display.text_center(ui.WIDTH // 2, 128, "Locked", ui.BOLD, ui.TITLE_GREY, ui.BG)
ui.display.text_center(ui.WIDTH // 2 + 10, 220, 'Tap to unlock', ui.BOLD, ui.TITLE_GREY, ui.BG)
ui.display.text_center(
ui.WIDTH // 2 + 10, 220, "Tap to unlock", ui.BOLD, ui.TITLE_GREY, ui.BG
)
ui.display.icon(45, 202, res.load(ui.ICON_CLICK), ui.TITLE_GREY, ui.BG)
await ui.backlight_slide(ui.BACKLIGHT_NORMAL)

View File

@ -1,3 +1,5 @@
# isort:skip_file
# unlock the device
import boot # noqa: F401
@ -13,6 +15,7 @@ import apps.wallet
import apps.ethereum
import apps.lisk
import apps.nem
if __debug__:
import apps.debug
else:
@ -41,5 +44,6 @@ utils.set_mode_unprivileged()
# run main event loop and specify which screen is the default
from apps.homescreen.homescreen import homescreen
workflow.startdefault(homescreen)
loop.run()

View File

@ -70,6 +70,7 @@ async def dump_uvarint(writer, n):
# But this is harder in Python because we don't natively know the bit size of the number.
# So we have to branch on whether the number is negative.
def sint_to_uint(sint):
res = sint << 1
if sint < 0:
@ -114,11 +115,10 @@ class MessageType:
setattr(self, kw, kwargs[kw])
def __eq__(self, rhs):
return (self.__class__ is rhs.__class__ and
self.__dict__ == rhs.__dict__)
return self.__class__ is rhs.__class__ and self.__dict__ == rhs.__dict__
def __repr__(self):
return '<%s>' % self.__class__.__name__
return "<%s>" % self.__class__.__name__
class LimitedReader:
@ -191,7 +191,7 @@ async def load_message(reader, msg_type):
elif ftype is UnicodeType:
fvalue = bytearray(ivalue)
await reader.areadinto(fvalue)
fvalue = str(fvalue, 'utf8')
fvalue = str(fvalue, "utf8")
elif issubclass(ftype, MessageType):
fvalue = await load_message(LimitedReader(reader, ivalue), ftype)
else:
@ -247,7 +247,7 @@ async def dump_message(writer, msg):
await writer.awrite(svalue)
elif ftype is UnicodeType:
bvalue = bytes(svalue, 'utf8')
bvalue = bytes(svalue, "utf8")
await dump_uvarint(writer, len(bvalue))
await writer.awrite(bvalue)

View File

@ -1,2 +1,2 @@
import trezorconfig as config # noqa: F401
import trezorio as io # noqa: F401
import trezorio as io # noqa: F401

View File

@ -1 +1,10 @@
from trezorcrypto import bip32, bip39, chacha20poly1305, crc, pbkdf2, random, rfc6979, nem # noqa: F401
from trezorcrypto import ( # noqa: F401
bip32,
bip39,
chacha20poly1305,
crc,
nem,
pbkdf2,
random,
rfc6979,
)

View File

@ -2,70 +2,70 @@ from trezorcrypto import AES
def AES_ECB_Encrypt(key: bytes) -> AES:
'''
"""
Create AES encryption context in ECB mode
'''
"""
return AES(AES.ECB | AES.Encrypt, key)
def AES_ECB_Decrypt(key: bytes) -> AES:
'''
"""
Create AES decryption context in ECB mode
'''
"""
return AES(AES.ECB | AES.Decrypt, key)
def AES_CBC_Encrypt(key: bytes, iv: bytes) -> AES:
'''
"""
Create AES encryption context in CBC mode
'''
"""
return AES(AES.CBC | AES.Encrypt, key, iv)
def AES_CBC_Decrypt(key: bytes, iv: bytes) -> AES:
'''
"""
Create AES decryption context in CBC mode
'''
"""
return AES(AES.CBC | AES.Decrypt, key, iv)
def AES_CFB_Encrypt(key: bytes, iv: bytes) -> AES:
'''
"""
Create AES encryption context in CFB mode
'''
"""
return AES(AES.CFB | AES.Encrypt, key, iv)
def AES_CFB_Decrypt(key: bytes, iv: bytes) -> AES:
'''
"""
Create AES decryption context in CFB mode
'''
"""
return AES(AES.CFB | AES.Decrypt, key, iv)
def AES_OFB_Encrypt(key: bytes, iv: bytes) -> AES:
'''
"""
Create AES encryption context in OFB mode
'''
"""
return AES(AES.OFB | AES.Encrypt, key, iv)
def AES_OFB_Decrypt(key: bytes, iv: bytes) -> AES:
'''
"""
Create AES decryption context in OFB mode
'''
"""
return AES(AES.OFB | AES.Decrypt, key, iv)
def AES_CTR_Encrypt(key: bytes) -> AES:
'''
"""
Create AES encryption context in CTR mode
'''
"""
return AES(AES.CTR | AES.Encrypt, key)
def AES_CTR_Decrypt(key: bytes) -> AES:
'''
"""
Create AES decryption context in CTR mode
'''
"""
return AES(AES.CTR | AES.Decrypt, key)

View File

@ -5,8 +5,7 @@
from ubinascii import unhexlify
from ustruct import unpack
_b32alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'
_b32alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"
_b32tab = [ord(c) for c in _b32alphabet]
_b32rev = dict([(ord(v), k) for k, v in enumerate(_b32alphabet)])
@ -25,27 +24,30 @@ def encode(s: bytes) -> str:
# leftover bit of c1 and tack it onto c2. Then we take the 2 leftover
# bits of c2 and tack them onto c3. The shifts and masks are intended
# to give us values of exactly 5 bits in width.
c1, c2, c3 = unpack('!HHB', s[i * 5:(i + 1) * 5])
c1, c2, c3 = unpack("!HHB", s[i * 5 : (i + 1) * 5])
c2 += (c1 & 1) << 16 # 17 bits wide
c3 += (c2 & 3) << 8 # 10 bits wide
encoded += bytes([_b32tab[c1 >> 11], # bits 1 - 5
_b32tab[(c1 >> 6) & 0x1f], # bits 6 - 10
_b32tab[(c1 >> 1) & 0x1f], # bits 11 - 15
_b32tab[c2 >> 12], # bits 16 - 20 (1 - 5)
_b32tab[(c2 >> 7) & 0x1f], # bits 21 - 25 (6 - 10)
_b32tab[(c2 >> 2) & 0x1f], # bits 26 - 30 (11 - 15)
_b32tab[c3 >> 5], # bits 31 - 35 (1 - 5)
_b32tab[c3 & 0x1f], # bits 36 - 40 (1 - 5)
])
c3 += (c2 & 3) << 8 # 10 bits wide
encoded += bytes(
[
_b32tab[c1 >> 11], # bits 1 - 5
_b32tab[(c1 >> 6) & 0x1f], # bits 6 - 10
_b32tab[(c1 >> 1) & 0x1f], # bits 11 - 15
_b32tab[c2 >> 12], # bits 16 - 20 (1 - 5)
_b32tab[(c2 >> 7) & 0x1f], # bits 21 - 25 (6 - 10)
_b32tab[(c2 >> 2) & 0x1f], # bits 26 - 30 (11 - 15)
_b32tab[c3 >> 5], # bits 31 - 35 (1 - 5)
_b32tab[c3 & 0x1f], # bits 36 - 40 (1 - 5)
]
)
# Adjust for any leftover partial quanta
if leftover == 1:
encoded = encoded[:-6] + b'======'
encoded = encoded[:-6] + b"======"
elif leftover == 2:
encoded = encoded[:-4] + b'===='
encoded = encoded[:-4] + b"===="
elif leftover == 3:
encoded = encoded[:-3] + b'==='
encoded = encoded[:-3] + b"==="
elif leftover == 4:
encoded = encoded[:-1] + b'='
encoded = encoded[:-1] + b"="
return bytes(encoded).decode()
@ -54,11 +56,11 @@ def decode(s: str) -> bytes:
s = s.encode()
quanta, leftover = divmod(len(s), 8)
if leftover:
raise ValueError('Incorrect padding')
raise ValueError("Incorrect padding")
# Strip off pad characters from the right. We need to count the pad
# characters because this will tell us how many null bytes to remove from
# the end of the decoded string.
padchars = s.find(b'=')
padchars = s.find(b"=")
if padchars > 0:
padchars = len(s) - padchars
s = s[:-padchars]
@ -72,17 +74,17 @@ def decode(s: str) -> bytes:
for c in s:
val = _b32rev.get(c)
if val is None:
raise ValueError('Non-base32 digit found')
raise ValueError("Non-base32 digit found")
acc += _b32rev[c] << shift
shift -= 5
if shift < 0:
parts.append(unhexlify(('%010x' % acc).encode()))
parts.append(unhexlify(("%010x" % acc).encode()))
acc = 0
shift = 35
# Process the last, partial quanta
last = unhexlify(bytes('%010x' % acc, "ascii"))
last = unhexlify(bytes("%010x" % acc, "ascii"))
if padchars == 0:
last = b'' # No characters
last = b"" # No characters
elif padchars == 1:
last = last[:-1]
elif padchars == 3:
@ -92,6 +94,6 @@ def decode(s: str) -> bytes:
elif padchars == 6:
last = last[:-4]
else:
raise ValueError('Incorrect padding')
raise ValueError("Incorrect padding")
parts.append(last)
return b''.join(parts)
return b"".join(parts)

View File

@ -14,15 +14,15 @@
#
# 58 character alphabet used
_alphabet = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'
_alphabet = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
def encode(data: bytes) -> str:
'''
"""
Convert bytes to base58 encoded string.
'''
"""
origlen = len(data)
data = data.lstrip(b'\0')
data = data.lstrip(b"\0")
newlen = len(data)
p, acc = 1, 0
@ -30,18 +30,18 @@ def encode(data: bytes) -> str:
acc += p * c
p = p << 8
result = ''
result = ""
while acc > 0:
acc, mod = divmod(acc, 58)
result += _alphabet[mod]
return ''.join((c for c in reversed(result + _alphabet[0] * (origlen - newlen))))
return "".join((c for c in reversed(result + _alphabet[0] * (origlen - newlen))))
def decode(string: str) -> bytes:
'''
"""
Convert base58 encoded string to bytes.
'''
"""
origlen = len(string)
string = string.lstrip(_alphabet[0])
newlen = len(string)
@ -61,28 +61,32 @@ def decode(string: str) -> bytes:
def sha256d_32(data: bytes) -> bytes:
from .hashlib import sha256
return sha256(sha256(data).digest()).digest()[:4]
def groestl512d_32(data: bytes) -> bytes:
from .hashlib import groestl512
return groestl512(groestl512(data).digest()).digest()[:4]
def encode_check(data: bytes, digestfunc=sha256d_32) -> str:
'''
"""
Convert bytes to base58 encoded string, append checksum.
'''
"""
return encode(data + digestfunc(data))
def decode_check(string: str, digestfunc=sha256d_32) -> bytes:
'''
"""
Convert base58 encoded string to bytes and verify checksum.
'''
"""
result = decode(string)
digestlen = len(digestfunc(b''))
digestlen = len(digestfunc(b""))
result, check = result[:-digestlen], result[-digestlen:]
if check != digestfunc(result):
raise ValueError('Invalid checksum')
raise ValueError("Invalid checksum")
return result

View File

@ -56,22 +56,23 @@ def bech32_create_checksum(hrp, data):
def bech32_encode(hrp, data):
"""Compute a Bech32 string given HRP and data values."""
combined = data + bech32_create_checksum(hrp, data)
return hrp + '1' + ''.join([CHARSET[d] for d in combined])
return hrp + "1" + "".join([CHARSET[d] for d in combined])
def bech32_decode(bech):
"""Validate a Bech32 string, and determine HRP and data."""
if ((any(ord(x) < 33 or ord(x) > 126 for x in bech)) or
(bech.lower() != bech and bech.upper() != bech)):
if (any(ord(x) < 33 or ord(x) > 126 for x in bech)) or (
bech.lower() != bech and bech.upper() != bech
):
return (None, None)
bech = bech.lower()
pos = bech.rfind('1')
pos = bech.rfind("1")
if pos < 1 or pos + 7 > len(bech) or len(bech) > 90:
return (None, None)
if not all(x in CHARSET for x in bech[pos + 1:]):
if not all(x in CHARSET for x in bech[pos + 1 :]):
return (None, None)
hrp = bech[:pos]
data = [CHARSET.find(x) for x in bech[pos + 1:]]
data = [CHARSET.find(x) for x in bech[pos + 1 :]]
if not bech32_verify_checksum(hrp, data):
return (None, None)
return (hrp, data[:-6])

View File

@ -20,7 +20,7 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
CHARSET = 'qpzry9x8gf2tvdw0s3jn54khce6mua7l'
CHARSET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l"
ADDRESS_TYPE_P2KH = 0
ADDRESS_TYPE_P2SH = 8
@ -60,7 +60,7 @@ def b32decode(inputs):
def b32encode(inputs):
out = ''
out = ""
for char_code in inputs:
out += CHARSET[char_code]
return out
@ -92,13 +92,13 @@ def encode(prefix, version, payload):
payload = bytes([version]) + payload
payload = convertbits(payload, 8, 5)
checksum = calculate_checksum(prefix, payload)
return prefix + ':' + b32encode(payload + checksum)
return prefix + ":" + b32encode(payload + checksum)
def decode(prefix, addr):
addr = addr.lower()
decoded = b32decode(addr)
if not verify_checksum(prefix, decoded):
raise ValueError('Bad cashaddr checksum')
raise ValueError("Bad cashaddr checksum")
data = bytes(convertbits(decoded, 5, 8))
return data[0], data[1:-6]

View File

@ -1,4 +1,3 @@
def encode_length(l: int) -> bytes:
if l < 0x80:
return bytes([l])
@ -11,14 +10,14 @@ def encode_length(l: int) -> bytes:
def encode_int(i: bytes) -> bytes:
i = i.lstrip(b'\x00')
i = i.lstrip(b"\x00")
if i[0] >= 0x80:
i = b'\x00' + i
return b'\x02' + encode_length(len(i)) + i
i = b"\x00" + i
return b"\x02" + encode_length(len(i)) + i
def encode_seq(seq: tuple) -> bytes:
res = b''
res = b""
for i in seq:
res += encode_int(i)
return b'\x30' + encode_length(len(res)) + res
return b"\x30" + encode_length(len(res)) + res

Some files were not shown because too many files have changed in this diff Show More