1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2024-12-23 06:48:16 +00:00

Merge pull request #293 from ph4r05/xmr-total-2

Monero support added
This commit is contained in:
Tomas Susanka 2018-10-25 15:23:22 +02:00 committed by GitHub
commit b4ca93e365
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
121 changed files with 9889 additions and 761 deletions

View File

@ -30,14 +30,22 @@ addons:
- python3.6-dev
- python3.6-venv
cache:
directories:
- $HOME/libsodium
before_install:
- python3.6 -m ensurepip --user
- python3.6 -m pip install --user pipenv
- python3.6 -m pip install --user "pipenv<2018.10"
install:
- curl -LO "https://github.com/google/protobuf/releases/download/v${PROTOBUF_VERSION}/protoc-${PROTOBUF_VERSION}-linux-x86_64.zip"
- unzip "protoc-${PROTOBUF_VERSION}-linux-x86_64.zip" -d protoc
- export PATH="$(pwd)/protoc/bin:$PATH"
- ./travis-install-libsodium.sh
- export PKG_CONFIG_PATH=$HOME/libsodium/lib/pkgconfig:$PKG_CONFIG_PATH
- export LD_LIBRARY_PATH=$HOME/libsodium/lib:$LD_LIBRARY_PATH
- pipenv run pip install pip==18.0
- pipenv install
before_script:
@ -59,6 +67,7 @@ script:
- test "$GOAL" != "unix" || pipenv run make build_unix_noui
- test "$GOAL" != "unix" || pipenv run make test
- test "$GOAL" != "unix" || test "$TREZOR_MODEL" = "1" || pipenv run make test_emu
- test "$GOAL" != "unix" || test "$TREZOR_MODEL" = "1" || pipenv run make test_emu_monero
notifications:
webhooks:

View File

@ -65,6 +65,9 @@ test: ## run unit tests
test_emu: ## run selected device tests from python-trezor
cd tests ; ./run_tests_device_emu.sh $(TESTOPTS)
test_emu_monero: ## run selected monero device tests from monero-agent
cd tests ; ./run_tests_device_emu_monero.sh $(TESTOPTS)
pylint: ## run pylint on application sources and tests
pylint -E $(shell find src tests -name *.py)

View File

@ -25,5 +25,9 @@ termcolor = ">=0.1.2"
Pillow = ">=5.2.0"
Mako = ">=1.0.7"
# monero
monero_agent = {version = ">=1.7.1", extras = ["tcry", "dev"]}
py_trezor_crypto_ph4 = {version = ">=0.1.1"}
[pipenv]
allow_prereleases = true

View File

@ -27,6 +27,7 @@ CPPDEFINES_MOD += [
'RAND_PLATFORM_INDEPENDENT',
('USE_KECCAK', '1'),
('USE_ETHEREUM', '1'),
('USE_MONERO', '1'),
('USE_CARDANO', '1'),
('USE_NEM', '1'),
]
@ -63,6 +64,9 @@ SOURCE_MOD += [
'vendor/trezor-crypto/ed25519-donna/ed25519-keccak.c',
'vendor/trezor-crypto/ed25519-donna/ed25519-sha3.c',
'vendor/trezor-crypto/ed25519-donna/modm-donna-32bit.c',
'vendor/trezor-crypto/monero/base58.c',
'vendor/trezor-crypto/monero/serialize.c',
'vendor/trezor-crypto/monero/xmr.c',
'vendor/trezor-crypto/groestl.c',
'vendor/trezor-crypto/hasher.c',
'vendor/trezor-crypto/hmac.c',

View File

@ -25,6 +25,7 @@ CPPDEFINES_MOD += [
'AES_192',
('USE_KECCAK', '1'),
('USE_ETHEREUM', '1'),
('USE_MONERO', '1'),
('USE_CARDANO', '1'),
('USE_NEM', '1'),
]
@ -60,6 +61,9 @@ SOURCE_MOD += [
'vendor/trezor-crypto/ed25519-donna/ed25519-keccak.c',
'vendor/trezor-crypto/ed25519-donna/ed25519-sha3.c',
'vendor/trezor-crypto/ed25519-donna/modm-donna-32bit.c',
'vendor/trezor-crypto/monero/base58.c',
'vendor/trezor-crypto/monero/serialize.c',
'vendor/trezor-crypto/monero/xmr.c',
'vendor/trezor-crypto/groestl.c',
'vendor/trezor-crypto/hasher.c',
'vendor/trezor-crypto/hmac.c',

View File

@ -30,6 +30,8 @@ enum AESMode {
CTR = 0x04,
};
/// package: trezorcrypto.__init__
/// class AES:
/// '''
/// AES context.

View File

@ -27,6 +27,8 @@
#include "memzero.h"
#include "nem.h"
/// package: trezorcrypto.bip32
/// class HDNode:
/// '''
/// BIP0032 HD node structure.

View File

@ -21,6 +21,8 @@
#include "bip39.h"
/// package: trezorcrypto.bip39
/// def find_word(prefix: str) -> Optional[str]:
/// '''
/// Return the first word from the wordlist starting with prefix.

View File

@ -22,6 +22,8 @@
#include "blake256.h"
#include "memzero.h"
/// package: trezorcrypto.__init__
/// class Blake256:
/// '''
/// Blake256 context.

View File

@ -24,6 +24,8 @@
#include "blake2b.h"
#include "memzero.h"
/// package: trezorcrypto.__init__
/// class Blake2b:
/// '''
/// Blake2b context.

View File

@ -24,6 +24,8 @@
#include "blake2s.h"
#include "memzero.h"
/// package: trezorcrypto.__init__
/// class Blake2s:
/// '''
/// Blake2s context.

View File

@ -22,6 +22,8 @@
#include "chacha20poly1305/rfc7539.h"
#include "memzero.h"
/// package: trezorcrypto.__init__
/// class ChaCha20Poly1305:
/// '''
/// ChaCha20Poly1305 context.

View File

@ -23,6 +23,8 @@
#include "rand.h"
/// package: trezorcrypto.curve25519
/// def generate_secret() -> bytes:
/// '''
/// Generate secret key.

View File

@ -24,6 +24,8 @@
#include "rand.h"
/// package: trezorcrypto.ed25519
/// def generate_secret() -> bytes:
/// '''
/// Generate secret key.

View File

@ -25,6 +25,8 @@
#define GROESTL512_DIGEST_LENGTH 64
#define GROESTL512_BLOCK_LENGTH 128
/// package: trezorcrypto.__init__
/// class Groestl512:
/// '''
/// GROESTL512 context.

File diff suppressed because it is too large Load Diff

View File

@ -23,6 +23,8 @@
#include "nem.h"
/// package: trezorcrypto.nem
/// def validate_address(address: str, network: int) -> bool:
/// '''
/// Validate a NEM address

View File

@ -22,6 +22,8 @@
#include "ecdsa.h"
#include "nist256p1.h"
/// package: trezorcrypto.nist256p1
/// def generate_secret() -> bytes:
/// '''
/// Generate secret key.

View File

@ -25,6 +25,8 @@
#define PRF_HMAC_SHA256 256
#define PRF_HMAC_SHA512 512
/// package: trezorcrypto.__init__
/// class Pbkdf2:
/// '''
/// PBKDF2 context.

View File

@ -23,6 +23,8 @@
#include "rand.h"
/// package: trezorcrypto.random
/// def uniform(n: int) -> int:
/// '''
/// Compute uniform random number from interval 0 ... n - 1.

View File

@ -21,6 +21,8 @@
#include "rfc6979.h"
/// package: trezorcrypto.__init__
/// class Rfc6979:
/// '''
/// RFC6979 context.

View File

@ -22,6 +22,8 @@
#include "ripemd160.h"
#include "memzero.h"
/// package: trezorcrypto.__init__
/// class Ripemd160:
/// '''
/// RIPEMD160 context.

View File

@ -22,6 +22,8 @@
#include "ecdsa.h"
#include "secp256k1.h"
/// package: trezorcrypto.secp256k1
/// def generate_secret() -> bytes:
/// '''
/// Generate secret key.

View File

@ -22,6 +22,8 @@
#include "sha2.h"
#include "memzero.h"
/// package: trezorcrypto.__init__
/// class Sha1:
/// '''
/// SHA1 context.

View File

@ -22,6 +22,8 @@
#include "sha2.h"
#include "memzero.h"
/// package: trezorcrypto.__init__
/// class Sha256:
/// '''
/// SHA256 context.

View File

@ -22,6 +22,8 @@
#include "sha3.h"
#include "memzero.h"
/// package: trezorcrypto.__init__
/// class Sha3_256:
/// '''
/// SHA3_256 context.

View File

@ -22,6 +22,8 @@
#include "sha3.h"
#include "memzero.h"
/// package: trezorcrypto.__init__
/// class Sha3_512:
/// '''
/// SHA3_512 context.

View File

@ -22,6 +22,8 @@
#include "sha2.h"
#include "memzero.h"
/// package: trezorcrypto.__init__
/// class Sha512:
/// '''
/// SHA512 context.

View File

@ -48,6 +48,7 @@
#include "modtrezorcrypto-sha512.h"
#include "modtrezorcrypto-sha3-256.h"
#include "modtrezorcrypto-sha3-512.h"
#include "modtrezorcrypto-monero.h"
STATIC const mp_rom_map_elem_t mp_module_trezorcrypto_globals_table[] = {
{ MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_trezorcrypto) },
@ -61,6 +62,7 @@ STATIC const mp_rom_map_elem_t mp_module_trezorcrypto_globals_table[] = {
{ MP_ROM_QSTR(MP_QSTR_crc), MP_ROM_PTR(&mod_trezorcrypto_crc_module) },
{ 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_monero), MP_ROM_PTR(&mod_trezorcrypto_monero_module) },
{ MP_ROM_QSTR(MP_QSTR_nist256p1), MP_ROM_PTR(&mod_trezorcrypto_nist256p1_module) },
{ MP_ROM_QSTR(MP_QSTR_groestl512), MP_ROM_PTR(&mod_trezorcrypto_Groestl512_type) },
{ MP_ROM_QSTR(MP_QSTR_nem), MP_ROM_PTR(&mod_trezorcrypto_nem_module) },

View File

@ -1,586 +0,0 @@
from typing import *
# extmod/modtrezorcrypto/modtrezorcrypto-aes.h
class AES:
'''
AES context.
'''
def __init__(self, mode: int, key: bytes, iv: bytes = None) -> None:
'''
Initialize AES context.
'''
def update(self, data: bytes) -> bytes:
'''
Update AES context with data.
'''
# extmod/modtrezorcrypto/modtrezorcrypto-bip32.h
class HDNode:
'''
BIP0032 HD node structure.
'''
def __init__(self,
depth: int,
fingerprint: int,
child_num: int,
chain_code: bytes,
private_key: bytes = None,
public_key: bytes = None,
curve_name: str = None) -> None:
'''
'''
def derive(self, index: int, public: bool=False) -> None:
'''
Derive a BIP0032 child node in place.
'''
def derive_path(self, path: List[int]) -> None:
'''
Go through a list of indexes and iteratively derive a child node in place.
'''
def serialize_public(self, version: int) -> str:
'''
Serialize the public info from HD node to base58 string.
'''
def serialize_private(self, version: int) -> str:
'''
Serialize the private info HD node to base58 string.
'''
def clone(self) -> HDNode:
'''
Returns a copy of the HD node.
'''
def depth(self) -> int:
'''
Returns a depth of the HD node.
'''
def fingerprint(self) -> int:
'''
Returns a fingerprint of the HD node (hash of the parent public key).
'''
def child_num(self) -> int:
'''
Returns a child index of the HD node.
'''
def chain_code(self) -> bytes:
'''
Returns a chain code of the HD node.
'''
def private_key(self) -> bytes:
'''
Returns a private key of the HD node.
'''
def public_key(self) -> bytes:
'''
Returns a public key of the HD node.
'''
def address(self, version: int) -> str:
'''
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.
'''
def deserialize(self, value: str, version_public: int, version_private: int) -> HDNode:
'''
Construct a BIP0032 HD node from a base58-serialized value.
'''
def from_seed(seed: bytes, curve_name: str) -> HDNode:
'''
Construct a BIP0032 HD node from a BIP0039 seed value.
'''
# extmod/modtrezorcrypto/modtrezorcrypto-bip39.h
def find_word(prefix: str) -> Optional[str]:
'''
Return the first word from the wordlist starting with prefix.
'''
# extmod/modtrezorcrypto/modtrezorcrypto-bip39.h
def complete_word(prefix: str) -> int:
'''
Return possible 1-letter suffixes for given word prefix.
Result is a bitmask, with 'a' on the lowest bit, 'b' on the second lowest, etc.
'''
# extmod/modtrezorcrypto/modtrezorcrypto-bip39.h
def generate(strength: int) -> str:
'''
Generate a mnemonic of given strength (128, 160, 192, 224 and 256 bits).
'''
# extmod/modtrezorcrypto/modtrezorcrypto-bip39.h
def from_data(data: bytes) -> str:
'''
Generate a mnemonic from given data (of 16, 20, 24, 28 and 32 bytes).
'''
# extmod/modtrezorcrypto/modtrezorcrypto-bip39.h
def check(mnemonic: str) -> bool:
'''
Check whether given mnemonic is valid.
'''
# extmod/modtrezorcrypto/modtrezorcrypto-bip39.h
def seed(mnemonic: str, passphrase: str) -> bytes:
'''
Generate seed from mnemonic and passphrase.
'''
# extmod/modtrezorcrypto/modtrezorcrypto-blake256.h
class Blake256:
'''
Blake256 context.
'''
def __init__(self, data: bytes = None) -> None:
'''
Creates a hash context object.
'''
def update(self, data: bytes) -> None:
'''
Update the hash context with hashed data.
'''
def digest(self) -> bytes:
'''
Returns the digest of hashed data.
'''
# extmod/modtrezorcrypto/modtrezorcrypto-blake2b.h
class Blake2b:
'''
Blake2b context.
'''
def __init__(self, data: bytes = None, key: bytes = None) -> None:
'''
Creates a hash context object.
'''
def update(self, data: bytes) -> None:
'''
Update the hash context with hashed data.
'''
def digest(self) -> bytes:
'''
Returns the digest of hashed data.
'''
# extmod/modtrezorcrypto/modtrezorcrypto-blake2s.h
class Blake2s:
'''
Blake2s context.
'''
def __init__(self, data: bytes = None, key: bytes = None) -> None:
'''
Creates a hash context object.
'''
def update(self, data: bytes) -> None:
'''
Update the hash context with hashed data.
'''
def digest(self) -> bytes:
'''
Returns the digest of hashed data.
'''
# extmod/modtrezorcrypto/modtrezorcrypto-chacha20poly1305.h
class ChaCha20Poly1305:
'''
ChaCha20Poly1305 context.
'''
def __init__(self, key: bytes, nonce: bytes) -> None:
'''
Initialize the ChaCha20 + Poly1305 context for encryption or decryption
using a 32 byte key and 12 byte nonce as in the RFC 7539 style.
'''
def encrypt(self, data: bytes) -> bytes:
'''
Encrypt data (length of data must be divisible by 64 except for the final value).
'''
def decrypt(self, data: bytes) -> bytes:
'''
Decrypt data (length of data must be divisible by 64 except for the final value).
'''
def auth(self, data: bytes) -> None:
'''
Include authenticated data in the Poly1305 MAC using the RFC 7539
style with 16 byte padding. This must only be called once and prior
to encryption or decryption.
'''
def finish(self) -> bytes:
'''
Compute RFC 7539-style Poly1305 MAC.
'''
# extmod/modtrezorcrypto/modtrezorcrypto-curve25519.h
def generate_secret() -> bytes:
'''
Generate secret key.
'''
# extmod/modtrezorcrypto/modtrezorcrypto-curve25519.h
def publickey(secret_key: bytes) -> bytes:
'''
Computes public key from secret key.
'''
# extmod/modtrezorcrypto/modtrezorcrypto-curve25519.h
def multiply(secret_key: bytes, public_key: bytes) -> bytes:
'''
Multiplies point defined by public_key with scalar defined by secret_key.
Useful for ECDH.
'''
# extmod/modtrezorcrypto/modtrezorcrypto-ed25519.h
def generate_secret() -> bytes:
'''
Generate secret key.
'''
# extmod/modtrezorcrypto/modtrezorcrypto-ed25519.h
def publickey(secret_key: bytes) -> bytes:
'''
Computes public key from secret key.
'''
# extmod/modtrezorcrypto/modtrezorcrypto-ed25519.h
def sign(secret_key: bytes, message: bytes, hasher: str='') -> bytes:
'''
Uses secret key to produce the signature of message.
'''
# extmod/modtrezorcrypto/modtrezorcrypto-ed25519.h
def verify(public_key: bytes, signature: bytes, message: bytes) -> bool:
'''
Uses public key to verify the signature of the message.
Returns True on success.
'''
# extmod/modtrezorcrypto/modtrezorcrypto-ed25519.h
def cosi_combine_publickeys(public_keys: List[bytes]) -> bytes:
'''
Combines a list of public keys used in COSI cosigning scheme.
'''
# extmod/modtrezorcrypto/modtrezorcrypto-ed25519.h
def cosi_combine_signatures(R: bytes, signatures: List[bytes]) -> bytes:
'''
Combines a list of signatures used in COSI cosigning scheme.
'''
# extmod/modtrezorcrypto/modtrezorcrypto-ed25519.h
def cosi_sign(secret_key: bytes, message: bytes, nonce: bytes, sigR: bytes, combined_pubkey: bytes) -> bytes:
'''
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:
'''
Generate secret key.
'''
# extmod/modtrezorcrypto/modtrezorcrypto-nist256p1.h
def publickey(secret_key: bytes, compressed: bool = True) -> bytes:
'''
Computes public key from secret key.
'''
# extmod/modtrezorcrypto/modtrezorcrypto-nist256p1.h
def sign(secret_key: bytes, digest: bytes, compressed: bool = True) -> bytes:
'''
Uses secret key to produce the signature of the digest.
'''
# extmod/modtrezorcrypto/modtrezorcrypto-nist256p1.h
def verify(public_key: bytes, signature: bytes, digest: bytes) -> bool:
'''
Uses public key to verify the signature of the digest.
Returns True on success.
'''
# extmod/modtrezorcrypto/modtrezorcrypto-nist256p1.h
def verify_recover(signature: bytes, digest: bytes) -> bytes:
'''
Uses signature of the digest to verify the digest and recover the public key.
Returns public key on success, None on failure.
'''
# extmod/modtrezorcrypto/modtrezorcrypto-nist256p1.h
def multiply(secret_key: bytes, public_key: bytes) -> bytes:
'''
Multiplies point defined by public_key with scalar defined by secret_key.
Useful for ECDH.
'''
# extmod/modtrezorcrypto/modtrezorcrypto-pbkdf2.h
class Pbkdf2:
'''
PBKDF2 context.
'''
def __init__(self, prf: str, password: bytes, salt: bytes, iterations: int = None) -> None:
'''
Create a PBKDF2 context.
'''
def update(self, iterations: int) -> None:
'''
Update a PBKDF2 context.
'''
def key(self) -> bytes:
'''
Retrieve derived key.
'''
# extmod/modtrezorcrypto/modtrezorcrypto-random.h
def uniform(n: int) -> int:
'''
Compute uniform random number from interval 0 ... n - 1.
'''
# extmod/modtrezorcrypto/modtrezorcrypto-random.h
def bytes(len: int) -> bytes:
'''
Generate random bytes sequence of length len.
'''
# extmod/modtrezorcrypto/modtrezorcrypto-random.h
def shuffle(data: list) -> None:
'''
Shuffles items of given list (in-place).
'''
# extmod/modtrezorcrypto/modtrezorcrypto-rfc6979.h
class Rfc6979:
'''
RFC6979 context.
'''
def __init__(self, secret_key: bytes, hash: bytes) -> None:
'''
Initialize RFC6979 context from secret key and a hash.
'''
def next(self) -> bytes:
'''
Compute next 32-bytes of pseudorandom data.
'''
# extmod/modtrezorcrypto/modtrezorcrypto-ripemd160.h
class Ripemd160:
'''
RIPEMD160 context.
'''
def __init__(self, data: bytes = None) -> None:
'''
Creates a hash context object.
'''
def update(self, data: bytes) -> None:
'''
Update the hash context with hashed data.
'''
def digest(self) -> bytes:
'''
Returns the digest of hashed data.
'''
# extmod/modtrezorcrypto/modtrezorcrypto-secp256k1.h
def generate_secret() -> bytes:
'''
Generate secret key.
'''
# extmod/modtrezorcrypto/modtrezorcrypto-secp256k1.h
def publickey(secret_key: bytes, compressed: bool = True) -> bytes:
'''
Computes public key from secret key.
'''
# extmod/modtrezorcrypto/modtrezorcrypto-secp256k1.h
def sign(secret_key: bytes, digest: bytes, compressed: bool = True) -> bytes:
'''
Uses secret key to produce the signature of the digest.
'''
# extmod/modtrezorcrypto/modtrezorcrypto-secp256k1.h
def verify(public_key: bytes, signature: bytes, digest: bytes) -> bool:
'''
Uses public key to verify the signature of the digest.
Returns True on success.
'''
# extmod/modtrezorcrypto/modtrezorcrypto-secp256k1.h
def verify_recover(signature: bytes, digest: bytes) -> bytes:
'''
Uses signature of the digest to verify the digest and recover the public key.
Returns public key on success, None on failure.
'''
# extmod/modtrezorcrypto/modtrezorcrypto-secp256k1.h
def multiply(secret_key: bytes, public_key: bytes) -> bytes:
'''
Multiplies point defined by public_key with scalar defined by secret_key.
Useful for ECDH.
'''
# extmod/modtrezorcrypto/modtrezorcrypto-sha1.h
class Sha1:
'''
SHA1 context.
'''
def __init__(self, data: bytes = None) -> None:
'''
Creates a hash context object.
'''
def update(self, data: bytes) -> None:
'''
Update the hash context with hashed data.
'''
def digest(self) -> bytes:
'''
Returns the digest of hashed data.
'''
# extmod/modtrezorcrypto/modtrezorcrypto-sha256.h
class Sha256:
'''
SHA256 context.
'''
def __init__(self, data: bytes = None) -> None:
'''
Creates a hash context object.
'''
def update(self, data: bytes) -> None:
'''
Update the hash context with hashed data.
'''
def digest(self) -> bytes:
'''
Returns the digest of hashed data.
'''
# extmod/modtrezorcrypto/modtrezorcrypto-sha3-256.h
class Sha3_256:
'''
SHA3_256 context.
'''
def __init__(self, data: bytes = None) -> None:
'''
Creates a hash context object.
'''
def update(self, data: bytes) -> None:
'''
Update the hash context with hashed data.
'''
def digest(self, keccak: bool = False) -> bytes:
'''
Returns the digest of hashed data.
'''
# extmod/modtrezorcrypto/modtrezorcrypto-sha3-512.h
class Sha3_512:
'''
SHA3_512 context.
'''
def __init__(self, data: bytes = None) -> None:
'''
Creates a hash context object.
'''
def update(self, data: bytes) -> None:
'''
Update the hash context with hashed data.
'''
def digest(self, keccak: bool = False) -> bytes:
'''
Returns the digest of hashed data.
'''
# extmod/modtrezorcrypto/modtrezorcrypto-sha512.h
class Sha512:
'''
SHA512 context.
'''
def __init__(self, data: bytes = None) -> None:
'''
Creates a hash context object.
'''
def hash(self, data: bytes) -> None:
'''
Update the hash context with hashed data.
'''
def digest(self) -> bytes:
'''
Returns the digest of hashed data.
'''

View File

@ -0,0 +1,312 @@
# extmod/modtrezorcrypto/modtrezorcrypto-aes.h
class AES:
'''
AES context.
'''
def __init__(self, mode: int, key: bytes, iv: bytes = None) -> None:
'''
Initialize AES context.
'''
def encrypt(self, data: bytes) -> bytes:
'''
Encrypt data and update AES context.
'''
def decrypt(self, data: bytes) -> bytes:
'''
Decrypt data and update AES context.
'''
# extmod/modtrezorcrypto/modtrezorcrypto-blake256.h
class Blake256:
'''
Blake256 context.
'''
def __init__(self, data: bytes = None) -> None:
'''
Creates a hash context object.
'''
def update(self, data: bytes) -> None:
'''
Update the hash context with hashed data.
'''
def digest(self) -> bytes:
'''
Returns the digest of hashed data.
'''
# extmod/modtrezorcrypto/modtrezorcrypto-blake2b.h
class Blake2b:
'''
Blake2b context.
'''
def __init__(self, data: bytes = None, outlen: int = Blake2b.digest_size, personal: bytes = None) -> None:
'''
Creates a hash context object.
'''
def update(self, data: bytes) -> None:
'''
Update the hash context with hashed data.
'''
def digest(self) -> bytes:
'''
Returns the digest of hashed data.
'''
# extmod/modtrezorcrypto/modtrezorcrypto-blake2s.h
class Blake2s:
'''
Blake2s context.
'''
def __init__(self, data: bytes = None, outlen: int = Blake2s.digest_size, key: bytes = None, personal: bytes = None) -> None:
'''
Creates a hash context object.
'''
def update(self, data: bytes) -> None:
'''
Update the hash context with hashed data.
'''
def digest(self) -> bytes:
'''
Returns the digest of hashed data.
'''
# extmod/modtrezorcrypto/modtrezorcrypto-chacha20poly1305.h
class ChaCha20Poly1305:
'''
ChaCha20Poly1305 context.
'''
def __init__(self, key: bytes, nonce: bytes) -> None:
'''
Initialize the ChaCha20 + Poly1305 context for encryption or decryption
using a 32 byte key and 12 byte nonce as in the RFC 7539 style.
'''
def encrypt(self, data: bytes) -> bytes:
'''
Encrypt data (length of data must be divisible by 64 except for the final value).
'''
def decrypt(self, data: bytes) -> bytes:
'''
Decrypt data (length of data must be divisible by 64 except for the final value).
'''
def auth(self, data: bytes) -> None:
'''
Include authenticated data in the Poly1305 MAC using the RFC 7539
style with 16 byte padding. This must only be called once and prior
to encryption or decryption.
'''
def finish(self) -> bytes:
'''
Compute RFC 7539-style Poly1305 MAC.
'''
# extmod/modtrezorcrypto/modtrezorcrypto-groestl.h
class Groestl512:
'''
GROESTL512 context.
'''
def __init__(self, data: bytes = None) -> None:
'''
Creates a hash context object.
'''
def update(self, data: bytes) -> None:
'''
Update the hash context with hashed data.
'''
def digest(self) -> bytes:
'''
Returns the digest of hashed data.
'''
# extmod/modtrezorcrypto/modtrezorcrypto-pbkdf2.h
class Pbkdf2:
'''
PBKDF2 context.
'''
def __init__(self, prf: int, password: bytes, salt: bytes, iterations: int = None, blocknr: int = 1) -> None:
'''
Create a PBKDF2 context.
'''
def update(self, iterations: int) -> None:
'''
Update a PBKDF2 context.
'''
def key(self) -> bytes:
'''
Retrieve derived key.
'''
# extmod/modtrezorcrypto/modtrezorcrypto-rfc6979.h
class Rfc6979:
'''
RFC6979 context.
'''
def __init__(self, secret_key: bytes, hash: bytes) -> None:
'''
Initialize RFC6979 context from secret key and a hash.
'''
def next(self) -> bytes:
'''
Compute next 32-bytes of pseudorandom data.
'''
# extmod/modtrezorcrypto/modtrezorcrypto-ripemd160.h
class Ripemd160:
'''
RIPEMD160 context.
'''
def __init__(self, data: bytes = None) -> None:
'''
Creates a hash context object.
'''
def update(self, data: bytes) -> None:
'''
Update the hash context with hashed data.
'''
def digest(self) -> bytes:
'''
Returns the digest of hashed data.
'''
# extmod/modtrezorcrypto/modtrezorcrypto-sha1.h
class Sha1:
'''
SHA1 context.
'''
def __init__(self, data: bytes = None) -> None:
'''
Creates a hash context object.
'''
def update(self, data: bytes) -> None:
'''
Update the hash context with hashed data.
'''
def digest(self) -> bytes:
'''
Returns the digest of hashed data.
'''
# extmod/modtrezorcrypto/modtrezorcrypto-sha256.h
class Sha256:
'''
SHA256 context.
'''
def __init__(self, data: bytes = None) -> None:
'''
Creates a hash context object.
'''
def update(self, data: bytes) -> None:
'''
Update the hash context with hashed data.
'''
def digest(self) -> bytes:
'''
Returns the digest of hashed data.
'''
# extmod/modtrezorcrypto/modtrezorcrypto-sha3-256.h
class Sha3_256:
'''
SHA3_256 context.
'''
def __init__(self, data: bytes = None, keccak = False) -> None:
'''
Creates a hash context object.
'''
def update(self, data: bytes) -> None:
'''
Update the hash context with hashed data.
'''
def digest(self) -> bytes:
'''
Returns the digest of hashed data.
'''
def copy(self) -> sha3:
'''
Returns the copy of the digest object with the current state
'''
# extmod/modtrezorcrypto/modtrezorcrypto-sha3-512.h
class Sha3_512:
'''
SHA3_512 context.
'''
def __init__(self, data: bytes = None, keccak = False) -> None:
'''
Creates a hash context object.
'''
def update(self, data: bytes) -> None:
'''
Update the hash context with hashed data.
'''
def digest(self) -> bytes:
'''
Returns the digest of hashed data.
'''
def copy(self) -> sha3:
'''
Returns the copy of the digest object with the current state
'''
# extmod/modtrezorcrypto/modtrezorcrypto-sha512.h
class Sha512:
'''
SHA512 context.
'''
def __init__(self, data: bytes = None) -> None:
'''
Creates a hash context object.
'''
def hash(self, data: bytes) -> None:
'''
Update the hash context with hashed data.
'''
def digest(self) -> bytes:
'''
Returns the digest of hashed data.
'''

View File

@ -0,0 +1,118 @@
from typing import *
# extmod/modtrezorcrypto/modtrezorcrypto-bip32.h
class HDNode:
'''
BIP0032 HD node structure.
'''
def __init__(self,
depth: int,
fingerprint: int,
child_num: int,
chain_code: bytes,
private_key: bytes = None,
public_key: bytes = None,
curve_name: str = None) -> None:
'''
'''
def derive(self, index: int, public: bool=False) -> None:
'''
Derive a BIP0032 child node in place.
'''
def derive_cardano(self, index: int) -> None:
'''
Derive a BIP0032 child node in place using Cardano algorithm.
'''
def derive_path(self, path: List[int]) -> None:
'''
Go through a list of indexes and iteratively derive a child node in place.
'''
def serialize_public(self, version: int) -> str:
'''
Serialize the public info from HD node to base58 string.
'''
def serialize_private(self, version: int) -> str:
'''
Serialize the private info HD node to base58 string.
'''
def clone(self) -> HDNode:
'''
Returns a copy of the HD node.
'''
def depth(self) -> int:
'''
Returns a depth of the HD node.
'''
def fingerprint(self) -> int:
'''
Returns a fingerprint of the HD node (hash of the parent public key).
'''
def child_num(self) -> int:
'''
Returns a child index of the HD node.
'''
def chain_code(self) -> bytes:
'''
Returns a chain code of the HD node.
'''
def private_key(self) -> bytes:
'''
Returns a private key of the HD node.
'''
def private_key_ext(self) -> bytes:
'''
Returns a private key extension of the HD node.
'''
def public_key(self) -> bytes:
'''
Returns a public key of the HD node.
'''
def address(self, version: int) -> str:
'''
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.
'''
def deserialize(self, value: str, version_public: int, version_private: int) -> HDNode:
'''
Construct a BIP0032 HD node from a base58-serialized value.
'''
def from_seed(seed: bytes, curve_name: str) -> HDNode:
'''
Construct a BIP0032 HD node from a BIP0039 seed value.
'''
def from_mnemonic_cardano(mnemonic: str) -> bytes:
'''
Convert mnemonic to hdnode
'''

View File

@ -0,0 +1,38 @@
from typing import *
# extmod/modtrezorcrypto/modtrezorcrypto-bip39.h
def find_word(prefix: str) -> Optional[str]:
'''
Return the first word from the wordlist starting with prefix.
'''
# extmod/modtrezorcrypto/modtrezorcrypto-bip39.h
def complete_word(prefix: str) -> int:
'''
Return possible 1-letter suffixes for given word prefix.
Result is a bitmask, with 'a' on the lowest bit, 'b' on the second lowest, etc.
'''
# extmod/modtrezorcrypto/modtrezorcrypto-bip39.h
def generate(strength: int) -> str:
'''
Generate a mnemonic of given strength (128, 160, 192, 224 and 256 bits).
'''
# extmod/modtrezorcrypto/modtrezorcrypto-bip39.h
def from_data(data: bytes) -> str:
'''
Generate a mnemonic from given data (of 16, 20, 24, 28 and 32 bytes).
'''
# extmod/modtrezorcrypto/modtrezorcrypto-bip39.h
def check(mnemonic: str) -> bool:
'''
Check whether given mnemonic is valid.
'''
# extmod/modtrezorcrypto/modtrezorcrypto-bip39.h
def seed(mnemonic: str, passphrase: str) -> bytes:
'''
Generate seed from mnemonic and passphrase.
'''

View File

@ -0,0 +1,20 @@
from typing import *
# extmod/modtrezorcrypto/modtrezorcrypto-curve25519.h
def generate_secret() -> bytes:
'''
Generate secret key.
'''
# extmod/modtrezorcrypto/modtrezorcrypto-curve25519.h
def publickey(secret_key: bytes) -> bytes:
'''
Computes public key from secret key.
'''
# extmod/modtrezorcrypto/modtrezorcrypto-curve25519.h
def multiply(secret_key: bytes, public_key: bytes) -> bytes:
'''
Multiplies point defined by public_key with scalar defined by secret_key.
Useful for ECDH.
'''

View File

@ -0,0 +1,50 @@
from typing import *
# extmod/modtrezorcrypto/modtrezorcrypto-ed25519.h
def generate_secret() -> bytes:
'''
Generate secret key.
'''
# extmod/modtrezorcrypto/modtrezorcrypto-ed25519.h
def publickey(secret_key: bytes) -> bytes:
'''
Computes public key from secret key.
'''
# extmod/modtrezorcrypto/modtrezorcrypto-ed25519.h
def sign(secret_key: bytes, message: bytes, hasher: str='') -> bytes:
'''
Uses secret key to produce the signature of message.
'''
# extmod/modtrezorcrypto/modtrezorcrypto-ed25519.h
def sign_ext(secret_key: bytes, secret_extension: bytes, message: bytes) -> bytes:
'''
Uses secret key to produce the cardano signature of message.
'''
# extmod/modtrezorcrypto/modtrezorcrypto-ed25519.h
def verify(public_key: bytes, signature: bytes, message: bytes) -> bool:
'''
Uses public key to verify the signature of the message.
Returns True on success.
'''
# extmod/modtrezorcrypto/modtrezorcrypto-ed25519.h
def cosi_combine_publickeys(public_keys: List[bytes]) -> bytes:
'''
Combines a list of public keys used in COSI cosigning scheme.
'''
# extmod/modtrezorcrypto/modtrezorcrypto-ed25519.h
def cosi_combine_signatures(R: bytes, signatures: List[bytes]) -> bytes:
'''
Combines a list of signatures used in COSI cosigning scheme.
'''
# extmod/modtrezorcrypto/modtrezorcrypto-ed25519.h
def cosi_sign(secret_key: bytes, message: bytes, nonce: bytes, sigR: bytes, combined_pubkey: bytes) -> bytes:
'''
Produce signature of message using COSI cosigning scheme.
'''

View File

@ -0,0 +1,313 @@
from typing import *
# extmod/modtrezorcrypto/modtrezorcrypto-monero.h
class Ge25519:
'''
EC point on ED25519
'''
def __init__(x: Optional[Union[Ge25519, bytes]] = None):
'''
Constructor
'''
# extmod/modtrezorcrypto/modtrezorcrypto-monero.h
class Sc25519:
'''
EC scalar on SC25519
'''
def __init__(x: Optional[Union[Sc25519, bytes, int]] = None):
'''
Constructor
'''
# extmod/modtrezorcrypto/modtrezorcrypto-monero.h
class Hasher:
'''
XMR hasher
'''
def __init__(x: Optional[bytes] = None):
'''
Constructor
'''
def update(buffer: bytes):
'''
Update hasher
'''
def digest() -> bytes:
'''
Computes digest
'''
def copy() -> Hasher:
'''
Creates copy of the hasher, preserving the state
'''
# extmod/modtrezorcrypto/modtrezorcrypto-monero.h
def init256_modm(dst: Optional[Sc25519], val: Union[int, bytes, Sc25519]) -> Sc25519:
'''
Initializes Sc25519 scalar
'''
# extmod/modtrezorcrypto/modtrezorcrypto-monero.h
def check256_modm(val: Sc25519):
'''
Throws exception if scalar is invalid
'''
# extmod/modtrezorcrypto/modtrezorcrypto-monero.h
def iszero256_modm(val: Sc25519) -> bool:
'''
Returns False if the scalar is zero
'''
# extmod/modtrezorcrypto/modtrezorcrypto-monero.h
def eq256_modm(a: Sc25519, b: Sc25519) -> int:
'''
Compares scalars, returns 1 on the same value
'''
# extmod/modtrezorcrypto/modtrezorcrypto-monero.h
def get256_modm(a: Sc25519) -> int:
'''
Extracts 64bit integer from the scalar. Raises exception if scalar is bigger than 2^64
'''
# extmod/modtrezorcrypto/modtrezorcrypto-monero.h
def add256_modm(r: Optional[Sc25519], a: Sc25519, b: Sc25519) -> Sc25519:
'''
Scalar addition
'''
# extmod/modtrezorcrypto/modtrezorcrypto-monero.h
def sub256_modm(r: Optional[Sc25519], a: Sc25519, b: Sc25519) -> Sc25519:
'''
Scalar subtraction
'''
# extmod/modtrezorcrypto/modtrezorcrypto-monero.h
def mul256_modm(r: Optional[Sc25519], a: Sc25519, b: Sc25519) -> Sc25519:
'''
Scalar multiplication
'''
# extmod/modtrezorcrypto/modtrezorcrypto-monero.h
def mulsub256_modm(r: Optional[Sc25519], a: Sc25519, b: Sc25519, c: Sc25519) -> Sc25519:
'''
c - a*b
'''
# extmod/modtrezorcrypto/modtrezorcrypto-monero.h
def muladd256_modm(r: Optional[Sc25519], a: Sc25519, b: Sc25519, c: Sc25519) -> Sc25519:
'''
c + a*b
'''
# extmod/modtrezorcrypto/modtrezorcrypto-monero.h
def inv256_modm(r: Optional[Sc25519], a: Sc25519) -> Sc25519:
'''
Scalar modular inversion
'''
# extmod/modtrezorcrypto/modtrezorcrypto-monero.h
def pack256_modm(r: Optional[bytes], a: Sc25519, offset: Optional[int] = 0) -> bytes:
'''
Scalar compression
'''
# extmod/modtrezorcrypto/modtrezorcrypto-monero.h
def unpack256_modm(r: Optional[Sc25519], a: bytes, offset: int = 0) -> Sc25519:
'''
Scalar decompression
'''
# extmod/modtrezorcrypto/modtrezorcrypto-monero.h
def unpack256_modm_noreduce(r: Optional[Sc25519], a: bytes, offset: int = 0) -> Sc25519:
'''
Scalar decompression, raw, without modular reduction
'''
# extmod/modtrezorcrypto/modtrezorcrypto-monero.h
def ge25519_set_neutral(r: Optional[Ge25519]) -> Ge25519:
'''
Sets neutral point
'''
# extmod/modtrezorcrypto/modtrezorcrypto-monero.h
def ge25519_set_xmr_h(r: Optional[Ge25519]) -> Ge25519:
'''
Sets H point
'''
# extmod/modtrezorcrypto/modtrezorcrypto-monero.h
def ge25519_check(r: Ge25519):
'''
Checks point, throws if not on curve
'''
# extmod/modtrezorcrypto/modtrezorcrypto-monero.h
def ge25519_eq(a: Ge25519, b: Ge25519) -> bool:
'''
Compares EC points
'''
# extmod/modtrezorcrypto/modtrezorcrypto-monero.h
def ge25519_add(r: Optional[Ge25519], a: Ge25519, b: Ge25519) -> Ge25519:
'''
Adds EC points
'''
# extmod/modtrezorcrypto/modtrezorcrypto-monero.h
def ge25519_sub(r: Optional[Ge25519], a: Ge25519, b: Ge25519) -> Ge25519:
'''
Subtracts EC points
'''
# extmod/modtrezorcrypto/modtrezorcrypto-monero.h
def ge25519_double(r: Optional[Ge25519], p: Ge25519) -> Ge25519:
'''
EC point doubling
'''
# extmod/modtrezorcrypto/modtrezorcrypto-monero.h
def ge25519_mul8(r: Optional[Ge25519], p: Ge25519) -> Ge25519:
'''
EC point * 8
'''
# extmod/modtrezorcrypto/modtrezorcrypto-monero.h
def ge25519_double_scalarmult_vartime(r: Optional[Ge25519], p1: Ge25519, s1: Sc25519, s2: Sc25519) -> Ge25519:
'''
s1 * G + s2 * p1
'''
# extmod/modtrezorcrypto/modtrezorcrypto-monero.h
def ge25519_double_scalarmult_vartime2(r: Optional[Ge25519], p1: Ge25519, s1: Sc25519, p2: Ge25519, s2: Sc25519) -> Ge25519:
'''
s1 * p1 + s2 * p2
'''
# extmod/modtrezorcrypto/modtrezorcrypto-monero.h
def ge25519_scalarmult_base(r: Optional[Ge25519], s: Union[Sc25519, int]) -> Ge25519:
'''
s * G
'''
# extmod/modtrezorcrypto/modtrezorcrypto-monero.h
def ge25519_scalarmult(r: Optional[Ge25519], p: Ge25519, s: Union[Sc25519, int]) -> Ge25519:
'''
s * p
'''
# extmod/modtrezorcrypto/modtrezorcrypto-monero.h
def ge25519_pack(r: bytes, p: Ge25519, offset: int = 0) -> bytes:
'''
Point compression
'''
# extmod/modtrezorcrypto/modtrezorcrypto-monero.h
def ge25519_unpack_vartime(r: Optional[Ge25519], buff: bytes, offset: int = 0) -> Ge25519:
'''
Point decompression
'''
# extmod/modtrezorcrypto/modtrezorcrypto-monero.h
def base58_addr_encode_check(tag: int, buff: bytes) -> bytes:
'''
Monero block base 58 encoding
'''
# extmod/modtrezorcrypto/modtrezorcrypto-monero.h
def base58_addr_decode_check(buff: bytes) -> Tuple[bytes, int]:
'''
Monero block base 58 decoding, returning (decoded, tag) or raising on error.
'''
# extmod/modtrezorcrypto/modtrezorcrypto-monero.h
def xmr_random_scalar(r: Optional[Sc25519] = None) -> Sc25519:
'''
Generates a random scalar
'''
# extmod/modtrezorcrypto/modtrezorcrypto-monero.h
def xmr_fast_hash(r: Optional[bytes], buff: bytes) -> bytes:
'''
XMR fast hash
'''
# extmod/modtrezorcrypto/modtrezorcrypto-monero.h
def xmr_hash_to_ec(r: Optional[Ge25519], buff: bytes) -> Ge25519:
'''
XMR hashing to EC point
'''
# extmod/modtrezorcrypto/modtrezorcrypto-monero.h
def xmr_hash_to_scalar(r: Optional[Sc25519], buff: bytes) -> Sc25519:
'''
XMR hashing to EC scalar
'''
# extmod/modtrezorcrypto/modtrezorcrypto-monero.h
def xmr_derivation_to_scalar(r: Optional[Sc25519], p: Ge25519, output_index: int) -> Sc25519:
'''
H_s(derivation || varint(output_index))
'''
# extmod/modtrezorcrypto/modtrezorcrypto-monero.h
def xmr_generate_key_derivation(r: Optional[Ge25519], A: Ge25519, b: Sc25519) -> Ge25519:
'''
8*(key2*key1)
'''
# extmod/modtrezorcrypto/modtrezorcrypto-monero.h
def xmr_derive_private_key(r: Optional[Sc25519], deriv: Ge25519, idx: int, base: Sc25519) -> Sc25519:
'''
base + H_s(derivation || varint(output_index))
'''
# extmod/modtrezorcrypto/modtrezorcrypto-monero.h
def xmr_derive_public_key(r: Optional[Ge25519], deriv: Ge25519, idx: int, base: Ge25519) -> Ge25519:
'''
H_s(derivation || varint(output_index))G + base
'''
# extmod/modtrezorcrypto/modtrezorcrypto-monero.h
def xmr_add_keys2(r: Optional[Ge25519], a: Sc25519, b: Sc25519, B: Ge25519) -> Ge25519:
'''
aG + bB, G is basepoint
'''
# extmod/modtrezorcrypto/modtrezorcrypto-monero.h
def xmr_add_keys2_vartime(r: Optional[Ge25519], a: Sc25519, b: Sc25519, B: Ge25519) -> Ge25519:
'''
aG + bB, G is basepoint
'''
# extmod/modtrezorcrypto/modtrezorcrypto-monero.h
def xmr_add_keys3(r: Optional[Ge25519], a: Sc25519, A: Ge25519, b: Sc25519, B: Ge25519) -> Ge25519:
'''
aA + bB
'''
# extmod/modtrezorcrypto/modtrezorcrypto-monero.h
def xmr_add_keys3_vartime(r: Optional[Ge25519], a: Sc25519, A: Ge25519, b: Sc25519, B: Ge25519) -> Ge25519:
'''
aA + bB
'''
# extmod/modtrezorcrypto/modtrezorcrypto-monero.h
def xmr_get_subaddress_secret_key(r: Optional[Sc25519], major: int, minor: int, m: Sc25519) -> Sc25519:
'''
Hs(SubAddr || a || index_major || index_minor)
'''
# extmod/modtrezorcrypto/modtrezorcrypto-monero.h
def xmr_gen_c(r: Optional[Ge25519], a: Sc25519, amount: int) -> Ge25519:
'''
aG + amount * H
'''
# extmod/modtrezorcrypto/modtrezorcrypto-monero.h
def ct_equals(a: bytes, b: bytes) -> bool:
'''
Constant time buffer comparison
'''

View File

@ -0,0 +1,13 @@
from typing import *
# 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
'''

View File

@ -0,0 +1,40 @@
from typing import *
# extmod/modtrezorcrypto/modtrezorcrypto-nist256p1.h
def generate_secret() -> bytes:
'''
Generate secret key.
'''
# extmod/modtrezorcrypto/modtrezorcrypto-nist256p1.h
def publickey(secret_key: bytes, compressed: bool = True) -> bytes:
'''
Computes public key from secret key.
'''
# extmod/modtrezorcrypto/modtrezorcrypto-nist256p1.h
def sign(secret_key: bytes, digest: bytes, compressed: bool = True) -> bytes:
'''
Uses secret key to produce the signature of the digest.
'''
# extmod/modtrezorcrypto/modtrezorcrypto-nist256p1.h
def verify(public_key: bytes, signature: bytes, digest: bytes) -> bool:
'''
Uses public key to verify the signature of the digest.
Returns True on success.
'''
# extmod/modtrezorcrypto/modtrezorcrypto-nist256p1.h
def verify_recover(signature: bytes, digest: bytes) -> bytes:
'''
Uses signature of the digest to verify the digest and recover the public key.
Returns public key on success, None on failure.
'''
# extmod/modtrezorcrypto/modtrezorcrypto-nist256p1.h
def multiply(secret_key: bytes, public_key: bytes) -> bytes:
'''
Multiplies point defined by public_key with scalar defined by secret_key.
Useful for ECDH.
'''

View File

@ -0,0 +1,19 @@
from typing import *
# extmod/modtrezorcrypto/modtrezorcrypto-random.h
def uniform(n: int) -> int:
'''
Compute uniform random number from interval 0 ... n - 1.
'''
# extmod/modtrezorcrypto/modtrezorcrypto-random.h
def bytes(len: int) -> bytes:
'''
Generate random bytes sequence of length len.
'''
# extmod/modtrezorcrypto/modtrezorcrypto-random.h
def shuffle(data: list) -> None:
'''
Shuffles items of given list (in-place).
'''

View File

@ -0,0 +1,40 @@
from typing import *
# extmod/modtrezorcrypto/modtrezorcrypto-secp256k1.h
def generate_secret() -> bytes:
'''
Generate secret key.
'''
# extmod/modtrezorcrypto/modtrezorcrypto-secp256k1.h
def publickey(secret_key: bytes, compressed: bool = True) -> bytes:
'''
Computes public key from secret key.
'''
# extmod/modtrezorcrypto/modtrezorcrypto-secp256k1.h
def sign(secret_key: bytes, digest: bytes, compressed: bool = True, ethereum_canonical: bool = False) -> bytes:
'''
Uses secret key to produce the signature of the digest.
'''
# extmod/modtrezorcrypto/modtrezorcrypto-secp256k1.h
def verify(public_key: bytes, signature: bytes, digest: bytes) -> bool:
'''
Uses public key to verify the signature of the digest.
Returns True on success.
'''
# extmod/modtrezorcrypto/modtrezorcrypto-secp256k1.h
def verify_recover(signature: bytes, digest: bytes) -> bytes:
'''
Uses signature of the digest to verify the digest and recover the public key.
Returns public key on success, None on failure.
'''
# extmod/modtrezorcrypto/modtrezorcrypto-secp256k1.h
def multiply(secret_key: bytes, public_key: bytes) -> bytes:
'''
Multiplies point defined by public_key with scalar defined by secret_key.
Useful for ECDH.
'''

View File

@ -138,7 +138,8 @@ class USB:
product: str='',
serial_number: str='',
interface: str='',
usb21_enabled: bool=True) -> None:
usb21_enabled: bool=True,
usb21_landing: bool=True) -> None:
'''
'''

