From 2d5c6ceea3a9ab1c7eff32b97300b5d1d2cc9aba Mon Sep 17 00:00:00 2001 From: Dusan Klinec Date: Wed, 15 Aug 2018 10:08:02 +0200 Subject: [PATCH] extmod: monero - reduce32 and ge25519_norm removed (squashed commit) - not needed in trezor-core (+4 squashed commits) Squashed commits: [90e6b5c5] xmr: bp optimization [4fda0d22] xmr: redundant ge_ functions removed [68903767] xmr: crypto - sc_reduce32 not needed [c8a6c807] xmr: test for inversion added (+12 squashed commits) Squashed commits: [378928db] xmr: adapting to new trezor-crypto [8f4ff8c1] protob sync [82dff70a] vendor: trezor-common version bump [fabc67b3] extmod: monero - inversion mod curve order optimized a bit [4f29fe4c] xmr: import fix [f6f8e300] xmr: bp - code cleanup [d54b4f3b] xmr: bp - memory cleaning [1065abc3] xmr: tsx_signer - bulletproofs fixes [9f8a700f] xmr: bp key vector iterator fix [49c25977] xmr.serialize: bulletproof fix [1ee77378] xmr: monero - format [cf0a7104] xmr: bp last mask fix (+20 squashed commits) Squashed commits: [fa1c3623] xmr: black [3f3e31f3] xmr: bulletproofs added to signer [d23d9284] xmr: protocol.tsx_sign_builder - logger collects [a28eb55f] xmr: bp - memory optimizations [d2fcb23a] xmr: tests for bulletproofs added [82eef146] xmr: bp - gc (+14 squashed commits) Squashed commits: [4cf70d97] xmr: bp - gc [42877b05] xmr: bp - minor memory optimization [2c612e45] xmr: bp - use sc_inv_into [d7e9dab4] xmr: bp - KeyVEval fix [1523f400] xmr: bp - blacked [b264a65b] xmr: bp - KeyVEval - caching current element, avoid allocations [83ba7a65] xmr: bp - memory view optimized [b517906c] xmr: bp - gc() during inversion [92d37c88] xmr: bp - gc.collect() after expensive inversion [e7fad558] xmr: bp - hashing memory optimization [4c278152] xmr: bp - deterministic masks optimization, prove_s1 optim [cbf74a70] xmr: bp - detect which modular inversion is usable [8ea1ec43] xmr: better memory tracing for bulletproofs [2f4dd552] xmr: bulletproofs added [1928e2d3] xmr: crypto - sc_inv_into added (+2 squashed commits) Squashed commits: [f895fa6e] xmr: crypto - hash to existing buffer [b76c6b09] xmr: crypto - in-place crypto functions added - required for Bulletproof to minimize the heap fragmentation [cab4366e] extmod: monero - modular inversion mod curve order added (+2 squashed commits) Squashed commits: [52a6e487] extmod: monero - hash into buffer added [695a3827] extmod: monero module - muladd256_modm added - required for Bulletproof [3f4498d7] xmr: crypto tests added - basic unit tests for crypto, tests monero module and underlying trezor-crypto + basic address manipulation [820d012d] pb sync [49eeddd1] vendor: trezor-common version bump [30382440] xmr: crypto - point norm not needed [89701c41] tests: xmr - serializer tests added [bfee46db] tests: support async unit tests, assertListEqual added [55c14487] xmr: serialize - serialization logic cleaned, refactored [4b771638] xmr: simplification, do not ask to confirm change tx output - change address checked to match main address in the builder [f334d8ad] xmr: protocol: simplification - require change address to equal the main address [1a3416eb] xmr: unpack256_modm_noreduce added - 32B array to integer mod curve order, without modular reduction after conversion - required for bulletproofs [1c94b5d4] xmr: readme added [3cc9f9fa] extmod/monero: mul256_modm added, required for BP --- SConscript.firmware | 2 +- SConscript.unix | 2 +- .../modtrezorcrypto/modtrezorcrypto-monero.h | 139 ++- src/apps/monero/README.md | 320 ++++++ src/apps/monero/controller/iface.py | 6 +- src/apps/monero/controller/misc.py | 5 + src/apps/monero/protocol/tsx_sign_builder.py | 92 +- .../monero/protocol/tsx_sign_state_holder.py | 1 + src/apps/monero/xmr/bulletproof.py | 1022 +++++++++++++++++ src/apps/monero/xmr/crypto.py | 278 ++++- src/apps/monero/xmr/mlsag2.py | 4 +- src/apps/monero/xmr/monero.py | 58 +- src/apps/monero/xmr/ring_ct.py | 20 + src/apps/monero/xmr/serialize/xmrserialize.py | 627 +++++----- .../monero/xmr/serialize_messages/tx_full.py | 6 +- tests/test_apps.monero.bulletproof.py | 149 +++ tests/test_apps.monero.crypto.py | 232 ++++ tests/test_apps.monero.serializer.py | 362 ++++++ tests/unittest.py | 9 + vendor/trezor-crypto | 2 +- 20 files changed, 2816 insertions(+), 520 deletions(-) create mode 100644 src/apps/monero/README.md create mode 100644 src/apps/monero/xmr/bulletproof.py create mode 100644 tests/test_apps.monero.bulletproof.py create mode 100644 tests/test_apps.monero.crypto.py create mode 100644 tests/test_apps.monero.serializer.py diff --git a/SConscript.firmware b/SConscript.firmware index 306bb0ac6..d462247f2 100644 --- a/SConscript.firmware +++ b/SConscript.firmware @@ -63,9 +63,9 @@ SOURCE_MOD += [ 'vendor/trezor-crypto/ed25519-donna/ed25519-donna-impl-base.c', 'vendor/trezor-crypto/ed25519-donna/ed25519-keccak.c', 'vendor/trezor-crypto/ed25519-donna/ed25519-sha3.c', + 'vendor/trezor-crypto/ed25519-donna/ge25519.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', diff --git a/SConscript.unix b/SConscript.unix index 2c44a34ad..1c436ba1d 100644 --- a/SConscript.unix +++ b/SConscript.unix @@ -60,9 +60,9 @@ SOURCE_MOD += [ 'vendor/trezor-crypto/ed25519-donna/ed25519-donna-impl-base.c', 'vendor/trezor-crypto/ed25519-donna/ed25519-keccak.c', 'vendor/trezor-crypto/ed25519-donna/ed25519-sha3.c', + 'vendor/trezor-crypto/ed25519-donna/ge25519.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', diff --git a/embed/extmod/modtrezorcrypto/modtrezorcrypto-monero.h b/embed/extmod/modtrezorcrypto/modtrezorcrypto-monero.h index 52e789be0..96d1926c2 100644 --- a/embed/extmod/modtrezorcrypto/modtrezorcrypto-monero.h +++ b/embed/extmod/modtrezorcrypto/modtrezorcrypto-monero.h @@ -22,6 +22,8 @@ #include "py/mpz.h" #include "monero/monero.h" +#include "bignum.h" + #define RSIG_SIZE 6176 typedef struct _mp_obj_hasher_t { @@ -313,35 +315,6 @@ STATIC mp_obj_t mod_trezorcrypto_monero_get256_modm(const mp_obj_t arg){ } 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(); @@ -368,6 +341,19 @@ STATIC mp_obj_t mod_trezorcrypto_monero_sub256_modm(size_t n_args, const mp_obj_ } STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_trezorcrypto_monero_sub256_modm_obj, 2, 3, mod_trezorcrypto_monero_sub256_modm); +//void sub256_modm +STATIC mp_obj_t mod_trezorcrypto_monero_mul256_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]); + mul256_modm(MP_OBJ_SCALAR(res), MP_OBJ_C_SCALAR(args[1+off]), MP_OBJ_C_SCALAR(args[2+off])); + return res; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_trezorcrypto_monero_mul256_modm_obj, 2, 3, mod_trezorcrypto_monero_mul256_modm); + //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(); @@ -382,6 +368,40 @@ STATIC mp_obj_t mod_trezorcrypto_monero_mulsub256_modm(size_t n_args, const mp_o } STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_trezorcrypto_monero_mulsub256_modm_obj, 3, 4, mod_trezorcrypto_monero_mulsub256_modm); +//void muladd256_modm +STATIC mp_obj_t mod_trezorcrypto_monero_muladd256_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]); + muladd256_modm(MP_OBJ_SCALAR(res), MP_OBJ_C_SCALAR(args[1+off]), MP_OBJ_C_SCALAR(args[2+off]), MP_OBJ_C_SCALAR(args[3+off])); + return res; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_trezorcrypto_monero_muladd256_modm_obj, 3, 4, mod_trezorcrypto_monero_muladd256_modm); + +//void inv256_modm +STATIC mp_obj_t mod_trezorcrypto_monero_inv256_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); + assert_scalar(args[1+off]); + + // bn_prime = curve order, little endian encoded + bignum256 bn_prime = {.val={0x1cf5d3ed, 0x20498c69, 0x2f79cd65, 0x37be77a8, 0x14, 0x0, 0x0, 0x0, 0x1000}}; + bignum256 bn_x; + + memcpy(&bn_x.val, MP_OBJ_C_SCALAR(args[1+off]), sizeof(bignum256modm)); + bn_inverse(&bn_x, &bn_prime); + memcpy(MP_OBJ_SCALAR(res), bn_x.val, sizeof(bignum256modm)); + + return res; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_trezorcrypto_monero_inv256_modm_obj, 1, 2, mod_trezorcrypto_monero_inv256_modm); + //void contract256_modm_r STATIC mp_obj_t mod_trezorcrypto_monero_pack256_modm(const mp_obj_t arg){ assert_scalar(arg); @@ -415,6 +435,23 @@ STATIC mp_obj_t mod_trezorcrypto_monero_unpack256_modm(size_t n_args, const mp_o } STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_trezorcrypto_monero_unpack256_modm_obj, 1, 2, mod_trezorcrypto_monero_unpack256_modm); +//expand256_modm_r +STATIC mp_obj_t mod_trezorcrypto_monero_unpack256_modm_noreduce(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_buffer_info_t buff; + mp_get_buffer_raise(args[1+off], &buff, MP_BUFFER_READ); + if (buff.len != 32) { + mp_raise_ValueError("Invalid length of secret key"); + } + + expand_raw256_modm(MP_OBJ_SCALAR(res), buff.buf); + return res; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_trezorcrypto_monero_unpack256_modm_noreduce_obj, 1, 2, mod_trezorcrypto_monero_unpack256_modm_noreduce); + // // GE25519 Defs // @@ -456,17 +493,6 @@ STATIC mp_obj_t mod_trezorcrypto_monero_ge25519_eq(const mp_obj_t a, const mp_ob } 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; @@ -575,7 +601,7 @@ STATIC mp_obj_t mod_trezorcrypto_monero_ge25519_scalarmult_base(size_t n_args, c } 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); +//void ge25519_scalarmult(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; @@ -583,11 +609,11 @@ STATIC mp_obj_t mod_trezorcrypto_monero_ge25519_scalarmult(size_t n_args, const 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])); + ge25519_scalarmult(&MP_OBJ_GE25519(res), &MP_OBJ_C_GE25519(args[1+off]), MP_OBJ_C_SCALAR(args[2+off])); } else if (mp_obj_is_integer(args[2+off])){ bignum256modm mlt; set256_modm(mlt, mp_obj_get_int(args[2+off])); - ge25519_scalarmult_wrapper(&MP_OBJ_GE25519(res), &MP_OBJ_C_GE25519(args[1+off]), mlt); + ge25519_scalarmult(&MP_OBJ_GE25519(res), &MP_OBJ_C_GE25519(args[1+off]), mlt); } else { mp_raise_ValueError("unknown mult type"); } @@ -679,14 +705,25 @@ STATIC mp_obj_t mod_trezorcrypto_monero_xmr_random_scalar(size_t n_args, const m 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){ +STATIC mp_obj_t mod_trezorcrypto_monero_xmr_fast_hash(size_t n_args, const mp_obj_t *args){ + const int off = n_args == 2 ? 0 : -1; uint8_t buff[32]; + uint8_t * buff_use = buff; + if (n_args > 1){ + mp_buffer_info_t odata; + mp_get_buffer_raise(args[0], &odata, MP_BUFFER_WRITE); + if (odata.len < 32){ + mp_raise_ValueError("Output buffer too small"); + } + buff_use = odata.buf; + } + mp_buffer_info_t data; - mp_get_buffer_raise(arg, &data, MP_BUFFER_READ); - xmr_fast_hash(buff, data.buf, data.len); - return mp_obj_new_bytes(buff, 32); + mp_get_buffer_raise(args[1+off], &data, MP_BUFFER_READ); + xmr_fast_hash(buff_use, data.buf, data.len); + return n_args == 2 ? args[0] : mp_obj_new_bytes(buff, 32); } -STATIC MP_DEFINE_CONST_FUN_OBJ_1(mod_trezorcrypto_monero_xmr_fast_hash_obj, mod_trezorcrypto_monero_xmr_fast_hash); +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_trezorcrypto_monero_xmr_fast_hash_obj, 1, 2, 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){ @@ -988,13 +1025,16 @@ STATIC const mp_rom_map_elem_t mod_trezorcrypto_monero_globals_table[] = { { 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_mul256_modm), MP_ROM_PTR(&mod_trezorcrypto_monero_mul256_modm_obj) }, { MP_ROM_QSTR(MP_QSTR_mulsub256_modm), MP_ROM_PTR(&mod_trezorcrypto_monero_mulsub256_modm_obj) }, + { MP_ROM_QSTR(MP_QSTR_muladd256_modm), MP_ROM_PTR(&mod_trezorcrypto_monero_muladd256_modm_obj) }, + { MP_ROM_QSTR(MP_QSTR_inv256_modm), MP_ROM_PTR(&mod_trezorcrypto_monero_inv256_modm_obj) }, { MP_ROM_QSTR(MP_QSTR_pack256_modm), MP_ROM_PTR(&mod_trezorcrypto_monero_pack256_modm_obj) }, { MP_ROM_QSTR(MP_QSTR_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_unpack256_modm_noreduce), MP_ROM_PTR(&mod_trezorcrypto_monero_unpack256_modm_noreduce_obj) }, { MP_ROM_QSTR(MP_QSTR_ge25519_set_neutral), MP_ROM_PTR(&mod_trezorcrypto_monero_ge25519_set_neutral_obj) }, { MP_ROM_QSTR(MP_QSTR_ge25519_set_h), MP_ROM_PTR(&mod_trezorcrypto_monero_ge25519_set_xmr_h_obj) }, { MP_ROM_QSTR(MP_QSTR_ge25519_pack), MP_ROM_PTR(&mod_trezorcrypto_monero_ge25519_pack_obj) }, @@ -1002,7 +1042,6 @@ STATIC const mp_rom_map_elem_t mod_trezorcrypto_monero_globals_table[] = { { 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) }, diff --git a/src/apps/monero/README.md b/src/apps/monero/README.md new file mode 100644 index 000000000..eed7c8ba9 --- /dev/null +++ b/src/apps/monero/README.md @@ -0,0 +1,320 @@ +# Monero + +MAINTAINER = ... + +AUTHOR = Dusan Klinec + +REVIEWER = ... + +ADVISORS = + +----- + +This Monero implementation was implemented from scratch originally for TREZOR by porting Monero C++ code to the Python codebase. + +The implementation heavily relies on the [trezor-crypto] Monero functionality which implements basic crypto primitives and +other Monero related functionality (e.g., monero base58, accelerated and optimized Borromean range signatures) + +A general high level description of the integration proposal is described in the documentation: [monero-doc]. + +## Features + +The implementation provides the following features: + +### Transaction signature + +Signs a Monero transaction on the TREZOR. + +- Designed so number of UTXO is practically unlimited (hundreds to thousands) +- Maximal number of outputs per transaction is 8 (usually there are only 2) +- Supports 8 B encrypted payment ID and 32 B unencrypted payment ID. + +### Key Image sync + +Key Image is computed with the spend key which is stored on the TREZOR. + +In order to detect if the UTXO has been already spent (thus computing balance due to change transactions) +and correct spending UTXOs the key images are required. + +Key image sync is a protocol that allows to compute key images for incoming transfers by TREZOR. + + +## Integration rationale + +The Monero codebase already contains cold wallet support. I.e., wallet not connected to the Internet, which should provide +better security guarantees as it minimizes attack surface compared to the hot wallet - always connected wallet. + +As the cold wallet is not connected to the Internet and does not have access nor to the blockchain neither to the monero +full node the all information for transaction construction have to be prepared by the hot wallet. + +When using the cold wallet, hot wallet is watch-only. It has only the view-key so it can scan blockchain for incoming +transactions but is not able to spend any transaction. + +Transaction signature with cold wallet works like this: + +- Create transaction construction data on hot wallet. `transfer
`. Works similar to the normal wallet operation +but instead of the signed transaction, the watch-only hot wallet generates `unsigned_txset` file which contains +transaction construction data. + +- Cold wallet opens `unsigned_txset`, verifies the signature on the transaction construction data and creates Monero transaction +using the data. Cold wallet creates `signed_txset` + +- Hot wallet opens `signed_txset`, verifies the transaction and asks user whether to submit transaction to the full node. + +### Cold wallet protocols + +As cold wallet support is already present in Monero codebase, the protocols were well designed and analyzed. +We decided to reuse the cold wallet approach when signing the transaction as the TREZOR pretty much behaves as the cold wallet, +i.e., does not have access to the blockchain or full Monero node. The whole transaction is built in the TREZOR thus +the integration has security properties of the cold wallet (which is belevied to be secure). This integration approach +makes security analysis easier and enables to use existing codebase and protocols. This makes merging TREZOR support to +the Monero codebase easier. +We believe that by choosing a bit more high-level approach in the protocol design we could easily add more advanced features, + +TREZOR implements cold wallet protocols in this integration scheme. + + +## Description + +Main high level protocol logic is implemented in `apps/monero/protocol/` directory. + +### Serialization + +The serialization in `apps/monero/xmr/serialize` is the cryptonote serialization format used to serialize data to blockchain. +The serialization was ported from Monero C++. Source comes from the library [monero-serialize]. + +Serialization scheme was inspired by protobuf serialization scheme. Later it was subject to optimizations as +scheme definition with `FIELDS` attribute was quite memory hungry. Serialization was refactred to specify +fields as a classmethod which is easier to `gc.collect()` after serialization is done compared to static `FIELDS` +which are not easy to deallocate. + +```python + @classmethod + def f_specs(cls): + return (("size", SizeT),) +``` + +Serialization works in `async/wait` manner, uses `reader/writer` interface as protobuf uses. + +Moreover the serialization funtionality is encapsulated in so-called Archive object which encapsulates serialization logic. +Archive works in a symmetric way, i.e., the same API is used for serialization and deserialization. + + +### Protocols + +Transaction signing and Key Image (KI) sync are multi-step stateful protocols. +The protocol have several roundtrips. + +In the signing protocol the connected host mainly serves as a dumb storage providing values to the TREZOR when needed, +mainly due to memory constrains on TREZOR. The offloaded data can be in plaintext. In this case data is HMACed with unique HMAC +key to avoid data tampering, reordering, replay, reuse, etc... Some data are offloaded as protected, encrypted and authenticated +with Chacha20Poly1305 with unique key (derived from the protocol step, message, purpose, counter, master secret). + +TREZOR builds the signed Monero transaction incrementally, i.e., one UTXO per round trip, one transaction output per roundtrip. + +### Protocol wrapping messages + +Due to the dispatcher design we decided to use wrapping message for the multi-step protocols. +The top wrapping message contains sub-message field for each possible message in the protocol. In this way we can register +one simple dispatcher on the wrapping message and do the sub-message multiplexing in the code, hidden in the abstraction. + +Without wrapping message we would have to register each sub-message to the same handler and then de-multiplex it again +in the protocol logic which is error prone and duplicates the code. When changing the flow later it would be prone to errors. + +Responses are not wrapped and each response has own wire ID. Response messages are not registered so we don't need wrapping. + +Protobuf messages are following the convention `MoneroXRequest`, `MoneroXAck`. + + +## Key Image sync work flow + +In the KI sync cold wallet protocol KIs are generated by the cold wallet. For each KI there is a ring signature +generated by the cold wallet (KI proof). + +KI sync is mainly needed to recover from some problem or when using a new hot-wallet (corruption of a wallet file or +using TREZOR on a different host). + +The KI protocol has 3 steps. Wrapping message `MoneroKeyImageSyncRequest`. + +### Init step + +- `MoneroKeyImageExportInitRequest` +- Contains commitment to all KIs we are going to compute (hash of all UTXOs). +- User can confirm / reject the KI sync in this step. Init message contains number of KIs for computation. + +### Sync + +- `MoneroKeyImageSyncStepRequest` +- Computes N KIs in this step. N = 10 for now. +- Returns encrypted result, `MoneroExportedKeyImage` + +### Finalization + +- `MoneroKeyImageSyncFinalRequest` +- When commitment on all KIs is correct (i.e, number of UTXOs matches, hashes match) the encryption key is released +to the agent/hot-wallet so it can decrypt computed KIs and import it + + +## Transaction signing + +For detailed description and rationale please refer to the [monero-doc]. + +- The wrapping message: `MoneroTransactionSignRequest`. +- The main multiplexor: `apps/monero/protocol/tsx_sign.py` +- The main signing logic is implemented in `apps/monero/protocol/tsx_sign_builder.py` +- State automaton watching correct state transitions: `apps/monero/protocol/tsx_sign_state.py` +- State hold between protocol messages: `apps/monero/protocol/tsx_sign_state_holder.py`. The state is externalized in the +dedicated class so the memory consumption is minimal between round trips. + + +### `MoneroTransactionInitRequest`: + +- Contains basic construction data for the transaction, e.g., transaction destinations, fee, mixin level. + +After receiving this message: +- The TREZOR prompts user for verification of the destination addresses and amounts. +- Commitments are computed thus later potential deviations from transaction destinations are detected and signing aborts. +- Secrets for HMACs / encryption are computed, TX key is computed. +- Precomputes sub-addresses if needed. + +### `MoneroTransactionSetInputRequest` + +- Sends one UTXO to the TREZOR for processing, encoded as `MoneroTransactionSourceEntry`. +- Contains construction data needed for signing the transaction, computing spending key for UTXO. + +TREZOR computes spending keys, `TxinToKey`, `pseudo_out`, HMACs for offloaded data + +### `MoneroTransactionInputsPermutationRequest` + +UTXOs have to be sorted by the key image in the valid blockchain transaction. +This message caries permutation on the key images so they are sorted in the desired way. + +### `MoneroTransactionInputViniRequest` + +- Step needed to correctly hash all transaction inputs, in the right order computed in the previous step. +- Contains `MoneroTransactionSourceEntry` and `TxinToKey` computed in the previous step. +- TREZOR Computes `tx_prefix_hash` is part of the signed data. + +### `MoneroTransactionSetOutputRequest` + +Sends transaction output, `MoneroTransactionDestinationEntry`, one per message. +HMAC prevents tampering with previously accepted data (in the init step). + +TREZOR computes data related to transaction output, e.g., range proofs, ECDH info for the receiver, output public key. + +### `MoneroTransactionAllOutSetRequest` + +Sent after all transaction outputs have been sent to the TREZOR for processing. +Request is empty, the response contains computed `extra` field (may contain additional public keys if sub-addresses are used), +computed `tx_prefix_hash` and basis for the final transaction signature `MoneroRingCtSig` (fee, transaction type). + +### `MoneroTransactionMlsagDoneRequest` + +Message sent to ask TREZOR to compute pre-MLSAG hash required for the signature. +Hash is computed incrementally by TREZOR since the init message and can be finalized in this step. +Request is empty, response contains message hash, required for the signature. + +### `MoneroTransactionSignInputRequest` + +- Caries `MoneroTransactionSourceEntry`, similarly as previous messages `MoneroTransactionSetInputRequest`, `MoneroTransactionInputViniRequest`. +- Caries computed transaction inputs, pseudo outputs, HMACs, encrypted spending keys and alpha masks +- TREZOR generates MLSAG for this UTXO, returns the signature. +- Code returns also `cout` value if the multisig mode is active - not fully implemented, will be needed later when implementing multisigs. + +### `MoneroTransactionFinalRequest` + +- Sent when all UTXOs have been signed properly +- Finalizes transaction signature +- Returns encrypted transaction private keys which are needed later, e.g. for TX proof. As TREZOR cannot store aux data +for all signed transactions its offloaded encrypted to the wallet. Later when TX proof is implemented in the TREZOR it +will load encrypted TX keys, decrypt it and generate the proof. + + +## Implementation notes + +Few notes on desing / implementation. + +### Cryptography + +Operation with Ed25519 points and scalars are implemented in [trezor-crypto] so the underlying cryptography layer +is fast, secure and constant-time. + +Ed Point coordinates are Extended Edwards, using type `ge25519` with coordinates `(x, y, z, t)`. Functions in Monero code +in the [trezor-crypto] use the `ge25519` for points (no other different point formats). + +Functions like `op256_modm` (e.g., `add256_modm`) operate on scalar values, i.e., 256 bit integers modulo curve order +`2**252 + 3*610042537739*15158679415041928064055629`. + +Functions `curve25519_*` operate on 256 bit integers modulo `2**255 - 19`, the coordinates of the point. +These are used mainly internally (e.g., for `hash_to_point()`) and not exported to the [trezor-core]. + +[trezor-crypto] contains also some Monero-specific functions, such as +`xmr_hash_to_scalar`, `xmr_hash_to_ec`, `xmr_generate_key_derivation`. Those are used in [trezor-core] where more high +level operations are implemented, such as MLSAG. + +#### Crypto API + +API bridging [trezor-crypto] and [trezor-core]: `embed/extmod/modtrezorcrypto/modtrezorcrypto-monero.h` + +It encapsulates Ed25519 points and scalars in corresponding Python classes which have memory-wiping destructor. +API provides basic functions for work with scalars and points and Monero specific functions. + +The API is designed in such a way it is easy to work with Ed25519 as there is only one point format which is always +normed to avoid complications when chaining operations such as `scalarmult`s. + +### Point normalization + +Points in [trezor-core] are normed, i.e., `z=1`. + +Normalization is mainly needed after `ge25519_scalarmult`, `ge25519_scalarmult_base_niels`, +which is already done in Monero code in [trezor-crypto]. + +if the norming is not performed, the operations could not be chained arbitrarily as the result is invalid. + +Note: +Point normalization operation is typically performed when compressing coordinate point representation to the 32 B array +as `z` needs to be 1. It requires to compute inversion which is not for free. + +On the other hand, the original Monero C++ code typically operates on 32 B keys by +decompressing and compressing it after each result so they are doing normalization in each step, basically. + +There are some optimized chunks, e.g., range sig verification, which improves blockchain scanning +(still takes 3 days to verify the blockchain). +Optimized chunks are using different point representations to avoid redundant normalizations but in general cases, +it is not a performance issue for the sake of correct computation, easy development and maintenance. + +### Range signatures + +Borromean range signatures were optimized and ported to [trezor-crypto]. + +Range signatures xmr_gen_range_sig are CPU intensive and memory intensive operations which were originally implemented +in python (trezor-core) but it was not feasible to run on the Trezor device due to a small amount of RAM and long +computation times. It was needed to optimize the algorithm and port it to C so it is feasible to run it on the real hardware and run it fast. + +Range signature is a well-contained problem with no allocations needed, simple API. +For memory and timing reasons its implemented directly in trezor-crypto (as it brings real benefit to the user). + +On the other hand, MLASG and other ring signatures are built from building blocks in python for easier development, +code readability, maintenance and debugging. Porting to C is not that straightforward and I don't see any benefit here. +The memory and CPU is not the problem as in the case of range signatures so I think it is fine to have it in Python. +Porting to C would also increase complexity of trezor-crypto and could lead to bugs. + +Using small and easily auditable & testable building blocks, such as ge25519_add (fast, in C) to build more complex +schemes in high level language is, in my opinion, a scalable and secure way to build the system. +Porting all Monero crypto schemes to C would be very time consuming and prone to errors. + +Having access to low-level features also speeds up development of new features, such as multisigs / bulletproofs. + +MLSAG may need to be slightly changed when implementing multisigs +(some preparations have been made already but we will see after this phase starts). + + + + + + + +[trezor-crypto]: https://github.com/trezor/trezor-crypto +[trezor-core]: https://github.com/trezor/trezor-core +[monero-doc]: https://github.com/ph4r05/monero-trezor-doc +[monero-serialize]: https://github.com/ph4r05/monero-serialize diff --git a/src/apps/monero/controller/iface.py b/src/apps/monero/controller/iface.py index 76acba6cc..0579b63eb 100644 --- a/src/apps/monero/controller/iface.py +++ b/src/apps/monero/controller/iface.py @@ -38,12 +38,16 @@ async def confirm_transaction(self, tsx_data, creds=None, ctx=None): from apps.monero import layout for idx, dst in enumerate(outs): + is_change = change_idx and idx == change_idx + if is_change: + continue + addr = encode_addr( net_version(creds.network_type), dst.addr.spend_public_key, dst.addr.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 ) diff --git a/src/apps/monero/controller/misc.py b/src/apps/monero/controller/misc.py index 761e17c0c..22ab20543 100644 --- a/src/apps/monero/controller/misc.py +++ b/src/apps/monero/controller/misc.py @@ -20,6 +20,11 @@ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) +class TrezorChangeAddressError(TrezorError): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + class StdObj(object): def __init__(self, **kwargs): for kw in kwargs: diff --git a/src/apps/monero/protocol/tsx_sign_builder.py b/src/apps/monero/protocol/tsx_sign_builder.py index c6fcb7beb..84aeadfa0 100644 --- a/src/apps/monero/protocol/tsx_sign_builder.py +++ b/src/apps/monero/protocol/tsx_sign_builder.py @@ -50,6 +50,7 @@ def __init__(self, trezor=None, creds=None, state=None, **kwargs): self.output_change = None self.mixin = 0 self.fee = 0 + self.account_idx = 0 self.additional_tx_private_keys = [] self.additional_tx_public_keys = [] @@ -142,7 +143,7 @@ def state_save(self): setattr(t, attr, cval) return t - def _log_trace(self, x=None): + def _log_trace(self, x=None, collect=False): log.debug( __name__, "Log trace %s, ... F: %s A: %s, S: %s", @@ -151,6 +152,8 @@ def _log_trace(self, x=None): gc.mem_alloc(), micropython.stack_use(), ) + if collect: + gc.collect() def assrt(self, condition, msg=None): """ @@ -179,9 +182,25 @@ def gen_r(self, use_r=None): self.r = crypto.random_scalar() if use_r is None else use_r self.r_pub = crypto.scalarmult_base(self.r) + def get_primary_change_address(self): + """ + Computes primary change address for the current account index + :return: + """ + D, C = monero.generate_sub_address_keys( + self.creds.view_key_private, + self.creds.spend_key_public, + self.account_idx, + 0, + ) + return misc.StdObj( + view_public_key=crypto.encodepoint(C), + spend_public_key=crypto.encodepoint(D), + ) + def check_change(self, outputs): """ - Checks if the change address is among tx outputs. + Checks if the change address is among tx outputs and it is equal to our address. :param outputs: :return: """ @@ -191,11 +210,20 @@ def check_change(self, outputs): if change_addr is None: return + found = False for out in outputs: if addr_eq(out.addr, change_addr): - return True + found = True + break - raise ValueError("Change address not found in outputs") + if not found: + raise misc.TrezorChangeAddressError("Change address not found in outputs") + + my_addr = self.get_primary_change_address() + if not addr_eq(my_addr, change_addr): + raise misc.TrezorChangeAddressError("Change address differs from ours") + + return True def in_memory(self): """ @@ -453,6 +481,7 @@ async def init_transaction(self, tsx_data, tsx_ctr): self.output_change = misc.dst_entry_to_stdobj(tsx_data.change_dts) self.mixin = tsx_data.mixin self.fee = tsx_data.fee + self.account_idx = tsx_data.account self.use_simple_rct = self.input_count > 1 self.use_bulletproof = tsx_data.is_bulletproof self.multi_sig = tsx_data.is_multisig @@ -476,8 +505,8 @@ async def init_transaction(self, tsx_data, tsx_ctr): # 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.spend_public_key) + self.r_pub = crypto.scalarmult( + crypto.decodepoint(single_dest_subaddress.spend_public_key), self.r ) self.need_additional_txkeys = num_subaddresses > 0 and ( @@ -893,9 +922,6 @@ async def range_proof(self, idx, dest_pub_key, amount, amount_key): """ 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 = ( @@ -908,32 +934,40 @@ async def range_proof(self, idx, dest_pub_key, amount, amount_key): C, mask, rsig = None, 0, None # Rangeproof - gc.collect() + self._log_trace("pre-rproof", collect=True) if self.use_bulletproof: - raise ValueError("Bulletproof not yet supported") + C, mask, rsig = await ring_ct.prove_range_bp(amount, last_mask) + self._log_trace("post-bp", collect=True) + + # Incremental hashing + await self.full_message_hasher.rsig_val(rsig, True, raw=False) + self._log_trace("post-bp-hash", collect=True) + + rsig = await misc.dump_msg(rsig, preallocate=9 * 32 + 2 * 6 * 32 + 2) + self._log_trace("post-bp-ser", collect=True) else: + rsig_buff = bytearray(32 * (64 + 64 + 64 + 1)) + rsig_mv = memoryview(rsig_buff) + 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") + await self.full_message_hasher.rsig_val(rsig, False, raw=True) + + self._log_trace("rproof", collect=True) + self.assrt( + crypto.point_eq( + C, + crypto.point_add( + crypto.scalarmult_base(mask), crypto.scalarmult_h(amount) + ), + ), + "rproof", + ) # Mask sum out_pk.mask = crypto.encodepoint(C) @@ -972,12 +1006,12 @@ async def _set_out1_additional_keys(self, dst_entr): ) if dst_entr.is_subaddress: - additional_txkey = crypto.ge_scalarmult( - additional_txkey_priv, + additional_txkey = crypto.scalarmult( crypto.decodepoint(dst_entr.addr.spend_public_key), + additional_txkey_priv, ) else: - additional_txkey = crypto.ge_scalarmult_base(additional_txkey_priv) + additional_txkey = crypto.scalarmult_base(additional_txkey_priv) self.additional_tx_public_keys.append(crypto.encodepoint(additional_txkey)) if not use_provided: diff --git a/src/apps/monero/protocol/tsx_sign_state_holder.py b/src/apps/monero/protocol/tsx_sign_state_holder.py index 3d0cd12fa..1d1797d93 100644 --- a/src/apps/monero/protocol/tsx_sign_state_holder.py +++ b/src/apps/monero/protocol/tsx_sign_state_holder.py @@ -26,6 +26,7 @@ def __init__(self, **kwargs): self.output_change = None self.mixin = 0 self.fee = 0 + self.account_idx = 0 self.additional_tx_private_keys = [] self.additional_tx_public_keys = [] diff --git a/src/apps/monero/xmr/bulletproof.py b/src/apps/monero/xmr/bulletproof.py new file mode 100644 index 000000000..b1b208107 --- /dev/null +++ b/src/apps/monero/xmr/bulletproof.py @@ -0,0 +1,1022 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# Author: Dusan Klinec, ph4r05, 2018 + +import gc + +from apps.monero.xmr import crypto +from apps.monero.xmr.serialize.int_serialize import dump_uvarint_b, dump_uvarint_b_into +from apps.monero.xmr.serialize_messages.tx_rsig_bulletproof import Bulletproof + +# Constants + +BP_LOG_N = 6 +BP_N = 1 << BP_LOG_N # 64 + +ZERO = b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" +ONE = b"\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" +TWO = b"\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + +# Monero H point +XMR_H = b"\x8b\x65\x59\x70\x15\x37\x99\xaf\x2a\xea\xdc\x9f\xf1\xad\xd0\xea\x6c\x72\x51\xd5\x41\x54\xcf\xa9\x2c\x17\x3a\x0d\xd3\x9c\x1f\x94" + +# get_exponent(Gi[i], XMR_H, i * 2 + 1) +BP_GI_PRE = b"\x0b\x48\xbe\x50\xe4\x9c\xad\x13\xfb\x3e\x01\x4f\x3f\xa7\xd6\x8b\xac\xa7\xc8\xa9\x10\x83\xdc\x9c\x59\xb3\x79\xaa\xab\x21\x8f\x15\xdf\x01\xa5\xd6\x3b\x3e\x3a\x38\x38\x2a\xfb\xd7\xbc\x68\x5f\x34\x3d\x61\x92\xda\x16\xed\x4b\x45\x1f\x15\xfd\xda\xb1\x70\xe2\x2d\x73\x69\xc8\xd5\xa7\x45\x42\x3d\x26\x06\x23\xa1\xf7\x5f\xae\x1f\xb1\xf8\x1b\x16\x9d\x42\x2a\xcd\x85\x58\xe9\xd5\x74\x25\x48\xbd\x81\xc0\x7d\x2b\xd8\x77\x1e\xb4\xbd\x84\x15\x5d\x38\xd7\x05\x31\xfe\x66\x2b\x78\xf0\xc4\x4a\x9a\xea\xea\x2e\xd2\xd6\xf0\xeb\xe1\x08\x96\xc5\xc2\x2f\x00\x70\xeb\xf0\x55\xdf\xe8\xdc\x1c\xb2\x05\x42\xef\x29\x15\x1a\xa0\x77\x1e\x58\x1e\x68\xfe\x78\x18\xef\x42\x35\xc8\xdf\x1a\x32\xae\xce\xed\xef\xcb\xdf\x6d\x91\xd5\x24\x92\x9b\x84\x02\xa0\x26\xcb\x85\x74\xe0\xe3\xa3\x34\x2c\xe2\x11\xbc\xd9\x67\xbc\x14\xe7\xab\xda\x6c\x17\xc2\xf2\x2a\x38\x1b\x84\xc2\x49\x75\x78\x52\xe9\x9d\x62\xc4\x5f\x16\x0e\x89\x15\xec\x21\xd4\xc8\xa3\x83\x1d\x7c\x2f\x24\x58\x1e\xc9\xd1\x50\x13\xdf\xcc\xb5\xeb\xa6\x9d\xf6\x91\xa0\x80\x02\xb3\x3d\x4f\x2f\xb0\x6c\xa9\xf2\x9c\xfb\xc7\x0d\xb0\x23\xa4\x8e\x45\x35\xf5\x83\x8f\x5e\xa2\x7f\x70\x98\x0d\x11\xec\xd9\x35\xb4\x78\x25\x8e\x2a\x4f\x10\x06\xb3\x2d\xa6\x38\x72\x92\x25\x9e\x69\xac\x0a\x82\x9e\xf3\x47\x69\x98\x96\x72\x8c\x0c\xc0\xca\xdc\x74\x6d\xae\x46\xfb\x31\x86\x4a\x59\xa5\xb9\xa1\x54\x9c\x77\xe4\xcf\x8a\xb8\xb2\x55\xa3\xa0\xae\xfa\xa4\xca\xd1\x25\xd2\x19\x94\x9c\x0a\xef\xf0\xc3\x56\x0a\xb1\x58\xed\x67\x17\x48\xa1\x75\x56\x41\x9e\xc9\x42\xe1\x6b\x90\x1d\xbb\x2f\xc6\xdf\x96\x60\x32\x4f\xcb\xcd\x6e\x40\xf2\x35\xd7\x5b\x76\x4f\xaf\xf6\x1c\x19\x05\x22\x2b\xaf\x87\xd5\x1d\x45\xf3\x55\x81\x38\xc8\x7c\xe5\x4c\x46\x4c\xc6\x40\xb9\x55\xe7\xfa\x33\x10\xf8\x3b\x13\xdd\x7b\x24\x73\x19\xe1\x3c\xe6\x19\x95\xbc\x77\x1e\xe1\xed\xe7\x36\x35\x99\xf0\x8f\xc5\xcf\xda\x89\x0e\xa8\x03\xe0\xec\xa7\x0a\x97\x70\x7e\x90\x56\x29\xa5\xe0\x6d\x18\x6a\x96\x4f\x32\x2f\xff\xba\xa7\xed\x2e\x78\x1d\x4d\x3f\xed\xe0\x74\x61\xf4\x4b\x2d\x98\xdb\xcc\x0c\xaa\x20\x55\x14\x6e\x13\xf5\x0e\xcf\x75\x49\x1d\xad\xd3\x6a\xd2\xba\xac\x56\xbc\x08\x56\x2e\xc6\x6c\xe1\x10\xb5\x44\x83\x1d\xbd\x34\xc6\xc2\x52\x95\x81\x51\xc4\x9a\x73\x4c\x6e\x62\x5e\x42\x60\x8c\x00\x5e\x79\x7e\xdb\x6d\x0a\x89\x34\xb3\x24\xa0\xe4\xd3\x1c\xba\x01\x57\x83\x50\x1e\xcd\xfa\x7a\x8e\xba\xe3\xa6\xbf\xd3\x2e\x6d\x1a\x36\x14\xb1\x11\x83\xc8\x09\x80\xd4\x54\x6c\xc3\xee\x5d\xb4\x7b\xfe\x97\x05\xaa\x95\xe2\xda\x29\xf2\x28\x23\x03\x53\x91\x7e\x5d\x2b\x19\x32\xfe\x48\x2f\xbc\xfe\xd7\x13\x4d\x55\x6d\x0c\x27\xf6\xcc\x6b\xf3\x01\x5c\x06\x61\x16\x25\x73\x9d\x88\x9c\x57\x89\xfa\x75\xb3\xc8\x39\x69\xcb\x88\xb1\xdf\x01\xc0\xac\xa4\x70\xf6\x65\xeb\x71\x82\xe0\x72\xbc\xa8\x9b\xc6\x69\xff\xe5\xb0\x29\x6f\xe2\x13\x43\xa8\xc3\x27\xc8\xa8\x41\x75\x02\x85\x5a\x25\xcc\xb7\x5b\x2f\x8e\xea\xc5\xd1\xdb\x25\x04\x4b\x0a\xea\xd2\xcf\x77\x02\x1e\xd9\x4f\x79\xf3\x00\x1e\x7b\x8e\x9d\xb7\x31\x1d\xb2\x8c\x45\xc9\x0d\x80\xa1\xe3\xd5\xb2\x7b\x43\xf8\xe3\x80\x21\x4d\x6a\x2c\x40\x46\xc8\xd4\x0f\x52\x4d\x47\x83\x53\x20\x4d\x01\xa1\x7c\x4f\xb7\xb1\x8c\x2f\x48\x27\x01\x50\xdb\x67\xd4\xb0\xb9\xce\x87\x86\xe0\x3c\x95\x50\xc5\x47\xfb\x18\x02\x9e\xf1\x6e\x56\x29\xe9\xa1\xc6\x68\xe1\xaa\x79\xc7\x88\x73\x55\xf5\xf5\x1b\x0c\xbb\x1f\x08\x35\xe0\x4e\x7a\xcc\x53\xac\x55\xa3\x57\x41\x97\xb5\x4c\x5a\xaa\xad\x47\xbe\x24\xdb\xbc\x11\xc1\xbd\x3e\xeb\x62\x46\x54\x2d\x2f\x5a\xe5\xf4\x39\x8d\xd4\xa7\x60\x17\x03\xcb\xbf\xd5\x9b\xad\xdd\x3a\x7c\xe6\xe3\x75\xe7\xd9\x00\x50\xe2\x71\xb1\x3f\x13\x2d\xf8\x5e\x1c\x12\xbe\x54\xfe\x66\xde\x81\xf6\x8a\x1c\x8f\x69\x6f\x3e\x77\x3c\x7e\xef\x57\xac\x13\x89\xbd\x02\x80\xd5\x58\xea\x78\x62\xf0\x1b\x64\x1e\xc6\xda\x0e\xfe\xfb\xee\xd0\x50\x9c\x53\x8a\x8c\x36\x16\x68\x1d\x76\x1a\xe5\xc6\xf9\xd2\xaa\xde\xd7\x18\x90\xda\x24\x96\x15\x60\x43\x08\x21\x82\xec\x85\x9c\x3a\xe4\x86\x93\xf9\x13\x43\xd0\xa5\xf0\xec\xbb\x7d\xec\x9b\x97\x3b\xf2\x13\x67\x8a\x65\x3b\x0d\x9d\xf5\x10\x65\x2a\x23\xc0\xb8\x06\x53\x67\x92\x4a\x4c\xfc\x78\x60\x36\xc0\x66\xca\xa7\x38\x34\x9c\xf1\xcd\xa7\x0d\xbf\xa8\x5c\xce\xb4\xa0\x9f\x85\x03\x9b\x6f\x77\x27\x4f\xa6\xe2\x79\x35\xbf\x89\xae\x37\x3a\x3b\x5a\xda\x58\x24\xbd\x4b\x2a\xec\x22\x2a\xeb\xd7\xfe\xe7\xa4\x82\xe9\xc1\x33\x58\xea\xb2\x5f\x94\x22\x36\xf3\xf4\xb6\xeb\xaf\xe1\xc3\xee\xee\xf7\x93\x83\x66\x80\x66\x7c\x66\x94\x64\xc3\xd4\xa0\x84\x7d\xf3\x02\x4b\xd5\xdf\x2a\xa4\xaa\x4d\x19\xe5\x51\xed\xe9\x3d\xd0\x75\xf7\x95\x3a\xca\xe5\x3f\x0f\x9e\x8a\x38\x4e\x49\x6c\x52\x50\xb0\x7e\x76\x17\xe8\x9e\x28\xf9\x53\xd0\x96\xec\x29\x87\xeb\xd8\xf3\xe7\x4d\x93\x39\x63\xb8\x27\x73\xd3\x7a\xb1\xb7\xa3\x60\x1d\xc8\x97\x13\x34\x82\x5d\xd1\xd6\x7e\x4c\x48\x29\x72\x92\xa0\x7a\x40\x62\x96\x75\xb3\xe8\x78\x8e\xfc\x68\x73\x85\x30\x04\x81\xae\x69\x74\x06\xd2\x4e\xf8\x8e\xbf\x9c\xa1\x97\x2c\x1d\x52\x84\x78\x85\x8e\xad\x85\x78\x2e\xd4\x10\xeb\xbc\x1f\x3d\xa4\x8b\xa8\x07\x83\x62\x36\xaa\xc0\xa8\xf0\x8a\x50\x29\x11\x5d\x57\xe7\xef\x18\xcb\x27\xcc\xe8\xd2\xc1\x57\xa9\xf4\xf5\x61\x5d\xcc\x34\x8a\xea\xc8\x0d\x0f\x28\xdf\x33\xba\xbe\x39\xf6\xec\xbd\x19\xa4\xa6\xaf\xa8\x53\xaa\x4d\xa0\x3b\x6b\xd7\xa8\x06\x22\x9d\xed\x76\xd2\xc5\xb9\xde\x11\x76\xd5\x19\xa7\x93\x94\x67\x92\xb5\x41\x7e\xaf\x7d\x2d\x51\x26\x97\x7c\x57\x04\xfc\x0f\xcd\x8e\x1b\x2f\x58\x9b\x1d\x41\x8d\x19\xdd\x28\xf7\xe9\x4c\x51\xa1\x78\x2d\x32\x2e\x03\xcb\xa4\x78\x85\x74\x24\x49\x7b\x4a\x37\x3f\xde\x0f\xba\xe4\xcc\xd9\x38\xcb\xbf\xa0\xf4\xad\x23\x97\xee\xd7\xf7\x6d\xc3\xcd\xb6\xb0\x6a\x36\x66\x0c\x07\x75\xd3\x91\xca\x47\x21\x33\x41\xf6\x59\xe9\x01\x4f\x70\x28\x4e\xfa\xa5\xfa\xab\xa4\xbb\x83\x79\xce\x02\x04\xf5\xae\xdc\x28\x26\x8d\x82\x43\x8b\x5b\x88\x1f\xdf\x2d\xee\x4a\xd7\xd4\x0e\xd1\x3d\xad\x57\xca\x92\x96\x14\xa6\x3a\x00\xfe\x3a\x78\xf3\x3b\x30\xb6\xfd\x5f\x39\xe4\x43\x70\x36\xdc\xed\x8d\x87\xaf\x43\x28\x2f\x43\xfa\x14\xab\xaf\x6c\x84\x15\xfc\x05\xee\x1a\xd1\x71\xd8\x1f\xaa\x46\x7d\xdf\xe5\xe0\x2e\xb6\x89\x5e\x56\x88\xde\xc0\x48\xf6\x66\x0e\x3a\x2f\xd8\xbd\xec\x60\x2a\xf5\x95\x90\xec\x4c\x6e\xab\x83\x4c\xc0\xde\xc8\x62\x1e\xb5\x10\xfb\xa6\xf7\xad\xf4\x76\x93\xc2\xfd\x57\x4d\x82\x20\xa2\xe7\x0e\x73\xad\x68\xe4\xc3\x32\x48\x8e\xb8\xe7\x31\xfe\x60\x0d\x1e\x9f\x6b\x8f\x5c\xbf\x69\x9c\x18\xd0\x6b\xcd\x73\xb7\xcf\xce\xf4\x2e\x68\xaf\x7a\xe6\x7f\xea\x46\xe9\x46\xde\x6a\x61\xfa\xa4\x2c\x53\x5c\xfc\xae\xaa\xd5\x33\x4f\xc1\xa9\xba\xd4\xa5\x3e\x57\xd1\x1c\x6a\xcc\xfc\xef\xd2\xe8\xab\x44\xcb\x12\xfb\x2e\x66\x4f\xcb\xdf\x5c\x82\xb2\x12\x89\x62\x6a\xc2\xa1\x40\x2b\xde\x7a\x86\x9e\xb9\xed\x78\x07\x33\x8d\xd3\xb2\xba\x82\x37\x84\x5d\xb9\x67\x71\xcc\x98\x80\x08\x1a\xcf\x05\x3d\x9b\xd5\x1c\x01\x01\x94\x1c\x4c\x26\xf6\x6a\xa5\xdb\xad\x3f\x53\x54\x60\x85\x77\xf9\xe5\x1a\xfe\x74\x3a\xdd\x50\xf1\xb5\x90\x1b\xea\x7b\xeb\x5a\xe7\x80\xb6\xec\xe9\x77\xf6\x5b\x9c\x62\x8e\x1d\xce\x0a\xd1\xe0\x78\xc7\x46\xc2\xf3\x8d\x0e\x7f\x06\xb0\x88\x70\x8a\xe9\xac\x11\x17\xe3\xa3\x79\x99\xc1\xd7\x5a\x62\xe9\xc9\xe0\x17\x01\x8e\x08\x8a\xeb\xfb\x37\x8d\xe2\x9c\x78\x93\xac\xf1\x09\x42\x58\x4b\xf5\x58\xa2\xd0\x2d\x75\x1e\x34\xf3\xf4\x84\xb0\x01\xe3\x19\x24\xcc\x21\x84\x8b\xf0\xdd\xaf\x1f\x3d\x8a\x31\x00\x49\x73\x6f\xf7\xf0\x49\x29\x4d\x8a\x59\x5f\x2c\xa7\x26\x3a\x36\x13\x84\x0c\x14\xb3\x3e\xf4\x83\xcd\xca\x5b\xbb\x8a\x4c\x70\x04\xcc\xb8\xf6\x71\x56\x26\x7e\xe3\x5f\x28\x0d\xb1\x26\x45\xde\x8e\x55\x2a\x93\x12\xdf\x57\x69\xa0\x30\xa6\xb4\x6d\x80\xdb\x2e\x6c\x06\xb3\xc7\x6c\x1a\xda\x42\x37\x3b\x29\xa0\x59\x1f\x39\x85\x67\x49\xdf\xdf\xb2\x66\x81\x16\x6a\x28\x6f\xb4\xf2\x09\x7a\x3b\x6f\x8f\xeb\xdb\xe4\x41\x3b\x67\xb5\x58\x68\x9c\x2e\x7c\x1d\x6d\x64\x08\xf4\x6a\x60\x94\xc7\x4b\x22\x81\xe7\x96\xe1\xd9\x00\xcc\x83\x53\x37\xa3\x1b\x53\x50\xca\xa9\xc4\x44\xc6\x70\xf7\x8f\x86\x6e\x03\xef\x6e\xc2\xcb\xcb\xc1\x79\x97\x41\x45\xb2\x39\xb9\x09\x12\xbb\xee\xf8\xf5\x76\x96\x1b\x5e\xfc\x69\x64\x1f\x7a\x71\x51\x70\x87\x75\xb6\x7c\x9e\x65\xed\x9b\xb9\xf5\xa8\x7b\xb7\x90\xda\x20\x35\x57\xbe\xd2\x67\x40\x55\xe8\xa6\xab\x36\x46\xc4\xe1\xa8\x45\xea\x53\xd8\x61\x4a\xe4\x90\x06\x5d\xef\x75\x76\x15\xa2\x65\xf2\xab\x98\x38\x80\x29\xae\xc3\xaf\xb5\xcc\xa3\xa6\x66\xab\x29\xb6\xd2\xc0\x02\x97\x9c\x63\x6a\x3b\x41\xb8\x83\x7a\x43\x2a\x81\xd6\xdb\x55\xcf\x40\x6b\x1f\x58\x42\xb0\xa8\x87\xfe\x6b\x2b\xd8\x8e\x46\x29\x8e\xd3\xec\xc3\x87\x4c\x98\x37\x73\x46\x33\x1f\xde\x7a\x2f\xf7\xf1\x04\x26\x5b\xbd\x2d\x02\x74\xc0\x33\xc7\x58\x38\x51\x00\x1d\xcd\xb3\xde\xd9\x0a\x9c\x09\x77\xc1\xf8\x6d" + +# get_exponent(Hi[i], XMR_H, i * 2) +BP_HI_PRE = b"\x42\xba\x66\x8a\x00\x7d\x0f\xcd\x6f\xea\x40\x09\xde\x8a\x64\x37\x24\x8f\x2d\x44\x52\x30\xaf\x00\x4a\x89\xfd\x04\x27\x9b\xc2\x97\xe5\x22\x4e\xf8\x71\xee\xb8\x72\x11\x51\x1d\x2a\x5c\xb8\x1e\xea\xa1\x60\xa8\xa5\x40\x8e\xab\x5d\xea\xeb\x9d\x45\x58\x78\x09\x47\x8f\xc5\x47\xc0\xc5\x2e\x90\xe0\x1e\xcd\x2c\xe4\x1b\xfc\x62\x40\x86\xf0\xec\xdc\x26\x0c\xf3\x0e\x1b\x9c\xae\x3b\x18\xed\x6b\x2c\x9f\x11\x04\x41\x45\xda\x98\xe3\x11\x1b\x40\xa1\x07\x8e\xa9\x04\x57\xb2\x8b\x01\x46\x2c\x90\xe3\xd8\x47\x94\x9e\xd8\xc1\xd3\x1d\x17\x96\x37\xec\x75\x65\xf7\x6f\xa2\x0a\xcc\x47\x1b\x16\x94\xb7\x95\xca\x44\x61\x8e\x4c\xc6\x8e\x0a\x46\xb2\x0f\x91\xe8\x67\x77\x25\x1d\xad\x91\xf0\xd5\xd4\x51\xd7\xe9\x4b\xfc\xd4\x13\x93\x4c\x1d\xa1\x73\xa9\x2d\xdc\x0d\x5e\x0e\x4c\x2c\xfb\xe5\x92\x5b\x0b\x88\x9c\x80\x22\xf3\xa7\xe4\x2f\xcf\xd4\xea\xcd\x06\x31\x63\x15\xc8\xc0\x6c\xb6\x67\x17\x6e\x8f\xd6\x75\xe1\x8a\x22\x96\x10\x0a\xd3\x42\x06\xfc\xf4\x44\x35\x7b\xe1\xe9\x87\x2f\x59\xd7\x1c\x4e\x66\xaf\xdf\x7c\x19\x6b\x6a\x59\x6b\xe2\x89\x0c\x0a\xea\x92\x8a\x9c\x69\xd2\xc4\xdf\x3b\x9c\x52\x8b\xce\x2c\x0c\x30\x6b\x62\x91\xde\xa2\x8d\xe1\xc0\x23\x32\x87\x19\xe9\xa1\xba\x1d\x84\x9c\x1b\xb4\x46\xbc\x0b\x0d\x37\x76\x25\x0d\xd6\x6d\x97\x27\xc2\x5d\x0e\xfe\xb0\xf9\x31\xfc\x53\x7a\xb2\xbd\x9f\x89\x78\x21\x6f\x6e\xb6\xe4\x23\xfa\xe0\xd3\x74\xd3\x4a\x20\x69\x4e\x39\x7a\x70\xb8\x4b\x75\xe3\xbe\x14\xb2\xcf\x53\x01\xc7\xcb\xc6\x62\x50\x96\x71\xa5\xe5\x93\x73\x6f\x61\x13\xc3\xf2\x88\xec\x00\xa1\xcc\x2f\xc7\x15\x6f\x4f\xff\xa1\x74\x8e\x9b\x2c\x2d\xdf\x2f\x43\x03\xbb\xfe\x7f\xfc\xee\x5e\x57\xb3\xb8\x42\x06\xa9\x1b\xcf\x32\xf7\x12\xc7\x5e\x5f\xa5\x10\x87\x85\xb8\xcc\x24\x47\x99\x83\x12\xca\x31\xab\x85\x00\xc8\x2c\x62\x68\x45\x39\xa2\x70\x01\xfb\x17\xf2\xa5\x64\x9d\xb2\xe2\xd6\x4b\x6b\x88\xf0\xd6\x81\x00\x9a\xe7\x8e\xae\xce\x9c\x73\x57\x80\x2c\x6c\x1c\xd8\x1e\xf6\x24\x86\x89\x85\x40\x89\xaa\xd6\x94\x47\x33\x91\xba\xd6\x18\xef\x01\xdf\xd6\x80\x98\x1a\x78\x97\x18\xe9\xd7\xca\xef\x06\x3d\xeb\x2d\x67\x5f\xe8\x43\xea\x63\x4d\xcf\x96\x77\xc1\xd3\xee\x92\x51\x39\x71\xb7\x24\xc7\x88\xe4\x10\x7a\x42\x40\xfe\x26\xe5\xfb\x36\xcc\x00\x7e\x76\x58\x96\x48\x82\xf7\x69\xf1\x8c\x78\x6a\xb1\x52\xf2\x5c\x5d\x2a\xe4\x72\xf7\x1e\x40\x13\xc4\xb0\xc5\x78\x7d\xc1\xd7\x8b\xdc\x8d\x52\x33\x10\x39\xaf\x41\x24\x11\x2e\xe9\x34\x6f\x11\x0a\x4e\x81\x18\xe8\x64\x11\x5d\x49\xb0\x82\xc8\x38\x51\xd4\xd5\xe1\x10\xa4\xab\xda\xdd\xbd\xa9\xb0\x22\x7f\x5b\x26\xbf\x52\xd5\xa2\x25\x25\x23\x59\x72\x84\x3d\xe9\x1d\x99\xd0\x09\x1f\x17\xf4\x78\x2d\x4f\xeb\x2b\x76\x0c\xd5\x8b\x6f\x24\x76\xe8\xb0\x2d\x90\x8a\x15\x15\x07\x8a\xa8\x08\xaa\x3a\x56\x5e\xfc\xb7\x16\x9f\xe0\xcb\xf7\x2c\x12\xce\x17\x50\xf2\x86\x1f\xb6\xc6\x85\x16\x13\xcb\xe9\x74\xef\xc1\x68\x4a\xeb\xbe\x8b\x8a\x52\x2a\xbb\xe7\x82\x77\xd0\xda\xa7\x89\x2d\x9d\xa8\x7c\x27\xbe\xcd\x3e\xc0\x38\x95\x23\x3a\xd4\x66\x31\x8c\x44\x3c\x4d\x6d\x5c\xf1\x2e\xba\x7d\xbd\x3e\x84\x32\x9d\xf6\x1a\xfc\x9b\x7e\x08\xfc\x13\x32\xa6\x82\x34\x42\x73\x39\x6e\xc7\xdc\xdc\xbe\xae\x48\xff\x70\xa1\x9a\x31\xd6\x62\x44\x3c\xce\x57\xf7\x7a\xfe\x05\x0b\x81\x22\x48\x60\x25\x5b\xcb\xc8\xf4\x80\xc4\x3c\xfd\xeb\xb1\xb2\xa6\x89\x72\xb7\xd3\x32\x3b\x03\x61\xf3\xa1\x14\x2f\x8b\x45\x2e\x92\x98\x77\x3d\xef\x56\x35\xc2\xe2\xef\xa3\x70\x0e\x4c\xc9\xe5\xd8\xde\x78\x96\x7e\x57\x35\x82\xcf\x7c\x74\x97\x7c\x30\xb5\x46\x9b\x2c\x0b\xac\xe8\xec\x25\x9f\x71\xba\x25\xc8\xdd\x1c\x51\xe5\xb0\x24\x1c\xca\x7c\x86\xf7\x18\xb7\xd2\xc3\xd4\x57\xa6\xe5\xe0\xb3\x9f\x1f\x39\xeb\xaf\xbb\x08\x83\xd4\x27\xd9\x36\x47\x60\x15\xad\x88\xb7\x92\xa0\x31\xe4\xdd\x98\x37\x57\xc9\x9a\xea\x39\x12\xe8\xf8\xc2\xf6\x59\xde\x4b\xc1\xa2\x20\x4c\xea\x13\x2e\x4f\x9e\xf7\x17\x77\x11\x91\x53\x63\x9a\x71\xff\x24\x17\xf5\x22\xfe\x41\xb8\x7e\x9c\x1c\xb7\x66\x9f\x40\xf9\xd6\x85\x88\x7d\xff\x81\x92\x7a\xa4\x2e\xda\x7f\x2a\x69\x67\x89\x09\x10\x33\xcf\x5b\xe2\xfc\x1f\x5f\x3a\x2d\xe2\x27\x15\xeb\x33\xd6\x28\x28\x92\x2d\xac\x86\x2e\xfc\x7f\xc6\xd5\x4c\x99\xe6\xec\x6e\x58\xc0\xb6\x4d\xa9\x57\xe7\x36\xd3\x00\x93\xc8\x67\xa1\x20\xd5\xdb\xfc\x55\x03\xca\x27\x64\x05\xdf\x4b\x2d\xbe\x6c\xfe\x7c\x2c\x56\xbc\xd2\x66\x9f\x1b\x7d\x82\xc9\xf9\x29\x91\xbf\x41\x02\xaf\x61\x10\xbf\x1b\xf5\xbd\xae\x89\x7f\x9a\x06\x42\x09\xcf\x31\x29\x96\x53\x13\x7e\x86\x5f\x90\x5c\x89\x29\x44\x91\x39\x54\x5a\xc8\x25\x3c\x32\xbe\x19\xcc\x8b\xd8\x54\xca\x7c\xdb\x07\xc2\xae\xba\x12\xa1\x4c\xcf\xa3\x08\x5f\x9f\xfd\x9f\x75\x39\x80\xc9\xd4\x5b\x7b\x4e\x0f\x5b\xe4\x6d\xf3\xae\x5c\x10\xc1\x89\xf1\xdc\x9e\xd2\x59\x2e\x24\x6b\xd2\x44\x9a\xa0\xda\xae\x45\x8a\xe8\xbf\xbd\x52\xf9\x83\xc3\xde\x44\x12\x37\x26\x71\x9c\x08\xd4\xc3\x7c\x8c\x9b\x0b\xe1\x7b\x6b\x49\x82\x61\x36\xaa\x7b\x90\x85\x31\xbc\x91\x73\x2b\x08\x7a\x41\x36\x03\x0b\xad\x7b\x5b\x1c\xfa\x7d\x9c\x98\xa9\xdc\x34\x7a\x92\x65\x1f\x29\xc2\xe1\x10\xaf\xf8\x89\x7f\x26\x7c\x04\x22\x10\xa6\xb7\x0a\x31\x3c\xc0\x6a\xfa\x2b\xd9\xc2\x91\x15\x37\xd6\x09\xd6\x8b\xec\x94\x32\xe8\x4b\x96\x79\x52\x7d\x6a\xbb\x58\x8b\xa7\x2b\xb2\x14\x98\x70\x69\xd8\x0b\x0a\xbc\x2b\xbd\x68\xeb\xa0\x33\x1e\x3a\xe5\xf4\x10\x6f\x7f\xc1\xe2\xe7\xb8\xd6\xe5\x37\x0e\x32\x01\xcc\xe2\xa0\x36\xb6\x8e\xd3\x54\x31\x63\x39\xf0\x92\xde\xc7\x66\x2b\xce\xbd\xd2\x06\x61\x11\xd1\x6c\xe5\x5a\x93\x7e\x2c\x61\x90\x7b\xc3\x66\xc8\x85\xda\xa3\x74\x95\xbe\x67\x1e\xf6\xc2\xf2\xe5\x54\xed\xe3\xb5\x3c\xe2\x80\xcb\xe8\x8a\x48\xb9\xd9\x74\x0e\x98\x0c\xea\xf8\x04\xed\xcd\x8c\x96\x85\x81\x93\xe6\xd5\x17\x8b\xf6\x04\xcc\x73\xbd\x8f\xaa\xd5\x0d\x53\x15\x49\x99\x31\x97\xcc\x27\x28\x27\x21\x6d\x1a\xf9\xdc\xc6\xe9\x86\x2a\x6e\x53\xa0\xa2\xc7\x32\x98\xe1\xfa\xdc\x0f\x91\x48\xcb\xc8\x5e\xc0\x56\x7c\x38\x76\x9c\x27\x65\xd6\x54\xc4\x26\x9f\x6e\xf1\x39\x47\xf1\x3c\x23\x9c\xbb\x08\xb7\xcf\x67\xa2\x5b\xac\x03\x0a\xd1\xb8\x92\xc4\x34\x79\x24\x64\x49\xf5\x32\x8d\xac\x31\x41\xd3\xd7\xc8\xa9\xa2\x54\x0d\xca\xc2\xcb\xc9\x8e\x27\x84\x31\x43\xe7\xd4\xb9\x6d\xde\x75\x21\xfc\x70\xb3\x28\x0a\x2a\x4c\x5f\x39\x28\x7f\x5d\x24\xd7\xa7\x59\xea\x03\x7b\x11\x44\x87\x39\xee\x2a\x28\xfc\x4b\x16\x0e\xac\x40\x61\x08\xae\xe6\xb5\x80\x62\x13\x11\xfe\x03\x0b\xf0\x8b\x4f\x6e\xed\x3d\x7d\x3d\x86\x93\xd3\xac\x52\x4d\xa2\xb4\xeb\xf1\x9e\x25\x59\xdc\x50\xff\x35\xe6\x2d\xa6\x20\xdc\x0a\x02\xed\xcb\xe4\xf3\x98\xb1\xbd\x86\xea\x15\x4b\x6a\x94\x00\x57\x9e\x3f\x1c\xd5\x7f\xdc\x2f\x10\xbd\x8c\xdb\x16\x7c\x0b\x28\x3f\x90\x07\xe6\x20\xd9\xca\x28\x06\x7f\xe2\xb0\x15\xed\x65\x7c\x91\x53\xb8\x44\x3d\x77\xe8\xe2\x5f\xf3\x48\xf4\xdf\x78\xbb\xc1\xce\x20\xa7\xba\xbd\xe4\x0e\xd2\xbd\xbe\xaf\x2b\x5c\xd9\x8e\x52\x02\xba\xf7\xe3\xdc\xf1\x8b\xa1\x15\x62\x0c\x51\xae\x8b\x58\xb4\x92\x3b\x9a\x86\x94\xc9\x3d\xf6\x4b\x17\x8c\x4c\xd2\xf9\xf6\xef\xc5\x1f\x45\x8b\x0c\x5e\xe8\x60\xa4\x0a\xc8\xce\xc3\x50\x6e\xc8\x5b\x99\xdc\x71\x6b\x95\xcb\xb3\x42\xdb\x91\xad\xe4\xb6\x1e\x17\x7f\x60\xf9\xfa\xbb\xff\x2c\x9b\xad\xee\x04\xcf\xd7\x41\xd6\x6d\x2f\x26\x32\x1e\x2c\xf5\x0a\x3c\xd0\x21\xf6\x28\x88\x63\xde\x2d\xad\xf8\xd5\x2d\x1f\x8b\x9f\x51\x42\x43\x05\xa3\xd4\x07\x96\x29\x63\xc1\xd0\xbe\xeb\x81\x13\xf8\x03\x07\xec\xc2\x19\x23\x94\x7f\xe8\xcb\xaf\x5c\x2c\x05\xae\x63\x69\x85\x21\x99\xc5\x2a\x17\x97\xb9\xaf\xf2\xa9\x24\x5d\x7a\x8b\x91\x72\xd5\x72\xb4\x43\x2f\x63\x44\x1f\xf5\x1c\x4a\x4e\x27\x0e\x3b\x61\xea\xe6\xe1\x3e\xef\xe3\x5e\x85\x42\x7b\xc7\x58\xef\x4a\xf4\xc0\x0f\x9c\x77\x52\x1c\x03\x61\xd2\x99\x43\x1f\x9d\x8e\x29\x8c\x13\x41\x4c\x46\x17\x0a\x1d\x82\xa1\x38\x0f\xba\xfe\x53\x1c\xa7\x01\x84\xab\x89\x65\xc4\xc8\x07\x06\x0e\x80\x39\xfe\xc4\x61\x5e\x59\x09\xd2\x7a\xc5\xca\x80\x41\xe3\xf9\x5b\x27\xf1\xc3\xd4\xd4\x06\xa2\x04\x8b\x1e\x6c\xe1\xe6\x37\xcb\x87\xc0\xf9\x7d\x36\x17\xd4\x6a\xef\xfd\xd1\xe8\x13\xc2\x55\xfb\x8b\x3e\xf9\x39\xa2\xc5\xfa\xd4\xd1\x09\x73\xc0\x8c\x05\x5f\x79\x13\xc5\x16\x64\x58\x9d\xa5\x14\x5a\x9c\x59\x72\xf4\xb2\x12\xeb\xf5\x11\x71\xd9\x23\x43\x83\x3a\x08\x95\x3c\xd8\x0c\xd0\xd9\x08\x90\x4c\x56\x3e\xdc\x34\x29\x42\x21\x86\x56\x33\xd8\xcf\x6f\xf5\x04\x44\xb9\xd2\x9b\xeb\x05\xa4\x7b\x8b\xb1\x21\xcb\x11\x8d\x6c\xb1\x6b\x24\xc4\x45\x09\x8a\xa9\x0e\x6d\x5a\x10\xea\xe0\xa0\xf3\x97\x7a\x28\x08\xf7\x9c\xaf\xe8\xf8\x70\x52\x97\xbd\x91\xeb\xbf\x27\x92\xa1\x89\x2c\xb0\x09\xdb\x0b\x7a\xc3\x51\xd0\x35\x3f\x43\xfe\x3a\xa9\x71\x92\xe8\xb9\xd7\xfe\xf5\xba\xec\x41\x5b\x0c\xa4\x8c\x92\x0e\x7c\xdd\x78\xf9\x24\x6a\xd2\x54\xe8\x7e\xe1\xb0\x65\x84\xb8\x60\xb0\xb8\x80\x0a\xae\xe1\x78\x96\xf0\x29\x0c\xb7\x89\xb0\xd7" + +# oneN = vector_powers(rct::identity(), BP_N); +BP_ONE_N = b"\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + +# twoN = vector_powers(TWO, BP_N); +BP_TWO_N = b"\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x08\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x20\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x40\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x08\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x20\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x40\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x08\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x20\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x40\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x08\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x20\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x40\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x08\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x20\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x40\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x08\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x20\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x40\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x08\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x20\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x40\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x08\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x20\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x40\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + +# ip12 = inner_product(oneN, twoN); +BP_IP12 = b"\xff\xff\xff\xff\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + + +# +# Rct keys operation +# + +tmp_bf_1 = bytearray(32) +tmp_bf_2 = bytearray(32) + +tmp_pt_1 = crypto.new_point() +tmp_pt_2 = crypto.new_point() +tmp_pt_3 = crypto.new_point() +tmp_pt_4 = crypto.new_point() + +tmp_sc_1 = crypto.new_scalar() +tmp_sc_2 = crypto.new_scalar() +tmp_sc_3 = crypto.new_scalar() +tmp_sc_4 = crypto.new_scalar() + + +def memcpy(dst, dst_off, src, src_off, len): + for i in range(len): + dst[dst_off + i] = src[src_off + i] + return dst + + +def _ensure_dst_key(dst=None): + if dst is None: + dst = bytearray(32) + return dst + + +def copy_key(dst, src): + for i in range(32): + dst[i] = src[i] + + +def copy_vector(dst, src): + for i in range(len(src)): + copy_key(dst[i], src[i]) + + +def invert(dst, x): + """ + Modular inversion mod curve order. + + Naive approach using large arithmetics in Python. + Should be moved to the crypto provider later. + :param x: 32byte contracted + :param dst: + :return: + """ + dst = _ensure_dst_key(dst) + crypto.decodeint_into_noreduce(tmp_sc_1, x) + crypto.sc_inv_into(tmp_sc_2, tmp_sc_1) + crypto.encodeint_into(tmp_sc_2, dst) + return dst + + +def scalarmult_key(dst, P, s): + dst = _ensure_dst_key(dst) + crypto.decodepoint_into(tmp_pt_1, P) + crypto.decodeint_into_noreduce(tmp_sc_1, s) + crypto.scalarmult_into(tmp_pt_2, tmp_pt_1, tmp_sc_1) + crypto.encodepoint_into(tmp_pt_2, dst) + return dst + + +def scalarmult_base(dst, x): + dst = _ensure_dst_key(dst) + crypto.decodeint_into_noreduce(tmp_sc_1, x) + crypto.scalarmult_base_into(tmp_pt_1, tmp_sc_1) + crypto.encodepoint_into(tmp_pt_1, dst) + return dst + + +def sc_gen(dst=None): + dst = _ensure_dst_key(dst) + crypto.random_scalar_into(tmp_sc_1) + crypto.encodeint_into(tmp_sc_1, dst) + return dst + + +def full_gen(dst=None): + dst = _ensure_dst_key(dst) + b = crypto.random_bytes(32) + copy_key(dst, b) + return dst + + +def sc_add(dst, a, b): + dst = _ensure_dst_key(dst) + crypto.decodeint_into_noreduce(tmp_sc_1, a) + crypto.decodeint_into_noreduce(tmp_sc_2, b) + crypto.sc_add_into(tmp_sc_3, tmp_sc_1, tmp_sc_2) + crypto.encodeint_into(tmp_sc_3, dst) + return dst + + +def sc_sub(dst, a, b): + dst = _ensure_dst_key(dst) + crypto.decodeint_into_noreduce(tmp_sc_1, a) + crypto.decodeint_into_noreduce(tmp_sc_2, b) + crypto.sc_sub_into(tmp_sc_3, tmp_sc_1, tmp_sc_2) + crypto.encodeint_into(tmp_sc_3, dst) + return dst + + +def sc_mul(dst, a, b): + dst = _ensure_dst_key(dst) + crypto.decodeint_into_noreduce(tmp_sc_1, a) + crypto.decodeint_into_noreduce(tmp_sc_2, b) + crypto.sc_mul_into(tmp_sc_3, tmp_sc_1, tmp_sc_2) + crypto.encodeint_into(tmp_sc_3, dst) + return dst + + +def sc_muladd(dst, a, b, c): + dst = _ensure_dst_key(dst) + crypto.decodeint_into_noreduce(tmp_sc_1, a) + crypto.decodeint_into_noreduce(tmp_sc_2, b) + crypto.decodeint_into_noreduce(tmp_sc_3, c) + crypto.sc_muladd_into(tmp_sc_4, tmp_sc_1, tmp_sc_2, tmp_sc_3) + crypto.encodeint_into(tmp_sc_4, dst) + return dst + + +def sc_mulsub(dst, a, b, c): + dst = _ensure_dst_key(dst) + crypto.decodeint_into_noreduce(tmp_sc_1, a) + crypto.decodeint_into_noreduce(tmp_sc_2, b) + crypto.decodeint_into_noreduce(tmp_sc_3, c) + crypto.sc_mulsub_into(tmp_sc_4, tmp_sc_1, tmp_sc_2, tmp_sc_3) + crypto.encodeint_into(tmp_sc_4, dst) + return dst + + +def add_keys(dst, A, B): + dst = _ensure_dst_key(dst) + crypto.decodepoint_into(tmp_pt_1, A) + crypto.decodepoint_into(tmp_pt_2, B) + crypto.point_add_into(tmp_pt_3, tmp_pt_1, tmp_pt_2) + crypto.encodepoint_into(tmp_pt_3, dst) + return dst + + +def add_keys2(dst, a, b, B): + dst = _ensure_dst_key(dst) + crypto.decodeint_into_noreduce(tmp_sc_1, a) + crypto.decodeint_into_noreduce(tmp_sc_2, b) + crypto.decodepoint_into(tmp_pt_1, B) + crypto.add_keys2_into(tmp_pt_2, tmp_sc_1, tmp_sc_2, tmp_pt_1) + crypto.encodepoint_into(tmp_pt_2, dst) + return dst + + +def add_keys3(dst, a, A, b, B): + dst = _ensure_dst_key(dst) + crypto.decodeint_into_noreduce(tmp_sc_1, a) + crypto.decodeint_into_noreduce(tmp_sc_2, b) + crypto.decodepoint_into(tmp_pt_1, A) + crypto.decodepoint_into(tmp_pt_2, B) + crypto.add_keys3_into(tmp_pt_3, tmp_sc_1, tmp_pt_1, tmp_sc_2, tmp_pt_2) + crypto.encodepoint_into(tmp_pt_3, dst) + return dst + + +def hash_to_scalar(dst, data): + dst = _ensure_dst_key(dst) + crypto.hash_to_scalar_into(tmp_sc_1, data) + crypto.encodeint_into(tmp_sc_1, dst) + return dst + + +# +# +# + + +class KeyV(object): + """ + KeyVector abstraction + Constant precomputed buffers = bytes, frozen. Same operation as normal. + """ + + def __init__(self, elems=64, src=None, buffer=None, const=False): + self.current_idx = 0 + self.d = None + self.mv = None + self.size = elems + self.const = const + if src: + self.d = bytearray(src.d) + self.size = src.size + elif buffer: + self.d = buffer # can be immutable (bytes) + self.size = len(buffer) // 32 + else: + self.d = bytearray(32 * elems) + self._set_mv() + + def _set_mv(self): + self.mv = memoryview(self.d) + + def __getitem__(self, item): + """ + Returns corresponding 32 byte array + :param item: + :return: + """ + return self.mv[item * 32 : (item + 1) * 32] + + def __setitem__(self, key, value): + """ + Sets given key to the particular place + :param key: + :param value: + :return: + """ + if self.const: + raise ValueError("Constant KeyV") + ck = self[key] + for i in range(32): + ck[i] = value[i] + + def __iter__(self): + self.current_idx = 0 + return self + + def __next__(self): + if self.current_idx >= self.size: + raise StopIteration + else: + self.current_idx += 1 + return self[self.current_idx - 1] + + def __len__(self): + return self.size + + def slice(self, res, start, stop): + for i in range(start, stop): + res[i - start] = self[i] + return res + + def slice_r(self, start, stop): + res = KeyV(stop - start) + return self.slice(res, start, stop) + + def copy_from(self, src): + self.size = self.size + self.d = bytearray(self.d) + self._set_mv() + + def copy(self, dst=None): + if dst: + dst.copy_from(self) + else: + dst = KeyV(src=self) + return dst + + def resize(self, nsize, chop=False): + if self.size == nsize: + return self + elif self.size > nsize and not chop: + self.d = self.d[: nsize * 32] + else: + self.d = bytearray(nsize * 32) + self.size = nsize + self._set_mv() + + +class KeyVEval(KeyV): + """ + KeyVector computed / evaluated on demand + """ + + def __init__(self, elems=64, src=None): + self.size = elems + self.fnc = src + self.buff = _ensure_dst_key() + self.mv = memoryview(self.buff) + + def __getitem__(self, item): + self.fnc(item, self.mv) + return self.mv + + def __setitem__(self, key, value): + raise ValueError("Constant vector") + + def slice(self, res, start, stop): + raise ValueError("Not supported") + + def slice_r(self, start, stop): + raise ValueError("Not supported") + + def copy(self, dst=None): + raise ValueError("Not supported") + + def resize(self, nsize, chop=False): + raise ValueError("Not supported") + + +def _ensure_dst_keyvect(dst=None, size=None): + if dst is None: + dst = KeyV(elems=size) + if size is not None: + dst.resize(size) + return dst + + +def vector_exponent_custom(A, B, a, b, dst=None): + dst = _ensure_dst_key(dst) + + crypto.identity_into(tmp_pt_1) + crypto.identity_into(tmp_pt_2) + + for i in range(len(a)): + crypto.decodeint_into_noreduce(tmp_sc_1, a[i]) + crypto.decodepoint_into(tmp_pt_3, A[i]) + crypto.decodeint_into_noreduce(tmp_sc_2, b[i]) + crypto.decodepoint_into(tmp_pt_4, B[i]) + crypto.add_keys3_into(tmp_pt_1, tmp_sc_1, tmp_pt_3, tmp_sc_2, tmp_pt_4) + crypto.point_add_into(tmp_pt_2, tmp_pt_2, tmp_pt_1) + crypto.encodepoint_into(tmp_pt_2, dst) + return dst + + +def vector_powers(x, n, dst=None): + dst = _ensure_dst_keyvect(dst, n) + if n == 0: + return dst + dst[0] = ONE + if n == 1: + return dst + dst[1] = x + for i in range(2, n): + sc_mul(dst[i], dst[i - 1], x) + return dst + + +def inner_product(a, b, dst=None): + if len(a) != len(b): + raise ValueError("Incompatible sizes of a and b") + dst = _ensure_dst_key(dst) + crypto.sc_init_into(tmp_sc_1, 0) + + for i in range(len(a)): + crypto.decodeint_into_noreduce(tmp_sc_2, a[i]) + crypto.decodeint_into_noreduce(tmp_sc_3, b[i]) + crypto.sc_muladd_into(tmp_sc_1, tmp_sc_2, tmp_sc_3, tmp_sc_1) + crypto.encodeint_into(tmp_sc_1, dst) + return dst + + +def hadamard(a, b, dst=None): + dst = _ensure_dst_keyvect(dst, len(a)) + for i in range(len(a)): + sc_mul(dst[i], a[i], b[i]) + return dst + + +def hadamard2(a, b, dst=None): + dst = _ensure_dst_keyvect(dst, len(a)) + for i in range(len(a)): + add_keys(dst[i], a[i], b[i]) + return dst + + +def vector_add(a, b, dst=None): + dst = _ensure_dst_keyvect(dst, len(a)) + for i in range(len(a)): + sc_add(dst[i], a[i], b[i]) + return dst + + +def vector_subtract(a, b, dst=None): + dst = _ensure_dst_keyvect(dst, len(a)) + for i in range(len(a)): + sc_sub(dst[i], a[i], b[i]) + return dst + + +def vector_scalar(a, x, dst=None): + dst = _ensure_dst_keyvect(dst, len(a)) + for i in range(len(a)): + sc_mul(dst[i], a[i], x) + return dst + + +def vector_scalar2(a, x, dst=None): + dst = _ensure_dst_keyvect(dst, len(a)) + for i in range(len(a)): + scalarmult_key(dst[i], a[i], x) + return dst + + +def hash_cache_mash(dst, hash_cache, *args): + dst = _ensure_dst_key(dst) + ctx = crypto.get_keccak() + ctx.update(hash_cache) + + for x in args: + if x is None: + break + ctx.update(x) + hsh = ctx.digest() + + crypto.decodeint_into(tmp_sc_1, hsh) + crypto.encodeint_into(tmp_sc_1, tmp_bf_1) + + copy_key(dst, tmp_bf_1) + copy_key(hash_cache, tmp_bf_1) + return dst + + +class BulletProofBuilder(object): + def __init__(self): + self.use_det_masks = True + self.value = None + self.value_enc = None + self.gamma = None + self.gamma_enc = None + self.proof_sec = None + self.Gprec = KeyV(buffer=BP_GI_PRE, const=True) + self.Hprec = KeyV(buffer=BP_HI_PRE, const=True) + self.oneN = KeyV(buffer=BP_ONE_N, const=True) + self.twoN = KeyV(buffer=BP_TWO_N, const=True) + self.ip12 = BP_IP12 + self.v_aL = None + self.v_aR = None + self.v_sL = None + self.v_sR = None + self.tmp_sc_1 = crypto.new_scalar() + self.tmp_det_buff = bytearray(64 + 1 + 1) + self.tmp_h_buff1 = bytearray(32) + self.gc_fnc = gc.collect + self.gc_trace = None + + def gc(self, *args): + if self.gc_trace: + self.gc_trace(*args) + if self.gc_fnc: + self.gc_fnc() + + def set_input(self, value=None, mask=None): + self.value = value + self.value_enc = crypto.encodeint(value) + self.gamma = mask + self.gamma_enc = crypto.encodeint(mask) + self.proof_sec = crypto.random_bytes(64) + + def aL(self, i, dst=None): + dst = _ensure_dst_key(dst) + if self.value_enc[i // 8] & (1 << (i % 8)): + copy_key(dst, ONE) + else: + copy_key(dst, ZERO) + return dst + + def aR(self, i, dst=None): + dst = _ensure_dst_key(dst) + self.aL(i, tmp_bf_1) + sc_sub(dst, tmp_bf_1, ONE) + return dst + + def aL_vct(self): + return KeyVEval(64, lambda x, r: self.aL(x, r)) + + def aR_vct(self): + return KeyVEval(64, lambda x, r: self.aR(x, r)) + + def _det_mask(self, i, is_sL=True, dst=None): + dst = _ensure_dst_key(dst) + self.tmp_det_buff[0] = int(is_sL) + memcpy(self.tmp_det_buff, 1, self.proof_sec, 0, len(self.proof_sec)) + dump_uvarint_b_into(i, self.tmp_det_buff, 65) + crypto.keccak_hash_into(self.tmp_h_buff1, self.tmp_det_buff) + crypto.keccak_hash_into(self.tmp_h_buff1, self.tmp_h_buff1) + crypto.decodeint_into(self.tmp_sc_1, self.tmp_h_buff1) + crypto.encodeint_into(self.tmp_sc_1, dst) + return dst + + def sL(self, i, dst=None): + return self._det_mask(i, True, dst) + + def sR(self, i, dst=None): + return self._det_mask(i, False, dst) + + def sL_vct(self): + return ( + KeyVEval(64, lambda x, r: self.sL(x, r)) + if self.use_det_masks + else self.sX_gen() + ) + + def sR_vct(self): + return ( + KeyVEval(64, lambda x, r: self.sR(x, r)) + if self.use_det_masks + else self.sX_gen() + ) + + def sX_gen(self): + buff = bytearray(64 * 32) + buff_mv = memoryview(buff) + sc = crypto.new_scalar() + for i in range(64): + crypto.random_scalar_into(sc) + crypto.encodeint_into(sc, buff_mv[i * 32 : (i + 1) * 32]) + return KeyV(buffer=buff) + + def vector_exponent(self, a, b, dst=None): + return vector_exponent_custom(self.Gprec, self.Hprec, a, b, dst) + + def prove_s1(self, V, A, S, T1, T2, taux, mu, t, x_ip, y, hash_cache, l, r): + add_keys2(V, self.gamma_enc, self.value_enc, XMR_H) + hash_to_scalar(hash_cache, V) + + # PAPER LINES 38-39 + alpha = sc_gen() + ve = _ensure_dst_key() + self.vector_exponent(self.v_aL, self.v_aR, ve) + add_keys(A, ve, scalarmult_base(tmp_bf_1, alpha)) + + # PAPER LINES 40-42 + rho = sc_gen() + self.vector_exponent(self.v_sL, self.v_sR, ve) + add_keys(S, ve, scalarmult_base(tmp_bf_1, rho)) + + # PAPER LINES 43-45 + z = _ensure_dst_key() + hash_cache_mash(y, hash_cache, A, S) + hash_to_scalar(hash_cache, y) + copy_key(z, hash_cache) + self.gc(1) + + # Polynomial construction before PAPER LINE 46 + t0 = _ensure_dst_key() + t1 = _ensure_dst_key() + t2 = _ensure_dst_key() + + yN = vector_powers(y, BP_N) + self.gc(2) + + ip1y = inner_product(self.oneN, yN) + sc_muladd(t0, z, ip1y, t0) + + zsq = _ensure_dst_key() + sc_mul(zsq, z, z) + sc_muladd(t0, zsq, self.value_enc, t0) + + k = _ensure_dst_key() + copy_key(k, ZERO) + sc_mulsub(k, zsq, ip1y, k) + + zcu = _ensure_dst_key() + sc_mul(zcu, zsq, z) + sc_mulsub(k, zcu, self.ip12, k) + sc_add(t0, t0, k) + self.gc(3) + + # step 2, tmp_vct = vpIz + tmp_vct = _ensure_dst_keyvect(None, BP_N) + vector_scalar(self.oneN, z, tmp_vct) + aL_vpIz = vector_subtract(self.v_aL, tmp_vct) + aR_vpIz = vector_add(self.v_aR, tmp_vct) + self.v_aL = None + self.v_aR = None + self.gc(4) + + # tmp_vct = HyNsR + hadamard(yN, self.v_sR, tmp_vct) + ip1 = inner_product(aL_vpIz, tmp_vct) + ip3 = inner_product(self.v_sL, tmp_vct) + self.gc(5) + + sc_add(t1, t1, ip1) + + vp2zsq = vector_scalar(self.twoN, zsq) + + # Originally: + # ip2 = inner_product(self.v_sL, vector_add(hadamard(yN, aR_vpIz), vp2zsq)) + hadamard(yN, aR_vpIz, tmp_vct) + self.gc(6) + + vector_add(tmp_vct, vp2zsq, tmp_vct) + ip2 = inner_product(self.v_sL, tmp_vct) + + self.gc(6) + sc_add(t1, t1, ip2) + sc_add(t2, t2, ip3) + + # PAPER LINES 47-48 + tau1 = sc_gen() + tau2 = sc_gen() + + add_keys( + T1, scalarmult_key(tmp_bf_1, XMR_H, t1), scalarmult_base(tmp_bf_2, tau1) + ) + add_keys( + T2, scalarmult_key(tmp_bf_1, XMR_H, t2), scalarmult_base(tmp_bf_2, tau2) + ) + + # PAPER LINES 49-51 + x = _ensure_dst_key() + hash_cache_mash(x, hash_cache, z, T1, T2) + + # PAPER LINES 52-53 + copy_key(taux, ZERO) + sc_mul(taux, tau1, x) + xsq = _ensure_dst_key() + sc_mul(xsq, x, x) + sc_muladd(taux, tau2, xsq, taux) + sc_muladd(taux, self.gamma_enc, zsq, taux) + sc_muladd(mu, x, rho, alpha) + self.gc(7) + + # PAPER LINES 54-57 + vector_scalar(self.v_sL, x, tmp_vct) + vector_add(aL_vpIz, tmp_vct, l) + self.v_sL = None + del aL_vpIz + self.gc(8) + + # Originally: + # vector_add(hadamard(yN, vector_add(aR_vpIz, vector_scalar(self.v_sR, x))), vp2zsq, r) + vector_scalar(self.v_sR, x, tmp_vct) + vector_add(aR_vpIz, tmp_vct, tmp_vct) + del aR_vpIz + self.gc(9) + + hadamard(yN, tmp_vct, tmp_vct) + del yN + self.gc(10) + + vector_add(tmp_vct, vp2zsq, r) + self.v_sR = None + del vp2zsq + del tmp_vct + self.gc(11) + + inner_product(l, r, t) + hash_cache_mash(x_ip, hash_cache, x, taux, mu, t) + + def prove_s2(self, x_ip, y, hash_cache, l, r, L, R, aprime0, bprime0): + Gprime = _ensure_dst_keyvect(None, BP_N) + Hprime = _ensure_dst_keyvect(None, BP_N) + + aprime = l + bprime = r + + yinv = invert(None, y) + self.gc(20) + + yinvpow = _ensure_dst_key() + copy_key(yinvpow, ONE) + for i in range(BP_N): + Gprime[i] = self.Gprec[i] + scalarmult_key(Hprime[i], self.Hprec[i], yinvpow) + sc_mul(yinvpow, yinvpow, yinv) + self.gc(21) + + round = 0 + nprime = BP_N + + _tmp_k_1 = _ensure_dst_key() + _tmp_vct_1 = _ensure_dst_keyvect(None, nprime // 2) + _tmp_vct_2 = _ensure_dst_keyvect(None, nprime // 2) + _tmp_vct_3 = _ensure_dst_keyvect(None, nprime // 2) + _tmp_vct_4 = _ensure_dst_keyvect(None, nprime // 2) + + tmp = _ensure_dst_key() + winv = _ensure_dst_key() + w = _ensure_dst_keyvect(None, BP_LOG_N) + cL = _ensure_dst_key() + cR = _ensure_dst_key() + + # PAPER LINE 13 + while nprime > 1: + # PAPER LINE 15 + nprime >>= 1 + _tmp_vct_1.resize(nprime, chop=True) + _tmp_vct_2.resize(nprime, chop=True) + _tmp_vct_3.resize(nprime, chop=True) + _tmp_vct_4.resize(nprime, chop=True) + self.gc(22) + + # PAPER LINES 16-17 + inner_product( + aprime.slice(_tmp_vct_1, 0, nprime), + bprime.slice(_tmp_vct_2, nprime, bprime.size), + cL, + ) + + inner_product( + aprime.slice(_tmp_vct_1, nprime, aprime.size), + bprime.slice(_tmp_vct_2, 0, nprime), + cR, + ) + + self.gc(23) + + # PAPER LINES 18-19 + vector_exponent_custom( + Gprime.slice(_tmp_vct_1, nprime, len(Gprime)), + Hprime.slice(_tmp_vct_2, 0, nprime), + aprime.slice(_tmp_vct_3, 0, nprime), + bprime.slice(_tmp_vct_4, nprime, len(bprime)), + L[round], + ) + + sc_mul(tmp, cL, x_ip) + add_keys(L[round], L[round], scalarmult_key(_tmp_k_1, XMR_H, tmp)) + self.gc(24) + + vector_exponent_custom( + Gprime.slice(_tmp_vct_1, 0, nprime), + Hprime.slice(_tmp_vct_2, nprime, len(Hprime)), + aprime.slice(_tmp_vct_3, nprime, len(aprime)), + bprime.slice(_tmp_vct_4, 0, nprime), + R[round], + ) + + sc_mul(tmp, cR, x_ip) + add_keys(R[round], R[round], scalarmult_key(_tmp_k_1, XMR_H, tmp)) + self.gc(25) + + # PAPER LINES 21-22 + hash_cache_mash(w[round], hash_cache, L[round], R[round]) + + # PAPER LINES 24-25 + invert(winv, w[round]) + self.gc(26) + + vector_scalar2(Gprime.slice(_tmp_vct_1, 0, nprime), winv, _tmp_vct_3) + vector_scalar2( + Gprime.slice(_tmp_vct_2, nprime, len(Gprime)), w[round], _tmp_vct_4 + ) + hadamard2(_tmp_vct_3, _tmp_vct_4, Gprime) + self.gc(27) + + vector_scalar2(Hprime.slice(_tmp_vct_1, 0, nprime), w[round], _tmp_vct_3) + vector_scalar2( + Hprime.slice(_tmp_vct_2, nprime, len(Hprime)), winv, _tmp_vct_4 + ) + hadamard2(_tmp_vct_3, _tmp_vct_4, Hprime) + self.gc(28) + + # PAPER LINES 28-29 + vector_scalar(aprime.slice(_tmp_vct_1, 0, nprime), w[round], _tmp_vct_3) + vector_scalar( + aprime.slice(_tmp_vct_2, nprime, len(aprime)), winv, _tmp_vct_4 + ) + vector_add(_tmp_vct_3, _tmp_vct_4, aprime) + self.gc(29) + + vector_scalar(bprime.slice(_tmp_vct_1, 0, nprime), winv, _tmp_vct_3) + vector_scalar( + bprime.slice(_tmp_vct_2, nprime, len(bprime)), w[round], _tmp_vct_4 + ) + vector_add(_tmp_vct_3, _tmp_vct_4, bprime) + + round += 1 + self.gc(30) + + copy_key(aprime0, aprime[0]) + copy_key(bprime0, bprime[0]) + + def init_vct(self): + self.v_aL = self.aL_vct() + self.v_aR = self.aR_vct() + self.v_sL = self.sL_vct() + self.v_sR = self.sR_vct() + + def prove(self): + # Prover state + V = _ensure_dst_key() + A = _ensure_dst_key() + S = _ensure_dst_key() + T1 = _ensure_dst_key() + T2 = _ensure_dst_key() + taux = _ensure_dst_key() + mu = _ensure_dst_key() + t = _ensure_dst_key() + x_ip = _ensure_dst_key() + y = _ensure_dst_key() + hash_cache = _ensure_dst_key() + aprime0 = _ensure_dst_key() + bprime0 = _ensure_dst_key() + + L = _ensure_dst_keyvect(None, BP_LOG_N) + R = _ensure_dst_keyvect(None, BP_LOG_N) + l = _ensure_dst_keyvect(None, BP_N) + r = _ensure_dst_keyvect(None, BP_N) + + self.init_vct() + self.gc(50) + + self.prove_s1(V, A, S, T1, T2, taux, mu, t, x_ip, y, hash_cache, l, r) + self.gc(51) + + self.prove_s2(x_ip, y, hash_cache, l, r, L, R, aprime0, bprime0) + self.gc(52) + + return Bulletproof( + V=[V], + A=A, + S=S, + T1=T1, + T2=T2, + taux=taux, + mu=mu, + L=L, + R=R, + a=aprime0, + b=bprime0, + t=t, + ) + + def verify(self, proof): + if len(proof.V) != 1: + raise ValueError("len(V) != 1") + if len(proof.L) != len(proof.R): + raise ValueError("|L| != |R|") + if len(proof.L) == 0: + raise ValueError("Empty proof") + if len(proof.L) != 6: + raise ValueError("Proof is not for 64 bits") + + hash_cache = _ensure_dst_key() + hash_to_scalar(hash_cache, proof.V[0]) + + x = _ensure_dst_key() + y = _ensure_dst_key() + z = _ensure_dst_key() + + # Reconstruct the challenges + hash_cache_mash(y, hash_cache, proof.A, proof.S) + hash_to_scalar(hash_cache, y) + copy_key(z, hash_cache) + hash_cache_mash(x, hash_cache, z, proof.T1, proof.T2) + + # Reconstruct the challenges + x_ip = _ensure_dst_key() + hash_cache_mash(x_ip, hash_cache, x, proof.taux, proof.mu, proof.t) + + # PAPER LINE 61 + _tmp_k_1 = _ensure_dst_key() + _tmp_k_2 = _ensure_dst_key() + L61Left = _ensure_dst_key() + add_keys( + L61Left, + scalarmult_base(_tmp_k_1, proof.taux), + scalarmult_key(_tmp_k_2, XMR_H, proof.t), + ) + + k = _ensure_dst_key() + yN = vector_powers(y, BP_N) + ip1y = inner_product(self.oneN, yN) + del yN + + zsq = _ensure_dst_key() + sc_mul(zsq, z, z) + + zcu = _ensure_dst_key() + tmp = _ensure_dst_key() + tmp2 = _ensure_dst_key() + sc_mulsub(k, zsq, ip1y, k) + sc_mul(zcu, zsq, z) + sc_mulsub(k, zcu, self.ip12, k) + sc_muladd(tmp, z, ip1y, k) + + L61Right = _ensure_dst_key() + scalarmult_key(L61Right, XMR_H, tmp) + scalarmult_key(tmp, proof.V[0], zsq) + add_keys(L61Right, L61Right, tmp) + + scalarmult_key(tmp, proof.T1, x) + add_keys(L61Right, L61Right, tmp) + + xsq = _ensure_dst_key() + sc_mul(xsq, x, x) + scalarmult_key(tmp, proof.T2, xsq) + add_keys(L61Right, L61Right, tmp) + self.gc(60) + + if L61Right != L61Left: + raise ValueError("Verification failure 1") + + del k + del ip1y + del zcu + del L61Left + del L61Right + + # PAPER LINE 62 + P = _ensure_dst_key() + add_keys(P, proof.A, scalarmult_key(_tmp_k_1, proof.S, x)) + + # Compute the number of rounds for the inner product + rounds = len(proof.L) + + # PAPER LINES 21-22 + w = _ensure_dst_keyvect(None, rounds) + for i in range(rounds): + hash_cache_mash(w[i], hash_cache, proof.L[i], proof.R[i]) + + # Basically PAPER LINES 24-25 + # Compute the curvepoints from G[i] and H[i] + inner_prod = _ensure_dst_key() + yinvpow = _ensure_dst_key() + ypow = _ensure_dst_key() + yinv = _ensure_dst_key() + + copy_key(inner_prod, ONE) + copy_key(yinvpow, ONE) + copy_key(ypow, ONE) + + invert(yinv, y) + self.gc(61) + + winv = _ensure_dst_keyvect(None, rounds) + for i in range(rounds): + invert(winv[i], w[i]) + self.gc(62) + + g_scalar = _ensure_dst_key() + h_scalar = _ensure_dst_key() + for i in range(BP_N): + copy_key(g_scalar, proof.a) + sc_mul(h_scalar, proof.b, yinvpow) + + for j in range(rounds - 1, -1, -1): + J = len(w) - j - 1 + + if (i & (1 << j)) == 0: + sc_mul(g_scalar, g_scalar, winv[J]) + sc_mul(h_scalar, h_scalar, w[J]) + else: + sc_mul(g_scalar, g_scalar, w[J]) + sc_mul(h_scalar, h_scalar, winv[J]) + + # Adjust the scalars using the exponents from PAPER LINE 62 + sc_add(g_scalar, g_scalar, z) + sc_mul(tmp, zsq, self.twoN[i]) + sc_muladd(tmp, z, ypow, tmp) + sc_mulsub(h_scalar, tmp, yinvpow, h_scalar) + + # Now compute the basepoint's scalar multiplication + # Each of these could be written as a multiexp operation instead + add_keys3(tmp, g_scalar, self.Gprec[i], h_scalar, self.Hprec[i]) + add_keys(inner_prod, inner_prod, tmp) + + if i != BP_N - 1: + sc_mul(yinvpow, yinvpow, yinv) + sc_mul(ypow, ypow, y) + self.gc(62) + + del g_scalar + del h_scalar + self.gc(63) + + # PAPER LINE 26 + pprime = _ensure_dst_key() + sc_sub(tmp, ZERO, proof.mu) + add_keys(pprime, P, scalarmult_base(_tmp_k_1, tmp)) + + for i in range(rounds): + sc_mul(tmp, w[i], w[i]) + sc_mul(tmp2, winv[i], winv[i]) + + add_keys3(tmp, tmp, proof.L[i], tmp2, proof.R[i]) + add_keys(pprime, pprime, tmp) + + sc_mul(tmp, proof.t, x_ip) + add_keys(pprime, pprime, scalarmult_key(_tmp_k_1, XMR_H, tmp)) + + sc_mul(tmp, proof.a, proof.b) + sc_mul(tmp, tmp, x_ip) + scalarmult_key(tmp, XMR_H, tmp) + add_keys(tmp, tmp, inner_prod) + self.gc(64) + + if pprime != tmp: + raise ValueError("Verification failure step 2") + return True diff --git a/src/apps/monero/xmr/crypto.py b/src/apps/monero/xmr/crypto.py index 7fa8908f1..c6f3cf443 100644 --- a/src/apps/monero/xmr/crypto.py +++ b/src/apps/monero/xmr/crypto.py @@ -46,6 +46,14 @@ def keccak_hash(inp): return tcry.xmr_fast_hash(inp) +def keccak_hash_into(r, inp): + """ + Hashesh input in one call + :return: + """ + return tcry.xmr_fast_hash(r, inp) + + def keccak_2hash(inp): """ Keccak double hashing @@ -96,10 +104,22 @@ def pbkdf2(inp, salt, length=32, count=1000, prf=None): # +def new_point(): + return tcry.ge25519_set_neutral() + + +def new_scalar(): + return tcry.init256_modm(0) + + def decodepoint(x): return tcry.ge25519_unpack_vartime(x) +def decodepoint_into(r, x): + return tcry.ge25519_unpack_vartime(r, x) + + def encodepoint(pt): return tcry.ge25519_pack(pt) @@ -112,6 +132,14 @@ def decodeint(x): return tcry.unpack256_modm(x) +def decodeint_into_noreduce(r, x): + return tcry.unpack256_modm_noreduce(r, x) + + +def decodeint_into(r, x): + return tcry.unpack256_modm(r, x) + + def encodeint(x): return tcry.pack256_modm(x) @@ -128,18 +156,34 @@ def scalarmult_base(a): return tcry.ge25519_scalarmult_base(a) +def scalarmult_base_into(r, a): + return tcry.ge25519_scalarmult_base(r, a) + + def scalarmult(P, e): return tcry.ge25519_scalarmult(P, e) +def scalarmult_into(r, P, e): + return tcry.ge25519_scalarmult(r, P, e) + + def point_add(P, Q): return tcry.ge25519_add(P, Q, 0) +def point_add_into(r, P, Q): + return tcry.ge25519_add(r, P, Q, 0) + + def point_sub(P, Q): return tcry.ge25519_add(P, Q, 1) +def point_sub_into(r, P, Q): + return tcry.ge25519_add(r, P, Q, 1) + + def point_eq(P, Q): return tcry.ge25519_eq(P, Q) @@ -148,16 +192,6 @@ 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 # @@ -171,6 +205,14 @@ def sc_0(): return tcry.init256_modm(0) +def sc_0_into(r): + """ + Sets 0 to the scalar value Zmod(m) + :return: + """ + return tcry.init256_modm(r, 0) + + def sc_init(x): """ Sets x to the scalar value Zmod(m) @@ -181,6 +223,16 @@ def sc_init(x): return tcry.init256_modm(x) +def sc_init_into(r, 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(r, x) + + def sc_get64(x): """ Returns 64bit value from the sc @@ -211,28 +263,25 @@ def check_sc(key): raise ValueError("Invalid scalar value") -def sc_reduce32(data): +def sc_add(aa, bb): """ - 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: + Scalar addition + :param aa: + :param bb: :return: """ - return tcry.reduce256_modm(data) + return tcry.add256_modm(aa, bb) -def sc_add(aa, bb): +def sc_add_into(r, aa, bb): """ Scalar addition + :param r: :param aa: :param bb: :return: """ - return tcry.add256_modm(aa, bb) + return tcry.add256_modm(r, aa, bb) def sc_sub(aa, bb): @@ -245,6 +294,38 @@ def sc_sub(aa, bb): return tcry.sub256_modm(aa, bb) +def sc_sub_into(r, aa, bb): + """ + Scalar subtraction + :param r: + :param aa: + :param bb: + :return: + """ + return tcry.sub256_modm(r, aa, bb) + + +def sc_mul(aa, bb): + """ + Scalar multiplication + :param aa: + :param bb: + :return: + """ + return tcry.mul256_modm(aa, bb) + + +def sc_mul_into(r, aa, bb): + """ + Scalar multiplication + :param r: + :param aa: + :param bb: + :return: + """ + return tcry.mul256_modm(r, aa, bb) + + def sc_isnonzero(c): """ Returns true if scalar is non-zero @@ -275,28 +356,62 @@ def sc_mulsub(aa, bb, cc): return tcry.mulsub256_modm(aa, bb, cc) -def random_scalar(): - return tcry.xmr_random_scalar() +def sc_mulsub_into(r, aa, bb, cc): + """ + (cc - aa * bb) % l + :param r: + :param aa: + :param bb: + :param cc: + :return: + """ + return tcry.mulsub256_modm(r, aa, bb, cc) -# -# GE - ed25519 group -# +def sc_muladd(aa, bb, cc): + """ + (cc + aa * bb) % l + :param aa: + :param bb: + :param cc: + :return: + """ + return tcry.muladd256_modm(aa, bb, cc) -def ge_scalarmult(a, A): - check_ed25519point(A) - return scalarmult(A, a) +def sc_muladd_into(r, aa, bb, cc): + """ + (cc + aa * bb) % l + :param r: + :param aa: + :param bb: + :param cc: + :return: + """ + return tcry.muladd256_modm(r, aa, bb, cc) -def ge_mul8(P): - check_ed25519point(P) - return tcry.ge25519_mul8(P) +def sc_inv_into(r, x): + """ + Modular inversion mod curve order L + :param r: + :param x: + :return: + """ + return tcry.inv256_modm(r, x) -def ge_scalarmult_base(a): - a = sc_reduce32(a) - return scalarmult_base(a) +def random_scalar(): + return tcry.xmr_random_scalar() + + +def random_scalar_into(r): + return tcry.xmr_random_scalar(r) + + +# +# GE - ed25519 group +# def ge_double_scalarmult_base_vartime(a, A, b): @@ -313,23 +428,6 @@ def ge_double_scalarmult_base_vartime(a, A, 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 @@ -362,6 +460,14 @@ def identity(byte_enc=False): return idd if not byte_enc else encodepoint(idd) +def identity_into(r): + """ + Identity point + :return: + """ + return tcry.ge25519_set_neutral(r) + + def ge_frombytes_vartime_check(point): """ https://www.imperialviolet.org/2013/12/25/elligator.html @@ -443,6 +549,18 @@ def hash_to_scalar(data, length=None): return tcry.xmr_hash_to_scalar(bytes(dt)) +def hash_to_scalar_into(r, data, length=None): + """ + H_s(P) + :param r: + :param data: + :param length: + :return: + """ + dt = data[:length] if length else data + return tcry.xmr_hash_to_scalar(r, bytes(dt)) + + def hash_to_ec(buf): """ H_p(buf) @@ -456,6 +574,20 @@ def hash_to_ec(buf): return tcry.xmr_hash_to_ec(buf) +def hash_to_ec_into(r, 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 r: + :param buf: + :return: + """ + return tcry.xmr_hash_to_ec(r, buf) + + # # XMR # @@ -485,6 +617,18 @@ def add_keys2(a, b, B): return tcry.xmr_add_keys2_vartime(a, b, B) +def add_keys2_into(r, a, b, B): + """ + aG + bB, G is basepoint + :param r: + :param a: + :param b: + :param B: + :return: + """ + return tcry.xmr_add_keys2_vartime(r, a, b, B) + + def add_keys3(a, A, b, B): """ aA + bB @@ -497,6 +641,19 @@ def add_keys3(a, A, b, B): return tcry.xmr_add_keys3_vartime(a, A, b, B) +def add_keys3_into(r, a, A, b, B): + """ + aA + bB + :param r: + :param a: + :param A: + :param b: + :param B: + :return: + """ + return tcry.xmr_add_keys3_vartime(r, a, A, b, B) + + def gen_c(a, amount): """ Generates Pedersen commitment @@ -566,6 +723,21 @@ def derive_secret_key(derivation, output_index, base): return tcry.xmr_derive_private_key(derivation, output_index, base) +def get_subaddress_secret_key(secret_key, major=0, minor=0): + """ + Builds subaddress secret key from the subaddress index + Hs(SubAddr || a || index_major || index_minor) + + :param secret_key: + :param index: + :param major: + :param minor: + :param little_endian: + :return: + """ + return tcry.xmr_get_subaddress_secret_key(major, minor, secret_key) + + def prove_range(amount, last_mask=None, *args, **kwargs): """ Range proof provided by the backend. Implemented in C for speed. @@ -635,8 +807,6 @@ def check_signature(data, c, r, pub): :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") diff --git a/src/apps/monero/xmr/mlsag2.py b/src/apps/monero/xmr/mlsag2.py index 89d5dd4d8..6b418b70f 100644 --- a/src/apps/monero/xmr/mlsag2.py +++ b/src/apps/monero/xmr/mlsag2.py @@ -180,7 +180,7 @@ def gen_mlsag_rows(message, rv, pk, xx, kLRki, index, dsRows, rows, cols): hash_point(hasher, aGi) c_old = hasher.digest() - c_old = crypto.sc_reduce32(crypto.decodeint(c_old)) + c_old = crypto.decodeint(c_old) return c_old, Ip, alpha @@ -231,7 +231,7 @@ def gen_mlsag_ext(message, pk, xx, kLRki, mscout, index, dsRows): hash_point(hasher, pk[i][j]) hash_point(hasher, L) - c = crypto.sc_reduce32(crypto.decodeint(hasher.digest())) + c = crypto.decodeint(hasher.digest()) c_old = c i = (i + 1) % cols diff --git a/src/apps/monero/xmr/monero.py b/src/apps/monero/xmr/monero.py index 9aca64c73..f420821c2 100644 --- a/src/apps/monero/xmr/monero.py +++ b/src/apps/monero/xmr/monero.py @@ -2,7 +2,6 @@ # -*- coding: utf-8 -*- # Author: Dusan Klinec, ph4r05, 2018 -import ustruct as struct from micropython import const from apps.monero.xmr import common, crypto @@ -15,48 +14,21 @@ 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 -): +def get_subaddress_secret_key(secret_key, index=None, major=None, minor=None): """ Builds subaddress secret key from the subaddress index Hs(SubAddr || a || index_major || index_minor) - 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) + return crypto.get_subaddress_secret_key(secret_key, major, minor) def get_subaddress_spend_public_key(view_private, spend_public, major, minor): @@ -108,7 +80,7 @@ def generate_key_image(public_key, secret_key): :return: """ point = crypto.hash_to_ec(public_key) - point2 = crypto.ge_scalarmult(secret_key, point) + point2 = crypto.scalarmult(point, secret_key) return point2 @@ -283,9 +255,8 @@ def generate_keys(recovery_key): :param recovery_key: :return: """ - sec = crypto.sc_reduce32(recovery_key) - pub = crypto.scalarmult_base(sec) - return sec, pub + pub = crypto.scalarmult_base(recovery_key) + return recovery_key, pub def generate_monero_keys(seed): @@ -301,3 +272,22 @@ def generate_monero_keys(seed): hash = crypto.cn_fast_hash(crypto.encodeint(spend_sec)) view_sec, view_pub = generate_keys(crypto.decodeint(hash)) return spend_sec, spend_pub, view_sec, view_pub + + +def generate_sub_address_keys(view_sec, spend_pub, major, minor): + """ + Computes generic public sub-address + :param view_sec: + :param spend_pub: + :param major: + :param minor: + :return: spend public, view public + """ + if major == 0 and minor == 0: # special case, Monero-defined + return spend_pub, crypto.scalarmult_base(view_sec) + + m = get_subaddress_secret_key(view_sec, major=major, minor=minor) + M = crypto.scalarmult_base(m) + D = crypto.point_add(spend_pub, M) + C = crypto.scalarmult(D, view_sec) + return D, C diff --git a/src/apps/monero/xmr/ring_ct.py b/src/apps/monero/xmr/ring_ct.py index 420e727ac..228485218 100644 --- a/src/apps/monero/xmr/ring_ct.py +++ b/src/apps/monero/xmr/ring_ct.py @@ -3,9 +3,29 @@ # Author: https://github.com/monero-project/mininero # Author: Dusan Klinec, ph4r05, 2018 +import gc + from apps.monero.xmr import crypto +async def prove_range_bp(amount, last_mask=None): + from apps.monero.xmr import bulletproof as bp + + bpi = bp.BulletProofBuilder() + + mask = last_mask if last_mask is not None else crypto.random_scalar() + bpi.set_input(crypto.sc_init(amount), mask) + bp_proof = bpi.prove() + C = crypto.decodepoint(bp_proof.V[0]) + + gc.collect() + + # Return as struct as the hash(BP_struct) != hash(BP_serialized) + # as the original hashing does not take vector lengths into account which are dynamic + # in the serialization scheme (and thus extraneous) + return C, mask, bp_proof + + def prove_range( amount, last_mask=None, decode=False, backend_impl=True, byte_enc=True, rsig=None ): diff --git a/src/apps/monero/xmr/serialize/xmrserialize.py b/src/apps/monero/xmr/serialize/xmrserialize.py index 7d9c4a00d..bc6e731da 100644 --- a/src/apps/monero/xmr/serialize/xmrserialize.py +++ b/src/apps/monero/xmr/serialize/xmrserialize.py @@ -180,20 +180,12 @@ async def container(self, container=None, container_type=None, params=None): ) if self.writing: - return await dump_container( - self.iobj, - container, - container_type, - params, - field_archiver=self.dump_field, + return await self._dump_container( + self.iobj, container, container_type, params ) else: - return await load_container( - self.iobj, - container_type, - params=params, - container=container, - field_archiver=self.load_field, + return await self._load_container( + self.iobj, container_type, params=params, container=container ) async def container_size( @@ -210,7 +202,7 @@ async def container_size( raise ValueError("not supported") if self.writing: - return await dump_container_size( + return await self._dump_container_size( self.iobj, container_len, container_type, params ) else: @@ -227,7 +219,9 @@ async def container_val(self, elem, container_type, params=None): if hasattr(container_type, "serialize_archive"): raise ValueError("not supported") if self.writing: - return await dump_container_val(self.iobj, elem, container_type, params) + return await self._dump_container_val( + self.iobj, elem, container_type, params + ) else: raise ValueError("Not supported") @@ -243,19 +237,13 @@ async def tuple(self, elem=None, elem_type=None, params=None): ) if self.writing: - return await dump_tuple( - self.iobj, elem, elem_type, params, field_archiver=self.dump_field - ) + return await self._dump_tuple(self.iobj, elem, elem_type, params) else: - return await load_tuple( - self.iobj, - elem_type, - params=params, - elem=elem, - field_archiver=self.load_field, + return await self._load_tuple( + self.iobj, elem_type, params=params, elem=elem ) - async def variant(self, elem=None, elem_type=None, params=None): + async def variant(self, elem=None, elem_type=None, params=None, wrapped=None): """ Loads/dumps variant type :param elem: @@ -271,20 +259,19 @@ async def variant(self, elem=None, elem_type=None, params=None): ) if self.writing: - return await dump_variant( + return await self._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( + return await self._load_variant( self.iobj, elem_type=elem_type if elem_type else elem.__class__, params=params, elem=elem, - field_archiver=self.load_field, + wrapped=wrapped, ) async def message(self, msg, msg_type=None): @@ -300,13 +287,9 @@ async def message(self, msg, msg_type=None): 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 - ) + return await self._dump_message(self.iobj, msg, msg_type=msg_type) else: - return await load_message( - self.iobj, msg_type, msg=msg, field_archiver=self.load_field - ) + return await self._load_message(self.iobj, msg_type, msg=msg) async def message_field(self, msg, field, fvalue=None): """ @@ -317,13 +300,9 @@ async def message_field(self, msg, field, fvalue=None): :return: """ if self.writing: - await dump_message_field( - self.iobj, msg, field, fvalue=fvalue, field_archiver=self.dump_field - ) + await self._dump_message_field(self.iobj, msg, field, fvalue=fvalue) else: - await load_message_field( - self.iobj, msg, field, field_archiver=self.load_field - ) + await self._load_message_field(self.iobj, msg, field) async def message_fields(self, msg, fields): """ @@ -337,9 +316,6 @@ async def message_fields(self, msg, fields): 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 @@ -454,6 +430,270 @@ async def root(self): :return: """ + async def _dump_container_size( + self, 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(self, writer, elem, container_type, params=None): + """ + Single elem dump + :param writer: + :param elem: + :param container_type: + :param params: + :return: + """ + elem_type = container_elem_type(container_type, params) + await self.dump_field(writer, elem, elem_type, params[1:] if params else None) + + async def _dump_container(self, writer, container, container_type, params=None): + """ + Dumps container of elements to the writer. + + :param writer: + :param container: + :param container_type: + :param params: + :return: + """ + await self._dump_container_size(writer, len(container), container_type) + + elem_type = container_elem_type(container_type, params) + + for elem in container: + await self.dump_field( + writer, elem, elem_type, params[1:] if params else None + ) + + async def _load_container( + self, reader, container_type, params=None, container=None + ): + """ + Loads container of elements from the reader. Supports the container ref. + Returns loaded container. + + :param reader: + :param container_type: + :param params: + :param container: + :return: + """ + + 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 self.load_field( + 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(self, writer, elem, elem_type, params=None): + """ + Dumps tuple of elements to the writer. + + :param writer: + :param elem: + :param elem_type: + :param params: + :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)) + + 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 self.dump_field( + writer, elem, elem_fields[idx], params[1:] if params else None + ) + + async def _load_tuple(self, reader, elem_type, params=None, elem=None): + """ + Loads tuple of elements from the reader. Supports the tuple ref. + Returns loaded tuple. + + :param reader: + :param elem_type: + :param params: + :param container: + :return: + """ + + 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 self.load_field( + 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(self, writer, msg, field, fvalue=None): + """ + Dumps a message field to the writer. Field is defined by the message field specification. + + :param writer: + :param msg: + :param field: + :param fvalue: + :return: + """ + fname, ftype, params = field[0], field[1], field[2:] + fvalue = getattr(msg, fname, None) if fvalue is None else fvalue + await self.dump_field(writer, fvalue, ftype, params) + + async def _load_message_field(self, reader, msg, field): + """ + 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: + :return: + """ + fname, ftype, params = field[0], field[1], field[2:] + await self.load_field(reader, ftype, params, eref(msg, fname)) + + async def _dump_message(self, writer, msg, msg_type=None): + """ + Dumps message to the writer. + + :param writer: + :param msg: + :param msg_type: + :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 self._dump_message_field(writer, msg=msg, field=field) + + async def _load_message(self, reader, msg_type, msg=None): + """ + Loads message if the given type from the reader. + Supports reading directly to existing message. + + :param reader: + :param msg_type: + :param msg: + :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 self._load_message_field(reader, msg, field) + + return msg + + async def _dump_variant(self, writer, elem, elem_type=None, params=None): + """ + Dumps variant type to the writer. + Supports both wrapped and raw variant. + + :param writer: + :param elem: + :param elem_type: + :param params: + :return: + """ + if isinstance(elem, VariantType) or elem_type.WRAPS_VALUE: + await dump_uint(writer, elem.variant_elem_type.VARIANT_CODE, 1) + await self.dump_field( + 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 self.dump_field(writer, elem, fdef[1]) + + async def _load_variant( + self, reader, elem_type, params=None, elem=None, wrapped=None + ): + """ + Loads variant type from the reader. + Supports both wrapped and raw variant. + + :param reader: + :param elem_type: + :param params: + :param elem: + :param wrapped: + :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 + + tag = await load_uint(reader, 1) + for field in elem_type.f_specs(): + ftype = field[1] + if ftype.VARIANT_CODE == tag: + fvalue = await self.load_field( + 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_blob(writer, elem, elem_type, params=None): """ @@ -527,237 +767,6 @@ async def load_unicode(reader): 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: @@ -771,73 +780,3 @@ def find_variant_fdef(elem_type, elem): 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/tx_full.py b/src/apps/monero/xmr/serialize_messages/tx_full.py index a8547c5b6..f3037b8a6 100644 --- a/src/apps/monero/xmr/serialize_messages/tx_full.py +++ b/src/apps/monero/xmr/serialize_messages/tx_full.py @@ -124,12 +124,12 @@ async def serialize_rctsig_prunable(self, ar, type, inputs, outputs, mixin): 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 ) + if len(self.bulletproofs) != outputs: + raise ValueError("Bulletproofs size mismatch") + for i in range(len(self.bulletproofs)): await ar.field(elem=eref(self.bulletproofs, i), elem_type=Bulletproof) diff --git a/tests/test_apps.monero.bulletproof.py b/tests/test_apps.monero.bulletproof.py new file mode 100644 index 000000000..e079d1722 --- /dev/null +++ b/tests/test_apps.monero.bulletproof.py @@ -0,0 +1,149 @@ +from common import * + +from apps.monero.xmr import bulletproof as bp, common, crypto, monero +from apps.monero.xmr.serialize_messages.tx_rsig_bulletproof import Bulletproof + + +class TestMoneroBulletproof(unittest.TestCase): + def test_1(self): + pass + + def mask_consistency_check(self, bpi): + self.assertEqual(bpi.sL(0), bpi.sL(0)) + self.assertEqual(bpi.sL(1), bpi.sL(1)) + self.assertEqual(bpi.sL(63), bpi.sL(63)) + self.assertNotEqual(bpi.sL(1), bpi.sL(0)) + + self.assertEqual(bpi.sR(0), bpi.sR(0)) + self.assertEqual(bpi.sR(1), bpi.sR(1)) + self.assertEqual(bpi.sR(63), bpi.sR(63)) + self.assertNotEqual(bpi.sR(1), bpi.sR(0)) + + self.assertNotEqual(bpi.sL(0), bpi.sR(0)) + self.assertNotEqual(bpi.sL(1), bpi.sR(1)) + self.assertNotEqual(bpi.sL(63), bpi.sR(63)) + + bpi.init_vct() + ve1 = bp._ensure_dst_key() + ve2 = bp._ensure_dst_key() + bpi.vector_exponent(bpi.v_aL, bpi.v_aR, ve1) + bpi.vector_exponent(bpi.v_aL, bpi.v_aR, ve2) + + bpi.vector_exponent(bpi.v_sL, bpi.v_sR, ve1) + bpi.vector_exponent(bpi.v_sL, bpi.v_sR, ve2) + self.assertEqual(ve1, ve2) + + def test_masks(self): + bpi = bp.BulletProofBuilder() + val = crypto.sc_init(123) + mask = crypto.sc_init(432) + bpi.set_input(val, mask) + self.mask_consistency_check(bpi) + + # Randomized masks + bpi.use_det_masks = False + self.mask_consistency_check(bpi) + + def test_verify(self): + bpi = bp.BulletProofBuilder() + + # fmt: off + bp_proof = Bulletproof( + V=[bytes( + [0x67, 0x54, 0xbf, 0x40, 0xcb, 0x45, 0x63, 0x0d, 0x4b, 0xea, 0x08, 0x9e, 0xd7, 0x86, 0xec, 0x3c, 0xe5, + 0xbd, 0x4e, 0xed, 0x8f, 0xf3, 0x25, 0x76, 0xae, 0xca, 0xb8, 0x9e, 0xf2, 0x5e, 0x41, 0x16])], + A=bytes( + [0x96, 0x10, 0x17, 0x66, 0x87, 0x7e, 0xef, 0x97, 0xb3, 0x82, 0xfb, 0x8e, 0x0c, 0x2a, 0x93, 0x68, 0x9e, + 0x05, 0x22, 0x07, 0xe3, 0x30, 0x94, 0x20, 0x58, 0x6f, 0x5d, 0x01, 0x6d, 0x4e, 0xd5, 0x88]), + S=bytes( + [0x50, 0x51, 0x38, 0x32, 0x96, 0x20, 0x7c, 0xc9, 0x60, 0x4d, 0xac, 0x7c, 0x7c, 0x21, 0xf9, 0xad, 0x1c, + 0xc2, 0x2d, 0xee, 0x88, 0x7b, 0xa2, 0xe2, 0x61, 0x81, 0x46, 0xf5, 0x99, 0xc3, 0x12, 0x57]), + T1=bytes( + [0x1a, 0x7d, 0x06, 0x51, 0x41, 0xe6, 0x12, 0xbe, 0xad, 0xd7, 0x68, 0x60, 0x85, 0xfc, 0xc4, 0x86, 0x0b, + 0x39, 0x4b, 0x06, 0xf7, 0xca, 0xb3, 0x29, 0xdf, 0x1d, 0xbf, 0x96, 0x5f, 0xbe, 0x8c, 0x87]), + T2=bytes( + [0x57, 0xae, 0x91, 0x04, 0xfa, 0xac, 0xf3, 0x73, 0x75, 0xf2, 0x83, 0xd6, 0x9a, 0xcb, 0xef, 0xe4, 0xfc, + 0xe5, 0x37, 0x55, 0x52, 0x09, 0xb5, 0x60, 0x6d, 0xab, 0x46, 0x85, 0x01, 0x23, 0x9e, 0x47]), + taux=bytes( + [0x44, 0x7a, 0x87, 0xd9, 0x5f, 0x1b, 0x17, 0xed, 0x53, 0x7f, 0xc1, 0x4f, 0x91, 0x9b, 0xca, 0x68, 0xce, + 0x20, 0x43, 0xc0, 0x88, 0xf1, 0xdf, 0x12, 0x7b, 0xd7, 0x7f, 0xe0, 0x27, 0xef, 0xef, 0x0d]), + mu=bytes( + [0x32, 0xf9, 0xe4, 0xe1, 0xc2, 0xd8, 0xe4, 0xb0, 0x0d, 0x49, 0xd1, 0x02, 0xbc, 0xcc, 0xf7, 0xa2, 0x5a, + 0xc7, 0x28, 0xf3, 0x05, 0xb5, 0x64, 0x2e, 0xde, 0xcf, 0x01, 0x61, 0xb8, 0x62, 0xfb, 0x0d]), + L=[ + bytes([0xde, 0x71, 0xca, 0x09, 0xf9, 0xd9, 0x1f, 0xa2, 0xae, 0xdf, 0x39, 0x49, 0x04, 0xaa, 0x6b, 0x58, + 0x67, 0x9d, 0x61, 0xa6, 0xfa, 0xec, 0x81, 0xf6, 0x4c, 0x15, 0x09, 0x9d, 0x10, 0x21, 0xff, 0x39]), + bytes([0x90, 0x47, 0xbf, 0xf0, 0x1f, 0x72, 0x47, 0x4e, 0xd5, 0x58, 0xfb, 0xc1, 0x16, 0x43, 0xb7, 0xd8, + 0xb1, 0x00, 0xa4, 0xa3, 0x19, 0x9b, 0xda, 0x5b, 0x27, 0xd3, 0x6c, 0x5a, 0x87, 0xf8, 0xf0, 0x28]), + bytes([0x03, 0x45, 0xef, 0x57, 0x19, 0x8b, 0xc7, 0x38, 0xb7, 0xcb, 0x9c, 0xe7, 0xe8, 0x23, 0x27, 0xbb, + 0xd3, 0x54, 0xcb, 0x38, 0x3c, 0x24, 0x8a, 0x60, 0x11, 0x20, 0x92, 0x99, 0xec, 0x35, 0x71, 0x9f]), + bytes([0x7a, 0xb6, 0x36, 0x42, 0x36, 0x83, 0xf3, 0xa6, 0xc1, 0x24, 0xc5, 0x63, 0xb0, 0x4c, 0x8b, 0xef, + 0x7c, 0x77, 0x25, 0x83, 0xa8, 0xbb, 0x8b, 0x57, 0x75, 0x1c, 0xb6, 0xd7, 0xca, 0xc9, 0x0d, 0x78]), + bytes([0x9d, 0x79, 0x66, 0x21, 0x64, 0x72, 0x97, 0x08, 0xa0, 0x5a, 0x94, 0x5a, 0x94, 0x7b, 0x11, 0xeb, + 0x4e, 0xe9, 0x43, 0x2f, 0x08, 0xa2, 0x57, 0xa5, 0xd5, 0x99, 0xb0, 0xa7, 0xde, 0x78, 0x80, 0xb7]), + bytes([0x9f, 0x88, 0x5c, 0xa5, 0xeb, 0x08, 0xef, 0x1a, 0xcf, 0xbb, 0x1d, 0x04, 0xc5, 0x47, 0x24, 0x37, + 0x49, 0xe4, 0x4e, 0x9c, 0x5d, 0x56, 0xd0, 0x97, 0xfd, 0x8a, 0xe3, 0x23, 0x1d, 0xab, 0x16, 0x03]), + ], + R=[ + bytes([0xae, 0x89, 0xeb, 0xa8, 0x5b, 0xd5, 0x65, 0xd6, 0x9f, 0x2a, 0xfd, 0x04, 0x66, 0xad, 0xb1, 0xf3, + 0x5e, 0xf6, 0x60, 0xa7, 0x26, 0x94, 0x3b, 0x72, 0x5a, 0x5c, 0x80, 0xfa, 0x0f, 0x75, 0x48, 0x27]), + bytes([0xc9, 0x1a, 0x61, 0x70, 0x6d, 0xea, 0xea, 0xb2, 0x42, 0xff, 0x27, 0x3b, 0x8e, 0x94, 0x07, 0x75, + 0x40, 0x7d, 0x33, 0xde, 0xfc, 0xbd, 0x53, 0xa0, 0x2a, 0xf9, 0x0c, 0x36, 0xb0, 0xdd, 0xbe, 0x8d]), + bytes([0xb7, 0x39, 0x7a, 0x0e, 0xa1, 0x42, 0x0f, 0x94, 0x62, 0x24, 0xcf, 0x54, 0x75, 0xe3, 0x0b, 0x0f, + 0xfb, 0xcb, 0x67, 0x7b, 0xbc, 0x98, 0x36, 0x01, 0x9f, 0x73, 0xa0, 0x70, 0xa1, 0x7e, 0xf0, 0xcf]), + bytes([0x40, 0x06, 0xd4, 0xfa, 0x22, 0x7c, 0x82, 0xbf, 0xe8, 0xe0, 0x35, 0x13, 0x28, 0xa2, 0xb9, 0x51, + 0xa3, 0x37, 0x34, 0xc0, 0xa6, 0x43, 0xd6, 0xb7, 0x7a, 0x40, 0xae, 0xf9, 0x36, 0x0e, 0xe3, 0xcc]), + bytes([0x88, 0x38, 0x64, 0xe9, 0x63, 0xe3, 0x33, 0xd9, 0xf6, 0xca, 0x47, 0xc4, 0xc7, 0x36, 0x70, 0x01, + 0xd2, 0xe4, 0x8c, 0x9f, 0x25, 0xc2, 0xce, 0xcf, 0x81, 0x89, 0x4f, 0x24, 0xcb, 0xb8, 0x40, 0x73]), + bytes([0xdc, 0x35, 0x65, 0xed, 0x6b, 0xb0, 0xa7, 0x1a, 0x1b, 0xf3, 0xd6, 0xfb, 0x47, 0x00, 0x48, 0x00, + 0x20, 0x6d, 0xd4, 0xeb, 0xff, 0xb9, 0xdc, 0x43, 0x30, 0x8a, 0x90, 0xfe, 0x43, 0x74, 0x75, 0x68]), + ], + a=bytes( + [0xb4, 0x8e, 0xc2, 0x31, 0xce, 0x05, 0x9a, 0x7a, 0xbc, 0x82, 0x8c, 0x30, 0xb3, 0xe3, 0x80, 0x86, 0x05, + 0xb8, 0x4c, 0x93, 0x9a, 0x8e, 0xce, 0x39, 0x0f, 0xb6, 0xee, 0x28, 0xf6, 0x7e, 0xd5, 0x07]), + b=bytes( + [0x47, 0x10, 0x62, 0xc2, 0xad, 0xc7, 0xe2, 0xc9, 0x14, 0x6f, 0xf4, 0xd1, 0xfe, 0x52, 0xa9, 0x1a, 0xe4, + 0xb6, 0xd0, 0x25, 0x4b, 0x19, 0x80, 0x7c, 0xcd, 0x62, 0x62, 0x1d, 0x97, 0x20, 0x71, 0x0b]), + t=bytes( + [0x47, 0x06, 0xea, 0x76, 0x8f, 0xdb, 0xa3, 0x15, 0xe0, 0x2c, 0x6b, 0x25, 0xa1, 0xf7, 0x3c, 0xc8, 0x1d, + 0x97, 0xa6, 0x52, 0x48, 0x75, 0x37, 0xf9, 0x1e, 0x14, 0xac, 0xb1, 0x2a, 0x34, 0xc6, 0x06]) + ) + # fmt: on + + self.assertTrue(bpi.verify(bp_proof)) + + def test_prove(self): + bpi = bp.BulletProofBuilder() + val = crypto.sc_init(123) + mask = crypto.sc_init(432) + bpi.set_input(val, mask) + bp_res = bpi.prove() + bpi.verify(bp_res) + + try: + bp_res.S[0] += 1 + bpi.verify(bp_res) + self.fail("Verification should have failed") + except: + pass + + def test_prove_2(self): + bpi = bp.BulletProofBuilder() + val = crypto.sc_init((1 << 30) - 1 + 16) + mask = crypto.random_scalar() + bpi.set_input(val, mask) + bp_res = bpi.prove() + bpi.verify(bp_res) + + def test_prove_random_masks(self): + bpi = bp.BulletProofBuilder() + bpi.use_det_masks = False # trully randomly generated mask vectors + val = crypto.sc_init((1 << 30) - 1 + 16) + mask = crypto.random_scalar() + bpi.set_input(val, mask) + bp_res = bpi.prove() + bpi.verify(bp_res) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_apps.monero.crypto.py b/tests/test_apps.monero.crypto.py new file mode 100644 index 000000000..925e89717 --- /dev/null +++ b/tests/test_apps.monero.crypto.py @@ -0,0 +1,232 @@ +from common import * + +from apps.monero.xmr import common, crypto, monero +from apps.monero.xmr.sub.addr import encode_addr +from apps.monero.xmr.sub.xmr_net import net_version, NetworkTypes +from apps.monero.xmr.sub.creds import AccountCreds + + +class TestMoneroCrypto(unittest.TestCase): + def test_encoding(self): + point = unhexlify( + b"2486224797d05cae3cba4be043be2db0df381f3f19cfa113f86ab38e3d8d2bd0" + ) + self.assertEqual(point, crypto.encodepoint(crypto.decodepoint(point))) + self.assertTrue( + crypto.point_eq( + crypto.decodepoint(point), + crypto.decodepoint(crypto.encodepoint(crypto.decodepoint(point))), + ) + ) + + def test_scalarmult_base(self): + scalar = crypto.decodeint( + unhexlify( + b"a0eea49140a3b036da30eacf64bd9d56ce3ef68ba82ef13571ec511edbcf8303" + ) + ) + exp = unhexlify( + b"16bb4a3c44e2ced511fc0d4cd86b13b3af21efc99fb0356199fac489f2544c09" + ) + + res = crypto.scalarmult_base(scalar) + self.assertEqual(exp, crypto.encodepoint(res)) + self.assertTrue(crypto.point_eq(crypto.decodepoint(exp), res)) + + scalar = crypto.decodeint( + unhexlify( + b"fd290dce39f781aebbdbd24584ed6d48bd300de19d9c3decfda0a6e2c6751d0f" + ) + ) + exp = unhexlify( + b"123daf90fc26f13c6529e6b49bfed498995ac383ef19c0db6771143f24ba8dd5" + ) + + res = crypto.scalarmult_base(scalar) + self.assertEqual(exp, crypto.encodepoint(res)) + self.assertTrue(crypto.point_eq(crypto.decodepoint(exp), res)) + + def test_scalarmult(self): + priv = unhexlify( + b"3482fb9735ef879fcae5ec7721b5d3646e155c4fb58d6cc11c732c9c9b76620a" + ) + pub = unhexlify( + b"2486224797d05cae3cba4be043be2db0df381f3f19cfa113f86ab38e3d8d2bd0" + ) + exp = unhexlify( + b"adcd1f5881f46f254900a03c654e71950a88a0236fa0a3a946c9b8daed6ef43d" + ) + + res = crypto.scalarmult(crypto.decodepoint(pub), crypto.decodeint(priv)) + self.assertEqual(exp, crypto.encodepoint(res)) + self.assertTrue(crypto.point_eq(crypto.decodepoint(exp), res)) + + def test_cn_fast_hash(self): + inp = unhexlify( + b"259ef2aba8feb473cf39058a0fe30b9ff6d245b42b6826687ebd6b63128aff6405" + ) + res = crypto.cn_fast_hash(inp) + self.assertEqual( + res, + unhexlify( + b"86db87b83fb1246efca5f3b0db09ce3fa4d605b0d10e6507cac253dd31a3ec16" + ), + ) + + def test_hash_to_scalar(self): + inp = unhexlify( + b"259ef2aba8feb473cf39058a0fe30b9ff6d245b42b6826687ebd6b63128aff6405" + ) + + res = crypto.hash_to_scalar(inp) + exp = crypto.decodeint( + unhexlify( + b"9907925b254e12162609fc0dfd0fef2aa4d605b0d10e6507cac253dd31a3ec06" + ) + ) + self.assertTrue(crypto.sc_eq(res, exp)) + + def test_hash_to_point(self): + data = unhexlify( + b"42f6835bf83114a1f5f6076fe79bdfa0bd67c74b88f127d54572d3910dd09201" + ) + res = crypto.hash_to_ec(data) + res_p = crypto.encodepoint(res) + self.assertEqual( + res_p, + unhexlify( + b"54863a0464c008acc99cffb179bc6cf34eb1bbdf6c29f7a070a7c6376ae30ab5" + ), + ) + + def test_derivation_to_scalar(self): + derivation = unhexlify( + b"e720a09f2e3a0bbf4e4ba7ad93653bb296885510121f806acb2a5f9168fafa01" + ) + scalar = unhexlify( + b"25d08763414c379aa9cf989cdcb3cadd36bd5193b500107d6bf5f921f18e470e" + ) + + sc_int = crypto.derivation_to_scalar(crypto.decodepoint(derivation), 0) + self.assertEqual(scalar, crypto.encodeint(sc_int)) + + def test_generate_key_derivation(self): + key_pub = crypto.decodepoint( + unhexlify( + b"7739c95d3298e2f87362dba9e0e0b3980a692ae8e2f16796b0e382098cd6bd83" + ) + ) + key_priv = crypto.decodeint( + unhexlify( + b"3482fb9735ef879fcae5ec7721b5d3646e155c4fb58d6cc11c732c9c9b76620a" + ) + ) + deriv_exp = unhexlify( + b"fa188a45a0e4daccc0e6d4f6f6858fd46392104be74183ec0047e7e9f4eaf739" + ) + + self.assertEqual( + deriv_exp, + crypto.encodepoint(crypto.generate_key_derivation(key_pub, key_priv)), + ) + + def test_h(self): + H = unhexlify( + b"8b655970153799af2aeadc9ff1add0ea6c7251d54154cfa92c173a0dd39c1f94" + ) + self.assertEqual(crypto.encodepoint(crypto.gen_H()), H) + + def test_sc_inversion(self): + res = crypto.new_scalar() + inp = crypto.decodeint( + unhexlify( + b"3482fb9735ef879fcae5ec7721b5d3646e155c4fb58d6cc11c732c9c9b76620a" + ) + ) + + crypto.sc_inv_into(res, inp) + self.assertEqual( + hexlify(crypto.encodeint(res)), + b"bcf365a551e6358f3f281a6241d4a25eded60230b60a1d48c67b51a85e33d70e", + ) + + def test_wallet_addr(self): + addr = encode_addr( + net_version(), + unhexlify( + b"3bec484c5d7f0246af520aab550452b5b6013733feabebd681c4a60d457b7fc1" + ), + unhexlify( + b"2d5918e31d3c003da3c778592c07b398ad6f961a67082a75fd49394d51e69bbe" + ), + ) + + self.assertEqual( + addr, + b"43tpGG9PKbwCpjRvNLn1jwXPpnacw2uVUcszAtgmDiVcZK4VgHwjJT9BJz1WGF9eMxSYASp8yNMkuLjeQfWqJn3CNWdWfzV", + ) + + w = AccountCreds.new_wallet( + crypto.b16_to_scalar( + b"4ce88c168e0f5f8d6524f712d5f8d7d83233b1e7a2a60b5aba5206cc0ea2bc08" + ), + crypto.b16_to_scalar( + b"f2644a3dd97d43e87887e74d1691d52baa0614206ad1b0c239ff4aa3b501750a" + ), + network_type=NetworkTypes.TESTNET, + ) + self.assertEqual( + w.address, + b"9vacMKaj8JJV6MnwDzh2oNVdwTLJfTDyNRiB6NzV9TT7fqvzLivH2dB8Tv7VYR3ncn8vCb3KdNMJzQWrPAF1otYJ9cPKpkr", + ) + + def test_derive_subaddress_public_key(self): + out_key = crypto.decodepoint( + unhexlify( + b"f4efc29da4ccd6bc6e81f52a6f47b2952966442a7efb49901cce06a7a3bef3e5" + ) + ) + deriv = crypto.decodepoint( + unhexlify( + b"259ef2aba8feb473cf39058a0fe30b9ff6d245b42b6826687ebd6b63128aff64" + ) + ) + res = crypto.encodepoint(monero.derive_subaddress_public_key(out_key, deriv, 5)) + self.assertEqual( + res, + unhexlify( + b"5a10cca900ee47a7f412cd661b29f5ab356d6a1951884593bb170b5ec8b6f2e8" + ), + ) + + def test_get_subaddress_secret_key(self): + a = crypto.b16_to_scalar( + b"4ce88c168e0f5f8d6524f712d5f8d7d83233b1e7a2a60b5aba5206cc0ea2bc08" + ) + m = monero.get_subaddress_secret_key(secret_key=a, major=0, minor=1) + self.assertEqual( + crypto.encodeint(m), + unhexlify( + b"b6ff4d689b95e3310efbf683850c075bcde46361923054e42ef30016b287ff0c" + ), + ) + + def test_public_spend(self): + derivation = unhexlify( + b"e720a09f2e3a0bbf4e4ba7ad93653bb296885510121f806acb2a5f9168fafa01" + ) + base = unhexlify( + b"7d996b0f2db6dbb5f2a086211f2399a4a7479b2c911af307fdc3f7f61a88cb0e" + ) + pkey_ex = unhexlify( + b"0846cae7405077b6b7800f0b932c10a186448370b6db318f8c9e13f781dab546" + ) + + pkey_comp = crypto.derive_public_key( + crypto.decodepoint(derivation), 0, crypto.decodepoint(base) + ) + self.assertEqual(pkey_ex, crypto.encodepoint(pkey_comp)) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_apps.monero.serializer.py b/tests/test_apps.monero.serializer.py new file mode 100644 index 000000000..def666c46 --- /dev/null +++ b/tests/test_apps.monero.serializer.py @@ -0,0 +1,362 @@ +from common import * +import utest + +from trezor import log, loop, utils +from apps.monero.xmr.serialize import xmrserialize as xms +from apps.monero.xmr.serialize.readwriter import MemoryReaderWriter +from apps.monero.xmr.serialize_messages.base import ECPoint +from apps.monero.xmr.serialize_messages.ct_keys import CtKey +from apps.monero.xmr.serialize_messages.tx_full import Transaction +from apps.monero.xmr.serialize_messages.tx_prefix import ( + TxinToKey, + TxinGen, + TxInV, + TxOut, + TxoutToKey, + TransactionPrefix, +) +from apps.monero.xmr.serialize_messages.tx_rsig_boro import BoroSig +from apps.monero.xmr.serialize_messages.tx_src_entry import OutputEntry + + +class XmrTstData(object): + """Simple tests data generator""" + + def __init__(self, *args, **kwargs): + super(XmrTstData, self).__init__() + self.ec_offset = 0 + + def reset(self): + self.ec_offset = 0 + + def generate_ec_key(self, use_offset=True): + """ + Returns test EC key, 32 element byte array + :param use_offset: + :return: + """ + offset = 0 + if use_offset: + offset = self.ec_offset + self.ec_offset += 1 + + return bytearray(range(offset, offset + 32)) + + def gen_transaction_prefix(self): + """ + Returns test transaction prefix + :return: + """ + vin = [ + TxinToKey( + amount=123, key_offsets=[1, 2, 3, 2 ** 76], k_image=bytearray(range(32)) + ), + TxinToKey( + amount=456, key_offsets=[9, 8, 7, 6], k_image=bytearray(range(32, 64)) + ), + TxinGen(height=99), + ] + + vout = [ + TxOut(amount=11, target=TxoutToKey(key=bytearray(range(32)))), + TxOut(amount=34, target=TxoutToKey(key=bytearray(range(64, 96)))), + ] + + msg = TransactionPrefix( + version=2, unlock_time=10, vin=vin, vout=vout, extra=list(range(31)) + ) + return msg + + def gen_borosig(self): + """ + Returns a BoroSig message + :return: + """ + ee = self.generate_ec_key() + s0 = [self.generate_ec_key() for _ in range(64)] + s1 = [self.generate_ec_key() for _ in range(64)] + msg = BoroSig(s0=s0, s1=s1, ee=ee) + return msg + + +class TestMoneroSerializer(unittest.TestCase): + def __init__(self, *args, **kwargs): + super(TestMoneroSerializer, self).__init__(*args, **kwargs) + self.tdata = XmrTstData() + + def setUp(self): + self.tdata.reset() + + async def test_async_varint(self): + """ + Var int + :return: + """ + # fmt: off + test_nums = [0, 1, 12, 44, 32, 63, 64, 127, 128, 255, 256, 1023, 1024, 8191, 8192, + 2**16, 2**16 - 1, 2**32, 2**32 - 1, 2**64, 2**64 - 1, 2**72 - 1, 2**112] + # fmt: on + + for test_num in test_nums: + writer = MemoryReaderWriter() + + await xms.dump_uvarint(writer, test_num) + test_deser = await xms.load_uvarint(MemoryReaderWriter(writer.get_buffer())) + + self.assertEqual(test_num, test_deser) + + async def test_async_ecpoint(self): + """ + Ec point + :return: + """ + ec_data = bytearray(range(32)) + writer = MemoryReaderWriter() + + await xms.dump_blob(writer, ec_data, ECPoint) + self.assertTrue(len(writer.get_buffer()), ECPoint.SIZE) + + test_deser = await xms.load_blob( + MemoryReaderWriter(writer.get_buffer()), ECPoint + ) + self.assertEqual(ec_data, test_deser) + + async def test_async_ecpoint_obj(self): + """ + EC point into + :return: + """ + ec_data = bytearray(list(range(32))) + ec_point = ECPoint() + ec_point.data = ec_data + writer = MemoryReaderWriter() + + await xms.dump_blob(writer, ec_point, ECPoint) + self.assertTrue(len(writer.get_buffer()), ECPoint.SIZE) + + ec_point2 = ECPoint() + test_deser = await xms.load_blob( + MemoryReaderWriter(writer.get_buffer()), ECPoint, elem=ec_point2 + ) + + self.assertEqual(ec_data, ec_point2.data) + self.assertEqual(ec_point, ec_point2) + + async def test_async_simple_msg(self): + """ + TxinGen + :return: + """ + msg = TxinGen(height=42) + + writer = MemoryReaderWriter() + ar1 = xms.Archive(writer, True) + await ar1.message(msg) + + ar2 = xms.Archive(MemoryReaderWriter(writer.get_buffer()), False) + test_deser = await ar2.message(None, msg_type=TxinGen) + self.assertEqual(msg.height, test_deser.height) + + async def test_async_simple_msg_into(self): + """ + TxinGen + :return: + """ + msg = TxinGen(height=42) + + writer = MemoryReaderWriter() + ar1 = xms.Archive(writer, True) + await ar1.message(msg) + + msg2 = TxinGen() + ar2 = xms.Archive(MemoryReaderWriter(writer.get_buffer()), False) + test_deser = await ar2.message(msg2, TxinGen) + self.assertEqual(msg.height, test_deser.height) + self.assertEqual(msg.height, msg2.height) + self.assertEqual(msg2, test_deser) + + async def test_async_tuple(self): + """ + Simple tuple type + :return: + """ + out_entry = [ + 123, + CtKey(dest=self.tdata.generate_ec_key(), mask=self.tdata.generate_ec_key()), + ] + writer = MemoryReaderWriter() + ar1 = xms.Archive(writer, True) + + await ar1.tuple(out_entry, OutputEntry) + ar2 = xms.Archive(MemoryReaderWriter(writer.get_buffer()), False) + test_deser = await ar2.tuple(None, OutputEntry) + + self.assertEqual(out_entry, test_deser) + + async def test_async_txin_to_key(self): + """ + TxinToKey + :return: + """ + msg = TxinToKey( + amount=123, key_offsets=[1, 2, 3, 2 ** 76], k_image=bytearray(range(32)) + ) + + writer = MemoryReaderWriter() + ar1 = xms.Archive(writer, True) + await ar1.message(msg) + + ar2 = xms.Archive(MemoryReaderWriter(writer.get_buffer()), False) + test_deser = await ar2.message(None, TxinToKey) + self.assertEqual(msg.amount, test_deser.amount) + self.assertEqual(msg, test_deser) + + async def test_async_txin_variant(self): + """ + TxInV + :return: + """ + msg1 = TxinToKey( + amount=123, key_offsets=[1, 2, 3, 2 ** 76], k_image=bytearray(range(32)) + ) + msg = TxInV() + msg.set_variant("txin_to_key", msg1) + + writer = MemoryReaderWriter() + ar1 = xms.Archive(writer, True) + await ar1.variant(msg) + + ar2 = xms.Archive(MemoryReaderWriter(writer.get_buffer()), False) + test_deser = await ar2.variant(None, TxInV, wrapped=True) + self.assertEqual(test_deser.__class__, TxInV) + self.assertEqual(msg, test_deser) + self.assertEqual(msg.variant_elem, test_deser.variant_elem) + self.assertEqual(msg.variant_elem_type, test_deser.variant_elem_type) + + async def test_async_tx_prefix(self): + """ + TransactionPrefix + :return: + """ + msg = self.tdata.gen_transaction_prefix() + + writer = MemoryReaderWriter() + ar1 = xms.Archive(writer, True) + await ar1.message(msg) + + ar2 = xms.Archive(MemoryReaderWriter(writer.get_buffer()), False) + test_deser = await ar2.message(None, TransactionPrefix) + self.assertEqual(test_deser.__class__, TransactionPrefix) + self.assertEqual(test_deser.version, msg.version) + self.assertEqual(test_deser.unlock_time, msg.unlock_time) + self.assertEqual(len(test_deser.vin), len(msg.vin)) + self.assertEqual(len(test_deser.vout), len(msg.vout)) + self.assertEqual(len(test_deser.extra), len(msg.extra)) + self.assertEqual(test_deser.extra, msg.extra) + self.assertListEqual(test_deser.vin, msg.vin) + self.assertListEqual(test_deser.vout, msg.vout) + self.assertEqual(test_deser, msg) + + async def test_async_boro_sig(self): + """ + BoroSig + :return: + """ + msg = self.tdata.gen_borosig() + + writer = MemoryReaderWriter() + ar1 = xms.Archive(writer, True) + await ar1.message(msg) + + ar2 = xms.Archive(MemoryReaderWriter(writer.get_buffer()), False) + test_deser = await ar2.message(None, BoroSig) + self.assertEqual(msg, test_deser) + + async def test_async_transaction_prefix(self): + """ + + :return: + """ + tsx_hex = b"013D01FF010680A0DB5002A9243CF5459DE5114E6A1AC08F9180C9F40A3CF9880778878104E9FEA578B6A780A8D6B90702AFEBACD6A4456AF979CCBE08D37A9A670BA421B5E39AB2968DF4219DD086018B8088ACA3CF020251748BADE758D1DD65A867FA3CEDD4878485BBC8307F905E3090A030290672798090CAD2C60E020C823CCBD4AB1A1F9240844400D72CDC8B498B3181B182B0B54A405B695406A680E08D84DDCB01022A9A926097548A723863923FBFEA4913B1134B2E4AE54946268DDA99564B5D8280C0CAF384A30202A868709A8BB91734AD3EBAC127638E018139E375C1987E01CCC2A8B04427727E2101F74BF5FB3DA064F48090D9B6705E598925313875B2B4F2A50EB0517264B0721C" + tsx_bin = unhexlify(tsx_hex) + + reader = MemoryReaderWriter(bytearray(tsx_bin)) + ar1 = xms.Archive(reader, False) + + test_deser = await ar1.message(None, TransactionPrefix) + self.assertIsNotNone(test_deser) + self.assertEqual(len(reader.get_buffer()), 0) # no data left to read + self.assertEqual(len(test_deser.extra), 33) + self.assertEqual(test_deser.extra[0], 1) + self.assertEqual(test_deser.extra[32], 28) + self.assertEqual(test_deser.unlock_time, 61) + self.assertEqual(test_deser.version, 1) + self.assertEqual(len(test_deser.vin), 1) + self.assertEqual(len(test_deser.vout), 6) + self.assertEqual(test_deser.vin[0].height, 1) + self.assertEqual(test_deser.vout[0].amount, 169267200) + self.assertEqual(len(test_deser.vout[0].target.key), 32) + self.assertEqual(test_deser.vout[1].amount, 2000000000) + self.assertEqual(len(test_deser.vout[1].target.key), 32) + self.assertEqual(test_deser.vout[5].amount, 10000000000000) + self.assertEqual(len(test_deser.vout[5].target.key), 32) + + async def test_async_transaction(self): + """ + + :return: + """ + tsx_hex = b"020002028088aca3cf0203002d4401000000000000000000000000000000000000000000000000000000000000000280e08d84ddcb0103054f260100000000000000000000000000000000000000000000000000000000000000020002f19f9fdbb490ee5a1568723c33ff1fba8cf08376e134b0ea3835478012fb3a35000208d4343dfa3a42aa9166534580960054372bc45701dffa0f9bcd2b9083f6bb2d2c020901e38a3cc24e1f88d001ca28ee97bd9754a3729285c0a5957c5ecf75f0cd0e182dea1ec1f9c93ae912c7028088e2ed60a1baf8383ceedd2b78f5b332c7028e0e8152337b072357ce2d95d18e9238535be5ee344ab24faea09a8f477c06efc5c08c20f3d60a954bd9e5f5a7dbc354ccf8f65dc3df1dade64b63548d0d6d83613f3b88fc1baab1c6b9a6e1d7e01aa814096adfc8b9a4de7e0f5f4d0452dd1125d81c8275ba13aa3a3a835518f54927f30785317238c3b75878b36d6b1dff190c7dffeb1f074fdb907e7524eed818feaa0a887ff34caa3e642bec9d3afa3119317f777579bb4cd040fc97e289771ce8f50f8fba4cb6ba2b01b198b12902b7bfcf21ac9aea38fbe339e8edd1304225ab5246fd5a70dc4eb1fd8f024a74f898b7918cee43bb11ec8b04601a5e9911636ac843820bf70806911ab43e0d7992ea133ac210bc5938eaeebb32fa1c329200cd85035d60c4f33531f8ce1e6f5ec4d47010d5d3e20ec182b46b73085844b4647fea0567c488aa13d40d2dacba0c19722f72fd95b1bc25abc2ac49c86afe1f44f9b6097de69ebb233b281d2b179d996978bef22d6dc78d28c3387b408e05377fb04408a37c58242d76be9744644daec79fbde061270daddddb90c6b402acac5bdfa20cfd0e9b2dba45fbefdd69b21faf9a7b3a881a1f9c9192b56955478e5283c84b0f79372237ef625834c30c201f262fab1ca7b9fc9d318c48a5b8ceaea72e368d0e4698ae7afdaa8f7c378d10e4a03d3e7a537a3fb124c6475e9243459bd324f50338a687df2145d66b06b17b65ee2ba8f10d2ec7979861da631f30ea9173bb5c026cde11db1fb80392c4ab7cc931dd1481fc01a5b5eb1392aef09129044ae0fd07e0c9809253f28313add1179522295ef79a40dceaf451d1d08d31d9b4ee1569012cdde41c22a366e5f050e1d681c9170f7db2ae75ada97fe616c8fdca87a8870b453a4fd890cb3239ce9a062fcbe581be441ff4f74e8274e6e4dbcd6690a16206c1f7b37504431b6fb2f735f27cea48d01e43c49dee981a7cf42f588c0602170bffb708ea53623db7e6933c4cad16ca6128f3428d1bf9805524a527c58d07460c9c4b72baae042711d8cef73f64aef86d3c82afefef2501c27bab52967fb1b40580a1673a80bdb2f4da4eeb0fa4100ffea36ab27041e8f3fd71007a9251ffb40eedfbc95a1b592901a607d78f8ed8f3d441558755ff7509f6ab5976d5282baf076b60f59b177fc27fa848dd25e76f457f8410b7706ba713da6fcd469db958e1012dcc5532598f7fe27e68b847e6998618c0a1aea7a2f73194e31b1564e4cdf80b3b0ce6a996034502526d1bb6b847edf19a681ad65d0eac545fda8871cf34ea0851e8db3f9c1fcd7711a9f47852d757dd7cdf941ff56ec12a6cf01a409ae53f03ab5d9ac409d44c26a56eb3aee8c889ac11ad360296dfa2626c141e4f099a4f09258b5fa5ed07fb0dcb82257c9ef28ba772a86bbb8eede9292cc828ba4478ea0f444113413038135ca1e866847c83acf9b898cb7bc0e43851940223cb2308bf065e87d7b835a57b16e7ba4d16f9308e6503a18e3816a7c1d3086ad31eafc7b70bdb5191f235cb2a18de0442518587667fe693c14c46725835b6aadbbcaa9eb90c7d28d76ef3f76a417cfc0337d40d0748251accdb0f837c401a03c9dacd5bb40cb792cf3895f106a256b5c9491d758fe35139ebf0adc2e2aa95232acf113d440877c7962501dc4065dab2307042411f7c3e5e24d8895c700389289f5757c34d09eea055ed488ebeb915bd3e6964159fc3f64c64459113e10dc0158f0c3c227107910b3d9c954b57cc9dc6ce38359da65ce35149610ee3dc8fa9f2f6e75042870b82d969cd0eadb4ee9ee552830ce863b93d566bff22f14fe1df4f9f2049782c0b60a7f326d15a78bfe345e8304d9e26070c81111be5b7da07ca1c9054eb7c3e048fc18f135c0121b62f057ef49ad593627501ca5102984f2d79b0e31f4b9849000e30441476b8eb6c7987a3bb9740a1951284206c71d8bf49b78d7753496a2601ebf89cf382c55803a2bb002905d6c90b049bb55f66ddc907b8aab40c1111e50199de696b600a3def533a9ec848523048ddf214cb7fb04f04f74525c935673a0ed87bbedb0923c6ffae8c46501d7b30f6f124e45063dda2f9fcec455423712a037f3b78516efeee2f541c27bf7787267e00513c630547fe7a89d179b6ab87ca097491b0fa74c93e5a2bd9ef567f5adc04f93937c53c78c0d9b3a759bc4a158a0e683e0a58d50e008e40e57e63e024e516365604df2b85b8ab4fbce39c1f46e309292a985f7d88f0bc4c3c9032e0a7ac50aeb5ab732400d03500a6b8c1a85aac0e6f56783e70106a0c85194445695d393b30fde6faa3961b273285ab602699cd0f5262063f116942fae636a0a408737fa7e9ad7bb0ec06977c9f29b98f5d2d9b06bcf3ce53948ff90648c7913baddaf500d207f048f90e8902ff436ee215f5a00232b88d6360a5bb20e97befb1074cac6ffe017ca61c4979ab0fff29a9100dea08ee27d3ba14e23fcd1e40d61ad8a98e580294acdc173823d3c93a0361327dee09d1cc5cd3a7dbbe30efd637226c15845bb808a024482856dc09902ee0268b5a02794a69931dd5e0abbeb44f11aa8888ff944b183732aea3923c9354b54aa7f10f49f058ed5a510f94f874f0245c971f1045324342e811f21cdaef622d62142306404f89e666d1f9069e7163fd1155973eeb046afbb3407f9ffe129238873b3f01f51c2053e5b1a31a242f95c82ff5e78044bab8bde495e4ba735a4f211df8db05154eef62d00e7b0b637c3d99e647861125057037e66420da2b10e3f390908207630bdf4eb0f27c9b2afb171153512d80eca7fdaaeb6e3e891ee4ca1359f4900a1a683894ba4fbf6b5ec260cd1fd6d4a3e327b393b1ece780556acb40ebae1309a0ed1468795ff08eb5c6e537d916b7e239aec9d97da94f57f186ae7dec072707f6d3141e331f233b2fe843807cd12afafb3a89f5f08c661756b563e45801610850db4503a8d009c4f61418efe290c418e5587f1fde280dc6a51159a5284f8e0c362b622cfe87274dff821ca0af0b47fb70fdf06092e27e093a56043df06ae30235ec1e1b7f34a015e1d516b5fd04b227954b57b0a0f8d15d912f885edcbe3f046c14a1caea3a107414a694643f1317e09d962b9bd240b2712ee20071a8b6e204e9b9a00a51e4f6e6d589c1de6e8c1d0f2118ca39180b575526b5c433dcb818013858e14e93470fe8442b118b91d9b6848fb0180c639604cc7b4c59360770730b77316ce36880d9eaab14fc1f17c2e983b42c22d4b239fdf7d4fb5989ae2d370c59a8c950a223bb27bef53a5ad40aba22a527033738adbf321b9398f195aaf006bfcc93ad433db3366ebc471a9bd5ab4aa5834c81045d3c4f60e409630cf3a4031aab6b9aa660f00db6276c1e597562e4472313db36dcd75c235bcaf915e6b800dbcf94763c4036c48e1ed55a852f433ae540a49692af8bad8e0e96fe2120bd0a9d6afb690800f02a3f67e52c137d62fa587892aff692d409633a4b67ffb0ef09af578da08f506a22fcf0b5a28a0097bfc47a7d5aeb9234d6ff20f9c5e330f405f655d9a0bc07f4e378d4625258e4a4c01435bad36a8b258c6350f989b4bfc50f12173e649db71c04ee6afc50cf1381911daee6168958b49ae083834ff07a2d0b54a404d31da6a8b291502fc7819084ebdf31258e4e9bc1cfb430603a6a4ce90fd561210385861ba5ad5afd335149ccc46dcf4e2253c26505233199d1b47e5c07b1316fc862e31f749693ed483acfd44e3010ccfda3ed75f76b9e490c31866801957c883bea7c8e0a43ddd239fa022e575848954f6e97931429232ad25556500c0d2a8763c473947c08848d8d916f4bfae06af46441cab90910744913e44ad704d0f8ac05a828d48416d7d71a64ad8c854cbe183a0d3cb9cbdb1a21c0006d2904ccc4b8bdac4003c0aae63cda0f1b7fd6ec0eb95a42a0ae9af62db85a7de1e50fbe17c6913c327cdc6a043923e49bfc9738441b4781d18b721b273c50d81d58020b23dbb46f25b58f0c52e5d1de5a553ad7471793379927ad34137c5f0fd3d50ee1f4552f6487ca1f351f240190cf3d5d6e43a7a7aba9f0d42308459f5eb3c9036d3e81c281d04841df28b1a7494645eb2e065c52ae18c10411740b83357daa0864d17d8d8bb94c10b418791fced725c5b7b5b7ecaea18ecf9541803b25bc7b0ee8d21502ad5738141ab9ad86b7f6085332d829805cb2c6875b1227446621790e982a4f2b6860be1798b6af4f1791352041bbcb8268c010631d5eac702f3f8b065e9a0ac257c4037c7f19dfe6220fcebcf157b73a44f2ad0c1d6dbf1846fcd900faf66534c6f8837a60ac757d9fe33b64475bc0e2e20f74c66d62009cca4b130851f224dd98fe95dd8df1937c06f32619667267421a34c692f2459b8ae854820fc3cbdc3d93b13992c46c51b90cd42e5d2a68eae463a21a2fedaa6f0905c368018225497244f8587ad2c0e97722f7df8bdf147125c15575c283c31f68b59cbc0db055eb6559e8f833d1250f113f966a8b0acd5e5dfb3b332bea28f7bc22fc870cf9e17edccf775b9cfdab810f59c50e9bd3a2d2cb57b58282f010cdfe6c0ffb01945beaee7644fd9a3f13249e18d14341f0df4bb2fb6066064d0ae7d32fdd2b0876004534da7126e1ac5237218d4a0e2b30394b64f151187c412002313ddc160b67155961149a3e74bc30ecb5745095b6e170c29113fceaf6245c456b7ea3b90431ef985e2fdbae53d6041966f402eb3034d5ee3a4467f938108bcf862343fc08391554f9a4166831f483eb4bd3881901f01cd2439f950207f258b0da6139f30c6a1a677f52f09af08086cb2528ba0a4564fd8e25621bf71622bc8f6ac3672e0ae0225e52705b94dbd9f1e7d317fdb11495845d162bb941db42f8e9bbb610180eb90faf11f421da0ceace86fd1cb39f1b6955e5e74ddbf5b0acba0147070bc5002bbbb9c2ca9e509667ffbebc92cb61e6a73234d511f11bd4281b75cc3d74790dda92571d2c0c87dcfc91c8a226db9d57466e1df26b28d0625c6416b3a8a7320765ba9c9e9015ac86602330abb698df83f13b02c9074f9b4a9468e77698502006136af7df2c8a5c9c5cd05b8fdd5ca1c5478329dbdfd61f72fb1285305efc03034086fa93d29a9aa324a99f14e418bb19c28915328f76b4db37800b567e0aba0947ba147f9c40afb4a2b9ec286dc34da5401af7592c2a62ea8df67b3c77725c05b3b74230ec19cb1be8e12355760f017c401b366234e03e9571dbf7263fb0ee09e0e2de4192db6a9fe0c863efa8a777e45519212d79849f4447bb534ce39d140f74d1eb0def9276759f4488e007108981d19e8e17d97bea92eaa31812fb386f0f188d27ec02c7278940f374190a515d5eeb62eb76a4f7cd20cbda0d79312e860656e2bc071cc970412d4d387f9efa05eef3d967e97fbca2f4aaecf3c5d5d33401c70058c11bffe73a6f66771129b8830601e85e778a6ecbb2d3a8e756dac2a9067ce364f6503159b94ace7494fcdee6b29c91aca958f6149c47c583693ebba706a79452877de09e3762a5416d8c33df3d2d66b1870f77258d79657ca7f771f503a01bf7ac284269efc81b3f98189b72880bf2f8fb1297ad2d9047ba80eafb1f0a1ef95556cbc8102ab2ec52266d8079d0500d977a9ee886e0144b0c7269291f07d991cb5fa0b73c725c04cc6466728ca25fbd9cfab960ea7429dec482e2045501a4be7089f8812d50fdbc5de8aa076cf34e9e20f38539c6b3474521f3f6bc5106f2cd735f1cc33f4f489d3c170d713d9037fcb103e50da0a3154eb8887a89ed0a665c8886c44058922fe781d771e6db11b13d88646c4f6851a205e14e07e1610b468fdf3ede3d1c779801ac554b2a54f182d2b6b3937a08751c3ebc1a1ea6740256f392b253cb949f67283c537d636de4ba8c4d579eaeadd20885e1d87c3a000436d6ba9fc36774a93df2a39c74d30ba898ae4f9afc45e85f44caf0c6249aab037c5ae7afd958c733da69fedf195985196835c1b4de8a996f64a0da54561abc05c47ffac0efc6d42177293af85e2a07705f23dd2a04f363bf520f5ef1d2e7ea0461950b03da15b78592fffc0301330d9147bd59b2f737a679c31fdd6bd603360d189ea288f3ff9d651cb852f3f1a7c447cdcbb1c2fb42c802f23917513910dd077a39fc87022416f3d4c97bf0a9085dcd9c5cd8cc5f9a83d23ca86451bff2b4e364b98222ae68cd22cd7b87034450ecaa79e3a73d8a74b0615d49fb9f31edd725e250f57f8fc367e0d11486eb469afb7b14d40a035987ecc57f6e4e1004fad4d457bc86a30bb25c7b4d44f23bd564a47130f23ab5a4db6800fa8452925584859f010bbc8b9e8b1377f3edcb75e8878f26ef6e8c16b05f65373dc6bdf6f322fb34b3f377a7aed3909606fa6aef5ca023b63e4ae2d3a97f7e17e82408a862d42d0836ad8b86888a54cc21bfc56af2c72ad069cb67053757cad476497dcd3511bd4e74a87a6408b7d0fa3cac7164671e0a8c4b196c0c56a723443dba5581fe27a8c41ee6911a0f5e561d63607901e694be2ae92f06ce78ed2ef01e197c6eac262f2443f9395e9479cf98d5e92006113c684cdf2db63c38859b0a402c16371d9dcd0db67259d7b53c69d20b0407e63b28d1a67d76cbb547673323ebd9da1d5ebc0d4db2c02b1016afa23551ac1c872778f613104557917d7b95366ad727a767998cb95fc5fa4986ff465d48eba4bcccbb275c945758b084d5cc0eea7ac26a7b9b7895b8d818afb30aca4024c7f9ee2df5dd060295d6bdb1258074840bfcb37b4b0d21a35e5946b875fdd1e615ae17d72df36b321cb29e925206862fc55cb55984d2f4ebdf75819e70d54addbc1484736366eb7302ac7dda6794e3b0ab08187e9b8e8f8a02361f6da057da03243a5934027c5037400c7e8bd54b8dff171e4998a2370611f36e174614801d1d2fcc121bbbf3c553aff1845edde8c699e6ae9a22a9678b2492aadd00a804efb9ac2d609ada1dfa084831b6741f66da10bc66c016d311c38d85de72960b03da2e6e6787a3ac5ff83a5302895f02d07c701ccd8953fd668cff6910f9a43cb0a08330ad043fae162ca08715db62b91d3e028a282726a39f91b400379a337bab45db6b7a28928e4b3b7481aa933aba2e5742131512d0be310d21d17c3e6d893af3e034ba469146189aaaf3b8fa7c9515d85978d6aaeb98e8e50bb14d3eae3a13551cc84ae8f9082013ec85fc125d325989b3251fdf994daf23df67792f061ae21a7bf7370b9efc92f2b5b8fd1a11626754d4af49eec2ffc582f11df2edc73aac94da2372cd1a259aa504bfe4354855981ebca127ebb1e5420a6ec7cf49bf83e7045c960c7401d93b0087e97541743bda8a5823c8cc2e80e0dfd6d955b04286df7826c1c20de5e4496e162a3b98c34d2d8e3e8956481c1b36bc8cc7527e39d65b7257d2bda8fba51d8481c03d9649221c890e3ba3e9c1c8d4376fb8cec4f1e6ca641b2444b4db0ad209bb50af0552051ef55e986d77a043956e54f2857806f56b68cf1c64158e78cb6cc846eb0e6a4e0b8d10a9f3ebbad14a0c5399087349d477dff83ce65dc381131ba439296c473e5538217e0ae10f156a95d9ffc580e1f04bb3fb161d631ceb26cbbef4717f719ce01bbcdf0853a5b33f13c2b1d6788ba4b312d062c52baabab579754af3dcf3f2fabd0231f4657b950156ffcb994496e0da8a27f20f9caf612e6bc7325ae9bd20727f03167d41a1cc8ea13b09d445524e9c02094228986ca07349fffd11124c3a465a09b25a30f461c8b4097013beb2c4158266190534ea523cb3fb2d54f86e1a57050d2dc092b02b5952649a4a6799b887ce84bf403de133415db9f889be184955131ee9f4826b50889b5dc70e0e87b1a28a3d6086492e0a5468dae05963625432574576253333de3110793b4a69143b9a5ded4e38a519d3c8ea86fa4b8ce58907267e6ef95ab83179877a6287ff1f6cd52a9794bfe7afa23ae66c7d0b9fbaa22ded0a05ce2cfb1ae9b3b8817713dd53e2b5afed38953409e661042690fae3bc98beae59f9ad44f28452d26668d3808777eba15df9c865831c5404f22f4f38722f474ce8d159550d0d48339cd0eb08c866972b36372954bfcb682fcc818f03cbc193fdb4c0c5fc09d3eb8e98b8e0c71c41337b8a616c823433e296e4dde16c75f1a5d63da1d0c8a6f86b81d1865e1424aa2feaa9afe079fbbc762e6b9b438a9e7588aec58378ea9ef305cc234fe6627bd1368d01bbb8abf0cbc253f38b632ddb8004c76c65b5a7a800079833e324e53fc405b4717786f41479ee55854d9fdd66ccfc2478b228bb8521e32d204bd55cd8c6f6db5cb7bbb74760016d874edb09301bc6d4a5875149e232b0203b6c69784c1c14c5ee082a52e9796021ce3ef6e360e9b9af92c3db2733da094c5639d60015b11ee06c328c84dbfb36d639a9123f11ad9e22e6b282b0160f92b0c09fd520c4d2921de95f976b62aec948c559e6ab2142a1177cf1c3357f7eab82bf7ecdba4755ae70ac59928b4a70497a6dca95f995706705ddf6d1edfe6d9f1b57b4e3f995e5e72b73e98cb0fca3a6b3923849f82f012d677a2aac907a2854a21df24103dba13bbc81bf318e82299949db9bcbc61b505804c6eb9499d448f494619e6f01e0e188faecb4b35786ac0c6366b09467bccc648b438c097044e5c0292c2abf3271aa7b5f90af005208c884d84163fc08664235b7c892079b4de0f7441dd40ff8c8abeb4ac79ce5a0897bd639ab47871dff53f219dd75b39164c9a5dfc2321d851d5128b3260916f4fa45d42c2ae6b3c81755f8d7e6a2b7d3bc30878f18003d0a5daf1cae94a2286a0b7e218fcce8bbcafe6d8190fa2b896f7f8fd1f3456356d65e7842c5d836a839236d65e7b45ec3f684884385f07d1f7c3b1080285d809094eaa6a11e9c3f13e6b32fc4e364b037ef0f7f7786e0a3c4478873465a0ea542d2a0b65e91f876403ebc3e51bcd4569c8ae1157174f129f405026ab0c49fb16056ae348a50e0e85e1651fac280b6e9ec3c3cce131bfa3985e0a9af27d9e47c90175a4fc8844382b5bec56a004c6a696150c0c543a84d227a12b1ac0171167b853918be999b32805a95e2cffa2a4fd71d08a193eb6a684821087dab00438217ddfb9f2516613ce575dd192737fa7f2bbbba246e338eab72c3ca55ad0dc7d5f0d03d5c62c35a9614a605b2e28fae4fb9aa2a4c2da2db1068082b72d90a97ff635ef324ba28925ca7b9b44bd8d5661fc2fd46eb1f97cc07ade74127bd0bc744d036be6c7e35969c6dd37fe7437bbed5ab9f9d818b358ea8107ec1dfeb02b0243794450282ec332ad705e8b3e89db9225101ef6b30074b886cd49edfa80392503e79eb8a30934716d5a86e965d1b849c764cc84a44d3c643c1d678cbe40541f10ff5f87ce93baeb88f4c2fb2cb11ba895851c894937ec918c7bc3b64410bac08c449b831f870351a12801c0037415e09e66955a031c6abf856ac19b30c0f0c91777e93f12f7dec827e4cca1afc5639cc1def44faa3eb4d2f5d8c9aaa000f34b080326af907232e1ec4568086bb37970d3084dccf27ae9be54c6864991b09adfecc6805622ddc463147b2e32347a7b07779c58d4eb329828a7c59cd58f40cc1447b5ed40e0b81251f17934e0667e47ecd21e14a7d103e41f2959043c5450798deb5355c80cc9b2d35cc8d373fcbd1e80379e15c8100fe35a5721d2abdf60c3a9d048ae5031d61c0936789e7d090009e19ab9153cbb5c86d2c606946f2fe0674ccb026d3ced377bc18719bf3c4ffbdf3bd4eae3801268403138d1ea295e1008366e5d0db756c946dbbab129ea2874f5a57b1c2dab09e995a83bb52592b1505c96c34bd00e1a98a3d52b41890b0e03c73b16a1c47f19fe204c709ae1161250c23aba9be1685f8cbce9f939e77161cfeb62566b7520c416f890621180b9f9701e0a2df22a76962ea13997076ae8dcb3d09e9ef57aa57826404dc228b927f5409270528e2dcf478c003d9902341d684962f4ad32ad2777efbd28c35099cd00204f94b8e09c9db6c8bfec8c3b906b0fae0d8da9b3db85d649e8ce58e531141f205add4ff61d3cfde89e60c4561bd2ac3aa7be53a5c56d1c0f1a3d4086e40a4c501657e862fda9ff30ff866efb450d5e5a74821e037ad15be216436f92e9439430d12bb42ac33188e527781b1c88f9d3fc254847985306b07b7c7fde1303fc64f0615b241434362b5384f0994e2261ce7a14691d3d17595fa108dd98fac04db8f0166e11e6d0694d2384405bb93914090ffbc28c21dfd921c285a23bd6076765308704258bae4e91e8d5487fec7b4778d5a8207c991a4c141ae7073a0be76f3520de50c11f2ca186822fbba7b76eae8ecb52633b84675f36ec2c3639224dc51d309e4f7af68e33767266846900e388765c2dc772180aee4ef6654826582b93ad90c2190b47f5f114e94a4b35241329b46423583e5e0464d293d4d95155604e9220889a2e834a0b78807c4f06cba0fa2900b92392cc724c6a94576b19687a6d70903538815dbd3de11ab39cb792b0f1fc78629ffb34330a5bb259c440930d225a201e72cb93a2848b13fd154ebec28b0f75a0221c54823a5c3405e4514bff7248b01887fe5ca1bdd585974d6f7d2ec1e6675b7d2f886fa6d00d9bd47490a7416ce065768dff30e3af261736b508aae38cfe5beb53bf83acfbc68dd05dc931a305c0734f173b4f4784ec62b54ca97342148a9a48a274278a862cfc8d92062aa774a0bb8f35bcd5ec65125d8c4d76b31a447a3b31a75451111e04aaa89a1119a71c20893d1c9f2e5de30fb0b47c44195005c859d524f49e8a9b24a309b15a158053409db0baacd42bd7484307b0205f66d2d69fbb96d85db273fe8d0d8b5a89cd8eb0eb2318bb1c017a7bee59fee3f964f0f3df0ee923a41c9c543956c58df2be9030feefb2b220021394f52d8f20dd7a4c806409050c80a65d88fc116eed81ae0070e71b230ceb95e532a7eb8748b70523f2435e81eb26ba517d6c04fb64604584801d7762211fd4fd85f7beef9f841581e041d7c90523e08a5f1bb43d22e881f0606433700a09570a04a1422114bff7d6c961c635641a45ed3f20b877c28cb10e30560cdc663db3632f4d685a62250d60baf5697bdb3d0c362fe6488bdb27ae063076474e56ae79eba573ac2e0b727aec88d3ccd6ae2600c78a13c7395a4842a920ce6691537fb2fb3aa69f7bf9f1a32c986438acf418f2cb7ee9e87b0052bae7d0e62b0b26d9ad6fd40a87eff4252d484bc936016a10bb61da5ca62a908b560740da967f27a29f117e0fc2981ea6b092011c7595af49451355a098b9ac5d82df704387e6f68d9f047566ee8a8312079e8218946ecbd2083a8f0469daa4575e5420d29a2d1e796e5a5bd0aa124bbd152907e0cbf3a975ded6f83ff05e6c070168e038f8ae126982a9487ee392c04e79e2a5c9b96c2c2bcb97f937e3849d88fd04500e333cd7d4de4234cee2883a18abaccf7d2154fbc53e0c1f2976bf4ff8a9cce0276c85b6414b48af26e38d904f64ecfc78986f576847d4cfc6a07159af8232d05dde862274bc178447f1c663db422addaca06645da69eca8d01e5d2c3beca100547b3e7592ec5fdfa49b77cd56def7ed5706f370021947adbb1bb8b3da53c7a06c109e4ccdb7467f492b48a4a1bfe10a8cf0dc52c556f2897ea005412b68de60878f936c01b56e7ea8bc6fbcecfa6f3288df369af54dc29dcb401eece3be1da04c84f56671105077e7167396015f9c0f21c46ced1ef59fe6f599ec8de9f7e4a0789d2b87bc1d160229a9ecb5b25e658b46854f7f80cea18b317801ceb02d92004c7076b176a81982ebfeee0b7468c51f536faf2315727864e61c35c4764a4ea018f9d24432391d3094b0130a58f735f3ec36846fe976456464b5b016479c5d00b10683d4c9e2e421216c123ba526e4f2b51ed6ea415b1deec8b38ba09877c9b0d5b00f6f45428d59ac13afa004f5aa8ab5a5a6fa2dd20bde725030e8a65e43700ad271572a075d259b176e298b1113565ff0ee5a1bf8830b768abc3f50fca2f0170edfec0d8c6199249524d1e74c3a9a27591fb4c790cd4a5c1eaa6440ca8b40cffe5a304776c3f20b82dd3cb529d085f104a7650c01bb9cb4c9ba52b5027b10940904dd03338086571fd3605954c5cf493bf8da529f441b68762bd744f378d06fd6688bede99b7da478048ec59f3417dcee761c71c1786d33c0b25558183460337b2a97867167e820376316986c81852f6e6eae379d39ffe8eba6172ca4ed902a90a6479b167fef6215c0032cf948601b5b923713b55c7f8a98a3ac53ac7dc0f21e9b1f3d9563daa9f30b2010c94a1274e8defbfa1b6fa71aff80a34cd0a3d0deafb992836010ef3675a6e42ccc0178a82d7e8bf478397b71dd10a6a74bc5f0324a67a51f9bc45edda1c3324c7304e2a5bb5f97cd4331c3ad785a0dce23ab00f0ffd0571e01c06e289f9ac69c50d468227348b586c09bf57599b58f30340c00b22569fd70111dfd5854191e2f90a5019a0264c0c92ec4979f1a4d35f37ddcf0dac4198c1a44d927809b65e9879c7a415f26ddba29bae4a1a8349b0854763540e41a1d86ecec217720feb185c4c484d27ba92e1fb653161bad3a5bd213c9bf1062db28fedeec2a43ffdafa9ce9faf660aa96159eb2bdba835037f48536b941b0e0ef577864c37324b5c7a0255640fa27dd593b080adcf04f112acbfa5ad8435084473935043a3737e88e083f6ab128b3951a30014790c261db987546a62350e0149cf3f88eea904e0f928697a4daf66ca515ccc9d527d5300ec3626a13321a60f4178c28a0f3b5f962840689928ec31ca8c2cabb2b01f8314a2d185a4d0d9e007556308d8f344da0936e7523b17b68f7fdd3ecc4c3e2358028ce7773dafae520ddf32eabd43590a0e01594ab7a683002f2da88080fa39b38d8123c050b6ce8c0126f1fae6cfdc5856276f00dd1e84f82fcbf7df4040a4d057c038916611155a0db866ae4675a83afc0f7e5a11a1eccb64d5b7b0cc1c4ad03ae0a9a15eb9637807a7fecf6680b7c5686cdb06849cf3e445ffe3eba98a53374729394bf71ff457022d7b43d34100bdc385a2b112b50ee56dd38112021a5d4791aeb2efd78626090a2b90bbcc26096bf1490f20366c280bb7416f2e45e1714cc62f7fb14849a83c0aea84c5c4c315edff67c8b5871b767d89b7185a69e94f36f104e5f759904bba0559f6ec284d2e795e2edd42064fbcc6c3b2f4258f66b96822bd41eb20578c5503057a4df54b7ccfce85c59d9744da23120c93ecc7e9e3adb8f41322932d2717056bcaa5e69d35ca2d3d8ff84c48d425ae922c75e416ecc83d984b3e755273990284d5c62674227de7fad42d6913da3de532170c0e79b77f4ce2c3adef3c5b81054acd91a01451f483a24e84d24a18436088151dcf203cfb3d9d93723277dbe50a39ed03f2a4a0d232eb2aa752f9bef7beb9378f0035a5aa941d97a7927b3c610517cf9665ca5bfb6bcc50c668bdce39740b463cadc57768aa89a5c5c13ab8e80f1a0e0782a061f2ef46de2520b4dc7c26db6fa827a4dec5a7668c20fe530d770ccc28942fd05350a14c33c7e8effb5cfd4d9407a1d21f2290d983ad342055d601e695ba720e6e61b4242ba5d2c8827998c38ee888f7889a50c2cff744fd43a20faa5d4d49be1803c9beca63174b9c8e029ca29ca19342578a7570c4124635c90d05bff8de80f057db39e687a02ab38e1b0d6eff3eeb84b3c7434118f9706a8f0ca65a4a7bcc814016baa1a14807502fa2e5693e9aaa35f58de0c3ce21200b170f7ba771f6c687de02b953573b7ec9f971de49434979cfcf1c56f6c2e5f9cd7506fe4a5ee817c8e73bd14632799f6352aad0aef416241b6ea23b979e4549138808ab9d9263234f8dc477463504e672e2124601ead45eb74a9c29468197517b6e06fda3049f6451d1167b14e45585eeb8c8cca1990ffa9b93553c415755169bb70742f6f347e04cb952bcea8fca8be21b02159786d2222f22156752c586541f4d074b94d8f356381eff83d0a3e856d9c040582b38d52a3fd2887e1a62525fc1ea010ebc5f87f0783768c33ddc050161419913e529723ec3f7ad5865958e7b4f800b1ad30adc1b0c6f6c6ef8d8a420cdb3649ea93fdcd1d0ff10a625d32cf989b30730654580ceb36f6b9929488d957fc863a9d56cd96b5e3f20407c6ca5193e5d059f5fd14a81a81fd01f17f0c1a0096ead8a609c656c9b60bbc32788e8238fc30f28d310b581d120aa5151943dedceb8f636779fda68e74ebc17358dd0f503280679cdcb95a867db5b0f408ef5502779cd4bade1d3b165becf33cbdc556260970fc7768b9fae2e66e5dac4e396555b33d1cf9a50c55ba1cd9802bc315f2930920c16326976d9cfa47d261af4c9adc7d7550aa688efbe23851a19f019728e10110a64d6b90842e26df39d3427aefeedc8c1008fb70c24d7e3cee98b866c182f88057d50c28f1d1c93ceca38d3d943f34f9e8deade4ea6cc32802fad0355094940040cac826a9bcd240b88576f0c5607674ccc636769abe9192821d9591e1c45b70e34a1af9d722d6c270d7738d494088aa354e25ba9dd32db91f06c919ca55bec014708f224af3afca8d127ebf908f4f74d484431cfccfef32b6cb50a83a739a206bd1a8122abe4f7003c1c3c60e13586f8cbf4f54ded5f7485439dd8e6a81eee06fb5004c8b51688ff3db6b8f216cc02747e65b5e15c6b560d880387b42ebe4b015482e18b4a41008bf857189fde5d78e1585c919bd37b100e8c488a019d87d209e78badaf831aa31f05007b66d0680085afb1536c5731083716deed5b57d46204eb79a8c75db63c0ac133a15bdd01afb8f6e104788af43b1fb7e4a3a3612b72024e2315fe43f9d5c77758f86a927b19784c01897b563faac1fb4e92665aa786e0df248e715cca8b3a620e8a49b36d26329c484c0f99d3fbca372925a182e63958112e526cab4032ee7e1b833a677b483a4769602093f8e1a2d68c8c7dfdf7c0be02934066ccd3e204f82f6fb152fc5479758bcb6ae9d355f4bd1a8dd064a16b23b36f0ce2d3dcaa9e97118ce0f1a8bcb008b20e9a04489a9ac9f31b82a7c14823dfc4ab9916aeca716602b4bd0bb1fc03b04cd6f228c4662f8ef52139dbb43005bd0834dd910d2c0a85d7dcacec54068c0348596c47ca52c3b5ed6f80bc58f01f2a1d0aa9e40af65d1a6e0222ec12d0c098c0cf923f2b880e884045597a21018ee63d3e9773190042ec9db01687b114d939d3aa7e6bdefc8fad80f8a6fa715ab57336c915ed0727098abaf04b4956840c34e83f31a38809009a0ce2df91979f2a874bba9f09ca9ef7354fcce71facf8fe49607a182f83d8cbd06ca6e9b9e38c0fc0f630e955d6922286e84417d662e4d71748a1c59b44efebf3f791de276e5593c744fc6ad8b7ba5c523533dd8f7d413ef8731b90033d1a51cb185fad1908f9690e2099109125f14987826afacfe952b6567db33dea01ac10d4856984956d319c5c27d77b26e1be955d14112d1049e88e031fbdd1c6a262fdf5d39c466f6da0f591b9735c677531acbd2eb2e65d1e8a36bed9d853d78ad6d6cf0ca170e65500c1e3c16e3766d589d4c57e7fe29059734520ded0acc17b80fd232064d82fe3b7dfbbf3498a4ca1d7c9eb99dac1f19c2e3ecf738f8ffdf738b68fbcb9e84ea1ea8857998c40e24a693c766e41e93917bb9ab63efe1381ee2db5bfacb277d5376cd1dd9225d56af79a3969faaf9edf91b080c178d9c09ebe3297b6b52f6c7d1528d61163819dd427375897f5efaee3074d0a3288f93bbbc29347e5241f80c42a3fe0e20baf3beb488664a81768d108535836036d186028506750d65f361d7e10f6b13f3d8c27464fa5147e6a56f99376a794d3efe935b39ec48c3ad70fe9fefd95ebea1215ad75aaaa2bb449e6d3b957a77eb91d82d602444db931ad949d8fde07f19bd6f7ddb4643c14fd4c22d343af49b4ca3d92b0cb71f438d6c55c541a0a92f21cce569e2a59cc63c693adee884c2b373abcd3f2641b567f234f44b7c0dde313be9973d5579cba19d857cc2e6bc4caa17436ac666bfd77f67325caa762297f7036c5875ab62fdbcdf318d99cb29b7577093e2360ade2f7a16e8bd946cea6edee81dc0a8835e86fc288074f6a02b5e456c0ac943b7fb720147697bf7688cc7dd2f9733caf3e9a8430df781ce0ab51bfbcd89bee67d5d51526d95b72914bd696e918276796fa24c6af70180e84dccaf2d7478222ec1f9e649502b8e783da7b2fc997e4f424fab9ba0911da9be4341c9192cdf000ea272d198d4eeface456be924cc4a848ce197b94afeed6d519bcc1842f7fcda898323a791c588dc823d6e67c3cca9bd344d443d285729c205c8d9668e1b8adadce46fb7a06ea487abd2078d216c1ff3c88399d68704451184c6da2b97ae285a780ffab8ac0999d4c98308b1a8704be621a587823fb1f1c0bdcaaa6c01a53f4a66d6b361a39bbd63cbe5cea9c9bf0c1d9da08994b20d6f914efa8b2e2aadbdc156d889a78d4c1e5cc0dbd33b30332ebf63d23a06467c12d81f86043411a52bed3704e92fe825008dd693bf73dd2ff84fe8bfd3fc42b2ec88ce11622d1049f40ddd3cbec508d37dca53ea5f43e3840ef18797eb31e3cdc231c5c038cde840b48feb7c7aa058d91c027a09355c50b66b46f360c7c916c2c14b4d62517fdefa868a7d075748d4ea6d3cc5b6ad953fe533365bdcb6674268cd006304cda6683c7c979a28b3a28f6118c04294d66f08f1e08d06e4ec4d4a923adada631042d8385905e47526b4e551d441ea431b0efbe720bf2862489e65a9fc541af6c9036a0d9fad0c43f4d9cdca26bb4bc88822984fd8f8236aca384e7d61489cfdd3d2ef93a3e77c06b00e1f49c5ce2b17d95fcf06c1317ea3d2e79d7048c216faacb3ecd318460ced8726c274a81333575c903e19544e16209b9fe3d1e63fdbfd1cafa741490e7330fd83b7f6175bc95c31cd67ca0e970a1ee33a3fafedc811e449da05e256b437d1a8edab2b25f731169af331be11e5ab8e3b58abf866026ebbcb632ebb2227a710a6b2b5ee496fed713c2ba83947d8536604d74736da4bffa74fc6f02e6891d0ce7fae4000c052e5a9520eaa6ef68e08e4521c074ec99a2b4dadee50749496df37002a05b820eec702f94a58ab6ea65949a3fd58af6e9f12d18b2044bf9863b56cf9ebc514078c85572d2f8d13e57ffb235b546644399f0d1f3f42ad4194998c31b56758832ceae226e7f9764e84da03ca837cc7e9ba741e42a3687192633a362444a15a852f1fcc63d2808023a97cd7c4357c2868b39494b69229ddaf1db1247ab75c6ccf7bbcbb07a36daa8b89a5488197e0f16f3d6577bd45eaf8b841b7fcbf1e6f0530784fd2e0ed8d3e29e24a51348393c2bce7b968c5e67a3397d4379de5bb23d508c22271cb87d5ce0a232b2389f920b32a52398217629a16c01bfc626302acf08df8bccc725ea81a97e6cf5711a35ef9ad16d548888f62ac6631b8022656622802bac45a276aee18ba8e2a97b741818059ecb7dbceacf664a4bc8512e7b16938485ab9f7af85e90fb81abf743c3e3bb4370ac6799d99257819dde80760ef81f12b7740e2968cf26863340c2f9c0662222e2404c02726e6176de274cb60eda7d0bc3085a06afc50122fc3798268c6d15c5f3a8ef9857a607c234169e23a8c2883d18a931b4f2407ac1f3a1f12546f0bd824b59959efbb32fc6764a8c8869721566bec91249c677d704027c94348fd9c8f38ba981ccfcf319933d0da105ba9af80cc4ebbb62c628be0a7283364a543e2c6094cc5ecd06ac231b50b60e6d31c68413c5c2f1069cfeed076d0b0dfd76177ad8a057a10a6f84956bd87c57e46d64a75106b2dc2965264402da33281bad691ab86dd35f8e78806100d0b79707c910adf4223e7b0b886a0b0e83d350f62d2eab1ee8ce8c56ba6ab86eb5e341a1d77c2180c6fd2cd5a887a5071779f04ab4201aa8b378f17e449e7bd6a43d9cadbcc8f956cdc15955b115b908aa0667b1e02827f2bce65e29aa933ec07cf111043f7187bd92b29b7a2597c80eace7e56a49c61ff394df56483e6481bd3dbe410d16589c2059a6abcd8b49af06447ad1d600b1e06abb20062a72f6d4f260cb98fdb22c477488f0f92b046ea50172eaa25f2a2a57d49275ba0464054cd61965285e6b625b51df05d76cc375200859881d2027945595fcaccbaaf1d635455315913e9a9857051bf81e7092febc0d775800f74c05f606371c104921867bc4ed094459491a533b7dbf792354a16a0c12a91711c4708e55270af81a5e27edbe0999088475049195a60f4c170093a701" + tsx_bin = unhexlify(tsx_hex) + reader = MemoryReaderWriter(bytearray(tsx_bin)) + ar = xms.Archive(reader, False) + + msg = Transaction() + await ar.message(msg) + self.assertIsNotNone(msg) + self.assertEqual(len(reader.get_buffer()), 0) # no data left to read + self.assertEqual(len(msg.extra), 44) + self.assertEqual(msg.extra[0], 2) + self.assertEqual(msg.extra[43], 199) + self.assertEqual(msg.version, 2) + self.assertEqual(msg.unlock_time, 0) + self.assertEqual(len(msg.vin), 2) + self.assertEqual(len(msg.vout), 2) + self.assertEqual(msg.vin[0].amount, 90000000000) + self.assertEqual(msg.vin[1].amount, 7000000000000) + self.assertEqual(msg.vin[0].key_offsets, [0, 45, 68]) + self.assertEqual(msg.vin[1].key_offsets, [5, 79, 38]) + self.assertEqual(len(msg.vin[0].k_image), 32) + self.assertEqual(msg.vout[0].amount, 0) + self.assertEqual(msg.vout[1].amount, 0) + self.assertIsNotNone(msg.rct_signatures) + + self.assertEqual(msg.rct_signatures.type, 2) + self.assertEqual(msg.rct_signatures.txnFee, 26000000000) + self.assertEqual(len(msg.rct_signatures.pseudoOuts), 2) + self.assertEqual(msg.rct_signatures.pseudoOuts[0][0], 161) + self.assertEqual(msg.rct_signatures.pseudoOuts[1][0], 229) + self.assertEqual(len(msg.rct_signatures.outPk), 2) + self.assertEqual(msg.rct_signatures.outPk[0].mask[0], 0x8f) + self.assertEqual(msg.rct_signatures.outPk[1].mask[0], 0xfd) + self.assertEqual(len(msg.rct_signatures.ecdhInfo), 2) + self.assertEqual(msg.rct_signatures.ecdhInfo[0].mask[0], 0xf6) + self.assertEqual(msg.rct_signatures.ecdhInfo[1].mask[0], 0x85) + + self.assertEqual(msg.rct_signatures.p.MGs[0].cc[0], 0x17) + self.assertEqual(len(msg.rct_signatures.p.MGs[0].ss), 3) + self.assertEqual(len(msg.rct_signatures.p.MGs[0].ss[0]), 2) + self.assertEqual(msg.rct_signatures.p.MGs[0].ss[0][0][0], 243) + self.assertEqual(msg.rct_signatures.p.MGs[0].ss[0][1][0], 2) + self.assertEqual(msg.rct_signatures.p.MGs[0].ss[1][0][0], 114) + self.assertEqual(msg.rct_signatures.p.MGs[0].ss[1][1][0], 109) + self.assertEqual(msg.rct_signatures.p.MGs[0].ss[2][0][0], 218) + self.assertEqual(msg.rct_signatures.p.MGs[0].ss[2][1][0], 131) + self.assertEqual(msg.rct_signatures.p.MGs[1].cc[0], 0x12) + self.assertEqual(msg.rct_signatures.p.rangeSigs[1].Ci[0][0], 0xeb) + self.assertEqual(msg.rct_signatures.p.rangeSigs[1].Ci[63][0], 0xfc) + self.assertEqual(msg.rct_signatures.p.rangeSigs[1].asig.ee[0], 0xe7) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/unittest.py b/tests/unittest.py index 508104188..0c52a08da 100644 --- a/tests/unittest.py +++ b/tests/unittest.py @@ -126,6 +126,15 @@ def assertRaises(self, exc, func=None, *args, **kwargs): return raise + def assertListEqual(self, x, y, msg=''): + if len(x) != len(y): + if not msg: + msg = "List lengths not equal" + ensure(False, msg) + + for i in range(len(x)): + self.assertEqual(x[i], y[i], msg) + def skip(msg): def _decor(fun): diff --git a/vendor/trezor-crypto b/vendor/trezor-crypto index d454a48b5..9cad61371 160000 --- a/vendor/trezor-crypto +++ b/vendor/trezor-crypto @@ -1 +1 @@ -Subproject commit d454a48b5169fddacd169e6ca4124b69449501c9 +Subproject commit 9cad613717b3ba0122c4cc075d7395fbec39fe27