diff --git a/.travis.yml b/.travis.yml
index 11de9b819..cc30b1c13 100644
--- a/.travis.yml
+++ b/.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:
diff --git a/Makefile b/Makefile
index 090a3deb4..6f150830f 100644
--- a/Makefile
+++ b/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)
diff --git a/Pipfile b/Pipfile
index 50aceacef..61c5230e2 100644
--- a/Pipfile
+++ b/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
diff --git a/SConscript.firmware b/SConscript.firmware
index 6a2fd7b7c..a406e62d2 100644
--- a/SConscript.firmware
+++ b/SConscript.firmware
@@ -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',
diff --git a/SConscript.unix b/SConscript.unix
index 4b96dff02..1ad8dcfac 100644
--- a/SConscript.unix
+++ b/SConscript.unix
@@ -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',
diff --git a/embed/extmod/modtrezorcrypto/modtrezorcrypto-aes.h b/embed/extmod/modtrezorcrypto/modtrezorcrypto-aes.h
index 5671d3ab9..aea3c25d9 100644
--- a/embed/extmod/modtrezorcrypto/modtrezorcrypto-aes.h
+++ b/embed/extmod/modtrezorcrypto/modtrezorcrypto-aes.h
@@ -30,6 +30,8 @@ enum AESMode {
CTR = 0x04,
};
+/// package: trezorcrypto.__init__
+
/// class AES:
/// '''
/// AES context.
diff --git a/embed/extmod/modtrezorcrypto/modtrezorcrypto-bip32.h b/embed/extmod/modtrezorcrypto/modtrezorcrypto-bip32.h
index 2b03b6990..76f696b1d 100644
--- a/embed/extmod/modtrezorcrypto/modtrezorcrypto-bip32.h
+++ b/embed/extmod/modtrezorcrypto/modtrezorcrypto-bip32.h
@@ -27,6 +27,8 @@
#include "memzero.h"
#include "nem.h"
+/// package: trezorcrypto.bip32
+
/// class HDNode:
/// '''
/// BIP0032 HD node structure.
diff --git a/embed/extmod/modtrezorcrypto/modtrezorcrypto-bip39.h b/embed/extmod/modtrezorcrypto/modtrezorcrypto-bip39.h
index e6f412f3d..fb245dece 100644
--- a/embed/extmod/modtrezorcrypto/modtrezorcrypto-bip39.h
+++ b/embed/extmod/modtrezorcrypto/modtrezorcrypto-bip39.h
@@ -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.
diff --git a/embed/extmod/modtrezorcrypto/modtrezorcrypto-blake256.h b/embed/extmod/modtrezorcrypto/modtrezorcrypto-blake256.h
index d15c2ca78..d7937cd4e 100644
--- a/embed/extmod/modtrezorcrypto/modtrezorcrypto-blake256.h
+++ b/embed/extmod/modtrezorcrypto/modtrezorcrypto-blake256.h
@@ -22,6 +22,8 @@
#include "blake256.h"
#include "memzero.h"
+/// package: trezorcrypto.__init__
+
/// class Blake256:
/// '''
/// Blake256 context.
diff --git a/embed/extmod/modtrezorcrypto/modtrezorcrypto-blake2b.h b/embed/extmod/modtrezorcrypto/modtrezorcrypto-blake2b.h
index d860237e4..ea61853d2 100644
--- a/embed/extmod/modtrezorcrypto/modtrezorcrypto-blake2b.h
+++ b/embed/extmod/modtrezorcrypto/modtrezorcrypto-blake2b.h
@@ -24,6 +24,8 @@
#include "blake2b.h"
#include "memzero.h"
+/// package: trezorcrypto.__init__
+
/// class Blake2b:
/// '''
/// Blake2b context.
diff --git a/embed/extmod/modtrezorcrypto/modtrezorcrypto-blake2s.h b/embed/extmod/modtrezorcrypto/modtrezorcrypto-blake2s.h
index fb14b352a..497109025 100644
--- a/embed/extmod/modtrezorcrypto/modtrezorcrypto-blake2s.h
+++ b/embed/extmod/modtrezorcrypto/modtrezorcrypto-blake2s.h
@@ -24,6 +24,8 @@
#include "blake2s.h"
#include "memzero.h"
+/// package: trezorcrypto.__init__
+
/// class Blake2s:
/// '''
/// Blake2s context.
diff --git a/embed/extmod/modtrezorcrypto/modtrezorcrypto-chacha20poly1305.h b/embed/extmod/modtrezorcrypto/modtrezorcrypto-chacha20poly1305.h
index 9b4a04c4d..98563f5bc 100644
--- a/embed/extmod/modtrezorcrypto/modtrezorcrypto-chacha20poly1305.h
+++ b/embed/extmod/modtrezorcrypto/modtrezorcrypto-chacha20poly1305.h
@@ -22,6 +22,8 @@
#include "chacha20poly1305/rfc7539.h"
#include "memzero.h"
+/// package: trezorcrypto.__init__
+
/// class ChaCha20Poly1305:
/// '''
/// ChaCha20Poly1305 context.
diff --git a/embed/extmod/modtrezorcrypto/modtrezorcrypto-curve25519.h b/embed/extmod/modtrezorcrypto/modtrezorcrypto-curve25519.h
index 653234b2f..bd24e6fcb 100644
--- a/embed/extmod/modtrezorcrypto/modtrezorcrypto-curve25519.h
+++ b/embed/extmod/modtrezorcrypto/modtrezorcrypto-curve25519.h
@@ -23,6 +23,8 @@
#include "rand.h"
+/// package: trezorcrypto.curve25519
+
/// def generate_secret() -> bytes:
/// '''
/// Generate secret key.
diff --git a/embed/extmod/modtrezorcrypto/modtrezorcrypto-ed25519.h b/embed/extmod/modtrezorcrypto/modtrezorcrypto-ed25519.h
index e042ca4b1..8ed1f5ea6 100644
--- a/embed/extmod/modtrezorcrypto/modtrezorcrypto-ed25519.h
+++ b/embed/extmod/modtrezorcrypto/modtrezorcrypto-ed25519.h
@@ -24,6 +24,8 @@
#include "rand.h"
+/// package: trezorcrypto.ed25519
+
/// def generate_secret() -> bytes:
/// '''
/// Generate secret key.
diff --git a/embed/extmod/modtrezorcrypto/modtrezorcrypto-groestl.h b/embed/extmod/modtrezorcrypto/modtrezorcrypto-groestl.h
index f9e4794f8..87071f5e6 100644
--- a/embed/extmod/modtrezorcrypto/modtrezorcrypto-groestl.h
+++ b/embed/extmod/modtrezorcrypto/modtrezorcrypto-groestl.h
@@ -25,6 +25,8 @@
#define GROESTL512_DIGEST_LENGTH 64
#define GROESTL512_BLOCK_LENGTH 128
+/// package: trezorcrypto.__init__
+
/// class Groestl512:
/// '''
/// GROESTL512 context.
diff --git a/embed/extmod/modtrezorcrypto/modtrezorcrypto-monero.h b/embed/extmod/modtrezorcrypto/modtrezorcrypto-monero.h
new file mode 100644
index 000000000..67508205f
--- /dev/null
+++ b/embed/extmod/modtrezorcrypto/modtrezorcrypto-monero.h
@@ -0,0 +1,1221 @@
+/*
+ * This file is part of the TREZOR project, https://trezor.io/
+ *
+ * Copyright (c) SatoshiLabs
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#include "py/objstr.h"
+#include "py/objint.h"
+#include "py/mpz.h"
+
+#include "monero/monero.h"
+#include "bignum.h"
+
+/// package: trezorcrypto.monero
+
+
+
+typedef struct _mp_obj_hasher_t {
+ mp_obj_base_t base;
+ Hasher h;
+} mp_obj_hasher_t;
+
+typedef struct _mp_obj_ge25519_t {
+ mp_obj_base_t base;
+ ge25519 p;
+} mp_obj_ge25519_t;
+
+typedef struct _mp_obj_bignum256modm_t {
+ mp_obj_base_t base;
+ bignum256modm p;
+} mp_obj_bignum256modm_t;
+
+
+//
+// Helpers
+//
+
+STATIC const mp_obj_type_t mod_trezorcrypto_monero_ge25519_type;
+STATIC const mp_obj_type_t mod_trezorcrypto_monero_bignum256modm_type;
+STATIC const mp_obj_type_t mod_trezorcrypto_monero_hasher_type;
+
+#define MP_OBJ_IS_GE25519(o) MP_OBJ_IS_TYPE((o), &mod_trezorcrypto_monero_ge25519_type)
+#define MP_OBJ_IS_SCALAR(o) MP_OBJ_IS_TYPE((o), &mod_trezorcrypto_monero_bignum256modm_type)
+#define MP_OBJ_PTR_MPC_GE25519(o) ((const mp_obj_ge25519_t*) (o))
+#define MP_OBJ_PTR_MPC_SCALAR(o) ((const mp_obj_bignum256modm_t*) (o))
+#define MP_OBJ_PTR_MP_GE25519(o) ((mp_obj_ge25519_t*) (o))
+#define MP_OBJ_PTR_MP_SCALAR(o) ((mp_obj_bignum256modm_t*) (o))
+#define MP_OBJ_C_GE25519(o) (MP_OBJ_PTR_MPC_GE25519(o)->p)
+#define MP_OBJ_GE25519(o) (MP_OBJ_PTR_MP_GE25519(o)->p)
+#define MP_OBJ_C_SCALAR(o) (MP_OBJ_PTR_MPC_SCALAR(o)->p)
+#define MP_OBJ_SCALAR(o) (MP_OBJ_PTR_MP_SCALAR(o)->p)
+
+STATIC inline void assert_ge25519(const mp_obj_t o){
+ if (!MP_OBJ_IS_GE25519(o)){
+ mp_raise_ValueError("ge25519 expected");
+ }
+}
+
+STATIC inline void assert_scalar(const mp_obj_t o){
+ if (!MP_OBJ_IS_SCALAR(o)){
+ mp_raise_ValueError("scalar expected");
+ }
+}
+
+
+static uint64_t mp_obj_uint64_get_checked(mp_const_obj_t self_in) {
+#if MICROPY_LONGINT_IMPL != MICROPY_LONGINT_IMPL_MPZ
+# error "MPZ supported only"
+#endif
+
+ if (MP_OBJ_IS_SMALL_INT(self_in)) {
+ return MP_OBJ_SMALL_INT_VALUE(self_in);
+ } else {
+ byte buff[8];
+ uint64_t res = 0;
+ mp_obj_t * o = MP_OBJ_TO_PTR(self_in);
+
+ mp_obj_int_to_bytes_impl(o, true, 8, buff);
+ for (int i = 0; i<8; i++){
+ res <<= i > 0 ? 8 : 0;
+ res |= (uint64_t)(buff[i] & 0xff);
+ }
+ return res;
+ }
+}
+
+static uint64_t mp_obj_get_uint64(mp_const_obj_t arg) {
+ if (arg == mp_const_false) {
+ return 0;
+ } else if (arg == mp_const_true) {
+ return 1;
+ } else if (MP_OBJ_IS_SMALL_INT(arg)) {
+ return MP_OBJ_SMALL_INT_VALUE(arg);
+ } else if (MP_OBJ_IS_TYPE(arg, &mp_type_int)) {
+ return mp_obj_uint64_get_checked(arg);
+ } else {
+ if (MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE) {
+ mp_raise_TypeError("can't convert to int");
+ } else {
+ nlr_raise(mp_obj_new_exception_msg_varg(&mp_type_TypeError,
+ "can't convert %s to int", mp_obj_get_type_str(arg)));
+ }
+ }
+}
+
+STATIC mp_obj_t mp_obj_new_scalar(){
+ mp_obj_bignum256modm_t *o = m_new_obj(mp_obj_bignum256modm_t);
+ o->base.type = &mod_trezorcrypto_monero_bignum256modm_type;
+ set256_modm(o->p, 0);
+ return MP_OBJ_FROM_PTR(o);
+}
+
+STATIC mp_obj_t mp_obj_new_scalar_r(mp_obj_t r){
+ if (r == mp_const_none){
+ return mp_obj_new_scalar();
+ }
+
+ assert_scalar(r);
+ return r;
+}
+
+STATIC mp_obj_t mp_obj_new_ge25519(){
+ mp_obj_ge25519_t *o = m_new_obj(mp_obj_ge25519_t);
+ o->base.type = &mod_trezorcrypto_monero_ge25519_type;
+ ge25519_set_neutral(&o->p);
+ return MP_OBJ_FROM_PTR(o);
+}
+
+STATIC mp_obj_t mp_obj_new_ge25519_r(mp_obj_t r){
+ if (r == mp_const_none){
+ return mp_obj_new_ge25519();
+ }
+
+ assert_ge25519(r);
+ return r;
+}
+
+STATIC void mp_unpack_ge25519(ge25519 * r, const mp_obj_t arg, mp_int_t offset){
+ mp_buffer_info_t buff;
+ mp_get_buffer_raise(arg, &buff, MP_BUFFER_READ);
+ if (buff.len < 32 + offset) {
+ mp_raise_ValueError("Invalid length of the EC point");
+ }
+
+ const int res = ge25519_unpack_vartime(r, ((uint8_t*)buff.buf) + offset);
+ if (res != 1){
+ mp_raise_ValueError("Point decoding error");
+ }
+}
+
+STATIC void mp_unpack_scalar(bignum256modm r, const mp_obj_t arg, mp_int_t offset){
+ mp_buffer_info_t buff;
+ mp_get_buffer_raise(arg, &buff, MP_BUFFER_READ);
+ if (buff.len < 32 + offset) {
+ mp_raise_ValueError("Invalid length of secret key");
+ }
+ expand256_modm(r, ((uint8_t*)buff.buf) + offset, 32);
+}
+
+
+
+//
+// Constructors
+//
+
+/// class Ge25519:
+/// '''
+/// EC point on ED25519
+/// '''
+///
+/// def __init__(x: Optional[Union[Ge25519, bytes]] = None):
+/// '''
+/// Constructor
+/// '''
+
+STATIC mp_obj_t mod_trezorcrypto_monero_ge25519_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) {
+ mp_arg_check_num(n_args, n_kw, 0, 1, false);
+ mp_obj_ge25519_t *o = m_new_obj(mp_obj_ge25519_t);
+ o->base.type = type;
+
+ if (n_args == 0 || args[0] == mp_const_none) {
+ ge25519_set_neutral(&o->p);
+ } else if (n_args == 1 && MP_OBJ_IS_GE25519(args[0])) {
+ ge25519_copy(&o->p, &MP_OBJ_C_GE25519(args[0]));
+ } else if (n_args == 1 && MP_OBJ_IS_STR_OR_BYTES(args[0])) {
+ mp_unpack_ge25519(&o->p, args[0], 0);
+ } else {
+ mp_raise_ValueError("Invalid ge25519 constructor");
+ }
+
+ return MP_OBJ_FROM_PTR(o);
+}
+
+STATIC mp_obj_t mod_trezorcrypto_monero_ge25519___del__(mp_obj_t self) {
+ mp_obj_ge25519_t *o = MP_OBJ_TO_PTR(self);
+ memzero(&(o->p), sizeof(ge25519));
+ return mp_const_none;
+}
+STATIC MP_DEFINE_CONST_FUN_OBJ_1(mod_trezorcrypto_monero_ge25519___del___obj, mod_trezorcrypto_monero_ge25519___del__);
+
+
+/// class Sc25519:
+/// '''
+/// EC scalar on SC25519
+/// '''
+///
+/// def __init__(x: Optional[Union[Sc25519, bytes, int]] = None):
+/// '''
+/// Constructor
+/// '''
+///
+///
+STATIC mp_obj_t mod_trezorcrypto_monero_bignum256modm_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) {
+ mp_arg_check_num(n_args, n_kw, 0, 1, false);
+ mp_obj_bignum256modm_t *o = m_new_obj(mp_obj_bignum256modm_t);
+ o->base.type = type;
+
+ if (n_args == 0 || args[0] == mp_const_none) {
+ set256_modm(o->p, 0);
+ } else if (n_args == 1 && MP_OBJ_IS_SCALAR(args[0])) {
+ copy256_modm(o->p, MP_OBJ_C_SCALAR(args[0]));
+ } else if (n_args == 1 && MP_OBJ_IS_STR_OR_BYTES(args[0])) {
+ mp_unpack_scalar(o->p, args[0], 0);
+ } else if (n_args == 1 && mp_obj_is_integer(args[0])) {
+ uint64_t v = mp_obj_get_uint64(args[0]);
+ set256_modm(o->p, v);
+ } else {
+ mp_raise_ValueError("Invalid scalar constructor");
+ }
+
+ return MP_OBJ_FROM_PTR(o);
+}
+
+STATIC mp_obj_t mod_trezorcrypto_monero_bignum256modm___del__(mp_obj_t self) {
+ mp_obj_bignum256modm_t *o = MP_OBJ_TO_PTR(self);
+ memzero(o->p, sizeof(bignum256modm));
+ return mp_const_none;
+}
+STATIC MP_DEFINE_CONST_FUN_OBJ_1(mod_trezorcrypto_monero_bignum256modm___del___obj, mod_trezorcrypto_monero_bignum256modm___del__);
+
+
+/// 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
+/// '''
+///
+///
+STATIC mp_obj_t mod_trezorcrypto_monero_hasher_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) {
+ mp_arg_check_num(n_args, n_kw, 0, 1, false);
+ mp_obj_hasher_t *o = m_new_obj(mp_obj_hasher_t);
+ o->base.type = type;
+ xmr_hasher_init(&(o->h));
+
+ if (n_args == 1 && MP_OBJ_IS_STR_OR_BYTES(args[0])) {
+ mp_buffer_info_t buff;
+ mp_get_buffer_raise(args[0], &buff, MP_BUFFER_READ);
+ xmr_hasher_update(&o->h, buff.buf, buff.len);
+ }
+
+ return MP_OBJ_FROM_PTR(o);
+}
+
+STATIC mp_obj_t mod_trezorcrypto_monero_hasher___del__(mp_obj_t self) {
+ mp_obj_hasher_t *o = MP_OBJ_TO_PTR(self);
+ memzero(&(o->h), sizeof(Hasher));
+ return mp_const_none;
+}
+STATIC MP_DEFINE_CONST_FUN_OBJ_1(mod_trezorcrypto_monero_hasher___del___obj, mod_trezorcrypto_monero_hasher___del__);
+
+
+//
+// Scalar defs
+//
+
+/// mock:global
+
+/// def init256_modm(dst: Optional[Sc25519], val: Union[int, bytes, Sc25519]) -> Sc25519:
+/// '''
+/// Initializes Sc25519 scalar
+/// '''
+STATIC mp_obj_t mod_trezorcrypto_monero_init256_modm(size_t n_args, const mp_obj_t *args){
+ const bool res_arg = n_args == 2;
+ const int off = res_arg ? 0 : -1;
+ mp_obj_t res = mp_obj_new_scalar_r(res_arg ? args[0] : mp_const_none);
+
+ if (n_args == 0 || args[0] == mp_const_none) {
+ set256_modm(MP_OBJ_SCALAR(res), 0);
+ } else if (n_args > 0 && MP_OBJ_IS_SCALAR(args[1+off])) {
+ copy256_modm(MP_OBJ_SCALAR(res), MP_OBJ_C_SCALAR(args[1+off]));
+ } else if (n_args > 0 && MP_OBJ_IS_STR_OR_BYTES(args[1+off])) {
+ mp_unpack_scalar(MP_OBJ_SCALAR(res), args[1+off], 0);
+ } else if (n_args > 0 && mp_obj_is_integer(args[1+off])) {
+ uint64_t v = mp_obj_get_uint64(args[1+off]);
+ set256_modm(MP_OBJ_SCALAR(res), v);
+ } else {
+ mp_raise_ValueError("Invalid scalar def");
+ }
+ return res;
+}
+STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_trezorcrypto_monero_init256_modm_obj, 0, 2, mod_trezorcrypto_monero_init256_modm);
+
+/// def check256_modm(val: Sc25519):
+/// '''
+/// Throws exception if scalar is invalid
+/// '''
+STATIC mp_obj_t mod_trezorcrypto_monero_check256_modm(const mp_obj_t arg){
+ assert_scalar(arg);
+ if (check256_modm(MP_OBJ_C_SCALAR(arg)) != 1){
+ mp_raise_ValueError("Ed25519 scalar invalid");
+ }
+ return mp_const_none;
+}
+STATIC MP_DEFINE_CONST_FUN_OBJ_1(mod_trezorcrypto_monero_check256_modm_obj, mod_trezorcrypto_monero_check256_modm);
+
+/// def iszero256_modm(val: Sc25519) -> bool:
+/// '''
+/// Returns False if the scalar is zero
+/// '''
+STATIC mp_obj_t mod_trezorcrypto_monero_iszero256_modm(const mp_obj_t arg){
+ assert_scalar(arg);
+ const int r = iszero256_modm(MP_OBJ_C_SCALAR(arg));
+ return mp_obj_new_int(r);
+}
+STATIC MP_DEFINE_CONST_FUN_OBJ_1(mod_trezorcrypto_monero_iszero256_modm_obj, mod_trezorcrypto_monero_iszero256_modm);
+
+/// def eq256_modm(a: Sc25519, b: Sc25519) -> int:
+/// '''
+/// Compares scalars, returns 1 on the same value
+/// '''
+STATIC mp_obj_t mod_trezorcrypto_monero_eq256_modm(const mp_obj_t a, const mp_obj_t b){
+ assert_scalar(a);
+ assert_scalar(b);
+ int r = eq256_modm(MP_OBJ_C_SCALAR(a), MP_OBJ_C_SCALAR(b));
+ return MP_OBJ_NEW_SMALL_INT(r);
+}
+STATIC MP_DEFINE_CONST_FUN_OBJ_2(mod_trezorcrypto_monero_eq256_modm_obj, mod_trezorcrypto_monero_eq256_modm);
+
+
+/// def get256_modm(a: Sc25519) -> int:
+/// '''
+/// Extracts 64bit integer from the scalar. Raises exception if scalar is bigger than 2^64
+/// '''
+STATIC mp_obj_t mod_trezorcrypto_monero_get256_modm(const mp_obj_t arg){
+ assert_scalar(arg);
+ uint64_t v;
+ if (!get256_modm(&v, MP_OBJ_C_SCALAR(arg))){
+ mp_raise_ValueError("Ed25519 scalar too big");
+ }
+ return mp_obj_new_int_from_ull(v);
+}
+STATIC MP_DEFINE_CONST_FUN_OBJ_1(mod_trezorcrypto_monero_get256_modm_obj, mod_trezorcrypto_monero_get256_modm);
+
+
+/// def add256_modm(r: Optional[Sc25519], a: Sc25519, b: Sc25519) -> Sc25519:
+/// '''
+/// Scalar addition
+/// '''
+STATIC mp_obj_t mod_trezorcrypto_monero_add256_modm(size_t n_args, const mp_obj_t *args){
+ const bool res_arg = n_args == 3;
+ const int off = res_arg ? 0 : -1;
+ mp_obj_t res = mp_obj_new_scalar_r(res_arg ? args[0] : mp_const_none);
+
+ assert_scalar(args[1+off]);
+ assert_scalar(args[2+off]);
+ add256_modm(MP_OBJ_SCALAR(res), MP_OBJ_C_SCALAR(args[1+off]), MP_OBJ_C_SCALAR(args[2+off]));
+ return res;
+}
+STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_trezorcrypto_monero_add256_modm_obj, 2, 3, mod_trezorcrypto_monero_add256_modm);
+
+/// def sub256_modm(r: Optional[Sc25519], a: Sc25519, b: Sc25519) -> Sc25519:
+/// '''
+/// Scalar subtraction
+/// '''
+STATIC mp_obj_t mod_trezorcrypto_monero_sub256_modm(size_t n_args, const mp_obj_t *args){
+ const bool res_arg = n_args == 3;
+ const int off = res_arg ? 0 : -1;
+ mp_obj_t res = mp_obj_new_scalar_r(res_arg ? args[0] : mp_const_none);
+
+ assert_scalar(args[1+off]);
+ assert_scalar(args[2+off]);
+ sub256_modm(MP_OBJ_SCALAR(res), MP_OBJ_C_SCALAR(args[1+off]), MP_OBJ_C_SCALAR(args[2+off]));
+ return res;
+}
+STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_trezorcrypto_monero_sub256_modm_obj, 2, 3, mod_trezorcrypto_monero_sub256_modm);
+
+/// def mul256_modm(r: Optional[Sc25519], a: Sc25519, b: Sc25519) -> Sc25519:
+/// '''
+/// Scalar multiplication
+/// '''
+STATIC mp_obj_t mod_trezorcrypto_monero_mul256_modm(size_t n_args, const mp_obj_t *args){
+ const bool res_arg = n_args == 3;
+ const int off = res_arg ? 0 : -1;
+ mp_obj_t res = mp_obj_new_scalar_r(res_arg ? args[0] : mp_const_none);
+
+ assert_scalar(args[1+off]);
+ assert_scalar(args[2+off]);
+ mul256_modm(MP_OBJ_SCALAR(res), MP_OBJ_C_SCALAR(args[1+off]), MP_OBJ_C_SCALAR(args[2+off]));
+ return res;
+}
+STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_trezorcrypto_monero_mul256_modm_obj, 2, 3, mod_trezorcrypto_monero_mul256_modm);
+
+/// def mulsub256_modm(r: Optional[Sc25519], a: Sc25519, b: Sc25519, c: Sc25519) -> Sc25519:
+/// '''
+/// c - a*b
+/// '''
+STATIC mp_obj_t mod_trezorcrypto_monero_mulsub256_modm(size_t n_args, const mp_obj_t *args){
+ const bool res_arg = n_args == 4;
+ const int off = res_arg ? 0 : -1;
+ mp_obj_t res = mp_obj_new_scalar_r(res_arg ? args[0] : mp_const_none);
+
+ assert_scalar(args[1+off]);
+ assert_scalar(args[2+off]);
+ assert_scalar(args[3+off]);
+ mulsub256_modm(MP_OBJ_SCALAR(res), MP_OBJ_C_SCALAR(args[1+off]), MP_OBJ_C_SCALAR(args[2+off]), MP_OBJ_C_SCALAR(args[3+off]));
+ return res;
+}
+STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_trezorcrypto_monero_mulsub256_modm_obj, 3, 4, mod_trezorcrypto_monero_mulsub256_modm);
+
+/// def muladd256_modm(r: Optional[Sc25519], a: Sc25519, b: Sc25519, c: Sc25519) -> Sc25519:
+/// '''
+/// c + a*b
+/// '''
+STATIC mp_obj_t mod_trezorcrypto_monero_muladd256_modm(size_t n_args, const mp_obj_t *args){
+ const bool res_arg = n_args == 4;
+ const int off = res_arg ? 0 : -1;
+ mp_obj_t res = mp_obj_new_scalar_r(res_arg ? args[0] : mp_const_none);
+
+ assert_scalar(args[1+off]);
+ assert_scalar(args[2+off]);
+ assert_scalar(args[3+off]);
+ muladd256_modm(MP_OBJ_SCALAR(res), MP_OBJ_C_SCALAR(args[1+off]), MP_OBJ_C_SCALAR(args[2+off]), MP_OBJ_C_SCALAR(args[3+off]));
+ return res;
+}
+STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_trezorcrypto_monero_muladd256_modm_obj, 3, 4, mod_trezorcrypto_monero_muladd256_modm);
+
+/// def inv256_modm(r: Optional[Sc25519], a: Sc25519) -> Sc25519:
+/// '''
+/// Scalar modular inversion
+/// '''
+STATIC mp_obj_t mod_trezorcrypto_monero_inv256_modm(size_t n_args, const mp_obj_t *args){
+ const bool res_arg = n_args == 2;
+ const int off = res_arg ? 0 : -1;
+ mp_obj_t res = mp_obj_new_scalar_r(res_arg ? args[0] : mp_const_none);
+ assert_scalar(args[1+off]);
+
+ // bn_prime = curve order, little endian encoded
+ bignum256 bn_prime = {.val={0x1cf5d3ed, 0x20498c69, 0x2f79cd65, 0x37be77a8, 0x14, 0x0, 0x0, 0x0, 0x1000}};
+ bignum256 bn_x;
+
+ memcpy(&bn_x.val, MP_OBJ_C_SCALAR(args[1+off]), sizeof(bignum256modm));
+ bn_inverse(&bn_x, &bn_prime);
+ memcpy(MP_OBJ_SCALAR(res), bn_x.val, sizeof(bignum256modm));
+
+ return res;
+}
+STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_trezorcrypto_monero_inv256_modm_obj, 1, 2, mod_trezorcrypto_monero_inv256_modm);
+
+/// def pack256_modm(r: Optional[bytes], a: Sc25519, offset: Optional[int] = 0) -> bytes:
+/// '''
+/// Scalar compression
+/// '''
+STATIC mp_obj_t mod_trezorcrypto_monero_pack256_modm(size_t n_args, const mp_obj_t *args){
+ if (n_args == 1 || args[0] == mp_const_none){
+ assert_scalar(args[0]);
+ uint8_t buff[32];
+ contract256_modm(buff, MP_OBJ_C_SCALAR(args[0]));
+ return mp_obj_new_bytes(buff, 32);
+
+ } else {
+ mp_buffer_info_t bufm;
+ mp_get_buffer_raise(args[0], &bufm, MP_BUFFER_WRITE);
+ const mp_int_t offset = n_args >= 3 ? mp_obj_get_int(args[2]) : 0;
+ if (bufm.len < 32 + offset) {
+ mp_raise_ValueError("Buffer too small");
+ }
+
+ contract256_modm(((uint8_t*)bufm.buf) + offset, MP_OBJ_C_SCALAR(args[1]));
+ return args[0];
+ }
+}
+STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_trezorcrypto_monero_pack256_modm_obj, 1, 3, mod_trezorcrypto_monero_pack256_modm);
+
+/// def unpack256_modm(r: Optional[Sc25519], a: bytes, offset: int = 0) -> Sc25519:
+/// '''
+/// Scalar decompression
+/// '''
+STATIC mp_obj_t mod_trezorcrypto_monero_unpack256_modm(size_t n_args, const mp_obj_t *args){
+ const bool res_arg = n_args >= 2;
+ const int off = res_arg ? 0 : -1;
+ mp_obj_t res = mp_obj_new_scalar_r(res_arg ? args[0] : mp_const_none);
+ const mp_int_t offset = n_args >= 3 ? mp_obj_get_int(args[2]) : 0;
+ mp_unpack_scalar(MP_OBJ_SCALAR(res), args[1+off], offset);
+ return res;
+}
+STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_trezorcrypto_monero_unpack256_modm_obj, 1, 3, mod_trezorcrypto_monero_unpack256_modm);
+
+/// def unpack256_modm_noreduce(r: Optional[Sc25519], a: bytes, offset: int = 0) -> Sc25519:
+/// '''
+/// Scalar decompression, raw, without modular reduction
+/// '''
+STATIC mp_obj_t mod_trezorcrypto_monero_unpack256_modm_noreduce(size_t n_args, const mp_obj_t *args){
+ const bool res_arg = n_args >= 2;
+ const int off = res_arg ? 0 : -1;
+ mp_obj_t res = mp_obj_new_scalar_r(res_arg ? args[0] : mp_const_none);
+ const mp_int_t offset = n_args >= 3 ? mp_obj_get_int(args[2]) : 0;
+
+ mp_buffer_info_t buff;
+ mp_get_buffer_raise(args[1+off], &buff, MP_BUFFER_READ);
+ if (buff.len != 32 + offset) {
+ mp_raise_ValueError("Invalid length of secret key");
+ }
+
+ expand_raw256_modm(MP_OBJ_SCALAR(res), ((uint8_t*)buff.buf) + offset);
+ return res;
+}
+STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_trezorcrypto_monero_unpack256_modm_noreduce_obj, 1, 3, mod_trezorcrypto_monero_unpack256_modm_noreduce);
+
+//
+// GE25519 Defs
+//
+
+/// def ge25519_set_neutral(r: Optional[Ge25519]) -> Ge25519:
+/// '''
+/// Sets neutral point
+/// '''
+STATIC mp_obj_t mod_trezorcrypto_monero_ge25519_set_neutral(size_t n_args, const mp_obj_t *args){
+ mp_obj_t res = mp_obj_new_ge25519_r(n_args == 1 ? args[0] : mp_const_none);
+ ge25519_set_neutral(&MP_OBJ_GE25519(res));
+ return res;
+}
+STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_trezorcrypto_monero_ge25519_set_neutral_obj, 0, 1, mod_trezorcrypto_monero_ge25519_set_neutral);
+
+/// def ge25519_set_xmr_h(r: Optional[Ge25519]) -> Ge25519:
+/// '''
+/// Sets H point
+/// '''
+STATIC mp_obj_t mod_trezorcrypto_monero_ge25519_set_xmr_h(size_t n_args, const mp_obj_t *args){
+ mp_obj_t res = mp_obj_new_ge25519_r(n_args == 1 ? args[0] : mp_const_none);
+ ge25519_set_xmr_h(&MP_OBJ_GE25519(res));
+ return res;
+}
+STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_trezorcrypto_monero_ge25519_set_xmr_h_obj, 0, 1, mod_trezorcrypto_monero_ge25519_set_xmr_h);
+
+/// def ge25519_check(r: Ge25519):
+/// '''
+/// Checks point, throws if not on curve
+/// '''
+STATIC mp_obj_t mod_trezorcrypto_monero_ge25519_check(const mp_obj_t arg){
+ assert_ge25519(arg);
+ if (ge25519_check(&MP_OBJ_C_GE25519(arg)) != 1){
+ mp_raise_ValueError("Ed25519 point not on curve");
+ }
+ return mp_const_none;
+}
+STATIC MP_DEFINE_CONST_FUN_OBJ_1(mod_trezorcrypto_monero_ge25519_check_obj, mod_trezorcrypto_monero_ge25519_check);
+
+/// def ge25519_eq(a: Ge25519, b: Ge25519) -> bool:
+/// '''
+/// Compares EC points
+/// '''
+STATIC mp_obj_t mod_trezorcrypto_monero_ge25519_eq(const mp_obj_t a, const mp_obj_t b){
+ assert_ge25519(a);
+ assert_ge25519(b);
+ int r = ge25519_eq(&MP_OBJ_C_GE25519(a), &MP_OBJ_C_GE25519(b));
+ return MP_OBJ_NEW_SMALL_INT(r);
+}
+STATIC MP_DEFINE_CONST_FUN_OBJ_2(mod_trezorcrypto_monero_ge25519_eq_obj, mod_trezorcrypto_monero_ge25519_eq);
+
+/// def ge25519_add(r: Optional[Ge25519], a: Ge25519, b: Ge25519) -> Ge25519:
+/// '''
+/// Adds EC points
+/// '''
+STATIC mp_obj_t mod_trezorcrypto_monero_ge25519_add(size_t n_args, const mp_obj_t *args){
+ const bool res_arg = n_args == 3;
+ const int off = res_arg ? 0 : -1;
+ mp_obj_t res = mp_obj_new_ge25519_r(res_arg ? args[0] : mp_const_none);
+
+ assert_ge25519(args[1+off]);
+ assert_ge25519(args[2+off]);
+ ge25519_add(&MP_OBJ_GE25519(res), &MP_OBJ_C_GE25519(args[1+off]), &MP_OBJ_C_GE25519(args[2+off]), 0);
+ return res;
+}
+STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_trezorcrypto_monero_ge25519_add_obj, 2, 3, mod_trezorcrypto_monero_ge25519_add);
+
+/// def ge25519_sub(r: Optional[Ge25519], a: Ge25519, b: Ge25519) -> Ge25519:
+/// '''
+/// Subtracts EC points
+/// '''
+STATIC mp_obj_t mod_trezorcrypto_monero_ge25519_sub(size_t n_args, const mp_obj_t *args){
+ const bool res_arg = n_args == 3;
+ const int off = res_arg ? 0 : -1;
+ mp_obj_t res = mp_obj_new_ge25519_r(res_arg ? args[0] : mp_const_none);
+
+ assert_ge25519(args[1+off]);
+ assert_ge25519(args[2+off]);
+ ge25519_add(&MP_OBJ_GE25519(res), &MP_OBJ_C_GE25519(args[1+off]), &MP_OBJ_C_GE25519(args[2+off]), 1);
+ return res;
+}
+STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_trezorcrypto_monero_ge25519_sub_obj, 2, 3, mod_trezorcrypto_monero_ge25519_sub);
+
+/// def ge25519_double(r: Optional[Ge25519], p: Ge25519) -> Ge25519:
+/// '''
+/// EC point doubling
+/// '''
+STATIC mp_obj_t mod_trezorcrypto_monero_ge25519_double(size_t n_args, const mp_obj_t *args){
+ const bool res_arg = n_args == 2;
+ mp_obj_t res = mp_obj_new_ge25519_r(res_arg ? args[0] : mp_const_none);
+ mp_obj_t src = res_arg ? args[1] : args[0];
+ assert_ge25519(src);
+ ge25519_double(&MP_OBJ_GE25519(res), &MP_OBJ_C_GE25519(src));
+ return res;
+}
+STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_trezorcrypto_monero_ge25519_double_obj, 1, 2, mod_trezorcrypto_monero_ge25519_double);
+
+/// def ge25519_mul8(r: Optional[Ge25519], p: Ge25519) -> Ge25519:
+/// '''
+/// EC point * 8
+/// '''
+STATIC mp_obj_t mod_trezorcrypto_monero_ge25519_mul8(size_t n_args, const mp_obj_t *args){
+ const bool res_arg = n_args == 2;
+ mp_obj_t res = mp_obj_new_ge25519_r(res_arg ? args[0] : mp_const_none);
+ mp_obj_t src = res_arg ? args[1] : args[0];
+ assert_ge25519(src);
+ ge25519_mul8(&MP_OBJ_GE25519(res), &MP_OBJ_C_GE25519(src));
+ return res;
+}
+STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_trezorcrypto_monero_ge25519_mul8_obj, 1, 2, mod_trezorcrypto_monero_ge25519_mul8);
+
+/// def ge25519_double_scalarmult_vartime(r: Optional[Ge25519], p1: Ge25519, s1: Sc25519, s2: Sc25519) -> Ge25519:
+/// '''
+/// s1 * G + s2 * p1
+/// '''
+STATIC mp_obj_t mod_trezorcrypto_monero_ge25519_double_scalarmult_vartime(size_t n_args, const mp_obj_t *args){
+ const bool res_arg = n_args == 4;
+ const int off = res_arg ? 0 : -1;
+ mp_obj_t res = mp_obj_new_ge25519_r(res_arg ? args[0] : mp_const_none);
+ assert_ge25519(args[1+off]);
+ assert_scalar(args[2+off]);
+ assert_scalar(args[3+off]);
+
+ ge25519_double_scalarmult_vartime(&MP_OBJ_GE25519(res), &MP_OBJ_C_GE25519(args[1+off]),
+ MP_OBJ_C_SCALAR(args[2+off]), MP_OBJ_C_SCALAR(args[3+off]));
+ return res;
+}
+STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_trezorcrypto_monero_ge25519_double_scalarmult_vartime_obj, 3, 4, mod_trezorcrypto_monero_ge25519_double_scalarmult_vartime);
+
+/// def ge25519_double_scalarmult_vartime2(r: Optional[Ge25519], p1: Ge25519, s1: Sc25519, p2: Ge25519, s2: Sc25519) -> Ge25519:
+/// '''
+/// s1 * p1 + s2 * p2
+/// '''
+STATIC mp_obj_t mod_trezorcrypto_monero_ge25519_double_scalarmult_vartime2(size_t n_args, const mp_obj_t *args){
+ const bool res_arg = n_args == 5;
+ const int off = res_arg ? 0 : -1;
+ mp_obj_t res = mp_obj_new_ge25519_r(res_arg ? args[0] : mp_const_none);
+
+ assert_ge25519(args[1+off]);
+ assert_scalar(args[2+off]);
+ assert_ge25519(args[3+off]);
+ assert_scalar(args[4+off]);
+
+ ge25519_double_scalarmult_vartime2(&MP_OBJ_GE25519(res), &MP_OBJ_C_GE25519(args[1+off]), MP_OBJ_C_SCALAR(args[2+off]),
+ &MP_OBJ_C_GE25519(args[3+off]), MP_OBJ_C_SCALAR(args[4+off]));
+ return res;
+}
+STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_trezorcrypto_monero_ge25519_double_scalarmult_vartime2_obj, 4, 5, mod_trezorcrypto_monero_ge25519_double_scalarmult_vartime2);
+
+/// def ge25519_scalarmult_base(r: Optional[Ge25519], s: Union[Sc25519, int]) -> Ge25519:
+/// '''
+/// s * G
+/// '''
+STATIC mp_obj_t mod_trezorcrypto_monero_ge25519_scalarmult_base(size_t n_args, const mp_obj_t *args){
+ const bool res_arg = n_args == 2;
+ const int off = res_arg ? 0 : -1;
+ mp_obj_t res = mp_obj_new_ge25519_r(res_arg ? args[0] : mp_const_none);
+
+ if (MP_OBJ_IS_SCALAR(args[1+off])){
+ ge25519_scalarmult_base_wrapper(&MP_OBJ_GE25519(res), MP_OBJ_C_SCALAR(args[1+off]));
+ } else if (mp_obj_is_integer(args[1+off])){
+ bignum256modm mlt;
+ set256_modm(mlt, mp_obj_get_int(args[1+off]));
+ ge25519_scalarmult_base_wrapper(&MP_OBJ_GE25519(res), mlt);
+ } else {
+ mp_raise_ValueError("unknown base mult type");
+ }
+
+ return res;
+}
+STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_trezorcrypto_monero_ge25519_scalarmult_base_obj, 1, 2, mod_trezorcrypto_monero_ge25519_scalarmult_base);
+
+/// def ge25519_scalarmult(r: Optional[Ge25519], p: Ge25519, s: Union[Sc25519, int]) -> Ge25519:
+/// '''
+/// s * p
+/// '''
+STATIC mp_obj_t mod_trezorcrypto_monero_ge25519_scalarmult(size_t n_args, const mp_obj_t *args){
+ const bool res_arg = n_args == 3;
+ const int off = res_arg ? 0 : -1;
+ mp_obj_t res = mp_obj_new_ge25519_r(res_arg ? args[0] : mp_const_none);
+ assert_ge25519(args[1+off]);
+
+ if (MP_OBJ_IS_SCALAR(args[2+off])){
+ ge25519_scalarmult(&MP_OBJ_GE25519(res), &MP_OBJ_C_GE25519(args[1+off]), MP_OBJ_C_SCALAR(args[2+off]));
+ } else if (mp_obj_is_integer(args[2+off])){
+ bignum256modm mlt;
+ set256_modm(mlt, mp_obj_get_int(args[2+off]));
+ ge25519_scalarmult(&MP_OBJ_GE25519(res), &MP_OBJ_C_GE25519(args[1+off]), mlt);
+ } else {
+ mp_raise_ValueError("unknown mult type");
+ }
+
+ return res;
+}
+STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_trezorcrypto_monero_ge25519_scalarmult_obj, 2, 3, mod_trezorcrypto_monero_ge25519_scalarmult);
+
+/// def ge25519_pack(r: bytes, p: Ge25519, offset: int = 0) -> bytes:
+/// '''
+/// Point compression
+/// '''
+STATIC mp_obj_t mod_trezorcrypto_monero_ge25519_pack(size_t n_args, const mp_obj_t *args){
+ if (n_args == 1 || args[0] == mp_const_none){
+ assert_ge25519(args[0]);
+ uint8_t buff[32];
+ ge25519_pack(buff, &MP_OBJ_C_GE25519(args[0]));
+ return mp_obj_new_bytes(buff, 32);
+
+ } else {
+ mp_buffer_info_t bufm;
+ mp_get_buffer_raise(args[0], &bufm, MP_BUFFER_WRITE);
+ const mp_int_t offset = n_args >= 3 ? mp_obj_get_int(args[2]) : 0;
+ if (bufm.len < 32 + offset) {
+ mp_raise_ValueError("Buffer too small");
+ }
+
+ ge25519_pack(((uint8_t*)bufm.buf) + offset, &MP_OBJ_C_GE25519(args[1]));
+ return args[0];
+ }
+
+}
+STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_trezorcrypto_monero_ge25519_pack_obj, 1, 3, mod_trezorcrypto_monero_ge25519_pack);
+
+/// def ge25519_unpack_vartime(r: Optional[Ge25519], buff: bytes, offset: int = 0) -> Ge25519:
+/// '''
+/// Point decompression
+/// '''
+STATIC mp_obj_t mod_trezorcrypto_monero_ge25519_unpack_vartime(size_t n_args, const mp_obj_t *args){
+ const bool res_arg = n_args >= 2;
+ const int off = res_arg ? 0 : -1;
+ mp_obj_t res = mp_obj_new_ge25519_r(res_arg ? args[0] : mp_const_none);
+ const mp_int_t offset = n_args >= 3 ? mp_obj_get_int(args[2]) : 0;
+ mp_unpack_ge25519(&MP_OBJ_GE25519(res), args[1+off], offset);
+ return res;
+}
+STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_trezorcrypto_monero_ge25519_unpack_vartime_obj, 1, 3, mod_trezorcrypto_monero_ge25519_unpack_vartime);
+
+//
+// XMR defs
+//
+
+/// def base58_addr_encode_check(tag: int, buff: bytes) -> bytes:
+/// '''
+/// Monero block base 58 encoding
+/// '''
+STATIC mp_obj_t mod_trezorcrypto_monero_xmr_base58_addr_encode_check(size_t n_args, const mp_obj_t *args){
+ uint8_t out[128];
+ mp_buffer_info_t data;
+ mp_get_buffer_raise(args[1], &data, MP_BUFFER_READ);
+
+ int sz = xmr_base58_addr_encode_check(mp_obj_get_int(args[0]), data.buf, data.len, (char *)out, sizeof(out));
+ if (sz == 0){
+ mp_raise_ValueError("b58 encoding error");
+ }
+
+ return mp_obj_new_bytes(out, sz);
+}
+STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_trezorcrypto_monero_xmr_base58_addr_encode_check_obj, 2, 2, mod_trezorcrypto_monero_xmr_base58_addr_encode_check);
+
+/// def base58_addr_decode_check(buff: bytes) -> Tuple[bytes, int]:
+/// '''
+/// Monero block base 58 decoding, returning (decoded, tag) or raising on error.
+/// '''
+STATIC mp_obj_t mod_trezorcrypto_monero_xmr_base58_addr_decode_check(size_t n_args, const mp_obj_t *args){
+ uint8_t out[128];
+ uint64_t tag;
+
+ mp_buffer_info_t data;
+ mp_get_buffer_raise(args[0], &data, MP_BUFFER_READ);
+
+ int sz = xmr_base58_addr_decode_check(data.buf, data.len, &tag, out, sizeof(out));
+ if (sz == 0){
+ mp_raise_ValueError("b58 decoding error");
+ }
+
+ mp_obj_tuple_t *tuple = MP_OBJ_TO_PTR(mp_obj_new_tuple(2, NULL));
+ tuple->items[0] = mp_obj_new_bytes(out, sz);
+ tuple->items[1] = mp_obj_new_int_from_ull(tag);
+ return MP_OBJ_FROM_PTR(tuple);
+}
+STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_trezorcrypto_monero_xmr_base58_addr_decode_check_obj, 1, 1, mod_trezorcrypto_monero_xmr_base58_addr_decode_check);
+
+/// def xmr_random_scalar(r: Optional[Sc25519] = None) -> Sc25519:
+/// '''
+/// Generates a random scalar
+/// '''
+STATIC mp_obj_t mod_trezorcrypto_monero_xmr_random_scalar(size_t n_args, const mp_obj_t *args){
+ mp_obj_t res = mp_obj_new_scalar_r(n_args == 1 ? args[0] : mp_const_none);
+ xmr_random_scalar(MP_OBJ_SCALAR(res));
+ return res;
+}
+STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_trezorcrypto_monero_xmr_random_scalar_obj, 0, 1, mod_trezorcrypto_monero_xmr_random_scalar);
+
+/// def xmr_fast_hash(r: Optional[bytes], buff: bytes) -> bytes:
+/// '''
+/// XMR fast hash
+/// '''
+STATIC mp_obj_t mod_trezorcrypto_monero_xmr_fast_hash(size_t n_args, const mp_obj_t *args){
+ const int off = n_args == 2 ? 0 : -1;
+ uint8_t buff[32];
+ uint8_t * buff_use = buff;
+ if (n_args > 1){
+ mp_buffer_info_t odata;
+ mp_get_buffer_raise(args[0], &odata, MP_BUFFER_WRITE);
+ if (odata.len < 32){
+ mp_raise_ValueError("Output buffer too small");
+ }
+ buff_use = odata.buf;
+ }
+
+ mp_buffer_info_t data;
+ mp_get_buffer_raise(args[1+off], &data, MP_BUFFER_READ);
+ xmr_fast_hash(buff_use, data.buf, data.len);
+ return n_args == 2 ? args[0] : mp_obj_new_bytes(buff, 32);
+}
+STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_trezorcrypto_monero_xmr_fast_hash_obj, 1, 2, mod_trezorcrypto_monero_xmr_fast_hash);
+
+/// def xmr_hash_to_ec(r: Optional[Ge25519], buff: bytes) -> Ge25519:
+/// '''
+/// XMR hashing to EC point
+/// '''
+STATIC mp_obj_t mod_trezorcrypto_monero_xmr_hash_to_ec(size_t n_args, const mp_obj_t *args){
+ const bool res_arg = n_args == 2;
+ const int off = res_arg ? 0 : -1;
+ mp_obj_t res = mp_obj_new_ge25519_r(res_arg ? args[0] : mp_const_none);
+ mp_buffer_info_t data;
+ mp_get_buffer_raise(args[1+off], &data, MP_BUFFER_READ);
+ xmr_hash_to_ec(&MP_OBJ_GE25519(res), data.buf, data.len);
+ return res;
+}
+STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_trezorcrypto_monero_xmr_hash_to_ec_obj, 1, 2, mod_trezorcrypto_monero_xmr_hash_to_ec);
+
+/// def xmr_hash_to_scalar(r: Optional[Sc25519], buff: bytes) -> Sc25519:
+/// '''
+/// XMR hashing to EC scalar
+/// '''
+STATIC mp_obj_t mod_trezorcrypto_monero_xmr_hash_to_scalar(size_t n_args, const mp_obj_t *args){
+ const bool res_arg = n_args == 2;
+ const int off = res_arg ? 0 : -1;
+ mp_obj_t res = mp_obj_new_scalar_r(res_arg ? args[0] : mp_const_none);
+ mp_buffer_info_t data;
+ mp_get_buffer_raise(args[1+off], &data, MP_BUFFER_READ);
+ xmr_hash_to_scalar(MP_OBJ_SCALAR(res), data.buf, data.len);
+ return res;
+}
+STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_trezorcrypto_monero_xmr_hash_to_scalar_obj, 1, 2, mod_trezorcrypto_monero_xmr_hash_to_scalar);
+
+/// def xmr_derivation_to_scalar(r: Optional[Sc25519], p: Ge25519, output_index: int) -> Sc25519:
+/// '''
+/// H_s(derivation || varint(output_index))
+/// '''
+STATIC mp_obj_t mod_trezorcrypto_monero_xmr_derivation_to_scalar(size_t n_args, const mp_obj_t *args){
+ const bool res_arg = n_args == 3;
+ const int off = res_arg ? 0 : -1;
+ mp_obj_t res = mp_obj_new_scalar_r(res_arg ? args[0] : mp_const_none);
+ assert_ge25519(args[1+off]);
+ xmr_derivation_to_scalar(MP_OBJ_SCALAR(res), &MP_OBJ_C_GE25519(args[1+off]), mp_obj_get_int(args[2+off]));
+ return res;
+}
+STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_trezorcrypto_monero_xmr_derivation_to_scalar_obj, 2, 3, mod_trezorcrypto_monero_xmr_derivation_to_scalar);
+
+/// def xmr_generate_key_derivation(r: Optional[Ge25519], A: Ge25519, b: Sc25519) -> Ge25519:
+/// '''
+/// 8*(key2*key1)
+/// '''
+STATIC mp_obj_t mod_trezorcrypto_monero_xmr_generate_key_derivation(size_t n_args, const mp_obj_t *args){
+ const bool res_arg = n_args == 3;
+ const int off = res_arg ? 0 : -1;
+ mp_obj_t res = mp_obj_new_ge25519_r(res_arg ? args[0] : mp_const_none);
+ assert_ge25519(args[1+off]);
+ assert_scalar(args[2+off]);
+ xmr_generate_key_derivation(&MP_OBJ_GE25519(res), &MP_OBJ_C_GE25519(args[1+off]), MP_OBJ_C_SCALAR(args[2+off]));
+ return res;
+}
+STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_trezorcrypto_monero_xmr_generate_key_derivation_obj, 2, 3, mod_trezorcrypto_monero_xmr_generate_key_derivation);
+
+/// def xmr_derive_private_key(r: Optional[Sc25519], deriv: Ge25519, idx: int, base: Sc25519) -> Sc25519:
+/// '''
+/// base + H_s(derivation || varint(output_index))
+/// '''
+STATIC mp_obj_t mod_trezorcrypto_monero_xmr_derive_private_key(size_t n_args, const mp_obj_t *args){
+ const bool res_arg = n_args == 4;
+ const int off = res_arg ? 0 : -1;
+ mp_obj_t res = mp_obj_new_scalar_r(res_arg ? args[0] : mp_const_none);
+ assert_ge25519(args[1+off]);
+ assert_scalar(args[3+off]);
+ xmr_derive_private_key(MP_OBJ_SCALAR(res), &MP_OBJ_C_GE25519(args[1+off]), mp_obj_get_int(args[2+off]), MP_OBJ_C_SCALAR(args[3+off]));
+ return res;
+}
+STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_trezorcrypto_monero_xmr_derive_private_key_obj, 3, 4, mod_trezorcrypto_monero_xmr_derive_private_key);
+
+/// def xmr_derive_public_key(r: Optional[Ge25519], deriv: Ge25519, idx: int, base: Ge25519) -> Ge25519:
+/// '''
+/// H_s(derivation || varint(output_index))G + base
+/// '''
+STATIC mp_obj_t mod_trezorcrypto_monero_xmr_derive_public_key(size_t n_args, const mp_obj_t *args){
+ const bool res_arg = n_args == 4;
+ const int off = res_arg ? 0 : -1;
+ mp_obj_t res = mp_obj_new_ge25519_r(res_arg ? args[0] : mp_const_none);
+ assert_ge25519(args[1+off]);
+ assert_ge25519(args[3+off]);
+ xmr_derive_public_key(&MP_OBJ_GE25519(res), &MP_OBJ_C_GE25519(args[1+off]), mp_obj_get_int(args[2+off]), &MP_OBJ_C_GE25519(args[3+off]));
+ return res;
+}
+STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_trezorcrypto_monero_xmr_derive_public_key_obj, 3, 4, mod_trezorcrypto_monero_xmr_derive_public_key);
+
+/// def xmr_add_keys2(r: Optional[Ge25519], a: Sc25519, b: Sc25519, B: Ge25519) -> Ge25519:
+/// '''
+/// aG + bB, G is basepoint
+/// '''
+STATIC mp_obj_t mod_trezorcrypto_monero_xmr_add_keys2(size_t n_args, const mp_obj_t *args){
+ const bool res_arg = n_args == 4;
+ const int off = res_arg ? 0 : -1;
+ mp_obj_t res = mp_obj_new_ge25519_r(res_arg ? args[0] : mp_const_none);
+ assert_scalar(args[1+off]);
+ assert_scalar(args[2+off]);
+ assert_ge25519(args[3+off]);
+ xmr_add_keys2(&MP_OBJ_GE25519(res), MP_OBJ_SCALAR(args[1+off]), MP_OBJ_SCALAR(args[2+off]), &MP_OBJ_C_GE25519(args[3+off]));
+ return res;
+}
+STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_trezorcrypto_monero_xmr_add_keys2_obj, 3, 4, mod_trezorcrypto_monero_xmr_add_keys2);
+
+/// def xmr_add_keys2_vartime(r: Optional[Ge25519], a: Sc25519, b: Sc25519, B: Ge25519) -> Ge25519:
+/// '''
+/// aG + bB, G is basepoint
+/// '''
+STATIC mp_obj_t mod_trezorcrypto_monero_xmr_add_keys2_vartime(size_t n_args, const mp_obj_t *args){
+ const bool res_arg = n_args == 4;
+ const int off = res_arg ? 0 : -1;
+ mp_obj_t res = mp_obj_new_ge25519_r(res_arg ? args[0] : mp_const_none);
+ assert_scalar(args[1+off]);
+ assert_scalar(args[2+off]);
+ assert_ge25519(args[3+off]);
+ xmr_add_keys2_vartime(&MP_OBJ_GE25519(res), MP_OBJ_SCALAR(args[1+off]), MP_OBJ_SCALAR(args[2+off]), &MP_OBJ_C_GE25519(args[3+off]));
+ return res;
+}
+STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_trezorcrypto_monero_xmr_add_keys2_vartime_obj, 3, 4, mod_trezorcrypto_monero_xmr_add_keys2_vartime);
+
+/// def xmr_add_keys3(r: Optional[Ge25519], a: Sc25519, A: Ge25519, b: Sc25519, B: Ge25519) -> Ge25519:
+/// '''
+/// aA + bB
+/// '''
+STATIC mp_obj_t mod_trezorcrypto_monero_xmr_add_keys3(size_t n_args, const mp_obj_t *args){
+ const bool res_arg = n_args == 5;
+ const int off = res_arg ? 0 : -1;
+ mp_obj_t res = mp_obj_new_ge25519_r(res_arg ? args[0] : mp_const_none);
+ assert_scalar(args[1+off]);
+ assert_ge25519(args[2+off]);
+ assert_scalar(args[3+off]);
+ assert_ge25519(args[4+off]);
+ xmr_add_keys3(&MP_OBJ_GE25519(res),
+ MP_OBJ_SCALAR(args[1+off]), &MP_OBJ_C_GE25519(args[2+off]),
+ MP_OBJ_SCALAR(args[3+off]), &MP_OBJ_C_GE25519(args[4+off]));
+ return res;
+}
+STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_trezorcrypto_monero_xmr_add_keys3_obj, 4, 5, mod_trezorcrypto_monero_xmr_add_keys3);
+
+/// def xmr_add_keys3_vartime(r: Optional[Ge25519], a: Sc25519, A: Ge25519, b: Sc25519, B: Ge25519) -> Ge25519:
+/// '''
+/// aA + bB
+/// '''
+STATIC mp_obj_t mod_trezorcrypto_monero_xmr_add_keys3_vartime(size_t n_args, const mp_obj_t *args){
+ const bool res_arg = n_args == 5;
+ const int off = res_arg ? 0 : -1;
+ mp_obj_t res = mp_obj_new_ge25519_r(res_arg ? args[0] : mp_const_none);
+ assert_scalar(args[1+off]);
+ assert_ge25519(args[2+off]);
+ assert_scalar(args[3+off]);
+ assert_ge25519(args[4+off]);
+ xmr_add_keys3_vartime(&MP_OBJ_GE25519(res),
+ MP_OBJ_SCALAR(args[1+off]), &MP_OBJ_C_GE25519(args[2+off]),
+ MP_OBJ_SCALAR(args[3+off]), &MP_OBJ_C_GE25519(args[4+off]));
+ return res;
+}
+STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_trezorcrypto_monero_xmr_add_keys3_vartime_obj, 4, 5, mod_trezorcrypto_monero_xmr_add_keys3_vartime);
+
+/// def xmr_get_subaddress_secret_key(r: Optional[Sc25519], major: int, minor: int, m: Sc25519) -> Sc25519:
+/// '''
+/// Hs(SubAddr || a || index_major || index_minor)
+/// '''
+STATIC mp_obj_t mod_trezorcrypto_monero_xmr_get_subaddress_secret_key(size_t n_args, const mp_obj_t *args){
+ const bool res_arg = n_args == 4;
+ const int off = res_arg ? 0 : -1;
+ mp_obj_t res = mp_obj_new_scalar_r(res_arg ? args[0] : mp_const_none);
+ assert_scalar(args[3+off]);
+ xmr_get_subaddress_secret_key(MP_OBJ_SCALAR(res), mp_obj_get_int(args[1+off]), mp_obj_get_int(args[2+off]), MP_OBJ_C_SCALAR(args[3+off]));
+ return res;
+}
+STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_trezorcrypto_monero_xmr_get_subaddress_secret_key_obj, 3, 4, mod_trezorcrypto_monero_xmr_get_subaddress_secret_key);
+
+/// def xmr_gen_c(r: Optional[Ge25519], a: Sc25519, amount: int) -> Ge25519:
+/// '''
+/// aG + amount * H
+/// '''
+STATIC mp_obj_t mod_trezorcrypto_monero_xmr_gen_c(size_t n_args, const mp_obj_t *args){
+ const bool res_arg = n_args == 3;
+ const int off = res_arg ? 0 : -1;
+ mp_obj_t res = mp_obj_new_ge25519_r(res_arg ? args[0] : mp_const_none);
+ assert_scalar(args[1+off]);
+ xmr_gen_c(&MP_OBJ_GE25519(res), MP_OBJ_C_SCALAR(args[1+off]), mp_obj_get_uint64(args[2+off]));
+ return res;
+}
+STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_trezorcrypto_monero_xmr_gen_c_obj, 2, 3, mod_trezorcrypto_monero_xmr_gen_c);
+
+/// def ct_equals(a: bytes, b: bytes) -> bool:
+/// '''
+/// Constant time buffer comparison
+/// '''
+STATIC mp_obj_t mod_trezorcrypto_ct_equals(const mp_obj_t a, const mp_obj_t b){
+ mp_buffer_info_t buff_a, buff_b;
+ mp_get_buffer_raise(a, &buff_a, MP_BUFFER_READ);
+ mp_get_buffer_raise(b, &buff_b, MP_BUFFER_READ);
+
+ if (buff_a.len != buff_b.len) {
+ return MP_OBJ_NEW_SMALL_INT(0);
+ }
+
+ int r = ed25519_verify(buff_a.buf, buff_b.buf, buff_a.len);
+ return MP_OBJ_NEW_SMALL_INT(r);
+}
+STATIC MP_DEFINE_CONST_FUN_OBJ_2(mod_trezorcrypto_ct_equals_obj, mod_trezorcrypto_ct_equals);
+
+// Hasher
+STATIC mp_obj_t mod_trezorcrypto_monero_hasher_update(mp_obj_t self, const mp_obj_t arg){
+ mp_obj_hasher_t *o = MP_OBJ_TO_PTR(self);
+ mp_buffer_info_t buff;
+ mp_get_buffer_raise(arg, &buff, MP_BUFFER_READ);
+ if (buff.len > 0) {
+ xmr_hasher_update(&o->h, buff.buf, buff.len);
+ }
+ return mp_const_none;
+}
+STATIC MP_DEFINE_CONST_FUN_OBJ_2(mod_trezorcrypto_monero_hasher_update_obj, mod_trezorcrypto_monero_hasher_update);
+
+STATIC mp_obj_t mod_trezorcrypto_monero_hasher_digest(size_t n_args, const mp_obj_t *args){
+ mp_obj_hasher_t *o = MP_OBJ_TO_PTR(args[0]);
+
+ Hasher ctx;
+ memcpy(&ctx, &(o->h), sizeof(Hasher));
+
+ uint8_t out[SHA3_256_DIGEST_LENGTH];
+ xmr_hasher_final(&ctx, out);
+ memset(&ctx, 0, sizeof(SHA3_CTX));
+
+ if (n_args == 1 || args[1] == mp_const_none){
+ return mp_obj_new_bytes(out, sizeof(out));
+
+ } else {
+ mp_buffer_info_t bufm;
+ mp_get_buffer_raise(args[1], &bufm, MP_BUFFER_WRITE);
+ const mp_int_t offset = n_args >= 3 ? mp_obj_get_int(args[2]) : 0;
+ if (bufm.len < 32 + offset) {
+ mp_raise_ValueError("Buffer too small");
+ }
+
+ memcpy((uint8_t*)bufm.buf + offset, out, SHA3_256_DIGEST_LENGTH);
+ return args[1];
+ }
+}
+STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_trezorcrypto_monero_hasher_digest_obj, 1, 3, mod_trezorcrypto_monero_hasher_digest);
+
+STATIC mp_obj_t mod_trezorcrypto_monero_hasher_copy(mp_obj_t self){
+ mp_obj_hasher_t *o = MP_OBJ_TO_PTR(self);
+ mp_obj_hasher_t *cp = m_new_obj(mp_obj_hasher_t);
+ cp->base.type = o->base.type;
+ memcpy(&(cp->h), &(o->h), sizeof(Hasher));
+ return MP_OBJ_FROM_PTR(o);
+}
+STATIC MP_DEFINE_CONST_FUN_OBJ_1(mod_trezorcrypto_monero_hasher_copy_obj, mod_trezorcrypto_monero_hasher_copy);
+
+
+//
+// Type defs
+//
+
+STATIC const mp_rom_map_elem_t mod_trezorcrypto_monero_ge25519_locals_dict_table[] = {
+ { MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&mod_trezorcrypto_monero_ge25519___del___obj) },
+};
+STATIC MP_DEFINE_CONST_DICT(mod_trezorcrypto_monero_ge25519_locals_dict, mod_trezorcrypto_monero_ge25519_locals_dict_table);
+
+STATIC const mp_obj_type_t mod_trezorcrypto_monero_ge25519_type = {
+ { &mp_type_type },
+ .name = MP_QSTR_Ge25519,
+ .make_new = mod_trezorcrypto_monero_ge25519_make_new,
+ .locals_dict = (void*)&mod_trezorcrypto_monero_ge25519_locals_dict,
+};
+
+STATIC const mp_rom_map_elem_t mod_trezorcrypto_monero_bignum256modm_locals_dict_table[] = {
+ { MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&mod_trezorcrypto_monero_bignum256modm___del___obj) },
+};
+STATIC MP_DEFINE_CONST_DICT(mod_trezorcrypto_monero_bignum256modm_locals_dict, mod_trezorcrypto_monero_bignum256modm_locals_dict_table);
+
+
+STATIC const mp_obj_type_t mod_trezorcrypto_monero_bignum256modm_type = {
+ { &mp_type_type },
+ .name = MP_QSTR_Sc25519,
+ .make_new = mod_trezorcrypto_monero_bignum256modm_make_new,
+ .locals_dict = (void*)&mod_trezorcrypto_monero_bignum256modm_locals_dict,
+};
+
+STATIC const mp_rom_map_elem_t mod_trezorcrypto_monero_hasher_locals_dict_table[] = {
+ { MP_ROM_QSTR(MP_QSTR_update), MP_ROM_PTR(&mod_trezorcrypto_monero_hasher_update_obj) },
+ { MP_ROM_QSTR(MP_QSTR_digest), MP_ROM_PTR(&mod_trezorcrypto_monero_hasher_digest_obj) },
+ { MP_ROM_QSTR(MP_QSTR_copy), MP_ROM_PTR(&mod_trezorcrypto_monero_hasher_copy_obj) },
+ { MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&mod_trezorcrypto_monero_hasher___del___obj) },
+ { MP_ROM_QSTR(MP_QSTR_block_size), MP_OBJ_NEW_SMALL_INT(SHA3_256_BLOCK_LENGTH) },
+ { MP_ROM_QSTR(MP_QSTR_digest_size), MP_OBJ_NEW_SMALL_INT(SHA3_256_DIGEST_LENGTH) },
+};
+STATIC MP_DEFINE_CONST_DICT(mod_trezorcrypto_monero_hasher_locals_dict, mod_trezorcrypto_monero_hasher_locals_dict_table);
+
+
+STATIC const mp_obj_type_t mod_trezorcrypto_monero_hasher_type = {
+ { &mp_type_type },
+ .name = MP_QSTR_hasher,
+ .make_new = mod_trezorcrypto_monero_hasher_make_new,
+ .locals_dict = (void*)&mod_trezorcrypto_monero_hasher_locals_dict,
+};
+
+STATIC const mp_rom_map_elem_t mod_trezorcrypto_monero_globals_table[] = {
+ { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_monero) },
+ { MP_ROM_QSTR(MP_QSTR_init256_modm), MP_ROM_PTR(&mod_trezorcrypto_monero_init256_modm_obj) },
+ { MP_ROM_QSTR(MP_QSTR_check256_modm), MP_ROM_PTR(&mod_trezorcrypto_monero_check256_modm_obj) },
+ { MP_ROM_QSTR(MP_QSTR_iszero256_modm), MP_ROM_PTR(&mod_trezorcrypto_monero_iszero256_modm_obj) },
+ { MP_ROM_QSTR(MP_QSTR_eq256_modm), MP_ROM_PTR(&mod_trezorcrypto_monero_eq256_modm_obj) },
+ { MP_ROM_QSTR(MP_QSTR_get256_modm), MP_ROM_PTR(&mod_trezorcrypto_monero_get256_modm_obj) },
+ { MP_ROM_QSTR(MP_QSTR_add256_modm), MP_ROM_PTR(&mod_trezorcrypto_monero_add256_modm_obj) },
+ { MP_ROM_QSTR(MP_QSTR_sub256_modm), MP_ROM_PTR(&mod_trezorcrypto_monero_sub256_modm_obj) },
+ { MP_ROM_QSTR(MP_QSTR_mul256_modm), MP_ROM_PTR(&mod_trezorcrypto_monero_mul256_modm_obj) },
+ { MP_ROM_QSTR(MP_QSTR_mulsub256_modm), MP_ROM_PTR(&mod_trezorcrypto_monero_mulsub256_modm_obj) },
+ { MP_ROM_QSTR(MP_QSTR_muladd256_modm), MP_ROM_PTR(&mod_trezorcrypto_monero_muladd256_modm_obj) },
+ { MP_ROM_QSTR(MP_QSTR_inv256_modm), MP_ROM_PTR(&mod_trezorcrypto_monero_inv256_modm_obj) },
+ { MP_ROM_QSTR(MP_QSTR_pack256_modm), MP_ROM_PTR(&mod_trezorcrypto_monero_pack256_modm_obj) },
+ { MP_ROM_QSTR(MP_QSTR_unpack256_modm), MP_ROM_PTR(&mod_trezorcrypto_monero_unpack256_modm_obj) },
+ { MP_ROM_QSTR(MP_QSTR_unpack256_modm_noreduce), MP_ROM_PTR(&mod_trezorcrypto_monero_unpack256_modm_noreduce_obj) },
+ { MP_ROM_QSTR(MP_QSTR_ge25519_set_neutral), MP_ROM_PTR(&mod_trezorcrypto_monero_ge25519_set_neutral_obj) },
+ { MP_ROM_QSTR(MP_QSTR_ge25519_set_h), MP_ROM_PTR(&mod_trezorcrypto_monero_ge25519_set_xmr_h_obj) },
+ { MP_ROM_QSTR(MP_QSTR_ge25519_pack), MP_ROM_PTR(&mod_trezorcrypto_monero_ge25519_pack_obj) },
+ { MP_ROM_QSTR(MP_QSTR_ge25519_unpack_vartime), MP_ROM_PTR(&mod_trezorcrypto_monero_ge25519_unpack_vartime_obj) },
+ { MP_ROM_QSTR(MP_QSTR_ge25519_check), MP_ROM_PTR(&mod_trezorcrypto_monero_ge25519_check_obj) },
+ { MP_ROM_QSTR(MP_QSTR_ge25519_eq), MP_ROM_PTR(&mod_trezorcrypto_monero_ge25519_eq_obj) },
+ { MP_ROM_QSTR(MP_QSTR_ge25519_add), MP_ROM_PTR(&mod_trezorcrypto_monero_ge25519_add_obj) },
+ { MP_ROM_QSTR(MP_QSTR_ge25519_sub), MP_ROM_PTR(&mod_trezorcrypto_monero_ge25519_sub_obj) },
+ { MP_ROM_QSTR(MP_QSTR_ge25519_double), MP_ROM_PTR(&mod_trezorcrypto_monero_ge25519_double_obj) },
+ { MP_ROM_QSTR(MP_QSTR_ge25519_mul8), MP_ROM_PTR(&mod_trezorcrypto_monero_ge25519_mul8_obj) },
+ { MP_ROM_QSTR(MP_QSTR_ge25519_double_scalarmult_vartime), MP_ROM_PTR(&mod_trezorcrypto_monero_ge25519_double_scalarmult_vartime_obj) },
+ { MP_ROM_QSTR(MP_QSTR_ge25519_double_scalarmult_vartime2), MP_ROM_PTR(&mod_trezorcrypto_monero_ge25519_double_scalarmult_vartime2_obj) },
+ { MP_ROM_QSTR(MP_QSTR_ge25519_scalarmult_base), MP_ROM_PTR(&mod_trezorcrypto_monero_ge25519_scalarmult_base_obj) },
+ { MP_ROM_QSTR(MP_QSTR_ge25519_scalarmult), MP_ROM_PTR(&mod_trezorcrypto_monero_ge25519_scalarmult_obj) },
+ { MP_ROM_QSTR(MP_QSTR_xmr_base58_addr_encode_check), MP_ROM_PTR(&mod_trezorcrypto_monero_xmr_base58_addr_encode_check_obj) },
+ { MP_ROM_QSTR(MP_QSTR_xmr_base58_addr_decode_check), MP_ROM_PTR(&mod_trezorcrypto_monero_xmr_base58_addr_decode_check_obj) },
+ { MP_ROM_QSTR(MP_QSTR_xmr_random_scalar), MP_ROM_PTR(&mod_trezorcrypto_monero_xmr_random_scalar_obj) },
+ { MP_ROM_QSTR(MP_QSTR_xmr_fast_hash), MP_ROM_PTR(&mod_trezorcrypto_monero_xmr_fast_hash_obj) },
+ { MP_ROM_QSTR(MP_QSTR_xmr_hash_to_ec), MP_ROM_PTR(&mod_trezorcrypto_monero_xmr_hash_to_ec_obj) },
+ { MP_ROM_QSTR(MP_QSTR_xmr_hash_to_scalar), MP_ROM_PTR(&mod_trezorcrypto_monero_xmr_hash_to_scalar_obj) },
+ { MP_ROM_QSTR(MP_QSTR_xmr_derivation_to_scalar), MP_ROM_PTR(&mod_trezorcrypto_monero_xmr_derivation_to_scalar_obj) },
+ { MP_ROM_QSTR(MP_QSTR_xmr_generate_key_derivation), MP_ROM_PTR(&mod_trezorcrypto_monero_xmr_generate_key_derivation_obj) },
+ { MP_ROM_QSTR(MP_QSTR_xmr_derive_private_key), MP_ROM_PTR(&mod_trezorcrypto_monero_xmr_derive_private_key_obj) },
+ { MP_ROM_QSTR(MP_QSTR_xmr_derive_public_key), MP_ROM_PTR(&mod_trezorcrypto_monero_xmr_derive_public_key_obj) },
+ { MP_ROM_QSTR(MP_QSTR_xmr_add_keys2), MP_ROM_PTR(&mod_trezorcrypto_monero_xmr_add_keys2_obj) },
+ { MP_ROM_QSTR(MP_QSTR_xmr_add_keys2_vartime), MP_ROM_PTR(&mod_trezorcrypto_monero_xmr_add_keys2_vartime_obj) },
+ { MP_ROM_QSTR(MP_QSTR_xmr_add_keys3), MP_ROM_PTR(&mod_trezorcrypto_monero_xmr_add_keys3_obj) },
+ { MP_ROM_QSTR(MP_QSTR_xmr_add_keys3_vartime), MP_ROM_PTR(&mod_trezorcrypto_monero_xmr_add_keys3_vartime_obj) },
+ { MP_ROM_QSTR(MP_QSTR_xmr_get_subaddress_secret_key), MP_ROM_PTR(&mod_trezorcrypto_monero_xmr_get_subaddress_secret_key_obj) },
+ { MP_ROM_QSTR(MP_QSTR_xmr_gen_c), MP_ROM_PTR(&mod_trezorcrypto_monero_xmr_gen_c_obj) },
+ { MP_ROM_QSTR(MP_QSTR_ct_equals), MP_ROM_PTR(&mod_trezorcrypto_ct_equals_obj) },
+};
+STATIC MP_DEFINE_CONST_DICT(mod_trezorcrypto_monero_globals, mod_trezorcrypto_monero_globals_table);
+
+STATIC const mp_obj_module_t mod_trezorcrypto_monero_module = {
+ .base = { &mp_type_module },
+ .globals = (mp_obj_dict_t*)&mod_trezorcrypto_monero_globals,
+};
diff --git a/embed/extmod/modtrezorcrypto/modtrezorcrypto-nem.h b/embed/extmod/modtrezorcrypto/modtrezorcrypto-nem.h
index 57d199029..11fa0b01d 100644
--- a/embed/extmod/modtrezorcrypto/modtrezorcrypto-nem.h
+++ b/embed/extmod/modtrezorcrypto/modtrezorcrypto-nem.h
@@ -23,6 +23,8 @@
#include "nem.h"
+/// package: trezorcrypto.nem
+
/// def validate_address(address: str, network: int) -> bool:
/// '''
/// Validate a NEM address
diff --git a/embed/extmod/modtrezorcrypto/modtrezorcrypto-nist256p1.h b/embed/extmod/modtrezorcrypto/modtrezorcrypto-nist256p1.h
index ee1cb1c74..8c6b5e5c1 100644
--- a/embed/extmod/modtrezorcrypto/modtrezorcrypto-nist256p1.h
+++ b/embed/extmod/modtrezorcrypto/modtrezorcrypto-nist256p1.h
@@ -22,6 +22,8 @@
#include "ecdsa.h"
#include "nist256p1.h"
+/// package: trezorcrypto.nist256p1
+
/// def generate_secret() -> bytes:
/// '''
/// Generate secret key.
diff --git a/embed/extmod/modtrezorcrypto/modtrezorcrypto-pbkdf2.h b/embed/extmod/modtrezorcrypto/modtrezorcrypto-pbkdf2.h
index ca48e400f..f941f9ed9 100644
--- a/embed/extmod/modtrezorcrypto/modtrezorcrypto-pbkdf2.h
+++ b/embed/extmod/modtrezorcrypto/modtrezorcrypto-pbkdf2.h
@@ -25,6 +25,8 @@
#define PRF_HMAC_SHA256 256
#define PRF_HMAC_SHA512 512
+/// package: trezorcrypto.__init__
+
/// class Pbkdf2:
/// '''
/// PBKDF2 context.
diff --git a/embed/extmod/modtrezorcrypto/modtrezorcrypto-random.h b/embed/extmod/modtrezorcrypto/modtrezorcrypto-random.h
index 288d8cbac..57e00bd83 100644
--- a/embed/extmod/modtrezorcrypto/modtrezorcrypto-random.h
+++ b/embed/extmod/modtrezorcrypto/modtrezorcrypto-random.h
@@ -23,6 +23,8 @@
#include "rand.h"
+/// package: trezorcrypto.random
+
/// def uniform(n: int) -> int:
/// '''
/// Compute uniform random number from interval 0 ... n - 1.
diff --git a/embed/extmod/modtrezorcrypto/modtrezorcrypto-rfc6979.h b/embed/extmod/modtrezorcrypto/modtrezorcrypto-rfc6979.h
index 1662993f1..f89294de0 100644
--- a/embed/extmod/modtrezorcrypto/modtrezorcrypto-rfc6979.h
+++ b/embed/extmod/modtrezorcrypto/modtrezorcrypto-rfc6979.h
@@ -21,6 +21,8 @@
#include "rfc6979.h"
+/// package: trezorcrypto.__init__
+
/// class Rfc6979:
/// '''
/// RFC6979 context.
diff --git a/embed/extmod/modtrezorcrypto/modtrezorcrypto-ripemd160.h b/embed/extmod/modtrezorcrypto/modtrezorcrypto-ripemd160.h
index 76797d94d..9aba1238f 100644
--- a/embed/extmod/modtrezorcrypto/modtrezorcrypto-ripemd160.h
+++ b/embed/extmod/modtrezorcrypto/modtrezorcrypto-ripemd160.h
@@ -22,6 +22,8 @@
#include "ripemd160.h"
#include "memzero.h"
+/// package: trezorcrypto.__init__
+
/// class Ripemd160:
/// '''
/// RIPEMD160 context.
diff --git a/embed/extmod/modtrezorcrypto/modtrezorcrypto-secp256k1.h b/embed/extmod/modtrezorcrypto/modtrezorcrypto-secp256k1.h
index f9eda2795..e994aa0ee 100644
--- a/embed/extmod/modtrezorcrypto/modtrezorcrypto-secp256k1.h
+++ b/embed/extmod/modtrezorcrypto/modtrezorcrypto-secp256k1.h
@@ -22,6 +22,8 @@
#include "ecdsa.h"
#include "secp256k1.h"
+/// package: trezorcrypto.secp256k1
+
/// def generate_secret() -> bytes:
/// '''
/// Generate secret key.
diff --git a/embed/extmod/modtrezorcrypto/modtrezorcrypto-sha1.h b/embed/extmod/modtrezorcrypto/modtrezorcrypto-sha1.h
index a9d8e584e..fff30dbfc 100644
--- a/embed/extmod/modtrezorcrypto/modtrezorcrypto-sha1.h
+++ b/embed/extmod/modtrezorcrypto/modtrezorcrypto-sha1.h
@@ -22,6 +22,8 @@
#include "sha2.h"
#include "memzero.h"
+/// package: trezorcrypto.__init__
+
/// class Sha1:
/// '''
/// SHA1 context.
diff --git a/embed/extmod/modtrezorcrypto/modtrezorcrypto-sha256.h b/embed/extmod/modtrezorcrypto/modtrezorcrypto-sha256.h
index 9d4acacbc..98c1d1caa 100644
--- a/embed/extmod/modtrezorcrypto/modtrezorcrypto-sha256.h
+++ b/embed/extmod/modtrezorcrypto/modtrezorcrypto-sha256.h
@@ -22,6 +22,8 @@
#include "sha2.h"
#include "memzero.h"
+/// package: trezorcrypto.__init__
+
/// class Sha256:
/// '''
/// SHA256 context.
diff --git a/embed/extmod/modtrezorcrypto/modtrezorcrypto-sha3-256.h b/embed/extmod/modtrezorcrypto/modtrezorcrypto-sha3-256.h
index c55333434..a76173953 100644
--- a/embed/extmod/modtrezorcrypto/modtrezorcrypto-sha3-256.h
+++ b/embed/extmod/modtrezorcrypto/modtrezorcrypto-sha3-256.h
@@ -22,6 +22,8 @@
#include "sha3.h"
#include "memzero.h"
+/// package: trezorcrypto.__init__
+
/// class Sha3_256:
/// '''
/// SHA3_256 context.
diff --git a/embed/extmod/modtrezorcrypto/modtrezorcrypto-sha3-512.h b/embed/extmod/modtrezorcrypto/modtrezorcrypto-sha3-512.h
index dd51e3c7c..e374ad1c7 100644
--- a/embed/extmod/modtrezorcrypto/modtrezorcrypto-sha3-512.h
+++ b/embed/extmod/modtrezorcrypto/modtrezorcrypto-sha3-512.h
@@ -22,6 +22,8 @@
#include "sha3.h"
#include "memzero.h"
+/// package: trezorcrypto.__init__
+
/// class Sha3_512:
/// '''
/// SHA3_512 context.
diff --git a/embed/extmod/modtrezorcrypto/modtrezorcrypto-sha512.h b/embed/extmod/modtrezorcrypto/modtrezorcrypto-sha512.h
index 70489ce48..09d7ac75a 100644
--- a/embed/extmod/modtrezorcrypto/modtrezorcrypto-sha512.h
+++ b/embed/extmod/modtrezorcrypto/modtrezorcrypto-sha512.h
@@ -22,6 +22,8 @@
#include "sha2.h"
#include "memzero.h"
+/// package: trezorcrypto.__init__
+
/// class Sha512:
/// '''
/// SHA512 context.
diff --git a/embed/extmod/modtrezorcrypto/modtrezorcrypto.c b/embed/extmod/modtrezorcrypto/modtrezorcrypto.c
index dd6b4a94c..9bd741123 100644
--- a/embed/extmod/modtrezorcrypto/modtrezorcrypto.c
+++ b/embed/extmod/modtrezorcrypto/modtrezorcrypto.c
@@ -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) },
diff --git a/mocks/generated/trezorcrypto.py b/mocks/generated/trezorcrypto.py
deleted file mode 100644
index 62d89b923..000000000
--- a/mocks/generated/trezorcrypto.py
+++ /dev/null
@@ -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.
- '''
diff --git a/mocks/generated/trezorcrypto/.mock-generated b/mocks/generated/trezorcrypto/.mock-generated
new file mode 100644
index 000000000..e69de29bb
diff --git a/mocks/generated/trezorcrypto/__init__.py b/mocks/generated/trezorcrypto/__init__.py
new file mode 100644
index 000000000..6db995c01
--- /dev/null
+++ b/mocks/generated/trezorcrypto/__init__.py
@@ -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.
+ '''
diff --git a/mocks/generated/trezorcrypto/bip32.py b/mocks/generated/trezorcrypto/bip32.py
new file mode 100644
index 000000000..95e4fb140
--- /dev/null
+++ b/mocks/generated/trezorcrypto/bip32.py
@@ -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
+ '''
diff --git a/mocks/generated/trezorcrypto/bip39.py b/mocks/generated/trezorcrypto/bip39.py
new file mode 100644
index 000000000..a3c2d2ece
--- /dev/null
+++ b/mocks/generated/trezorcrypto/bip39.py
@@ -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.
+ '''
diff --git a/mocks/generated/trezorcrypto/curve25519.py b/mocks/generated/trezorcrypto/curve25519.py
new file mode 100644
index 000000000..dfdef99f9
--- /dev/null
+++ b/mocks/generated/trezorcrypto/curve25519.py
@@ -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.
+ '''
diff --git a/mocks/generated/trezorcrypto/ed25519.py b/mocks/generated/trezorcrypto/ed25519.py
new file mode 100644
index 000000000..1c5fda393
--- /dev/null
+++ b/mocks/generated/trezorcrypto/ed25519.py
@@ -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.
+ '''
diff --git a/mocks/generated/trezorcrypto/monero.py b/mocks/generated/trezorcrypto/monero.py
new file mode 100644
index 000000000..d967d3b87
--- /dev/null
+++ b/mocks/generated/trezorcrypto/monero.py
@@ -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
+ '''
diff --git a/mocks/generated/trezorcrypto/nem.py b/mocks/generated/trezorcrypto/nem.py
new file mode 100644
index 000000000..27f70943c
--- /dev/null
+++ b/mocks/generated/trezorcrypto/nem.py
@@ -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
+ '''
diff --git a/mocks/generated/trezorcrypto/nist256p1.py b/mocks/generated/trezorcrypto/nist256p1.py
new file mode 100644
index 000000000..d06f446d1
--- /dev/null
+++ b/mocks/generated/trezorcrypto/nist256p1.py
@@ -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.
+ '''
diff --git a/mocks/generated/trezorcrypto/random.py b/mocks/generated/trezorcrypto/random.py
new file mode 100644
index 000000000..6e9248de9
--- /dev/null
+++ b/mocks/generated/trezorcrypto/random.py
@@ -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).
+ '''
diff --git a/mocks/generated/trezorcrypto/secp256k1.py b/mocks/generated/trezorcrypto/secp256k1.py
new file mode 100644
index 000000000..0dc82025a
--- /dev/null
+++ b/mocks/generated/trezorcrypto/secp256k1.py
@@ -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.
+ '''
diff --git a/mocks/generated/trezorio.py b/mocks/generated/trezorio.py
index e816c89c0..9c7bd4cf4 100644
--- a/mocks/generated/trezorio.py
+++ b/mocks/generated/trezorio.py
@@ -138,7 +138,8 @@ def __init__(self,
product: str='',
serial_number: str='',
interface: str='',
- usb21_enabled: bool=True) -> None:
+ usb21_enabled: bool=True,
+ usb21_landing: bool=True) -> None:
'''
'''
diff --git a/mocks/generated/trezorui.py b/mocks/generated/trezorui.py
index 16392fd67..d42a3083c 100644
--- a/mocks/generated/trezorui.py
+++ b/mocks/generated/trezorui.py
@@ -51,27 +51,41 @@ def icon(self, x: int, y: int, icon: bytes, fgcolor: int, bgcolor: int) -> None:
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 @@ def qrcode(self, x: int, y: int, data: bytes, scale: int) -> None:
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.
diff --git a/mocks/generated/trezorutils.py b/mocks/generated/trezorutils.py
index 8c7a3763c..27001b7c4 100644
--- a/mocks/generated/trezorutils.py
+++ b/mocks/generated/trezorutils.py
@@ -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.
- '''
diff --git a/src/apps/ethereum/networks.py b/src/apps/ethereum/networks.py
index 28b8d7a18..f1fd674e8 100644
--- a/src/apps/ethereum/networks.py
+++ b/src/apps/ethereum/networks.py
@@ -72,13 +72,6 @@ def __init__(
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 @@ def __init__(
name="EtherGem",
rskip60=False,
),
+ NetworkInfo(
+ chain_id=2018,
+ slip44=2018,
+ shortcut="EOSC",
+ name="EOS Classic",
+ rskip60=False,
+ ),
NetworkInfo(
chain_id=31102,
slip44=31102,
diff --git a/src/apps/monero/README.md b/src/apps/monero/README.md
new file mode 100644
index 000000000..4d0154404
--- /dev/null
+++ b/src/apps/monero/README.md
@@ -0,0 +1,321 @@
+# Monero
+
+MAINTAINER = ...
+
+AUTHOR = Dusan Klinec
+
+REVIEWER = Tomas Susanka ,
+ Jan Pochyla ,
+ Ondrej Vejpustek
+
+-----
+
+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 `. 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
diff --git a/src/apps/monero/__init__.py b/src/apps/monero/__init__.py
new file mode 100644
index 000000000..c2a8364cd
--- /dev/null
+++ b/src/apps/monero/__init__.py
@@ -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")
diff --git a/src/apps/monero/diag.py b/src/apps/monero/diag.py
new file mode 100644
index 000000000..50de2687a
--- /dev/null
+++ b/src/apps/monero/diag.py
@@ -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()
diff --git a/src/apps/monero/get_address.py b/src/apps/monero/get_address.py
new file mode 100644
index 000000000..e80f6c1d1
--- /dev/null
+++ b/src/apps/monero/get_address.py
@@ -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)
diff --git a/src/apps/monero/get_watch_only.py b/src/apps/monero/get_watch_only.py
new file mode 100644
index 000000000..8131dde5f
--- /dev/null
+++ b/src/apps/monero/get_watch_only.py
@@ -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)
diff --git a/src/apps/monero/key_image_sync.py b/src/apps/monero/key_image_sync.py
new file mode 100644
index 000000000..a72001d07
--- /dev/null
+++ b/src/apps/monero/key_image_sync.py
@@ -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)
diff --git a/src/apps/monero/layout/common.py b/src/apps/monero/layout/common.py
new file mode 100644
index 000000000..5ebadb9f4
--- /dev/null
+++ b/src/apps/monero/layout/common.py
@@ -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)
diff --git a/src/apps/monero/layout/confirms.py b/src/apps/monero/layout/confirms.py
new file mode 100644
index 000000000..1d2b3c4a2
--- /dev/null
+++ b/src/apps/monero/layout/confirms.py
@@ -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()
diff --git a/src/apps/monero/misc.py b/src/apps/monero/misc.py
new file mode 100644
index 000000000..b6d32283d
--- /dev/null
+++ b/src/apps/monero/misc.py
@@ -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
diff --git a/src/apps/monero/sign_tx.py b/src/apps/monero/sign_tx.py
new file mode 100644
index 000000000..c85118681
--- /dev/null
+++ b/src/apps/monero/sign_tx.py
@@ -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")
diff --git a/src/apps/monero/signing/__init__.py b/src/apps/monero/signing/__init__.py
new file mode 100644
index 000000000..45e1880bb
--- /dev/null
+++ b/src/apps/monero/signing/__init__.py
@@ -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
diff --git a/src/apps/monero/signing/offloading_keys.py b/src/apps/monero/signing/offloading_keys.py
new file mode 100644
index 000000000..9e99c192e
--- /dev/null
+++ b/src/apps/monero/signing/offloading_keys.py
@@ -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
diff --git a/src/apps/monero/signing/state.py b/src/apps/monero/signing/state.py
new file mode 100644
index 000000000..1586f2c3e
--- /dev/null
+++ b/src/apps/monero/signing/state.py
@@ -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
diff --git a/src/apps/monero/signing/step_01_init_transaction.py b/src/apps/monero/signing/step_01_init_transaction.py
new file mode 100644
index 000000000..5b3ea4dd5
--- /dev/null
+++ b/src/apps/monero/signing/step_01_init_transaction.py
@@ -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
diff --git a/src/apps/monero/signing/step_02_set_input.py b/src/apps/monero/signing/step_02_set_input.py
new file mode 100644
index 000000000..b5db60f8c
--- /dev/null
+++ b/src/apps/monero/signing/step_02_set_input.py
@@ -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
diff --git a/src/apps/monero/signing/step_03_inputs_permutation.py b/src/apps/monero/signing/step_03_inputs_permutation.py
new file mode 100644
index 000000000..cbd19c36b
--- /dev/null
+++ b/src/apps/monero/signing/step_03_inputs_permutation.py
@@ -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")
diff --git a/src/apps/monero/signing/step_04_input_vini.py b/src/apps/monero/signing/step_04_input_vini.py
new file mode 100644
index 000000000..ace9fa282
--- /dev/null
+++ b/src/apps/monero/signing/step_04_input_vini.py
@@ -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)
diff --git a/src/apps/monero/signing/step_05_all_inputs_set.py b/src/apps/monero/signing/step_05_all_inputs_set.py
new file mode 100644
index 000000000..00a9ede89
--- /dev/null
+++ b/src/apps/monero/signing/step_05_all_inputs_set.py
@@ -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
diff --git a/src/apps/monero/signing/step_06_set_output.py b/src/apps/monero/signing/step_06_set_output.py
new file mode 100644
index 000000000..142d1678f
--- /dev/null
+++ b/src/apps/monero/signing/step_06_set_output.py
@@ -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
diff --git a/src/apps/monero/signing/step_07_all_outputs_set.py b/src/apps/monero/signing/step_07_all_outputs_set.py
new file mode 100644
index 000000000..61213dd2e
--- /dev/null
+++ b/src/apps/monero/signing/step_07_all_outputs_set.py
@@ -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)
diff --git a/src/apps/monero/signing/step_09_sign_input.py b/src/apps/monero/signing/step_09_sign_input.py
new file mode 100644
index 000000000..c9ea302e1
--- /dev/null
+++ b/src/apps/monero/signing/step_09_sign_input.py
@@ -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
diff --git a/src/apps/monero/signing/step_10_sign_final.py b/src/apps/monero/signing/step_10_sign_final.py
new file mode 100644
index 000000000..441f9f669
--- /dev/null
+++ b/src/apps/monero/signing/step_10_sign_final.py
@@ -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
diff --git a/src/apps/monero/xmr/__init__.py b/src/apps/monero/xmr/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/src/apps/monero/xmr/addresses.py b/src/apps/monero/xmr/addresses.py
new file mode 100644
index 000000000..be4671879
--- /dev/null
+++ b/src/apps/monero/xmr/addresses.py
@@ -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
diff --git a/src/apps/monero/xmr/bulletproof.py b/src/apps/monero/xmr/bulletproof.py
new file mode 100644
index 000000000..2265e304f
--- /dev/null
+++ b/src/apps/monero/xmr/bulletproof.py
@@ -0,0 +1,1583 @@
+import gc
+
+from trezor import utils
+from trezor.utils import memcpy as _memcpy
+
+from apps.monero.xmr import crypto
+from apps.monero.xmr.serialize.int_serialize import dump_uvarint_b_into, uvarint_size
+
+# Constants
+
+BP_LOG_N = 6
+BP_N = 64 # 1 << BP_LOG_N
+BP_M = 16 # maximal number of bulletproofs
+
+ZERO = 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"
+ONE = b"\x01\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"
+TWO = b"\x02\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"
+EIGHT = b"\x08\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"
+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"
+MINUS_ONE = b"\xec\xd3\xf5\x5c\x1a\x63\x12\x58\xd6\x9c\xf7\xa2\xde\xf9\xde\x14\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x10"
+MINUS_INV_EIGHT = b"\x74\xa4\x19\x7a\xf0\x7d\x0b\xf7\x05\xc2\xda\x25\x2b\x5c\x0b\x0d\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0a"
+
+# Monero H point
+XMR_H = b"\x8b\x65\x59\x70\x15\x37\x99\xaf\x2a\xea\xdc\x9f\xf1\xad\xd0\xea\x6c\x72\x51\xd5\x41\x54\xcf\xa9\x2c\x17\x3a\x0d\xd3\x9c\x1f\x94"
+XMR_HP = crypto.xmr_H()
+
+# get_exponent(Gi[i], XMR_H, i * 2 + 1)
+BP_GI_PRE = b"\x0b\x48\xbe\x50\xe4\x9c\xad\x13\xfb\x3e\x01\x4f\x3f\xa7\xd6\x8b\xac\xa7\xc8\xa9\x10\x83\xdc\x9c\x59\xb3\x79\xaa\xab\x21\x8f\x15\xdf\x01\xa5\xd6\x3b\x3e\x3a\x38\x38\x2a\xfb\xd7\xbc\x68\x5f\x34\x3d\x61\x92\xda\x16\xed\x4b\x45\x1f\x15\xfd\xda\xb1\x70\xe2\x2d\x73\x69\xc8\xd5\xa7\x45\x42\x3d\x26\x06\x23\xa1\xf7\x5f\xae\x1f\xb1\xf8\x1b\x16\x9d\x42\x2a\xcd\x85\x58\xe9\xd5\x74\x25\x48\xbd\x81\xc0\x7d\x2b\xd8\x77\x1e\xb4\xbd\x84\x15\x5d\x38\xd7\x05\x31\xfe\x66\x2b\x78\xf0\xc4\x4a\x9a\xea\xea\x2e\xd2\xd6\xf0\xeb\xe1\x08\x96\xc5\xc2\x2f\x00\x70\xeb\xf0\x55\xdf\xe8\xdc\x1c\xb2\x05\x42\xef\x29\x15\x1a\xa0\x77\x1e\x58\x1e\x68\xfe\x78\x18\xef\x42\x35\xc8\xdf\x1a\x32\xae\xce\xed\xef\xcb\xdf\x6d\x91\xd5\x24\x92\x9b\x84\x02\xa0\x26\xcb\x85\x74\xe0\xe3\xa3\x34\x2c\xe2\x11\xbc\xd9\x67\xbc\x14\xe7\xab\xda\x6c\x17\xc2\xf2\x2a\x38\x1b\x84\xc2\x49\x75\x78\x52\xe9\x9d\x62\xc4\x5f\x16\x0e\x89\x15\xec\x21\xd4\xc8\xa3\x83\x1d\x7c\x2f\x24\x58\x1e\xc9\xd1\x50\x13\xdf\xcc\xb5\xeb\xa6\x9d\xf6\x91\xa0\x80\x02\xb3\x3d\x4f\x2f\xb0\x6c\xa9\xf2\x9c\xfb\xc7\x0d\xb0\x23\xa4\x8e\x45\x35\xf5\x83\x8f\x5e\xa2\x7f\x70\x98\x0d\x11\xec\xd9\x35\xb4\x78\x25\x8e\x2a\x4f\x10\x06\xb3\x2d\xa6\x38\x72\x92\x25\x9e\x69\xac\x0a\x82\x9e\xf3\x47\x69\x98\x96\x72\x8c\x0c\xc0\xca\xdc\x74\x6d\xae\x46\xfb\x31\x86\x4a\x59\xa5\xb9\xa1\x54\x9c\x77\xe4\xcf\x8a\xb8\xb2\x55\xa3\xa0\xae\xfa\xa4\xca\xd1\x25\xd2\x19\x94\x9c\x0a\xef\xf0\xc3\x56\x0a\xb1\x58\xed\x67\x17\x48\xa1\x75\x56\x41\x9e\xc9\x42\xe1\x6b\x90\x1d\xbb\x2f\xc6\xdf\x96\x60\x32\x4f\xcb\xcd\x6e\x40\xf2\x35\xd7\x5b\x76\x4f\xaf\xf6\x1c\x19\x05\x22\x2b\xaf\x87\xd5\x1d\x45\xf3\x55\x81\x38\xc8\x7c\xe5\x4c\x46\x4c\xc6\x40\xb9\x55\xe7\xfa\x33\x10\xf8\x3b\x13\xdd\x7b\x24\x73\x19\xe1\x3c\xe6\x19\x95\xbc\x77\x1e\xe1\xed\xe7\x36\x35\x99\xf0\x8f\xc5\xcf\xda\x89\x0e\xa8\x03\xe0\xec\xa7\x0a\x97\x70\x7e\x90\x56\x29\xa5\xe0\x6d\x18\x6a\x96\x4f\x32\x2f\xff\xba\xa7\xed\x2e\x78\x1d\x4d\x3f\xed\xe0\x74\x61\xf4\x4b\x2d\x98\xdb\xcc\x0c\xaa\x20\x55\x14\x6e\x13\xf5\x0e\xcf\x75\x49\x1d\xad\xd3\x6a\xd2\xba\xac\x56\xbc\x08\x56\x2e\xc6\x6c\xe1\x10\xb5\x44\x83\x1d\xbd\x34\xc6\xc2\x52\x95\x81\x51\xc4\x9a\x73\x4c\x6e\x62\x5e\x42\x60\x8c\x00\x5e\x79\x7e\xdb\x6d\x0a\x89\x34\xb3\x24\xa0\xe4\xd3\x1c\xba\x01\x57\x83\x50\x1e\xcd\xfa\x7a\x8e\xba\xe3\xa6\xbf\xd3\x2e\x6d\x1a\x36\x14\xb1\x11\x83\xc8\x09\x80\xd4\x54\x6c\xc3\xee\x5d\xb4\x7b\xfe\x97\x05\xaa\x95\xe2\xda\x29\xf2\x28\x23\x03\x53\x91\x7e\x5d\x2b\x19\x32\xfe\x48\x2f\xbc\xfe\xd7\x13\x4d\x55\x6d\x0c\x27\xf6\xcc\x6b\xf3\x01\x5c\x06\x61\x16\x25\x73\x9d\x88\x9c\x57\x89\xfa\x75\xb3\xc8\x39\x69\xcb\x88\xb1\xdf\x01\xc0\xac\xa4\x70\xf6\x65\xeb\x71\x82\xe0\x72\xbc\xa8\x9b\xc6\x69\xff\xe5\xb0\x29\x6f\xe2\x13\x43\xa8\xc3\x27\xc8\xa8\x41\x75\x02\x85\x5a\x25\xcc\xb7\x5b\x2f\x8e\xea\xc5\xd1\xdb\x25\x04\x4b\x0a\xea\xd2\xcf\x77\x02\x1e\xd9\x4f\x79\xf3\x00\x1e\x7b\x8e\x9d\xb7\x31\x1d\xb2\x8c\x45\xc9\x0d\x80\xa1\xe3\xd5\xb2\x7b\x43\xf8\xe3\x80\x21\x4d\x6a\x2c\x40\x46\xc8\xd4\x0f\x52\x4d\x47\x83\x53\x20\x4d\x01\xa1\x7c\x4f\xb7\xb1\x8c\x2f\x48\x27\x01\x50\xdb\x67\xd4\xb0\xb9\xce\x87\x86\xe0\x3c\x95\x50\xc5\x47\xfb\x18\x02\x9e\xf1\x6e\x56\x29\xe9\xa1\xc6\x68\xe1\xaa\x79\xc7\x88\x73\x55\xf5\xf5\x1b\x0c\xbb\x1f\x08\x35\xe0\x4e\x7a\xcc\x53\xac\x55\xa3\x57\x41\x97\xb5\x4c\x5a\xaa\xad\x47\xbe\x24\xdb\xbc\x11\xc1\xbd\x3e\xeb\x62\x46\x54\x2d\x2f\x5a\xe5\xf4\x39\x8d\xd4\xa7\x60\x17\x03\xcb\xbf\xd5\x9b\xad\xdd\x3a\x7c\xe6\xe3\x75\xe7\xd9\x00\x50\xe2\x71\xb1\x3f\x13\x2d\xf8\x5e\x1c\x12\xbe\x54\xfe\x66\xde\x81\xf6\x8a\x1c\x8f\x69\x6f\x3e\x77\x3c\x7e\xef\x57\xac\x13\x89\xbd\x02\x80\xd5\x58\xea\x78\x62\xf0\x1b\x64\x1e\xc6\xda\x0e\xfe\xfb\xee\xd0\x50\x9c\x53\x8a\x8c\x36\x16\x68\x1d\x76\x1a\xe5\xc6\xf9\xd2\xaa\xde\xd7\x18\x90\xda\x24\x96\x15\x60\x43\x08\x21\x82\xec\x85\x9c\x3a\xe4\x86\x93\xf9\x13\x43\xd0\xa5\xf0\xec\xbb\x7d\xec\x9b\x97\x3b\xf2\x13\x67\x8a\x65\x3b\x0d\x9d\xf5\x10\x65\x2a\x23\xc0\xb8\x06\x53\x67\x92\x4a\x4c\xfc\x78\x60\x36\xc0\x66\xca\xa7\x38\x34\x9c\xf1\xcd\xa7\x0d\xbf\xa8\x5c\xce\xb4\xa0\x9f\x85\x03\x9b\x6f\x77\x27\x4f\xa6\xe2\x79\x35\xbf\x89\xae\x37\x3a\x3b\x5a\xda\x58\x24\xbd\x4b\x2a\xec\x22\x2a\xeb\xd7\xfe\xe7\xa4\x82\xe9\xc1\x33\x58\xea\xb2\x5f\x94\x22\x36\xf3\xf4\xb6\xeb\xaf\xe1\xc3\xee\xee\xf7\x93\x83\x66\x80\x66\x7c\x66\x94\x64\xc3\xd4\xa0\x84\x7d\xf3\x02\x4b\xd5\xdf\x2a\xa4\xaa\x4d\x19\xe5\x51\xed\xe9\x3d\xd0\x75\xf7\x95\x3a\xca\xe5\x3f\x0f\x9e\x8a\x38\x4e\x49\x6c\x52\x50\xb0\x7e\x76\x17\xe8\x9e\x28\xf9\x53\xd0\x96\xec\x29\x87\xeb\xd8\xf3\xe7\x4d\x93\x39\x63\xb8\x27\x73\xd3\x7a\xb1\xb7\xa3\x60\x1d\xc8\x97\x13\x34\x82\x5d\xd1\xd6\x7e\x4c\x48\x29\x72\x92\xa0\x7a\x40\x62\x96\x75\xb3\xe8\x78\x8e\xfc\x68\x73\x85\x30\x04\x81\xae\x69\x74\x06\xd2\x4e\xf8\x8e\xbf\x9c\xa1\x97\x2c\x1d\x52\x84\x78\x85\x8e\xad\x85\x78\x2e\xd4\x10\xeb\xbc\x1f\x3d\xa4\x8b\xa8\x07\x83\x62\x36\xaa\xc0\xa8\xf0\x8a\x50\x29\x11\x5d\x57\xe7\xef\x18\xcb\x27\xcc\xe8\xd2\xc1\x57\xa9\xf4\xf5\x61\x5d\xcc\x34\x8a\xea\xc8\x0d\x0f\x28\xdf\x33\xba\xbe\x39\xf6\xec\xbd\x19\xa4\xa6\xaf\xa8\x53\xaa\x4d\xa0\x3b\x6b\xd7\xa8\x06\x22\x9d\xed\x76\xd2\xc5\xb9\xde\x11\x76\xd5\x19\xa7\x93\x94\x67\x92\xb5\x41\x7e\xaf\x7d\x2d\x51\x26\x97\x7c\x57\x04\xfc\x0f\xcd\x8e\x1b\x2f\x58\x9b\x1d\x41\x8d\x19\xdd\x28\xf7\xe9\x4c\x51\xa1\x78\x2d\x32\x2e\x03\xcb\xa4\x78\x85\x74\x24\x49\x7b\x4a\x37\x3f\xde\x0f\xba\xe4\xcc\xd9\x38\xcb\xbf\xa0\xf4\xad\x23\x97\xee\xd7\xf7\x6d\xc3\xcd\xb6\xb0\x6a\x36\x66\x0c\x07\x75\xd3\x91\xca\x47\x21\x33\x41\xf6\x59\xe9\x01\x4f\x70\x28\x4e\xfa\xa5\xfa\xab\xa4\xbb\x83\x79\xce\x02\x04\xf5\xae\xdc\x28\x26\x8d\x82\x43\x8b\x5b\x88\x1f\xdf\x2d\xee\x4a\xd7\xd4\x0e\xd1\x3d\xad\x57\xca\x92\x96\x14\xa6\x3a\x00\xfe\x3a\x78\xf3\x3b\x30\xb6\xfd\x5f\x39\xe4\x43\x70\x36\xdc\xed\x8d\x87\xaf\x43\x28\x2f\x43\xfa\x14\xab\xaf\x6c\x84\x15\xfc\x05\xee\x1a\xd1\x71\xd8\x1f\xaa\x46\x7d\xdf\xe5\xe0\x2e\xb6\x89\x5e\x56\x88\xde\xc0\x48\xf6\x66\x0e\x3a\x2f\xd8\xbd\xec\x60\x2a\xf5\x95\x90\xec\x4c\x6e\xab\x83\x4c\xc0\xde\xc8\x62\x1e\xb5\x10\xfb\xa6\xf7\xad\xf4\x76\x93\xc2\xfd\x57\x4d\x82\x20\xa2\xe7\x0e\x73\xad\x68\xe4\xc3\x32\x48\x8e\xb8\xe7\x31\xfe\x60\x0d\x1e\x9f\x6b\x8f\x5c\xbf\x69\x9c\x18\xd0\x6b\xcd\x73\xb7\xcf\xce\xf4\x2e\x68\xaf\x7a\xe6\x7f\xea\x46\xe9\x46\xde\x6a\x61\xfa\xa4\x2c\x53\x5c\xfc\xae\xaa\xd5\x33\x4f\xc1\xa9\xba\xd4\xa5\x3e\x57\xd1\x1c\x6a\xcc\xfc\xef\xd2\xe8\xab\x44\xcb\x12\xfb\x2e\x66\x4f\xcb\xdf\x5c\x82\xb2\x12\x89\x62\x6a\xc2\xa1\x40\x2b\xde\x7a\x86\x9e\xb9\xed\x78\x07\x33\x8d\xd3\xb2\xba\x82\x37\x84\x5d\xb9\x67\x71\xcc\x98\x80\x08\x1a\xcf\x05\x3d\x9b\xd5\x1c\x01\x01\x94\x1c\x4c\x26\xf6\x6a\xa5\xdb\xad\x3f\x53\x54\x60\x85\x77\xf9\xe5\x1a\xfe\x74\x3a\xdd\x50\xf1\xb5\x90\x1b\xea\x7b\xeb\x5a\xe7\x80\xb6\xec\xe9\x77\xf6\x5b\x9c\x62\x8e\x1d\xce\x0a\xd1\xe0\x78\xc7\x46\xc2\xf3\x8d\x0e\x7f\x06\xb0\x88\x70\x8a\xe9\xac\x11\x17\xe3\xa3\x79\x99\xc1\xd7\x5a\x62\xe9\xc9\xe0\x17\x01\x8e\x08\x8a\xeb\xfb\x37\x8d\xe2\x9c\x78\x93\xac\xf1\x09\x42\x58\x4b\xf5\x58\xa2\xd0\x2d\x75\x1e\x34\xf3\xf4\x84\xb0\x01\xe3\x19\x24\xcc\x21\x84\x8b\xf0\xdd\xaf\x1f\x3d\x8a\x31\x00\x49\x73\x6f\xf7\xf0\x49\x29\x4d\x8a\x59\x5f\x2c\xa7\x26\x3a\x36\x13\x84\x0c\x14\xb3\x3e\xf4\x83\xcd\xca\x5b\xbb\x8a\x4c\x70\x04\xcc\xb8\xf6\x71\x56\x26\x7e\xe3\x5f\x28\x0d\xb1\x26\x45\xde\x8e\x55\x2a\x93\x12\xdf\x57\x69\xa0\x30\xa6\xb4\x6d\x80\xdb\x2e\x6c\x06\xb3\xc7\x6c\x1a\xda\x42\x37\x3b\x29\xa0\x59\x1f\x39\x85\x67\x49\xdf\xdf\xb2\x66\x81\x16\x6a\x28\x6f\xb4\xf2\x09\x7a\x3b\x6f\x8f\xeb\xdb\xe4\x41\x3b\x67\xb5\x58\x68\x9c\x2e\x7c\x1d\x6d\x64\x08\xf4\x6a\x60\x94\xc7\x4b\x22\x81\xe7\x96\xe1\xd9\x00\xcc\x83\x53\x37\xa3\x1b\x53\x50\xca\xa9\xc4\x44\xc6\x70\xf7\x8f\x86\x6e\x03\xef\x6e\xc2\xcb\xcb\xc1\x79\x97\x41\x45\xb2\x39\xb9\x09\x12\xbb\xee\xf8\xf5\x76\x96\x1b\x5e\xfc\x69\x64\x1f\x7a\x71\x51\x70\x87\x75\xb6\x7c\x9e\x65\xed\x9b\xb9\xf5\xa8\x7b\xb7\x90\xda\x20\x35\x57\xbe\xd2\x67\x40\x55\xe8\xa6\xab\x36\x46\xc4\xe1\xa8\x45\xea\x53\xd8\x61\x4a\xe4\x90\x06\x5d\xef\x75\x76\x15\xa2\x65\xf2\xab\x98\x38\x80\x29\xae\xc3\xaf\xb5\xcc\xa3\xa6\x66\xab\x29\xb6\xd2\xc0\x02\x97\x9c\x63\x6a\x3b\x41\xb8\x83\x7a\x43\x2a\x81\xd6\xdb\x55\xcf\x40\x6b\x1f\x58\x42\xb0\xa8\x87\xfe\x6b\x2b\xd8\x8e\x46\x29\x8e\xd3\xec\xc3\x87\x4c\x98\x37\x73\x46\x33\x1f\xde\x7a\x2f\xf7\xf1\x04\x26\x5b\xbd\x2d\x02\x74\xc0\x33\xc7\x58\x38\x51\x00\x1d\xcd\xb3\xde\xd9\x0a\x9c\x09\x77\xc1\xf8\x6d\x58\x46\x47\x55\x73\x30\xce\xe5\x0a\x53\xbb\x15\xab\x2b\x5a\x8d\x8a\x2b\x5f\xb2\x9f\xfd\xa0\xe1\x54\xb2\x63\x67\xe5\xba\x1c\x67\xa8\x79\xdd\xdb\x61\xd4\x08\x67\xb6\xcd\xf5\xe3\x8e\x0f\xcb\xdb\x9a\x92\x5f\x62\x2c\x7d\xe9\x34\xa3\x08\x23\x90\x67\xda\x11\x65\xdc\x9b\xa8\x91\xfd\x29\xbb\x9d\x9d\xff\x2c\x46\xc6\x0f\x95\x39\x0d\x3d\xda\x52\xaf\x9e\xf6\x4f\xf6\x3d\x2d\xf7\x22\x25\x38\x3b\x92\xbe\x1b\xa4\x8b\x17\x40\x56\x78\xd2\x24\x35\x65\xbb\x58\x86\x9b\xc4\xb2\xb8\x79\xcc\xc8\x99\xd2\xf9\x36\xc2\xb8\x19\x5f\xd4\x74\x05\xd8\x6f\xfb\x46\x97\x55\xb7\x72\xe4\xb5\xfb\xe7\xe4\xa2\x93\xb5\xdb\x71\x00\x08\xc9\x00\x6d\xab\xd6\xa9\xab\xec\xdf\x60\x9a\x3c\x78\x9a\xcf\xe2\xf2\x33\xae\x14\x21\x93\x1e\xf6\x63\x34\xfe\x74\x3f\x48\x77\xbf\xcd\x3a\x63\x71\xfb\xe3\x78\x46\x5b\xd6\x3b\xf5\xda\xee\x92\x0b\x1a\x64\x13\xa4\x6c\x69\x69\x3f\x72\xfc\x87\x9f\xff\xe1\xa9\x17\x38\x08\x4d\xf8\x46\xbe\x95\x43\x89\x28\x93\x95\x83\x44\xa0\x01\x58\x1e\xe3\x2c\x21\xa7\x28\xe8\x05\x04\x69\x95\x3b\xda\xb7\x83\xee\xae\xc5\x56\xa2\xa5\xab\x1c\xf3\xd0\x8c\x2b\x92\x33\xf5\xf3\x64\x1b\xcd\x76\x08\x7c\xf2\x95\x5b\x60\xae\x14\x1e\xd4\xca\xde\x54\x5d\xf6\x0a\x61\x9a\x55\x60\x71\xd2\xef\x3d\xea\x09\xe7\xbf\x54\x60\x50\xe5\xdf\x3a\x8f\xdf\x04\xf6\x63\x16\xf6\xda\xa4\xdb\xb8\xcb\xd2\x42\xd0\x6a\xae\xcb\x1f\x1d\xbb\x4d\xae\x42\xc1\x58\x9c\x32\x83\x02\x77\xd2\x09\xbe\x16\x0d\xaf\x62\x8b\x5b\x19\x92\xf2\xb1\xc5\xf0\xea\xcd\xa1\xe3\x8e\x00\x87\xb7\xca\x8d\xab\x93\x50\x15\x40\x48\x5a\xd1\x8f\xf8\x69\xab\x4f\x4b\x8b\x01\x6f\x45\x43\xc6\xa7\xa9\x36\xfd\x35\x1f\xa5\xcc\x62\x7d\x24\xeb\xbb\x30\xd8\x18\x9c\x42\x58\x86\x45\xcb\xf1\xff\x87\xee\xc9\xb7\xd7\x09\xc0\x5e\x64\x9d\x9b\x64\x01\xa9\x9b\x3a\xca\xb8\xaf\xc1\xc6\x9b\x85\xea\xaf\x3f\xf3\x68\x67\xde\x12\x50\xd9\x8a\xcf\x2d\xc6\xb2\xe1\xd7\x1c\xd3\x2e\xf0\xc7\x14\x02\xcd\x66\x5f\x03\x05\x3d\x22\xa9\xc5\x8d\xe0\x81\xc4\xec\x3a\x5e\xae\x8a\x20\xeb\xe8\x08\x56\x19\x78\xae\x45\x59\x43\xc6\x54\x2d\x03\x19\x86\x4c\xf7\x75\x4f\x64\x9a\xe4\x64\x69\x69\xbe\x65\x18\x5f\xb7\x59\xe6\x98\x27\x4b\x45\x6b\x74\x8b\x9a\x51\xf7\xfd\xef\xd4\xcd\x48\x60\x9c\xbe\x64\x60\xa3\x19\x76\x52\x9f\xe4\xb6\xfc\x64\x37\x45\xfd\x2d\xd3\x63\x1c\x8f\x75\x76\xbc\x80\x52\xbd\x32\x90\x5f\xbd\x42\x65\x94\x79\xbf\x79\xde\x2d\x3b\xe3\xcb\x19\xe3\xd4\xd0\x28\x83\x44\x9b\xe1\x37\x4b\x6e\x24\x3a\xaa\x87\x14\x94\x77\xc2\x5f\xf6\x82\xe5\xc8\x4a\x03\x03\xf1\x12\x31\x08\x6e\x4a\x57\x8f\xf5\x2b\x63\x06\x00\xfc\xbb\xe7\x1f\xb7\x19\xc3\x03\xb9\x16\x31\x05\x81\xb3\x2e\xf2\xdc\x89\xf4\x09\x4e\x0b\x37\x0b\x08\x3b\xd3\x21\x4a\x0f\x3f\x45\x04\xfc\xb3\x72\xd3\xad\x4a\xe2\x06\xbd\x8e\x34\xd1\xe2\x0f\xce\x72\xa8\xab\x40\xc1\xca\x4e\x45\x46\x35\xee\xbf\xe7\x2d\xea\x80\xfd\x95\xf9\xc6\x53\xe3\x2e\xde\x81\x6b\x43\x2b\xa4\x8e\x38\xbe\x49\x57\x0c\xf5\x55\x0b\x7e\x82\x67\xfc\x2f\x15\x64\xbc\x4b\x64\xef\xee\x9b\xe2\xb5\x81\xf9\x0e\xb9\x6e\xd0\x1d\x5f\x34\xe3\x42\x62\x31\x4c\x87\xf6\x02\xc9\xb1\xd3\x1c\xce\x77\x2c\x1a\x02\x3c\x2c\x02\x95\x28\xcf\x8d\x6f\x9e\xa1\xb3\x3b\x61\x26\xc4\xa8\xc2\x87\x72\x38\x8f\x38\x2a\x7a\x87\x56\xe1\xe2\xfc\xca\x54\x9b\x00\x85\x8d\xe2\x82\x9f\x87\xaa\x20\xeb\x08\x30\xa0\x04\xbd\x8a\xe7\xd5\xb3\xe3\x36\x31\xaa\x57\xcf\x07\x18\x26\x2c\x0e\x76\x80\x62\xeb\x36\xa0\x37\xf8\xf0\xab\x7c\xca\x79\x29\xf7\x8f\x03\xc9\x17\x27\x17\x4e\x53\xe6\x02\x47\x39\xbe\x73\x24\xfe\xc9\xb4\x50\xf4\xab\x82\xd1\x05\xbb\x3a\xcb\x69\x33\x27\x55\x8f\x80\x96\x89\xd4\x52\xe9\xa4\x6e\x3d\xe7\xda\xfa\xdf\x87\x92\x1f\x17\xfa\x24\xce\x87\x4d\x3e\xf2\xa5\x56\x10\x0a\x0c\x88\x4f\x84\x40\x48\x1f\x97\x4d\xeb\x9e\x17\x86\xe5\xfa\x8e\x14\x19\x1b\x6f\x79\x7d\xbc\x0c\x86\x42\xa1\xf7\x34\xbe\xd7\xcd\x72\xb2\x15\xd0\xb2\xb5\xb9\x87\xd2\x30\xdf\x63\x74\x2b\xbc\x4b\x10\x17\x6a\x99\x15\xcf\xc7\x49\x8d\x48\xb3\xfd\x30\x47\x57\xb2\xa1\xf5\x9e\x08\x97\x88\x18\xcf\x04\x3a\x6e\x9c\xdc\xe9\xbe\xaf\x09\xd6\x15\x30\xde\x06\x1f\xd7\xad\xc9\xd4\x7d\x23\xe7\x52\x88\xd0\x92\x91\xc6\x01\xba\xd4\xa7\x6d\xa8\xdf\xf2\x62\xa7\x23\x46\xc0\x77\xee\x25\x15\x62\x1d\xf9\x07\xaa\xbc\xfd\xbc\x14\x59\x04\xff\xfd\x9f\x8b\x04\xe2\x47\x65\xa0\xfb\xb0\xb4\x34\xab\xd3\xb7\x86\xd5\x60\xec\xea\xb5\xf9\xfc\xe4\x2a\x85\xec\x05\xe8\x85\xe1\xc6\x6b\xaf\xdd\x0f\x6e\xb7\xde\x3a\xb1\x45\x11\xda\x69\xc7\x72\x3d\xec\x82\x92\xbb\x3f\xf2\x01\xff\xe6\x0d\xf6\xe2\xc5\x24\x0d\x1a\xd7\xcc\x0c\x51\xa3\xdc\xb2\xf6\xaf\x1f\x30\xe3\xa9\x7a\xf1\xf3\x2f\x30\xda\x0c\x7d\x6c\xeb\x89\x71\x99\xc9\x36\x7d\x4d\xad\xd7\xa8\x43\xcd\x2f\xbd\xa1\x36\x14\x69\x2b\xa4\x92\xbb\xb3\x7c\xea\xe1\x51\xd5\x6f\x81\x53\x2f\xeb\x37\x8a\x18\x6c\xca\x0f\xc8\xf1\x36\x63\xce\x01\x7e\x2c\x7c\x21\x99\x76\xc7\x6b\x93\xfe\xc4\x0b\x52\x72\xb2\x59\x72\xba\x14\x67\x2e\x7b\x72\x00\x05\xdf\xfd\x08\xf7\x82\x18\x8c\xc1\x6e\x37\x1a\x3b\x9e\x98\x5f\x9f\x51\x90\x7c\x49\xb0\x17\xa4\xa8\x8a\x14\xe3\x2b\x67\x36\x4e\xf0\xde\x7a\x12\xed\x65\x88\x33\x4b\xdf\xbf\xe2\xe4\xea\xd9\x9e\x7b\xe5\xbe\x31\x93\x34\xe0\x75\x52\x9a\x84\x37\xc7\x24\xf6\x1e\x6c\xc5\x16\xd3\xb2\xaa\xb6\xb4\xf9\x35\x35\x1f\x01\xa8\x37\xb0\xb3\xe6\x4e\xac\x92\xee\xb8\xa1\x46\xaf\x9b\xe9\x76\x61\x02\x2c\xf9\xc6\x6b\x98\x3b\xb1\x55\x7c\x42\xd9\xbe\xd4\x12\x34\xe9\x93\x6b\x31\x86\x64\xd8\xcb\x24\x06\xe8\xa4\xdb\x92\x67\x8a\xe1\x2f\x94\x0f\x3c\xa3\x3b\xe2\x7b\x37\x5d\x29\x65\x05\xd3\x7d\xb1\xf3\x7b\x45\x7f\xe4\x45\x6a\x92\xda\x58\x4a\x66\xd0\xbd\x4d\x57\x6e\x07\xcd\x4b\x1b\x50\x97\xf4\x01\xe1\x88\xab\x16\xbc\x92\xc8\x23\xac\x02\xf2\xae\x41\xe2\x2f\xb5\xd4\xce\x56\x25\x7c\x55\x27\xd0\x88\x92\x49\x3e\x33\xe8\xa1\xc2\x9a\x91\x55\x83\x1f\x5c\x86\x98\x96\x81\xb6\x9d\x83\xd7\x38\xb1\x5b\x75\xc6\x9e\x38\x62\xce\xdd\xb9\x0b\x96\x97\xf7\x27\xe0\xca\x28\xc8\x67\x38\xc4\x2f\x4d\x2e\xb8\x4d\xe2\x3a\x3c\x5c\x32\xc8\x6d\xdc\x8c\xe2\x6f\x0b\xa1\xca\xd9\x9d\xb4\x2e\x34\x6a\xc1\xae\x59\x23\x21\x43\xb9\x39\x24\xbf\xd2\xf1\x59\x05\x42\x03\xb8\x40\xb8\x60\x47\x24\xf9\x81\x94\xe1\x3a\x7a\x42\x07\xb4\x1b\x37\xe0\xb3\xfa\xd4\x1c\x24\x4b\x19\xe7\xc2\x5f\x22\x76\x4d\x88\x8c\x5c\x8c\x61\xe1\x15\x57\x6d\xea\x39\x9e\xf2\xa2\x73\x2f\xd8\xc2\x4c\xc6\xaa\x0f\x72\x5f\xe4\x91\x6c\x4e\x23\x4e\x92\x4c\xb4\xf7\x08\x4d\x2d\xf1\x9c\x06\xbd\xc3\x24\x86\x6d\xbd\x5b\x4a\x16\x54\x29\x61\x9f\x2e\x99\xf0\x30\xef\xb2\x23\x30\x1c\x47\x56\x5a\x48\xa9\xa2\x8f\xc5\x39\xc1\x82\xcf\xc9\x0c\x44\x06\xba\xfc\xa7\x4f\xe4\x58\x22\x85\xa3\x79\x1e\x3c\xbe\xbf\x2a\x1a\xca\xdb\xe8\x7d\xa6\x55\xc9\xce\xe9\x4d\xe6\x8f\xd1\x34\x61\xf0\x1f\x5d\x22\x5e\x06\x1f\xcf\x05\x17\x12\x26\xde\x40\xa3\x9b\xd2\xb0\x17\x7e\xa0\xde\xae\xd2\x36\x10\xad\xe4\x1e\xee\x1e\xa3\x25\xdf\xe4\x3c\xca\x28\x00\x0d\xaf\xcd\x79\x6c\xb2\x8c\x23\xcf\xd9\xbd\x9e\x8b\x6a\xfb\xb2\x1d\x33\x34\xc3\x96\xca\xfd\x23\xf6\x7d\xd8\xcd\xcd\x58\x66\xf9\x36\xf1\x29\x70\xb3\xa1\xfb\x4b\xd7\x38\x6e\xec\x4f\xaf\xff\x64\xff\x65\x8d\x14\xfc\xe9\x2d\x81\x1f\x93\xe9\xde\x6d\x01\x24\x1c\x85\x1c\xf3\xef\xbe\x72\xbb\x17\x7e\x81\x8f\x62\x45\xfc\x7e\x8f\xb8\x57\x1f\x0f\x48\x04\x01\x49\x2f\x11\x0e\xed\xe8\x4d\x5e\xe2\xf4\x25\x2c\x56\x23\x6a\x0f\x5a\x8f\x93\xdd\xfc\xf2\x22\x40\xdf\x56\xe5\x8c\xf9\xe5\xa6\x64\x7b\x69\x7e\xd1\xf5\x87\x19\x55\xa2\xfb\xce\x9e\x11\xca\x26\xc1\x3a\xde\x04\xea\xbb\x67\x5d\x69\xf4\xce\x3e\x60\x9c\xe5\x06\xf3\x7a\x03\x97\x8c\xe5\x26\x01\xba\x4a\xe9\xbb\x28\xb0\x5a\xb7\xb2\x4e\x93\xb9\xfe\x6a\x21\x59\xab\x58\x9a\xb5\xf5\xe3\x4b\x9e\xea\xc4\x6f\xd6\x7c\x97\xa5\x9d\x08\xd0\xc9\xdc\x89\x7f\x89\x45\x95\x24\x00\x7b\x62\x21\x70\x0f\xfa\x78\xf6\xfe\xdf\xb1\x73\xc0\xc3\xf5\xe4\x10\x5b\x7e\x25\xfa\x5f\xa4\x3d\x66\xe6\x6e\xd8\x0a\x0d\x0a\xbf\xab\xd9\x41\x12\xe1\x58\x31\x89\x92\x49\xef\x96\xb9\xdf\x1f\xa1\x76\xad\x3b\x44\x23\x70\x8c\x3f\xcd\x4e\xd8\x8e\x92\xe5\xcc\x9b\x69\xb7\xd4\x69\xaf\x43\x12\x07\xe0\x0a\x0f\x0f\x99\xa0\xb8\xca\x94\xbc\x12\xb6\x78\x08\xe3\xf2\xc7\xdc\xc2\x4b\xa5\x82\xcd\xb8\xcb\x33\xa8\xf4\x15\x23\x47\xf0\x49\xca\xe5\x99\x45\xd7\x02\x7a\x2c\x27\xbd\x88\x4a\xbf\x2e\x02\x13\x0b\xaf\xe9\x33\xf3\xc7\xff\xc1\x6d\x61\xf6\x8a\xd0\x6d\x01\x7b\xc6\xb1\x01\x5a\x12\xab\xf3\xb1\x4d\xf5\xb7\xe2\x1f\x34\x03\x74\x62\x05\x6c\xe8\xc3\x98\x65\x7d\xc9\x8f\x20\xbc\x62\xa5\x4f\x83\xfd\x4a\x67\x73\xc6\x54\x95\xe8\x1e\x97\x9a\x6e\x04\x40\xd1\x72\x25\x79\x4e\x05\x7f\x76\x65\x7d\x58\x19\xbf\x6c\x9a\x42\x87\x32\x13\x02\xe4\x2f\xb0\xbb\x92\xd4\x0b\xfd\xc2\x3d\x5e\x96\x04\xaa\xd6\x74\xf1\x8c\xab\xd4\xdf\x79\xf8\x57\xca\x8b\xf6\xcd\xe8\x65\x9b\xed\x37\xc9\x58\x3a\x5f\x1b\x3d\x01\xbb\xb8\x70\x9b\x5f\x5b\x90\x6f\x04\xb2\x04\x04\x98\xb0\xfc\xdb\x41\xc3\x31\xd7\xfa\x76\x46\x90\x55\x39\x0b\x09\x94\x30\x31\xc2\x14\x4c\x0b\x56\x0c\xa4\xb2\x80\xec\x38\x83\xab\x53\xb3\xa7\xf6\x79\x4c\x15\x23\x72\xef\x90\xfd\xf1\x1f\xbb\xd9\xa9\xb6\x45\xed\x91\x96\x7f\x13\xfd\x40\xc3\x7f\xfb\xbc\x4d\x91\x1f\xb9\x67\x0c\xe2\x52\x4d\xd3\xa8\x05\x54\x5b\x85\xd8\x21\x97\x86\x99\x82\x77\xf9\x0b\xea\x24\xb8\x86\x2e\x70\x6f\x57\x72\xe5\x55\x29\x3e\x5c\x7b\xaf\xfe\x5a\xaa\xc0\x15\x36\x91\x47\x06\x40\xbe\x77\x77\x8d\x00\xad\x98\xf5\x45\xc6\xc7\x52\x97\x38\x64\x58\xc2\x88\x7a\xb7\x78\x7d\xb0\x01\x92\x19\x05\x94\x29\x10\x31\x06\x8b\x98\xc1\x93\x48\x62\x1b\x2f\xc6\x55\x39\xc5\xe2\xf6\xe5\xf3\x0e\x2b\x7c\xc0\xe9\xc3\x53\xc4\x74\x4e\x42\x81\xe0\x2e\x61\xa7\x4b\x41\xc2\x80\x54\xba\x0c\xa3\x82\x67\x08\x34\xc5\x19\x7e\x89\xfa\x1d\xee\x66\xb5\xd4\x58\xf5\x40\xe9\x43\xa6\xf0\x7b\xbc\x5e\xfa\x4d\x5f\xdc\xdc\x93\x8b\x06\xdd\x8f\x24\x9d\x93\x9e\x82\x92\x13\x6e\x32\x7e\x0d\x7f\x97\xfc\x1c\xca\x48\xdc\xd0\x1d\xe5\x74\x5b\x9a\x68\x53\xe0\xf1\x81\x85\x16\x3f\xfb\xe1\x96\x7c\x00\xc6\xfb\x5e\xa6\xd5\x9f\xff\x5b\xd5\xb4\xf7\xc9\xda\x29\xa7\x8f\x93\xe3\x77\xae\x08\x24\x75\xaa\x1f\xd0\xb5\x5b\x13\xf8\xed\x58\xb9\x91\xdf\xe8\x61\xb0\xf6\xd7\x9f\x79\xcc\x8f\xdc\x3a\xd0\x06\x7b\xc3\xfc\x37\x75\xe9\x03\xde\x54\x8e\xd4\x8e\xde\xf2\x6e\x50\xd9\x30\xa6\x12\x53\x0e\x5f\x38\x5c\xe5\x83\xe3\x34\xa2\x7b\xaf\x05\xde\xf1\xd4\x91\x13\xf2\x08\x80\x95\xb9\x7c\x40\xbd\xf6\x08\xdc\xad\x81\xe0\xe9\x30\x00\x63\xc6\x5d\x74\x2b\x14\x21\xea\xc8\xc6\xdb\x74\xc5\xde\xd9\x47\x6c\x16\x94\xd5\xbc\x75\xde\xb7\x97\x6f\xb6\x93\x78\x80\x88\x3a\x04\xe6\x04\x24\xff\x67\x7c\xb2\x95\xae\x3b\x58\x93\x80\x67\x1c\xcf\x61\x86\x34\x1f\x52\x85\x30\xbd\xd2\x8f\x6a\x66\x2f\xbc\xeb\x26\xd2\x08\x91\x25\xaa\xcc\x0d\xbb\xd7\xa4\x08\xc9\x82\xe9\x38\x08\x4b\xaf\x7f\x6c\xf2\x23\x04\x33\xa7\x6b\x4b\x79\xa7\x00\x6c\xcd\xa2\x98\xfd\xff\x1b\x5f\xd1\x00\xec\x1f\xaa\x2d\xc5\x09\x52\xe4\x35\x2a\xb1\xb1\x2c\xeb\xa0\x5c\x29\x17\x78\xfe\xf2\x80\x0a\x17\xa4\x38\x31\x37\xea\x5a\x74\x1d\x54\xfa\xc4\x1e\xc2\x46\xe8\x8f\x63\x16\x61\x2e\xb1\x67\xd6\x04\x39\xe8\xb9\xde\x19\x05\xee\xb1\xb6\x08\x89\xab\xc0\x0b\x5e\xe6\x42\x72\x23\x87\x63\xc4\x91\x29\xf9\x15\xdd\xf6\xf9\xe2\x4c\xec\xff\xcf\x33\x1b\xcb\xce\xa9\x68\x49\x7f\xc0\x8f\x55\xdb\xa0\x13\xab\xa5\xe3\xaa\xbf\x96\xfb\x7d\x83\x6d\xb6\xaa\x13\xec\x80\xb0\xd5\x05\x06\x8f\x5b\x0d\x92\x65\x8e\x01\x98\x13\x78\xe4\x71\x65\xa7\xaf\x5c\x08\x65\x81\xe8\x13\xc2\x68\xd8\x51\x3a\x5e\xa6\x62\x95\xfd\x0f\x27\x00\x4a\x6e\x86\x56\x43\x23\xbc\xd8\x67\x17\x08\x8d\x7a\xaf\x6d\x7e\x03\x55\x28\xca\xbb\xfa\xb6\x5a\xb5\xd3\xeb\x9d\x6a\x92\x8b\x29\x49\xe3\xe8\xd5\x39\xd5\xcf\x85\x5d\x5a\x63\xfb\x5d\x53\xcf\xee\xcb\x21\x3a\x0a\x7c\x16\xd8\x1b\x43\xdc\xac\x3e\x70\xb3\x4f\x5a\x00\x0d\x45\x35\xc8\xd5\xa5\xe0\xd2\xbf\xb1\x03\xbf\x10\x16\x6c\xa9\xab\xb9\x3e\xe2\x0b\x50\x77\x3e\xa4\x82\xd9\xbd\xaa\xec\xe1\x8f\x7f\x57\xb8\x3e\x63\x19\xa3\x09\xe8\x2c\xf6\xc7\xab\x52\x27\xc0\x51\x98\xf4\x5a\x8c\xb7\xd7\x9f\x84\x14\x80\x69\x5a\x01\xa9\xc4\xa9\x13\x25\x97\xc6\x40\x8d\xc9\x9f\x52\x68\xe0\x8a\x6f\x44\x95\xce\x32\xd9\xba\x89\xc4\x3f\x21\x4d\x15\x89\xc7\x53\x50\x56\xaa\xa3\x8c\x47\x94\x24\xc8\xc5\x91\x9a\xe3\xd2\xc3\xd3\xee\xd1\xaf\xc1\x6c\x97\x96\xba\x48\xca\x2a\x74\x12\xce\x7c\x02\x7a\x3f\x66\x75\x17\x14\x57\x9e\x6c\x0a\x37\x08\xe6\xa9\x15\x1b\xc8\x64\x1f\x2c\x31\x86\x2d\xd1\x64\x43\xdf\xa7\x48\xc4\x34\xbc\xbc\x12\x8a\x47\x4f\xb2\xfc\x33\xd7\xe4\xe0\xf9\x6c\xaa\x74\xa9\x4a\x4b\xf2\xc7\x79\xce\xe9\xa9\x9e\x27\x66\x14\xd1\xe2\xc6\xe0\xfb\xd9\x6f\x28\xe1\x90\x81\xc0\x5b\x80\xeb\x9d\x66\xec\x39\xc5\xfc\x0d\x7a\x1e\x00\xfd\xcb\xa0\x76\x74\x74\x62\xd6\xc2\x2f\x85\x29\x28\xcc\x1c\x17\x50\x34\xf9\xf2\xd8\x8d\xbc\xbc\x32\xc6\x65\x82\x4e\x61\xa6\x15\xf4\x79\x72\x06\x39\x66\x95\xec\x94\xce\x6d\x1b\x62\x99\x18\x19\x1a\xb8\x25\xe8\xfa\x3e\x5e\xb9\x06\x28\xc1\x46\xd1\x32\x90\xff\xbe\xc1\x98\x93\x69\x1a\x26\xa0\x84\xa2\x48\xf0\x37\x06\x97\xa8\x16\x98\x46\x31\xb7\x1c\x0e\xc0\x52\x08\x80\x15\x17\x3d\xc6\x29\x29\x5a\x63\x63\xc2\x81\x3a\x59\x16\xd3\xe3\xeb\x51\xbb\xba\x08\xaa\xa0\xa5\x97\x86\x18\x39\x69\x43\x9d\x15\x38\xb7\x17\xf8\x3d\xbb\x57\x1c\x8f\x80\x9d\xbf\xf5\xd6\xbc\x5f\x20\x72\x45\x17\x70\xd6\xd9\x7d\x5e\x6b\xa3\x5a\xe4\x72\x2b\xbd\x89\x55\xd4\x1b\xae\xa6\xd4\x66\xdc\x14\xf0\xe1\x67\xdb\x9f\x7c\xec\xa7\x95\xee\x38\x46\x0c\x14\xf0\x4d\x35\x34\x18\xcf\xd4\x41\x0f\xa4\xd7\x9e\x3a\x72\x1b\xac\xf8\x23\x8a\xb6\x2b\x7f\xed\xee\xbb\x5d\xeb\xc6\xe3\xef\xd5\x80\x86\xde\x28\x6a\xdd\xeb\xf2\x7d\x4c\x80\x78\x9b\x96\x94\xf5\xce\x8b\x3b\xa8\x0f\xee\x00\x99\xe6\x81\xed\x74\xc3\xc6\xca\xc6\x10\x3f\x1c\x5d\x86\x41\x1b\xfc\x70\x5e\xa6\xd9\x79\x6d\xbe\x00\x62\xd6\xa7\x93\xaa\x20\x86\xa2\x3d\x26\x81\xe3\x99\x6b\x85\xf0\xf2\xb8\x62\x16\x70\xd6\x14\xbd\x3e\xab\xdc\x53\xbe\x0b\x0e\xbe\x6c\x4f\x3b\xfa\x56\x2d\x6a\x7a\x40\x24\x1d\xdd\x6d\x9f\xfb\x28\xa0\xb2\xbb\x6f\xa4\xe1\xf4\xd7\xc2\x12\x99\xe7\x37\xf9\x72\xd8\xe5\x35\x9d\x39\x48\x62\xfe\xbb\xf4\xaa\xe1\x5d\x68\x89\x22\x35\xc1\xf3\x4f\x31\xd7\xc6\xb0\xaa\xfe\x76\xda\xf0\x43\x79\xc6\x9d\xb5\x2a\x55\x5e\xe0\x2e\x99\x37\x62\xcd\x0b\x72\xaa\x86\x59\x6d\xc3\x11\xcf\xa4\x6b\x64\x30\x07\xee\x46\xac\x8b\x68\x67\x32\xfc\x10\xc0\x7b\x38\x9f\x23\x7d\x0b\x63\x0f\x76\x72\xb0\xf6\x3d\x63\x19\x9b\xe5\x46\x4f\x71\x8a\x71\x42\x76\xd7\xf5\x47\x1c\xa0\x69\x34\x5a\xaf\x3b\x38\xd9\x41\xc6\xb6\x4e\xde\xc9\xc0\x66\xd2\x79\x9c\x4b\x51\xbd\x04\x05\x35\x8f\x83\x4b\xd5\x98\x8c\x78\x61\x72\xb8\xf2\x17\x68\x06\x78\x77\xa5\x5e\xfe\x94\x11\x25\x9d\x24\x03\x6c\xf0\xd4\xe4\x42\xa9\x22\xc1\xe9\xfd\x70\x15\xc0\x04\xbe\xdd\x94\x06\xe9\x41\x0c\x9e\x1b\x55\xa3\xde\xa6\x7b\x11\x96\x1d\x0c\x0d\x6c\x7f\xb9\xfa\x61\xb7\xb7\x40\xe0\x5a\x58\x57\x35\x9d\xa1\x79\xff\x9e\x49\x35\xd7\xad\xe8\x2e\xb2\x0f\x30\x8b\xfc\xe2\x9d\x13\xe9\x8c\x81\x85\x3c\x22\x96\x46\x95\xdc\x18\x8b\xe9\xce\xeb\xff\x8d\xae\x55\x8f\x5f\x7b\x03\xdb\x0a\x22\x3e\x31\xb9\xad\xce\xa0\x00\x26\x32\x5f\xb8\x85\x8e\xc2\x10\x6e\x10\x20\x7d\xaf\xd4\x1e\x7a\xdd\x2a\x42\xc3\x13\xe2\xfd\xeb\x38\x09\x5f\x66\x54\x23\x46\x64\xf9\xf2\x04\x05\x3e\x7f\xe1\xa0\xdb\x12\x53\x1d\x3f\xcd\x7f\x0c\x7c\xe6\xc5\x58\x0b\xd6\x37\xd7\x35\x80\x1d\x65\x10\xcf\xfe\x72\xe0\x1a\x88\x4c\xbe\x76\xaf\xe9\x3a\x58\xc2\x23\x79\xc1\xe6\x4d\x7c\x7a\x02\xdf\xd1\x6d\xa2\x3d\x26\xc5\x84\xdb\xec\x9a\xba\xae\x15\x13\xc0\xa3\x81\xc9\x6e\x49\xf6\x3c\x04\x04\x6e\x42\x26\xd3\xcf\xa3\x2d\xdb\x98\x75\x4e\x2c\x99\x93\xf8\xa9\x7f\xb8\xa5\x93\xab\x6f\xb1\x9f\xc0\xa8\xd1\x4b\x8a\xdf\x71\xd9\x25\xef\x06\x7a\x3b\x96\x65\xa6\xba\x66\xfc\x5c\xb6\x88\xfa\x14\x86\xc2\x81\x1f\xd8\x1b\xd8\x28\xea\x49\x4d\x0d\xf1\x59\x91\x8e\xff\x1d\x38\x0b\x5c\xfd\x57\x7b\x01\x7f\x3c\xff\xc9\x0b\x0d\xc0\x74\x03\x3c\x8c\xf5\xe8\x1f\xff\xcf\xba\x0d\x0c\xc3\xfe\x1e\x36\x0f\x2b\x0c\x3d\xcc\xf0\xc2\xc4\x04\x3d\xfa\xde\xc2\xf5\xbe\xf1\x68\x5e\x77\xb6\xf0\x1b\xd8\x8f\xb5\xfc\x8b\xfe\x46\x2a\x64\x1d\xc6\x6c\xe7\xc1\x82\x4c\x2b\x4d\xdc\x73\xf3\xca\xe3\x3b\xd0\xb6\x69\xb8\xf6\x42\xf7\x77\x5e\xc3\x18\x0a\xaf\x67\x4c\xa8\x04\x2a\x2d\x76\x40\xd4\x02\x14\x9e\x1b\x64\x21\xfa\xb0\xa3\xad\x0c\x30\xe2\x48\x9b\x6c\xcd\x3a\x98\x95\x3f\x62\xb5\x5a\xfc\x3a\xfe\xb6\x05\x51\x7e\xdb\x95\xcd\x7d\xab\xb1\x32\x67\x10\x6c\xf0\x0b\xda\x79\x7c\x3a\x57\x45\x80\x0b\xc1\x7a\x7f\x31\xf1\x2d\xf4\x4f\x95\xb1\x69\x5a\xde\xbc\x35\xa7\xa9\xcc\x20\x58\x42\xe6\x03\x30\x3d\xe6\x02\x87\xfd\x1c\xe9\x24\x0d\x9d\x78\x75\xaa\x2d\x9f\x59\x1c\xdc\x45\x65\xe1\x7c\x18\x4d\x43\x8a\xf2\x9f\xfe\x02\xc2\x49\x34\x0c\xa1\x09\x19\x31\xbb\x97\x84\x96\x22\x99\x71\x8d\xab\x96\x9d\xde\xe6\xd7\x1d\x2e\x4d\x57\x12\xd9\xa1\x31\xb0\xf6\xfb\x7a\x90\x81\xcc\x3f\x69\x23\xb5\x36\x72\xd6\xe5\xda\xdc\x54\x5e\x79\xb0\x92\x62\x82\xb2\xae\x53\x48\x22\x6b\x2a\x14\x6e\x2d\x46\xc3\x56\x7b\xd3\x16\x1d\x23\x13\x30\x26\xe9\x6c\xd9\x53\xca\x96\xf5\x8b\x09\x1b\x2c\xdc\xe9\x48\xb2\xc1\x84\xfc\xc2\x4f\xfd\xbd\xb8\x14\x7d\x62\xe6\xb3\xdd\x03\x17\x5b\xb6\x8a\xea\x5b\xe5\x0e\x23\xf9\xc8\x9d\xff\xf0\xe9\x27\x89\x35\xfd\xdb\x23\x89\xb0\xfd\x8b\xd8\xea\x2f\xde\x7c\x3f\xa9\x0f\x8d\xcd\xa6\x6c\x29\x71\xa4\x31\xed\x85\xb2\x87\x1c\x54\xc8\x0c\x99\x5b\xab\x33\x97\x07\x6b\x2a\x17\xfa\x8f\x59\x57\x5b\x87\xf2\xfa\xfc\xb6\x17\x19\xcf\x77\xc3\xe6\x52\xd2\x52\xac\x14\xae\x0b\xac\x6e\xa5\xf3\x2d\xbb\x42\x65\xee\x86\xd6\x18\x02\xf5\x52\x0a\x78\x95\xf0\xcb\x69\xa1\xf2\xf6\xfa\xf4\xd8\x8b\xc2\x9a\xab\x48\x38\x06\x4a\x11\x50\x65\x59\x9b\xe8\x3f\x67\x99\xf3\x7a\x5c\x5b\x6f\xe5\x2c\x2e\xb9\x10\xf9\xe7\x15\x0e\x33\x96\xcc\x41\xa0\x40\xf4\x13\xdd\xac\xfc\xce\x34\x4d\xe2\xbb\xd1\xe6\x6f\x5a\xb6\x62\x75\x32\xca\x7d\xc2\xd1\x9d\x22\x75\x7a\x7d\x14\xa1\xca\x01\x1e\x6e\xde\x6c\x41\xcc\x3b\xb8\x30\xe9\x54\x0a\x6e\x75\x22\xa4\xa9\x72\x3d\xbb\x8c\x05\xdf\xbc\x40\xfe\x3a\xb1\x07\x05\x4f\x9f\x5e\xc8\x68\x22\xd7\x7f\x6d\xac\x7e\x4e\x17\x0a\xf8\xea\x42\x26\xea\xe3\xc2\x1c\xbd\x1d\xfb\x94\xcc\x22\x3b\x2c\x67\x09\x72\x4f\x71\xd3\xf9\x96\xd8\x10\x17\xa1\x20\x9c\x8d\xcc\xb8\x54\xf9\xf0\x6d\x0a\xac\x59\xda\x98\x9e\x31\x54\xcd\x20\x91\xd0\x1c\x89\xe2\xb4\x9f\x2e\xd3\xfa\xd6\xf4\x34\x19\xb3\x37\x74\x50\x23\x84\xc3\x7a\x02\x31\x0e\x8e\x5d\x44\x1f\x89\xcc\x9b\xd5\x9d\x09\x67\x1f\x41\xf1\x95\x9a\xad\xd2\x8b\x69\x44\xd5\x1c\x91\x08\x67\x30\x68\x25\x3d\xda\x0d\xca\x16\xa3\x37\x8f\xab\xa3\xf2\x98\x0b\x60\xe0\x8b\x11\xe7\x9e\x7a\x05\x29\x2b\x82\x44\xd1\xce\x8b\x51\x8a\x92\xcf\xff\xdd\x72\x02\x51\x75\x01\x07\x76\x38\xa9\x66\x7c\x88\x4c\xd4\xdb\x63\xdf\xc4\x79\x81\x43\xb7\xb7\xb8\x7b\x7a\x0b\x74\x30\xd0\xe6\xf3\x19\x50\xa5\x1e\xb8\x8a\x72\x2b\x6d\xf7\xcf\xb9\xab\xe9\x51\x81\x48\xd9\xe3\xf8\x7c\xe0\xc3\x2d\x96\x3a\xbb\x82\xe1\x33\x44\x8c\xf9\xc4\xcc\x42\xf0\xb6\x3c\x7c\x04\x31\xc2\xea\xe5\xdf\x57\x29\x39\x03\x62\x5d\x04\x55\xc3\xf1\x0d\x1c\x76\xb2\x52\x20\x84\x5b\x91\xd1\x5f\x9c\x9a\xd1\x2c\xf7\xb7\xe3\x74\xe7\x93\xf9\xca\x2f\xcc\xf4\xcd\xec\x2e\x67\x4f\x2f\x36\x77\xf7\xfa\x16\x5b\xb6\x66\xd1\xca\x00\x74\xa8\x50\x84\xaa\x62\x64\xb1\x19\x2c\x92\xc1\xaa\xc4\x90\xea\xdd\x00\x76\xcf\x39\xb7\xb1\xcc\xb7\x8a\xcf\xa5\x0b\x2d\x0d\x3f\x4d\x42\xda\x7f\x43\x4e\xff\x4b\x53\xb3\x4d\x3c\xcb\x2a\xe8\x29\x0e\x58\x11\x19\xf4\x7f\xc3\x3a\x52\x79\x15\x85\x07\x08\xbf\x23\x5f\xb8\x49\xf2\x17\xcd\x77\x70\xf7\x95\xa7\x1c\x6c\xcb\x7b\x0c\x98\x7d\x3d\x4f\x35\xf1\x51\x74\xa2\xdf\x6a\xb2\xd5\x0c\x3d\xd3\x7f\x0a\x7f\xa2\x3a\xf9\x51\x53\xfe\x14\xe4\x16\xd3\x17\x40\x35\x11\x27\xa6\x55\xfa\xac\xeb\x30\x70\x73\x0e\x58\xa9\x40\xbd\xc7\xb8\xfd\x7b\x16\xcf\x45\x28\x54\x44\x26\x16\xd6\x24\x69\x7c\x84\xd9\xca\xe9\x68\x99\x1a\xa0\x38\x64\xdb\xb1\xd6\x3d\x11\x83\xdf\x0d\xd7\xdc\x60\x49\xc2\x0f\x09\x02\x22\x76\x49\x83\x46\xed\xeb\xf3\x06\x3a\xe9\xc9\x90\x36\x7f\x1d\xa3\xd8\x6d\x98\xa0\x97\x56\x8d\xa7\x4e\x49\x9f\x29\x7b\x85\xe0\x55\xe9\x67\x08\x63\x9f\x7f\x5b\x98\x29\x95\xd2\x77\x00\x4b\x2d\x21\x38\xd4\x7a\xcb\xab\x8e\x89\x08\x9b\x91\x7a\x72\x28\xf6\x45\x67\xc9\x72\x76\x7f\xc9\x63\xa2\x9c\xc9\x95\xba\x9b\xf6\xd4\x99\xd4\xba\x16\x22\xa2\xc5\x8b\x08\xa2\xa7\x78\x71\x4d\x95\xed\x56\x3a\x36\xcc\x48\xa4\x6f\x7f\x76\xb6\x57\x80\x2f\x5c\xd9\x42\xfa\x0a\xa5\x17\x05\x34\x57\x22\xc3\x24\xa5\x4a\x0e\x51\xa0\xd1\x3a\x09\xf2\xb7\xe4\xa2\x14\xcf\x89\x7b\x51\xeb\x1d\x8c\x84\x70\x37\x26\x60\xa3\x19\x50\x4e\x39\xae\x4a\x2b\xc1\x92\x93\xb6\x03\x87\x6c\x4d\x4b\xfc\x04\x9d\x66\x1a\x5e\x63\xa7\xeb\xe6\xc2\x77\xa0\xe2\xb9\x25\xd0\xe0\xb1\xc5\x0f\x74\xae\xaa\x15\x1d\x42\x22\x27\xf2\x86\x19\x5d\xc0\xd8\x8d\x09\x45\xe8\x51\xbf\x8e\xdd\x52\xbf\x6f\x3b\x1e\xf3\x56\x4c\x97\x0d\x04\x9e\x90\x20\x7e\x79\x9f\x75\x5f\xbd\xfc\xd5\x23\xe3\x2b\x29\xcb\xc0\x35\xe1\x3f\x04\x05\x19\x81\x8f\x16\xdf\xc5\x5c\x59\xc1\x8d\x72\x8c\x89\xd1\xdb\x6e\x60\x3b\xae\x87\xf8\x44\x14\x95\x03\xc3\xae\xf4\xdf\x2a\x98\x89\x44\xa6\x7b\x0d\xb6\xef\x82\x71\x1c\xcf\x0f\x66\x43\x91\x0c\x4e\x5c\x50\x28\x5a\x17\x14\x28\xfa\x89\x16\x44\xdc\x35\x0e\xe5\x7e\x10\xf6\x26\x6c\xd0\x9d\x86\xd0\x0a\xba\xe7\xff\x10\x7d\x9e\xfe\x91\x19\xf4\xb4\x1a\xe4\x8c\x25\x90\xa0\x2c\x79\x22\x9c\x6a\x40\x1d\x97\x5a\xe5\x63\xd1\x68\x51\xda\x23\x06\xe5\x66\xea\x40\x57\xba\x5b\x05\x21\x1f\xc6\x75\xf7\xf1\x95\x7f\x62\x62\x7d\xc8\xf4\x5b\xeb\xc7\x88\x97\x4f\x5b\x13\x06\x79\x44\xf5\xa9\x01\x23\x99\xd1\xfb\x7d\xd6\x7d\xa3\x0f\xa8\x78\x1f\x3e\xe7\x28\xb2\x7c\x86\x05\x36\xc7\xe9\xef\x90\xe9\x6c\xdb\xec\xa6\x44\x33\xc6\x0a\x9a\x9c\x98\xfd\x0e\xb4\x4e\xd9\xa9\x34\x11\x7d\x54\xe3\xbb\x05\x3a\xee\xc2\x90\x3a\xde\x8e\x51\x08\x53\x8d\x37\x6f\xe1\x62\xfb\xb7\x1a\x37\x0c\xc0\xb4\x23\x99\xd4\x0b\x1b\x9d\x7c\xd7\x2e\xac\x1a\x5c\x07\xd6\x69\x31\xd0\x6b\xaa\x89\x00\xea\x8a\xed\x80\x04\x59\x17\x88\x13\x6d\x35\xce\xff\x6b\x09\xae\x69\x35\xd2\x83\x58\xc4\xf9\xb6\xef\x3c\x81\xc2\xf2\xf6\x16\xce\x54\xcc\x1d\x54\x24\x04\x12\x74\x3b\xb1\xfd\xe7\x0d\xbe\x1c\xf6\x2a\xda\xf2\xb2\xa3\xaa\x16\x7a\x8a\x0d\xb0\x09\xec\x42\xf2\x40\x00\xea\x2a\xb3\xde\xd5\xf0\x9a\xf3\x5c\xc8\x3a\xba\x1a\xb8\x28\xe2\xf7\x09\x70\x0d\x1e\x93\x1e\x94\x9f\x69\x2d\x38\xc5\xe3\x73\x50\x8e\xb5\xa3\x85\xd0\x84\x18\xdb\x9b\x6a\x14\xf4\x9c\xae\x9e\x6c\xeb\xf9\x88\x12\xbd\xb5\x8f\x94\x77\x2b\x8b\xc3\x52\x7c\x90\x75\x96\x5b\x62\x66\x15\x8b\xb5\x7d\x67\xac\x31\xb5\xc0\x8e\x8a\x22\x4a\xd0\x86\x01\xda\x5b\xfd\x5f\x18\xda\x9f\x54\x23\xa6\x3c\x05\x23\xe6\x03\xfc\xe0\xcd\x53\x43\xcb\x81\xbe\xb3\x73\x7d\xa5\x4b\xd3\xf6\x48\x71\xee\x89\x65\x79\x05\xfd\x66\x86\xa6\xa5\xf3\x32\x5b\x1c\xf7\xab\x3a\x22\xf8\xea\x7d\x7b\x66\xb2\x98\xc1\xc2\xda\x1f\x06\x07\x83\xe6\x4d\x66\xf0\xc9\xcc\xc7\xbf\x96\x46\x14\x2c\x5a\x09\x35\xc7\x17\xbb\x1d\xc2\x00\x15\x9b\xb9\xeb\x8a\x01\x8c\xed\x15\xc2\x56\x48\xf2\xdc\xa9\x95\x4c\xc3\xa8\x36\x7f\x9d\x85\x04\xd9\xb5\xec\x3e\x94\x9d\xdf\x8f\x98\xd8\xf2\xea\xc3\xe3\xe8\xfb\x2d\xbd\x96\x79\x06\x21\x2c\xed\x9b\x97\x56\x63\x10\x82\xb6\xb9\xd5\xe4\x68\xb2\x46\x11\x6a\xf4\xc4\xeb\xd3\xf2\xce\x84\x0b\x57\x0e\x48\xd3\xc7\x30\x84\xcb\x85\x08\xe0\x17\xf1\x01\x2f\xe5\x05\xbd\x07\xb8\x8e\x09\xe5\x8b\x18\xe1\x93\x66\xce\xb6\x4b\xd2\xac\xa3\x9b\xb8\x9f\x2a\x9c\xae\x59\x49\x56\x8f\xdd\x20\x2c\x5c\x30\x16\xd1\x62\xec\x89\xec\xdc\xf5\xb1\x3a\x84\x90\x02\xce\x69\xfb\x75\xd9\x04\xe7\x49\x8f\x74\xe9\x1d\xad\xaa\x69\x92\xdb\x89\x01\x88\x1c\xf1\x47\x8a\x65\xf8\x4b\x3e\x88\x8a\xde\x70\xc3\xb6\x9f\xef\xaf\x12\x42\x8c\x27\x64\xc7\x4b\xe0\x7c\xb5\x77\x2e\xbc\x0d\x9e\x41\xf9\x3a\xf9\x12\xda\x24\x2a\x78\xcd\x76\xb1\x3f\xf4\x67\x41\x13\xd9\x79\x60\xf6\xde\xc0\xdc\xdf\x2a\x29\x7d\xb1\xf0\x8a\x64\x43\xb5\xbb\x2f\xa8\xb0\xd4\x92\xd3\xb2\x2f\xc9\x6f\x17\x31\x40\x7d\x0a\x2e\x2e\x12\x4b\x91\xf2\x07\x6f\xd0\xf4\xc6\x61\x5c\xcb\xea\xe7\xce\x4d\xd7\x5e\xa5\xf1\x37\xa7\x06\x47\x89\xa2\x9b\x08\x2f\xd1\x42\x49\x06\x1c\xe2\x09\xda\x43\xd5\x96\x7b\xb7\x3d\x81\x96\x32\x61\xa4\x9d\x6c\x6d\x23\x32\x87\xe3\x91\x8b\x92\xb0\x05\xf4\x60\x91\xb2\xe0\x94\xe0\xf4\xf5\x52\xb1\x6a\xbd\x4c\x27\x61\xc9\x72\x55\x7e\xa7\x6a\xd2\x6f\xda\xf2\xb3\x04\x52\xab\x4a\x59\x07\x1c\xf3\xa3\x01\xfc\x37\xbc\xa4\x41\x1f\x9e\x00\x9c\x6e\xcc\x5c\x83\xe1\xe0\xb8\xb6\x9d\x58\xf6\x19\x8c\x03\xdd\x05\xf7\x98\xb5\x15\xd9\xae\xce\x42\x7e\x89\x56\x2a\x86\x78\x45\x6b\xe8\x40\xef\x8e\x5c\x07\x55\x73\x62\x6b\x3b\x26\x5b\x05\xe9\x91\x8d\x3c\x30\x8a\x5f\xa5\xd6\xad\xc3\x36\xd8\x7b\x29\xf3\xc7\xc3\xda\x97\xa2\xef\x36\x3e\x14\x76\x6f\xa1\x76\x0b\xae\x9b\xe6\xab\x73\x03\x45\xf7\x2d\xc7\x75\xd1\x8b\x72\x2b\x20\x41\xaf\x17\x4c\xeb\x82\xc9\xf9\x48\xff\x0d\x2d\x73\x5e\x4e\x04\xeb\xdc\xd0\xc1\x96\x6e\x4e\xc0\x6f\x2f\xe4\x18\xa8\x92\x66\xe1\x10\xdb\x9c\x6d\x97\x76\x39\x76\x4c\x07\x7a\x3c\x0d\x7b\x1c\xdb\xff\xe4\x2d\xf6\x8b\xc3\x24\x8e\x1e\xa2\xfb\x40\x54\x3b\x9d\xea\x96\x93\xe0\x8e\x84\xe6\x39\x77\x61\x19\x8c\xd4\x3a\xd5\x74\x37\x3e\x96\x7e\xa6\x41\xdb\xde\xcd\xcc\xa2\x46\x8f\x19\x51\x9b\xee\x24\xfd\x94\x5a\x46\xb8\xb6\x69\x77\xc0\xb6\x4b\xab\xa0\x76\xfd\x0f\x3d\xa3\x31\x8e\x29\x57\x3b\x85\x5f\xe6\xf3\x3f\x33\x86\x95\x28\xd7\xd9\xa3\x93\x70\x26\x1b\x1b\x45\x9c\xd7\x94\xdb\xf0\x8d\x2b\xf3\x27\xd2\xe3\x2e\xc2\x6f\x78\x63\x91\xcf\x29\x85\xd3\x9f\x6e\x23\x3e\xf7\xcf\x7b\x3d\xf4\x89\x1c\xc6\x14\xf7\xa3\xc9\x1e\x07\xb4\xd1\x44\xb7\x78\xcc\xa9\xef\xc4\x05\x3e\x78\xf8\x25\xa5\x4b\x28\xcd\xb9\x0e\x4e\x4b\x36\x4e\x38\x00\xc8\xb5\x65\x14\x54\xff\xe6\x7c\x32\x01\xc8\x39\x1b\x3d\xc0\xb8\x32\x1e\x41\xcf\x08\xe8\x2a\xae\xd3\xae\x81\x98\xfa\xbb\x5e\x95\x86\xda\xfb\xa0\xd0\x16\x96\x5d\x7a\x3d\xfc\x17\xc8\x21\x63\x82\xd7\xf1\xf3\x2a\x07\x86\xb6\x77\x5a\x9e\x09\x2a\x82\x07\x05\x2d\xef\x8c\xc8\x8f\x6e\x07\x68\x2d\x9d\x58\x49\xb1\x97\x63\x3a\x70\xbe\x88\x16\x95\x14\x10\xd5\x64\x42\x53\x59\x78\xfb\x5e\xe1\x7a\xfd\x0f\xf1\x01\x37\x24\x4c\x77\xd4\xf3\x53\x6f\xee\xa8\x10\x2a\x6a\x41\x0d\x0e\x2d\xe0\x92\x1b\x16\xcb\x19\xb0\x73\x3e\x63\x54\x0e\xfe\x90\xfe\x65\x54\xe2\x3f\x7e\x8f\x1e\x61\x54\x4d\x51\x89\xc9\xdc\x1e\xc1\x3c\x25\x65"
+
+# get_exponent(Hi[i], XMR_H, i * 2)
+BP_HI_PRE = b"\x42\xba\x66\x8a\x00\x7d\x0f\xcd\x6f\xea\x40\x09\xde\x8a\x64\x37\x24\x8f\x2d\x44\x52\x30\xaf\x00\x4a\x89\xfd\x04\x27\x9b\xc2\x97\xe5\x22\x4e\xf8\x71\xee\xb8\x72\x11\x51\x1d\x2a\x5c\xb8\x1e\xea\xa1\x60\xa8\xa5\x40\x8e\xab\x5d\xea\xeb\x9d\x45\x58\x78\x09\x47\x8f\xc5\x47\xc0\xc5\x2e\x90\xe0\x1e\xcd\x2c\xe4\x1b\xfc\x62\x40\x86\xf0\xec\xdc\x26\x0c\xf3\x0e\x1b\x9c\xae\x3b\x18\xed\x6b\x2c\x9f\x11\x04\x41\x45\xda\x98\xe3\x11\x1b\x40\xa1\x07\x8e\xa9\x04\x57\xb2\x8b\x01\x46\x2c\x90\xe3\xd8\x47\x94\x9e\xd8\xc1\xd3\x1d\x17\x96\x37\xec\x75\x65\xf7\x6f\xa2\x0a\xcc\x47\x1b\x16\x94\xb7\x95\xca\x44\x61\x8e\x4c\xc6\x8e\x0a\x46\xb2\x0f\x91\xe8\x67\x77\x25\x1d\xad\x91\xf0\xd5\xd4\x51\xd7\xe9\x4b\xfc\xd4\x13\x93\x4c\x1d\xa1\x73\xa9\x2d\xdc\x0d\x5e\x0e\x4c\x2c\xfb\xe5\x92\x5b\x0b\x88\x9c\x80\x22\xf3\xa7\xe4\x2f\xcf\xd4\xea\xcd\x06\x31\x63\x15\xc8\xc0\x6c\xb6\x67\x17\x6e\x8f\xd6\x75\xe1\x8a\x22\x96\x10\x0a\xd3\x42\x06\xfc\xf4\x44\x35\x7b\xe1\xe9\x87\x2f\x59\xd7\x1c\x4e\x66\xaf\xdf\x7c\x19\x6b\x6a\x59\x6b\xe2\x89\x0c\x0a\xea\x92\x8a\x9c\x69\xd2\xc4\xdf\x3b\x9c\x52\x8b\xce\x2c\x0c\x30\x6b\x62\x91\xde\xa2\x8d\xe1\xc0\x23\x32\x87\x19\xe9\xa1\xba\x1d\x84\x9c\x1b\xb4\x46\xbc\x0b\x0d\x37\x76\x25\x0d\xd6\x6d\x97\x27\xc2\x5d\x0e\xfe\xb0\xf9\x31\xfc\x53\x7a\xb2\xbd\x9f\x89\x78\x21\x6f\x6e\xb6\xe4\x23\xfa\xe0\xd3\x74\xd3\x4a\x20\x69\x4e\x39\x7a\x70\xb8\x4b\x75\xe3\xbe\x14\xb2\xcf\x53\x01\xc7\xcb\xc6\x62\x50\x96\x71\xa5\xe5\x93\x73\x6f\x61\x13\xc3\xf2\x88\xec\x00\xa1\xcc\x2f\xc7\x15\x6f\x4f\xff\xa1\x74\x8e\x9b\x2c\x2d\xdf\x2f\x43\x03\xbb\xfe\x7f\xfc\xee\x5e\x57\xb3\xb8\x42\x06\xa9\x1b\xcf\x32\xf7\x12\xc7\x5e\x5f\xa5\x10\x87\x85\xb8\xcc\x24\x47\x99\x83\x12\xca\x31\xab\x85\x00\xc8\x2c\x62\x68\x45\x39\xa2\x70\x01\xfb\x17\xf2\xa5\x64\x9d\xb2\xe2\xd6\x4b\x6b\x88\xf0\xd6\x81\x00\x9a\xe7\x8e\xae\xce\x9c\x73\x57\x80\x2c\x6c\x1c\xd8\x1e\xf6\x24\x86\x89\x85\x40\x89\xaa\xd6\x94\x47\x33\x91\xba\xd6\x18\xef\x01\xdf\xd6\x80\x98\x1a\x78\x97\x18\xe9\xd7\xca\xef\x06\x3d\xeb\x2d\x67\x5f\xe8\x43\xea\x63\x4d\xcf\x96\x77\xc1\xd3\xee\x92\x51\x39\x71\xb7\x24\xc7\x88\xe4\x10\x7a\x42\x40\xfe\x26\xe5\xfb\x36\xcc\x00\x7e\x76\x58\x96\x48\x82\xf7\x69\xf1\x8c\x78\x6a\xb1\x52\xf2\x5c\x5d\x2a\xe4\x72\xf7\x1e\x40\x13\xc4\xb0\xc5\x78\x7d\xc1\xd7\x8b\xdc\x8d\x52\x33\x10\x39\xaf\x41\x24\x11\x2e\xe9\x34\x6f\x11\x0a\x4e\x81\x18\xe8\x64\x11\x5d\x49\xb0\x82\xc8\x38\x51\xd4\xd5\xe1\x10\xa4\xab\xda\xdd\xbd\xa9\xb0\x22\x7f\x5b\x26\xbf\x52\xd5\xa2\x25\x25\x23\x59\x72\x84\x3d\xe9\x1d\x99\xd0\x09\x1f\x17\xf4\x78\x2d\x4f\xeb\x2b\x76\x0c\xd5\x8b\x6f\x24\x76\xe8\xb0\x2d\x90\x8a\x15\x15\x07\x8a\xa8\x08\xaa\x3a\x56\x5e\xfc\xb7\x16\x9f\xe0\xcb\xf7\x2c\x12\xce\x17\x50\xf2\x86\x1f\xb6\xc6\x85\x16\x13\xcb\xe9\x74\xef\xc1\x68\x4a\xeb\xbe\x8b\x8a\x52\x2a\xbb\xe7\x82\x77\xd0\xda\xa7\x89\x2d\x9d\xa8\x7c\x27\xbe\xcd\x3e\xc0\x38\x95\x23\x3a\xd4\x66\x31\x8c\x44\x3c\x4d\x6d\x5c\xf1\x2e\xba\x7d\xbd\x3e\x84\x32\x9d\xf6\x1a\xfc\x9b\x7e\x08\xfc\x13\x32\xa6\x82\x34\x42\x73\x39\x6e\xc7\xdc\xdc\xbe\xae\x48\xff\x70\xa1\x9a\x31\xd6\x62\x44\x3c\xce\x57\xf7\x7a\xfe\x05\x0b\x81\x22\x48\x60\x25\x5b\xcb\xc8\xf4\x80\xc4\x3c\xfd\xeb\xb1\xb2\xa6\x89\x72\xb7\xd3\x32\x3b\x03\x61\xf3\xa1\x14\x2f\x8b\x45\x2e\x92\x98\x77\x3d\xef\x56\x35\xc2\xe2\xef\xa3\x70\x0e\x4c\xc9\xe5\xd8\xde\x78\x96\x7e\x57\x35\x82\xcf\x7c\x74\x97\x7c\x30\xb5\x46\x9b\x2c\x0b\xac\xe8\xec\x25\x9f\x71\xba\x25\xc8\xdd\x1c\x51\xe5\xb0\x24\x1c\xca\x7c\x86\xf7\x18\xb7\xd2\xc3\xd4\x57\xa6\xe5\xe0\xb3\x9f\x1f\x39\xeb\xaf\xbb\x08\x83\xd4\x27\xd9\x36\x47\x60\x15\xad\x88\xb7\x92\xa0\x31\xe4\xdd\x98\x37\x57\xc9\x9a\xea\x39\x12\xe8\xf8\xc2\xf6\x59\xde\x4b\xc1\xa2\x20\x4c\xea\x13\x2e\x4f\x9e\xf7\x17\x77\x11\x91\x53\x63\x9a\x71\xff\x24\x17\xf5\x22\xfe\x41\xb8\x7e\x9c\x1c\xb7\x66\x9f\x40\xf9\xd6\x85\x88\x7d\xff\x81\x92\x7a\xa4\x2e\xda\x7f\x2a\x69\x67\x89\x09\x10\x33\xcf\x5b\xe2\xfc\x1f\x5f\x3a\x2d\xe2\x27\x15\xeb\x33\xd6\x28\x28\x92\x2d\xac\x86\x2e\xfc\x7f\xc6\xd5\x4c\x99\xe6\xec\x6e\x58\xc0\xb6\x4d\xa9\x57\xe7\x36\xd3\x00\x93\xc8\x67\xa1\x20\xd5\xdb\xfc\x55\x03\xca\x27\x64\x05\xdf\x4b\x2d\xbe\x6c\xfe\x7c\x2c\x56\xbc\xd2\x66\x9f\x1b\x7d\x82\xc9\xf9\x29\x91\xbf\x41\x02\xaf\x61\x10\xbf\x1b\xf5\xbd\xae\x89\x7f\x9a\x06\x42\x09\xcf\x31\x29\x96\x53\x13\x7e\x86\x5f\x90\x5c\x89\x29\x44\x91\x39\x54\x5a\xc8\x25\x3c\x32\xbe\x19\xcc\x8b\xd8\x54\xca\x7c\xdb\x07\xc2\xae\xba\x12\xa1\x4c\xcf\xa3\x08\x5f\x9f\xfd\x9f\x75\x39\x80\xc9\xd4\x5b\x7b\x4e\x0f\x5b\xe4\x6d\xf3\xae\x5c\x10\xc1\x89\xf1\xdc\x9e\xd2\x59\x2e\x24\x6b\xd2\x44\x9a\xa0\xda\xae\x45\x8a\xe8\xbf\xbd\x52\xf9\x83\xc3\xde\x44\x12\x37\x26\x71\x9c\x08\xd4\xc3\x7c\x8c\x9b\x0b\xe1\x7b\x6b\x49\x82\x61\x36\xaa\x7b\x90\x85\x31\xbc\x91\x73\x2b\x08\x7a\x41\x36\x03\x0b\xad\x7b\x5b\x1c\xfa\x7d\x9c\x98\xa9\xdc\x34\x7a\x92\x65\x1f\x29\xc2\xe1\x10\xaf\xf8\x89\x7f\x26\x7c\x04\x22\x10\xa6\xb7\x0a\x31\x3c\xc0\x6a\xfa\x2b\xd9\xc2\x91\x15\x37\xd6\x09\xd6\x8b\xec\x94\x32\xe8\x4b\x96\x79\x52\x7d\x6a\xbb\x58\x8b\xa7\x2b\xb2\x14\x98\x70\x69\xd8\x0b\x0a\xbc\x2b\xbd\x68\xeb\xa0\x33\x1e\x3a\xe5\xf4\x10\x6f\x7f\xc1\xe2\xe7\xb8\xd6\xe5\x37\x0e\x32\x01\xcc\xe2\xa0\x36\xb6\x8e\xd3\x54\x31\x63\x39\xf0\x92\xde\xc7\x66\x2b\xce\xbd\xd2\x06\x61\x11\xd1\x6c\xe5\x5a\x93\x7e\x2c\x61\x90\x7b\xc3\x66\xc8\x85\xda\xa3\x74\x95\xbe\x67\x1e\xf6\xc2\xf2\xe5\x54\xed\xe3\xb5\x3c\xe2\x80\xcb\xe8\x8a\x48\xb9\xd9\x74\x0e\x98\x0c\xea\xf8\x04\xed\xcd\x8c\x96\x85\x81\x93\xe6\xd5\x17\x8b\xf6\x04\xcc\x73\xbd\x8f\xaa\xd5\x0d\x53\x15\x49\x99\x31\x97\xcc\x27\x28\x27\x21\x6d\x1a\xf9\xdc\xc6\xe9\x86\x2a\x6e\x53\xa0\xa2\xc7\x32\x98\xe1\xfa\xdc\x0f\x91\x48\xcb\xc8\x5e\xc0\x56\x7c\x38\x76\x9c\x27\x65\xd6\x54\xc4\x26\x9f\x6e\xf1\x39\x47\xf1\x3c\x23\x9c\xbb\x08\xb7\xcf\x67\xa2\x5b\xac\x03\x0a\xd1\xb8\x92\xc4\x34\x79\x24\x64\x49\xf5\x32\x8d\xac\x31\x41\xd3\xd7\xc8\xa9\xa2\x54\x0d\xca\xc2\xcb\xc9\x8e\x27\x84\x31\x43\xe7\xd4\xb9\x6d\xde\x75\x21\xfc\x70\xb3\x28\x0a\x2a\x4c\x5f\x39\x28\x7f\x5d\x24\xd7\xa7\x59\xea\x03\x7b\x11\x44\x87\x39\xee\x2a\x28\xfc\x4b\x16\x0e\xac\x40\x61\x08\xae\xe6\xb5\x80\x62\x13\x11\xfe\x03\x0b\xf0\x8b\x4f\x6e\xed\x3d\x7d\x3d\x86\x93\xd3\xac\x52\x4d\xa2\xb4\xeb\xf1\x9e\x25\x59\xdc\x50\xff\x35\xe6\x2d\xa6\x20\xdc\x0a\x02\xed\xcb\xe4\xf3\x98\xb1\xbd\x86\xea\x15\x4b\x6a\x94\x00\x57\x9e\x3f\x1c\xd5\x7f\xdc\x2f\x10\xbd\x8c\xdb\x16\x7c\x0b\x28\x3f\x90\x07\xe6\x20\xd9\xca\x28\x06\x7f\xe2\xb0\x15\xed\x65\x7c\x91\x53\xb8\x44\x3d\x77\xe8\xe2\x5f\xf3\x48\xf4\xdf\x78\xbb\xc1\xce\x20\xa7\xba\xbd\xe4\x0e\xd2\xbd\xbe\xaf\x2b\x5c\xd9\x8e\x52\x02\xba\xf7\xe3\xdc\xf1\x8b\xa1\x15\x62\x0c\x51\xae\x8b\x58\xb4\x92\x3b\x9a\x86\x94\xc9\x3d\xf6\x4b\x17\x8c\x4c\xd2\xf9\xf6\xef\xc5\x1f\x45\x8b\x0c\x5e\xe8\x60\xa4\x0a\xc8\xce\xc3\x50\x6e\xc8\x5b\x99\xdc\x71\x6b\x95\xcb\xb3\x42\xdb\x91\xad\xe4\xb6\x1e\x17\x7f\x60\xf9\xfa\xbb\xff\x2c\x9b\xad\xee\x04\xcf\xd7\x41\xd6\x6d\x2f\x26\x32\x1e\x2c\xf5\x0a\x3c\xd0\x21\xf6\x28\x88\x63\xde\x2d\xad\xf8\xd5\x2d\x1f\x8b\x9f\x51\x42\x43\x05\xa3\xd4\x07\x96\x29\x63\xc1\xd0\xbe\xeb\x81\x13\xf8\x03\x07\xec\xc2\x19\x23\x94\x7f\xe8\xcb\xaf\x5c\x2c\x05\xae\x63\x69\x85\x21\x99\xc5\x2a\x17\x97\xb9\xaf\xf2\xa9\x24\x5d\x7a\x8b\x91\x72\xd5\x72\xb4\x43\x2f\x63\x44\x1f\xf5\x1c\x4a\x4e\x27\x0e\x3b\x61\xea\xe6\xe1\x3e\xef\xe3\x5e\x85\x42\x7b\xc7\x58\xef\x4a\xf4\xc0\x0f\x9c\x77\x52\x1c\x03\x61\xd2\x99\x43\x1f\x9d\x8e\x29\x8c\x13\x41\x4c\x46\x17\x0a\x1d\x82\xa1\x38\x0f\xba\xfe\x53\x1c\xa7\x01\x84\xab\x89\x65\xc4\xc8\x07\x06\x0e\x80\x39\xfe\xc4\x61\x5e\x59\x09\xd2\x7a\xc5\xca\x80\x41\xe3\xf9\x5b\x27\xf1\xc3\xd4\xd4\x06\xa2\x04\x8b\x1e\x6c\xe1\xe6\x37\xcb\x87\xc0\xf9\x7d\x36\x17\xd4\x6a\xef\xfd\xd1\xe8\x13\xc2\x55\xfb\x8b\x3e\xf9\x39\xa2\xc5\xfa\xd4\xd1\x09\x73\xc0\x8c\x05\x5f\x79\x13\xc5\x16\x64\x58\x9d\xa5\x14\x5a\x9c\x59\x72\xf4\xb2\x12\xeb\xf5\x11\x71\xd9\x23\x43\x83\x3a\x08\x95\x3c\xd8\x0c\xd0\xd9\x08\x90\x4c\x56\x3e\xdc\x34\x29\x42\x21\x86\x56\x33\xd8\xcf\x6f\xf5\x04\x44\xb9\xd2\x9b\xeb\x05\xa4\x7b\x8b\xb1\x21\xcb\x11\x8d\x6c\xb1\x6b\x24\xc4\x45\x09\x8a\xa9\x0e\x6d\x5a\x10\xea\xe0\xa0\xf3\x97\x7a\x28\x08\xf7\x9c\xaf\xe8\xf8\x70\x52\x97\xbd\x91\xeb\xbf\x27\x92\xa1\x89\x2c\xb0\x09\xdb\x0b\x7a\xc3\x51\xd0\x35\x3f\x43\xfe\x3a\xa9\x71\x92\xe8\xb9\xd7\xfe\xf5\xba\xec\x41\x5b\x0c\xa4\x8c\x92\x0e\x7c\xdd\x78\xf9\x24\x6a\xd2\x54\xe8\x7e\xe1\xb0\x65\x84\xb8\x60\xb0\xb8\x80\x0a\xae\xe1\x78\x96\xf0\x29\x0c\xb7\x89\xb0\xd7\x9e\xcd\x7d\x04\xcd\xed\xa7\x10\xef\xa5\x5e\x76\xe4\x73\x14\x85\xba\x1f\xf8\x6a\x31\xfa\xad\xfa\xf5\x62\x8f\xbf\x17\x11\x34\x6d\x57\x32\x54\xba\x0f\x56\xb5\x38\xf7\x5b\x6f\xc4\x32\x17\x45\xed\x42\xa3\x19\xa8\xef\xb8\x68\xa6\xf3\x5a\x64\xa6\xc7\x44\xa6\x96\xc9\x1a\x61\x12\x5f\xad\x25\x92\x02\x02\x56\xfa\x8a\x61\x41\xa7\x4b\x9d\x49\x62\xde\xec\x53\xab\x75\x14\xca\x3a\xce\xde\xd5\x24\x99\x7a\x9a\xeb\x7c\x43\x0f\xbf\x01\xd9\x36\xfd\xc7\x6f\x55\xee\xcb\x74\x96\x32\x48\xdf\xb4\xa1\x5f\x71\xf7\xa9\x30\x35\x14\x3c\x8f\x42\xe3\x1a\xed\x71\xbd\xec\xd9\x53\x54\x24\xe7\x7d\x31\x48\x49\x1f\xf7\xe4\x63\xf6\x38\xa6\x2d\xad\x56\x95\xef\x93\x4e\x96\xfd\x5e\x21\x30\x4a\xce\x05\xc4\x2a\xf9\x86\x60\x77\x81\xab\x57\x66\xc1\x83\x83\x07\x1f\xe3\x5f\x16\xa5\x89\xd4\xe4\x81\xa9\x32\x21\x7c\x3b\xc5\x62\x8d\x67\x29\x3f\x50\xbf\xfe\x46\x98\x8e\xe1\x0c\x3e\x30\x58\x6c\x77\x45\x1b\xa7\x58\x08\x46\x4d\xc1\xac\x64\x50\x2e\x8f\xde\x87\xb5\xd3\xc8\x97\x7e\xe9\x0d\x63\x7d\x4b\x2d\xb9\x2a\xce\xd2\x0c\xdc\xaf\x5d\xa0\xad\x15\x4a\xdc\x68\x2d\xd7\x2f\x0f\x33\xbc\xf6\xbd\xcc\xe6\xfe\x17\x8c\x82\xca\x18\x3d\xce\x8c\x82\x37\xf9\xf5\x8f\x84\xb4\xb1\x08\xf7\x97\x85\x71\x08\x31\x17\x66\x84\x02\x97\xca\xcb\x0d\x7d\x2a\x28\xb4\x47\x55\x5f\x39\xa1\xda\x0d\xda\x87\x5e\x9d\x06\xe0\xa4\x8e\x2b\x63\x7a\x8f\xd3\x6f\x34\xe5\x6e\x30\x28\xde\xeb\x36\x36\xc1\x4f\x53\x87\xcc\x83\x2b\xee\xe7\xad\xd4\x1d\x8a\xbd\x1f\x6a\x50\x3d\xbf\x2e\xf9\xca\x55\xe5\xdb\x15\x9a\x02\x4f\xb4\x6f\xa8\x9c\x00\x32\x23\x30\xa0\xee\xc9\xd8\x67\x3a\x8c\xf5\x60\x0d\xb4\x77\xac\xae\xd4\xaf\xa2\x41\x13\x42\x9e\x21\x24\xda\x81\x07\xda\xe9\x46\x7c\x5f\x97\x6e\xb0\x1d\x36\xad\x57\x5e\xe4\x48\x6e\xbd\x2c\xa6\xb0\xd9\x34\xe1\xf4\xcd\x6e\xcc\x0f\x0f\x10\xa5\x44\x43\x88\x03\x15\xce\xb9\x7f\xcd\x3f\xa2\x09\x3d\xbe\xab\xef\xe5\xd4\x9b\xe6\x7e\xca\x9f\x4a\xf0\x2f\x64\xa8\x13\xc3\xa3\x75\xbe\x11\x50\xa2\x8c\x45\x27\xff\x67\x5d\xc0\x76\xf4\x4c\xe1\x9c\x6f\xa2\x81\x06\xb7\x97\xc9\xc0\xcb\x32\xc5\x90\xf7\x6c\xaa\xa9\x51\xa6\x70\x9d\xc3\x75\x26\xcc\xc3\x06\x16\x53\x94\x3d\x4b\x4f\xc6\x1a\xc6\x02\x2a\x63\xde\x69\x5a\xd2\x3d\x95\x26\x62\x9a\x6b\xf6\xff\x33\x82\xd4\xbe\x95\x49\x11\x27\x53\x1b\xb6\x9e\xa3\x69\x9c\xac\x5f\x21\xfe\x65\x2e\x10\x8e\xcc\xd0\x5d\x9a\x10\xc0\x42\x3f\x72\x2f\x97\x1c\x84\x1d\x0f\xe0\x2a\x82\xce\x3f\x65\xca\x23\xd0\xde\xde\x39\x26\x4b\xf2\x7f\x9c\x70\x52\x66\x36\xfa\x49\x3e\xbc\xe0\x6e\xd2\x90\x5b\xeb\x2d\x66\x32\xe8\x09\x7c\xc1\xc4\x6a\x48\x79\x33\xf4\x63\x30\xa0\x48\x58\x20\x21\xb0\xab\x0c\x6d\xf2\x4c\x54\x7b\x99\xe9\x8a\x19\xfe\xcd\x8d\x18\x67\xd9\x99\xf4\xf5\x9d\x7b\xca\x19\x13\xb2\xcf\x74\x6d\xd3\xbe\xa6\xec\x65\xb1\xed\xc2\x0c\xde\x46\xad\x02\xcf\x55\x59\x97\xf0\xb1\x88\xc0\xeb\x02\xab\x4c\x8a\xff\x11\xc2\x02\xc1\x9f\x16\x07\xcf\x2e\x8d\x64\xa7\xbf\x6f\x21\xc2\x4f\x1e\xb1\x25\xbc\xc6\x26\x4a\x35\xb7\x81\x72\xe4\xf9\xd3\x4b\x12\x0a\x36\xf2\x54\x9f\x6b\xac\xa4\x56\x85\x20\xb5\x7a\xe0\xd0\xb1\xce\x5d\x3b\x2b\x79\x32\xb8\x11\x0f\xae\xcd\xab\xb3\x64\x55\xeb\x5a\xd3\xec\x87\x5d\x16\x34\xf6\xc5\xb6\xb9\xa5\xb0\xd3\xa0\x32\x6a\x79\xc0\x34\x08\xb8\x18\xe7\x30\xe1\x9c\x22\xe9\xfb\xda\x0a\xd0\xee\xb7\x85\x19\x8e\x18\xad\x6e\x98\xe4\x44\xbd\xd5\xde\xda\x8c\xe9\x0b\xa9\xe8\xfd\x27\x6f\x4e\x13\x5a\xe5\xb2\x75\x1e\xc4\x0b\xf3\x5d\xb6\xc3\xd2\x75\x33\x8b\xb9\x59\xa0\x75\x0d\xe7\x00\xb0\x74\xab\xc4\x1b\x12\x6c\x69\x0c\xae\xed\x54\xa2\x96\xd9\x01\x22\xdc\xf4\x8a\x7a\x81\xe1\x3f\x40\x1b\x7a\x58\x52\x68\x58\x68\x39\xf7\xca\x16\x6a\x46\xf2\x98\x9b\x72\x25\x8d\xc8\x28\xc2\x3d\x40\xaf\x74\x9b\x4d\xb9\x45\xd3\xfa\xe8\x83\x3c\xcf\x22\xe5\xf4\xc3\xa2\xe9\xab\xf2\x27\xdc\x00\x4c\x1f\xc5\xe4\x96\x97\x65\x57\xed\xd7\x09\xd4\x1a\x7e\xd6\xaf\x6b\xe4\x8d\x7e\xde\x5e\x8e\x6d\xe3\x1d\x2f\xe8\x12\xd7\x77\xeb\x0d\x59\xa6\xe9\x15\x52\x20\xb2\x0d\xd8\xd4\x3a\xa3\xaf\x50\x99\x49\x3b\x25\xfb\xa1\x33\x7d\x4e\x79\xec\x2c\x10\xb4\x8f\xad\xbb\x4c\x93\xb9\x32\xe8\x06\xcb\x49\xd9\xfa\x17\xb1\xd8\x63\x17\xc0\x98\x03\x2e\x16\x46\x63\xc4\x66\x9a\xcb\xee\xb4\x38\x45\xdd\x2d\xed\x34\x1a\x5f\x05\x6b\x8a\x2b\x71\x02\xfe\xc1\x8c\xaf\x25\x04\xbc\x37\xc0\xd4\xc9\xb3\x8f\x1b\xf3\x62\x38\x1e\x13\xa5\xa3\x93\x9a\xe3\x06\xe8\x21\x62\x0d\x07\xa9\x00\x88\x01\xe9\x69\xad\x40\x86\xc1\x11\xa6\xa1\xfb\xd2\x29\xb8\xfd\x06\x09\xcf\xd0\x8a\x01\x9c\x0f\x46\x38\x2a\x7a\x91\x89\xe2\x96\x8f\x00\x12\xb7\x0f\xc7\x73\x45\x1c\xed\x06\x5c\x0f\x80\x48\x17\x1c\x0f\x5d\xfc\x96\x5b\x22\x60\x53\x0f\xb0\x50\x47\x5b\x00\xea\x42\xcf\x37\xf0\xd4\x28\xe0\x1e\x76\x1f\x17\x29\x29\x4f\x07\x0e\xaa\x8e\x15\x5e\x2e\xa0\xd9\x6c\x96\x54\x81\x8a\x6e\x5f\x15\x23\xac\x3b\xd8\x7b\xf2\x56\x34\x86\x1d\x6a\x9e\x28\x6a\x3f\xa3\xfb\x26\xaf\xb4\xe5\x54\x45\x85\x31\x9c\xf0\x23\x08\x73\x07\x16\x91\x5e\x70\xb3\x67\xb2\xba\xa9\x50\x2c\xb7\x1d\xe0\xcb\xc4\xc4\xde\xab\x33\xc4\xb9\x81\xd8\xf8\x0c\x95\x5d\x9e\x0d\xaf\x9b\xb6\x1b\x05\xb4\xc7\xe5\xe1\xf9\xa8\x09\x36\xd3\x4b\xb8\x64\x57\x59\x26\xe7\x46\xd7\xd3\x4e\x76\x71\xe0\xb8\x24\xb1\x8e\x7a\x31\x61\x67\xee\xc9\x87\xf1\x4b\x95\x5e\x35\x7f\x99\x1c\x89\xd2\xfa\xfa\x2a\xc6\xdb\x87\x6c\x7f\x35\x7f\x06\xe4\x82\x36\x5e\xe9\x1c\x15\xa9\x23\x95\xa1\xa7\x63\xa1\x3b\xff\x1a\x36\xde\xd2\x10\xc6\x1f\xc7\xac\x8c\xf4\x44\x8d\xbb\xb4\x93\x5e\x84\x0b\xc6\xbf\x11\x3f\xd2\x43\xbd\xf8\x74\xac\x91\x64\xa3\x14\x31\xcc\x4d\xaa\xf5\xc0\x3d\xb9\xbc\x80\xd9\x1e\x4f\x2c\xaa\x8c\xec\xe5\x8d\x41\x5f\x26\xb0\x03\xbf\x5b\x74\xa1\x7d\x8b\x8e\x52\x9a\x64\xe4\x14\xd0\xbd\x88\x49\xa6\x2a\x88\x45\xe7\x51\xcc\x3c\xba\xa4\x21\xc4\x67\x90\x0e\x52\x99\x0c\x35\x86\xda\x11\xab\x33\x77\x2d\x9e\x13\x44\x6c\x71\x4c\x66\x78\x00\x4d\x72\x5e\x02\x87\xda\xad\x19\x6d\xa1\x23\xab\x11\x38\x60\xce\x49\xf5\x0b\x12\xe6\xdf\xaf\xa0\xc0\xa8\xce\x79\x9c\x8c\x40\xd1\xe6\x8d\xef\x09\xec\x1d\x62\x63\xe5\xe8\x52\x08\x77\x28\xd5\x50\x8e\xdb\xbe\x17\xa3\x76\x87\x13\xa4\xb7\x5e\x49\xd6\x99\xd8\x30\xc7\x59\xcc\x83\xd5\xa8\x95\xe9\x6e\x42\xdd\x26\x8f\x0b\x7c\x14\x73\x3d\xba\x87\xb1\xa9\x47\xe3\xc0\x0e\x7e\x22\x8b\x63\x07\x06\xd4\x62\x93\x88\x9e\x9f\x53\x9b\x58\x93\x3b\xbd\xea\x70\x8c\x30\x10\xc1\x8a\x01\xb4\x5e\xb0\x3e\xa5\x06\x37\xb7\xb3\x3a\x07\x9f\x3c\x73\x06\x09\xab\xa4\x20\x12\xcd\xab\x7f\x79\x11\x17\xf6\xdb\xe4\x3d\x1a\x6f\x06\x1e\xee\x27\x0f\x3e\x4e\xc2\xfb\xfd\x7c\xab\x58\x45\xff\xdc\xa3\xd4\x5c\x5d\x3d\x99\xda\x7d\x74\xe9\xb5\x55\xa6\x59\x7c\x43\x72\xe1\x79\x61\x6c\x36\xdf\xe9\xcc\x48\x7a\x97\xa7\x9b\x6f\xf0\x2a\xa8\x12\xa6\xad\x55\x37\xba\x99\x15\x35\x03\x25\x4d\x00\xe2\x33\x53\xc6\x0a\xf1\xe1\xc4\x8a\xac\x02\x38\x17\x6d\xff\x63\x73\x04\x73\x35\xaa\xd4\x16\xae\x20\x50\x59\xef\xf6\x67\x14\x8d\xe2\x9d\x2a\x6d\x24\x42\xd7\x76\x1c\x91\x9b\x3d\x11\x07\x30\x4a\xa6\x54\x0a\xf0\x55\x20\x70\xd8\x3e\xa5\xe3\x49\xc3\x6e\x80\x2c\xbc\x05\x35\xdb\x5c\x55\xa0\x3c\x10\xe3\xd5\xc5\x61\xa4\xb7\x1f\xcb\x25\x03\x0f\x04\xfd\x00\x5d\x31\xfb\xe2\xc4\x89\x96\xc6\x28\x5b\x74\x3d\x8d\x62\xb1\x3b\x7f\x58\xb9\x2f\xf3\x9e\x58\xe7\x04\x4a\x8d\x39\xa0\x69\xb4\x3a\xbe\xe2\x59\x2e\x28\x71\x81\xb0\x17\xeb\x2b\x91\xa8\xb1\x48\xbd\x71\x3b\xd6\xe1\xc5\x2b\x2f\x08\x8e\x38\xec\xe1\xdc\x9c\x0f\x92\xe9\xa5\x39\x65\xa0\x8f\x19\x5e\xb5\x1a\x74\x36\xc1\x2a\x68\xd6\xc6\xee\xec\xe2\x6f\xc8\x32\xc8\x51\x81\x51\x39\x78\xfc\x4d\xa4\x63\x61\x8b\xda\x41\x27\x37\x03\xb2\xee\x10\x7c\x3c\xf0\x24\x55\xcc\x32\xc4\x4b\xc1\x8a\x7a\x1b\xb4\xf6\xf3\xff\x6f\x7d\x03\x89\x6f\x6c\xea\x1a\x8f\x5b\x1b\xf7\x49\x68\x69\x44\x7b\xaf\x5f\x8f\x34\xb8\x75\x30\x0a\x6f\xc7\xde\xb9\xf4\x67\x29\xc8\x36\xaa\xb0\xfc\x82\xb3\x24\x1b\xde\x63\x4b\x3b\x95\x78\xf8\x0f\x08\x4b\x8f\x17\xcb\x85\xee\x39\x2a\x79\xec\x92\x8a\x6a\x57\xd1\xba\x9a\x64\x8a\x00\x9f\x21\xbb\x21\xc1\xf3\x59\x68\xe9\x70\x46\x13\xa0\x9c\x53\x5a\x27\xb6\x7b\xb6\xd8\xdd\x3b\xff\xe2\xdb\x01\x10\xce\x90\x8f\x24\x70\x0b\x31\xc0\xdb\x03\x1c\x50\x98\x43\x41\xc3\xe2\x51\xf3\xea\x2d\x5b\x81\x35\xd1\x81\xd2\xb6\xd2\x62\x60\x93\x5d\x70\xe3\xcc\xf6\xa2\x2b\x54\x0f\x5c\xac\x73\x05\x91\xe0\x8c\x72\xa2\x05\x69\x8c\x9b\x2d\x3c\xf9\xa0\x4d\xf9\x94\x1c\xb4\x85\x45\x4e\xac\x12\x14\x24\xd7\xfe\x86\x56\x82\xd3\x82\xaa\x31\x1b\x38\x56\xdc\x28\x9f\x91\xf4\x5e\xef\xe0\xb8\x19\xd5\xdb\x86\xe8\xac\x5e\x8b\x54\x7e\xa2\xbe\xbf\xb8\xac\xf1\xbc\xa2\x92\xac\x1c\x84\xb6\xa3\xa3\x28\x30\xc8\x4d\xd5\x45\x14\x75\x74\xf6\x97\x8c\x04\x62\xc2\xcf\x43\x90\xe8\xe5\x94\xf1\xcf\x0e\x3c\x58\x28\xfa\xfd\xdb\x4b\x8a\x36\x82\x3d\x1c\x38\x9c\x30\x4f\x9c\x20\x5f\xda\x6a\x7e\x88\x44\x7e\xd4\xe0\xdb\xfc\xc6\x81\xb5\x86\x0b\xe0\xed\x2e\x90\x99\xc3\x0b\xfc\x57\x9f\x1a\xfb\x04\x94\xa0\x1e\xc1\x98\x88\x3f\xc2\x1c\xea\xcd\x14\x8d\x5b\x21\x1a\x87\x2f\xb3\x63\xb9\x9a\x4b\xf0\x86\x43\x02\x9d\x4c\x2f\x2c\xa1\x53\xe5\x6e\x91\xe8\xb3\x72\x75\x54\xcf\xb9\x1b\xba\xcb\xb1\xc8\x5b\x18\x18\x38\x36\x13\x58\x47\x32\x6b\xe6\x96\x29\x56\xd3\x5e\x4b\x47\xcf\x15\xd6\x26\x26\xf7\x4e\x89\x78\xea\x80\xe0\x40\x3e\xf5\x3a\xd1\xac\x48\x64\x2e\x3f\x25\x6d\x82\x66\x8c\x47\xd5\x05\x17\x5d\xdb\x7f\xdd\x83\x9a\x91\x41\xa3\xf0\xd9\x1a\x71\x51\x7a\x36\x39\x6e\x9e\x3c\x20\x55\x99\x58\x44\xdf\xed\x40\x96\xc6\xec\xd9\xfc\xef\xe1\x40\x57\x83\xe8\xa6\xd8\xc1\x86\xc9\xd4\xa6\xc9\x2d\xf7\xd2\xc6\x08\x16\xf7\xba\xbc\x34\x5c\x2e\x79\x1a\x88\x11\x39\xf4\xd7\x4c\x5d\x43\x0f\xa2\x4b\x1f\xbe\x95\xb8\x23\x7b\xce\x02\x61\x5a\x19\x27\x98\x83\x75\x3d\x41\x6d\xa9\xdc\xd5\x1a\xc6\x1b\xef\x11\x29\xf5\xc8\xd0\x6f\xba\x4a\xfa\x2a\x4b\xc7\x5a\x72\xfa\x34\xfc\x7c\xcb\x1e\x91\x09\xb0\x2b\x1d\x00\xd6\x18\x78\xc7\x14\xb0\xde\x7e\x1a\x1c\xba\xac\x36\xe6\x6c\xb5\x9f\xb3\x0e\x38\x5c\xee\x9c\xb9\xd4\x88\x2b\x40\xad\xaf\xee\xc9\xb4\x61\x87\x51\xbd\x2f\xd2\xdb\x07\xb6\x4d\x42\xcf\xd8\xf6\x73\xdc\x25\x75\x12\xfd\xd8\xf8\x62\x7b\x6a\x67\xa6\x81\x5c\xf9\x41\x31\xa2\xf0\x44\x96\xd6\xae\xd9\xed\xfd\xc9\xc3\xde\x43\x59\x46\x91\xaf\x23\x42\xd8\xb0\xc3\x7e\xb5\x26\x76\x42\x1a\xe9\xfc\xf6\x84\x57\x8b\x3f\xd7\xfb\x1b\x3e\xe0\x1a\xd5\xb2\x71\xdd\x6b\x72\xe0\x92\xf7\xfb\x99\xe7\x5c\xb8\x2d\x59\x94\x7a\x65\x70\x5a\x1a\x5e\x64\x0c\xe1\xd7\x82\x45\x0d\x2e\xa4\x9d\x4d\x80\xf1\xbb\x9e\xb0\x1c\x8c\xcc\xf2\x66\x50\x8b\xfe\x01\x24\x67\x59\x69\x1b\x03\x22\x34\x2e\xbb\x5d\x28\x66\xd5\x2d\x09\x89\xb5\x4f\xcf\xef\xc1\x07\xda\x24\x48\x5f\x83\x75\x1d\x2b\x72\x5d\x82\xde\x16\xa6\x2a\x04\x08\x30\xee\x01\x2f\x6c\x20\x88\xd9\x88\x24\xf7\xaa\xa7\x1e\xda\xd0\xa9\xe6\x8a\x33\x3b\x86\xd0\x10\x54\xba\x9c\x79\xda\xa7\x8c\x73\x33\xab\xb5\xc5\x6c\xf7\x7a\x13\x9b\x5e\x85\x6a\xa2\xe9\xfd\xf7\x8e\x5d\x35\x5e\x6e\x82\xab\xb1\x08\xbd\x4c\x2a\xd7\x32\xca\xaf\x6e\xfa\xe2\x39\x85\xb2\x4d\x6a\x4a\x02\x16\x63\x3f\x5e\x97\x0f\x16\x57\x3c\x15\xfa\x2f\xad\x51\x1e\x09\x1e\x4d\x5f\x8e\xf7\x14\xec\x22\xbb\xe6\x82\x9e\xf9\xf6\x04\xb1\x2d\x03\x2c\xcc\x1f\x5a\x3a\x83\x25\x5e\x77\x8a\x8d\x90\x02\x68\x66\x66\xf6\xde\x05\x1c\xd1\x20\x7e\x7a\xf6\xe8\x1c\xa9\x96\x39\xcd\x64\x82\xa1\x3a\xc4\x89\xd1\xa2\x5a\xca\xd6\x18\xc5\x70\x87\xab\x02\xf4\x81\xca\x50\x12\x6c\x92\x25\xdd\x0c\x37\xc2\x93\xae\x3b\x50\x15\x5c\x72\xfb\xaf\xe5\xa5\xc2\xd0\x45\xc8\x80\x0a\xaf\x0e\xa9\x4b\x89\x85\xf3\x1c\x25\x53\x8f\x73\x0d\x33\x91\xa2\x36\xa7\xd9\x08\xbf\x00\xd7\xdd\x97\x3a\x8b\x64\xc4\x34\x09\x4e\x04\x5d\xf9\xf9\x63\x50\xef\x5f\x86\x84\x3a\xfd\x10\x98\xbb\x6c\x8d\x7b\x59\x37\x53\x79\x0c\x30\x19\xd2\x7d\x3b\x9f\xe8\xe8\x6d\x1f\x95\x8d\x4b\xdf\x15\x14\x87\xa5\xbe\x3d\xfd\x74\xf5\xa3\x98\x0c\x76\x48\x94\x26\x90\x2a\x2d\xdf\xb4\x21\x66\x9c\xe2\x7b\xa3\xdb\xfd\xe4\xa4\x91\x5c\xd0\xd1\x43\x01\x55\x16\x4d\x37\xea\x70\xb1\xf4\x30\x27\x59\xda\x61\x1d\x6d\x62\x32\x83\x5d\x20\xd5\x0e\x7e\xa4\x42\x43\x5e\xea\x8c\x76\xd6\xa2\xee\x93\x4b\x7b\x86\xb4\x61\x1b\x72\x52\x71\xde\xb8\x88\x05\x00\x2c\xad\xde\xf5\xe6\x8c\xaa\x08\xa3\x02\xab\xe3\x6a\xf1\xa2\x64\x5a\x24\x08\xe2\xf3\x89\x9e\xcd\x19\x79\x1f\x54\xa0\x91\xef\x99\xe4\xf8\xfd\x1e\x7b\x37\x26\xfe\x56\xb6\xc4\xf4\x9f\x5a\x0d\x42\x6d\x8e\x81\xc0\xc8\xb8\x44\x85\x37\xbc\xe5\xa1\x9c\xff\xe5\x41\x4c\xc1\x9e\xcf\x15\x7d\x66\xbb\x39\xaf\x6c\x3a\xe1\xec\x25\x4a\x5d\x72\x65\x5c\xba\xa4\x26\x81\xab\x44\x7f\x06\x7e\x2e\x6d\x68\xf9\xb7\xaf\x80\x21\x18\xf5\x38\xd8\x50\x08\xaa\x6b\x96\x8c\x3d\x27\x87\x32\x2f\xf0\x71\x09\x89\xc6\xdb\x4d\xbc\x13\x72\x06\x91\x8c\xb5\x3b\xe1\x6d\xea\x6c\x86\x35\x41\x2e\xb0\xd7\x84\x20\xdd\x43\x2f\x83\xc2\xf2\x4d\x67\x2d\x85\x98\xf0\xab\x88\x87\x65\xee\x41\x33\xcd\xaa\x5c\x9f\x58\x4a\xb1\xd2\x43\xf4\x54\x5f\x37\x58\xbc\x49\x94\x41\x28\x61\xac\x9d\xde\x12\x46\x1c\xd4\x4c\xae\x83\xd2\x19\xfb\xb4\x70\xaf\x4a\x76\xa6\x74\xfe\x37\x9c\xfc\x98\x64\xb1\x8e\xd1\x28\x4d\xb2\x4b\xf5\x44\x61\xfe\xc7\x43\xbf\x4f\x3e\xb5\x2a\xaa\xc0\xbc\xb8\xee\xa5\x61\x8a\x2b\xb4\x41\xcf\x65\xcb\xd5\xf5\xce\x47\x02\x4a\x3a\xd6\xc7\xd3\x07\x33\xee\x0d\xfb\x2b\x4e\x91\xce\x96\xda\xcc\x4e\xc8\x9f\xc9\x6b\xa3\x77\x01\x25\x48\xf9\x21\x5b\x0a\xaf\xc3\x0a\xc3\xc1\xb1\x70\xaf\xf9\x37\x26\x57\x13\x26\xeb\x96\x41\x58\xb1\x56\x59\x01\x55\xda\x48\x25\x4e\xc2\x6c\xe9\xb3\x3e\x65\x88\x4b\x42\x01\x98\x40\xd0\xdd\x0e\x99\xc6\x13\xb3\x4b\xa4\x9c\xb6\xec\x37\x8b\x22\x71\x81\x1b\x48\x28\x9b\xf7\xe5\x66\x28\x8e\x2a\xa9\xff\x75\xfd\x22\xfa\xca\x62\x2a\x84\x32\xd7\x65\x96\x94\x90\xca\x77\x81\xaf\xc7\x83\x77\xd0\x51\x26\x7d\x61\xb1\xfb\x50\xff\xea\xcc\xc8\xb1\xfa\xcb\xb4\x7a\x9e\xa9\x78\xd7\x3c\x16\xb7\x0c\x96\x06\x06\x50\xf7\x63\xef\xc8\xdd\xb9\xac\xfd\x5a\xcb\x9c\x57\x4c\xaa\x11\x1d\x6b\x67\xb0\x9e\x64\xf5\xe5\xaa\xa5\xda\xb2\x2d\x25\x69\x2a\x34\x27\xa7\x8c\x40\x7c\xd0\x50\xee\x8d\x16\xf3\xdf\x31\x8f\x23\xfb\x62\x90\x42\xfb\x99\x8a\x88\xa9\x9f\xe6\x1b\x13\x36\x54\x40\x76\x25\x01\xb3\x08\x76\x9b\x83\x45\x1b\xf3\x5c\x56\x4b\x23\x50\xdf\x06\xc6\x92\xf6\x1d\x52\x03\x05\xc1\x64\xbd\x5a\x03\x02\xce\x9f\x7c\x4e\x3f\x8f\xb1\x2f\x76\x9d\x9a\xfb\x57\xeb\x23\xf4\x98\xff\x74\x4e\x41\xe9\x11\x14\x42\xb4\x2d\x6a\x86\x86\xee\x2b\x73\x36\x67\x0d\x33\xfc\xa0\xee\xb1\xd1\xe8\xa7\x45\x8b\xe5\x84\xab\xd5\x2b\x59\xd7\xdc\xc6\x9a\xaa\x49\x79\x47\xcc\x48\x64\x8f\xae\xd2\x41\xcf\x57\x3e\xe4\x7f\xc5\x2f\xef\x4a\x3c\x06\xf9\x4d\xe1\xaa\xd1\x45\xb4\x58\xbc\xd8\x74\x48\xc3\x74\x16\xeb\x1c\x3c\xa8\x98\xeb\xa5\xb8\x7f\x6b\x4c\xd7\x83\xd6\x07\xd0\x23\xb7\x50\x12\x38\xbd\x4f\x40\xf5\xba\x8d\x21\xd3\xe7\x1a\xa7\x5d\x49\xf9\xfa\xa9\xfc\xec\xe7\x5d\x1e\xb5\xa7\x64\x13\x95\xc1\x3c\xda\x18\x98\x07\xb0\x73\xf3\xb3\x8f\xb0\x6e\x54\xb3\xbf\x5b\x3d\x72\x60\xff\xcb\xca\xdc\xcf\x4f\xf9\x93\x6a\xbc\x79\xc3\x2f\x01\xb5\x77\xc3\x76\x6e\x12\x1b\x68\x0b\x3b\xde\x82\xbd\x70\x3a\x6a\x95\xf9\x7b\xba\x36\x9b\xbb\xb6\xfd\x82\x56\xbb\x01\x75\x83\x18\x0a\x81\xf8\x87\x80\x74\x12\x64\x1c\x7a\x3a\x5e\xfb\x1a\x92\xf8\x77\xcf\xab\x21\xba\x2d\x34\xf1\xe4\x7e\xa4\x72\xc6\xc9\x05\x89\x4a\xd0\xbc\x58\xd4\xfe\xfd\xb1\xdc\x71\xe5\x05\x00\x43\xb3\x5a\x36\x69\xe8\xdd\x13\xdc\x70\x32\x29\x10\x80\x20\x14\x0b\x01\xed\x2a\x72\x0e\x27\x1b\xd3\x0a\xe7\xf5\x33\xda\xad\x1a\x02\x25\x9b\xae\x7e\x71\x08\x95\x2d\xad\x85\x4b\xcc\x90\x6a\xb5\x60\x04\x8d\xae\xc7\x58\x4c\x0e\x4f\x69\xe4\x97\x9c\x1d\x65\xd5\x80\x5e\x4d\xc7\x60\x66\xdb\x0c\xe0\xbd\xfb\x19\x50\x21\x82\xcf\xed\x5b\x0f\x05\x60\xa6\x0f\x10\xd0\xf1\x43\x92\xe5\x5f\x63\x77\x46\x75\x27\x23\xe2\x4a\x7e\x40\x35\x0c\x25\x38\xdc\xd9\xe4\x92\xbe\x99\x71\x2c\x63\x0b\xb5\xf4\x98\xeb\x71\x95\x7e\x01\xb6\x2b\x5c\x37\xf1\x1d\x2d\x07\x00\x37\x38\xf4\x5b\x7f\x45\xd5\xb6\x18\xfa\x51\x73\xf1\x44\x1e\xaf\x3f\x00\x20\x8d\xe6\x0c\xae\xf7\x33\xaf\x14\x61\x1a\x89\x55\xf0\x4c\x44\xf9\x38\x74\xca\x17\x7e\x28\x86\xd3\x95\x13\xbd\x94\xd8\xff\x6f\x02\x35\xe5\x52\xa1\x89\x16\x30\xd6\xee\x22\x79\xf1\xe4\x6a\xe5\x46\x61\x7a\x29\xb8\x2e\x9f\xa7\xf0\x93\xf8\x45\x36\x02\x69\x1d\xb6\xd2\x3f\x70\xcc\x61\xd3\x4d\x73\x25\xb0\x15\xc1\x54\x30\xe1\x4e\xa8\x0f\x7f\x52\xf2\x37\x66\x1d\xa7\x59\x8f\x0c\x71\x18\xa5\xb9\xb4\xda\x4f\xd8\xf5\x1e\xc7\xf3\x54\xa8\x0a\x5f\x98\xe9\x82\x73\xa3\x53\x72\xbc\x63\x04\x47\xfc\xfe\x9f\x43\x63\xad\x05\x82\x4f\x64\x28\xff\x9b\xa6\x0b\x4f\xec\x62\x42\x67\xd4\xf6\x8b\xf9\x62\x37\xcd\x25\xed\x94\x0d\x10\x00\x8a\xa5\xdc\xb9\x4d\x91\x63\x68\x82\x6d\x4d\x9d\xc8\x1c\x60\xa1\xf4\x1d\xd9\xda\x7a\xde\x58\xaf\xdf\x1b\x49\x35\x7b\xe1\xcf\x67\x34\x02\xab\x99\x15\x99\xfc\xa1\xc0\x71\x4f\xe4\x43\xae\xbb\x5e\xf3\x29\xb5\xff\x5d\x19\xe4\x2c\x8e\x51\x24\x27\x92\x1b\xb4\x40\x32\x50\xcd\x5b\x1b\x09\x1d\xf1\x0a\xe0\xbc\x7c\x34\x44\x23\x6a\xb5\x4f\x49\x29\x89\xb4\x07\x13\x95\x4d\x59\x3a\xa2\x7a\xa0\xb1\xfb\x14\xe8\x9b\xc7\x96\xd1\x04\x5c\xf4\xf0\xfb\x83\x49\x88\x06\x86\x0e\x7f\x54\x10\x65\x95\xcb\x7a\xef\x4b\x81\x97\xc6\x36\x73\xbc\x01\xe9\xa7\xd2\xbd\xff\xcb\x07\x86\x00\xab\x5c\xd0\x00\x9e\xa1\xdc\x68\xa2\xa8\x24\x3f\x57\xaf\xdc\xf6\x65\xf6\x7d\x7a\x05\x68\xc8\xa8\x43\x53\x56\xee\x90\x46\xb6\x5b\xac\x0c\xd1\x0c\x78\xc9\xb2\x6a\x77\xba\x53\xcf\xdc\x7d\xdd\x99\xdf\xa4\x45\xdf\xca\xf3\x45\x2c\xb7\x22\xb4\xbb\x6c\x05\x03\xad\x5e\x86\x31\x93\x53\x72\xab\x96\x48\x42\x7a\x32\xea\x8f\x7a\xea\x9c\x94\xf2\xd7\x1b\xb9\x59\xcd\x63\xda\x6f\x9b\xe0\xe2\xd8\xc8\x1b\xb2\x12\x3e\xa8\x8a\x51\x08\x9e\xb4\xde\x60\xd4\xa4\xf6\xe9\xb8\x55\x48\xc0\x47\x4a\x86\xf1\x1f\xa7\xb5\xa7\x77\x18\xf2\x2b\x3b\x9e\xf7\xbb\x4d\x74\x82\x6c\x77\xaf\x84\xb2\xa5\x49\x1b\x7d\x9c\xbd\x87\xc7\x45\xe4\xb2\xf4\xd6\xe0\x54\x88\x64\x66\x8e\xaa\x76\xc0\x14\x4c\xed\x60\x19\x68\x67\xee\xaf\x93\x52\xf6\x7c\x1d\x31\xc6\x70\x9d\x9d\x16\xf2\xa4\xd4\x65\xba\x19\x48\x71\x54\x15\xdd\xe4\x73\x00\x92\xdf\x2a\x6a\x91\xd1\xa4\x82\x67\x36\x09\x19\x92\xd8\x00\x6c\x50\x4f\xae\xdb\x68\x4e\x5d\x46\xfd\xc6\x25\x0b\xc9\xac\x5c\x81\x6b\x0d\xb2\xc2\x5e\x8a\xf5\x2d\x95\x8b\x3e\xf6\x32\x83\xbf\x6c\xb4\x16\x06\xbf\xb6\x8b\x77\x01\xfc\xda\x7c\x8e\xdb\x5b\xd2\x10\x0d\x77\xe6\x59\xb2\x48\xec\x83\xd0\xb9\xdf\xd8\x8c\x43\x7c\x0d\x36\x48\x1c\x08\xc0\x8e\x92\x1f\x4f\x04\x39\xa4\x31\x69\x0c\x8f\x44\x84\x48\x1c\xc6\xf5\xea\x28\x18\x9e\x2a\x5c\x97\xe9\x9e\x90\xe6\x93\xcb\xa4\xdc\x7c\x22\x91\x45\x31\x23\x32\x3f\xc5\x0a\xfb\x92\xda\x8c\x29\x66\x5c\xac\xd2\x02\x35\x01\x1f\xa9\x49\xce\x0f\xb9\x83\xbc\x0e\x17\x36\x17\x5e\x3f\xbe\x7b\x2d\x28\xcb\x1b\x45\x79\xa4\xce\x88\x7c\xc3\x21\x3c\x48\x65\x5c\x30\xc4\x56\x95\x7a\xe2\xa6\xad\x59\x0a\x90\x6c\x0d\xc0\xe8\xa3\xde\xe5\xaf\xa0\xf8\x2a\xdf\x07\xc2\xae\x01\x04\x94\x21\xae\xdf\x59\x26\x5c\x9a\x2c\xe5\xcf\x91\x65\x86\xe0\x89\xa4\xba\x9e\xeb\xdf\xfd\x24\x36\x91\x4b\xdd\x04\x5d\x6b\x59\x53\x47\xe7\xda\x43\x81\xda\x75\x84\x9d\x61\x79\xd2\x1e\x38\x4b\x72\xa5\x53\x90\x0a\x29\x64\x52\x41\x1f\x99\x04\xd5\xfa\x8e\xd4\xf9\xf9\x99\x6f\xcc\x5c\xf7\x66\xb0\x15\x92\xd3\x07\xc3\x2f\x2c\xe7\x48\xd0\x58\x4d\xbf\xa6\x08\x3d\xf8\x60\x2a\xbe\xef\xa7\xa8\x10\x3a\x90\x21\xe2\x43\x0c\xc1\x57\xf4\x3f\x58\x2b\xc4\xb6\x9e\x3e\x3f\x30\x10\x1c\xfe\xee\x95\xa4\x12\xeb\xd7\x97\x07\x29\x23\x2b\x31\x61\xd1\x0a\xec\xfb\xd0\xa3\xaf\x3b\x63\xc6\x29\x79\x21\xb4\xbd\xf5\x5d\x60\x0e\x60\xaf\x39\xf1\x85\x63\x08\xd4\xd6\xf0\x71\xe2\x66\x56\xbd\x19\xc9\x64\x13\xa2\x78\x8c\x3f\xf2\x38\xa1\xbe\x89\x4e\x41\x29\x04\x48\xe4\x69\xc5\xdf\x5a\x0a\x2a\x63\xa9\xc1\x95\xf1\x2b\x36\x3b\x68\x45\x75\xa0\x76\x46\x66\x36\x18\xf4\xd9\xb7\xe8\x8a\x95\x12\x43\x34\x5d\x46\x14\xb5\xa4\xe1\x6f\x31\xdf\xc4\xfe\x64\x15\x80\xa8\xcd\x88\x12\x7e\xbd\x71\xdb\x65\xf5\x90\x97\x84\x32\x04\x81\xd7\xa8\x55\x14\xe9\x36\x23\x6f\x4c\xbe\xba\x74\x74\x0d\x22\x78\x93\x9b\x5a\x87\x1a\x72\xc8\x80\x19\xc2\x08\x39\x0e\x51\xce\xec\x46\xd1\x4d\x03\x88\xe0\x61\xa2\xd8\x4f\x58\x02\x8a\xad\x0a\xab\xfd\xe8\xba\xf0\x8a\xe0\x97\xfc\x75\x16\x17\xf9\xc2\x75\x35\x9c\x4a\xb8\x46\x40\xf1\x24\xf2\x5c\x0b\x44\x65\x28\x06\xfa\xef\xad\x46\x9c\xc4\xb5\x7c\x92\x93\x02\xa1\xd0\xa1\xb1\x16\x0a\x8c\x25\x9f\x12\x73\x9a\x7c\xa1\xc2\x04\xb8\xd3\x64\x8e\xf5\x0c\x5d\x98\x9f\x1c\xf6\xf5\xf8\xd1\x25\x01\x70\x43\xf2\x73\xd0\xce\x0f\x55\x93\x75\xd8\x85\xbe\xb3\x27\x7d\x9a\x3d\xf9\xc1\xaa\x16\x6d\x4a\x10\xad\x36\x5f\x1d\x20\xca\x4b\x09\x91\x66\xee\x27\xfd\x1a\xde\x59\x07\x1e\xde\xad\x02\xe9\x51\x2c\xd4\x84\x03\xfb\xce\xe1\x9c\x69\x60\x17\x19\x3e\x2e\xe3\x5f\x36\x6e\x85\xfc\x14\xf2\xfa\x1c\x0a\x32\xc8\x45\xbb\x5f\xd9\x15\x0d\x66\x48\x3d\x3b\x85\x7c\x33\x26\xf3\x86\x71\xab\x17\x4a\x49\x65\xab\x6c\x2f\x73\x31\xe1\xfa\xd8\x43\x4b\xee\x22\x1b\x17\xda\x60\xf5\x8c\x7d\x99\xd2\x3d\xfb\x6c\x0a\x52\x86\xf0\x0b\x86\x4d\x0c\x35\x05\xf2\x12\x2f\x9c\x8d\x02\x41\xac\xe7\xcb\x2e\x51\xb6\x07\xaa\x66\x7d\x28\x92\x6c\x8f\xc6\xc9\xdc\x15\x70\x63\x2b\xed\x12\x2a\x17\xaa\xdd\x95\xbf\x63\x6b\x40\xbe\xdb\x3f\x57\x18\xd6\x25\x31\xcb\x24\x70\x5f\x1a\x64\xc3\x9d\x0b\x62\x36\x4e\xef\x18\x57\x00\xd0\x93\x06\x2c\xb1\xc3\xc2\xf6\x53\xff\x12\xc8\x00\x42\xc7\x50\x0d\x9b\x5a\x43\x7f\xe8\x92\x8b\xc8\xe1\xa1\xcd\x85\xb9\xb2\x28\x11\x54\x26\xe7\x9c\x29\x19\xf4\x4d\x2c\x90\x69\x14\x57\x01\xe3\x76\x71\x87\x78\x01\x10\xce\x43\x79\x08\x81\x7d\x4d\x56\xb5\x68\x54\x12\xd6\x87\x94\x4a\x14\x61\xf2\xe6\x87\x0d\x4d\x11\x3b\xc2\x71\x9c\x8e\xa0\x11\x21\x16\x7a\x1c\x3e\xd0\xdf\x9c\x35\x57\x7f\xbd\xd0\x3d\xcc\xef\x63\xd2\xd4\x03\x11\xcf\x6a\xe5\x1b\x7e\x20\x15\x96\x75\x98\x0c\xf8\x4e\xd7\xdc\x69\xb8\x71\xef\xcd\x58\x4f\x45\x1e\x66\xc4\xf5\x4f\xbf\xc9\x66\xbb\xf1\x88\xd2\x22\x3c\xae\x5a\xb1\xcb\x3d\x5e\xaf\x4e\x0d\x49\x52\x9e\x12\x78\x77\xd3\x4a\x9c\xb1\x5a\xcd\x27\x4d\x6e\xd1\xc9\xb8\xe1\xb4\x6c\x23\xb1\x54\x7c\xca\x92\x1b\xea\xd9\xc0\xc9\xd2\x7a\xd8\xa6\x2f\x92\x1e\xc0\xe8\x7a\x82\x71\xfd\x14\x03\xaf\x22\x1e\xb0\x3a\x5a\xa6\x41\x8a\x56\x78\x6d\x7d\xc3\x3d\x05\x79\x2d\x9c\x80\xfa\x3a\x55\x1b\xa3\x9a\x83\x23\xf4\x88\x53\x62\x0b\x78\x04\x62\x55\x50\xfc\x22\x20\xee\xd0\xcc\xcb\x33\xca\x47\xb3\x27\xef\x53\x7d\x48\x5f\x31\x2a\x14\x9d\xe8\x66\xb1\x0e\xf9\x54\x48\x9a\x41\x38\x7f\x4f\x58\x51\xd8\xde\x6c\x01\x2b\x0f\x94\x1f\xf6\xd4\xf6\x06\x1e\x39\xae\x27\xd9\xd4\x8b\x33\xd6\x83\x5f\x72\x76\x23\x1e\x09\x17\x40\xdb\xb1\x6b\x60\xa3\x21\x0b\x3a\xe1\x08\xed\xa6\x93\xb4\x33\xed\xf5\x98\x58\x4d\xf1\x0f\xfc\x19\xf8\xe4\xdd\x9f\x57\xec\x30\x1d\xd3\xc1\xd9\x38\x49\x85\x83\x8a\x3a\x31\xf5\x19\x15\x5a\x86\xfd\x96\x5f\x53\x88\xee\x8c\x77\xea\x3f\xe8\x96\x3b\x49\x57\x17\xf5\x52\x95\xd1\x50\x3e\xee\xeb\x41\x01\x0f\x76\x87\xc0\x9e\x73\x94\xbb\xd4\x11\xed\x00\x43\x67\xf2\x75\x97\xf2\x20\x1b\x60\x57\xbf\x57\xba\xd0\x19\xc9\x9f\x1e\xcd\x45\xba\x2e\xe7\x83\x9a\x62\x34\x08\xde\x7a\x97\xd4\xe8\x5a\x49\xbf\xf8\xae\x9d\xa5\xc2\xbf\x47\x2a\x2c\x74\xa1\x5e\x96\x08\x85\xf7\x6c\x7a\x65\x44\xb8\x08\x3b\xac\x07\x6d\x4a\x95\x3d\x2e\x96\x6b\x5b\x19\xbe\x89\xa3\xdd\x17\x9c\xcb\x26\xef\x8e\x1f\x90\x12\xdd\x2b\x82\xd9\xc4\x91\x2e\x03\xc4\x86\x80\x2b\xbe\xce\x6f\x82\x96\x0e\x06\xba\x69\xb9\x86\xee\xff\xd2\x1f\x8b\xf8\x3c\x05\xc1\xac\xac\x64\x7e\x0d\xeb\xee\x97\x7d\x5f\xf3\xe9\xc2\x91\x18\x13\xeb\x9e\xda\xb9\x43\x2d\xa4\xde\x46\xae\xf8\xc7\x50\xaf\xa6\xa4\x01\xb2\x69\xfe\x8e\xea\xaf\x59\x06\x40\xa9\xf5\xfc\x77\xe5\x46\x60\xb3\x63\x8f\x9b\x67\x88\x86\xe6\xb3\x85\xef\x04\xe3\x22\xbb\x22\x42\xd0\x23\x21\x89\xc3\x3f\x32\x15\x2a\xc2\x06\xc4\xa2\x7e\x41\xf5\x15\x3c\xed\x76\x9b\xea\x6c\x3a\x33\x1a\x7e\x74\x93\xd1\x0e\xcc\xbc\xf2\x9c\x6a\x00\x24\x7f\xe6\x85\xd5\x04\x1b\x79\x7a\xd3\x44\x4d\x3b\x48\x57\x7f\x63\xec\x3d\x12\xf4\xf2\xc4\x19\x22\x85\x92\xbc\x5d\xca\xc1\x13\x48\x3d\x9a\x07\xc6\x91\xfe\x02\xea\x81\x47\x17\xc4\xdb\xf8\x22\x0c\xf5\xc2\xf9\xf9\x71\x20\x41\x08\xdd\xc4\x43\x0c\x77\x9e\x19\xab\x2e\x4c\x97\x72\x45\x81\x58\x20\x6d\x5d\x16\xad\x71\xce\x2a\xb2\xde\x0a\xff\xd0\x0e\x85\x2c\x67\xbb\x6f\x39\x88\x62\x15\xd6\x0c\xdf\x12\x6c\x94\x74\xa0\x96\x7d\x7e\xb5\xf2\x3f\xb9\x33\xf5\x9b\x30\x86\x1d\xfd\xe2\x90\x63\xf9\x57\xe0\x00\xfb\xf0\xad\xc2\x52\x07\x45\xf2\x79\xfc\xcb\xc8\xa4\x8f\xdb\xa1\xcc\xb7\x05\x1e\xc4\x11\x20\x42\x64\x42\x08\x3f\x35\x35\x6e\xd4\xb7\x30\x61\xb9\x6c\xc9\x15\xa8\xb3\x94\x64\x06\x08\xad\xa9\x72\x6d\xfd\x56\x9e\x6b\xf3\x1b\x36\x2b\x9d\x18\xa0\x4b\x7d\xa5\x96\xd7\xc0\xbf\xe3\x28\xd5\xbc\x18\x3b\xc9\x5c\x48\x0f\x88\x2c\x7f\xdc\x94\x6a\x6b\xa4\x4e\x7b\xa4\x02\xae\xf4\xfb\xa0\x16\x3d\xe3\xf7\xc3\x82\x81\x6a\xc2\x3e\x75\xef\xc7\xd5\x82\x6c\xa4\xa7\xb0\x55\xa1\xd2\x65\x0a\x97\xf6\x3f\xd9\x59\x6e\xa1\x29\xeb\x35\xbf\x0d\x33\x27\x83\x93\x1e\xd1\xf2\x7a\x9a\xd9\x8c\xe4\x45\x08\x69\x53\xb8\x57\xb2\x0b\x4e\xea\xf9\xd3\x30\xa3\x80\xee\xb0\x9e\xe9\x27\xbc\xab\xa3\x96\x66\xc6\x89\xb6\x60\x4f\xd3\xe2\xbe\xcf\x31\xe2\xa1\x17\x16\x32\xdf\xf2\x18\x61\x0f\xa1\x1e\x40\xd7\x89\xd6\x0d\x14\x5b\x94\xc4\xb8\xa7\x2b\xb5\x3f\x98\xd0\xd5\x6a\x80\x45\x64\x74\x58\x82\x2c\xcc\x29\x0b\x28\x20\x4d\x61\xd7\xc8\xda\x0f\x1a\x53\xe6\x51\x5d\x0d\x2c\x52\x41\xfa\xd6\x6a\x3d\x3b\x5c\x8f\x1b\x0f\xa8\x27\x69\x3a\xc4\xa2\x58\x76\x59\xb6\x03\xfc\x34\x3f\x29\x01\x13\xb7\x10\x6e\x83\x29\xc4\xc9\xf6\x6d\xf1\x2a\x1d\x9e\xb5\xb1\x3d\xab\x50\xde\xd6\xfd\xf2\x73\x29\x9e\xb4\x6f\x67\x16\x65\x89\x11\x0f\x1b\x18\xb6\xbd\x1d\xe4\x50\x49\x8d\xb4\x01\x50\x53\x22\x75\xd4\x29\x92\xec\x73\xbb\x4f\xa9\x13\x2a\xd3\xa0\x0a\x75\xab\x3b\x6a\xd2\x14\x87\x51\x5a\xc0\xb9\xa5\x9f\xa7\x33\x15\xbf\xed\xf5\x98\x1c\x7a\x24\x36\x32\x30\xd4\x45\xe1\x95\xe3\x8b\xd2\x97\x29\xbe\x8e\xc8\xd0\x68\xbb\xd0\xad\xa4\x87\xbd\xa2\xda\xb4\x8e\x7a\x10\xcd\x11\x7a\x78\x0d\x93\xc2\x5c\x1c\xf0\x56\x5c\x02\x9e\x8a\xd4\x3c\x25\xb1\x94\xc5\x13\x95\x2e\x89\x5e\x13\xbe\xae\x87\x8c\x15\xa0\x11\xea\xc4\x1c\x53\xbf\xb3\x16\xd4\x46\x62\xeb\xf6\xaa\xc8\x92\x73\xaf\x9f\xe6\x95\xd9\x8c\x88\x52\xeb\x78\x1c\xba\xab\xe3\x5d\xf9\x21\xd1\x91\xe1\x87\x48\x43\xc1\xbe\x60\xd4\xf3\x57\x06\x9a\xda"
+
+# twoN = vector_powers(TWO, BP_N);
+BP_TWO_N = b"\x01\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\x02\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\x04\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\x08\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\x10\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\x20\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\x40\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\x80\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\x01\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\x02\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\x04\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\x08\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\x10\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\x20\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\x40\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\x80\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\x01\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\x02\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\x04\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\x08\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\x10\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\x20\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\x40\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\x80\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\x01\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\x02\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\x04\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\x08\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\x10\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\x20\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\x40\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\x80\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\x01\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\x02\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\x04\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\x08\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\x10\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\x20\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\x40\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\x80\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\x01\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\x02\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\x04\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\x08\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\x10\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\x20\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\x40\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\x80\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\x01\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\x02\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\x04\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\x08\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\x10\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\x20\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\x40\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\x80\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\x01\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\x02\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\x04\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\x08\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\x10\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\x20\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\x40\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\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+
+# ip12 = inner_product(oneN, twoN);
+BP_IP12 = b"\xff\xff\xff\xff\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+
+
+#
+# Rct keys operations
+# tmp_x are global working registers to minimize memory allocations / heap fragmentation.
+# Caution has to be exercised when using the registers and operations using the registers
+#
+
+tmp_bf_0 = bytearray(32)
+tmp_bf_1 = bytearray(32)
+tmp_bf_2 = bytearray(32)
+tmp_bf_exp = bytearray(11 + 32 + 4)
+tmp_bf_exp_mv = memoryview(tmp_bf_exp)
+
+tmp_pt_1 = crypto.new_point()
+tmp_pt_2 = crypto.new_point()
+tmp_pt_3 = crypto.new_point()
+tmp_pt_4 = crypto.new_point()
+
+tmp_sc_1 = crypto.new_scalar()
+tmp_sc_2 = crypto.new_scalar()
+tmp_sc_3 = crypto.new_scalar()
+tmp_sc_4 = crypto.new_scalar()
+
+
+def _ensure_dst_key(dst=None):
+ if dst is None:
+ dst = bytearray(32)
+ return dst
+
+
+def memcpy(dst, dst_off, src, src_off, len):
+ if dst is not None:
+ _memcpy(dst, dst_off, src, src_off, len)
+ return dst
+
+
+def alloc_scalars(num=1):
+ return (crypto.new_scalar() for _ in range(num))
+
+
+def copy_key(dst, src):
+ for i in range(32):
+ dst[i] = src[i]
+ return dst
+
+
+def init_key(val, dst=None):
+ dst = _ensure_dst_key(dst)
+ return copy_key(dst, val)
+
+
+def gc_iter(i):
+ if i & 127 == 0:
+ gc.collect()
+
+
+def invert(dst, x):
+ dst = _ensure_dst_key(dst)
+ crypto.decodeint_into_noreduce(tmp_sc_1, x)
+ crypto.sc_inv_into(tmp_sc_2, tmp_sc_1)
+ crypto.encodeint_into(dst, tmp_sc_2)
+ return dst
+
+
+def scalarmult_key(dst, P, s):
+ dst = _ensure_dst_key(dst)
+ crypto.decodepoint_into(tmp_pt_1, P)
+ crypto.decodeint_into_noreduce(tmp_sc_1, s)
+ crypto.scalarmult_into(tmp_pt_2, tmp_pt_1, tmp_sc_1)
+ crypto.encodepoint_into(dst, tmp_pt_2)
+ return dst
+
+
+def scalarmultH(dst, x):
+ dst = _ensure_dst_key(dst)
+ crypto.decodeint_into(tmp_sc_1, x)
+ crypto.scalarmult_into(tmp_pt_1, XMR_HP, tmp_sc_1)
+ crypto.encodepoint_into(dst, tmp_pt_1)
+ return dst
+
+
+def scalarmult_base(dst, x):
+ dst = _ensure_dst_key(dst)
+ crypto.decodeint_into_noreduce(tmp_sc_1, x)
+ crypto.scalarmult_base_into(tmp_pt_1, tmp_sc_1)
+ crypto.encodepoint_into(dst, tmp_pt_1)
+ return dst
+
+
+def sc_gen(dst=None):
+ dst = _ensure_dst_key(dst)
+ crypto.random_scalar(tmp_sc_1)
+ crypto.encodeint_into(dst, tmp_sc_1)
+ return dst
+
+
+def sc_add(dst, a, b):
+ dst = _ensure_dst_key(dst)
+ crypto.decodeint_into_noreduce(tmp_sc_1, a)
+ crypto.decodeint_into_noreduce(tmp_sc_2, b)
+ crypto.sc_add_into(tmp_sc_3, tmp_sc_1, tmp_sc_2)
+ crypto.encodeint_into(dst, tmp_sc_3)
+ return dst
+
+
+def sc_sub(dst, a, b):
+ dst = _ensure_dst_key(dst)
+ crypto.decodeint_into_noreduce(tmp_sc_1, a)
+ crypto.decodeint_into_noreduce(tmp_sc_2, b)
+ crypto.sc_sub_into(tmp_sc_3, tmp_sc_1, tmp_sc_2)
+ crypto.encodeint_into(dst, tmp_sc_3)
+ return dst
+
+
+def sc_mul(dst, a, b):
+ dst = _ensure_dst_key(dst)
+ crypto.decodeint_into_noreduce(tmp_sc_1, a)
+ crypto.decodeint_into_noreduce(tmp_sc_2, b)
+ crypto.sc_mul_into(tmp_sc_3, tmp_sc_1, tmp_sc_2)
+ crypto.encodeint_into(dst, tmp_sc_3)
+ return dst
+
+
+def sc_muladd(dst, a, b, c):
+ dst = _ensure_dst_key(dst)
+ crypto.decodeint_into_noreduce(tmp_sc_1, a)
+ crypto.decodeint_into_noreduce(tmp_sc_2, b)
+ crypto.decodeint_into_noreduce(tmp_sc_3, c)
+ crypto.sc_muladd_into(tmp_sc_4, tmp_sc_1, tmp_sc_2, tmp_sc_3)
+ crypto.encodeint_into(dst, tmp_sc_4)
+ return dst
+
+
+def sc_mulsub(dst, a, b, c):
+ dst = _ensure_dst_key(dst)
+ crypto.decodeint_into_noreduce(tmp_sc_1, a)
+ crypto.decodeint_into_noreduce(tmp_sc_2, b)
+ crypto.decodeint_into_noreduce(tmp_sc_3, c)
+ crypto.sc_mulsub_into(tmp_sc_4, tmp_sc_1, tmp_sc_2, tmp_sc_3)
+ crypto.encodeint_into(dst, tmp_sc_4)
+ return dst
+
+
+def add_keys(dst, A, B):
+ dst = _ensure_dst_key(dst)
+ crypto.decodepoint_into(tmp_pt_1, A)
+ crypto.decodepoint_into(tmp_pt_2, B)
+ crypto.point_add_into(tmp_pt_3, tmp_pt_1, tmp_pt_2)
+ crypto.encodepoint_into(dst, tmp_pt_3)
+ return dst
+
+
+def sub_keys(dst, A, B):
+ dst = _ensure_dst_key(dst)
+ crypto.decodepoint_into(tmp_pt_1, A)
+ crypto.decodepoint_into(tmp_pt_2, B)
+ crypto.point_sub_into(tmp_pt_3, tmp_pt_1, tmp_pt_2)
+ crypto.encodepoint_into(dst, tmp_pt_3)
+ return dst
+
+
+def add_keys2(dst, a, b, B):
+ dst = _ensure_dst_key(dst)
+ crypto.decodeint_into_noreduce(tmp_sc_1, a)
+ crypto.decodeint_into_noreduce(tmp_sc_2, b)
+ crypto.decodepoint_into(tmp_pt_1, B)
+ crypto.add_keys2_into(tmp_pt_2, tmp_sc_1, tmp_sc_2, tmp_pt_1)
+ crypto.encodepoint_into(dst, tmp_pt_2)
+ return dst
+
+
+def add_keys3(dst, a, A, b, B):
+ dst = _ensure_dst_key(dst)
+ crypto.decodeint_into_noreduce(tmp_sc_1, a)
+ crypto.decodeint_into_noreduce(tmp_sc_2, b)
+ crypto.decodepoint_into(tmp_pt_1, A)
+ crypto.decodepoint_into(tmp_pt_2, B)
+ crypto.add_keys3_into(tmp_pt_3, tmp_sc_1, tmp_pt_1, tmp_sc_2, tmp_pt_2)
+ crypto.encodepoint_into(dst, tmp_pt_3)
+ return dst
+
+
+def hash_to_scalar(dst, data):
+ dst = _ensure_dst_key(dst)
+ crypto.hash_to_scalar_into(tmp_sc_1, data)
+ crypto.encodeint_into(dst, tmp_sc_1)
+ return dst
+
+
+def hash_vct_to_scalar(dst, data):
+ dst = _ensure_dst_key(dst)
+ ctx = crypto.get_keccak()
+ for x in data:
+ ctx.update(x)
+ hsh = ctx.digest()
+
+ crypto.decodeint_into(tmp_sc_1, hsh)
+ crypto.encodeint_into(tmp_bf_1, tmp_sc_1)
+ copy_key(dst, tmp_bf_1)
+ return dst
+
+
+def get_exponent(dst, base, idx):
+ dst = _ensure_dst_key(dst)
+ salt = b"bulletproof"
+ idx_size = uvarint_size(idx)
+ final_size = len(salt) + 32 + idx_size
+ buff = tmp_bf_exp_mv
+ memcpy(buff, 0, base, 0, 32)
+ memcpy(buff, 32, salt, 0, len(salt))
+ dump_uvarint_b_into(idx, buff, 32 + len(salt))
+ crypto.keccak_hash_into(tmp_bf_1, buff[:final_size])
+ crypto.hash_to_point_into(tmp_pt_1, tmp_bf_1)
+ crypto.encodepoint_into(dst, tmp_pt_1)
+ return dst
+
+
+#
+# Key Vectors
+#
+
+
+class KeyVBase:
+ """
+ Base KeyVector object
+ """
+
+ def __init__(self, elems=64):
+ self.current_idx = 0
+ self.size = elems
+
+ def idxize(self, idx):
+ if idx < 0:
+ idx = self.size + idx
+ if idx >= self.size:
+ raise IndexError("Index out of bounds")
+ return idx
+
+ def __getitem__(self, item):
+ raise ValueError("Not supported")
+
+ def __setitem__(self, key, value):
+ raise ValueError("Not supported")
+
+ def __iter__(self):
+ self.current_idx = 0
+ return self
+
+ def __next__(self):
+ if self.current_idx >= self.size:
+ raise StopIteration
+ else:
+ self.current_idx += 1
+ return self[self.current_idx - 1]
+
+ def __len__(self):
+ return self.size
+
+ def to(self, idx, buff, offset=0):
+ return memcpy(buff, offset, self[self.idxize(idx)], 0, 32)
+
+ def read(self, idx, buff, offset=0):
+ raise ValueError()
+
+ def slice(self, res, start, stop):
+ for i in range(start, stop):
+ res[i - start] = self[i]
+ return res
+
+ def slice_view(self, start, stop):
+ return KeyVSliced(self, start, stop)
+
+
+class KeyV(KeyVBase):
+ """
+ KeyVector abstraction
+ Constant precomputed buffers = bytes, frozen. Same operation as normal.
+
+ Non-constant KeyVector is separated to 64 elements chunks to avoid problems with
+ the heap fragmentation. In this way the chunks are more probable to be correctly
+ allocated as smaller chunk of continuous memory is required. Chunk is assumed to
+ have 64 elements at all times to minimize corner cases handling. BP require either
+ multiple of 64 elements vectors or less than 64.
+
+ Some chunk-dependent cases are not implemented as they are currently not needed in the BP.
+ """
+
+ def __init__(self, elems=64, buffer=None, const=False, no_init=False):
+ super().__init__(elems)
+ self.d = None
+ self.mv = None
+ self.const = const
+ self.cur = _ensure_dst_key()
+ self.chunked = False
+ if no_init:
+ pass
+ elif buffer:
+ self.d = buffer # can be immutable (bytes)
+ self.size = len(buffer) // 32
+ else:
+ self._set_d(elems)
+
+ if not no_init:
+ self._set_mv()
+
+ def _set_d(self, elems):
+ if elems > 64 and elems % 64 == 0:
+ self.chunked = True
+ self.d = [bytearray(32 * 64) for _ in range(elems // 64)]
+
+ else:
+ self.chunked = False
+ self.d = bytearray(32 * elems)
+
+ def _set_mv(self):
+ if not self.chunked:
+ self.mv = memoryview(self.d)
+
+ def __getitem__(self, item):
+ """
+ Returns corresponding 32 byte array.
+ Creates new memoryview on access.
+ """
+ if self.chunked:
+ raise ValueError("Not supported") # not needed
+ item = self.idxize(item)
+ return self.mv[item * 32 : (item + 1) * 32]
+
+ def __setitem__(self, key, value):
+ if self.chunked:
+ raise ValueError("Not supported") # not needed
+ if self.const:
+ raise ValueError("Constant KeyV")
+ ck = self[key]
+ for i in range(32):
+ ck[i] = value[i]
+
+ def to(self, idx, buff=None, offset=0):
+ idx = self.idxize(idx)
+ if self.chunked:
+ memcpy(
+ buff if buff else self.cur,
+ offset,
+ self.d[idx >> 6],
+ (idx & 63) << 5,
+ 32,
+ )
+ else:
+ memcpy(buff if buff else self.cur, offset, self.d, idx << 5, 32)
+ return buff if buff else self.cur
+
+ def read(self, idx, buff, offset=0):
+ idx = self.idxize(idx)
+ if self.chunked:
+ memcpy(self.d[idx >> 6], (idx & 63) << 5, buff, offset, 32)
+ else:
+ memcpy(self.d, idx << 5, buff, offset, 32)
+
+ def resize(self, nsize, chop=False, realloc=False):
+ if self.size == nsize:
+ return self
+
+ if self.chunked and nsize <= 64:
+ self.chunked = False # de-chunk
+ if self.size > nsize and realloc:
+ self.d = bytearray(self.d[0][: nsize << 5])
+ elif self.size > nsize and not chop:
+ self.d = self.d[0][: nsize << 5]
+ else:
+ self.d = bytearray(nsize << 5)
+
+ elif self.chunked:
+ raise ValueError("Unsupported") # not needed
+
+ else:
+ if self.size > nsize and realloc:
+ self.d = bytearray(self.d[: nsize << 5])
+ elif self.size > nsize and not chop:
+ self.d = self.d[: nsize << 5]
+ else:
+ self.d = bytearray(nsize << 5)
+
+ self.size = nsize
+ self._set_mv()
+
+ def realloc(self, nsize, collect=False):
+ self.d = None
+ self.mv = None
+ if collect:
+ gc.collect() # gc collect prev. allocation
+
+ self._set_d(nsize)
+ self.size = nsize
+ self._set_mv()
+
+ def realloc_init_from(self, nsize, src, offset=0, collect=False):
+ if not isinstance(src, KeyV):
+ raise ValueError("KeyV supported only")
+ self.realloc(nsize, collect)
+
+ if not self.chunked and not src.chunked:
+ memcpy(self.d, 0, src.d, offset << 5, nsize << 5)
+
+ elif self.chunked and not src.chunked:
+ raise ValueError("Unsupported") # not needed
+
+ elif self.chunked and src.chunked:
+ raise ValueError("Unsupported") # not needed
+
+ elif not self.chunked and src.chunked:
+ for i in range(nsize >> 6):
+ memcpy(
+ self.d,
+ i << 11,
+ src.d[i + (offset >> 6)],
+ (offset & 63) << 5 if i == 0 else 0,
+ nsize << 5 if i <= nsize >> 6 else (nsize & 64) << 5,
+ )
+
+
+class KeyVEval(KeyVBase):
+ """
+ KeyVector computed / evaluated on demand
+ """
+
+ def __init__(self, elems=64, src=None):
+ super().__init__(elems)
+ self.fnc = src
+ self.buff = _ensure_dst_key()
+ self.mv = memoryview(self.buff)
+
+ def __getitem__(self, item):
+ return self.fnc(self.idxize(item), self.buff)
+
+ def to(self, idx, buff=None, offset=0):
+ self.fnc(self.idxize(idx), self.buff)
+ memcpy(buff, offset, self.buff, 0, 32)
+ return buff if buff else self.buff
+
+
+class KeyVSized(KeyVBase):
+ """
+ Resized vector, wrapping possibly larger vector
+ (e.g., precomputed, but has to have exact size for further computations)
+ """
+
+ def __init__(self, wrapped, new_size):
+ super().__init__(new_size)
+ self.wrapped = wrapped
+
+ def __getitem__(self, item):
+ return self.wrapped[self.idxize(item)]
+
+ def __setitem__(self, key, value):
+ self.wrapped[self.idxize(key)] = value
+
+
+class KeyVConst(KeyVBase):
+ def __init__(self, size, elem, copy=True):
+ super().__init__(size)
+ self.elem = init_key(elem) if copy else elem
+
+ def __getitem__(self, item):
+ return self.elem
+
+ def to(self, idx, buff=None, offset=0):
+ memcpy(buff, offset, self.elem, 0, 32)
+ return buff if buff else self.elem
+
+
+class KeyVPrecomp(KeyVBase):
+ """
+ Vector with possibly large size and some precomputed prefix.
+ Usable for Gi vector with precomputed usual sizes (i.e., 2 output transactions)
+ but possible to compute further
+ """
+
+ def __init__(self, size, precomp_prefix, aux_comp_fnc):
+ super().__init__(size)
+ self.precomp_prefix = precomp_prefix
+ self.aux_comp_fnc = aux_comp_fnc
+ self.buff = _ensure_dst_key()
+
+ def __getitem__(self, item):
+ item = self.idxize(item)
+ if item < len(self.precomp_prefix):
+ return self.precomp_prefix[item]
+ return self.aux_comp_fnc(item, self.buff)
+
+ def to(self, idx, buff=None, offset=0):
+ item = self.idxize(idx)
+ if item < len(self.precomp_prefix):
+ return self.precomp_prefix.to(item, buff if buff else self.buff, offset)
+ self.aux_comp_fnc(item, self.buff)
+ memcpy(buff, offset, self.buff, 0, 32)
+ return buff if buff else self.buff
+
+
+class KeyVSliced(KeyVBase):
+ """
+ Sliced in-memory vector version, remapping
+ """
+
+ def __init__(self, src, start, stop):
+ super().__init__(stop - start)
+ self.wrapped = src
+ self.offset = start
+
+ def __getitem__(self, item):
+ return self.wrapped[self.offset + self.idxize(item)]
+
+ def __setitem__(self, key, value):
+ self.wrapped[self.offset + self.idxize(key)] = value
+
+ def resize(self, nsize, chop=False):
+ raise ValueError("Not supported")
+
+ def to(self, idx, buff=None, offset=0):
+ return self.wrapped.to(self.offset + self.idxize(idx), buff, offset)
+
+ def read(self, idx, buff, offset=0):
+ return self.wrapped.read(self.offset + self.idxize(idx), buff, offset)
+
+
+class KeyVPowers(KeyVBase):
+ """
+ Vector of x^i. Allows only sequential access (no jumping). Resets on [0,1] access.
+ """
+
+ def __init__(self, size, x, **kwargs):
+ super().__init__(size)
+ self.x = x
+ self.cur = bytearray(32)
+ self.last_idx = 0
+
+ def __getitem__(self, item):
+ prev = self.last_idx
+ item = self.idxize(item)
+ self.last_idx = item
+
+ if item == 0:
+ return copy_key(self.cur, ONE)
+ elif item == 1:
+ return copy_key(self.cur, self.x)
+ elif item == prev + 1:
+ return sc_mul(self.cur, self.cur, self.x)
+ else:
+ IndexError("Only linear scan allowed")
+
+
+class KeyVZtwo(KeyVBase):
+ """
+ Ztwo vector - see vector_z_two_i
+ """
+
+ def __init__(self, N, logN, M, zpow, twoN, raw=False):
+ super().__init__(N * M)
+ self.N = N
+ self.logN = logN
+ self.M = M
+ self.zpow = zpow
+ self.twoN = twoN
+ self.raw = raw
+ self.sc = crypto.new_scalar()
+ self.cur = bytearray(32) if not raw else None
+
+ def __getitem__(self, item):
+ vector_z_two_i(self.logN, self.zpow, self.twoN, self.idxize(item), self.sc)
+ if self.raw:
+ return self.sc
+
+ crypto.encodeint_into(self.cur, self.sc)
+ return self.cur
+
+
+def _ensure_dst_keyvect(dst=None, size=None):
+ if dst is None:
+ dst = KeyV(elems=size)
+ return dst
+ if size is not None and size != len(dst):
+ dst.resize(size)
+ return dst
+
+
+def const_vector(val, elems=BP_N, copy=True):
+ return KeyVConst(elems, val, copy)
+
+
+def vector_exponent_custom(A, B, a, b, dst=None):
+ dst = _ensure_dst_key(dst)
+ crypto.identity_into(tmp_pt_2)
+
+ for i in range(len(a)):
+ crypto.decodeint_into_noreduce(tmp_sc_1, a.to(i))
+ crypto.decodepoint_into(tmp_pt_3, A.to(i))
+ crypto.decodeint_into_noreduce(tmp_sc_2, b.to(i))
+ crypto.decodepoint_into(tmp_pt_4, B.to(i))
+ crypto.add_keys3_into(tmp_pt_1, tmp_sc_1, tmp_pt_3, tmp_sc_2, tmp_pt_4)
+ crypto.point_add_into(tmp_pt_2, tmp_pt_2, tmp_pt_1)
+ gc_iter(i)
+ crypto.encodepoint_into(dst, tmp_pt_2)
+ return dst
+
+
+def vector_powers(x, n, dst=None, dynamic=False, **kwargs):
+ if dynamic:
+ return KeyVPowers(n, x, **kwargs)
+ dst = _ensure_dst_keyvect(dst, n)
+ if n == 0:
+ return dst
+ dst.read(0, ONE)
+ if n == 1:
+ return dst
+ dst.read(1, x)
+
+ crypto.decodeint_into_noreduce(tmp_sc_1, x)
+ crypto.decodeint_into_noreduce(tmp_sc_2, x)
+ for i in range(2, n):
+ crypto.sc_mul_into(tmp_sc_1, tmp_sc_1, tmp_sc_2)
+ crypto.encodeint_into(tmp_bf_0, tmp_sc_1)
+ dst.read(i, tmp_bf_0)
+ gc_iter(i)
+ return dst
+
+
+def vector_power_sum(x, n, dst=None):
+ dst = _ensure_dst_key(dst)
+ if n == 0:
+ return copy_key(dst, ZERO)
+
+ copy_key(dst, ONE)
+ if n == 1:
+ return dst
+
+ prev = init_key(x)
+ for i in range(1, n):
+ if i > 1:
+ sc_mul(prev, prev, x)
+ sc_add(dst, dst, prev)
+ gc_iter(i)
+ return dst
+
+
+def inner_product(a, b, dst=None):
+ if len(a) != len(b):
+ raise ValueError("Incompatible sizes of a and b")
+ dst = _ensure_dst_key(dst)
+ crypto.sc_init_into(tmp_sc_1, 0)
+
+ for i in range(len(a)):
+ crypto.decodeint_into_noreduce(tmp_sc_2, a.to(i))
+ crypto.decodeint_into_noreduce(tmp_sc_3, b.to(i))
+ crypto.sc_muladd_into(tmp_sc_1, tmp_sc_2, tmp_sc_3, tmp_sc_1)
+ gc_iter(i)
+
+ crypto.encodeint_into(dst, tmp_sc_1)
+ return dst
+
+
+def hadamard(a, b, dst=None):
+ dst = _ensure_dst_keyvect(dst, len(a))
+ for i in range(len(a)):
+ sc_mul(tmp_bf_1, a.to(i), b.to(i))
+ dst.read(i, tmp_bf_1)
+ gc_iter(i)
+ return dst
+
+
+def hadamard_fold(v, a, b, into=None, into_offset=0):
+ """
+ Folds a curvepoint array using a two way scaled Hadamard product
+
+ ln = len(v); h = ln // 2
+ v[i] = a * v[i] + b * v[h + i]
+ """
+ h = len(v) // 2
+ crypto.decodeint_into_noreduce(tmp_sc_1, a)
+ crypto.decodeint_into_noreduce(tmp_sc_2, b)
+ into = into if into else v
+
+ for i in range(h):
+ crypto.decodepoint_into(tmp_pt_1, v.to(i))
+ crypto.decodepoint_into(tmp_pt_2, v.to(h + i))
+ crypto.add_keys3_into(tmp_pt_3, tmp_sc_1, tmp_pt_1, tmp_sc_2, tmp_pt_2)
+ crypto.encodepoint_into(tmp_bf_0, tmp_pt_3)
+ into.read(i + into_offset, tmp_bf_0)
+ gc_iter(i)
+
+ return into
+
+
+def scalar_fold(v, a, b, into=None, into_offset=0):
+ """
+ ln = len(v); h = ln // 2
+ v[i] = v[i] * a + v[h+i] * b)
+ """
+ h = len(v) // 2
+ crypto.decodeint_into_noreduce(tmp_sc_1, a)
+ crypto.decodeint_into_noreduce(tmp_sc_2, b)
+ into = into if into else v
+
+ for i in range(h):
+ crypto.decodeint_into_noreduce(tmp_sc_3, v.to(i))
+ crypto.decodeint_into_noreduce(tmp_sc_4, v.to(h + i))
+ crypto.sc_mul_into(tmp_sc_3, tmp_sc_3, tmp_sc_1)
+ crypto.sc_mul_into(tmp_sc_4, tmp_sc_4, tmp_sc_2)
+ crypto.sc_add_into(tmp_sc_3, tmp_sc_3, tmp_sc_4)
+ crypto.encodeint_into(tmp_bf_0, tmp_sc_3)
+ into.read(i + into_offset, tmp_bf_0)
+ gc_iter(i)
+
+ return into
+
+
+def cross_inner_product(l0, r0, l1, r1):
+ """
+ t1_1 = l0 . r1, t1_2 = l1 . r0
+ t1 = t1_1 + t1_2, t2 = l1 . r1
+ """
+ sc_t1_1, sc_t1_2, sc_t2 = alloc_scalars(3)
+ cl0, cr0, cl1, cr1 = alloc_scalars(4)
+
+ for i in range(len(l0)):
+ crypto.decodeint_into_noreduce(cl0, l0.to(i))
+ crypto.decodeint_into_noreduce(cr0, r0.to(i))
+ crypto.decodeint_into_noreduce(cl1, l1.to(i))
+ crypto.decodeint_into_noreduce(cr1, r1.to(i))
+
+ crypto.sc_muladd_into(sc_t1_1, cl0, cr1, sc_t1_1)
+ crypto.sc_muladd_into(sc_t1_2, cl1, cr0, sc_t1_2)
+ crypto.sc_muladd_into(sc_t2, cl1, cr1, sc_t2)
+ gc_iter(i)
+
+ crypto.sc_add_into(sc_t1_1, sc_t1_1, sc_t1_2)
+ return crypto.encodeint(sc_t1_1), crypto.encodeint(sc_t2)
+
+
+def vector_gen(dst, size, op):
+ dst = _ensure_dst_keyvect(dst, size)
+ for i in range(size):
+ dst.to(i, tmp_bf_0)
+ op(i, tmp_bf_0)
+ dst.read(i, tmp_bf_0)
+ gc_iter(i)
+ return dst
+
+
+def vector_add(a, b, dst=None):
+ dst = _ensure_dst_keyvect(dst, len(a))
+ for i in range(len(a)):
+ sc_add(tmp_bf_1, a.to(i), b.to(i))
+ dst.read(i, tmp_bf_1)
+ gc_iter(i)
+ return dst
+
+
+def vector_subtract(a, b, dst=None):
+ dst = _ensure_dst_keyvect(dst, len(a))
+ for i in range(len(a)):
+ sc_sub(tmp_bf_1, a.to(i), b.to(i))
+ dst.read(i, tmp_bf_1)
+ gc_iter(i)
+ return dst
+
+
+def vector_dup(x, n, dst=None):
+ dst = _ensure_dst_keyvect(dst, n)
+ for i in range(n):
+ dst[i] = x
+ gc_iter(i)
+ return dst
+
+
+def vector_z_two_i(logN, zpow, twoN, i, dst_sc=None):
+ """
+ 0...N|N+1...2N|2N+1...3N|....
+ zt[i] = z^b 2^c, where
+ b = 2 + blockNumber. BlockNumber is idx of N block
+ c = i % N = i - N * blockNumber
+ """
+ j = i >> logN
+ crypto.decodeint_into_noreduce(tmp_sc_1, zpow.to(j + 2))
+ crypto.decodeint_into_noreduce(tmp_sc_2, twoN.to(i & ((1 << logN) - 1)))
+ crypto.sc_mul_into(dst_sc, tmp_sc_1, tmp_sc_2)
+ return dst_sc
+
+
+def vector_z_two(N, logN, M, zpow, twoN, zero_twos=None, dynamic=False, **kwargs):
+ if dynamic:
+ return KeyVZtwo(N, logN, M, zpow, twoN, **kwargs)
+ else:
+ raise NotImplementedError()
+
+
+def hash_cache_mash(dst, hash_cache, *args):
+ dst = _ensure_dst_key(dst)
+ ctx = crypto.get_keccak()
+ ctx.update(hash_cache)
+
+ for x in args:
+ if x is None:
+ break
+ ctx.update(x)
+ hsh = ctx.digest()
+
+ crypto.decodeint_into(tmp_sc_1, hsh)
+ crypto.encodeint_into(tmp_bf_1, tmp_sc_1)
+
+ copy_key(dst, tmp_bf_1)
+ copy_key(hash_cache, tmp_bf_1)
+ return dst
+
+
+def is_reduced(sc):
+ return crypto.encodeint(crypto.decodeint(sc)) == sc
+
+
+class MultiExpSequential:
+ """
+ MultiExp object similar to MultiExp array of [(scalar, point), ]
+ MultiExp computes simply: res = \\sum_i scalar_i * point_i
+ Straus / Pippenger algorithms are implemented in the original Monero C++ code for the speed
+ but the memory cost is around 1 MB which is not affordable here in HW devices.
+
+ Moreover, Monero needs speed for very fast verification for blockchain verification which is not
+ priority in this use case.
+
+ MultiExp holder with sequential evaluation
+ """
+
+ def __init__(self, size=None, points=None, point_fnc=None):
+ self.current_idx = 0
+ self.size = size if size else None
+ self.points = points if points else []
+ self.point_fnc = point_fnc
+ if points and size is None:
+ self.size = len(points) if points else 0
+ else:
+ self.size = 0
+
+ self.acc = crypto.identity()
+ self.tmp = _ensure_dst_key()
+
+ def get_point(self, idx):
+ return (
+ self.point_fnc(idx, None) if idx >= len(self.points) else self.points[idx]
+ )
+
+ def add_pair(self, scalar, point):
+ self._acc(scalar, point)
+
+ def add_scalar(self, scalar):
+ self._acc(scalar, self.get_point(self.current_idx))
+
+ def _acc(self, scalar, point):
+ crypto.decodeint_into_noreduce(tmp_sc_1, scalar)
+ crypto.decodepoint_into(tmp_pt_2, point)
+ crypto.scalarmult_into(tmp_pt_3, tmp_pt_2, tmp_sc_1)
+ crypto.point_add_into(self.acc, self.acc, tmp_pt_3)
+ self.current_idx += 1
+ self.size += 1
+
+ def eval(self, dst, GiHi=False):
+ dst = _ensure_dst_key(dst)
+ return crypto.encodepoint_into(dst, self.acc)
+
+
+def multiexp(dst=None, data=None, GiHi=False):
+ return data.eval(dst, GiHi)
+
+
+class BulletProofBuilder:
+ def __init__(self):
+ self.use_det_masks = True
+ self.proof_sec = None
+
+ self.Gprec = KeyV(buffer=BP_GI_PRE, const=True)
+ self.Hprec = KeyV(buffer=BP_HI_PRE, const=True)
+ self.oneN = const_vector(ONE, 64)
+ self.twoN = KeyV(buffer=BP_TWO_N, const=True)
+ self.ip12 = BP_IP12
+ self.fnc_det_mask = None
+
+ self.tmp_sc_1 = crypto.new_scalar()
+ self.tmp_det_buff = bytearray(64 + 1 + 4)
+
+ self.gc_fnc = gc.collect
+ self.gc_trace = None
+
+ def gc(self, *args):
+ if self.gc_trace:
+ self.gc_trace(*args)
+ if self.gc_fnc:
+ self.gc_fnc()
+
+ def aX_vcts(self, sv, MN):
+ num_inp = len(sv)
+
+ def e_xL(idx, d=None, is_a=True):
+ j, i = idx // BP_N, idx % BP_N
+ r = None
+ if j >= num_inp:
+ r = ZERO if is_a else MINUS_ONE
+ elif sv[j][i // 8] & (1 << i % 8):
+ r = ONE if is_a else ZERO
+ else:
+ r = ZERO if is_a else MINUS_ONE
+ if d:
+ memcpy(d, 0, r, 0, 32)
+ return r
+
+ aL = KeyVEval(MN, lambda i, d: e_xL(i, d, True))
+ aR = KeyVEval(MN, lambda i, d: e_xL(i, d, False))
+ return aL, aR
+
+ def _det_mask_init(self):
+ memcpy(self.tmp_det_buff, 0, self.proof_sec, 0, len(self.proof_sec))
+
+ def _det_mask(self, i, is_sL=True, dst=None):
+ dst = _ensure_dst_key(dst)
+ if self.fnc_det_mask:
+ return self.fnc_det_mask(i, is_sL, dst)
+ self.tmp_det_buff[64] = int(is_sL)
+ memcpy(self.tmp_det_buff, 65, ZERO, 0, 4)
+ dump_uvarint_b_into(i, self.tmp_det_buff, 65)
+ crypto.hash_to_scalar_into(self.tmp_sc_1, self.tmp_det_buff)
+ crypto.encodeint_into(dst, self.tmp_sc_1)
+ return dst
+
+ def _gprec_aux(self, size):
+ return KeyVPrecomp(
+ size, self.Gprec, lambda i, d: get_exponent(d, XMR_H, i * 2 + 1)
+ )
+
+ def _hprec_aux(self, size):
+ return KeyVPrecomp(size, self.Hprec, lambda i, d: get_exponent(d, XMR_H, i * 2))
+
+ def _two_aux(self, size):
+ # Simple recursive exponentiation from precomputed results
+ lx = len(self.twoN)
+
+ def pow_two(i, d=None):
+ if i < lx:
+ return self.twoN[i]
+
+ d = _ensure_dst_key(d)
+ flr = i // 2
+
+ lw = pow_two(flr)
+ rw = pow_two(flr + 1 if flr != i / 2.0 else lw)
+ return sc_mul(d, lw, rw)
+
+ return KeyVPrecomp(size, self.twoN, pow_two)
+
+ def sL_vct(self, ln=BP_N):
+ return (
+ KeyVEval(ln, lambda i, dst: self._det_mask(i, True, dst))
+ if self.use_det_masks
+ else self.sX_gen(ln)
+ )
+
+ def sR_vct(self, ln=BP_N):
+ return (
+ KeyVEval(ln, lambda i, dst: self._det_mask(i, False, dst))
+ if self.use_det_masks
+ else self.sX_gen(ln)
+ )
+
+ def sX_gen(self, ln=BP_N):
+ buff = bytearray(ln * 32)
+ buff_mv = memoryview(buff)
+ sc = crypto.new_scalar()
+ for i in range(ln):
+ crypto.random_scalar(sc)
+ crypto.encodeint_into(buff_mv[i * 32 : (i + 1) * 32], sc)
+ gc_iter(i)
+ return KeyV(buffer=buff)
+
+ def vector_exponent(self, a, b, dst=None):
+ return vector_exponent_custom(self.Gprec, self.Hprec, a, b, dst)
+
+ def prove_testnet(self, sv, gamma):
+ return self.prove(sv, gamma, proof_v8=True)
+
+ def prove(self, sv, gamma, proof_v8=False):
+ return self.prove_batch([sv], [gamma], proof_v8=proof_v8)
+
+ def prove_setup(self, sv, gamma, proof_v8=False):
+ utils.ensure(len(sv) == len(gamma), "|sv| != |gamma|")
+ utils.ensure(len(sv) > 0, "sv empty")
+
+ self.proof_sec = crypto.random_bytes(64)
+ self._det_mask_init()
+ sv = [crypto.encodeint(x) for x in sv]
+ gamma = [crypto.encodeint(x) for x in gamma]
+
+ M, logM = 1, 0
+ while M <= BP_M and M < len(sv):
+ logM += 1
+ M = 1 << logM
+ MN = M * BP_N
+
+ V = _ensure_dst_keyvect(None, len(sv))
+ for i in range(len(sv)):
+ add_keys2(tmp_bf_0, gamma[i], sv[i], XMR_H)
+ if not proof_v8:
+ scalarmult_key(tmp_bf_0, tmp_bf_0, INV_EIGHT)
+ V.read(i, tmp_bf_0)
+
+ aL, aR = self.aX_vcts(sv, MN)
+ return M, logM, aL, aR, V, gamma
+
+ def prove_batch(self, sv, gamma, proof_v8=False):
+ M, logM, aL, aR, V, gamma = self.prove_setup(sv, gamma, proof_v8)
+ hash_cache = _ensure_dst_key()
+ while True:
+ self.gc(10)
+ r = self._prove_batch_main(
+ V, gamma, aL, aR, hash_cache, logM, BP_LOG_N, M, BP_N, proof_v8
+ )
+ if r[0]:
+ break
+ return r[1]
+
+ def _prove_batch_main(
+ self, V, gamma, aL, aR, hash_cache, logM, logN, M, N, proof_v8=False
+ ):
+ logMN = logM + logN
+ MN = M * N
+ hash_vct_to_scalar(hash_cache, V)
+
+ # Extended precomputed GiHi
+ Gprec = self._gprec_aux(MN)
+ Hprec = self._hprec_aux(MN)
+
+ # PAPER LINES 38-39
+ alpha = sc_gen()
+ ve = _ensure_dst_key()
+ A = _ensure_dst_key()
+ vector_exponent_custom(Gprec, Hprec, aL, aR, ve)
+ add_keys(A, ve, scalarmult_base(tmp_bf_1, alpha))
+ if not proof_v8:
+ scalarmult_key(A, A, INV_EIGHT)
+ self.gc(11)
+
+ # PAPER LINES 40-42
+ sL = self.sL_vct(MN)
+ sR = self.sR_vct(MN)
+ rho = sc_gen()
+ vector_exponent_custom(Gprec, Hprec, sL, sR, ve)
+ S = _ensure_dst_key()
+ add_keys(S, ve, scalarmult_base(tmp_bf_1, rho))
+ if not proof_v8:
+ scalarmult_key(S, S, INV_EIGHT)
+ del (ve)
+ self.gc(12)
+
+ # PAPER LINES 43-45
+ y = _ensure_dst_key()
+ hash_cache_mash(y, hash_cache, A, S)
+ if y == ZERO:
+ return (0,)
+
+ z = _ensure_dst_key()
+ hash_to_scalar(hash_cache, y)
+ copy_key(z, hash_cache)
+ if z == ZERO:
+ return (0,)
+
+ # Polynomial construction by coefficients
+ zMN = const_vector(z, MN)
+ l0 = _ensure_dst_keyvect(None, MN)
+ vector_subtract(aL, zMN, l0)
+ l1 = sL
+ self.gc(13)
+
+ # This computes the ugly sum/concatenation from PAPER LINE 65
+ # r0 = aR + z
+ r0 = vector_add(aR, zMN)
+ del (zMN)
+ self.gc(14)
+
+ # r0 = r0 \odot yMN => r0[i] = r0[i] * y^i
+ # r1 = sR \odot yMN => r1[i] = sR[i] * y^i
+ yMN = vector_powers(y, MN, dynamic=False)
+ hadamard(r0, yMN, dst=r0)
+ self.gc(15)
+
+ # r0 = r0 + zero_twos
+ zpow = vector_powers(z, M + 2)
+ twoN = self._two_aux(MN)
+ zero_twos = vector_z_two(N, logN, M, zpow, twoN, dynamic=True, raw=True)
+ vector_gen(
+ r0,
+ len(r0),
+ lambda i, d: crypto.encodeint_into(
+ d,
+ crypto.sc_add_into(
+ tmp_sc_1,
+ zero_twos[i], # noqa: F821
+ crypto.decodeint_into_noreduce(tmp_sc_2, r0.to(i)), # noqa: F821
+ ),
+ ),
+ )
+
+ del (zero_twos, twoN)
+ self.gc(15)
+
+ # Polynomial construction before PAPER LINE 46
+ # r1 = KeyVEval(MN, lambda i, d: sc_mul(d, yMN[i], sR[i]))
+ # r1 optimization possible, but has clashing sc registers.
+ # Moreover, max memory complexity is 4MN as below (while loop).
+ r1 = hadamard(yMN, sR, yMN) # re-use yMN vector for r1
+ del (yMN, sR)
+ self.gc(16)
+
+ # Inner products
+ # l0 = aL - z r0 = ((aR + z) \cdot ypow) + zt
+ # l1 = sL r1 = sR \cdot ypow
+ # t1_1 = l0 . r1, t1_2 = l1 . r0
+ # t1 = t1_1 + t1_2, t2 = l1 . r1
+ # l = l0 \odot x*l1 r = r0 \odot x*r1
+ t1, t2 = cross_inner_product(l0, r0, l1, r1)
+ self.gc(17)
+
+ # PAPER LINES 47-48
+ tau1, tau2 = sc_gen(), sc_gen()
+ T1, T2 = _ensure_dst_key(), _ensure_dst_key()
+
+ add_keys(T1, scalarmultH(tmp_bf_1, t1), scalarmult_base(tmp_bf_2, tau1))
+ if not proof_v8:
+ scalarmult_key(T1, T1, INV_EIGHT)
+
+ add_keys(T2, scalarmultH(tmp_bf_1, t2), scalarmult_base(tmp_bf_2, tau2))
+ if not proof_v8:
+ scalarmult_key(T2, T2, INV_EIGHT)
+ del (t1, t2)
+ self.gc(17)
+
+ # PAPER LINES 49-51
+ x = _ensure_dst_key()
+ hash_cache_mash(x, hash_cache, z, T1, T2)
+ if x == ZERO:
+ return (0,)
+
+ # PAPER LINES 52-53
+ taux = _ensure_dst_key()
+ copy_key(taux, ZERO)
+ sc_mul(taux, tau1, x)
+ xsq = _ensure_dst_key()
+ sc_mul(xsq, x, x)
+ sc_muladd(taux, tau2, xsq, taux)
+ del (xsq, tau1, tau2)
+ for j in range(1, len(V) + 1):
+ sc_muladd(taux, zpow.to(j + 1), gamma[j - 1], taux)
+ del (zpow)
+
+ self.gc(18)
+ mu = _ensure_dst_key()
+ sc_muladd(mu, x, rho, alpha)
+ del (rho, alpha)
+
+ # PAPER LINES 54-57
+ # l = l0 \odot x*l1, has to evaluated as it becomes aprime in the loop
+ l = vector_gen(
+ l0,
+ len(l0),
+ lambda i, d: sc_add(d, d, sc_mul(tmp_bf_1, l1.to(i), x)), # noqa: F821
+ )
+ del (l0, l1, sL)
+ self.gc(19)
+
+ # r = r0 \odot x*r1, has to evaluated as it becomes bprime in the loop
+ r = vector_gen(
+ r0,
+ len(r0),
+ lambda i, d: sc_add(d, d, sc_mul(tmp_bf_1, r1.to(i), x)), # noqa: F821
+ )
+ t = inner_product(l, r)
+ del (r1, r0)
+ self.gc(19)
+
+ # PAPER LINES 32-33
+ x_ip = hash_cache_mash(None, hash_cache, x, taux, mu, t)
+ if x_ip == ZERO:
+ return 0, None
+
+ # PHASE 2
+ # These are used in the inner product rounds
+ nprime = MN
+ Gprime = _ensure_dst_keyvect(None, MN)
+ Hprime = _ensure_dst_keyvect(None, MN)
+ aprime = l
+ bprime = r
+ yinv = invert(None, y)
+ yinvpow = init_key(ONE)
+ self.gc(20)
+
+ for i in range(0, MN):
+ Gprime.read(i, Gprec.to(i))
+ scalarmult_key(tmp_bf_0, Hprec.to(i), yinvpow)
+ Hprime.read(i, tmp_bf_0)
+ sc_mul(yinvpow, yinvpow, yinv)
+ gc_iter(i)
+ self.gc(21)
+
+ L = _ensure_dst_keyvect(None, logMN)
+ R = _ensure_dst_keyvect(None, logMN)
+ cL = _ensure_dst_key()
+ cR = _ensure_dst_key()
+ winv = _ensure_dst_key()
+ w_round = _ensure_dst_key()
+ tmp = _ensure_dst_key()
+
+ round = 0
+ _tmp_k_1 = _ensure_dst_key()
+
+ # PAPER LINE 13
+ while nprime > 1:
+ # PAPER LINE 15
+ npr2 = nprime
+ nprime >>= 1
+ self.gc(22)
+
+ # PAPER LINES 16-17
+ inner_product(
+ aprime.slice_view(0, nprime), bprime.slice_view(nprime, npr2), cL
+ )
+
+ inner_product(
+ aprime.slice_view(nprime, npr2), bprime.slice_view(0, nprime), cR
+ )
+ self.gc(23)
+
+ # PAPER LINES 18-19
+ vector_exponent_custom(
+ Gprime.slice_view(nprime, npr2),
+ Hprime.slice_view(0, nprime),
+ aprime.slice_view(0, nprime),
+ bprime.slice_view(nprime, npr2),
+ tmp_bf_0,
+ )
+
+ sc_mul(tmp, cL, x_ip)
+ add_keys(tmp_bf_0, tmp_bf_0, scalarmultH(_tmp_k_1, tmp))
+ if not proof_v8:
+ scalarmult_key(tmp_bf_0, tmp_bf_0, INV_EIGHT)
+ L.read(round, tmp_bf_0)
+ self.gc(24)
+
+ vector_exponent_custom(
+ Gprime.slice_view(0, nprime),
+ Hprime.slice_view(nprime, npr2),
+ aprime.slice_view(nprime, npr2),
+ bprime.slice_view(0, nprime),
+ tmp_bf_0,
+ )
+
+ sc_mul(tmp, cR, x_ip)
+ add_keys(tmp_bf_0, tmp_bf_0, scalarmultH(_tmp_k_1, tmp))
+ if not proof_v8:
+ scalarmult_key(tmp_bf_0, tmp_bf_0, INV_EIGHT)
+ R.read(round, tmp_bf_0)
+ self.gc(25)
+
+ # PAPER LINES 21-22
+ hash_cache_mash(w_round, hash_cache, L.to(round), R.to(round))
+ if w_round == ZERO:
+ return (0,)
+
+ # PAPER LINES 24-25
+ invert(winv, w_round)
+ self.gc(26)
+
+ hadamard_fold(Gprime, winv, w_round)
+ self.gc(27)
+
+ hadamard_fold(Hprime, w_round, winv, Gprime, nprime)
+ Hprime.realloc_init_from(nprime, Gprime, nprime, round < 2)
+ self.gc(28)
+
+ # PAPER LINES 28-29
+ scalar_fold(aprime, w_round, winv, Gprime, nprime)
+ aprime.realloc_init_from(nprime, Gprime, nprime, round < 2)
+ self.gc(29)
+
+ scalar_fold(bprime, winv, w_round, Gprime, nprime)
+ bprime.realloc_init_from(nprime, Gprime, nprime, round < 2)
+ self.gc(30)
+
+ # Finally resize Gprime which was buffer for all ops
+ Gprime.resize(nprime, realloc=True)
+ round += 1
+
+ from apps.monero.xmr.serialize_messages.tx_rsig_bulletproof import Bulletproof
+
+ return (
+ 1,
+ Bulletproof(
+ V=V,
+ A=A,
+ S=S,
+ T1=T1,
+ T2=T2,
+ taux=taux,
+ mu=mu,
+ L=L,
+ R=R,
+ a=aprime.to(0),
+ b=bprime.to(0),
+ t=t,
+ ),
+ )
+
+ def verify_testnet(self, proof):
+ return self.verify(proof, proof_v8=True)
+
+ def verify(self, proof, proof_v8=False):
+ return self.verify_batch([proof], proof_v8=proof_v8)
+
+ def verify_batch(self, proofs, single_optim=True, proof_v8=False):
+ """
+ BP batch verification
+ :param proofs:
+ :param single_optim: single proof memory optimization
+ :param proof_v8: previous testnet version
+ :return:
+ """
+ max_length = 0
+ for proof in proofs:
+ utils.ensure(is_reduced(proof.taux), "Input scalar not in range")
+ utils.ensure(is_reduced(proof.mu), "Input scalar not in range")
+ utils.ensure(is_reduced(proof.a), "Input scalar not in range")
+ utils.ensure(is_reduced(proof.b), "Input scalar not in range")
+ utils.ensure(is_reduced(proof.t), "Input scalar not in range")
+ utils.ensure(len(proof.V) >= 1, "V does not have at least one element")
+ utils.ensure(len(proof.L) == len(proof.R), "|L| != |R|")
+ utils.ensure(len(proof.L) > 0, "Empty proof")
+ max_length = max(max_length, len(proof.L))
+
+ utils.ensure(max_length < 32, "At least one proof is too large")
+
+ maxMN = 1 << max_length
+ logN = 6
+ N = 1 << logN
+ tmp = _ensure_dst_key()
+
+ # setup weighted aggregates
+ is_single = len(proofs) == 1 and single_optim # ph4
+ z1 = init_key(ZERO)
+ z3 = init_key(ZERO)
+ m_z4 = vector_dup(ZERO, maxMN) if not is_single else None
+ m_z5 = vector_dup(ZERO, maxMN) if not is_single else None
+ m_y0 = init_key(ZERO)
+ y1 = init_key(ZERO)
+ muex_acc = init_key(ONE)
+
+ Gprec = self._gprec_aux(maxMN)
+ Hprec = self._hprec_aux(maxMN)
+
+ for proof in proofs:
+ M = 1
+ logM = 0
+ while M <= BP_M and M < len(proof.V):
+ logM += 1
+ M = 1 << logM
+
+ utils.ensure(len(proof.L) == 6 + logM, "Proof is not the expected size")
+ MN = M * N
+ weight_y = crypto.encodeint(crypto.random_scalar())
+ weight_z = crypto.encodeint(crypto.random_scalar())
+
+ # Reconstruct the challenges
+ hash_cache = hash_vct_to_scalar(None, proof.V)
+ y = hash_cache_mash(None, hash_cache, proof.A, proof.S)
+ utils.ensure(y != ZERO, "y == 0")
+ z = hash_to_scalar(None, y)
+ copy_key(hash_cache, z)
+ utils.ensure(z != ZERO, "z == 0")
+
+ x = hash_cache_mash(None, hash_cache, z, proof.T1, proof.T2)
+ utils.ensure(x != ZERO, "x == 0")
+ x_ip = hash_cache_mash(None, hash_cache, x, proof.taux, proof.mu, proof.t)
+ utils.ensure(x_ip != ZERO, "x_ip == 0")
+
+ # PAPER LINE 61
+ sc_mulsub(m_y0, proof.taux, weight_y, m_y0)
+ zpow = vector_powers(z, M + 3)
+
+ k = _ensure_dst_key()
+ ip1y = vector_power_sum(y, MN)
+ sc_mulsub(k, zpow[2], ip1y, ZERO)
+ for j in range(1, M + 1):
+ utils.ensure(j + 2 < len(zpow), "invalid zpow index")
+ sc_mulsub(k, zpow.to(j + 2), BP_IP12, k)
+
+ # VERIFY_line_61rl_new
+ sc_muladd(tmp, z, ip1y, k)
+ sc_sub(tmp, proof.t, tmp)
+
+ sc_muladd(y1, tmp, weight_y, y1)
+ weight_y8 = init_key(weight_y)
+ if not proof_v8:
+ weight_y8 = sc_mul(None, weight_y, EIGHT)
+
+ muex = MultiExpSequential(points=[pt for pt in proof.V])
+ for j in range(len(proof.V)):
+ sc_mul(tmp, zpow[j + 2], weight_y8)
+ muex.add_scalar(init_key(tmp))
+
+ sc_mul(tmp, x, weight_y8)
+ muex.add_pair(init_key(tmp), proof.T1)
+
+ xsq = _ensure_dst_key()
+ sc_mul(xsq, x, x)
+
+ sc_mul(tmp, xsq, weight_y8)
+ muex.add_pair(init_key(tmp), proof.T2)
+
+ weight_z8 = init_key(weight_z)
+ if not proof_v8:
+ weight_z8 = sc_mul(None, weight_z, EIGHT)
+
+ muex.add_pair(weight_z8, proof.A)
+ sc_mul(tmp, x, weight_z8)
+ muex.add_pair(init_key(tmp), proof.S)
+
+ multiexp(tmp, muex, False)
+ add_keys(muex_acc, muex_acc, tmp)
+ del (muex)
+
+ # Compute the number of rounds for the inner product
+ rounds = logM + logN
+ utils.ensure(rounds > 0, "Zero rounds")
+
+ # PAPER LINES 21-22
+ # The inner product challenges are computed per round
+ w = _ensure_dst_keyvect(None, rounds)
+ for i in range(rounds):
+ hash_cache_mash(tmp_bf_0, hash_cache, proof.L[i], proof.R[i])
+ w.read(i, tmp_bf_0)
+ utils.ensure(w[i] != ZERO, "w[i] == 0")
+
+ # Basically PAPER LINES 24-25
+ # Compute the curvepoints from G[i] and H[i]
+ yinvpow = init_key(ONE)
+ ypow = init_key(ONE)
+ yinv = invert(None, y)
+ self.gc(61)
+
+ winv = _ensure_dst_keyvect(None, rounds)
+ for i in range(rounds):
+ invert(tmp_bf_0, w.to(i))
+ winv.read(i, tmp_bf_0)
+ self.gc(62)
+
+ g_scalar = _ensure_dst_key()
+ h_scalar = _ensure_dst_key()
+ twoN = self._two_aux(N)
+ for i in range(MN):
+ copy_key(g_scalar, proof.a)
+ sc_mul(h_scalar, proof.b, yinvpow)
+
+ for j in range(rounds - 1, -1, -1):
+ J = len(w) - j - 1
+
+ if (i & (1 << j)) == 0:
+ sc_mul(g_scalar, g_scalar, winv.to(J))
+ sc_mul(h_scalar, h_scalar, w.to(J))
+ else:
+ sc_mul(g_scalar, g_scalar, w.to(J))
+ sc_mul(h_scalar, h_scalar, winv.to(J))
+
+ # Adjust the scalars using the exponents from PAPER LINE 62
+ sc_add(g_scalar, g_scalar, z)
+ utils.ensure(2 + i // N < len(zpow), "invalid zpow index")
+ utils.ensure(i % N < len(twoN), "invalid twoN index")
+ sc_mul(tmp, zpow.to(2 + i // N), twoN.to(i % N))
+ sc_muladd(tmp, z, ypow, tmp)
+ sc_mulsub(h_scalar, tmp, yinvpow, h_scalar)
+
+ if not is_single: # ph4
+ sc_mulsub(m_z4[i], g_scalar, weight_z, m_z4[i])
+ sc_mulsub(m_z5[i], h_scalar, weight_z, m_z5[i])
+ else:
+ sc_mul(tmp, g_scalar, weight_z)
+ sub_keys(muex_acc, muex_acc, scalarmult_key(tmp, Gprec.to(i), tmp))
+
+ sc_mul(tmp, h_scalar, weight_z)
+ sub_keys(muex_acc, muex_acc, scalarmult_key(tmp, Hprec.to(i), tmp))
+
+ if i != MN - 1:
+ sc_mul(yinvpow, yinvpow, yinv)
+ sc_mul(ypow, ypow, y)
+ if i & 15 == 0:
+ self.gc(62)
+
+ del (g_scalar, h_scalar, twoN)
+ self.gc(63)
+
+ sc_muladd(z1, proof.mu, weight_z, z1)
+ muex = MultiExpSequential(
+ point_fnc=lambda i, d: proof.L[i // 2]
+ if i & 1 == 0
+ else proof.R[i // 2]
+ )
+ for i in range(rounds):
+ sc_mul(tmp, w[i], w[i])
+ sc_mul(tmp, tmp, weight_z8)
+ muex.add_scalar(tmp)
+ sc_mul(tmp, winv[i], winv[i])
+ sc_mul(tmp, tmp, weight_z8)
+ muex.add_scalar(tmp)
+
+ acc = multiexp(None, muex, False)
+ add_keys(muex_acc, muex_acc, acc)
+
+ sc_mulsub(tmp, proof.a, proof.b, proof.t)
+ sc_mul(tmp, tmp, x_ip)
+ sc_muladd(z3, tmp, weight_z, z3)
+
+ sc_sub(tmp, m_y0, z1)
+ z3p = sc_sub(None, z3, y1)
+
+ check2 = crypto.encodepoint(
+ crypto.ge25519_double_scalarmult_base_vartime(
+ crypto.decodeint(z3p), crypto.xmr_H(), crypto.decodeint(tmp)
+ )
+ )
+ add_keys(muex_acc, muex_acc, check2)
+
+ if not is_single: # ph4
+ muex = MultiExpSequential(
+ point_fnc=lambda i, d: Gprec.to(i // 2)
+ if i & 1 == 0
+ else Hprec.to(i // 2)
+ )
+ for i in range(maxMN):
+ muex.add_scalar(m_z4[i])
+ muex.add_scalar(m_z5[i])
+ add_keys(muex_acc, muex_acc, multiexp(None, muex, True))
+
+ if muex_acc != ONE:
+ raise ValueError("Verification failure at step 2")
+ return True
diff --git a/src/apps/monero/xmr/credentials.py b/src/apps/monero/xmr/credentials.py
new file mode 100644
index 000000000..b841c42ed
--- /dev/null
+++ b/src/apps/monero/xmr/credentials.py
@@ -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,
+ )
diff --git a/src/apps/monero/xmr/crypto/__init__.py b/src/apps/monero/xmr/crypto/__init__.py
new file mode 100644
index 000000000..9f746eec9
--- /dev/null
+++ b/src/apps/monero/xmr/crypto/__init__.py
@@ -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)
diff --git a/src/apps/monero/xmr/crypto/chacha_poly.py b/src/apps/monero/xmr/crypto/chacha_poly.py
new file mode 100644
index 000000000..7c90995f0
--- /dev/null
+++ b/src/apps/monero/xmr/crypto/chacha_poly.py
@@ -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)
diff --git a/src/apps/monero/xmr/keccak_hasher.py b/src/apps/monero/xmr/keccak_hasher.py
new file mode 100644
index 000000000..0a3dc0001
--- /dev/null
+++ b/src/apps/monero/xmr/keccak_hasher.py
@@ -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)
diff --git a/src/apps/monero/xmr/key_image.py b/src/apps/monero/xmr/key_image.py
new file mode 100644
index 000000000..5522f6685
--- /dev/null
+++ b/src/apps/monero/xmr/key_image.py
@@ -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
diff --git a/src/apps/monero/xmr/mlsag.py b/src/apps/monero/xmr/mlsag.py
new file mode 100644
index 000000000..9aef687cb
--- /dev/null
+++ b/src/apps/monero/xmr/mlsag.py
@@ -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)
diff --git a/src/apps/monero/xmr/mlsag_hasher.py b/src/apps/monero/xmr/mlsag_hasher.py
new file mode 100644
index 000000000..7129ffd75
--- /dev/null
+++ b/src/apps/monero/xmr/mlsag_hasher.py
@@ -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()
diff --git a/src/apps/monero/xmr/monero.py b/src/apps/monero/xmr/monero.py
new file mode 100644
index 000000000..ef4f94a00
--- /dev/null
+++ b/src/apps/monero/xmr/monero.py
@@ -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)
+
+ # , 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)
+ #
+
+ 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
diff --git a/src/apps/monero/xmr/networks.py b/src/apps/monero/xmr/networks.py
new file mode 100644
index 000000000..e9f5faefa
--- /dev/null
+++ b/src/apps/monero/xmr/networks.py
@@ -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])
diff --git a/src/apps/monero/xmr/range_signatures.py b/src/apps/monero/xmr/range_signatures.py
new file mode 100644
index 000000000..1e43a0418
--- /dev/null
+++ b/src/apps/monero/xmr/range_signatures.py
@@ -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]
diff --git a/src/apps/monero/xmr/serialize/__init__.py b/src/apps/monero/xmr/serialize/__init__.py
new file mode 100644
index 000000000..93814207d
--- /dev/null
+++ b/src/apps/monero/xmr/serialize/__init__.py
@@ -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
diff --git a/src/apps/monero/xmr/serialize/base_types.py b/src/apps/monero/xmr/serialize/base_types.py
new file mode 100644
index 000000000..b76c10b72
--- /dev/null
+++ b/src/apps/monero/xmr/serialize/base_types.py
@@ -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
diff --git a/src/apps/monero/xmr/serialize/int_serialize.py b/src/apps/monero/xmr/serialize/int_serialize.py
new file mode 100644
index 000000000..97b17c945
--- /dev/null
+++ b/src/apps/monero/xmr/serialize/int_serialize.py
@@ -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
diff --git a/src/apps/monero/xmr/serialize/message_types.py b/src/apps/monero/xmr/serialize/message_types.py
new file mode 100644
index 000000000..f5cbbd5bf
--- /dev/null
+++ b/src/apps/monero/xmr/serialize/message_types.py
@@ -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 ()
diff --git a/src/apps/monero/xmr/serialize/readwriter.py b/src/apps/monero/xmr/serialize/readwriter.py
new file mode 100644
index 000000000..54cd8ddaa
--- /dev/null
+++ b/src/apps/monero/xmr/serialize/readwriter.py
@@ -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]
diff --git a/src/apps/monero/xmr/serialize_messages/__init__.py b/src/apps/monero/xmr/serialize_messages/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/src/apps/monero/xmr/serialize_messages/base.py b/src/apps/monero/xmr/serialize_messages/base.py
new file mode 100644
index 000000000..2bdf1b115
--- /dev/null
+++ b/src/apps/monero/xmr/serialize_messages/base.py
@@ -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
diff --git a/src/apps/monero/xmr/serialize_messages/ct_keys.py b/src/apps/monero/xmr/serialize_messages/ct_keys.py
new file mode 100644
index 000000000..42a914fbf
--- /dev/null
+++ b/src/apps/monero/xmr/serialize_messages/ct_keys.py
@@ -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))
diff --git a/src/apps/monero/xmr/serialize_messages/tx_ecdh.py b/src/apps/monero/xmr/serialize_messages/tx_ecdh.py
new file mode 100644
index 000000000..27a43c084
--- /dev/null
+++ b/src/apps/monero/xmr/serialize_messages/tx_ecdh.py
@@ -0,0 +1,5 @@
+from apps.monero.xmr.serialize.message_types import MessageType
+
+
+class EcdhTuple(MessageType):
+ __slots__ = ("mask", "amount")
diff --git a/src/apps/monero/xmr/serialize_messages/tx_full.py b/src/apps/monero/xmr/serialize_messages/tx_full.py
new file mode 100644
index 000000000..1c98619b6
--- /dev/null
+++ b/src/apps/monero/xmr/serialize_messages/tx_full.py
@@ -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))
diff --git a/src/apps/monero/xmr/serialize_messages/tx_prefix.py b/src/apps/monero/xmr/serialize_messages/tx_prefix.py
new file mode 100644
index 000000000..0a0ddd0d2
--- /dev/null
+++ b/src/apps/monero/xmr/serialize_messages/tx_prefix.py
@@ -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))
diff --git a/src/apps/monero/xmr/serialize_messages/tx_rsig_bulletproof.py b/src/apps/monero/xmr/serialize_messages/tx_rsig_bulletproof.py
new file mode 100644
index 000000000..0f0d280c6
--- /dev/null
+++ b/src/apps/monero/xmr/serialize_messages/tx_rsig_bulletproof.py
@@ -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),
+ )
diff --git a/src/apps/monero/xmr/types.py b/src/apps/monero/xmr/types.py
new file mode 100644
index 000000000..e3523e65f
--- /dev/null
+++ b/src/apps/monero/xmr/types.py
@@ -0,0 +1,6 @@
+if False:
+ from trezor.crypto import monero as tcry
+ from typing import * # noqa: F401
+
+ Ge25519 = tcry.ge25519
+ Sc25519 = tcry.bignum256modm
diff --git a/src/main.py b/src/main.py
index f4fff50eb..c54ae0838 100644
--- a/src/main.py
+++ b/src/main.py
@@ -14,6 +14,7 @@
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.wallet.boot()
apps.ethereum.boot()
apps.lisk.boot()
+apps.monero.boot()
apps.nem.boot()
apps.stellar.boot()
apps.ripple.boot()
diff --git a/src/trezor/crypto/__init__.py b/src/trezor/crypto/__init__.py
index c7b2f9bfa..7bc581b6a 100644
--- a/src/trezor/crypto/__init__.py
+++ b/src/trezor/crypto/__init__.py
@@ -6,6 +6,7 @@
bip39,
chacha20poly1305,
crc,
+ monero,
nem,
pbkdf2,
random,
diff --git a/src/trezor/messages/MessageType.py b/src/trezor/messages/MessageType.py
index 2355b955d..617006d4e 100644
--- a/src/trezor/messages/MessageType.py
+++ b/src/trezor/messages/MessageType.py
@@ -151,12 +151,10 @@
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
diff --git a/src/trezor/messages/MoneroExportedKeyImage.py b/src/trezor/messages/MoneroExportedKeyImage.py
index 198930912..07ddc5f19 100644
--- a/src/trezor/messages/MoneroExportedKeyImage.py
+++ b/src/trezor/messages/MoneroExportedKeyImage.py
@@ -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),
}
diff --git a/src/trezor/messages/MoneroOutputEntry.py b/src/trezor/messages/MoneroOutputEntry.py
index 12657e13e..ff9616f07 100644
--- a/src/trezor/messages/MoneroOutputEntry.py
+++ b/src/trezor/messages/MoneroOutputEntry.py
@@ -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 @@ def __init__(
def get_fields(cls):
return {
1: ('idx', p.UVarintType, 0),
- 2: ('key', MoneroRctKey, 0),
+ 2: ('key', MoneroRctKeyPublic, 0),
}
diff --git a/src/trezor/messages/MoneroRctKey.py b/src/trezor/messages/MoneroRctKeyPublic.py
similarity index 63%
rename from src/trezor/messages/MoneroRctKey.py
rename to src/trezor/messages/MoneroRctKeyPublic.py
index 9f55bad5c..7bb84290c 100644
--- a/src/trezor/messages/MoneroRctKey.py
+++ b/src/trezor/messages/MoneroRctKeyPublic.py
@@ -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),
}
diff --git a/src/trezor/messages/MoneroTransactionAllInputsSetRequest.py b/src/trezor/messages/MoneroTransactionAllInputsSetRequest.py
index 84870d165..f06c558f8 100644
--- a/src/trezor/messages/MoneroTransactionAllInputsSetRequest.py
+++ b/src/trezor/messages/MoneroTransactionAllInputsSetRequest.py
@@ -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),
- }
diff --git a/src/trezor/messages/MoneroTransactionAllOutSetAck.py b/src/trezor/messages/MoneroTransactionAllOutSetAck.py
index 37d53fffc..6be052ef8 100644
--- a/src/trezor/messages/MoneroTransactionAllOutSetAck.py
+++ b/src/trezor/messages/MoneroTransactionAllOutSetAck.py
@@ -3,7 +3,6 @@
import protobuf as p
from .MoneroRingCtSig import MoneroRingCtSig
-from .MoneroTransactionRsigData import MoneroTransactionRsigData
class MoneroTransactionAllOutSetAck(p.MessageType):
@@ -13,19 +12,19 @@ def __init__(
self,
extra: bytes = None,
tx_prefix_hash: bytes = None,
- rsig_data: MoneroTransactionRsigData = None,
rv: MoneroRingCtSig = None,
+ full_message_hash: bytes = None,
) -> None:
self.extra = extra
self.tx_prefix_hash = tx_prefix_hash
- self.rsig_data = rsig_data
self.rv = rv
+ self.full_message_hash = full_message_hash
@classmethod
def get_fields(cls):
return {
1: ('extra', p.BytesType, 0),
2: ('tx_prefix_hash', p.BytesType, 0),
- 3: ('rsig_data', MoneroTransactionRsigData, 0),
4: ('rv', MoneroRingCtSig, 0),
+ 5: ('full_message_hash', p.BytesType, 0),
}
diff --git a/src/trezor/messages/MoneroTransactionData.py b/src/trezor/messages/MoneroTransactionData.py
index ff3283131..1a19a5886 100644
--- a/src/trezor/messages/MoneroTransactionData.py
+++ b/src/trezor/messages/MoneroTransactionData.py
@@ -26,9 +26,6 @@ def __init__(
fee: int = None,
account: int = None,
minor_indices: List[int] = None,
- is_multisig: bool = None,
- exp_tx_prefix_hash: bytes = None,
- use_tx_keys: List[bytes] = None,
rsig_data: MoneroTransactionRsigData = None,
integrated_indices: List[int] = None,
) -> None:
@@ -42,9 +39,6 @@ def __init__(
self.fee = fee
self.account = account
self.minor_indices = minor_indices if minor_indices is not None else []
- self.is_multisig = is_multisig
- self.exp_tx_prefix_hash = exp_tx_prefix_hash
- self.use_tx_keys = use_tx_keys if use_tx_keys is not None else []
self.rsig_data = rsig_data
self.integrated_indices = integrated_indices if integrated_indices is not None else []
@@ -61,9 +55,6 @@ def get_fields(cls):
8: ('fee', p.UVarintType, 0),
9: ('account', p.UVarintType, 0),
10: ('minor_indices', p.UVarintType, p.FLAG_REPEATED),
- 11: ('is_multisig', p.BoolType, 0),
- 12: ('exp_tx_prefix_hash', p.BytesType, 0),
- 13: ('use_tx_keys', p.BytesType, p.FLAG_REPEATED),
- 14: ('rsig_data', MoneroTransactionRsigData, 0),
- 15: ('integrated_indices', p.UVarintType, p.FLAG_REPEATED),
+ 11: ('rsig_data', MoneroTransactionRsigData, 0),
+ 12: ('integrated_indices', p.UVarintType, p.FLAG_REPEATED),
}
diff --git a/src/trezor/messages/MoneroTransactionFinalAck.py b/src/trezor/messages/MoneroTransactionFinalAck.py
index f01f57a1e..16ec3073a 100644
--- a/src/trezor/messages/MoneroTransactionFinalAck.py
+++ b/src/trezor/messages/MoneroTransactionFinalAck.py
@@ -4,7 +4,7 @@
class MoneroTransactionFinalAck(p.MessageType):
- MESSAGE_WIRE_TYPE = 520
+ MESSAGE_WIRE_TYPE = 518
def __init__(
self,
diff --git a/src/trezor/messages/MoneroTransactionFinalRequest.py b/src/trezor/messages/MoneroTransactionFinalRequest.py
index 43a81035d..c72a09adf 100644
--- a/src/trezor/messages/MoneroTransactionFinalRequest.py
+++ b/src/trezor/messages/MoneroTransactionFinalRequest.py
@@ -4,4 +4,4 @@
class MoneroTransactionFinalRequest(p.MessageType):
- MESSAGE_WIRE_TYPE = 519
+ MESSAGE_WIRE_TYPE = 517
diff --git a/src/trezor/messages/MoneroTransactionInitAck.py b/src/trezor/messages/MoneroTransactionInitAck.py
index ec962c3bf..4c53d71a1 100644
--- a/src/trezor/messages/MoneroTransactionInitAck.py
+++ b/src/trezor/messages/MoneroTransactionInitAck.py
@@ -16,30 +16,15 @@ class MoneroTransactionInitAck(p.MessageType):
def __init__(
self,
- version: int = None,
- status: int = None,
- in_memory: bool = None,
hmacs: List[bytes] = None,
- many_inputs: bool = None,
- many_outputs: bool = None,
rsig_data: MoneroTransactionRsigData = None,
) -> None:
- self.version = version
- self.status = status
- self.in_memory = in_memory
self.hmacs = hmacs if hmacs is not None else []
- self.many_inputs = many_inputs
- self.many_outputs = many_outputs
self.rsig_data = rsig_data
@classmethod
def get_fields(cls):
return {
- 1: ('version', p.UVarintType, 0),
- 2: ('status', p.UVarintType, 0),
- 3: ('in_memory', p.BoolType, 0),
- 4: ('hmacs', p.BytesType, p.FLAG_REPEATED),
- 5: ('many_inputs', p.BoolType, 0),
- 6: ('many_outputs', p.BoolType, 0),
- 7: ('rsig_data', MoneroTransactionRsigData, 0),
+ 1: ('hmacs', p.BytesType, p.FLAG_REPEATED),
+ 2: ('rsig_data', MoneroTransactionRsigData, 0),
}
diff --git a/src/trezor/messages/MoneroTransactionMlsagDoneAck.py b/src/trezor/messages/MoneroTransactionMlsagDoneAck.py
deleted file mode 100644
index e93bf5a8f..000000000
--- a/src/trezor/messages/MoneroTransactionMlsagDoneAck.py
+++ /dev/null
@@ -1,19 +0,0 @@
-# Automatically generated by pb2py
-# fmt: off
-import protobuf as p
-
-
-class MoneroTransactionMlsagDoneAck(p.MessageType):
- MESSAGE_WIRE_TYPE = 516
-
- def __init__(
- self,
- full_message_hash: bytes = None,
- ) -> None:
- self.full_message_hash = full_message_hash
-
- @classmethod
- def get_fields(cls):
- return {
- 1: ('full_message_hash', p.BytesType, 0),
- }
diff --git a/src/trezor/messages/MoneroTransactionMlsagDoneRequest.py b/src/trezor/messages/MoneroTransactionMlsagDoneRequest.py
deleted file mode 100644
index 04fb493c0..000000000
--- a/src/trezor/messages/MoneroTransactionMlsagDoneRequest.py
+++ /dev/null
@@ -1,7 +0,0 @@
-# Automatically generated by pb2py
-# fmt: off
-import protobuf as p
-
-
-class MoneroTransactionMlsagDoneRequest(p.MessageType):
- MESSAGE_WIRE_TYPE = 515
diff --git a/src/trezor/messages/MoneroTransactionRsigData.py b/src/trezor/messages/MoneroTransactionRsigData.py
index c39d50b42..bd5c57e43 100644
--- a/src/trezor/messages/MoneroTransactionRsigData.py
+++ b/src/trezor/messages/MoneroTransactionRsigData.py
@@ -2,8 +2,6 @@
# fmt: off
import protobuf as p
-from .MoneroTransactionDestinationEntry import MoneroTransactionDestinationEntry
-
if __debug__:
try:
from typing import List
@@ -15,45 +13,27 @@ class MoneroTransactionRsigData(p.MessageType):
def __init__(
self,
- version: int = None,
rsig_type: int = None,
offload_type: int = None,
grouping: List[int] = None,
- step: int = None,
- operation: int = None,
- seed: bytes = None,
mask: bytes = None,
- amount: bytes = None,
rsig: bytes = None,
rsig_parts: List[bytes] = None,
- outputs: List[MoneroTransactionDestinationEntry] = None,
) -> None:
- self.version = version
self.rsig_type = rsig_type
self.offload_type = offload_type
self.grouping = grouping if grouping is not None else []
- self.step = step
- self.operation = operation
- self.seed = seed
self.mask = mask
- self.amount = amount
self.rsig = rsig
self.rsig_parts = rsig_parts if rsig_parts is not None else []
- self.outputs = outputs if outputs is not None else []
@classmethod
def get_fields(cls):
return {
- 1: ('version', p.UVarintType, 0),
- 2: ('rsig_type', p.UVarintType, 0),
- 3: ('offload_type', p.UVarintType, 0),
- 4: ('grouping', p.UVarintType, p.FLAG_REPEATED),
- 5: ('step', p.UVarintType, 0),
- 6: ('operation', p.UVarintType, 0),
- 7: ('seed', p.BytesType, 0),
- 8: ('mask', p.BytesType, 0),
- 9: ('amount', p.BytesType, 0),
- 10: ('rsig', p.BytesType, 0),
- 11: ('rsig_parts', p.BytesType, p.FLAG_REPEATED),
- 12: ('outputs', MoneroTransactionDestinationEntry, p.FLAG_REPEATED),
+ 1: ('rsig_type', p.UVarintType, 0),
+ 2: ('offload_type', p.UVarintType, 0),
+ 3: ('grouping', p.UVarintType, p.FLAG_REPEATED),
+ 4: ('mask', p.BytesType, 0),
+ 5: ('rsig', p.BytesType, 0),
+ 6: ('rsig_parts', p.BytesType, p.FLAG_REPEATED),
}
diff --git a/src/trezor/messages/MoneroTransactionSetInputAck.py b/src/trezor/messages/MoneroTransactionSetInputAck.py
index 1ae34c2cd..774587431 100644
--- a/src/trezor/messages/MoneroTransactionSetInputAck.py
+++ b/src/trezor/messages/MoneroTransactionSetInputAck.py
@@ -12,15 +12,15 @@ def __init__(
vini_hmac: bytes = None,
pseudo_out: bytes = None,
pseudo_out_hmac: bytes = None,
- alpha_enc: bytes = None,
- spend_enc: bytes = None,
+ pseudo_out_alpha: bytes = None,
+ spend_key: bytes = None,
) -> None:
self.vini = vini
self.vini_hmac = vini_hmac
self.pseudo_out = pseudo_out
self.pseudo_out_hmac = pseudo_out_hmac
- self.alpha_enc = alpha_enc
- self.spend_enc = spend_enc
+ self.pseudo_out_alpha = pseudo_out_alpha
+ self.spend_key = spend_key
@classmethod
def get_fields(cls):
@@ -29,6 +29,6 @@ def get_fields(cls):
2: ('vini_hmac', p.BytesType, 0),
3: ('pseudo_out', p.BytesType, 0),
4: ('pseudo_out_hmac', p.BytesType, 0),
- 5: ('alpha_enc', p.BytesType, 0),
- 6: ('spend_enc', p.BytesType, 0),
+ 5: ('pseudo_out_alpha', p.BytesType, 0),
+ 6: ('spend_key', p.BytesType, 0),
}
diff --git a/src/trezor/messages/MoneroTransactionSetInputRequest.py b/src/trezor/messages/MoneroTransactionSetInputRequest.py
index f0a9f185f..ff6b747d4 100644
--- a/src/trezor/messages/MoneroTransactionSetInputRequest.py
+++ b/src/trezor/messages/MoneroTransactionSetInputRequest.py
@@ -10,15 +10,12 @@ class MoneroTransactionSetInputRequest(p.MessageType):
def __init__(
self,
- version: int = None,
src_entr: MoneroTransactionSourceEntry = None,
) -> None:
- self.version = version
self.src_entr = src_entr
@classmethod
def get_fields(cls):
return {
- 1: ('version', p.UVarintType, 0),
- 2: ('src_entr', MoneroTransactionSourceEntry, 0),
+ 1: ('src_entr', MoneroTransactionSourceEntry, 0),
}
diff --git a/src/trezor/messages/MoneroTransactionSignInputAck.py b/src/trezor/messages/MoneroTransactionSignInputAck.py
index 66b239cd9..a0feb2289 100644
--- a/src/trezor/messages/MoneroTransactionSignInputAck.py
+++ b/src/trezor/messages/MoneroTransactionSignInputAck.py
@@ -4,19 +4,16 @@
class MoneroTransactionSignInputAck(p.MessageType):
- MESSAGE_WIRE_TYPE = 518
+ MESSAGE_WIRE_TYPE = 516
def __init__(
self,
signature: bytes = None,
- cout: bytes = None,
) -> None:
self.signature = signature
- self.cout = cout
@classmethod
def get_fields(cls):
return {
1: ('signature', p.BytesType, 0),
- 2: ('cout', p.BytesType, 0),
}
diff --git a/src/trezor/messages/MoneroTransactionSignInputRequest.py b/src/trezor/messages/MoneroTransactionSignInputRequest.py
index 70d018fd0..54f9fc36d 100644
--- a/src/trezor/messages/MoneroTransactionSignInputRequest.py
+++ b/src/trezor/messages/MoneroTransactionSignInputRequest.py
@@ -6,7 +6,7 @@
class MoneroTransactionSignInputRequest(p.MessageType):
- MESSAGE_WIRE_TYPE = 517
+ MESSAGE_WIRE_TYPE = 515
def __init__(
self,
@@ -15,16 +15,16 @@ def __init__(
vini_hmac: bytes = None,
pseudo_out: bytes = None,
pseudo_out_hmac: bytes = None,
- alpha_enc: bytes = None,
- spend_enc: bytes = None,
+ pseudo_out_alpha: bytes = None,
+ spend_key: bytes = None,
) -> None:
self.src_entr = src_entr
self.vini = vini
self.vini_hmac = vini_hmac
self.pseudo_out = pseudo_out
self.pseudo_out_hmac = pseudo_out_hmac
- self.alpha_enc = alpha_enc
- self.spend_enc = spend_enc
+ self.pseudo_out_alpha = pseudo_out_alpha
+ self.spend_key = spend_key
@classmethod
def get_fields(cls):
@@ -34,6 +34,6 @@ def get_fields(cls):
3: ('vini_hmac', p.BytesType, 0),
4: ('pseudo_out', p.BytesType, 0),
5: ('pseudo_out_hmac', p.BytesType, 0),
- 6: ('alpha_enc', p.BytesType, 0),
- 7: ('spend_enc', p.BytesType, 0),
+ 6: ('pseudo_out_alpha', p.BytesType, 0),
+ 7: ('spend_key', p.BytesType, 0),
}
diff --git a/src/trezor/utils.py b/src/trezor/utils.py
index 0b505680a..3af4e3c3d 100644
--- a/src/trezor/utils.py
+++ b/src/trezor/utils.py
@@ -63,15 +63,57 @@ def format_ordinal(number):
class HashWriter:
def __init__(self, hashfunc, *hashargs, **hashkwargs):
- self.ctx = hashfunc(*hashargs, **hashkwargs)
+ if callable(hashfunc):
+ self.ctx = hashfunc(*hashargs, **hashkwargs)
+ else:
+ self.ctx = hashfunc
self.buf = bytearray(1) # used in append()
def extend(self, buf: bytearray):
self.ctx.update(buf)
+ def write(self, buf: bytearray): # alias for extend()
+ self.ctx.update(buf)
+
+ async def awrite(self, buf: bytearray): # AsyncWriter interface
+ return self.ctx.update(buf)
+
def append(self, b: int):
self.buf[0] = b
self.ctx.update(self.buf)
def get_digest(self) -> bytes:
return self.ctx.digest()
+
+
+def obj_eq(l, r):
+ """
+ Compares object contents, supports __slots__.
+ """
+ if l.__class__ is not r.__class__:
+ return False
+ if hasattr(l, "__slots__"):
+ return obj_slots_dict(l) == obj_slots_dict(r)
+ else:
+ return l.__dict__ == r.__dict__
+
+
+def obj_repr(o):
+ """
+ Returns a string representation of object, supports __slots__.
+ """
+ if hasattr(o, "__slots__"):
+ d = obj_slots_dict(o)
+ else:
+ d = o.__dict__
+ return "<%s: %s>" % (o.__class__.__name__, d)
+
+
+def obj_slots_dict(o):
+ """
+ Builds dict for o from defined __slots__.
+ """
+ d = {}
+ for f in o.__slots__:
+ d[f] = getattr(o, f, None)
+ return d
diff --git a/tests/run_tests_device_emu_monero.sh b/tests/run_tests_device_emu_monero.sh
new file mode 100755
index 000000000..a8cff51bc
--- /dev/null
+++ b/tests/run_tests_device_emu_monero.sh
@@ -0,0 +1,22 @@
+#!/bin/bash
+
+MICROPYTHON=../build/unix/micropython
+PYOPT=0
+
+# run emulator
+cd ../src
+$MICROPYTHON -O$PYOPT main.py >/dev/null &
+upy_pid=$!
+sleep 1
+
+export TREZOR_PATH=udp:127.0.0.1:21324
+
+# run tests
+cd ..
+
+export EC_BACKEND_FORCE=1
+export EC_BACKEND=1
+python3 -m unittest trezor_monero_test.test_trezor
+error=$?
+kill $upy_pid
+exit $error
diff --git a/tests/test_apps.monero.bulletproof.py b/tests/test_apps.monero.bulletproof.py
new file mode 100644
index 000000000..347cb981a
--- /dev/null
+++ b/tests/test_apps.monero.bulletproof.py
@@ -0,0 +1,440 @@
+from common import *
+
+from apps.monero.xmr import bulletproof as bp, crypto, monero
+from apps.monero.xmr.serialize_messages.tx_rsig_bulletproof import Bulletproof
+
+
+class TestMoneroBulletproof(unittest.TestCase):
+ def test_1(self):
+ pass
+
+ def mask_consistency_check(self, bpi):
+ sv = [crypto.sc_init(123)]
+ gamma = [crypto.sc_init(432)]
+
+ M, logM, aL, aR, V, gamma = bpi.prove_setup(sv, gamma)
+ x = bp._ensure_dst_key()
+ y = bp._ensure_dst_key()
+
+ sL = bpi.sL_vct(64)
+ sR = bpi.sR_vct(64)
+
+ self.assertEqual(sL.to(0, x), sL.to(0, y))
+ self.assertEqual(sL.to(1, x), sL.to(1, y))
+ self.assertEqual(sL.to(63, x), sL.to(63, y))
+ self.assertNotEqual(sL.to(1, x), sL.to(0, y))
+ self.assertNotEqual(sL.to(10, x), sL.to(0, y))
+
+ self.assertEqual(sR.to(0, x), sR.to(0, y))
+ self.assertEqual(sR.to(1, x), sR.to(1, y))
+ self.assertEqual(sR.to(63, x), sR.to(63, y))
+ self.assertNotEqual(sR.to(1, x), sR.to(0, y))
+
+ self.assertNotEqual(sL.to(0, x), sR.to(0, y))
+ self.assertNotEqual(sL.to(1, x), sR.to(1, y))
+ self.assertNotEqual(sL.to(63, x), sR.to(63, y))
+
+ ve1 = bp._ensure_dst_key()
+ ve2 = bp._ensure_dst_key()
+ bpi.vector_exponent(aL, aR, ve1)
+ bpi.vector_exponent(aL, aR, ve2)
+
+ bpi.vector_exponent(sL, sR, ve1)
+ bpi.vector_exponent(sL, sR, ve2)
+ self.assertEqual(ve1, ve2)
+
+ # fmt: off
+ def bproof_1(self):
+ return Bulletproof(
+ V=[
+ unhexlify(b"3c705e1da4bbe43a0535a5ad3a8e6c148fb8c1a4118ba6b65412b2fe6511b261"),
+ ],
+ A=unhexlify(b"7372db75c0d9d409524924fff5dd13e867eb4c5789f3f5cc6ef860be68d5e4e5"),
+ S=unhexlify(b"be8f2d87ace0a528056d567881e74f44817a811e110cdb3890376262a2084ab3"),
+ T1=unhexlify(b"8dfc541c379efbe6000bb2339c3a52288ffa4300fcc0f0f0de777e54b5488160"),
+ T2=unhexlify(b"cf7d046c86c33bea6c5167bb6482c0a31332989dc9493eacc04a07deb6536953"),
+ taux=unhexlify(b"abaaf209cc9a800d933d51bb398b81ee7284efc9c92727066a640fdccc954009"),
+ mu=unhexlify(b"ec743e23abb555dca26164a86614306f117a733fcd395eb8675411cd31915608"),
+ L=[unhexlify(b"0ee1acc28126656eaf0934314a97e1cf2232a13f5636d319a233cedd58b2882f"),
+ unhexlify(b"cc3d2ec5635de569343bea37fc46a93413ae66bf803a4333f427f79f341d1696"),
+ unhexlify(b"518c80669bed0960fd03e802a9e837e1aa4a4910bb5853067447d7d22eaca325"),
+ unhexlify(b"251a586e8e79a5d767b89931e012acdae317c13c434a6f5f121e44b3b59240b2"),
+ unhexlify(b"09b41426e6c9808f6a58ded987cc39936f703f136b50493dd1c92c9b1ec4e7fc"),
+ unhexlify(b"984d1369c3c7f2687eebca26395576810c66623408958efde4f36b0bb63a2475"),
+ ],
+ R=[unhexlify(b"31768a0465315ff0dd1ea2228ae8c34d1474e873a863362feab7b050f29a211a"),
+ unhexlify(b"27d1b2533ed78d3dacc396afa50fa533cffc5d1563b679a4049a482436718d3c"),
+ unhexlify(b"a49388b042c8a4c6526054661fac1706cf450181ec1f9eed005b283614ec7f95"),
+ unhexlify(b"3f053243fe16f8fd302395c125ffedd93831829b13abbb195bf69fc139069de9"),
+ unhexlify(b"5a32d7f7132043d1f0cc8cd88cce94e5241337ed616c35a1d753436b2d1c4a93"),
+ unhexlify(b"bbd7f9b3031cf41b613a9ee726de9693457238b4be6317083d278e00717f8c14"),
+ ],
+ a=unhexlify(b"83d8d128f35aa02fc063792df9f4e9de0d4e58b8c6e7c449a672d6e4286ee309"),
+ b=unhexlify(b"741d679f1dfe749f7d1ede687f8dd48f7fd3b5a52a5e6a453488d5e25b3fff0e"),
+ t=unhexlify(b"88331e9fd7573135016629f337240225f9c0a5b70bad4157ad60d4260feb2b03")
+ )
+
+ def bproof_2(self):
+ return Bulletproof(
+ V=[
+ unhexlify(b"3c705e1da4bbe43a0535a5ad3a8e6c148fb8c1a4118ba6b65412b2fe6511b261"),
+ unhexlify(b"de5b617501a37ff257e05b0cf93041253fdb85126549640891f7471d4ede167c"),
+ ],
+ A=unhexlify(b"447843c57f05fc8d68c5fdc96fe09d3599aacfe9b25e403d67482fdbe8ffbdbb"),
+ S=unhexlify(b"105b0186d1ec9a8e17e3f6f7909317458681275f888f6ac8a891ec3b5d51dfd5"),
+ T1=unhexlify(b"552c8f7b1e382842feb79b982738350b0d7aeed850ac06bc86ca7c99e43fbfcc"),
+ T2=unhexlify(b"2947b12ecc6c1667b0f0233ec1290893c992f655351edfd1ca877f8bcc070fc0"),
+ taux=unhexlify(b"8cceaccd9626c55166e8892fa6a7e200f9db27e3b46619f6c84e20b3c7ab200c"),
+ mu=unhexlify(b"c08a546e487b0c19e1e125c5dda6032bf198fe296d0dff52d58d091737a97b03"),
+ L=[unhexlify(b"4c5f56522c1e239ccc7edd45b6cc03c7ea46c3d521953bf529989f9d5935a01d"),
+ unhexlify(b"ba764db54e1ed9472df5d1527badd51be2a0223695a136d2114be631d135e1a9"),
+ unhexlify(b"7fecaae48171615c9f282c146ade72befc0f88c402a178be133b5f51afd3dbfc"),
+ unhexlify(b"3c66bbea3376133d8c571fced01b98ce96326fe233f311b4faf77564598d2021"),
+ unhexlify(b"1179c7e24a6d7655bff0b5017ccb85b21f39822c6d845cb1894737a33030e17a"),
+ unhexlify(b"461a200a1b5a7194c021faac7cda64a80388cea2ca26330ca06179aab409d6b1"),
+ unhexlify(b"5e5c377a648ac4d5c900a1ea527a9358083aa1c7777085c3ef81d0316ed16b47"),
+ ],
+ R=[unhexlify(b"110ea38dd587c1f53a8211198cd033a982d173c4d1cdbb0873685a37c7126cb5"),
+ unhexlify(b"960d6ef5dd857bb48148b4fb6927468d02f2a6474d535fd571b61c2c9b2b5613"),
+ unhexlify(b"dd6454b5e029fe4ff8f9647be237a68d0de9457e742df9dafe6e20c1f6ead444"),
+ unhexlify(b"ba9e3d1d9758184679283ee611144ed31d242700af13ac543bf5901472686d1a"),
+ unhexlify(b"05db7c85b62d95dd74f56fab6e3eee3b72b01514640601200770869616b123d1"),
+ unhexlify(b"b8b037b10f5647e79c7c5e7f735a554c8fb656037b304bd94383b769095bc17a"),
+ unhexlify(b"43f4bd0bc55b60c73ab73bb5c3f9376165f815364dc97ae62de2447e0b428632"),
+ ],
+ a=unhexlify(b"0f7696d2b23cfd84f9b62ce906458580db6fe73aaba1682e0e17e4cb9dae1b02"),
+ b=unhexlify(b"76541c70a127d08110a4bc09e6c6c6a0104956d089bcc0699f32dc5fde20ff03"),
+ t=unhexlify(b"66b4498e8980dafea640ce36c763367aba1b415c2d469b564c96d718ff009d0a")
+ )
+
+ def bproof_2_invalid(self):
+ return Bulletproof(
+ V=[
+ unhexlify(b"3c705e1da4bbe43a0535a5ad3a8e6c148fb8c1a4118ba6b65412b2fe6511b261"),
+ unhexlify(b"de5b617501a37ff257e05b0cf93041253fdb85126549640891f7471d4ede167c"),
+ ],
+ A=unhexlify(b"447843c57f05fc8d68c5fdc96fe09d3599aacfe9b25e403d67482fdbe8ffbdbb"),
+ S=unhexlify(b"005b0186d1ec9a8e17e3f6f7909317458681275f888f6ac8a891ec3b5d51dfd5"),
+ T1=unhexlify(b"552c8f7b1e382842feb79b982738350b0d7aeed850ac06bc86ca7c99e43fbfcc"),
+ T2=unhexlify(b"2947b12ecc6c1667b0f0233ec1290893c992f655351edfd1ca877f8bcc070fc0"),
+ taux=unhexlify(b"8cceaccd9626c55166e8892fa6a7e200f9db27e3b46619f6c84e20b3c7ab200c"),
+ mu=unhexlify(b"c08a546e487b0c19e1e125c5dda6032bf198fe296d0dff52d58d091737a97b03"),
+ L=[unhexlify(b"4c5f56522c1e239ccc7edd45b6cc03c7ea46c3d521953bf529989f9d5935a01d"),
+ unhexlify(b"ba764db54e1ed9472df5d1527badd51be2a0223695a136d2114be631d135e1a9"),
+ unhexlify(b"7fecaae48171615c9f282c146ade72befc0f88c402a178be133b5f51afd3dbfc"),
+ unhexlify(b"3c66bbea3376133d8c571fced01b98ce96326fe233f311b4faf77564598d2021"),
+ unhexlify(b"1179c7e24a6d7655bff0b5017ccb85b21f39822c6d845cb1894737a33030e17a"),
+ unhexlify(b"461a200a1b5a7194c021faac7cda64a80388cea2ca26330ca06179aab409d6b1"),
+ unhexlify(b"5e5c377a648ac4d5c900a1ea527a9358083aa1c7777085c3ef81d0316ed16b47"),
+ ],
+ R=[unhexlify(b"110ea38dd587c1f53a8211198cd033a982d173c4d1cdbb0873685a37c7126cb5"),
+ unhexlify(b"960d6ef5dd857bb48148b4fb6927468d02f2a6474d535fd571b61c2c9b2b5613"),
+ unhexlify(b"dd6454b5e029fe4ff8f9647be237a68d0de9457e742df9dafe6e20c1f6ead444"),
+ unhexlify(b"ba9e3d1d9758184679283ee611144ed31d242700af13ac543bf5901472686d1a"),
+ unhexlify(b"05db7c85b62d95dd74f56fab6e3eee3b72b01514640601200770869616b123d1"),
+ unhexlify(b"b8b037b10f5647e79c7c5e7f735a554c8fb656037b304bd94383b769095bc17a"),
+ unhexlify(b"43f4bd0bc55b60c73ab73bb5c3f9376165f815364dc97ae62de2447e0b428632"),
+ ],
+ a=unhexlify(b"0f7696d2b23cfd84f9b62ce906458580db6fe73aaba1682e0e17e4cb9dae1b02"),
+ b=unhexlify(b"76541c70a127d08110a4bc09e6c6c6a0104956d089bcc0699f32dc5fde20ff03"),
+ t=unhexlify(b"66b4498e8980dafea640ce36c763367aba1b415c2d469b564c96d718ff009d0a")
+ )
+
+ def bproof_4(self):
+ return Bulletproof(
+ V=[
+ unhexlify(b"8bb0da134d14ad399af3b3ab476afbf3a9ad39c610d770ad86be8f8fcf4d5334"),
+ unhexlify(b"5321769a89359b519df85e8aaf9d310920641a09796b1c07917c505dfea3c638"),
+ unhexlify(b"4b7dfc193c8e717f66f8811aa30ed5aa27cde9f5b64826346c96040be6311256"),
+ unhexlify(b"e3a5474501cef576428521ab71c17676477ea75ca2de0f1950cc62a91831bb4b"),
+ ],
+ A=unhexlify(b"d5be7a928f686ac09eaa8d18c3329e587d6e8e8cc9a35f50a747a128c94da69a"),
+ S=unhexlify(b"35654816c07d7537e1091bbe5768eb5733c986b642aad9e1ecf8e2d9dca5894e"),
+ T1=unhexlify(b"b0fcf5c8e6b23bbcbbc7e31776ba08166b01a3fb22930f871c5afae01c5bfa30"),
+ T2=unhexlify(b"a918af0bfb87b142ef86ef4cebff56e7ff372ac554f5bb50e11ef9cf730eb984"),
+ taux=unhexlify(b"f8596b4f35387c2b7bbda10bc668f4233c1c34ec9ab702e5064476182de38405"),
+ mu=unhexlify(b"5ebde106b2c6096808e359dddaec2d9e0dc558a38f9958fabb60dd90ba3a1701"),
+ L=[unhexlify(b"a98b5961c6988f9a31a9fd982e5e992f0c899edf91ba09d87f254eff45e20c88"),
+ unhexlify(b"81e161c3b3573fcf8f5e365a01b2882b1dbacc1dbf273eba984eb8ae575794e6"),
+ unhexlify(b"0b3c65d81b2d0384aa2d3ec128e880b2385f6c7de942a5f906d84d930f458798"),
+ unhexlify(b"38fc712591ca80d106e0d207a9342d7fea1be529909de7aeb3df1e6e805520f5"),
+ unhexlify(b"8cc71b0aa59c67f1f9c3f0f6f64e8feb3622406a45f9575cd96697fdfce98ba8"),
+ unhexlify(b"8456936b65204ec32bc5e378485d6d7931581cf9f5d734c5af34a3dde67de785"),
+ unhexlify(b"4fca68547aea92ab546e33d43151821b94c153cc045388a4b409276c8c52110b"),
+ unhexlify(b"197f85a00316bee804a89f215b91edb5e259e92b002bf7a410174fc8b5987e6c"),
+ ],
+ R=[unhexlify(b"02a06fa825460b77fb3bdc6724da7849b81ecd98602cd666720235319133673a"),
+ unhexlify(b"bc633534483f4e6a86133281d6d841c81d75e3305785463d55b1991c2e2d5492"),
+ unhexlify(b"c816c4d05b92288d0d6431513bfbcbdefd15e39cfc665ea6445ebb8903811931"),
+ unhexlify(b"27def4cc98f1c7c43aed78968aacc3fb06394ebf305de4495998cd3e6cbb515a"),
+ unhexlify(b"9a54fba6a21aafc95c3e80639558b6608257e3289dc005855b37245f6f5a0d85"),
+ unhexlify(b"495dd5d57df30aff8be48f538142c2c50d04675953286dbd82095cb7e9ec45f7"),
+ unhexlify(b"d504b4927875bf39651c4593a4dc27d78a14ff0ddc46b056c0bcd1d6ab5dce90"),
+ unhexlify(b"80a87eb25f02539fbb44649c477ce0044e7ec8e99410d16242796aad168f6731"),
+ ],
+ a=unhexlify(b"ae3789e27324e3d4ecc48993b83052a8843fdcc67e1e4d30221e2dba4dd3c205"),
+ b=unhexlify(b"4206e54ee16aaba98c43ab34ce7b094c05bc1d5c89c2cfc15436346f808fc305"),
+ t=unhexlify(b"91ac52bd644dd0cf47064c340a0fb7e87f66eee3f9286af0f75a910260f46406")
+ )
+
+ def bproof_8(self):
+ return Bulletproof(
+ V=[
+ unhexlify(b"8968230f6104ecadab81a61b71d7e5d35b62fb5e983ef0fa143f399e1b455556"),
+ unhexlify(b"3ad4c8fc2476f239767b2c98b8adbd613e1d48290577ac2f060e5eae4578bc07"),
+ unhexlify(b"5bfa33de351ec800057b9a94009cbe3c4b2207f8518adf338db39a4a541f99fb"),
+ unhexlify(b"65ad56ed1e1253f3f6d912e46ce76a59f1e0ce76133e94c6fcff06aa8e57847f"),
+ unhexlify(b"0b6c65c33a06a5fe402c735c5e58981e9cc5ed6d7020df746d828b203566010f"),
+ unhexlify(b"eb01d17406d4b71b5b01358c3a8187da02de64cd6a18dcaaff107e1c0310283b"),
+ unhexlify(b"5826183d16cc353b8b07778354b4d5e4bec71c9c915b8db4cd314e1a4fc8515c"),
+ unhexlify(b"cf980756a69c3535f9a52897e13cb3649211bc9870246b8456a55311b9d47b65"),
+ ],
+ A=unhexlify(b"9a8aa683d90464a9a02ab9d002bdaf04d306c271285caa916d0275bfed5786a8"),
+ S=unhexlify(b"6eee7bf4b3b9fd00b0018c4a95d9e0dbdf4a4d8d68891c212a99ae040fad12db"),
+ T1=unhexlify(b"d8b154556661544b0967529ddbb1650d8e82f6c43a2698f4191e36f815c44106"),
+ T2=unhexlify(b"21a987b97cebcc51116dcdffc0576a8d727970bc5e075d4c885c9612ec53f01a"),
+ taux=unhexlify(b"3f7b2ea15183fed911215d05d839907d057f324c01e630bb66dfb7f2e939f601"),
+ mu=unhexlify(b"86440d5b853a4d39b43a05346535f6c62d434d3543eda161c2415f8a62387403"),
+ L=[unhexlify(b"110ddcd1c72e576fa2a7388a31e5632779a15394a8f82c6db4a16aa12ee8c673"),
+ unhexlify(b"0013e290ca6453f4327d79a010d7158588e4df5da07e64913ec460ebf1992729"),
+ unhexlify(b"1f4af7b9e76c84fdd28ed1419f9b7d90f42c87399f8aca81b52dcf4f22dcf4db"),
+ unhexlify(b"2e05fcc9835dac5e6a3124068726fdcfab49e6c5215dcb0d6ccf55befacbcc10"),
+ unhexlify(b"0a8564bb2c7938382541e2f51996eb0d8c9a944a1c4c7abba51938fd3a2498b1"),
+ unhexlify(b"a3b02cdbe3af1bb9f5961cdee787b1a55ce1e083cd4377543e7c11b3aa3a4789"),
+ unhexlify(b"1d3301fe9b7438dcced3ca052a364aa442b1abe189f4013003e7ec8245331e5a"),
+ unhexlify(b"31a0387da1091c18618ffc85ae1c84774ddd3885e6f9a525e108dab92333151a"),
+ unhexlify(b"550e00179778a332d960438d443caab60b571c878787c3dc90a056cb102c47bb"),
+ ],
+ R=[unhexlify(b"b8d336c3521a854856bd1f78d8c7566f4cfe1441af74f38562bca947d98ab884"),
+ unhexlify(b"b5514c68e2765ee4c39af1c65360a42f76e2538cea04de97e4e5cfb159fa46bc"),
+ unhexlify(b"9f41d7fa770cae09b83600a5852ed36ef3418ec9bca566881046db6ceddaa87a"),
+ unhexlify(b"b9dfcd2f889ccff138b98e84aad4cbbcb1723db0722950b421f0c7f7ca550312"),
+ unhexlify(b"6fdcc16d2f6c202a2c386eb7d3b61dad86b8fe7d2c4d87c73a87fd87fa931828"),
+ unhexlify(b"90aa28db8b75a86c7a867ae8323b5e327086047fe88131230c874fcae818a711"),
+ unhexlify(b"398bfe592022ec1c801e13bd35577d563faa727d37f37daea5977c8abe584d69"),
+ unhexlify(b"7dd85aff7c63f98b65384e0439407db98df9428f0375b7d7581291e519b097ec"),
+ unhexlify(b"27a37d34a9f0fc5e1b7f20256a2b59b19954b52ae39f29870731750dab52bb1f"),
+ ],
+ a=unhexlify(b"de7d5bebe81a3ca0cb151e187e078ce98e7c19cc3f4ba448c2ccadee969a0f0a"),
+ b=unhexlify(b"f8f88ee64bc03c68fa40d855f96b3d9f0cf16e3035aef3a129f76c89196b7207"),
+ t=unhexlify(b"e9f0f74b2efcff21ac16c842e27b79d6615a31873be399d38e257a85bcc7c00b")
+ )
+
+ def bproof_16(self):
+ return Bulletproof(
+ V=[
+ unhexlify(b"fcb9064e19894c5703f49d515ab0e6e98c87f9ec4230f0b898bc22061bb8d39f"),
+ unhexlify(b"507ff564c127cc2beb83c9a539408ffd2ec5f648dda711724bba1a8b79d66e32"),
+ unhexlify(b"dbd4de0ef0184378c483b9e821a6da2d80ed24d0c7444104efa4bbc710ed14ef"),
+ unhexlify(b"8baeb7c9d69946547a8f73cdb0e6fae3134fe5e1734e8bbefe8bb452da1ae59e"),
+ unhexlify(b"7f28196a49a4130b68b5589df47d9a2a08dc27518809016a3753ab2db4d8c453"),
+ unhexlify(b"708ad84f91f702dbcdeee179f7e94328314190935ada0f85eddd5db35d631c8c"),
+ unhexlify(b"3925935ecd1d2bb5fda9a15a822db8128c045b1b9fa017f4913231289329ff41"),
+ unhexlify(b"d75438e7d308bb758e68d6af7d80087755dcffc0a47255ce17c2f653501997ef"),
+ unhexlify(b"dd24a43fc1c31240c7d64248bd100e4ed7cefd9bd80ac05471ce947a71176de6"),
+ unhexlify(b"c0011f8ae31c76f3658eb971bb520cac0d051fee4e5cf3ba833d55e093643f08"),
+ unhexlify(b"c7b7edc4c584a1cc41b079550ce9e6ff7bc781b52d16c0c4b667988f422d66a8"),
+ unhexlify(b"483d8df224106bbb6f45f50becbc70b55bbcbd262c0447a42d16f62ad57c057d"),
+ unhexlify(b"9af16713061f6a43112092a3221a07ab7dd377145dc705611ad03f7cc407626a"),
+ unhexlify(b"74b066e1866043d736bde08a790879e5387838e793fbdfb1e05d11404ad4c08f"),
+ unhexlify(b"add0f5a30bf8111541ce5ab176bebf77f0b470f5ccac57ec5fda1dd5da641dfd"),
+ unhexlify(b"d74989bc336a0557a027ad440683406273ca4d04d4138eac69107074ea7e18ed"),
+ ],
+ A=unhexlify(b"a66ce244c86883c8b8ef5ad8e38b3cb8db00306698813f9858edb226d294e52d"),
+ S=unhexlify(b"34be5df1324bd561fd94695eb4b6e084068f188039831ca81be0e77849b639a7"),
+ T1=unhexlify(b"6a86c12f2c1bd2c2c43193da1a1b4c13e8829c0583318977417a6e1144e3c1e9"),
+ T2=unhexlify(b"f16761fd77f03a3d74854fe33143e2b02274747f27c6a4b45973622a0d1cec73"),
+ taux=unhexlify(b"272decc39da7103cddfa0dc7ea4042ea313ab3f740c9234d060d35db04b9700b"),
+ mu=unhexlify(b"acaa00e33078217cc1ee795e2886b771dded4da964ea8db682b7c4a9039c4906"),
+ L=[unhexlify(b"f964f4a86362e788978371d38052e825e22e17e8e52a82a5b61cff4447516d1d"),
+ unhexlify(b"ea7d1b2e10fb0aa15b8bf4be7300c0619036a0846bc0ae4ef62eca61fd2545f3"),
+ unhexlify(b"d4cdfb68a899503f317edd6050da54e85a2979c7b145c10c76f69899a1dee450"),
+ unhexlify(b"9c56aadad7366addf6c7ea3e39bde810056dd59ee9a6020109c92e8939734583"),
+ unhexlify(b"56685894507fdbb994ff007f94ac16ad5b5d7e4cec2a2de6bbc0c0532b5cb190"),
+ unhexlify(b"023f99b43ae8509446f625241bf263052c141a49f02356ff65d9eeec287ecd05"),
+ unhexlify(b"eee0982bd28d2e85f0712d043879c00c1899de69ab1362cc0ac5369b3b17f32d"),
+ unhexlify(b"8d4f9f498d376fda7b06a5dd95be852b32b65d6e2e38363f3e805477e996eec4"),
+ unhexlify(b"e5d9ee5f8910b67c1cc02232e8bb018f0c39966f76b34aee2ce3441fd2737da2"),
+ unhexlify(b"620ab92c82b294a97815d06548a4ac669228d04551c24d174902db1cbb8f10aa"),
+ ],
+ R=[unhexlify(b"a7b55c962351bd40dffe898a5192a3f05a6a89a0e3e61d6a2c4af27640c9cafe"),
+ unhexlify(b"010a1259e188677332b3e3b60030f9b8fe57ff95d4ff4a3c110a147f26a0bf07"),
+ unhexlify(b"891a65627a950b55428d86e2face1543b3297ec5d13839d7de684d92a9c7626b"),
+ unhexlify(b"382e4354aad61f42223c9ff903aee83557716130bb159645ac48e8d27485feb9"),
+ unhexlify(b"e8cd6098fdf7fba0f03db32e0fd41b549bfeaf489bfccf72c391fba9d5046737"),
+ unhexlify(b"ed15099d4d39a73dc48a338246828c6ce60c8f68b648a0d205bf65b3d5a3623b"),
+ unhexlify(b"8d1deb97bbfb33bec7dda8f56428a588a92512ee41ebdcb7b2f7631fc45d1a12"),
+ unhexlify(b"35bd424cef108222b900b58078b90387a65c70f3fd1e1367dd827ee172f393fd"),
+ unhexlify(b"63ecb848be5fab31d355c976876abd892cdc9d0ebc90e90cd8dab46e7f417740"),
+ unhexlify(b"9189a79c56400ac08d8821d283d94f1f5ebebaf6f9660a62c995fccb192431d6"),
+ ],
+ a=unhexlify(b"e43b7890911f413e0d870f099961da40d8e053d9ddd21a56f7eb3308828dbc04"),
+ b=unhexlify(b"dfea0fe39d9a7c5497fd01e92fc7fa8b39cda75b340322f77e0cac15194aa007"),
+ t=unhexlify(b"0de43b393686af8dd0d89f4832a2995cda14e6288de9ecd2b4bf2fa39baba408")
+ )
+ # fmt: on
+
+ def test_masks(self):
+ bpi = bp.BulletProofBuilder()
+ self.mask_consistency_check(bpi)
+
+ # Randomized masks
+ bpi.use_det_masks = False
+ self.mask_consistency_check(bpi)
+
+ def test_verify_testnet(self):
+ bpi = bp.BulletProofBuilder()
+
+ # fmt: off
+ bp_proof = Bulletproof(
+ V=[bytes(
+ [0x67, 0x54, 0xbf, 0x40, 0xcb, 0x45, 0x63, 0x0d, 0x4b, 0xea, 0x08, 0x9e, 0xd7, 0x86, 0xec, 0x3c, 0xe5,
+ 0xbd, 0x4e, 0xed, 0x8f, 0xf3, 0x25, 0x76, 0xae, 0xca, 0xb8, 0x9e, 0xf2, 0x5e, 0x41, 0x16])],
+ A=bytes(
+ [0x96, 0x10, 0x17, 0x66, 0x87, 0x7e, 0xef, 0x97, 0xb3, 0x82, 0xfb, 0x8e, 0x0c, 0x2a, 0x93, 0x68, 0x9e,
+ 0x05, 0x22, 0x07, 0xe3, 0x30, 0x94, 0x20, 0x58, 0x6f, 0x5d, 0x01, 0x6d, 0x4e, 0xd5, 0x88]),
+ S=bytes(
+ [0x50, 0x51, 0x38, 0x32, 0x96, 0x20, 0x7c, 0xc9, 0x60, 0x4d, 0xac, 0x7c, 0x7c, 0x21, 0xf9, 0xad, 0x1c,
+ 0xc2, 0x2d, 0xee, 0x88, 0x7b, 0xa2, 0xe2, 0x61, 0x81, 0x46, 0xf5, 0x99, 0xc3, 0x12, 0x57]),
+ T1=bytes(
+ [0x1a, 0x7d, 0x06, 0x51, 0x41, 0xe6, 0x12, 0xbe, 0xad, 0xd7, 0x68, 0x60, 0x85, 0xfc, 0xc4, 0x86, 0x0b,
+ 0x39, 0x4b, 0x06, 0xf7, 0xca, 0xb3, 0x29, 0xdf, 0x1d, 0xbf, 0x96, 0x5f, 0xbe, 0x8c, 0x87]),
+ T2=bytes(
+ [0x57, 0xae, 0x91, 0x04, 0xfa, 0xac, 0xf3, 0x73, 0x75, 0xf2, 0x83, 0xd6, 0x9a, 0xcb, 0xef, 0xe4, 0xfc,
+ 0xe5, 0x37, 0x55, 0x52, 0x09, 0xb5, 0x60, 0x6d, 0xab, 0x46, 0x85, 0x01, 0x23, 0x9e, 0x47]),
+ taux=bytes(
+ [0x44, 0x7a, 0x87, 0xd9, 0x5f, 0x1b, 0x17, 0xed, 0x53, 0x7f, 0xc1, 0x4f, 0x91, 0x9b, 0xca, 0x68, 0xce,
+ 0x20, 0x43, 0xc0, 0x88, 0xf1, 0xdf, 0x12, 0x7b, 0xd7, 0x7f, 0xe0, 0x27, 0xef, 0xef, 0x0d]),
+ mu=bytes(
+ [0x32, 0xf9, 0xe4, 0xe1, 0xc2, 0xd8, 0xe4, 0xb0, 0x0d, 0x49, 0xd1, 0x02, 0xbc, 0xcc, 0xf7, 0xa2, 0x5a,
+ 0xc7, 0x28, 0xf3, 0x05, 0xb5, 0x64, 0x2e, 0xde, 0xcf, 0x01, 0x61, 0xb8, 0x62, 0xfb, 0x0d]),
+ L=[
+ bytes([0xde, 0x71, 0xca, 0x09, 0xf9, 0xd9, 0x1f, 0xa2, 0xae, 0xdf, 0x39, 0x49, 0x04, 0xaa, 0x6b, 0x58,
+ 0x67, 0x9d, 0x61, 0xa6, 0xfa, 0xec, 0x81, 0xf6, 0x4c, 0x15, 0x09, 0x9d, 0x10, 0x21, 0xff, 0x39]),
+ bytes([0x90, 0x47, 0xbf, 0xf0, 0x1f, 0x72, 0x47, 0x4e, 0xd5, 0x58, 0xfb, 0xc1, 0x16, 0x43, 0xb7, 0xd8,
+ 0xb1, 0x00, 0xa4, 0xa3, 0x19, 0x9b, 0xda, 0x5b, 0x27, 0xd3, 0x6c, 0x5a, 0x87, 0xf8, 0xf0, 0x28]),
+ bytes([0x03, 0x45, 0xef, 0x57, 0x19, 0x8b, 0xc7, 0x38, 0xb7, 0xcb, 0x9c, 0xe7, 0xe8, 0x23, 0x27, 0xbb,
+ 0xd3, 0x54, 0xcb, 0x38, 0x3c, 0x24, 0x8a, 0x60, 0x11, 0x20, 0x92, 0x99, 0xec, 0x35, 0x71, 0x9f]),
+ bytes([0x7a, 0xb6, 0x36, 0x42, 0x36, 0x83, 0xf3, 0xa6, 0xc1, 0x24, 0xc5, 0x63, 0xb0, 0x4c, 0x8b, 0xef,
+ 0x7c, 0x77, 0x25, 0x83, 0xa8, 0xbb, 0x8b, 0x57, 0x75, 0x1c, 0xb6, 0xd7, 0xca, 0xc9, 0x0d, 0x78]),
+ bytes([0x9d, 0x79, 0x66, 0x21, 0x64, 0x72, 0x97, 0x08, 0xa0, 0x5a, 0x94, 0x5a, 0x94, 0x7b, 0x11, 0xeb,
+ 0x4e, 0xe9, 0x43, 0x2f, 0x08, 0xa2, 0x57, 0xa5, 0xd5, 0x99, 0xb0, 0xa7, 0xde, 0x78, 0x80, 0xb7]),
+ bytes([0x9f, 0x88, 0x5c, 0xa5, 0xeb, 0x08, 0xef, 0x1a, 0xcf, 0xbb, 0x1d, 0x04, 0xc5, 0x47, 0x24, 0x37,
+ 0x49, 0xe4, 0x4e, 0x9c, 0x5d, 0x56, 0xd0, 0x97, 0xfd, 0x8a, 0xe3, 0x23, 0x1d, 0xab, 0x16, 0x03]),
+ ],
+ R=[
+ bytes([0xae, 0x89, 0xeb, 0xa8, 0x5b, 0xd5, 0x65, 0xd6, 0x9f, 0x2a, 0xfd, 0x04, 0x66, 0xad, 0xb1, 0xf3,
+ 0x5e, 0xf6, 0x60, 0xa7, 0x26, 0x94, 0x3b, 0x72, 0x5a, 0x5c, 0x80, 0xfa, 0x0f, 0x75, 0x48, 0x27]),
+ bytes([0xc9, 0x1a, 0x61, 0x70, 0x6d, 0xea, 0xea, 0xb2, 0x42, 0xff, 0x27, 0x3b, 0x8e, 0x94, 0x07, 0x75,
+ 0x40, 0x7d, 0x33, 0xde, 0xfc, 0xbd, 0x53, 0xa0, 0x2a, 0xf9, 0x0c, 0x36, 0xb0, 0xdd, 0xbe, 0x8d]),
+ bytes([0xb7, 0x39, 0x7a, 0x0e, 0xa1, 0x42, 0x0f, 0x94, 0x62, 0x24, 0xcf, 0x54, 0x75, 0xe3, 0x0b, 0x0f,
+ 0xfb, 0xcb, 0x67, 0x7b, 0xbc, 0x98, 0x36, 0x01, 0x9f, 0x73, 0xa0, 0x70, 0xa1, 0x7e, 0xf0, 0xcf]),
+ bytes([0x40, 0x06, 0xd4, 0xfa, 0x22, 0x7c, 0x82, 0xbf, 0xe8, 0xe0, 0x35, 0x13, 0x28, 0xa2, 0xb9, 0x51,
+ 0xa3, 0x37, 0x34, 0xc0, 0xa6, 0x43, 0xd6, 0xb7, 0x7a, 0x40, 0xae, 0xf9, 0x36, 0x0e, 0xe3, 0xcc]),
+ bytes([0x88, 0x38, 0x64, 0xe9, 0x63, 0xe3, 0x33, 0xd9, 0xf6, 0xca, 0x47, 0xc4, 0xc7, 0x36, 0x70, 0x01,
+ 0xd2, 0xe4, 0x8c, 0x9f, 0x25, 0xc2, 0xce, 0xcf, 0x81, 0x89, 0x4f, 0x24, 0xcb, 0xb8, 0x40, 0x73]),
+ bytes([0xdc, 0x35, 0x65, 0xed, 0x6b, 0xb0, 0xa7, 0x1a, 0x1b, 0xf3, 0xd6, 0xfb, 0x47, 0x00, 0x48, 0x00,
+ 0x20, 0x6d, 0xd4, 0xeb, 0xff, 0xb9, 0xdc, 0x43, 0x30, 0x8a, 0x90, 0xfe, 0x43, 0x74, 0x75, 0x68]),
+ ],
+ a=bytes(
+ [0xb4, 0x8e, 0xc2, 0x31, 0xce, 0x05, 0x9a, 0x7a, 0xbc, 0x82, 0x8c, 0x30, 0xb3, 0xe3, 0x80, 0x86, 0x05,
+ 0xb8, 0x4c, 0x93, 0x9a, 0x8e, 0xce, 0x39, 0x0f, 0xb6, 0xee, 0x28, 0xf6, 0x7e, 0xd5, 0x07]),
+ b=bytes(
+ [0x47, 0x10, 0x62, 0xc2, 0xad, 0xc7, 0xe2, 0xc9, 0x14, 0x6f, 0xf4, 0xd1, 0xfe, 0x52, 0xa9, 0x1a, 0xe4,
+ 0xb6, 0xd0, 0x25, 0x4b, 0x19, 0x80, 0x7c, 0xcd, 0x62, 0x62, 0x1d, 0x97, 0x20, 0x71, 0x0b]),
+ t=bytes(
+ [0x47, 0x06, 0xea, 0x76, 0x8f, 0xdb, 0xa3, 0x15, 0xe0, 0x2c, 0x6b, 0x25, 0xa1, 0xf7, 0x3c, 0xc8, 0x1d,
+ 0x97, 0xa6, 0x52, 0x48, 0x75, 0x37, 0xf9, 0x1e, 0x14, 0xac, 0xb1, 0x2a, 0x34, 0xc6, 0x06])
+ )
+ # fmt: on
+
+ self.assertTrue(bpi.verify_testnet(bp_proof))
+
+ def test_verify(self):
+ bpi = bp.BulletProofBuilder()
+ self.assertTrue(bpi.verify(self.bproof_1()))
+ self.assertTrue(bpi.verify(self.bproof_2()))
+ self.assertTrue(bpi.verify(self.bproof_4()))
+
+ def test_prove_testnet(self):
+ bpi = bp.BulletProofBuilder()
+ val = crypto.sc_init(123)
+ mask = crypto.sc_init(432)
+
+ bp_res = bpi.prove_testnet(val, mask)
+ bpi.verify_testnet(bp_res)
+
+ try:
+ bp_res.S[0] += 1
+ bpi.verify(bp_res)
+ self.fail("Verification should have failed")
+ except:
+ pass
+
+ def test_prove_testnet_2(self):
+ bpi = bp.BulletProofBuilder()
+ val = crypto.sc_init((1 << 30) - 1 + 16)
+ mask = crypto.random_scalar()
+
+ bp_res = bpi.prove_testnet(val, mask)
+ bpi.verify_testnet(bp_res)
+
+ def test_verify_batch_1(self):
+ bpi = bp.BulletProofBuilder()
+ bpi.verify_batch([self.bproof_1()])
+ bpi.verify_batch([self.bproof_2()])
+ bpi.verify_batch([self.bproof_4()])
+ bpi.verify_batch([self.bproof_8()])
+ bpi.verify_batch([self.bproof_16()])
+ with self.assertRaises(Exception):
+ bpi.verify_batch([self.bproof_2_invalid()])
+ with self.assertRaises(Exception):
+ bpi.verify_batch([self.bproof_2_invalid()])
+
+ def test_prove_random_masks(self):
+ bpi = bp.BulletProofBuilder()
+ bpi.use_det_masks = False # trully randomly generated mask vectors
+ val = crypto.sc_init((1 << 30) - 1 + 16)
+ mask = crypto.random_scalar()
+
+ bp_res = bpi.prove(val, mask)
+ bpi.verify(bp_res)
+
+ def test_prove_testnet_random_masks(self):
+ bpi = bp.BulletProofBuilder()
+ bpi.use_det_masks = False # trully randomly generated mask vectors
+ val = crypto.sc_init((1 << 30) - 1 + 16)
+ mask = crypto.random_scalar()
+
+ bp_res = bpi.prove_testnet(val, mask)
+ bpi.verify_testnet(bp_res)
+
+ def ctest_multiexp(self):
+ scalars = [0, 1, 2, 3, 4, 99]
+ point_base = [0, 2, 4, 7, 12, 18]
+ scalar_sc = [crypto.sc_init(x) for x in scalars]
+ points = [crypto.scalarmult_base(crypto.sc_init(x)) for x in point_base]
+
+ muex = bp.MultiExp(scalars=[crypto.encodeint(x) for x in scalar_sc],
+ point_fnc=lambda i, d: crypto.encodepoint(points[i]))
+
+ self.assertEqual(len(muex), len(scalars))
+ res = bp.multiexp(None, muex)
+ res2 = bp.vector_exponent_custom(
+ A=bp.KeyVEval(3, lambda i, d: crypto.encodepoint_into(crypto.scalarmult_base(crypto.sc_init(point_base[i])), d)),
+ B=bp.KeyVEval(3, lambda i, d: crypto.encodepoint_into(crypto.scalarmult_base(crypto.sc_init(point_base[3+i])), d)),
+ a=bp.KeyVEval(3, lambda i, d: crypto.encodeint_into(crypto.sc_init(scalars[i]), d),),
+ b=bp.KeyVEval(3, lambda i, d: crypto.encodeint_into(crypto.sc_init(scalars[i+3]), d)),
+ )
+ self.assertEqual(res, res2)
+
+ def test_prove_batch(self):
+ bpi = bp.BulletProofBuilder()
+ sv = [crypto.sc_init(123), crypto.sc_init(768)]
+ gamma = [crypto.sc_init(456), crypto.sc_init(901)]
+ proof = bpi.prove_batch(sv, gamma)
+ bpi.verify_batch([proof])
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/tests/test_apps.monero.crypto.py b/tests/test_apps.monero.crypto.py
new file mode 100644
index 000000000..fb589fba2
--- /dev/null
+++ b/tests/test_apps.monero.crypto.py
@@ -0,0 +1,238 @@
+from common import *
+
+from apps.monero.xmr import crypto, monero
+from apps.monero.xmr.addresses import encode_addr
+from apps.monero.xmr.credentials import AccountCreds
+from apps.monero.xmr.networks import NetworkTypes, net_version
+
+
+class TestMoneroCrypto(unittest.TestCase):
+ def test_encoding(self):
+ point = unhexlify(
+ b"2486224797d05cae3cba4be043be2db0df381f3f19cfa113f86ab38e3d8d2bd0"
+ )
+ self.assertEqual(point, crypto.encodepoint(crypto.decodepoint(point)))
+ self.assertTrue(
+ crypto.point_eq(
+ crypto.decodepoint(point),
+ crypto.decodepoint(crypto.encodepoint(crypto.decodepoint(point))),
+ )
+ )
+
+ def test_scalarmult_base(self):
+ scalar = crypto.decodeint(
+ unhexlify(
+ b"a0eea49140a3b036da30eacf64bd9d56ce3ef68ba82ef13571ec511edbcf8303"
+ )
+ )
+ exp = unhexlify(
+ b"16bb4a3c44e2ced511fc0d4cd86b13b3af21efc99fb0356199fac489f2544c09"
+ )
+
+ res = crypto.scalarmult_base(scalar)
+ self.assertEqual(exp, crypto.encodepoint(res))
+ self.assertTrue(crypto.point_eq(crypto.decodepoint(exp), res))
+
+ scalar = crypto.decodeint(
+ unhexlify(
+ b"fd290dce39f781aebbdbd24584ed6d48bd300de19d9c3decfda0a6e2c6751d0f"
+ )
+ )
+ exp = unhexlify(
+ b"123daf90fc26f13c6529e6b49bfed498995ac383ef19c0db6771143f24ba8dd5"
+ )
+
+ res = crypto.scalarmult_base(scalar)
+ self.assertEqual(exp, crypto.encodepoint(res))
+ self.assertTrue(crypto.point_eq(crypto.decodepoint(exp), res))
+
+ def test_scalarmult(self):
+ priv = unhexlify(
+ b"3482fb9735ef879fcae5ec7721b5d3646e155c4fb58d6cc11c732c9c9b76620a"
+ )
+ pub = unhexlify(
+ b"2486224797d05cae3cba4be043be2db0df381f3f19cfa113f86ab38e3d8d2bd0"
+ )
+ exp = unhexlify(
+ b"adcd1f5881f46f254900a03c654e71950a88a0236fa0a3a946c9b8daed6ef43d"
+ )
+
+ res = crypto.scalarmult(crypto.decodepoint(pub), crypto.decodeint(priv))
+ self.assertEqual(exp, crypto.encodepoint(res))
+ self.assertTrue(crypto.point_eq(crypto.decodepoint(exp), res))
+
+ def test_cn_fast_hash(self):
+ inp = unhexlify(
+ b"259ef2aba8feb473cf39058a0fe30b9ff6d245b42b6826687ebd6b63128aff6405"
+ )
+ res = crypto.cn_fast_hash(inp)
+ self.assertEqual(
+ res,
+ unhexlify(
+ b"86db87b83fb1246efca5f3b0db09ce3fa4d605b0d10e6507cac253dd31a3ec16"
+ ),
+ )
+
+ def test_hash_to_scalar(self):
+ inp = unhexlify(
+ b"259ef2aba8feb473cf39058a0fe30b9ff6d245b42b6826687ebd6b63128aff6405"
+ )
+
+ res = crypto.hash_to_scalar(inp)
+ exp = crypto.decodeint(
+ unhexlify(
+ b"9907925b254e12162609fc0dfd0fef2aa4d605b0d10e6507cac253dd31a3ec06"
+ )
+ )
+ self.assertTrue(crypto.sc_eq(res, exp))
+
+ def test_hash_to_point(self):
+ data = unhexlify(
+ b"42f6835bf83114a1f5f6076fe79bdfa0bd67c74b88f127d54572d3910dd09201"
+ )
+ res = crypto.hash_to_point(data)
+ res_p = crypto.encodepoint(res)
+ self.assertEqual(
+ res_p,
+ unhexlify(
+ b"54863a0464c008acc99cffb179bc6cf34eb1bbdf6c29f7a070a7c6376ae30ab5"
+ ),
+ )
+
+ def test_derivation_to_scalar(self):
+ derivation = unhexlify(
+ b"e720a09f2e3a0bbf4e4ba7ad93653bb296885510121f806acb2a5f9168fafa01"
+ )
+ scalar = unhexlify(
+ b"25d08763414c379aa9cf989cdcb3cadd36bd5193b500107d6bf5f921f18e470e"
+ )
+
+ sc_int = crypto.derivation_to_scalar(crypto.decodepoint(derivation), 0)
+ self.assertEqual(scalar, crypto.encodeint(sc_int))
+
+ def test_generate_key_derivation(self):
+ key_pub = crypto.decodepoint(
+ unhexlify(
+ b"7739c95d3298e2f87362dba9e0e0b3980a692ae8e2f16796b0e382098cd6bd83"
+ )
+ )
+ key_priv = crypto.decodeint(
+ unhexlify(
+ b"3482fb9735ef879fcae5ec7721b5d3646e155c4fb58d6cc11c732c9c9b76620a"
+ )
+ )
+ deriv_exp = unhexlify(
+ b"fa188a45a0e4daccc0e6d4f6f6858fd46392104be74183ec0047e7e9f4eaf739"
+ )
+
+ self.assertEqual(
+ deriv_exp,
+ crypto.encodepoint(crypto.generate_key_derivation(key_pub, key_priv)),
+ )
+
+ def test_h(self):
+ H = unhexlify(
+ b"8b655970153799af2aeadc9ff1add0ea6c7251d54154cfa92c173a0dd39c1f94"
+ )
+ self.assertEqual(crypto.encodepoint(crypto.xmr_H()), H)
+
+ def test_sc_inversion(self):
+ res = crypto.new_scalar()
+ inp = crypto.decodeint(
+ unhexlify(
+ b"3482fb9735ef879fcae5ec7721b5d3646e155c4fb58d6cc11c732c9c9b76620a"
+ )
+ )
+
+ crypto.sc_inv_into(res, inp)
+ self.assertEqual(
+ hexlify(crypto.encodeint(res)),
+ b"bcf365a551e6358f3f281a6241d4a25eded60230b60a1d48c67b51a85e33d70e",
+ )
+
+ def test_wallet_addr(self):
+ addr = encode_addr(
+ net_version(),
+ unhexlify(
+ b"3bec484c5d7f0246af520aab550452b5b6013733feabebd681c4a60d457b7fc1"
+ ),
+ unhexlify(
+ b"2d5918e31d3c003da3c778592c07b398ad6f961a67082a75fd49394d51e69bbe"
+ ),
+ )
+
+ self.assertEqual(
+ addr,
+ b"43tpGG9PKbwCpjRvNLn1jwXPpnacw2uVUcszAtgmDiVcZK4VgHwjJT9BJz1WGF9eMxSYASp8yNMkuLjeQfWqJn3CNWdWfzV",
+ )
+
+ w = AccountCreds.new_wallet(
+ crypto.decodeint(
+ unhexlify(
+ b"4ce88c168e0f5f8d6524f712d5f8d7d83233b1e7a2a60b5aba5206cc0ea2bc08"
+ )
+ ),
+ crypto.decodeint(
+ unhexlify(
+ b"f2644a3dd97d43e87887e74d1691d52baa0614206ad1b0c239ff4aa3b501750a"
+ )
+ ),
+ network_type=NetworkTypes.TESTNET,
+ )
+ self.assertEqual(
+ w.address,
+ b"9vacMKaj8JJV6MnwDzh2oNVdwTLJfTDyNRiB6NzV9TT7fqvzLivH2dB8Tv7VYR3ncn8vCb3KdNMJzQWrPAF1otYJ9cPKpkr",
+ )
+
+ def test_derive_subaddress_public_key(self):
+ out_key = crypto.decodepoint(
+ unhexlify(
+ b"f4efc29da4ccd6bc6e81f52a6f47b2952966442a7efb49901cce06a7a3bef3e5"
+ )
+ )
+ deriv = crypto.decodepoint(
+ unhexlify(
+ b"259ef2aba8feb473cf39058a0fe30b9ff6d245b42b6826687ebd6b63128aff64"
+ )
+ )
+ res = crypto.encodepoint(monero.derive_subaddress_public_key(out_key, deriv, 5))
+ self.assertEqual(
+ res,
+ unhexlify(
+ b"5a10cca900ee47a7f412cd661b29f5ab356d6a1951884593bb170b5ec8b6f2e8"
+ ),
+ )
+
+ def test_get_subaddress_secret_key(self):
+ a = crypto.decodeint(
+ unhexlify(
+ b"4ce88c168e0f5f8d6524f712d5f8d7d83233b1e7a2a60b5aba5206cc0ea2bc08"
+ )
+ )
+ m = monero.get_subaddress_secret_key(secret_key=a, major=0, minor=1)
+ self.assertEqual(
+ crypto.encodeint(m),
+ unhexlify(
+ b"b6ff4d689b95e3310efbf683850c075bcde46361923054e42ef30016b287ff0c"
+ ),
+ )
+
+ def test_public_spend(self):
+ derivation = unhexlify(
+ b"e720a09f2e3a0bbf4e4ba7ad93653bb296885510121f806acb2a5f9168fafa01"
+ )
+ base = unhexlify(
+ b"7d996b0f2db6dbb5f2a086211f2399a4a7479b2c911af307fdc3f7f61a88cb0e"
+ )
+ pkey_ex = unhexlify(
+ b"0846cae7405077b6b7800f0b932c10a186448370b6db318f8c9e13f781dab546"
+ )
+
+ pkey_comp = crypto.derive_public_key(
+ crypto.decodepoint(derivation), 0, crypto.decodepoint(base)
+ )
+ self.assertEqual(pkey_ex, crypto.encodepoint(pkey_comp))
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/tests/test_apps.monero.serializer.py b/tests/test_apps.monero.serializer.py
new file mode 100644
index 000000000..2dc0f2ea1
--- /dev/null
+++ b/tests/test_apps.monero.serializer.py
@@ -0,0 +1,158 @@
+import utest
+from common import *
+from trezor import log, loop, utils
+
+from apps.monero.xmr.serialize.int_serialize import (
+ dump_uint,
+ dump_uvarint,
+ load_uint,
+ load_uvarint,
+)
+from apps.monero.xmr.serialize.readwriter import MemoryReaderWriter
+from apps.monero.xmr.serialize_messages.base import ECPoint
+from apps.monero.xmr.serialize_messages.tx_prefix import (
+ TxinGen,
+ TxinToKey,
+ TxInV,
+ TxOut,
+ TxoutToKey,
+)
+
+
+class XmrTstData(object):
+ """Simple tests data generator"""
+
+ def __init__(self, *args, **kwargs):
+ super(XmrTstData, self).__init__()
+ self.ec_offset = 0
+
+ def reset(self):
+ self.ec_offset = 0
+
+ def generate_ec_key(self, use_offset=True):
+ """
+ Returns test EC key, 32 element byte array
+ :param use_offset:
+ :return:
+ """
+ offset = 0
+ if use_offset:
+ offset = self.ec_offset
+ self.ec_offset += 1
+
+ return bytearray(range(offset, offset + 32))
+
+ def gen_transaction_prefix(self):
+ """
+ Returns test transaction prefix
+ :return:
+ """
+ vin = [
+ TxinToKey(
+ amount=123, key_offsets=[1, 2, 3, 2 ** 76], k_image=bytearray(range(32))
+ ),
+ TxinToKey(
+ amount=456, key_offsets=[9, 8, 7, 6], k_image=bytearray(range(32, 64))
+ ),
+ TxinGen(height=99),
+ ]
+
+ vout = [
+ TxOut(amount=11, target=TxoutToKey(key=bytearray(range(32)))),
+ TxOut(amount=34, target=TxoutToKey(key=bytearray(range(64, 96)))),
+ ]
+
+ msg = TransactionPrefix(
+ version=2, unlock_time=10, vin=vin, vout=vout, extra=list(range(31))
+ )
+ return msg
+
+
+class TestMoneroSerializer(unittest.TestCase):
+ def __init__(self, *args, **kwargs):
+ super(TestMoneroSerializer, self).__init__(*args, **kwargs)
+ self.tdata = XmrTstData()
+
+ def setUp(self):
+ self.tdata.reset()
+
+ def test_varint(self):
+ """
+ Var int
+ :return:
+ """
+ # fmt: off
+ test_nums = [0, 1, 12, 44, 32, 63, 64, 127, 128, 255, 256, 1023, 1024, 8191, 8192,
+ 2**16, 2**16 - 1, 2**32, 2**32 - 1, 2**64, 2**64 - 1, 2**72 - 1, 2**112]
+ # fmt: on
+
+ for test_num in test_nums:
+ writer = MemoryReaderWriter()
+
+ dump_uvarint(writer, test_num)
+ test_deser = load_uvarint(MemoryReaderWriter(writer.get_buffer()))
+
+ self.assertEqual(test_num, test_deser)
+
+ def test_ecpoint(self):
+ """
+ Ec point
+ :return:
+ """
+ ec_data = bytearray(range(32))
+ writer = MemoryReaderWriter()
+
+ ECPoint.dump(writer, ec_data)
+ self.assertTrue(len(writer.get_buffer()), ECPoint.SIZE)
+
+ test_deser = ECPoint.load(MemoryReaderWriter(writer.get_buffer()))
+ self.assertEqual(ec_data, test_deser)
+
+ def test_simple_msg(self):
+ """
+ TxinGen
+ :return:
+ """
+ msg = TxinGen(height=42)
+
+ writer = MemoryReaderWriter()
+ TxinGen.dump(writer, msg)
+ test_deser = TxinGen.load(MemoryReaderWriter(writer.get_buffer()))
+
+ self.assertEqual(msg.height, test_deser.height)
+
+ def test_txin_to_key(self):
+ """
+ TxinToKey
+ :return:
+ """
+ msg = TxinToKey(
+ amount=123, key_offsets=[1, 2, 3, 2 ** 76], k_image=bytearray(range(32))
+ )
+
+ writer = MemoryReaderWriter()
+ TxinToKey.dump(writer, msg)
+ test_deser = TxinToKey.load(MemoryReaderWriter(writer.get_buffer()))
+
+ self.assertEqual(msg.amount, test_deser.amount)
+ self.assertEqual(msg, test_deser)
+
+ def test_txin_variant(self):
+ """
+ TxInV
+ :return:
+ """
+ msg1 = TxinToKey(
+ amount=123, key_offsets=[1, 2, 3, 2 ** 76], k_image=bytearray(range(32))
+ )
+
+ writer = MemoryReaderWriter()
+ TxInV.dump(writer, msg1)
+ test_deser = TxInV.load(MemoryReaderWriter(writer.get_buffer()))
+
+ self.assertEqual(test_deser.__class__, TxinToKey)
+ self.assertEqual(msg1, test_deser)
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/tests/unittest.py b/tests/unittest.py
index 508104188..0c52a08da 100644
--- a/tests/unittest.py
+++ b/tests/unittest.py
@@ -126,6 +126,15 @@ def assertRaises(self, exc, func=None, *args, **kwargs):
return
raise
+ def assertListEqual(self, x, y, msg=''):
+ if len(x) != len(y):
+ if not msg:
+ msg = "List lengths not equal"
+ ensure(False, msg)
+
+ for i in range(len(x)):
+ self.assertEqual(x[i], y[i], msg)
+
def skip(msg):
def _decor(fun):
diff --git a/tools/build_mocks b/tools/build_mocks
index 1fb8fb711..749a7df27 100755
--- a/tools/build_mocks
+++ b/tools/build_mocks
@@ -13,6 +13,16 @@ def split_to_parts(line, mod_desc=None):
global current_indent
global current_class
global current_method
+ global current_package
+
+ if line.startswith("package: "):
+ current_package = line[9:].strip()
+ return
+
+ if line.startswith("mock:global"):
+ current_indent = 0
+ current_class = None
+ return
if line.startswith("class "):
current_class = line[6:].split("(")[0].strip(":")
@@ -38,15 +48,16 @@ def split_to_parts(line, mod_desc=None):
def store_to_file(dest, parts):
for package, line in parts:
- dirpath = os.path.abspath(dest)
- filename = package
+ package = package.replace(".", "/")
+ dirpath = os.path.join(os.path.abspath(dest), os.path.dirname(package))
+ filename = os.path.basename(package) + ".py"
if not os.path.exists(dirpath):
os.makedirs(dirpath)
open(os.path.join(dirpath, "__init__.py"), "w").close()
open(os.path.join(dirpath, ".mock-generated"), "w").close()
- filepath = os.path.join(dirpath, filename + ".py")
+ filepath = os.path.join(dirpath, filename)
if not os.path.exists(filepath):
with open(filepath, "a") as f:
@@ -83,7 +94,9 @@ def build_module(mod_file, dest):
def build_directory(dir, dest):
print("Building mocks for", dir, "to", dest)
- for pkg in sorted([x for x in os.listdir(dir) if os.path.isdir(os.path.join(dir, x))]):
+ for pkg in sorted(
+ [x for x in os.listdir(dir) if os.path.isdir(os.path.join(dir, x))]
+ ):
for mod in sorted(os.listdir(os.path.join(dir, pkg))):
build_module(os.path.join(dir, pkg, mod), dest)
diff --git a/travis-install-libsodium.sh b/travis-install-libsodium.sh
new file mode 100755
index 000000000..3e73215fd
--- /dev/null
+++ b/travis-install-libsodium.sh
@@ -0,0 +1,21 @@
+#!/bin/sh
+# libsodium-dev replacement
+#
+# The purpose of this file is to install libsodium in
+# the Travis CI environment. Outside this environment,
+# you would probably not want to install it like this.
+
+set -e
+export LIBSODIUM_VER="1.0.16"
+
+# check if libsodium is already installed
+if [ ! -d "$HOME/libsodium/lib" ]; then
+ wget "https://github.com/jedisct1/libsodium/releases/download/${LIBSODIUM_VER}/libsodium-${LIBSODIUM_VER}.tar.gz"
+ tar xvfz "libsodium-${LIBSODIUM_VER}.tar.gz"
+ cd "libsodium-${LIBSODIUM_VER}"
+ ./configure --prefix=$HOME/libsodium
+ make
+ make install
+else
+ echo 'Using cached directory.'
+fi
diff --git a/vendor/trezor-common b/vendor/trezor-common
index ea0262266..588f8e03f 160000
--- a/vendor/trezor-common
+++ b/vendor/trezor-common
@@ -1 +1 @@
-Subproject commit ea0262266d9c42315617e9abbcf0b539c373ebc6
+Subproject commit 588f8e03f5ac111adf719f0a437de67481a26aed