View File

@ -51,27 +51,41 @@ class Display:
The icon needs to be in TREZOR Optimized Image Format (TOIF) - gray-scale mode.
'''
def loader(self, progress: int, yoffset: int, fgcolor: int, bgcolor: int, icon: bytes = None, iconfgcolor: int = None) -> None:
'''
Renders a rotating loader graphic.
Progress determines its position (0-1000), fgcolor is used as foreground color, bgcolor as background.
When icon and iconfgcolor are provided, an icon is drawn in the middle using the color specified in iconfgcolor.
Icon needs to be of exactly LOADER_ICON_SIZE x LOADER_ICON_SIZE pixels size.
'''
def print(self, text: str) -> None:
'''
Renders text using 5x8 bitmap font (using special text mode).
'''
def text(self, x: int, y: int, text: str, font: int, fgcolor: int, bgcolor: int, minwidth: int=None) -> None:
def text(self, x: int, y: int, text: str, font: int, fgcolor: int, bgcolor: int, minwidth: int=None) -> int:
'''
Renders left-aligned text at position (x,y) where x is left position and y is baseline.
Font font is used for rendering, fgcolor is used as foreground color, bgcolor as background.
Fills at least minwidth pixels with bgcolor.
Returns width of rendered text in pixels.
'''
def text_center(self, x: int, y: int, text: str, font: int, fgcolor: int, bgcolor: int, minwidth: int=None) -> None:
def text_center(self, x: int, y: int, text: str, font: int, fgcolor: int, bgcolor: int, minwidth: int=None) -> int:
'''
Renders text centered at position (x,y) where x is text center and y is baseline.
Font font is used for rendering, fgcolor is used as foreground color, bgcolor as background.
Fills at least minwidth pixels with bgcolor.
Returns width of rendered text in pixels.
'''
def text_right(self, x: int, y: int, text: str, font: int, fgcolor: int, bgcolor: int, minwidth: int=None) -> None:
def text_right(self, x: int, y: int, text: str, font: int, fgcolor: int, bgcolor: int, minwidth: int=None) -> int:
'''
Renders right-aligned text at position (x,y) where x is right position and y is baseline.
Font font is used for rendering, fgcolor is used as foreground color, bgcolor as background.
Fills at least minwidth pixels with bgcolor.
Returns width of rendered text in pixels.
'''
def text_width(self, text: str, font: int) -> int:
@ -85,14 +99,6 @@ class Display:
Scale determines a zoom factor.
'''
def loader(self, progress: int, yoffset: int, fgcolor: int, bgcolor: int, icon: bytes = None, iconfgcolor: int = None) -> None:
'''
Renders a rotating loader graphic.
Progress determines its position (0-1000), fgcolor is used as foreground color, bgcolor as background.
When icon and iconfgcolor are provided, an icon is drawn in the middle using the color specified in iconfgcolor.
Icon needs to be of exactly LOADER_ICON_SIZE x LOADER_ICON_SIZE pixels size.
'''
def orientation(self, degrees: int = None) -> int:
'''
Sets display orientation to 0, 90, 180 or 270 degrees.

View File

@ -30,15 +30,3 @@ 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

@ -72,13 +72,6 @@ NETWORKS = [
name="Ubiq",
rskip60=False,
),
NetworkInfo(
chain_id=20,
slip44=2018,
shortcut="EOSC",
name="EOS Classic",
rskip60=False,
),
NetworkInfo(
chain_id=28,
slip44=1128,
@ -156,6 +149,13 @@ NETWORKS = [
name="EtherGem",
rskip60=False,
),
NetworkInfo(
chain_id=2018,
slip44=2018,
shortcut="EOSC",
name="EOS Classic",
rskip60=False,
),
NetworkInfo(
chain_id=31102,
slip44=31102,

321
src/apps/monero/README.md Normal file
View File

@ -0,0 +1,321 @@
# Monero
MAINTAINER = ...
AUTHOR = Dusan Klinec <dusan.klinec@gmail.com>
REVIEWER = Tomas Susanka <tomas.susanka@satoshilabs.com>,
Jan Pochyla <jan.pochyla@satoshilabs.com>,
Ondrej Vejpustek <ondrej.vejpustek@satoshilabs.com>
-----
This Monero implementation was implemented from scratch originally for TREZOR by porting Monero C++ code to the Python codebase.
The implementation heavily relies on the [trezor-crypto] Monero functionality which implements basic crypto primitives and
other Monero related functionality (e.g., monero base58, accelerated and optimized Borromean range signatures)
A general high level description of the integration proposal is described in the documentation: [monero-doc].
## Features
The implementation provides the following features:
### Transaction signature
Signs a Monero transaction on the TREZOR.
- Designed so number of UTXO is practically unlimited (hundreds to thousands)
- Maximal number of outputs per transaction is 8 (usually there are only 2)
- Supports 8 B encrypted payment ID and 32 B unencrypted payment ID.
### Key Image sync
Key Image is computed with the spend key which is stored on the TREZOR.
In order to detect if the UTXO has been already spent (thus computing balance due to change transactions)
and correct spending UTXOs the key images are required. Without the key images the Monero view only
wallet incorrectly computes balance as it sees all ever received transactions as unspent.
Key image sync is a protocol that allows to compute key images for incoming transfers by TREZOR.
Example: 20 XMR in the single UTXO is received, thus real balance is 20. 1 XMR is sent to a different
address and remaining 19 are sent back with a change transaction. Correct balance is 19 but without
correct key image the view only wallet shows balance 39. Without knowing which UTXO is spent
the newly constructed spending transactions can pick already spent input. Such transaction is
rejected by a Monero daemon as a double spending transaction.
Normally, the Key image sync is not needed as the key image computation is done by
the transaction signing algorithm. However, if the wallet file is somehow corrupted
or the wallet is used on a new host / restored from the TREZOR the key
image sync is required for correct function of the wallet. It recomputes key images
for all received transaction inputs.
## Integration rationale
The Monero codebase already contains cold wallet support. I.e., wallet not connected to the Internet, which should provide
better security guarantees as it minimizes attack surface compared to the hot wallet - always connected wallet.
As the cold wallet is not connected to the Internet and does not have access nor to the blockchain neither to the monero
full node the all information for transaction construction have to be prepared by the hot wallet.
When using the cold wallet, hot wallet is watch-only. It has only the view-key so it can scan blockchain for incoming
transactions but is not able to spend any transaction.
Transaction signature with cold wallet works like this:
- Create transaction construction data on hot wallet. `transfer <address> <amount>`. Works similar to the normal wallet operation
but instead of the signed transaction, the watch-only hot wallet generates `unsigned_txset` file which contains
transaction construction data.
- Cold wallet opens `unsigned_txset`, verifies the signature on the transaction construction data and creates Monero transaction
using the data. Cold wallet creates `signed_txset`
- Hot wallet opens `signed_txset`, verifies the transaction and asks user whether to submit transaction to the full node.
### Cold wallet protocols
As cold wallet support is already present in Monero codebase, the protocols were well designed and analyzed.
We decided to reuse the cold wallet approach when signing the transaction as the TREZOR pretty much behaves as the cold wallet,
i.e., does not have access to the blockchain or full Monero node. The whole transaction is built in the TREZOR thus
the integration has security properties of the cold wallet (which is belevied to be secure). This integration approach
makes security analysis easier and enables to use existing codebase and protocols. This makes merging TREZOR support to
the Monero codebase easier.
We believe that by choosing a bit more high-level approach in the protocol design we could easily add more advanced features,
TREZOR implements cold wallet protocols in this integration scheme.
## Description
Main high level protocol logic is implemented in `apps/monero/protocol/` directory.
### Serialization
The serialization in `apps/monero/xmr/serialize` is the cryptonote serialization format used to serialize data to blockchain.
The serialization was ported from Monero C++. Source comes from the library [monero-serialize].
Serialization scheme was inspired by protobuf serialization scheme.
Fields are specified as a classmethod which is easier to `gc.collect()` after serialization is done.
```python
@classmethod
def f_specs(cls):
return (("size", SizeT),)
```
Serialization is synchronous.
### Protocols
Transaction signing and Key Image (KI) sync are multi-step stateful protocols.
The protocol have several roundtrips.
In the signing protocol the connected host mainly serves as a dumb storage providing values to the TREZOR when needed,
mainly due to memory constrains on TREZOR. The offloaded data can be in plaintext. In this case data is HMACed with unique HMAC
key to avoid data tampering, reordering, replay, reuse, etc... Some data are offloaded as protected, encrypted and authenticated
with Chacha20Poly1305 with unique key (derived from the protocol step, message, purpose, counter, master secret).
TREZOR builds the signed Monero transaction incrementally, i.e., one UTXO per round trip, one transaction output per roundtrip.
### Protocol workflow
Key image sync and transaction signing protocols are stateful.
Both protocols implement custom workflow managing the protocol state and state transitions explicitly.
Entry to the protocol workflow is passed on the initial protocol message, i.e., only the initial protocol message
is registered via `wire.add()`. The workflow internally manages receiving / sending protocol messages.
Each finished protocol step specifies the next expected message set which helps to govern protocol state transitions,
i.e., exception is thrown if another message is received as expected.
As the protocols implement custom workflow the general package unimport in `wire` is not called which
could lead to memory problems as locally imported packages are not freed from memory on `gc.collect()`.
Thus protocols call unimport manually after processing the protocol messages.
Protobuf messages are following the convention `MoneroXRequest`, `MoneroXAck`.
## Key Image sync work flow
In the KI sync cold wallet protocol KIs are generated by the cold wallet. For each KI there is a ring signature
generated by the cold wallet (KI proof).
KI sync is mainly needed to recover from some problem or when using a new hot-wallet (corruption of a wallet file or
using TREZOR on a different host).
The KI protocol has 3 steps.
### Init step
- `MoneroKeyImageExportInitRequest`
- Contains commitment to all KIs we are going to compute (hash of all UTXOs).
- User can confirm / reject the KI sync in this step. Init message contains number of KIs for computation.
### Sync
- `MoneroKeyImageSyncStepRequest`
- Computes N KIs in this step. N = 10 for now.
- Returns encrypted result, `MoneroExportedKeyImage`
### Finalization
- `MoneroKeyImageSyncFinalRequest`
- When commitment on all KIs is correct (i.e, number of UTXOs matches, hashes match) the encryption key is released
to the agent/hot-wallet so it can decrypt computed KIs and import it
## Transaction signing
For detailed description and rationale please refer to the [monero-doc].
- The protocol workflow `apps/monero/sign_tx.py`
- The protocol is implemented in `apps/monero/protocol/signing/`
### `MoneroTransactionInitRequest`:
- Contains basic construction data for the transaction, e.g., transaction destinations, fee, mixin level,
range proof details (type of the range proof, batching scheme).
After receiving this message:
- The TREZOR prompts user for verification of the destination addresses and amounts.
- Commitments are computed thus later potential deviations from transaction destinations are detected and signing aborts.
- Secrets for HMACs / encryption are computed, TX key is computed.
- Precomputes required sub-addresses (init message indicates which sub-addresses are needed).
### `MoneroTransactionSetInputRequest`
- Sends one UTXO to the TREZOR for processing, encoded as `MoneroTransactionSourceEntry`.
- Contains construction data needed for signing the transaction, computing spending key for UTXO.
TREZOR computes spending keys, `TxinToKey`, `pseudo_out`, HMACs for offloaded data
### `MoneroTransactionInputsPermutationRequest`
UTXOs have to be sorted by the key image in the valid blockchain transaction.
This message caries permutation on the key images so they are sorted in the desired way.
### `MoneroTransactionInputViniRequest`
- Step needed to correctly hash all transaction inputs, in the right order (permutation computed in the previous step).
- Contains `MoneroTransactionSourceEntry` and `TxinToKey` computed in the previous step.
- TREZOR Computes `tx_prefix_hash` is part of the signed data.
### `MoneroTransactionAllInputsSetRequest`
- Sent after all inputs have been processed.
- Used in the range proof offloading to the host. E.g., in case of batched Bulletproofs with more than 2 transaction outputs.
The message response contains TREZOR-generated commitment masks so host can compute range proof correctly.
### `MoneroTransactionSetOutputRequest`
- Sends transaction output, `MoneroTransactionDestinationEntry`, one per message.
- HMAC prevents tampering with previously accepted data (in the init step).
- TREZOR computes data related to transaction output, e.g., range proofs, ECDH info for the receiver, output public key.
- In case offloaded range proof is used the request can carry computed range proof.
### `MoneroTransactionAllOutSetRequest`
Sent after all transaction outputs have been sent to the TREZOR for processing.
Request is empty, the response contains computed `extra` field (may contain additional public keys if sub-addresses are used),
computed `tx_prefix_hash` and basis for the final transaction signature `MoneroRingCtSig` (fee, transaction type).
### `MoneroTransactionMlsagDoneRequest`
Message sent to ask TREZOR to compute pre-MLSAG hash required for the signature.
Hash is computed incrementally by TREZOR since the init message and can be finalized in this step.
Request is empty, response contains message hash, required for the signature.
### `MoneroTransactionSignInputRequest`
- Caries `MoneroTransactionSourceEntry`, similarly as previous messages `MoneroTransactionSetInputRequest`, `MoneroTransactionInputViniRequest`.
- Caries computed transaction inputs, pseudo outputs, HMACs, encrypted spending keys and alpha masks
- TREZOR generates MLSAG for this UTXO, returns the signature.
- Code returns also `cout` value if the multisig mode is active - not fully implemented, will be needed later when implementing multisigs.
### `MoneroTransactionFinalRequest`
- Sent when all UTXOs have been signed properly
- Finalizes transaction signature
- Returns encrypted transaction private keys which are needed later, e.g. for TX proof. As TREZOR cannot store aux data
for all signed transactions its offloaded encrypted to the wallet. Later when TX proof is implemented in the TREZOR it
will load encrypted TX keys, decrypt it and generate the proof.
## Implementation notes
Few notes on design / implementation.
### Cryptography
Operation with Ed25519 points and scalars are implemented in [trezor-crypto] so the underlying cryptography layer
is fast, secure and constant-time.
Ed Point coordinates are Extended Edwards, using type `ge25519` with coordinates `(x, y, z, t)`. Functions in Monero code
in the [trezor-crypto] use the `ge25519` for points (no other different point formats).
Functions like `op256_modm` (e.g., `add256_modm`) operate on scalar values, i.e., 256 bit integers modulo curve order
`2**252 + 3*610042537739*15158679415041928064055629`.
Functions `curve25519_*` operate on 256 bit integers modulo `2**255 - 19`, the coordinates of the point.
These are used mainly internally (e.g., for `hash_to_point()`) and not exported to the [trezor-core].
[trezor-crypto] contains also some Monero-specific functions, such as
`xmr_hash_to_scalar`, `xmr_hash_to_ec`, `xmr_generate_key_derivation`. Those are used in [trezor-core] where more high
level operations are implemented, such as MLSAG.
#### Crypto API
API bridging [trezor-crypto] and [trezor-core]: `embed/extmod/modtrezorcrypto/modtrezorcrypto-monero.h`
It encapsulates Ed25519 points and scalars in corresponding Python classes which have memory-wiping destructor.
API provides basic functions for work with scalars and points and Monero specific functions.
The API is designed in such a way it is easy to work with Ed25519 as there is only one point format which is always
normed to avoid complications when chaining operations such as `scalarmult`s.
### Range signatures
Borromean range signatures were optimized and ported to [trezor-crypto].
Range signatures xmr_gen_range_sig are CPU intensive and memory intensive operations which were originally implemented
in python (trezor-core) but it was not feasible to run on the Trezor device due to a small amount of RAM and long
computation times. It was needed to optimize the algorithm and port it to C so it is feasible to run it on the real hardware and run it fast.
Range signature is a well-contained problem with no allocations needed, simple API.
For memory and timing reasons its implemented directly in trezor-crypto (as it brings real benefit to the user).
On the other hand, MLASG and other ring signatures are built from building blocks in python for easier development,
code readability, maintenance and debugging. Porting to C is not that straightforward and I don't see any benefit here.
The memory and CPU is not the problem as in the case of range signatures so I think it is fine to have it in Python.
Porting to C would also increase complexity of trezor-crypto and could lead to bugs.
Using small and easily auditable & testable building blocks, such as ge25519_add (fast, in C) to build more complex
schemes in high level language is, in my opinion, a scalable and secure way to build the system.
Porting all Monero crypto schemes to C would be very time consuming and prone to errors.
Having access to low-level features also speeds up development of new features, such as multisigs.
MLSAG may need to be slightly changed when implementing multisigs
(some preparations have been made already but we will see after this phase starts).
Bulletproof generation and verification is implemented, however the device can handle maximum 2 batched outputs
in the bulletproof due to high memory requirements (more on that in [monero-doc]). If number of outputs is larger
than 2 the offloading to host is required. In such case, the bulletproofs are first computed at the host and sent to
Trezor for verification.
Bulletproof implementation is covered by unit tests, the proofs in unittest were generated by the Monero C++
implementation.
[trezor-crypto]: https://github.com/trezor/trezor-crypto
[trezor-core]: https://github.com/trezor/trezor-core
[monero-doc]: https://github.com/ph4r05/monero-trezor-doc
[monero-serialize]: https://github.com/ph4r05/monero-serialize

View File

@ -0,0 +1,12 @@
from trezor import wire
from trezor.messages import MessageType
def boot():
wire.add(MessageType.MoneroGetAddress, __name__, "get_address")
wire.add(MessageType.MoneroGetWatchKey, __name__, "get_watch_only")
wire.add(MessageType.MoneroTransactionInitRequest, __name__, "sign_tx")
wire.add(MessageType.MoneroKeyImageExportInitRequest, __name__, "key_image_sync")
if __debug__ and hasattr(MessageType, "DebugMoneroDiagRequest"):
wire.add(MessageType.DebugMoneroDiagRequest, __name__, "diag")

114
src/apps/monero/diag.py Normal file
View File

@ -0,0 +1,114 @@
if __debug__:
import gc
import micropython
import sys
from trezor import log
PREV_MEM = gc.mem_free()
CUR_MES = 0
def log_trace(x=None):
log.debug(
__name__,
"Log trace %s, ... F: %s A: %s, S: %s",
x,
gc.mem_free(),
gc.mem_alloc(),
micropython.stack_use(),
)
def check_mem(x=""):
global PREV_MEM, CUR_MES
gc.collect()
free = gc.mem_free()
diff = PREV_MEM - free
log.debug(
__name__,
"======= {} {} Diff: {} Free: {} Allocated: {}".format(
CUR_MES, x, diff, free, gc.mem_alloc()
),
)
micropython.mem_info()
gc.collect()
CUR_MES += 1
PREV_MEM = free
def retit(**kwargs):
from trezor.messages.Failure import Failure
return Failure(**kwargs)
async def diag(ctx, msg, **kwargs):
log.debug(__name__, "----diagnostics")
gc.collect()
if msg.ins == 0:
check_mem(0)
return retit()
elif msg.ins == 1:
check_mem(1)
micropython.mem_info(1)
return retit()
elif msg.ins == 2:
log.debug(__name__, "_____________________________________________")
log.debug(__name__, "_____________________________________________")
log.debug(__name__, "_____________________________________________")
return retit()
elif msg.ins == 3:
pass
elif msg.ins == 4:
total = 0
monero = 0
for k, v in sys.modules.items():
log.info(__name__, "Mod[%s]: %s", k, v)
total += 1
if k.startswith("apps.monero"):
monero += 1
log.info(__name__, "Total modules: %s, Monero modules: %s", total, monero)
return retit()
elif msg.ins in [5, 6, 7]:
check_mem()
from apps.monero.xmr import bulletproof as bp
check_mem("BP Imported")
from apps.monero.xmr import crypto
check_mem("Crypto Imported")
bpi = bp.BulletProofBuilder()
bpi.gc_fnc = gc.collect
bpi.gc_trace = log_trace
vals = [crypto.sc_init((1 << 30) - 1 + 16), crypto.sc_init(22222)]
masks = [crypto.random_scalar(), crypto.random_scalar()]
check_mem("BP pre input")
if msg.ins == 5:
bp_res = bpi.prove_testnet(vals[0], masks[0])
check_mem("BP post prove")
bpi.verify_testnet(bp_res)
check_mem("BP post verify")
elif msg.ins == 6:
bp_res = bpi.prove(vals[0], masks[0])
check_mem("BP post prove")
bpi.verify(bp_res)
check_mem("BP post verify")
elif msg.ins == 7:
bp_res = bpi.prove_batch(vals, masks)
check_mem("BP post prove")
bpi.verify(bp_res)
check_mem("BP post verify")
return retit()
return retit()

View File

@ -0,0 +1,17 @@
from trezor.messages.MoneroAddress import MoneroAddress
from apps.common.layout import show_address, show_qr
from apps.monero import misc
async def get_address(ctx, msg):
creds = await misc.get_creds(ctx, msg.address_n, msg.network_type)
if msg.show_display:
while True:
if await show_address(ctx, creds.address.decode("ascii"), msg.address_n):
break
if await show_qr(ctx, creds.address.decode("ascii")):
break
return MoneroAddress(address=creds.address)

View File

@ -0,0 +1,16 @@
from trezor.messages.MoneroGetWatchKey import MoneroGetWatchKey
from trezor.messages.MoneroWatchKey import MoneroWatchKey
from apps.monero import misc
from apps.monero.layout import confirms
from apps.monero.xmr import crypto
async def get_watch_only(ctx, msg: MoneroGetWatchKey):
await confirms.require_confirm_watchkey(ctx)
creds = await misc.get_creds(ctx, msg.address_n, msg.network_type)
address = creds.address
watch_key = crypto.encodeint(creds.view_key_private)
return MoneroWatchKey(watch_key=watch_key, address=address)

View File

@ -0,0 +1,108 @@
import gc
from trezor import log, wire
from trezor.messages import MessageType
from trezor.messages.MoneroExportedKeyImage import MoneroExportedKeyImage
from trezor.messages.MoneroKeyImageExportInitAck import MoneroKeyImageExportInitAck
from trezor.messages.MoneroKeyImageSyncFinalAck import MoneroKeyImageSyncFinalAck
from trezor.messages.MoneroKeyImageSyncStepAck import MoneroKeyImageSyncStepAck
from apps.monero import misc
from apps.monero.layout import confirms
from apps.monero.xmr import crypto, key_image, monero
from apps.monero.xmr.crypto import chacha_poly
async def key_image_sync(ctx, msg):
state = KeyImageSync()
res = await _init_step(state, ctx, msg)
while True:
msg = await ctx.call(
res,
MessageType.MoneroKeyImageSyncStepRequest,
MessageType.MoneroKeyImageSyncFinalRequest,
)
del res
if msg.MESSAGE_WIRE_TYPE == MessageType.MoneroKeyImageSyncStepRequest:
res = await _sync_step(state, ctx, msg)
else:
res = await _final_step(state, ctx)
break
gc.collect()
return res
class KeyImageSync:
def __init__(self):
self.current_output = -1
self.num_outputs = 0
self.expected_hash = None
self.enc_key = None
self.creds = None
self.subaddresses = {}
self.hasher = crypto.get_keccak()
async def _init_step(s, ctx, msg):
s.creds = await misc.get_creds(ctx, msg.address_n, msg.network_type)
await confirms.require_confirm_keyimage_sync(ctx)
s.num_outputs = msg.num
s.expected_hash = msg.hash
s.enc_key = crypto.random_bytes(32)
for sub in msg.subs:
monero.compute_subaddresses(
s.creds, sub.account, sub.minor_indices, s.subaddresses
)
return MoneroKeyImageExportInitAck()
async def _sync_step(s, ctx, tds):
if not tds.tdis:
raise wire.DataError("Empty")
kis = []
buff = bytearray(32 * 3)
buff_mv = memoryview(buff)
for td in tds.tdis:
s.current_output += 1
if s.current_output >= s.num_outputs:
raise wire.DataError("Too many outputs")
if __debug__:
log.debug(__name__, "ki_sync, step i: %d", s.current_output)
# Update the control hash
s.hasher.update(key_image.compute_hash(td))
# Compute keyimage + signature
ki, sig = key_image.export_key_image(s.creds, s.subaddresses, td)
# Serialize into buff
crypto.encodepoint_into(buff_mv[0:32], ki)
crypto.encodeint_into(buff_mv[32:64], sig[0][0])
crypto.encodeint_into(buff_mv[64:], sig[0][1])
# Encrypt with enc_key
nonce, ciph, _ = chacha_poly.encrypt(s.enc_key, buff)
kis.append(MoneroExportedKeyImage(iv=nonce, blob=ciph))
return MoneroKeyImageSyncStepAck(kis=kis)
async def _final_step(s, ctx):
if s.current_output + 1 != s.num_outputs:
raise wire.DataError("Invalid number of outputs")
final_hash = s.hasher.digest()
if final_hash != s.expected_hash:
raise wire.DataError("Invalid number of outputs")
return MoneroKeyImageSyncFinalAck(enc_key=s.enc_key)

View File

@ -0,0 +1,121 @@
from trezor import res, ui
from trezor.messages import ButtonRequestType
from trezor.ui.text import Text
from trezor.utils import chunks
def paginate_lines(lines, lines_per_page=5):
pages = []
cpage = []
nlines = 0
last_modifier = None
for line in lines:
cpage.append(line)
if not isinstance(line, int):
nlines += 1
else:
last_modifier = line
if nlines >= lines_per_page:
pages.append(cpage)
cpage = []
nlines = 0
if last_modifier is not None:
cpage.append(last_modifier)
if nlines > 0:
pages.append(cpage)
return pages
@ui.layout
async def tx_dialog(
ctx,
code,
content,
cancel_btn,
confirm_btn,
cancel_style,
confirm_style,
scroll_tuple=None,
):
from trezor.messages import MessageType
from trezor.messages.ButtonRequest import ButtonRequest
from trezor.ui.confirm import ConfirmDialog
from trezor.ui.scroll import Scrollpage
await ctx.call(ButtonRequest(code=code), MessageType.ButtonAck)
dialog = ConfirmDialog(
content,
cancel=cancel_btn,
confirm=confirm_btn,
cancel_style=cancel_style,
confirm_style=confirm_style,
)
if scroll_tuple and scroll_tuple[1] > 1:
dialog = Scrollpage(dialog, scroll_tuple[0], scroll_tuple[1])
return await ctx.wait(dialog)
async def naive_pagination(
ctx, lines, title, icon=ui.ICON_RESET, icon_color=ui.ORANGE, per_page=5
):
from trezor.ui.confirm import CANCELLED, CONFIRMED, DEFAULT_CANCEL, DEFAULT_CONFIRM
if isinstance(lines, (list, tuple)):
lines = lines
else:
lines = list(chunks(lines, 16))
pages = paginate_lines(lines, per_page)
npages = len(pages)
cur_step = 0
code = ButtonRequestType.SignTx
iback = res.load(ui.ICON_BACK)
inext = res.load(ui.ICON_CLICK)
while cur_step <= npages:
text = pages[cur_step]
fst_page = cur_step == 0
lst_page = cur_step + 1 >= npages
cancel_btn = DEFAULT_CANCEL if fst_page else iback
cancel_style = ui.BTN_CANCEL if fst_page else ui.BTN_DEFAULT
confirm_btn = DEFAULT_CONFIRM if lst_page else inext
confirm_style = ui.BTN_CONFIRM if lst_page else ui.BTN_DEFAULT
paging = ("%d/%d" % (cur_step + 1, npages)) if npages > 1 else ""
content = Text("%s %s" % (title, paging), icon, icon_color=icon_color)
content.normal(*text)
reaction = await tx_dialog(
ctx,
code,
content,
cancel_btn,
confirm_btn,
cancel_style,
confirm_style,
(cur_step, npages),
)
if fst_page and reaction == CANCELLED:
return False
elif not lst_page and reaction == CONFIRMED:
cur_step += 1
elif lst_page and reaction == CONFIRMED:
return True
elif reaction == CANCELLED:
cur_step -= 1
elif reaction == CONFIRMED:
cur_step += 1
def format_amount(value):
return "%f XMR" % (value / 1000000000000)
def split_address(address):
return chunks(address, 16)

View File

@ -0,0 +1,125 @@
from ubinascii import hexlify
from trezor import ui, wire
from trezor.messages import ButtonRequestType
from trezor.ui.text import Text
from trezor.utils import chunks
from apps.common.confirm import require_confirm, require_hold_to_confirm
from apps.monero.layout import common
async def require_confirm_watchkey(ctx):
content = Text("Confirm export", ui.ICON_SEND, icon_color=ui.GREEN)
content.normal("Do you really want to", "export watch-only", "credentials?")
return await require_confirm(ctx, content, ButtonRequestType.SignTx)
async def require_confirm_keyimage_sync(ctx):
content = Text("Confirm ki sync", ui.ICON_SEND, icon_color=ui.GREEN)
content.normal("Do you really want to", "sync key images?")
return await require_confirm(ctx, content, ButtonRequestType.SignTx)
async def require_confirm_transaction(ctx, tsx_data, network_type):
"""
Ask for confirmation from user.
"""
from apps.monero.xmr.addresses import get_change_addr_idx
outputs = tsx_data.outputs
change_idx = get_change_addr_idx(outputs, tsx_data.change_dts)
has_integrated = bool(tsx_data.integrated_indices)
has_payment = bool(tsx_data.payment_id)
for idx, dst in enumerate(outputs):
is_change = change_idx is not None and idx == change_idx
if is_change:
continue # Change output does not need confirmation
is_dummy = change_idx is None and dst.amount == 0 and len(outputs) == 2
if is_dummy:
continue # Dummy output does not need confirmation
if has_integrated and idx in tsx_data.integrated_indices:
cur_payment = tsx_data.payment_id
else:
cur_payment = None
await _require_confirm_output(ctx, dst, network_type, cur_payment)
if has_payment and not has_integrated:
await _require_confirm_payment_id(ctx, tsx_data.payment_id)
await _require_confirm_fee(ctx, tsx_data.fee)
text = Text("Signing transaction", ui.ICON_SEND, icon_color=ui.BLUE)
text.normal("Signing...")
text.render()
async def _require_confirm_output(ctx, dst, network_type, payment_id):
"""
Single transaction destination confirmation
"""
from apps.monero.xmr.addresses import encode_addr
from apps.monero.xmr.networks import net_version
version = net_version(network_type, dst.is_subaddress, payment_id is not None)
addr = encode_addr(
version, dst.addr.spend_public_key, dst.addr.view_public_key, payment_id
)
text_addr = common.split_address(addr.decode())
text_amount = common.format_amount(dst.amount)
if not await common.naive_pagination(
ctx,
[ui.BOLD, text_amount, ui.MONO] + list(text_addr),
"Confirm send",
ui.ICON_SEND,
ui.GREEN,
4,
):
raise wire.ActionCancelled("Cancelled")
async def _require_confirm_payment_id(ctx, payment_id):
if not await common.naive_pagination(
ctx,
[ui.MONO] + list(chunks(hexlify((payment_id)), 16)),
"Payment ID",
ui.ICON_SEND,
ui.GREEN,
):
raise wire.ActionCancelled("Cancelled")
async def _require_confirm_fee(ctx, fee):
content = Text("Confirm fee", ui.ICON_SEND, icon_color=ui.GREEN)
content.bold(common.format_amount(fee))
await require_hold_to_confirm(ctx, content, ButtonRequestType.ConfirmOutput)
@ui.layout
async def transaction_step(ctx, step, sub_step=None, sub_step_total=None):
info = []
if step == 100:
info = ["Processing inputs", "%d/%d" % (sub_step + 1, sub_step_total)]
elif step == 200:
info = ["Sorting"]
elif step == 300:
info = [
"Processing inputs",
"phase 2",
"%d/%d" % (sub_step + 1, sub_step_total),
]
elif step == 400:
info = ["Processing outputs", "%d/%d" % (sub_step + 1, sub_step_total)]
elif step == 500:
info = ["Postprocessing..."]
elif step == 600:
info = ["Signing inputs", "%d/%d" % (sub_step + 1, sub_step_total)]
else:
info = ["Processing..."]
text = Text("Signing transaction", ui.ICON_SEND, icon_color=ui.BLUE)
text.normal(*info)
text.render()

21
src/apps/monero/misc.py Normal file
View File

@ -0,0 +1,21 @@
async def get_creds(ctx, address_n=None, network_type=None):
from apps.common import seed
from apps.monero.xmr import crypto, monero
from apps.monero.xmr.credentials import AccountCreds
use_slip0010 = 0 not in address_n # If path contains 0 it is not SLIP-0010
if use_slip0010:
curve = "ed25519"
else:
curve = "secp256k1"
node = await seed.derive_node(ctx, address_n, curve)
if use_slip0010:
key_seed = node.private_key()
else:
key_seed = crypto.cn_fast_hash(node.private_key())
spend_sec, _, view_sec, _ = monero.generate_monero_keys(key_seed)
creds = AccountCreds.new_wallet(view_sec, spend_sec, network_type)
return creds

139
src/apps/monero/sign_tx.py Normal file
View File

@ -0,0 +1,139 @@
import gc
from trezor import log, utils, wire
from trezor.messages import MessageType
from apps.monero.signing.state import State
async def sign_tx(ctx, received_msg):
state = State(ctx)
mods = utils.unimport_begin()
# Splitting ctx.call() to write() and read() helps to reduce memory fragmentation
# between calls.
while True:
if __debug__:
log.debug(__name__, "#### F: %s, A: %s", gc.mem_free(), gc.mem_alloc())
gc.collect()
gc.threshold(gc.mem_free() // 4 + gc.mem_alloc())
result_msg, accept_msgs = await sign_tx_dispatch(state, received_msg)
if accept_msgs is None:
break
await ctx.write(result_msg)
del (result_msg, received_msg)
utils.unimport_end(mods)
received_msg = await ctx.read(accept_msgs)
utils.unimport_end(mods)
return result_msg
async def sign_tx_dispatch(state, msg):
if msg.MESSAGE_WIRE_TYPE == MessageType.MoneroTransactionInitRequest:
from apps.monero.signing import step_01_init_transaction
return (
await step_01_init_transaction.init_transaction(
state, msg.address_n, msg.network_type, msg.tsx_data
),
(MessageType.MoneroTransactionSetInputRequest,),
)
elif msg.MESSAGE_WIRE_TYPE == MessageType.MoneroTransactionSetInputRequest:
from apps.monero.signing import step_02_set_input
return (
await step_02_set_input.set_input(state, msg.src_entr),
(
MessageType.MoneroTransactionSetInputRequest,
MessageType.MoneroTransactionInputsPermutationRequest,
),
)
elif msg.MESSAGE_WIRE_TYPE == MessageType.MoneroTransactionInputsPermutationRequest:
from apps.monero.signing import step_03_inputs_permutation
return (
await step_03_inputs_permutation.tsx_inputs_permutation(state, msg.perm),
(MessageType.MoneroTransactionInputViniRequest,),
)
elif msg.MESSAGE_WIRE_TYPE == MessageType.MoneroTransactionInputViniRequest:
from apps.monero.signing import step_04_input_vini
return (
await step_04_input_vini.input_vini(
state,
msg.src_entr,
msg.vini,
msg.vini_hmac,
msg.pseudo_out,
msg.pseudo_out_hmac,
),
(
MessageType.MoneroTransactionInputViniRequest,
MessageType.MoneroTransactionAllInputsSetRequest,
),
)
elif msg.MESSAGE_WIRE_TYPE == MessageType.MoneroTransactionAllInputsSetRequest:
from apps.monero.signing import step_05_all_inputs_set
return (
await step_05_all_inputs_set.all_inputs_set(state),
(MessageType.MoneroTransactionSetOutputRequest,),
)
elif msg.MESSAGE_WIRE_TYPE == MessageType.MoneroTransactionSetOutputRequest:
from apps.monero.signing import step_06_set_output
dst, dst_hmac, rsig_data = msg.dst_entr, msg.dst_entr_hmac, msg.rsig_data
del msg
return (
await step_06_set_output.set_output(state, dst, dst_hmac, rsig_data),
(
MessageType.MoneroTransactionSetOutputRequest,
MessageType.MoneroTransactionAllOutSetRequest,
),
)
elif msg.MESSAGE_WIRE_TYPE == MessageType.MoneroTransactionAllOutSetRequest:
from apps.monero.signing import step_07_all_outputs_set
return (
await step_07_all_outputs_set.all_outputs_set(state),
(MessageType.MoneroTransactionSignInputRequest,),
)
elif msg.MESSAGE_WIRE_TYPE == MessageType.MoneroTransactionSignInputRequest:
from apps.monero.signing import step_09_sign_input
return (
await step_09_sign_input.sign_input(
state,
msg.src_entr,
msg.vini,
msg.vini_hmac,
msg.pseudo_out,
msg.pseudo_out_hmac,
msg.pseudo_out_alpha,
msg.spend_key,
),
(
MessageType.MoneroTransactionSignInputRequest,
MessageType.MoneroTransactionFinalRequest,
),
)
elif msg.MESSAGE_WIRE_TYPE == MessageType.MoneroTransactionFinalRequest:
from apps.monero.signing import step_10_sign_final
return await step_10_sign_final.final_msg(state), None
else:
raise wire.DataError("Unknown message")

View File

@ -0,0 +1,60 @@
from trezor import wire
class Error(wire.DataError):
pass
class ChangeAddressError(wire.DataError):
pass
class NotEnoughOutputsError(wire.DataError):
pass
class RctType:
"""
There are two types of monero Ring Confidential Transactions:
1. RCTTypeFull = 1 (used if num_inputs == 1)
2. RCTTypeSimple = 2 (for num_inputs > 1)
There is actually also RCTTypeNull but we ignore that one.
"""
Full = 1
Simple = 2
class RsigType:
"""
Range signature types
There are four types of range proofs/signatures in official Monero:
1. RangeProofBorromean = 0
2. RangeProofBulletproof = 1
3. RangeProofMultiOutputBulletproof = 2
4. RangeProofPaddedBulletproof = 3
We simplify all the bulletproofs into one.
"""
Borromean = 0
Bulletproof = 1
def get_monero_rct_type(rct_type, rsig_type):
"""
This converts our internal representation of RctType and RsigType
into what is used in Monero:
- Null = 0
- Full = 1
- Simple = 2
- Simple/Full with bulletproof = 3
"""
if rsig_type == RsigType.Bulletproof:
return 3 # Bulletproofs
if rct_type == RctType.Simple:
return 2 # Simple
else:
return 1 # Full

View File

@ -0,0 +1,130 @@
from trezor import utils
from apps.monero.xmr import crypto
def _build_key(secret, discriminator=None, index: int = None) -> bytes:
"""
Creates an unique-purpose key
"""
key_buff = bytearray(32 + 12 + 4) # key + disc + index
offset = 32
utils.memcpy(key_buff, 0, secret, 0, len(secret))
if discriminator is not None:
utils.memcpy(key_buff, offset, discriminator, 0, len(discriminator))
offset += len(discriminator)
if index is not None:
# dump_uvarint_b_into, saving import
shifted = True
while shifted:
shifted = index >> 7
key_buff[offset] = (index & 0x7F) | (0x80 if shifted else 0x00)
offset += 1
index = shifted
return crypto.keccak_2hash(key_buff)
def hmac_key_txin(key_hmac, idx: int) -> bytes:
"""
(TxSourceEntry[i] || tx.vin[i]) hmac key
"""
return _build_key(key_hmac, b"txin", idx)
def hmac_key_txin_comm(key_hmac, idx: int) -> bytes:
"""
pseudo_outputs[i] hmac key. Pedersen commitment for inputs.
"""
return _build_key(key_hmac, b"txin-comm", idx)
def hmac_key_txdst(key_hmac, idx: int) -> bytes:
"""
TxDestinationEntry[i] hmac key
"""
return _build_key(key_hmac, b"txdest", idx)
def hmac_key_txout(key_hmac, idx: int) -> bytes:
"""
(TxDestinationEntry[i] || tx.vout[i]) hmac key
"""
return _build_key(key_hmac, b"txout", idx)
def hmac_key_txout_asig(key_hmac, idx: int) -> bytes:
"""
rsig[i] hmac key. Range signature HMAC
"""
return _build_key(key_hmac, b"txout-asig", idx)
def enc_key_txin_alpha(key_enc, idx: int) -> bytes:
"""
Chacha20Poly1305 encryption key for alpha[i] used in Pedersen commitment in pseudo_outs[i]
"""
return _build_key(key_enc, b"txin-alpha", idx)
def enc_key_spend(key_enc, idx: int) -> bytes:
"""
Chacha20Poly1305 encryption key for alpha[i] used in Pedersen commitment in pseudo_outs[i]
"""
return _build_key(key_enc, b"txin-spend", idx)
def enc_key_cout(key_enc, idx: int = None) -> bytes:
"""
Chacha20Poly1305 encryption key for multisig C values from MLASG.
"""
return _build_key(key_enc, b"cout", idx)
async def gen_hmac_vini(key, src_entr, vini_bin, idx: int) -> bytes:
"""
Computes hmac (TxSourceEntry[i] || tx.vin[i])
"""
import protobuf
from apps.monero.xmr.keccak_hasher import get_keccak_writer
kwriter = get_keccak_writer()
await protobuf.dump_message(kwriter, src_entr)
kwriter.write(vini_bin)
hmac_key_vini = hmac_key_txin(key, idx)
hmac_vini = crypto.compute_hmac(hmac_key_vini, kwriter.get_digest())
return hmac_vini
async def gen_hmac_vouti(key, dst_entr, tx_out_bin, idx: int) -> bytes:
"""
Generates HMAC for (TxDestinationEntry[i] || tx.vout[i])
"""
import protobuf
from apps.monero.xmr.keccak_hasher import get_keccak_writer
kwriter = get_keccak_writer()
await protobuf.dump_message(kwriter, dst_entr)
kwriter.write(tx_out_bin)
hmac_key_vouti = hmac_key_txout(key, idx)
hmac_vouti = crypto.compute_hmac(hmac_key_vouti, kwriter.get_digest())
return hmac_vouti
async def gen_hmac_tsxdest(key, dst_entr, idx: int) -> bytes:
"""
Generates HMAC for TxDestinationEntry[i]
"""
import protobuf
from apps.monero.xmr.keccak_hasher import get_keccak_writer
kwriter = get_keccak_writer()
await protobuf.dump_message(kwriter, dst_entr)
hmac_key = hmac_key_txdst(key, idx)
hmac_tsxdest = crypto.compute_hmac(hmac_key, kwriter.get_digest())
return hmac_tsxdest

View File

@ -0,0 +1,146 @@
import gc
from micropython import const
from trezor import log
from apps.monero.xmr import crypto
class TprefixStub:
__slots__ = ("version", "unlock_time", "vin", "vout", "extra")
def __init__(self, **kwargs):
for kw in kwargs:
setattr(self, kw, kwargs[kw])
class State:
STEP_INP = const(100)
STEP_PERM = const(200)
STEP_VINI = const(300)
STEP_ALL_IN = const(350)
STEP_OUT = const(400)
STEP_ALL_OUT = const(500)
STEP_SIGN = const(600)
def __init__(self, ctx):
from apps.monero.xmr.keccak_hasher import KeccakXmrArchive
from apps.monero.xmr.mlsag_hasher import PreMlsagHasher
self.ctx = ctx
"""
Account credentials
type: AccountCreds
- view private/public key
- spend private/public key
- and its corresponding address
"""
self.creds = None
# HMAC/encryption keys used to protect offloaded data
self.key_hmac = None
self.key_enc = None
"""
Transaction keys
- also denoted as r/R
- tx_priv is a random number
- tx_pub is equal to `r*G` or `r*D` for subaddresses
- for subaddresses the `r` is commonly denoted as `s`, however it is still just a random number
- the keys are used to derive the one time address and its keys (P = H(A*r)*G + B)
"""
self.tx_priv = None
self.tx_pub = None
"""
In some cases when subaddresses are used we need more tx_keys
(explained in step 1).
"""
self.need_additional_txkeys = False
# Ring Confidential Transaction type
# allowed values: RctType.{Full, Simple}
self.rct_type = None
# Range Signature type (also called range proof)
# allowed values: RsigType.{Borromean, Bulletproof}
self.rsig_type = None
self.input_count = 0
self.output_count = 0
self.output_change = None
self.fee = 0
# wallet sub-address major index
self.account_idx = 0
# contains additional tx keys if need_additional_tx_keys is True
self.additional_tx_private_keys = []
self.additional_tx_public_keys = []
# currently processed input/output index
self.current_input_index = -1
self.current_output_index = -1
self.summary_inputs_money = 0
self.summary_outs_money = 0
# output commitments
self.output_pk_commitments = []
# masks used in the output commitment
self.output_sk_masks = []
self.output_amounts = []
# output *range proof* masks
self.output_masks = []
# the range proofs are calculated in batches, this denotes the grouping
self.rsig_grouping = []
# is range proof computing offloaded or not
self.rsig_offload = False
# sum of all inputs' pseudo out masks
self.sumpouts_alphas = crypto.sc_0()
# sum of all output' pseudo out masks
self.sumout = crypto.sc_0()
self.subaddresses = {}
# simple stub containing items hashed into tx prefix
self.tx = TprefixStub(vin=[], vout=[], extra=b"")
# contains an array where each item denotes the input's position
# (inputs are sorted by key images)
self.source_permutation = []
"""
Tx prefix hasher/hash. We use the hasher to incrementally hash and then
store the final hash in tx_prefix_hash.
See Monero-Trezor documentation section 3.3 for more details.
"""
self.tx_prefix_hasher = KeccakXmrArchive()
self.tx_prefix_hash = None
"""
Full message hasher/hash that is to be signed using MLSAG.
Contains tx_prefix_hash.
See Monero-Trezor documentation section 3.3 for more details.
"""
self.full_message_hasher = PreMlsagHasher()
self.full_message = None
def mem_trace(self, x=None, collect=False):
if __debug__:
log.debug(
__name__,
"Log trace: %s, ... F: %s A: %s",
x,
gc.mem_free(),
gc.mem_alloc(),
)
if collect:
gc.collect()
def change_address(self):
return self.output_change.addr if self.output_change else None

View File

@ -0,0 +1,375 @@
"""
Initializes a new transaction.
"""
import gc
from apps.monero import misc, signing
from apps.monero.layout import confirms
from apps.monero.signing import RctType, RsigType
from apps.monero.signing.state import State
from apps.monero.xmr import crypto, monero
if False:
from trezor.messages.MoneroTransactionData import MoneroTransactionData
from trezor.messages.MoneroTransactionRsigData import MoneroTransactionRsigData
async def init_transaction(
state: State, address_n: list, network_type: int, tsx_data: MoneroTransactionData
):
from apps.monero.signing import offloading_keys
state.creds = await misc.get_creds(state.ctx, address_n, network_type)
state.fee = state.fee if state.fee > 0 else 0
state.tx_priv = crypto.random_scalar()
state.tx_pub = crypto.scalarmult_base(state.tx_priv)
state.mem_trace(1)
# Ask for confirmation
await confirms.require_confirm_transaction(
state.ctx, tsx_data, state.creds.network_type
)
gc.collect()
state.mem_trace(3)
# Basic transaction parameters
state.input_count = tsx_data.num_inputs
state.output_count = len(tsx_data.outputs)
state.output_change = tsx_data.change_dts
state.mixin = tsx_data.mixin
state.fee = tsx_data.fee
state.account_idx = tsx_data.account
# Ensure change is correct
_check_change(state, tsx_data.outputs)
# At least two outpus are required, this applies also for sweep txs
# where one fake output is added. See _check_change for more info
if state.output_count < 2:
raise signing.NotEnoughOutputsError("At least two outputs are required")
_check_rsig_data(state, tsx_data.rsig_data)
_check_subaddresses(state, tsx_data.outputs)
# Extra processing, payment id
state.tx.version = 2 # current Monero transaction format (RingCT = 2)
state.tx.unlock_time = tsx_data.unlock_time
_process_payment_id(state, tsx_data)
await _compute_sec_keys(state, tsx_data)
gc.collect()
# Iterative tx_prefix_hash hash computation
state.tx_prefix_hasher.uvarint(state.tx.version)
state.tx_prefix_hasher.uvarint(state.tx.unlock_time)
state.tx_prefix_hasher.uvarint(state.input_count) # ContainerType, size
state.mem_trace(10, True)
# Final message hasher
state.full_message_hasher.init(state.rct_type == RctType.Simple)
state.full_message_hasher.set_type_fee(
signing.get_monero_rct_type(state.rct_type, state.rsig_type), state.fee
)
# Sub address precomputation
if tsx_data.account is not None and tsx_data.minor_indices:
_precompute_subaddr(state, tsx_data.account, tsx_data.minor_indices)
state.mem_trace(5, True)
# HMACs all outputs to disallow tampering.
# Each HMAC is then sent alongside the output
# and trezor validates it.
hmacs = []
for idx in range(state.output_count):
c_hmac = await offloading_keys.gen_hmac_tsxdest(
state.key_hmac, tsx_data.outputs[idx], idx
)
hmacs.append(c_hmac)
gc.collect()
state.mem_trace(6)
from trezor.messages.MoneroTransactionInitAck import MoneroTransactionInitAck
from trezor.messages.MoneroTransactionRsigData import MoneroTransactionRsigData
rsig_data = MoneroTransactionRsigData(offload_type=state.rsig_offload)
return MoneroTransactionInitAck(hmacs=hmacs, rsig_data=rsig_data)
def _check_subaddresses(state: State, outputs: list):
"""
Using subaddresses leads to a few poorly documented exceptions.
Normally we set R=r*G (tx_pub), however for subaddresses this is equal to R=r*D
to achieve the nice blockchain scanning property.
Remember, that R is per-transaction and not per-input. It's all good if we have a
single output or we have a single destination and the second output is our change.
This is because although the R=r*D, we can still derive the change using our private view-key.
In other words, calculate the one-time address as P = H(x*R)*G + Y (where X,Y is the change).
However, this does not work for other outputs than change, because we do not have the
recipient's view key, so we cannot use the same formula -- we need a new R.
The solution is very straightforward -- we create additional `R`s and use the `extra`
field to include them under the `ADDITIONAL_PUBKEYS` tag.
See:
- https://lab.getmonero.org/pubs/MRL-0006.pdf
- https://github.com/monero-project/monero/pull/2056
"""
from apps.monero.xmr.addresses import classify_subaddresses
# let's first figure out what kind of destinations we have
num_stdaddresses, num_subaddresses, single_dest_subaddress = classify_subaddresses(
outputs, state.change_address()
)
# if this is a single-destination transfer to a subaddress,
# we set (override) the tx pubkey to R=r*D and no additional
# tx keys are needed
if num_stdaddresses == 0 and num_subaddresses == 1:
state.tx_pub = crypto.scalarmult(
crypto.decodepoint(single_dest_subaddress.spend_public_key), state.tx_priv
)
# if a subaddress is used and either standard address is as well
# or more than one subaddress is used we need to add additional tx keys
state.need_additional_txkeys = num_subaddresses > 0 and (
num_stdaddresses > 0 or num_subaddresses > 1
)
state.mem_trace(4, True)
def _get_primary_change_address(state: State):
"""
Computes primary change address for the current account index
"""
from trezor.messages.MoneroAccountPublicAddress import MoneroAccountPublicAddress
D, C = monero.generate_sub_address_keys(
state.creds.view_key_private, state.creds.spend_key_public, state.account_idx, 0
)
return MoneroAccountPublicAddress(
view_public_key=crypto.encodepoint(C), spend_public_key=crypto.encodepoint(D)
)
def _check_rsig_data(state: State, rsig_data: MoneroTransactionRsigData):
"""
There are two types of monero ring confidential transactions:
1. RCTTypeFull = 1 (used if num_inputs == 1)
2. RCTTypeSimple = 2 (for num_inputs > 1)
and four types of range proofs (set in `rsig_data.rsig_type`):
1. RangeProofBorromean = 0
2. RangeProofBulletproof = 1
3. RangeProofMultiOutputBulletproof = 2
4. RangeProofPaddedBulletproof = 3
"""
state.rsig_grouping = rsig_data.grouping
if rsig_data.rsig_type == 0:
state.rsig_type = RsigType.Borromean
elif rsig_data.rsig_type in (1, 2, 3):
state.rsig_type = RsigType.Bulletproof
else:
raise ValueError("Unknown rsig type")
# unintuitively RctType.Simple is used for more inputs
if state.input_count > 1 or state.rsig_type == RsigType.Bulletproof:
state.rct_type = RctType.Simple
else:
state.rct_type = RctType.Full
if state.rsig_type == RsigType.Bulletproof and state.output_count > 2:
state.rsig_offload = True
_check_grouping(state)
def _check_grouping(state: State):
acc = 0
for x in state.rsig_grouping:
if x is None or x <= 0:
raise ValueError("Invalid grouping batch")
acc += x
if acc != state.output_count:
raise ValueError("Invalid grouping")
def _check_change(state: State, outputs: list):
"""
Check if the change address in state.output_change (from `tsx_data.outputs`) is
a) among tx outputs
b) is equal to our address
The change output is in `tsx_data.change_dts`, but also has to be in `tsx_data.outputs`.
This is what Monero does in its cold wallet signing protocol.
In other words, these structures are built by Monero when generating unsigned transaction set
and we do not want to modify this logic. We just translate the unsigned tx to the protobuf message.
So, although we could probably optimize this by having the change output in `change_dts`
only, we intentionally do not do so.
"""
from apps.monero.xmr.addresses import addr_eq, get_change_addr_idx
change_index = get_change_addr_idx(outputs, state.output_change)
change_addr = state.change_address()
# if there is no change, there is nothing to check
if change_addr is None:
state.mem_trace("No change" if __debug__ else None)
return
"""
Sweep tx is just one output and no change.
To prevent recognition of such transactions another fake output is added
that spends exactly 0 coins to a random address.
See https://github.com/monero-project/monero/pull/1415
"""
if change_index is None and state.output_change.amount == 0 and len(outputs) == 2:
state.mem_trace("Sweep tsx" if __debug__ else None)
return
found = False
for out in outputs:
if addr_eq(out.addr, change_addr):
found = True
break
if not found:
raise signing.ChangeAddressError("Change address not found in outputs")
my_addr = _get_primary_change_address(state)
if not addr_eq(my_addr, change_addr):
raise signing.ChangeAddressError("Change address differs from ours")
async def _compute_sec_keys(state: State, tsx_data: MoneroTransactionData):
"""
Generate master key H( H(TsxData || tx_priv) || rand )
"""
import protobuf
from apps.monero.xmr.keccak_hasher import get_keccak_writer
writer = get_keccak_writer()
await protobuf.dump_message(writer, tsx_data)
writer.write(crypto.encodeint(state.tx_priv))
master_key = crypto.keccak_2hash(
writer.get_digest() + crypto.encodeint(crypto.random_scalar())
)
state.key_hmac = crypto.keccak_2hash(b"hmac" + master_key)
state.key_enc = crypto.keccak_2hash(b"enc" + master_key)
def _precompute_subaddr(state: State, account: int, indices):
"""
Precomputes subaddresses for account (major) and list of indices (minors)
Subaddresses have to be stored in encoded form - unique representation.
Single point can have multiple extended coordinates representation - would not match during subaddress search.
"""
monero.compute_subaddresses(state.creds, account, indices, state.subaddresses)
def _process_payment_id(state: State, tsx_data: MoneroTransactionData):
"""
Writes payment id to the `extra` field under the TX_EXTRA_NONCE = 0x02 tag.
The second tag describes if the payment id is encrypted or not.
If the payment id is 8 bytes long it implies encryption and
therefore the TX_EXTRA_NONCE_ENCRYPTED_PAYMENT_ID = 0x01 tag is used.
If it is not encrypted, we use TX_EXTRA_NONCE_PAYMENT_ID = 0x00.
See:
- https://github.com/monero-project/monero/blob/ff7dc087ae5f7de162131cea9dbcf8eac7c126a1/src/cryptonote_basic/tx_extra.h
"""
if not tsx_data.payment_id:
return
# encrypted payment id
if len(tsx_data.payment_id) == 8:
view_key_pub_enc = _get_key_for_payment_id_encryption(
tsx_data.outputs, state.change_address()
)
view_key_pub = crypto.decodepoint(view_key_pub_enc)
payment_id_encr = _encrypt_payment_id(
tsx_data.payment_id, view_key_pub, state.tx_priv
)
extra_nonce = payment_id_encr
extra_prefix = 1 # TX_EXTRA_NONCE_ENCRYPTED_PAYMENT_ID
# plain text payment id
elif len(tsx_data.payment_id) == 32:
extra_nonce = tsx_data.payment_id
extra_prefix = 0 # TX_EXTRA_NONCE_PAYMENT_ID
else:
raise ValueError("Payment ID size invalid")
lextra = len(extra_nonce)
if lextra >= 255:
raise ValueError("Nonce could be 255 bytes max")
# write it to extra
extra_buff = bytearray(3 + lextra)
extra_buff[0] = 2 # TX_EXTRA_NONCE
extra_buff[1] = lextra + 1
extra_buff[2] = extra_prefix
extra_buff[3:] = extra_nonce
state.tx.extra = extra_buff
def _get_key_for_payment_id_encryption(destinations: list, change_addr=None):
"""
Returns destination address public view key to be used for
payment id encryption.
"""
from apps.monero.xmr.addresses import addr_eq
from trezor.messages.MoneroAccountPublicAddress import MoneroAccountPublicAddress
addr = MoneroAccountPublicAddress(
spend_public_key=crypto.NULL_KEY_ENC, view_public_key=crypto.NULL_KEY_ENC
)
count = 0
for dest in destinations:
if dest.amount == 0:
continue
if change_addr and addr_eq(dest.addr, change_addr):
continue
if addr_eq(dest.addr, addr):
continue
if count > 0:
raise ValueError(
"Destinations have to have exactly one output to support encrypted payment ids"
)
addr = dest.addr
count += 1
if addr.view_public_key == crypto.NULL_KEY_ENC:
raise ValueError("Invalid key")
return addr.view_public_key
def _encrypt_payment_id(payment_id, public_key, secret_key):
"""
Encrypts payment_id hex.
Used in the transaction extra. Only recipient is able to decrypt.
"""
derivation_p = crypto.generate_key_derivation(public_key, secret_key)
derivation = bytearray(33)
derivation = crypto.encodepoint_into(derivation, derivation_p)
derivation[32] = 0x8B
hash = crypto.cn_fast_hash(derivation)
pm_copy = bytearray(payment_id)
for i in range(8):
pm_copy[i] ^= hash[i]
return pm_copy

View File

@ -0,0 +1,171 @@
"""
UTXOs are sent one by one to Trezor for processing, encoded as MoneroTransactionSourceEntry.
MoneroTransactionSourceEntry contains the actual UTXO to be spent, but also the other decoy/mixin
outputs. So all the outputs are in one list and then the `real_output` index specifies which output
is the real one to be spent.
This step computes spending secret key, key image, tx.vin[i] + HMAC, Pedersen commitment on amount.
If number of inputs is small, in-memory mode is used = alpha, pseudo_outs are kept in the Trezor.
Otherwise pseudo_outs are offloaded with HMAC, alpha is offloaded encrypted under chacha_poly with
key derived for exactly this purpose.
"""
from .state import State
from apps.monero.layout import confirms
from apps.monero.signing import RctType
from apps.monero.xmr import crypto, monero, serialize
if False:
from trezor.messages.MoneroTransactionSourceEntry import (
MoneroTransactionSourceEntry,
)
async def set_input(state: State, src_entr: MoneroTransactionSourceEntry):
from trezor.messages.MoneroTransactionSetInputAck import (
MoneroTransactionSetInputAck,
)
from apps.monero.xmr.crypto import chacha_poly
from apps.monero.xmr.serialize_messages.tx_prefix import TxinToKey
from apps.monero.signing import offloading_keys
state.current_input_index += 1
await confirms.transaction_step(
state.STEP_INP, state.current_input_index, state.input_count
)
if state.current_input_index >= state.input_count:
raise ValueError("Too many inputs")
# real_output denotes which output in outputs is the real one (ours)
if src_entr.real_output >= len(src_entr.outputs):
raise ValueError(
"real_output index %s bigger than output_keys.size() %s"
% (src_entr.real_output, len(src_entr.outputs))
)
state.summary_inputs_money += src_entr.amount
# Secrets derivation
# the UTXO's one-time address P
out_key = crypto.decodepoint(src_entr.outputs[src_entr.real_output].key.dest)
# the tx_pub of our UTXO stored inside its transaction
tx_key = crypto.decodepoint(src_entr.real_out_tx_key)
additional_keys = [
crypto.decodepoint(x) for x in src_entr.real_out_additional_tx_keys
]
"""
Calculates `derivation = Ra`, private spend key `x = H(Ra||i) + b` to be able
to spend the UTXO; and key image `I = x*H(P||i)`
"""
xi, ki, di = monero.generate_tx_spend_and_key_image_and_derivation(
state.creds,
state.subaddresses,
out_key,
tx_key,
additional_keys,
src_entr.real_output_in_tx_index,
)
state.mem_trace(1, True)
# Construct tx.vin
# If multisig is used then ki in vini should be src_entr.multisig_kLRki.ki
vini = TxinToKey(amount=src_entr.amount, k_image=crypto.encodepoint(ki))
vini.key_offsets = _absolute_output_offsets_to_relative(
[x.idx for x in src_entr.outputs]
)
if src_entr.rct:
vini.amount = 0
"""
Serialize `vini` with variant code for TxinToKey (prefix = TxinToKey.VARIANT_CODE).
The binary `vini_bin` is later sent to step 4 and 9 with its hmac,
where it is checked and directly used.
"""
vini_bin = serialize.dump_msg(vini, preallocate=64, prefix=b"\x02")
state.mem_trace(2, True)
# HMAC(T_in,i || vin_i)
hmac_vini = await offloading_keys.gen_hmac_vini(
state.key_hmac, src_entr, vini_bin, state.current_input_index
)
state.mem_trace(3, True)
# PseudoOuts commitment, alphas stored to state
pseudo_out = None
pseudo_out_hmac = None
alpha_enc = None
if state.rct_type == RctType.Simple:
alpha, pseudo_out = _gen_commitment(state, src_entr.amount)
pseudo_out = crypto.encodepoint(pseudo_out)
# In full version the alpha is encrypted and passed back for storage
pseudo_out_hmac = crypto.compute_hmac(
offloading_keys.hmac_key_txin_comm(
state.key_hmac, state.current_input_index
),
pseudo_out,
)
alpha_enc = chacha_poly.encrypt_pack(
offloading_keys.enc_key_txin_alpha(
state.key_enc, state.current_input_index
),
crypto.encodeint(alpha),
)
spend_enc = chacha_poly.encrypt_pack(
offloading_keys.enc_key_spend(state.key_enc, state.current_input_index),
crypto.encodeint(xi),
)
if state.current_input_index + 1 == state.input_count:
"""
When we finish the inputs processing, we no longer need
the precomputed subaddresses so we clear them to save memory.
"""
state.subaddresses = None
return MoneroTransactionSetInputAck(
vini=vini_bin,
vini_hmac=hmac_vini,
pseudo_out=pseudo_out,
pseudo_out_hmac=pseudo_out_hmac,
pseudo_out_alpha=alpha_enc,
spend_key=spend_enc,
)
def _gen_commitment(state: State, in_amount):
"""
Computes Pedersen commitment - pseudo outs
Here is slight deviation from the original protocol.
We want that \\sum Alpha = \\sum A_{i,j} where A_{i,j} is a mask from range proof for output i, bit j.
Previously this was computed in such a way that Alpha_{last} = \\sum A{i,j} - \\sum_{i=0}^{last-1} Alpha
But we would prefer to compute commitment before range proofs so alphas are generated completely randomly
and the last A mask is computed in this special way.
Returns pseudo_out
"""
alpha = crypto.random_scalar()
state.sumpouts_alphas = crypto.sc_add(state.sumpouts_alphas, alpha)
return alpha, crypto.gen_commitment(alpha, in_amount)
def _absolute_output_offsets_to_relative(off):
"""
Mixin outputs are specified in relative numbers. First index is absolute
and the rest is an offset of a previous one.
Helps with varint encoding size.
Example: absolute {7,11,15,20} is converted to {7,4,4,5}
"""
if len(off) == 0:
return off
off.sort()
for i in range(len(off) - 1, 0, -1):
off[i] -= off[i - 1]
return off

View File

@ -0,0 +1,42 @@
"""
Inputs in transaction need to be sorted by their key image, otherwise the
transaction is rejected. The sorting is done on host and then sent here in
the MoneroTransactionInputsPermutationRequest message.
The message contains just a simple array where each item stands for the
input's position in the transaction.
We do not do the actual sorting here (we do not store the complete input
data anyway, so we can't) we just save the array to the state and use
it later when needed.
"""
from .state import State
from apps.monero.layout.confirms import transaction_step
async def tsx_inputs_permutation(state: State, permutation: list):
from trezor.messages.MoneroTransactionInputsPermutationAck import (
MoneroTransactionInputsPermutationAck,
)
await transaction_step(state.ctx, state.STEP_PERM)
"""
Set permutation on the inputs - sorted by key image on host.
"""
if len(permutation) != state.input_count:
raise ValueError("Invalid permutation size")
_check_permutation(permutation)
state.source_permutation = permutation
state.current_input_index = -1
return MoneroTransactionInputsPermutationAck()
def _check_permutation(permutation):
for n in range(len(permutation)):
if n not in permutation:
raise ValueError("Invalid permutation")

View File

@ -0,0 +1,73 @@
"""
This step successively hashes the inputs in the order
received in the previous step.
Also hashes `pseudo_out` to the final_message.
"""
from .state import State
from apps.monero.layout import confirms
from apps.monero.signing import RctType, RsigType, offloading_keys
from apps.monero.xmr import crypto
if False:
from trezor.messages.MoneroTransactionSourceEntry import (
MoneroTransactionSourceEntry,
)
async def input_vini(
state: State,
src_entr: MoneroTransactionSourceEntry,
vini_bin: bytes,
vini_hmac: bytes,
pseudo_out: bytes,
pseudo_out_hmac: bytes,
):
from trezor.messages.MoneroTransactionInputViniAck import (
MoneroTransactionInputViniAck,
)
await confirms.transaction_step(
state.ctx, state.STEP_VINI, state.current_input_index + 1, state.input_count
)
if state.current_input_index >= state.input_count:
raise ValueError("Too many inputs")
state.current_input_index += 1
# HMAC(T_in,i || vin_i)
hmac_vini_comp = await offloading_keys.gen_hmac_vini(
state.key_hmac,
src_entr,
vini_bin,
state.source_permutation[state.current_input_index],
)
if not crypto.ct_equals(hmac_vini_comp, vini_hmac):
raise ValueError("HMAC is not correct")
"""
Incremental hasing of tx.vin[i]
"""
state.tx_prefix_hasher.buffer(vini_bin)
# in monero version >= 8 pseudo outs were moved to a different place
# bulletproofs imply version >= 8
if state.rct_type == RctType.Simple and state.rsig_type != RsigType.Bulletproof:
_hash_vini_pseudo_out(state, pseudo_out, pseudo_out_hmac)
return MoneroTransactionInputViniAck()
def _hash_vini_pseudo_out(state: State, pseudo_out: bytes, pseudo_out_hmac: bytes):
"""
Incremental hasing of pseudo output. Only applicable for simple rct.
"""
idx = state.source_permutation[state.current_input_index]
pseudo_out_hmac_comp = crypto.compute_hmac(
offloading_keys.hmac_key_txin_comm(state.key_hmac, idx), pseudo_out
)
if not crypto.ct_equals(pseudo_out_hmac, pseudo_out_hmac_comp):
raise ValueError("HMAC invalid for pseudo outs")
state.full_message_hasher.set_pseudo_out(pseudo_out)

View File

@ -0,0 +1,58 @@
"""
All inputs set. Defining range signature parameters.
If in the applicable offloading mode, generate commitment masks.
"""
from trezor import utils
from .state import State
from apps.monero.layout import confirms
from apps.monero.signing import RctType
from apps.monero.xmr import crypto
async def all_inputs_set(state: State):
state.mem_trace(0)
await confirms.transaction_step(state.ctx, state.STEP_ALL_IN)
from trezor.messages.MoneroTransactionAllInputsSetAck import (
MoneroTransactionAllInputsSetAck,
)
from trezor.messages.MoneroTransactionRsigData import MoneroTransactionRsigData
# Generate random commitment masks to be used in range proofs.
# If SimpleRCT is used the sum of the masks must match the input masks sum.
state.sumout = crypto.sc_init(0)
for i in range(state.output_count):
cur_mask = crypto.new_scalar() # new mask for each output
is_last = i + 1 == state.output_count
if is_last and state.rct_type == RctType.Simple:
# in SimpleRCT the last mask needs to be calculated as an offset of the sum
crypto.sc_sub_into(cur_mask, state.sumpouts_alphas, state.sumout)
else:
crypto.random_scalar(cur_mask)
crypto.sc_add_into(state.sumout, state.sumout, cur_mask)
state.output_masks.append(cur_mask)
if state.rct_type == RctType.Simple:
utils.ensure(
crypto.sc_eq(state.sumout, state.sumpouts_alphas), "Invalid masks sum"
) # sum check
state.sumout = crypto.sc_init(0)
rsig_data = MoneroTransactionRsigData()
resp = MoneroTransactionAllInputsSetAck(rsig_data=rsig_data)
# If range proofs are being offloaded, we send the masks to the host, which uses them
# to create the range proof. If not, we do not send any and we use them in the following step.
if state.rsig_offload:
tmp_buff = bytearray(32)
rsig_data.mask = bytearray(32 * state.output_count)
for i in range(state.output_count):
crypto.encodeint_into(tmp_buff, state.output_masks[i])
utils.memcpy(rsig_data.mask, 32 * i, tmp_buff, 0, 32)
return resp

View File

@ -0,0 +1,440 @@
"""
Output destinations are streamed one by one.
Computes destination one-time address, amount key, range proof + HMAC, out_pk, ecdh_info.
"""
import gc
from trezor import utils
from .state import State
from apps.monero import signing
from apps.monero.layout import confirms
from apps.monero.signing import RsigType, offloading_keys
from apps.monero.xmr import crypto, serialize
async def set_output(state: State, dst_entr, dst_entr_hmac, rsig_data):
state.mem_trace(0, True)
mods = utils.unimport_begin()
await confirms.transaction_step(
state.ctx, state.STEP_OUT, state.current_output_index + 1, state.output_count
)
state.mem_trace(1)
state.current_output_index += 1
state.mem_trace(2, True)
await _validate(state, dst_entr, dst_entr_hmac)
# First output - we include the size of the container into the tx prefix hasher
if state.current_output_index == 0:
state.tx_prefix_hasher.uvarint(state.output_count)
state.mem_trace(4, True)
state.output_amounts.append(dst_entr.amount)
state.summary_outs_money += dst_entr.amount
utils.unimport_end(mods)
state.mem_trace(5, True)
# Range proof first, memory intensive
rsig, mask = _range_proof(state, dst_entr.amount, rsig_data)
utils.unimport_end(mods)
state.mem_trace(6, True)
# additional tx key if applicable
additional_txkey_priv = _set_out_additional_keys(state, dst_entr)
# derivation = a*R or r*A or s*C
derivation = _set_out_derivation(state, dst_entr, additional_txkey_priv)
# amount key = H_s(derivation || i)
amount_key = crypto.derivation_to_scalar(derivation, state.current_output_index)
# one-time destination address P = H_s(derivation || i)*G + B
tx_out_key = crypto.derive_public_key(
derivation,
state.current_output_index,
crypto.decodepoint(dst_entr.addr.spend_public_key),
)
del (derivation, additional_txkey_priv)
state.mem_trace(7, True)
# Tx header prefix hashing, hmac dst_entr
tx_out_bin, hmac_vouti = await _set_out_tx_out(state, dst_entr, tx_out_key)
state.mem_trace(11, True)
out_pk_dest, out_pk_commitment, ecdh_info_bin = _get_ecdh_info_and_out_pk(
state=state,
tx_out_key=tx_out_key,
amount=dst_entr.amount,
mask=mask,
amount_key=amount_key,
)
del (dst_entr, mask, amount_key, tx_out_key)
state.mem_trace(12, True)
# Incremental hashing of the ECDH info.
# RctSigBase allows to hash only one of the (ecdh, out_pk) as they are serialized
# as whole vectors. We choose to hash ECDH first, because it saves state space.
state.full_message_hasher.set_ecdh(ecdh_info_bin)
state.mem_trace(13, True)
# output_pk_commitment is stored to the state as it is used during the signature and hashed to the
# RctSigBase later. No need to store amount, it was already stored.
state.output_pk_commitments.append(out_pk_commitment)
state.mem_trace(14, True)
from trezor.messages.MoneroTransactionSetOutputAck import (
MoneroTransactionSetOutputAck,
)
out_pk_bin = bytearray(64)
utils.memcpy(out_pk_bin, 0, out_pk_dest, 0, 32)
utils.memcpy(out_pk_bin, 32, out_pk_commitment, 0, 32)
return MoneroTransactionSetOutputAck(
tx_out=tx_out_bin,
vouti_hmac=hmac_vouti,
rsig_data=_return_rsig_data(rsig),
out_pk=out_pk_bin,
ecdh_info=ecdh_info_bin,
)
async def _validate(state: State, dst_entr, dst_entr_hmac):
if state.current_input_index + 1 != state.input_count:
raise ValueError("Invalid number of inputs")
if state.current_output_index >= state.output_count:
raise ValueError("Invalid output index")
if dst_entr.amount <= 0:
raise ValueError("Destination with wrong amount: %s" % dst_entr.amount)
# HMAC check of the destination
dst_entr_hmac_computed = await offloading_keys.gen_hmac_tsxdest(
state.key_hmac, dst_entr, state.current_output_index
)
if not crypto.ct_equals(dst_entr_hmac, dst_entr_hmac_computed):
raise ValueError("HMAC invalid")
del (dst_entr_hmac, dst_entr_hmac_computed)
state.mem_trace(3, True)
async def _set_out_tx_out(state: State, dst_entr, tx_out_key):
"""
Manually serializes TxOut(0, TxoutToKey(key)) and calculates hmac.
"""
tx_out_bin = bytearray(34)
tx_out_bin[0] = 0 # amount varint
tx_out_bin[1] = 2 # variant code TxoutToKey
crypto.encodepoint_into(tx_out_bin, tx_out_key, 2)
state.mem_trace(8)
# Tx header prefix hashing
state.tx_prefix_hasher.buffer(tx_out_bin)
state.mem_trace(9, True)
# Hmac dst_entr
hmac_vouti = await offloading_keys.gen_hmac_vouti(
state.key_hmac, dst_entr, tx_out_bin, state.current_output_index
)
state.mem_trace(10, True)
return tx_out_bin, hmac_vouti
def _range_proof(state, amount, rsig_data):
"""
Computes rangeproof
In order to optimize incremental transaction build, the mask computation is changed compared
to the official Monero code. In the official code, the input pedersen commitments are computed
after range proof in such a way summed masks for commitments (alpha) and rangeproofs (ai) are equal.
In order to save roundtrips we compute commitments randomly and then for the last rangeproof
a[63] = (\\sum_{i=0}^{num_inp}alpha_i - \\sum_{i=0}^{num_outs-1} amasks_i) - \\sum_{i=0}^{62}a_i
The range proof is incrementally hashed to the final_message.
"""
from apps.monero.xmr import range_signatures
mask = state.output_masks[state.current_output_index]
provided_rsig = None
if rsig_data and rsig_data.rsig and len(rsig_data.rsig) > 0:
provided_rsig = rsig_data.rsig
if not state.rsig_offload and provided_rsig:
raise signing.Error("Provided unexpected rsig")
# Batching
bidx = _get_rsig_batch(state, state.current_output_index)
batch_size = state.rsig_grouping[bidx]
last_in_batch = _is_last_in_batch(state, state.current_output_index, bidx)
if state.rsig_offload and provided_rsig and not last_in_batch:
raise signing.Error("Provided rsig too early")
if state.rsig_offload and last_in_batch and not provided_rsig:
raise signing.Error("Rsig expected, not provided")
# Batch not finished, skip range sig generation now
if not last_in_batch:
return None, mask
# Rangeproof
# Pedersen commitment on the value, mask from the commitment, range signature.
C, rsig = None, None
state.mem_trace("pre-rproof" if __debug__ else None, collect=True)
if state.rsig_type == RsigType.Bulletproof and not state.rsig_offload:
"""Bulletproof calculation in trezor"""
rsig = range_signatures.prove_range_bp_batch(
state.output_amounts, state.output_masks
)
state.mem_trace("post-bp" if __debug__ else None, collect=True)
# Incremental BP hashing
# BP is hashed with raw=False as hash does not contain L, R
# array sizes compared to the serialized bulletproof format
# thus direct serialization cannot be used.
state.full_message_hasher.rsig_val(rsig, True, raw=False)
state.mem_trace("post-bp-hash" if __debug__ else None, collect=True)
rsig = _dump_rsig_bp(rsig)
state.mem_trace(
"post-bp-ser, size: %s" % len(rsig) if __debug__ else None, collect=True
)
elif state.rsig_type == RsigType.Borromean and not state.rsig_offload:
"""Borromean calculation in trezor"""
C, mask, rsig = range_signatures.prove_range_borromean(amount, mask)
del range_signatures
# Incremental hashing
state.full_message_hasher.rsig_val(rsig, False, raw=True)
_check_out_commitment(state, amount, mask, C)
elif state.rsig_type == RsigType.Bulletproof and state.rsig_offload:
"""Bulletproof calculated on host, verify in trezor"""
from apps.monero.xmr.serialize_messages.tx_rsig_bulletproof import Bulletproof
# TODO this should be tested
# last_in_batch = True (see above) so this is fine
masks = state.output_masks[
1 + state.current_output_index - batch_size : 1 + state.current_output_index
]
bp_obj = serialize.parse_msg(rsig_data.rsig, Bulletproof)
rsig_data.rsig = None
# BP is hashed with raw=False as hash does not contain L, R
# array sizes compared to the serialized bulletproof format
# thus direct serialization cannot be used.
state.full_message_hasher.rsig_val(bp_obj, True, raw=False)
res = range_signatures.verify_bp(bp_obj, state.output_amounts, masks)
utils.ensure(res, "BP verification fail")
state.mem_trace("BP verified" if __debug__ else None, collect=True)
del (bp_obj, range_signatures)
elif state.rsig_type == RsigType.Borromean and state.rsig_offload:
"""Borromean offloading not supported"""
raise signing.Error(
"Unsupported rsig state (Borromean offloaded is not supported)"
)
else:
raise signing.Error("Unexpected rsig state")
state.mem_trace("rproof" if __debug__ else None, collect=True)
if state.current_output_index + 1 == state.output_count:
# output masks and amounts are not needed anymore
state.output_amounts = []
state.output_masks = []
return rsig, mask
def _dump_rsig_bp(rsig):
if len(rsig.L) > 127:
raise ValueError("Too large")
# Manual serialization as the generic purpose serialize.dump_msg_gc
# is more memory intensive which is not desired in the range proof section.
# BP: V, A, S, T1, T2, taux, mu, L, R, a, b, t
# Commitment vector V is not serialized
# Vector size under 127 thus varint occupies 1 B
buff_size = 32 * (9 + 2 * (len(rsig.L))) + 2
buff = bytearray(buff_size)
utils.memcpy(buff, 0, rsig.A, 0, 32)
utils.memcpy(buff, 32, rsig.S, 0, 32)
utils.memcpy(buff, 32 * 2, rsig.T1, 0, 32)
utils.memcpy(buff, 32 * 3, rsig.T2, 0, 32)
utils.memcpy(buff, 32 * 4, rsig.taux, 0, 32)
utils.memcpy(buff, 32 * 5, rsig.mu, 0, 32)
buff[32 * 6] = len(rsig.L)
offset = 32 * 6 + 1
for x in rsig.L:
utils.memcpy(buff, offset, x, 0, 32)
offset += 32
buff[offset] = len(rsig.R)
offset += 1
for x in rsig.R:
utils.memcpy(buff, offset, x, 0, 32)
offset += 32
utils.memcpy(buff, offset, rsig.a, 0, 32)
offset += 32
utils.memcpy(buff, offset, rsig.b, 0, 32)
offset += 32
utils.memcpy(buff, offset, rsig.t, 0, 32)
return buff
def _return_rsig_data(rsig):
if rsig is None:
return None
from trezor.messages.MoneroTransactionRsigData import MoneroTransactionRsigData
if isinstance(rsig, list):
return MoneroTransactionRsigData(rsig_parts=rsig)
else:
return MoneroTransactionRsigData(rsig=rsig)
def _get_ecdh_info_and_out_pk(state: State, tx_out_key, amount, mask, amount_key):
"""
Calculates the Pedersen commitment C = aG + bH and returns it as CtKey.
Also encodes the two items - `mask` and `amount` - into ecdh info,
so the recipient is able to reconstruct the commitment.
"""
out_pk_dest = crypto.encodepoint(tx_out_key)
out_pk_commitment = crypto.encodepoint(crypto.gen_commitment(mask, amount))
state.sumout = crypto.sc_add(state.sumout, mask)
state.output_sk_masks.append(mask)
# masking of mask and amount
ecdh_info = _ecdh_encode(mask, amount, crypto.encodeint(amount_key))
# Manual ECDH info serialization
ecdh_info_bin = bytearray(64)
utils.memcpy(ecdh_info_bin, 0, ecdh_info.mask, 0, 32)
utils.memcpy(ecdh_info_bin, 32, ecdh_info.amount, 0, 32)
gc.collect()
return out_pk_dest, out_pk_commitment, ecdh_info_bin
def _ecdh_encode(mask, amount, amount_key):
"""
Output recipients need be able to reconstruct the amount commitments.
This means the blinding factor `mask` and `amount` must be communicated
to the receiver somehow.
The mask and amount are stored as:
- mask = mask + Hs(amount_key)
- amount = amount + Hs(Hs(amount_key))
Because the receiver can derive the `amount_key` they can
easily derive both mask and amount as well.
"""
from apps.monero.xmr.serialize_messages.tx_ecdh import EcdhTuple
ecdh_info = EcdhTuple(mask=mask, amount=crypto.sc_init(amount))
amount_key_hash_single = crypto.hash_to_scalar(amount_key)
amount_key_hash_double = crypto.hash_to_scalar(
crypto.encodeint(amount_key_hash_single)
)
ecdh_info.mask = crypto.sc_add(ecdh_info.mask, amount_key_hash_single)
ecdh_info.amount = crypto.sc_add(ecdh_info.amount, amount_key_hash_double)
return _recode_ecdh(ecdh_info)
def _recode_ecdh(ecdh_info):
"""
In-place ecdh_info tuple recoding
"""
ecdh_info.mask = crypto.encodeint(ecdh_info.mask)
ecdh_info.amount = crypto.encodeint(ecdh_info.amount)
return ecdh_info
def _set_out_additional_keys(state: State, dst_entr):
"""
If needed (decided in step 1), additional tx keys are calculated
for this particular output.
"""
if not state.need_additional_txkeys:
return None
additional_txkey_priv = crypto.random_scalar()
if dst_entr.is_subaddress:
# R=r*D
additional_txkey = crypto.scalarmult(
crypto.decodepoint(dst_entr.addr.spend_public_key), additional_txkey_priv
)
else:
# R=r*G
additional_txkey = crypto.scalarmult_base(additional_txkey_priv)
state.additional_tx_public_keys.append(crypto.encodepoint(additional_txkey))
state.additional_tx_private_keys.append(additional_txkey_priv)
return additional_txkey_priv
def _set_out_derivation(state: State, dst_entr, additional_txkey_priv):
"""
Calculates derivation which is then used in the one-time address as
`P = H(derivation)*G + B`.
For change outputs the derivation equals a*R, because we know the
private view key. For others it is either `r*A` for traditional
addresses, or `s*C` for subaddresses. Both `r` and `s` are random
scalars, `s` is used in the context of subaddresses, but it's
basically the same thing.
"""
from apps.monero.xmr.addresses import addr_eq
change_addr = state.change_address()
if change_addr and addr_eq(dst_entr.addr, change_addr):
# sending change to yourself; derivation = a*R
derivation = crypto.generate_key_derivation(
state.tx_pub, state.creds.view_key_private
)
else:
# sending to the recipient; derivation = r*A (or s*C in the subaddress scheme)
if dst_entr.is_subaddress and state.need_additional_txkeys:
deriv_priv = additional_txkey_priv
else:
deriv_priv = state.tx_priv
derivation = crypto.generate_key_derivation(
crypto.decodepoint(dst_entr.addr.view_public_key), deriv_priv
)
return derivation
def _check_out_commitment(state: State, amount, mask, C):
utils.ensure(
crypto.point_eq(
C,
crypto.point_add(crypto.scalarmult_base(mask), crypto.scalarmult_h(amount)),
),
"OutC fail",
)
def _is_last_in_batch(state: State, idx, bidx):
"""
Returns true if the current output is last in the rsig batch
"""
batch_size = state.rsig_grouping[bidx]
return (idx - sum(state.rsig_grouping[:bidx])) + 1 == batch_size
def _get_rsig_batch(state: State, idx):
"""
Returns index of the current rsig batch
"""
r = 0
c = 0
while c < idx + 1:
c += state.rsig_grouping[r]
r += 1
return r - 1

View File

@ -0,0 +1,171 @@
"""
All outputs were set in this phase. This step serializes tx pub keys
into the tx extra field and then hashes it into the prefix hash.
The prefix hash is then complete.
"""
import gc
from trezor import utils
from .state import State
from apps.monero.layout import confirms
from apps.monero.signing import get_monero_rct_type
from apps.monero.xmr import crypto
async def all_outputs_set(state: State):
state.mem_trace(0)
await confirms.transaction_step(state.ctx, state.STEP_ALL_OUT)
state.mem_trace(1)
_validate(state)
state.mem_trace(2)
_set_tx_extra(state)
# tx public keys not needed anymore
state.additional_tx_public_keys = None
state.tx_pub = None
gc.collect()
state.mem_trace(3)
# Completes the transaction prefix hash by including extra
_set_tx_prefix(state)
extra_b = state.tx.extra
state.tx = None
gc.collect()
state.mem_trace(4)
# In the multisig mode here needs to be a check whether currently computed
# transaction prefix matches expected transaction prefix sent in the
# init step.
from trezor.messages.MoneroRingCtSig import MoneroRingCtSig
from trezor.messages.MoneroTransactionAllOutSetAck import (
MoneroTransactionAllOutSetAck,
)
# Initializes RCTsig structure (fee, tx prefix hash, type)
rv_pb = MoneroRingCtSig(
txn_fee=state.fee,
message=state.tx_prefix_hash,
rv_type=get_monero_rct_type(state.rct_type, state.rsig_type),
)
_out_pk(state)
state.full_message_hasher.rctsig_base_done()
state.current_output_index = -1
state.current_input_index = -1
state.full_message = state.full_message_hasher.get_digest()
state.full_message_hasher = None
return MoneroTransactionAllOutSetAck(
extra=extra_b,
tx_prefix_hash=state.tx_prefix_hash,
rv=rv_pb,
full_message_hash=state.full_message,
)
def _validate(state: State):
from apps.monero.signing import RctType
if state.current_output_index + 1 != state.output_count:
raise ValueError("Invalid out num")
# Test if \sum Alpha == \sum A
if state.rct_type == RctType.Simple:
utils.ensure(crypto.sc_eq(state.sumout, state.sumpouts_alphas))
# Fee test
if state.fee != (state.summary_inputs_money - state.summary_outs_money):
raise ValueError(
"Fee invalid %s vs %s, out: %s"
% (
state.fee,
state.summary_inputs_money - state.summary_outs_money,
state.summary_outs_money,
)
)
if state.summary_outs_money > state.summary_inputs_money:
raise ValueError(
"Transaction inputs money (%s) less than outputs money (%s)"
% (state.summary_inputs_money, state.summary_outs_money)
)
def _set_tx_extra(state: State):
"""
Sets tx public keys into transaction's extra.
"""
state.tx.extra = _add_tx_pub_key_to_extra(state.tx.extra, state.tx_pub)
if state.need_additional_txkeys:
state.tx.extra = _add_additional_tx_pub_keys_to_extra(
state.tx.extra, state.additional_tx_public_keys
)
def _set_tx_prefix(state: State):
"""
Adds `extra` to the tx_prefix_hash, which is the last needed item,
so the tx_prefix_hash is now complete and can be incorporated
into full_message_hash.
"""
# Serializing "extra" type as BlobType.
# uvarint(len(extra)) || extra
state.tx_prefix_hasher.uvarint(len(state.tx.extra))
state.tx_prefix_hasher.buffer(state.tx.extra)
state.tx_prefix_hash = state.tx_prefix_hasher.get_digest()
state.tx_prefix_hasher = None
state.full_message_hasher.set_message(state.tx_prefix_hash)
def _add_tx_pub_key_to_extra(tx_extra, pub_key):
"""
Adds public key to the extra
"""
to_add = bytearray(33)
to_add[0] = 1 # TX_EXTRA_TAG_PUBKEY
crypto.encodepoint_into(memoryview(to_add)[1:], pub_key)
return tx_extra + to_add
def _add_additional_tx_pub_keys_to_extra(tx_extra, pub_keys):
"""
Adds all additional tx public keys to the extra buffer
"""
from apps.monero.xmr.serialize import int_serialize
# format: variant_tag (0x4) | array len varint | 32B | 32B | ...
num_keys = len(pub_keys)
len_size = int_serialize.uvarint_size(num_keys)
buffer = bytearray(1 + len_size + 32 * num_keys)
buffer[0] = 0x4 # TX_EXTRA_TAG_ADDITIONAL_PUBKEYS
int_serialize.dump_uvarint_b_into(num_keys, buffer, 1) # uvarint(num_keys)
offset = 1 + len_size
for idx in range(num_keys):
buffer[offset : offset + 32] = pub_keys[idx]
offset += 32
tx_extra += buffer
return tx_extra
def _out_pk(state: State):
"""
Hashes out_pk into the full message.
"""
if state.output_count != len(state.output_pk_commitments):
raise ValueError("Invalid number of ecdh")
for out in state.output_pk_commitments:
state.full_message_hasher.set_out_pk_commitment(out)

View File

@ -0,0 +1,192 @@
"""
Generates a MLSAG signature for one input.
"""
import gc
from trezor import utils
from .state import State
from apps.monero.layout import confirms
from apps.monero.signing import RctType
from apps.monero.xmr import crypto, serialize
if False:
from trezor.messages.MoneroTransactionSourceEntry import (
MoneroTransactionSourceEntry,
)
async def sign_input(
state: State,
src_entr: MoneroTransactionSourceEntry,
vini_bin: bytes,
vini_hmac: bytes,
pseudo_out: bytes,
pseudo_out_hmac: bytes,
pseudo_out_alpha_enc: bytes,
spend_enc: bytes,
):
"""
:param state: transaction state
:param src_entr: Source entry
:param vini_bin: tx.vin[i] for the transaction. Contains key image, offsets, amount (usually zero)
:param vini_hmac: HMAC for the tx.vin[i] as returned from Trezor
:param pseudo_out: Pedersen commitment for the current input, uses pseudo_out_alpha
as a mask. Only applicable for RCTTypeSimple.
:param pseudo_out_hmac: HMAC for pseudo_out
:param pseudo_out_alpha_enc: alpha mask used in pseudo_out, only applicable for RCTTypeSimple. Encrypted.
:param spend_enc: one time address spending private key. Encrypted.
:return: Generated signature MGs[i]
"""
from apps.monero.signing import offloading_keys
await confirms.transaction_step(
state.ctx, state.STEP_SIGN, state.current_input_index + 1, state.input_count
)
state.current_input_index += 1
if state.current_input_index >= state.input_count:
raise ValueError("Invalid inputs count")
if state.rct_type == RctType.Simple and pseudo_out is None:
raise ValueError("SimpleRCT requires pseudo_out but none provided")
if state.rct_type == RctType.Simple and pseudo_out_alpha_enc is None:
raise ValueError("SimpleRCT requires pseudo_out's mask but none provided")
if state.current_input_index >= 1 and not state.rct_type == RctType.Simple:
raise ValueError("Two and more inputs must imply SimpleRCT")
input_position = state.source_permutation[state.current_input_index]
# Check input's HMAC
vini_hmac_comp = await offloading_keys.gen_hmac_vini(
state.key_hmac, src_entr, vini_bin, input_position
)
if not crypto.ct_equals(vini_hmac_comp, vini_hmac):
raise ValueError("HMAC is not correct")
gc.collect()
state.mem_trace(1)
if state.rct_type == RctType.Simple:
# both pseudo_out and its mask were offloaded so we need to
# validate pseudo_out's HMAC and decrypt the alpha
pseudo_out_hmac_comp = crypto.compute_hmac(
offloading_keys.hmac_key_txin_comm(state.key_hmac, input_position),
pseudo_out,
)
if not crypto.ct_equals(pseudo_out_hmac_comp, pseudo_out_hmac):
raise ValueError("HMAC is not correct")
gc.collect()
state.mem_trace(2)
from apps.monero.xmr.crypto import chacha_poly
pseudo_out_alpha = crypto.decodeint(
chacha_poly.decrypt_pack(
offloading_keys.enc_key_txin_alpha(state.key_enc, input_position),
bytes(pseudo_out_alpha_enc),
)
)
pseudo_out_c = crypto.decodepoint(pseudo_out)
# Spending secret
from apps.monero.xmr.crypto import chacha_poly
from apps.monero.xmr.serialize_messages.ct_keys import CtKey
spend_key = crypto.decodeint(
chacha_poly.decrypt_pack(
offloading_keys.enc_key_spend(state.key_enc, input_position),
bytes(spend_enc),
)
)
gc.collect()
state.mem_trace(3)
# Basic setup, sanity check
index = src_entr.real_output
input_secret_key = CtKey(dest=spend_key, mask=crypto.decodeint(src_entr.mask))
kLRki = None # for multisig: src_entr.multisig_kLRki
# Private key correctness test
utils.ensure(
crypto.point_eq(
crypto.decodepoint(src_entr.outputs[src_entr.real_output].key.dest),
crypto.scalarmult_base(input_secret_key.dest),
),
"Real source entry's destination does not equal spend key's",
)
utils.ensure(
crypto.point_eq(
crypto.decodepoint(src_entr.outputs[src_entr.real_output].key.commitment),
crypto.gen_commitment(input_secret_key.mask, src_entr.amount),
),
"Real source entry's mask does not equal spend key's",
)
gc.collect()
state.mem_trace(4)
from apps.monero.xmr import mlsag
if state.rct_type == RctType.Simple:
ring_pubkeys = [x.key for x in src_entr.outputs]
mg = mlsag.generate_mlsag_simple(
state.full_message,
ring_pubkeys,
input_secret_key,
pseudo_out_alpha,
pseudo_out_c,
kLRki,
index,
)
else:
# Full RingCt, only one input
txn_fee_key = crypto.scalarmult_h(state.fee)
ring_pubkeys = [[x.key] for x in src_entr.outputs]
mg = mlsag.generate_mlsag_full(
state.full_message,
ring_pubkeys,
[input_secret_key],
state.output_sk_masks,
state.output_pk_commitments,
kLRki,
index,
txn_fee_key,
)
gc.collect()
state.mem_trace(5)
# Encode
mgs = _recode_msg([mg])
gc.collect()
state.mem_trace(6)
from trezor.messages.MoneroTransactionSignInputAck import (
MoneroTransactionSignInputAck,
)
return MoneroTransactionSignInputAck(
signature=serialize.dump_msg_gc(mgs[0], preallocate=488)
)
def _recode_msg(mgs):
"""
Recodes MGs signatures from raw forms to bytearrays so it works with serialization
"""
for idx in range(len(mgs)):
mgs[idx].cc = crypto.encodeint(mgs[idx].cc)
if hasattr(mgs[idx], "II") and mgs[idx].II:
for i in range(len(mgs[idx].II)):
mgs[idx].II[i] = crypto.encodepoint(mgs[idx].II[i])
for i in range(len(mgs[idx].ss)):
for j in range(len(mgs[idx].ss[i])):
mgs[idx].ss[i][j] = crypto.encodeint(mgs[idx].ss[i][j])
return mgs

View File

@ -0,0 +1,43 @@
"""
Final message, signatures were already returned in the previous step.
Here we return private tx keys in encrypted form using transaction specific key,
derived from tx hash and the private spend key. The key is deterministic,
so we can recover it just from the transaction and the spend key.
The private tx keys are used in other numerous Monero features.
"""
from trezor.messages.MoneroTransactionFinalAck import MoneroTransactionFinalAck
from .state import State
from apps.monero.xmr import crypto
from apps.monero.xmr.crypto import chacha_poly
async def final_msg(state: State):
tx_key, salt, rand_mult = _compute_tx_key(
state.creds.spend_key_private, state.tx_prefix_hash
)
key_buff = crypto.encodeint(state.tx_priv) + b"".join(
[crypto.encodeint(x) for x in state.additional_tx_private_keys]
)
tx_enc_keys = chacha_poly.encrypt_pack(tx_key, key_buff)
return MoneroTransactionFinalAck(
cout_key=None, salt=salt, rand_mult=rand_mult, tx_enc_keys=tx_enc_keys
)
def _compute_tx_key(spend_key_private, tx_prefix_hash):
salt = crypto.random_bytes(32)
rand_mult_num = crypto.random_scalar()
rand_mult = crypto.encodeint(rand_mult_num)
rand_inp = crypto.sc_add(spend_key_private, rand_mult_num)
passwd = crypto.keccak_2hash(crypto.encodeint(rand_inp) + tx_prefix_hash)
tx_key = crypto.compute_hmac(salt, passwd)
return tx_key, salt, rand_mult

View File

View File

@ -0,0 +1,86 @@
from trezor.crypto import monero as tcry
from apps.monero.xmr.networks import NetworkTypes, net_version
def addr_to_hash(addr):
"""
Creates hashable address representation
"""
return bytes(addr.spend_public_key + addr.view_public_key)
def encode_addr(version, spend_pub, view_pub, payment_id=None):
"""
Encodes public keys as versions
"""
buf = spend_pub + view_pub
if payment_id:
buf += bytes(payment_id)
return tcry.xmr_base58_addr_encode_check(ord(version), bytes(buf))
def decode_addr(addr):
"""
Given address, get version and public spend and view keys.
"""
d, version = tcry.xmr_base58_addr_decode_check(bytes(addr))
pub_spend_key = d[0:32]
pub_view_key = d[32:64]
return version, pub_spend_key, pub_view_key
def public_addr_encode(pub_addr, is_sub=False, net=NetworkTypes.MAINNET):
"""
Encodes public address to Monero address
"""
net_ver = net_version(net, is_sub)
return encode_addr(net_ver, pub_addr.spend_public_key, pub_addr.view_public_key)
def classify_subaddresses(tx_dests, change_addr):
"""
Classify destination subaddresses
"""
num_stdaddresses = 0
num_subaddresses = 0
single_dest_subaddress = None
addr_set = set()
for tx in tx_dests:
if change_addr and addr_eq(change_addr, tx.addr):
continue
addr_hashed = addr_to_hash(tx.addr)
if addr_hashed in addr_set:
continue
addr_set.add(addr_hashed)
if tx.is_subaddress:
num_subaddresses += 1
single_dest_subaddress = tx.addr
else:
num_stdaddresses += 1
return num_stdaddresses, num_subaddresses, single_dest_subaddress
def addr_eq(a, b):
return (
a.spend_public_key == b.spend_public_key
and a.view_public_key == b.view_public_key
)
def get_change_addr_idx(outputs, change_dts):
"""
Returns ID of the change output from the change_dts and outputs
"""
if change_dts is None:
return None
change_idx = None
for idx, dst in enumerate(outputs):
if (
change_dts.amount
and change_dts.amount == dst.amount
and addr_eq(change_dts.addr, dst.addr)
):
change_idx = idx
return change_idx

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,45 @@
from apps.monero.xmr import crypto
from apps.monero.xmr.addresses import encode_addr
from apps.monero.xmr.networks import NetworkTypes, net_version
class AccountCreds:
"""
Stores account private keys
"""
def __init__(
self,
view_key_private=None,
spend_key_private=None,
view_key_public=None,
spend_key_public=None,
address=None,
network_type=NetworkTypes.MAINNET,
):
self.view_key_private = view_key_private
self.view_key_public = view_key_public
self.spend_key_private = spend_key_private
self.spend_key_public = spend_key_public
self.address = address
self.network_type = network_type
@classmethod
def new_wallet(
cls, priv_view_key, priv_spend_key, network_type=NetworkTypes.MAINNET
):
pub_view_key = crypto.scalarmult_base(priv_view_key)
pub_spend_key = crypto.scalarmult_base(priv_spend_key)
addr = encode_addr(
net_version(network_type),
crypto.encodepoint(pub_spend_key),
crypto.encodepoint(pub_view_key),
)
return cls(
view_key_private=priv_view_key,
spend_key_private=priv_spend_key,
view_key_public=pub_view_key,
spend_key_public=pub_spend_key,
address=addr,
network_type=network_type,
)

View File

@ -0,0 +1,299 @@
# Author: Dusan Klinec, ph4r05, 2018
#
# Resources:
# https://cr.yp.to
# https://github.com/monero-project/mininero
# https://godoc.org/github.com/agl/ed25519/edwards25519
# https://tools.ietf.org/html/draft-josefsson-eddsa-ed25519-00#section-4
# https://github.com/monero-project/research-lab
from trezor.crypto import hmac, monero as tcry, random
from trezor.crypto.hashlib import sha3_256
NULL_KEY_ENC = b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
random_bytes = random.bytes
ct_equals = tcry.ct_equals
def keccak_factory(data=None):
return sha3_256(data=data, keccak=True)
get_keccak = keccak_factory
keccak_hash = tcry.xmr_fast_hash
keccak_hash_into = tcry.xmr_fast_hash
def keccak_2hash(inp):
return keccak_hash(keccak_hash(inp))
def compute_hmac(key, msg=None):
h = hmac.new(key, msg=msg, digestmod=keccak_factory)
return h.digest()
#
# EC
#
new_point = tcry.ge25519_set_neutral
def new_scalar():
return tcry.init256_modm(0)
decodepoint = tcry.ge25519_unpack_vartime
decodepoint_into = tcry.ge25519_unpack_vartime
encodepoint = tcry.ge25519_pack
encodepoint_into = tcry.ge25519_pack
decodeint = tcry.unpack256_modm
decodeint_into_noreduce = tcry.unpack256_modm_noreduce
decodeint_into = tcry.unpack256_modm
encodeint = tcry.pack256_modm
encodeint_into = tcry.pack256_modm
check_ed25519point = tcry.ge25519_check
scalarmult_base = tcry.ge25519_scalarmult_base
scalarmult_base_into = tcry.ge25519_scalarmult_base
scalarmult = tcry.ge25519_scalarmult
scalarmult_into = tcry.ge25519_scalarmult
point_add = tcry.ge25519_add
point_add_into = tcry.ge25519_add
point_sub = tcry.ge25519_sub
point_sub_into = tcry.ge25519_sub
point_eq = tcry.ge25519_eq
point_double = tcry.ge25519_double
point_double_into = tcry.ge25519_double
point_mul8 = tcry.ge25519_mul8
point_mul8_into = tcry.ge25519_mul8
INV_EIGHT = b"\x79\x2f\xdc\xe2\x29\xe5\x06\x61\xd0\xda\x1c\x7d\xb3\x9d\xd3\x07\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x06"
INV_EIGHT_SC = decodeint(INV_EIGHT)
def sc_inv_eight():
return INV_EIGHT_SC
#
# Zmod(order), scalar values field
#
def sc_0():
return tcry.init256_modm(0)
def sc_0_into(r):
return tcry.init256_modm(r, 0)
def sc_init(x):
if x >= (1 << 64):
raise ValueError("Initialization works up to 64-bit only")
return tcry.init256_modm(x)
def sc_init_into(r, x):
if x >= (1 << 64):
raise ValueError("Initialization works up to 64-bit only")
return tcry.init256_modm(r, x)
sc_get64 = tcry.get256_modm
sc_check = tcry.check256_modm
check_sc = tcry.check256_modm
sc_add = tcry.add256_modm
sc_add_into = tcry.add256_modm
sc_sub = tcry.sub256_modm
sc_sub_into = tcry.sub256_modm
sc_mul = tcry.mul256_modm
sc_mul_into = tcry.mul256_modm
def sc_isnonzero(c):
"""
Returns true if scalar is non-zero
"""
return not tcry.iszero256_modm(c)
sc_eq = tcry.eq256_modm
sc_mulsub = tcry.mulsub256_modm
sc_mulsub_into = tcry.mulsub256_modm
sc_muladd = tcry.muladd256_modm
sc_muladd_into = tcry.muladd256_modm
sc_inv_into = tcry.inv256_modm
def random_scalar(r=None):
return tcry.xmr_random_scalar(r if r is not None else new_scalar())
#
# GE - ed25519 group
#
def ge25519_double_scalarmult_base_vartime(a, A, b):
"""
void ge25519_double_scalarmult_vartime(ge25519 *r, const ge25519 *p1, const bignum256modm s1, const bignum256modm s2);
r = a * A + b * B
"""
R = tcry.ge25519_double_scalarmult_vartime(A, a, b)
return R
ge25519_double_scalarmult_vartime2 = tcry.xmr_add_keys3
def identity(byte_enc=False):
idd = tcry.ge25519_set_neutral()
return idd if not byte_enc else encodepoint(idd)
identity_into = tcry.ge25519_set_neutral
"""
https://www.imperialviolet.org/2013/12/25/elligator.html
http://elligator.cr.yp.to/
http://elligator.cr.yp.to/elligator-20130828.pdf
"""
ge_frombytes_vartime_check = tcry.ge25519_check
#
# Monero specific
#
cn_fast_hash = keccak_hash
def hash_to_scalar(data, length=None):
"""
H_s(P)
"""
dt = data[:length] if length else data
return tcry.xmr_hash_to_scalar(dt)
def hash_to_scalar_into(r, data, length=None):
dt = data[:length] if length else data
return tcry.xmr_hash_to_scalar(r, dt)
"""
H_p(buf)
Code adapted from MiniNero: https://github.com/monero-project/mininero
https://github.com/monero-project/research-lab/blob/master/whitepaper/ge_fromfe_writeup/ge_fromfe.pdf
http://archive.is/yfINb
"""
hash_to_point = tcry.xmr_hash_to_ec
hash_to_point_into = tcry.xmr_hash_to_ec
#
# XMR
#
xmr_H = tcry.ge25519_set_h
def scalarmult_h(i):
return scalarmult(xmr_H(), sc_init(i) if isinstance(i, int) else i)
add_keys2 = tcry.xmr_add_keys2_vartime
add_keys2_into = tcry.xmr_add_keys2_vartime
add_keys3 = tcry.xmr_add_keys3_vartime
add_keys3_into = tcry.xmr_add_keys3_vartime
gen_commitment = tcry.xmr_gen_c
def generate_key_derivation(pub, sec):
"""
Key derivation: 8*(key2*key1)
"""
sc_check(sec) # checks that the secret key is uniform enough...
ge_frombytes_vartime_check(pub)
return tcry.xmr_generate_key_derivation(pub, sec)
def derivation_to_scalar(derivation, output_index):
"""
H_s(derivation || varint(output_index))
"""
check_ed25519point(derivation)
return tcry.xmr_derivation_to_scalar(derivation, output_index)
def derive_public_key(derivation, output_index, B):
"""
H_s(derivation || varint(output_index))G + B
"""
ge_frombytes_vartime_check(B) # check some conditions on the point
check_ed25519point(B)
return tcry.xmr_derive_public_key(derivation, output_index, B)
def derive_secret_key(derivation, output_index, base):
"""
base + H_s(derivation || varint(output_index))
"""
sc_check(base)
return tcry.xmr_derive_private_key(derivation, output_index, base)
def get_subaddress_secret_key(secret_key, major=0, minor=0):
"""
Builds subaddress secret key from the subaddress index
Hs(SubAddr || a || index_major || index_minor)
"""
return tcry.xmr_get_subaddress_secret_key(major, minor, secret_key)
#
# Repr invariant
#
def generate_signature(data, priv):
"""
Generate EC signature
crypto_ops::generate_signature(const hash &prefix_hash, const public_key &pub, const secret_key &sec, signature &sig)
"""
pub = scalarmult_base(priv)
k = random_scalar()
comm = scalarmult_base(k)
buff = data + encodepoint(pub) + encodepoint(comm)
c = hash_to_scalar(buff)
r = sc_mulsub(priv, c, k)
return c, r, pub
def check_signature(data, c, r, pub):
"""
EC signature verification
"""
check_ed25519point(pub)
if sc_check(c) != 0 or sc_check(r) != 0:
raise ValueError("Signature error")
tmp2 = point_add(scalarmult(pub, c), scalarmult_base(r))
buff = data + encodepoint(pub) + encodepoint(tmp2)
tmp_c = hash_to_scalar(buff)
res = sc_sub(tmp_c, c)
return not sc_isnonzero(res)

View File

@ -0,0 +1,40 @@
from trezor.crypto import chacha20poly1305 as ChaCha20Poly1305, monero, random
def encrypt(key, plaintext, associated_data=None):
"""
Uses ChaCha20Poly1305 for encryption
"""
nonce = random.bytes(12)
cipher = ChaCha20Poly1305(key, nonce)
if associated_data:
cipher.auth(associated_data)
ciphertext = cipher.encrypt(plaintext)
tag = cipher.finish()
return nonce, ciphertext + tag, b""
def decrypt(key, iv, ciphertext, tag=None, associated_data=None):
"""
ChaCha20Poly1305 decryption
"""
cipher = ChaCha20Poly1305(key, iv)
if associated_data:
cipher.auth(associated_data)
exp_tag, ciphertext = ciphertext[-16:], ciphertext[:-16]
plaintext = cipher.decrypt(ciphertext)
tag = cipher.finish()
if not monero.ct_equals(tag, exp_tag):
raise ValueError("tag invalid")
return plaintext
def encrypt_pack(key, plaintext, associated_data=None):
b = encrypt(key, plaintext, associated_data)
return b[0] + b[1]
def decrypt_pack(key, ciphertext):
cp = memoryview(ciphertext)
return decrypt(key, cp[:12], cp[12:], None)

View File

@ -0,0 +1,27 @@
from trezor.utils import HashWriter
from apps.monero.xmr import crypto
from apps.monero.xmr.serialize import int_serialize
class KeccakXmrArchive:
def __init__(self, ctx=None):
self.kwriter = get_keccak_writer(ctx)
def get_digest(self):
return self.kwriter.get_digest()
def buffer(self, buf):
return self.kwriter.write(buf)
def uvarint(self, i):
int_serialize.dump_uvarint(self.kwriter, i)
def uint(self, i, width):
int_serialize.dump_uint(self.kwriter, i, width)
def get_keccak_writer(ctx=None):
if ctx is None:
ctx = crypto.get_keccak()
return HashWriter(ctx)

View File

@ -0,0 +1,111 @@
from apps.monero.xmr import crypto, monero
from apps.monero.xmr.serialize.int_serialize import dump_uvarint_b
def compute_hash(rr):
kck = crypto.get_keccak()
kck.update(rr.out_key)
kck.update(rr.tx_pub_key)
if rr.additional_tx_pub_keys:
for x in rr.additional_tx_pub_keys:
kck.update(x)
kck.update(dump_uvarint_b(rr.internal_output_index))
return kck.digest()
def export_key_image(creds, subaddresses, td):
out_key = crypto.decodepoint(td.out_key)
tx_pub_key = crypto.decodepoint(td.tx_pub_key)
additional_tx_pub_keys = [crypto.decodepoint(x) for x in td.additional_tx_pub_keys]
ki, sig = _export_key_image(
creds,
subaddresses,
out_key,
tx_pub_key,
additional_tx_pub_keys,
td.internal_output_index,
)
return ki, sig
def _export_key_image(
creds, subaddresses, pkey, tx_pub_key, additional_tx_pub_keys, out_idx, test=True
):
"""
Generates key image for the TXO + signature for the key image
"""
r = monero.generate_tx_spend_and_key_image_and_derivation(
creds, subaddresses, pkey, tx_pub_key, additional_tx_pub_keys, out_idx
)
xi, ki, recv_derivation = r[:3]
phash = crypto.encodepoint(ki)
sig = _generate_ring_signature(phash, ki, [pkey], xi, 0, test)
return ki, sig
def _generate_ring_signature(prefix_hash, image, pubs, sec, sec_idx, test=False):
"""
Generates ring signature with key image.
void crypto_ops::generate_ring_signature()
"""
from trezor.utils import memcpy
if test:
t = crypto.scalarmult_base(sec)
if not crypto.point_eq(t, pubs[sec_idx]):
raise ValueError("Invalid sec key")
k_i = monero.generate_key_image(crypto.encodepoint(pubs[sec_idx]), sec)
if not crypto.point_eq(k_i, image):
raise ValueError("Key image invalid")
for k in pubs:
crypto.ge_frombytes_vartime_check(k)
buff_off = len(prefix_hash)
buff = bytearray(buff_off + 2 * 32 * len(pubs))
memcpy(buff, 0, prefix_hash, 0, buff_off)
mvbuff = memoryview(buff)
sum = crypto.sc_0()
k = crypto.sc_0()
sig = []
for i in range(len(pubs)):
sig.append([crypto.sc_0(), crypto.sc_0()]) # c, r
for i in range(len(pubs)):
if i == sec_idx:
k = crypto.random_scalar()
tmp3 = crypto.scalarmult_base(k)
crypto.encodepoint_into(mvbuff[buff_off : buff_off + 32], tmp3)
buff_off += 32
tmp3 = crypto.hash_to_point(crypto.encodepoint(pubs[i]))
tmp2 = crypto.scalarmult(tmp3, k)
crypto.encodepoint_into(mvbuff[buff_off : buff_off + 32], tmp2)
buff_off += 32
else:
sig[i] = [crypto.random_scalar(), crypto.random_scalar()]
tmp3 = pubs[i]
tmp2 = crypto.ge25519_double_scalarmult_base_vartime(
sig[i][0], tmp3, sig[i][1]
)
crypto.encodepoint_into(mvbuff[buff_off : buff_off + 32], tmp2)
buff_off += 32
tmp3 = crypto.hash_to_point(crypto.encodepoint(tmp3))
tmp2 = crypto.ge25519_double_scalarmult_vartime2(
sig[i][1], tmp3, sig[i][0], image
)
crypto.encodepoint_into(mvbuff[buff_off : buff_off + 32], tmp2)
buff_off += 32
sum = crypto.sc_add(sum, sig[i][0])
h = crypto.hash_to_scalar(buff)
sig[sec_idx][0] = crypto.sc_sub(h, sum)
sig[sec_idx][1] = crypto.sc_mulsub(sig[sec_idx][0], sec, k)
return sig

View File

@ -0,0 +1,320 @@
"""
Multilayer Linkable Spontaneous Anonymous Group (MLSAG)
Optimized versions with incremental hashing.
Both Simple and Full Monero tx types are supported.
See https://eprint.iacr.org/2015/1098.pdf for details.
Also explained in From Zero to Monero section 3.3 and 5.
----------
Please note, that the MLSAG code is written in a generic manner,
where it is designed for multiple public keys (aka inputs). In another
words, MLSAG should be used to sign multiple inputs, but that is currently
not the case of Monero, where the inputs are signed one by one.
So the public keys matrix has always two rows (one for public keys,
one for commitments), although the algorithm is designed for `n` rows.
This has one unfortunate effect where `rows` is always equal to 2 and
dsRows always to 1, but the algorithm is still written as the numbers
are arbitrary. That's why there are loops such as `for i in range(dsRows)`
where it is run only once currently.
----------
Also note, that the matrix of public keys is indexed by columns first.
This is because the code was ported from the official Monero client,
which is written in C++ and where it does have some memory advantages.
For ring size = 3 and one input the matrix M will look like this:
|------------------------|------------------------|------------------------|
| public key 0 | public key 1 | public key 2 |
| cmt 0 - pseudo_out cmt | cmt 1 - pseudo_out cmt | cmt 2 - pseudo_out cmt |
and `sk` is equal to:
|--------------|-----------------------------------------------------|
| private key* | input secret key's mask - pseudo_out's mask (alpha) |
* corresponding to one of the public keys (`index` denotes which one)
----------
Mostly ported from official Monero client, but also inspired by Mininero.
Author: Dusan Klinec, ph4r05, 2018
"""
from apps.monero.xmr import crypto
def generate_mlsag_full(
message, pubs, in_sk, out_sk_mask, out_pk_commitments, kLRki, index, txn_fee_key
):
cols = len(pubs)
if cols == 0:
raise ValueError("Empty pubs")
rows = len(pubs[0])
if rows == 0:
raise ValueError("Empty pub row")
for i in range(cols):
if len(pubs[i]) != rows:
raise ValueError("pub is not rectangular")
if len(in_sk) != rows:
raise ValueError("Bad inSk size")
if len(out_sk_mask) != len(out_pk_commitments):
raise ValueError("Bad outsk/putpk size")
sk = _key_vector(rows + 1)
M = _key_matrix(rows + 1, cols)
for i in range(rows + 1):
sk[i] = crypto.sc_0()
for i in range(cols):
M[i][rows] = crypto.identity()
for j in range(rows):
M[i][j] = crypto.decodepoint(pubs[i][j].dest)
M[i][rows] = crypto.point_add(
M[i][rows], crypto.decodepoint(pubs[i][j].commitment)
)
sk[rows] = crypto.sc_0()
for j in range(rows):
sk[j] = in_sk[j].dest
sk[rows] = crypto.sc_add(sk[rows], in_sk[j].mask) # add masks in last row
for i in range(cols):
for j in range(len(out_pk_commitments)):
M[i][rows] = crypto.point_sub(
M[i][rows], crypto.decodepoint(out_pk_commitments[j])
) # subtract output Ci's in last row
# Subtract txn fee output in last row
M[i][rows] = crypto.point_sub(M[i][rows], txn_fee_key)
for j in range(len(out_pk_commitments)):
sk[rows] = crypto.sc_sub(
sk[rows], out_sk_mask[j]
) # subtract output masks in last row
return generate_mlsag(message, M, sk, kLRki, index, rows)
def generate_mlsag_simple(message, pubs, in_sk, a, cout, kLRki, index):
"""
MLSAG for RctType.Simple
:param message: the full message to be signed (actually its hash)
:param pubs: vector of MoneroRctKey; this forms the ring; point values in encoded form; (dest, mask) = (P, C)
:param in_sk: CtKey; spending private key with input commitment mask (original); better_name: input_secret_key
:param a: mask from the pseudo output commitment; better name: pseudo_out_alpha
:param cout: pseudo output commitment; point, decoded; better name: pseudo_out_c
:param kLRki: used only in multisig, currently not implemented
:param index: specifies corresponding public key to the `in_sk` in the pubs array
:return: MgSig
"""
# Monero signs inputs separately, so `rows` always equals 2 (pubkey, commitment)
# and `dsRows` is always 1 (denotes where the pubkeys "end")
rows = 2
dsRows = 1
cols = len(pubs)
if cols == 0:
raise ValueError("Empty pubs")
sk = _key_vector(rows)
M = _key_matrix(rows, cols)
sk[0] = in_sk.dest
sk[1] = crypto.sc_sub(in_sk.mask, a)
for i in range(cols):
M[i][0] = crypto.decodepoint(pubs[i].dest)
M[i][1] = crypto.point_sub(crypto.decodepoint(pubs[i].commitment), cout)
return generate_mlsag(message, M, sk, kLRki, index, dsRows)
def gen_mlsag_assert(pk, xx, kLRki, index, dsRows):
"""
Conditions check
"""
cols = len(pk)
if cols <= 1:
raise ValueError("Cols == 1")
if index >= cols:
raise ValueError("Index out of range")
rows = len(pk[0])
if rows == 0:
raise ValueError("Empty pk")
for i in range(cols):
if len(pk[i]) != rows:
raise ValueError("pk is not rectangular")
if len(xx) != rows:
raise ValueError("Bad xx size")
if dsRows > rows:
raise ValueError("Bad dsRows size")
if kLRki and dsRows != 1:
raise ValueError("Multisig requires exactly 1 dsRows")
if kLRki:
raise NotImplementedError("Multisig not implemented")
return rows, cols
def generate_first_c_and_key_images(
message, rv, pk, xx, kLRki, index, dsRows, rows, cols
):
"""
MLSAG computation - the part with secret keys
:param message: the full message to be signed (actually its hash)
:param rv: MgSig
:param pk: matrix of public keys and commitments
:param xx: input secret array composed of a private key and commitment mask
:param kLRki: used only in multisig, currently not implemented
:param index: specifies corresponding public key to the `xx`'s private key in the `pk` array
:param dsRows: row number where the pubkeys "end" (and commitments follow)
:param rows: total number of rows
:param cols: size of ring
"""
Ip = _key_vector(dsRows)
rv.II = _key_vector(dsRows)
alpha = _key_vector(rows)
rv.ss = _key_matrix(rows, cols)
tmp_buff = bytearray(32)
hasher = _hasher_message(message)
for i in range(dsRows):
# this is somewhat extra as compared to the Ring Confidential Tx paper
# see footnote in From Zero to Monero section 3.3
hasher.update(crypto.encodepoint(pk[index][i]))
if kLRki:
raise NotImplementedError("Multisig not implemented")
# alpha[i] = kLRki.k
# rv.II[i] = kLRki.ki
# hash_point(hasher, kLRki.L, tmp_buff)
# hash_point(hasher, kLRki.R, tmp_buff)
else:
Hi = crypto.hash_to_point(crypto.encodepoint(pk[index][i]))
alpha[i] = crypto.random_scalar()
# L = alpha_i * G
aGi = crypto.scalarmult_base(alpha[i])
# Ri = alpha_i * H(P_i)
aHPi = crypto.scalarmult(Hi, alpha[i])
# key image
rv.II[i] = crypto.scalarmult(Hi, xx[i])
_hash_point(hasher, aGi, tmp_buff)
_hash_point(hasher, aHPi, tmp_buff)
Ip[i] = rv.II[i]
for i in range(dsRows, rows):
alpha[i] = crypto.random_scalar()
# L = alpha_i * G
aGi = crypto.scalarmult_base(alpha[i])
# for some reasons we omit calculating R here, which seems
# contrary to the paper, but it is in the Monero official client
# see https://github.com/monero-project/monero/blob/636153b2050aa0642ba86842c69ac55a5d81618d/src/ringct/rctSigs.cpp#L191
_hash_point(hasher, pk[index][i], tmp_buff)
_hash_point(hasher, aGi, tmp_buff)
# the first c
c_old = hasher.digest()
c_old = crypto.decodeint(c_old)
return c_old, Ip, alpha
def generate_mlsag(message, pk, xx, kLRki, index, dsRows):
"""
Multilayered Spontaneous Anonymous Group Signatures (MLSAG signatures)
:param message: the full message to be signed (actually its hash)
:param pk: matrix of public keys and commitments
:param xx: input secret array composed of a private key and commitment mask
:param kLRki: used only in multisig, currently not implemented
:param index: specifies corresponding public key to the `xx`'s private key in the `pk` array
:param dsRows: separates pubkeys from commitment
:return MgSig
"""
from apps.monero.xmr.serialize_messages.tx_full import MgSig
rows, cols = gen_mlsag_assert(pk, xx, kLRki, index, dsRows)
rv = MgSig()
c, L, R, Hi = 0, None, None, None
# calculates the "first" c, key images and random scalars alpha
c_old, Ip, alpha = generate_first_c_and_key_images(
message, rv, pk, xx, kLRki, index, dsRows, rows, cols
)
i = (index + 1) % cols
if i == 0:
rv.cc = c_old
tmp_buff = bytearray(32)
while i != index:
rv.ss[i] = _generate_random_vector(rows)
hasher = _hasher_message(message)
for j in range(dsRows):
# L = rv.ss[i][j] * G + c_old * pk[i][j]
L = crypto.add_keys2(rv.ss[i][j], c_old, pk[i][j])
Hi = crypto.hash_to_point(crypto.encodepoint(pk[i][j]))
# R = rv.ss[i][j] * H(pk[i][j]) + c_old * Ip[j]
R = crypto.add_keys3(rv.ss[i][j], Hi, c_old, rv.II[j])
_hash_point(hasher, pk[i][j], tmp_buff)
_hash_point(hasher, L, tmp_buff)
_hash_point(hasher, R, tmp_buff)
for j in range(dsRows, rows):
# again, omitting R here as discussed above
L = crypto.add_keys2(rv.ss[i][j], c_old, pk[i][j])
_hash_point(hasher, pk[i][j], tmp_buff)
_hash_point(hasher, L, tmp_buff)
c = crypto.decodeint(hasher.digest())
c_old = c
i = (i + 1) % cols
if i == 0:
rv.cc = c_old
for j in range(rows):
rv.ss[index][j] = crypto.sc_mulsub(c, xx[j], alpha[j])
return rv
def _key_vector(rows):
return [None] * rows
def _key_matrix(rows, cols):
"""
first index is columns (so slightly backward from math)
"""
rv = [None] * cols
for i in range(0, cols):
rv[i] = _key_vector(rows)
return rv
def _generate_random_vector(n):
"""
Generates vector of random scalars
"""
return [crypto.random_scalar() for _ in range(0, n)]
def _hasher_message(message):
"""
Returns incremental hasher for MLSAG
"""
ctx = crypto.get_keccak()
ctx.update(message)
return ctx
def _hash_point(hasher, point, tmp_buff):
crypto.encodepoint_into(tmp_buff, point)
hasher.update(tmp_buff)

View File

@ -0,0 +1,112 @@
from apps.monero.xmr import crypto
from apps.monero.xmr.keccak_hasher import KeccakXmrArchive
class PreMlsagHasher:
"""
Iterative construction of the pre_mlsag_hash
"""
def __init__(self):
self.is_simple = None
self.state = 0
self.kc_master = crypto.get_keccak()
self.rsig_hasher = crypto.get_keccak()
self.rtcsig_hasher = KeccakXmrArchive()
def init(self, is_simple):
if self.state != 0:
raise ValueError("State error")
self.state = 1
self.is_simple = is_simple
def set_message(self, message):
self.kc_master.update(message)
def set_type_fee(self, rv_type, fee):
if self.state != 1:
raise ValueError("State error")
self.state = 2
self.rtcsig_hasher.uint(rv_type, 1) # UInt8
self.rtcsig_hasher.uvarint(fee) # UVarintType
def set_pseudo_out(self, out):
if self.state != 2 and self.state != 3:
raise ValueError("State error")
self.state = 3
# Manual serialization of the ECKey
self.rtcsig_hasher.buffer(out)
def set_ecdh(self, ecdh):
if self.state != 2 and self.state != 3 and self.state != 4:
raise ValueError("State error")
self.state = 4
self.rtcsig_hasher.buffer(ecdh)
def set_out_pk_commitment(self, out_pk_commitment):
if self.state != 4 and self.state != 5:
raise ValueError("State error")
self.state = 5
self.rtcsig_hasher.buffer(out_pk_commitment) # ECKey
def rctsig_base_done(self):
if self.state != 5:
raise ValueError("State error")
self.state = 6
c_hash = self.rtcsig_hasher.get_digest()
self.kc_master.update(c_hash)
self.rtcsig_hasher = None
def rsig_val(self, p, bulletproof, raw=False):
if self.state == 8:
raise ValueError("State error")
if raw:
# Avoiding problem with the memory fragmentation.
# If the range proof is passed as a list, hash each element
# as the range proof is split to multiple byte arrays while
# preserving the byte ordering
if isinstance(p, list):
for x in p:
self.rsig_hasher.update(x)
else:
self.rsig_hasher.update(p)
return
if bulletproof:
self.rsig_hasher.update(p.A)
self.rsig_hasher.update(p.S)
self.rsig_hasher.update(p.T1)
self.rsig_hasher.update(p.T2)
self.rsig_hasher.update(p.taux)
self.rsig_hasher.update(p.mu)
for i in range(len(p.L)):
self.rsig_hasher.update(p.L[i])
for i in range(len(p.R)):
self.rsig_hasher.update(p.R[i])
self.rsig_hasher.update(p.a)
self.rsig_hasher.update(p.b)
self.rsig_hasher.update(p.t)
else:
for i in range(64):
self.rsig_hasher.update(p.asig.s0[i])
for i in range(64):
self.rsig_hasher.update(p.asig.s1[i])
self.rsig_hasher.update(p.asig.ee)
for i in range(64):
self.rsig_hasher.update(p.Ci[i])
def get_digest(self):
if self.state != 6:
raise ValueError("State error")
self.state = 8
c_hash = self.rsig_hasher.digest()
self.rsig_hasher = None
self.kc_master.update(c_hash)
return self.kc_master.digest()

View File

@ -0,0 +1,261 @@
from micropython import const
from apps.monero.xmr import crypto
if False:
from apps.monero.xmr.types import *
DISPLAY_DECIMAL_POINT = const(12)
class XmrException(Exception):
pass
class XmrNoSuchAddressException(XmrException):
pass
def get_subaddress_secret_key(secret_key, index=None, major=None, minor=None):
"""
Builds subaddress secret key from the subaddress index
Hs(SubAddr || a || index_major || index_minor)
"""
if index:
major = index.major
minor = index.minor
if major == 0 and minor == 0:
return secret_key
return crypto.get_subaddress_secret_key(secret_key, major, minor)
def get_subaddress_spend_public_key(view_private, spend_public, major, minor):
"""
Generates subaddress spend public key D_{major, minor}
"""
if major == 0 and minor == 0:
return spend_public
m = get_subaddress_secret_key(view_private, major=major, minor=minor)
M = crypto.scalarmult_base(m)
D = crypto.point_add(spend_public, M)
return D
def derive_subaddress_public_key(out_key, derivation, output_index):
"""
out_key - H_s(derivation || varint(output_index))G
"""
crypto.check_ed25519point(out_key)
scalar = crypto.derivation_to_scalar(derivation, output_index)
point2 = crypto.scalarmult_base(scalar)
point4 = crypto.point_sub(out_key, point2)
return point4
def generate_key_image(public_key, secret_key):
"""
Key image: secret_key * H_p(pub_key)
"""
point = crypto.hash_to_point(public_key)
point2 = crypto.scalarmult(point, secret_key)
return point2
def is_out_to_account(
subaddresses: dict,
out_key: Ge25519,
derivation: Ge25519,
additional_derivations: list,
output_index: int,
):
"""
Checks whether the given transaction is sent to the account.
Searches subaddresses for the computed subaddress_spendkey.
Corresponds to is_out_to_acc_precomp() in the Monero codebase.
If found, returns (major, minor), derivation, otherwise None.
"""
subaddress_spendkey = crypto.encodepoint(
derive_subaddress_public_key(out_key, derivation, output_index)
)
if subaddress_spendkey in subaddresses:
return subaddresses[subaddress_spendkey], derivation
if additional_derivations and len(additional_derivations) > 0:
if output_index >= len(additional_derivations):
raise ValueError("Wrong number of additional derivations")
subaddress_spendkey = derive_subaddress_public_key(
out_key, additional_derivations[output_index], output_index
)
subaddress_spendkey = crypto.encodepoint(subaddress_spendkey)
if subaddress_spendkey in subaddresses:
return (
subaddresses[subaddress_spendkey],
additional_derivations[output_index],
)
return None
def generate_tx_spend_and_key_image(
ack, out_key, recv_derivation, real_output_index, received_index: tuple
) -> Optional[Tuple[Sc25519, Ge25519]]:
"""
Generates UTXO spending key and key image.
Corresponds to generate_key_image_helper_precomp() in the Monero codebase.
:param ack: sender credentials
:type ack: apps.monero.xmr.credentials.AccountCreds
:param out_key: real output (from input RCT) destination key
:param recv_derivation:
:param real_output_index:
:param received_index: subaddress index this payment was received to
:return:
"""
if not crypto.sc_isnonzero(ack.spend_key_private):
raise ValueError("Watch-only wallet not supported")
# derive secret key with subaddress - step 1: original CN derivation
scalar_step1 = crypto.derive_secret_key(
recv_derivation, real_output_index, ack.spend_key_private
)
# step 2: add Hs(SubAddr || a || index_major || index_minor)
subaddr_sk = None
if received_index == (0, 0):
scalar_step2 = scalar_step1
else:
subaddr_sk = get_subaddress_secret_key(
ack.view_key_private, major=received_index[0], minor=received_index[1]
)
scalar_step2 = crypto.sc_add(scalar_step1, subaddr_sk)
# When not in multisig, we know the full spend secret key, so the output pubkey can be obtained by scalarmultBase
pub_ver = crypto.scalarmult_base(scalar_step2)
# <Multisig>, branch deactivated until implemented
# # When in multisig, we only know the partial spend secret key. But we do know the full spend public key,
# # so the output pubkey can be obtained by using the standard CN key derivation.
# pub_ver = crypto.derive_public_key(
# recv_derivation, real_output_index, ack.spend_key_public
# )
#
# # Add the contribution from the subaddress part
# if received_index != (0, 0):
# subaddr_pk = crypto.scalarmult_base(subaddr_sk)
# pub_ver = crypto.point_add(pub_ver, subaddr_pk)
# </Multisig>
if not crypto.point_eq(pub_ver, out_key):
raise ValueError(
"key image helper precomp: given output pubkey doesn't match the derived one"
)
ki = generate_key_image(crypto.encodepoint(pub_ver), scalar_step2)
return scalar_step2, ki
def generate_tx_spend_and_key_image_and_derivation(
creds,
subaddresses: dict,
out_key: Ge25519,
tx_public_key: Ge25519,
additional_tx_public_keys: list,
real_output_index: int,
) -> Tuple[Sc25519, Ge25519, Ge25519]:
"""
Generates UTXO spending key and key image and corresponding derivation.
Supports subaddresses.
Corresponds to generate_key_image_helper() in the Monero codebase.
:param creds:
:param subaddresses:
:param out_key: real output (from input RCT) destination key
:param tx_public_key: R, transaction public key
:param additional_tx_public_keys: Additional Rs, for subaddress destinations
:param real_output_index: index of the real output in the RCT
:return:
"""
recv_derivation = crypto.generate_key_derivation(
tx_public_key, creds.view_key_private
)
additional_recv_derivations = []
for add_pub_key in additional_tx_public_keys:
additional_recv_derivations.append(
crypto.generate_key_derivation(add_pub_key, creds.view_key_private)
)
subaddr_recv_info = is_out_to_account(
subaddresses,
out_key,
recv_derivation,
additional_recv_derivations,
real_output_index,
)
if subaddr_recv_info is None:
raise XmrNoSuchAddressException("No such addr")
xi, ki = generate_tx_spend_and_key_image(
creds, out_key, subaddr_recv_info[1], real_output_index, subaddr_recv_info[0]
)
return xi, ki, recv_derivation
def compute_subaddresses(creds, account: int, indices, subaddresses=None):
"""
Computes subaddress public spend key for receiving transactions.
:param creds: credentials
:param account: major index
:param indices: array of minor indices
:param subaddresses: subaddress dict. optional.
:return:
"""
if subaddresses is None:
subaddresses = {}
for idx in indices:
if account == 0 and idx == 0:
subaddresses[crypto.encodepoint(creds.spend_key_public)] = (0, 0)
continue
pub = get_subaddress_spend_public_key(
creds.view_key_private, creds.spend_key_public, major=account, minor=idx
)
pub = crypto.encodepoint(pub)
subaddresses[pub] = (account, idx)
return subaddresses
def generate_keys(recovery_key):
pub = crypto.scalarmult_base(recovery_key)
return recovery_key, pub
def generate_monero_keys(seed):
"""
Generates spend key / view key from the seed in the same manner as Monero code does.
account.cpp:
crypto::secret_key account_base::generate(const crypto::secret_key& recovery_key, bool recover, bool two_random).
"""
spend_sec, spend_pub = generate_keys(crypto.decodeint(seed))
hash = crypto.cn_fast_hash(crypto.encodeint(spend_sec))
view_sec, view_pub = generate_keys(crypto.decodeint(hash))
return spend_sec, spend_pub, view_sec, view_pub
def generate_sub_address_keys(view_sec, spend_pub, major, minor):
if major == 0 and minor == 0: # special case, Monero-defined
return spend_pub, crypto.scalarmult_base(view_sec)
m = get_subaddress_secret_key(view_sec, major=major, minor=minor)
M = crypto.scalarmult_base(m)
D = crypto.point_add(spend_pub, M)
C = crypto.scalarmult(D, view_sec)
return D, C

View File

@ -0,0 +1,51 @@
class NetworkTypes:
MAINNET = 0
TESTNET = 1
STAGENET = 2
FAKECHAIN = 3
class MainNet:
PUBLIC_ADDRESS_BASE58_PREFIX = 18
PUBLIC_INTEGRATED_ADDRESS_BASE58_PREFIX = 19
PUBLIC_SUBADDRESS_BASE58_PREFIX = 42
class TestNet:
PUBLIC_ADDRESS_BASE58_PREFIX = 53
PUBLIC_INTEGRATED_ADDRESS_BASE58_PREFIX = 54
PUBLIC_SUBADDRESS_BASE58_PREFIX = 63
class StageNet:
PUBLIC_ADDRESS_BASE58_PREFIX = 24
PUBLIC_INTEGRATED_ADDRESS_BASE58_PREFIX = 25
PUBLIC_SUBADDRESS_BASE58_PREFIX = 36
def net_version(
network_type=NetworkTypes.MAINNET, is_subaddr=False, is_integrated=False
):
"""
Network version bytes used for address construction
"""
if is_integrated and is_subaddr:
raise ValueError("Subaddress cannot be integrated")
c_net = None
if network_type is None or network_type == NetworkTypes.MAINNET:
c_net = MainNet
elif network_type == NetworkTypes.TESTNET:
c_net = TestNet
elif network_type == NetworkTypes.STAGENET:
c_net = StageNet
else:
raise ValueError("Unknown network type: %s" % network_type)
prefix = c_net.PUBLIC_ADDRESS_BASE58_PREFIX
if is_subaddr:
prefix = c_net.PUBLIC_SUBADDRESS_BASE58_PREFIX
elif is_integrated:
prefix = c_net.PUBLIC_INTEGRATED_ADDRESS_BASE58_PREFIX
return bytes([prefix])

View File

@ -0,0 +1,144 @@
"""
Computes range signature
Can compute Borromean range proof or Bulletproof.
Also can verify Bulletproof, in case the computation was offloaded.
Mostly ported from official Monero client, but also inspired by Mininero.
Author: Dusan Klinec, ph4r05, 2018
"""
import gc
from apps.monero.xmr import crypto
def prove_range_bp_batch(amounts, masks):
"""Calculates Bulletproof in batches"""
from apps.monero.xmr import bulletproof as bp
bpi = bp.BulletProofBuilder()
bp_proof = bpi.prove_batch([crypto.sc_init(a) for a in amounts], masks)
del (bpi, bp)
gc.collect()
return bp_proof
def verify_bp(bp_proof, amounts, masks):
"""Verifies Bulletproof"""
from apps.monero.xmr import bulletproof as bp
if amounts:
bp_proof.V = []
for i in range(len(amounts)):
C = crypto.gen_commitment(masks[i], amounts[i])
crypto.scalarmult_into(C, C, crypto.sc_inv_eight())
bp_proof.V.append(crypto.encodepoint(C))
bpi = bp.BulletProofBuilder()
res = bpi.verify(bp_proof)
gc.collect()
return res
def prove_range_borromean(amount, last_mask):
"""Calculates Borromean range proof"""
# The large chunks allocated first to avoid potential memory fragmentation issues.
ai = bytearray(32 * 64)
alphai = bytearray(32 * 64)
Cis = bytearray(32 * 64)
s0s = bytearray(32 * 64)
s1s = bytearray(32 * 64)
buff = bytearray(32)
ee_bin = bytearray(32)
a = crypto.sc_init(0)
si = crypto.sc_init(0)
c = crypto.sc_init(0)
ee = crypto.sc_init(0)
tmp_ai = crypto.sc_init(0)
tmp_alpha = crypto.sc_init(0)
C_acc = crypto.identity()
C_h = crypto.xmr_H()
C_tmp = crypto.identity()
L = crypto.identity()
kck = crypto.get_keccak()
for ii in range(64):
crypto.random_scalar(tmp_ai)
if last_mask is not None and ii == 63:
crypto.sc_sub_into(tmp_ai, last_mask, a)
crypto.sc_add_into(a, a, tmp_ai)
crypto.random_scalar(tmp_alpha)
crypto.scalarmult_base_into(L, tmp_alpha)
crypto.scalarmult_base_into(C_tmp, tmp_ai)
# if 0: C_tmp += Zero (nothing is added)
# if 1: C_tmp += 2^i*H
# 2^i*H is already stored in C_h
if (amount >> ii) & 1 == 1:
crypto.point_add_into(C_tmp, C_tmp, C_h)
crypto.point_add_into(C_acc, C_acc, C_tmp)
# Set Ci[ii] to sigs
crypto.encodepoint_into(Cis, C_tmp, ii << 5)
crypto.encodeint_into(ai, tmp_ai, ii << 5)
crypto.encodeint_into(alphai, tmp_alpha, ii << 5)
if ((amount >> ii) & 1) == 0:
crypto.random_scalar(si)
crypto.encodepoint_into(buff, L)
crypto.hash_to_scalar_into(c, buff)
crypto.point_sub_into(C_tmp, C_tmp, C_h)
crypto.add_keys2_into(L, si, c, C_tmp)
crypto.encodeint_into(s1s, si, ii << 5)
crypto.encodepoint_into(buff, L)
kck.update(buff)
crypto.point_double_into(C_h, C_h)
# Compute ee
tmp_ee = kck.digest()
crypto.decodeint_into(ee, tmp_ee)
del (tmp_ee, kck)
C_h = crypto.xmr_H()
gc.collect()
# Second pass, s0, s1
for ii in range(64):
crypto.decodeint_into(tmp_alpha, alphai, ii << 5)
crypto.decodeint_into(tmp_ai, ai, ii << 5)
if ((amount >> ii) & 1) == 0:
crypto.sc_mulsub_into(si, tmp_ai, ee, tmp_alpha)
crypto.encodeint_into(s0s, si, ii << 5)
else:
crypto.random_scalar(si)
crypto.encodeint_into(s0s, si, ii << 5)
crypto.decodepoint_into(C_tmp, Cis, ii << 5)
crypto.add_keys2_into(L, si, ee, C_tmp)
crypto.encodepoint_into(buff, L)
crypto.hash_to_scalar_into(c, buff)
crypto.sc_mulsub_into(si, tmp_ai, c, tmp_alpha)
crypto.encodeint_into(s1s, si, ii << 5)
crypto.point_double_into(C_h, C_h)
crypto.encodeint_into(ee_bin, ee)
del (ai, alphai, buff, tmp_ai, tmp_alpha, si, c, ee, C_tmp, C_h, L)
gc.collect()
return C_acc, a, [s0s, s1s, ee_bin, Cis]

View File

@ -0,0 +1,27 @@
import gc
def parse_msg(buf: bytes, msg_type):
from apps.monero.xmr.serialize.readwriter import MemoryReaderWriter
reader = MemoryReaderWriter(memoryview(buf))
return msg_type.load(reader)
def dump_msg(msg, preallocate: int = None, prefix: bytes = None) -> bytes:
from apps.monero.xmr.serialize.readwriter import MemoryReaderWriter
writer = MemoryReaderWriter(preallocate=preallocate)
if prefix:
writer.write(prefix)
msg_type = msg.__class__
msg_type.dump(writer, msg)
return writer.get_buffer()
def dump_msg_gc(msg, preallocate: int = None, prefix: bytes = None) -> bytes:
buf = dump_msg(msg, preallocate, prefix)
del msg
gc.collect()
return buf

View File

@ -0,0 +1,36 @@
from apps.monero.xmr.serialize.int_serialize import (
dump_uint,
dump_uvarint,
load_uint,
load_uvarint,
)
class XmrType:
pass
class UVarintType(XmrType):
@staticmethod
def load(reader) -> int:
return load_uvarint(reader)
@staticmethod
def dump(writer, n: int):
return dump_uvarint(writer, n)
class IntType(XmrType):
WIDTH = 0
@classmethod
def load(cls, reader) -> int:
return load_uint(reader, cls.WIDTH)
@classmethod
def dump(cls, writer, n: int):
return dump_uint(writer, n, cls.WIDTH)
class UInt8(IntType):
WIDTH = 1

View File

@ -0,0 +1,109 @@
_UINT_BUFFER = bytearray(1)
def load_uint(reader, width):
"""
Constant-width integer serialization
"""
buffer = _UINT_BUFFER
result = 0
shift = 0
for _ in range(width):
reader.readinto(buffer)
result += buffer[0] << shift
shift += 8
return result
def dump_uint(writer, n, width):
"""
Constant-width integer serialization
"""
buffer = _UINT_BUFFER
for _ in range(width):
buffer[0] = n & 0xFF
writer.write(buffer)
n >>= 8
def uvarint_size(n):
"""
Returns size in bytes n would occupy serialized as varint
"""
bts = 0 if n != 0 else 1
while n:
n >>= 7
bts += 1
return bts
def load_uvarint_b(buffer):
"""
Variable int deserialization, synchronous from buffer.
"""
result = 0
idx = 0
byte = 0x80
while byte & 0x80:
byte = buffer[idx]
result += (byte & 0x7F) << (7 * idx)
idx += 1
return result
def dump_uvarint_b(n):
"""
Serializes uvarint to the buffer
"""
buffer = bytearray(uvarint_size(n))
return dump_uvarint_b_into(n, buffer, 0)
def dump_uvarint_b_into(n, buffer, offset=0):
"""
Serializes n as variable size integer to the provided buffer.
"""
if n < 0:
raise ValueError("Cannot dump signed value, convert it to unsigned first.")
shifted = True
while shifted:
shifted = n >> 7
buffer[offset] = (n & 0x7F) | (0x80 if shifted else 0x00)
offset += 1
n = shifted
return buffer
def dump_uint_b_into(n, width, buffer, offset=0):
"""
Serializes fixed size integer to the buffer
"""
for idx in range(width):
buffer[idx + offset] = n & 0xFF
n >>= 8
return buffer
def load_uvarint(reader):
buffer = _UINT_BUFFER
result = 0
shift = 0
byte = 0x80
while byte & 0x80:
reader.readinto(buffer)
byte = buffer[0]
result += (byte & 0x7F) << shift
shift += 7
return result
def dump_uvarint(writer, n):
if n < 0:
raise ValueError("Cannot dump signed value, convert it to unsigned first.")
buffer = _UINT_BUFFER
shifted = True
while shifted:
shifted = n >> 7
buffer[0] = (n & 0x7F) | (0x80 if shifted else 0x00)
writer.write(buffer)
n = shifted

View File

@ -0,0 +1,163 @@
from trezor.utils import obj_eq, obj_repr
from apps.monero.xmr.serialize.base_types import XmrType
from apps.monero.xmr.serialize.int_serialize import (
dump_uint,
dump_uvarint,
load_uint,
load_uvarint,
)
class UnicodeType(XmrType):
"""
Unicode data in UTF-8 encoding.
"""
@staticmethod
def dump(writer, s):
dump_uvarint(writer, len(s))
writer.write(bytes(s))
@staticmethod
def load(reader):
ivalue = load_uvarint(reader)
fvalue = bytearray(ivalue)
reader.readinto(fvalue)
return str(fvalue)
class BlobType(XmrType):
"""
Binary data, represented as bytearray. BlobType is only a scheme
descriptor. Behaves in the same way as primitive types.
"""
FIX_SIZE = 0
SIZE = 0
@classmethod
def dump(cls, writer, elem: bytes):
if cls.FIX_SIZE:
if cls.SIZE != len(elem):
raise ValueError("Size mismatch")
else:
dump_uvarint(writer, len(elem))
writer.write(elem)
@classmethod
def load(cls, reader) -> bytearray:
if cls.FIX_SIZE:
size = cls.SIZE
else:
size = load_uvarint(reader)
elem = bytearray(size)
reader.readinto(elem)
return elem
class ContainerType(XmrType):
"""
Array of elements, represented as a list of items. ContainerType is only a
scheme descriptor.
"""
FIX_SIZE = 0
SIZE = 0
ELEM_TYPE = None
@classmethod
def dump(cls, writer, elems, elem_type=None):
if elem_type is None:
elem_type = cls.ELEM_TYPE
if cls.FIX_SIZE:
if cls.SIZE != len(elems):
raise ValueError("Size mismatch")
else:
dump_uvarint(writer, len(elems))
for elem in elems:
elem_type.dump(writer, elem)
@classmethod
def load(cls, reader, elem_type=None):
if elem_type is None:
elem_type = cls.ELEM_TYPE
if cls.FIX_SIZE:
size = cls.SIZE
else:
size = load_uvarint(reader)
elems = []
for _ in range(size):
elem = elem_type.load(reader)
elems.append(elem)
return elems
class VariantType(XmrType):
"""
Union of types, differentiated by variant tags. VariantType is only a scheme
descriptor.
"""
@classmethod
def dump(cls, writer, elem):
for field in cls.f_specs():
ftype = field[1]
if isinstance(elem, ftype):
break
else:
raise ValueError("Unrecognized variant: %s" % elem)
dump_uint(writer, ftype.VARIANT_CODE, 1)
ftype.dump(writer, elem)
@classmethod
def load(cls, reader):
tag = load_uint(reader, 1)
for field in cls.f_specs():
ftype = field[1]
if ftype.VARIANT_CODE == tag:
fvalue = ftype.load(reader)
break
else:
raise ValueError("Unknown tag: %s" % tag)
return fvalue
@classmethod
def f_specs(cls):
return ()
class MessageType(XmrType):
"""
Message composed of fields with specific types.
"""
def __init__(self, **kwargs):
for kw in kwargs:
setattr(self, kw, kwargs[kw])
__eq__ = obj_eq
__repr__ = obj_repr
@classmethod
def dump(cls, writer, msg):
defs = cls.f_specs()
for field in defs:
fname, ftype, *fparams = field
fvalue = getattr(msg, fname, None)
ftype.dump(writer, fvalue, *fparams)
@classmethod
def load(cls, reader):
msg = cls()
defs = cls.f_specs()
for field in defs:
fname, ftype, *fparams = field
fvalue = ftype.load(reader, *fparams)
setattr(msg, fname, fvalue)
return msg
@classmethod
def f_specs(cls):
return ()

View File

@ -0,0 +1,106 @@
import gc
class MemoryReaderWriter:
def __init__(
self,
buffer=None,
read_empty=False,
threshold=None,
do_gc=False,
preallocate=None,
**kwargs
):
self.buffer = buffer
self.nread = 0
self.nwritten = 0
self.ndata = 0
self.offset = 0
self.woffset = 0
self.read_empty = read_empty
self.threshold = threshold
self.do_gc = do_gc
if preallocate is not None:
self.preallocate(preallocate)
elif self.buffer is None:
self.buffer = bytearray(0)
else:
self.woffset = len(buffer)
def is_empty(self):
return self.offset == len(self.buffer) or self.offset == self.woffset
def preallocate(self, size):
self.buffer = bytearray(size)
self.offset = 0
self.woffset = 0
def readinto(self, buf):
ln = len(buf)
if not self.read_empty and ln > 0 and self.offset == len(self.buffer):
raise EOFError
nread = min(ln, len(self.buffer) - self.offset)
for idx in range(nread):
buf[idx] = self.buffer[self.offset + idx]
self.offset += nread
self.nread += nread
self.ndata -= nread
# Deallocation threshold triggered
if self.threshold is not None and self.offset >= self.threshold:
self.buffer = self.buffer[self.offset :]
self.woffset -= self.offset
self.offset = 0
if self.do_gc:
gc.collect()
return nread
async def areadinto(self, buf):
return self.readinto(buf)
def write(self, buf):
nwritten = len(buf)
nall = len(self.buffer)
towrite = nwritten
bufoff = 0
# Fill existing place in the buffer
while towrite > 0 and nall - self.woffset > 0:
self.buffer[self.woffset] = buf[bufoff]
self.woffset += 1
bufoff += 1
towrite -= 1
# Allocate next chunk if needed
while towrite > 0:
_towrite = min(32, towrite)
chunk = bytearray(32) # chunk size typical for EC point
for i in range(_towrite):
chunk[i] = buf[bufoff]
self.woffset += 1
bufoff += 1
towrite -= 1
self.buffer.extend(chunk)
if self.do_gc:
chunk = None # dereference
gc.collect()
self.nwritten += nwritten
self.ndata += nwritten
return nwritten
async def awrite(self, buf):
return self.write(buf)
def get_buffer(self):
mv = memoryview(self.buffer)
return mv[self.offset : self.woffset]

View File

@ -0,0 +1,31 @@
from micropython import const
from apps.monero.xmr.serialize.message_types import BlobType
_c0 = const(0)
_c1 = const(1)
_c32 = const(32)
_c64 = const(64)
#
# cryptonote_basic.h
#
class Hash(BlobType):
__slots__ = ("data",)
DATA_ATTR = "data"
FIX_SIZE = _c1
SIZE = _c32
class ECKey(BlobType):
__slots__ = ("bytes",)
DATA_ATTR = "bytes"
FIX_SIZE = _c1
SIZE = _c32
ECPoint = Hash
ECPublicKey = ECPoint
KeyImage = ECPoint

View File

@ -0,0 +1,24 @@
from micropython import const
from apps.monero.xmr.serialize.message_types import ContainerType, MessageType
from apps.monero.xmr.serialize_messages.base import ECKey
_c0 = const(0)
class KeyV(ContainerType):
FIX_SIZE = _c0
ELEM_TYPE = ECKey
class KeyM(ContainerType):
FIX_SIZE = _c0
ELEM_TYPE = KeyV
class CtKey(MessageType):
__slots__ = ("dest", "mask")
@classmethod
def f_specs(cls):
return (("dest", ECKey), ("mask", ECKey))

View File

@ -0,0 +1,5 @@
from apps.monero.xmr.serialize.message_types import MessageType
class EcdhTuple(MessageType):
__slots__ = ("mask", "amount")

View File

@ -0,0 +1,11 @@
from apps.monero.xmr.serialize.message_types import MessageType
from apps.monero.xmr.serialize_messages.base import ECKey
from apps.monero.xmr.serialize_messages.ct_keys import KeyM
class MgSig(MessageType):
__slots__ = ("ss", "cc", "II")
@classmethod
def f_specs(cls):
return (("ss", KeyM), ("cc", ECKey))

View File

@ -0,0 +1,102 @@
from micropython import const
from apps.monero.xmr.serialize.base_types import UInt8, UVarintType
from apps.monero.xmr.serialize.message_types import (
ContainerType,
MessageType,
VariantType,
)
from apps.monero.xmr.serialize_messages.base import ECPublicKey, Hash, KeyImage
_c0 = const(0)
_c1 = const(1)
_c32 = const(32)
_c64 = const(64)
class TxoutToScript(MessageType):
__slots__ = ("keys", "script")
VARIANT_CODE = 0x0
@classmethod
def f_specs(cls):
return (("keys", ContainerType, ECPublicKey), ("script", ContainerType, UInt8))
class TxoutToKey(MessageType):
__slots__ = ("key",)
VARIANT_CODE = 0x2
@classmethod
def f_specs(cls):
return (("key", ECPublicKey),)
class TxoutToScriptHash(MessageType):
__slots__ = ("hash",)
VARIANT_CODE = 0x1
@classmethod
def f_specs(cls):
return (("hash", Hash),)
class TxoutTargetV(VariantType):
@classmethod
def f_specs(cls):
return (
("txout_to_script", TxoutToScript),
("txout_to_scripthash", TxoutToScriptHash),
("txout_to_key", TxoutToKey),
)
class TxinGen(MessageType):
__slots__ = ("height",)
VARIANT_CODE = 0xFF
@classmethod
def f_specs(cls):
return (("height", UVarintType),)
class TxinToKey(MessageType):
__slots__ = ("amount", "key_offsets", "k_image")
VARIANT_CODE = 0x2
@classmethod
def f_specs(cls):
return (
("amount", UVarintType),
("key_offsets", ContainerType, UVarintType),
("k_image", KeyImage),
)
class TxinToScript(MessageType):
__slots__ = ()
VARIANT_CODE = _c0
class TxinToScriptHash(MessageType):
__slots__ = ()
VARIANT_CODE = _c1
class TxInV(VariantType):
@classmethod
def f_specs(cls):
return (
("txin_gen", TxinGen),
("txin_to_script", TxinToScript),
("txin_to_scripthash", TxinToScriptHash),
("txin_to_key", TxinToKey),
)
class TxOut(MessageType):
__slots__ = ("amount", "target")
@classmethod
def f_specs(cls):
return (("amount", UVarintType), ("target", TxoutTargetV))

View File

@ -0,0 +1,21 @@
from apps.monero.xmr.serialize.message_types import MessageType
from apps.monero.xmr.serialize_messages.base import ECKey
from apps.monero.xmr.serialize_messages.ct_keys import KeyV
class Bulletproof(MessageType):
@classmethod
def f_specs(cls):
return (
("A", ECKey),
("S", ECKey),
("T1", ECKey),
("T2", ECKey),
("taux", ECKey),
("mu", ECKey),
("L", KeyV),
("R", KeyV),
("a", ECKey),
("b", ECKey),
("t", ECKey),
)

View File

@ -0,0 +1,6 @@
if False:
from trezor.crypto import monero as tcry
from typing import * # noqa: F401
Ge25519 = tcry.ge25519
Sc25519 = tcry.bignum256modm

View File

@ -14,6 +14,7 @@ import apps.management
import apps.wallet
import apps.ethereum
import apps.lisk
import apps.monero
import apps.nem
import apps.stellar
import apps.ripple
@ -31,6 +32,7 @@ apps.management.boot()
apps.wallet.boot()
apps.ethereum.boot()
apps.lisk.boot()
apps.monero.boot()
apps.nem.boot()
apps.stellar.boot()
apps.ripple.boot()

View File

@ -6,6 +6,7 @@ from trezorcrypto import ( # noqa: F401
bip39,
chacha20poly1305,
crc,
monero,
nem,
pbkdf2,
random,

View File

@ -151,12 +151,10 @@ MoneroTransactionSetOutputRequest = 511
MoneroTransactionSetOutputAck = 512
MoneroTransactionAllOutSetRequest = 513
MoneroTransactionAllOutSetAck = 514
MoneroTransactionMlsagDoneRequest = 515
MoneroTransactionMlsagDoneAck = 516
MoneroTransactionSignInputRequest = 517
MoneroTransactionSignInputAck = 518
MoneroTransactionFinalRequest = 519
MoneroTransactionFinalAck = 520
MoneroTransactionSignInputRequest = 515
MoneroTransactionSignInputAck = 516
MoneroTransactionFinalRequest = 517
MoneroTransactionFinalAck = 518
MoneroKeyImageExportInitRequest = 530
MoneroKeyImageExportInitAck = 531
MoneroKeyImageSyncStepRequest = 532

View File

@ -8,17 +8,14 @@ class MoneroExportedKeyImage(p.MessageType):
def __init__(
self,
iv: bytes = None,
tag: bytes = None,
blob: bytes = None,
) -> None:
self.iv = iv
self.tag = tag
self.blob = blob
@classmethod
def get_fields(cls):
return {
1: ('iv', p.BytesType, 0),
2: ('tag', p.BytesType, 0),
3: ('blob', p.BytesType, 0),
}

View File

@ -2,7 +2,7 @@
# fmt: off
import protobuf as p
from .MoneroRctKey import MoneroRctKey
from .MoneroRctKeyPublic import MoneroRctKeyPublic
class MoneroOutputEntry(p.MessageType):
@ -10,7 +10,7 @@ class MoneroOutputEntry(p.MessageType):
def __init__(
self,
idx: int = None,
key: MoneroRctKey = None,
key: MoneroRctKeyPublic = None,
) -> None:
self.idx = idx
self.key = key
@ -19,5 +19,5 @@ class MoneroOutputEntry(p.MessageType):
def get_fields(cls):
return {
1: ('idx', p.UVarintType, 0),
2: ('key', MoneroRctKey, 0),
2: ('key', MoneroRctKeyPublic, 0),
}

View File

@ -3,19 +3,19 @@
import protobuf as p
class MoneroRctKey(p.MessageType):
class MoneroRctKeyPublic(p.MessageType):
def __init__(
self,
dest: bytes = None,
mask: bytes = None,
commitment: bytes = None,
) -> None:
self.dest = dest
self.mask = mask
self.commitment = commitment
@classmethod
def get_fields(cls):
return {
1: ('dest', p.BytesType, 0),
2: ('mask', p.BytesType, 0),
2: ('commitment', p.BytesType, 0),
}

View File

@ -2,20 +2,6 @@
# fmt: off
import protobuf as p
from .MoneroTransactionRsigData import MoneroTransactionRsigData
class MoneroTransactionAllInputsSetRequest(p.MessageType):
MESSAGE_WIRE_TYPE = 509
def __init__(
self,
rsig_data: MoneroTransactionRsigData = None,
) -> None:
self.rsig_data = rsig_data
@classmethod
def get_fields(cls):
return {
1: ('rsig_data', MoneroTransactionRsigData, 0),
}

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