diff --git a/.gitignore b/.gitignore
index 1d8824a32..d174f8bb2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -8,3 +8,6 @@ _attic/
build/
build-docker/
emu.config
+venv/
+cmake-build-*
+.idea
diff --git a/SConscript.firmware b/SConscript.firmware
index 9eb7fde8d..5b6550e73 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,11 @@ 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/crypto.c',
+ 'vendor/trezor-crypto/monero/serialize.c',
+ 'vendor/trezor-crypto/monero/range_proof.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 237275fea..0d5670bac 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,11 @@ 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/crypto.c',
+ 'vendor/trezor-crypto/monero/serialize.c',
+ 'vendor/trezor-crypto/monero/range_proof.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-monero.h b/embed/extmod/modtrezorcrypto/modtrezorcrypto-monero.h
new file mode 100644
index 000000000..52e789be0
--- /dev/null
+++ b/embed/extmod/modtrezorcrypto/modtrezorcrypto-monero.h
@@ -0,0 +1,1037 @@
+/*
+ * 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"
+#define RSIG_SIZE 6176
+
+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;
+
+typedef union {
+ xmr_range_sig_t r;
+ unsigned char d[RSIG_SIZE];
+} rsig_union;
+
+
+//
+// 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;
+
+
+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_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_from_scalar(const bignum256modm in){
+ mp_obj_bignum256modm_t *o = m_new_obj(mp_obj_bignum256modm_t);
+ o->base.type = &mod_trezorcrypto_monero_bignum256modm_type;
+ memcpy(&o->p, in, sizeof(bignum256modm));
+ return MP_OBJ_FROM_PTR(o);
+}
+
+STATIC mp_obj_t mp_obj_from_ge25519(const ge25519 * in){
+ mp_obj_ge25519_t *o = m_new_obj(mp_obj_ge25519_t);
+ o->base.type = &mod_trezorcrypto_monero_ge25519_type;
+ memcpy(&o->p, in, sizeof(ge25519));
+ return MP_OBJ_FROM_PTR(o);
+}
+
+STATIC void mp_unpack_ge25519(ge25519 * r, const mp_obj_t arg){
+ mp_buffer_info_t buff;
+ mp_get_buffer_raise(arg, &buff, MP_BUFFER_READ);
+ if (buff.len != 32) {
+ mp_raise_ValueError("Invalid length of the EC point");
+ }
+
+ const int res = ge25519_unpack_vartime(r, buff.buf);
+ if (res != 1){
+ mp_raise_ValueError("Point decoding error");
+ }
+}
+
+STATIC void mp_unpack_scalar(bignum256modm r, const mp_obj_t arg){
+ mp_buffer_info_t buff;
+ mp_get_buffer_raise(arg, &buff, MP_BUFFER_READ);
+ if (buff.len < 32 || buff.len > 64) {
+ mp_raise_ValueError("Invalid length of secret key");
+ }
+ expand256_modm(r, buff.buf, buff.len);
+}
+
+#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");
+ }
+}
+
+//
+// Constructors
+//
+
+
+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) {
+ 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]);
+ } 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__);
+
+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) {
+ 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]);
+ } 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__);
+
+
+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
+//
+
+// init256_modm_r
+STATIC mp_obj_t mod_trezorcrypto_monero_init256_modm(size_t n_args, const mp_obj_t *args){
+ mp_obj_t res = n_args == 2 ? args[0] : mp_obj_new_scalar();
+ const int off = n_args == 2 ? 0 : -1;
+ assert_scalar(res);
+
+ if (n_args == 0) {
+ 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]);
+ } 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);
+
+//int check256_modm
+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);
+
+//int iszero256_modm
+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);
+
+//int eq256_modm
+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);
+
+//int get256_modm_r
+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);
+
+// barrett_reduce256_modm_r, 1arg = lo, 2args = hi, lo, 3args = r, hi, lo
+STATIC mp_obj_t mod_trezorcrypto_monero_reduce256_modm(size_t n_args, const mp_obj_t *args){
+ mp_obj_t res = n_args == 3 ? args[0] : mp_obj_new_scalar();
+ const int off = n_args == 3 ? 0 : -1;
+ const bignum256modm hi_z = {0};
+ const bignum256modm *hi = &hi_z;
+ const bignum256modm *lo = NULL;
+
+ assert_scalar(res);
+ if (n_args > 1){
+ assert_scalar(args[2+off]);
+ lo = &MP_OBJ_C_SCALAR(args[2+off]);
+
+ if (args[1+off] == NULL || MP_OBJ_IS_TYPE(args[1+off], &mp_type_NoneType)){
+ ;
+ } else {
+ assert_scalar(args[1+off]);
+ hi = &MP_OBJ_C_SCALAR(args[1+off]);
+ }
+ } else {
+ assert_scalar(args[1+off]);
+ lo = &MP_OBJ_C_SCALAR(args[1+off]);
+ }
+
+ barrett_reduce256_modm(MP_OBJ_SCALAR(res), *hi, *lo);
+ return res;
+}
+STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_trezorcrypto_monero_reduce256_modm_obj, 1, 3, mod_trezorcrypto_monero_reduce256_modm);
+
+//void add256_modm
+STATIC mp_obj_t mod_trezorcrypto_monero_add256_modm(size_t n_args, const mp_obj_t *args){
+ mp_obj_t res = n_args == 3 ? args[0] : mp_obj_new_scalar();
+ const int off = n_args == 3 ? 0 : -1;
+
+ assert_scalar(res);
+ 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);
+
+//void sub256_modm
+STATIC mp_obj_t mod_trezorcrypto_monero_sub256_modm(size_t n_args, const mp_obj_t *args){
+ mp_obj_t res = n_args == 3 ? args[0] : mp_obj_new_scalar();
+ const int off = n_args == 3 ? 0 : -1;
+
+ assert_scalar(res);
+ 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);
+
+//void mulsub256_modm
+STATIC mp_obj_t mod_trezorcrypto_monero_mulsub256_modm(size_t n_args, const mp_obj_t *args){
+ mp_obj_t res = n_args == 4 ? args[0] : mp_obj_new_scalar();
+ const int off = n_args == 4 ? 0 : -1;
+
+ assert_scalar(res);
+ 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);
+
+//void contract256_modm_r
+STATIC mp_obj_t mod_trezorcrypto_monero_pack256_modm(const mp_obj_t arg){
+ assert_scalar(arg);
+ uint8_t buff[32];
+ contract256_modm(buff, MP_OBJ_C_SCALAR(arg));
+ return mp_obj_new_bytes(buff, 32);
+}
+STATIC MP_DEFINE_CONST_FUN_OBJ_1(mod_trezorcrypto_monero_pack256_modm_obj, mod_trezorcrypto_monero_pack256_modm);
+
+//void contract256_modm_r
+STATIC mp_obj_t mod_trezorcrypto_monero_pack256_modm_into(const mp_obj_t arg, const mp_obj_t buf){
+ assert_scalar(arg);
+ mp_buffer_info_t bufm;
+ mp_get_buffer_raise(buf, &bufm, MP_BUFFER_WRITE);
+ if (bufm.len < 32) {
+ mp_raise_ValueError("Buffer too small");
+ }
+
+ contract256_modm(bufm.buf, MP_OBJ_C_SCALAR(arg));
+ return buf;
+}
+STATIC MP_DEFINE_CONST_FUN_OBJ_2(mod_trezorcrypto_monero_pack256_modm_into_obj, mod_trezorcrypto_monero_pack256_modm_into);
+
+//expand256_modm_r
+STATIC mp_obj_t mod_trezorcrypto_monero_unpack256_modm(size_t n_args, const mp_obj_t *args){
+ mp_obj_t res = n_args == 2 ? args[0] : mp_obj_new_scalar();
+ const int off = n_args == 2 ? 0 : -1;
+ assert_scalar(res);
+ mp_unpack_scalar(MP_OBJ_SCALAR(res), args[1+off]);
+ return res;
+}
+STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_trezorcrypto_monero_unpack256_modm_obj, 1, 2, mod_trezorcrypto_monero_unpack256_modm);
+
+//
+// GE25519 Defs
+//
+
+//void ge25519_set_neutral(ge25519 *r);
+STATIC mp_obj_t mod_trezorcrypto_monero_ge25519_set_neutral(size_t n_args, const mp_obj_t *args){
+ mp_obj_t res = n_args == 1 ? args[0] : mp_obj_new_ge25519();
+ assert_ge25519(res);
+ 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);
+
+//void ge25519_set_xmr_h(ge25519 *r);
+STATIC mp_obj_t mod_trezorcrypto_monero_ge25519_set_xmr_h(size_t n_args, const mp_obj_t *args){
+ mp_obj_t res = n_args == 1 ? args[0] : mp_obj_new_ge25519();
+ assert_ge25519(res);
+ 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);
+
+//int ge25519_check(const ge25519 *r);
+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);
+
+//int ge25519_eq(const ge25519 *a, const ge25519 *b);
+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);
+
+//void ge25519_norm(ge25519 *r, const ge25519 *t);
+STATIC mp_obj_t mod_trezorcrypto_monero_ge25519_norm(size_t n_args, const mp_obj_t *args){
+ mp_obj_t res = n_args == 2 ? args[0] : mp_obj_new_ge25519();
+ mp_obj_t src = n_args == 2 ? args[1] : args[0];
+ assert_ge25519(res);
+ assert_ge25519(src);
+ ge25519_norm(&MP_OBJ_GE25519(res), &MP_OBJ_C_GE25519(src));
+ return res;
+}
+STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_trezorcrypto_monero_ge25519_norm_obj, 1, 2, mod_trezorcrypto_monero_ge25519_norm);
+
+//void ge25519_add(ge25519 *r, const ge25519 *a, const ge25519 *b, unsigned char signbit);
+STATIC mp_obj_t mod_trezorcrypto_monero_ge25519_add(size_t n_args, const mp_obj_t *args){
+ mp_int_t s = 0;
+ int off = 0;
+ mp_obj_t res = args[0];
+
+ if (n_args == 2){ // a, b
+ off = -1;
+ } else if (n_args == 3){ // r, a, b || a, b, s
+ if (mp_obj_is_integer(args[2])){
+ s = mp_obj_get_int(args[2]);
+ off = -1;
+ }
+ } else if (n_args == 4){ // r, a, b, s
+ s = mp_obj_get_int(args[3]);
+ } else {
+ mp_raise_ValueError(NULL);
+ }
+
+ if (off == -1){
+ res = mp_obj_new_ge25519();
+ }
+
+ assert_ge25519(res);
+ 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]), s);
+ return res;
+}
+STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_trezorcrypto_monero_ge25519_add_obj, 3, 4, mod_trezorcrypto_monero_ge25519_add);
+
+//void ge25519_double(ge25519 *r, const ge25519 *p);
+STATIC mp_obj_t mod_trezorcrypto_monero_ge25519_double(size_t n_args, const mp_obj_t *args){
+ mp_obj_t res = n_args == 2 ? args[0] : mp_obj_new_ge25519();
+ mp_obj_t src = n_args == 2 ? args[1] : args[0];
+ assert_ge25519(src);
+ assert_ge25519(res);
+
+ 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);
+
+//void ge25519_mul8(ge25519 *r, const ge25519 *p);
+STATIC mp_obj_t mod_trezorcrypto_monero_ge25519_mul8(size_t n_args, const mp_obj_t *args){
+ mp_obj_t res = n_args == 2 ? args[0] : mp_obj_new_ge25519();
+ mp_obj_t src = n_args == 2 ? args[1] : args[0];
+ assert_ge25519(src);
+ assert_ge25519(res);
+
+ 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);
+
+//void ge25519_double_scalarmult_vartime(ge25519 *r, const ge25519 *p1, const bignum256modm s1, const bignum256modm s2);
+STATIC mp_obj_t mod_trezorcrypto_monero_ge25519_double_scalarmult_vartime(size_t n_args, const mp_obj_t *args){
+ mp_obj_t res = n_args == 4 ? args[0] : mp_obj_new_ge25519();
+ const int off = n_args == 4 ? 0 : -1;
+
+ assert_ge25519(res);
+ 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);
+
+//void ge25519_double_scalarmult_vartime2(ge25519 *r, const ge25519 *p1, const bignum256modm s1, const ge25519 *p2, const bignum256modm s2);
+STATIC mp_obj_t mod_trezorcrypto_monero_ge25519_double_scalarmult_vartime2(size_t n_args, const mp_obj_t *args){
+ mp_obj_t res = n_args == 5 ? args[0] : mp_obj_new_ge25519();
+ const int off = n_args == 5 ? 0 : -1;
+
+ assert_ge25519(res);
+ 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);
+
+//void ge25519_scalarmult_base_wrapper(ge25519 *r, const bignum256modm s);
+STATIC mp_obj_t mod_trezorcrypto_monero_ge25519_scalarmult_base(size_t n_args, const mp_obj_t *args){
+ mp_obj_t res = n_args == 2 ? args[0] : mp_obj_new_ge25519();
+ const int off = n_args == 2 ? 0 : -1;
+ assert_ge25519(res);
+ 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);
+
+//void ge25519_scalarmult_wrapper(ge25519 *r, const ge25519 *P, const bignum256modm a);
+STATIC mp_obj_t mod_trezorcrypto_monero_ge25519_scalarmult(size_t n_args, const mp_obj_t *args){
+ mp_obj_t res = n_args == 3 ? args[0] : mp_obj_new_ge25519();
+ const int off = n_args == 3 ? 0 : -1;
+ assert_ge25519(res);
+ assert_ge25519(args[1+off]);
+
+ if (MP_OBJ_IS_SCALAR(args[2+off])){
+ ge25519_scalarmult_wrapper(&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_wrapper(&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);
+
+//void ge25519_pack(unsigned char r[32], const ge25519 *p)
+STATIC mp_obj_t mod_trezorcrypto_monero_ge25519_pack(const mp_obj_t arg){
+ assert_ge25519(arg);
+ uint8_t buff[32];
+ ge25519_pack(buff, &MP_OBJ_C_GE25519(arg));
+
+ return mp_obj_new_bytes(buff, 32);
+}
+STATIC MP_DEFINE_CONST_FUN_OBJ_1(mod_trezorcrypto_monero_ge25519_pack_obj, mod_trezorcrypto_monero_ge25519_pack);
+
+//void ge25519_pack(unsigned char r[32], const ge25519 *p)
+STATIC mp_obj_t mod_trezorcrypto_monero_ge25519_pack_into(const mp_obj_t arg, const mp_obj_t buf){
+ assert_ge25519(arg);
+ mp_buffer_info_t bufm;
+ mp_get_buffer_raise(buf, &bufm, MP_BUFFER_WRITE);
+ if (bufm.len < 32) {
+ mp_raise_ValueError("Buffer too small");
+ }
+
+ ge25519_pack(bufm.buf, &MP_OBJ_C_GE25519(arg));
+ return buf;
+}
+STATIC MP_DEFINE_CONST_FUN_OBJ_2(mod_trezorcrypto_monero_ge25519_pack_into_obj, mod_trezorcrypto_monero_ge25519_pack_into);
+
+//int ge25519_unpack_vartime(ge25519 *r, const unsigned char *s)
+STATIC mp_obj_t mod_trezorcrypto_monero_ge25519_unpack_vartime(size_t n_args, const mp_obj_t *args){
+ mp_obj_t res = n_args == 2 ? args[0] : mp_obj_new_ge25519();
+ const int off = n_args == 2 ? 0 : -1;
+ assert_ge25519(res);
+ mp_unpack_ge25519(&MP_OBJ_GE25519(res), args[1+off]);
+ return res;
+}
+STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_trezorcrypto_monero_ge25519_unpack_vartime_obj, 1, 2, mod_trezorcrypto_monero_ge25519_unpack_vartime);
+
+//
+// XMR defs
+//
+
+// int xmr_base58_addr_encode_check(uint64_t tag, const uint8_t *data, size_t binsz, char *b58, size_t b58sz);
+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);
+
+// int xmr_base58_addr_decode_check(const char *addr, size_t sz, uint64_t *tag, void *data, size_t datalen);
+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);
+
+// xmr_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 = n_args == 1 ? args[0] : mp_obj_new_scalar();
+ assert_scalar(res);
+ 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);
+
+//xmr_fast_hash
+STATIC mp_obj_t mod_trezorcrypto_monero_xmr_fast_hash(const mp_obj_t arg){
+ uint8_t buff[32];
+ mp_buffer_info_t data;
+ mp_get_buffer_raise(arg, &data, MP_BUFFER_READ);
+ xmr_fast_hash(buff, data.buf, data.len);
+ return mp_obj_new_bytes(buff, 32);
+}
+STATIC MP_DEFINE_CONST_FUN_OBJ_1(mod_trezorcrypto_monero_xmr_fast_hash_obj, mod_trezorcrypto_monero_xmr_fast_hash);
+
+//xmr_hash_to_ec
+STATIC mp_obj_t mod_trezorcrypto_monero_xmr_hash_to_ec(size_t n_args, const mp_obj_t *args){
+ mp_obj_t res = n_args == 2 ? args[0] : mp_obj_new_ge25519();
+ const int off = n_args == 2 ? 0 : -1;
+ mp_buffer_info_t data;
+ assert_ge25519(res);
+ 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);
+
+//xmr_hash_to_scalar
+STATIC mp_obj_t mod_trezorcrypto_monero_xmr_hash_to_scalar(size_t n_args, const mp_obj_t *args){
+ mp_obj_t res = n_args == 2 ? args[0] : mp_obj_new_scalar();
+ const int off = n_args == 2 ? 0 : -1;
+ mp_buffer_info_t data;
+ assert_scalar(res);
+ 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);
+
+//void xmr_derivation_to_scalar(bignum256modm s, const ge25519 * p, uint32_t output_index);
+STATIC mp_obj_t mod_trezorcrypto_monero_xmr_derivation_to_scalar(size_t n_args, const mp_obj_t *args){
+ mp_obj_t res = n_args == 3 ? args[0] : mp_obj_new_scalar();
+ const int off = n_args == 3 ? 0 : -1;
+ assert_scalar(res);
+ 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);
+
+//void xmr_generate_key_derivation(ge25519 * r, const ge25519 * A, const bignum256modm b);
+STATIC mp_obj_t mod_trezorcrypto_monero_xmr_generate_key_derivation(size_t n_args, const mp_obj_t *args){
+ mp_obj_t res = n_args == 3 ? args[0] : mp_obj_new_ge25519();
+ const int off = n_args == 3 ? 0 : -1;
+ assert_ge25519(res);
+ 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);
+
+//void xmr_derive_private_key(bignum256modm s, const ge25519 * deriv, uint32_t idx, const bignum256modm base);
+STATIC mp_obj_t mod_trezorcrypto_monero_xmr_derive_private_key(size_t n_args, const mp_obj_t *args){
+ mp_obj_t res = n_args == 4 ? args[0] : mp_obj_new_scalar();
+ const int off = n_args == 4 ? 0 : -1;
+ assert_scalar(res);
+ 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);
+
+//void xmr_derive_public_key(ge25519 * r, const ge25519 * deriv, uint32_t idx, const ge25519 * base);
+STATIC mp_obj_t mod_trezorcrypto_monero_xmr_derive_public_key(size_t n_args, const mp_obj_t *args){
+ mp_obj_t res = n_args == 4 ? args[0] : mp_obj_new_ge25519();
+ const int off = n_args == 4 ? 0 : -1;
+ assert_ge25519(res);
+ 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);
+
+//void xmr_add_keys2(ge25519 * r, const bignum256modm a, const bignum256modm b, const ge25519 * B);
+STATIC mp_obj_t mod_trezorcrypto_monero_xmr_add_keys2(size_t n_args, const mp_obj_t *args){
+ mp_obj_t res = n_args == 4 ? args[0] : mp_obj_new_ge25519();
+ const int off = n_args == 4 ? 0 : -1;
+ assert_ge25519(res);
+ 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);
+
+//void xmr_add_keys2_vartime(ge25519 * r, const bignum256modm a, const bignum256modm b, const ge25519 * B);
+STATIC mp_obj_t mod_trezorcrypto_monero_xmr_add_keys2_vartime(size_t n_args, const mp_obj_t *args){
+ mp_obj_t res = n_args == 4 ? args[0] : mp_obj_new_ge25519();
+ const int off = n_args == 4 ? 0 : -1;
+ assert_ge25519(res);
+ 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);
+
+//void xmr_add_keys3(ge25519 * r, const bignum256modm a, const ge25519 * A, const bignum256modm b, const ge25519 * B);
+STATIC mp_obj_t mod_trezorcrypto_monero_xmr_add_keys3(size_t n_args, const mp_obj_t *args){
+ mp_obj_t res = n_args == 5 ? args[0] : mp_obj_new_ge25519();
+ const int off = n_args == 5 ? 0 : -1;
+ assert_ge25519(res);
+ 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);
+
+//void xmr_add_keys3_vartime(ge25519 * r, const bignum256modm a, const ge25519 * A, const bignum256modm b, const ge25519 * B);
+STATIC mp_obj_t mod_trezorcrypto_monero_xmr_add_keys3_vartime(size_t n_args, const mp_obj_t *args){
+ mp_obj_t res = n_args == 5 ? args[0] : mp_obj_new_ge25519();
+ const int off = n_args == 5 ? 0 : -1;
+ assert_ge25519(res);
+ 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);
+
+//void xmr_get_subaddress_secret_key(bignum256modm r, uint32_t major, uint32_t minor, const bignum256modm m);
+STATIC mp_obj_t mod_trezorcrypto_monero_xmr_get_subaddress_secret_key(size_t n_args, const mp_obj_t *args){
+ mp_obj_t res = n_args == 4 ? args[0] : mp_obj_new_scalar();
+ const int off = n_args == 4 ? 0 : -1;
+ assert_scalar(res);
+ 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);
+
+//void xmr_gen_c(ge25519 * r, const bignum256modm a, uint64_t amount);
+STATIC mp_obj_t mod_trezorcrypto_monero_xmr_gen_c(size_t n_args, const mp_obj_t *args){
+ mp_obj_t res = n_args == 3 ? args[0] : mp_obj_new_ge25519();
+ const int off = n_args == 3 ? 0 : -1;
+ assert_ge25519(res);
+ 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
+STATIC mp_obj_t mod_trezorcrypto_monero_gen_range_proof(size_t n_args, const mp_obj_t *args) {
+ uint64_t amount;
+ ge25519 C;
+ bignum256modm mask;
+
+ if (sizeof(xmr_range_sig_t) != RSIG_SIZE){
+ mp_raise_ValueError("rsize invalid");
+ }
+
+ mp_buffer_info_t rsig_buff;
+ mp_get_buffer_raise(args[0], &rsig_buff, MP_BUFFER_WRITE);
+ if (rsig_buff.len < RSIG_SIZE){
+ mp_raise_ValueError("rsize buff too small");
+ }
+
+ xmr_range_sig_t * rsig = (xmr_range_sig_t*)rsig_buff.buf;
+ bignum256modm * last_mask = NULL;
+ amount = mp_obj_get_uint64(args[1]);
+ if (n_args > 2 && MP_OBJ_IS_SCALAR(args[2])){
+ last_mask = &MP_OBJ_SCALAR(args[2]);
+ }
+
+ if (n_args > 4){
+ const size_t mem_limit = sizeof(bignum256modm)*64;
+ mp_buffer_info_t buf_ai, buf_alpha;
+ mp_get_buffer_raise(args[3], &buf_ai, MP_BUFFER_WRITE);
+ mp_get_buffer_raise(args[4], &buf_alpha, MP_BUFFER_WRITE);
+ if (buf_ai.len < mem_limit || buf_alpha.len < mem_limit) {
+ mp_raise_ValueError("Buffer too small");
+ }
+
+ xmr_gen_range_sig_ex(rsig, &C, mask, amount, last_mask, buf_ai.buf, buf_alpha.buf);
+ } else {
+ xmr_gen_range_sig(rsig, &C, mask, amount, last_mask);
+ }
+
+ mp_obj_tuple_t *tuple = MP_OBJ_TO_PTR(mp_obj_new_tuple(3, NULL));
+ tuple->items[0] = mp_obj_from_ge25519(&C);
+ tuple->items[1] = mp_obj_from_scalar(mask);
+ tuple->items[2] = args[0];
+ return MP_OBJ_FROM_PTR(tuple);
+}
+STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_trezorcrypto_monero_gen_range_proof_obj, 2, 5, mod_trezorcrypto_monero_gen_range_proof);
+
+
+/// def
+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(mp_obj_t self){
+ mp_obj_hasher_t *o = MP_OBJ_TO_PTR(self);
+ uint8_t out[SHA3_256_DIGEST_LENGTH];
+ Hasher ctx;
+ memcpy(&ctx, &(o->h), sizeof(Hasher));
+
+ xmr_hasher_final(&ctx, out);
+ memset(&ctx, 0, sizeof(SHA3_CTX));
+ return mp_obj_new_bytes(out, sizeof(out));
+}
+STATIC MP_DEFINE_CONST_FUN_OBJ_1(mod_trezorcrypto_monero_hasher_digest_obj, 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_bignum256modm,
+ .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_reduce256_modm), MP_ROM_PTR(&mod_trezorcrypto_monero_reduce256_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_mulsub256_modm), MP_ROM_PTR(&mod_trezorcrypto_monero_mulsub256_modm_obj) },
+ { MP_ROM_QSTR(MP_QSTR_pack256_modm), MP_ROM_PTR(&mod_trezorcrypto_monero_pack256_modm_obj) },
+ { MP_ROM_QSTR(MP_QSTR_pack256_modm_into), MP_ROM_PTR(&mod_trezorcrypto_monero_pack256_modm_into_obj) },
+ { MP_ROM_QSTR(MP_QSTR_unpack256_modm), MP_ROM_PTR(&mod_trezorcrypto_monero_unpack256_modm_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_pack_into), MP_ROM_PTR(&mod_trezorcrypto_monero_ge25519_pack_into_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_norm), MP_ROM_PTR(&mod_trezorcrypto_monero_ge25519_norm_obj) },
+ { MP_ROM_QSTR(MP_QSTR_ge25519_add), MP_ROM_PTR(&mod_trezorcrypto_monero_ge25519_add_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_gen_range_proof), MP_ROM_PTR(&mod_trezorcrypto_monero_gen_range_proof_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.c b/embed/extmod/modtrezorcrypto/modtrezorcrypto.c
index 2ecc86f89..be24f2f59 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/src/apps/monero/__init__.py b/src/apps/monero/__init__.py
new file mode 100644
index 000000000..77c901dae
--- /dev/null
+++ b/src/apps/monero/__init__.py
@@ -0,0 +1,61 @@
+import gc
+
+from trezor import log
+from trezor.messages.MessageType import (
+ DebugMoneroDiagRequest,
+ MoneroGetAddress,
+ MoneroGetWatchKey,
+ MoneroKeyImageSyncRequest,
+ MoneroTransactionSignRequest,
+)
+from trezor.wire import protobuf_workflow, register
+
+
+# persistent state objects
+class Holder(object):
+ def __init__(self):
+ self.ctx_sign = None
+ self.ctx_ki = None
+
+
+STATE = Holder()
+
+
+def dispatch_MoneroGetAddress(*args, **kwargs):
+ from apps.monero.get_address import layout_monero_get_address
+
+ return layout_monero_get_address(*args, **kwargs)
+
+
+def dispatch_MoneroGetWatchKey(*args, **kwargs):
+ from apps.monero.get_watch_only import layout_monero_get_watch_only
+
+ return layout_monero_get_watch_only(*args, **kwargs)
+
+
+def dispatch_MoneroTsxSign(*args, **kwargs):
+ from apps.monero.sign_tx import layout_sign_tx
+
+ return layout_sign_tx(STATE, *args, **kwargs)
+
+
+def dispatch_MoneroKeyImageSync(*args, **kwargs):
+ from apps.monero.key_image_sync import layout_key_image_sync
+
+ return layout_key_image_sync(STATE, *args, **kwargs)
+
+
+def dispatch_MoneroDiag(*args, **kwargs):
+ log.debug(__name__, "----diagnostics")
+ gc.collect()
+ from apps.monero.diag import dispatch_diag
+
+ return dispatch_diag(*args, **kwargs)
+
+
+def boot():
+ register(MoneroGetAddress, protobuf_workflow, dispatch_MoneroGetAddress)
+ register(MoneroGetWatchKey, protobuf_workflow, dispatch_MoneroGetWatchKey)
+ register(MoneroTransactionSignRequest, protobuf_workflow, dispatch_MoneroTsxSign)
+ register(MoneroKeyImageSyncRequest, protobuf_workflow, dispatch_MoneroKeyImageSync)
+ register(DebugMoneroDiagRequest, protobuf_workflow, dispatch_MoneroDiag)
diff --git a/src/apps/monero/controller/__init__.py b/src/apps/monero/controller/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/src/apps/monero/controller/iface.py b/src/apps/monero/controller/iface.py
new file mode 100644
index 000000000..876f8f943
--- /dev/null
+++ b/src/apps/monero/controller/iface.py
@@ -0,0 +1,191 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# Author: Dusan Klinec, ph4r05, 2018
+
+
+class TrezorInterface(object):
+ def __init__(self, ctx=None):
+ self.ctx = ctx
+
+ def gctx(self, ctx):
+ return ctx if ctx is not None else self.ctx
+
+ async def restore_default(self):
+ from trezor import workflow
+
+ workflow.restartdefault()
+
+ async def confirm_transaction(self, tsx_data, creds=None, ctx=None):
+ """
+ Ask for confirmation from user
+ :param tsx_data:
+ :param creds:
+ :param ctx:
+ :return:
+ """
+ from apps.monero.xmr.sub.addr import encode_addr, get_change_addr_idx
+ from apps.monero.xmr.sub.xmr_net import net_version
+
+ outs = tsx_data.outputs
+ change_idx = get_change_addr_idx(outs, tsx_data.change_dts)
+
+ if change_idx is not None:
+ outs = [x for i, x in enumerate(outs) if i != change_idx] + [
+ outs[change_idx]
+ ]
+ change_idx = len(outs) - 1
+
+ from apps.monero import layout
+
+ for idx, dst in enumerate(outs):
+ addr = encode_addr(
+ net_version(creds.network_type),
+ dst.addr.m_spend_public_key,
+ dst.addr.m_view_public_key,
+ )
+ is_change = change_idx and idx == change_idx
+ await layout.require_confirm_tx(
+ self.gctx(ctx), addr.decode("ascii"), dst.amount, is_change
+ )
+
+ await layout.require_confirm_fee(self.gctx(ctx), tsx_data.fee)
+
+ from trezor.ui.text import Text
+ from trezor import ui
+ from trezor import loop
+ from trezor import log
+ from trezor import workflow
+ from trezor.ui import BACKLIGHT_DIM, BACKLIGHT_NORMAL
+
+ await ui.backlight_slide(BACKLIGHT_DIM)
+ slide = ui.backlight_slide(BACKLIGHT_NORMAL)
+ # await ui.backlight_slide(BACKLIGHT_NORMAL)
+
+ text = Text("Signing transaction", ui.ICON_SEND, icon_color=ui.BLUE)
+ text.normal("Signing...")
+
+ try:
+ layout = await layout.simple_text(text, tm=1000)
+ log.debug(__name__, "layout: %s", layout)
+ workflow.closedefault()
+ workflow.onlayoutstart(layout)
+ loop.schedule(slide)
+ # display.clear()
+
+ finally:
+ pass
+ # loop.close(slide)
+ # workflow.onlayoutclose(layout)
+
+ await loop.sleep(500 * 1000)
+ return True
+
+ async def transaction_error(self, *args, **kwargs):
+ """
+ Transaction error
+ :return:
+ """
+ from trezor import ui
+ from trezor.ui.text import Text
+ from apps.monero import layout
+
+ text = Text("Error", ui.ICON_SEND, icon_color=ui.RED)
+ text.normal("Transaction failed")
+
+ await layout.ui_text(text, tm=3 * 1000 * 1000)
+ await self.restore_default()
+
+ async def transaction_signed(self, ctx=None):
+ """
+ Notifies the transaction was completely signed
+ :return:
+ """
+
+ async def transaction_finished(self, ctx=None):
+ """
+ Notifies the transaction has been completed (all data were sent)
+ :return:
+ """
+ from trezor import ui
+ from trezor.ui.text import Text
+ from apps.monero import layout
+
+ text = Text("Success", ui.ICON_SEND, icon_color=ui.GREEN)
+ text.normal("Transaction signed")
+
+ await layout.ui_text(text, tm=3 * 1000 * 1000)
+ await self.restore_default()
+
+ async def transaction_step(self, step, sub_step=None, sub_step_total=None):
+ """
+ Transaction progress
+ :param step:
+ :param sub_step:
+ :param sub_step_total:
+ :return:
+ """
+ from trezor import ui
+ from trezor.ui.text import Text
+ from apps.monero import layout
+
+ 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 = ["Postprocessing..."]
+ elif step == 700:
+ 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)
+
+ await layout.simple_text(text, tm=10 * 1000)
+
+ async def confirm_ki_sync(self, init_msg, ctx=None):
+ """
+ Ask confirmation on key image sync
+ :param init_msg:
+ :return:
+ """
+ from apps.monero import layout
+
+ await layout.require_confirm_keyimage_sync(self.gctx(ctx))
+ return True
+
+ async def ki_error(self, e, ctx=None):
+ """
+ Key image sync error
+ :param e:
+ :return:
+ """
+
+ async def ki_step(self, i, ctx=None):
+ """
+ Key image sync step
+ :param i:
+ :return:
+ """
+
+ async def ki_finished(self, ctx=None):
+ """
+ Ki sync finished
+ :return:
+ """
+
+
+def get_iface(ctx=None):
+ return TrezorInterface(ctx)
diff --git a/src/apps/monero/controller/misc.py b/src/apps/monero/controller/misc.py
new file mode 100644
index 000000000..832b41f9a
--- /dev/null
+++ b/src/apps/monero/controller/misc.py
@@ -0,0 +1,141 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# Author: Dusan Klinec, ph4r05, 2018
+
+
+class TrezorError(Exception):
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ for kw in kwargs:
+ setattr(self, kw, kwargs[kw])
+
+
+class TrezorSecurityError(TrezorError):
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+
+
+class TrezorTxPrefixHashNotMatchingError(TrezorError):
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+
+
+class StdObj(object):
+ def __init__(self, **kwargs):
+ for kw in kwargs:
+ setattr(self, kw, kwargs[kw])
+
+
+def compute_tx_key(spend_key_private, tx_prefix_hash, salt=None, rand_mult=None):
+ """
+
+ :param spend_key_private:
+ :param tx_prefix_hash:
+ :param salt:
+ :param rand_mult:
+ :return:
+ """
+ from apps.monero.xmr import crypto
+
+ if not salt:
+ salt = crypto.random_bytes(32)
+
+ if not rand_mult:
+ rand_mult_num = crypto.random_scalar()
+ rand_mult = crypto.encodeint(rand_mult_num)
+ else:
+ rand_mult_num = crypto.decodeint(rand_mult)
+
+ 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
+
+
+def translate_monero_dest_entry(dst_entry):
+ from apps.monero.xmr.serialize_messages.tx_dest_entry import TxDestinationEntry
+ from apps.monero.xmr.serialize_messages.addr import AccountPublicAddress
+
+ d = TxDestinationEntry()
+ d.amount = dst_entry.amount
+ d.is_subaddress = dst_entry.is_subaddress
+ d.addr = AccountPublicAddress(
+ m_spend_public_key=dst_entry.addr.spend_public_key,
+ m_view_public_key=dst_entry.addr.view_public_key,
+ )
+ return d
+
+
+async def translate_tsx_data(tsx_data):
+ from apps.monero.xmr.tsx_data import TsxData
+
+ tsxd = TsxData()
+ for fld in TsxData.f_specs():
+ fname = fld[0]
+ if hasattr(tsx_data, fname):
+ setattr(tsxd, fname, getattr(tsx_data, fname))
+
+ if tsx_data.change_dts:
+ tsxd.change_dts = translate_monero_dest_entry(tsx_data.change_dts)
+
+ tsxd.outputs = [translate_monero_dest_entry(x) for x in tsx_data.outputs]
+ return tsxd
+
+
+async def parse_msg(bts, msg):
+ from apps.monero.xmr.serialize import xmrserialize
+ from apps.monero.xmr.serialize.readwriter import MemoryReaderWriter
+
+ reader = MemoryReaderWriter(memoryview(bts))
+ ar = xmrserialize.Archive(reader, False)
+ return await ar.message(msg)
+
+
+async def parse_src_entry(bts):
+ from apps.monero.xmr.serialize_messages.tx_src_entry import TxSourceEntry
+
+ return await parse_msg(bts, TxSourceEntry())
+
+
+async def parse_dst_entry(bts):
+ from apps.monero.xmr.serialize_messages.tx_dest_entry import TxDestinationEntry
+
+ return await parse_msg(bts, TxDestinationEntry())
+
+
+async def parse_vini(bts):
+ from apps.monero.xmr.serialize_messages.tx_prefix import TxinToKey
+
+ return await parse_msg(bts, TxinToKey())
+
+
+async def dump_msg(msg, preallocate=None, msg_type=None):
+ from apps.monero.xmr.serialize import xmrserialize
+ from apps.monero.xmr.serialize.readwriter import MemoryReaderWriter
+
+ writer = MemoryReaderWriter(preallocate=preallocate)
+ ar = xmrserialize.Archive(writer, True)
+ await ar.message(msg, msg_type=msg_type)
+ return writer.get_buffer()
+
+
+async def dump_msg_gc(msg, preallocate=None, msg_type=None, del_msg=False):
+ b = await dump_msg(msg, preallocate=preallocate, msg_type=msg_type)
+ if del_msg:
+ del msg
+
+ import gc
+
+ gc.collect()
+ return b
+
+
+def dst_entry_to_stdobj(dst):
+ if dst is None:
+ return None
+
+ addr = StdObj(
+ m_spend_public_key=dst.addr.m_spend_public_key,
+ m_view_public_key=dst.addr.m_view_public_key,
+ )
+ return StdObj(amount=dst.amount, addr=addr, is_subaddress=dst.is_subaddress)
diff --git a/src/apps/monero/controller/wrapper.py b/src/apps/monero/controller/wrapper.py
new file mode 100644
index 000000000..c5bf6e4a5
--- /dev/null
+++ b/src/apps/monero/controller/wrapper.py
@@ -0,0 +1,34 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# Author: Dusan Klinec, ph4r05, 2018
+
+
+MONERO_CURVE = "secp256k1" # 'ed25519-keccak'
+
+
+async def monero_get_creds(ctx, address_n=None, network_type=None):
+ from apps.common import seed
+ from apps.monero.xmr import crypto
+ from apps.monero.xmr import monero
+ from apps.monero.xmr.sub.creds import AccountCreds
+
+ address_n = address_n or ()
+ node = await seed.derive_node(ctx, address_n, MONERO_CURVE)
+
+ key_seed = crypto.cn_fast_hash(node.private_key())
+ keys = monero.generate_monero_keys(
+ key_seed
+ ) # spend_sec, spend_pub, view_sec, view_pub
+
+ creds = AccountCreds.new_wallet(keys[2], keys[0], network_type)
+ return creds
+
+
+def get_interface(ctx):
+ from apps.monero.controller import iface
+
+ return iface.get_iface(ctx)
+
+
+def exc2str(e):
+ return str(e)
diff --git a/src/apps/monero/diag.py b/src/apps/monero/diag.py
new file mode 100644
index 000000000..6b9aaa022
--- /dev/null
+++ b/src/apps/monero/diag.py
@@ -0,0 +1,70 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# Author: Dusan Klinec, ph4r05, 2018
+
+import gc
+import micropython
+import sys
+
+from trezor import log
+
+PREV_MEM = gc.mem_free()
+CUR_MES = 0
+
+
+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 dispatch_diag(ctx, msg, **kwargs):
+ 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()
+
+ return retit()
diff --git a/src/apps/monero/get_address.py b/src/apps/monero/get_address.py
new file mode 100644
index 000000000..18e4018a4
--- /dev/null
+++ b/src/apps/monero/get_address.py
@@ -0,0 +1,18 @@
+from trezor.messages.MoneroAddress import MoneroAddress
+
+from apps.common.display_address import show_address, show_qr
+from apps.monero.controller import wrapper
+
+
+async def layout_monero_get_address(ctx, msg):
+ address_n = msg.address_n or ()
+ creds = await wrapper.monero_get_creds(ctx, address_n, msg.network_type)
+
+ if msg.show_display:
+ while True:
+ if await show_address(ctx, creds.address.decode("ascii")):
+ 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..bf45f8c43
--- /dev/null
+++ b/src/apps/monero/get_watch_only.py
@@ -0,0 +1,15 @@
+from trezor.messages.MoneroGetWatchKey import MoneroGetWatchKey
+from trezor.messages.MoneroWatchKey import MoneroWatchKey
+
+from apps.monero import layout
+from apps.monero.controller import wrapper
+from apps.monero.xmr import crypto
+
+
+async def layout_monero_get_watch_only(ctx, msg: MoneroGetWatchKey):
+ address_n = msg.address_n or ()
+ await layout.require_confirm_watchkey(ctx)
+ creds = await wrapper.monero_get_creds(ctx, address_n, msg.network_type)
+ return MoneroWatchKey(
+ watch_key=crypto.encodeint(creds.view_key_private), address=creds.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..0d459bfab
--- /dev/null
+++ b/src/apps/monero/key_image_sync.py
@@ -0,0 +1,61 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# Author: Dusan Klinec, ph4r05, 2018
+
+import gc
+import micropython
+
+from trezor import log
+
+
+async def layout_key_image_sync(state, ctx, msg):
+ log.debug(
+ __name__,
+ "### KI SYNC. Free: {} Allocated: {}".format(gc.mem_free(), gc.mem_alloc()),
+ )
+ log.debug(__name__, "KI sync state: %s", state.ctx_ki)
+
+ from apps.monero.protocol import key_image_sync
+
+ log.debug(
+ __name__,
+ "### KI sync imported. Free: {} Allocated: {}".format(
+ gc.mem_free(), gc.mem_alloc()
+ ),
+ )
+
+ gc.collect()
+ micropython.mem_info()
+ micropython.mem_info(1)
+
+ try:
+ if msg.init:
+ log.debug(__name__, "ki_sync, init")
+ from apps.monero.controller import iface
+
+ state.ctx_ki = key_image_sync.KeyImageSync(
+ ctx=ctx, iface=iface.get_iface(ctx)
+ )
+ return await state.ctx_ki.init(ctx, msg.init)
+
+ elif msg.step:
+ log.debug(__name__, "ki_sync, step")
+ return await state.ctx_ki.sync(ctx, msg.step)
+
+ elif msg.final_msg:
+ log.debug(__name__, "ki_sync, final")
+ res = await state.ctx_ki.final(ctx, msg.final_msg)
+ state.ctx_ki = None
+ return res
+
+ else:
+ raise ValueError("Unknown error")
+
+ except Exception as e:
+ state.ctx_ki = None
+
+ log.debug(__name__, "KI error, %s: %s", type(e), e)
+
+ from trezor.messages.Failure import Failure
+
+ return Failure()
diff --git a/src/apps/monero/layout.py b/src/apps/monero/layout.py
new file mode 100644
index 000000000..fc7fa821b
--- /dev/null
+++ b/src/apps/monero/layout.py
@@ -0,0 +1,180 @@
+from trezor import ui
+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
+
+
+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_tx_plain(ctx, to, value, is_change=False):
+ content = Text(
+ "Confirm " + ("sending" if not is_change else "change"),
+ ui.ICON_SEND,
+ icon_color=ui.GREEN,
+ )
+ content.bold(format_amount(value))
+ content.normal("to")
+ content.mono(*split_address(to))
+ return await require_confirm(ctx, content, code=ButtonRequestType.SignTx)
+
+
+@ui.layout
+async def tx_dialog(
+ ctx, code, content, cancel_btn, confirm_btn, cancel_style, confirm_style
+):
+ from trezor.messages import MessageType
+ from trezor.messages.ButtonRequest import ButtonRequest
+ from trezor.ui.confirm import ConfirmDialog
+
+ 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,
+ )
+ return await ctx.wait(dialog)
+
+
+async def require_confirm_tx(ctx, to, value, is_change=False):
+ from trezor import loop
+
+ len_addr = (len(to) + 15) // 16
+ if len_addr <= 2:
+ return await require_confirm_tx_plain(ctx, to, value, is_change)
+
+ else:
+ to_chunks = list(split_address(to))
+ from trezor import res, wire
+ from trezor.ui.confirm import (
+ CONFIRMED,
+ CANCELLED,
+ DEFAULT_CANCEL,
+ DEFAULT_CONFIRM,
+ )
+
+ npages = 1 + ((len_addr - 2) + 3) // 4
+ cur_step = 0
+ code = ButtonRequestType.SignTx
+ iback = res.load(ui.ICON_BACK)
+ inext = res.load(ui.ICON_CLICK)
+
+ while cur_step <= npages:
+ text = []
+ if cur_step == 0:
+ text = [
+ ui.BOLD,
+ format_amount(value),
+ ui.NORMAL,
+ "to",
+ ui.MONO,
+ ] + to_chunks[:2]
+ else:
+ off = 4 * (cur_step - 1)
+ cur_chunks = to_chunks[2 + off : 2 + off + 4]
+ ctext = [list(x) for x in zip([ui.MONO] * len(cur_chunks), cur_chunks)]
+ for x in ctext:
+ text += x
+
+ if cur_step == 0:
+ cancel_btn = DEFAULT_CANCEL
+ cancel_style = ui.BTN_CANCEL
+ confirm_btn = inext
+ confirm_style = ui.BTN_DEFAULT
+ elif cur_step + 1 < npages:
+ cancel_btn = iback
+ cancel_style = ui.BTN_DEFAULT
+ confirm_btn = inext
+ confirm_style = ui.BTN_DEFAULT
+ else:
+ cancel_btn = iback
+ cancel_style = ui.BTN_DEFAULT
+ confirm_btn = DEFAULT_CONFIRM
+ confirm_style = ui.BTN_CONFIRM
+
+ conf_text = "Confirm send" if not is_change else "Con. change"
+ content = Text(
+ "%s %d/%d" % (conf_text, cur_step + 1, npages),
+ ui.ICON_SEND,
+ icon_color=ui.GREEN,
+ )
+ content.normal(*text)
+
+ reaction = await tx_dialog(
+ ctx, code, content, cancel_btn, confirm_btn, cancel_style, confirm_style
+ )
+
+ if cur_step == 0 and reaction == CANCELLED:
+ raise wire.ActionCancelled("Cancelled")
+ elif cur_step + 1 < npages and reaction == CONFIRMED:
+ cur_step += 1
+ elif cur_step + 1 >= npages and reaction == CONFIRMED:
+ await loop.sleep(1000 * 1000)
+ return
+ elif reaction == CANCELLED:
+ cur_step -= 1
+ elif reaction == CONFIRMED:
+ cur_step += 1
+
+
+async def require_confirm_fee(ctx, fee):
+ content = Text("Confirm fee", ui.ICON_SEND, icon_color=ui.GREEN)
+ content.normal("Fee: ")
+ content.bold(format_amount(fee))
+ await require_hold_to_confirm(ctx, content, ButtonRequestType.ConfirmOutput)
+
+
+@ui.layout
+async def simple_wait(tm):
+ from trezor import loop
+
+ await loop.sleep(tm)
+
+
+async def light_on():
+ from trezor import loop
+
+ slide = await ui.backlight_slide(ui.BACKLIGHT_NORMAL, delay=0)
+ loop.schedule(slide)
+
+
+@ui.layout
+async def ui_text(text, tm=None) -> None:
+ from trezor import loop
+
+ text.render()
+
+ if tm is not None:
+ await loop.sleep(tm)
+
+
+async def simple_text(text, tm=None) -> None:
+ from trezor import loop
+ from trezor.ui import display
+
+ display.clear()
+ text.render()
+
+ if tm is not None:
+ await loop.sleep(tm)
+
+
+def format_amount(value):
+ return "%f XMR" % (value / 1000000000000)
+
+
+def split_address(address):
+ return chunks(address, 16)
diff --git a/src/apps/monero/protocol/__init__.py b/src/apps/monero/protocol/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/src/apps/monero/protocol/key_image_sync.py b/src/apps/monero/protocol/key_image_sync.py
new file mode 100644
index 000000000..2181b3db3
--- /dev/null
+++ b/src/apps/monero/protocol/key_image_sync.py
@@ -0,0 +1,120 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# Author: Dusan Klinec, ph4r05, 2018
+
+from trezor import log
+
+from apps.monero.controller import wrapper as twrap
+
+
+class KeyImageSync(object):
+ def __init__(self, ctx=None, iface=None, creds=None):
+ from apps.monero.xmr import crypto
+ from apps.monero.xmr.sub.keccak_hasher import HashWrapper
+
+ self.ctx = ctx
+ self.iface = iface
+ self.creds = creds # type: monero.AccountCreds
+
+ self.num = 0
+ self.c_idx = -1
+ self.hash = None
+ self.blocked = None
+ self.enc_key = None
+ self.subaddresses = {}
+ self.hasher = HashWrapper(crypto.get_keccak())
+
+ async def derive_creds(self, msg):
+ self.creds = await twrap.monero_get_creds(
+ self.ctx, msg.address_n or (), msg.network_type
+ )
+
+ async def init(self, ctx, msg):
+ from apps.monero.xmr import crypto
+ from apps.monero.xmr import monero
+ from trezor.messages import FailureType
+ from trezor.messages.Failure import Failure
+ from trezor.messages.MoneroKeyImageExportInitAck import (
+ MoneroKeyImageExportInitAck
+ )
+
+ self.ctx = ctx
+ await self.derive_creds(msg)
+
+ confirmation = await self.iface.confirm_ki_sync(msg, ctx=ctx)
+ if not confirmation:
+ return Failure(code=FailureType.ActionCancelled, message="rejected")
+
+ self.num = msg.num
+ self.hash = msg.hash
+ self.enc_key = crypto.random_bytes(32)
+
+ # Sub address precomputation
+ if msg.subs and len(msg.subs) > 0:
+ for sub in msg.subs: # type: MoneroSubAddressIndicesList
+ monero.compute_subaddresses(
+ self.creds, sub.account, sub.minor_indices, self.subaddresses
+ )
+ return MoneroKeyImageExportInitAck()
+
+ async def sync(self, ctx, tds):
+ from apps.monero.xmr import crypto
+ from apps.monero.xmr.enc import chacha_poly
+ from apps.monero.xmr import key_image
+ from trezor.messages.MoneroExportedKeyImage import MoneroExportedKeyImage
+ from trezor.messages.MoneroKeyImageSyncStepAck import MoneroKeyImageSyncStepAck
+
+ log.debug(__name__, "ki_sync, step i")
+
+ self.ctx = ctx
+ if self.blocked:
+ raise ValueError("Blocked")
+ if len(tds.tdis) == 0:
+ raise ValueError("Empty")
+
+ resp = []
+ buff = bytearray(32 * 3)
+ buff_mv = memoryview(buff)
+
+ for td in tds.tdis:
+ self.c_idx += 1
+ if self.c_idx >= self.num:
+ raise ValueError("Too many outputs")
+
+ log.debug(__name__, "ki_sync, step i: %d", self.c_idx)
+ chash = key_image.compute_hash(td)
+
+ self.hasher.update(chash)
+ ki, sig = await key_image.export_key_image(
+ self.creds, self.subaddresses, td
+ )
+
+ crypto.encodepoint_into(ki, buff_mv[0:32])
+ crypto.encodeint_into(sig[0][0], buff_mv[32:64])
+ crypto.encodeint_into(sig[0][1], buff_mv[64:])
+
+ nonce, ciph, _ = chacha_poly.encrypt(self.enc_key, buff)
+ eki = MoneroExportedKeyImage(iv=nonce, tag=b"", blob=ciph)
+ resp.append(eki)
+
+ return MoneroKeyImageSyncStepAck(kis=resp)
+
+ async def final(self, ctx, msg=None):
+ from trezor.messages.MoneroKeyImageSyncFinalAck import (
+ MoneroKeyImageSyncFinalAck
+ )
+
+ self.ctx = ctx
+ if self.blocked:
+ raise ValueError("Blocked")
+
+ if self.c_idx + 1 != self.num:
+ await self.iface.ki_error("Invalid number of outputs", ctx=self.ctx)
+ raise ValueError("Invalid number of outputs")
+
+ final_hash = self.hasher.digest()
+ if final_hash != self.hash:
+ await self.iface.ki_error("Invalid hash", ctx=self.ctx)
+ raise ValueError("Invalid hash")
+
+ return MoneroKeyImageSyncFinalAck(enc_key=self.enc_key)
diff --git a/src/apps/monero/protocol/tsx_sign.py b/src/apps/monero/protocol/tsx_sign.py
new file mode 100644
index 000000000..08422013e
--- /dev/null
+++ b/src/apps/monero/protocol/tsx_sign.py
@@ -0,0 +1,284 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# Author: Dusan Klinec, ph4r05, 2018
+import gc
+
+from trezor import log
+
+from apps.monero.controller import misc
+
+
+class TsxSigner(object):
+ """
+ Monero Transaction signer.
+ Provides interface to the host, packages messages.
+ """
+
+ def __init__(self):
+ from apps.monero.controller import iface
+
+ self.ctx = None
+ self.tsx_ctr = 0
+ self.err_ctr = 0
+ self.tsx_obj = None # type: TTransactionBuilder
+ self.creds = None # type: apps.monero.xmr.sub.creds.AccountCreds
+ self.iface = iface.get_iface()
+ self.debug = True
+ self.purge = False
+
+ async def tsx_exc_handler(self, e):
+ """
+ Handles the exception thrown in the Trezor processing. Clears transaction state.
+ We could use decorator/wrapper for message calls but not sure how uPython handles them
+ so now are entry points wrapped in try-catch.
+
+ :param e:
+ :return:
+ """
+ if self.debug:
+ log.warning(__name__, "Transaction exception: %s: %s", type(e), e)
+
+ self.err_ctr += 1
+ self.purge = True
+ self.tsx_obj = None # clear transaction object
+ await self.iface.transaction_error(e)
+
+ async def should_purge(self):
+ """
+ Delete global state?
+ :return:
+ """
+ return self.purge or (self.tsx_obj and self.tsx_obj.is_terminal())
+
+ async def setup(self, msg):
+ from apps.monero.controller import wrapper
+
+ self.creds = await wrapper.monero_get_creds(
+ self.ctx, msg.address_n or (), msg.network_type
+ )
+
+ async def restore(self, state):
+ from apps.monero.protocol.tsx_sign_builder import TTransactionBuilder
+
+ self.tsx_obj = TTransactionBuilder(self, creds=self.creds, state=state)
+
+ async def state_save(self):
+ try:
+ s = self.tsx_obj.state_save()
+ self.tsx_obj = None
+ finally:
+ gc.collect()
+ return s
+
+ async def sign(self, ctx, state, msg):
+ """
+ Main multiplex point
+ :param ctx:
+ :param state:
+ :param msg:
+ :return:
+ """
+ from apps.monero.controller import iface
+
+ self.ctx = ctx
+ self.iface = iface.get_iface(ctx)
+ gc.collect()
+
+ log.debug(__name__, "sign()")
+ log.debug(
+ __name__, "Mem Free: {} Allocated: {}".format(gc.mem_free(), gc.mem_alloc())
+ )
+
+ if msg.init:
+ log.debug(__name__, "setup")
+ await self.setup(msg.init)
+ await self.restore(state if not msg.init else None)
+
+ if msg.init:
+ log.debug(__name__, "sign_init")
+ return await self.tsx_init(msg.init.tsx_data)
+ elif msg.set_input:
+ log.debug(__name__, "sign_inp")
+ return await self.tsx_set_input(msg.set_input)
+ elif msg.input_permutation:
+ log.debug(__name__, "sign_perm")
+ return await self.tsx_inputs_permutation(msg.input_permutation)
+ elif msg.input_vini:
+ log.debug(__name__, "sign_vin")
+ return await self.tsx_input_vini(msg.input_vini)
+ elif msg.set_output:
+ log.debug(__name__, "sign_out")
+ return await self.tsx_set_output1(msg.set_output)
+ elif msg.all_out_set:
+ log.debug(__name__, "sign_out_set")
+ return await self.tsx_all_out1_set(msg.all_out_set)
+ elif msg.mlsag_done:
+ log.debug(__name__, "sign_done")
+ return await self.tsx_mlsag_done()
+ elif msg.sign_input:
+ log.debug(__name__, "sign_sinp")
+ return await self.tsx_sign_input(msg.sign_input)
+ elif msg.final_msg:
+ log.debug(__name__, "sign_final")
+ return await self.tsx_sign_final(msg.final_msg)
+ else:
+ raise ValueError("Unknown message")
+
+ async def tsx_init(self, tsx_data):
+ """
+ Initialize transaction state.
+ :param tsx_data:
+ :return:
+ """
+ self.tsx_ctr += 1
+ try:
+ tsxd = await misc.translate_tsx_data(tsx_data)
+ del tsx_data
+
+ return await self.tsx_obj.init_transaction(tsxd, self.tsx_ctr)
+ except Exception as e:
+ await self.tsx_exc_handler(e)
+ raise
+
+ async def tsx_set_input(self, msg):
+ """
+ Sets UTXO one by one.
+ 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 AES-GCM() with
+ key derived for exactly this purpose.
+
+ :param msg
+ :return:
+ """
+ try:
+ src_entr = await misc.parse_src_entry(msg.src_entr)
+ del msg.src_entr
+
+ return await self.tsx_obj.set_input(src_entr)
+ except Exception as e:
+ await self.tsx_exc_handler(e)
+ raise
+
+ async def tsx_inputs_permutation(self, msg):
+ """
+ Set permutation on the inputs - sorted by key image on host.
+
+ :return:
+ """
+ try:
+ return await self.tsx_obj.tsx_inputs_permutation(msg.perm)
+ except Exception as e:
+ await self.tsx_exc_handler(e)
+ raise
+
+ async def tsx_input_vini(self, msg):
+ """
+ Set tx.vin[i] for incremental tx prefix hash computation.
+ After sorting by key images on host.
+
+ :return:
+ """
+ try:
+ src_entr = await misc.parse_src_entry(msg.src_entr)
+ vini = await misc.parse_vini(msg.vini)
+ del msg.src_entr
+ del msg.vini
+
+ return await self.tsx_obj.input_vini(
+ src_entr, vini, msg.vini_hmac, msg.pseudo_out, msg.pseudo_out_hmac
+ )
+ except Exception as e:
+ await self.tsx_exc_handler(e)
+ raise
+
+ async def tsx_set_output1(self, msg):
+ """
+ Set destination entry one by one.
+ Computes destination stealth address, amount key, range proof + HMAC, out_pk, ecdh_info.
+
+ :param msg
+ :return:
+ """
+ try:
+ dst_entr = await misc.parse_dst_entry(msg.dst_entr)
+ del msg.dst_entr
+
+ return await self.tsx_obj.set_out1(dst_entr, msg.dst_entr_hmac)
+ except Exception as e:
+ await self.tsx_exc_handler(e)
+ raise
+
+ async def tsx_all_out1_set(self, msg=None):
+ """
+ All outputs were set in this phase. Computes additional public keys (if needed), tx.extra and
+ transaction prefix hash.
+ Adds additional public keys to the tx.extra
+
+ :return: tx.extra, tx_prefix_hash
+ """
+ try:
+ return await self.tsx_obj.all_out1_set()
+
+ except misc.TrezorTxPrefixHashNotMatchingError as e:
+ await self.tsx_exc_handler(e)
+
+ from trezor.messages.Failure import Failure
+ from apps.monero.controller.wrapper import exc2str
+
+ return Failure(code=10, message=exc2str(e))
+
+ except Exception as e:
+ await self.tsx_exc_handler(e)
+ raise
+
+ async def tsx_mlsag_done(self, msg=None):
+ """
+ MLSAG message computed.
+
+ :return:
+ """
+ try:
+ return await self.tsx_obj.mlsag_done()
+ except Exception as e:
+ await self.tsx_exc_handler(e)
+ raise
+
+ async def tsx_sign_input(self, msg):
+ """
+ Generates a signature for one input.
+
+ :return:
+ """
+ try:
+ src_entr = await misc.parse_src_entry(msg.src_entr)
+ vini = await misc.parse_vini(msg.vini)
+ del msg.src_entr
+ del msg.vini
+
+ return await self.tsx_obj.sign_input(
+ src_entr,
+ vini,
+ msg.vini_hmac,
+ msg.pseudo_out,
+ msg.pseudo_out_hmac,
+ msg.alpha_enc,
+ msg.spend_enc,
+ )
+ except Exception as e:
+ await self.tsx_exc_handler(e)
+ raise
+
+ async def tsx_sign_final(self, msg=None):
+ """
+ Final message.
+ Offloading tx related data, encrypted.
+
+ :return:
+ """
+ try:
+ return await self.tsx_obj.final_msg()
+ except Exception as e:
+ await self.tsx_exc_handler(e)
+ raise
diff --git a/src/apps/monero/protocol/tsx_sign_builder.py b/src/apps/monero/protocol/tsx_sign_builder.py
new file mode 100644
index 000000000..028583094
--- /dev/null
+++ b/src/apps/monero/protocol/tsx_sign_builder.py
@@ -0,0 +1,1476 @@
+import gc
+import micropython
+from micropython import const
+
+from trezor import log
+
+from apps.monero.controller import misc
+from apps.monero.xmr import common, crypto, monero
+
+
+class TprefixStub(object):
+ __slots__ = ("version", "unlock_time", "vin", "vout", "extra")
+
+ def __init__(self, **kwargs):
+ for kw in kwargs:
+ setattr(self, kw, kwargs[kw])
+
+
+class TTransactionBuilder(object):
+ """
+ Transaction builder
+ """
+
+ STEP_INP = const(100)
+ STEP_PERM = const(200)
+ STEP_VINI = const(300)
+ STEP_OUT = const(400)
+ STEP_ALL_OUT = const(500)
+ STEP_MLSAG = const(600)
+ STEP_SIGN = const(700)
+
+ def __init__(self, trezor=None, creds=None, state=None, **kwargs):
+ self.trezor = trezor
+ self.creds = creds
+ self.key_master = None
+ self.key_hmac = None
+ self.key_enc = None
+
+ self.r = None # txkey
+ self.r_pub = None
+ self.state = None
+
+ self.multi_sig = False
+ self.need_additional_txkeys = False
+ self.use_bulletproof = False
+ self.use_rct = True
+ self.use_simple_rct = False
+ self.input_count = 0
+ self.output_count = 0
+ self.output_change = None
+ self.mixin = 0
+ self.fee = 0
+
+ self.additional_tx_private_keys = []
+ self.additional_tx_public_keys = []
+ self.inp_idx = -1
+ self.out_idx = -1
+ self.summary_inputs_money = 0
+ self.summary_outs_money = 0
+ self.input_secrets = []
+ self.input_alphas = []
+ self.input_pseudo_outs = []
+ self.output_sk = []
+ self.output_pk = []
+ self.sumout = crypto.sc_0()
+ self.sumpouts_alphas = crypto.sc_0()
+ self.subaddresses = {}
+ self.tx = None
+ self.source_permutation = [] # sorted by key images
+ self.tx_prefix_hasher = None
+ self.tx_prefix_hash = None
+ self.full_message_hasher = None
+ self.full_message = None
+ self.exp_tx_prefix_hash = None
+
+ if state is None:
+ self._init()
+ else:
+ self.state_load(state)
+
+ def _init(self):
+ from apps.monero.xmr.sub.keccak_hasher import KeccakArchive
+ from apps.monero.xmr.sub.mlsag_hasher import PreMlsagHasher
+ from apps.monero.protocol.tsx_sign_state import TState
+
+ self.state = TState()
+ self.tx = TprefixStub(vin=[], vout=[], extra=b"")
+ self.tx_prefix_hasher = KeccakArchive()
+ self.full_message_hasher = PreMlsagHasher()
+
+ def state_load(self, t):
+ from apps.monero.xmr.sub.keccak_hasher import KeccakArchive
+ from apps.monero.xmr.sub.mlsag_hasher import PreMlsagHasher
+ from apps.monero.protocol.tsx_sign_state import TState
+
+ self._log_trace(t.state)
+
+ for attr in t.__dict__:
+ if attr.startswith("_"):
+ continue
+
+ cval = getattr(t, attr)
+ if cval is None:
+ setattr(self, attr, cval)
+ continue
+
+ if attr == "state":
+ self.state = TState()
+ self.state.state_load(t.state)
+ elif attr == "tx_prefix_hasher":
+ self.tx_prefix_hasher = KeccakArchive(ctx=t.tx_prefix_hasher)
+ elif attr == "full_message_hasher":
+ self.full_message_hasher = PreMlsagHasher(state=t.full_message_hasher)
+ else:
+ setattr(self, attr, cval)
+
+ def state_save(self):
+ from apps.monero.protocol.tsx_sign_state_holder import TsxSignStateHolder
+
+ t = TsxSignStateHolder()
+
+ for attr in self.__dict__:
+ if attr.startswith("_"):
+ continue
+
+ cval = getattr(self, attr)
+ if cval is None:
+ setattr(t, attr, cval)
+ continue
+
+ if attr == "state":
+ t.state = self.state.state_save()
+ elif attr in ["trezor"]:
+ continue
+ elif attr.startswith("STEP"):
+ continue
+ elif attr == "tx_prefix_hasher":
+ t.tx_prefix_hasher = self.tx_prefix_hasher.ctx()
+ elif attr == "full_message_hasher":
+ t.full_message_hasher = self.full_message_hasher.state_save()
+ else:
+ setattr(t, attr, cval)
+ return t
+
+ def _log_trace(self, 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 assrt(self, condition, msg=None):
+ """
+ Asserts condition
+ :param condition:
+ :param msg:
+ :return:
+ """
+ if condition:
+ return
+ raise ValueError("Assertion error%s" % (" : %s" % msg if msg else ""))
+
+ def is_terminal(self):
+ """
+ Returns true if the state is terminal
+ :return:
+ """
+ return self.state.is_terminal()
+
+ def gen_r(self, use_r=None):
+ """
+ Generates a new transaction key pair.
+ :param use_r:
+ :return:
+ """
+ self.r = crypto.random_scalar() if use_r is None else use_r
+ self.r_pub = crypto.scalarmult_base(self.r)
+
+ def check_change(self, outputs):
+ """
+ Checks if the change address is among tx outputs.
+ :param outputs:
+ :return:
+ """
+ from apps.monero.xmr.sub.addr import addr_eq
+
+ change_addr = self.change_address()
+ if change_addr is None:
+ return
+
+ for out in outputs:
+ if addr_eq(out.addr, change_addr):
+ return True
+
+ raise ValueError("Change address not found in outputs")
+
+ def in_memory(self):
+ """
+ Returns true if the input transaction can be processed whole in-memory
+ :return:
+ """
+ return False and self.input_count <= 1
+
+ def many_inputs(self):
+ """
+ Returns true if number of inputs > 10 (secret spending key offloaded)
+ :return:
+ """
+ return self.input_count >= 10
+
+ def many_outputs(self):
+ """
+ Returns true if number of outputs > 10 (increases number of roundtrips of the protocol)
+ :return:
+ """
+ return self.output_count >= 10
+
+ def num_inputs(self):
+ """
+ Number of inputs
+ :return:
+ """
+ return self.input_count
+
+ def num_dests(self):
+ """
+ Number of destinations
+ :return:
+ """
+ return self.output_count
+
+ def get_fee(self):
+ """
+ Txn fee
+ :return:
+ """
+ return self.fee if self.fee > 0 else 0
+
+ def change_address(self):
+ """
+ Returns change address if change dst is set
+ :return:
+ """
+ return self.output_change.addr if self.output_change else None
+
+ def get_rct_type(self):
+ """
+ RCTsig type (simple/full x Borromean/Bulletproof)
+ :return:
+ """
+ from apps.monero.xmr.serialize_messages.tx_rsig import RctType
+
+ if self.use_simple_rct:
+ return RctType.SimpleBulletproof if self.use_bulletproof else RctType.Simple
+ else:
+ return RctType.FullBulletproof if self.use_bulletproof else RctType.Full
+
+ def init_rct_sig(self):
+ """
+ Initializes RCTsig structure (fee, tx prefix hash, type)
+ :return:
+ """
+ rv = misc.StdObj(
+ txnFee=self.get_fee(), message=self.tx_prefix_hash, type=self.get_rct_type()
+ )
+ return rv
+
+ def hmac_key_txin(self, idx):
+ """
+ (TxSourceEntry[i] || tx.vin[i]) hmac key
+ :param idx:
+ :return:
+ """
+ from apps.monero.xmr.serialize.int_serialize import dump_uvarint_b
+
+ return crypto.keccak_2hash(self.key_hmac + b"txin" + dump_uvarint_b(idx))
+
+ def hmac_key_txin_comm(self, idx):
+ """
+ pseudo_outputs[i] hmac key. Pedersen commitment for inputs.
+ :param idx:
+ :return:
+ """
+ from apps.monero.xmr.serialize.int_serialize import dump_uvarint_b
+
+ return crypto.keccak_2hash(self.key_hmac + b"txin-comm" + dump_uvarint_b(idx))
+
+ def hmac_key_txdst(self, idx):
+ """
+ TxDestinationEntry[i] hmac key
+ :param idx:
+ :return:
+ """
+ from apps.monero.xmr.serialize.int_serialize import dump_uvarint_b
+
+ return crypto.keccak_2hash(self.key_hmac + b"txdest" + dump_uvarint_b(idx))
+
+ def hmac_key_txout(self, idx):
+ """
+ (TxDestinationEntry[i] || tx.vout[i]) hmac key
+ :param idx:
+ :return:
+ """
+ from apps.monero.xmr.serialize.int_serialize import dump_uvarint_b
+
+ return crypto.keccak_2hash(self.key_hmac + b"txout" + dump_uvarint_b(idx))
+
+ def hmac_key_txout_asig(self, idx):
+ """
+ rsig[i] hmac key. Range signature HMAC
+ :param idx:
+ :return:
+ """
+ from apps.monero.xmr.serialize.int_serialize import dump_uvarint_b
+
+ return crypto.keccak_2hash(self.key_hmac + b"txout-asig" + dump_uvarint_b(idx))
+
+ def enc_key_txin_alpha(self, idx):
+ """
+ Chacha20Poly1305 encryption key for alpha[i] used in Pedersen commitment in pseudo_outs[i]
+ :param idx:
+ :return:
+ """
+ from apps.monero.xmr.serialize.int_serialize import dump_uvarint_b
+
+ return crypto.keccak_2hash(self.key_enc + b"txin-alpha" + dump_uvarint_b(idx))
+
+ def enc_key_spend(self, idx):
+ """
+ Chacha20Poly1305 encryption key for alpha[i] used in Pedersen commitment in pseudo_outs[i]
+ :param idx:
+ :return:
+ """
+ from apps.monero.xmr.serialize.int_serialize import dump_uvarint_b
+
+ return crypto.keccak_2hash(self.key_enc + b"txin-spend" + dump_uvarint_b(idx))
+
+ def enc_key_cout(self, idx=None):
+ """
+ Chacha20Poly1305 encryption key for multisig C values from MLASG.
+ :param idx:
+ :return:
+ """
+ from apps.monero.xmr.serialize.int_serialize import dump_uvarint_b
+
+ return crypto.keccak_2hash(
+ self.key_enc + b"cout" + (dump_uvarint_b(idx) if idx else b"")
+ )
+
+ async def gen_hmac_vini(self, src_entr, vini, idx):
+ """
+ Computes hmac (TxSourceEntry[i] || tx.vin[i])
+ :param src_entr:
+ :param vini:
+ :param idx:
+ :return:
+ """
+ from apps.monero.xmr.sub.keccak_hasher import get_keccak_writer
+ from apps.monero.xmr.serialize import xmrserialize
+ from apps.monero.xmr.serialize_messages.tx_src_entry import TxSourceEntry
+ from apps.monero.xmr.serialize_messages.tx_prefix import TxinToKey
+
+ kwriter = get_keccak_writer()
+ ar = xmrserialize.Archive(kwriter, True)
+ await ar.message(src_entr, TxSourceEntry)
+ await ar.message(vini, TxinToKey)
+
+ hmac_key_vini = self.hmac_key_txin(idx)
+ hmac_vini = crypto.compute_hmac(hmac_key_vini, kwriter.get_digest())
+ return hmac_vini
+
+ async def gen_hmac_vouti(self, dst_entr, tx_out, idx):
+ """
+ Generates HMAC for (TxDestinationEntry[i] || tx.vout[i])
+ :param dst_entr:
+ :param tx_out:
+ :param idx:
+ :return:
+ """
+ from apps.monero.xmr.sub.keccak_hasher import get_keccak_writer
+ from apps.monero.xmr.serialize import xmrserialize
+ from apps.monero.xmr.serialize_messages.tx_dest_entry import TxDestinationEntry
+ from apps.monero.xmr.serialize_messages.tx_prefix import TxOut
+
+ kwriter = get_keccak_writer()
+ ar = xmrserialize.Archive(kwriter, True)
+ await ar.message(dst_entr, TxDestinationEntry)
+ await ar.message(tx_out, TxOut)
+
+ hmac_key_vouti = self.hmac_key_txout(idx)
+ hmac_vouti = crypto.compute_hmac(hmac_key_vouti, kwriter.get_digest())
+ return hmac_vouti
+
+ async def gen_hmac_tsxdest(self, dst_entr, idx):
+ """
+ Generates HMAC for TxDestinationEntry[i]
+ :param dst_entr:
+ :param idx:
+ :return:
+ """
+ from apps.monero.xmr.sub.keccak_hasher import get_keccak_writer
+ from apps.monero.xmr.serialize import xmrserialize
+ from apps.monero.xmr.serialize_messages.tx_dest_entry import TxDestinationEntry
+
+ kwriter = get_keccak_writer()
+ ar = xmrserialize.Archive(kwriter, True)
+ await ar.message(dst_entr, TxDestinationEntry)
+
+ hmac_key = self.hmac_key_txdst(idx)
+ hmac_tsxdest = crypto.compute_hmac(hmac_key, kwriter.get_digest())
+ return hmac_tsxdest
+
+ async def _tprefix_update(self):
+ from apps.monero.xmr.serialize_messages.tx_prefix import TransactionPrefix
+
+ tx_fields = TransactionPrefix.f_specs()
+ await self.tx_prefix_hasher.ar.message_field(self.tx, tx_fields[0])
+ await self.tx_prefix_hasher.ar.message_field(self.tx, tx_fields[1])
+ await self.tx_prefix_hasher.ar.container_size(
+ self.num_inputs(), tx_fields[2][1]
+ )
+ self._log_trace(10)
+
+ async def init_transaction(self, tsx_data, tsx_ctr):
+ """
+ Initializes a new transaction.
+ :param tsx_data:
+ :type tsx_data: TsxData
+ :param tsx_ctr:
+ :return:
+ """
+ from apps.monero.xmr.sub.addr import classify_subaddresses
+
+ self.gen_r()
+ self.state.init_tsx()
+ self._log_trace(1)
+
+ # Ask for confirmation
+ confirmation = await self.trezor.iface.confirm_transaction(tsx_data, self.creds)
+ if not confirmation:
+ from trezor.messages import FailureType
+ from trezor.messages.Failure import Failure
+
+ return Failure(code=FailureType.ActionCancelled, message="rejected")
+
+ gc.collect()
+ self._log_trace(3)
+
+ # Basic transaction parameters
+ self.input_count = tsx_data.num_inputs
+ self.output_count = len(tsx_data.outputs)
+ self.output_change = misc.dst_entry_to_stdobj(tsx_data.change_dts)
+ self.mixin = tsx_data.mixin
+ self.fee = tsx_data.fee
+ self.use_simple_rct = self.input_count > 1
+ self.use_bulletproof = tsx_data.is_bulletproof
+ self.multi_sig = tsx_data.is_multisig
+ self.state.inp_cnt(self.in_memory())
+ self.check_change(tsx_data.outputs)
+ self.exp_tx_prefix_hash = common.defval_empty(tsx_data.exp_tx_prefix_hash, None)
+
+ # Provided tx key, used mostly in multisig.
+ if len(tsx_data.use_tx_keys) > 0:
+ for ckey in tsx_data.use_tx_keys:
+ crypto.check_sc(crypto.decodeint(ckey))
+
+ self.gen_r(use_r=crypto.decodeint(tsx_data.use_tx_keys[0]))
+ self.additional_tx_private_keys = [
+ crypto.decodeint(x) for x in tsx_data.use_tx_keys[1:]
+ ]
+
+ # Additional keys w.r.t. subaddress destinations
+ class_res = classify_subaddresses(tsx_data.outputs, self.change_address())
+ num_stdaddresses, num_subaddresses, single_dest_subaddress = class_res
+
+ # if this is a single-destination transfer to a subaddress, we set the tx pubkey to R=s*D
+ if num_stdaddresses == 0 and num_subaddresses == 1:
+ self.r_pub = crypto.ge_scalarmult(
+ self.r, crypto.decodepoint(single_dest_subaddress.m_spend_public_key)
+ )
+
+ self.need_additional_txkeys = num_subaddresses > 0 and (
+ num_stdaddresses > 0 or num_subaddresses > 1
+ )
+ self._log_trace(4)
+
+ # Extra processing, payment id
+ self.tx.version = 2
+ self.tx.unlock_time = tsx_data.unlock_time
+ await self.process_payment_id(tsx_data)
+ await self.compute_sec_keys(tsx_data, tsx_ctr)
+ gc.collect()
+
+ # Iterative tx_prefix_hash hash computation
+ await self._tprefix_update()
+ gc.collect()
+
+ # Final message hasher
+ self.full_message_hasher.init(self.use_simple_rct)
+ await self.full_message_hasher.set_type_fee(self.get_rct_type(), self.get_fee())
+
+ # Sub address precomputation
+ if tsx_data.account is not None and tsx_data.minor_indices:
+ self.precompute_subaddr(tsx_data.account, tsx_data.minor_indices)
+ self._log_trace(5)
+
+ # HMAC outputs - pinning
+ hmacs = []
+ for idx in range(self.num_dests()):
+ c_hmac = await self.gen_hmac_tsxdest(tsx_data.outputs[idx], idx)
+ hmacs.append(c_hmac)
+ gc.collect()
+
+ self._log_trace(6)
+
+ from trezor.messages.MoneroTransactionInitAck import MoneroTransactionInitAck
+
+ return MoneroTransactionInitAck(
+ in_memory=self.in_memory(),
+ many_inputs=self.many_inputs(),
+ many_outputs=self.many_outputs(),
+ hmacs=hmacs,
+ )
+
+ async def process_payment_id(self, tsx_data):
+ """
+ Payment id -> extra
+ :return:
+ """
+ if common.is_empty(tsx_data.payment_id):
+ return
+
+ from apps.monero.xmr.sub import tsx_helper
+
+ if len(tsx_data.payment_id) == 8:
+ view_key_pub_enc = tsx_helper.get_destination_view_key_pub(
+ tsx_data.outputs, self.change_address()
+ )
+ if view_key_pub_enc == crypto.NULL_KEY_ENC:
+ raise ValueError(
+ "Destinations have to have exactly one output to support encrypted payment ids"
+ )
+
+ view_key_pub = crypto.decodepoint(view_key_pub_enc)
+ payment_id_encr = tsx_helper.encrypt_payment_id(
+ tsx_data.payment_id, view_key_pub, self.r
+ )
+
+ extra_nonce = tsx_helper.set_encrypted_payment_id_to_tx_extra_nonce(
+ payment_id_encr
+ )
+
+ elif len(tsx_data.payment_id) == 32:
+ extra_nonce = tsx_helper.set_payment_id_to_tx_extra_nonce(
+ tsx_data.payment_id
+ )
+
+ else:
+ raise ValueError("Payment ID size invalid")
+
+ self.tx.extra = tsx_helper.add_extra_nonce_to_tx_extra(b"", extra_nonce)
+
+ async def compute_sec_keys(self, tsx_data, tsx_ctr):
+ """
+ Generate master key H(TsxData || r || c_tsx)
+ :return:
+ """
+ from apps.monero.xmr.sub.keccak_hasher import get_keccak_writer
+ from apps.monero.xmr.serialize import xmrserialize
+
+ writer = get_keccak_writer()
+ ar1 = xmrserialize.Archive(writer, True)
+ await ar1.message(tsx_data)
+ await writer.awrite(crypto.encodeint(self.r))
+ await xmrserialize.dump_uvarint(writer, tsx_ctr)
+ self.key_master = crypto.keccak_2hash(
+ writer.get_digest() + crypto.encodeint(crypto.random_scalar())
+ )
+ self.key_hmac = crypto.keccak_2hash(b"hmac" + self.key_master)
+ self.key_enc = crypto.keccak_2hash(b"enc" + self.key_master)
+
+ def precompute_subaddr(self, account, 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.
+ :param account:
+ :param indices:
+ :return:
+ """
+ monero.compute_subaddresses(self.creds, account, indices, self.subaddresses)
+
+ async def set_input(self, src_entr):
+ """
+ Sets UTXO one by one.
+ 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 Chacha20Poly1305()
+ with key derived for exactly this purpose.
+
+ :param src_entr:
+ :type src_entr: apps.monero.xmr.serialize_messages.tx_construct.TxSourceEntry
+ :return:
+ """
+ from trezor.messages.MoneroTransactionSetInputAck import (
+ MoneroTransactionSetInputAck
+ )
+ from apps.monero.xmr.enc import chacha_poly
+ from apps.monero.xmr.sub import tsx_helper
+ from apps.monero.xmr.serialize_messages.tx_prefix import TxinToKey
+
+ self.state.input()
+ self.inp_idx += 1
+
+ await self.trezor.iface.transaction_step(
+ self.STEP_INP, self.inp_idx, self.num_inputs()
+ )
+
+ if self.inp_idx >= self.num_inputs():
+ raise ValueError("Too many inputs")
+ 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))
+ )
+ self.summary_inputs_money += src_entr.amount
+
+ # Secrets derivation
+ out_key = crypto.decodepoint(src_entr.outputs[src_entr.real_output][1].dest)
+ 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
+ ]
+
+ secs = monero.generate_key_image_helper(
+ self.creds,
+ self.subaddresses,
+ out_key,
+ tx_key,
+ additional_keys,
+ src_entr.real_output_in_tx_index,
+ )
+ xi, ki, di = secs
+
+ # Construct tx.vin
+ ki_real = src_entr.multisig_kLRki.ki if self.multi_sig else ki
+ vini = TxinToKey(amount=src_entr.amount, k_image=crypto.encodepoint(ki_real))
+ vini.key_offsets = [x[0] for x in src_entr.outputs]
+ vini.key_offsets = tsx_helper.absolute_output_offsets_to_relative(
+ vini.key_offsets
+ )
+
+ if src_entr.rct:
+ vini.amount = 0
+
+ if self.in_memory():
+ self.tx.vin.append(vini)
+
+ # HMAC(T_in,i || vin_i)
+ hmac_vini = await self.gen_hmac_vini(src_entr, vini, self.inp_idx)
+
+ # PseudoOuts commitment, alphas stored to state
+ pseudo_out = None
+ pseudo_out_hmac = None
+ alpha_enc = None
+ spend_enc = None
+
+ if self.use_simple_rct:
+ alpha, pseudo_out = await self.commitment(src_entr.amount)
+ pseudo_out = crypto.encodepoint(pseudo_out)
+
+ # In full version the alpha is encrypted and passed back for storage
+ if self.in_memory():
+ self.input_alphas.append(alpha)
+ self.input_pseudo_outs.append(pseudo_out)
+ else:
+ pseudo_out_hmac = crypto.compute_hmac(
+ self.hmac_key_txin_comm(self.inp_idx), pseudo_out
+ )
+ alpha_enc = chacha_poly.encrypt_pack(
+ self.enc_key_txin_alpha(self.inp_idx), crypto.encodeint(alpha)
+ )
+
+ if self.many_inputs():
+ spend_enc = chacha_poly.encrypt_pack(
+ self.enc_key_spend(self.inp_idx), crypto.encodeint(xi)
+ )
+ else:
+ self.input_secrets.append(xi)
+
+ # All inputs done?
+ if self.inp_idx + 1 == self.num_inputs():
+ await self.tsx_inputs_done()
+
+ return MoneroTransactionSetInputAck(
+ vini=await misc.dump_msg(vini, preallocate=64),
+ vini_hmac=hmac_vini,
+ pseudo_out=pseudo_out,
+ pseudo_out_hmac=pseudo_out_hmac,
+ alpha_enc=alpha_enc,
+ spend_enc=spend_enc,
+ )
+
+ async def tsx_inputs_done(self):
+ """
+ All inputs set
+ :return:
+ """
+ self.state.input_done()
+ self.subaddresses = None
+
+ if self.inp_idx + 1 != self.num_inputs():
+ raise ValueError("Input count mismatch")
+
+ if self.in_memory():
+ return await self.tsx_inputs_done_inm()
+
+ async def tsx_inputs_done_inm(self):
+ """
+ In-memory post processing - tx.vin[i] sorting by key image.
+ Used only if number of inputs is small - computable in Trezor without offloading.
+
+ :return:
+ """
+ # Sort tx.in by key image
+ self.source_permutation = list(range(self.num_inputs()))
+ self.source_permutation.sort(key=lambda x: self.tx.vin[x].k_image, reverse=True)
+ await self._tsx_inputs_permutation(self.source_permutation)
+
+ async def tsx_inputs_permutation(self, permutation):
+ """
+ Set permutation on the inputs - sorted by key image on host.
+
+ :param permutation:
+ :return:
+ """
+ from trezor.messages.MoneroTransactionInputsPermutationAck import (
+ MoneroTransactionInputsPermutationAck
+ )
+
+ await self.trezor.iface.transaction_step(self.STEP_PERM)
+
+ if self.in_memory():
+ return
+ await self._tsx_inputs_permutation(permutation)
+ return MoneroTransactionInputsPermutationAck()
+
+ async def _tsx_inputs_permutation(self, permutation):
+ """
+ Set permutation on the inputs - sorted by key image on host.
+
+ :param permutation:
+ :return:
+ """
+ self.state.input_permutation()
+ self.source_permutation = permutation
+
+ def swapper(x, y):
+ if not self.many_inputs():
+ self.input_secrets[x], self.input_secrets[y] = (
+ self.input_secrets[y],
+ self.input_secrets[x],
+ )
+ if self.in_memory() and self.use_simple_rct:
+ self.input_alphas[x], self.input_alphas[y] = (
+ self.input_alphas[y],
+ self.input_alphas[x],
+ )
+ self.input_pseudo_outs[x], self.input_pseudo_outs[y] = (
+ self.input_pseudo_outs[y],
+ self.input_pseudo_outs[x],
+ )
+ if self.in_memory():
+ self.tx.vin[x], self.tx.vin[y] = self.tx.vin[y], self.tx.vin[x]
+
+ common.apply_permutation(self.source_permutation, swapper)
+ self.inp_idx = -1
+
+ # Incremental hashing
+ if self.in_memory():
+ for idx in range(self.num_inputs()):
+ await self.hash_vini_pseudo_out(self.tx.vin[idx], idx)
+
+ async def input_vini(self, src_entr, vini, hmac, pseudo_out, pseudo_out_hmac):
+ """
+ Set tx.vin[i] for incremental tx prefix hash computation.
+ After sorting by key images on host.
+ Hashes pseudo_out to the final_message.
+
+ :param src_entr:
+ :param vini: tx.vin[i]
+ :param hmac: HMAC of tx.vin[i]
+ :param pseudo_out: pseudo_out for the current entry
+ :param pseudo_out_hmac: hmac of pseudo_out
+ :return:
+ """
+ from trezor.messages.MoneroTransactionInputViniAck import (
+ MoneroTransactionInputViniAck
+ )
+
+ await self.trezor.iface.transaction_step(
+ self.STEP_VINI, self.inp_idx + 1, self.num_inputs()
+ )
+
+ if self.in_memory():
+ return
+ if self.inp_idx >= self.num_inputs():
+ raise ValueError("Too many inputs")
+
+ self.state.input_vins()
+ self.inp_idx += 1
+
+ # HMAC(T_in,i || vin_i)
+ hmac_vini = await self.gen_hmac_vini(
+ src_entr, vini, self.source_permutation[self.inp_idx]
+ )
+ if not common.ct_equal(hmac_vini, hmac):
+ raise ValueError("HMAC is not correct")
+
+ await self.hash_vini_pseudo_out(vini, self.inp_idx, pseudo_out, pseudo_out_hmac)
+ return MoneroTransactionInputViniAck()
+
+ async def hash_vini_pseudo_out(
+ self, vini, inp_idx, pseudo_out=None, pseudo_out_hmac=None
+ ):
+ """
+ Incremental hasing of tx.vin[i] and pseudo output
+ :param vini:
+ :param inp_idx:
+ :param pseudo_out:
+ :param pseudo_out_hmac:
+ :return:
+ """
+ # Serialize particular input type
+ from apps.monero.xmr.serialize import xmrserialize
+ from apps.monero.xmr.serialize_messages.tx_prefix import TxInV
+
+ self.tx_prefix_hasher.refresh(xser=xmrserialize)
+
+ await self.tx_prefix_hasher.ar.field(vini, TxInV)
+
+ # Pseudo_out incremental hashing - applicable only in simple rct
+ if not self.use_simple_rct:
+ return
+
+ if not self.in_memory():
+ idx = self.source_permutation[inp_idx]
+ pseudo_out_hmac_comp = crypto.compute_hmac(
+ self.hmac_key_txin_comm(idx), pseudo_out
+ )
+ if not common.ct_equal(pseudo_out_hmac, pseudo_out_hmac_comp):
+ raise ValueError("HMAC invalid for pseudo outs")
+ else:
+ pseudo_out = self.input_pseudo_outs[inp_idx]
+
+ await self.full_message_hasher.set_pseudo_out(pseudo_out)
+
+ async def commitment(self, 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
+ :return:
+ """
+ alpha = crypto.random_scalar()
+ self.sumpouts_alphas = crypto.sc_add(self.sumpouts_alphas, alpha)
+ return alpha, crypto.gen_c(alpha, in_amount)
+
+ async def range_proof(self, idx, dest_pub_key, amount, amount_key):
+ """
+ Computes rangeproof and related information - out_sk, out_pk, ecdh_info.
+ 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.
+
+ :param idx:
+ :param dest_pub_key:
+ :param amount:
+ :param amount_key:
+ :return:
+ """
+ from apps.monero.xmr import ring_ct
+
+ rsig = bytearray(32 * (64 + 64 + 64 + 1))
+ rsig_mv = memoryview(rsig)
+
+ out_pk = misc.StdObj(dest=dest_pub_key, mask=None)
+ is_last = idx + 1 == self.num_dests()
+ last_mask = (
+ None
+ if not is_last or not self.use_simple_rct
+ else crypto.sc_sub(self.sumpouts_alphas, self.sumout)
+ )
+
+ # Pedersen commitment on the value, mask from the commitment, range signature.
+ C, mask, rsig = None, 0, None
+
+ # Rangeproof
+ gc.collect()
+ if self.use_bulletproof:
+ raise ValueError("Bulletproof not yet supported")
+
+ else:
+ C, mask, rsig = ring_ct.prove_range(
+ amount, last_mask, backend_impl=True, byte_enc=True, rsig=rsig_mv
+ )
+ rsig = memoryview(rsig)
+
+ self.assrt(
+ crypto.point_eq(
+ C,
+ crypto.point_add(
+ crypto.scalarmult_base(mask), crypto.scalarmult_h(amount)
+ ),
+ ),
+ "rproof",
+ )
+
+ # Incremental hashing
+ await self.full_message_hasher.rsig_val(
+ rsig, self.use_bulletproof, raw=True
+ )
+ gc.collect()
+ self._log_trace("rproof")
+
+ # Mask sum
+ out_pk.mask = crypto.encodepoint(C)
+ self.sumout = crypto.sc_add(self.sumout, mask)
+ self.output_sk.append(misc.StdObj(mask=mask))
+
+ # ECDH masking
+ from apps.monero.xmr.sub.recode import recode_ecdh
+ from apps.monero.xmr.serialize_messages.tx_ecdh import EcdhTuple
+
+ ecdh_info = EcdhTuple(mask=mask, amount=crypto.sc_init(amount))
+ ecdh_info = ring_ct.ecdh_encode(
+ ecdh_info, derivation=crypto.encodeint(amount_key)
+ )
+ recode_ecdh(ecdh_info, encode=True)
+ gc.collect()
+
+ return rsig, out_pk, ecdh_info
+
+ async def _set_out1_prefix(self):
+ from apps.monero.xmr.serialize_messages.tx_prefix import TransactionPrefix
+
+ await self.tx_prefix_hasher.ar.container_size(
+ self.num_dests(), TransactionPrefix.f_specs()[3][1]
+ )
+
+ async def _set_out1_additional_keys(self, dst_entr):
+ additional_txkey = None
+ additional_txkey_priv = None
+ if self.need_additional_txkeys:
+ use_provided = self.num_dests() == len(self.additional_tx_private_keys)
+ additional_txkey_priv = (
+ self.additional_tx_private_keys[self.out_idx]
+ if use_provided
+ else crypto.random_scalar()
+ )
+
+ if dst_entr.is_subaddress:
+ additional_txkey = crypto.ge_scalarmult(
+ additional_txkey_priv,
+ crypto.decodepoint(dst_entr.addr.m_spend_public_key),
+ )
+ else:
+ additional_txkey = crypto.ge_scalarmult_base(additional_txkey_priv)
+
+ self.additional_tx_public_keys.append(crypto.encodepoint(additional_txkey))
+ if not use_provided:
+ self.additional_tx_private_keys.append(additional_txkey_priv)
+ return additional_txkey_priv
+
+ async def _set_out1_derivation(self, dst_entr, additional_txkey_priv):
+ from apps.monero.xmr.sub.addr import addr_eq
+
+ change_addr = self.change_address()
+ if change_addr and addr_eq(dst_entr.addr, change_addr):
+ # sending change to yourself; derivation = a*R
+ derivation = monero.generate_key_derivation(
+ self.r_pub, self.creds.view_key_private
+ )
+
+ else:
+ # sending to the recipient; derivation = r*A (or s*C in the subaddress scheme)
+ deriv_priv = (
+ additional_txkey_priv
+ if dst_entr.is_subaddress and self.need_additional_txkeys
+ else self.r
+ )
+ derivation = monero.generate_key_derivation(
+ crypto.decodepoint(dst_entr.addr.m_view_public_key), deriv_priv
+ )
+ return derivation
+
+ async def set_out1(self, dst_entr, dst_entr_hmac):
+ """
+ Set destination entry one by one.
+ Computes destination stealth address, amount key, range proof + HMAC, out_pk, ecdh_info.
+
+ :param dst_entr
+ :type dst_entr: TxDestinationEntry
+ :param dst_entr_hmac
+ :return:
+ """
+ from apps.monero.xmr.serialize import xmrserialize
+
+ await self.trezor.iface.transaction_step(
+ self.STEP_OUT, self.out_idx + 1, self.num_dests()
+ )
+ self._log_trace(1)
+
+ if self.state.is_input_vins() and self.inp_idx + 1 != self.num_inputs():
+ raise ValueError("Invalid number of inputs")
+
+ self.state.set_output()
+ self.out_idx += 1
+ self._log_trace(2)
+
+ if dst_entr.amount <= 0 and self.tx.version <= 1:
+ raise ValueError("Destination with wrong amount: %s" % dst_entr.amount)
+
+ # HMAC check of the destination
+ dst_entr_hmac_computed = await self.gen_hmac_tsxdest(dst_entr, self.out_idx)
+ if not common.ct_equal(dst_entr_hmac, dst_entr_hmac_computed):
+ raise ValueError("HMAC invalid")
+ gc.collect()
+ self._log_trace(3)
+
+ # First output - tx prefix hasher - size of the container
+ self.tx_prefix_hasher.refresh(xser=xmrserialize)
+ if self.out_idx == 0:
+ await self._set_out1_prefix()
+ gc.collect()
+
+ self._log_trace(4)
+ additional_txkey_priv = await self._set_out1_additional_keys(dst_entr)
+ derivation = await self._set_out1_derivation(dst_entr, additional_txkey_priv)
+
+ gc.collect()
+ self._log_trace(5)
+
+ amount_key = crypto.derivation_to_scalar(derivation, self.out_idx)
+ tx_out_key = crypto.derive_public_key(
+ derivation,
+ self.out_idx,
+ crypto.decodepoint(dst_entr.addr.m_spend_public_key),
+ )
+
+ from apps.monero.xmr.serialize_messages.tx_prefix import TxoutToKey
+ from apps.monero.xmr.serialize_messages.tx_prefix import TxOut
+
+ tk = TxoutToKey(key=crypto.encodepoint(tx_out_key))
+ tx_out = TxOut(amount=0, target=tk)
+ self.summary_outs_money += dst_entr.amount
+ self._log_trace(6)
+
+ # Tx header prefix hashing
+ await self.tx_prefix_hasher.ar.field(tx_out, TxOut)
+ gc.collect()
+
+ # Hmac dest_entr.
+ hmac_vouti = await self.gen_hmac_vouti(dst_entr, tx_out, self.out_idx)
+ gc.collect()
+ self._log_trace(7)
+
+ # Range proof, out_pk, ecdh_info
+ rsig, out_pk, ecdh_info = await self.range_proof(
+ self.out_idx,
+ dest_pub_key=tk.key,
+ amount=dst_entr.amount,
+ amount_key=amount_key,
+ )
+ gc.collect()
+ self._log_trace(8)
+
+ # Incremental hashing of the ECDH info.
+ # RctSigBase allows to hash only one of the (ecdh, out_pk) as they are serialized
+ # as whole vectors. Hashing ECDH info saves state space.
+ await self.full_message_hasher.set_ecdh(ecdh_info)
+ self._log_trace(9)
+
+ # Output_pk is stored to the state as it is used during the signature and hashed to the
+ # RctSigBase later.
+ self.output_pk.append(out_pk)
+ gc.collect()
+
+ self._log_trace(10)
+ from trezor.messages.MoneroTransactionSetOutputAck import (
+ MoneroTransactionSetOutputAck
+ )
+ from apps.monero.xmr.serialize_messages.ct_keys import CtKey
+
+ return MoneroTransactionSetOutputAck(
+ tx_out=await misc.dump_msg(tx_out, preallocate=34),
+ vouti_hmac=hmac_vouti,
+ rsig=rsig, # rsig is already byte-encoded
+ out_pk=await misc.dump_msg(out_pk, preallocate=64, msg_type=CtKey),
+ ecdh_info=await misc.dump_msg(ecdh_info, preallocate=64),
+ )
+
+ async def all_out1_set_tx_extra(self):
+ from apps.monero.xmr.sub import tsx_helper
+
+ self.tx.extra = tsx_helper.add_tx_pub_key_to_extra(self.tx.extra, self.r_pub)
+
+ # Not needed to remove - extra is clean
+ # self.tx.extra = await monero.remove_field_from_tx_extra(self.tx.extra, xmrtypes.TxExtraAdditionalPubKeys)
+ if self.need_additional_txkeys:
+ self.tx.extra = await tsx_helper.add_additional_tx_pub_keys_to_extra(
+ self.tx.extra, pub_enc=self.additional_tx_public_keys
+ )
+
+ async def all_out1_set_tx_prefix(self):
+ from apps.monero.xmr.serialize.message_types import BlobType
+
+ await self.tx_prefix_hasher.ar.message_field(
+ self.tx, ("extra", BlobType)
+ ) # extra
+
+ self.tx_prefix_hash = self.tx_prefix_hasher.kwriter.get_digest()
+ self.tx_prefix_hasher = None
+
+ # Hash message to the final_message
+ await self.full_message_hasher.set_message(self.tx_prefix_hash)
+
+ async def all_out1_set(self):
+ """
+ All outputs were set in this phase. Computes additional public keys (if needed), tx.extra and
+ transaction prefix hash.
+ Adds additional public keys to the tx.extra
+
+ :return: tx.extra, tx_prefix_hash
+ """
+ self._log_trace(0)
+ self.state.set_output_done()
+ await self.trezor.iface.transaction_step(self.STEP_ALL_OUT)
+ self._log_trace(1)
+
+ if self.out_idx + 1 != self.num_dests():
+ raise ValueError("Invalid out num")
+
+ # Test if \sum Alpha == \sum A
+ if self.use_simple_rct:
+ self.assrt(crypto.sc_eq(self.sumout, self.sumpouts_alphas))
+
+ # Fee test
+ if self.fee != (self.summary_inputs_money - self.summary_outs_money):
+ raise ValueError(
+ "Fee invalid %s vs %s, out: %s"
+ % (
+ self.fee,
+ self.summary_inputs_money - self.summary_outs_money,
+ self.summary_outs_money,
+ )
+ )
+ self._log_trace(2)
+
+ # Set public key to the extra
+ # Not needed to remove - extra is clean
+ await self.all_out1_set_tx_extra()
+ self.additional_tx_public_keys = None
+
+ gc.collect()
+ self._log_trace(3)
+
+ if self.summary_outs_money > self.summary_inputs_money:
+ raise ValueError(
+ "Transaction inputs money (%s) less than outputs money (%s)"
+ % (self.summary_inputs_money, self.summary_outs_money)
+ )
+
+ # Hashing transaction prefix
+ await self.all_out1_set_tx_prefix()
+ extra_b = self.tx.extra
+ self.tx = None
+ gc.collect()
+ self._log_trace(4)
+
+ # Txprefix match check for multisig
+ if not common.is_empty(self.exp_tx_prefix_hash) and not common.ct_equal(
+ self.exp_tx_prefix_hash, self.tx_prefix_hash
+ ):
+ self.state.set_fail()
+ raise misc.TrezorTxPrefixHashNotMatchingError("Tx prefix invalid")
+
+ gc.collect()
+ self._log_trace(5)
+
+ from trezor.messages.MoneroRingCtSig import MoneroRingCtSig
+ from trezor.messages.MoneroTransactionAllOutSetAck import (
+ MoneroTransactionAllOutSetAck
+ )
+
+ rv = self.init_rct_sig()
+ rv_pb = MoneroRingCtSig(txn_fee=rv.txnFee, message=rv.message, rv_type=rv.type)
+ return MoneroTransactionAllOutSetAck(
+ extra=extra_b, tx_prefix_hash=self.tx_prefix_hash, rv=rv_pb
+ )
+
+ async def tsx_mlsag_ecdh_info(self):
+ """
+ Sets ecdh info for the incremental hashing mlsag.
+
+ :return:
+ """
+ pass
+
+ async def tsx_mlsag_out_pk(self):
+ """
+ Sets out_pk for the incremental hashing mlsag.
+
+ :return:
+ """
+ if self.num_dests() != len(self.output_pk):
+ raise ValueError("Invalid number of ecdh")
+
+ for out in self.output_pk:
+ await self.full_message_hasher.set_out_pk(out)
+
+ async def mlsag_done(self):
+ """
+ MLSAG message computed.
+
+ :return:
+ """
+ from trezor.messages.MoneroTransactionMlsagDoneAck import (
+ MoneroTransactionMlsagDoneAck
+ )
+
+ self.state.set_final_message_done()
+ await self.trezor.iface.transaction_step(self.STEP_MLSAG)
+
+ await self.tsx_mlsag_ecdh_info()
+ await self.tsx_mlsag_out_pk()
+ await self.full_message_hasher.rctsig_base_done()
+ self.out_idx = -1
+ self.inp_idx = -1
+
+ self.full_message = await self.full_message_hasher.get_digest()
+ self.full_message_hasher = None
+
+ return MoneroTransactionMlsagDoneAck(full_message_hash=self.full_message)
+
+ async def sign_input(
+ self,
+ src_entr,
+ vini,
+ hmac_vini,
+ pseudo_out,
+ pseudo_out_hmac,
+ alpha_enc,
+ spend_enc,
+ ):
+ """
+ Generates a signature for one input.
+
+ :param src_entr: Source entry
+ :type src_entr: apps.monero.xmr.serialize_messages.tx_construct.TxSourceEntry
+ :param vini: tx.vin[i] for the transaction. Contains key image, offsets, amount (usually zero)
+ :param hmac_vini: HMAC for the tx.vin[i] as returned from Trezor
+ :param pseudo_out: pedersen commitment for the current input, uses alpha as the mask.
+ Only in memory offloaded scenario. Tuple containing HMAC, as returned from the Trezor.
+ :param pseudo_out_hmac:
+ :param alpha_enc: alpha mask for the current input. Only in memory offloaded scenario,
+ tuple as returned from the Trezor
+ :param spend_enc:
+ :return: Generated signature MGs[i]
+ """
+ self.state.set_signature()
+ await self.trezor.iface.transaction_step(
+ self.STEP_SIGN, self.inp_idx + 1, self.num_inputs()
+ )
+
+ self.inp_idx += 1
+ if self.inp_idx >= self.num_inputs():
+ raise ValueError("Invalid ins")
+ if self.use_simple_rct and (not self.in_memory() and alpha_enc is None):
+ raise ValueError("Inconsistent1")
+ if self.use_simple_rct and (not self.in_memory() and pseudo_out is None):
+ raise ValueError("Inconsistent2")
+ if self.inp_idx >= 1 and not self.use_simple_rct:
+ raise ValueError("Inconsistent3")
+
+ inv_idx = self.source_permutation[self.inp_idx]
+
+ # Check HMAC of all inputs
+ hmac_vini_comp = await self.gen_hmac_vini(src_entr, vini, inv_idx)
+ if not common.ct_equal(hmac_vini_comp, hmac_vini):
+ raise ValueError("HMAC is not correct")
+
+ gc.collect()
+ self._log_trace(1)
+
+ if self.use_simple_rct and not self.in_memory():
+ pseudo_out_hmac_comp = crypto.compute_hmac(
+ self.hmac_key_txin_comm(inv_idx), pseudo_out
+ )
+ if not common.ct_equal(pseudo_out_hmac_comp, pseudo_out_hmac):
+ raise ValueError("HMAC is not correct")
+
+ gc.collect()
+ self._log_trace(2)
+
+ from apps.monero.xmr.enc import chacha_poly
+
+ alpha_c = crypto.decodeint(
+ chacha_poly.decrypt_pack(
+ self.enc_key_txin_alpha(inv_idx), bytes(alpha_enc)
+ )
+ )
+ pseudo_out_c = crypto.decodepoint(pseudo_out)
+
+ elif self.use_simple_rct:
+ alpha_c = self.input_alphas[self.inp_idx]
+ pseudo_out_c = crypto.decodepoint(self.input_pseudo_outs[self.inp_idx])
+
+ else:
+ alpha_c = None
+ pseudo_out_c = None
+
+ # Spending secret
+ if self.many_inputs():
+ from apps.monero.xmr.enc import chacha_poly
+
+ input_secret = crypto.decodeint(
+ chacha_poly.decrypt_pack(self.enc_key_spend(inv_idx), bytes(spend_enc))
+ )
+ else:
+ input_secret = self.input_secrets[self.inp_idx]
+
+ gc.collect()
+ self._log_trace(3)
+
+ # Basic setup, sanity check
+ index = src_entr.real_output
+ in_sk = misc.StdObj(dest=input_secret, mask=crypto.decodeint(src_entr.mask))
+ kLRki = src_entr.multisig_kLRki if self.multi_sig else None
+
+ # Private key correctness test
+ self.assrt(
+ crypto.point_eq(
+ crypto.decodepoint(src_entr.outputs[src_entr.real_output][1].dest),
+ crypto.scalarmult_base(in_sk.dest),
+ ),
+ "a1",
+ )
+ self.assrt(
+ crypto.point_eq(
+ crypto.decodepoint(src_entr.outputs[src_entr.real_output][1].mask),
+ crypto.gen_c(in_sk.mask, src_entr.amount),
+ ),
+ "a2",
+ )
+
+ gc.collect()
+ self._log_trace(4)
+
+ # RCT signature
+ gc.collect()
+ from apps.monero.xmr import mlsag2
+
+ mg = None
+ if self.use_simple_rct:
+ # Simple RingCT
+ mix_ring = [x[1] for x in src_entr.outputs]
+ mg, msc = mlsag2.prove_rct_mg_simple(
+ self.full_message,
+ mix_ring,
+ in_sk,
+ alpha_c,
+ pseudo_out_c,
+ kLRki,
+ None,
+ index,
+ )
+
+ else:
+ # Full RingCt, only one input
+ txn_fee_key = crypto.scalarmult_h(self.get_fee())
+ mix_ring = [[x[1]] for x in src_entr.outputs]
+
+ mg, msc = mlsag2.prove_rct_mg(
+ self.full_message,
+ mix_ring,
+ [in_sk],
+ self.output_sk,
+ self.output_pk,
+ kLRki,
+ None,
+ index,
+ txn_fee_key,
+ )
+
+ gc.collect()
+ self._log_trace(5)
+
+ # Encode
+ from apps.monero.xmr.sub.recode import recode_msg
+
+ mgs = recode_msg([mg])
+ cout = None
+
+ gc.collect()
+ self._log_trace(6)
+
+ # Multisig values returned encrypted, keys returned after finished successfully.
+ if self.multi_sig:
+ from apps.monero.xmr.enc import chacha_poly
+
+ cout = chacha_poly.encrypt_pack(self.enc_key_cout(), crypto.encodeint(msc))
+
+ # Final state transition
+ if self.inp_idx + 1 == self.num_inputs():
+ self.state.set_signature_done()
+ await self.trezor.iface.transaction_signed()
+
+ gc.collect()
+ self._log_trace()
+
+ from trezor.messages.MoneroTransactionSignInputAck import (
+ MoneroTransactionSignInputAck
+ )
+
+ return MoneroTransactionSignInputAck(
+ signature=await misc.dump_msg_gc(mgs[0], preallocate=488, del_msg=True),
+ cout=cout,
+ )
+
+ async def final_msg(self, *args, **kwargs):
+ """
+ Final step after transaction signing.
+
+ :param args:
+ :param kwargs:
+ :return:
+ """
+ from trezor.messages.MoneroTransactionFinalAck import MoneroTransactionFinalAck
+ from apps.monero.xmr.enc import chacha_poly
+
+ self.state.set_final()
+
+ cout_key = self.enc_key_cout() if self.multi_sig else None
+
+ # Encrypted tx keys under transaction specific key, derived from txhash and spend key.
+ # Deterministic transaction key, so we can recover it just from transaction and the spend key.
+ tx_key, salt, rand_mult = misc.compute_tx_key(
+ self.creds.spend_key_private, self.tx_prefix_hash
+ )
+
+ key_buff = crypto.encodeint(self.r) + b"".join(
+ [crypto.encodeint(x) for x in self.additional_tx_private_keys]
+ )
+ tx_enc_keys = chacha_poly.encrypt_pack(tx_key, key_buff)
+
+ await self.trezor.iface.transaction_finished()
+ gc.collect()
+
+ return MoneroTransactionFinalAck(
+ cout_key=cout_key, salt=salt, rand_mult=rand_mult, tx_enc_keys=tx_enc_keys
+ )
diff --git a/src/apps/monero/protocol/tsx_sign_state.py b/src/apps/monero/protocol/tsx_sign_state.py
new file mode 100644
index 000000000..54d447fe5
--- /dev/null
+++ b/src/apps/monero/protocol/tsx_sign_state.py
@@ -0,0 +1,105 @@
+from micropython import const
+
+
+class TState(object):
+ """
+ Transaction state
+ """
+
+ START = const(0)
+ INIT = const(1)
+ INP_CNT = const(2)
+ INPUT = const(3)
+ INPUT_DONE = const(4)
+ INPUT_PERM = const(5)
+ INPUT_VINS = const(6)
+ OUTPUT = const(7)
+ OUTPUT_DONE = const(8)
+ FINAL_MESSAGE = const(9)
+ SIGNATURE = const(10)
+ SIGNATURE_DONE = const(11)
+ FINAL = const(12)
+ FAIL = const(250)
+
+ def __init__(self):
+ self.s = self.START
+ self.in_mem = False
+
+ def state_save(self):
+ return self.s, self.in_mem
+
+ def state_load(self, x):
+ self.s, self.in_mem = x
+
+ def init_tsx(self):
+ if self.s != self.START:
+ raise ValueError("Illegal state")
+ self.s = self.INIT
+
+ def inp_cnt(self, in_mem):
+ if self.s != self.INIT:
+ raise ValueError("Illegal state")
+ self.s = self.INP_CNT
+ self.in_mem = in_mem
+
+ def input(self):
+ if self.s != self.INP_CNT and self.s != self.INPUT:
+ raise ValueError("Illegal state")
+ self.s = self.INPUT
+
+ def input_done(self):
+ if self.s != self.INPUT:
+ raise ValueError("Illegal state")
+ self.s = self.INPUT_DONE
+
+ def input_permutation(self):
+ if self.s != self.INPUT_DONE:
+ raise ValueError("Illegal state")
+ self.s = self.INPUT_PERM
+
+ def input_vins(self):
+ if self.s != self.INPUT_PERM and self.s != self.INPUT_VINS:
+ raise ValueError("Illegal state")
+ self.s = self.INPUT_VINS
+
+ def is_input_vins(self):
+ return self.s == self.INPUT_VINS
+
+ def set_output(self):
+ if (
+ (not self.in_mem and self.s != self.INPUT_VINS)
+ or (self.in_mem and self.s != self.INPUT_PERM)
+ ) and self.s != self.OUTPUT:
+ raise ValueError("Illegal state")
+ self.s = self.OUTPUT
+
+ def set_output_done(self):
+ if self.s != self.OUTPUT:
+ raise ValueError("Illegal state")
+ self.s = self.OUTPUT_DONE
+
+ def set_final_message_done(self):
+ if self.s != self.OUTPUT_DONE:
+ raise ValueError("Illegal state")
+ self.s = self.FINAL_MESSAGE
+
+ def set_signature(self):
+ if self.s != self.FINAL_MESSAGE and self.s != self.SIGNATURE:
+ raise ValueError("Illegal state")
+ self.s = self.SIGNATURE
+
+ def set_signature_done(self):
+ if self.s != self.SIGNATURE:
+ raise ValueError("Illegal state")
+ self.s = self.SIGNATURE_DONE
+
+ def set_final(self):
+ if self.s != self.SIGNATURE_DONE:
+ raise ValueError("Illegal state")
+ self.s = self.FINAL
+
+ def set_fail(self):
+ self.s = self.FAIL
+
+ def is_terminal(self):
+ return self.s in [self.FINAL, self.FAIL]
diff --git a/src/apps/monero/protocol/tsx_sign_state_holder.py b/src/apps/monero/protocol/tsx_sign_state_holder.py
new file mode 100644
index 000000000..3d0cd12fa
--- /dev/null
+++ b/src/apps/monero/protocol/tsx_sign_state_holder.py
@@ -0,0 +1,50 @@
+class TsxSignStateHolder(object):
+ """
+ Simple transaction signer state holder.
+ Externalized state uses smaller amount of memory compared to storing the builder instance in the state.
+ Moreover the state contains stripped down attributes, i.e., instead of heavy hashers only sha3 context
+ is preserved and hashers are re-initialized on the next protocol step.
+ """
+
+ def __init__(self, **kwargs):
+ self.creds = None
+ self.key_master = None
+ self.key_hmac = None
+ self.key_enc = None
+
+ self.r = None # txkey
+ self.r_pub = None
+ self.state = None
+
+ self.multi_sig = False
+ self.need_additional_txkeys = False
+ self.use_bulletproof = False
+ self.use_rct = True
+ self.use_simple_rct = False
+ self.input_count = 0
+ self.output_count = 0
+ self.output_change = None
+ self.mixin = 0
+ self.fee = 0
+
+ self.additional_tx_private_keys = []
+ self.additional_tx_public_keys = []
+ self.inp_idx = -1
+ self.out_idx = -1
+ self.summary_inputs_money = 0
+ self.summary_outs_money = 0
+ self.input_secrets = []
+ self.input_alphas = []
+ self.input_pseudo_outs = []
+ self.output_sk = []
+ self.output_pk = []
+ self.sumout = None
+ self.sumpouts_alphas = None
+ self.subaddresses = {}
+ self.tx = None
+ self.source_permutation = [] # sorted by key images
+ self.tx_prefix_hasher = None
+ self.tx_prefix_hash = None
+ self.full_message_hasher = None
+ self.full_message = None
+ self.exp_tx_prefix_hash = None
diff --git a/src/apps/monero/sign_tx.py b/src/apps/monero/sign_tx.py
new file mode 100644
index 000000000..8541a5ce9
--- /dev/null
+++ b/src/apps/monero/sign_tx.py
@@ -0,0 +1,44 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# Author: Dusan Klinec, ph4r05, 2018
+
+import gc
+import micropython
+
+from trezor import log
+
+
+async def layout_sign_tx(state, ctx, msg):
+ gc.threshold(gc.mem_free() // 4 + gc.mem_alloc())
+ log.debug(
+ __name__,
+ "############################ TSX. Free: {} Allocated: {} thr: {}".format(
+ gc.mem_free(), gc.mem_alloc(), gc.mem_free() // 4 + gc.mem_alloc()
+ ),
+ )
+ gc.collect()
+ micropython.mem_info()
+
+ from apps.monero.protocol.tsx_sign import TsxSigner
+
+ log.debug(
+ __name__,
+ "TsxSigner. Free: {} Allocated: {}".format(gc.mem_free(), gc.mem_alloc()),
+ )
+ log.debug(__name__, "TsxState: %s", state.ctx_sign)
+ gc.collect()
+
+ try:
+ signer = TsxSigner()
+ res = await signer.sign(ctx, state.ctx_sign, msg)
+ if await signer.should_purge():
+ state.ctx_sign = None
+ else:
+ state.ctx_sign = await signer.state_save()
+
+ return res
+
+ except Exception as e:
+ state.ctx_sign = None
+ log.error(__name__, "Tsx exception: %s %s", type(e), e)
+ raise
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/common.py b/src/apps/monero/xmr/common.py
new file mode 100644
index 000000000..c0e52c580
--- /dev/null
+++ b/src/apps/monero/xmr/common.py
@@ -0,0 +1,108 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# Author: Dusan Klinec, ph4r05, 2018
+
+
+from trezor.crypto import monero, random
+
+
+class XmrException(Exception):
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+
+
+def random_bytes(by):
+ """
+ Generates X random bytes, returns byte-string
+ :param by:
+ :return:
+ """
+ return random.bytes(by)
+
+
+def ct_equal(a, b):
+ """
+ Constant time a,b comparison
+ :param a:
+ :param b:
+ :return:
+ """
+ return monero.ct_equals(a, b)
+
+
+def memcpy(dst, dst_from, src, src_from, length):
+ from trezor.utils import memcpy
+
+ return memcpy(dst, dst_from, src, src_from, length)
+
+
+def check_permutation(permutation):
+ """
+ Check permutation sanity
+ :param permutation:
+ :return:
+ """
+ for n in range(len(permutation)):
+ if n not in permutation:
+ raise ValueError("Invalid permutation")
+
+
+def apply_permutation(permutation, swapper):
+ """
+ Apply permutation from idx. Used for in-place permutation application with swapper.
+ Ported from Monero.
+ :param permutation:
+ :param swapper: function(x,y)
+ :return:
+ """
+ check_permutation(permutation)
+ perm = list(permutation)
+ for i in range(len(perm)):
+ current = i
+ while i != perm[current]:
+ nxt = perm[current]
+ swapper(current, nxt)
+ perm[current] = current
+ current = nxt
+ perm[current] = current
+
+
+def is_empty(inp):
+ """
+ True if none or empty
+ :param inp:
+ :return:
+ """
+ return inp is None or len(inp) == 0
+
+
+def defval(val, default=None):
+ """
+ Returns val if is not None, default instead
+ :param val:
+ :param default:
+ :return:
+ """
+ return val if val is not None else default
+
+
+def defval_empty(val, default=None):
+ """
+ Returns val if is not None, default instead
+ :param val:
+ :param default:
+ :return:
+ """
+ return val if not is_empty(val) else default
+
+
+def chunk(arr, size=1):
+ res = []
+ idx = 0
+ while True:
+ c = arr[idx : idx + size]
+ res.append(c)
+ idx += size
+ if len(c) != size:
+ break
+ return res
diff --git a/src/apps/monero/xmr/crypto.py b/src/apps/monero/xmr/crypto.py
new file mode 100644
index 000000000..7fa8908f1
--- /dev/null
+++ b/src/apps/monero/xmr/crypto.py
@@ -0,0 +1,647 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# 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
+
+import ubinascii as binascii
+
+from trezor.crypto import hmac, monero as tcry, pbkdf2 as tpbkdf2, random
+from trezor.crypto.hashlib import sha3_256
+
+NULL_KEY_ENC = [0] * 32
+
+
+def random_bytes(by):
+ """
+ Generates X random bytes, returns byte-string
+ :param by:
+ :return:
+ """
+ return random.bytes(by)
+
+
+def keccak_factory(data=None):
+ return sha3_256(data=data, keccak=True)
+
+
+def get_keccak():
+ """
+ Simple keccak 256
+ :return:
+ """
+ return keccak_factory()
+
+
+def keccak_hash(inp):
+ """
+ Hashesh input in one call
+ :return:
+ """
+ return tcry.xmr_fast_hash(inp)
+
+
+def keccak_2hash(inp):
+ """
+ Keccak double hashing
+ :param inp:
+ :return:
+ """
+ return keccak_hash(keccak_hash(inp))
+
+
+def get_hmac(key, msg=None):
+ """
+ Returns HMAC object (uses Keccak256)
+ :param key:
+ :param msg:
+ :return:
+ """
+ return hmac.new(key, msg=msg, digestmod=keccak_factory)
+
+
+def compute_hmac(key, msg=None):
+ """
+ Computes and returns HMAC of the msg using Keccak256
+ :param key:
+ :param msg:
+ :return:
+ """
+ h = hmac.new(key, msg=msg, digestmod=keccak_factory)
+ return h.digest()
+
+
+def pbkdf2(inp, salt, length=32, count=1000, prf=None):
+ """
+ PBKDF2 with default PRF as HMAC-KECCAK-256
+ :param inp:
+ :param salt:
+ :param length:
+ :param count:
+ :param prf:
+ :return:
+ """
+ pb = tpbkdf2("hmac-sha256", inp, salt)
+ pb.update(count)
+ return pb.key()
+
+
+#
+# EC
+#
+
+
+def decodepoint(x):
+ return tcry.ge25519_unpack_vartime(x)
+
+
+def encodepoint(pt):
+ return tcry.ge25519_pack(pt)
+
+
+def encodepoint_into(pt, b):
+ return tcry.ge25519_pack_into(pt, b)
+
+
+def decodeint(x):
+ return tcry.unpack256_modm(x)
+
+
+def encodeint(x):
+ return tcry.pack256_modm(x)
+
+
+def encodeint_into(x, b):
+ return tcry.pack256_modm_into(x, b)
+
+
+def check_ed25519point(x):
+ return tcry.ge25519_check(x)
+
+
+def scalarmult_base(a):
+ return tcry.ge25519_scalarmult_base(a)
+
+
+def scalarmult(P, e):
+ return tcry.ge25519_scalarmult(P, e)
+
+
+def point_add(P, Q):
+ return tcry.ge25519_add(P, Q, 0)
+
+
+def point_sub(P, Q):
+ return tcry.ge25519_add(P, Q, 1)
+
+
+def point_eq(P, Q):
+ return tcry.ge25519_eq(P, Q)
+
+
+def point_double(P):
+ return tcry.ge25519_double(P)
+
+
+def point_norm(P):
+ """
+ Normalizes point after multiplication
+ Extended edwards coordinates (X,Y,Z,T)
+ :param P:
+ :return:
+ """
+ return tcry.ge25519_norm(P)
+
+
+#
+# Zmod(order), scalar values field
+#
+
+
+def sc_0():
+ """
+ Sets 0 to the scalar value Zmod(m)
+ :return:
+ """
+ return tcry.init256_modm(0)
+
+
+def sc_init(x):
+ """
+ Sets x to the scalar value Zmod(m)
+ :return:
+ """
+ if x >= (1 << 64):
+ raise ValueError("Initialization works up to 64-bit only")
+ return tcry.init256_modm(x)
+
+
+def sc_get64(x):
+ """
+ Returns 64bit value from the sc
+ :param x:
+ :return:
+ """
+ return tcry.get256_modm(x)
+
+
+def sc_check(key):
+ """
+ sc_check is not relevant for long-integer scalar representation.
+
+ :param key:
+ :return:
+ """
+ tcry.check256_modm(key)
+ return 0
+
+
+def check_sc(key):
+ """
+ throws exception on invalid key
+ :param key:
+ :return:
+ """
+ if sc_check(key) != 0:
+ raise ValueError("Invalid scalar value")
+
+
+def sc_reduce32(data):
+ """
+ Exactly the same as sc_reduce (which is default lib sodium)
+ except it is assumed that your input s is alread in the form:
+ s[0]+256*s[1]+...+256^31*s[31] = s
+
+ And the rest is reducing mod l,
+ so basically take a 32 byte input, and reduce modulo the prime.
+ :param data:
+ :return:
+ """
+ return tcry.reduce256_modm(data)
+
+
+def sc_add(aa, bb):
+ """
+ Scalar addition
+ :param aa:
+ :param bb:
+ :return:
+ """
+ return tcry.add256_modm(aa, bb)
+
+
+def sc_sub(aa, bb):
+ """
+ Scalar subtraction
+ :param aa:
+ :param bb:
+ :return:
+ """
+ return tcry.sub256_modm(aa, bb)
+
+
+def sc_isnonzero(c):
+ """
+ Returns true if scalar is non-zero
+ :param c:
+ :return:
+ """
+ return not tcry.iszero256_modm(c)
+
+
+def sc_eq(a, b):
+ """
+ Returns true if scalars are equal
+ :param a:
+ :param b:
+ :return:
+ """
+ return tcry.eq256_modm(a, b)
+
+
+def sc_mulsub(aa, bb, cc):
+ """
+ (cc - aa * bb) % l
+ :param aa:
+ :param bb:
+ :param cc:
+ :return:
+ """
+ return tcry.mulsub256_modm(aa, bb, cc)
+
+
+def random_scalar():
+ return tcry.xmr_random_scalar()
+
+
+#
+# GE - ed25519 group
+#
+
+
+def ge_scalarmult(a, A):
+ check_ed25519point(A)
+ return scalarmult(A, a)
+
+
+def ge_mul8(P):
+ check_ed25519point(P)
+ return tcry.ge25519_mul8(P)
+
+
+def ge_scalarmult_base(a):
+ a = sc_reduce32(a)
+ return scalarmult_base(a)
+
+
+def ge_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
+ where a = a[0]+256*a[1]+...+256^31 a[31].
+ and b = b[0]+256*b[1]+...+256^31 b[31].
+ B is the Ed25519 base point (x,4/5) with x positive.
+
+ :param a:
+ :param A:
+ :param b:
+ :return:
+ """
+ R = tcry.ge25519_double_scalarmult_vartime(A, a, b)
+ tcry.ge25519_norm(R, R)
+ return R
+
+
+def ge_double_scalarmult_base_vartime2(a, A, b, B):
+ """
+ void ge25519_double_scalarmult_vartime2(ge25519 *r, const ge25519 *p1, const bignum256modm s1, const ge25519 *p2, const bignum256modm s2);
+ r = a * A + b * B
+
+ :param a:
+ :param A:
+ :param b:
+ :param B:
+ :return:
+ """
+ R = tcry.ge25519_double_scalarmult_vartime2(A, a, B, b)
+ tcry.ge25519_norm(R, R)
+ return R
+
+
+def ge_double_scalarmult_precomp_vartime(a, A, b, Bi):
+ """
+ void ge_double_scalarmult_precomp_vartime(ge_p2 *r, const unsigned char *a, const ge_p3 *A, const unsigned char *b, const ge_dsmp Bi)
+ :return:
+ """
+ return ge_double_scalarmult_precomp_vartime2(a, A, b, Bi)
+
+
+def ge_double_scalarmult_precomp_vartime2(a, Ai, b, Bi):
+ """
+ void ge_double_scalarmult_precomp_vartime2(ge_p2 *r, const unsigned char *a, const ge_dsmp Ai, const unsigned char *b, const ge_dsmp Bi)
+ :param a:
+ :param Ai:
+ :param b:
+ :param Bi:
+ :return:
+ """
+ return tcry.xmr_add_keys3(a, Ai, b, Bi)
+
+
+def identity(byte_enc=False):
+ """
+ Identity point
+ :return:
+ """
+ idd = tcry.ge25519_set_neutral()
+ return idd if not byte_enc else encodepoint(idd)
+
+
+def ge_frombytes_vartime_check(point):
+ """
+ https://www.imperialviolet.org/2013/12/25/elligator.html
+ http://elligator.cr.yp.to/
+ http://elligator.cr.yp.to/elligator-20130828.pdf
+
+ Basically it takes some bytes of data
+ converts to a point on the edwards curve
+ if the bytes aren't on the curve
+ also does some checking on the numbers
+ ex. your secret key has to be at least >= 4294967277
+ also it rejects certain curve points, i.e. "if x = 0, sign must be positive"
+
+ sqrt(s) = s^((q+3) / 8) if s^((q+3)/4) == s
+ = sqrt(-1) s ^((q+3) / 8) otherwise
+
+ :param point:
+ :return:
+ """
+ # if tcry.ge25519_check(point) != 1:
+ # raise ValueError('Point check failed')
+ #
+ # return 0
+ tcry.ge25519_check(point)
+ return 0
+
+
+def ge_frombytes_vartime(point):
+ """
+ https://www.imperialviolet.org/2013/12/25/elligator.html
+
+ :param point:
+ :return:
+ """
+ ge_frombytes_vartime_check(point)
+ return point
+
+
+def precomp(point):
+ """
+ Precomputation placeholder
+ :param point:
+ :return:
+ """
+ return point
+
+
+def ge_dsm_precomp(point):
+ """
+ void ge_dsm_precomp(ge_dsmp r, const ge_p3 *s)
+ :param point:
+ :return:
+ """
+ return point
+
+
+#
+# Monero specific
+#
+
+
+def cn_fast_hash(buff):
+ """
+ Keccak 256, original one (before changes made in SHA3 standard)
+ :param buff:
+ :return:
+ """
+ return keccak_hash(buff)
+
+
+def hash_to_scalar(data, length=None):
+ """
+ H_s(P)
+ :param data:
+ :param length:
+ :return:
+ """
+ dt = data[:length] if length else data
+ return tcry.xmr_hash_to_scalar(bytes(dt))
+
+
+def hash_to_ec(buf):
+ """
+ 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
+ :param buf:
+ :return:
+ """
+ return tcry.xmr_hash_to_ec(buf)
+
+
+#
+# XMR
+#
+
+
+def gen_H():
+ """
+ Returns point H
+ 8b655970153799af2aeadc9ff1add0ea6c7251d54154cfa92c173a0dd39c1f94
+ :return:
+ """
+ return tcry.ge25519_set_h()
+
+
+def scalarmult_h(i):
+ return scalarmult(gen_H(), sc_init(i) if isinstance(i, int) else i)
+
+
+def add_keys2(a, b, B):
+ """
+ aG + bB, G is basepoint
+ :param a:
+ :param b:
+ :param B:
+ :return:
+ """
+ return tcry.xmr_add_keys2_vartime(a, b, B)
+
+
+def add_keys3(a, A, b, B):
+ """
+ aA + bB
+ :param a:
+ :param A:
+ :param b:
+ :param B:
+ :return:
+ """
+ return tcry.xmr_add_keys3_vartime(a, A, b, B)
+
+
+def gen_c(a, amount):
+ """
+ Generates Pedersen commitment
+ C = aG + bH
+
+ :param a:
+ :param amount:
+ :return:
+ """
+ return tcry.xmr_gen_c(a, amount)
+
+
+def generate_key_derivation(key1, key2):
+ """
+ Key derivation: 8*(key2*key1)
+
+ :param key1: public key of receiver Bob (see page 7)
+ :param key2: Alice's private
+ :return:
+ """
+ if sc_check(key2) != 0:
+ # checks that the secret key is uniform enough...
+ raise ValueError("error in sc_check in keyder")
+ if ge_frombytes_vartime_check(key1) != 0:
+ raise ValueError("didn't pass curve checks in keyder")
+
+ return tcry.xmr_generate_key_derivation(key1, key2)
+
+
+def derivation_to_scalar(derivation, output_index):
+ """
+ H_s(derivation || varint(output_index))
+ :param derivation:
+ :param output_index:
+ :return:
+ """
+ check_ed25519point(derivation)
+ return tcry.xmr_derivation_to_scalar(derivation, output_index)
+
+
+def derive_public_key(derivation, output_index, base):
+ """
+ H_s(derivation || varint(output_index))G + base
+
+ :param derivation:
+ :param output_index:
+ :param base:
+ :return:
+ """
+ if ge_frombytes_vartime_check(base) != 0: # check some conditions on the point
+ raise ValueError("derive pub key bad point")
+ check_ed25519point(base)
+
+ return tcry.xmr_derive_public_key(derivation, output_index, base)
+
+
+def derive_secret_key(derivation, output_index, base):
+ """
+ base + H_s(derivation || varint(output_index))
+ :param derivation:
+ :param output_index:
+ :param base:
+ :return:
+ """
+ if sc_check(base) != 0:
+ raise ValueError("cs_check in derive_secret_key")
+ return tcry.xmr_derive_private_key(derivation, output_index, base)
+
+
+def prove_range(amount, last_mask=None, *args, **kwargs):
+ """
+ Range proof provided by the backend. Implemented in C for speed.
+
+ :param amount:
+ :param last_mask:
+ :return:
+ """
+ C, a, R = tcry.gen_range_proof(amount, last_mask, *args, **kwargs)
+
+ # Trezor micropython extmod returns byte-serialized/flattened rsig
+ return C, a, R
+
+
+def b16_to_scalar(bts):
+ """
+ Converts hexcoded bytearray to the scalar
+ :param bts:
+ :return:
+ """
+ return decodeint(binascii.unhexlify(bts))
+
+
+#
+# Repr invariant
+#
+
+
+def hmac_point(key, point):
+ """
+ HMAC single point
+ :param key:
+ :param point:
+ :return:
+ """
+ return compute_hmac(key, encodepoint(point))
+
+
+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)
+
+ :param data:
+ :param priv:
+ :return:
+ """
+ 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
+
+ :param data:
+ :param pub:
+ :param c:
+ :param r:
+ :return:
+ """
+ check_ed25519point(pub)
+ c = sc_reduce32(c)
+ r = sc_reduce32(r)
+ 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/enc/__init__.py b/src/apps/monero/xmr/enc/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/src/apps/monero/xmr/enc/chacha_poly.py b/src/apps/monero/xmr/enc/chacha_poly.py
new file mode 100644
index 000000000..73964fb23
--- /dev/null
+++ b/src/apps/monero/xmr/enc/chacha_poly.py
@@ -0,0 +1,54 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# Author: Dusan Klinec, ph4r05, 2018
+
+from trezor.crypto import chacha20poly1305 as ChaCha20Poly1305, monero, random
+
+
+def encrypt(key, plaintext, associated_data=None):
+ """
+ Uses ChaCha20Poly1305 for encryption
+ :param key:
+ :param plaintext:
+ :param associated_data:
+ :return: iv, ciphertext, tag
+ """
+ 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
+ :param key:
+ :param iv:
+ :param ciphertext:
+ :param tag:
+ :param associated_data:
+ :return:
+ """
+ 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/key_image.py b/src/apps/monero/xmr/key_image.py
new file mode 100644
index 000000000..347fca6ea
--- /dev/null
+++ b/src/apps/monero/xmr/key_image.py
@@ -0,0 +1,51 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# Author: Dusan Klinec, ph4r05, 2018
+
+from apps.monero.xmr import common, crypto, ring_ct
+from apps.monero.xmr.serialize.int_serialize import dump_uvarint_b
+
+
+def compute_hash(rr):
+ """
+ Hash over output to ki-sync
+ :param rr:
+ :type rr: TransferDetails
+ :return:
+ """
+ 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()
+
+
+async def export_key_image(creds, subaddresses, td):
+ """
+ Key image export
+ :param creds:
+ :param subaddresses:
+ :param td:
+ :return:
+ """
+ out_key = crypto.decodepoint(td.out_key)
+ tx_pub_key = crypto.decodepoint(td.tx_pub_key)
+ additional_tx_pub_keys = []
+ if not common.is_empty(td.additional_tx_pub_keys):
+ additional_tx_pub_keys = [
+ crypto.decodepoint(x) for x in td.additional_tx_pub_keys
+ ]
+
+ ki, sig = ring_ct.export_key_image(
+ creds,
+ subaddresses,
+ out_key,
+ tx_pub_key,
+ additional_tx_pub_keys,
+ td.internal_output_index,
+ )
+
+ return ki, sig
diff --git a/src/apps/monero/xmr/mlsag2.py b/src/apps/monero/xmr/mlsag2.py
new file mode 100644
index 000000000..89d5dd4d8
--- /dev/null
+++ b/src/apps/monero/xmr/mlsag2.py
@@ -0,0 +1,356 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# Author: https://github.com/monero-project/mininero
+# Author: Dusan Klinec, ph4r05, 2018
+# see https://eprint.iacr.org/2015/1098.pdf
+
+from apps.monero.xmr import crypto
+
+
+def key_zero_vector(rows):
+ """
+ Empty key vector
+ :param rows:
+ :return:
+ """
+ vct = []
+ for i in range(rows):
+ vct.append(crypto.sc_0())
+ return vct
+
+
+def key_vector(rows):
+ """
+ Empty key vector
+ :param rows:
+ :return:
+ """
+ return [None] * rows
+
+
+def key_matrix(rows, cols):
+ """
+ first index is columns (so slightly backward from math)
+ :param rows:
+ :param cols:
+ :return:
+ """
+ rv = [None] * cols
+ for i in range(0, cols):
+ rv[i] = key_vector(rows)
+ return rv
+
+
+def hash_key_vector(v):
+ """
+ Hashes key vector
+ :param v:
+ :return:
+ """
+ return [crypto.hash_to_ec(vi) for vi in v]
+
+
+def key_image_vector(x):
+ """
+ Takes as input a keyvector, returns the keyimage-vector
+ TODO: use crypto for generating key images
+ :param x:
+ :return:
+ """
+ return [
+ crypto.scalarmult(crypto.hash_to_ec(crypto.scalarmult_base(xx)), xx) for xx in x
+ ]
+
+
+def scalar_gen_vector(n):
+ """
+ Generates vector of scalars
+ :param n:
+ :return:
+ """
+ return [crypto.random_scalar() for i in range(0, n)]
+
+
+#
+# Optimized versions with incremental hashing,
+# Simple and full variants for Monero
+#
+
+
+def hasher_message(message):
+ """
+ Returns incremental hasher for MLSAG
+ :param message:
+ :return:
+ """
+ from apps.monero.xmr.sub.keccak_hasher import HashWrapper
+
+ ctx = HashWrapper(crypto.get_keccak())
+ ctx.update(message)
+ ctx.zbuff = bytearray(32)
+ return ctx
+
+
+def hash_point(hasher, point):
+ crypto.encodepoint_into(point, hasher.zbuff)
+ hasher.update(hasher.zbuff)
+
+
+def gen_mlsag_assert(pk, xx, kLRki, mscout, index, dsRows):
+ """
+ Conditions check for gen_mlsag_ext.
+ :param pk:
+ :param xx:
+ :param kLRki:
+ :param mscout:
+ :param index:
+ :param dsRows:
+ :return:
+ """
+ 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 (not kLRki or not mscout) and (kLRki or mscout):
+ raise ValueError("Only one of kLRki/mscout is present")
+ if kLRki and dsRows != 1:
+ raise ValueError("Multisig requires exactly 1 dsRows")
+ return rows, cols
+
+
+def gen_mlsag_rows(message, rv, pk, xx, kLRki, index, dsRows, rows, cols):
+ """
+ MLSAG computation - the part with secret keys
+ :param message:
+ :param rv:
+ :param pk:
+ :param xx:
+ :param kLRki:
+ :param index:
+ :param dsRows:
+ :param rows:
+ :param cols:
+ :return:
+ """
+ Ip = key_vector(dsRows)
+ rv.II = key_vector(dsRows)
+ alpha = key_vector(rows)
+ rv.ss = key_matrix(rows, cols)
+
+ hasher = hasher_message(message)
+
+ for i in range(dsRows):
+ hasher.update(crypto.encodepoint(pk[index][i]))
+ if kLRki:
+ alpha[i] = kLRki.k
+ rv.II[i] = kLRki.ki
+ hash_point(hasher, kLRki.L)
+ hash_point(hasher, kLRki.R)
+
+ else:
+ Hi = crypto.hash_to_ec(
+ crypto.encodepoint(pk[index][i])
+ ) # originally hashToPoint()
+ alpha[i] = crypto.random_scalar()
+ aGi = crypto.scalarmult_base(alpha[i])
+ aHPi = crypto.scalarmult(Hi, alpha[i])
+ rv.II[i] = crypto.scalarmult(Hi, xx[i])
+ hash_point(hasher, aGi)
+ hash_point(hasher, aHPi)
+
+ Ip[i] = crypto.precomp(rv.II[i])
+
+ for i in range(dsRows, rows):
+ alpha[i] = crypto.random_scalar()
+ aGi = crypto.scalarmult_base(alpha[i])
+ hash_point(hasher, pk[index][i])
+ hash_point(hasher, aGi)
+
+ c_old = hasher.digest()
+ c_old = crypto.sc_reduce32(crypto.decodeint(c_old))
+ return c_old, Ip, alpha
+
+
+def gen_mlsag_ext(message, pk, xx, kLRki, mscout, index, dsRows):
+ """
+ Multilayered Spontaneous Anonymous Group Signatures (MLSAG signatures)
+
+ :param message:
+ :param pk: matrix of points, point form (not encoded)
+ :param xx:
+ :param kLRki:
+ :param mscout:
+ :param index:
+ :param dsRows:
+ :return:
+ """
+ from apps.monero.xmr.serialize_messages.tx_full import MgSig
+
+ rows, cols = gen_mlsag_assert(pk, xx, kLRki, mscout, index, dsRows)
+
+ rv = MgSig()
+ c, L, R, Hi = 0, None, None, None
+
+ c_old, Ip, alpha = gen_mlsag_rows(
+ message, rv, pk, xx, kLRki, index, dsRows, rows, cols
+ )
+
+ i = (index + 1) % cols
+ if i == 0:
+ rv.cc = c_old
+
+ while i != index:
+ rv.ss[i] = scalar_gen_vector(rows)
+ hasher = hasher_message(message)
+
+ for j in range(dsRows):
+ L = crypto.add_keys2(rv.ss[i][j], c_old, pk[i][j])
+ Hi = crypto.hash_to_ec(
+ crypto.encodepoint(pk[i][j])
+ ) # originally hashToPoint()
+ R = crypto.add_keys3(rv.ss[i][j], Hi, c_old, Ip[j])
+ hash_point(hasher, pk[i][j])
+ hash_point(hasher, L)
+ hash_point(hasher, R)
+
+ for j in range(dsRows, rows):
+ L = crypto.add_keys2(rv.ss[i][j], c_old, pk[i][j])
+ hash_point(hasher, pk[i][j])
+ hash_point(hasher, L)
+
+ c = crypto.sc_reduce32(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]
+ ) # alpha[j] - c * xx[j]; sc_mulsub in original does c-ab
+
+ if mscout:
+ mscout(c)
+
+ return rv, c
+
+
+def prove_rct_mg(
+ message, pubs, in_sk, out_sk, out_pk, kLRki, mscout, index, txn_fee_key
+):
+ """
+ c.f. http://eprint.iacr.org/2015/1098 section 4. definition 10.
+ This does the MG sig on the "dest" part of the given key matrix, and
+ the last row is the sum of input commitments from that column - sum output commitments
+ this shows that sum inputs = sum outputs
+ :param message:
+ :param pubs: matrix of CtKeys. points are encoded.
+ :param in_sk:
+ :param out_sk:
+ :param out_pk:
+ :param kLRki:
+ :param mscout:
+ :param index:
+ :param txn_fee_key:
+ :return:
+ """
+ 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) != len(out_pk):
+ raise ValueError("Bad outsk/putpk size")
+ if (not kLRki or not mscout) and (kLRki and mscout):
+ raise ValueError("Only one of kLRki/mscout is present")
+
+ 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].mask)
+ )
+
+ 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)):
+ M[i][rows] = crypto.point_sub(
+ M[i][rows], crypto.decodepoint(out_pk[j].mask)
+ ) # 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)):
+ sk[rows] = crypto.sc_sub(
+ sk[rows], out_sk[j].mask
+ ) # subtract output masks in last row
+
+ return gen_mlsag_ext(message, M, sk, kLRki, mscout, index, rows)
+
+
+def prove_rct_mg_simple(message, pubs, in_sk, a, cout, kLRki, mscout, index):
+ """
+ Simple version for when we assume only
+ post rct inputs
+ here pubs is a vector of (P, C) length mixin
+
+ :param message:
+ :param pubs: vector of CtKeys, public, point values, encoded form. (dest, mask) = (P, C)
+ :param in_sk: CtKey, private. (spending private key, input commitment mask (original))
+ :param a: mask from the pseudo_output commitment (alpha)
+ :param cout: point, decoded. Pseudo output public key.
+ :param kLRki:
+ :param mscout: lambda accepting c
+ :param index:
+ :return:
+ """
+ rows = 1
+ cols = len(pubs)
+ if cols == 0:
+ raise ValueError("Empty pubs")
+ if (not kLRki or not mscout) and (kLRki and mscout):
+ raise ValueError("Only one of kLRki/mscout is present")
+
+ sk = key_vector(rows + 1)
+ M = key_matrix(rows + 1, 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].mask), cout)
+
+ return gen_mlsag_ext(message, M, sk, kLRki, mscout, index, rows)
diff --git a/src/apps/monero/xmr/monero.py b/src/apps/monero/xmr/monero.py
new file mode 100644
index 000000000..9aca64c73
--- /dev/null
+++ b/src/apps/monero/xmr/monero.py
@@ -0,0 +1,303 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# Author: Dusan Klinec, ph4r05, 2018
+
+import ustruct as struct
+from micropython import const
+
+from apps.monero.xmr import common, crypto
+
+DISPLAY_DECIMAL_POINT = const(12)
+
+
+class XmrNoSuchAddressException(common.XmrException):
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+
+
+def get_subaddress_secret_key(
+ secret_key, index=None, major=None, minor=None, little_endian=True
+):
+ """
+ Builds subaddress secret key from the subaddress index
+ Hs(SubAddr || a || index_major || index_minor)
+
+ UPDATE: Monero team fixed this problem. Always use little endian.
+ Note: need to handle endianity in the index
+ C-code simply does: memcpy(data + sizeof(prefix) + sizeof(crypto::secret_key), &index, sizeof(subaddress_index));
+ Where the index has the following form:
+
+ struct subaddress_index {
+ uint32_t major;
+ uint32_t minor;
+ }
+
+ https://docs.python.org/3/library/struct.html#byte-order-size-and-alignment
+ :param secret_key:
+ :param index:
+ :param major:
+ :param minor:
+ :param little_endian:
+ :return:
+ """
+ if index:
+ major = index.major
+ minor = index.minor
+ endianity = "<" if little_endian else ">"
+ prefix = b"SubAddr"
+ buffer = bytearray(len(prefix) + 1 + 32 + 4 + 4)
+ struct.pack_into(
+ "%s7sb32sLL" % endianity,
+ buffer,
+ 0,
+ prefix,
+ 0,
+ crypto.encodeint(secret_key),
+ major,
+ minor,
+ )
+ return crypto.hash_to_scalar(buffer)
+
+
+def get_subaddress_spend_public_key(view_private, spend_public, major, minor):
+ """
+ Generates subaddress spend public key D_{major, minor}
+ :param view_private:
+ :param spend_public:
+ :param major:
+ :param minor:
+ :return:
+ """
+ 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 generate_key_derivation(pub_key, priv_key):
+ """
+ Generates derivation priv_key * pub_key.
+ Simple ECDH.
+ :param pub_key:
+ :param priv_key:
+ :return:
+ """
+ return crypto.generate_key_derivation(pub_key, priv_key)
+
+
+def derive_subaddress_public_key(out_key, derivation, output_index):
+ """
+ out_key - H_s(derivation || varint(output_index))G
+ :param out_key:
+ :param derivation:
+ :param output_index:
+ :return:
+ """
+ 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)
+ :param public_key: encoded point
+ :param secret_key:
+ :return:
+ """
+ point = crypto.hash_to_ec(public_key)
+ point2 = crypto.ge_scalarmult(secret_key, point)
+ return point2
+
+
+def is_out_to_acc_precomp(
+ subaddresses, out_key, derivation, additional_derivations, output_index
+):
+ """
+ Searches subaddresses for the computed subaddress_spendkey.
+ If found, returns (major, minor), derivation.
+
+ :param subaddresses:
+ :param out_key:
+ :param derivation:
+ :param additional_derivations:
+ :param output_index:
+ :return:
+ """
+ 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_key_image_helper_precomp(
+ ack, out_key, recv_derivation, real_output_index, received_index
+):
+ """
+ Generates UTXO spending key and key image.
+
+ :param ack: sender credentials
+ :type ack: apps.monero.xmr.sub.creds.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 ack.spend_key_private == 0:
+ 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
+ scalar_step2 = 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
+ if len(ack.multisig_keys) == 0:
+ pub_ver = crypto.scalarmult_base(scalar_step2)
+
+ else:
+ # 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_key_image_helper(
+ creds,
+ subaddresses,
+ out_key,
+ tx_public_key,
+ additional_tx_public_keys,
+ real_output_index,
+):
+ """
+ Generates UTXO spending key and key image.
+ Supports subaddresses.
+
+ :param creds:
+ :param subaddresses:
+ :param out_key: real output (from input RCT) destination key
+ :param tx_public_key: real output (from input RCT) public key
+ :param additional_tx_public_keys:
+ :param real_output_index: index of the real output in the RCT
+ :return:
+ """
+ recv_derivation = 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(
+ generate_key_derivation(add_pub_key, creds.view_key_private)
+ )
+
+ subaddr_recv_info = is_out_to_acc_precomp(
+ 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_key_image_helper_precomp(
+ creds, out_key, subaddr_recv_info[1], real_output_index, subaddr_recv_info[0]
+ )
+ return xi, ki, recv_derivation
+
+
+def compute_subaddresses(creds, account, 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):
+ """
+ Wallet gen.
+ :param recovery_key:
+ :return:
+ """
+ sec = crypto.sc_reduce32(recovery_key)
+ pub = crypto.scalarmult_base(sec)
+ return sec, 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).
+ :param seed:
+ :return:
+ """
+ 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
diff --git a/src/apps/monero/xmr/ring_ct.py b/src/apps/monero/xmr/ring_ct.py
new file mode 100644
index 000000000..420e727ac
--- /dev/null
+++ b/src/apps/monero/xmr/ring_ct.py
@@ -0,0 +1,268 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# Author: https://github.com/monero-project/mininero
+# Author: Dusan Klinec, ph4r05, 2018
+
+from apps.monero.xmr import crypto
+
+
+def prove_range(
+ amount, last_mask=None, decode=False, backend_impl=True, byte_enc=True, rsig=None
+):
+ """
+ Range proof generator.
+ In order to minimize the memory consumption and CPU usage during transaction generation the returned values
+ are returned encoded.
+
+ :param amount:
+ :param last_mask:
+ :param backend_impl: backend implementation, if available
+ :param decode: decodes output
+ :param byte_enc: byte encoded
+ :param rsig: buffer for rsig
+ :return:
+ """
+ if not backend_impl or not byte_enc or decode:
+ raise ValueError("Unsupported params")
+
+ C, a, R = None, None, None
+ try:
+ if rsig is None:
+ rsig = bytearray(32 * (64 + 64 + 64 + 1))
+
+ buf_ai = bytearray(4 * 9 * 64)
+ buf_alpha = bytearray(4 * 9 * 64)
+ C, a, R = crypto.prove_range(
+ rsig, amount, last_mask, buf_ai, buf_alpha
+ ) # backend returns encoded
+
+ finally:
+ import gc
+
+ buf_ai = None
+ buf_alpha = None
+ gc.collect()
+
+ return C, a, R
+
+
+# Ring-ct MG sigs
+# Prove:
+# c.f. http:#eprint.iacr.org/2015/1098 section 4. definition 10.
+# This does the MG sig on the "dest" part of the given key matrix, and
+# the last row is the sum of input commitments from that column - sum output commitments
+# this shows that sum inputs = sum outputs
+# Ver:
+# verifies the above sig is created corretly
+
+
+def ecdh_encode(unmasked, receiver_pk=None, derivation=None):
+ """
+ Elliptic Curve Diffie-Helman: encodes and decodes the amount b and mask a
+ where C= aG + bH
+ :param unmasked:
+ :param receiver_pk:
+ :param derivation:
+ :return:
+ """
+ from apps.monero.xmr.serialize_messages.tx_ecdh import EcdhTuple
+
+ rv = EcdhTuple()
+ if derivation is None:
+ esk = crypto.random_scalar()
+ rv.senderPk = crypto.scalarmult_base(esk)
+ derivation = crypto.encodepoint(crypto.scalarmult(receiver_pk, esk))
+
+ sharedSec1 = crypto.hash_to_scalar(derivation)
+ sharedSec2 = crypto.hash_to_scalar(crypto.encodeint(sharedSec1))
+
+ rv.mask = crypto.sc_add(unmasked.mask, sharedSec1)
+ rv.amount = crypto.sc_add(unmasked.amount, sharedSec2)
+ return rv
+
+
+def ecdh_decode(masked, receiver_sk=None, derivation=None):
+ """
+ Elliptic Curve Diffie-Helman: encodes and decodes the amount b and mask a
+ where C= aG + bH
+ :param masked:
+ :param receiver_sk:
+ :param derivation:
+ :return:
+ """
+ from apps.monero.xmr.serialize_messages.tx_ecdh import EcdhTuple
+
+ rv = EcdhTuple()
+
+ if derivation is None:
+ derivation = crypto.scalarmult(masked.senderPk, receiver_sk)
+
+ sharedSec1 = crypto.hash_to_scalar(derivation)
+ sharedSec2 = crypto.hash_to_scalar(crypto.encodeint(sharedSec1))
+
+ rv.mask = crypto.sc_sub(masked.mask, sharedSec1)
+ rv.amount = crypto.sc_sub(masked.amount, sharedSec2)
+ return rv
+
+
+#
+# Key image import / export
+#
+
+
+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()
+
+ :param prefix_hash:
+ :param image:
+ :param pubs:
+ :param sec:
+ :param sec_idx:
+ :param test:
+ :return:
+ """
+ from apps.monero.xmr.common import memcpy
+
+ if test:
+ from apps.monero.xmr import monero
+
+ 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)
+
+ image_unp = crypto.ge_frombytes_vartime(image)
+ image_pre = crypto.ge_dsm_precomp(image_unp)
+
+ 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(tmp3, mvbuff[buff_off : buff_off + 32])
+ buff_off += 32
+
+ tmp3 = crypto.hash_to_ec(crypto.encodepoint(pubs[i]))
+ tmp2 = crypto.scalarmult(tmp3, k)
+ crypto.encodepoint_into(tmp2, mvbuff[buff_off : buff_off + 32])
+ buff_off += 32
+
+ else:
+ sig[i] = [crypto.random_scalar(), crypto.random_scalar()]
+ tmp3 = crypto.ge_frombytes_vartime(pubs[i])
+ tmp2 = crypto.ge_double_scalarmult_base_vartime(sig[i][0], tmp3, sig[i][1])
+ crypto.encodepoint_into(tmp2, mvbuff[buff_off : buff_off + 32])
+ buff_off += 32
+
+ tmp3 = crypto.hash_to_ec(crypto.encodepoint(tmp3))
+ tmp2 = crypto.ge_double_scalarmult_precomp_vartime(
+ sig[i][1], tmp3, sig[i][0], image_pre
+ )
+ crypto.encodepoint_into(tmp2, mvbuff[buff_off : buff_off + 32])
+ 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
+
+
+def check_ring_singature(prefix_hash, image, pubs, sig):
+ """
+ Checks ring signature generated with generate_ring_signature
+ :param prefix_hash:
+ :param image:
+ :param pubs:
+ :param sig:
+ :return:
+ """
+ from apps.monero.xmr.common import memcpy
+
+ image_unp = crypto.ge_frombytes_vartime(image)
+ image_pre = crypto.ge_dsm_precomp(image_unp)
+
+ 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()
+ for i in range(len(pubs)):
+ if crypto.sc_check(sig[i][0]) != 0 or crypto.sc_check(sig[i][1]) != 0:
+ return False
+
+ tmp3 = crypto.ge_frombytes_vartime(pubs[i])
+ tmp2 = crypto.ge_double_scalarmult_base_vartime(sig[i][0], tmp3, sig[i][1])
+ crypto.encodepoint_into(tmp2, mvbuff[buff_off : buff_off + 32])
+ buff_off += 32
+
+ tmp3 = crypto.hash_to_ec(crypto.encodepoint(pubs[i]))
+ tmp2 = crypto.ge_double_scalarmult_precomp_vartime(
+ sig[i][1], tmp3, sig[i][0], image_pre
+ )
+ crypto.encodepoint_into(tmp2, mvbuff[buff_off : buff_off + 32])
+ buff_off += 32
+
+ sum = crypto.sc_add(sum, sig[i][0])
+
+ h = crypto.hash_to_scalar(buff)
+ h = crypto.sc_sub(h, sum)
+ return crypto.sc_isnonzero(h) == 0
+
+
+def export_key_image(
+ creds,
+ subaddresses,
+ pkey,
+ tx_pub_key,
+ additional_tx_pub_keys,
+ out_idx,
+ test=True,
+ verify=True,
+):
+ """
+ Generates key image for the TXO + signature for the key image
+ :param creds:
+ :param subaddresses:
+ :param pkey:
+ :param tx_pub_key:
+ :param additional_tx_pub_keys:
+ :param out_idx:
+ :param test:
+ :param verify:
+ :return:
+ """
+ from apps.monero.xmr import monero
+
+ r = monero.generate_key_image_helper(
+ 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)
+
+ if verify:
+ if check_ring_singature(phash, ki, [pkey], sig) != 1:
+ raise ValueError("Signature error")
+
+ return ki, sig
diff --git a/src/apps/monero/xmr/serialize/__init__.py b/src/apps/monero/xmr/serialize/__init__.py
new file mode 100644
index 000000000..e69de29bb
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..ff66ca28d
--- /dev/null
+++ b/src/apps/monero/xmr/serialize/base_types.py
@@ -0,0 +1,64 @@
+class XmrType:
+ VERSION = 0
+
+
+class UVarintType(XmrType):
+ pass
+
+
+class IntType(XmrType):
+ WIDTH = 0
+ SIGNED = 0
+ VARIABLE = 0
+
+ def __repr__(self):
+ return "%s:" % (
+ self.__class__,
+ self.WIDTH,
+ self.SIGNED,
+ self.VARIABLE,
+ )
+
+
+class BoolType(IntType):
+ WIDTH = 1
+
+
+class UInt8(IntType):
+ WIDTH = 1
+
+
+class Int8(IntType):
+ SIGNED = 1
+ WIDTH = 1
+
+
+class UInt16(IntType):
+ WIDTH = 2
+
+
+class Int16(IntType):
+ SIGNED = 1
+ WIDTH = 2
+
+
+class UInt32(IntType):
+ WIDTH = 4
+
+
+class Int32(IntType):
+ SIGNED = 1
+ WIDTH = 4
+
+
+class UInt64(IntType):
+ WIDTH = 8
+
+
+class SizeT(UInt64):
+ WIDTH = 8
+
+
+class Int64(IntType):
+ SIGNED = 1
+ WIDTH = 8
diff --git a/src/apps/monero/xmr/serialize/erefs.py b/src/apps/monero/xmr/serialize/erefs.py
new file mode 100644
index 000000000..9c8d30603
--- /dev/null
+++ b/src/apps/monero/xmr/serialize/erefs.py
@@ -0,0 +1,89 @@
+class ElemRefObj:
+ def __repr__(self):
+ return "RefObj"
+
+
+class ElemRefArr:
+ def __repr__(self):
+ return "RefAssoc"
+
+
+def is_elem_ref(elem_ref):
+ """
+ Returns true if the elem_ref is an element reference
+
+ :param elem_ref:
+ :return:
+ """
+ return (
+ elem_ref
+ and isinstance(elem_ref, tuple)
+ and len(elem_ref) == 3
+ and (elem_ref[0] == ElemRefObj or elem_ref[0] == ElemRefArr)
+ )
+
+
+def has_elem(elem_ref):
+ """
+ Has element?
+ :param elem_ref:
+ :return:
+ """
+ if not is_elem_ref(elem_ref):
+ return False
+ elif elem_ref[0] == ElemRefObj:
+ return hasattr(elem_ref[1], elem_ref[2])
+ elif elem_ref[0] == ElemRefArr:
+ return elem_ref[2] in elem_ref[1]
+
+
+def get_elem(elem_ref, default=None):
+ """
+ Gets the element referenced by elem_ref or returns the elem_ref directly if its not a reference.
+
+ :param elem_ref:
+ :param default:
+ :return:
+ """
+ if not is_elem_ref(elem_ref):
+ return elem_ref
+ elif elem_ref[0] == ElemRefObj:
+ return getattr(elem_ref[1], elem_ref[2], default)
+ elif elem_ref[0] == ElemRefArr:
+ return elem_ref[1][elem_ref[2]]
+
+
+def set_elem(elem_ref, elem):
+ """
+ Sets element referenced by the elem_ref. Returns the elem.
+
+ :param elem_ref:
+ :param elem:
+ :return:
+ """
+ if elem_ref is None or elem_ref == elem or not is_elem_ref(elem_ref):
+ return elem
+
+ elif elem_ref[0] == ElemRefObj:
+ setattr(elem_ref[1], elem_ref[2], elem)
+ return elem
+
+ elif elem_ref[0] == ElemRefArr:
+ elem_ref[1][elem_ref[2]] = elem
+ return elem
+
+
+def eref(obj, key, is_assoc=None):
+ """
+ Returns element reference
+ :param obj:
+ :param key:
+ :param is_assoc:
+ :return:
+ """
+ if obj is None:
+ return None
+ if isinstance(key, int) or (is_assoc is not None and is_assoc):
+ return ElemRefArr, get_elem(obj), key
+ else:
+ return ElemRefObj, get_elem(obj), key
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..a20d7c7cd
--- /dev/null
+++ b/src/apps/monero/xmr/serialize/int_serialize.py
@@ -0,0 +1,126 @@
+_UINT_BUFFER = bytearray(1)
+
+
+async def load_uint(reader, width):
+ """
+ Constant-width integer serialization
+ :param reader:
+ :param width:
+ :return:
+ """
+ buffer = _UINT_BUFFER
+ result = 0
+ shift = 0
+ for _ in range(width):
+ await reader.areadinto(buffer)
+ result += buffer[0] << shift
+ shift += 8
+ return result
+
+
+async def dump_uint(writer, n, width):
+ """
+ Constant-width integer serialization
+ :param writer:
+ :param n:
+ :param width:
+ :return:
+ """
+ buffer = _UINT_BUFFER
+ for _ in range(width):
+ buffer[0] = n & 0xff
+ await writer.awrite(buffer)
+ n >>= 8
+
+
+def uvarint_size(n):
+ """
+ Returns size in bytes n would occupy serialized as varint
+ :param n:
+ :return:
+ """
+ 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.
+ :param buffer:
+ :return:
+ """
+ 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
+ :param n:
+ :return:
+ """
+ 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.
+ Buffer has to ha
+ :param n:
+ :param buffer:
+ :param offset:
+ :return:
+ """
+ shifted = True
+ while shifted:
+ shifted = n >> 7
+ buffer[offset] = (n & 0x7F) | (0x80 if shifted else 0x00)
+ offset += 1
+ n = shifted
+ return buffer
+
+
+def load_uint_b(buffer, width):
+ """
+ Loads fixed size integer from the buffer
+ :param buffer:
+ :return:
+ """
+ result = 0
+ for idx in range(width):
+ result += buffer[idx] << (8 * idx)
+ return result
+
+
+def dump_uint_b(n, width):
+ """
+ Serializes fixed size integer to the buffer
+ :param n:
+ :param width:
+ :return:
+ """
+ buffer = bytearray(width)
+ return dump_uvarint_b_into(n, buffer, 0)
+
+
+def dump_uint_b_into(n, width, buffer, offset=0):
+ """
+ Serializes fixed size integer to the buffer
+ :param n:
+ :param width:
+ :return:
+ """
+ for idx in range(width):
+ buffer[idx + offset] = n & 0xff
+ n >>= 8
+ return buffer
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..c36366cd8
--- /dev/null
+++ b/src/apps/monero/xmr/serialize/message_types.py
@@ -0,0 +1,141 @@
+from apps.monero.xmr.serialize.base_types import XmrType
+from apps.monero.xmr.serialize.obj_helper import eq_obj_contents, is_type, slot_obj_dict
+
+
+class BlobType(XmrType):
+ """
+ Binary data
+
+ Represented as bytearray() or a list of values in data structures.
+ Not wrapped in the BlobType, the BlobType is only a scheme descriptor.
+ Behaves in the same way as primitive types
+
+ Supports also the wrapped version (__init__, DATA_ATTR, eq, repr...),
+ """
+
+ DATA_ATTR = "data"
+ FIX_SIZE = 0
+ SIZE = 0
+
+ def __eq__(self, rhs):
+ return eq_obj_contents(self, rhs)
+
+ def __repr__(self):
+ dct = slot_obj_dict(self) if hasattr(self, "__slots__") else self.__dict__
+ return "<%s: %s>" % (self.__class__.__name__, dct)
+
+
+class UnicodeType(XmrType):
+ pass
+
+
+class VariantType(XmrType):
+ """
+ Union of types, variant tags needed. is only one of the types. List in typedef, enum.
+ Wraps the variant type in order to unambiguously support variant of variants.
+ Supports also unwrapped value using type system to distinguish variants - simplifies the construction.
+ """
+
+ WRAPS_VALUE = False
+
+ def __init__(self):
+ self.variant_elem = None
+ self.variant_elem_type = None
+
+ @classmethod
+ def f_specs(cls):
+ return ()
+
+ def set_variant(self, fname, fvalue):
+ self.variant_elem = fname
+ self.variant_elem_type = fvalue.__class__
+ setattr(self, fname, fvalue)
+
+ def __eq__(self, rhs):
+ return eq_obj_contents(self, rhs)
+
+ def __repr__(self):
+ dct = slot_obj_dict(self) if hasattr(self, "__slots__") else self.__dict__
+ return "<%s: %s>" % (self.__class__.__name__, dct)
+
+
+class ContainerType(XmrType):
+ """
+ Array of elements
+ Represented as a real array in the data structures, not wrapped in the ContainerType.
+ The Container type is used only as a schema descriptor for serialization.
+ """
+
+ FIX_SIZE = 0
+ SIZE = 0
+ ELEM_TYPE = None
+
+
+class TupleType(XmrType):
+ @classmethod
+ def f_specs(cls):
+ return ()
+
+
+class MessageType(XmrType):
+ def __init__(self, **kwargs):
+ for kw in kwargs:
+ setattr(self, kw, kwargs[kw])
+
+ def __eq__(self, rhs):
+ return eq_obj_contents(self, rhs)
+
+ def __repr__(self):
+ dct = slot_obj_dict(self) if hasattr(self, "__slots__") else self.__dict__
+ return "<%s: %s>" % (self.__class__.__name__, dct)
+
+ @classmethod
+ def f_specs(cls):
+ return ()
+
+ def _field(self, fname=None, idx=None):
+ fld = None
+ specs = self.f_specs()
+ if fname is not None:
+ fld = [x for x in specs if x[0] == fname][0]
+ elif idx is not None:
+ fld = specs[idx]
+ return fld
+
+ async def _msg_field(self, ar, fname=None, idx=None, **kwargs):
+ return await ar.message_field(self, self._field(fname=fname, idx=idx), **kwargs)
+
+
+def container_elem_type(container_type, params):
+ """
+ Returns container element type
+
+ :param container_type:
+ :param params:
+ :return:
+ """
+ elem_type = params[0] if params else None
+ if elem_type is None:
+ elem_type = container_type.ELEM_TYPE
+ return elem_type
+
+
+def gen_elem_array(size, elem_type=None):
+ """
+ Generates element array of given size and initializes with given type.
+ Supports container type, used for pre-allocation before deserialization.
+ :param size:
+ :param elem_type:
+ :return:
+ """
+ if elem_type is None or not callable(elem_type):
+ return [elem_type] * size
+ if is_type(elem_type, ContainerType):
+
+ def elem_type():
+ return []
+
+ res = []
+ for _ in range(size):
+ res.append(elem_type())
+ return res
diff --git a/src/apps/monero/xmr/serialize/obj_helper.py b/src/apps/monero/xmr/serialize/obj_helper.py
new file mode 100644
index 000000000..877b9495a
--- /dev/null
+++ b/src/apps/monero/xmr/serialize/obj_helper.py
@@ -0,0 +1,66 @@
+def eq_obj_slots(l, r):
+ """
+ Compares objects with __slots__ defined
+ :param l:
+ :param r:
+ :return:
+ """
+ for f in l.__slots__:
+ if getattr(l, f, None) != getattr(r, f, None):
+ return False
+ return True
+
+
+def eq_obj_contents(l, r):
+ """
+ Compares object contents, supports slots
+ :param l:
+ :param r:
+ :return:
+ """
+ if l.__class__ is not r.__class__:
+ return False
+ if hasattr(l, "__slots__"):
+ return eq_obj_slots(l, r)
+ else:
+ return l.__dict__ == r.__dict__
+
+
+def slot_obj_dict(o):
+ """
+ Builds dict for o with __slots__ defined
+ :param o:
+ :return:
+ """
+ d = {}
+ for f in o.__slots__:
+ d[f] = getattr(o, f, None)
+ return d
+
+
+def is_type(x, types, full=False):
+ """
+ Returns true if x is of type in types tuple
+ :param x:
+ :param types:
+ :param full:
+ :return:
+ """
+ types = types if isinstance(types, tuple) else (types,)
+ ins = isinstance(x, types)
+ sub = False
+ try:
+ sub = issubclass(x, types)
+ except Exception:
+ pass
+ res = ins or sub
+ return res if not full else (res, ins)
+
+
+def get_ftype_params(field):
+ """
+ Convenient getter
+ :param field:
+ :return:
+ """
+ return field[1], field[2:]
diff --git a/src/apps/monero/xmr/serialize/readwriter.py b/src/apps/monero/xmr/serialize/readwriter.py
new file mode 100644
index 000000000..0eba726e1
--- /dev/null
+++ b/src/apps/monero/xmr/serialize/readwriter.py
@@ -0,0 +1,100 @@
+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
+
+ async def areadinto(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 awrite(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
+
+ def get_buffer(self):
+ mv = memoryview(self.buffer)
+ return mv[self.offset : self.woffset]
diff --git a/src/apps/monero/xmr/serialize/xmrserialize.py b/src/apps/monero/xmr/serialize/xmrserialize.py
new file mode 100644
index 000000000..7d9c4a00d
--- /dev/null
+++ b/src/apps/monero/xmr/serialize/xmrserialize.py
@@ -0,0 +1,843 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+'''
+Minimal streaming codec for a Monero binary serialization.
+Used for a binary serialization in blockchain and for hash computation for signatures.
+
+Equivalent of BEGIN_SERIALIZE_OBJECT(), /src/serialization/serialization.h
+
+- The wire binary format does not use tags. Structure has to be read from the binary stream
+with the scheme specified in order to parse the structure.
+
+- Heavily uses variable integer serialization - similar to the UTF8 or LZ4 number encoding.
+
+- Supports: blob, string, integer types - variable or fixed size, containers of elements,
+ variant types, messages of elements
+
+For de-serializing (loading) types, object with `AsyncReader`
+interface is required:
+
+>>> class AsyncReader:
+>>> async def areadinto(self, buffer):
+>>> """
+>>> Reads `len(buffer)` bytes into `buffer`, or raises `EOFError`.
+>>> """
+
+For serializing (dumping) types, object with `AsyncWriter` interface is
+required:
+
+>>> class AsyncWriter:
+>>> async def awrite(self, buffer):
+>>> """
+>>> Writes all bytes from `buffer`, or raises `EOFError`.
+>>> """
+'''
+
+import sys
+
+from protobuf import dump_uvarint, load_uvarint
+from trezor import log
+
+from apps.monero.xmr.serialize.base_types import IntType, UVarintType, XmrType
+from apps.monero.xmr.serialize.erefs import eref, get_elem, set_elem
+from apps.monero.xmr.serialize.int_serialize import dump_uint, load_uint
+from apps.monero.xmr.serialize.message_types import (
+ BlobType,
+ ContainerType,
+ MessageType,
+ TupleType,
+ UnicodeType,
+ VariantType,
+ container_elem_type,
+ gen_elem_array,
+)
+
+
+def import_def(module, name):
+ if module not in sys.modules:
+ if not module.startswith("apps.monero"):
+ raise ValueError("Module not allowed: %s" % module)
+
+ log.debug(__name__, "Importing: from %s import %s", module, name)
+ __import__(module, None, None, (name,), 0)
+
+ r = getattr(sys.modules[module], name)
+ return r
+
+
+class Archive(object):
+ """
+ Archive object for object binary serialization / deserialization.
+ Resembles Archive API from the Monero codebase or Boost serialization archive.
+
+ The design goal is to provide uniform API both for serialization and deserialization
+ so the code is not duplicated for serialization and deserialization but the same
+ for both ways in order to minimize potential bugs in the code.
+
+ In order to use the archive for both ways we have to use so-called field references
+ as we cannot directly modify given element as a parameter (value-passing) as its performed
+ in C++ code. see: eref(), get_elem(), set_elem()
+ """
+
+ def __init__(self, iobj, writing=True, **kwargs):
+ self.writing = writing
+ self.iobj = iobj
+
+ async def prepare_container(self, size, container, elem_type=None):
+ """
+ Prepares container for serialization
+ :param size:
+ :param container:
+ :return:
+ """
+ if not self.writing:
+ if container is None:
+ return gen_elem_array(size, elem_type)
+
+ fvalue = get_elem(container)
+ if fvalue is None:
+ fvalue = []
+ fvalue += gen_elem_array(max(0, size - len(fvalue)), elem_type)
+ set_elem(container, fvalue)
+ return fvalue
+
+ async def prepare_message(self, msg, msg_type):
+ """
+ Prepares message for serialization
+ :param msg:
+ :param msg_type:
+ :return:
+ """
+ if self.writing:
+ return
+ return set_elem(msg, msg_type())
+
+ async def uvarint(self, elem):
+ """
+ Uvarint
+ :param elem:
+ :return:
+ """
+ if self.writing:
+ return await dump_uvarint(self.iobj, elem)
+ else:
+ return await load_uvarint(self.iobj)
+
+ async def uint(self, elem, elem_type, params=None):
+ """
+ Fixed size int
+ :param elem:
+ :param elem_type:
+ :param params:
+ :return:
+ """
+ if self.writing:
+ return await dump_uint(self.iobj, elem, elem_type.WIDTH)
+ else:
+ return await load_uint(self.iobj, elem_type.WIDTH)
+
+ async def unicode_type(self, elem):
+ """
+ Unicode type
+ :param elem:
+ :return:
+ """
+ if self.writing:
+ return await dump_unicode(self.iobj, elem)
+ else:
+ return await load_unicode(self.iobj)
+
+ async def blob(self, elem=None, elem_type=None, params=None):
+ """
+ Loads/dumps blob
+ :return:
+ """
+ elem_type = elem_type if elem_type else elem.__class__
+ if hasattr(elem_type, "serialize_archive"):
+ elem = elem_type() if elem is None else elem
+ return await elem.serialize_archive(
+ self, elem=elem, elem_type=elem_type, params=params
+ )
+
+ if self.writing:
+ return await dump_blob(
+ self.iobj, elem=elem, elem_type=elem_type, params=params
+ )
+ else:
+ return await load_blob(
+ self.iobj, elem_type=elem_type, params=params, elem=elem
+ )
+
+ async def container(self, container=None, container_type=None, params=None):
+ """
+ Loads/dumps container
+ :return:
+ """
+ if hasattr(container_type, "serialize_archive"):
+ container = container_type() if container is None else container
+ return await container.serialize_archive(
+ self, elem=container, elem_type=container_type, params=params
+ )
+
+ if self.writing:
+ return await dump_container(
+ self.iobj,
+ container,
+ container_type,
+ params,
+ field_archiver=self.dump_field,
+ )
+ else:
+ return await load_container(
+ self.iobj,
+ container_type,
+ params=params,
+ container=container,
+ field_archiver=self.load_field,
+ )
+
+ async def container_size(
+ self, container_len=None, container_type=None, params=None
+ ):
+ """
+ Container size
+ :param container_len:
+ :param container_type:
+ :param params:
+ :return:
+ """
+ if hasattr(container_type, "serialize_archive"):
+ raise ValueError("not supported")
+
+ if self.writing:
+ return await dump_container_size(
+ self.iobj, container_len, container_type, params
+ )
+ else:
+ raise ValueError("Not supported")
+
+ async def container_val(self, elem, container_type, params=None):
+ """
+ Single cont value
+ :param elem:
+ :param container_type:
+ :param params:
+ :return:
+ """
+ if hasattr(container_type, "serialize_archive"):
+ raise ValueError("not supported")
+ if self.writing:
+ return await dump_container_val(self.iobj, elem, container_type, params)
+ else:
+ raise ValueError("Not supported")
+
+ async def tuple(self, elem=None, elem_type=None, params=None):
+ """
+ Loads/dumps tuple
+ :return:
+ """
+ if hasattr(elem_type, "serialize_archive"):
+ container = elem_type() if elem is None else elem
+ return await container.serialize_archive(
+ self, elem=elem, elem_type=elem_type, params=params
+ )
+
+ if self.writing:
+ return await dump_tuple(
+ self.iobj, elem, elem_type, params, field_archiver=self.dump_field
+ )
+ else:
+ return await load_tuple(
+ self.iobj,
+ elem_type,
+ params=params,
+ elem=elem,
+ field_archiver=self.load_field,
+ )
+
+ async def variant(self, elem=None, elem_type=None, params=None):
+ """
+ Loads/dumps variant type
+ :param elem:
+ :param elem_type:
+ :param params:
+ :return:
+ """
+ elem_type = elem_type if elem_type else elem.__class__
+ if hasattr(elem_type, "serialize_archive"):
+ elem = elem_type() if elem is None else elem
+ return await elem.serialize_archive(
+ self, elem=elem, elem_type=elem_type, params=params
+ )
+
+ if self.writing:
+ return await dump_variant(
+ self.iobj,
+ elem=elem,
+ elem_type=elem_type if elem_type else elem.__class__,
+ params=params,
+ field_archiver=self.dump_field,
+ )
+ else:
+ return await load_variant(
+ self.iobj,
+ elem_type=elem_type if elem_type else elem.__class__,
+ params=params,
+ elem=elem,
+ field_archiver=self.load_field,
+ )
+
+ async def message(self, msg, msg_type=None):
+ """
+ Loads/dumps message
+ :param msg:
+ :param msg_type:
+ :return:
+ """
+ elem_type = msg_type if msg_type is not None else msg.__class__
+ if hasattr(elem_type, "serialize_archive"):
+ msg = elem_type() if msg is None else msg
+ return await msg.serialize_archive(self)
+
+ if self.writing:
+ return await dump_message(
+ self.iobj, msg, msg_type=msg_type, field_archiver=self.dump_field
+ )
+ else:
+ return await load_message(
+ self.iobj, msg_type, msg=msg, field_archiver=self.load_field
+ )
+
+ async def message_field(self, msg, field, fvalue=None):
+ """
+ Dumps/Loads message field
+ :param msg:
+ :param field:
+ :param fvalue: explicit value for dump
+ :return:
+ """
+ if self.writing:
+ await dump_message_field(
+ self.iobj, msg, field, fvalue=fvalue, field_archiver=self.dump_field
+ )
+ else:
+ await load_message_field(
+ self.iobj, msg, field, field_archiver=self.load_field
+ )
+
+ async def message_fields(self, msg, fields):
+ """
+ Load/dump individual message fields
+ :param msg:
+ :param fields:
+ :return:
+ """
+ for field in fields:
+ await self.message_field(msg, field)
+ return msg
+
+ def _get_type(self, elem_type):
+ # log.info(__name__, 'elem: %s %s %s %s %s | %s %s',
+ # type(elem_type), elem_type.__name__, elem_type.__module__, elem_type, issubclass(elem_type, XmrType), id(elem_type), id(XmrType))
+
+ # If part of our hierarchy - return the object
+ if issubclass(elem_type, XmrType):
+ return elem_type
+
+ # Basic decision types
+ etypes = (
+ UVarintType,
+ IntType,
+ BlobType,
+ UnicodeType,
+ VariantType,
+ ContainerType,
+ TupleType,
+ MessageType,
+ )
+ cname = elem_type.__name__
+ for e in etypes:
+ if cname == e.__name__:
+ return e
+
+ # Inferred type: need to translate it to the current
+ try:
+ m = elem_type.__module__
+ r = import_def(m, cname)
+ sub_test = issubclass(r, XmrType)
+ log.debug(
+ __name__,
+ "resolved %s, sub: %s, id_e: %s, id_mod: %s",
+ r,
+ sub_test,
+ id(r),
+ id(sys.modules[m]),
+ )
+ if not sub_test:
+ log.warning(__name__, "resolution hierarchy broken")
+
+ return r
+
+ except Exception as e:
+ raise ValueError(
+ "Could not translate elem type: %s %s, exc: %s %s"
+ % (type(elem_type), elem_type, type(e), e)
+ )
+
+ def _is_type(self, elem_type, test_type):
+ return issubclass(elem_type, test_type)
+
+ async def field(self, elem=None, elem_type=None, params=None):
+ """
+ Archive field
+ :param elem:
+ :param elem_type:
+ :param params:
+ :return:
+ """
+ elem_type = elem_type if elem_type else elem.__class__
+ fvalue = None
+
+ etype = self._get_type(elem_type)
+ if self._is_type(etype, UVarintType):
+ fvalue = await self.uvarint(get_elem(elem))
+
+ elif self._is_type(etype, IntType):
+ fvalue = await self.uint(
+ elem=get_elem(elem), elem_type=elem_type, params=params
+ )
+
+ elif self._is_type(etype, BlobType):
+ fvalue = await self.blob(
+ elem=get_elem(elem), elem_type=elem_type, params=params
+ )
+
+ elif self._is_type(etype, UnicodeType):
+ fvalue = await self.unicode_type(get_elem(elem))
+
+ elif self._is_type(etype, VariantType):
+ fvalue = await self.variant(
+ elem=get_elem(elem), elem_type=elem_type, params=params
+ )
+
+ elif self._is_type(etype, ContainerType): # container ~ simple list
+ fvalue = await self.container(
+ container=get_elem(elem), container_type=elem_type, params=params
+ )
+
+ elif self._is_type(etype, TupleType): # tuple ~ simple list
+ fvalue = await self.tuple(
+ elem=get_elem(elem), elem_type=elem_type, params=params
+ )
+
+ elif self._is_type(etype, MessageType):
+ fvalue = await self.message(get_elem(elem), msg_type=elem_type)
+
+ else:
+ raise TypeError(
+ "unknown type: %s %s %s" % (elem_type, type(elem_type), elem)
+ )
+
+ return fvalue if self.writing else set_elem(elem, fvalue)
+
+ async def dump_field(self, writer, elem, elem_type, params=None):
+ assert self.iobj == writer
+ return await self.field(elem=elem, elem_type=elem_type, params=params)
+
+ async def load_field(self, reader, elem_type, params=None, elem=None):
+ assert self.iobj == reader
+ return await self.field(elem=elem, elem_type=elem_type, params=params)
+
+ async def root(self):
+ """
+ Root level archive init
+ :return:
+ """
+
+
+async def dump_blob(writer, elem, elem_type, params=None):
+ """
+ Dumps blob message to the writer.
+ Supports both blob and raw value.
+
+ :param writer:
+ :param elem:
+ :param elem_type:
+ :param params:
+ :return:
+ """
+ elem_is_blob = isinstance(elem, BlobType)
+ elem_params = elem if elem_is_blob or elem_type is None else elem_type
+ data = bytes(getattr(elem, BlobType.DATA_ATTR) if elem_is_blob else elem)
+
+ if not elem_params.FIX_SIZE:
+ await dump_uvarint(writer, len(elem))
+ elif len(data) != elem_params.SIZE:
+ raise ValueError("Fixed size blob has not defined size: %s" % elem_params.SIZE)
+ await writer.awrite(data)
+
+
+async def load_blob(reader, elem_type, params=None, elem=None):
+ """
+ Loads blob from reader to the element. Returns the loaded blob.
+
+ :param reader:
+ :param elem_type:
+ :param params:
+ :param elem:
+ :return:
+ """
+ ivalue = elem_type.SIZE if elem_type.FIX_SIZE else await load_uvarint(reader)
+ fvalue = bytearray(ivalue)
+ await reader.areadinto(fvalue)
+
+ if elem is None:
+ return fvalue # array by default
+
+ elif isinstance(elem, BlobType):
+ setattr(elem, elem_type.DATA_ATTR, fvalue)
+ return elem
+
+ else:
+ elem.extend(fvalue)
+
+ return elem
+
+
+async def dump_unicode(writer, elem):
+ """
+ Dumps string as UTF8 encoded string
+ :param writer:
+ :param elem:
+ :return:
+ """
+ await dump_uvarint(writer, len(elem))
+ await writer.awrite(bytes(elem, "utf8"))
+
+
+async def load_unicode(reader):
+ """
+ Loads UTF8 string
+ :param reader:
+ :return:
+ """
+ ivalue = await load_uvarint(reader)
+ fvalue = bytearray(ivalue)
+ await reader.areadinto(fvalue)
+ return str(fvalue, "utf8")
+
+
+async def dump_container_size(writer, container_len, container_type, params=None):
+ """
+ Dumps container size - per element streaming
+ :param writer:
+ :param container_len:
+ :param container_type:
+ :param params:
+ :return:
+ """
+ if not container_type or not container_type.FIX_SIZE:
+ await dump_uvarint(writer, container_len)
+ elif container_len != container_type.SIZE:
+ raise ValueError(
+ "Fixed size container has not defined size: %s" % container_type.SIZE
+ )
+
+
+async def dump_container_val(
+ writer, elem, container_type, params=None, field_archiver=None
+):
+ """
+ Single elem dump
+ :param writer:
+ :param elem:
+ :param container_type:
+ :param params:
+ :return:
+ """
+ field_archiver = field_archiver if field_archiver else dump_field
+ elem_type = container_elem_type(container_type, params)
+ await field_archiver(writer, elem, elem_type, params[1:] if params else None)
+
+
+async def dump_container(
+ writer, container, container_type, params=None, field_archiver=None
+):
+ """
+ Dumps container of elements to the writer.
+
+ :param writer:
+ :param container:
+ :param container_type:
+ :param params:
+ :param field_archiver:
+ :return:
+ """
+ await dump_container_size(writer, len(container), container_type)
+
+ field_archiver = field_archiver if field_archiver else dump_field
+ elem_type = container_elem_type(container_type, params)
+
+ for elem in container:
+ await field_archiver(writer, elem, elem_type, params[1:] if params else None)
+
+
+async def load_container(
+ reader, container_type, params=None, container=None, field_archiver=None
+):
+ """
+ Loads container of elements from the reader. Supports the container ref.
+ Returns loaded container.
+
+ :param reader:
+ :param container_type:
+ :param params:
+ :param container:
+ :param field_archiver:
+ :return:
+ """
+ field_archiver = field_archiver if field_archiver else load_field
+
+ c_len = (
+ container_type.SIZE if container_type.FIX_SIZE else await load_uvarint(reader)
+ )
+ if container and c_len != len(container):
+ raise ValueError("Size mismatch")
+
+ elem_type = container_elem_type(container_type, params)
+ res = container if container else []
+ for i in range(c_len):
+ fvalue = await field_archiver(
+ reader,
+ elem_type,
+ params[1:] if params else None,
+ eref(res, i) if container else None,
+ )
+ if not container:
+ res.append(fvalue)
+ return res
+
+
+async def dump_tuple(writer, elem, elem_type, params=None, field_archiver=None):
+ """
+ Dumps tuple of elements to the writer.
+
+ :param writer:
+ :param elem:
+ :param elem_type:
+ :param params:
+ :param field_archiver:
+ :return:
+ """
+ if len(elem) != len(elem_type.f_specs()):
+ raise ValueError(
+ "Fixed size tuple has not defined size: %s" % len(elem_type.f_specs())
+ )
+ await dump_uvarint(writer, len(elem))
+
+ field_archiver = field_archiver if field_archiver else dump_field
+ elem_fields = params[0] if params else None
+ if elem_fields is None:
+ elem_fields = elem_type.f_specs()
+ for idx, elem in enumerate(elem):
+ await field_archiver(
+ writer, elem, elem_fields[idx], params[1:] if params else None
+ )
+
+
+async def load_tuple(reader, elem_type, params=None, elem=None, field_archiver=None):
+ """
+ Loads tuple of elements from the reader. Supports the tuple ref.
+ Returns loaded tuple.
+
+ :param reader:
+ :param elem_type:
+ :param params:
+ :param container:
+ :param field_archiver:
+ :return:
+ """
+ field_archiver = field_archiver if field_archiver else load_field
+
+ c_len = await load_uvarint(reader)
+ if elem and c_len != len(elem):
+ raise ValueError("Size mismatch")
+ if c_len != len(elem_type.f_specs()):
+ raise ValueError("Tuple size mismatch")
+
+ elem_fields = params[0] if params else None
+ if elem_fields is None:
+ elem_fields = elem_type.f_specs()
+
+ res = elem if elem else []
+ for i in range(c_len):
+ fvalue = await field_archiver(
+ reader,
+ elem_fields[i],
+ params[1:] if params else None,
+ eref(res, i) if elem else None,
+ )
+ if not elem:
+ res.append(fvalue)
+ return res
+
+
+async def dump_message_field(writer, msg, field, fvalue=None, field_archiver=None):
+ """
+ Dumps a message field to the writer. Field is defined by the message field specification.
+
+ :param writer:
+ :param msg:
+ :param field:
+ :param fvalue:
+ :param field_archiver:
+ :return:
+ """
+ fname, ftype, params = field[0], field[1], field[2:]
+ fvalue = getattr(msg, fname, None) if fvalue is None else fvalue
+ field_archiver = field_archiver if field_archiver else dump_field
+ await field_archiver(writer, fvalue, ftype, params)
+
+
+async def load_message_field(reader, msg, field, field_archiver=None):
+ """
+ Loads message field from the reader. Field is defined by the message field specification.
+ Returns loaded value, supports field reference.
+
+ :param reader:
+ :param msg:
+ :param field:
+ :param field_archiver:
+ :return:
+ """
+ fname, ftype, params = field[0], field[1], field[2:]
+ field_archiver = field_archiver if field_archiver else load_field
+ await field_archiver(reader, ftype, params, eref(msg, fname))
+
+
+async def dump_message(writer, msg, msg_type=None, field_archiver=None):
+ """
+ Dumps message to the writer.
+
+ :param writer:
+ :param msg:
+ :param msg_type:
+ :param field_archiver:
+ :return:
+ """
+ mtype = msg.__class__ if msg_type is None else msg_type
+ fields = mtype.f_specs()
+ if hasattr(mtype, "serialize_archive"):
+ raise ValueError("Cannot directly load, has to use archive with %s" % mtype)
+
+ for field in fields:
+ await dump_message_field(
+ writer, msg=msg, field=field, field_archiver=field_archiver
+ )
+
+
+async def load_message(reader, msg_type, msg=None, field_archiver=None):
+ """
+ Loads message if the given type from the reader.
+ Supports reading directly to existing message.
+
+ :param reader:
+ :param msg_type:
+ :param msg:
+ :param field_archiver:
+ :return:
+ """
+ msg = msg_type() if msg is None else msg
+ fields = msg_type.f_specs() if msg_type else msg.__class__.f_specs()
+ if hasattr(msg_type, "serialize_archive"):
+ raise ValueError("Cannot directly load, has to use archive with %s" % msg_type)
+
+ for field in fields:
+ await load_message_field(reader, msg, field, field_archiver=field_archiver)
+
+ return msg
+
+
+def find_variant_fdef(elem_type, elem):
+ fields = elem_type.f_specs()
+ for x in fields:
+ if isinstance(elem, x[1]):
+ return x
+
+ # Not direct hierarchy
+ name = elem.__class__.__name__
+ for x in fields:
+ if name == x[1].__name__:
+ return x
+
+ raise ValueError("Unrecognized variant: %s" % elem)
+
+
+async def dump_variant(writer, elem, elem_type=None, params=None, field_archiver=None):
+ """
+ Dumps variant type to the writer.
+ Supports both wrapped and raw variant.
+
+ :param writer:
+ :param elem:
+ :param elem_type:
+ :param params:
+ :param field_archiver:
+ :return:
+ """
+ field_archiver = field_archiver if field_archiver else dump_field
+ if isinstance(elem, VariantType) or elem_type.WRAPS_VALUE:
+ await dump_uint(writer, elem.variant_elem_type.VARIANT_CODE, 1)
+ await field_archiver(
+ writer, getattr(elem, elem.variant_elem), elem.variant_elem_type
+ )
+
+ else:
+ fdef = find_variant_fdef(elem_type, elem)
+ await dump_uint(writer, fdef[1].VARIANT_CODE, 1)
+ await field_archiver(writer, elem, fdef[1])
+
+
+async def load_variant(
+ reader, elem_type, params=None, elem=None, wrapped=None, field_archiver=None
+):
+ """
+ Loads variant type from the reader.
+ Supports both wrapped and raw variant.
+
+ :param reader:
+ :param elem_type:
+ :param params:
+ :param elem:
+ :param wrapped:
+ :param field_archiver:
+ :return:
+ """
+ is_wrapped = (
+ (isinstance(elem, VariantType) or elem_type.WRAPS_VALUE)
+ if wrapped is None
+ else wrapped
+ )
+ if is_wrapped:
+ elem = elem_type() if elem is None else elem
+
+ field_archiver = field_archiver if field_archiver else load_field
+ tag = await load_uint(reader, 1)
+ for field in elem_type.f_specs():
+ ftype = field[1]
+ if ftype.VARIANT_CODE == tag:
+ fvalue = await field_archiver(
+ reader, ftype, field[2:], elem if not is_wrapped else None
+ )
+ if is_wrapped:
+ elem.set_variant(field[0], fvalue)
+ return elem if is_wrapped else fvalue
+ raise ValueError("Unknown tag: %s" % tag)
+
+
+async def dump_field(writer, elem, elem_type, params=None):
+ raise TypeError("type")
+
+
+async def load_field(reader, elem_type, params=None, elem=None):
+ raise TypeError("type")
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/addr.py b/src/apps/monero/xmr/serialize_messages/addr.py
new file mode 100644
index 000000000..f5f69b5fc
--- /dev/null
+++ b/src/apps/monero/xmr/serialize_messages/addr.py
@@ -0,0 +1,19 @@
+from apps.monero.xmr.serialize.base_types import UInt32
+from apps.monero.xmr.serialize.message_types import MessageType
+from apps.monero.xmr.serialize_messages.base import ECPublicKey
+
+
+class AccountPublicAddress(MessageType):
+ __slots__ = ("m_spend_public_key", "m_view_public_key")
+
+ @classmethod
+ def f_specs(cls):
+ return (("m_spend_public_key", ECPublicKey), ("m_view_public_key", ECPublicKey))
+
+
+class SubaddressIndex(MessageType):
+ __slots__ = ("major", "minor")
+
+ @classmethod
+ def f_specs(cls):
+ return (("major", UInt32), ("minor", UInt32))
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..1846a3027
--- /dev/null
+++ b/src/apps/monero/xmr/serialize_messages/base.py
@@ -0,0 +1,39 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+"""
+XMR types
+"""
+
+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
+SecretKey = ECKey
+ECPublicKey = ECPoint
+KeyImage = ECPoint
+KeyDerivation = 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..4d677fc68
--- /dev/null
+++ b/src/apps/monero/xmr/serialize_messages/ct_keys.py
@@ -0,0 +1,53 @@
+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)
+_c1 = const(1)
+_c32 = const(32)
+_c64 = const(64)
+
+
+class Key64(ContainerType):
+ FIX_SIZE = _c1
+ SIZE = _c64
+ ELEM_TYPE = ECKey
+
+
+class KeyV(ContainerType):
+ FIX_SIZE = _c0
+ ELEM_TYPE = ECKey
+
+
+class KeyM(ContainerType):
+ FIX_SIZE = _c0
+ ELEM_TYPE = KeyV
+
+
+class KeyVFix(ContainerType):
+ FIX_SIZE = _c1
+ ELEM_TYPE = ECKey
+
+
+class KeyMFix(ContainerType):
+ FIX_SIZE = _c1
+ ELEM_TYPE = KeyVFix
+
+
+class CtKey(MessageType):
+ __slots__ = ("dest", "mask")
+
+ @classmethod
+ def f_specs(cls):
+ return (("dest", ECKey), ("mask", ECKey))
+
+
+class CtkeyV(ContainerType):
+ FIX_SIZE = 0
+ ELEM_TYPE = CtKey
+
+
+class CtkeyM(ContainerType):
+ FIX_SIZE = 0
+ ELEM_TYPE = CtkeyV
diff --git a/src/apps/monero/xmr/serialize_messages/tx_construct.py b/src/apps/monero/xmr/serialize_messages/tx_construct.py
new file mode 100644
index 000000000..26c5d4ede
--- /dev/null
+++ b/src/apps/monero/xmr/serialize_messages/tx_construct.py
@@ -0,0 +1,96 @@
+from apps.monero.xmr.serialize.base_types import (
+ BoolType,
+ SizeT,
+ UInt8,
+ UInt32,
+ UInt64,
+ UVarintType,
+)
+from apps.monero.xmr.serialize.message_types import ContainerType, MessageType
+from apps.monero.xmr.serialize_messages.addr import SubaddressIndex
+from apps.monero.xmr.serialize_messages.base import ECKey, ECPublicKey, Hash, KeyImage
+from apps.monero.xmr.serialize_messages.tx_dest_entry import TxDestinationEntry
+from apps.monero.xmr.serialize_messages.tx_full import RctSig
+from apps.monero.xmr.serialize_messages.tx_prefix import TransactionPrefix
+from apps.monero.xmr.serialize_messages.tx_src_entry import TxSourceEntry
+
+
+class MultisigOut(MessageType):
+ @classmethod
+ def f_specs(cls):
+ return (("c", ContainerType, ECKey),)
+
+
+class MultisigLR(MessageType):
+ __slots__ = ("L", "R")
+
+ @classmethod
+ def f_specs(cls):
+ return (("L", ECKey), ("R", ECKey))
+
+
+class MultisigInfo(MessageType):
+ __slots__ = ("signer", "LR", "partial_key_images")
+
+ @classmethod
+ def f_specs(cls):
+ return (
+ ("signer", ECPublicKey),
+ ("LR", ContainerType, MultisigLR),
+ ("partial_key_images", ContainerType, KeyImage),
+ )
+
+
+class MultisigStruct(MessageType):
+ __slots__ = ("sigs", "ignore", "used_L", "signing_keys", "msout")
+
+ @classmethod
+ def f_specs(cls):
+ return (
+ ("sigs", RctSig),
+ ("ignore", ECPublicKey),
+ ("used_L", ContainerType, ECKey),
+ ("signing_keys", ContainerType, ECPublicKey),
+ ("msout", MultisigOut),
+ )
+
+
+class TransferDetails(MessageType):
+ @classmethod
+ def f_specs(cls):
+ return (
+ ("m_block_height", UInt64),
+ ("m_tx", TransactionPrefix),
+ ("m_txid", Hash),
+ ("m_internal_output_index", SizeT),
+ ("m_global_output_index", UInt64),
+ ("m_spent", BoolType),
+ ("m_spent_height", UInt64),
+ ("m_key_image", KeyImage),
+ ("m_mask", ECKey),
+ ("m_amount", UInt64),
+ ("m_rct", BoolType),
+ ("m_key_image_known", BoolType),
+ ("m_pk_index", SizeT),
+ ("m_subaddr_index", SubaddressIndex),
+ ("m_key_image_partial", BoolType),
+ ("m_multisig_k", ContainerType, ECKey),
+ ("m_multisig_info", ContainerType, MultisigInfo),
+ )
+
+
+class TxConstructionData(MessageType):
+ @classmethod
+ def f_specs(cls):
+ return (
+ ("sources", ContainerType, TxSourceEntry),
+ ("change_dts", TxDestinationEntry),
+ ("splitted_dsts", ContainerType, TxDestinationEntry),
+ ("selected_transfers", ContainerType, SizeT),
+ ("extra", ContainerType, UInt8),
+ ("unlock_time", UInt64),
+ ("use_rct", BoolType),
+ ("dests", ContainerType, TxDestinationEntry),
+ ("subaddr_account", UInt32),
+ ("subaddr_indices", ContainerType, UVarintType), # original: x.UInt32
+ )
diff --git a/src/apps/monero/xmr/serialize_messages/tx_dest_entry.py b/src/apps/monero/xmr/serialize_messages/tx_dest_entry.py
new file mode 100644
index 000000000..e0f3b4348
--- /dev/null
+++ b/src/apps/monero/xmr/serialize_messages/tx_dest_entry.py
@@ -0,0 +1,15 @@
+from apps.monero.xmr.serialize.base_types import BoolType, UVarintType
+from apps.monero.xmr.serialize.message_types import MessageType
+from apps.monero.xmr.serialize_messages.addr import AccountPublicAddress
+
+
+class TxDestinationEntry(MessageType):
+ __slots__ = ("amount", "addr", "is_subaddress")
+
+ @classmethod
+ def f_specs(cls):
+ return (
+ ("amount", UVarintType), # original: UInt64
+ ("addr", AccountPublicAddress),
+ ("is_subaddress", BoolType),
+ )
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..c3990ddf5
--- /dev/null
+++ b/src/apps/monero/xmr/serialize_messages/tx_ecdh.py
@@ -0,0 +1,14 @@
+from apps.monero.xmr.serialize.message_types import ContainerType, MessageType
+from apps.monero.xmr.serialize_messages.base import ECKey
+
+
+class EcdhTuple(MessageType):
+ __slots__ = ("mask", "amount")
+
+ @classmethod
+ def f_specs(cls):
+ return (("mask", ECKey), ("amount", ECKey))
+
+
+class EcdhInfo(ContainerType):
+ ELEM_TYPE = EcdhTuple
diff --git a/src/apps/monero/xmr/serialize_messages/tx_extra.py b/src/apps/monero/xmr/serialize_messages/tx_extra.py
new file mode 100644
index 000000000..c9bbbb487
--- /dev/null
+++ b/src/apps/monero/xmr/serialize_messages/tx_extra.py
@@ -0,0 +1,112 @@
+
+from apps.monero.xmr.serialize.base_types import SizeT, UInt8, UVarintType
+from apps.monero.xmr.serialize.message_types import (
+ BlobType,
+ ContainerType,
+ MessageType,
+ VariantType,
+)
+from apps.monero.xmr.serialize_messages.base import ECPublicKey, Hash
+
+
+class TxExtraPadding(MessageType):
+ __slots__ = ("size",)
+ TX_EXTRA_PADDING_MAX_COUNT = 255
+
+ VARIANT_CODE = 0x0
+
+ @classmethod
+ def f_specs(cls):
+ return (("size", SizeT),)
+
+ def __init__(self, **kwargs):
+ super().__init__(**kwargs)
+ self.size = 0
+
+ async def serialize_archive(self, ar):
+ if ar.writing:
+ if self.size > self.TX_EXTRA_PADDING_MAX_COUNT:
+ raise ValueError("Padding too big")
+ for i in range(self.size):
+ ar.uint(0, UInt8)
+
+ else:
+ self.size = 0
+ buffer = bytearray(1)
+ for i in range(self.TX_EXTRA_PADDING_MAX_COUNT + 1):
+ self.size += 1
+ try:
+ nread = await ar.iobj.areadinto(buffer)
+ if nread == 0:
+ break
+ except EOFError:
+ break
+
+ if buffer[0] != 0:
+ raise ValueError("Padding error")
+ return self
+
+
+class TxExtraPubKey(MessageType):
+ __slots__ = ("pub_key",)
+ VARIANT_CODE = 0x1
+
+ @classmethod
+ def f_specs(cls):
+ return (("pub_key", ECPublicKey),)
+
+
+class TxExtraNonce(MessageType):
+ __slots__ = ("nonce",)
+ VARIANT_CODE = 0x2
+
+ @classmethod
+ def f_specs(cls):
+ return (("nonce", BlobType),)
+
+
+class TxExtraMergeMiningTag(MessageType):
+ VARIANT_CODE = 0x3
+
+ @classmethod
+ def f_specs(cls):
+ return (
+ ("field_len", UVarintType),
+ ("depth", UVarintType),
+ ("merkle_root", Hash),
+ )
+
+
+class TxExtraAdditionalPubKeys(MessageType):
+ __slots__ = ("data",)
+ VARIANT_CODE = 0x4
+
+ @classmethod
+ def f_specs(cls):
+ return (("data", ContainerType, ECPublicKey),)
+
+
+class TxExtraMysteriousMinergate(MessageType):
+ __slots__ = ("data",)
+ VARIANT_CODE = 0xde
+
+ @classmethod
+ def f_specs(cls):
+ return (("data", BlobType),)
+
+
+class TxExtraField(VariantType):
+ @classmethod
+ def f_specs(cls):
+ return (
+ ("tx_extra_padding", TxExtraPadding),
+ ("tx_extra_pub_key", TxExtraPubKey),
+ ("tx_extra_nonce", TxExtraNonce),
+ ("tx_extra_merge_mining_tag", TxExtraMergeMiningTag),
+ ("tx_extra_additional_pub_keys", TxExtraAdditionalPubKeys),
+ ("tx_extra_mysterious_minergate", TxExtraMysteriousMinergate),
+ )
+
+
+class TxExtraFields(ContainerType):
+ ELEM_TYPE = TxExtraField
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..a8547c5b6
--- /dev/null
+++ b/src/apps/monero/xmr/serialize_messages/tx_full.py
@@ -0,0 +1,264 @@
+
+from apps.monero.xmr.serialize.base_types import UInt8, UVarintType
+from apps.monero.xmr.serialize.erefs import eref
+from apps.monero.xmr.serialize.message_types import ContainerType, MessageType
+from apps.monero.xmr.serialize_messages.base import ECKey
+from apps.monero.xmr.serialize_messages.ct_keys import CtKey, CtkeyM, CtkeyV, KeyM, KeyV
+from apps.monero.xmr.serialize_messages.tx_ecdh import EcdhInfo, EcdhTuple
+from apps.monero.xmr.serialize_messages.tx_prefix import TransactionPrefix, TxinToKey
+from apps.monero.xmr.serialize_messages.tx_rsig import RctType
+from apps.monero.xmr.serialize_messages.tx_rsig_boro import RangeSig
+from apps.monero.xmr.serialize_messages.tx_rsig_bulletproof import Bulletproof
+from apps.monero.xmr.serialize_messages.tx_sig import (
+ Signature,
+ SignatureArray,
+ get_signature_size,
+)
+
+
+class MgSig(MessageType):
+ __slots__ = ("ss", "cc", "II")
+
+ @classmethod
+ def f_specs(cls):
+ return (("ss", KeyM), ("cc", ECKey))
+
+
+class RctSigBase(MessageType):
+ __slots__ = (
+ "type",
+ "txnFee",
+ "message",
+ "mixRing",
+ "pseudoOuts",
+ "ecdhInfo",
+ "outPk",
+ )
+
+ @classmethod
+ def f_specs(cls):
+ return (
+ ("type", UInt8),
+ ("txnFee", UVarintType),
+ ("message", ECKey),
+ ("mixRing", CtkeyM),
+ ("pseudoOuts", KeyV),
+ ("ecdhInfo", EcdhInfo),
+ ("outPk", CtkeyV),
+ )
+
+ async def serialize_rctsig_base(self, ar, inputs, outputs):
+ """
+ Custom serialization
+ :param ar:
+ :type ar: x.Archive
+ :return:
+ """
+ await self._msg_field(ar, idx=0)
+ if self.type == RctType.Null:
+ return
+ if (
+ self.type != RctType.Full
+ and self.type != RctType.FullBulletproof
+ and self.type != RctType.Simple
+ and self.type != RctType.SimpleBulletproof
+ ):
+ raise ValueError("Unknown type")
+
+ await self._msg_field(ar, idx=1)
+ if self.type == RctType.Simple:
+ await ar.prepare_container(inputs, eref(self, "pseudoOuts"), KeyV)
+ if ar.writing and len(self.pseudoOuts) != inputs:
+ raise ValueError("pseudoOuts size mismatch")
+
+ for i in range(inputs):
+ await ar.field(eref(self.pseudoOuts, i), KeyV.ELEM_TYPE)
+
+ await ar.prepare_container(outputs, eref(self, "ecdhInfo"), EcdhTuple)
+ if ar.writing and len(self.ecdhInfo) != outputs:
+ raise ValueError("EcdhInfo size mismatch")
+
+ for i in range(outputs):
+ await ar.field(eref(self.ecdhInfo, i), EcdhInfo.ELEM_TYPE)
+
+ await ar.prepare_container((outputs), eref(self, "outPk"), CtKey)
+ if ar.writing and len(self.outPk) != outputs:
+ raise ValueError("outPk size mismatch")
+
+ for i in range(outputs):
+ await ar.field(eref(self.outPk[i], "mask"), ECKey)
+
+
+class RctSigPrunable(MessageType):
+ __slots__ = ("rangeSigs", "bulletproofs", "MGs", "pseudoOuts")
+
+ @classmethod
+ def f_specs(cls):
+ return (
+ ("rangeSigs", ContainerType, RangeSig),
+ ("bulletproofs", ContainerType, Bulletproof),
+ ("MGs", ContainerType, MgSig),
+ ("pseudoOuts", KeyV),
+ )
+
+ async def serialize_rctsig_prunable(self, ar, type, inputs, outputs, mixin):
+ """
+ Serialize rct sig
+ :param ar:
+ :type ar: x.Archive
+ :param type:
+ :param inputs:
+ :param outputs:
+ :param mixin:
+ :return:
+ """
+ if type == RctType.Null:
+ return True
+
+ if (
+ type != RctType.Full
+ and type != RctType.FullBulletproof
+ and type != RctType.Simple
+ and type != RctType.SimpleBulletproof
+ ):
+ raise ValueError("Unknown type")
+
+ if type == RctType.SimpleBulletproof or type == RctType.FullBulletproof:
+ if len(self.bulletproofs) != outputs:
+ raise ValueError("Bulletproofs size mismatch")
+
+ await ar.prepare_container(
+ outputs, eref(self, "bulletproofs"), elem_type=Bulletproof
+ )
+ for i in range(len(self.bulletproofs)):
+ await ar.field(elem=eref(self.bulletproofs, i), elem_type=Bulletproof)
+
+ else:
+ await ar.prepare_container(
+ outputs, eref(self, "rangeSigs"), elem_type=RangeSig
+ )
+ if len(self.rangeSigs) != outputs:
+ raise ValueError("rangeSigs size mismatch")
+
+ for i in range(len(self.rangeSigs)):
+ await ar.field(elem=eref(self.rangeSigs, i), elem_type=RangeSig)
+
+ # We keep a byte for size of MGs, because we don't know whether this is
+ # a simple or full rct signature, and it's starting to annoy the hell out of me
+ mg_elements = (
+ inputs if type == RctType.Simple or type == RctType.SimpleBulletproof else 1
+ )
+ await ar.prepare_container(mg_elements, eref(self, "MGs"), elem_type=MgSig)
+ if len(self.MGs) != mg_elements:
+ raise ValueError("MGs size mismatch")
+
+ for i in range(mg_elements):
+ # We save the MGs contents directly, because we want it to save its
+ # arrays and matrices without the size prefixes, and the load can't
+ # know what size to expect if it's not in the data
+
+ await ar.prepare_container(
+ mixin + 1, eref(self.MGs[i], "ss"), elem_type=KeyM
+ )
+ if ar.writing and len(self.MGs[i].ss) != mixin + 1:
+ raise ValueError("MGs size mismatch")
+
+ for j in range(mixin + 1):
+ mg_ss2_elements = 1 + (
+ 1
+ if type == RctType.Simple or type == RctType.SimpleBulletproof
+ else inputs
+ )
+ await ar.prepare_container(
+ mg_ss2_elements, eref(self.MGs[i].ss, j), elem_type=KeyM.ELEM_TYPE
+ )
+
+ if ar.writing and len(self.MGs[i].ss[j]) != mg_ss2_elements:
+ raise ValueError("MGs size mismatch 2")
+
+ for k in range(mg_ss2_elements):
+ await ar.field(eref(self.MGs[i].ss[j], k), elem_type=KeyV.ELEM_TYPE)
+
+ await ar.field(eref(self.MGs[i], "cc"), elem_type=ECKey)
+
+ if type == RctType.SimpleBulletproof:
+ await ar.prepare_container(inputs, eref(self, "pseudoOuts"), elem_type=KeyV)
+ if ar.writing and len(self.pseudoOuts) != inputs:
+ raise ValueError("pseudoOuts size mismatch")
+
+ for i in range(inputs):
+ await ar.field(eref(self.pseudoOuts, i), elem_type=KeyV.ELEM_TYPE)
+
+
+class RctSig(RctSigBase):
+ @classmethod
+ def f_specs(cls):
+ return RctSigBase.f_specs() + (("p", RctSigPrunable),)
+
+
+class Transaction(TransactionPrefix):
+ @classmethod
+ def f_specs(cls):
+ return TransactionPrefix.f_specs() + (
+ ("signatures", ContainerType, SignatureArray),
+ ("rct_signatures", RctSig),
+ )
+
+ async def serialize_archive(self, ar):
+ """
+ Serialize the transaction
+ :param ar:
+ :type ar: x.Archive
+ :return:
+ """
+ # Transaction prefix serialization first.
+ await ar.message(self, TransactionPrefix)
+
+ if self.version == 1:
+ await ar.prepare_container(
+ len(self.vin), eref(self, "signatures"), elem_type=SignatureArray
+ )
+ signatures_not_expected = len(self.signatures) == 0
+ if not signatures_not_expected and len(self.vin) != len(self.signatures):
+ raise ValueError("Signature size mismatch")
+
+ for i in range(len(self.vin)):
+ sig_size = get_signature_size(self.vin[i])
+ if signatures_not_expected:
+ if 0 == sig_size:
+ continue
+ else:
+ raise ValueError("Unexpected sig")
+
+ await ar.prepare_container(
+ sig_size, eref(self.signatures, i), elem_type=Signature
+ )
+ if sig_size != len(self.signatures[i]):
+ raise ValueError("Unexpected sig size")
+
+ await ar.message(self.signatures[i], Signature)
+
+ else:
+ if len(self.vin) == 0:
+ return
+
+ await ar.prepare_message(eref(self, "rct_signatures"), RctSig)
+ await self.rct_signatures.serialize_rctsig_base(
+ ar, len(self.vin), len(self.vout)
+ )
+
+ if self.rct_signatures.type != RctType.Null:
+ mixin_size = (
+ len(self.vin[0].key_offsets) - 1
+ if len(self.vin) > 0 and isinstance(self.vin[0], TxinToKey)
+ else 0
+ )
+ await ar.prepare_message(eref(self.rct_signatures, "p"), RctSigPrunable)
+ await self.rct_signatures.p.serialize_rctsig_prunable(
+ ar,
+ self.rct_signatures.type,
+ len(self.vin),
+ len(self.vout),
+ mixin_size,
+ )
+ return self
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..55037a189
--- /dev/null
+++ b/src/apps/monero/xmr/serialize_messages/tx_prefix.py
@@ -0,0 +1,122 @@
+from micropython import const
+
+from apps.monero.xmr.serialize.base_types import UInt8, UVarintType
+from apps.monero.xmr.serialize.message_types import (
+ BlobType,
+ 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))
+
+
+class TransactionPrefix(MessageType):
+ @classmethod
+ def f_specs(cls):
+ return (
+ ("version", UVarintType),
+ ("unlock_time", UVarintType),
+ ("vin", ContainerType, TxInV),
+ ("vout", ContainerType, TxOut),
+ ("extra", ContainerType, UInt8),
+ )
+
+
+class TransactionPrefixExtraBlob(TransactionPrefix):
+ # noinspection PyTypeChecker
+ @classmethod
+ def f_specs(cls):
+ return TransactionPrefix.f_specs()[:-1] + (("extra", BlobType),)
diff --git a/src/apps/monero/xmr/serialize_messages/tx_rsig.py b/src/apps/monero/xmr/serialize_messages/tx_rsig.py
new file mode 100644
index 000000000..c1c8c0944
--- /dev/null
+++ b/src/apps/monero/xmr/serialize_messages/tx_rsig.py
@@ -0,0 +1,6 @@
+class RctType(object):
+ Null = 0
+ Full = 1
+ Simple = 2
+ FullBulletproof = 3
+ SimpleBulletproof = 4
diff --git a/src/apps/monero/xmr/serialize_messages/tx_rsig_boro.py b/src/apps/monero/xmr/serialize_messages/tx_rsig_boro.py
new file mode 100644
index 000000000..740971750
--- /dev/null
+++ b/src/apps/monero/xmr/serialize_messages/tx_rsig_boro.py
@@ -0,0 +1,19 @@
+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 Key64
+
+
+class BoroSig(MessageType):
+ __slots__ = ("s0", "s1", "ee")
+
+ @classmethod
+ def f_specs(cls):
+ return (("s0", Key64), ("s1", Key64), ("ee", ECKey))
+
+
+class RangeSig(MessageType):
+ __slots__ = ("asig", "Ci")
+
+ @classmethod
+ def f_specs(cls):
+ return (("asig", BoroSig), ("Ci", Key64))
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..40a3d9eb8
--- /dev/null
+++ b/src/apps/monero/xmr/serialize_messages/tx_rsig_bulletproof.py
@@ -0,0 +1,23 @@
+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):
+ __slots__ = ("V", "A", "S", "T1", "T2", "taux", "mu", "L", "R", "a", "b", "t")
+
+ @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/serialize_messages/tx_sig.py b/src/apps/monero/xmr/serialize_messages/tx_sig.py
new file mode 100644
index 000000000..3823f9238
--- /dev/null
+++ b/src/apps/monero/xmr/serialize_messages/tx_sig.py
@@ -0,0 +1,41 @@
+from apps.monero.xmr.serialize.erefs import eref
+from apps.monero.xmr.serialize.message_types import ContainerType, MessageType
+from apps.monero.xmr.serialize_messages.base import ECKey
+from apps.monero.xmr.serialize_messages.tx_prefix import (
+ TxinGen,
+ TxinToKey,
+ TxinToScript,
+ TxinToScriptHash,
+)
+
+
+class Signature(MessageType):
+ __slots__ = ("c", "r")
+
+ @classmethod
+ def f_specs(cls):
+ return (("c", ECKey), ("r", ECKey))
+
+ async def serialize_archive(self, ar):
+ ar.field(eref(self, "c"), ECKey)
+ ar.field(eref(self, "r"), ECKey)
+ return self
+
+
+class SignatureArray(ContainerType):
+ FIX_SIZE = 0
+ ELEM_TYPE = Signature
+
+
+def get_signature_size(msg):
+ """
+ Returns a signature size for the input
+ :param msg:
+ :return:
+ """
+ if isinstance(msg, (TxinGen, TxinToScript, TxinToScriptHash)):
+ return 0
+ elif isinstance(msg, TxinToKey):
+ return len(msg.key_offsets)
+ else:
+ raise ValueError("Unknown tx in")
diff --git a/src/apps/monero/xmr/serialize_messages/tx_src_entry.py b/src/apps/monero/xmr/serialize_messages/tx_src_entry.py
new file mode 100644
index 000000000..f3778d7fb
--- /dev/null
+++ b/src/apps/monero/xmr/serialize_messages/tx_src_entry.py
@@ -0,0 +1,36 @@
+from apps.monero.xmr.serialize.base_types import BoolType, SizeT, UInt64, UVarintType
+from apps.monero.xmr.serialize.message_types import (
+ ContainerType,
+ MessageType,
+ TupleType,
+)
+from apps.monero.xmr.serialize_messages.base import ECKey, ECPublicKey
+from apps.monero.xmr.serialize_messages.ct_keys import CtKey
+
+
+class MultisigKLRki(MessageType):
+ @classmethod
+ def f_specs(cls):
+ return (("K", ECKey), ("L", ECKey), ("R", ECKey), ("ki", ECKey))
+
+
+class OutputEntry(TupleType):
+ @classmethod
+ def f_specs(cls):
+ return (UVarintType, CtKey) # original: x.UInt64
+
+
+class TxSourceEntry(MessageType):
+ @classmethod
+ def f_specs(cls):
+ return (
+ ("outputs", ContainerType, OutputEntry),
+ ("real_output", SizeT),
+ ("real_out_tx_key", ECPublicKey),
+ ("real_out_additional_tx_keys", ContainerType, ECPublicKey),
+ ("real_output_in_tx_index", UInt64),
+ ("amount", UInt64),
+ ("rct", BoolType),
+ ("mask", ECKey),
+ ("multisig_kLRki", MultisigKLRki),
+ )
diff --git a/src/apps/monero/xmr/sub/__init__.py b/src/apps/monero/xmr/sub/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/src/apps/monero/xmr/sub/addr.py b/src/apps/monero/xmr/sub/addr.py
new file mode 100644
index 000000000..f00d5303f
--- /dev/null
+++ b/src/apps/monero/xmr/sub/addr.py
@@ -0,0 +1,131 @@
+from trezor.crypto import monero as tcry
+
+from apps.monero.xmr.sub.xmr_net import NetworkTypes, net_version
+
+
+def addr_to_hash(addr):
+ """
+ Creates hashable address representation
+ :param addr:
+ :return:
+ """
+ return bytes(addr.m_spend_public_key + addr.m_view_public_key)
+
+
+def encode_addr(version, spend_pub, view_pub):
+ """
+ Encodes public keys as versions
+ :param version:
+ :param spend_pub:
+ :param view_pub:
+ :return:
+ """
+ buf = spend_pub + view_pub
+ 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.
+
+ :param addr:
+ :return:
+ """
+ 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
+ :param pub_addr:
+ :type pub_addr: apps.monero.xmr.serialize_messages.addr.AccountPublicAddress
+ :param is_sub:
+ :param net:
+ :return:
+ """
+ net_ver = net_version(net, is_sub)
+ return encode_addr(net_ver, pub_addr.m_spend_public_key, pub_addr.m_view_public_key)
+
+
+def classify_subaddresses(tx_dests, change_addr):
+ """
+ Classify destination subaddresses
+ void classify_addresses()
+ :param tx_dests:
+ :type tx_dests: list[apps.monero.xmr.serialize_messages.tx_construct.TxDestinationEntry]
+ :param change_addr:
+ :return:
+ """
+ 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):
+ """
+ Address comparisson. Allocation free.
+ :param a:
+ :param b:
+ :return:
+ """
+ return pub_eq(a.m_spend_public_key, b.m_spend_public_key) and pub_eq(
+ a.m_view_public_key, b.m_view_public_key
+ )
+
+
+def pub_eq(a, b):
+ """
+ Simple non-constant time public key compare
+ :param a:
+ :param b:
+ :return:
+ """
+ if a == b:
+ return True
+ if (a is None and b is not None) or (a is not None and b is None):
+ return False
+ if len(a) != len(b):
+ return False
+ for i in range(len(a)):
+ if a[i] != b[i]:
+ return False
+ return True
+
+
+def get_change_addr_idx(outputs, change_dts):
+ """
+ Returns ID of the change output from the change_dts and outputs
+ :param tsx_data:
+ :return:
+ """
+ if change_dts is None:
+ return None
+
+ change_idx = None
+ change_coord = change_dts.amount, change_dts.addr
+ for idx, dst in enumerate(outputs):
+ if (
+ change_coord
+ and change_coord[0]
+ and change_coord[0] == dst.amount
+ and addr_eq(change_coord[1], dst.addr)
+ ):
+ change_idx = idx
+ return change_idx
diff --git a/src/apps/monero/xmr/sub/creds.py b/src/apps/monero/xmr/sub/creds.py
new file mode 100644
index 000000000..84336c602
--- /dev/null
+++ b/src/apps/monero/xmr/sub/creds.py
@@ -0,0 +1,46 @@
+from apps.monero.xmr import crypto
+from apps.monero.xmr.sub.addr import encode_addr
+from apps.monero.xmr.sub.xmr_net import NetworkTypes, net_version
+
+
+class AccountCreds(object):
+ """
+ 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
+ self.multisig_keys = []
+
+ @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/sub/keccak_hasher.py b/src/apps/monero/xmr/sub/keccak_hasher.py
new file mode 100644
index 000000000..00ace5930
--- /dev/null
+++ b/src/apps/monero/xmr/sub/keccak_hasher.py
@@ -0,0 +1,67 @@
+from apps.monero.xmr import crypto
+from apps.monero.xmr.serialize import xmrserialize
+
+
+class KeccakArchive(object):
+ def __init__(self, ctx=None):
+ self.kwriter = get_keccak_writer(ctx=ctx)
+ self.ar = xmrserialize.Archive(self.kwriter, True)
+
+ def ctx(self):
+ return self.kwriter.ctx()
+
+ def refresh(self, ctx=None, xser=None):
+ if ctx is None:
+ ctx = self.kwriter.ctx()
+ if xser is None:
+ xser = xmrserialize
+
+ self.kwriter = get_keccak_writer(ctx=ctx)
+ self.ar = xser.Archive(self.kwriter, True)
+ return self.ar
+
+
+class HashWrapper(object):
+ def __init__(self, ctx):
+ self.ctx = ctx
+
+ def update(self, buf):
+ if len(buf) == 0:
+ return
+ self.ctx.update(buf)
+
+ def digest(self):
+ return self.ctx.digest()
+
+ def hexdigest(self):
+ return self.ctx.hexdigest()
+
+
+class AHashWriter:
+ def __init__(self, hasher, sub_writer=None):
+ self.hasher = hasher
+ self.sub_writer = sub_writer
+
+ async def awrite(self, buf):
+ self.hasher.update(buf)
+ if self.sub_writer:
+ await self.sub_writer.awrite(buf)
+ return len(buf)
+
+ def get_digest(self, *args) -> bytes:
+ return self.hasher.digest(*args)
+
+ def ctx(self):
+ return self.hasher.ctx
+
+
+def get_keccak_writer(sub_writer=None, ctx=None):
+ """
+ Creates new fresh async Keccak writer
+ :param sub_writer:
+ :param ctx:
+ :return:
+ """
+ return AHashWriter(
+ HashWrapper(crypto.get_keccak() if ctx is None else ctx), sub_writer=sub_writer
+ )
diff --git a/src/apps/monero/xmr/sub/mlsag_hasher.py b/src/apps/monero/xmr/sub/mlsag_hasher.py
new file mode 100644
index 000000000..263a2146b
--- /dev/null
+++ b/src/apps/monero/xmr/sub/mlsag_hasher.py
@@ -0,0 +1,139 @@
+from apps.monero.xmr import crypto
+from apps.monero.xmr.serialize_messages.base import ECKey
+from apps.monero.xmr.serialize_messages.ct_keys import KeyV
+from apps.monero.xmr.serialize_messages.tx_ecdh import EcdhInfo
+from apps.monero.xmr.serialize_messages.tx_full import RctSigBase
+
+
+class PreMlsagHasher(object):
+ """
+ Iterative construction of the pre_mlsag_hash
+ """
+
+ def __init__(self, state=None):
+ from apps.monero.xmr.sub.keccak_hasher import KeccakArchive, HashWrapper
+
+ self.is_simple = state[0] if state else None
+ self.state = state[1] if state else 0
+ self.kc_master = HashWrapper(state[2] if state else crypto.get_keccak())
+ self.rsig_hasher = state[3] if state else crypto.get_keccak()
+ self.rtcsig_hasher = None
+ if state:
+ self.rtcsig_hasher = KeccakArchive(state[4]) if state[4] else None
+ else:
+ self.rtcsig_hasher = KeccakArchive()
+
+ def state_save(self):
+ return (
+ self.is_simple,
+ self.state,
+ self.kc_master.ctx,
+ self.rsig_hasher,
+ self.rtcsig_hasher.ctx() if self.rtcsig_hasher else None,
+ )
+
+ def state_load(self, x):
+ from apps.monero.xmr.sub.keccak_hasher import KeccakArchive, HashWrapper
+
+ self.is_simple = x[0]
+ self.state = x[1]
+ self.kc_master = HashWrapper(x[2])
+ self.rsig_hasher = x[3]
+ if x[4]:
+ self.rtcsig_hasher = KeccakArchive(x[4])
+ else:
+ self.rtcsig_hasher = None
+
+ def init(self, is_simple):
+ if self.state != 0:
+ raise ValueError("State error")
+
+ self.state = 1
+ self.is_simple = is_simple
+
+ async def set_message(self, message):
+ self.kc_master.update(message)
+
+ async def set_type_fee(self, rv_type, fee):
+ if self.state != 1:
+ raise ValueError("State error")
+ self.state = 2
+
+ rfields = RctSigBase.f_specs()
+ await self.rtcsig_hasher.ar.message_field(
+ None, field=rfields[0], fvalue=rv_type
+ )
+ await self.rtcsig_hasher.ar.message_field(None, field=rfields[1], fvalue=fee)
+
+ async def set_pseudo_out(self, out):
+ if self.state != 2 and self.state != 3:
+ raise ValueError("State error")
+ self.state = 3
+
+ await self.rtcsig_hasher.ar.field(out, KeyV.ELEM_TYPE)
+
+ async def set_ecdh(self, ecdh):
+ if self.state != 2 and self.state != 3 and self.state != 4:
+ raise ValueError("State error")
+ self.state = 4
+
+ await self.rtcsig_hasher.ar.field(ecdh, EcdhInfo.ELEM_TYPE)
+
+ async def set_out_pk(self, out_pk, mask=None):
+ if self.state != 4 and self.state != 5:
+ raise ValueError("State error")
+ self.state = 5
+
+ await self.rtcsig_hasher.ar.field(mask if mask else out_pk.mask, ECKey)
+
+ async def rctsig_base_done(self):
+ if self.state != 5:
+ raise ValueError("State error")
+ self.state = 6
+
+ c_hash = self.rtcsig_hasher.kwriter.get_digest()
+ self.kc_master.update(c_hash)
+ self.rtcsig_hasher = None
+
+ async def rsig_val(self, p, bulletproof, raw=False):
+ if self.state == 8:
+ raise ValueError("State error")
+
+ if raw:
+ 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])
+
+ async 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/sub/recode.py b/src/apps/monero/xmr/sub/recode.py
new file mode 100644
index 000000000..be6d351ff
--- /dev/null
+++ b/src/apps/monero/xmr/sub/recode.py
@@ -0,0 +1,46 @@
+from apps.monero.xmr import crypto
+from apps.monero.xmr.serialize_messages.tx_ecdh import EcdhTuple
+
+
+def copy_ecdh(ecdh):
+ """
+ Clones ECDH tuple
+ :param ecdh:
+ :return:
+ """
+ return EcdhTuple(mask=ecdh.mask, amount=ecdh.amount)
+
+
+def recode_ecdh(ecdh, encode=True):
+ """
+ In-place ecdhtuple recoding
+ :param ecdh:
+ :param encode: if true encodes to byte representation, otherwise decodes from byte representation
+ :return:
+ """
+ recode_int = crypto.encodeint if encode else crypto.decodeint
+ ecdh.mask = recode_int(ecdh.mask)
+ ecdh.amount = recode_int(ecdh.amount)
+ return ecdh
+
+
+def recode_msg(mgs, encode=True):
+ """
+ Recodes MGs signatures from raw forms to bytearrays so it works with serialization
+ :param rv:
+ :param encode: if true encodes to byte representation, otherwise decodes from byte representation
+ :return:
+ """
+ recode_int = crypto.encodeint if encode else crypto.decodeint
+ recode_point = crypto.encodepoint if encode else crypto.decodepoint
+
+ for idx in range(len(mgs)):
+ mgs[idx].cc = recode_int(mgs[idx].cc)
+ if hasattr(mgs[idx], "II") and mgs[idx].II:
+ for i in range(len(mgs[idx].II)):
+ mgs[idx].II[i] = recode_point(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] = recode_int(mgs[idx].ss[i][j])
+ return mgs
diff --git a/src/apps/monero/xmr/sub/tsx_helper.py b/src/apps/monero/xmr/sub/tsx_helper.py
new file mode 100644
index 000000000..cafebe663
--- /dev/null
+++ b/src/apps/monero/xmr/sub/tsx_helper.py
@@ -0,0 +1,236 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# Author: Dusan Klinec, ph4r05, 2018
+from apps.monero.xmr import crypto
+from apps.monero.xmr.serialize import xmrserialize
+from apps.monero.xmr.serialize.readwriter import MemoryReaderWriter
+from apps.monero.xmr.serialize_messages.addr import AccountPublicAddress
+from apps.monero.xmr.serialize_messages.tx_extra import (
+ TxExtraAdditionalPubKeys,
+ TxExtraField,
+)
+
+
+async def parse_extra_fields(extra_buff):
+ """
+ Parses extra buffer to the extra fields vector
+ :param extra_buff:
+ :return:
+ """
+ extras = []
+ rw = MemoryReaderWriter(extra_buff)
+ ar2 = xmrserialize.Archive(rw, False)
+ while len(rw.get_buffer()) > 0:
+ extras.append(await ar2.variant(elem_type=TxExtraField))
+ return extras
+
+
+def find_tx_extra_field_by_type(extra_fields, msg, idx=0):
+ """
+ Finds given message type in the extra array, or returns None if not found
+ :param extra_fields:
+ :param msg:
+ :param idx:
+ :return:
+ """
+ cur_idx = 0
+ for x in extra_fields:
+ if isinstance(x, msg):
+ if cur_idx == idx:
+ return x
+ cur_idx += 1
+ return None
+
+
+def has_encrypted_payment_id(extra_nonce):
+ """
+ Returns true if encrypted payment id is present
+ :param extra_nonce:
+ :return:
+ """
+ return len(extra_nonce) == 9 and extra_nonce[0] == 1
+
+
+def has_payment_id(extra_nonce):
+ """
+ Returns true if payment id is present
+ :param extra_nonce:
+ :return:
+ """
+ return len(extra_nonce) == 33 and extra_nonce[0] == 0
+
+
+def get_payment_id_from_tx_extra_nonce(extra_nonce):
+ """
+ Extracts encrypted payment id from extra
+ :param extra_nonce:
+ :return:
+ """
+ if 33 != len(extra_nonce):
+ raise ValueError("Nonce size mismatch")
+ if 0x0 != extra_nonce[0]:
+ raise ValueError("Nonce payment type invalid")
+ return extra_nonce[1:]
+
+
+def get_encrypted_payment_id_from_tx_extra_nonce(extra_nonce):
+ """
+ Extracts encrypted payment id from extra
+ :param extra_nonce:
+ :return:
+ """
+ if 9 != len(extra_nonce):
+ raise ValueError("Nonce size mismatch")
+ if 0x1 != extra_nonce[0]:
+ raise ValueError("Nonce payment type invalid")
+ return extra_nonce[1:]
+
+
+def set_payment_id_to_tx_extra_nonce(payment_id):
+ """
+ Sets payment ID to the extra
+ :param payment_id:
+ :return:
+ """
+ return b"\x00" + payment_id
+
+
+def absolute_output_offsets_to_relative(off):
+ """
+ Relative offsets, prev + cur = next.
+ Helps with varint encoding size.
+ :param off:
+ :return:
+ """
+ if len(off) == 0:
+ return off
+ res = sorted(off)
+ for i in range(len(off) - 1, 0, -1):
+ res[i] -= res[i - 1]
+ return res
+
+
+def get_destination_view_key_pub(destinations, change_addr=None):
+ """
+ Returns destination address public view key
+ :param destinations:
+ :type destinations: list[apps.monero.xmr.serialize_messages.tx_construct.TxDestinationEntry]
+ :param change_addr:
+ :return:
+ """
+ from apps.monero.xmr.sub.addr import addr_eq
+
+ addr = AccountPublicAddress(
+ m_spend_public_key=crypto.NULL_KEY_ENC, m_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:
+ return crypto.NULL_KEY_ENC
+ addr = dest.addr
+ count += 1
+ return addr.m_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.
+ :param payment_id:
+ :param public_key:
+ :param secret_key:
+ :return:
+ """
+ derivation_p = crypto.generate_key_derivation(public_key, secret_key)
+ derivation = bytearray(33)
+ derivation = crypto.encodepoint_into(derivation_p, derivation)
+ 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
+
+
+def set_encrypted_payment_id_to_tx_extra_nonce(payment_id):
+ return b"\x01" + payment_id
+
+
+async def remove_field_from_tx_extra(extra, mtype):
+ """
+ Removes extra field of fiven type from the buffer
+ Reserializes with skipping the given mtype.
+ :param extra:
+ :param mtype:
+ :return:
+ """
+ if len(extra) == 0:
+ return []
+
+ reader = MemoryReaderWriter(extra)
+ writer = MemoryReaderWriter()
+ ar_read = xmrserialize.Archive(reader, False)
+ ar_write = xmrserialize.Archive(writer, True)
+ while len(reader.get_buffer()) > 0:
+ c_extras = await ar_read.variant(elem_type=TxExtraField)
+ if not isinstance(c_extras, mtype):
+ await ar_write.variant(c_extras, elem_type=TxExtraField)
+
+ return writer.get_buffer()
+
+
+def add_extra_nonce_to_tx_extra(extra, extra_nonce):
+ """
+ Appends nonce extra to the extra buffer
+ :param extra:
+ :param extra_nonce:
+ :return:
+ """
+ if len(extra_nonce) > 255:
+ raise ValueError("Nonce could be 255 bytes max")
+ extra += b"\x02" + len(extra_nonce).to_bytes(1, "big") + extra_nonce
+ return extra
+
+
+def add_tx_pub_key_to_extra(tx_extra, pub_key):
+ """
+ Adds public key to the extra
+ :param tx_extra:
+ :param pub_key:
+ :return:
+ """
+ to_add = bytearray(33)
+ to_add[0] = 1
+ crypto.encodepoint_into(pub_key, memoryview(to_add)[1:]) # TX_EXTRA_TAG_PUBKEY
+ return tx_extra + to_add
+
+
+async def add_additional_tx_pub_keys_to_extra(
+ tx_extra, additional_pub_keys=None, pub_enc=None
+):
+ """
+ Adds all pubkeys to the extra
+ :param tx_extra:
+ :param additional_pub_keys:
+ :param pub_enc: None
+ :return:
+ """
+ pubs_msg = TxExtraAdditionalPubKeys(
+ data=pub_enc
+ if pub_enc
+ else [crypto.encodepoint(x) for x in additional_pub_keys]
+ )
+
+ rw = MemoryReaderWriter()
+ ar = xmrserialize.Archive(rw, True)
+
+ # format: variant_tag (0x4) | array len varint | 32B | 32B | ...
+ await ar.variant(pubs_msg, TxExtraField)
+ tx_extra += bytes(rw.get_buffer())
+ return tx_extra
diff --git a/src/apps/monero/xmr/sub/xmr_net.py b/src/apps/monero/xmr/sub/xmr_net.py
new file mode 100644
index 000000000..308215640
--- /dev/null
+++ b/src/apps/monero/xmr/sub/xmr_net.py
@@ -0,0 +1,46 @@
+class NetworkTypes(object):
+ MAINNET = 0
+ TESTNET = 1
+ STAGENET = 2
+ FAKECHAIN = 3
+
+
+class MainNet(object):
+ PUBLIC_ADDRESS_BASE58_PREFIX = 18
+ PUBLIC_INTEGRATED_ADDRESS_BASE58_PREFIX = 19
+ PUBLIC_SUBADDRESS_BASE58_PREFIX = 42
+
+
+class TestNet(object):
+ PUBLIC_ADDRESS_BASE58_PREFIX = 53
+ PUBLIC_INTEGRATED_ADDRESS_BASE58_PREFIX = 54
+ PUBLIC_SUBADDRESS_BASE58_PREFIX = 63
+
+
+class StageNet(object):
+ 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):
+ """
+ Network version bytes used for address construction
+ :return:
+ """
+ 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 not is_subaddr
+ else c_net.PUBLIC_SUBADDRESS_BASE58_PREFIX
+ )
+ return bytes([prefix])
diff --git a/src/apps/monero/xmr/tsx_data.py b/src/apps/monero/xmr/tsx_data.py
new file mode 100644
index 000000000..38733d688
--- /dev/null
+++ b/src/apps/monero/xmr/tsx_data.py
@@ -0,0 +1,61 @@
+from apps.monero.xmr.serialize.base_types import BoolType, UVarintType
+from apps.monero.xmr.serialize.message_types import BlobType, ContainerType, MessageType
+from apps.monero.xmr.serialize_messages.base import SecretKey
+from apps.monero.xmr.serialize_messages.tx_dest_entry import TxDestinationEntry
+
+
+class TsxData(MessageType):
+ """
+ TsxData, initial input to the transaction processing.
+ Serialization structure for easy hashing.
+ """
+
+ __slots__ = (
+ "version",
+ "payment_id",
+ "unlock_time",
+ "outputs",
+ "change_dts",
+ "num_inputs",
+ "mixin",
+ "fee",
+ "account",
+ "minor_indices",
+ "is_multisig",
+ "exp_tx_prefix_hash",
+ "use_tx_keys",
+ "is_bulletproof",
+ )
+
+ @classmethod
+ def f_specs(cls):
+ return (
+ ("version", UVarintType),
+ ("payment_id", BlobType),
+ ("unlock_time", UVarintType),
+ ("outputs", ContainerType, TxDestinationEntry),
+ ("change_dts", TxDestinationEntry),
+ ("num_inputs", UVarintType),
+ ("mixin", UVarintType),
+ ("fee", UVarintType),
+ ("account", UVarintType),
+ ("minor_indices", ContainerType, UVarintType),
+ ("is_multisig", BoolType),
+ ("exp_tx_prefix_hash", BlobType), # expected prefix hash, bail on error
+ ("use_tx_keys", ContainerType, SecretKey), # use this secret key, multisig
+ ("is_bulletproof", BoolType),
+ )
+
+ def __init__(self, payment_id=None, outputs=None, change_dts=None, **kwargs):
+ super().__init__(**kwargs)
+
+ self.payment_id = payment_id
+ self.change_dts = change_dts
+ self.fee = 0
+ self.account = 0
+ self.minor_indices = [0]
+ self.outputs = outputs if outputs else [] # type: list[TxDestinationEntry]
+ self.is_multisig = False
+ self.is_bulletproof = False
+ self.exp_tx_prefix_hash = b""
+ self.use_tx_keys = []
diff --git a/src/main.py b/src/main.py
index 24af03958..9fd1dced1 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
@@ -30,6 +31,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 7eb67a3d9..3bdbcaf74 100644
--- a/src/trezor/crypto/__init__.py
+++ b/src/trezor/crypto/__init__.py
@@ -3,6 +3,7 @@
bip39,
chacha20poly1305,
crc,
+ monero,
nem,
pbkdf2,
random,