mirror of
https://github.com/trezor/trezor-firmware.git
synced 2024-12-22 22:38:08 +00:00
commit
b4ca93e365
11
.travis.yml
11
.travis.yml
@ -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:
|
||||
|
3
Makefile
3
Makefile
@ -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)
|
||||
|
||||
|
4
Pipfile
4
Pipfile
@ -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
|
||||
|
@ -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',
|
||||
|
@ -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',
|
||||
|
@ -30,6 +30,8 @@ enum AESMode {
|
||||
CTR = 0x04,
|
||||
};
|
||||
|
||||
/// package: trezorcrypto.__init__
|
||||
|
||||
/// class AES:
|
||||
/// '''
|
||||
/// AES context.
|
||||
|
@ -27,6 +27,8 @@
|
||||
#include "memzero.h"
|
||||
#include "nem.h"
|
||||
|
||||
/// package: trezorcrypto.bip32
|
||||
|
||||
/// class HDNode:
|
||||
/// '''
|
||||
/// BIP0032 HD node structure.
|
||||
|
@ -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.
|
||||
|
@ -22,6 +22,8 @@
|
||||
#include "blake256.h"
|
||||
#include "memzero.h"
|
||||
|
||||
/// package: trezorcrypto.__init__
|
||||
|
||||
/// class Blake256:
|
||||
/// '''
|
||||
/// Blake256 context.
|
||||
|
@ -24,6 +24,8 @@
|
||||
#include "blake2b.h"
|
||||
#include "memzero.h"
|
||||
|
||||
/// package: trezorcrypto.__init__
|
||||
|
||||
/// class Blake2b:
|
||||
/// '''
|
||||
/// Blake2b context.
|
||||
|
@ -24,6 +24,8 @@
|
||||
#include "blake2s.h"
|
||||
#include "memzero.h"
|
||||
|
||||
/// package: trezorcrypto.__init__
|
||||
|
||||
/// class Blake2s:
|
||||
/// '''
|
||||
/// Blake2s context.
|
||||
|
@ -22,6 +22,8 @@
|
||||
#include "chacha20poly1305/rfc7539.h"
|
||||
#include "memzero.h"
|
||||
|
||||
/// package: trezorcrypto.__init__
|
||||
|
||||
/// class ChaCha20Poly1305:
|
||||
/// '''
|
||||
/// ChaCha20Poly1305 context.
|
||||
|
@ -23,6 +23,8 @@
|
||||
|
||||
#include "rand.h"
|
||||
|
||||
/// package: trezorcrypto.curve25519
|
||||
|
||||
/// def generate_secret() -> bytes:
|
||||
/// '''
|
||||
/// Generate secret key.
|
||||
|
@ -24,6 +24,8 @@
|
||||
|
||||
#include "rand.h"
|
||||
|
||||
/// package: trezorcrypto.ed25519
|
||||
|
||||
/// def generate_secret() -> bytes:
|
||||
/// '''
|
||||
/// Generate secret key.
|
||||
|
@ -25,6 +25,8 @@
|
||||
#define GROESTL512_DIGEST_LENGTH 64
|
||||
#define GROESTL512_BLOCK_LENGTH 128
|
||||
|
||||
/// package: trezorcrypto.__init__
|
||||
|
||||
/// class Groestl512:
|
||||
/// '''
|
||||
/// GROESTL512 context.
|
||||
|
1221
embed/extmod/modtrezorcrypto/modtrezorcrypto-monero.h
Normal file
1221
embed/extmod/modtrezorcrypto/modtrezorcrypto-monero.h
Normal file
File diff suppressed because it is too large
Load Diff
@ -23,6 +23,8 @@
|
||||
|
||||
#include "nem.h"
|
||||
|
||||
/// package: trezorcrypto.nem
|
||||
|
||||
/// def validate_address(address: str, network: int) -> bool:
|
||||
/// '''
|
||||
/// Validate a NEM address
|
||||
|
@ -22,6 +22,8 @@
|
||||
#include "ecdsa.h"
|
||||
#include "nist256p1.h"
|
||||
|
||||
/// package: trezorcrypto.nist256p1
|
||||
|
||||
/// def generate_secret() -> bytes:
|
||||
/// '''
|
||||
/// Generate secret key.
|
||||
|
@ -25,6 +25,8 @@
|
||||
#define PRF_HMAC_SHA256 256
|
||||
#define PRF_HMAC_SHA512 512
|
||||
|
||||
/// package: trezorcrypto.__init__
|
||||
|
||||
/// class Pbkdf2:
|
||||
/// '''
|
||||
/// PBKDF2 context.
|
||||
|
@ -23,6 +23,8 @@
|
||||
|
||||
#include "rand.h"
|
||||
|
||||
/// package: trezorcrypto.random
|
||||
|
||||
/// def uniform(n: int) -> int:
|
||||
/// '''
|
||||
/// Compute uniform random number from interval 0 ... n - 1.
|
||||
|
@ -21,6 +21,8 @@
|
||||
|
||||
#include "rfc6979.h"
|
||||
|
||||
/// package: trezorcrypto.__init__
|
||||
|
||||
/// class Rfc6979:
|
||||
/// '''
|
||||
/// RFC6979 context.
|
||||
|
@ -22,6 +22,8 @@
|
||||
#include "ripemd160.h"
|
||||
#include "memzero.h"
|
||||
|
||||
/// package: trezorcrypto.__init__
|
||||
|
||||
/// class Ripemd160:
|
||||
/// '''
|
||||
/// RIPEMD160 context.
|
||||
|
@ -22,6 +22,8 @@
|
||||
#include "ecdsa.h"
|
||||
#include "secp256k1.h"
|
||||
|
||||
/// package: trezorcrypto.secp256k1
|
||||
|
||||
/// def generate_secret() -> bytes:
|
||||
/// '''
|
||||
/// Generate secret key.
|
||||
|
@ -22,6 +22,8 @@
|
||||
#include "sha2.h"
|
||||
#include "memzero.h"
|
||||
|
||||
/// package: trezorcrypto.__init__
|
||||
|
||||
/// class Sha1:
|
||||
/// '''
|
||||
/// SHA1 context.
|
||||
|
@ -22,6 +22,8 @@
|
||||
#include "sha2.h"
|
||||
#include "memzero.h"
|
||||
|
||||
/// package: trezorcrypto.__init__
|
||||
|
||||
/// class Sha256:
|
||||
/// '''
|
||||
/// SHA256 context.
|
||||
|
@ -22,6 +22,8 @@
|
||||
#include "sha3.h"
|
||||
#include "memzero.h"
|
||||
|
||||
/// package: trezorcrypto.__init__
|
||||
|
||||
/// class Sha3_256:
|
||||
/// '''
|
||||
/// SHA3_256 context.
|
||||
|
@ -22,6 +22,8 @@
|
||||
#include "sha3.h"
|
||||
#include "memzero.h"
|
||||
|
||||
/// package: trezorcrypto.__init__
|
||||
|
||||
/// class Sha3_512:
|
||||
/// '''
|
||||
/// SHA3_512 context.
|
||||
|
@ -22,6 +22,8 @@
|
||||
#include "sha2.h"
|
||||
#include "memzero.h"
|
||||
|
||||
/// package: trezorcrypto.__init__
|
||||
|
||||
/// class Sha512:
|
||||
/// '''
|
||||
/// SHA512 context.
|
||||
|
@ -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) },
|
||||
|
@ -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.
|
||||
'''
|
0
mocks/generated/trezorcrypto/.mock-generated
Normal file
0
mocks/generated/trezorcrypto/.mock-generated
Normal file
312
mocks/generated/trezorcrypto/__init__.py
Normal file
312
mocks/generated/trezorcrypto/__init__.py
Normal 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.
|
||||
'''
|
118
mocks/generated/trezorcrypto/bip32.py
Normal file
118
mocks/generated/trezorcrypto/bip32.py
Normal 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
|
||||
'''
|
38
mocks/generated/trezorcrypto/bip39.py
Normal file
38
mocks/generated/trezorcrypto/bip39.py
Normal 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.
|
||||
'''
|
20
mocks/generated/trezorcrypto/curve25519.py
Normal file
20
mocks/generated/trezorcrypto/curve25519.py
Normal 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.
|
||||
'''
|
50
mocks/generated/trezorcrypto/ed25519.py
Normal file
50
mocks/generated/trezorcrypto/ed25519.py
Normal 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.
|
||||
'''
|
313
mocks/generated/trezorcrypto/monero.py
Normal file
313
mocks/generated/trezorcrypto/monero.py
Normal 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
|
||||
'''
|
13
mocks/generated/trezorcrypto/nem.py
Normal file
13
mocks/generated/trezorcrypto/nem.py
Normal 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
|
||||
'''
|
40
mocks/generated/trezorcrypto/nist256p1.py
Normal file
40
mocks/generated/trezorcrypto/nist256p1.py
Normal 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.
|
||||
'''
|
19
mocks/generated/trezorcrypto/random.py
Normal file
19
mocks/generated/trezorcrypto/random.py
Normal 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).
|
||||
'''
|
40
mocks/generated/trezorcrypto/secp256k1.py
Normal file
40
mocks/generated/trezorcrypto/secp256k1.py
Normal 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.
|
||||
'''
|
@ -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:
|
||||
'''
|
||||
'''
|
||||
|
||||
|
@ -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.
|
||||
|
@ -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.
|
||||
'''
|
||||
|
@ -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
321
src/apps/monero/README.md
Normal 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
|
12
src/apps/monero/__init__.py
Normal file
12
src/apps/monero/__init__.py
Normal 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
114
src/apps/monero/diag.py
Normal 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()
|
17
src/apps/monero/get_address.py
Normal file
17
src/apps/monero/get_address.py
Normal 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)
|
16
src/apps/monero/get_watch_only.py
Normal file
16
src/apps/monero/get_watch_only.py
Normal 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)
|
108
src/apps/monero/key_image_sync.py
Normal file
108
src/apps/monero/key_image_sync.py
Normal 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)
|
121
src/apps/monero/layout/common.py
Normal file
121
src/apps/monero/layout/common.py
Normal 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)
|
125
src/apps/monero/layout/confirms.py
Normal file
125
src/apps/monero/layout/confirms.py
Normal 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
21
src/apps/monero/misc.py
Normal 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
139
src/apps/monero/sign_tx.py
Normal 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")
|
60
src/apps/monero/signing/__init__.py
Normal file
60
src/apps/monero/signing/__init__.py
Normal 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
|
130
src/apps/monero/signing/offloading_keys.py
Normal file
130
src/apps/monero/signing/offloading_keys.py
Normal 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
|
146
src/apps/monero/signing/state.py
Normal file
146
src/apps/monero/signing/state.py
Normal 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
|
375
src/apps/monero/signing/step_01_init_transaction.py
Normal file
375
src/apps/monero/signing/step_01_init_transaction.py
Normal 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
|
171
src/apps/monero/signing/step_02_set_input.py
Normal file
171
src/apps/monero/signing/step_02_set_input.py
Normal 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
|
42
src/apps/monero/signing/step_03_inputs_permutation.py
Normal file
42
src/apps/monero/signing/step_03_inputs_permutation.py
Normal 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")
|
73
src/apps/monero/signing/step_04_input_vini.py
Normal file
73
src/apps/monero/signing/step_04_input_vini.py
Normal 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)
|
58
src/apps/monero/signing/step_05_all_inputs_set.py
Normal file
58
src/apps/monero/signing/step_05_all_inputs_set.py
Normal 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
|
440
src/apps/monero/signing/step_06_set_output.py
Normal file
440
src/apps/monero/signing/step_06_set_output.py
Normal 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
|
171
src/apps/monero/signing/step_07_all_outputs_set.py
Normal file
171
src/apps/monero/signing/step_07_all_outputs_set.py
Normal 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)
|
192
src/apps/monero/signing/step_09_sign_input.py
Normal file
192
src/apps/monero/signing/step_09_sign_input.py
Normal 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
|
43
src/apps/monero/signing/step_10_sign_final.py
Normal file
43
src/apps/monero/signing/step_10_sign_final.py
Normal 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
|
0
src/apps/monero/xmr/__init__.py
Normal file
0
src/apps/monero/xmr/__init__.py
Normal file
86
src/apps/monero/xmr/addresses.py
Normal file
86
src/apps/monero/xmr/addresses.py
Normal 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
|
1583
src/apps/monero/xmr/bulletproof.py
Normal file
1583
src/apps/monero/xmr/bulletproof.py
Normal file
File diff suppressed because one or more lines are too long
45
src/apps/monero/xmr/credentials.py
Normal file
45
src/apps/monero/xmr/credentials.py
Normal 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,
|
||||
)
|
299
src/apps/monero/xmr/crypto/__init__.py
Normal file
299
src/apps/monero/xmr/crypto/__init__.py
Normal 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)
|
40
src/apps/monero/xmr/crypto/chacha_poly.py
Normal file
40
src/apps/monero/xmr/crypto/chacha_poly.py
Normal 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)
|
27
src/apps/monero/xmr/keccak_hasher.py
Normal file
27
src/apps/monero/xmr/keccak_hasher.py
Normal 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)
|
111
src/apps/monero/xmr/key_image.py
Normal file
111
src/apps/monero/xmr/key_image.py
Normal 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
|
320
src/apps/monero/xmr/mlsag.py
Normal file
320
src/apps/monero/xmr/mlsag.py
Normal 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)
|
112
src/apps/monero/xmr/mlsag_hasher.py
Normal file
112
src/apps/monero/xmr/mlsag_hasher.py
Normal 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()
|
261
src/apps/monero/xmr/monero.py
Normal file
261
src/apps/monero/xmr/monero.py
Normal 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
|
51
src/apps/monero/xmr/networks.py
Normal file
51
src/apps/monero/xmr/networks.py
Normal 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])
|
144
src/apps/monero/xmr/range_signatures.py
Normal file
144
src/apps/monero/xmr/range_signatures.py
Normal 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]
|
27
src/apps/monero/xmr/serialize/__init__.py
Normal file
27
src/apps/monero/xmr/serialize/__init__.py
Normal 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
|
36
src/apps/monero/xmr/serialize/base_types.py
Normal file
36
src/apps/monero/xmr/serialize/base_types.py
Normal 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
|
109
src/apps/monero/xmr/serialize/int_serialize.py
Normal file
109
src/apps/monero/xmr/serialize/int_serialize.py
Normal 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
|
163
src/apps/monero/xmr/serialize/message_types.py
Normal file
163
src/apps/monero/xmr/serialize/message_types.py
Normal 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 ()
|
106
src/apps/monero/xmr/serialize/readwriter.py
Normal file
106
src/apps/monero/xmr/serialize/readwriter.py
Normal 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]
|
0
src/apps/monero/xmr/serialize_messages/__init__.py
Normal file
0
src/apps/monero/xmr/serialize_messages/__init__.py
Normal file
31
src/apps/monero/xmr/serialize_messages/base.py
Normal file
31
src/apps/monero/xmr/serialize_messages/base.py
Normal 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
|
24
src/apps/monero/xmr/serialize_messages/ct_keys.py
Normal file
24
src/apps/monero/xmr/serialize_messages/ct_keys.py
Normal 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))
|
5
src/apps/monero/xmr/serialize_messages/tx_ecdh.py
Normal file
5
src/apps/monero/xmr/serialize_messages/tx_ecdh.py
Normal file
@ -0,0 +1,5 @@
|
||||
from apps.monero.xmr.serialize.message_types import MessageType
|
||||
|
||||
|
||||
class EcdhTuple(MessageType):
|
||||
__slots__ = ("mask", "amount")
|
11
src/apps/monero/xmr/serialize_messages/tx_full.py
Normal file
11
src/apps/monero/xmr/serialize_messages/tx_full.py
Normal 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))
|
102
src/apps/monero/xmr/serialize_messages/tx_prefix.py
Normal file
102
src/apps/monero/xmr/serialize_messages/tx_prefix.py
Normal 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))
|
@ -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),
|
||||
)
|
6
src/apps/monero/xmr/types.py
Normal file
6
src/apps/monero/xmr/types.py
Normal 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
|
@ -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()
|
||||
|
@ -6,6 +6,7 @@ from trezorcrypto import ( # noqa: F401
|
||||
bip39,
|
||||
chacha20poly1305,
|
||||
crc,
|
||||
monero,
|
||||
nem,
|
||||
pbkdf2,
|
||||
random,
|
||||
|
@ -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
|
||||
|
@ -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),
|
||||
}
|
||||
|
@ -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),
|
||||
}
|
||||
|
@ -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),
|
||||
}
|
@ -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
Loading…
Reference in New Issue
Block a user