1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2024-12-16 19:38:09 +00:00

Merge pull request #179 from trezor/tsusanka/nem

NEM
This commit is contained in:
Jan Pochyla 2018-06-07 13:31:07 +02:00 committed by GitHub
commit b1a870177c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
46 changed files with 2629 additions and 14 deletions

View File

@ -27,6 +27,7 @@ CPPDEFINES_MOD += [
'RAND_PLATFORM_INDEPENDENT',
('USE_KECCAK', '1'),
('USE_ETHEREUM', '1'),
('USE_NEM', '1'),
]
SOURCE_MOD += [
'embed/extmod/modtrezorcrypto/modtrezorcrypto.c',
@ -38,6 +39,7 @@ SOURCE_MOD += [
'vendor/trezor-crypto/aes/aes_modes.c',
'vendor/trezor-crypto/aes/aestab.c',
'vendor/trezor-crypto/base58.c',
'vendor/trezor-crypto/base32.c',
'vendor/trezor-crypto/bignum.c',
'vendor/trezor-crypto/bip32.c',
'vendor/trezor-crypto/bip39.c',
@ -63,6 +65,7 @@ SOURCE_MOD += [
'vendor/trezor-crypto/hasher.c',
'vendor/trezor-crypto/hmac.c',
'vendor/trezor-crypto/memzero.c',
'vendor/trezor-crypto/nem.c',
'vendor/trezor-crypto/nist256p1.c',
'vendor/trezor-crypto/pbkdf2.c',
'vendor/trezor-crypto/rand.c',

View File

@ -25,6 +25,7 @@ CPPDEFINES_MOD += [
'AES_192',
('USE_KECCAK', '1'),
('USE_ETHEREUM', '1'),
('USE_NEM', '1'),
]
SOURCE_MOD += [
'embed/extmod/modtrezorcrypto/modtrezorcrypto.c',
@ -35,6 +36,7 @@ SOURCE_MOD += [
'vendor/trezor-crypto/aes/aes_modes.c',
'vendor/trezor-crypto/aes/aestab.c',
'vendor/trezor-crypto/base58.c',
'vendor/trezor-crypto/base32.c',
'vendor/trezor-crypto/bignum.c',
'vendor/trezor-crypto/bip32.c',
'vendor/trezor-crypto/bip39.c',
@ -67,6 +69,7 @@ SOURCE_MOD += [
'vendor/trezor-crypto/secp256k1.c',
'vendor/trezor-crypto/sha2.c',
'vendor/trezor-crypto/sha3.c',
'vendor/trezor-crypto/nem.c',
]
# modtrezorio

View File

@ -24,6 +24,7 @@
#include "bip32.h"
#include "curves.h"
#include "memzero.h"
#include "nem.h"
/// class HDNode:
/// '''
@ -307,12 +308,68 @@ STATIC mp_obj_t mod_trezorcrypto_HDNode_address(mp_obj_t self, mp_obj_t version)
mp_obj_HDNode_t *o = MP_OBJ_TO_PTR(self);
uint32_t v = trezor_obj_get_uint(version);
char address[ADDRESS_MAXLEN];
hdnode_get_address(&o->hdnode, v, address, ADDRESS_MAXLEN);
return mp_obj_new_str(address, strlen(address), false);
}
STATIC MP_DEFINE_CONST_FUN_OBJ_2(mod_trezorcrypto_HDNode_address_obj, mod_trezorcrypto_HDNode_address);
/// def nem_address(self, network: int) -> str:
/// '''
/// Compute a NEM address string from the HD node.
/// '''
STATIC mp_obj_t mod_trezorcrypto_HDNode_nem_address(mp_obj_t self, mp_obj_t network) {
mp_obj_HDNode_t *o = MP_OBJ_TO_PTR(self);
uint8_t n = trezor_obj_get_uint8(network);
char address[NEM_ADDRESS_SIZE + 1]; // + 1 for the 0 byte
if (!hdnode_get_nem_address(&o->hdnode, n, address)) {
mp_raise_ValueError("Failed to compute a NEM address");
}
return mp_obj_new_str_of_type(&mp_type_str, (const uint8_t *)address, strlen(address));
}
STATIC MP_DEFINE_CONST_FUN_OBJ_2(mod_trezorcrypto_HDNode_nem_address_obj, mod_trezorcrypto_HDNode_nem_address);
/// def nem_encrypt(self, transfer_public_key: bytes, iv: bytes, salt: bytes, payload: bytes) -> bytes:
/// '''
/// Encrypts payload using the transfer's public key
/// '''
STATIC mp_obj_t mod_trezorcrypto_HDNode_nem_encrypt(size_t n_args, const mp_obj_t *args) {
mp_obj_HDNode_t *o = MP_OBJ_TO_PTR(args[0]);
mp_buffer_info_t transfer_pk;
mp_get_buffer_raise(args[1], &transfer_pk, MP_BUFFER_READ);
if (transfer_pk.len != 32) {
mp_raise_ValueError("transfer_public_key has invalid length");
}
mp_buffer_info_t iv;
mp_get_buffer_raise(args[2], &iv, MP_BUFFER_READ);
if (iv.len != 16) {
mp_raise_ValueError("iv has invalid length");
}
mp_buffer_info_t salt;
mp_get_buffer_raise(args[3], &salt, MP_BUFFER_READ);
if (salt.len != NEM_SALT_SIZE) {
mp_raise_ValueError("salt has invalid length");
}
mp_buffer_info_t payload;
mp_get_buffer_raise(args[4], &payload, MP_BUFFER_READ);
if (payload.len == 0) {
mp_raise_ValueError("payload is empty");
}
vstr_t vstr;
vstr_init_len(&vstr, NEM_ENCRYPTED_SIZE(payload.len));
if (!hdnode_nem_encrypt(&o->hdnode, *(const ed25519_public_key *)transfer_pk.buf, iv.buf, salt.buf, payload.buf, payload.len, (uint8_t *)vstr.buf)) {
mp_raise_ValueError("HDNode nem encrypt failed");
}
return mp_obj_new_str_from_vstr(&mp_type_bytes, &vstr);
}
STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_trezorcrypto_HDNode_nem_encrypt_obj, 5, 5, mod_trezorcrypto_HDNode_nem_encrypt);
/// def ethereum_pubkeyhash(self) -> bytes:
/// '''
/// Compute an Ethereum pubkeyhash (aka address) from the HD node.
@ -340,6 +397,8 @@ STATIC const mp_rom_map_elem_t mod_trezorcrypto_HDNode_locals_dict_table[] = {
{ MP_ROM_QSTR(MP_QSTR_private_key), MP_ROM_PTR(&mod_trezorcrypto_HDNode_private_key_obj) },
{ MP_ROM_QSTR(MP_QSTR_public_key), MP_ROM_PTR(&mod_trezorcrypto_HDNode_public_key_obj) },
{ MP_ROM_QSTR(MP_QSTR_address), MP_ROM_PTR(&mod_trezorcrypto_HDNode_address_obj) },
{ MP_ROM_QSTR(MP_QSTR_nem_address), MP_ROM_PTR(&mod_trezorcrypto_HDNode_nem_address_obj) },
{ MP_ROM_QSTR(MP_QSTR_nem_encrypt), MP_ROM_PTR(&mod_trezorcrypto_HDNode_nem_encrypt_obj) },
{ MP_ROM_QSTR(MP_QSTR_ethereum_pubkeyhash), MP_ROM_PTR(&mod_trezorcrypto_HDNode_ethereum_pubkeyhash_obj) },
};
STATIC MP_DEFINE_CONST_DICT(mod_trezorcrypto_HDNode_locals_dict, mod_trezorcrypto_HDNode_locals_dict_table);

View File

@ -20,6 +20,7 @@
#include "py/objstr.h"
#include "ed25519-donna/ed25519.h"
#include "ed25519-donna/ed25519-keccak.h"
#include "rand.h"
@ -54,14 +55,14 @@ STATIC mp_obj_t mod_trezorcrypto_ed25519_publickey(mp_obj_t secret_key) {
}
STATIC MP_DEFINE_CONST_FUN_OBJ_1(mod_trezorcrypto_ed25519_publickey_obj, mod_trezorcrypto_ed25519_publickey);
/// def sign(secret_key: bytes, message: bytes) -> bytes:
/// def sign(secret_key: bytes, message: bytes, hasher: str='') -> bytes:
/// '''
/// Uses secret key to produce the signature of message.
/// '''
STATIC mp_obj_t mod_trezorcrypto_ed25519_sign(mp_obj_t secret_key, mp_obj_t message) {
STATIC mp_obj_t mod_trezorcrypto_ed25519_sign(size_t n_args, const mp_obj_t *args) {
mp_buffer_info_t sk, msg;
mp_get_buffer_raise(secret_key, &sk, MP_BUFFER_READ);
mp_get_buffer_raise(message, &msg, MP_BUFFER_READ);
mp_get_buffer_raise(args[0], &sk, MP_BUFFER_READ);
mp_get_buffer_raise(args[1], &msg, MP_BUFFER_READ);
if (sk.len != 32) {
mp_raise_ValueError("Invalid length of secret key");
}
@ -69,12 +70,26 @@ STATIC mp_obj_t mod_trezorcrypto_ed25519_sign(mp_obj_t secret_key, mp_obj_t mess
mp_raise_ValueError("Empty data to sign");
}
ed25519_public_key pk;
ed25519_publickey(*(const ed25519_secret_key *)sk.buf, pk);
uint8_t out[64];
ed25519_sign(msg.buf, msg.len, *(const ed25519_secret_key *)sk.buf, pk, *(ed25519_signature *)out);
mp_buffer_info_t hash_func;
if (n_args == 3) {
mp_get_buffer_raise(args[2], &hash_func, MP_BUFFER_READ);
// if hash_func == 'keccak':
if (memcmp(hash_func.buf, "keccak", sizeof("keccak")) == 0) {
ed25519_publickey_keccak(*(const ed25519_secret_key *)sk.buf, pk);
ed25519_sign_keccak(msg.buf, msg.len, *(const ed25519_secret_key *)sk.buf, pk, *(ed25519_signature *)out);
} else {
mp_raise_ValueError("Unknown hash function");
}
} else {
ed25519_publickey(*(const ed25519_secret_key *)sk.buf, pk);
ed25519_sign(msg.buf, msg.len, *(const ed25519_secret_key *)sk.buf, pk, *(ed25519_signature *)out);
}
return mp_obj_new_bytes(out, sizeof(out));
}
STATIC MP_DEFINE_CONST_FUN_OBJ_2(mod_trezorcrypto_ed25519_sign_obj, mod_trezorcrypto_ed25519_sign);
STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_trezorcrypto_ed25519_sign_obj, 2, 3, mod_trezorcrypto_ed25519_sign);
/// def verify(public_key: bytes, signature: bytes, message: bytes) -> bool:
/// '''

View File

@ -0,0 +1,69 @@
/*
* This file is part of the TREZOR project, https://trezor.io/
*
* Copyright (c) SatoshiLabs
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "py/objstr.h"
#include "embed/extmod/trezorobj.h"
#include "nem.h"
/// def validate_address(address: str, network: int) -> bool:
/// '''
/// Validate a NEM address
/// '''
STATIC mp_obj_t mod_trezorcrypto_nem_validate_address(mp_obj_t address, mp_obj_t network) {
mp_buffer_info_t addr;
mp_get_buffer_raise(address, &addr, MP_BUFFER_READ);
uint32_t n = trezor_obj_get_uint(network);
return mp_obj_new_bool(nem_validate_address(addr.buf, n));
}
STATIC MP_DEFINE_CONST_FUN_OBJ_2(mod_trezorcrypto_nem_validate_address_obj, mod_trezorcrypto_nem_validate_address);
/// def compute_address(public_key: bytes, network: int) -> str:
/// '''
/// Compute a NEM address from a public key
/// '''
STATIC mp_obj_t mod_trezorcrypto_nem_compute_address(mp_obj_t public_key, mp_obj_t network) {
mp_buffer_info_t p;
mp_get_buffer_raise(public_key, &p, MP_BUFFER_READ);
uint32_t n = trezor_obj_get_uint(network);
char address[NEM_ADDRESS_SIZE + 1]; // + 1 for the 0 byte
if (!nem_get_address(p.buf, n, address)) {
mp_raise_ValueError("Failed to compute a NEM address from provided public key");
}
return mp_obj_new_str_of_type(&mp_type_str, (const uint8_t *)address, strlen(address));
}
STATIC MP_DEFINE_CONST_FUN_OBJ_2(mod_trezorcrypto_nem_compute_address_obj, mod_trezorcrypto_nem_compute_address);
// objects definition
STATIC const mp_rom_map_elem_t mod_trezorcrypto_nem_globals_table[] = {
{ MP_ROM_QSTR(MP_QSTR_validate_address), MP_ROM_PTR(&mod_trezorcrypto_nem_validate_address_obj) },
{ MP_ROM_QSTR(MP_QSTR_compute_address), MP_ROM_PTR(&mod_trezorcrypto_nem_compute_address_obj) },
};
STATIC MP_DEFINE_CONST_DICT(mod_trezorcrypto_nem_globals, mod_trezorcrypto_nem_globals_table);
// module definition
STATIC const mp_obj_module_t mod_trezorcrypto_nem_module = {
.base = { &mp_type_module },
.globals = (mp_obj_dict_t*)&mod_trezorcrypto_nem_globals,
};

View File

@ -36,6 +36,7 @@
#include "modtrezorcrypto-curve25519.h"
#include "modtrezorcrypto-ed25519.h"
#include "modtrezorcrypto-nist256p1.h"
#include "modtrezorcrypto-nem.h"
#include "modtrezorcrypto-pbkdf2.h"
#include "modtrezorcrypto-random.h"
#include "modtrezorcrypto-rfc6979.h"
@ -60,6 +61,7 @@ STATIC const mp_rom_map_elem_t mp_module_trezorcrypto_globals_table[] = {
{ MP_ROM_QSTR(MP_QSTR_curve25519), MP_ROM_PTR(&mod_trezorcrypto_curve25519_module) },
{ MP_ROM_QSTR(MP_QSTR_ed25519), MP_ROM_PTR(&mod_trezorcrypto_ed25519_module) },
{ MP_ROM_QSTR(MP_QSTR_nist256p1), MP_ROM_PTR(&mod_trezorcrypto_nist256p1_module) },
{ MP_ROM_QSTR(MP_QSTR_nem), MP_ROM_PTR(&mod_trezorcrypto_nem_module) },
{ MP_ROM_QSTR(MP_QSTR_pbkdf2), MP_ROM_PTR(&mod_trezorcrypto_Pbkdf2_type) },
{ MP_ROM_QSTR(MP_QSTR_random), MP_ROM_PTR(&mod_trezorcrypto_random_module) },
{ MP_ROM_QSTR(MP_QSTR_rfc6979), MP_ROM_PTR(&mod_trezorcrypto_Rfc6979_type) },

View File

@ -7,6 +7,12 @@ def init() -> None:
called from this module!
'''
# extmod/modtrezorconfig/modtrezorconfig.c
def check_pin(pin: int, waitcallback: (int, int -> None)) -> bool:
'''
Check the given PIN. Returns True on success, False on failure.
'''
# extmod/modtrezorconfig/modtrezorconfig.c
def unlock(pin: int, waitcallback: (int, int -> None)) -> bool:
'''

View File

@ -93,6 +93,16 @@ class HDNode:
Compute a base58-encoded address string from the HD node.
'''
def nem_address(self, network: int) -> str:
'''
Compute a NEM address string from the HD node.
'''
def nem_encrypt(self, transfer_public_key: bytes, iv: bytes, salt: bytes, payload: bytes) -> bytes:
'''
Encrypts payload using the transfer's public key
'''
def ethereum_pubkeyhash(self) -> bytes:
'''
Compute an Ethereum pubkeyhash (aka address) from the HD node.
@ -274,7 +284,7 @@ def publickey(secret_key: bytes) -> bytes:
'''
# extmod/modtrezorcrypto/modtrezorcrypto-ed25519.h
def sign(secret_key: bytes, message: bytes) -> bytes:
def sign(secret_key: bytes, message: bytes, hasher: str='') -> bytes:
'''
Uses secret key to produce the signature of message.
'''
@ -304,6 +314,18 @@ def cosi_sign(secret_key: bytes, message: bytes, nonce: bytes, sigR: bytes, comb
Produce signature of message using COSI cosigning scheme.
'''
# extmod/modtrezorcrypto/modtrezorcrypto-nem.h
def validate_address(address: str, network: int) -> bool:
'''
Validate a NEM address
'''
# extmod/modtrezorcrypto/modtrezorcrypto-nem.h
def compute_address(public_key: bytes, network: int) -> str:
'''
Compute a NEM address from a public key
'''
# extmod/modtrezorcrypto/modtrezorcrypto-nist256p1.h
def generate_secret() -> bytes:
'''

View File

@ -128,14 +128,17 @@ class USB:
'''
def __init__(self,
device_class: int=0,
device_subclass: int=0,
device_protocol: int=0,
vendor_id: int,
product_id: int,
release_num: int,
manufacturer: str='',
product: str='',
serial_number: str='',
configuration: str='',
interface: str='') -> None:
interface: str='',
usb21_enabled: bool=True) -> None:
'''
'''

View File

@ -112,7 +112,7 @@ class Display:
Call without the xy parameter to just perform the read of the value.
'''
def save(self, filename: str) -> None:
def save(self, prefix: str) -> None:
'''
Saves current display contents to file filename.
Saves current display contents to PNG file with given prefix.
'''

View File

@ -24,3 +24,21 @@ def halt(msg: str = None) -> None:
'''
Halts execution.
'''
# extmod/modtrezorutils/modtrezorutils.c
def set_mode_unprivileged() -> None:
'''
Set unprivileged mode.
'''
# extmod/modtrezorutils/modtrezorutils.c
def symbol(name: str) -> str/int/None:
'''
Retrieve internal symbol.
'''
# extmod/modtrezorutils/modtrezorutils.c
def model() -> str:
'''
Return which hardware model we are running on.
'''

View File

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

64
src/apps/nem/README.md Normal file
View File

@ -0,0 +1,64 @@
# NEM
MAINTAINER = Tomas Susanka <tomas.susanka@satoshilabs.com>
AUTHOR = Tomas Susanka <tomas.susanka@satoshilabs.com>
REVIEWER = Jan Pochyla <jan.pochyla@satoshilabs.com>
ADVISORS = Grégory Saive, Saleem Rashid
-----
This implementation of NEM for Trezor Core is mostly based on the trezor-mcu C implementation by Saleem Rashid. The protobuf messages are heavily inspired by the [NEM NSI API](https://nemproject.github.io/).
You can read a lot about NEM in the [Technical Reference paper](https://nem.io/wp-content/themes/nem/files/NEM_techRef.pdf).
This app supports number of NEM services (transfers, mosaics, namespaces and multisig), also called _transactions_ as the general term. Each of those services is divided into corresponding folders. In those folders we use `serialize.py` for the transaction serialization and `layout.py` to display data and to require user interactions.
In this app we support the following:
### Mosaics
You can read more on mosaics [here](https://blog.nem.io/mosaics-and-namespaces-2/). Each mosaic has a name and lives under certain namespace, this identification _namespace.mosaic_ is unique in the network.
Trezor Core supports mosaic creation and changing the mosaic's supply.
### Namespaces
You can read more on namespaces at the [same link](https://blog.nem.io/mosaics-and-namespaces-2/). Namespace creation is supported.
### Transfers
There is a number of things the term _transfer_ may refer to:
##### Regular transfers
The traditional transfer of the native NEM coins XEM.
##### Mosaic transfers
Except XEM you can also transfer mosaics. Mosaics are _attached_ to a regular transfer.
Each such attached mosaic has a `quantity` denoting the amount of such mosaic to be transferred. There is a catch though: the actual amount of the mosaic to be transferred isn't `quantity` but `mosaic.quantity * transfer.amount`. In other words, the quantity is multiplied by the `amount` field in transfer. This is most likely due to backwards compatibility where transfers with amount 0 where discarded.
You can also transfer XEM and mosaics at the same time. In that case you need to attach the nem.xem mosaic. From the user point of view Trezor shows this as a regular XEM transfer.
##### Importance transfer
Importance transfer is a special kind of transaction, which enables (or disables) delegated harvesting.
### Multisig
NEM supports multisig accounts. First you convert an account into a multisig and add cosignatories. After that any cosignatory can initiate a multisig transaction.
Multisig is a wrapper, so you can use any of the services mentioned above wrapped into a multisig transaction requiring n out of m signatures.
A common scenario to test out multisig with NanoWallet might be:
- Create a simple account without Trezor (account A)
- Create another account with Trezor One (account B)
- Convert A to a multisig with B as a cosigner
- Create another account with Trezor T (account C)
- Add C as a cosigner
- Try to send a transaction where B and C cosigns

17
src/apps/nem/__init__.py Normal file
View File

@ -0,0 +1,17 @@
from trezor.wire import register, protobuf_workflow
from trezor.messages.wire_types import NEMGetAddress, NEMSignTx
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)
def boot():
register(NEMGetAddress, protobuf_workflow, dispatch_NemGetAddress)
register(NEMSignTx, protobuf_workflow, dispatch_NemSignTx)

View File

@ -0,0 +1,32 @@
from trezor import ui
from trezor.messages import ButtonRequestType
from trezor.messages.NEMAddress import NEMAddress
from trezor.ui.text import Text
from apps.common import seed
from apps.common.confirm import require_confirm
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)
node = await seed.derive_node(ctx, msg.address_n, NEM_CURVE)
address = node.nem_address(network)
if msg.show_display:
await _require_confirm_address(ctx, address, network)
return NEMAddress(address=address)
async def _require_confirm_address(ctx, address: str, network: int):
lines = split_address(address)
content = Text(
'Export NEM address', ui.ICON_RECEIVE,
ui.NORMAL, '%s network' % get_network_str(network),
ui.MONO, *lines,
icon_color=ui.GREEN)
await require_confirm(ctx, content, code=ButtonRequestType.Address)

37
src/apps/nem/helpers.py Normal file
View File

@ -0,0 +1,37 @@
from micropython import const
NEM_NETWORK_MAINNET = const(0x68)
NEM_NETWORK_TESTNET = const(0x98)
NEM_NETWORK_MIJIN = const(0x60)
NEM_CURVE = 'ed25519-keccak'
NEM_TRANSACTION_TYPE_TRANSFER = const(0x0101)
NEM_TRANSACTION_TYPE_IMPORTANCE_TRANSFER = const(0x0801)
NEM_TRANSACTION_TYPE_AGGREGATE_MODIFICATION = const(0x1001)
NEM_TRANSACTION_TYPE_MULTISIG_SIGNATURE = const(0x1002)
NEM_TRANSACTION_TYPE_MULTISIG = const(0x1004)
NEM_TRANSACTION_TYPE_PROVISION_NAMESPACE = const(0x2001)
NEM_TRANSACTION_TYPE_MOSAIC_CREATION = const(0x4001)
NEM_TRANSACTION_TYPE_MOSAIC_SUPPLY_CHANGE = const(0x4002)
NEM_MAX_DIVISIBILITY = const(6)
NEM_MAX_SUPPLY = const(9000000000)
NEM_SALT_SIZE = const(32)
AES_BLOCK_SIZE = const(16)
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)
NEM_MAX_PLAIN_PAYLOAD_SIZE = const(1024)
NEM_MAX_ENCRYPTED_PAYLOAD_SIZE = const(960)
def get_network_str(network: int) -> str:
if network == NEM_NETWORK_MAINNET:
return 'Mainnet'
elif network == NEM_NETWORK_TESTNET:
return 'Testnet'
elif network == NEM_NETWORK_MIJIN:
return 'Mijin'

47
src/apps/nem/layout.py Normal file
View File

@ -0,0 +1,47 @@
from trezor import ui
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
async def require_confirm_text(ctx, action: str):
words = split_words(action, 18)
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),
)
await require_confirm_content(ctx, 'Confirm fee', content)
async def require_confirm_content(ctx, headline: str, content: list):
text = Text(headline, ui.ICON_SEND, *content, icon_color=ui.GREEN)
await require_confirm(ctx, text, ButtonRequestType.ConfirmOutput)
async def require_confirm_final(ctx, fee: int):
content = Text(
'Final confirm', ui.ICON_SEND,
ui.NORMAL, 'Sign this transaction',
ui.BOLD, 'and pay %s XEM' % format_amount(fee, NEM_MAX_DIVISIBILITY),
ui.NORMAL, 'for network fee?',
icon_color=ui.GREEN)
# we use SignTx, not ConfirmOutput, for compatibility with T1
await require_hold_to_confirm(ctx, content, ButtonRequestType.SignTx)
def split_address(address: str):
return chunks(address, 17)
def trim(payload: str, length: int) -> str:
if len(payload) > length:
return payload[:length] + '..'
return payload

View File

@ -0,0 +1,15 @@
from trezor.messages.NEMTransactionCommon import NEMTransactionCommon
from trezor.messages.NEMMosaicCreation import NEMMosaicCreation
from trezor.messages.NEMMosaicSupplyChange import NEMMosaicSupplyChange
from . import layout, serialize
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:
await layout.ask_supply_change(ctx, common, change)
return serialize.serialize_mosaic_supply_change(common, change, public_key)

View File

@ -0,0 +1,15 @@
from .nem_mosaics import mosaics
def get_mosaic_definition(namespace_name: str, mosaic_name: str, network: int) -> dict:
for m in mosaics:
if namespace_name == m["namespace"] and mosaic_name == m["mosaic"]:
if ("networks" not in m) or (network in m["networks"]):
return m
return None
def is_nem_xem_mosaic(namespace_name: str, mosaic_name: str) -> bool:
if namespace_name == "nem" and mosaic_name == "xem":
return True
return False

View File

@ -0,0 +1,123 @@
from micropython import const
from trezor import ui
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)
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_final(ctx, common.fee)
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?'
elif change.type == NEMSupplyChangeType.SupplyChange_Increase:
msg = 'Increase supply by ' + str(change.delta) + ' whole units?'
else:
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]
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]
async def _require_confirm_properties(ctx, definition: NEMMosaicDefinition):
properties = _get_mosaic_properties(definition)
first_page = const(0)
paginator = paginate(_show_page, len(properties), first_page, properties)
await ctx.wait(paginator)
@ui.layout
async def _show_page(page: int, page_count: int, content):
content = Scrollpage(content[page], page, page_count)
if page + 1 == page_count:
await ConfirmDialog(content)
else:
content.render()
await animate_swipe()
def _get_mosaic_properties(definition: NEMMosaicDefinition):
properties = []
if definition.description:
t = Text('Confirm properties', ui.ICON_SEND,
ui.BOLD, 'Description:',
ui.NORMAL, *split_words(trim(definition.description, 70), 22))
properties.append(t)
if definition.transferable:
transferable = 'Yes'
else:
transferable = 'No'
t = Text('Confirm properties', ui.ICON_SEND,
ui.BOLD, 'Transferable?',
ui.NORMAL, transferable)
properties.append(t)
if definition.mutable_supply:
imm = 'mutable'
else:
imm = 'immutable'
if definition.supply:
t = Text('Confirm properties', ui.ICON_SEND,
ui.BOLD, 'Initial supply:',
ui.NORMAL, str(definition.supply),
ui.NORMAL, imm)
else:
t = Text('Confirm properties', ui.ICON_SEND,
ui.BOLD, 'Initial supply:',
ui.NORMAL, imm)
properties.append(t)
if definition.levy:
t = Text('Confirm properties', ui.ICON_SEND,
ui.BOLD, 'Levy recipient:',
ui.MONO, *split_address(definition.levy_address))
properties.append(t)
t = Text('Confirm properties', ui.ICON_SEND,
ui.BOLD, 'Levy fee:',
ui.NORMAL, str(definition.fee),
ui.BOLD, 'Levy divisibility:',
ui.NORMAL, str(definition.divisibility))
properties.append(t)
t = Text('Confirm properties', ui.ICON_SEND,
ui.BOLD, 'Levy namespace:',
ui.NORMAL, definition.levy_namespace,
ui.BOLD, 'Levy mosaic:',
ui.NORMAL, definition.levy_mosaic)
properties.append(t)
if definition.levy == NEMMosaicLevy.MosaicLevy_Absolute:
levy_type = 'absolute'
else:
levy_type = 'percentile'
t = Text('Confirm properties', ui.ICON_SEND,
ui.BOLD, 'Levy type:',
ui.NORMAL, levy_type)
properties.append(t)
return properties

View File

@ -0,0 +1,59 @@
# generated using gen_nem_mosaics.py from trezor-common nem_mosaics.json - do not edit directly!
mosaics = [
{
"name": "XEM",
"ticker": " XEM",
"namespace": "nem",
"mosaic": "xem",
"divisibility": 6,
},
{
"name": "DIMCOIN",
"ticker": " DIM",
"namespace": "dim",
"mosaic": "coin",
"divisibility": 6,
"levy": "MosaicLevy_Percentile",
"fee": 10,
"levy_namespace": "dim",
"levy_mosaic": "coin",
"networks": [104],
},
{
"name": "DIM TOKEN",
"ticker": " DIMTOK",
"namespace": "dim",
"mosaic": "token",
"divisibility": 6,
"networks": [104],
},
{
"name": "Breeze Token",
"ticker": " BREEZE",
"namespace": "breeze",
"mosaic": "breeze-token",
"divisibility": 0,
"networks": [104],
},
{
"name": "PacNEM Game Credits",
"ticker": " PAC:HRT",
"namespace": "pacnem",
"mosaic": "heart",
"divisibility": 0,
"networks": [104],
},
{
"name": "PacNEM Score Tokens",
"ticker": " PAC:CHS",
"namespace": "pacnem",
"mosaic": "cheese",
"divisibility": 6,
"levy": "MosaicLevy_Percentile",
"fee": 100,
"levy_namespace": "nem",
"levy_mosaic": "xem",
"networks": [104],
},
]

View File

@ -0,0 +1,79 @@
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)
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)
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)
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)
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_mosaic))
write_uint64(mosaics_w, creation.definition.fee)
else:
write_uint32(mosaics_w, 0)
# write mosaic bytes with length
write_bytes_with_length(w, mosaics_w)
write_bytes_with_length(w, bytearray(creation.sink))
write_uint64(w, creation.fee)
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)
identifier_length = 4 + len(change.namespace) + 4 + len(change.mosaic)
write_uint32(w, identifier_length)
write_bytes_with_length(w, bytearray(change.namespace))
write_bytes_with_length(w, bytearray(change.mosaic))
write_uint32(w, change.type)
write_uint64(w, change.delta)
return w
def _write_property(w: bytearray, name: str, value):
if value is None:
if name in ('divisibility', 'initialSupply'):
value = 0
elif name in ('supplyMutable', 'transferable'):
value = False
if type(value) == bool:
if value:
value = 'true'
else:
value = 'false'
elif type(value) == int:
value = str(value)
elif type(value) != str:
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

@ -0,0 +1,33 @@
from trezor.messages.NEMAggregateModification import NEMAggregateModification
from trezor.messages.NEMSignTx import NEMSignTx
from trezor.messages.NEMTransactionCommon import NEMTransactionCommon
from . import layout, serialize
async def ask(ctx, msg: NEMSignTx):
await layout.ask_multisig(ctx, msg)
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:
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):
await layout.ask_aggregate_modification(ctx, common, aggr, multisig)
w = serialize.serialize_aggregate_modification(common, aggr, public_key)
for m in aggr.modifications:
serialize.serialize_cosignatory_modification(w, m.type, m.public_key)
if aggr.relative_change:
serialize.serialize_minimum_cosignatories(w, aggr.relative_change)
return w

View File

@ -0,0 +1,49 @@
from trezor import ui
from trezor.crypto import nem
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)
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)
else:
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):
if not multisig:
await require_confirm_text(ctx, 'Convert account to multisig account?')
for m in mod.modifications:
if m.type == NEMModificationType.CosignatoryModification_Add:
action = 'Add'
else:
action = 'Remove'
address = nem.compute_address(m.public_key, common.network)
await _require_confirm_address(ctx, action + ' cosignatory', address)
if mod.relative_change:
if multisig:
action = 'Modify the number of cosignatories by '
else:
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):
content = Text('Confirm address', ui.ICON_SEND,
ui.NORMAL, action,
ui.MONO, *split_address(address),
icon_color=ui.GREEN)
await require_confirm(ctx, content, ButtonRequestType.ConfirmOutput)

View File

@ -0,0 +1,51 @@
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 ..writers import write_bytes_with_length, write_common, write_uint32
def serialize_multisig(common: NEMTransactionCommon, public_key: bytes, inner: bytes):
w = write_common(common, bytearray(public_key), NEM_TRANSACTION_TYPE_MULTISIG)
write_bytes_with_length(w, bytearray(inner))
return w
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)
digest = hashlib.sha3_256(inner).digest(True)
write_uint32(w, 4 + len(digest))
write_bytes_with_length(w, digest)
write_bytes_with_length(w, address)
return w
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)
write_uint32(w, len(mod.modifications))
return w
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))
return w
def serialize_minimum_cosignatories(w: bytearray, relative_change: int):
write_uint32(w, 4)
write_uint32(w, relative_change)

View File

@ -0,0 +1,9 @@
from trezor.messages.NEMTransactionCommon import NEMTransactionCommon
from trezor.messages.NEMProvisionNamespace import NEMProvisionNamespace
from . import layout, serialize
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

@ -0,0 +1,22 @@
from trezor import ui
from trezor.messages import NEMProvisionNamespace, NEMTransactionCommon
from ..layout import (require_confirm_content, require_confirm_fee,
require_confirm_final)
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)
else:
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_final(ctx, common.fee)

View File

@ -0,0 +1,21 @@
from trezor.messages.NEMProvisionNamespace import NEMProvisionNamespace
from trezor.messages.NEMTransactionCommon import NEMTransactionCommon
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)
write_bytes_with_length(tx, bytearray(namespace.sink))
write_uint64(tx, namespace.fee)
write_bytes_with_length(tx, bytearray(namespace.namespace))
if namespace.parent:
write_bytes_with_length(tx, bytearray(namespace.parent))
else:
write_uint32(tx, 0xffffffff)
return tx

55
src/apps/nem/signing.py Normal file
View File

@ -0,0 +1,55 @@
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
async def sign_tx(ctx, msg: NEMSignTx):
validate(msg)
node = await seed.derive_node(ctx, msg.transaction.address_n, NEM_CURVE)
if msg.multisig:
public_key = msg.multisig.signer
await multisig.ask(ctx, msg)
common = msg.multisig
else:
public_key = _get_public_key(node)
common = msg.transaction
if msg.transfer:
tx = await transfer.transfer(ctx, public_key, common, msg.transfer, node)
elif msg.provision_namespace:
tx = await namespace.namespace(ctx, public_key, common, msg.provision_namespace)
elif msg.mosaic_creation:
tx = await mosaic.mosaic_creation(ctx, public_key, common, msg.mosaic_creation)
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)
elif msg.importance_transfer:
tx = await transfer.importance_transfer(ctx, public_key, common, msg.importance_transfer)
else:
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)
else:
tx = multisig.initiate(_get_public_key(node), msg.transaction, tx)
signature = ed25519.sign(node.private_key(), tx, NEM_HASH_ALG)
resp = NEMSignedTx()
resp.data = tx
resp.signature = signature
return resp
def _get_public_key(node) -> bytes:
# 0x01 prefix is not part of the actual public key, hence removed
return node.public_key()[1:]

View File

@ -0,0 +1,22 @@
from trezor.messages.NEMTransfer import NEMTransfer
from trezor.messages.NEMTransactionCommon import NEMTransactionCommon
from trezor.messages.NEMImportanceTransfer import NEMImportanceTransfer
from . import layout, serialize
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)
await layout.ask_transfer(ctx, common, transfer, payload, encrypted)
w = serialize.serialize_transfer(common, transfer, public_key, payload, encrypted)
for mosaic in transfer.mosaics:
serialize.serialize_mosaic(w, mosaic.namespace, mosaic.mosaic, mosaic.quantity)
return w
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

@ -0,0 +1,128 @@
from trezor import ui
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 ..layout import require_confirm_final, require_confirm_text, split_address
from ..mosaic.helpers import get_mosaic_definition, is_nem_xem_mosaic
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:
await ask_transfer_mosaic(ctx, common, transfer, mosaic)
await _require_confirm_transfer(ctx, transfer.recipient, _get_xem_amount(transfer))
await require_confirm_final(ctx, common.fee)
async def ask_transfer_mosaic(ctx, common: NEMTransactionCommon, transfer: NEMTransfer, mosaic: NEMMosaic):
if is_nem_xem_mosaic(mosaic.namespace, mosaic.mosaic):
return
definition = get_mosaic_definition(mosaic.namespace, mosaic.mosaic, common.network)
mosaic_quantity = mosaic.quantity * transfer.amount / NEM_MOSAIC_AMOUNT_DIVISOR
if definition:
msg = Text('Confirm mosaic', ui.ICON_SEND,
'Confirm transfer of',
ui.BOLD, format_amount(mosaic_quantity, definition['divisibility']) + definition['ticker'],
ui.NORMAL, 'of',
ui.BOLD, definition['name'],
icon_color=ui.GREEN)
await require_confirm(ctx, msg, ButtonRequestType.ConfirmOutput)
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,
'Confirm mosaic',
'levy fee of',
ui.BOLD, levy_msg,
icon_color=ui.GREEN)
await require_confirm(ctx, msg, ButtonRequestType.ConfirmOutput)
else:
msg = Text('Confirm mosaic', ui.ICON_SEND,
ui.BOLD, 'Unknown mosaic!',
ui.NORMAL, *split_words('Divisibility and levy cannot be shown for unknown mosaics', 22),
icon_color=ui.RED)
await require_confirm(ctx, msg, ButtonRequestType.ConfirmOutput)
msg = Text('Confirm mosaic', ui.ICON_SEND,
ui.NORMAL, 'Confirm transfer of',
ui.BOLD, '%s raw units' % mosaic_quantity,
ui.NORMAL, 'of',
ui.BOLD, '%s.%s' % (mosaic.namespace, mosaic.mosaic),
icon_color=ui.GREEN)
await require_confirm(ctx, msg, ButtonRequestType.ConfirmOutput)
def _get_xem_amount(transfer: NEMTransfer):
# if mosaics are empty the transfer.amount denotes the xem amount
if not transfer.mosaics:
return transfer.amount
# otherwise xem amount is taken from the nem xem mosaic if present
for mosaic in transfer.mosaics:
if is_nem_xem_mosaic(mosaic.namespace, mosaic.mosaic):
return mosaic.quantity * transfer.amount / NEM_MOSAIC_AMOUNT_DIVISOR
# if there are mosaics but do not include xem, 0 xem is sent
return 0
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']
else:
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):
if imp.mode == NEMImportanceTransferMode.ImportanceTransfer_Activate:
m = 'Activate'
else:
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):
content = Text('Confirm transfer', ui.ICON_SEND,
ui.BOLD, 'Send %s XEM' % format_amount(value, NEM_MAX_DIVISIBILITY),
ui.NORMAL, 'to',
ui.MONO, *split_address(recipient),
icon_color=ui.GREEN)
await require_confirm(ctx, content, ButtonRequestType.ConfirmOutput)
async def _require_confirm_payload(ctx, payload: bytes, encrypt=False):
payload = str(payload, 'utf-8')
if len(payload) > 48:
payload = payload[:48] + '..'
if encrypt:
content = Text('Confirm payload', ui.ICON_SEND,
ui.BOLD, 'Encrypted:',
ui.NORMAL, *split_words(payload, 22),
icon_color=ui.GREEN)
else:
content = Text('Confirm payload', ui.ICON_SEND,
ui.BOLD, 'Unencrypted:',
ui.NORMAL, *split_words(payload, 22),
icon_color=ui.RED)
await require_confirm(ctx, content, ButtonRequestType.ConfirmOutput)

View File

@ -0,0 +1,117 @@
from trezor.crypto import random
from trezor.messages.NEMImportanceTransfer import NEMImportanceTransfer
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 ..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))
write_bytes_with_length(tx, bytearray(transfer.recipient))
write_uint64(tx, transfer.amount)
if payload:
# payload + payload size (u32) + encryption flag (u32)
write_uint32(tx, len(payload) + 2 * 4)
if encrypted:
write_uint32(tx, 0x02)
else:
write_uint32(tx, 0x01)
write_bytes_with_length(tx, bytearray(payload))
else:
write_uint32(tx, 0)
if transfer.mosaics:
write_uint32(tx, len(transfer.mosaics))
return tx
def serialize_mosaic(w: bytearray, namespace: str, mosaic: str, quantity: int):
identifier_length = 4 + len(namespace) + 4 + len(mosaic)
# indentifier length (u32) + quantity (u64) + identifier size
write_uint32(w, 4 + 8 + identifier_length)
write_uint32(w, identifier_length)
write_bytes_with_length(w, bytearray(namespace))
write_bytes_with_length(w, bytearray(mosaic))
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)
write_uint32(w, imp.mode)
write_bytes_with_length(w, bytearray(imp.public_key))
return w
def get_transfer_payload(transfer: NEMTransfer, node) -> [bytes, bool]:
payload = transfer.payload
encrypted = False
if transfer.public_key is not None:
if payload is None:
raise ValueError('Public key provided but no payload to encrypt')
payload = _encrypt(node, transfer.public_key, transfer.payload)
encrypted = True
return payload, encrypted
def _encrypt(node, public_key: bytes, payload: bytes) -> bytes:
salt = random.bytes(NEM_SALT_SIZE)
iv = random.bytes(AES_BLOCK_SIZE)
encrypted = node.nem_encrypt(public_key, iv, salt, payload)
return iv + salt + encrypted
def _get_version(network, mosaics=None) -> int:
if mosaics:
return network << 24 | 2
return network << 24 | 1
def canonicalize_mosaics(mosaics: list):
if len(mosaics) <= 1:
return mosaics
mosaics = merge_mosaics(mosaics)
return sort_mosaics(mosaics)
def are_mosaics_equal(a: NEMMosaic, b: NEMMosaic) -> bool:
if a.namespace == b.namespace and a.mosaic == b.mosaic:
return True
return False
def merge_mosaics(mosaics: list) -> list:
if not mosaics:
return list()
ret = list()
for i in mosaics:
found = False
for k, y in enumerate(ret):
if are_mosaics_equal(i, y):
ret[k].quantity += i.quantity
found = True
if not found:
ret.append(i)
return ret
def sort_mosaics(mosaics: list) -> list:
return sorted(mosaics, key=lambda m: (m.namespace, m.mosaic))

236
src/apps/nem/validators.py Normal file
View File

@ -0,0 +1,236 @@
from trezor.crypto import nem
from trezor.messages import NEMModificationType
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)
def validate(msg: NEMSignTx):
if msg.transaction is None:
raise ValueError('No common provided')
_validate_single_tx(msg)
_validate_common(msg.transaction)
if msg.multisig:
_validate_common(msg.multisig, True)
_validate_multisig(msg.multisig, msg.transaction.network)
if not msg.multisig and msg.cosigning:
raise ValueError('No multisig transaction to cosign')
if msg.transfer:
_validate_transfer(msg.transfer, msg.transaction.network)
if msg.provision_namespace:
_validate_provision_namespace(msg.provision_namespace, msg.transaction.network)
if msg.mosaic_creation:
_validate_mosaic_creation(msg.mosaic_creation, msg.transaction.network)
if msg.supply_change:
_validate_supply_change(msg.supply_change)
if msg.aggregate_modification:
_validate_aggregate_modification(msg.aggregate_modification, msg.multisig is None)
if msg.importance_transfer:
_validate_importance_transfer(msg.importance_transfer)
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 ValueError('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)
if tx_count == 0:
raise ValueError('No transaction provided')
if tx_count > 1:
raise ValueError('More than one transaction provided')
def _validate_common(common: NEMTransactionCommon, inner: bool = False):
common.network = validate_network(common.network)
err = None
if common.timestamp is None:
err = 'timestamp'
if common.fee is None:
err = 'fee'
if common.deadline is None:
err = 'deadline'
if not inner and common.signer:
raise ValueError('Signer not allowed in outer transaction')
if inner and common.signer is None:
err = 'signer'
if err:
if inner:
raise ValueError('No %s provided in inner transaction' % err)
else:
raise ValueError('No %s provided' % err)
if common.signer is not None:
_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 ValueError('%s (none provided)' % err_msg)
if len(public_key) != NEM_PUBLIC_KEY_SIZE:
raise ValueError('%s (invalid length)' % err_msg)
def _validate_importance_transfer(importance_transfer: NEMImportanceTransfer):
if importance_transfer.mode is None:
raise ValueError('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 ValueError('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):
if creation and not aggregate_modification.modifications:
raise ValueError('No modifications provided')
for m in aggregate_modification.modifications:
if not m.type:
raise ValueError('No modification type provided')
if m.type not in (
NEMModificationType.CosignatoryModification_Add,
NEMModificationType.CosignatoryModification_Delete
):
raise ValueError('Unknown aggregate modification')
if creation and m.type == NEMModificationType.CosignatoryModification_Delete:
raise ValueError('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 ValueError('No namespace provided')
if supply_change.mosaic is None:
raise ValueError('No mosaic provided')
if supply_change.type is None:
raise ValueError('No type provided')
if supply_change.delta is None:
raise ValueError('No delta provided')
def _validate_mosaic_creation(mosaic_creation: NEMMosaicCreation, network: int):
if mosaic_creation.definition is None:
raise ValueError('No mosaic definition provided')
if mosaic_creation.sink is None:
raise ValueError('No creation sink provided')
if mosaic_creation.fee is None:
raise ValueError('No creation sink fee provided')
if not nem.validate_address(mosaic_creation.sink, network):
raise ValueError('Invalid creation sink address')
if mosaic_creation.definition.name is not None:
raise ValueError('Name not allowed in mosaic creation transactions')
if mosaic_creation.definition.ticker is not None:
raise ValueError('Ticker not allowed in mosaic creation transactions')
if mosaic_creation.definition.networks:
raise ValueError('Networks not allowed in mosaic creation transactions')
if mosaic_creation.definition.namespace is None:
raise ValueError('No mosaic namespace provided')
if mosaic_creation.definition.mosaic is None:
raise ValueError('No mosaic name provided')
if mosaic_creation.definition.supply is not None and mosaic_creation.definition.divisibility is None:
raise ValueError('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 ValueError('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 ValueError('No levy fee provided')
if mosaic_creation.definition.levy_address is None:
raise ValueError('No levy address provided')
if mosaic_creation.definition.levy_namespace is None:
raise ValueError('No levy namespace provided')
if mosaic_creation.definition.levy_mosaic is None:
raise ValueError('No levy mosaic name provided')
if mosaic_creation.definition.divisibility is None:
raise ValueError('No divisibility provided')
if mosaic_creation.definition.supply is None:
raise ValueError('No supply provided')
if mosaic_creation.definition.mutable_supply is None:
raise ValueError('No supply mutability provided')
if mosaic_creation.definition.transferable is None:
raise ValueError('No mosaic transferability provided')
if mosaic_creation.definition.description is None:
raise ValueError('No description provided')
if mosaic_creation.definition.divisibility > NEM_MAX_DIVISIBILITY:
raise ValueError('Invalid divisibility provided')
if mosaic_creation.definition.supply > NEM_MAX_SUPPLY:
raise ValueError('Invalid supply provided')
if not nem.validate_address(mosaic_creation.definition.levy_address, network):
raise ValueError('Invalid levy address')
def _validate_provision_namespace(provision_namespace: NEMProvisionNamespace, network: int):
if provision_namespace.namespace is None:
raise ValueError('No namespace provided')
if provision_namespace.sink is None:
raise ValueError('No rental sink provided')
if provision_namespace.fee is None:
raise ValueError('No rental sink fee provided')
if not nem.validate_address(provision_namespace.sink, network):
raise ValueError('Invalid rental sink address')
def _validate_transfer(transfer: NEMTransfer, network: int):
if transfer.recipient is None:
raise ValueError('No recipient provided')
if transfer.amount is None:
raise ValueError('No amount provided')
if transfer.public_key is not None:
_validate_public_key(transfer.public_key, 'Invalid recipient public key')
if transfer.payload:
if len(transfer.payload) > NEM_MAX_PLAIN_PAYLOAD_SIZE:
raise ValueError('Payload too large')
if transfer.public_key and len(transfer.payload) > NEM_MAX_ENCRYPTED_PAYLOAD_SIZE:
raise ValueError('Payload too large')
if not nem.validate_address(transfer.recipient, network):
raise ValueError('Invalid recipient address')
for m in transfer.mosaics:
if m.namespace is None:
raise ValueError('No mosaic namespace provided')
if m.mosaic is None:
raise ValueError('No mosaic name provided')
if m.quantity is None:
raise ValueError('No mosaic quantity provided')

47
src/apps/nem/writers.py Normal file
View File

@ -0,0 +1,47 @@
from trezor.messages.NEMTransactionCommon import NEMTransactionCommon
def write_uint32(w, n: int):
w.append(n & 0xFF)
w.append((n >> 8) & 0xFF)
w.append((n >> 16) & 0xFF)
w.append((n >> 24) & 0xFF)
def write_uint64(w, n: int):
w.append(n & 0xFF)
w.append((n >> 8) & 0xFF)
w.append((n >> 16) & 0xFF)
w.append((n >> 24) & 0xFF)
w.append((n >> 32) & 0xFF)
w.append((n >> 40) & 0xFF)
w.append((n >> 48) & 0xFF)
w.append((n >> 56) & 0xFF)
def write_bytes(w, buf: bytearray):
w.extend(buf)
def write_bytes_with_length(w, buf: bytearray):
write_uint32(w, len(buf))
write_bytes(w, buf)
def write_common(common: NEMTransactionCommon,
public_key: bytearray,
transaction_type: int,
version: int = None) -> bytearray:
ret = bytearray()
write_uint32(ret, transaction_type)
if version is None:
version = common.network << 24 | 1
write_uint32(ret, version)
write_uint32(ret, common.timestamp)
write_bytes_with_length(ret, public_key)
write_uint64(ret, common.fee)
write_uint32(ret, common.deadline)
return ret

View File

@ -12,6 +12,7 @@ import apps.management
import apps.wallet
import apps.ethereum
import apps.lisk
import apps.nem
if __debug__:
import apps.debug
else:
@ -23,6 +24,7 @@ apps.management.boot()
apps.wallet.boot()
apps.ethereum.boot()
apps.lisk.boot()
apps.nem.boot()
if __debug__:
apps.debug.boot()
else:

View File

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

View File

@ -0,0 +1,37 @@
from common import *
from ubinascii import unhexlify
from trezor.crypto import nem
from apps.nem.helpers import NEM_NETWORK_MAINNET, NEM_NETWORK_TESTNET
class TestNemAddress(unittest.TestCase):
def test_addresses(self):
pubkey = unhexlify('c5f54ba980fcbb657dbaaa42700539b207873e134d2375efeab5f1ab52f87844')
address = nem.compute_address(pubkey, NEM_NETWORK_MAINNET)
self.assertEqual(address, 'NDD2CT6LQLIYQ56KIXI3ENTM6EK3D44P5JFXJ4R4')
pubkey = unhexlify('114171230ad6f8522a000cdc73fbc5c733b30bb71f2b146ccbdf34499f79a810')
address = nem.compute_address(pubkey, NEM_NETWORK_MAINNET)
self.assertEqual(address, 'NCUKWDY3J3THKQHAKOK5ALF6ANJQABZHCH7VN6DP')
def test_validate_address(self):
validity = nem.validate_address('NDD2CT6LQLIYQ56KIXI3ENTM6EK3D44P5JFXJ4R4', NEM_NETWORK_MAINNET)
self.assertTrue(validity)
validity = nem.validate_address('NCUKWDY3J3THKQHAKOK5ALF6ANJQABZHCH7VN6DP', NEM_NETWORK_MAINNET)
self.assertTrue(validity)
validity = nem.validate_address('TAU5HO3DRQZNELFEMZZTUKQEZGQ7IUAHKPO7OOLK', NEM_NETWORK_TESTNET)
self.assertTrue(validity)
validity = nem.validate_address('nope', NEM_NETWORK_TESTNET)
self.assertFalse(validity)
# not valid on testnet
validity = nem.validate_address('NCUKWDY3J3THKQHAKOK5ALF6ANJQABZHCH7VN6DP', NEM_NETWORK_TESTNET)
self.assertFalse(validity)
if __name__ == '__main__':
unittest.main()

View File

@ -0,0 +1,237 @@
from common import *
from ubinascii import unhexlify
from trezor.crypto import bip32
from apps.nem.helpers import NEM_NETWORK_MAINNET, NEM_CURVE
class TestNemHDNode(unittest.TestCase):
def test_addresses(self):
# test vectors from https://raw.githubusercontent.com/NemProject/nem-test-vectors/master/1.test-keys.dat
# private key, public key, address
test_cases = [
('575dbb3062267eff57c970a336ebbc8fbcfe12c5bd3ed7bc11eb0481d7704ced',
'c5f54ba980fcbb657dbaaa42700539b207873e134d2375efeab5f1ab52f87844',
'NDD2CT6LQLIYQ56KIXI3ENTM6EK3D44P5JFXJ4R4'),
('5b0e3fa5d3b49a79022d7c1e121ba1cbbf4db5821f47ab8c708ef88defc29bfe',
'96eb2a145211b1b7ab5f0d4b14f8abc8d695c7aee31a3cfc2d4881313c68eea3',
'NABHFGE5ORQD3LE4O6B7JUFN47ECOFBFASC3SCAC'),
('738ba9bb9110aea8f15caa353aca5653b4bdfca1db9f34d0efed2ce1325aeeda',
'2d8425e4ca2d8926346c7a7ca39826acd881a8639e81bd68820409c6e30d142a',
'NAVOZX4HDVOAR4W6K4WJHWPD3MOFU27DFHC7KZOZ'),
('e8bf9bc0f35c12d8c8bf94dd3a8b5b4034f1063948e3cc5304e55e31aa4b95a6',
'4feed486777ed38e44c489c7c4e93a830e4c4a907fa19a174e630ef0f6ed0409',
'NBZ6JK5YOCU6UPSSZ5D3G27UHAPHTY5HDQMGE6TT'),
('c325ea529674396db5675939e7988883d59a5fc17a28ca977e3ba85370232a83',
'83ee32e4e145024d29bca54f71fa335a98b3e68283f1a3099c4d4ae113b53e54',
'NCQW2P5DNZ5BBXQVGS367DQ4AHC3RXOEVGRCLY6V'),
('a811cb7a80a7227ae61f6da536534ee3c2744e3c7e4b85f3e0df3c6a9c5613df',
'6d34c04f3a0e42f0c3c6f50e475ae018cfa2f56df58c481ad4300424a6270cbb',
'NA5IG3XFXZHIPJ5QLKX2FBJPEZYPMBPPK2ZRC3EH'),
('9c66de1ec77f4dfaaebdf9c8bc599ca7e8e6f0bc71390ffee2c9dd3f3619242a',
'a8fefd72a3b833dc7c7ed7d57ed86906dac22f88f1f4331873eb2da3152a3e77',
'NAABHVFJDBM74XMJJ52R7QN2MTTG2ZUXPQS62QZ7'),
('c56bc16ecf727878c15e24f4ae68569600ac7b251218a44ef50ce54175776edc',
'c92f761e6d83d20068fd46fe4bd5b97f4c6ba05d23180679b718d1f3e4fb066e',
'NCLK3OLMHR3F2E3KSBUIZ4K5PNWUDN37MLSJBJZP'),
('9dd73599283882fa1561ddfc9be5830b5dd453c90465d3fe5eeb646a3606374e',
'eaf16a4833e59370a04ccd5c63395058de34877b48c17174c71db5ed37b537ed',
'ND3AHW4VTI5R5QE5V44KIGPRU5FBJ5AFUCJXOY5H'),
('d9639dc6f49dad02a42fd8c217f1b1b4f8ce31ccd770388b645e639c72ff24fa',
'0f74a2f537cd9c986df018994dde75bdeee05e35eb9fe27adf506ca8475064f7',
'NCTZ4YAP43ONK3UYTASQVNDMBO24ZHJE65F3QPYE'),
('efc1992cd50b70ca55ac12c07aa5d026a8b78ffe28a7dbffc9228b26e02c38c1',
'2ebff201255f6cf948c78f528658b99a7c13ac791942fa22d59af610558111f5',
'NDQ2TMCMXBSFPZQPE2YKH6XLC24HD6LUMN6Z4GIC'),
('143a815e92e43f3ed1a921ee48cd143931b88b7c3d8e1e981f743c2a5be3c5ba',
'419ed11d48730e4ae2c93f0ea4df853b8d578713a36dab227517cf965861af4e',
'NA32IDDW2C53BDSBJNFL3Z6UU3J5CJZJMCZDXCF4'),
('bc1a082f5ac6fdd3a83ade211e5986ac0551bad6c7da96727ec744e5df963e2a',
'a160e6f9112233a7ce94202ed7a4443e1dac444b5095f9fecbb965fba3f92cac',
'NADUCEQLC3FTGB25GTA5HOUTB53CBVQNVOIP7NTJ'),
('4e47b4c6f4c7886e49ec109c61f4af5cfbb1637283218941d55a7f9fe1053f72',
'fbb91b16df828e21a9802980a44fc757c588bc1382a4cea429d6fa2ae0333f56',
'NBAF3BFLLPWH33MYE6VUPP5T6DQBZBKIDEQKZQOE'),
('efc4389da48ce49f85365cfa578c746530e9eac42db1b64ec346119b1becd347',
'2232f24dda0f2ded3ecd831210d4e8521a096b50cadd5a34f3f7083374e1ec12',
'NBOGTK2I2ATOGGD7ZFJHROG5MWL7XCKAUKSWIVSA'),
('bdba57c78ca7da16a3360efd13f06276284db8c40351de7fcd38ba0c35ac754d',
'c334c6c0dad5aaa2a0d0fb4c6032cb6a0edd96bf61125b5ea9062d5a00ee0eee',
'NCLERTEFYXKLK7RA4MVACEFMXMK3P7QMWTM7FBW2'),
('20694c1ec3c4a311bcdb29ed2edc428f6d4f9a4c429ad6a5bf3222084e35695f',
'518c4de412efa93de06a55947d11f697639443916ec8fcf04ebc3e6d17d0bd93',
'NB5V4BPIJHXVONO7UGMJDPFARMFA73BOBNOOYCOV'),
('e0d4f3760ac107b33c22c2cac24ab2f520b282684f5f66a4212ff95d926323ce',
'b3d16f4ead9de67c290144da535a0ed2504b03c05e5f1ceb8c7863762f786857',
'NC4PBAO5TPCAVQKBVOC4F6DMZP3CFSQBU46PSKBD'),
('efa9afc617412093c9c7a7c211a5332dd556f941e1a88c494ec860608610eea2',
'7e7716e4cebceb731d6f1fd28676f34888e9a0000fcfa1471db1c616c2ddf559',
'NCFW2LPXIWLBWAQN2QVIWEOD7IVDO3HQBD2OU56K'),
('d98499b3db61944684ce06a91735af4e14105338473fcf6ebe2b0bcada3dfd21',
'114171230ad6f8522a000cdc73fbc5c733b30bb71f2b146ccbdf34499f79a810',
'NCUKWDY3J3THKQHAKOK5ALF6ANJQABZHCH7VN6DP')
]
for test in test_cases:
private_key = bytearray(reversed(unhexlify(test[0])))
node = bip32.HDNode(
depth=0,
fingerprint=0,
child_num=0,
chain_code=bytearray(32),
private_key=private_key,
curve_name=NEM_CURVE
)
self.assertEqual(node.nem_address(NEM_NETWORK_MAINNET), test[2])
# public key is prepended with 1, removing
self.assertEqual(node.public_key()[1:], unhexlify(test[1]))
def test_encryption(self):
# test vectors from https://raw.githubusercontent.com/NemProject/nem-test-vectors/master/4.test-cipher.dat
# private key, transfer public key, salt, iv, plain text, cipher text
test_cases = [
{'private': '3140f94c79f249787d1ec75a97a885980eb8f0a7d9b7aa03e7200296e422b2b6',
'public': '57a70eb553a7b3fd621f0dba6abf51312ea2e2a2a1e19d0305516730f4bcbd21',
'salt': '83616c67f076d356fd1288a6e0fd7a60488ba312a3adf0088b1b33c7655c3e6a',
'iv': 'a73ff5c32f8fd055b09775817a6a3f95',
'input': '86ddb9e713a8ebf67a51830eff03b837e147c20d75e67b2a54aa29e98c',
'output': '70815da779b1b954d7a7f00c16940e9917a0412a06a444b539bf147603eef87f'},
{'private': '3140f94c79f249787d1ec75a97a885980eb8f0a7d9b7aa03e7200296e422b2b6',
'public': '57a70eb553a7b3fd621f0dba6abf51312ea2e2a2a1e19d0305516730f4bcbd21',
'salt': '703ce0b1d276b10eef35672df03234385a903460db18ba9d4e05b3ad31abb284',
'iv': '91246c2d5493867c4fa3e78f85963677',
'input': '86ddb9e713a8ebf67a51830eff03b837e147c20d75e67b2a54aa29e98c',
'output': '564b2f40d42c0efc1bd6f057115a5abd1564cae36d7ccacf5d825d38401aa894'},
{'private': '3140f94c79f249787d1ec75a97a885980eb8f0a7d9b7aa03e7200296e422b2b6',
'public': '57a70eb553a7b3fd621f0dba6abf51312ea2e2a2a1e19d0305516730f4bcbd21',
'salt': 'b22e8e8e7373ac31ca7f0f6eb8b93130aba5266772a658593f3a11792e7e8d92',
'iv': '9f8e33d82374dad6aac0e3dbe7aea704',
'input': '86ddb9e713a8ebf67a51830eff03b837e147c20d75e67b2a54aa29e98c',
'output': '7cab88d00a3fc656002eccbbd966e1d5d14a3090d92cf502cdbf843515625dcf'},
{'private': '3140f94c79f249787d1ec75a97a885980eb8f0a7d9b7aa03e7200296e422b2b6',
'public': '57a70eb553a7b3fd621f0dba6abf51312ea2e2a2a1e19d0305516730f4bcbd21',
'salt': 'af646c54cd153dffe453b60efbceeb85c1e95a414ea0036c4da94afb3366f5d9',
'iv': '6acdf8e01acc8074ddc807281b6af888',
'input': '86ddb9e713a8ebf67a51830eff03b837e147c20d75e67b2a54aa29e98c',
'output': 'aa70543a485b63a4dd141bb7fd78019092ac6fad731e914280a287c7467bae1a'},
{'private': '3140f94c79f249787d1ec75a97a885980eb8f0a7d9b7aa03e7200296e422b2b6',
'public': '57a70eb553a7b3fd621f0dba6abf51312ea2e2a2a1e19d0305516730f4bcbd21',
'salt': 'd9c0d386636c8a024935c024589f9cd39e820a16485b14951e690a967830e269',
'iv': 'f2e9f18aeb374965f54d2f4e31189a8f',
'input': '86ddb9e713a8ebf67a51830eff03b837e147c20d75e67b2a54aa29e98c',
'output': '33d97c216ea6498dfddabf94c2e2403d73efc495e9b284d9d90aaff840217d25'},
{'private': 'd5c0762ecea2cd6b5c56751b58debcb32713aab348f4a59c493e38beb3244f3a',
'public': '66a35941d615b5644d19c2a602c363ada8b1a8a0dac3682623852dcab4afac04',
'salt': '06c227baac1ae3b0b1dc583f4850f13f9ba5d53be4a98fa5c3ea16217847530d',
'iv': '3735123e78c44895df6ea33fa57e9a72',
'input': '86ddb9e713a8ebf67a51830eff03b837e147c20d75e67b2a54aa29e98c',
'output': 'd5b5d66ba8cee0eb7ecf95b143fa77a46d6de13749e12eff40f5a7e649167ccb'},
{'private': 'd5c0762ecea2cd6b5c56751b58debcb32713aab348f4a59c493e38beb3244f3a',
'public': '66a35941d615b5644d19c2a602c363ada8b1a8a0dac3682623852dcab4afac04',
'salt': '92f55ba5bc6fc2f23e3eedc299357c71518e36ba2447a4da7a9dfe9dfeb107b5',
'iv': '1cbc4982e53e370052af97ab088fa942',
'input': '86ddb9e713a8ebf67a51830eff03b837e147c20d75e67b2a54aa29e98c',
'output': 'd48ef1ef526d805656cfc932aff259eadb17aa3391dde1877a722cba31d935b2'},
{'private': 'd5c0762ecea2cd6b5c56751b58debcb32713aab348f4a59c493e38beb3244f3a',
'public': '66a35941d615b5644d19c2a602c363ada8b1a8a0dac3682623852dcab4afac04',
'salt': '10f15a39ba49866292a43b7781bc71ca8bbd4889f1616461caf056bcb91b0158',
'iv': 'c40d531d92bfee969dce91417346c892',
'input': '49de3cd5890e0cd0559f143807ff688ff62789b7236a332b7d7255ec0b4e73e6b3a4',
'output': 'e6d75afdb542785669b42198577c5b358d95397d71ec6f5835dca46d332cc08dbf73ea790b7bcb169a65719c0d55054c'},
{'private': 'd5c0762ecea2cd6b5c56751b58debcb32713aab348f4a59c493e38beb3244f3a',
'public': '66a35941d615b5644d19c2a602c363ada8b1a8a0dac3682623852dcab4afac04',
'salt': '9c01ed42b219b3bbe1a43ae9d7af5c1dd09363baacfdba8f4d03d1046915e26e',
'iv': '059a35d5f83249e632790015ed6518b9',
'input': '49de3cd5890e0cd0559f143807ff688ff62789b7236a332b7d7255ec0b4e73e6b3a4',
'output': '5ef11aadff2eccee8b712dab968fa842eb770818ec0e6663ed242ea8b6bbc1c66d6285ee5b5f03d55dfee382fb4fa25d'},
{'private': 'd5c0762ecea2cd6b5c56751b58debcb32713aab348f4a59c493e38beb3244f3a',
'public': '66a35941d615b5644d19c2a602c363ada8b1a8a0dac3682623852dcab4afac04',
'salt': 'bc1067e2a7415ea45ff1ca9894338c591ff15f2e57ae2789ae31b9d5bea0f11e',
'iv': '8c73f0d6613898daeefa3cf8b0686d37',
'input': '49de3cd5890e0cd0559f143807ff688ff62789b7236a332b7d7255ec0b4e73e6b3a4',
'output': '6d220213b1878cd40a458f2a1e6e3b48040455fdf504dcd857f4f2ca1ad642e3a44fc401d04e339d302f66a9fad3d919'},
{'private': '9ef87ba8aa2e664bdfdb978b98bc30fb61773d9298e7b8c72911683eeff41921',
'public': '441e76d7e53be0a967181076a842f69c20fd8c0e3f0ce3aa421b490b059fe094',
'salt': 'cf4a21cb790552165827b678ca9695fcaf77566d382325112ff79483455de667',
'iv': 'bfbf5482e06f55b88bdd9e053b7eee6e',
'input': '49de3cd5890e0cd0559f143807ff688ff62789b7236a332b7d7255ec0b4e73e6b3a4',
'output': '1198a78c29c215d5c450f7b8513ead253160bc9fde80d9cc8e6bee2efe9713cf5a09d6293c41033271c9e8c22036a28b'},
{'private': '9ef87ba8aa2e664bdfdb978b98bc30fb61773d9298e7b8c72911683eeff41921',
'public': '441e76d7e53be0a967181076a842f69c20fd8c0e3f0ce3aa421b490b059fe094',
'salt': 'eba5eae8aef79114082c3e70baef95bb02edf13b3897e8be7a70272962ef8838',
'iv': 'af9a56da3da18e2fbd2948a16332532b',
'input': '49de3cd5890e0cd0559f143807ff688ff62789b7236a332b7d7255ec0b4e73e6b3a4',
'output': '1062ab5fbbdee9042ad35bdadfd3047c0a2127fe0f001da1be1b0582185edfc9687be8d68f85795833bb04af9cedd3bb'},
{'private': '9ef87ba8aa2e664bdfdb978b98bc30fb61773d9298e7b8c72911683eeff41921',
'public': '441e76d7e53be0a967181076a842f69c20fd8c0e3f0ce3aa421b490b059fe094',
'salt': '518f8dfd0c138f1ffb4ea8029db15441d70abd893c3d767dc668f23ba7770e27',
'iv': '42d28307974a1b2a2d921d270cfce03b',
'input': '49de3cd5890e0cd0559f143807ff688ff62789b7236a332b7d7255ec0b4e73e6b3a4',
'output': '005e49fb7c5da540a84b034c853fc9f78a6b901ea495aed0c2abd4f08f1a96f9ffefc6a57f1ac09e0aea95ca0f03ffd8'},
{'private': '9ef87ba8aa2e664bdfdb978b98bc30fb61773d9298e7b8c72911683eeff41921',
'public': '441e76d7e53be0a967181076a842f69c20fd8c0e3f0ce3aa421b490b059fe094',
'salt': '582fdf58b53715c26e10ba809e8f2ab70502e5a3d4e9a81100b7227732ab0bbc',
'iv': '91f2aad3189bb2edc93bc891e73911ba',
'input': '49de3cd5890e0cd0559f143807ff688ff62789b7236a332b7d7255ec0b4e73e6b3a4',
'output': '821a69cb16c57f0cb866e590b38069e35faec3ae18f158bb067db83a11237d29ab1e6b868b3147236a0958f15c2e2167'},
{'private': '9ef87ba8aa2e664bdfdb978b98bc30fb61773d9298e7b8c72911683eeff41921',
'public': '441e76d7e53be0a967181076a842f69c20fd8c0e3f0ce3aa421b490b059fe094',
'salt': 'a415b4c006118fb72fc37b2746ef288e23ac45c8ff7ade5f368a31557b6ac93a',
'iv': '2b7c5f75606c0b8106c6489ea5657a9e',
'input': '24512b714aefd5cbc4bcc4ef44ce6c67ffc447c65460a6c6e4a92e85',
'output': '2781d5ee8ef1cb1596f8902b33dfae5045f84a987ca58173af5830dbce386062'},
{'private': 'ed93c5a101ab53382ceee4f7e6b5aa112621d3bb9d18891509b1834ede235bcc',
'public': '5a5e14c633d7d269302849d739d80344ff14db51d7bcda86045723f05c4e4541',
'salt': '47e73ec362ea82d3a7c5d55532ad51d2cdf5316b981b2b2bd542b0efa027e8ea',
'iv': 'b2193f59030c8d05a7d3577b7f64dd33',
'input': '24512b714aefd5cbc4bcc4ef44ce6c67ffc447c65460a6c6e4a92e85',
'output': '3f43912db8dd6672b9996e5272e18c4b88fec9d7e8372db9c5f4709a4af1d86f'},
{'private': 'ed93c5a101ab53382ceee4f7e6b5aa112621d3bb9d18891509b1834ede235bcc',
'public': '5a5e14c633d7d269302849d739d80344ff14db51d7bcda86045723f05c4e4541',
'salt': 'aaa006c57b6d1e402650577fe9787d8d285f4bacd7c01f998be49c766f8860c7',
'iv': '130304ddb9adc8870cf56bcae9487b7f',
'input': '24512b714aefd5cbc4bcc4ef44ce6c67ffc447c65460a6c6e4a92e85',
'output': '878cc7d8c0ef8dac0182a78eedc8080a402f59d8062a6b4ca8f4a74f3c3b3de7'},
{'private': 'ed93c5a101ab53382ceee4f7e6b5aa112621d3bb9d18891509b1834ede235bcc',
'public': '5a5e14c633d7d269302849d739d80344ff14db51d7bcda86045723f05c4e4541',
'salt': '28dc7ccd6c2a939eef64b8be7b9ae248295e7fcd8471c22fa2f98733fea97611',
'iv': 'cb13890d3a11bc0a7433738263006710',
'input': '24512b714aefd5cbc4bcc4ef44ce6c67ffc447c65460a6c6e4a92e85',
'output': 'e74ded846bebfa912fa1720e4c1415e6e5df7e7a1a7fedb5665d68f1763209a4'},
{'private': 'ed93c5a101ab53382ceee4f7e6b5aa112621d3bb9d18891509b1834ede235bcc',
'public': '5a5e14c633d7d269302849d739d80344ff14db51d7bcda86045723f05c4e4541',
'salt': '79974fa2cad95154d0873902c153ccc3e7d54b17f2eeb3f29b6344cad9365a9a',
'iv': '22123357979d20f44cc8eb0263d84e0e',
'input': '24512b714aefd5cbc4bcc4ef44ce6c67ffc447c65460a6c6e4a92e85',
'output': 'eb14dec7b8b64d81a2ee4db07b0adf144d4f79a519bbf332b823583fa2d45405'},
{'private': 'ed93c5a101ab53382ceee4f7e6b5aa112621d3bb9d18891509b1834ede235bcc',
'public': '5a5e14c633d7d269302849d739d80344ff14db51d7bcda86045723f05c4e4541',
'salt': '3409a6f8c4dcd9bd04144eb67e55a98696b674735b01bf1196191f29871ef966',
'iv': 'a823a0965969380ea1f8659ea5fd8fdd',
'input': '24512b714aefd5cbc4bcc4ef44ce6c67ffc447c65460a6c6e4a92e85',
'output': '00a7eb708eae745847173f8217efb05be13059710aee632e3f471ac3c6202b51'},
]
for test in test_cases:
private_key = bytearray(reversed(unhexlify(test['private'])))
node = bip32.HDNode(
depth=0,
fingerprint=0,
child_num=0,
chain_code=bytearray(32),
private_key=private_key,
curve_name=NEM_CURVE
)
encrypted = node.nem_encrypt(unhexlify(test['public']),
unhexlify(test['iv']),
unhexlify(test['salt']),
unhexlify(test['input']))
self.assertEqual(encrypted, unhexlify(test['output']))
if __name__ == '__main__':
unittest.main()

View File

@ -0,0 +1,165 @@
from common import *
from trezor.messages.NEMMosaic import NEMMosaic
from apps.nem.mosaic.helpers import get_mosaic_definition
from apps.nem.transfer import *
from apps.nem.transfer.serialize import *
class TestNemMosaic(unittest.TestCase):
def test_get_mosaic_definition(self):
m = get_mosaic_definition("nem", "xem", 104)
self.assertEqual(m["name"], "XEM")
self.assertEqual(m["ticker"], " XEM")
m = get_mosaic_definition("nem", "xxx", 104)
self.assertEqual(m, None)
m = get_mosaic_definition("aaaa", "xxx", 104)
self.assertEqual(m, None)
m = get_mosaic_definition("pacnem", "cheese", 104)
self.assertEqual(m["name"], "PacNEM Score Tokens")
self.assertEqual(m["ticker"], " PAC:CHS")
self.assertEqual(m["fee"], 100)
def test_mosaic_canonicalization(self):
a = NEMMosaic()
a.namespace = 'abc'
a.quantity = 3
a.mosaic = 'mosaic'
b = NEMMosaic()
b.namespace = 'abc'
b.quantity = 4
b.mosaic = 'a'
c = NEMMosaic()
c.namespace = 'zzz'
c.quantity = 3
c.mosaic = 'mosaic'
d = NEMMosaic()
d.namespace = 'abc'
d.quantity = 8
d.mosaic = 'mosaic'
e = NEMMosaic()
e.namespace = 'aaa'
e.quantity = 1
e.mosaic = 'mosaic'
f = NEMMosaic()
f.namespace = 'aaa'
f.quantity = 1
f.mosaic = 'mosaicz'
g = NEMMosaic()
g.namespace = 'zzz'
g.quantity = 30
g.mosaic = 'mosaic'
res = canonicalize_mosaics([a, b, c, d, e, f, g])
self.assertEqual(res, [e, f, b, a, c])
self.assertEqual(res[2].quantity, b.quantity)
self.assertEqual(res[3].quantity, 3 + 8) # a + d
self.assertEqual(res[4].quantity, 3 + 30) # c + g
def test_mosaic_merge(self):
a = NEMMosaic()
a.namespace = 'abc'
a.quantity = 1
a.mosaic = 'mosaic'
b = NEMMosaic()
b.namespace = 'abc'
b.quantity = 1
b.mosaic = 'mosaic'
merged = merge_mosaics([a, b])
self.assertEqual(merged[0].quantity, 2)
self.assertEqual(len(merged), 1)
a.quantity = 1
b.quantity = 10
merged = merge_mosaics([a, b])
self.assertEqual(merged[0].quantity, 11)
a.namespace = 'abcdef'
merged = merge_mosaics([a, b])
self.assertEqual(len(merged), 2)
c = NEMMosaic()
c.namespace = 'abc'
c.mosaic = 'xxx'
c.quantity = 2
merged = merge_mosaics([a, b, c])
self.assertEqual(len(merged), 3)
a.namespace = 'abcdef'
a.quantity = 1
a.mosaic = 'mosaic'
b.namespace = 'abc'
b.quantity = 2
b.mosaic = 'mosaic'
c.namespace = 'abc'
c.mosaic = 'mosaic'
c.quantity = 3
merged = merge_mosaics([a, b, c])
self.assertEqual(merged[0].quantity, 1)
self.assertEqual(merged[1].quantity, 5)
self.assertEqual(len(merged), 2)
a.namespace = 'abc'
a.quantity = 1
a.mosaic = 'mosaic'
b.namespace = 'abc'
b.quantity = 2
b.mosaic = 'mosaic'
c.namespace = 'abc'
c.mosaic = 'mosaic'
c.quantity = 3
merged = merge_mosaics([a, b, c])
self.assertEqual(merged[0].quantity, 6)
self.assertEqual(len(merged), 1)
def test_mosaic_sort(self):
a = NEMMosaic()
a.namespace = 'abcz'
a.quantity = 1
a.mosaic = 'mosaic'
b = NEMMosaic()
b.namespace = 'abca'
b.quantity = 1
b.mosaic = 'mosaic'
res = sort_mosaics([a, b])
self.assertEqual(res, [b, a])
a.namespace = ''
b.namespace = 'a.b.c'
res = sort_mosaics([a, b])
self.assertEqual(res, [a, b])
a.namespace = 'z.z.z'
b.namespace = 'a.b.c'
res = sort_mosaics([a, b])
self.assertEqual(res, [b, a])
a.namespace = 'a'
b.namespace = 'a'
a.mosaic = 'mosaic'
b.mosaic = 'mosaic'
res = sort_mosaics([a, b])
self.assertEqual(res, [a, b])
a.mosaic = 'www'
b.mosaic = 'aaa'
res = sort_mosaics([a, b])
self.assertEqual(res, [b, a])
c = NEMMosaic()
c.namespace = 'a'
c.mosaic = 'zzz'
res = sort_mosaics([a, b, c])
self.assertEqual(res, [b, a, c])
c.mosaic = 'bbb'
res = sort_mosaics([a, b, c])
self.assertEqual(res, [b, c, a])
if __name__ == '__main__':
unittest.main()

View File

@ -0,0 +1,154 @@
from common import *
from apps.nem.helpers import *
from apps.nem.mosaic import *
from apps.nem.mosaic.serialize import *
from trezor.crypto import hashlib
from trezor.messages.NEMSignTx import NEMSignTx
from trezor.messages.NEMMosaicCreation import NEMMosaicCreation
from trezor.messages.NEMMosaicDefinition import NEMMosaicDefinition
class TestNemMosaicCreation(unittest.TestCase):
def test_nem_transaction_mosaic_creation(self):
# http://bob.nem.ninja:8765/#/mosaic/68364353c29105e6d361ad1a42abbccbf419cfc7adb8b74c8f35d8f8bdaca3fa/0
m = _create_msg(NEM_NETWORK_TESTNET,
14070896,
108000000,
14074496,
'gimre.games.pong',
'paddles',
'Paddles for the bong game.\n',
0,
10000,
True,
True,
0,
0,
'',
'',
'',
'TBMOSAICOD4F54EE5CDMR23CCBGOAM2XSJBR5OLC',
50000000000)
t = serialize_mosaic_creation(m.transaction, m.mosaic_creation, unhexlify('994793ba1c789fa9bdea918afc9b06e2d0309beb1081ac5b6952991e4defd324'))
self.assertEqual(t, unhexlify('014000000100009870b4d60020000000994793ba1c789fa9bdea918afc9b06e2d0309beb1081ac5b6952991e4defd32400f36f060000000080c2d600de00000020000000994793ba1c789fa9bdea918afc9b06e2d0309beb1081ac5b6952991e4defd3241f0000001000000067696d72652e67616d65732e706f6e6707000000706164646c65731b000000506164646c657320666f722074686520626f6e672067616d652e0a04000000150000000c00000064697669736962696c69747901000000301a0000000d000000696e697469616c537570706c79050000003130303030190000000d000000737570706c794d757461626c650400000074727565180000000c0000007472616e7366657261626c650400000074727565000000002800000054424d4f534149434f443446353445453543444d523233434342474f414d3258534a4252354f4c4300743ba40b000000'))
self.assertEqual(hashlib.sha3_256(t).digest(True), unhexlify('68364353c29105e6d361ad1a42abbccbf419cfc7adb8b74c8f35d8f8bdaca3fa'))
def test_nem_transaction_mosaic_creation_with_levy(self):
# http://bob.nem.ninja:8765/#/mosaic/b2f4a98113ff1f3a8f1e9d7197aa982545297fe0aa3fa6094af8031569953a55/0
m = _create_msg(NEM_NETWORK_TESTNET,
21497248,
108000000,
21500848,
"alice.misc",
"bar",
"Special offer: get one bar extra by bying one foo!",
0,
1000,
False,
True,
1,
1,
"TALICE2GMA34CXHD7XLJQ536NM5UNKQHTORNNT2J",
"nem",
"xem",
"TBMOSAICOD4F54EE5CDMR23CCBGOAM2XSJBR5OLC",
50000000000)
t = serialize_mosaic_creation(m.transaction, m.mosaic_creation, unhexlify("244fa194e2509ac0d2fbc18779c2618d8c2ebb61c16a3bcbebcf448c661ba8dc"),)
self.assertEqual(hashlib.sha3_256(t).digest(True), unhexlify('b2f4a98113ff1f3a8f1e9d7197aa982545297fe0aa3fa6094af8031569953a55'))
# http://chain.nem.ninja/#/mosaic/e8dc14821dbea4831d9051f86158ef348001447968fc22c01644fdaf2bda75c6/0
m = _create_msg(NEM_NETWORK_MAINNET,
69251020,
20000000,
69337420,
"dim",
"coin",
"DIM COIN",
6,
9000000000,
False,
True,
2,
10,
"NCGGLVO2G3CUACVI5GNX2KRBJSQCN4RDL2ZWJ4DP",
"dim",
"coin",
"NBMOSAICOD4F54EE5CDMR23CCBGOAM2XSIUX6TRS",
500000000)
t = serialize_mosaic_creation(m.transaction, m.mosaic_creation, unhexlify("a1df5306355766bd2f9a64efdc089eb294be265987b3359093ae474c051d7d5a"))
self.assertEqual(t, unhexlify('0140000001000068ccaf200420000000a1df5306355766bd2f9a64efdc089eb294be265987b3359093ae474c051d7d5a002d3101000000004c0122040c01000020000000a1df5306355766bd2f9a64efdc089eb294be265987b3359093ae474c051d7d5a0f0000000300000064696d04000000636f696e0800000044494d20434f494e04000000150000000c00000064697669736962696c69747901000000361f0000000d000000696e697469616c537570706c790a000000393030303030303030301a0000000d000000737570706c794d757461626c650500000066616c7365180000000c0000007472616e7366657261626c6504000000747275654b00000002000000280000004e4347474c564f32473343554143564935474e58324b52424a5351434e3452444c325a574a3444500f0000000300000064696d04000000636f696e0a00000000000000280000004e424d4f534149434f443446353445453543444d523233434342474f414d325853495558365452530065cd1d00000000'))
self.assertEqual(hashlib.sha3_256(t).digest(True), unhexlify('e8dc14821dbea4831d9051f86158ef348001447968fc22c01644fdaf2bda75c6'))
def test_nem_transaction_mosaic_creation_with_description(self):
# http://chain.nem.ninja/#/mosaic/269c6fda657aba3053a0e5b138c075808cc20e244e1182d9b730798b60a1f77b/0
m = _create_msg(NEM_NETWORK_MAINNET,
26729938,
108000000,
26733538,
"jabo38",
"red_token",
"This token is to celebrate the release of Namespaces and Mosaics "
"on the NEM system. This token was the fist ever mosaic created "
"other than nem.xem. There are only 10,000 Red Tokens that will "
"ever be created. It has no levy and can be traded freely among "
"third parties.",
2,
10000,
False,
True,
0,
0,
"",
"",
"",
"NBMOSAICOD4F54EE5CDMR23CCBGOAM2XSIUX6TRS",
50000000000)
t = serialize_mosaic_creation(m.transaction, m.mosaic_creation, unhexlify("58956ac77951622dc5f1c938affbf017c458e30e6b21ddb5783d38b302531f23"))
self.assertEqual(t, unhexlify('0140000001000068d2dd97012000000058956ac77951622dc5f1c938affbf017c458e30e6b21ddb5783d38b302531f2300f36f0600000000e2eb9701c80100002000000058956ac77951622dc5f1c938affbf017c458e30e6b21ddb5783d38b302531f2317000000060000006a61626f3338090000007265645f746f6b656e0c0100005468697320746f6b656e20697320746f2063656c656272617465207468652072656c65617365206f66204e616d6573706163657320616e64204d6f7361696373206f6e20746865204e454d2073797374656d2e205468697320746f6b656e207761732074686520666973742065766572206d6f736169632063726561746564206f74686572207468616e206e656d2e78656d2e20546865726520617265206f6e6c792031302c3030302052656420546f6b656e7320746861742077696c6c206576657220626520637265617465642e20497420686173206e6f206c65767920616e642063616e2062652074726164656420667265656c7920616d6f6e6720746869726420706172746965732e04000000150000000c00000064697669736962696c69747901000000321a0000000d000000696e697469616c537570706c790500000031303030301a0000000d000000737570706c794d757461626c650500000066616c7365180000000c0000007472616e7366657261626c65040000007472756500000000280000004e424d4f534149434f443446353445453543444d523233434342474f414d3258534955583654525300743ba40b000000'))
self.assertEqual(hashlib.sha3_256(t).digest(True), unhexlify('269c6fda657aba3053a0e5b138c075808cc20e244e1182d9b730798b60a1f77b'))
def _create_msg(network: int, timestamp: int, fee: int, deadline: int,
namespace: str, mosaic: str, description: str,
divisibility: int, supply: int, mutable_supply: bool, transferable: bool,
levy_type: int, levy_fee: int, levy_address: str, levy_namespace: str,
levy_mosaic: str, creation_sink: str, creation_fee: int):
m = NEMSignTx()
m.transaction = NEMTransactionCommon()
m.transaction.network = network
m.transaction.timestamp = timestamp
m.transaction.fee = fee
m.transaction.deadline = deadline
m.mosaic_creation = NEMMosaicCreation()
m.mosaic_creation.sink = creation_sink
m.mosaic_creation.fee = creation_fee
m.mosaic_creation.definition = NEMMosaicDefinition()
m.mosaic_creation.definition.namespace = namespace
m.mosaic_creation.definition.mosaic = mosaic
m.mosaic_creation.definition.description = description
m.mosaic_creation.definition.divisibility = divisibility
m.mosaic_creation.definition.supply = supply
m.mosaic_creation.definition.mutable_supply = mutable_supply
m.mosaic_creation.definition.transferable = transferable
m.mosaic_creation.definition.levy = levy_type
m.mosaic_creation.definition.fee = levy_fee
m.mosaic_creation.definition.levy_address = levy_address
m.mosaic_creation.definition.levy_namespace = levy_namespace
m.mosaic_creation.definition.levy_mosaic = levy_mosaic
return m
if __name__ == '__main__':
unittest.main()

View File

@ -0,0 +1,91 @@
from common import *
from apps.nem.helpers import *
from apps.nem.mosaic import *
from apps.nem.mosaic.serialize import *
from trezor.crypto import hashlib
from trezor.messages.NEMSignTx import NEMSignTx
from trezor.messages.NEMMosaicSupplyChange import NEMMosaicSupplyChange
class TestNemMosaicSupplyChange(unittest.TestCase):
def test_nem_transaction_create_mosaic_supply_change(self):
# http://bigalice2.nem.ninja:7890/transaction/get?hash=33a50fdd4a54913643a580b2af08b9a5b51b7cee922bde380e84c573a7969c50
m = _create_msg(NEM_NETWORK_TESTNET,
14071648,
108000000,
14075248,
"gimre.games.pong",
"paddles",
1,
1234)
t = serialize_mosaic_supply_change(m.transaction, m.supply_change, unhexlify("994793ba1c789fa9bdea918afc9b06e2d0309beb1081ac5b6952991e4defd324"))
self.assertEqual(hashlib.sha3_256(t).digest(True),
unhexlify('33a50fdd4a54913643a580b2af08b9a5b51b7cee922bde380e84c573a7969c50'))
# http://bigalice2.nem.ninja:7890/transaction/get?hash=1ce8e8894d077a66ff22294b000825d090a60742ec407efd80eb8b19657704f2
m = _create_msg(NEM_NETWORK_TESTNET,
14126909,
108000000,
14130509,
"jabo38_ltd.fuzzy_kittens_cafe",
"coupons",
2,
1)
t = serialize_mosaic_supply_change(m.transaction, m.supply_change, unhexlify("84afa1bbc993b7f5536344914dde86141e61f8cbecaf8c9cefc07391f3287cf5"))
self.assertEqual(hashlib.sha3_256(t).digest(True),
unhexlify('1ce8e8894d077a66ff22294b000825d090a60742ec407efd80eb8b19657704f2'))
# http://bigalice3.nem.ninja:7890/transaction/get?hash=694e493e9576d2bcf60d85747e302ac2e1cc27783187947180d4275a713ff1ff
m = _create_msg(NEM_NETWORK_MAINNET,
53377685,
20000000,
53464085,
"abvapp",
"abv",
1,
9000000)
t = serialize_mosaic_supply_change(m.transaction, m.supply_change, unhexlify("b7ccc27b21ba6cf5c699a8dc86ba6ba98950442597ff9fa30e0abe0f5f4dd05d"))
self.assertEqual(hashlib.sha3_256(t).digest(True),
unhexlify('694e493e9576d2bcf60d85747e302ac2e1cc27783187947180d4275a713ff1ff'))
# http://bigalice3.nem.ninja:7890/transaction/get?hash=09836334e123970e068d5b411e4d1df54a3ead10acf1ad5935a2cdd9f9680185
m = _create_msg(NEM_NETWORK_MAINNET,
55176304,
20000000,
55262704,
"sushi",
"wasabi",
2,
20)
t = serialize_mosaic_supply_change(m.transaction, m.supply_change, unhexlify("75f001a8641e2ce5c4386883dda561399ed346177411b492a677b73899502f13"))
self.assertEqual(hashlib.sha3_256(t).digest(True),
unhexlify('09836334e123970e068d5b411e4d1df54a3ead10acf1ad5935a2cdd9f9680185'))
def _create_msg(network: int, timestamp: int, fee: int, deadline: int,
namespace: str, mosaic: str, mod_type: int, delta: int):
m = NEMSignTx()
m.transaction = NEMTransactionCommon()
m.transaction.network = network
m.transaction.timestamp = timestamp
m.transaction.fee = fee
m.transaction.deadline = deadline
m.supply_change = NEMMosaicSupplyChange()
m.supply_change.namespace = namespace
m.supply_change.mosaic = mosaic
m.supply_change.type = mod_type
m.supply_change.delta = delta
return m
if __name__ == '__main__':
unittest.main()

View File

@ -0,0 +1,98 @@
from common import *
from apps.nem.helpers import *
from apps.nem.multisig import *
from apps.nem.multisig.serialize import *
from trezor.crypto import hashlib
from trezor.messages.NEMSignTx import NEMSignTx
from trezor.messages.NEMAggregateModification import NEMAggregateModification
from trezor.messages.NEMCosignatoryModification import NEMCosignatoryModification
from trezor.messages.NEMTransactionCommon import NEMTransactionCommon
class TestNemMultisigAggregateModification(unittest.TestCase):
def test_nem_transaction_aggregate_modification(self):
# http://bob.nem.ninja:8765/#/aggregate/6a55471b17159e5b6cd579c421e95a4e39d92e3f78b0a55ee337e785a601d3a2
m = _create_msg(NEM_NETWORK_TESTNET,
0,
22000000,
0,
2,
0)
t = serialize_aggregate_modification(m.transaction, m.aggregate_modification, unhexlify("462ee976890916e54fa825d26bdd0235f5eb5b6a143c199ab0ae5ee9328e08ce"))
serialize_cosignatory_modification(t, 1, unhexlify(
"994793ba1c789fa9bdea918afc9b06e2d0309beb1081ac5b6952991e4defd324"))
serialize_cosignatory_modification(t, 1, unhexlify(
"c54d6e33ed1446eedd7f7a80a588dd01857f723687a09200c1917d5524752f8b"))
self.assertEqual(hashlib.sha3_256(t).digest(True),
unhexlify("6a55471b17159e5b6cd579c421e95a4e39d92e3f78b0a55ee337e785a601d3a2"))
# http://chain.nem.ninja/#/aggregate/cc64ca69bfa95db2ff7ac1e21fe6d27ece189c603200ebc9778d8bb80ca25c3c
m = _create_msg(NEM_NETWORK_MAINNET,
0,
40000000,
0,
5,
0)
t = serialize_aggregate_modification(m.transaction, m.aggregate_modification, unhexlify("f41b99320549741c5cce42d9e4bb836d98c50ed5415d0c3c2912d1bb50e6a0e5"))
serialize_cosignatory_modification(t, 1, unhexlify(
"1fbdbdde28daf828245e4533765726f0b7790e0b7146e2ce205df3e86366980b"))
serialize_cosignatory_modification(t, 1, unhexlify(
"f94e8702eb1943b23570b1b83be1b81536df35538978820e98bfce8f999e2d37"))
serialize_cosignatory_modification(t, 1, unhexlify(
"826cedee421ff66e708858c17815fcd831a4bb68e3d8956299334e9e24380ba8"))
serialize_cosignatory_modification(t, 1, unhexlify(
"719862cd7d0f4e875a6a0274c9a1738f38f40ad9944179006a54c34724c1274d"))
serialize_cosignatory_modification(t, 1, unhexlify(
"43aa69177018fc3e2bdbeb259c81cddf24be50eef9c5386db51d82386c41475a"))
self.assertEqual(hashlib.sha3_256(t).digest(True),
unhexlify("cc64ca69bfa95db2ff7ac1e21fe6d27ece189c603200ebc9778d8bb80ca25c3c"))
def test_nem_transaction_aggregate_modification_relative_change(self):
# http://bob.nem.ninja:8765/#/aggregate/1fbdae5ba753e68af270930413ae90f671eb8ab58988116684bac0abd5726584
m = _create_msg(NEM_NETWORK_TESTNET,
6542254,
40000000,
6545854,
4,
2)
t = serialize_aggregate_modification(m.transaction, m.aggregate_modification, unhexlify("6bf7849c1eec6a2002995cc457dc00c4e29bad5c88de63f51e42dfdcd7b2131d"))
serialize_cosignatory_modification(t, 1, unhexlify(
"5f53d076c8c3ec3110b98364bc423092c3ec2be2b1b3c40fd8ab68d54fa39295"))
serialize_cosignatory_modification(t, 1, unhexlify(
"9eb199c2b4d406f64cb7aa5b2b0815264b56ba8fe44d558a6cb423a31a33c4c2"))
serialize_cosignatory_modification(t, 1, unhexlify(
"94b2323dab23a3faba24fa6ddda0ece4fbb06acfedd74e76ad9fae38d006882b"))
serialize_cosignatory_modification(t, 1, unhexlify(
"d88c6ee2a2cd3929d0d76b6b14ecb549d21296ab196a2b3a4cb2536bcce32e87"))
serialize_minimum_cosignatories(t, 2)
self.assertEqual(hashlib.sha3_256(t).digest(True),
unhexlify("1fbdae5ba753e68af270930413ae90f671eb8ab58988116684bac0abd5726584"))
def _create_msg(network: int, timestamp: int, fee: int, deadline: int,
modifications: int, relative_change: int):
m = NEMSignTx()
m.transaction = NEMTransactionCommon()
m.transaction.network = network
m.transaction.timestamp = timestamp
m.transaction.fee = fee
m.transaction.deadline = deadline
m.aggregate_modification = NEMAggregateModification()
for i in range(modifications):
m.aggregate_modification.modifications.append(NEMCosignatoryModification())
m.aggregate_modification.relative_change = relative_change
return m
if __name__ == '__main__':
unittest.main()

View File

@ -0,0 +1,116 @@
from common import *
from apps.nem.helpers import *
from apps.nem.multisig import *
from apps.nem.multisig.serialize import *
from apps.nem.namespace import *
from apps.nem.namespace.serialize import *
from trezor.messages.NEMSignTx import NEMSignTx
from trezor.messages.NEMAggregateModification import NEMAggregateModification
from trezor.messages.NEMProvisionNamespace import NEMProvisionNamespace
from trezor.messages.NEMCosignatoryModification import NEMCosignatoryModification
class TestNemMultisig(unittest.TestCase):
def test_nem_multisig(self):
# http://bob.nem.ninja:8765/#/multisig/7d3a7087023ee29005262016706818579a2b5499eb9ca76bad98c1e6f4c46642
m = _create_msg(NEM_NETWORK_TESTNET,
3939039,
16000000,
3960639,
1,
0)
base_tx = serialize_aggregate_modification(m.transaction, m.aggregate_modification, unhexlify("abac2ee3d4aaa7a3bfb65261a00cc04c761521527dd3f2cf741e2815cbba83ac"))
base_tx = serialize_cosignatory_modification(base_tx, 2, unhexlify("e6cff9b3725a91f31089c3acca0fac3e341c00b1c8c6e9578f66c4514509c3b3"))
m = _create_common_msg(NEM_NETWORK_TESTNET,
3939039,
6000000,
3960639)
multisig = serialize_multisig(m, unhexlify("59d89076964742ef2a2089d26a5aa1d2c7a7bb052a46c1de159891e91ad3d76e"), base_tx)
self.assertEqual(multisig, unhexlify("0410000001000098df1a3c002000000059d89076964742ef2a2089d26a5aa1d2c7a7bb052a46c1de159891e91ad3d76e808d5b00000000003f6f3c006c0000000110000001000098df1a3c0020000000abac2ee3d4aaa7a3bfb65261a00cc04c761521527dd3f2cf741e2815cbba83ac0024f400000000003f6f3c0001000000280000000200000020000000e6cff9b3725a91f31089c3acca0fac3e341c00b1c8c6e9578f66c4514509c3b3"))
address_pubkey = unhexlify("abac2ee3d4aaa7a3bfb65261a00cc04c761521527dd3f2cf741e2815cbba83ac")
m = _create_common_msg(NEM_NETWORK_TESTNET,
3939891,
6000000,
3961491)
multisig = serialize_multisig_signature(m, unhexlify("71cba4f2a28fd19f902ba40e9937994154d9eeaad0631d25d525ec37922567d4"), base_tx, address_pubkey)
self.assertEqual(multisig, unhexlify("0210000001000098331e3c002000000071cba4f2a28fd19f902ba40e9937994154d9eeaad0631d25d525ec37922567d4808d5b000000000093723c0024000000200000008ec165580bdabfd31ce6007a1748ce5bdf30eab7a214743097de3bc822ac7e002800000054435258595551494d464137414f474c354c463359574c43375641424c59554d4a35414342554e4c"))
def test_nem_multisig_2(self):
# http://chain.nem.ninja/#/multisig/1016cf3bdd61bd57b9b2b07b6ff2dee390279d8d899265bdc23d42360abe2e6c
m = _create_provision_msg(NEM_NETWORK_MAINNET,
59414272,
20000000,
59500672,
"dim",
"",
"NAMESPACEWH4MKFMBCVFERDPOOP4FK7MTBXDPZZA",
5000000000)
base_tx = serialize_provision_namespace(m.transaction, m.provision_namespace, unhexlify("a1df5306355766bd2f9a64efdc089eb294be265987b3359093ae474c051d7d5a"))
m = _create_common_msg(NEM_NETWORK_MAINNET,
59414272,
6000000,
59500672)
multisig = serialize_multisig(m, unhexlify("cfe58463f0eaebceb5d00717f8aead49171a5d7c08f6b1299bd534f11715acc9"), base_tx)
self.assertEqual(multisig, unhexlify("041000000100006800978a0320000000cfe58463f0eaebceb5d00717f8aead49171a5d7c08f6b1299bd534f11715acc9808d5b000000000080e88b037b000000012000000100006800978a0320000000a1df5306355766bd2f9a64efdc089eb294be265987b3359093ae474c051d7d5a002d31010000000080e88b03280000004e414d4553504143455748344d4b464d42435646455244504f4f5034464b374d54425844505a5a4100f2052a010000000300000064696dffffffff"))
m = _create_common_msg(NEM_NETWORK_MAINNET,
59414342,
6000000,
59500742)
address_pubkey = unhexlify("a1df5306355766bd2f9a64efdc089eb294be265987b3359093ae474c051d7d5a")
multisig = serialize_multisig_signature(m, unhexlify("1b49b80203007117d034e45234ffcdf402c044aeef6dbb06351f346ca892bce2"), base_tx, address_pubkey)
self.assertEqual(multisig, unhexlify("021000000100006846978a03200000001b49b80203007117d034e45234ffcdf402c044aeef6dbb06351f346ca892bce2808d5b0000000000c6e88b032400000020000000bfa2088f7720f89dd4664d650e321dabd02fab61b7355bc88a391a848a49786a280000004e4444524733554542354c5a5a5a4d445742453452544b5a4b37334a424850414957424843464d56"))
m = _create_common_msg(NEM_NETWORK_MAINNET,
59414381,
6000000,
59500781)
multisig = serialize_multisig_signature(m, unhexlify("7ba4b39209f1b9846b098fe43f74381e43cb2882ccde780f558a63355840aa87"), base_tx, address_pubkey)
self.assertEqual(multisig, unhexlify("02100000010000686d978a03200000007ba4b39209f1b9846b098fe43f74381e43cb2882ccde780f558a63355840aa87808d5b0000000000ede88b032400000020000000bfa2088f7720f89dd4664d650e321dabd02fab61b7355bc88a391a848a49786a280000004e4444524733554542354c5a5a5a4d445742453452544b5a4b37334a424850414957424843464d56"))
def _create_common_msg(network: int, timestamp: int, fee: int, deadline: int):
m = NEMTransactionCommon()
m.network = network
m.timestamp = timestamp
m.fee = fee
m.deadline = deadline
return m
def _create_msg(network: int, timestamp: int, fee: int, deadline: int,
modifications: int, relative_change: int):
m = NEMSignTx()
m.transaction = _create_common_msg(network, timestamp, fee, deadline)
m.aggregate_modification = NEMAggregateModification()
for i in range(modifications):
m.aggregate_modification.modifications.append(NEMCosignatoryModification())
m.aggregate_modification.relative_change = relative_change
return m
def _create_provision_msg(network: int, timestamp: int, fee: int, deadline: int,
name: str, parent: str, sink: str, rental_fee: int):
m = NEMSignTx()
m.transaction = _create_common_msg(network, timestamp, fee, deadline)
m.provision_namespace = NEMProvisionNamespace()
m.provision_namespace.namespace = name
m.provision_namespace.parent = parent
m.provision_namespace.sink = sink
m.provision_namespace.fee = rental_fee
return m
if __name__ == '__main__':
unittest.main()

View File

@ -0,0 +1,72 @@
from common import *
from apps.nem.helpers import *
from apps.nem.namespace import *
from apps.nem.namespace.serialize import *
from trezor.crypto import hashlib
from trezor.messages.NEMProvisionNamespace import NEMProvisionNamespace
from trezor.messages.NEMSignTx import NEMSignTx
class TestNemNamespace(unittest.TestCase):
def test_create_provision_namespace(self):
# http://bob.nem.ninja:8765/#/transfer/0acbf8df91e6a65dc56c56c43d65f31ff2a6a48d06fc66e78c7f3436faf3e74f
m = _create_msg(NEM_NETWORK_TESTNET,
56999445,
20000000,
57003045,
'gimre',
'',
'TAMESPACEWH4MKFMBCVFERDPOOP4FK7MTDJEYP35',
5000000000)
t = serialize_provision_namespace(m.transaction, m.provision_namespace, unhexlify('84afa1bbc993b7f5536344914dde86141e61f8cbecaf8c9cefc07391f3287cf5'))
self.assertEqual(hashlib.sha3_256(t).digest(True), unhexlify('f7cab28da57204d01a907c697836577a4ae755e6c9bac60dcc318494a22debb3'))
# http://bob.nem.ninja:8765/#/namespace/7ddd5fe607e1bfb5606e0ac576024c318c8300d237273117d4db32a60c49524d
m = _create_msg(NEM_NETWORK_TESTNET,
21496797,
108000000,
21500397,
'misc',
'alice',
'TAMESPACEWH4MKFMBCVFERDPOOP4FK7MTDJEYP35',
5000000000)
t = serialize_provision_namespace(m.transaction, m.provision_namespace, unhexlify('244fa194e2509ac0d2fbc18779c2618d8c2ebb61c16a3bcbebcf448c661ba8dc'))
self.assertEqual(hashlib.sha3_256(t).digest(True), unhexlify('7ddd5fe607e1bfb5606e0ac576024c318c8300d237273117d4db32a60c49524d'))
# http://chain.nem.ninja/#/namespace/57071aad93ca125dc231dc02c07ad8610cd243d35068f9b36a7d231383907569
m = _create_msg(NEM_NETWORK_MAINNET,
26699717,
108000000,
26703317,
'sex',
'',
'NAMESPACEWH4MKFMBCVFERDPOOP4FK7MTBXDPZZA',
50000000000)
t = serialize_provision_namespace(m.transaction, m.provision_namespace, unhexlify('9f3c14f304309c8b72b2821339c4428793b1518bea72d58dd01f19d523518614'))
self.assertEqual(hashlib.sha3_256(t).digest(True), unhexlify('57071aad93ca125dc231dc02c07ad8610cd243d35068f9b36a7d231383907569'))
def _create_msg(network: int, timestamp: int, fee: int, deadline: int,
name: str, parent: str, sink: str, rental_fee: int):
m = NEMSignTx()
m.transaction = NEMTransactionCommon()
m.transaction.network = network
m.transaction.timestamp = timestamp
m.transaction.fee = fee
m.transaction.deadline = deadline
m.provision_namespace = NEMProvisionNamespace()
m.provision_namespace.namespace = name
m.provision_namespace.parent = parent
m.provision_namespace.sink = sink
m.provision_namespace.fee = rental_fee
return m
if __name__ == '__main__':
unittest.main()

View File

@ -0,0 +1,119 @@
from common import *
from apps.nem.helpers import *
from apps.nem.mosaic import *
from apps.nem.transfer import *
from apps.nem.transfer.serialize import *
from trezor.crypto import hashlib
from trezor.messages.NEMTransfer import NEMTransfer
from trezor.messages.NEMSignTx import NEMSignTx
class TestNemTransfer(unittest.TestCase):
def test_create_transfer(self):
# http://bob.nem.ninja:8765/#/transfer/0acbf8df91e6a65dc56c56c43d65f31ff2a6a48d06fc66e78c7f3436faf3e74f
m = _create_msg(NEM_NETWORK_TESTNET,
0,
0,
0,
'TBGIMRE4SBFRUJXMH7DVF2IBY36L2EDWZ37GVSC4',
50000000000000)
t = serialize_transfer(m.transaction, m.transfer, unhexlify('e59ef184a612d4c3c4d89b5950eb57262c69862b2f96e59c5043bf41765c482f'))
self.assertEqual(t, unhexlify('01010000010000980000000020000000e59ef184a612d4c3c4d89b5950eb57262c69862b2f96e59c5043bf41765c482f00000000000000000000000028000000544247494d52453453424652554a584d48374456463249425933364c324544575a3337475653433400203d88792d000000000000'))
self.assertEqual(hashlib.sha3_256(t).digest(True), unhexlify('0acbf8df91e6a65dc56c56c43d65f31ff2a6a48d06fc66e78c7f3436faf3e74f'))
def test_create_transfer_with_payload(self):
# http://chain.nem.ninja/#/transfer/e90e98614c7598fbfa4db5411db1b331d157c2f86b558fb7c943d013ed9f71cb
m = _create_msg(NEM_NETWORK_MAINNET,
0,
0,
0,
'NBT3WHA2YXG2IR4PWKFFMO772JWOITTD2V4PECSB',
5175000000000)
t = serialize_transfer(m.transaction, m.transfer,
unhexlify('8d07f90fb4bbe7715fa327c926770166a11be2e494a970605f2e12557f66c9b9'),
bytearray('Good luck!'))
self.assertEqual(hashlib.sha3_256(t).digest(True), unhexlify('e90e98614c7598fbfa4db5411db1b331d157c2f86b558fb7c943d013ed9f71cb'))
def test_create_transfer_with_encrypted_payload(self):
# http://chain.nem.ninja/#/transfer/40e89160e6f83d37f7c82defc0afe2c1605ae8c919134570a51dd27ea1bb516c
m = _create_msg(NEM_NETWORK_MAINNET,
77229,
30000000,
80829,
'NALICEPFLZQRZGPRIJTMJOCPWDNECXTNNG7QLSG3',
30000000)
t = serialize_transfer(m.transaction, m.transfer,
unhexlify('f85ab43dad059b9d2331ddacc384ad925d3467f03207182e01296bacfb242d01'),
unhexlify('4d9dcf9186967d30be93d6d5404ded22812dbbae7c3f0de501bcd7228cba45bded13000eec7b4c6215fc4d3588168c9218167cec98e6977359153a4132e050f594548e61e0dc61c153f0f53c5e65c595239c9eb7c4e7d48e0f4bb8b1dd2f5ddc'),
True)
self.assertEqual(hashlib.sha3_256(t).digest(True), unhexlify('40e89160e6f83d37f7c82defc0afe2c1605ae8c919134570a51dd27ea1bb516c'))
def test_create_transfer_with_mosaic(self):
# http://bob.nem.ninja:8765/#/transfer/3409d9ece28d6296d6d5e220a7e3cb8641a3fb235ffcbd20c95da64f003ace6c
m = _create_msg(NEM_NETWORK_TESTNET,
14072100,
194000000,
14075700,
'TBLOODPLWOWMZ2TARX4RFPOSOWLULHXMROBN2WXI',
3000000,
2)
t = serialize_transfer(m.transaction, m.transfer,
unhexlify('994793ba1c789fa9bdea918afc9b06e2d0309beb1081ac5b6952991e4defd324'),
bytearray('sending you 3 pairs of paddles\n'),
False)
self.assertEqual(t, unhexlify('010100000200009824b9d60020000000994793ba1c789fa9bdea918afc9b06e2d0309beb1081ac5b6952991e4defd3248034900b0000000034c7d6002800000054424c4f4f44504c574f574d5a3254415258345246504f534f574c554c48584d524f424e32575849c0c62d000000000027000000010000001f00000073656e64696e6720796f752033207061697273206f6620706164646c65730a02000000'))
serialize_mosaic(t, 'gimre.games.pong', 'paddles', 2)
serialize_mosaic(t, 'nem', 'xem', 44000000)
self.assertEqual(hashlib.sha3_256(t).digest(True), unhexlify('3409d9ece28d6296d6d5e220a7e3cb8641a3fb235ffcbd20c95da64f003ace6c'))
# http://chain.nem.ninja/#/transfer/882dca18dcbe075e15e0ec5a1d7e6ccd69cc0f1309ffd3fde227bfbc107b3f6e
m = _create_msg(NEM_NETWORK_MAINNET,
26730750,
179500000,
26734350,
'NBE223WPKEBHQPCYUC4U4CDUQCRRFMPZLOQLB5OP',
1000000,
1)
t = serialize_transfer(m.transaction, m.transfer,
unhexlify('f85ab43dad059b9d2331ddacc384ad925d3467f03207182e01296bacfb242d01'),
bytearray('enjoy! :)'),
False)
serialize_mosaic(t, 'imre.g', 'tokens', 1)
self.assertEqual(hashlib.sha3_256(t).digest(True), unhexlify('882dca18dcbe075e15e0ec5a1d7e6ccd69cc0f1309ffd3fde227bfbc107b3f6e'))
def _create_msg(network: int, timestamp: int, fee: int, deadline: int,
recipient: str, amount: int, mosaics: int = 0):
m = NEMSignTx()
m.transaction = NEMTransactionCommon()
m.transaction.network = network
m.transaction.timestamp = timestamp
m.transaction.fee = fee
m.transaction.deadline = deadline
m.transfer = NEMTransfer()
m.transfer.recipient = recipient
m.transfer.amount = amount
m.transfer.mosaics = list()
for i in range(mosaics):
m.transfer.mosaics.append(NEMMosaic())
return m
if __name__ == '__main__':
unittest.main()

View File

@ -0,0 +1,46 @@
#!/usr/bin/env python3
import json
def format_str(value):
return '"' + value + '"'
def format_primitive(value):
if isinstance(value, int):
return value
elif isinstance(value, str):
return format_str(value)
elif isinstance(value, list):
return value
else:
raise TypeError
fields = [
'name',
'ticker',
'namespace',
'mosaic',
'divisibility',
'levy',
'fee',
'levy_namespace',
'levy_mosaic',
'networks',
]
mosaics = json.load(open('../../vendor/trezor-common/defs/nem/nem_mosaics.json', 'r'))
print('# generated using gen_nem_mosaics.py from trezor-common nem_mosaics.json - do not edit directly!')
print('')
print('mosaics = [')
for m in mosaics:
print(' {')
for name in fields:
if name in m:
print(' %s: %s,' % (format_str(name), format_primitive(m[name])))
# else:
# print(' %s: None,' % format_str(name))
print(' },')
print(']')