From 6724e4e40997c417378d22081b2a1b2fc7e61357 Mon Sep 17 00:00:00 2001 From: Dusan Klinec Date: Mon, 6 Aug 2018 14:07:14 +0200 Subject: [PATCH] xmr: master merging commit templates: build style: correct math in comment [260130c1] xmr: show address fix [abd27e6c] xmr: reorganize module structure [cd9e5a5d] xmr: simplify layout code [a5b56f17] xmr: monero.crypto test fix [60bc30ec] xmr: minor fixes [f82bd9c0] xmr: use trezor.utils.ensure [adf119ac] xmr: get rid of xmr.common module [3531a42f] pipenv: temporary fix attempt for travis - until pipenv bug is resolved [d172f86f] xmr: protob messages refactored [e83085ea] trezor-common version bump & messages regenerated xmr: mlsag_hasher simplified [feb5f1c6] xmr: simplify key_image_sync workflow [d4cb0084] xmr: tiny note in README and typo [62411cde] xmr: readme updates [ff15b46e] xmr: rename and order mlsag functions [3fb57dab] xmr: mlsag notes [c27ae90b] xmr: output index check added in step6 [973c457b] xmr: grouping is mandatory [684c7e19] xmr: range sig grouping check added [012ca766] xmr: small refactor in borromean range proof [1ba72b65] xmr: move range signatures to seperate file; rename mlsag2 to mlsag [a89f3ab0] xmr: fix wrong annotation in modtrezorcrypto [8303b42f] xmr: state's use_simple_rct and use_bulletproof modified to enums [276712a9] xmr: re-export most of functions in apps.monero.xmr.crypto [74165457] xmr: out_pk_masks changed to out_pk_commitments [1e186727] xmr: state comments [3c69a2e1] xmr: TrezorTxPrefixHashNotMatchingError note removed only concerns multisig [5af0feae] xmr: master merging commit xmr: step 10 review [d8e99375] xmr: step 09 review [a510150d] travis: workaround form 6a0ea22b6eb3bdc9bce62d7c88fb8841ef8fdb0c [03d27115] mocks: regenerate [bd24bb35] mocks: add support for entering the global scope [f75c1902] mocks: regenerate [bce85968] modtrezorcrypto: define mock package [7c077521] mocks: support package definition [b3f1017b] xmr: step 05 and 06 masks and range proofs review Masks are now always generated in step 5 and stored in state. Range proofs were reviewed only in a high-level manner and will be reviewed later. [67f391cd] xmr: step 08 review [673bf01f] xmr: steps 04, 05, 06 (almost) and 07 review _range_proof in step 06 is still to be reviewed [24c52517] xmr: simplify serialization, remove Archive [896cdebf] xmr: redundant exception removed [cb3813aa] xmr: serializer simplified [471213b4] xmr: serializer flake8 fix [9d4df17f] xmr: aescbc not needed [52dd8b3b] xmr: serializer - erefs kicked out [9e3be78f] xmr: sign cleanup, comments - state cleanup, comments added, unused code removed [164a7d6a] xmr: sign step 09 - fix in_memory artifact, dead branch [8fa4066d] xmr: sign step 03 - permutation length check added [55a593aa] xmr: multisig removed from protocol and functions [6470678d] xmr: black styling [02664401] xmr: step 03 review [daf7b7d6] xmr: step 02 review xmr: adding agent tests to travis [c752866f] xmr: unused imports removed [85115fd9] xmr: serializer test fixed - removed unsupported messages after serialization simplification [60874751] xmr: sign step 06 - comment on bulletproof hashing - hash_bp(bp) != hash(serialize(bp)) because hash does not contain array lengths [4abb5473] xmr: sign protocol - multisig logic removed [813cb3a0] xmr: lite protocol removed - backup left in xmr-total-full-with-lite branch [e5f5b5bd] xmr: serialization slimming - base types reduced, not needed for now - some int serialization methods not used now [fb515aaf] xmr: serialization - archive simplified - simple parameter passing, no kwargs - unused methods removed - reader/writer passing removed for archive methods [ec4c4adb] xmr: KeccakXmrArchive simplified, no archive used - getting rid of container_size. We dont use containers with fixed size so this special case can be abandoned. - KeccakXmrArchive is lighweight without need to touch main serialization [90065bd4] xmr: serialization - serialize_archive removed - custom serialization routine is not required at this moment [b98c2f89] xmr: extra serialization refactored, manual serialization - extra is serialized manually to reduce serialization overhead - extra contains simple structures now: - payment ID = already serialized manually - tx pub key = easy to serialize manually - tx additional pub keys = serialized manually with little effort, more efficient memory usage [8ce28a54] xmr: state 6 - provided tx keys removed - needed only in the multisig scenario which is pruned now [8d827f46] xmr: PreMlsagHasher pseudo out hashing fix [49e552da] xmr: redundant import removed [4199943d] xmr: KeccakXmrArchive simplified [69bbf5fa] xmr: PreMlsagHasher - KeyV import removed, comment added [9a194fa2] xmr: step7 - manual hashing of Extra [d8a09283] xmr: PreMlsagHasher state load/save removed - not needed, state not serialized anymore [d5f43fa4] xmr: serialize reimport removed - complex types are not surviving protocol boundary anymore, no need to fix hierarchy problems due to unimporting [3b045612] xmr: serialize - TupleType removed [b9a56981] xmr: serialization schemes simplified [a59dbb8d] xmr: HashWrapper removed [d0d1f05b] xmr: step 01 cleanup and comments [8f7a7782] xmr: isort, black, flake8 fixes [14265eb2] xmr: getting rid of CtKey from the state - only lightweight objects are kept in the state. CtKey is import heavy object. Each set_out call locally imports a new own version of the Ctkey which causes a memory leak. [c0cfc203] xmr: set_out minor function call fix [c11c4686] xmr: sign_tx unimport optimization to reduce fragmentation [df0a1dfc] xmr: range_sig allocation reordering, large chunks first [63cddd5f] xmr: remove misc.StdObj [8c8e3f33] xmr: proper memory usage in workflow [9be1e0ac] xmr: typos and renames [87f718b5] xmr: back to flat workflow [64751339] xmr: refactor builder to seperate steps - lot of work to be done, but the general idea will probably stay - the messages workflow works, but the signed tx was not accepted by daemon, so there is a bug somewhere - additional cleanup/refactoring is defintely needed [14b0a851] xmr: iface modified to layout [27d568e1] xmr: extmod refactoring - *_into removed, replaced by generic methods - point_add, point_sub added - code cleanup (+1 squashed commit) Squashed commits: [fbe39496] monero support added Squashed commits: xmr: hmac/enc keys removed from builder [41028dff] xmr: unused function removed [358573e3] xmr: PR comments fixes [4abf9dc9] xmr: test fixes after refactoring [192785ad] template rebuilt [57a1f25e] xmr: wrapper protocol messages removed [6f40ce17] xmr: trezor-common version bump & sync [03e71de2] xmr: check input permutation [6fc8b0ec] xmr: code cleanup, refactoring [85ecc151] xmr: crypto code cleanup [20b4113c] xmr: chunked bulletproof vectors - workaround for the heap fragmentation problems [66786f9d] tools: enable to reset class level indentation for mocks gen [dc6f84a9] xmr: extmod-monero comments added, for mocks [b1d4ab11] xmr: code cleanup [447a8625] xmr: tsx confirmation raises exception on cancellation [00dd8f6c] xmr: protocol optimizations removed, flow unified - in_memory optimization stored tx.vin parts in the memory which enabled to skip roundtrips with permutations and hash_vini. Optimizations was removed so the protocol flow is unified among inputs, independent of the tx specs - many_inputs: optimization stored spending keys for UTXO in memory, now it is offloaded in the encrypted form. [ea69c7ad] vendor: trezor-common version bump & pb sync [5d81c2a8] xmr: manual BP serialization - more memory effective as the memory is critical in the range proof section [d64bda7c] xmr: range_proof C-impl deprecated - using now partitioned implementation in Python, which is also quite fast and easier to maintain due to allocations and buffers. [18604e0f] xmr: borromean range sig generated by partitions - overcomes heap fragmentation problem [65a5116b] xmr: comments removed [ca2bd0cc] xmr: auto-generated intelliJ param comments removed [a75ef32e] xmr: code cleanup, heap fragmentations fixes Squashed commits: [d2ac2eb6] xmr: addr cleanup [7e4c1a9c] xmr: code cleanup, heap fragmentations fixes [93af8afb] xmr: refactoring, typing, comments [28df8662] xmr: comment fix [8b4f4d9c] xmr: serializer test fix (+34 squashed commits) Squashed commits: [823ee192] xmr: crypto comment cleanup [6debfb6b] xmr: ring_ct cleanup [759f52bf] xmr: tsx signer code style, hintins [0b175bc9] xmr: tsx builder external state removed [fee4a5ad] xmr: builder state fix [92736fa7] xmr: sign_tx unimport [a570ecb9] xmr: misc code cleanup [4a496bb7] xmr: hash wrapper not needed in writer [fefdb834] xmr: signer serialization improved [8fa6eec9] xmr: signer mem clean [66c53fe5] xmr: isort [6996bd90] xmr: black [59915a85] xmr: tsx input serialization refactored [326af131] xmr: msg dump with prefix [6e39801d] xmr: manual serialization of tx prefix [9e5e047a] xmr: manual serialization improvements [d07cee60] xmr: manual serialization of txout elements [8d56c80e] xmr: TxOut custom serialization optimized II [c19ba122] xmr: TxOut custom serialization optimized [ce0d9b0b] xmr: TxOut manual serialization [44e38342] xmr: sing_tx unimport [61ac61be] xmr: lite log trace rename [176b4275] xmr: de-async overhaul [89ae3bad] xmr: diag style [5ccb2fbb] xmr: wrappers cleanup [aa86fb19] xmr: py3 only inheritance [8031b1b0] xmr: builder, log_trace -> mem_trace for clarity [25bf70d7] xmr: debugging logging only in debug mode [c7c8d3c8] xmr: iface cleanup [b0373396] xmr: lite debug only [b1f6ce04] xmr: diag only in debug [de7d718b] xmr: tsx counter removed [76729beb] xmr: tsx_sign removed [c6e6ffa8] Merge commit 'ba500bf4ec1ef9cd953bdf5a47888c5226db8d0b' into xmr [ee97ef9f] xmr: minor code cleanup xmr: black xmr: minor code cleanup [bae3ecac] xmr: bp comments [5e812e6f] xmr: sign - mem_trace, pydoc [7216a8c6] xmr: pydoc removed [e87365f4] xmr: layout cleanup [8d21d82e] xmr: redundant constructors removed [9aa82bed] xmr: redundant comments removed [9b926d6c] xmr: preludes removed [bc9e77f1] xmr: readme update [cf62047a] xmr: aggregated bulletproofs + rsig offloading xmr: change idx fix xmr: iface refactoring, integrated address (+5 squashed commits) xmr: layout pagination refactoring xmr: addr - integrated address pb: sync vendor: trezor-common version bump xmr: style fixes xmr: handle sweep tsx correctly - handle dummy change address correctly xmr: integrated address generation build: fix after trezor-crypto version bump xmr: new protocol dispatch handlers xmr: slip0010 [43cf4c3c] xmr: comment fix xmr: extmod pointer aritm fix xmr: _into api unified, result is the first parameter xmr: bp cleanup xmr: scalar nullity test fix xmr: msg registration improved - lite protocol optional - diag protocol optional xmr: unused imports (+33 squashed commits) [b4d045ae] xmr: bp - noqa flake8 false positive [2c79d4be] xmr: isort [8b9d2835] xmr: code cleanup [eb7496e9] xmr: iface - shorter timeouts for faster tests [59520b63] xmr: ringct comment [6b16088e] xmr: signer - comment fixes [a08958e2] xmr: simple and bulletproof condition fix [4e0289a9] vendor: trezor-common version bump [de472e5a] xmr: black [234d2249] xmr: lightening, fixes, KeccakXmrArchive - builder keys - unload mods before memory intensive operation [abdec665] xmr: sign_tx logging [989d8687] xmr: serialize lightening [7d61f056] xmr: tsx sign refactoring, lightening - wake_up state restore - minimize import weight [3a0daa8b] xmr: serialize thinning [65ad1d2e] xmr: serialize thinning [501221d5] xmr: bp - thinning [3d980377] xmr: bp - generalization with proof_v8 [10d11d60] xmr: extended rsig - offloading protocol [a8f5caa2] xmr: crypto - rsig params fix [f5e130b8] xmr: crypto - inv8 [dbc3f9d8] xmr: rsig pb sync [5748a13e] xmr: bp - data for bp4 fix (+18 squashed commits) Squashed commits: [5bcd54e3] xmr: bp - black [e93e97dd] xmr: bp refactoring, large memory optimizations - memoryview in __getitem__ requires new memory allocation so the refactored version uses to(), read() methods that can operate directly on buffers without need to create memory views. [c30745ae] xmr: bp - black [f5c40699] xmr: bp - tests extended [8dae75de] xmr: bp - get_exponent optim [3e59ff80] xmr: bp - precomputations for 4 statements [d1d2e29c] xmr: bp - gc.collect [1bb6b5ba] xmr: bp - optimizations, streamlining [2a2b0cb7] xmr: bp - verification in log(MN) memory for 1 proof - not allocating MN vectors - sequential multiexec added for memory efficient verification - bulletproofs: maintain -z4, -z5, and -y0 to avoid subtractions [8276d25] - bulletproofs: merge multiexps as per sarang's new python code [acd64d2b] [75aa7dee] xmr: bp - memory optimization [a10d05a9] xmr: bp - deterministic mask generation init [5060d6a7] xmr: bp optimizations [dd69eb1b] xmr: bp - black [19f0f64b] xmr: bp - optimizations, power key vector [2ba63f8c] xmr: bp - minor cleanup, optimizations, scalarmultH [31c9ca2d] xmr: bp - mem clean [3fc2c793] xmr: bp - memory save [5b16c9cc] bp: black [f1040c97] xmr: crypto - memory leak fix [ff863510] xmr: iface - flake [6ebf69c2] xmr: lite - flake8, black [eee55d62] xmr: bp - memory diag [2767009b] xmr: bulletproofs upgrade, mainnet version, cleaning [be6ebbd5] xmr: lite protocol [d603e96d] xmr: pb sync [5da15da9] vendor: trezor-common fix [0373b97e] xmr: iface - output confirmation split, subaddr fix [2cf32176] xmr: monero - subaddress fixed for index (0, 0) [3bb8f08b] xmr: enc.aescbc added - for lite protocol (+1 squashed commit) Squashed commits: [011dbaab] TMP: trezor-common on master, crypto on ph4 - trezor-crypto on ph4r04 fork as it has all required stuff - Lite protocol not merged in master, thus does not work in the PR [795b34e1] xmr: get_address fix [2d39c90c] xmr: bp - import fix (squashed commit) Squashed commits: [2d5c6cee] 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 [5cf77a4c] xmr: monero support added [for review] depends on PRs: https://github.com/trezor/trezor-crypto/pull/162 https://github.com/trezor/trezor-core/pull/286 --- .travis.yml | 9 + Makefile | 3 + Pipfile | 4 + SConscript.firmware | 4 + SConscript.unix | 4 + .../modtrezorcrypto/modtrezorcrypto-aes.h | 2 + .../modtrezorcrypto/modtrezorcrypto-bip32.h | 2 + .../modtrezorcrypto/modtrezorcrypto-bip39.h | 2 + .../modtrezorcrypto-blake256.h | 2 + .../modtrezorcrypto/modtrezorcrypto-blake2b.h | 2 + .../modtrezorcrypto/modtrezorcrypto-blake2s.h | 2 + .../modtrezorcrypto-chacha20poly1305.h | 2 + .../modtrezorcrypto-curve25519.h | 2 + .../modtrezorcrypto/modtrezorcrypto-ed25519.h | 2 + .../modtrezorcrypto/modtrezorcrypto-groestl.h | 2 + .../modtrezorcrypto/modtrezorcrypto-monero.h | 1221 +++++++++++++ .../modtrezorcrypto/modtrezorcrypto-nem.h | 2 + .../modtrezorcrypto-nist256p1.h | 2 + .../modtrezorcrypto/modtrezorcrypto-pbkdf2.h | 2 + .../modtrezorcrypto/modtrezorcrypto-random.h | 2 + .../modtrezorcrypto/modtrezorcrypto-rfc6979.h | 2 + .../modtrezorcrypto-ripemd160.h | 2 + .../modtrezorcrypto-secp256k1.h | 2 + .../modtrezorcrypto/modtrezorcrypto-sha1.h | 2 + .../modtrezorcrypto/modtrezorcrypto-sha256.h | 2 + .../modtrezorcrypto-sha3-256.h | 2 + .../modtrezorcrypto-sha3-512.h | 2 + .../modtrezorcrypto/modtrezorcrypto-sha512.h | 2 + .../extmod/modtrezorcrypto/modtrezorcrypto.c | 2 + mocks/generated/trezorcrypto.py | 586 ------ mocks/generated/trezorcrypto/.mock-generated | 0 mocks/generated/trezorcrypto/__init__.py | 312 ++++ mocks/generated/trezorcrypto/bip32.py | 118 ++ mocks/generated/trezorcrypto/bip39.py | 38 + mocks/generated/trezorcrypto/curve25519.py | 20 + mocks/generated/trezorcrypto/ed25519.py | 50 + mocks/generated/trezorcrypto/monero.py | 313 ++++ mocks/generated/trezorcrypto/nem.py | 13 + mocks/generated/trezorcrypto/nist256p1.py | 40 + mocks/generated/trezorcrypto/random.py | 19 + mocks/generated/trezorcrypto/secp256k1.py | 40 + mocks/generated/trezorio.py | 3 +- mocks/generated/trezorui.py | 28 +- mocks/generated/trezorutils.py | 12 - src/apps/ethereum/networks.py | 14 +- src/apps/monero/README.md | 321 ++++ src/apps/monero/__init__.py | 12 + src/apps/monero/diag.py | 114 ++ src/apps/monero/get_address.py | 17 + src/apps/monero/get_watch_only.py | 16 + src/apps/monero/key_image_sync.py | 108 ++ src/apps/monero/layout/common.py | 121 ++ src/apps/monero/layout/confirms.py | 125 ++ src/apps/monero/misc.py | 21 + src/apps/monero/sign_tx.py | 139 ++ src/apps/monero/signing/__init__.py | 60 + src/apps/monero/signing/offloading_keys.py | 130 ++ src/apps/monero/signing/state.py | 146 ++ .../signing/step_01_init_transaction.py | 375 ++++ src/apps/monero/signing/step_02_set_input.py | 171 ++ .../signing/step_03_inputs_permutation.py | 42 + src/apps/monero/signing/step_04_input_vini.py | 73 + .../monero/signing/step_05_all_inputs_set.py | 58 + src/apps/monero/signing/step_06_set_output.py | 440 +++++ .../monero/signing/step_07_all_outputs_set.py | 171 ++ src/apps/monero/signing/step_09_sign_input.py | 192 ++ src/apps/monero/signing/step_10_sign_final.py | 43 + src/apps/monero/xmr/__init__.py | 0 src/apps/monero/xmr/addresses.py | 86 + src/apps/monero/xmr/bulletproof.py | 1583 +++++++++++++++++ src/apps/monero/xmr/credentials.py | 45 + src/apps/monero/xmr/crypto/__init__.py | 299 ++++ src/apps/monero/xmr/crypto/chacha_poly.py | 40 + src/apps/monero/xmr/keccak_hasher.py | 27 + src/apps/monero/xmr/key_image.py | 111 ++ src/apps/monero/xmr/mlsag.py | 320 ++++ src/apps/monero/xmr/mlsag_hasher.py | 112 ++ src/apps/monero/xmr/monero.py | 261 +++ src/apps/monero/xmr/networks.py | 51 + src/apps/monero/xmr/range_signatures.py | 144 ++ src/apps/monero/xmr/serialize/__init__.py | 27 + src/apps/monero/xmr/serialize/base_types.py | 36 + .../monero/xmr/serialize/int_serialize.py | 109 ++ .../monero/xmr/serialize/message_types.py | 163 ++ src/apps/monero/xmr/serialize/readwriter.py | 106 ++ .../monero/xmr/serialize_messages/__init__.py | 0 .../monero/xmr/serialize_messages/base.py | 31 + .../monero/xmr/serialize_messages/ct_keys.py | 24 + .../monero/xmr/serialize_messages/tx_ecdh.py | 5 + .../monero/xmr/serialize_messages/tx_full.py | 11 + .../xmr/serialize_messages/tx_prefix.py | 102 ++ .../serialize_messages/tx_rsig_bulletproof.py | 21 + src/apps/monero/xmr/types.py | 6 + src/main.py | 2 + src/trezor/crypto/__init__.py | 1 + src/trezor/messages/MessageType.py | 10 +- src/trezor/messages/MoneroExportedKeyImage.py | 3 - src/trezor/messages/MoneroOutputEntry.py | 6 +- ...{MoneroRctKey.py => MoneroRctKeyPublic.py} | 8 +- .../MoneroTransactionAllInputsSetRequest.py | 14 - .../messages/MoneroTransactionAllOutSetAck.py | 7 +- src/trezor/messages/MoneroTransactionData.py | 13 +- .../messages/MoneroTransactionFinalAck.py | 2 +- .../messages/MoneroTransactionFinalRequest.py | 2 +- .../messages/MoneroTransactionInitAck.py | 19 +- .../messages/MoneroTransactionMlsagDoneAck.py | 19 - .../MoneroTransactionMlsagDoneRequest.py | 7 - .../messages/MoneroTransactionRsigData.py | 32 +- .../messages/MoneroTransactionSetInputAck.py | 12 +- .../MoneroTransactionSetInputRequest.py | 5 +- .../messages/MoneroTransactionSignInputAck.py | 5 +- .../MoneroTransactionSignInputRequest.py | 14 +- src/trezor/utils.py | 44 +- tests/run_tests_device_emu_monero.sh | 22 + tests/test_apps.monero.bulletproof.py | 440 +++++ tests/test_apps.monero.crypto.py | 238 +++ tests/test_apps.monero.serializer.py | 158 ++ tests/unittest.py | 9 + tools/build_mocks | 21 +- travis-install-libsodium.sh | 21 + vendor/trezor-common | 2 +- 121 files changed, 9888 insertions(+), 760 deletions(-) create mode 100644 embed/extmod/modtrezorcrypto/modtrezorcrypto-monero.h delete mode 100644 mocks/generated/trezorcrypto.py create mode 100644 mocks/generated/trezorcrypto/.mock-generated create mode 100644 mocks/generated/trezorcrypto/__init__.py create mode 100644 mocks/generated/trezorcrypto/bip32.py create mode 100644 mocks/generated/trezorcrypto/bip39.py create mode 100644 mocks/generated/trezorcrypto/curve25519.py create mode 100644 mocks/generated/trezorcrypto/ed25519.py create mode 100644 mocks/generated/trezorcrypto/monero.py create mode 100644 mocks/generated/trezorcrypto/nem.py create mode 100644 mocks/generated/trezorcrypto/nist256p1.py create mode 100644 mocks/generated/trezorcrypto/random.py create mode 100644 mocks/generated/trezorcrypto/secp256k1.py create mode 100644 src/apps/monero/README.md create mode 100644 src/apps/monero/__init__.py create mode 100644 src/apps/monero/diag.py create mode 100644 src/apps/monero/get_address.py create mode 100644 src/apps/monero/get_watch_only.py create mode 100644 src/apps/monero/key_image_sync.py create mode 100644 src/apps/monero/layout/common.py create mode 100644 src/apps/monero/layout/confirms.py create mode 100644 src/apps/monero/misc.py create mode 100644 src/apps/monero/sign_tx.py create mode 100644 src/apps/monero/signing/__init__.py create mode 100644 src/apps/monero/signing/offloading_keys.py create mode 100644 src/apps/monero/signing/state.py create mode 100644 src/apps/monero/signing/step_01_init_transaction.py create mode 100644 src/apps/monero/signing/step_02_set_input.py create mode 100644 src/apps/monero/signing/step_03_inputs_permutation.py create mode 100644 src/apps/monero/signing/step_04_input_vini.py create mode 100644 src/apps/monero/signing/step_05_all_inputs_set.py create mode 100644 src/apps/monero/signing/step_06_set_output.py create mode 100644 src/apps/monero/signing/step_07_all_outputs_set.py create mode 100644 src/apps/monero/signing/step_09_sign_input.py create mode 100644 src/apps/monero/signing/step_10_sign_final.py create mode 100644 src/apps/monero/xmr/__init__.py create mode 100644 src/apps/monero/xmr/addresses.py create mode 100644 src/apps/monero/xmr/bulletproof.py create mode 100644 src/apps/monero/xmr/credentials.py create mode 100644 src/apps/monero/xmr/crypto/__init__.py create mode 100644 src/apps/monero/xmr/crypto/chacha_poly.py create mode 100644 src/apps/monero/xmr/keccak_hasher.py create mode 100644 src/apps/monero/xmr/key_image.py create mode 100644 src/apps/monero/xmr/mlsag.py create mode 100644 src/apps/monero/xmr/mlsag_hasher.py create mode 100644 src/apps/monero/xmr/monero.py create mode 100644 src/apps/monero/xmr/networks.py create mode 100644 src/apps/monero/xmr/range_signatures.py create mode 100644 src/apps/monero/xmr/serialize/__init__.py create mode 100644 src/apps/monero/xmr/serialize/base_types.py create mode 100644 src/apps/monero/xmr/serialize/int_serialize.py create mode 100644 src/apps/monero/xmr/serialize/message_types.py create mode 100644 src/apps/monero/xmr/serialize/readwriter.py create mode 100644 src/apps/monero/xmr/serialize_messages/__init__.py create mode 100644 src/apps/monero/xmr/serialize_messages/base.py create mode 100644 src/apps/monero/xmr/serialize_messages/ct_keys.py create mode 100644 src/apps/monero/xmr/serialize_messages/tx_ecdh.py create mode 100644 src/apps/monero/xmr/serialize_messages/tx_full.py create mode 100644 src/apps/monero/xmr/serialize_messages/tx_prefix.py create mode 100644 src/apps/monero/xmr/serialize_messages/tx_rsig_bulletproof.py create mode 100644 src/apps/monero/xmr/types.py rename src/trezor/messages/{MoneroRctKey.py => MoneroRctKeyPublic.py} (63%) delete mode 100644 src/trezor/messages/MoneroTransactionMlsagDoneAck.py delete mode 100644 src/trezor/messages/MoneroTransactionMlsagDoneRequest.py create mode 100755 tests/run_tests_device_emu_monero.sh 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 create mode 100755 travis-install-libsodium.sh diff --git a/.travis.yml b/.travis.yml index 11de9b819..7451bd3fb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -30,6 +30,10 @@ addons: - python3.6-dev - python3.6-venv +cache: + directories: + - $HOME/libsodium + before_install: - python3.6 -m ensurepip --user - python3.6 -m pip install --user pipenv @@ -38,6 +42,10 @@ install: - curl -LO "https://github.com/google/protobuf/releases/download/v${PROTOBUF_VERSION}/protoc-${PROTOBUF_VERSION}-linux-x86_64.zip" - unzip "protoc-${PROTOBUF_VERSION}-linux-x86_64.zip" -d protoc - export PATH="$(pwd)/protoc/bin:$PATH" + - ./travis-install-libsodium.sh + - export PKG_CONFIG_PATH=$HOME/libsodium/lib/pkgconfig:$PKG_CONFIG_PATH + - export LD_LIBRARY_PATH=$HOME/libsodium/lib:$LD_LIBRARY_PATH + - pipenv run pip install pip==18.0 - pipenv install before_script: @@ -59,6 +67,7 @@ script: - test "$GOAL" != "unix" || pipenv run make build_unix_noui - test "$GOAL" != "unix" || pipenv run make test - test "$GOAL" != "unix" || test "$TREZOR_MODEL" = "1" || pipenv run make test_emu + - test "$GOAL" != "unix" || test "$TREZOR_MODEL" = "1" || pipenv run make test_emu_monero notifications: webhooks: diff --git a/Makefile b/Makefile index 090a3deb4..6f150830f 100644 --- a/Makefile +++ b/Makefile @@ -65,6 +65,9 @@ test: ## run unit tests test_emu: ## run selected device tests from python-trezor cd tests ; ./run_tests_device_emu.sh $(TESTOPTS) +test_emu_monero: ## run selected monero device tests from monero-agent + cd tests ; ./run_tests_device_emu_monero.sh $(TESTOPTS) + pylint: ## run pylint on application sources and tests pylint -E $(shell find src tests -name *.py) diff --git a/Pipfile b/Pipfile index 50aceacef..61c5230e2 100644 --- a/Pipfile +++ b/Pipfile @@ -25,5 +25,9 @@ termcolor = ">=0.1.2" Pillow = ">=5.2.0" Mako = ">=1.0.7" +# monero +monero_agent = {version = ">=1.7.1", extras = ["tcry", "dev"]} +py_trezor_crypto_ph4 = {version = ">=0.1.1"} + [pipenv] allow_prereleases = true diff --git a/SConscript.firmware b/SConscript.firmware index 6a2fd7b7c..a406e62d2 100644 --- a/SConscript.firmware +++ b/SConscript.firmware @@ -27,6 +27,7 @@ CPPDEFINES_MOD += [ 'RAND_PLATFORM_INDEPENDENT', ('USE_KECCAK', '1'), ('USE_ETHEREUM', '1'), + ('USE_MONERO', '1'), ('USE_CARDANO', '1'), ('USE_NEM', '1'), ] @@ -63,6 +64,9 @@ SOURCE_MOD += [ 'vendor/trezor-crypto/ed25519-donna/ed25519-keccak.c', 'vendor/trezor-crypto/ed25519-donna/ed25519-sha3.c', 'vendor/trezor-crypto/ed25519-donna/modm-donna-32bit.c', + 'vendor/trezor-crypto/monero/base58.c', + 'vendor/trezor-crypto/monero/serialize.c', + 'vendor/trezor-crypto/monero/xmr.c', 'vendor/trezor-crypto/groestl.c', 'vendor/trezor-crypto/hasher.c', 'vendor/trezor-crypto/hmac.c', diff --git a/SConscript.unix b/SConscript.unix index 4b96dff02..1ad8dcfac 100644 --- a/SConscript.unix +++ b/SConscript.unix @@ -25,6 +25,7 @@ CPPDEFINES_MOD += [ 'AES_192', ('USE_KECCAK', '1'), ('USE_ETHEREUM', '1'), + ('USE_MONERO', '1'), ('USE_CARDANO', '1'), ('USE_NEM', '1'), ] @@ -60,6 +61,9 @@ SOURCE_MOD += [ 'vendor/trezor-crypto/ed25519-donna/ed25519-keccak.c', 'vendor/trezor-crypto/ed25519-donna/ed25519-sha3.c', 'vendor/trezor-crypto/ed25519-donna/modm-donna-32bit.c', + 'vendor/trezor-crypto/monero/base58.c', + 'vendor/trezor-crypto/monero/serialize.c', + 'vendor/trezor-crypto/monero/xmr.c', 'vendor/trezor-crypto/groestl.c', 'vendor/trezor-crypto/hasher.c', 'vendor/trezor-crypto/hmac.c', diff --git a/embed/extmod/modtrezorcrypto/modtrezorcrypto-aes.h b/embed/extmod/modtrezorcrypto/modtrezorcrypto-aes.h index 5671d3ab9..aea3c25d9 100644 --- a/embed/extmod/modtrezorcrypto/modtrezorcrypto-aes.h +++ b/embed/extmod/modtrezorcrypto/modtrezorcrypto-aes.h @@ -30,6 +30,8 @@ enum AESMode { CTR = 0x04, }; +/// package: trezorcrypto.__init__ + /// class AES: /// ''' /// AES context. diff --git a/embed/extmod/modtrezorcrypto/modtrezorcrypto-bip32.h b/embed/extmod/modtrezorcrypto/modtrezorcrypto-bip32.h index 2b03b6990..76f696b1d 100644 --- a/embed/extmod/modtrezorcrypto/modtrezorcrypto-bip32.h +++ b/embed/extmod/modtrezorcrypto/modtrezorcrypto-bip32.h @@ -27,6 +27,8 @@ #include "memzero.h" #include "nem.h" +/// package: trezorcrypto.bip32 + /// class HDNode: /// ''' /// BIP0032 HD node structure. diff --git a/embed/extmod/modtrezorcrypto/modtrezorcrypto-bip39.h b/embed/extmod/modtrezorcrypto/modtrezorcrypto-bip39.h index e6f412f3d..fb245dece 100644 --- a/embed/extmod/modtrezorcrypto/modtrezorcrypto-bip39.h +++ b/embed/extmod/modtrezorcrypto/modtrezorcrypto-bip39.h @@ -21,6 +21,8 @@ #include "bip39.h" +/// package: trezorcrypto.bip39 + /// def find_word(prefix: str) -> Optional[str]: /// ''' /// Return the first word from the wordlist starting with prefix. diff --git a/embed/extmod/modtrezorcrypto/modtrezorcrypto-blake256.h b/embed/extmod/modtrezorcrypto/modtrezorcrypto-blake256.h index d15c2ca78..d7937cd4e 100644 --- a/embed/extmod/modtrezorcrypto/modtrezorcrypto-blake256.h +++ b/embed/extmod/modtrezorcrypto/modtrezorcrypto-blake256.h @@ -22,6 +22,8 @@ #include "blake256.h" #include "memzero.h" +/// package: trezorcrypto.__init__ + /// class Blake256: /// ''' /// Blake256 context. diff --git a/embed/extmod/modtrezorcrypto/modtrezorcrypto-blake2b.h b/embed/extmod/modtrezorcrypto/modtrezorcrypto-blake2b.h index d860237e4..ea61853d2 100644 --- a/embed/extmod/modtrezorcrypto/modtrezorcrypto-blake2b.h +++ b/embed/extmod/modtrezorcrypto/modtrezorcrypto-blake2b.h @@ -24,6 +24,8 @@ #include "blake2b.h" #include "memzero.h" +/// package: trezorcrypto.__init__ + /// class Blake2b: /// ''' /// Blake2b context. diff --git a/embed/extmod/modtrezorcrypto/modtrezorcrypto-blake2s.h b/embed/extmod/modtrezorcrypto/modtrezorcrypto-blake2s.h index fb14b352a..497109025 100644 --- a/embed/extmod/modtrezorcrypto/modtrezorcrypto-blake2s.h +++ b/embed/extmod/modtrezorcrypto/modtrezorcrypto-blake2s.h @@ -24,6 +24,8 @@ #include "blake2s.h" #include "memzero.h" +/// package: trezorcrypto.__init__ + /// class Blake2s: /// ''' /// Blake2s context. diff --git a/embed/extmod/modtrezorcrypto/modtrezorcrypto-chacha20poly1305.h b/embed/extmod/modtrezorcrypto/modtrezorcrypto-chacha20poly1305.h index 9b4a04c4d..98563f5bc 100644 --- a/embed/extmod/modtrezorcrypto/modtrezorcrypto-chacha20poly1305.h +++ b/embed/extmod/modtrezorcrypto/modtrezorcrypto-chacha20poly1305.h @@ -22,6 +22,8 @@ #include "chacha20poly1305/rfc7539.h" #include "memzero.h" +/// package: trezorcrypto.__init__ + /// class ChaCha20Poly1305: /// ''' /// ChaCha20Poly1305 context. diff --git a/embed/extmod/modtrezorcrypto/modtrezorcrypto-curve25519.h b/embed/extmod/modtrezorcrypto/modtrezorcrypto-curve25519.h index 653234b2f..bd24e6fcb 100644 --- a/embed/extmod/modtrezorcrypto/modtrezorcrypto-curve25519.h +++ b/embed/extmod/modtrezorcrypto/modtrezorcrypto-curve25519.h @@ -23,6 +23,8 @@ #include "rand.h" +/// package: trezorcrypto.curve25519 + /// def generate_secret() -> bytes: /// ''' /// Generate secret key. diff --git a/embed/extmod/modtrezorcrypto/modtrezorcrypto-ed25519.h b/embed/extmod/modtrezorcrypto/modtrezorcrypto-ed25519.h index e042ca4b1..8ed1f5ea6 100644 --- a/embed/extmod/modtrezorcrypto/modtrezorcrypto-ed25519.h +++ b/embed/extmod/modtrezorcrypto/modtrezorcrypto-ed25519.h @@ -24,6 +24,8 @@ #include "rand.h" +/// package: trezorcrypto.ed25519 + /// def generate_secret() -> bytes: /// ''' /// Generate secret key. diff --git a/embed/extmod/modtrezorcrypto/modtrezorcrypto-groestl.h b/embed/extmod/modtrezorcrypto/modtrezorcrypto-groestl.h index f9e4794f8..87071f5e6 100644 --- a/embed/extmod/modtrezorcrypto/modtrezorcrypto-groestl.h +++ b/embed/extmod/modtrezorcrypto/modtrezorcrypto-groestl.h @@ -25,6 +25,8 @@ #define GROESTL512_DIGEST_LENGTH 64 #define GROESTL512_BLOCK_LENGTH 128 +/// package: trezorcrypto.__init__ + /// class Groestl512: /// ''' /// GROESTL512 context. diff --git a/embed/extmod/modtrezorcrypto/modtrezorcrypto-monero.h b/embed/extmod/modtrezorcrypto/modtrezorcrypto-monero.h new file mode 100644 index 000000000..67508205f --- /dev/null +++ b/embed/extmod/modtrezorcrypto/modtrezorcrypto-monero.h @@ -0,0 +1,1221 @@ +/* + * This file is part of the TREZOR project, https://trezor.io/ + * + * Copyright (c) SatoshiLabs + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "py/objstr.h" +#include "py/objint.h" +#include "py/mpz.h" + +#include "monero/monero.h" +#include "bignum.h" + +/// package: trezorcrypto.monero + + + +typedef struct _mp_obj_hasher_t { + mp_obj_base_t base; + Hasher h; +} mp_obj_hasher_t; + +typedef struct _mp_obj_ge25519_t { + mp_obj_base_t base; + ge25519 p; +} mp_obj_ge25519_t; + +typedef struct _mp_obj_bignum256modm_t { + mp_obj_base_t base; + bignum256modm p; +} mp_obj_bignum256modm_t; + + +// +// Helpers +// + +STATIC const mp_obj_type_t mod_trezorcrypto_monero_ge25519_type; +STATIC const mp_obj_type_t mod_trezorcrypto_monero_bignum256modm_type; +STATIC const mp_obj_type_t mod_trezorcrypto_monero_hasher_type; + +#define MP_OBJ_IS_GE25519(o) MP_OBJ_IS_TYPE((o), &mod_trezorcrypto_monero_ge25519_type) +#define MP_OBJ_IS_SCALAR(o) MP_OBJ_IS_TYPE((o), &mod_trezorcrypto_monero_bignum256modm_type) +#define MP_OBJ_PTR_MPC_GE25519(o) ((const mp_obj_ge25519_t*) (o)) +#define MP_OBJ_PTR_MPC_SCALAR(o) ((const mp_obj_bignum256modm_t*) (o)) +#define MP_OBJ_PTR_MP_GE25519(o) ((mp_obj_ge25519_t*) (o)) +#define MP_OBJ_PTR_MP_SCALAR(o) ((mp_obj_bignum256modm_t*) (o)) +#define MP_OBJ_C_GE25519(o) (MP_OBJ_PTR_MPC_GE25519(o)->p) +#define MP_OBJ_GE25519(o) (MP_OBJ_PTR_MP_GE25519(o)->p) +#define MP_OBJ_C_SCALAR(o) (MP_OBJ_PTR_MPC_SCALAR(o)->p) +#define MP_OBJ_SCALAR(o) (MP_OBJ_PTR_MP_SCALAR(o)->p) + +STATIC inline void assert_ge25519(const mp_obj_t o){ + if (!MP_OBJ_IS_GE25519(o)){ + mp_raise_ValueError("ge25519 expected"); + } +} + +STATIC inline void assert_scalar(const mp_obj_t o){ + if (!MP_OBJ_IS_SCALAR(o)){ + mp_raise_ValueError("scalar expected"); + } +} + + +static uint64_t mp_obj_uint64_get_checked(mp_const_obj_t self_in) { +#if MICROPY_LONGINT_IMPL != MICROPY_LONGINT_IMPL_MPZ +# error "MPZ supported only" +#endif + + if (MP_OBJ_IS_SMALL_INT(self_in)) { + return MP_OBJ_SMALL_INT_VALUE(self_in); + } else { + byte buff[8]; + uint64_t res = 0; + mp_obj_t * o = MP_OBJ_TO_PTR(self_in); + + mp_obj_int_to_bytes_impl(o, true, 8, buff); + for (int i = 0; i<8; i++){ + res <<= i > 0 ? 8 : 0; + res |= (uint64_t)(buff[i] & 0xff); + } + return res; + } +} + +static uint64_t mp_obj_get_uint64(mp_const_obj_t arg) { + if (arg == mp_const_false) { + return 0; + } else if (arg == mp_const_true) { + return 1; + } else if (MP_OBJ_IS_SMALL_INT(arg)) { + return MP_OBJ_SMALL_INT_VALUE(arg); + } else if (MP_OBJ_IS_TYPE(arg, &mp_type_int)) { + return mp_obj_uint64_get_checked(arg); + } else { + if (MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE) { + mp_raise_TypeError("can't convert to int"); + } else { + nlr_raise(mp_obj_new_exception_msg_varg(&mp_type_TypeError, + "can't convert %s to int", mp_obj_get_type_str(arg))); + } + } +} + +STATIC mp_obj_t mp_obj_new_scalar(){ + mp_obj_bignum256modm_t *o = m_new_obj(mp_obj_bignum256modm_t); + o->base.type = &mod_trezorcrypto_monero_bignum256modm_type; + set256_modm(o->p, 0); + return MP_OBJ_FROM_PTR(o); +} + +STATIC mp_obj_t mp_obj_new_scalar_r(mp_obj_t r){ + if (r == mp_const_none){ + return mp_obj_new_scalar(); + } + + assert_scalar(r); + return r; +} + +STATIC mp_obj_t mp_obj_new_ge25519(){ + mp_obj_ge25519_t *o = m_new_obj(mp_obj_ge25519_t); + o->base.type = &mod_trezorcrypto_monero_ge25519_type; + ge25519_set_neutral(&o->p); + return MP_OBJ_FROM_PTR(o); +} + +STATIC mp_obj_t mp_obj_new_ge25519_r(mp_obj_t r){ + if (r == mp_const_none){ + return mp_obj_new_ge25519(); + } + + assert_ge25519(r); + return r; +} + +STATIC void mp_unpack_ge25519(ge25519 * r, const mp_obj_t arg, mp_int_t offset){ + mp_buffer_info_t buff; + mp_get_buffer_raise(arg, &buff, MP_BUFFER_READ); + if (buff.len < 32 + offset) { + mp_raise_ValueError("Invalid length of the EC point"); + } + + const int res = ge25519_unpack_vartime(r, ((uint8_t*)buff.buf) + offset); + if (res != 1){ + mp_raise_ValueError("Point decoding error"); + } +} + +STATIC void mp_unpack_scalar(bignum256modm r, const mp_obj_t arg, mp_int_t offset){ + mp_buffer_info_t buff; + mp_get_buffer_raise(arg, &buff, MP_BUFFER_READ); + if (buff.len < 32 + offset) { + mp_raise_ValueError("Invalid length of secret key"); + } + expand256_modm(r, ((uint8_t*)buff.buf) + offset, 32); +} + + + +// +// Constructors +// + +/// class Ge25519: +/// ''' +/// EC point on ED25519 +/// ''' +/// +/// def __init__(x: Optional[Union[Ge25519, bytes]] = None): +/// ''' +/// Constructor +/// ''' + +STATIC mp_obj_t mod_trezorcrypto_monero_ge25519_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { + mp_arg_check_num(n_args, n_kw, 0, 1, false); + mp_obj_ge25519_t *o = m_new_obj(mp_obj_ge25519_t); + o->base.type = type; + + if (n_args == 0 || args[0] == mp_const_none) { + ge25519_set_neutral(&o->p); + } else if (n_args == 1 && MP_OBJ_IS_GE25519(args[0])) { + ge25519_copy(&o->p, &MP_OBJ_C_GE25519(args[0])); + } else if (n_args == 1 && MP_OBJ_IS_STR_OR_BYTES(args[0])) { + mp_unpack_ge25519(&o->p, args[0], 0); + } else { + mp_raise_ValueError("Invalid ge25519 constructor"); + } + + return MP_OBJ_FROM_PTR(o); +} + +STATIC mp_obj_t mod_trezorcrypto_monero_ge25519___del__(mp_obj_t self) { + mp_obj_ge25519_t *o = MP_OBJ_TO_PTR(self); + memzero(&(o->p), sizeof(ge25519)); + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(mod_trezorcrypto_monero_ge25519___del___obj, mod_trezorcrypto_monero_ge25519___del__); + + +/// class Sc25519: +/// ''' +/// EC scalar on SC25519 +/// ''' +/// +/// def __init__(x: Optional[Union[Sc25519, bytes, int]] = None): +/// ''' +/// Constructor +/// ''' +/// +/// +STATIC mp_obj_t mod_trezorcrypto_monero_bignum256modm_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { + mp_arg_check_num(n_args, n_kw, 0, 1, false); + mp_obj_bignum256modm_t *o = m_new_obj(mp_obj_bignum256modm_t); + o->base.type = type; + + if (n_args == 0 || args[0] == mp_const_none) { + set256_modm(o->p, 0); + } else if (n_args == 1 && MP_OBJ_IS_SCALAR(args[0])) { + copy256_modm(o->p, MP_OBJ_C_SCALAR(args[0])); + } else if (n_args == 1 && MP_OBJ_IS_STR_OR_BYTES(args[0])) { + mp_unpack_scalar(o->p, args[0], 0); + } else if (n_args == 1 && mp_obj_is_integer(args[0])) { + uint64_t v = mp_obj_get_uint64(args[0]); + set256_modm(o->p, v); + } else { + mp_raise_ValueError("Invalid scalar constructor"); + } + + return MP_OBJ_FROM_PTR(o); +} + +STATIC mp_obj_t mod_trezorcrypto_monero_bignum256modm___del__(mp_obj_t self) { + mp_obj_bignum256modm_t *o = MP_OBJ_TO_PTR(self); + memzero(o->p, sizeof(bignum256modm)); + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(mod_trezorcrypto_monero_bignum256modm___del___obj, mod_trezorcrypto_monero_bignum256modm___del__); + + +/// class Hasher: +/// ''' +/// XMR hasher +/// ''' +/// +/// def __init__(x: Optional[bytes] = None): +/// ''' +/// Constructor +/// ''' +/// +/// def update(buffer: bytes): +/// ''' +/// Update hasher +/// ''' +/// +/// def digest() -> bytes: +/// ''' +/// Computes digest +/// ''' +/// +/// def copy() -> Hasher: +/// ''' +/// Creates copy of the hasher, preserving the state +/// ''' +/// +/// +STATIC mp_obj_t mod_trezorcrypto_monero_hasher_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { + mp_arg_check_num(n_args, n_kw, 0, 1, false); + mp_obj_hasher_t *o = m_new_obj(mp_obj_hasher_t); + o->base.type = type; + xmr_hasher_init(&(o->h)); + + if (n_args == 1 && MP_OBJ_IS_STR_OR_BYTES(args[0])) { + mp_buffer_info_t buff; + mp_get_buffer_raise(args[0], &buff, MP_BUFFER_READ); + xmr_hasher_update(&o->h, buff.buf, buff.len); + } + + return MP_OBJ_FROM_PTR(o); +} + +STATIC mp_obj_t mod_trezorcrypto_monero_hasher___del__(mp_obj_t self) { + mp_obj_hasher_t *o = MP_OBJ_TO_PTR(self); + memzero(&(o->h), sizeof(Hasher)); + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(mod_trezorcrypto_monero_hasher___del___obj, mod_trezorcrypto_monero_hasher___del__); + + +// +// Scalar defs +// + +/// mock:global + +/// def init256_modm(dst: Optional[Sc25519], val: Union[int, bytes, Sc25519]) -> Sc25519: +/// ''' +/// Initializes Sc25519 scalar +/// ''' +STATIC mp_obj_t mod_trezorcrypto_monero_init256_modm(size_t n_args, const mp_obj_t *args){ + const bool res_arg = n_args == 2; + const int off = res_arg ? 0 : -1; + mp_obj_t res = mp_obj_new_scalar_r(res_arg ? args[0] : mp_const_none); + + if (n_args == 0 || args[0] == mp_const_none) { + set256_modm(MP_OBJ_SCALAR(res), 0); + } else if (n_args > 0 && MP_OBJ_IS_SCALAR(args[1+off])) { + copy256_modm(MP_OBJ_SCALAR(res), MP_OBJ_C_SCALAR(args[1+off])); + } else if (n_args > 0 && MP_OBJ_IS_STR_OR_BYTES(args[1+off])) { + mp_unpack_scalar(MP_OBJ_SCALAR(res), args[1+off], 0); + } else if (n_args > 0 && mp_obj_is_integer(args[1+off])) { + uint64_t v = mp_obj_get_uint64(args[1+off]); + set256_modm(MP_OBJ_SCALAR(res), v); + } else { + mp_raise_ValueError("Invalid scalar def"); + } + return res; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_trezorcrypto_monero_init256_modm_obj, 0, 2, mod_trezorcrypto_monero_init256_modm); + +/// def check256_modm(val: Sc25519): +/// ''' +/// Throws exception if scalar is invalid +/// ''' +STATIC mp_obj_t mod_trezorcrypto_monero_check256_modm(const mp_obj_t arg){ + assert_scalar(arg); + if (check256_modm(MP_OBJ_C_SCALAR(arg)) != 1){ + mp_raise_ValueError("Ed25519 scalar invalid"); + } + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(mod_trezorcrypto_monero_check256_modm_obj, mod_trezorcrypto_monero_check256_modm); + +/// def iszero256_modm(val: Sc25519) -> bool: +/// ''' +/// Returns False if the scalar is zero +/// ''' +STATIC mp_obj_t mod_trezorcrypto_monero_iszero256_modm(const mp_obj_t arg){ + assert_scalar(arg); + const int r = iszero256_modm(MP_OBJ_C_SCALAR(arg)); + return mp_obj_new_int(r); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(mod_trezorcrypto_monero_iszero256_modm_obj, mod_trezorcrypto_monero_iszero256_modm); + +/// def eq256_modm(a: Sc25519, b: Sc25519) -> int: +/// ''' +/// Compares scalars, returns 1 on the same value +/// ''' +STATIC mp_obj_t mod_trezorcrypto_monero_eq256_modm(const mp_obj_t a, const mp_obj_t b){ + assert_scalar(a); + assert_scalar(b); + int r = eq256_modm(MP_OBJ_C_SCALAR(a), MP_OBJ_C_SCALAR(b)); + return MP_OBJ_NEW_SMALL_INT(r); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_2(mod_trezorcrypto_monero_eq256_modm_obj, mod_trezorcrypto_monero_eq256_modm); + + +/// def get256_modm(a: Sc25519) -> int: +/// ''' +/// Extracts 64bit integer from the scalar. Raises exception if scalar is bigger than 2^64 +/// ''' +STATIC mp_obj_t mod_trezorcrypto_monero_get256_modm(const mp_obj_t arg){ + assert_scalar(arg); + uint64_t v; + if (!get256_modm(&v, MP_OBJ_C_SCALAR(arg))){ + mp_raise_ValueError("Ed25519 scalar too big"); + } + return mp_obj_new_int_from_ull(v); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(mod_trezorcrypto_monero_get256_modm_obj, mod_trezorcrypto_monero_get256_modm); + + +/// def add256_modm(r: Optional[Sc25519], a: Sc25519, b: Sc25519) -> Sc25519: +/// ''' +/// Scalar addition +/// ''' +STATIC mp_obj_t mod_trezorcrypto_monero_add256_modm(size_t n_args, const mp_obj_t *args){ + const bool res_arg = n_args == 3; + const int off = res_arg ? 0 : -1; + mp_obj_t res = mp_obj_new_scalar_r(res_arg ? args[0] : mp_const_none); + + assert_scalar(args[1+off]); + assert_scalar(args[2+off]); + add256_modm(MP_OBJ_SCALAR(res), MP_OBJ_C_SCALAR(args[1+off]), MP_OBJ_C_SCALAR(args[2+off])); + return res; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_trezorcrypto_monero_add256_modm_obj, 2, 3, mod_trezorcrypto_monero_add256_modm); + +/// def sub256_modm(r: Optional[Sc25519], a: Sc25519, b: Sc25519) -> Sc25519: +/// ''' +/// Scalar subtraction +/// ''' +STATIC mp_obj_t mod_trezorcrypto_monero_sub256_modm(size_t n_args, const mp_obj_t *args){ + const bool res_arg = n_args == 3; + const int off = res_arg ? 0 : -1; + mp_obj_t res = mp_obj_new_scalar_r(res_arg ? args[0] : mp_const_none); + + assert_scalar(args[1+off]); + assert_scalar(args[2+off]); + sub256_modm(MP_OBJ_SCALAR(res), MP_OBJ_C_SCALAR(args[1+off]), MP_OBJ_C_SCALAR(args[2+off])); + return res; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_trezorcrypto_monero_sub256_modm_obj, 2, 3, mod_trezorcrypto_monero_sub256_modm); + +/// def mul256_modm(r: Optional[Sc25519], a: Sc25519, b: Sc25519) -> Sc25519: +/// ''' +/// Scalar multiplication +/// ''' +STATIC mp_obj_t mod_trezorcrypto_monero_mul256_modm(size_t n_args, const mp_obj_t *args){ + const bool res_arg = n_args == 3; + const int off = res_arg ? 0 : -1; + mp_obj_t res = mp_obj_new_scalar_r(res_arg ? args[0] : mp_const_none); + + assert_scalar(args[1+off]); + assert_scalar(args[2+off]); + mul256_modm(MP_OBJ_SCALAR(res), MP_OBJ_C_SCALAR(args[1+off]), MP_OBJ_C_SCALAR(args[2+off])); + return res; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_trezorcrypto_monero_mul256_modm_obj, 2, 3, mod_trezorcrypto_monero_mul256_modm); + +/// def mulsub256_modm(r: Optional[Sc25519], a: Sc25519, b: Sc25519, c: Sc25519) -> Sc25519: +/// ''' +/// c - a*b +/// ''' +STATIC mp_obj_t mod_trezorcrypto_monero_mulsub256_modm(size_t n_args, const mp_obj_t *args){ + const bool res_arg = n_args == 4; + const int off = res_arg ? 0 : -1; + mp_obj_t res = mp_obj_new_scalar_r(res_arg ? args[0] : mp_const_none); + + assert_scalar(args[1+off]); + assert_scalar(args[2+off]); + assert_scalar(args[3+off]); + mulsub256_modm(MP_OBJ_SCALAR(res), MP_OBJ_C_SCALAR(args[1+off]), MP_OBJ_C_SCALAR(args[2+off]), MP_OBJ_C_SCALAR(args[3+off])); + return res; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_trezorcrypto_monero_mulsub256_modm_obj, 3, 4, mod_trezorcrypto_monero_mulsub256_modm); + +/// def muladd256_modm(r: Optional[Sc25519], a: Sc25519, b: Sc25519, c: Sc25519) -> Sc25519: +/// ''' +/// c + a*b +/// ''' +STATIC mp_obj_t mod_trezorcrypto_monero_muladd256_modm(size_t n_args, const mp_obj_t *args){ + const bool res_arg = n_args == 4; + const int off = res_arg ? 0 : -1; + mp_obj_t res = mp_obj_new_scalar_r(res_arg ? args[0] : mp_const_none); + + assert_scalar(args[1+off]); + assert_scalar(args[2+off]); + assert_scalar(args[3+off]); + muladd256_modm(MP_OBJ_SCALAR(res), MP_OBJ_C_SCALAR(args[1+off]), MP_OBJ_C_SCALAR(args[2+off]), MP_OBJ_C_SCALAR(args[3+off])); + return res; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_trezorcrypto_monero_muladd256_modm_obj, 3, 4, mod_trezorcrypto_monero_muladd256_modm); + +/// def inv256_modm(r: Optional[Sc25519], a: Sc25519) -> Sc25519: +/// ''' +/// Scalar modular inversion +/// ''' +STATIC mp_obj_t mod_trezorcrypto_monero_inv256_modm(size_t n_args, const mp_obj_t *args){ + const bool res_arg = n_args == 2; + const int off = res_arg ? 0 : -1; + mp_obj_t res = mp_obj_new_scalar_r(res_arg ? args[0] : mp_const_none); + assert_scalar(args[1+off]); + + // bn_prime = curve order, little endian encoded + bignum256 bn_prime = {.val={0x1cf5d3ed, 0x20498c69, 0x2f79cd65, 0x37be77a8, 0x14, 0x0, 0x0, 0x0, 0x1000}}; + bignum256 bn_x; + + memcpy(&bn_x.val, MP_OBJ_C_SCALAR(args[1+off]), sizeof(bignum256modm)); + bn_inverse(&bn_x, &bn_prime); + memcpy(MP_OBJ_SCALAR(res), bn_x.val, sizeof(bignum256modm)); + + return res; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_trezorcrypto_monero_inv256_modm_obj, 1, 2, mod_trezorcrypto_monero_inv256_modm); + +/// def pack256_modm(r: Optional[bytes], a: Sc25519, offset: Optional[int] = 0) -> bytes: +/// ''' +/// Scalar compression +/// ''' +STATIC mp_obj_t mod_trezorcrypto_monero_pack256_modm(size_t n_args, const mp_obj_t *args){ + if (n_args == 1 || args[0] == mp_const_none){ + assert_scalar(args[0]); + uint8_t buff[32]; + contract256_modm(buff, MP_OBJ_C_SCALAR(args[0])); + return mp_obj_new_bytes(buff, 32); + + } else { + mp_buffer_info_t bufm; + mp_get_buffer_raise(args[0], &bufm, MP_BUFFER_WRITE); + const mp_int_t offset = n_args >= 3 ? mp_obj_get_int(args[2]) : 0; + if (bufm.len < 32 + offset) { + mp_raise_ValueError("Buffer too small"); + } + + contract256_modm(((uint8_t*)bufm.buf) + offset, MP_OBJ_C_SCALAR(args[1])); + return args[0]; + } +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_trezorcrypto_monero_pack256_modm_obj, 1, 3, mod_trezorcrypto_monero_pack256_modm); + +/// def unpack256_modm(r: Optional[Sc25519], a: bytes, offset: int = 0) -> Sc25519: +/// ''' +/// Scalar decompression +/// ''' +STATIC mp_obj_t mod_trezorcrypto_monero_unpack256_modm(size_t n_args, const mp_obj_t *args){ + const bool res_arg = n_args >= 2; + const int off = res_arg ? 0 : -1; + mp_obj_t res = mp_obj_new_scalar_r(res_arg ? args[0] : mp_const_none); + const mp_int_t offset = n_args >= 3 ? mp_obj_get_int(args[2]) : 0; + mp_unpack_scalar(MP_OBJ_SCALAR(res), args[1+off], offset); + return res; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_trezorcrypto_monero_unpack256_modm_obj, 1, 3, mod_trezorcrypto_monero_unpack256_modm); + +/// def unpack256_modm_noreduce(r: Optional[Sc25519], a: bytes, offset: int = 0) -> Sc25519: +/// ''' +/// Scalar decompression, raw, without modular reduction +/// ''' +STATIC mp_obj_t mod_trezorcrypto_monero_unpack256_modm_noreduce(size_t n_args, const mp_obj_t *args){ + const bool res_arg = n_args >= 2; + const int off = res_arg ? 0 : -1; + mp_obj_t res = mp_obj_new_scalar_r(res_arg ? args[0] : mp_const_none); + const mp_int_t offset = n_args >= 3 ? mp_obj_get_int(args[2]) : 0; + + mp_buffer_info_t buff; + mp_get_buffer_raise(args[1+off], &buff, MP_BUFFER_READ); + if (buff.len != 32 + offset) { + mp_raise_ValueError("Invalid length of secret key"); + } + + expand_raw256_modm(MP_OBJ_SCALAR(res), ((uint8_t*)buff.buf) + offset); + return res; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_trezorcrypto_monero_unpack256_modm_noreduce_obj, 1, 3, mod_trezorcrypto_monero_unpack256_modm_noreduce); + +// +// GE25519 Defs +// + +/// def ge25519_set_neutral(r: Optional[Ge25519]) -> Ge25519: +/// ''' +/// Sets neutral point +/// ''' +STATIC mp_obj_t mod_trezorcrypto_monero_ge25519_set_neutral(size_t n_args, const mp_obj_t *args){ + mp_obj_t res = mp_obj_new_ge25519_r(n_args == 1 ? args[0] : mp_const_none); + ge25519_set_neutral(&MP_OBJ_GE25519(res)); + return res; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_trezorcrypto_monero_ge25519_set_neutral_obj, 0, 1, mod_trezorcrypto_monero_ge25519_set_neutral); + +/// def ge25519_set_xmr_h(r: Optional[Ge25519]) -> Ge25519: +/// ''' +/// Sets H point +/// ''' +STATIC mp_obj_t mod_trezorcrypto_monero_ge25519_set_xmr_h(size_t n_args, const mp_obj_t *args){ + mp_obj_t res = mp_obj_new_ge25519_r(n_args == 1 ? args[0] : mp_const_none); + ge25519_set_xmr_h(&MP_OBJ_GE25519(res)); + return res; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_trezorcrypto_monero_ge25519_set_xmr_h_obj, 0, 1, mod_trezorcrypto_monero_ge25519_set_xmr_h); + +/// def ge25519_check(r: Ge25519): +/// ''' +/// Checks point, throws if not on curve +/// ''' +STATIC mp_obj_t mod_trezorcrypto_monero_ge25519_check(const mp_obj_t arg){ + assert_ge25519(arg); + if (ge25519_check(&MP_OBJ_C_GE25519(arg)) != 1){ + mp_raise_ValueError("Ed25519 point not on curve"); + } + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(mod_trezorcrypto_monero_ge25519_check_obj, mod_trezorcrypto_monero_ge25519_check); + +/// def ge25519_eq(a: Ge25519, b: Ge25519) -> bool: +/// ''' +/// Compares EC points +/// ''' +STATIC mp_obj_t mod_trezorcrypto_monero_ge25519_eq(const mp_obj_t a, const mp_obj_t b){ + assert_ge25519(a); + assert_ge25519(b); + int r = ge25519_eq(&MP_OBJ_C_GE25519(a), &MP_OBJ_C_GE25519(b)); + return MP_OBJ_NEW_SMALL_INT(r); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_2(mod_trezorcrypto_monero_ge25519_eq_obj, mod_trezorcrypto_monero_ge25519_eq); + +/// def ge25519_add(r: Optional[Ge25519], a: Ge25519, b: Ge25519) -> Ge25519: +/// ''' +/// Adds EC points +/// ''' +STATIC mp_obj_t mod_trezorcrypto_monero_ge25519_add(size_t n_args, const mp_obj_t *args){ + const bool res_arg = n_args == 3; + const int off = res_arg ? 0 : -1; + mp_obj_t res = mp_obj_new_ge25519_r(res_arg ? args[0] : mp_const_none); + + assert_ge25519(args[1+off]); + assert_ge25519(args[2+off]); + ge25519_add(&MP_OBJ_GE25519(res), &MP_OBJ_C_GE25519(args[1+off]), &MP_OBJ_C_GE25519(args[2+off]), 0); + return res; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_trezorcrypto_monero_ge25519_add_obj, 2, 3, mod_trezorcrypto_monero_ge25519_add); + +/// def ge25519_sub(r: Optional[Ge25519], a: Ge25519, b: Ge25519) -> Ge25519: +/// ''' +/// Subtracts EC points +/// ''' +STATIC mp_obj_t mod_trezorcrypto_monero_ge25519_sub(size_t n_args, const mp_obj_t *args){ + const bool res_arg = n_args == 3; + const int off = res_arg ? 0 : -1; + mp_obj_t res = mp_obj_new_ge25519_r(res_arg ? args[0] : mp_const_none); + + assert_ge25519(args[1+off]); + assert_ge25519(args[2+off]); + ge25519_add(&MP_OBJ_GE25519(res), &MP_OBJ_C_GE25519(args[1+off]), &MP_OBJ_C_GE25519(args[2+off]), 1); + return res; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_trezorcrypto_monero_ge25519_sub_obj, 2, 3, mod_trezorcrypto_monero_ge25519_sub); + +/// def ge25519_double(r: Optional[Ge25519], p: Ge25519) -> Ge25519: +/// ''' +/// EC point doubling +/// ''' +STATIC mp_obj_t mod_trezorcrypto_monero_ge25519_double(size_t n_args, const mp_obj_t *args){ + const bool res_arg = n_args == 2; + mp_obj_t res = mp_obj_new_ge25519_r(res_arg ? args[0] : mp_const_none); + mp_obj_t src = res_arg ? args[1] : args[0]; + assert_ge25519(src); + ge25519_double(&MP_OBJ_GE25519(res), &MP_OBJ_C_GE25519(src)); + return res; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_trezorcrypto_monero_ge25519_double_obj, 1, 2, mod_trezorcrypto_monero_ge25519_double); + +/// def ge25519_mul8(r: Optional[Ge25519], p: Ge25519) -> Ge25519: +/// ''' +/// EC point * 8 +/// ''' +STATIC mp_obj_t mod_trezorcrypto_monero_ge25519_mul8(size_t n_args, const mp_obj_t *args){ + const bool res_arg = n_args == 2; + mp_obj_t res = mp_obj_new_ge25519_r(res_arg ? args[0] : mp_const_none); + mp_obj_t src = res_arg ? args[1] : args[0]; + assert_ge25519(src); + ge25519_mul8(&MP_OBJ_GE25519(res), &MP_OBJ_C_GE25519(src)); + return res; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_trezorcrypto_monero_ge25519_mul8_obj, 1, 2, mod_trezorcrypto_monero_ge25519_mul8); + +/// def ge25519_double_scalarmult_vartime(r: Optional[Ge25519], p1: Ge25519, s1: Sc25519, s2: Sc25519) -> Ge25519: +/// ''' +/// s1 * G + s2 * p1 +/// ''' +STATIC mp_obj_t mod_trezorcrypto_monero_ge25519_double_scalarmult_vartime(size_t n_args, const mp_obj_t *args){ + const bool res_arg = n_args == 4; + const int off = res_arg ? 0 : -1; + mp_obj_t res = mp_obj_new_ge25519_r(res_arg ? args[0] : mp_const_none); + assert_ge25519(args[1+off]); + assert_scalar(args[2+off]); + assert_scalar(args[3+off]); + + ge25519_double_scalarmult_vartime(&MP_OBJ_GE25519(res), &MP_OBJ_C_GE25519(args[1+off]), + MP_OBJ_C_SCALAR(args[2+off]), MP_OBJ_C_SCALAR(args[3+off])); + return res; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_trezorcrypto_monero_ge25519_double_scalarmult_vartime_obj, 3, 4, mod_trezorcrypto_monero_ge25519_double_scalarmult_vartime); + +/// def ge25519_double_scalarmult_vartime2(r: Optional[Ge25519], p1: Ge25519, s1: Sc25519, p2: Ge25519, s2: Sc25519) -> Ge25519: +/// ''' +/// s1 * p1 + s2 * p2 +/// ''' +STATIC mp_obj_t mod_trezorcrypto_monero_ge25519_double_scalarmult_vartime2(size_t n_args, const mp_obj_t *args){ + const bool res_arg = n_args == 5; + const int off = res_arg ? 0 : -1; + mp_obj_t res = mp_obj_new_ge25519_r(res_arg ? args[0] : mp_const_none); + + assert_ge25519(args[1+off]); + assert_scalar(args[2+off]); + assert_ge25519(args[3+off]); + assert_scalar(args[4+off]); + + ge25519_double_scalarmult_vartime2(&MP_OBJ_GE25519(res), &MP_OBJ_C_GE25519(args[1+off]), MP_OBJ_C_SCALAR(args[2+off]), + &MP_OBJ_C_GE25519(args[3+off]), MP_OBJ_C_SCALAR(args[4+off])); + return res; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_trezorcrypto_monero_ge25519_double_scalarmult_vartime2_obj, 4, 5, mod_trezorcrypto_monero_ge25519_double_scalarmult_vartime2); + +/// def ge25519_scalarmult_base(r: Optional[Ge25519], s: Union[Sc25519, int]) -> Ge25519: +/// ''' +/// s * G +/// ''' +STATIC mp_obj_t mod_trezorcrypto_monero_ge25519_scalarmult_base(size_t n_args, const mp_obj_t *args){ + const bool res_arg = n_args == 2; + const int off = res_arg ? 0 : -1; + mp_obj_t res = mp_obj_new_ge25519_r(res_arg ? args[0] : mp_const_none); + + if (MP_OBJ_IS_SCALAR(args[1+off])){ + ge25519_scalarmult_base_wrapper(&MP_OBJ_GE25519(res), MP_OBJ_C_SCALAR(args[1+off])); + } else if (mp_obj_is_integer(args[1+off])){ + bignum256modm mlt; + set256_modm(mlt, mp_obj_get_int(args[1+off])); + ge25519_scalarmult_base_wrapper(&MP_OBJ_GE25519(res), mlt); + } else { + mp_raise_ValueError("unknown base mult type"); + } + + return res; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_trezorcrypto_monero_ge25519_scalarmult_base_obj, 1, 2, mod_trezorcrypto_monero_ge25519_scalarmult_base); + +/// def ge25519_scalarmult(r: Optional[Ge25519], p: Ge25519, s: Union[Sc25519, int]) -> Ge25519: +/// ''' +/// s * p +/// ''' +STATIC mp_obj_t mod_trezorcrypto_monero_ge25519_scalarmult(size_t n_args, const mp_obj_t *args){ + const bool res_arg = n_args == 3; + const int off = res_arg ? 0 : -1; + mp_obj_t res = mp_obj_new_ge25519_r(res_arg ? args[0] : mp_const_none); + assert_ge25519(args[1+off]); + + if (MP_OBJ_IS_SCALAR(args[2+off])){ + ge25519_scalarmult(&MP_OBJ_GE25519(res), &MP_OBJ_C_GE25519(args[1+off]), MP_OBJ_C_SCALAR(args[2+off])); + } else if (mp_obj_is_integer(args[2+off])){ + bignum256modm mlt; + set256_modm(mlt, mp_obj_get_int(args[2+off])); + ge25519_scalarmult(&MP_OBJ_GE25519(res), &MP_OBJ_C_GE25519(args[1+off]), mlt); + } else { + mp_raise_ValueError("unknown mult type"); + } + + return res; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_trezorcrypto_monero_ge25519_scalarmult_obj, 2, 3, mod_trezorcrypto_monero_ge25519_scalarmult); + +/// def ge25519_pack(r: bytes, p: Ge25519, offset: int = 0) -> bytes: +/// ''' +/// Point compression +/// ''' +STATIC mp_obj_t mod_trezorcrypto_monero_ge25519_pack(size_t n_args, const mp_obj_t *args){ + if (n_args == 1 || args[0] == mp_const_none){ + assert_ge25519(args[0]); + uint8_t buff[32]; + ge25519_pack(buff, &MP_OBJ_C_GE25519(args[0])); + return mp_obj_new_bytes(buff, 32); + + } else { + mp_buffer_info_t bufm; + mp_get_buffer_raise(args[0], &bufm, MP_BUFFER_WRITE); + const mp_int_t offset = n_args >= 3 ? mp_obj_get_int(args[2]) : 0; + if (bufm.len < 32 + offset) { + mp_raise_ValueError("Buffer too small"); + } + + ge25519_pack(((uint8_t*)bufm.buf) + offset, &MP_OBJ_C_GE25519(args[1])); + return args[0]; + } + +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_trezorcrypto_monero_ge25519_pack_obj, 1, 3, mod_trezorcrypto_monero_ge25519_pack); + +/// def ge25519_unpack_vartime(r: Optional[Ge25519], buff: bytes, offset: int = 0) -> Ge25519: +/// ''' +/// Point decompression +/// ''' +STATIC mp_obj_t mod_trezorcrypto_monero_ge25519_unpack_vartime(size_t n_args, const mp_obj_t *args){ + const bool res_arg = n_args >= 2; + const int off = res_arg ? 0 : -1; + mp_obj_t res = mp_obj_new_ge25519_r(res_arg ? args[0] : mp_const_none); + const mp_int_t offset = n_args >= 3 ? mp_obj_get_int(args[2]) : 0; + mp_unpack_ge25519(&MP_OBJ_GE25519(res), args[1+off], offset); + return res; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_trezorcrypto_monero_ge25519_unpack_vartime_obj, 1, 3, mod_trezorcrypto_monero_ge25519_unpack_vartime); + +// +// XMR defs +// + +/// def base58_addr_encode_check(tag: int, buff: bytes) -> bytes: +/// ''' +/// Monero block base 58 encoding +/// ''' +STATIC mp_obj_t mod_trezorcrypto_monero_xmr_base58_addr_encode_check(size_t n_args, const mp_obj_t *args){ + uint8_t out[128]; + mp_buffer_info_t data; + mp_get_buffer_raise(args[1], &data, MP_BUFFER_READ); + + int sz = xmr_base58_addr_encode_check(mp_obj_get_int(args[0]), data.buf, data.len, (char *)out, sizeof(out)); + if (sz == 0){ + mp_raise_ValueError("b58 encoding error"); + } + + return mp_obj_new_bytes(out, sz); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_trezorcrypto_monero_xmr_base58_addr_encode_check_obj, 2, 2, mod_trezorcrypto_monero_xmr_base58_addr_encode_check); + +/// def base58_addr_decode_check(buff: bytes) -> Tuple[bytes, int]: +/// ''' +/// Monero block base 58 decoding, returning (decoded, tag) or raising on error. +/// ''' +STATIC mp_obj_t mod_trezorcrypto_monero_xmr_base58_addr_decode_check(size_t n_args, const mp_obj_t *args){ + uint8_t out[128]; + uint64_t tag; + + mp_buffer_info_t data; + mp_get_buffer_raise(args[0], &data, MP_BUFFER_READ); + + int sz = xmr_base58_addr_decode_check(data.buf, data.len, &tag, out, sizeof(out)); + if (sz == 0){ + mp_raise_ValueError("b58 decoding error"); + } + + mp_obj_tuple_t *tuple = MP_OBJ_TO_PTR(mp_obj_new_tuple(2, NULL)); + tuple->items[0] = mp_obj_new_bytes(out, sz); + tuple->items[1] = mp_obj_new_int_from_ull(tag); + return MP_OBJ_FROM_PTR(tuple); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_trezorcrypto_monero_xmr_base58_addr_decode_check_obj, 1, 1, mod_trezorcrypto_monero_xmr_base58_addr_decode_check); + +/// def xmr_random_scalar(r: Optional[Sc25519] = None) -> Sc25519: +/// ''' +/// Generates a random scalar +/// ''' +STATIC mp_obj_t mod_trezorcrypto_monero_xmr_random_scalar(size_t n_args, const mp_obj_t *args){ + mp_obj_t res = mp_obj_new_scalar_r(n_args == 1 ? args[0] : mp_const_none); + xmr_random_scalar(MP_OBJ_SCALAR(res)); + return res; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_trezorcrypto_monero_xmr_random_scalar_obj, 0, 1, mod_trezorcrypto_monero_xmr_random_scalar); + +/// def xmr_fast_hash(r: Optional[bytes], buff: bytes) -> bytes: +/// ''' +/// XMR fast hash +/// ''' +STATIC mp_obj_t mod_trezorcrypto_monero_xmr_fast_hash(size_t n_args, const mp_obj_t *args){ + const int off = n_args == 2 ? 0 : -1; + uint8_t buff[32]; + uint8_t * buff_use = buff; + if (n_args > 1){ + mp_buffer_info_t odata; + mp_get_buffer_raise(args[0], &odata, MP_BUFFER_WRITE); + if (odata.len < 32){ + mp_raise_ValueError("Output buffer too small"); + } + buff_use = odata.buf; + } + + mp_buffer_info_t data; + mp_get_buffer_raise(args[1+off], &data, MP_BUFFER_READ); + xmr_fast_hash(buff_use, data.buf, data.len); + return n_args == 2 ? args[0] : mp_obj_new_bytes(buff, 32); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_trezorcrypto_monero_xmr_fast_hash_obj, 1, 2, mod_trezorcrypto_monero_xmr_fast_hash); + +/// def xmr_hash_to_ec(r: Optional[Ge25519], buff: bytes) -> Ge25519: +/// ''' +/// XMR hashing to EC point +/// ''' +STATIC mp_obj_t mod_trezorcrypto_monero_xmr_hash_to_ec(size_t n_args, const mp_obj_t *args){ + const bool res_arg = n_args == 2; + const int off = res_arg ? 0 : -1; + mp_obj_t res = mp_obj_new_ge25519_r(res_arg ? args[0] : mp_const_none); + mp_buffer_info_t data; + mp_get_buffer_raise(args[1+off], &data, MP_BUFFER_READ); + xmr_hash_to_ec(&MP_OBJ_GE25519(res), data.buf, data.len); + return res; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_trezorcrypto_monero_xmr_hash_to_ec_obj, 1, 2, mod_trezorcrypto_monero_xmr_hash_to_ec); + +/// def xmr_hash_to_scalar(r: Optional[Sc25519], buff: bytes) -> Sc25519: +/// ''' +/// XMR hashing to EC scalar +/// ''' +STATIC mp_obj_t mod_trezorcrypto_monero_xmr_hash_to_scalar(size_t n_args, const mp_obj_t *args){ + const bool res_arg = n_args == 2; + const int off = res_arg ? 0 : -1; + mp_obj_t res = mp_obj_new_scalar_r(res_arg ? args[0] : mp_const_none); + mp_buffer_info_t data; + mp_get_buffer_raise(args[1+off], &data, MP_BUFFER_READ); + xmr_hash_to_scalar(MP_OBJ_SCALAR(res), data.buf, data.len); + return res; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_trezorcrypto_monero_xmr_hash_to_scalar_obj, 1, 2, mod_trezorcrypto_monero_xmr_hash_to_scalar); + +/// def xmr_derivation_to_scalar(r: Optional[Sc25519], p: Ge25519, output_index: int) -> Sc25519: +/// ''' +/// H_s(derivation || varint(output_index)) +/// ''' +STATIC mp_obj_t mod_trezorcrypto_monero_xmr_derivation_to_scalar(size_t n_args, const mp_obj_t *args){ + const bool res_arg = n_args == 3; + const int off = res_arg ? 0 : -1; + mp_obj_t res = mp_obj_new_scalar_r(res_arg ? args[0] : mp_const_none); + assert_ge25519(args[1+off]); + xmr_derivation_to_scalar(MP_OBJ_SCALAR(res), &MP_OBJ_C_GE25519(args[1+off]), mp_obj_get_int(args[2+off])); + return res; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_trezorcrypto_monero_xmr_derivation_to_scalar_obj, 2, 3, mod_trezorcrypto_monero_xmr_derivation_to_scalar); + +/// def xmr_generate_key_derivation(r: Optional[Ge25519], A: Ge25519, b: Sc25519) -> Ge25519: +/// ''' +/// 8*(key2*key1) +/// ''' +STATIC mp_obj_t mod_trezorcrypto_monero_xmr_generate_key_derivation(size_t n_args, const mp_obj_t *args){ + const bool res_arg = n_args == 3; + const int off = res_arg ? 0 : -1; + mp_obj_t res = mp_obj_new_ge25519_r(res_arg ? args[0] : mp_const_none); + assert_ge25519(args[1+off]); + assert_scalar(args[2+off]); + xmr_generate_key_derivation(&MP_OBJ_GE25519(res), &MP_OBJ_C_GE25519(args[1+off]), MP_OBJ_C_SCALAR(args[2+off])); + return res; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_trezorcrypto_monero_xmr_generate_key_derivation_obj, 2, 3, mod_trezorcrypto_monero_xmr_generate_key_derivation); + +/// def xmr_derive_private_key(r: Optional[Sc25519], deriv: Ge25519, idx: int, base: Sc25519) -> Sc25519: +/// ''' +/// base + H_s(derivation || varint(output_index)) +/// ''' +STATIC mp_obj_t mod_trezorcrypto_monero_xmr_derive_private_key(size_t n_args, const mp_obj_t *args){ + const bool res_arg = n_args == 4; + const int off = res_arg ? 0 : -1; + mp_obj_t res = mp_obj_new_scalar_r(res_arg ? args[0] : mp_const_none); + assert_ge25519(args[1+off]); + assert_scalar(args[3+off]); + xmr_derive_private_key(MP_OBJ_SCALAR(res), &MP_OBJ_C_GE25519(args[1+off]), mp_obj_get_int(args[2+off]), MP_OBJ_C_SCALAR(args[3+off])); + return res; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_trezorcrypto_monero_xmr_derive_private_key_obj, 3, 4, mod_trezorcrypto_monero_xmr_derive_private_key); + +/// def xmr_derive_public_key(r: Optional[Ge25519], deriv: Ge25519, idx: int, base: Ge25519) -> Ge25519: +/// ''' +/// H_s(derivation || varint(output_index))G + base +/// ''' +STATIC mp_obj_t mod_trezorcrypto_monero_xmr_derive_public_key(size_t n_args, const mp_obj_t *args){ + const bool res_arg = n_args == 4; + const int off = res_arg ? 0 : -1; + mp_obj_t res = mp_obj_new_ge25519_r(res_arg ? args[0] : mp_const_none); + assert_ge25519(args[1+off]); + assert_ge25519(args[3+off]); + xmr_derive_public_key(&MP_OBJ_GE25519(res), &MP_OBJ_C_GE25519(args[1+off]), mp_obj_get_int(args[2+off]), &MP_OBJ_C_GE25519(args[3+off])); + return res; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_trezorcrypto_monero_xmr_derive_public_key_obj, 3, 4, mod_trezorcrypto_monero_xmr_derive_public_key); + +/// def xmr_add_keys2(r: Optional[Ge25519], a: Sc25519, b: Sc25519, B: Ge25519) -> Ge25519: +/// ''' +/// aG + bB, G is basepoint +/// ''' +STATIC mp_obj_t mod_trezorcrypto_monero_xmr_add_keys2(size_t n_args, const mp_obj_t *args){ + const bool res_arg = n_args == 4; + const int off = res_arg ? 0 : -1; + mp_obj_t res = mp_obj_new_ge25519_r(res_arg ? args[0] : mp_const_none); + assert_scalar(args[1+off]); + assert_scalar(args[2+off]); + assert_ge25519(args[3+off]); + xmr_add_keys2(&MP_OBJ_GE25519(res), MP_OBJ_SCALAR(args[1+off]), MP_OBJ_SCALAR(args[2+off]), &MP_OBJ_C_GE25519(args[3+off])); + return res; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_trezorcrypto_monero_xmr_add_keys2_obj, 3, 4, mod_trezorcrypto_monero_xmr_add_keys2); + +/// def xmr_add_keys2_vartime(r: Optional[Ge25519], a: Sc25519, b: Sc25519, B: Ge25519) -> Ge25519: +/// ''' +/// aG + bB, G is basepoint +/// ''' +STATIC mp_obj_t mod_trezorcrypto_monero_xmr_add_keys2_vartime(size_t n_args, const mp_obj_t *args){ + const bool res_arg = n_args == 4; + const int off = res_arg ? 0 : -1; + mp_obj_t res = mp_obj_new_ge25519_r(res_arg ? args[0] : mp_const_none); + assert_scalar(args[1+off]); + assert_scalar(args[2+off]); + assert_ge25519(args[3+off]); + xmr_add_keys2_vartime(&MP_OBJ_GE25519(res), MP_OBJ_SCALAR(args[1+off]), MP_OBJ_SCALAR(args[2+off]), &MP_OBJ_C_GE25519(args[3+off])); + return res; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_trezorcrypto_monero_xmr_add_keys2_vartime_obj, 3, 4, mod_trezorcrypto_monero_xmr_add_keys2_vartime); + +/// def xmr_add_keys3(r: Optional[Ge25519], a: Sc25519, A: Ge25519, b: Sc25519, B: Ge25519) -> Ge25519: +/// ''' +/// aA + bB +/// ''' +STATIC mp_obj_t mod_trezorcrypto_monero_xmr_add_keys3(size_t n_args, const mp_obj_t *args){ + const bool res_arg = n_args == 5; + const int off = res_arg ? 0 : -1; + mp_obj_t res = mp_obj_new_ge25519_r(res_arg ? args[0] : mp_const_none); + assert_scalar(args[1+off]); + assert_ge25519(args[2+off]); + assert_scalar(args[3+off]); + assert_ge25519(args[4+off]); + xmr_add_keys3(&MP_OBJ_GE25519(res), + MP_OBJ_SCALAR(args[1+off]), &MP_OBJ_C_GE25519(args[2+off]), + MP_OBJ_SCALAR(args[3+off]), &MP_OBJ_C_GE25519(args[4+off])); + return res; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_trezorcrypto_monero_xmr_add_keys3_obj, 4, 5, mod_trezorcrypto_monero_xmr_add_keys3); + +/// def xmr_add_keys3_vartime(r: Optional[Ge25519], a: Sc25519, A: Ge25519, b: Sc25519, B: Ge25519) -> Ge25519: +/// ''' +/// aA + bB +/// ''' +STATIC mp_obj_t mod_trezorcrypto_monero_xmr_add_keys3_vartime(size_t n_args, const mp_obj_t *args){ + const bool res_arg = n_args == 5; + const int off = res_arg ? 0 : -1; + mp_obj_t res = mp_obj_new_ge25519_r(res_arg ? args[0] : mp_const_none); + assert_scalar(args[1+off]); + assert_ge25519(args[2+off]); + assert_scalar(args[3+off]); + assert_ge25519(args[4+off]); + xmr_add_keys3_vartime(&MP_OBJ_GE25519(res), + MP_OBJ_SCALAR(args[1+off]), &MP_OBJ_C_GE25519(args[2+off]), + MP_OBJ_SCALAR(args[3+off]), &MP_OBJ_C_GE25519(args[4+off])); + return res; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_trezorcrypto_monero_xmr_add_keys3_vartime_obj, 4, 5, mod_trezorcrypto_monero_xmr_add_keys3_vartime); + +/// def xmr_get_subaddress_secret_key(r: Optional[Sc25519], major: int, minor: int, m: Sc25519) -> Sc25519: +/// ''' +/// Hs(SubAddr || a || index_major || index_minor) +/// ''' +STATIC mp_obj_t mod_trezorcrypto_monero_xmr_get_subaddress_secret_key(size_t n_args, const mp_obj_t *args){ + const bool res_arg = n_args == 4; + const int off = res_arg ? 0 : -1; + mp_obj_t res = mp_obj_new_scalar_r(res_arg ? args[0] : mp_const_none); + assert_scalar(args[3+off]); + xmr_get_subaddress_secret_key(MP_OBJ_SCALAR(res), mp_obj_get_int(args[1+off]), mp_obj_get_int(args[2+off]), MP_OBJ_C_SCALAR(args[3+off])); + return res; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_trezorcrypto_monero_xmr_get_subaddress_secret_key_obj, 3, 4, mod_trezorcrypto_monero_xmr_get_subaddress_secret_key); + +/// def xmr_gen_c(r: Optional[Ge25519], a: Sc25519, amount: int) -> Ge25519: +/// ''' +/// aG + amount * H +/// ''' +STATIC mp_obj_t mod_trezorcrypto_monero_xmr_gen_c(size_t n_args, const mp_obj_t *args){ + const bool res_arg = n_args == 3; + const int off = res_arg ? 0 : -1; + mp_obj_t res = mp_obj_new_ge25519_r(res_arg ? args[0] : mp_const_none); + assert_scalar(args[1+off]); + xmr_gen_c(&MP_OBJ_GE25519(res), MP_OBJ_C_SCALAR(args[1+off]), mp_obj_get_uint64(args[2+off])); + return res; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_trezorcrypto_monero_xmr_gen_c_obj, 2, 3, mod_trezorcrypto_monero_xmr_gen_c); + +/// def ct_equals(a: bytes, b: bytes) -> bool: +/// ''' +/// Constant time buffer comparison +/// ''' +STATIC mp_obj_t mod_trezorcrypto_ct_equals(const mp_obj_t a, const mp_obj_t b){ + mp_buffer_info_t buff_a, buff_b; + mp_get_buffer_raise(a, &buff_a, MP_BUFFER_READ); + mp_get_buffer_raise(b, &buff_b, MP_BUFFER_READ); + + if (buff_a.len != buff_b.len) { + return MP_OBJ_NEW_SMALL_INT(0); + } + + int r = ed25519_verify(buff_a.buf, buff_b.buf, buff_a.len); + return MP_OBJ_NEW_SMALL_INT(r); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_2(mod_trezorcrypto_ct_equals_obj, mod_trezorcrypto_ct_equals); + +// Hasher +STATIC mp_obj_t mod_trezorcrypto_monero_hasher_update(mp_obj_t self, const mp_obj_t arg){ + mp_obj_hasher_t *o = MP_OBJ_TO_PTR(self); + mp_buffer_info_t buff; + mp_get_buffer_raise(arg, &buff, MP_BUFFER_READ); + if (buff.len > 0) { + xmr_hasher_update(&o->h, buff.buf, buff.len); + } + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_2(mod_trezorcrypto_monero_hasher_update_obj, mod_trezorcrypto_monero_hasher_update); + +STATIC mp_obj_t mod_trezorcrypto_monero_hasher_digest(size_t n_args, const mp_obj_t *args){ + mp_obj_hasher_t *o = MP_OBJ_TO_PTR(args[0]); + + Hasher ctx; + memcpy(&ctx, &(o->h), sizeof(Hasher)); + + uint8_t out[SHA3_256_DIGEST_LENGTH]; + xmr_hasher_final(&ctx, out); + memset(&ctx, 0, sizeof(SHA3_CTX)); + + if (n_args == 1 || args[1] == mp_const_none){ + return mp_obj_new_bytes(out, sizeof(out)); + + } else { + mp_buffer_info_t bufm; + mp_get_buffer_raise(args[1], &bufm, MP_BUFFER_WRITE); + const mp_int_t offset = n_args >= 3 ? mp_obj_get_int(args[2]) : 0; + if (bufm.len < 32 + offset) { + mp_raise_ValueError("Buffer too small"); + } + + memcpy((uint8_t*)bufm.buf + offset, out, SHA3_256_DIGEST_LENGTH); + return args[1]; + } +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_trezorcrypto_monero_hasher_digest_obj, 1, 3, mod_trezorcrypto_monero_hasher_digest); + +STATIC mp_obj_t mod_trezorcrypto_monero_hasher_copy(mp_obj_t self){ + mp_obj_hasher_t *o = MP_OBJ_TO_PTR(self); + mp_obj_hasher_t *cp = m_new_obj(mp_obj_hasher_t); + cp->base.type = o->base.type; + memcpy(&(cp->h), &(o->h), sizeof(Hasher)); + return MP_OBJ_FROM_PTR(o); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(mod_trezorcrypto_monero_hasher_copy_obj, mod_trezorcrypto_monero_hasher_copy); + + +// +// Type defs +// + +STATIC const mp_rom_map_elem_t mod_trezorcrypto_monero_ge25519_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&mod_trezorcrypto_monero_ge25519___del___obj) }, +}; +STATIC MP_DEFINE_CONST_DICT(mod_trezorcrypto_monero_ge25519_locals_dict, mod_trezorcrypto_monero_ge25519_locals_dict_table); + +STATIC const mp_obj_type_t mod_trezorcrypto_monero_ge25519_type = { + { &mp_type_type }, + .name = MP_QSTR_Ge25519, + .make_new = mod_trezorcrypto_monero_ge25519_make_new, + .locals_dict = (void*)&mod_trezorcrypto_monero_ge25519_locals_dict, +}; + +STATIC const mp_rom_map_elem_t mod_trezorcrypto_monero_bignum256modm_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&mod_trezorcrypto_monero_bignum256modm___del___obj) }, +}; +STATIC MP_DEFINE_CONST_DICT(mod_trezorcrypto_monero_bignum256modm_locals_dict, mod_trezorcrypto_monero_bignum256modm_locals_dict_table); + + +STATIC const mp_obj_type_t mod_trezorcrypto_monero_bignum256modm_type = { + { &mp_type_type }, + .name = MP_QSTR_Sc25519, + .make_new = mod_trezorcrypto_monero_bignum256modm_make_new, + .locals_dict = (void*)&mod_trezorcrypto_monero_bignum256modm_locals_dict, +}; + +STATIC const mp_rom_map_elem_t mod_trezorcrypto_monero_hasher_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_update), MP_ROM_PTR(&mod_trezorcrypto_monero_hasher_update_obj) }, + { MP_ROM_QSTR(MP_QSTR_digest), MP_ROM_PTR(&mod_trezorcrypto_monero_hasher_digest_obj) }, + { MP_ROM_QSTR(MP_QSTR_copy), MP_ROM_PTR(&mod_trezorcrypto_monero_hasher_copy_obj) }, + { MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&mod_trezorcrypto_monero_hasher___del___obj) }, + { MP_ROM_QSTR(MP_QSTR_block_size), MP_OBJ_NEW_SMALL_INT(SHA3_256_BLOCK_LENGTH) }, + { MP_ROM_QSTR(MP_QSTR_digest_size), MP_OBJ_NEW_SMALL_INT(SHA3_256_DIGEST_LENGTH) }, +}; +STATIC MP_DEFINE_CONST_DICT(mod_trezorcrypto_monero_hasher_locals_dict, mod_trezorcrypto_monero_hasher_locals_dict_table); + + +STATIC const mp_obj_type_t mod_trezorcrypto_monero_hasher_type = { + { &mp_type_type }, + .name = MP_QSTR_hasher, + .make_new = mod_trezorcrypto_monero_hasher_make_new, + .locals_dict = (void*)&mod_trezorcrypto_monero_hasher_locals_dict, +}; + +STATIC const mp_rom_map_elem_t mod_trezorcrypto_monero_globals_table[] = { + { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_monero) }, + { MP_ROM_QSTR(MP_QSTR_init256_modm), MP_ROM_PTR(&mod_trezorcrypto_monero_init256_modm_obj) }, + { MP_ROM_QSTR(MP_QSTR_check256_modm), MP_ROM_PTR(&mod_trezorcrypto_monero_check256_modm_obj) }, + { MP_ROM_QSTR(MP_QSTR_iszero256_modm), MP_ROM_PTR(&mod_trezorcrypto_monero_iszero256_modm_obj) }, + { MP_ROM_QSTR(MP_QSTR_eq256_modm), MP_ROM_PTR(&mod_trezorcrypto_monero_eq256_modm_obj) }, + { MP_ROM_QSTR(MP_QSTR_get256_modm), MP_ROM_PTR(&mod_trezorcrypto_monero_get256_modm_obj) }, + { MP_ROM_QSTR(MP_QSTR_add256_modm), MP_ROM_PTR(&mod_trezorcrypto_monero_add256_modm_obj) }, + { MP_ROM_QSTR(MP_QSTR_sub256_modm), MP_ROM_PTR(&mod_trezorcrypto_monero_sub256_modm_obj) }, + { MP_ROM_QSTR(MP_QSTR_mul256_modm), MP_ROM_PTR(&mod_trezorcrypto_monero_mul256_modm_obj) }, + { MP_ROM_QSTR(MP_QSTR_mulsub256_modm), MP_ROM_PTR(&mod_trezorcrypto_monero_mulsub256_modm_obj) }, + { MP_ROM_QSTR(MP_QSTR_muladd256_modm), MP_ROM_PTR(&mod_trezorcrypto_monero_muladd256_modm_obj) }, + { MP_ROM_QSTR(MP_QSTR_inv256_modm), MP_ROM_PTR(&mod_trezorcrypto_monero_inv256_modm_obj) }, + { MP_ROM_QSTR(MP_QSTR_pack256_modm), MP_ROM_PTR(&mod_trezorcrypto_monero_pack256_modm_obj) }, + { MP_ROM_QSTR(MP_QSTR_unpack256_modm), MP_ROM_PTR(&mod_trezorcrypto_monero_unpack256_modm_obj) }, + { MP_ROM_QSTR(MP_QSTR_unpack256_modm_noreduce), MP_ROM_PTR(&mod_trezorcrypto_monero_unpack256_modm_noreduce_obj) }, + { MP_ROM_QSTR(MP_QSTR_ge25519_set_neutral), MP_ROM_PTR(&mod_trezorcrypto_monero_ge25519_set_neutral_obj) }, + { MP_ROM_QSTR(MP_QSTR_ge25519_set_h), MP_ROM_PTR(&mod_trezorcrypto_monero_ge25519_set_xmr_h_obj) }, + { MP_ROM_QSTR(MP_QSTR_ge25519_pack), MP_ROM_PTR(&mod_trezorcrypto_monero_ge25519_pack_obj) }, + { MP_ROM_QSTR(MP_QSTR_ge25519_unpack_vartime), MP_ROM_PTR(&mod_trezorcrypto_monero_ge25519_unpack_vartime_obj) }, + { MP_ROM_QSTR(MP_QSTR_ge25519_check), MP_ROM_PTR(&mod_trezorcrypto_monero_ge25519_check_obj) }, + { MP_ROM_QSTR(MP_QSTR_ge25519_eq), MP_ROM_PTR(&mod_trezorcrypto_monero_ge25519_eq_obj) }, + { MP_ROM_QSTR(MP_QSTR_ge25519_add), MP_ROM_PTR(&mod_trezorcrypto_monero_ge25519_add_obj) }, + { MP_ROM_QSTR(MP_QSTR_ge25519_sub), MP_ROM_PTR(&mod_trezorcrypto_monero_ge25519_sub_obj) }, + { MP_ROM_QSTR(MP_QSTR_ge25519_double), MP_ROM_PTR(&mod_trezorcrypto_monero_ge25519_double_obj) }, + { MP_ROM_QSTR(MP_QSTR_ge25519_mul8), MP_ROM_PTR(&mod_trezorcrypto_monero_ge25519_mul8_obj) }, + { MP_ROM_QSTR(MP_QSTR_ge25519_double_scalarmult_vartime), MP_ROM_PTR(&mod_trezorcrypto_monero_ge25519_double_scalarmult_vartime_obj) }, + { MP_ROM_QSTR(MP_QSTR_ge25519_double_scalarmult_vartime2), MP_ROM_PTR(&mod_trezorcrypto_monero_ge25519_double_scalarmult_vartime2_obj) }, + { MP_ROM_QSTR(MP_QSTR_ge25519_scalarmult_base), MP_ROM_PTR(&mod_trezorcrypto_monero_ge25519_scalarmult_base_obj) }, + { MP_ROM_QSTR(MP_QSTR_ge25519_scalarmult), MP_ROM_PTR(&mod_trezorcrypto_monero_ge25519_scalarmult_obj) }, + { MP_ROM_QSTR(MP_QSTR_xmr_base58_addr_encode_check), MP_ROM_PTR(&mod_trezorcrypto_monero_xmr_base58_addr_encode_check_obj) }, + { MP_ROM_QSTR(MP_QSTR_xmr_base58_addr_decode_check), MP_ROM_PTR(&mod_trezorcrypto_monero_xmr_base58_addr_decode_check_obj) }, + { MP_ROM_QSTR(MP_QSTR_xmr_random_scalar), MP_ROM_PTR(&mod_trezorcrypto_monero_xmr_random_scalar_obj) }, + { MP_ROM_QSTR(MP_QSTR_xmr_fast_hash), MP_ROM_PTR(&mod_trezorcrypto_monero_xmr_fast_hash_obj) }, + { MP_ROM_QSTR(MP_QSTR_xmr_hash_to_ec), MP_ROM_PTR(&mod_trezorcrypto_monero_xmr_hash_to_ec_obj) }, + { MP_ROM_QSTR(MP_QSTR_xmr_hash_to_scalar), MP_ROM_PTR(&mod_trezorcrypto_monero_xmr_hash_to_scalar_obj) }, + { MP_ROM_QSTR(MP_QSTR_xmr_derivation_to_scalar), MP_ROM_PTR(&mod_trezorcrypto_monero_xmr_derivation_to_scalar_obj) }, + { MP_ROM_QSTR(MP_QSTR_xmr_generate_key_derivation), MP_ROM_PTR(&mod_trezorcrypto_monero_xmr_generate_key_derivation_obj) }, + { MP_ROM_QSTR(MP_QSTR_xmr_derive_private_key), MP_ROM_PTR(&mod_trezorcrypto_monero_xmr_derive_private_key_obj) }, + { MP_ROM_QSTR(MP_QSTR_xmr_derive_public_key), MP_ROM_PTR(&mod_trezorcrypto_monero_xmr_derive_public_key_obj) }, + { MP_ROM_QSTR(MP_QSTR_xmr_add_keys2), MP_ROM_PTR(&mod_trezorcrypto_monero_xmr_add_keys2_obj) }, + { MP_ROM_QSTR(MP_QSTR_xmr_add_keys2_vartime), MP_ROM_PTR(&mod_trezorcrypto_monero_xmr_add_keys2_vartime_obj) }, + { MP_ROM_QSTR(MP_QSTR_xmr_add_keys3), MP_ROM_PTR(&mod_trezorcrypto_monero_xmr_add_keys3_obj) }, + { MP_ROM_QSTR(MP_QSTR_xmr_add_keys3_vartime), MP_ROM_PTR(&mod_trezorcrypto_monero_xmr_add_keys3_vartime_obj) }, + { MP_ROM_QSTR(MP_QSTR_xmr_get_subaddress_secret_key), MP_ROM_PTR(&mod_trezorcrypto_monero_xmr_get_subaddress_secret_key_obj) }, + { MP_ROM_QSTR(MP_QSTR_xmr_gen_c), MP_ROM_PTR(&mod_trezorcrypto_monero_xmr_gen_c_obj) }, + { MP_ROM_QSTR(MP_QSTR_ct_equals), MP_ROM_PTR(&mod_trezorcrypto_ct_equals_obj) }, +}; +STATIC MP_DEFINE_CONST_DICT(mod_trezorcrypto_monero_globals, mod_trezorcrypto_monero_globals_table); + +STATIC const mp_obj_module_t mod_trezorcrypto_monero_module = { + .base = { &mp_type_module }, + .globals = (mp_obj_dict_t*)&mod_trezorcrypto_monero_globals, +}; diff --git a/embed/extmod/modtrezorcrypto/modtrezorcrypto-nem.h b/embed/extmod/modtrezorcrypto/modtrezorcrypto-nem.h index 57d199029..11fa0b01d 100644 --- a/embed/extmod/modtrezorcrypto/modtrezorcrypto-nem.h +++ b/embed/extmod/modtrezorcrypto/modtrezorcrypto-nem.h @@ -23,6 +23,8 @@ #include "nem.h" +/// package: trezorcrypto.nem + /// def validate_address(address: str, network: int) -> bool: /// ''' /// Validate a NEM address diff --git a/embed/extmod/modtrezorcrypto/modtrezorcrypto-nist256p1.h b/embed/extmod/modtrezorcrypto/modtrezorcrypto-nist256p1.h index ee1cb1c74..8c6b5e5c1 100644 --- a/embed/extmod/modtrezorcrypto/modtrezorcrypto-nist256p1.h +++ b/embed/extmod/modtrezorcrypto/modtrezorcrypto-nist256p1.h @@ -22,6 +22,8 @@ #include "ecdsa.h" #include "nist256p1.h" +/// package: trezorcrypto.nist256p1 + /// def generate_secret() -> bytes: /// ''' /// Generate secret key. diff --git a/embed/extmod/modtrezorcrypto/modtrezorcrypto-pbkdf2.h b/embed/extmod/modtrezorcrypto/modtrezorcrypto-pbkdf2.h index ca48e400f..f941f9ed9 100644 --- a/embed/extmod/modtrezorcrypto/modtrezorcrypto-pbkdf2.h +++ b/embed/extmod/modtrezorcrypto/modtrezorcrypto-pbkdf2.h @@ -25,6 +25,8 @@ #define PRF_HMAC_SHA256 256 #define PRF_HMAC_SHA512 512 +/// package: trezorcrypto.__init__ + /// class Pbkdf2: /// ''' /// PBKDF2 context. diff --git a/embed/extmod/modtrezorcrypto/modtrezorcrypto-random.h b/embed/extmod/modtrezorcrypto/modtrezorcrypto-random.h index 288d8cbac..57e00bd83 100644 --- a/embed/extmod/modtrezorcrypto/modtrezorcrypto-random.h +++ b/embed/extmod/modtrezorcrypto/modtrezorcrypto-random.h @@ -23,6 +23,8 @@ #include "rand.h" +/// package: trezorcrypto.random + /// def uniform(n: int) -> int: /// ''' /// Compute uniform random number from interval 0 ... n - 1. diff --git a/embed/extmod/modtrezorcrypto/modtrezorcrypto-rfc6979.h b/embed/extmod/modtrezorcrypto/modtrezorcrypto-rfc6979.h index 1662993f1..f89294de0 100644 --- a/embed/extmod/modtrezorcrypto/modtrezorcrypto-rfc6979.h +++ b/embed/extmod/modtrezorcrypto/modtrezorcrypto-rfc6979.h @@ -21,6 +21,8 @@ #include "rfc6979.h" +/// package: trezorcrypto.__init__ + /// class Rfc6979: /// ''' /// RFC6979 context. diff --git a/embed/extmod/modtrezorcrypto/modtrezorcrypto-ripemd160.h b/embed/extmod/modtrezorcrypto/modtrezorcrypto-ripemd160.h index 76797d94d..9aba1238f 100644 --- a/embed/extmod/modtrezorcrypto/modtrezorcrypto-ripemd160.h +++ b/embed/extmod/modtrezorcrypto/modtrezorcrypto-ripemd160.h @@ -22,6 +22,8 @@ #include "ripemd160.h" #include "memzero.h" +/// package: trezorcrypto.__init__ + /// class Ripemd160: /// ''' /// RIPEMD160 context. diff --git a/embed/extmod/modtrezorcrypto/modtrezorcrypto-secp256k1.h b/embed/extmod/modtrezorcrypto/modtrezorcrypto-secp256k1.h index f9eda2795..e994aa0ee 100644 --- a/embed/extmod/modtrezorcrypto/modtrezorcrypto-secp256k1.h +++ b/embed/extmod/modtrezorcrypto/modtrezorcrypto-secp256k1.h @@ -22,6 +22,8 @@ #include "ecdsa.h" #include "secp256k1.h" +/// package: trezorcrypto.secp256k1 + /// def generate_secret() -> bytes: /// ''' /// Generate secret key. diff --git a/embed/extmod/modtrezorcrypto/modtrezorcrypto-sha1.h b/embed/extmod/modtrezorcrypto/modtrezorcrypto-sha1.h index a9d8e584e..fff30dbfc 100644 --- a/embed/extmod/modtrezorcrypto/modtrezorcrypto-sha1.h +++ b/embed/extmod/modtrezorcrypto/modtrezorcrypto-sha1.h @@ -22,6 +22,8 @@ #include "sha2.h" #include "memzero.h" +/// package: trezorcrypto.__init__ + /// class Sha1: /// ''' /// SHA1 context. diff --git a/embed/extmod/modtrezorcrypto/modtrezorcrypto-sha256.h b/embed/extmod/modtrezorcrypto/modtrezorcrypto-sha256.h index 9d4acacbc..98c1d1caa 100644 --- a/embed/extmod/modtrezorcrypto/modtrezorcrypto-sha256.h +++ b/embed/extmod/modtrezorcrypto/modtrezorcrypto-sha256.h @@ -22,6 +22,8 @@ #include "sha2.h" #include "memzero.h" +/// package: trezorcrypto.__init__ + /// class Sha256: /// ''' /// SHA256 context. diff --git a/embed/extmod/modtrezorcrypto/modtrezorcrypto-sha3-256.h b/embed/extmod/modtrezorcrypto/modtrezorcrypto-sha3-256.h index c55333434..a76173953 100644 --- a/embed/extmod/modtrezorcrypto/modtrezorcrypto-sha3-256.h +++ b/embed/extmod/modtrezorcrypto/modtrezorcrypto-sha3-256.h @@ -22,6 +22,8 @@ #include "sha3.h" #include "memzero.h" +/// package: trezorcrypto.__init__ + /// class Sha3_256: /// ''' /// SHA3_256 context. diff --git a/embed/extmod/modtrezorcrypto/modtrezorcrypto-sha3-512.h b/embed/extmod/modtrezorcrypto/modtrezorcrypto-sha3-512.h index dd51e3c7c..e374ad1c7 100644 --- a/embed/extmod/modtrezorcrypto/modtrezorcrypto-sha3-512.h +++ b/embed/extmod/modtrezorcrypto/modtrezorcrypto-sha3-512.h @@ -22,6 +22,8 @@ #include "sha3.h" #include "memzero.h" +/// package: trezorcrypto.__init__ + /// class Sha3_512: /// ''' /// SHA3_512 context. diff --git a/embed/extmod/modtrezorcrypto/modtrezorcrypto-sha512.h b/embed/extmod/modtrezorcrypto/modtrezorcrypto-sha512.h index 70489ce48..09d7ac75a 100644 --- a/embed/extmod/modtrezorcrypto/modtrezorcrypto-sha512.h +++ b/embed/extmod/modtrezorcrypto/modtrezorcrypto-sha512.h @@ -22,6 +22,8 @@ #include "sha2.h" #include "memzero.h" +/// package: trezorcrypto.__init__ + /// class Sha512: /// ''' /// SHA512 context. diff --git a/embed/extmod/modtrezorcrypto/modtrezorcrypto.c b/embed/extmod/modtrezorcrypto/modtrezorcrypto.c index dd6b4a94c..9bd741123 100644 --- a/embed/extmod/modtrezorcrypto/modtrezorcrypto.c +++ b/embed/extmod/modtrezorcrypto/modtrezorcrypto.c @@ -48,6 +48,7 @@ #include "modtrezorcrypto-sha512.h" #include "modtrezorcrypto-sha3-256.h" #include "modtrezorcrypto-sha3-512.h" +#include "modtrezorcrypto-monero.h" STATIC const mp_rom_map_elem_t mp_module_trezorcrypto_globals_table[] = { { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_trezorcrypto) }, @@ -61,6 +62,7 @@ STATIC const mp_rom_map_elem_t mp_module_trezorcrypto_globals_table[] = { { MP_ROM_QSTR(MP_QSTR_crc), MP_ROM_PTR(&mod_trezorcrypto_crc_module) }, { MP_ROM_QSTR(MP_QSTR_curve25519), MP_ROM_PTR(&mod_trezorcrypto_curve25519_module) }, { MP_ROM_QSTR(MP_QSTR_ed25519), MP_ROM_PTR(&mod_trezorcrypto_ed25519_module) }, + { MP_ROM_QSTR(MP_QSTR_monero), MP_ROM_PTR(&mod_trezorcrypto_monero_module) }, { MP_ROM_QSTR(MP_QSTR_nist256p1), MP_ROM_PTR(&mod_trezorcrypto_nist256p1_module) }, { MP_ROM_QSTR(MP_QSTR_groestl512), MP_ROM_PTR(&mod_trezorcrypto_Groestl512_type) }, { MP_ROM_QSTR(MP_QSTR_nem), MP_ROM_PTR(&mod_trezorcrypto_nem_module) }, diff --git a/mocks/generated/trezorcrypto.py b/mocks/generated/trezorcrypto.py deleted file mode 100644 index 62d89b923..000000000 --- a/mocks/generated/trezorcrypto.py +++ /dev/null @@ -1,586 +0,0 @@ -from typing import * - -# extmod/modtrezorcrypto/modtrezorcrypto-aes.h -class AES: - ''' - AES context. - ''' - - def __init__(self, mode: int, key: bytes, iv: bytes = None) -> None: - ''' - Initialize AES context. - ''' - - def update(self, data: bytes) -> bytes: - ''' - Update AES context with data. - ''' - -# extmod/modtrezorcrypto/modtrezorcrypto-bip32.h -class HDNode: - ''' - BIP0032 HD node structure. - ''' - - def __init__(self, - depth: int, - fingerprint: int, - child_num: int, - chain_code: bytes, - private_key: bytes = None, - public_key: bytes = None, - curve_name: str = None) -> None: - ''' - ''' - - def derive(self, index: int, public: bool=False) -> None: - ''' - Derive a BIP0032 child node in place. - ''' - - def derive_path(self, path: List[int]) -> None: - ''' - Go through a list of indexes and iteratively derive a child node in place. - ''' - - def serialize_public(self, version: int) -> str: - ''' - Serialize the public info from HD node to base58 string. - ''' - - def serialize_private(self, version: int) -> str: - ''' - Serialize the private info HD node to base58 string. - ''' - - def clone(self) -> HDNode: - ''' - Returns a copy of the HD node. - ''' - - def depth(self) -> int: - ''' - Returns a depth of the HD node. - ''' - - def fingerprint(self) -> int: - ''' - Returns a fingerprint of the HD node (hash of the parent public key). - ''' - - def child_num(self) -> int: - ''' - Returns a child index of the HD node. - ''' - - def chain_code(self) -> bytes: - ''' - Returns a chain code of the HD node. - ''' - - def private_key(self) -> bytes: - ''' - Returns a private key of the HD node. - ''' - - def public_key(self) -> bytes: - ''' - Returns a public key of the HD node. - ''' - - def address(self, version: int) -> str: - ''' - Compute a base58-encoded address string from the HD node. - ''' - - def nem_address(self, network: int) -> str: - ''' - Compute a NEM address string from the HD node. - ''' - - def nem_encrypt(self, transfer_public_key: bytes, iv: bytes, salt: bytes, payload: bytes) -> bytes: - ''' - Encrypts payload using the transfer's public key - ''' - - def ethereum_pubkeyhash(self) -> bytes: - ''' - Compute an Ethereum pubkeyhash (aka address) from the HD node. - ''' - - def deserialize(self, value: str, version_public: int, version_private: int) -> HDNode: - ''' - Construct a BIP0032 HD node from a base58-serialized value. - ''' - - def from_seed(seed: bytes, curve_name: str) -> HDNode: - ''' - Construct a BIP0032 HD node from a BIP0039 seed value. - ''' - -# extmod/modtrezorcrypto/modtrezorcrypto-bip39.h -def find_word(prefix: str) -> Optional[str]: - ''' - Return the first word from the wordlist starting with prefix. - ''' - -# extmod/modtrezorcrypto/modtrezorcrypto-bip39.h -def complete_word(prefix: str) -> int: - ''' - Return possible 1-letter suffixes for given word prefix. - Result is a bitmask, with 'a' on the lowest bit, 'b' on the second lowest, etc. - ''' - -# extmod/modtrezorcrypto/modtrezorcrypto-bip39.h -def generate(strength: int) -> str: - ''' - Generate a mnemonic of given strength (128, 160, 192, 224 and 256 bits). - ''' - -# extmod/modtrezorcrypto/modtrezorcrypto-bip39.h -def from_data(data: bytes) -> str: - ''' - Generate a mnemonic from given data (of 16, 20, 24, 28 and 32 bytes). - ''' - -# extmod/modtrezorcrypto/modtrezorcrypto-bip39.h -def check(mnemonic: str) -> bool: - ''' - Check whether given mnemonic is valid. - ''' - -# extmod/modtrezorcrypto/modtrezorcrypto-bip39.h -def seed(mnemonic: str, passphrase: str) -> bytes: - ''' - Generate seed from mnemonic and passphrase. - ''' - -# extmod/modtrezorcrypto/modtrezorcrypto-blake256.h -class Blake256: - ''' - Blake256 context. - ''' - - def __init__(self, data: bytes = None) -> None: - ''' - Creates a hash context object. - ''' - - def update(self, data: bytes) -> None: - ''' - Update the hash context with hashed data. - ''' - - def digest(self) -> bytes: - ''' - Returns the digest of hashed data. - ''' - -# extmod/modtrezorcrypto/modtrezorcrypto-blake2b.h -class Blake2b: - ''' - Blake2b context. - ''' - - def __init__(self, data: bytes = None, key: bytes = None) -> None: - ''' - Creates a hash context object. - ''' - - def update(self, data: bytes) -> None: - ''' - Update the hash context with hashed data. - ''' - - def digest(self) -> bytes: - ''' - Returns the digest of hashed data. - ''' - -# extmod/modtrezorcrypto/modtrezorcrypto-blake2s.h -class Blake2s: - ''' - Blake2s context. - ''' - - def __init__(self, data: bytes = None, key: bytes = None) -> None: - ''' - Creates a hash context object. - ''' - - def update(self, data: bytes) -> None: - ''' - Update the hash context with hashed data. - ''' - - def digest(self) -> bytes: - ''' - Returns the digest of hashed data. - ''' - -# extmod/modtrezorcrypto/modtrezorcrypto-chacha20poly1305.h -class ChaCha20Poly1305: - ''' - ChaCha20Poly1305 context. - ''' - - def __init__(self, key: bytes, nonce: bytes) -> None: - ''' - Initialize the ChaCha20 + Poly1305 context for encryption or decryption - using a 32 byte key and 12 byte nonce as in the RFC 7539 style. - ''' - - def encrypt(self, data: bytes) -> bytes: - ''' - Encrypt data (length of data must be divisible by 64 except for the final value). - ''' - - def decrypt(self, data: bytes) -> bytes: - ''' - Decrypt data (length of data must be divisible by 64 except for the final value). - ''' - - def auth(self, data: bytes) -> None: - ''' - Include authenticated data in the Poly1305 MAC using the RFC 7539 - style with 16 byte padding. This must only be called once and prior - to encryption or decryption. - ''' - - def finish(self) -> bytes: - ''' - Compute RFC 7539-style Poly1305 MAC. - ''' - -# extmod/modtrezorcrypto/modtrezorcrypto-curve25519.h -def generate_secret() -> bytes: - ''' - Generate secret key. - ''' - -# extmod/modtrezorcrypto/modtrezorcrypto-curve25519.h -def publickey(secret_key: bytes) -> bytes: - ''' - Computes public key from secret key. - ''' - -# extmod/modtrezorcrypto/modtrezorcrypto-curve25519.h -def multiply(secret_key: bytes, public_key: bytes) -> bytes: - ''' - Multiplies point defined by public_key with scalar defined by secret_key. - Useful for ECDH. - ''' - -# extmod/modtrezorcrypto/modtrezorcrypto-ed25519.h -def generate_secret() -> bytes: - ''' - Generate secret key. - ''' - -# extmod/modtrezorcrypto/modtrezorcrypto-ed25519.h -def publickey(secret_key: bytes) -> bytes: - ''' - Computes public key from secret key. - ''' - -# extmod/modtrezorcrypto/modtrezorcrypto-ed25519.h -def sign(secret_key: bytes, message: bytes, hasher: str='') -> bytes: - ''' - Uses secret key to produce the signature of message. - ''' - -# extmod/modtrezorcrypto/modtrezorcrypto-ed25519.h -def verify(public_key: bytes, signature: bytes, message: bytes) -> bool: - ''' - Uses public key to verify the signature of the message. - Returns True on success. - ''' - -# extmod/modtrezorcrypto/modtrezorcrypto-ed25519.h -def cosi_combine_publickeys(public_keys: List[bytes]) -> bytes: - ''' - Combines a list of public keys used in COSI cosigning scheme. - ''' - -# extmod/modtrezorcrypto/modtrezorcrypto-ed25519.h -def cosi_combine_signatures(R: bytes, signatures: List[bytes]) -> bytes: - ''' - Combines a list of signatures used in COSI cosigning scheme. - ''' - -# extmod/modtrezorcrypto/modtrezorcrypto-ed25519.h -def cosi_sign(secret_key: bytes, message: bytes, nonce: bytes, sigR: bytes, combined_pubkey: bytes) -> bytes: - ''' - Produce signature of message using COSI cosigning scheme. - ''' - -# extmod/modtrezorcrypto/modtrezorcrypto-nem.h -def validate_address(address: str, network: int) -> bool: - ''' - Validate a NEM address - ''' - -# extmod/modtrezorcrypto/modtrezorcrypto-nem.h -def compute_address(public_key: bytes, network: int) -> str: - ''' - Compute a NEM address from a public key - ''' - -# extmod/modtrezorcrypto/modtrezorcrypto-nist256p1.h -def generate_secret() -> bytes: - ''' - Generate secret key. - ''' - -# extmod/modtrezorcrypto/modtrezorcrypto-nist256p1.h -def publickey(secret_key: bytes, compressed: bool = True) -> bytes: - ''' - Computes public key from secret key. - ''' - -# extmod/modtrezorcrypto/modtrezorcrypto-nist256p1.h -def sign(secret_key: bytes, digest: bytes, compressed: bool = True) -> bytes: - ''' - Uses secret key to produce the signature of the digest. - ''' - -# extmod/modtrezorcrypto/modtrezorcrypto-nist256p1.h -def verify(public_key: bytes, signature: bytes, digest: bytes) -> bool: - ''' - Uses public key to verify the signature of the digest. - Returns True on success. - ''' - -# extmod/modtrezorcrypto/modtrezorcrypto-nist256p1.h -def verify_recover(signature: bytes, digest: bytes) -> bytes: - ''' - Uses signature of the digest to verify the digest and recover the public key. - Returns public key on success, None on failure. - ''' - -# extmod/modtrezorcrypto/modtrezorcrypto-nist256p1.h -def multiply(secret_key: bytes, public_key: bytes) -> bytes: - ''' - Multiplies point defined by public_key with scalar defined by secret_key. - Useful for ECDH. - ''' - -# extmod/modtrezorcrypto/modtrezorcrypto-pbkdf2.h -class Pbkdf2: - ''' - PBKDF2 context. - ''' - - def __init__(self, prf: str, password: bytes, salt: bytes, iterations: int = None) -> None: - ''' - Create a PBKDF2 context. - ''' - - def update(self, iterations: int) -> None: - ''' - Update a PBKDF2 context. - ''' - - def key(self) -> bytes: - ''' - Retrieve derived key. - ''' - -# extmod/modtrezorcrypto/modtrezorcrypto-random.h -def uniform(n: int) -> int: - ''' - Compute uniform random number from interval 0 ... n - 1. - ''' - -# extmod/modtrezorcrypto/modtrezorcrypto-random.h -def bytes(len: int) -> bytes: - ''' - Generate random bytes sequence of length len. - ''' - -# extmod/modtrezorcrypto/modtrezorcrypto-random.h -def shuffle(data: list) -> None: - ''' - Shuffles items of given list (in-place). - ''' - -# extmod/modtrezorcrypto/modtrezorcrypto-rfc6979.h -class Rfc6979: - ''' - RFC6979 context. - ''' - - def __init__(self, secret_key: bytes, hash: bytes) -> None: - ''' - Initialize RFC6979 context from secret key and a hash. - ''' - - def next(self) -> bytes: - ''' - Compute next 32-bytes of pseudorandom data. - ''' - -# extmod/modtrezorcrypto/modtrezorcrypto-ripemd160.h -class Ripemd160: - ''' - RIPEMD160 context. - ''' - - def __init__(self, data: bytes = None) -> None: - ''' - Creates a hash context object. - ''' - - def update(self, data: bytes) -> None: - ''' - Update the hash context with hashed data. - ''' - - def digest(self) -> bytes: - ''' - Returns the digest of hashed data. - ''' - -# extmod/modtrezorcrypto/modtrezorcrypto-secp256k1.h -def generate_secret() -> bytes: - ''' - Generate secret key. - ''' - -# extmod/modtrezorcrypto/modtrezorcrypto-secp256k1.h -def publickey(secret_key: bytes, compressed: bool = True) -> bytes: - ''' - Computes public key from secret key. - ''' - -# extmod/modtrezorcrypto/modtrezorcrypto-secp256k1.h -def sign(secret_key: bytes, digest: bytes, compressed: bool = True) -> bytes: - ''' - Uses secret key to produce the signature of the digest. - ''' - -# extmod/modtrezorcrypto/modtrezorcrypto-secp256k1.h -def verify(public_key: bytes, signature: bytes, digest: bytes) -> bool: - ''' - Uses public key to verify the signature of the digest. - Returns True on success. - ''' - -# extmod/modtrezorcrypto/modtrezorcrypto-secp256k1.h -def verify_recover(signature: bytes, digest: bytes) -> bytes: - ''' - Uses signature of the digest to verify the digest and recover the public key. - Returns public key on success, None on failure. - ''' - -# extmod/modtrezorcrypto/modtrezorcrypto-secp256k1.h -def multiply(secret_key: bytes, public_key: bytes) -> bytes: - ''' - Multiplies point defined by public_key with scalar defined by secret_key. - Useful for ECDH. - ''' - -# extmod/modtrezorcrypto/modtrezorcrypto-sha1.h -class Sha1: - ''' - SHA1 context. - ''' - - def __init__(self, data: bytes = None) -> None: - ''' - Creates a hash context object. - ''' - - def update(self, data: bytes) -> None: - ''' - Update the hash context with hashed data. - ''' - - def digest(self) -> bytes: - ''' - Returns the digest of hashed data. - ''' - -# extmod/modtrezorcrypto/modtrezorcrypto-sha256.h -class Sha256: - ''' - SHA256 context. - ''' - - def __init__(self, data: bytes = None) -> None: - ''' - Creates a hash context object. - ''' - - def update(self, data: bytes) -> None: - ''' - Update the hash context with hashed data. - ''' - - def digest(self) -> bytes: - ''' - Returns the digest of hashed data. - ''' - -# extmod/modtrezorcrypto/modtrezorcrypto-sha3-256.h -class Sha3_256: - ''' - SHA3_256 context. - ''' - - def __init__(self, data: bytes = None) -> None: - ''' - Creates a hash context object. - ''' - - def update(self, data: bytes) -> None: - ''' - Update the hash context with hashed data. - ''' - - def digest(self, keccak: bool = False) -> bytes: - ''' - Returns the digest of hashed data. - ''' - -# extmod/modtrezorcrypto/modtrezorcrypto-sha3-512.h -class Sha3_512: - ''' - SHA3_512 context. - ''' - - def __init__(self, data: bytes = None) -> None: - ''' - Creates a hash context object. - ''' - - def update(self, data: bytes) -> None: - ''' - Update the hash context with hashed data. - ''' - - def digest(self, keccak: bool = False) -> bytes: - ''' - Returns the digest of hashed data. - ''' - -# extmod/modtrezorcrypto/modtrezorcrypto-sha512.h -class Sha512: - ''' - SHA512 context. - ''' - - def __init__(self, data: bytes = None) -> None: - ''' - Creates a hash context object. - ''' - - def hash(self, data: bytes) -> None: - ''' - Update the hash context with hashed data. - ''' - - def digest(self) -> bytes: - ''' - Returns the digest of hashed data. - ''' diff --git a/mocks/generated/trezorcrypto/.mock-generated b/mocks/generated/trezorcrypto/.mock-generated new file mode 100644 index 000000000..e69de29bb diff --git a/mocks/generated/trezorcrypto/__init__.py b/mocks/generated/trezorcrypto/__init__.py new file mode 100644 index 000000000..6db995c01 --- /dev/null +++ b/mocks/generated/trezorcrypto/__init__.py @@ -0,0 +1,312 @@ + +# extmod/modtrezorcrypto/modtrezorcrypto-aes.h +class AES: + ''' + AES context. + ''' + + def __init__(self, mode: int, key: bytes, iv: bytes = None) -> None: + ''' + Initialize AES context. + ''' + + def encrypt(self, data: bytes) -> bytes: + ''' + Encrypt data and update AES context. + ''' + + def decrypt(self, data: bytes) -> bytes: + ''' + Decrypt data and update AES context. + ''' + +# extmod/modtrezorcrypto/modtrezorcrypto-blake256.h +class Blake256: + ''' + Blake256 context. + ''' + + def __init__(self, data: bytes = None) -> None: + ''' + Creates a hash context object. + ''' + + def update(self, data: bytes) -> None: + ''' + Update the hash context with hashed data. + ''' + + def digest(self) -> bytes: + ''' + Returns the digest of hashed data. + ''' + +# extmod/modtrezorcrypto/modtrezorcrypto-blake2b.h +class Blake2b: + ''' + Blake2b context. + ''' + + def __init__(self, data: bytes = None, outlen: int = Blake2b.digest_size, personal: bytes = None) -> None: + ''' + Creates a hash context object. + ''' + + def update(self, data: bytes) -> None: + ''' + Update the hash context with hashed data. + ''' + + def digest(self) -> bytes: + ''' + Returns the digest of hashed data. + ''' + +# extmod/modtrezorcrypto/modtrezorcrypto-blake2s.h +class Blake2s: + ''' + Blake2s context. + ''' + + def __init__(self, data: bytes = None, outlen: int = Blake2s.digest_size, key: bytes = None, personal: bytes = None) -> None: + ''' + Creates a hash context object. + ''' + + def update(self, data: bytes) -> None: + ''' + Update the hash context with hashed data. + ''' + + def digest(self) -> bytes: + ''' + Returns the digest of hashed data. + ''' + +# extmod/modtrezorcrypto/modtrezorcrypto-chacha20poly1305.h +class ChaCha20Poly1305: + ''' + ChaCha20Poly1305 context. + ''' + + def __init__(self, key: bytes, nonce: bytes) -> None: + ''' + Initialize the ChaCha20 + Poly1305 context for encryption or decryption + using a 32 byte key and 12 byte nonce as in the RFC 7539 style. + ''' + + def encrypt(self, data: bytes) -> bytes: + ''' + Encrypt data (length of data must be divisible by 64 except for the final value). + ''' + + def decrypt(self, data: bytes) -> bytes: + ''' + Decrypt data (length of data must be divisible by 64 except for the final value). + ''' + + def auth(self, data: bytes) -> None: + ''' + Include authenticated data in the Poly1305 MAC using the RFC 7539 + style with 16 byte padding. This must only be called once and prior + to encryption or decryption. + ''' + + def finish(self) -> bytes: + ''' + Compute RFC 7539-style Poly1305 MAC. + ''' + +# extmod/modtrezorcrypto/modtrezorcrypto-groestl.h +class Groestl512: + ''' + GROESTL512 context. + ''' + + def __init__(self, data: bytes = None) -> None: + ''' + Creates a hash context object. + ''' + + def update(self, data: bytes) -> None: + ''' + Update the hash context with hashed data. + ''' + + def digest(self) -> bytes: + ''' + Returns the digest of hashed data. + ''' + +# extmod/modtrezorcrypto/modtrezorcrypto-pbkdf2.h +class Pbkdf2: + ''' + PBKDF2 context. + ''' + + def __init__(self, prf: int, password: bytes, salt: bytes, iterations: int = None, blocknr: int = 1) -> None: + ''' + Create a PBKDF2 context. + ''' + + def update(self, iterations: int) -> None: + ''' + Update a PBKDF2 context. + ''' + + def key(self) -> bytes: + ''' + Retrieve derived key. + ''' + +# extmod/modtrezorcrypto/modtrezorcrypto-rfc6979.h +class Rfc6979: + ''' + RFC6979 context. + ''' + + def __init__(self, secret_key: bytes, hash: bytes) -> None: + ''' + Initialize RFC6979 context from secret key and a hash. + ''' + + def next(self) -> bytes: + ''' + Compute next 32-bytes of pseudorandom data. + ''' + +# extmod/modtrezorcrypto/modtrezorcrypto-ripemd160.h +class Ripemd160: + ''' + RIPEMD160 context. + ''' + + def __init__(self, data: bytes = None) -> None: + ''' + Creates a hash context object. + ''' + + def update(self, data: bytes) -> None: + ''' + Update the hash context with hashed data. + ''' + + def digest(self) -> bytes: + ''' + Returns the digest of hashed data. + ''' + +# extmod/modtrezorcrypto/modtrezorcrypto-sha1.h +class Sha1: + ''' + SHA1 context. + ''' + + def __init__(self, data: bytes = None) -> None: + ''' + Creates a hash context object. + ''' + + def update(self, data: bytes) -> None: + ''' + Update the hash context with hashed data. + ''' + + def digest(self) -> bytes: + ''' + Returns the digest of hashed data. + ''' + +# extmod/modtrezorcrypto/modtrezorcrypto-sha256.h +class Sha256: + ''' + SHA256 context. + ''' + + def __init__(self, data: bytes = None) -> None: + ''' + Creates a hash context object. + ''' + + def update(self, data: bytes) -> None: + ''' + Update the hash context with hashed data. + ''' + + def digest(self) -> bytes: + ''' + Returns the digest of hashed data. + ''' + +# extmod/modtrezorcrypto/modtrezorcrypto-sha3-256.h +class Sha3_256: + ''' + SHA3_256 context. + ''' + + def __init__(self, data: bytes = None, keccak = False) -> None: + ''' + Creates a hash context object. + ''' + + def update(self, data: bytes) -> None: + ''' + Update the hash context with hashed data. + ''' + + def digest(self) -> bytes: + ''' + Returns the digest of hashed data. + ''' + + def copy(self) -> sha3: + ''' + Returns the copy of the digest object with the current state + ''' + +# extmod/modtrezorcrypto/modtrezorcrypto-sha3-512.h +class Sha3_512: + ''' + SHA3_512 context. + ''' + + def __init__(self, data: bytes = None, keccak = False) -> None: + ''' + Creates a hash context object. + ''' + + def update(self, data: bytes) -> None: + ''' + Update the hash context with hashed data. + ''' + + def digest(self) -> bytes: + ''' + Returns the digest of hashed data. + ''' + + def copy(self) -> sha3: + ''' + Returns the copy of the digest object with the current state + ''' + +# extmod/modtrezorcrypto/modtrezorcrypto-sha512.h +class Sha512: + ''' + SHA512 context. + ''' + + def __init__(self, data: bytes = None) -> None: + ''' + Creates a hash context object. + ''' + + def hash(self, data: bytes) -> None: + ''' + Update the hash context with hashed data. + ''' + + def digest(self) -> bytes: + ''' + Returns the digest of hashed data. + ''' diff --git a/mocks/generated/trezorcrypto/bip32.py b/mocks/generated/trezorcrypto/bip32.py new file mode 100644 index 000000000..95e4fb140 --- /dev/null +++ b/mocks/generated/trezorcrypto/bip32.py @@ -0,0 +1,118 @@ +from typing import * + +# extmod/modtrezorcrypto/modtrezorcrypto-bip32.h +class HDNode: + ''' + BIP0032 HD node structure. + ''' + + def __init__(self, + depth: int, + fingerprint: int, + child_num: int, + chain_code: bytes, + private_key: bytes = None, + public_key: bytes = None, + curve_name: str = None) -> None: + ''' + ''' + + def derive(self, index: int, public: bool=False) -> None: + ''' + Derive a BIP0032 child node in place. + ''' + + def derive_cardano(self, index: int) -> None: + ''' + Derive a BIP0032 child node in place using Cardano algorithm. + ''' + + def derive_path(self, path: List[int]) -> None: + ''' + Go through a list of indexes and iteratively derive a child node in place. + ''' + + def serialize_public(self, version: int) -> str: + ''' + Serialize the public info from HD node to base58 string. + ''' + + def serialize_private(self, version: int) -> str: + ''' + Serialize the private info HD node to base58 string. + ''' + + def clone(self) -> HDNode: + ''' + Returns a copy of the HD node. + ''' + + def depth(self) -> int: + ''' + Returns a depth of the HD node. + ''' + + def fingerprint(self) -> int: + ''' + Returns a fingerprint of the HD node (hash of the parent public key). + ''' + + def child_num(self) -> int: + ''' + Returns a child index of the HD node. + ''' + + def chain_code(self) -> bytes: + ''' + Returns a chain code of the HD node. + ''' + + def private_key(self) -> bytes: + ''' + Returns a private key of the HD node. + ''' + + def private_key_ext(self) -> bytes: + ''' + Returns a private key extension of the HD node. + ''' + + def public_key(self) -> bytes: + ''' + Returns a public key of the HD node. + ''' + + def address(self, version: int) -> str: + ''' + Compute a base58-encoded address string from the HD node. + ''' + + def nem_address(self, network: int) -> str: + ''' + Compute a NEM address string from the HD node. + ''' + + def nem_encrypt(self, transfer_public_key: bytes, iv: bytes, salt: bytes, payload: bytes) -> bytes: + ''' + Encrypts payload using the transfer's public key + ''' + + def ethereum_pubkeyhash(self) -> bytes: + ''' + Compute an Ethereum pubkeyhash (aka address) from the HD node. + ''' + + def deserialize(self, value: str, version_public: int, version_private: int) -> HDNode: + ''' + Construct a BIP0032 HD node from a base58-serialized value. + ''' + + def from_seed(seed: bytes, curve_name: str) -> HDNode: + ''' + Construct a BIP0032 HD node from a BIP0039 seed value. + ''' + + def from_mnemonic_cardano(mnemonic: str) -> bytes: + ''' + Convert mnemonic to hdnode + ''' diff --git a/mocks/generated/trezorcrypto/bip39.py b/mocks/generated/trezorcrypto/bip39.py new file mode 100644 index 000000000..a3c2d2ece --- /dev/null +++ b/mocks/generated/trezorcrypto/bip39.py @@ -0,0 +1,38 @@ +from typing import * + +# extmod/modtrezorcrypto/modtrezorcrypto-bip39.h +def find_word(prefix: str) -> Optional[str]: + ''' + Return the first word from the wordlist starting with prefix. + ''' + +# extmod/modtrezorcrypto/modtrezorcrypto-bip39.h +def complete_word(prefix: str) -> int: + ''' + Return possible 1-letter suffixes for given word prefix. + Result is a bitmask, with 'a' on the lowest bit, 'b' on the second lowest, etc. + ''' + +# extmod/modtrezorcrypto/modtrezorcrypto-bip39.h +def generate(strength: int) -> str: + ''' + Generate a mnemonic of given strength (128, 160, 192, 224 and 256 bits). + ''' + +# extmod/modtrezorcrypto/modtrezorcrypto-bip39.h +def from_data(data: bytes) -> str: + ''' + Generate a mnemonic from given data (of 16, 20, 24, 28 and 32 bytes). + ''' + +# extmod/modtrezorcrypto/modtrezorcrypto-bip39.h +def check(mnemonic: str) -> bool: + ''' + Check whether given mnemonic is valid. + ''' + +# extmod/modtrezorcrypto/modtrezorcrypto-bip39.h +def seed(mnemonic: str, passphrase: str) -> bytes: + ''' + Generate seed from mnemonic and passphrase. + ''' diff --git a/mocks/generated/trezorcrypto/curve25519.py b/mocks/generated/trezorcrypto/curve25519.py new file mode 100644 index 000000000..dfdef99f9 --- /dev/null +++ b/mocks/generated/trezorcrypto/curve25519.py @@ -0,0 +1,20 @@ +from typing import * + +# extmod/modtrezorcrypto/modtrezorcrypto-curve25519.h +def generate_secret() -> bytes: + ''' + Generate secret key. + ''' + +# extmod/modtrezorcrypto/modtrezorcrypto-curve25519.h +def publickey(secret_key: bytes) -> bytes: + ''' + Computes public key from secret key. + ''' + +# extmod/modtrezorcrypto/modtrezorcrypto-curve25519.h +def multiply(secret_key: bytes, public_key: bytes) -> bytes: + ''' + Multiplies point defined by public_key with scalar defined by secret_key. + Useful for ECDH. + ''' diff --git a/mocks/generated/trezorcrypto/ed25519.py b/mocks/generated/trezorcrypto/ed25519.py new file mode 100644 index 000000000..1c5fda393 --- /dev/null +++ b/mocks/generated/trezorcrypto/ed25519.py @@ -0,0 +1,50 @@ +from typing import * + +# extmod/modtrezorcrypto/modtrezorcrypto-ed25519.h +def generate_secret() -> bytes: + ''' + Generate secret key. + ''' + +# extmod/modtrezorcrypto/modtrezorcrypto-ed25519.h +def publickey(secret_key: bytes) -> bytes: + ''' + Computes public key from secret key. + ''' + +# extmod/modtrezorcrypto/modtrezorcrypto-ed25519.h +def sign(secret_key: bytes, message: bytes, hasher: str='') -> bytes: + ''' + Uses secret key to produce the signature of message. + ''' + +# extmod/modtrezorcrypto/modtrezorcrypto-ed25519.h +def sign_ext(secret_key: bytes, secret_extension: bytes, message: bytes) -> bytes: + ''' + Uses secret key to produce the cardano signature of message. + ''' + +# extmod/modtrezorcrypto/modtrezorcrypto-ed25519.h +def verify(public_key: bytes, signature: bytes, message: bytes) -> bool: + ''' + Uses public key to verify the signature of the message. + Returns True on success. + ''' + +# extmod/modtrezorcrypto/modtrezorcrypto-ed25519.h +def cosi_combine_publickeys(public_keys: List[bytes]) -> bytes: + ''' + Combines a list of public keys used in COSI cosigning scheme. + ''' + +# extmod/modtrezorcrypto/modtrezorcrypto-ed25519.h +def cosi_combine_signatures(R: bytes, signatures: List[bytes]) -> bytes: + ''' + Combines a list of signatures used in COSI cosigning scheme. + ''' + +# extmod/modtrezorcrypto/modtrezorcrypto-ed25519.h +def cosi_sign(secret_key: bytes, message: bytes, nonce: bytes, sigR: bytes, combined_pubkey: bytes) -> bytes: + ''' + Produce signature of message using COSI cosigning scheme. + ''' diff --git a/mocks/generated/trezorcrypto/monero.py b/mocks/generated/trezorcrypto/monero.py new file mode 100644 index 000000000..d967d3b87 --- /dev/null +++ b/mocks/generated/trezorcrypto/monero.py @@ -0,0 +1,313 @@ +from typing import * + +# extmod/modtrezorcrypto/modtrezorcrypto-monero.h +class Ge25519: + ''' + EC point on ED25519 + ''' + def __init__(x: Optional[Union[Ge25519, bytes]] = None): + ''' + Constructor + ''' + +# extmod/modtrezorcrypto/modtrezorcrypto-monero.h +class Sc25519: + ''' + EC scalar on SC25519 + ''' + def __init__(x: Optional[Union[Sc25519, bytes, int]] = None): + ''' + Constructor + ''' + +# extmod/modtrezorcrypto/modtrezorcrypto-monero.h +class Hasher: + ''' + XMR hasher + ''' + def __init__(x: Optional[bytes] = None): + ''' + Constructor + ''' + def update(buffer: bytes): + ''' + Update hasher + ''' + def digest() -> bytes: + ''' + Computes digest + ''' + def copy() -> Hasher: + ''' + Creates copy of the hasher, preserving the state + ''' + +# extmod/modtrezorcrypto/modtrezorcrypto-monero.h +def init256_modm(dst: Optional[Sc25519], val: Union[int, bytes, Sc25519]) -> Sc25519: + ''' + Initializes Sc25519 scalar + ''' + +# extmod/modtrezorcrypto/modtrezorcrypto-monero.h +def check256_modm(val: Sc25519): + ''' + Throws exception if scalar is invalid + ''' + +# extmod/modtrezorcrypto/modtrezorcrypto-monero.h +def iszero256_modm(val: Sc25519) -> bool: + ''' + Returns False if the scalar is zero + ''' + +# extmod/modtrezorcrypto/modtrezorcrypto-monero.h +def eq256_modm(a: Sc25519, b: Sc25519) -> int: + ''' + Compares scalars, returns 1 on the same value + ''' + +# extmod/modtrezorcrypto/modtrezorcrypto-monero.h +def get256_modm(a: Sc25519) -> int: + ''' + Extracts 64bit integer from the scalar. Raises exception if scalar is bigger than 2^64 + ''' + +# extmod/modtrezorcrypto/modtrezorcrypto-monero.h +def add256_modm(r: Optional[Sc25519], a: Sc25519, b: Sc25519) -> Sc25519: + ''' + Scalar addition + ''' + +# extmod/modtrezorcrypto/modtrezorcrypto-monero.h +def sub256_modm(r: Optional[Sc25519], a: Sc25519, b: Sc25519) -> Sc25519: + ''' + Scalar subtraction + ''' + +# extmod/modtrezorcrypto/modtrezorcrypto-monero.h +def mul256_modm(r: Optional[Sc25519], a: Sc25519, b: Sc25519) -> Sc25519: + ''' + Scalar multiplication + ''' + +# extmod/modtrezorcrypto/modtrezorcrypto-monero.h +def mulsub256_modm(r: Optional[Sc25519], a: Sc25519, b: Sc25519, c: Sc25519) -> Sc25519: + ''' + c - a*b + ''' + +# extmod/modtrezorcrypto/modtrezorcrypto-monero.h +def muladd256_modm(r: Optional[Sc25519], a: Sc25519, b: Sc25519, c: Sc25519) -> Sc25519: + ''' + c + a*b + ''' + +# extmod/modtrezorcrypto/modtrezorcrypto-monero.h +def inv256_modm(r: Optional[Sc25519], a: Sc25519) -> Sc25519: + ''' + Scalar modular inversion + ''' + +# extmod/modtrezorcrypto/modtrezorcrypto-monero.h +def pack256_modm(r: Optional[bytes], a: Sc25519, offset: Optional[int] = 0) -> bytes: + ''' + Scalar compression + ''' + +# extmod/modtrezorcrypto/modtrezorcrypto-monero.h +def unpack256_modm(r: Optional[Sc25519], a: bytes, offset: int = 0) -> Sc25519: + ''' + Scalar decompression + ''' + +# extmod/modtrezorcrypto/modtrezorcrypto-monero.h +def unpack256_modm_noreduce(r: Optional[Sc25519], a: bytes, offset: int = 0) -> Sc25519: + ''' + Scalar decompression, raw, without modular reduction + ''' + +# extmod/modtrezorcrypto/modtrezorcrypto-monero.h +def ge25519_set_neutral(r: Optional[Ge25519]) -> Ge25519: + ''' + Sets neutral point + ''' + +# extmod/modtrezorcrypto/modtrezorcrypto-monero.h +def ge25519_set_xmr_h(r: Optional[Ge25519]) -> Ge25519: + ''' + Sets H point + ''' + +# extmod/modtrezorcrypto/modtrezorcrypto-monero.h +def ge25519_check(r: Ge25519): + ''' + Checks point, throws if not on curve + ''' + +# extmod/modtrezorcrypto/modtrezorcrypto-monero.h +def ge25519_eq(a: Ge25519, b: Ge25519) -> bool: + ''' + Compares EC points + ''' + +# extmod/modtrezorcrypto/modtrezorcrypto-monero.h +def ge25519_add(r: Optional[Ge25519], a: Ge25519, b: Ge25519) -> Ge25519: + ''' + Adds EC points + ''' + +# extmod/modtrezorcrypto/modtrezorcrypto-monero.h +def ge25519_sub(r: Optional[Ge25519], a: Ge25519, b: Ge25519) -> Ge25519: + ''' + Subtracts EC points + ''' + +# extmod/modtrezorcrypto/modtrezorcrypto-monero.h +def ge25519_double(r: Optional[Ge25519], p: Ge25519) -> Ge25519: + ''' + EC point doubling + ''' + +# extmod/modtrezorcrypto/modtrezorcrypto-monero.h +def ge25519_mul8(r: Optional[Ge25519], p: Ge25519) -> Ge25519: + ''' + EC point * 8 + ''' + +# extmod/modtrezorcrypto/modtrezorcrypto-monero.h +def ge25519_double_scalarmult_vartime(r: Optional[Ge25519], p1: Ge25519, s1: Sc25519, s2: Sc25519) -> Ge25519: + ''' + s1 * G + s2 * p1 + ''' + +# extmod/modtrezorcrypto/modtrezorcrypto-monero.h +def ge25519_double_scalarmult_vartime2(r: Optional[Ge25519], p1: Ge25519, s1: Sc25519, p2: Ge25519, s2: Sc25519) -> Ge25519: + ''' + s1 * p1 + s2 * p2 + ''' + +# extmod/modtrezorcrypto/modtrezorcrypto-monero.h +def ge25519_scalarmult_base(r: Optional[Ge25519], s: Union[Sc25519, int]) -> Ge25519: + ''' + s * G + ''' + +# extmod/modtrezorcrypto/modtrezorcrypto-monero.h +def ge25519_scalarmult(r: Optional[Ge25519], p: Ge25519, s: Union[Sc25519, int]) -> Ge25519: + ''' + s * p + ''' + +# extmod/modtrezorcrypto/modtrezorcrypto-monero.h +def ge25519_pack(r: bytes, p: Ge25519, offset: int = 0) -> bytes: + ''' + Point compression + ''' + +# extmod/modtrezorcrypto/modtrezorcrypto-monero.h +def ge25519_unpack_vartime(r: Optional[Ge25519], buff: bytes, offset: int = 0) -> Ge25519: + ''' + Point decompression + ''' + +# extmod/modtrezorcrypto/modtrezorcrypto-monero.h +def base58_addr_encode_check(tag: int, buff: bytes) -> bytes: + ''' + Monero block base 58 encoding + ''' + +# extmod/modtrezorcrypto/modtrezorcrypto-monero.h +def base58_addr_decode_check(buff: bytes) -> Tuple[bytes, int]: + ''' + Monero block base 58 decoding, returning (decoded, tag) or raising on error. + ''' + +# extmod/modtrezorcrypto/modtrezorcrypto-monero.h +def xmr_random_scalar(r: Optional[Sc25519] = None) -> Sc25519: + ''' + Generates a random scalar + ''' + +# extmod/modtrezorcrypto/modtrezorcrypto-monero.h +def xmr_fast_hash(r: Optional[bytes], buff: bytes) -> bytes: + ''' + XMR fast hash + ''' + +# extmod/modtrezorcrypto/modtrezorcrypto-monero.h +def xmr_hash_to_ec(r: Optional[Ge25519], buff: bytes) -> Ge25519: + ''' + XMR hashing to EC point + ''' + +# extmod/modtrezorcrypto/modtrezorcrypto-monero.h +def xmr_hash_to_scalar(r: Optional[Sc25519], buff: bytes) -> Sc25519: + ''' + XMR hashing to EC scalar + ''' + +# extmod/modtrezorcrypto/modtrezorcrypto-monero.h +def xmr_derivation_to_scalar(r: Optional[Sc25519], p: Ge25519, output_index: int) -> Sc25519: + ''' + H_s(derivation || varint(output_index)) + ''' + +# extmod/modtrezorcrypto/modtrezorcrypto-monero.h +def xmr_generate_key_derivation(r: Optional[Ge25519], A: Ge25519, b: Sc25519) -> Ge25519: + ''' + 8*(key2*key1) + ''' + +# extmod/modtrezorcrypto/modtrezorcrypto-monero.h +def xmr_derive_private_key(r: Optional[Sc25519], deriv: Ge25519, idx: int, base: Sc25519) -> Sc25519: + ''' + base + H_s(derivation || varint(output_index)) + ''' + +# extmod/modtrezorcrypto/modtrezorcrypto-monero.h +def xmr_derive_public_key(r: Optional[Ge25519], deriv: Ge25519, idx: int, base: Ge25519) -> Ge25519: + ''' + H_s(derivation || varint(output_index))G + base + ''' + +# extmod/modtrezorcrypto/modtrezorcrypto-monero.h +def xmr_add_keys2(r: Optional[Ge25519], a: Sc25519, b: Sc25519, B: Ge25519) -> Ge25519: + ''' + aG + bB, G is basepoint + ''' + +# extmod/modtrezorcrypto/modtrezorcrypto-monero.h +def xmr_add_keys2_vartime(r: Optional[Ge25519], a: Sc25519, b: Sc25519, B: Ge25519) -> Ge25519: + ''' + aG + bB, G is basepoint + ''' + +# extmod/modtrezorcrypto/modtrezorcrypto-monero.h +def xmr_add_keys3(r: Optional[Ge25519], a: Sc25519, A: Ge25519, b: Sc25519, B: Ge25519) -> Ge25519: + ''' + aA + bB + ''' + +# extmod/modtrezorcrypto/modtrezorcrypto-monero.h +def xmr_add_keys3_vartime(r: Optional[Ge25519], a: Sc25519, A: Ge25519, b: Sc25519, B: Ge25519) -> Ge25519: + ''' + aA + bB + ''' + +# extmod/modtrezorcrypto/modtrezorcrypto-monero.h +def xmr_get_subaddress_secret_key(r: Optional[Sc25519], major: int, minor: int, m: Sc25519) -> Sc25519: + ''' + Hs(SubAddr || a || index_major || index_minor) + ''' + +# extmod/modtrezorcrypto/modtrezorcrypto-monero.h +def xmr_gen_c(r: Optional[Ge25519], a: Sc25519, amount: int) -> Ge25519: + ''' + aG + amount * H + ''' + +# extmod/modtrezorcrypto/modtrezorcrypto-monero.h +def ct_equals(a: bytes, b: bytes) -> bool: + ''' + Constant time buffer comparison + ''' diff --git a/mocks/generated/trezorcrypto/nem.py b/mocks/generated/trezorcrypto/nem.py new file mode 100644 index 000000000..27f70943c --- /dev/null +++ b/mocks/generated/trezorcrypto/nem.py @@ -0,0 +1,13 @@ +from typing import * + +# extmod/modtrezorcrypto/modtrezorcrypto-nem.h +def validate_address(address: str, network: int) -> bool: + ''' + Validate a NEM address + ''' + +# extmod/modtrezorcrypto/modtrezorcrypto-nem.h +def compute_address(public_key: bytes, network: int) -> str: + ''' + Compute a NEM address from a public key + ''' diff --git a/mocks/generated/trezorcrypto/nist256p1.py b/mocks/generated/trezorcrypto/nist256p1.py new file mode 100644 index 000000000..d06f446d1 --- /dev/null +++ b/mocks/generated/trezorcrypto/nist256p1.py @@ -0,0 +1,40 @@ +from typing import * + +# extmod/modtrezorcrypto/modtrezorcrypto-nist256p1.h +def generate_secret() -> bytes: + ''' + Generate secret key. + ''' + +# extmod/modtrezorcrypto/modtrezorcrypto-nist256p1.h +def publickey(secret_key: bytes, compressed: bool = True) -> bytes: + ''' + Computes public key from secret key. + ''' + +# extmod/modtrezorcrypto/modtrezorcrypto-nist256p1.h +def sign(secret_key: bytes, digest: bytes, compressed: bool = True) -> bytes: + ''' + Uses secret key to produce the signature of the digest. + ''' + +# extmod/modtrezorcrypto/modtrezorcrypto-nist256p1.h +def verify(public_key: bytes, signature: bytes, digest: bytes) -> bool: + ''' + Uses public key to verify the signature of the digest. + Returns True on success. + ''' + +# extmod/modtrezorcrypto/modtrezorcrypto-nist256p1.h +def verify_recover(signature: bytes, digest: bytes) -> bytes: + ''' + Uses signature of the digest to verify the digest and recover the public key. + Returns public key on success, None on failure. + ''' + +# extmod/modtrezorcrypto/modtrezorcrypto-nist256p1.h +def multiply(secret_key: bytes, public_key: bytes) -> bytes: + ''' + Multiplies point defined by public_key with scalar defined by secret_key. + Useful for ECDH. + ''' diff --git a/mocks/generated/trezorcrypto/random.py b/mocks/generated/trezorcrypto/random.py new file mode 100644 index 000000000..6e9248de9 --- /dev/null +++ b/mocks/generated/trezorcrypto/random.py @@ -0,0 +1,19 @@ +from typing import * + +# extmod/modtrezorcrypto/modtrezorcrypto-random.h +def uniform(n: int) -> int: + ''' + Compute uniform random number from interval 0 ... n - 1. + ''' + +# extmod/modtrezorcrypto/modtrezorcrypto-random.h +def bytes(len: int) -> bytes: + ''' + Generate random bytes sequence of length len. + ''' + +# extmod/modtrezorcrypto/modtrezorcrypto-random.h +def shuffle(data: list) -> None: + ''' + Shuffles items of given list (in-place). + ''' diff --git a/mocks/generated/trezorcrypto/secp256k1.py b/mocks/generated/trezorcrypto/secp256k1.py new file mode 100644 index 000000000..0dc82025a --- /dev/null +++ b/mocks/generated/trezorcrypto/secp256k1.py @@ -0,0 +1,40 @@ +from typing import * + +# extmod/modtrezorcrypto/modtrezorcrypto-secp256k1.h +def generate_secret() -> bytes: + ''' + Generate secret key. + ''' + +# extmod/modtrezorcrypto/modtrezorcrypto-secp256k1.h +def publickey(secret_key: bytes, compressed: bool = True) -> bytes: + ''' + Computes public key from secret key. + ''' + +# extmod/modtrezorcrypto/modtrezorcrypto-secp256k1.h +def sign(secret_key: bytes, digest: bytes, compressed: bool = True, ethereum_canonical: bool = False) -> bytes: + ''' + Uses secret key to produce the signature of the digest. + ''' + +# extmod/modtrezorcrypto/modtrezorcrypto-secp256k1.h +def verify(public_key: bytes, signature: bytes, digest: bytes) -> bool: + ''' + Uses public key to verify the signature of the digest. + Returns True on success. + ''' + +# extmod/modtrezorcrypto/modtrezorcrypto-secp256k1.h +def verify_recover(signature: bytes, digest: bytes) -> bytes: + ''' + Uses signature of the digest to verify the digest and recover the public key. + Returns public key on success, None on failure. + ''' + +# extmod/modtrezorcrypto/modtrezorcrypto-secp256k1.h +def multiply(secret_key: bytes, public_key: bytes) -> bytes: + ''' + Multiplies point defined by public_key with scalar defined by secret_key. + Useful for ECDH. + ''' diff --git a/mocks/generated/trezorio.py b/mocks/generated/trezorio.py index e816c89c0..9c7bd4cf4 100644 --- a/mocks/generated/trezorio.py +++ b/mocks/generated/trezorio.py @@ -138,7 +138,8 @@ class USB: product: str='', serial_number: str='', interface: str='', - usb21_enabled: bool=True) -> None: + usb21_enabled: bool=True, + usb21_landing: bool=True) -> None: ''' ''' diff --git a/mocks/generated/trezorui.py b/mocks/generated/trezorui.py index 16392fd67..d42a3083c 100644 --- a/mocks/generated/trezorui.py +++ b/mocks/generated/trezorui.py @@ -51,27 +51,41 @@ class Display: The icon needs to be in TREZOR Optimized Image Format (TOIF) - gray-scale mode. ''' + def loader(self, progress: int, yoffset: int, fgcolor: int, bgcolor: int, icon: bytes = None, iconfgcolor: int = None) -> None: + ''' + Renders a rotating loader graphic. + Progress determines its position (0-1000), fgcolor is used as foreground color, bgcolor as background. + When icon and iconfgcolor are provided, an icon is drawn in the middle using the color specified in iconfgcolor. + Icon needs to be of exactly LOADER_ICON_SIZE x LOADER_ICON_SIZE pixels size. + ''' + def print(self, text: str) -> None: ''' Renders text using 5x8 bitmap font (using special text mode). ''' - def text(self, x: int, y: int, text: str, font: int, fgcolor: int, bgcolor: int, minwidth: int=None) -> None: + def text(self, x: int, y: int, text: str, font: int, fgcolor: int, bgcolor: int, minwidth: int=None) -> int: ''' Renders left-aligned text at position (x,y) where x is left position and y is baseline. Font font is used for rendering, fgcolor is used as foreground color, bgcolor as background. + Fills at least minwidth pixels with bgcolor. + Returns width of rendered text in pixels. ''' - def text_center(self, x: int, y: int, text: str, font: int, fgcolor: int, bgcolor: int, minwidth: int=None) -> None: + def text_center(self, x: int, y: int, text: str, font: int, fgcolor: int, bgcolor: int, minwidth: int=None) -> int: ''' Renders text centered at position (x,y) where x is text center and y is baseline. Font font is used for rendering, fgcolor is used as foreground color, bgcolor as background. + Fills at least minwidth pixels with bgcolor. + Returns width of rendered text in pixels. ''' - def text_right(self, x: int, y: int, text: str, font: int, fgcolor: int, bgcolor: int, minwidth: int=None) -> None: + def text_right(self, x: int, y: int, text: str, font: int, fgcolor: int, bgcolor: int, minwidth: int=None) -> int: ''' Renders right-aligned text at position (x,y) where x is right position and y is baseline. Font font is used for rendering, fgcolor is used as foreground color, bgcolor as background. + Fills at least minwidth pixels with bgcolor. + Returns width of rendered text in pixels. ''' def text_width(self, text: str, font: int) -> int: @@ -85,14 +99,6 @@ class Display: Scale determines a zoom factor. ''' - def loader(self, progress: int, yoffset: int, fgcolor: int, bgcolor: int, icon: bytes = None, iconfgcolor: int = None) -> None: - ''' - Renders a rotating loader graphic. - Progress determines its position (0-1000), fgcolor is used as foreground color, bgcolor as background. - When icon and iconfgcolor are provided, an icon is drawn in the middle using the color specified in iconfgcolor. - Icon needs to be of exactly LOADER_ICON_SIZE x LOADER_ICON_SIZE pixels size. - ''' - def orientation(self, degrees: int = None) -> int: ''' Sets display orientation to 0, 90, 180 or 270 degrees. diff --git a/mocks/generated/trezorutils.py b/mocks/generated/trezorutils.py index 8c7a3763c..27001b7c4 100644 --- a/mocks/generated/trezorutils.py +++ b/mocks/generated/trezorutils.py @@ -30,15 +30,3 @@ def set_mode_unprivileged() -> None: ''' Set unprivileged mode. ''' - -# extmod/modtrezorutils/modtrezorutils.c -def symbol(name: str) -> str/int/None: - ''' - Retrieve internal symbol. - ''' - -# extmod/modtrezorutils/modtrezorutils.c -def model() -> str: - ''' - Return which hardware model we are running on. - ''' diff --git a/src/apps/ethereum/networks.py b/src/apps/ethereum/networks.py index 28b8d7a18..f1fd674e8 100644 --- a/src/apps/ethereum/networks.py +++ b/src/apps/ethereum/networks.py @@ -72,13 +72,6 @@ NETWORKS = [ name="Ubiq", rskip60=False, ), - NetworkInfo( - chain_id=20, - slip44=2018, - shortcut="EOSC", - name="EOS Classic", - rskip60=False, - ), NetworkInfo( chain_id=28, slip44=1128, @@ -156,6 +149,13 @@ NETWORKS = [ name="EtherGem", rskip60=False, ), + NetworkInfo( + chain_id=2018, + slip44=2018, + shortcut="EOSC", + name="EOS Classic", + rskip60=False, + ), NetworkInfo( chain_id=31102, slip44=31102, diff --git a/src/apps/monero/README.md b/src/apps/monero/README.md new file mode 100644 index 000000000..4d0154404 --- /dev/null +++ b/src/apps/monero/README.md @@ -0,0 +1,321 @@ +# Monero + +MAINTAINER = ... + +AUTHOR = Dusan Klinec + +REVIEWER = Tomas Susanka , + Jan Pochyla , + Ondrej Vejpustek + +----- + +This Monero implementation was implemented from scratch originally for TREZOR by porting Monero C++ code to the Python codebase. + +The implementation heavily relies on the [trezor-crypto] Monero functionality which implements basic crypto primitives and +other Monero related functionality (e.g., monero base58, accelerated and optimized Borromean range signatures) + +A general high level description of the integration proposal is described in the documentation: [monero-doc]. + +## Features + +The implementation provides the following features: + +### Transaction signature + +Signs a Monero transaction on the TREZOR. + +- Designed so number of UTXO is practically unlimited (hundreds to thousands) +- Maximal number of outputs per transaction is 8 (usually there are only 2) +- Supports 8 B encrypted payment ID and 32 B unencrypted payment ID. + +### Key Image sync + +Key Image is computed with the spend key which is stored on the TREZOR. + +In order to detect if the UTXO has been already spent (thus computing balance due to change transactions) +and correct spending UTXOs the key images are required. Without the key images the Monero view only +wallet incorrectly computes balance as it sees all ever received transactions as unspent. + +Key image sync is a protocol that allows to compute key images for incoming transfers by TREZOR. + +Example: 20 XMR in the single UTXO is received, thus real balance is 20. 1 XMR is sent to a different +address and remaining 19 are sent back with a change transaction. Correct balance is 19 but without +correct key image the view only wallet shows balance 39. Without knowing which UTXO is spent +the newly constructed spending transactions can pick already spent input. Such transaction is +rejected by a Monero daemon as a double spending transaction. + +Normally, the Key image sync is not needed as the key image computation is done by +the transaction signing algorithm. However, if the wallet file is somehow corrupted +or the wallet is used on a new host / restored from the TREZOR the key +image sync is required for correct function of the wallet. It recomputes key images +for all received transaction inputs. + + +## Integration rationale + +The Monero codebase already contains cold wallet support. I.e., wallet not connected to the Internet, which should provide +better security guarantees as it minimizes attack surface compared to the hot wallet - always connected wallet. + +As the cold wallet is not connected to the Internet and does not have access nor to the blockchain neither to the monero +full node the all information for transaction construction have to be prepared by the hot wallet. + +When using the cold wallet, hot wallet is watch-only. It has only the view-key so it can scan blockchain for incoming +transactions but is not able to spend any transaction. + +Transaction signature with cold wallet works like this: + +- Create transaction construction data on hot wallet. `transfer
`. Works similar to the normal wallet operation +but instead of the signed transaction, the watch-only hot wallet generates `unsigned_txset` file which contains +transaction construction data. + +- Cold wallet opens `unsigned_txset`, verifies the signature on the transaction construction data and creates Monero transaction +using the data. Cold wallet creates `signed_txset` + +- Hot wallet opens `signed_txset`, verifies the transaction and asks user whether to submit transaction to the full node. + +### Cold wallet protocols + +As cold wallet support is already present in Monero codebase, the protocols were well designed and analyzed. +We decided to reuse the cold wallet approach when signing the transaction as the TREZOR pretty much behaves as the cold wallet, +i.e., does not have access to the blockchain or full Monero node. The whole transaction is built in the TREZOR thus +the integration has security properties of the cold wallet (which is belevied to be secure). This integration approach +makes security analysis easier and enables to use existing codebase and protocols. This makes merging TREZOR support to +the Monero codebase easier. +We believe that by choosing a bit more high-level approach in the protocol design we could easily add more advanced features, + +TREZOR implements cold wallet protocols in this integration scheme. + + +## Description + +Main high level protocol logic is implemented in `apps/monero/protocol/` directory. + +### Serialization + +The serialization in `apps/monero/xmr/serialize` is the cryptonote serialization format used to serialize data to blockchain. +The serialization was ported from Monero C++. Source comes from the library [monero-serialize]. + +Serialization scheme was inspired by protobuf serialization scheme. +Fields are specified as a classmethod which is easier to `gc.collect()` after serialization is done. + +```python + @classmethod + def f_specs(cls): + return (("size", SizeT),) +``` + +Serialization is synchronous. + + +### Protocols + +Transaction signing and Key Image (KI) sync are multi-step stateful protocols. +The protocol have several roundtrips. + +In the signing protocol the connected host mainly serves as a dumb storage providing values to the TREZOR when needed, +mainly due to memory constrains on TREZOR. The offloaded data can be in plaintext. In this case data is HMACed with unique HMAC +key to avoid data tampering, reordering, replay, reuse, etc... Some data are offloaded as protected, encrypted and authenticated +with Chacha20Poly1305 with unique key (derived from the protocol step, message, purpose, counter, master secret). + +TREZOR builds the signed Monero transaction incrementally, i.e., one UTXO per round trip, one transaction output per roundtrip. + +### Protocol workflow + +Key image sync and transaction signing protocols are stateful. +Both protocols implement custom workflow managing the protocol state and state transitions explicitly. + +Entry to the protocol workflow is passed on the initial protocol message, i.e., only the initial protocol message +is registered via `wire.add()`. The workflow internally manages receiving / sending protocol messages. + +Each finished protocol step specifies the next expected message set which helps to govern protocol state transitions, +i.e., exception is thrown if another message is received as expected. + +As the protocols implement custom workflow the general package unimport in `wire` is not called which +could lead to memory problems as locally imported packages are not freed from memory on `gc.collect()`. +Thus protocols call unimport manually after processing the protocol messages. + +Protobuf messages are following the convention `MoneroXRequest`, `MoneroXAck`. + + +## Key Image sync work flow + +In the KI sync cold wallet protocol KIs are generated by the cold wallet. For each KI there is a ring signature +generated by the cold wallet (KI proof). + +KI sync is mainly needed to recover from some problem or when using a new hot-wallet (corruption of a wallet file or +using TREZOR on a different host). + +The KI protocol has 3 steps. + +### Init step + +- `MoneroKeyImageExportInitRequest` +- Contains commitment to all KIs we are going to compute (hash of all UTXOs). +- User can confirm / reject the KI sync in this step. Init message contains number of KIs for computation. + +### Sync + +- `MoneroKeyImageSyncStepRequest` +- Computes N KIs in this step. N = 10 for now. +- Returns encrypted result, `MoneroExportedKeyImage` + +### Finalization + +- `MoneroKeyImageSyncFinalRequest` +- When commitment on all KIs is correct (i.e, number of UTXOs matches, hashes match) the encryption key is released +to the agent/hot-wallet so it can decrypt computed KIs and import it + + +## Transaction signing + +For detailed description and rationale please refer to the [monero-doc]. + +- The protocol workflow `apps/monero/sign_tx.py` +- The protocol is implemented in `apps/monero/protocol/signing/` + +### `MoneroTransactionInitRequest`: + +- Contains basic construction data for the transaction, e.g., transaction destinations, fee, mixin level, +range proof details (type of the range proof, batching scheme). + +After receiving this message: +- The TREZOR prompts user for verification of the destination addresses and amounts. +- Commitments are computed thus later potential deviations from transaction destinations are detected and signing aborts. +- Secrets for HMACs / encryption are computed, TX key is computed. +- Precomputes required sub-addresses (init message indicates which sub-addresses are needed). + +### `MoneroTransactionSetInputRequest` + +- Sends one UTXO to the TREZOR for processing, encoded as `MoneroTransactionSourceEntry`. +- Contains construction data needed for signing the transaction, computing spending key for UTXO. + +TREZOR computes spending keys, `TxinToKey`, `pseudo_out`, HMACs for offloaded data + +### `MoneroTransactionInputsPermutationRequest` + +UTXOs have to be sorted by the key image in the valid blockchain transaction. +This message caries permutation on the key images so they are sorted in the desired way. + +### `MoneroTransactionInputViniRequest` + +- Step needed to correctly hash all transaction inputs, in the right order (permutation computed in the previous step). +- Contains `MoneroTransactionSourceEntry` and `TxinToKey` computed in the previous step. +- TREZOR Computes `tx_prefix_hash` is part of the signed data. + + +### `MoneroTransactionAllInputsSetRequest` + +- Sent after all inputs have been processed. +- Used in the range proof offloading to the host. E.g., in case of batched Bulletproofs with more than 2 transaction outputs. +The message response contains TREZOR-generated commitment masks so host can compute range proof correctly. + +### `MoneroTransactionSetOutputRequest` + +- Sends transaction output, `MoneroTransactionDestinationEntry`, one per message. +- HMAC prevents tampering with previously accepted data (in the init step). +- TREZOR computes data related to transaction output, e.g., range proofs, ECDH info for the receiver, output public key. +- In case offloaded range proof is used the request can carry computed range proof. + +### `MoneroTransactionAllOutSetRequest` + +Sent after all transaction outputs have been sent to the TREZOR for processing. +Request is empty, the response contains computed `extra` field (may contain additional public keys if sub-addresses are used), +computed `tx_prefix_hash` and basis for the final transaction signature `MoneroRingCtSig` (fee, transaction type). + +### `MoneroTransactionMlsagDoneRequest` + +Message sent to ask TREZOR to compute pre-MLSAG hash required for the signature. +Hash is computed incrementally by TREZOR since the init message and can be finalized in this step. +Request is empty, response contains message hash, required for the signature. + +### `MoneroTransactionSignInputRequest` + +- Caries `MoneroTransactionSourceEntry`, similarly as previous messages `MoneroTransactionSetInputRequest`, `MoneroTransactionInputViniRequest`. +- Caries computed transaction inputs, pseudo outputs, HMACs, encrypted spending keys and alpha masks +- TREZOR generates MLSAG for this UTXO, returns the signature. +- Code returns also `cout` value if the multisig mode is active - not fully implemented, will be needed later when implementing multisigs. + +### `MoneroTransactionFinalRequest` + +- Sent when all UTXOs have been signed properly +- Finalizes transaction signature +- Returns encrypted transaction private keys which are needed later, e.g. for TX proof. As TREZOR cannot store aux data +for all signed transactions its offloaded encrypted to the wallet. Later when TX proof is implemented in the TREZOR it +will load encrypted TX keys, decrypt it and generate the proof. + + +## Implementation notes + +Few notes on design / implementation. + +### Cryptography + +Operation with Ed25519 points and scalars are implemented in [trezor-crypto] so the underlying cryptography layer +is fast, secure and constant-time. + +Ed Point coordinates are Extended Edwards, using type `ge25519` with coordinates `(x, y, z, t)`. Functions in Monero code +in the [trezor-crypto] use the `ge25519` for points (no other different point formats). + +Functions like `op256_modm` (e.g., `add256_modm`) operate on scalar values, i.e., 256 bit integers modulo curve order +`2**252 + 3*610042537739*15158679415041928064055629`. + +Functions `curve25519_*` operate on 256 bit integers modulo `2**255 - 19`, the coordinates of the point. +These are used mainly internally (e.g., for `hash_to_point()`) and not exported to the [trezor-core]. + +[trezor-crypto] contains also some Monero-specific functions, such as +`xmr_hash_to_scalar`, `xmr_hash_to_ec`, `xmr_generate_key_derivation`. Those are used in [trezor-core] where more high +level operations are implemented, such as MLSAG. + +#### Crypto API + +API bridging [trezor-crypto] and [trezor-core]: `embed/extmod/modtrezorcrypto/modtrezorcrypto-monero.h` + +It encapsulates Ed25519 points and scalars in corresponding Python classes which have memory-wiping destructor. +API provides basic functions for work with scalars and points and Monero specific functions. + +The API is designed in such a way it is easy to work with Ed25519 as there is only one point format which is always +normed to avoid complications when chaining operations such as `scalarmult`s. + + +### Range signatures + +Borromean range signatures were optimized and ported to [trezor-crypto]. + +Range signatures xmr_gen_range_sig are CPU intensive and memory intensive operations which were originally implemented +in python (trezor-core) but it was not feasible to run on the Trezor device due to a small amount of RAM and long +computation times. It was needed to optimize the algorithm and port it to C so it is feasible to run it on the real hardware and run it fast. + +Range signature is a well-contained problem with no allocations needed, simple API. +For memory and timing reasons its implemented directly in trezor-crypto (as it brings real benefit to the user). + +On the other hand, MLASG and other ring signatures are built from building blocks in python for easier development, +code readability, maintenance and debugging. Porting to C is not that straightforward and I don't see any benefit here. +The memory and CPU is not the problem as in the case of range signatures so I think it is fine to have it in Python. +Porting to C would also increase complexity of trezor-crypto and could lead to bugs. + +Using small and easily auditable & testable building blocks, such as ge25519_add (fast, in C) to build more complex +schemes in high level language is, in my opinion, a scalable and secure way to build the system. +Porting all Monero crypto schemes to C would be very time consuming and prone to errors. + +Having access to low-level features also speeds up development of new features, such as multisigs. + +MLSAG may need to be slightly changed when implementing multisigs +(some preparations have been made already but we will see after this phase starts). + +Bulletproof generation and verification is implemented, however the device can handle maximum 2 batched outputs +in the bulletproof due to high memory requirements (more on that in [monero-doc]). If number of outputs is larger +than 2 the offloading to host is required. In such case, the bulletproofs are first computed at the host and sent to +Trezor for verification. + +Bulletproof implementation is covered by unit tests, the proofs in unittest were generated by the Monero C++ +implementation. + + + + + +[trezor-crypto]: https://github.com/trezor/trezor-crypto +[trezor-core]: https://github.com/trezor/trezor-core +[monero-doc]: https://github.com/ph4r05/monero-trezor-doc +[monero-serialize]: https://github.com/ph4r05/monero-serialize diff --git a/src/apps/monero/__init__.py b/src/apps/monero/__init__.py new file mode 100644 index 000000000..c2a8364cd --- /dev/null +++ b/src/apps/monero/__init__.py @@ -0,0 +1,12 @@ +from trezor import wire +from trezor.messages import MessageType + + +def boot(): + wire.add(MessageType.MoneroGetAddress, __name__, "get_address") + wire.add(MessageType.MoneroGetWatchKey, __name__, "get_watch_only") + wire.add(MessageType.MoneroTransactionInitRequest, __name__, "sign_tx") + wire.add(MessageType.MoneroKeyImageExportInitRequest, __name__, "key_image_sync") + + if __debug__ and hasattr(MessageType, "DebugMoneroDiagRequest"): + wire.add(MessageType.DebugMoneroDiagRequest, __name__, "diag") diff --git a/src/apps/monero/diag.py b/src/apps/monero/diag.py new file mode 100644 index 000000000..50de2687a --- /dev/null +++ b/src/apps/monero/diag.py @@ -0,0 +1,114 @@ +if __debug__: + import gc + import micropython + import sys + + from trezor import log + + PREV_MEM = gc.mem_free() + CUR_MES = 0 + + def log_trace(x=None): + log.debug( + __name__, + "Log trace %s, ... F: %s A: %s, S: %s", + x, + gc.mem_free(), + gc.mem_alloc(), + micropython.stack_use(), + ) + + def check_mem(x=""): + global PREV_MEM, CUR_MES + + gc.collect() + free = gc.mem_free() + diff = PREV_MEM - free + log.debug( + __name__, + "======= {} {} Diff: {} Free: {} Allocated: {}".format( + CUR_MES, x, diff, free, gc.mem_alloc() + ), + ) + micropython.mem_info() + gc.collect() + CUR_MES += 1 + PREV_MEM = free + + def retit(**kwargs): + from trezor.messages.Failure import Failure + + return Failure(**kwargs) + + async def diag(ctx, msg, **kwargs): + log.debug(__name__, "----diagnostics") + gc.collect() + + if msg.ins == 0: + check_mem(0) + return retit() + + elif msg.ins == 1: + check_mem(1) + micropython.mem_info(1) + return retit() + + elif msg.ins == 2: + log.debug(__name__, "_____________________________________________") + log.debug(__name__, "_____________________________________________") + log.debug(__name__, "_____________________________________________") + return retit() + + elif msg.ins == 3: + pass + + elif msg.ins == 4: + total = 0 + monero = 0 + + for k, v in sys.modules.items(): + log.info(__name__, "Mod[%s]: %s", k, v) + total += 1 + if k.startswith("apps.monero"): + monero += 1 + log.info(__name__, "Total modules: %s, Monero modules: %s", total, monero) + return retit() + + elif msg.ins in [5, 6, 7]: + check_mem() + from apps.monero.xmr import bulletproof as bp + + check_mem("BP Imported") + from apps.monero.xmr import crypto + + check_mem("Crypto Imported") + + bpi = bp.BulletProofBuilder() + bpi.gc_fnc = gc.collect + bpi.gc_trace = log_trace + + vals = [crypto.sc_init((1 << 30) - 1 + 16), crypto.sc_init(22222)] + masks = [crypto.random_scalar(), crypto.random_scalar()] + check_mem("BP pre input") + + if msg.ins == 5: + bp_res = bpi.prove_testnet(vals[0], masks[0]) + check_mem("BP post prove") + bpi.verify_testnet(bp_res) + check_mem("BP post verify") + + elif msg.ins == 6: + bp_res = bpi.prove(vals[0], masks[0]) + check_mem("BP post prove") + bpi.verify(bp_res) + check_mem("BP post verify") + + elif msg.ins == 7: + bp_res = bpi.prove_batch(vals, masks) + check_mem("BP post prove") + bpi.verify(bp_res) + check_mem("BP post verify") + + return retit() + + return retit() diff --git a/src/apps/monero/get_address.py b/src/apps/monero/get_address.py new file mode 100644 index 000000000..e80f6c1d1 --- /dev/null +++ b/src/apps/monero/get_address.py @@ -0,0 +1,17 @@ +from trezor.messages.MoneroAddress import MoneroAddress + +from apps.common.layout import show_address, show_qr +from apps.monero import misc + + +async def get_address(ctx, msg): + creds = await misc.get_creds(ctx, msg.address_n, msg.network_type) + + if msg.show_display: + while True: + if await show_address(ctx, creds.address.decode("ascii"), msg.address_n): + break + if await show_qr(ctx, creds.address.decode("ascii")): + break + + return MoneroAddress(address=creds.address) diff --git a/src/apps/monero/get_watch_only.py b/src/apps/monero/get_watch_only.py new file mode 100644 index 000000000..8131dde5f --- /dev/null +++ b/src/apps/monero/get_watch_only.py @@ -0,0 +1,16 @@ +from trezor.messages.MoneroGetWatchKey import MoneroGetWatchKey +from trezor.messages.MoneroWatchKey import MoneroWatchKey + +from apps.monero import misc +from apps.monero.layout import confirms +from apps.monero.xmr import crypto + + +async def get_watch_only(ctx, msg: MoneroGetWatchKey): + await confirms.require_confirm_watchkey(ctx) + + creds = await misc.get_creds(ctx, msg.address_n, msg.network_type) + address = creds.address + watch_key = crypto.encodeint(creds.view_key_private) + + return MoneroWatchKey(watch_key=watch_key, address=address) diff --git a/src/apps/monero/key_image_sync.py b/src/apps/monero/key_image_sync.py new file mode 100644 index 000000000..a72001d07 --- /dev/null +++ b/src/apps/monero/key_image_sync.py @@ -0,0 +1,108 @@ +import gc + +from trezor import log, wire +from trezor.messages import MessageType +from trezor.messages.MoneroExportedKeyImage import MoneroExportedKeyImage +from trezor.messages.MoneroKeyImageExportInitAck import MoneroKeyImageExportInitAck +from trezor.messages.MoneroKeyImageSyncFinalAck import MoneroKeyImageSyncFinalAck +from trezor.messages.MoneroKeyImageSyncStepAck import MoneroKeyImageSyncStepAck + +from apps.monero import misc +from apps.monero.layout import confirms +from apps.monero.xmr import crypto, key_image, monero +from apps.monero.xmr.crypto import chacha_poly + + +async def key_image_sync(ctx, msg): + state = KeyImageSync() + + res = await _init_step(state, ctx, msg) + while True: + msg = await ctx.call( + res, + MessageType.MoneroKeyImageSyncStepRequest, + MessageType.MoneroKeyImageSyncFinalRequest, + ) + del res + if msg.MESSAGE_WIRE_TYPE == MessageType.MoneroKeyImageSyncStepRequest: + res = await _sync_step(state, ctx, msg) + else: + res = await _final_step(state, ctx) + break + gc.collect() + + return res + + +class KeyImageSync: + def __init__(self): + self.current_output = -1 + self.num_outputs = 0 + self.expected_hash = None + self.enc_key = None + self.creds = None + self.subaddresses = {} + self.hasher = crypto.get_keccak() + + +async def _init_step(s, ctx, msg): + s.creds = await misc.get_creds(ctx, msg.address_n, msg.network_type) + + await confirms.require_confirm_keyimage_sync(ctx) + + s.num_outputs = msg.num + s.expected_hash = msg.hash + s.enc_key = crypto.random_bytes(32) + + for sub in msg.subs: + monero.compute_subaddresses( + s.creds, sub.account, sub.minor_indices, s.subaddresses + ) + + return MoneroKeyImageExportInitAck() + + +async def _sync_step(s, ctx, tds): + if not tds.tdis: + raise wire.DataError("Empty") + + kis = [] + buff = bytearray(32 * 3) + buff_mv = memoryview(buff) + + for td in tds.tdis: + s.current_output += 1 + if s.current_output >= s.num_outputs: + raise wire.DataError("Too many outputs") + + if __debug__: + log.debug(__name__, "ki_sync, step i: %d", s.current_output) + + # Update the control hash + s.hasher.update(key_image.compute_hash(td)) + + # Compute keyimage + signature + ki, sig = key_image.export_key_image(s.creds, s.subaddresses, td) + + # Serialize into buff + crypto.encodepoint_into(buff_mv[0:32], ki) + crypto.encodeint_into(buff_mv[32:64], sig[0][0]) + crypto.encodeint_into(buff_mv[64:], sig[0][1]) + + # Encrypt with enc_key + nonce, ciph, _ = chacha_poly.encrypt(s.enc_key, buff) + + kis.append(MoneroExportedKeyImage(iv=nonce, blob=ciph)) + + return MoneroKeyImageSyncStepAck(kis=kis) + + +async def _final_step(s, ctx): + if s.current_output + 1 != s.num_outputs: + raise wire.DataError("Invalid number of outputs") + + final_hash = s.hasher.digest() + if final_hash != s.expected_hash: + raise wire.DataError("Invalid number of outputs") + + return MoneroKeyImageSyncFinalAck(enc_key=s.enc_key) diff --git a/src/apps/monero/layout/common.py b/src/apps/monero/layout/common.py new file mode 100644 index 000000000..5ebadb9f4 --- /dev/null +++ b/src/apps/monero/layout/common.py @@ -0,0 +1,121 @@ +from trezor import res, ui +from trezor.messages import ButtonRequestType +from trezor.ui.text import Text +from trezor.utils import chunks + + +def paginate_lines(lines, lines_per_page=5): + pages = [] + cpage = [] + nlines = 0 + last_modifier = None + for line in lines: + cpage.append(line) + if not isinstance(line, int): + nlines += 1 + else: + last_modifier = line + + if nlines >= lines_per_page: + pages.append(cpage) + cpage = [] + nlines = 0 + if last_modifier is not None: + cpage.append(last_modifier) + + if nlines > 0: + pages.append(cpage) + return pages + + +@ui.layout +async def tx_dialog( + ctx, + code, + content, + cancel_btn, + confirm_btn, + cancel_style, + confirm_style, + scroll_tuple=None, +): + from trezor.messages import MessageType + from trezor.messages.ButtonRequest import ButtonRequest + from trezor.ui.confirm import ConfirmDialog + from trezor.ui.scroll import Scrollpage + + await ctx.call(ButtonRequest(code=code), MessageType.ButtonAck) + + dialog = ConfirmDialog( + content, + cancel=cancel_btn, + confirm=confirm_btn, + cancel_style=cancel_style, + confirm_style=confirm_style, + ) + if scroll_tuple and scroll_tuple[1] > 1: + dialog = Scrollpage(dialog, scroll_tuple[0], scroll_tuple[1]) + + return await ctx.wait(dialog) + + +async def naive_pagination( + ctx, lines, title, icon=ui.ICON_RESET, icon_color=ui.ORANGE, per_page=5 +): + from trezor.ui.confirm import CANCELLED, CONFIRMED, DEFAULT_CANCEL, DEFAULT_CONFIRM + + if isinstance(lines, (list, tuple)): + lines = lines + else: + lines = list(chunks(lines, 16)) + + pages = paginate_lines(lines, per_page) + npages = len(pages) + cur_step = 0 + code = ButtonRequestType.SignTx + iback = res.load(ui.ICON_BACK) + inext = res.load(ui.ICON_CLICK) + + while cur_step <= npages: + text = pages[cur_step] + fst_page = cur_step == 0 + lst_page = cur_step + 1 >= npages + + cancel_btn = DEFAULT_CANCEL if fst_page else iback + cancel_style = ui.BTN_CANCEL if fst_page else ui.BTN_DEFAULT + confirm_btn = DEFAULT_CONFIRM if lst_page else inext + confirm_style = ui.BTN_CONFIRM if lst_page else ui.BTN_DEFAULT + + paging = ("%d/%d" % (cur_step + 1, npages)) if npages > 1 else "" + content = Text("%s %s" % (title, paging), icon, icon_color=icon_color) + content.normal(*text) + + reaction = await tx_dialog( + ctx, + code, + content, + cancel_btn, + confirm_btn, + cancel_style, + confirm_style, + (cur_step, npages), + ) + + if fst_page and reaction == CANCELLED: + return False + elif not lst_page and reaction == CONFIRMED: + cur_step += 1 + elif lst_page and reaction == CONFIRMED: + return True + elif reaction == CANCELLED: + cur_step -= 1 + elif reaction == CONFIRMED: + cur_step += 1 + + +def format_amount(value): + return "%f XMR" % (value / 1000000000000) + + +def split_address(address): + return chunks(address, 16) diff --git a/src/apps/monero/layout/confirms.py b/src/apps/monero/layout/confirms.py new file mode 100644 index 000000000..1d2b3c4a2 --- /dev/null +++ b/src/apps/monero/layout/confirms.py @@ -0,0 +1,125 @@ +from ubinascii import hexlify + +from trezor import ui, wire +from trezor.messages import ButtonRequestType +from trezor.ui.text import Text +from trezor.utils import chunks + +from apps.common.confirm import require_confirm, require_hold_to_confirm +from apps.monero.layout import common + + +async def require_confirm_watchkey(ctx): + content = Text("Confirm export", ui.ICON_SEND, icon_color=ui.GREEN) + content.normal("Do you really want to", "export watch-only", "credentials?") + return await require_confirm(ctx, content, ButtonRequestType.SignTx) + + +async def require_confirm_keyimage_sync(ctx): + content = Text("Confirm ki sync", ui.ICON_SEND, icon_color=ui.GREEN) + content.normal("Do you really want to", "sync key images?") + return await require_confirm(ctx, content, ButtonRequestType.SignTx) + + +async def require_confirm_transaction(ctx, tsx_data, network_type): + """ + Ask for confirmation from user. + """ + from apps.monero.xmr.addresses import get_change_addr_idx + + outputs = tsx_data.outputs + change_idx = get_change_addr_idx(outputs, tsx_data.change_dts) + has_integrated = bool(tsx_data.integrated_indices) + has_payment = bool(tsx_data.payment_id) + + for idx, dst in enumerate(outputs): + is_change = change_idx is not None and idx == change_idx + if is_change: + continue # Change output does not need confirmation + is_dummy = change_idx is None and dst.amount == 0 and len(outputs) == 2 + if is_dummy: + continue # Dummy output does not need confirmation + if has_integrated and idx in tsx_data.integrated_indices: + cur_payment = tsx_data.payment_id + else: + cur_payment = None + await _require_confirm_output(ctx, dst, network_type, cur_payment) + + if has_payment and not has_integrated: + await _require_confirm_payment_id(ctx, tsx_data.payment_id) + + await _require_confirm_fee(ctx, tsx_data.fee) + + text = Text("Signing transaction", ui.ICON_SEND, icon_color=ui.BLUE) + text.normal("Signing...") + text.render() + + +async def _require_confirm_output(ctx, dst, network_type, payment_id): + """ + Single transaction destination confirmation + """ + from apps.monero.xmr.addresses import encode_addr + from apps.monero.xmr.networks import net_version + + version = net_version(network_type, dst.is_subaddress, payment_id is not None) + addr = encode_addr( + version, dst.addr.spend_public_key, dst.addr.view_public_key, payment_id + ) + + text_addr = common.split_address(addr.decode()) + text_amount = common.format_amount(dst.amount) + + if not await common.naive_pagination( + ctx, + [ui.BOLD, text_amount, ui.MONO] + list(text_addr), + "Confirm send", + ui.ICON_SEND, + ui.GREEN, + 4, + ): + raise wire.ActionCancelled("Cancelled") + + +async def _require_confirm_payment_id(ctx, payment_id): + if not await common.naive_pagination( + ctx, + [ui.MONO] + list(chunks(hexlify((payment_id)), 16)), + "Payment ID", + ui.ICON_SEND, + ui.GREEN, + ): + raise wire.ActionCancelled("Cancelled") + + +async def _require_confirm_fee(ctx, fee): + content = Text("Confirm fee", ui.ICON_SEND, icon_color=ui.GREEN) + content.bold(common.format_amount(fee)) + await require_hold_to_confirm(ctx, content, ButtonRequestType.ConfirmOutput) + + +@ui.layout +async def transaction_step(ctx, step, sub_step=None, sub_step_total=None): + info = [] + if step == 100: + info = ["Processing inputs", "%d/%d" % (sub_step + 1, sub_step_total)] + elif step == 200: + info = ["Sorting"] + elif step == 300: + info = [ + "Processing inputs", + "phase 2", + "%d/%d" % (sub_step + 1, sub_step_total), + ] + elif step == 400: + info = ["Processing outputs", "%d/%d" % (sub_step + 1, sub_step_total)] + elif step == 500: + info = ["Postprocessing..."] + elif step == 600: + info = ["Signing inputs", "%d/%d" % (sub_step + 1, sub_step_total)] + else: + info = ["Processing..."] + + text = Text("Signing transaction", ui.ICON_SEND, icon_color=ui.BLUE) + text.normal(*info) + text.render() diff --git a/src/apps/monero/misc.py b/src/apps/monero/misc.py new file mode 100644 index 000000000..b6d32283d --- /dev/null +++ b/src/apps/monero/misc.py @@ -0,0 +1,21 @@ +async def get_creds(ctx, address_n=None, network_type=None): + from apps.common import seed + from apps.monero.xmr import crypto, monero + from apps.monero.xmr.credentials import AccountCreds + + use_slip0010 = 0 not in address_n # If path contains 0 it is not SLIP-0010 + + if use_slip0010: + curve = "ed25519" + else: + curve = "secp256k1" + node = await seed.derive_node(ctx, address_n, curve) + + if use_slip0010: + key_seed = node.private_key() + else: + key_seed = crypto.cn_fast_hash(node.private_key()) + spend_sec, _, view_sec, _ = monero.generate_monero_keys(key_seed) + + creds = AccountCreds.new_wallet(view_sec, spend_sec, network_type) + return creds diff --git a/src/apps/monero/sign_tx.py b/src/apps/monero/sign_tx.py new file mode 100644 index 000000000..c85118681 --- /dev/null +++ b/src/apps/monero/sign_tx.py @@ -0,0 +1,139 @@ +import gc + +from trezor import log, utils, wire +from trezor.messages import MessageType + +from apps.monero.signing.state import State + + +async def sign_tx(ctx, received_msg): + state = State(ctx) + mods = utils.unimport_begin() + + # Splitting ctx.call() to write() and read() helps to reduce memory fragmentation + # between calls. + while True: + if __debug__: + log.debug(__name__, "#### F: %s, A: %s", gc.mem_free(), gc.mem_alloc()) + gc.collect() + gc.threshold(gc.mem_free() // 4 + gc.mem_alloc()) + + result_msg, accept_msgs = await sign_tx_dispatch(state, received_msg) + if accept_msgs is None: + break + + await ctx.write(result_msg) + del (result_msg, received_msg) + utils.unimport_end(mods) + + received_msg = await ctx.read(accept_msgs) + + utils.unimport_end(mods) + return result_msg + + +async def sign_tx_dispatch(state, msg): + if msg.MESSAGE_WIRE_TYPE == MessageType.MoneroTransactionInitRequest: + from apps.monero.signing import step_01_init_transaction + + return ( + await step_01_init_transaction.init_transaction( + state, msg.address_n, msg.network_type, msg.tsx_data + ), + (MessageType.MoneroTransactionSetInputRequest,), + ) + + elif msg.MESSAGE_WIRE_TYPE == MessageType.MoneroTransactionSetInputRequest: + from apps.monero.signing import step_02_set_input + + return ( + await step_02_set_input.set_input(state, msg.src_entr), + ( + MessageType.MoneroTransactionSetInputRequest, + MessageType.MoneroTransactionInputsPermutationRequest, + ), + ) + + elif msg.MESSAGE_WIRE_TYPE == MessageType.MoneroTransactionInputsPermutationRequest: + from apps.monero.signing import step_03_inputs_permutation + + return ( + await step_03_inputs_permutation.tsx_inputs_permutation(state, msg.perm), + (MessageType.MoneroTransactionInputViniRequest,), + ) + + elif msg.MESSAGE_WIRE_TYPE == MessageType.MoneroTransactionInputViniRequest: + from apps.monero.signing import step_04_input_vini + + return ( + await step_04_input_vini.input_vini( + state, + msg.src_entr, + msg.vini, + msg.vini_hmac, + msg.pseudo_out, + msg.pseudo_out_hmac, + ), + ( + MessageType.MoneroTransactionInputViniRequest, + MessageType.MoneroTransactionAllInputsSetRequest, + ), + ) + + elif msg.MESSAGE_WIRE_TYPE == MessageType.MoneroTransactionAllInputsSetRequest: + from apps.monero.signing import step_05_all_inputs_set + + return ( + await step_05_all_inputs_set.all_inputs_set(state), + (MessageType.MoneroTransactionSetOutputRequest,), + ) + + elif msg.MESSAGE_WIRE_TYPE == MessageType.MoneroTransactionSetOutputRequest: + from apps.monero.signing import step_06_set_output + + dst, dst_hmac, rsig_data = msg.dst_entr, msg.dst_entr_hmac, msg.rsig_data + del msg + + return ( + await step_06_set_output.set_output(state, dst, dst_hmac, rsig_data), + ( + MessageType.MoneroTransactionSetOutputRequest, + MessageType.MoneroTransactionAllOutSetRequest, + ), + ) + + elif msg.MESSAGE_WIRE_TYPE == MessageType.MoneroTransactionAllOutSetRequest: + from apps.monero.signing import step_07_all_outputs_set + + return ( + await step_07_all_outputs_set.all_outputs_set(state), + (MessageType.MoneroTransactionSignInputRequest,), + ) + + elif msg.MESSAGE_WIRE_TYPE == MessageType.MoneroTransactionSignInputRequest: + from apps.monero.signing import step_09_sign_input + + return ( + await step_09_sign_input.sign_input( + state, + msg.src_entr, + msg.vini, + msg.vini_hmac, + msg.pseudo_out, + msg.pseudo_out_hmac, + msg.pseudo_out_alpha, + msg.spend_key, + ), + ( + MessageType.MoneroTransactionSignInputRequest, + MessageType.MoneroTransactionFinalRequest, + ), + ) + + elif msg.MESSAGE_WIRE_TYPE == MessageType.MoneroTransactionFinalRequest: + from apps.monero.signing import step_10_sign_final + + return await step_10_sign_final.final_msg(state), None + + else: + raise wire.DataError("Unknown message") diff --git a/src/apps/monero/signing/__init__.py b/src/apps/monero/signing/__init__.py new file mode 100644 index 000000000..45e1880bb --- /dev/null +++ b/src/apps/monero/signing/__init__.py @@ -0,0 +1,60 @@ +from trezor import wire + + +class Error(wire.DataError): + pass + + +class ChangeAddressError(wire.DataError): + pass + + +class NotEnoughOutputsError(wire.DataError): + pass + + +class RctType: + """ + There are two types of monero Ring Confidential Transactions: + 1. RCTTypeFull = 1 (used if num_inputs == 1) + 2. RCTTypeSimple = 2 (for num_inputs > 1) + + There is actually also RCTTypeNull but we ignore that one. + """ + + Full = 1 + Simple = 2 + + +class RsigType: + """ + Range signature types + + There are four types of range proofs/signatures in official Monero: + 1. RangeProofBorromean = 0 + 2. RangeProofBulletproof = 1 + 3. RangeProofMultiOutputBulletproof = 2 + 4. RangeProofPaddedBulletproof = 3 + + We simplify all the bulletproofs into one. + """ + + Borromean = 0 + Bulletproof = 1 + + +def get_monero_rct_type(rct_type, rsig_type): + """ + This converts our internal representation of RctType and RsigType + into what is used in Monero: + - Null = 0 + - Full = 1 + - Simple = 2 + - Simple/Full with bulletproof = 3 + """ + if rsig_type == RsigType.Bulletproof: + return 3 # Bulletproofs + if rct_type == RctType.Simple: + return 2 # Simple + else: + return 1 # Full diff --git a/src/apps/monero/signing/offloading_keys.py b/src/apps/monero/signing/offloading_keys.py new file mode 100644 index 000000000..9e99c192e --- /dev/null +++ b/src/apps/monero/signing/offloading_keys.py @@ -0,0 +1,130 @@ +from trezor import utils + +from apps.monero.xmr import crypto + + +def _build_key(secret, discriminator=None, index: int = None) -> bytes: + """ + Creates an unique-purpose key + """ + key_buff = bytearray(32 + 12 + 4) # key + disc + index + offset = 32 + utils.memcpy(key_buff, 0, secret, 0, len(secret)) + + if discriminator is not None: + utils.memcpy(key_buff, offset, discriminator, 0, len(discriminator)) + offset += len(discriminator) + + if index is not None: + # dump_uvarint_b_into, saving import + shifted = True + while shifted: + shifted = index >> 7 + key_buff[offset] = (index & 0x7F) | (0x80 if shifted else 0x00) + offset += 1 + index = shifted + + return crypto.keccak_2hash(key_buff) + + +def hmac_key_txin(key_hmac, idx: int) -> bytes: + """ + (TxSourceEntry[i] || tx.vin[i]) hmac key + """ + return _build_key(key_hmac, b"txin", idx) + + +def hmac_key_txin_comm(key_hmac, idx: int) -> bytes: + """ + pseudo_outputs[i] hmac key. Pedersen commitment for inputs. + """ + return _build_key(key_hmac, b"txin-comm", idx) + + +def hmac_key_txdst(key_hmac, idx: int) -> bytes: + """ + TxDestinationEntry[i] hmac key + """ + return _build_key(key_hmac, b"txdest", idx) + + +def hmac_key_txout(key_hmac, idx: int) -> bytes: + """ + (TxDestinationEntry[i] || tx.vout[i]) hmac key + """ + return _build_key(key_hmac, b"txout", idx) + + +def hmac_key_txout_asig(key_hmac, idx: int) -> bytes: + """ + rsig[i] hmac key. Range signature HMAC + """ + return _build_key(key_hmac, b"txout-asig", idx) + + +def enc_key_txin_alpha(key_enc, idx: int) -> bytes: + """ + Chacha20Poly1305 encryption key for alpha[i] used in Pedersen commitment in pseudo_outs[i] + """ + return _build_key(key_enc, b"txin-alpha", idx) + + +def enc_key_spend(key_enc, idx: int) -> bytes: + """ + Chacha20Poly1305 encryption key for alpha[i] used in Pedersen commitment in pseudo_outs[i] + """ + return _build_key(key_enc, b"txin-spend", idx) + + +def enc_key_cout(key_enc, idx: int = None) -> bytes: + """ + Chacha20Poly1305 encryption key for multisig C values from MLASG. + """ + return _build_key(key_enc, b"cout", idx) + + +async def gen_hmac_vini(key, src_entr, vini_bin, idx: int) -> bytes: + """ + Computes hmac (TxSourceEntry[i] || tx.vin[i]) + """ + import protobuf + from apps.monero.xmr.keccak_hasher import get_keccak_writer + + kwriter = get_keccak_writer() + await protobuf.dump_message(kwriter, src_entr) + kwriter.write(vini_bin) + + hmac_key_vini = hmac_key_txin(key, idx) + hmac_vini = crypto.compute_hmac(hmac_key_vini, kwriter.get_digest()) + return hmac_vini + + +async def gen_hmac_vouti(key, dst_entr, tx_out_bin, idx: int) -> bytes: + """ + Generates HMAC for (TxDestinationEntry[i] || tx.vout[i]) + """ + import protobuf + from apps.monero.xmr.keccak_hasher import get_keccak_writer + + kwriter = get_keccak_writer() + await protobuf.dump_message(kwriter, dst_entr) + kwriter.write(tx_out_bin) + + hmac_key_vouti = hmac_key_txout(key, idx) + hmac_vouti = crypto.compute_hmac(hmac_key_vouti, kwriter.get_digest()) + return hmac_vouti + + +async def gen_hmac_tsxdest(key, dst_entr, idx: int) -> bytes: + """ + Generates HMAC for TxDestinationEntry[i] + """ + import protobuf + from apps.monero.xmr.keccak_hasher import get_keccak_writer + + kwriter = get_keccak_writer() + await protobuf.dump_message(kwriter, dst_entr) + + hmac_key = hmac_key_txdst(key, idx) + hmac_tsxdest = crypto.compute_hmac(hmac_key, kwriter.get_digest()) + return hmac_tsxdest diff --git a/src/apps/monero/signing/state.py b/src/apps/monero/signing/state.py new file mode 100644 index 000000000..1586f2c3e --- /dev/null +++ b/src/apps/monero/signing/state.py @@ -0,0 +1,146 @@ +import gc +from micropython import const + +from trezor import log + +from apps.monero.xmr import crypto + + +class TprefixStub: + __slots__ = ("version", "unlock_time", "vin", "vout", "extra") + + def __init__(self, **kwargs): + for kw in kwargs: + setattr(self, kw, kwargs[kw]) + + +class State: + + STEP_INP = const(100) + STEP_PERM = const(200) + STEP_VINI = const(300) + STEP_ALL_IN = const(350) + STEP_OUT = const(400) + STEP_ALL_OUT = const(500) + STEP_SIGN = const(600) + + def __init__(self, ctx): + from apps.monero.xmr.keccak_hasher import KeccakXmrArchive + from apps.monero.xmr.mlsag_hasher import PreMlsagHasher + + self.ctx = ctx + + """ + Account credentials + type: AccountCreds + - view private/public key + - spend private/public key + - and its corresponding address + """ + self.creds = None + + # HMAC/encryption keys used to protect offloaded data + self.key_hmac = None + self.key_enc = None + + """ + Transaction keys + - also denoted as r/R + - tx_priv is a random number + - tx_pub is equal to `r*G` or `r*D` for subaddresses + - for subaddresses the `r` is commonly denoted as `s`, however it is still just a random number + - the keys are used to derive the one time address and its keys (P = H(A*r)*G + B) + """ + self.tx_priv = None + self.tx_pub = None + + """ + In some cases when subaddresses are used we need more tx_keys + (explained in step 1). + """ + self.need_additional_txkeys = False + + # Ring Confidential Transaction type + # allowed values: RctType.{Full, Simple} + self.rct_type = None + # Range Signature type (also called range proof) + # allowed values: RsigType.{Borromean, Bulletproof} + self.rsig_type = None + + self.input_count = 0 + self.output_count = 0 + self.output_change = None + self.fee = 0 + + # wallet sub-address major index + self.account_idx = 0 + + # contains additional tx keys if need_additional_tx_keys is True + self.additional_tx_private_keys = [] + self.additional_tx_public_keys = [] + + # currently processed input/output index + self.current_input_index = -1 + self.current_output_index = -1 + + self.summary_inputs_money = 0 + self.summary_outs_money = 0 + + # output commitments + self.output_pk_commitments = [] + # masks used in the output commitment + self.output_sk_masks = [] + + self.output_amounts = [] + # output *range proof* masks + self.output_masks = [] + + # the range proofs are calculated in batches, this denotes the grouping + self.rsig_grouping = [] + # is range proof computing offloaded or not + self.rsig_offload = False + + # sum of all inputs' pseudo out masks + self.sumpouts_alphas = crypto.sc_0() + # sum of all output' pseudo out masks + self.sumout = crypto.sc_0() + + self.subaddresses = {} + + # simple stub containing items hashed into tx prefix + self.tx = TprefixStub(vin=[], vout=[], extra=b"") + + # contains an array where each item denotes the input's position + # (inputs are sorted by key images) + self.source_permutation = [] + + """ + Tx prefix hasher/hash. We use the hasher to incrementally hash and then + store the final hash in tx_prefix_hash. + See Monero-Trezor documentation section 3.3 for more details. + """ + self.tx_prefix_hasher = KeccakXmrArchive() + self.tx_prefix_hash = None + + """ + Full message hasher/hash that is to be signed using MLSAG. + Contains tx_prefix_hash. + See Monero-Trezor documentation section 3.3 for more details. + """ + self.full_message_hasher = PreMlsagHasher() + self.full_message = None + + def mem_trace(self, x=None, collect=False): + if __debug__: + log.debug( + __name__, + "Log trace: %s, ... F: %s A: %s", + x, + gc.mem_free(), + gc.mem_alloc(), + ) + if collect: + gc.collect() + + def change_address(self): + return self.output_change.addr if self.output_change else None diff --git a/src/apps/monero/signing/step_01_init_transaction.py b/src/apps/monero/signing/step_01_init_transaction.py new file mode 100644 index 000000000..5b3ea4dd5 --- /dev/null +++ b/src/apps/monero/signing/step_01_init_transaction.py @@ -0,0 +1,375 @@ +""" +Initializes a new transaction. +""" + +import gc + +from apps.monero import misc, signing +from apps.monero.layout import confirms +from apps.monero.signing import RctType, RsigType +from apps.monero.signing.state import State +from apps.monero.xmr import crypto, monero + +if False: + from trezor.messages.MoneroTransactionData import MoneroTransactionData + from trezor.messages.MoneroTransactionRsigData import MoneroTransactionRsigData + + +async def init_transaction( + state: State, address_n: list, network_type: int, tsx_data: MoneroTransactionData +): + from apps.monero.signing import offloading_keys + + state.creds = await misc.get_creds(state.ctx, address_n, network_type) + state.fee = state.fee if state.fee > 0 else 0 + state.tx_priv = crypto.random_scalar() + state.tx_pub = crypto.scalarmult_base(state.tx_priv) + + state.mem_trace(1) + + # Ask for confirmation + await confirms.require_confirm_transaction( + state.ctx, tsx_data, state.creds.network_type + ) + gc.collect() + state.mem_trace(3) + + # Basic transaction parameters + state.input_count = tsx_data.num_inputs + state.output_count = len(tsx_data.outputs) + state.output_change = tsx_data.change_dts + state.mixin = tsx_data.mixin + state.fee = tsx_data.fee + state.account_idx = tsx_data.account + + # Ensure change is correct + _check_change(state, tsx_data.outputs) + + # At least two outpus are required, this applies also for sweep txs + # where one fake output is added. See _check_change for more info + if state.output_count < 2: + raise signing.NotEnoughOutputsError("At least two outputs are required") + + _check_rsig_data(state, tsx_data.rsig_data) + _check_subaddresses(state, tsx_data.outputs) + + # Extra processing, payment id + state.tx.version = 2 # current Monero transaction format (RingCT = 2) + state.tx.unlock_time = tsx_data.unlock_time + _process_payment_id(state, tsx_data) + await _compute_sec_keys(state, tsx_data) + gc.collect() + + # Iterative tx_prefix_hash hash computation + state.tx_prefix_hasher.uvarint(state.tx.version) + state.tx_prefix_hasher.uvarint(state.tx.unlock_time) + state.tx_prefix_hasher.uvarint(state.input_count) # ContainerType, size + state.mem_trace(10, True) + + # Final message hasher + state.full_message_hasher.init(state.rct_type == RctType.Simple) + state.full_message_hasher.set_type_fee( + signing.get_monero_rct_type(state.rct_type, state.rsig_type), state.fee + ) + + # Sub address precomputation + if tsx_data.account is not None and tsx_data.minor_indices: + _precompute_subaddr(state, tsx_data.account, tsx_data.minor_indices) + state.mem_trace(5, True) + + # HMACs all outputs to disallow tampering. + # Each HMAC is then sent alongside the output + # and trezor validates it. + hmacs = [] + for idx in range(state.output_count): + c_hmac = await offloading_keys.gen_hmac_tsxdest( + state.key_hmac, tsx_data.outputs[idx], idx + ) + hmacs.append(c_hmac) + gc.collect() + + state.mem_trace(6) + + from trezor.messages.MoneroTransactionInitAck import MoneroTransactionInitAck + from trezor.messages.MoneroTransactionRsigData import MoneroTransactionRsigData + + rsig_data = MoneroTransactionRsigData(offload_type=state.rsig_offload) + + return MoneroTransactionInitAck(hmacs=hmacs, rsig_data=rsig_data) + + +def _check_subaddresses(state: State, outputs: list): + """ + Using subaddresses leads to a few poorly documented exceptions. + + Normally we set R=r*G (tx_pub), however for subaddresses this is equal to R=r*D + to achieve the nice blockchain scanning property. + + Remember, that R is per-transaction and not per-input. It's all good if we have a + single output or we have a single destination and the second output is our change. + This is because although the R=r*D, we can still derive the change using our private view-key. + In other words, calculate the one-time address as P = H(x*R)*G + Y (where X,Y is the change). + + However, this does not work for other outputs than change, because we do not have the + recipient's view key, so we cannot use the same formula -- we need a new R. + + The solution is very straightforward -- we create additional `R`s and use the `extra` + field to include them under the `ADDITIONAL_PUBKEYS` tag. + + See: + - https://lab.getmonero.org/pubs/MRL-0006.pdf + - https://github.com/monero-project/monero/pull/2056 + """ + from apps.monero.xmr.addresses import classify_subaddresses + + # let's first figure out what kind of destinations we have + num_stdaddresses, num_subaddresses, single_dest_subaddress = classify_subaddresses( + outputs, state.change_address() + ) + + # if this is a single-destination transfer to a subaddress, + # we set (override) the tx pubkey to R=r*D and no additional + # tx keys are needed + if num_stdaddresses == 0 and num_subaddresses == 1: + state.tx_pub = crypto.scalarmult( + crypto.decodepoint(single_dest_subaddress.spend_public_key), state.tx_priv + ) + + # if a subaddress is used and either standard address is as well + # or more than one subaddress is used we need to add additional tx keys + state.need_additional_txkeys = num_subaddresses > 0 and ( + num_stdaddresses > 0 or num_subaddresses > 1 + ) + state.mem_trace(4, True) + + +def _get_primary_change_address(state: State): + """ + Computes primary change address for the current account index + """ + from trezor.messages.MoneroAccountPublicAddress import MoneroAccountPublicAddress + + D, C = monero.generate_sub_address_keys( + state.creds.view_key_private, state.creds.spend_key_public, state.account_idx, 0 + ) + return MoneroAccountPublicAddress( + view_public_key=crypto.encodepoint(C), spend_public_key=crypto.encodepoint(D) + ) + + +def _check_rsig_data(state: State, rsig_data: MoneroTransactionRsigData): + """ + There are two types of monero ring confidential transactions: + 1. RCTTypeFull = 1 (used if num_inputs == 1) + 2. RCTTypeSimple = 2 (for num_inputs > 1) + + and four types of range proofs (set in `rsig_data.rsig_type`): + 1. RangeProofBorromean = 0 + 2. RangeProofBulletproof = 1 + 3. RangeProofMultiOutputBulletproof = 2 + 4. RangeProofPaddedBulletproof = 3 + """ + state.rsig_grouping = rsig_data.grouping + + if rsig_data.rsig_type == 0: + state.rsig_type = RsigType.Borromean + elif rsig_data.rsig_type in (1, 2, 3): + state.rsig_type = RsigType.Bulletproof + else: + raise ValueError("Unknown rsig type") + + # unintuitively RctType.Simple is used for more inputs + if state.input_count > 1 or state.rsig_type == RsigType.Bulletproof: + state.rct_type = RctType.Simple + else: + state.rct_type = RctType.Full + + if state.rsig_type == RsigType.Bulletproof and state.output_count > 2: + state.rsig_offload = True + + _check_grouping(state) + + +def _check_grouping(state: State): + acc = 0 + for x in state.rsig_grouping: + if x is None or x <= 0: + raise ValueError("Invalid grouping batch") + acc += x + + if acc != state.output_count: + raise ValueError("Invalid grouping") + + +def _check_change(state: State, outputs: list): + """ + Check if the change address in state.output_change (from `tsx_data.outputs`) is + a) among tx outputs + b) is equal to our address + + The change output is in `tsx_data.change_dts`, but also has to be in `tsx_data.outputs`. + This is what Monero does in its cold wallet signing protocol. + + In other words, these structures are built by Monero when generating unsigned transaction set + and we do not want to modify this logic. We just translate the unsigned tx to the protobuf message. + + So, although we could probably optimize this by having the change output in `change_dts` + only, we intentionally do not do so. + """ + from apps.monero.xmr.addresses import addr_eq, get_change_addr_idx + + change_index = get_change_addr_idx(outputs, state.output_change) + + change_addr = state.change_address() + # if there is no change, there is nothing to check + if change_addr is None: + state.mem_trace("No change" if __debug__ else None) + return + + """ + Sweep tx is just one output and no change. + To prevent recognition of such transactions another fake output is added + that spends exactly 0 coins to a random address. + See https://github.com/monero-project/monero/pull/1415 + """ + if change_index is None and state.output_change.amount == 0 and len(outputs) == 2: + state.mem_trace("Sweep tsx" if __debug__ else None) + return + + found = False + for out in outputs: + if addr_eq(out.addr, change_addr): + found = True + break + + if not found: + raise signing.ChangeAddressError("Change address not found in outputs") + + my_addr = _get_primary_change_address(state) + if not addr_eq(my_addr, change_addr): + raise signing.ChangeAddressError("Change address differs from ours") + + +async def _compute_sec_keys(state: State, tsx_data: MoneroTransactionData): + """ + Generate master key H( H(TsxData || tx_priv) || rand ) + """ + import protobuf + from apps.monero.xmr.keccak_hasher import get_keccak_writer + + writer = get_keccak_writer() + await protobuf.dump_message(writer, tsx_data) + writer.write(crypto.encodeint(state.tx_priv)) + + master_key = crypto.keccak_2hash( + writer.get_digest() + crypto.encodeint(crypto.random_scalar()) + ) + state.key_hmac = crypto.keccak_2hash(b"hmac" + master_key) + state.key_enc = crypto.keccak_2hash(b"enc" + master_key) + + +def _precompute_subaddr(state: State, account: int, indices): + """ + Precomputes subaddresses for account (major) and list of indices (minors) + Subaddresses have to be stored in encoded form - unique representation. + Single point can have multiple extended coordinates representation - would not match during subaddress search. + """ + monero.compute_subaddresses(state.creds, account, indices, state.subaddresses) + + +def _process_payment_id(state: State, tsx_data: MoneroTransactionData): + """ + Writes payment id to the `extra` field under the TX_EXTRA_NONCE = 0x02 tag. + + The second tag describes if the payment id is encrypted or not. + If the payment id is 8 bytes long it implies encryption and + therefore the TX_EXTRA_NONCE_ENCRYPTED_PAYMENT_ID = 0x01 tag is used. + If it is not encrypted, we use TX_EXTRA_NONCE_PAYMENT_ID = 0x00. + + See: + - https://github.com/monero-project/monero/blob/ff7dc087ae5f7de162131cea9dbcf8eac7c126a1/src/cryptonote_basic/tx_extra.h + """ + if not tsx_data.payment_id: + return + + # encrypted payment id + if len(tsx_data.payment_id) == 8: + view_key_pub_enc = _get_key_for_payment_id_encryption( + tsx_data.outputs, state.change_address() + ) + + view_key_pub = crypto.decodepoint(view_key_pub_enc) + payment_id_encr = _encrypt_payment_id( + tsx_data.payment_id, view_key_pub, state.tx_priv + ) + + extra_nonce = payment_id_encr + extra_prefix = 1 # TX_EXTRA_NONCE_ENCRYPTED_PAYMENT_ID + + # plain text payment id + elif len(tsx_data.payment_id) == 32: + extra_nonce = tsx_data.payment_id + extra_prefix = 0 # TX_EXTRA_NONCE_PAYMENT_ID + + else: + raise ValueError("Payment ID size invalid") + + lextra = len(extra_nonce) + if lextra >= 255: + raise ValueError("Nonce could be 255 bytes max") + + # write it to extra + extra_buff = bytearray(3 + lextra) + extra_buff[0] = 2 # TX_EXTRA_NONCE + extra_buff[1] = lextra + 1 + extra_buff[2] = extra_prefix + extra_buff[3:] = extra_nonce + state.tx.extra = extra_buff + + +def _get_key_for_payment_id_encryption(destinations: list, change_addr=None): + """ + Returns destination address public view key to be used for + payment id encryption. + """ + from apps.monero.xmr.addresses import addr_eq + from trezor.messages.MoneroAccountPublicAddress import MoneroAccountPublicAddress + + addr = MoneroAccountPublicAddress( + spend_public_key=crypto.NULL_KEY_ENC, view_public_key=crypto.NULL_KEY_ENC + ) + count = 0 + for dest in destinations: + if dest.amount == 0: + continue + if change_addr and addr_eq(dest.addr, change_addr): + continue + if addr_eq(dest.addr, addr): + continue + if count > 0: + raise ValueError( + "Destinations have to have exactly one output to support encrypted payment ids" + ) + addr = dest.addr + count += 1 + + if addr.view_public_key == crypto.NULL_KEY_ENC: + raise ValueError("Invalid key") + + return addr.view_public_key + + +def _encrypt_payment_id(payment_id, public_key, secret_key): + """ + Encrypts payment_id hex. + Used in the transaction extra. Only recipient is able to decrypt. + """ + derivation_p = crypto.generate_key_derivation(public_key, secret_key) + derivation = bytearray(33) + derivation = crypto.encodepoint_into(derivation, derivation_p) + derivation[32] = 0x8B + hash = crypto.cn_fast_hash(derivation) + pm_copy = bytearray(payment_id) + for i in range(8): + pm_copy[i] ^= hash[i] + return pm_copy diff --git a/src/apps/monero/signing/step_02_set_input.py b/src/apps/monero/signing/step_02_set_input.py new file mode 100644 index 000000000..b5db60f8c --- /dev/null +++ b/src/apps/monero/signing/step_02_set_input.py @@ -0,0 +1,171 @@ +""" +UTXOs are sent one by one to Trezor for processing, encoded as MoneroTransactionSourceEntry. + +MoneroTransactionSourceEntry contains the actual UTXO to be spent, but also the other decoy/mixin +outputs. So all the outputs are in one list and then the `real_output` index specifies which output +is the real one to be spent. + +This step computes spending secret key, key image, tx.vin[i] + HMAC, Pedersen commitment on amount. + +If number of inputs is small, in-memory mode is used = alpha, pseudo_outs are kept in the Trezor. +Otherwise pseudo_outs are offloaded with HMAC, alpha is offloaded encrypted under chacha_poly with +key derived for exactly this purpose. +""" +from .state import State + +from apps.monero.layout import confirms +from apps.monero.signing import RctType +from apps.monero.xmr import crypto, monero, serialize + +if False: + from trezor.messages.MoneroTransactionSourceEntry import ( + MoneroTransactionSourceEntry, + ) + + +async def set_input(state: State, src_entr: MoneroTransactionSourceEntry): + from trezor.messages.MoneroTransactionSetInputAck import ( + MoneroTransactionSetInputAck, + ) + from apps.monero.xmr.crypto import chacha_poly + from apps.monero.xmr.serialize_messages.tx_prefix import TxinToKey + from apps.monero.signing import offloading_keys + + state.current_input_index += 1 + + await confirms.transaction_step( + state.STEP_INP, state.current_input_index, state.input_count + ) + + if state.current_input_index >= state.input_count: + raise ValueError("Too many inputs") + # real_output denotes which output in outputs is the real one (ours) + if src_entr.real_output >= len(src_entr.outputs): + raise ValueError( + "real_output index %s bigger than output_keys.size() %s" + % (src_entr.real_output, len(src_entr.outputs)) + ) + state.summary_inputs_money += src_entr.amount + + # Secrets derivation + # the UTXO's one-time address P + out_key = crypto.decodepoint(src_entr.outputs[src_entr.real_output].key.dest) + # the tx_pub of our UTXO stored inside its transaction + tx_key = crypto.decodepoint(src_entr.real_out_tx_key) + additional_keys = [ + crypto.decodepoint(x) for x in src_entr.real_out_additional_tx_keys + ] + + """ + Calculates `derivation = Ra`, private spend key `x = H(Ra||i) + b` to be able + to spend the UTXO; and key image `I = x*H(P||i)` + """ + xi, ki, di = monero.generate_tx_spend_and_key_image_and_derivation( + state.creds, + state.subaddresses, + out_key, + tx_key, + additional_keys, + src_entr.real_output_in_tx_index, + ) + state.mem_trace(1, True) + + # Construct tx.vin + # If multisig is used then ki in vini should be src_entr.multisig_kLRki.ki + vini = TxinToKey(amount=src_entr.amount, k_image=crypto.encodepoint(ki)) + vini.key_offsets = _absolute_output_offsets_to_relative( + [x.idx for x in src_entr.outputs] + ) + + if src_entr.rct: + vini.amount = 0 + + """ + Serialize `vini` with variant code for TxinToKey (prefix = TxinToKey.VARIANT_CODE). + The binary `vini_bin` is later sent to step 4 and 9 with its hmac, + where it is checked and directly used. + """ + vini_bin = serialize.dump_msg(vini, preallocate=64, prefix=b"\x02") + state.mem_trace(2, True) + + # HMAC(T_in,i || vin_i) + hmac_vini = await offloading_keys.gen_hmac_vini( + state.key_hmac, src_entr, vini_bin, state.current_input_index + ) + state.mem_trace(3, True) + + # PseudoOuts commitment, alphas stored to state + pseudo_out = None + pseudo_out_hmac = None + alpha_enc = None + + if state.rct_type == RctType.Simple: + alpha, pseudo_out = _gen_commitment(state, src_entr.amount) + pseudo_out = crypto.encodepoint(pseudo_out) + + # In full version the alpha is encrypted and passed back for storage + pseudo_out_hmac = crypto.compute_hmac( + offloading_keys.hmac_key_txin_comm( + state.key_hmac, state.current_input_index + ), + pseudo_out, + ) + alpha_enc = chacha_poly.encrypt_pack( + offloading_keys.enc_key_txin_alpha( + state.key_enc, state.current_input_index + ), + crypto.encodeint(alpha), + ) + + spend_enc = chacha_poly.encrypt_pack( + offloading_keys.enc_key_spend(state.key_enc, state.current_input_index), + crypto.encodeint(xi), + ) + + if state.current_input_index + 1 == state.input_count: + """ + When we finish the inputs processing, we no longer need + the precomputed subaddresses so we clear them to save memory. + """ + state.subaddresses = None + + return MoneroTransactionSetInputAck( + vini=vini_bin, + vini_hmac=hmac_vini, + pseudo_out=pseudo_out, + pseudo_out_hmac=pseudo_out_hmac, + pseudo_out_alpha=alpha_enc, + spend_key=spend_enc, + ) + + +def _gen_commitment(state: State, in_amount): + """ + Computes Pedersen commitment - pseudo outs + Here is slight deviation from the original protocol. + We want that \\sum Alpha = \\sum A_{i,j} where A_{i,j} is a mask from range proof for output i, bit j. + + Previously this was computed in such a way that Alpha_{last} = \\sum A{i,j} - \\sum_{i=0}^{last-1} Alpha + But we would prefer to compute commitment before range proofs so alphas are generated completely randomly + and the last A mask is computed in this special way. + Returns pseudo_out + """ + alpha = crypto.random_scalar() + state.sumpouts_alphas = crypto.sc_add(state.sumpouts_alphas, alpha) + return alpha, crypto.gen_commitment(alpha, in_amount) + + +def _absolute_output_offsets_to_relative(off): + """ + Mixin outputs are specified in relative numbers. First index is absolute + and the rest is an offset of a previous one. + Helps with varint encoding size. + + Example: absolute {7,11,15,20} is converted to {7,4,4,5} + """ + if len(off) == 0: + return off + off.sort() + for i in range(len(off) - 1, 0, -1): + off[i] -= off[i - 1] + return off diff --git a/src/apps/monero/signing/step_03_inputs_permutation.py b/src/apps/monero/signing/step_03_inputs_permutation.py new file mode 100644 index 000000000..cbd19c36b --- /dev/null +++ b/src/apps/monero/signing/step_03_inputs_permutation.py @@ -0,0 +1,42 @@ +""" +Inputs in transaction need to be sorted by their key image, otherwise the +transaction is rejected. The sorting is done on host and then sent here in +the MoneroTransactionInputsPermutationRequest message. + +The message contains just a simple array where each item stands for the +input's position in the transaction. + +We do not do the actual sorting here (we do not store the complete input +data anyway, so we can't) we just save the array to the state and use +it later when needed. +""" + +from .state import State + +from apps.monero.layout.confirms import transaction_step + + +async def tsx_inputs_permutation(state: State, permutation: list): + from trezor.messages.MoneroTransactionInputsPermutationAck import ( + MoneroTransactionInputsPermutationAck, + ) + + await transaction_step(state.ctx, state.STEP_PERM) + + """ + Set permutation on the inputs - sorted by key image on host. + """ + if len(permutation) != state.input_count: + raise ValueError("Invalid permutation size") + _check_permutation(permutation) + + state.source_permutation = permutation + state.current_input_index = -1 + + return MoneroTransactionInputsPermutationAck() + + +def _check_permutation(permutation): + for n in range(len(permutation)): + if n not in permutation: + raise ValueError("Invalid permutation") diff --git a/src/apps/monero/signing/step_04_input_vini.py b/src/apps/monero/signing/step_04_input_vini.py new file mode 100644 index 000000000..ace9fa282 --- /dev/null +++ b/src/apps/monero/signing/step_04_input_vini.py @@ -0,0 +1,73 @@ +""" +This step successively hashes the inputs in the order +received in the previous step. +Also hashes `pseudo_out` to the final_message. +""" + +from .state import State + +from apps.monero.layout import confirms +from apps.monero.signing import RctType, RsigType, offloading_keys +from apps.monero.xmr import crypto + +if False: + from trezor.messages.MoneroTransactionSourceEntry import ( + MoneroTransactionSourceEntry, + ) + + +async def input_vini( + state: State, + src_entr: MoneroTransactionSourceEntry, + vini_bin: bytes, + vini_hmac: bytes, + pseudo_out: bytes, + pseudo_out_hmac: bytes, +): + from trezor.messages.MoneroTransactionInputViniAck import ( + MoneroTransactionInputViniAck, + ) + + await confirms.transaction_step( + state.ctx, state.STEP_VINI, state.current_input_index + 1, state.input_count + ) + if state.current_input_index >= state.input_count: + raise ValueError("Too many inputs") + + state.current_input_index += 1 + + # HMAC(T_in,i || vin_i) + hmac_vini_comp = await offloading_keys.gen_hmac_vini( + state.key_hmac, + src_entr, + vini_bin, + state.source_permutation[state.current_input_index], + ) + if not crypto.ct_equals(hmac_vini_comp, vini_hmac): + raise ValueError("HMAC is not correct") + + """ + Incremental hasing of tx.vin[i] + """ + state.tx_prefix_hasher.buffer(vini_bin) + + # in monero version >= 8 pseudo outs were moved to a different place + # bulletproofs imply version >= 8 + if state.rct_type == RctType.Simple and state.rsig_type != RsigType.Bulletproof: + _hash_vini_pseudo_out(state, pseudo_out, pseudo_out_hmac) + + return MoneroTransactionInputViniAck() + + +def _hash_vini_pseudo_out(state: State, pseudo_out: bytes, pseudo_out_hmac: bytes): + """ + Incremental hasing of pseudo output. Only applicable for simple rct. + """ + idx = state.source_permutation[state.current_input_index] + pseudo_out_hmac_comp = crypto.compute_hmac( + offloading_keys.hmac_key_txin_comm(state.key_hmac, idx), pseudo_out + ) + if not crypto.ct_equals(pseudo_out_hmac, pseudo_out_hmac_comp): + raise ValueError("HMAC invalid for pseudo outs") + + state.full_message_hasher.set_pseudo_out(pseudo_out) diff --git a/src/apps/monero/signing/step_05_all_inputs_set.py b/src/apps/monero/signing/step_05_all_inputs_set.py new file mode 100644 index 000000000..00a9ede89 --- /dev/null +++ b/src/apps/monero/signing/step_05_all_inputs_set.py @@ -0,0 +1,58 @@ +""" +All inputs set. Defining range signature parameters. +If in the applicable offloading mode, generate commitment masks. +""" + +from trezor import utils + +from .state import State + +from apps.monero.layout import confirms +from apps.monero.signing import RctType +from apps.monero.xmr import crypto + + +async def all_inputs_set(state: State): + state.mem_trace(0) + + await confirms.transaction_step(state.ctx, state.STEP_ALL_IN) + + from trezor.messages.MoneroTransactionAllInputsSetAck import ( + MoneroTransactionAllInputsSetAck, + ) + from trezor.messages.MoneroTransactionRsigData import MoneroTransactionRsigData + + # Generate random commitment masks to be used in range proofs. + # If SimpleRCT is used the sum of the masks must match the input masks sum. + state.sumout = crypto.sc_init(0) + for i in range(state.output_count): + cur_mask = crypto.new_scalar() # new mask for each output + is_last = i + 1 == state.output_count + if is_last and state.rct_type == RctType.Simple: + # in SimpleRCT the last mask needs to be calculated as an offset of the sum + crypto.sc_sub_into(cur_mask, state.sumpouts_alphas, state.sumout) + else: + crypto.random_scalar(cur_mask) + + crypto.sc_add_into(state.sumout, state.sumout, cur_mask) + state.output_masks.append(cur_mask) + + if state.rct_type == RctType.Simple: + utils.ensure( + crypto.sc_eq(state.sumout, state.sumpouts_alphas), "Invalid masks sum" + ) # sum check + state.sumout = crypto.sc_init(0) + + rsig_data = MoneroTransactionRsigData() + resp = MoneroTransactionAllInputsSetAck(rsig_data=rsig_data) + + # If range proofs are being offloaded, we send the masks to the host, which uses them + # to create the range proof. If not, we do not send any and we use them in the following step. + if state.rsig_offload: + tmp_buff = bytearray(32) + rsig_data.mask = bytearray(32 * state.output_count) + for i in range(state.output_count): + crypto.encodeint_into(tmp_buff, state.output_masks[i]) + utils.memcpy(rsig_data.mask, 32 * i, tmp_buff, 0, 32) + + return resp diff --git a/src/apps/monero/signing/step_06_set_output.py b/src/apps/monero/signing/step_06_set_output.py new file mode 100644 index 000000000..142d1678f --- /dev/null +++ b/src/apps/monero/signing/step_06_set_output.py @@ -0,0 +1,440 @@ +""" +Output destinations are streamed one by one. +Computes destination one-time address, amount key, range proof + HMAC, out_pk, ecdh_info. +""" +import gc + +from trezor import utils + +from .state import State + +from apps.monero import signing +from apps.monero.layout import confirms +from apps.monero.signing import RsigType, offloading_keys +from apps.monero.xmr import crypto, serialize + + +async def set_output(state: State, dst_entr, dst_entr_hmac, rsig_data): + state.mem_trace(0, True) + mods = utils.unimport_begin() + + await confirms.transaction_step( + state.ctx, state.STEP_OUT, state.current_output_index + 1, state.output_count + ) + state.mem_trace(1) + + state.current_output_index += 1 + state.mem_trace(2, True) + await _validate(state, dst_entr, dst_entr_hmac) + + # First output - we include the size of the container into the tx prefix hasher + if state.current_output_index == 0: + state.tx_prefix_hasher.uvarint(state.output_count) + state.mem_trace(4, True) + + state.output_amounts.append(dst_entr.amount) + state.summary_outs_money += dst_entr.amount + utils.unimport_end(mods) + state.mem_trace(5, True) + + # Range proof first, memory intensive + rsig, mask = _range_proof(state, dst_entr.amount, rsig_data) + utils.unimport_end(mods) + state.mem_trace(6, True) + + # additional tx key if applicable + additional_txkey_priv = _set_out_additional_keys(state, dst_entr) + # derivation = a*R or r*A or s*C + derivation = _set_out_derivation(state, dst_entr, additional_txkey_priv) + # amount key = H_s(derivation || i) + amount_key = crypto.derivation_to_scalar(derivation, state.current_output_index) + # one-time destination address P = H_s(derivation || i)*G + B + tx_out_key = crypto.derive_public_key( + derivation, + state.current_output_index, + crypto.decodepoint(dst_entr.addr.spend_public_key), + ) + del (derivation, additional_txkey_priv) + state.mem_trace(7, True) + + # Tx header prefix hashing, hmac dst_entr + tx_out_bin, hmac_vouti = await _set_out_tx_out(state, dst_entr, tx_out_key) + state.mem_trace(11, True) + + out_pk_dest, out_pk_commitment, ecdh_info_bin = _get_ecdh_info_and_out_pk( + state=state, + tx_out_key=tx_out_key, + amount=dst_entr.amount, + mask=mask, + amount_key=amount_key, + ) + del (dst_entr, mask, amount_key, tx_out_key) + state.mem_trace(12, True) + + # Incremental hashing of the ECDH info. + # RctSigBase allows to hash only one of the (ecdh, out_pk) as they are serialized + # as whole vectors. We choose to hash ECDH first, because it saves state space. + state.full_message_hasher.set_ecdh(ecdh_info_bin) + state.mem_trace(13, True) + + # output_pk_commitment is stored to the state as it is used during the signature and hashed to the + # RctSigBase later. No need to store amount, it was already stored. + state.output_pk_commitments.append(out_pk_commitment) + state.mem_trace(14, True) + + from trezor.messages.MoneroTransactionSetOutputAck import ( + MoneroTransactionSetOutputAck, + ) + + out_pk_bin = bytearray(64) + utils.memcpy(out_pk_bin, 0, out_pk_dest, 0, 32) + utils.memcpy(out_pk_bin, 32, out_pk_commitment, 0, 32) + + return MoneroTransactionSetOutputAck( + tx_out=tx_out_bin, + vouti_hmac=hmac_vouti, + rsig_data=_return_rsig_data(rsig), + out_pk=out_pk_bin, + ecdh_info=ecdh_info_bin, + ) + + +async def _validate(state: State, dst_entr, dst_entr_hmac): + if state.current_input_index + 1 != state.input_count: + raise ValueError("Invalid number of inputs") + if state.current_output_index >= state.output_count: + raise ValueError("Invalid output index") + if dst_entr.amount <= 0: + raise ValueError("Destination with wrong amount: %s" % dst_entr.amount) + + # HMAC check of the destination + dst_entr_hmac_computed = await offloading_keys.gen_hmac_tsxdest( + state.key_hmac, dst_entr, state.current_output_index + ) + if not crypto.ct_equals(dst_entr_hmac, dst_entr_hmac_computed): + raise ValueError("HMAC invalid") + del (dst_entr_hmac, dst_entr_hmac_computed) + state.mem_trace(3, True) + + +async def _set_out_tx_out(state: State, dst_entr, tx_out_key): + """ + Manually serializes TxOut(0, TxoutToKey(key)) and calculates hmac. + """ + tx_out_bin = bytearray(34) + tx_out_bin[0] = 0 # amount varint + tx_out_bin[1] = 2 # variant code TxoutToKey + crypto.encodepoint_into(tx_out_bin, tx_out_key, 2) + state.mem_trace(8) + + # Tx header prefix hashing + state.tx_prefix_hasher.buffer(tx_out_bin) + state.mem_trace(9, True) + + # Hmac dst_entr + hmac_vouti = await offloading_keys.gen_hmac_vouti( + state.key_hmac, dst_entr, tx_out_bin, state.current_output_index + ) + state.mem_trace(10, True) + return tx_out_bin, hmac_vouti + + +def _range_proof(state, amount, rsig_data): + """ + Computes rangeproof + In order to optimize incremental transaction build, the mask computation is changed compared + to the official Monero code. In the official code, the input pedersen commitments are computed + after range proof in such a way summed masks for commitments (alpha) and rangeproofs (ai) are equal. + + In order to save roundtrips we compute commitments randomly and then for the last rangeproof + a[63] = (\\sum_{i=0}^{num_inp}alpha_i - \\sum_{i=0}^{num_outs-1} amasks_i) - \\sum_{i=0}^{62}a_i + + The range proof is incrementally hashed to the final_message. + """ + from apps.monero.xmr import range_signatures + + mask = state.output_masks[state.current_output_index] + provided_rsig = None + if rsig_data and rsig_data.rsig and len(rsig_data.rsig) > 0: + provided_rsig = rsig_data.rsig + if not state.rsig_offload and provided_rsig: + raise signing.Error("Provided unexpected rsig") + + # Batching + bidx = _get_rsig_batch(state, state.current_output_index) + batch_size = state.rsig_grouping[bidx] + last_in_batch = _is_last_in_batch(state, state.current_output_index, bidx) + if state.rsig_offload and provided_rsig and not last_in_batch: + raise signing.Error("Provided rsig too early") + if state.rsig_offload and last_in_batch and not provided_rsig: + raise signing.Error("Rsig expected, not provided") + + # Batch not finished, skip range sig generation now + if not last_in_batch: + return None, mask + + # Rangeproof + # Pedersen commitment on the value, mask from the commitment, range signature. + C, rsig = None, None + + state.mem_trace("pre-rproof" if __debug__ else None, collect=True) + if state.rsig_type == RsigType.Bulletproof and not state.rsig_offload: + """Bulletproof calculation in trezor""" + rsig = range_signatures.prove_range_bp_batch( + state.output_amounts, state.output_masks + ) + state.mem_trace("post-bp" if __debug__ else None, collect=True) + + # Incremental BP hashing + # BP is hashed with raw=False as hash does not contain L, R + # array sizes compared to the serialized bulletproof format + # thus direct serialization cannot be used. + state.full_message_hasher.rsig_val(rsig, True, raw=False) + state.mem_trace("post-bp-hash" if __debug__ else None, collect=True) + + rsig = _dump_rsig_bp(rsig) + state.mem_trace( + "post-bp-ser, size: %s" % len(rsig) if __debug__ else None, collect=True + ) + + elif state.rsig_type == RsigType.Borromean and not state.rsig_offload: + """Borromean calculation in trezor""" + C, mask, rsig = range_signatures.prove_range_borromean(amount, mask) + del range_signatures + + # Incremental hashing + state.full_message_hasher.rsig_val(rsig, False, raw=True) + _check_out_commitment(state, amount, mask, C) + + elif state.rsig_type == RsigType.Bulletproof and state.rsig_offload: + """Bulletproof calculated on host, verify in trezor""" + from apps.monero.xmr.serialize_messages.tx_rsig_bulletproof import Bulletproof + + # TODO this should be tested + # last_in_batch = True (see above) so this is fine + masks = state.output_masks[ + 1 + state.current_output_index - batch_size : 1 + state.current_output_index + ] + bp_obj = serialize.parse_msg(rsig_data.rsig, Bulletproof) + rsig_data.rsig = None + + # BP is hashed with raw=False as hash does not contain L, R + # array sizes compared to the serialized bulletproof format + # thus direct serialization cannot be used. + state.full_message_hasher.rsig_val(bp_obj, True, raw=False) + res = range_signatures.verify_bp(bp_obj, state.output_amounts, masks) + utils.ensure(res, "BP verification fail") + state.mem_trace("BP verified" if __debug__ else None, collect=True) + del (bp_obj, range_signatures) + + elif state.rsig_type == RsigType.Borromean and state.rsig_offload: + """Borromean offloading not supported""" + raise signing.Error( + "Unsupported rsig state (Borromean offloaded is not supported)" + ) + + else: + raise signing.Error("Unexpected rsig state") + + state.mem_trace("rproof" if __debug__ else None, collect=True) + if state.current_output_index + 1 == state.output_count: + # output masks and amounts are not needed anymore + state.output_amounts = [] + state.output_masks = [] + return rsig, mask + + +def _dump_rsig_bp(rsig): + if len(rsig.L) > 127: + raise ValueError("Too large") + + # Manual serialization as the generic purpose serialize.dump_msg_gc + # is more memory intensive which is not desired in the range proof section. + + # BP: V, A, S, T1, T2, taux, mu, L, R, a, b, t + # Commitment vector V is not serialized + # Vector size under 127 thus varint occupies 1 B + buff_size = 32 * (9 + 2 * (len(rsig.L))) + 2 + buff = bytearray(buff_size) + + utils.memcpy(buff, 0, rsig.A, 0, 32) + utils.memcpy(buff, 32, rsig.S, 0, 32) + utils.memcpy(buff, 32 * 2, rsig.T1, 0, 32) + utils.memcpy(buff, 32 * 3, rsig.T2, 0, 32) + utils.memcpy(buff, 32 * 4, rsig.taux, 0, 32) + utils.memcpy(buff, 32 * 5, rsig.mu, 0, 32) + + buff[32 * 6] = len(rsig.L) + offset = 32 * 6 + 1 + + for x in rsig.L: + utils.memcpy(buff, offset, x, 0, 32) + offset += 32 + + buff[offset] = len(rsig.R) + offset += 1 + + for x in rsig.R: + utils.memcpy(buff, offset, x, 0, 32) + offset += 32 + + utils.memcpy(buff, offset, rsig.a, 0, 32) + offset += 32 + utils.memcpy(buff, offset, rsig.b, 0, 32) + offset += 32 + utils.memcpy(buff, offset, rsig.t, 0, 32) + return buff + + +def _return_rsig_data(rsig): + if rsig is None: + return None + from trezor.messages.MoneroTransactionRsigData import MoneroTransactionRsigData + + if isinstance(rsig, list): + return MoneroTransactionRsigData(rsig_parts=rsig) + else: + return MoneroTransactionRsigData(rsig=rsig) + + +def _get_ecdh_info_and_out_pk(state: State, tx_out_key, amount, mask, amount_key): + """ + Calculates the Pedersen commitment C = aG + bH and returns it as CtKey. + Also encodes the two items - `mask` and `amount` - into ecdh info, + so the recipient is able to reconstruct the commitment. + """ + out_pk_dest = crypto.encodepoint(tx_out_key) + out_pk_commitment = crypto.encodepoint(crypto.gen_commitment(mask, amount)) + + state.sumout = crypto.sc_add(state.sumout, mask) + state.output_sk_masks.append(mask) + + # masking of mask and amount + ecdh_info = _ecdh_encode(mask, amount, crypto.encodeint(amount_key)) + + # Manual ECDH info serialization + ecdh_info_bin = bytearray(64) + utils.memcpy(ecdh_info_bin, 0, ecdh_info.mask, 0, 32) + utils.memcpy(ecdh_info_bin, 32, ecdh_info.amount, 0, 32) + gc.collect() + + return out_pk_dest, out_pk_commitment, ecdh_info_bin + + +def _ecdh_encode(mask, amount, amount_key): + """ + Output recipients need be able to reconstruct the amount commitments. + This means the blinding factor `mask` and `amount` must be communicated + to the receiver somehow. + + The mask and amount are stored as: + - mask = mask + Hs(amount_key) + - amount = amount + Hs(Hs(amount_key)) + Because the receiver can derive the `amount_key` they can + easily derive both mask and amount as well. + """ + from apps.monero.xmr.serialize_messages.tx_ecdh import EcdhTuple + + ecdh_info = EcdhTuple(mask=mask, amount=crypto.sc_init(amount)) + amount_key_hash_single = crypto.hash_to_scalar(amount_key) + amount_key_hash_double = crypto.hash_to_scalar( + crypto.encodeint(amount_key_hash_single) + ) + + ecdh_info.mask = crypto.sc_add(ecdh_info.mask, amount_key_hash_single) + ecdh_info.amount = crypto.sc_add(ecdh_info.amount, amount_key_hash_double) + return _recode_ecdh(ecdh_info) + + +def _recode_ecdh(ecdh_info): + """ + In-place ecdh_info tuple recoding + """ + ecdh_info.mask = crypto.encodeint(ecdh_info.mask) + ecdh_info.amount = crypto.encodeint(ecdh_info.amount) + return ecdh_info + + +def _set_out_additional_keys(state: State, dst_entr): + """ + If needed (decided in step 1), additional tx keys are calculated + for this particular output. + """ + if not state.need_additional_txkeys: + return None + + additional_txkey_priv = crypto.random_scalar() + + if dst_entr.is_subaddress: + # R=r*D + additional_txkey = crypto.scalarmult( + crypto.decodepoint(dst_entr.addr.spend_public_key), additional_txkey_priv + ) + else: + # R=r*G + additional_txkey = crypto.scalarmult_base(additional_txkey_priv) + + state.additional_tx_public_keys.append(crypto.encodepoint(additional_txkey)) + state.additional_tx_private_keys.append(additional_txkey_priv) + return additional_txkey_priv + + +def _set_out_derivation(state: State, dst_entr, additional_txkey_priv): + """ + Calculates derivation which is then used in the one-time address as + `P = H(derivation)*G + B`. + For change outputs the derivation equals a*R, because we know the + private view key. For others it is either `r*A` for traditional + addresses, or `s*C` for subaddresses. Both `r` and `s` are random + scalars, `s` is used in the context of subaddresses, but it's + basically the same thing. + """ + from apps.monero.xmr.addresses import addr_eq + + change_addr = state.change_address() + if change_addr and addr_eq(dst_entr.addr, change_addr): + # sending change to yourself; derivation = a*R + derivation = crypto.generate_key_derivation( + state.tx_pub, state.creds.view_key_private + ) + + else: + # sending to the recipient; derivation = r*A (or s*C in the subaddress scheme) + if dst_entr.is_subaddress and state.need_additional_txkeys: + deriv_priv = additional_txkey_priv + else: + deriv_priv = state.tx_priv + derivation = crypto.generate_key_derivation( + crypto.decodepoint(dst_entr.addr.view_public_key), deriv_priv + ) + return derivation + + +def _check_out_commitment(state: State, amount, mask, C): + utils.ensure( + crypto.point_eq( + C, + crypto.point_add(crypto.scalarmult_base(mask), crypto.scalarmult_h(amount)), + ), + "OutC fail", + ) + + +def _is_last_in_batch(state: State, idx, bidx): + """ + Returns true if the current output is last in the rsig batch + """ + batch_size = state.rsig_grouping[bidx] + return (idx - sum(state.rsig_grouping[:bidx])) + 1 == batch_size + + +def _get_rsig_batch(state: State, idx): + """ + Returns index of the current rsig batch + """ + r = 0 + c = 0 + while c < idx + 1: + c += state.rsig_grouping[r] + r += 1 + return r - 1 diff --git a/src/apps/monero/signing/step_07_all_outputs_set.py b/src/apps/monero/signing/step_07_all_outputs_set.py new file mode 100644 index 000000000..61213dd2e --- /dev/null +++ b/src/apps/monero/signing/step_07_all_outputs_set.py @@ -0,0 +1,171 @@ +""" +All outputs were set in this phase. This step serializes tx pub keys +into the tx extra field and then hashes it into the prefix hash. +The prefix hash is then complete. +""" + +import gc + +from trezor import utils + +from .state import State + +from apps.monero.layout import confirms +from apps.monero.signing import get_monero_rct_type +from apps.monero.xmr import crypto + + +async def all_outputs_set(state: State): + state.mem_trace(0) + + await confirms.transaction_step(state.ctx, state.STEP_ALL_OUT) + state.mem_trace(1) + + _validate(state) + state.mem_trace(2) + + _set_tx_extra(state) + # tx public keys not needed anymore + state.additional_tx_public_keys = None + state.tx_pub = None + gc.collect() + state.mem_trace(3) + + # Completes the transaction prefix hash by including extra + _set_tx_prefix(state) + extra_b = state.tx.extra + state.tx = None + gc.collect() + state.mem_trace(4) + + # In the multisig mode here needs to be a check whether currently computed + # transaction prefix matches expected transaction prefix sent in the + # init step. + + from trezor.messages.MoneroRingCtSig import MoneroRingCtSig + from trezor.messages.MoneroTransactionAllOutSetAck import ( + MoneroTransactionAllOutSetAck, + ) + + # Initializes RCTsig structure (fee, tx prefix hash, type) + rv_pb = MoneroRingCtSig( + txn_fee=state.fee, + message=state.tx_prefix_hash, + rv_type=get_monero_rct_type(state.rct_type, state.rsig_type), + ) + + _out_pk(state) + state.full_message_hasher.rctsig_base_done() + state.current_output_index = -1 + state.current_input_index = -1 + + state.full_message = state.full_message_hasher.get_digest() + state.full_message_hasher = None + + return MoneroTransactionAllOutSetAck( + extra=extra_b, + tx_prefix_hash=state.tx_prefix_hash, + rv=rv_pb, + full_message_hash=state.full_message, + ) + + +def _validate(state: State): + from apps.monero.signing import RctType + + if state.current_output_index + 1 != state.output_count: + raise ValueError("Invalid out num") + + # Test if \sum Alpha == \sum A + if state.rct_type == RctType.Simple: + utils.ensure(crypto.sc_eq(state.sumout, state.sumpouts_alphas)) + + # Fee test + if state.fee != (state.summary_inputs_money - state.summary_outs_money): + raise ValueError( + "Fee invalid %s vs %s, out: %s" + % ( + state.fee, + state.summary_inputs_money - state.summary_outs_money, + state.summary_outs_money, + ) + ) + + if state.summary_outs_money > state.summary_inputs_money: + raise ValueError( + "Transaction inputs money (%s) less than outputs money (%s)" + % (state.summary_inputs_money, state.summary_outs_money) + ) + + +def _set_tx_extra(state: State): + """ + Sets tx public keys into transaction's extra. + """ + state.tx.extra = _add_tx_pub_key_to_extra(state.tx.extra, state.tx_pub) + + if state.need_additional_txkeys: + state.tx.extra = _add_additional_tx_pub_keys_to_extra( + state.tx.extra, state.additional_tx_public_keys + ) + + +def _set_tx_prefix(state: State): + """ + Adds `extra` to the tx_prefix_hash, which is the last needed item, + so the tx_prefix_hash is now complete and can be incorporated + into full_message_hash. + """ + # Serializing "extra" type as BlobType. + # uvarint(len(extra)) || extra + state.tx_prefix_hasher.uvarint(len(state.tx.extra)) + state.tx_prefix_hasher.buffer(state.tx.extra) + + state.tx_prefix_hash = state.tx_prefix_hasher.get_digest() + state.tx_prefix_hasher = None + + state.full_message_hasher.set_message(state.tx_prefix_hash) + + +def _add_tx_pub_key_to_extra(tx_extra, pub_key): + """ + Adds public key to the extra + """ + to_add = bytearray(33) + to_add[0] = 1 # TX_EXTRA_TAG_PUBKEY + crypto.encodepoint_into(memoryview(to_add)[1:], pub_key) + return tx_extra + to_add + + +def _add_additional_tx_pub_keys_to_extra(tx_extra, pub_keys): + """ + Adds all additional tx public keys to the extra buffer + """ + from apps.monero.xmr.serialize import int_serialize + + # format: variant_tag (0x4) | array len varint | 32B | 32B | ... + num_keys = len(pub_keys) + len_size = int_serialize.uvarint_size(num_keys) + buffer = bytearray(1 + len_size + 32 * num_keys) + + buffer[0] = 0x4 # TX_EXTRA_TAG_ADDITIONAL_PUBKEYS + int_serialize.dump_uvarint_b_into(num_keys, buffer, 1) # uvarint(num_keys) + offset = 1 + len_size + + for idx in range(num_keys): + buffer[offset : offset + 32] = pub_keys[idx] + offset += 32 + + tx_extra += buffer + return tx_extra + + +def _out_pk(state: State): + """ + Hashes out_pk into the full message. + """ + if state.output_count != len(state.output_pk_commitments): + raise ValueError("Invalid number of ecdh") + + for out in state.output_pk_commitments: + state.full_message_hasher.set_out_pk_commitment(out) diff --git a/src/apps/monero/signing/step_09_sign_input.py b/src/apps/monero/signing/step_09_sign_input.py new file mode 100644 index 000000000..c9ea302e1 --- /dev/null +++ b/src/apps/monero/signing/step_09_sign_input.py @@ -0,0 +1,192 @@ +""" +Generates a MLSAG signature for one input. +""" + +import gc + +from trezor import utils + +from .state import State + +from apps.monero.layout import confirms +from apps.monero.signing import RctType +from apps.monero.xmr import crypto, serialize + +if False: + from trezor.messages.MoneroTransactionSourceEntry import ( + MoneroTransactionSourceEntry, + ) + + +async def sign_input( + state: State, + src_entr: MoneroTransactionSourceEntry, + vini_bin: bytes, + vini_hmac: bytes, + pseudo_out: bytes, + pseudo_out_hmac: bytes, + pseudo_out_alpha_enc: bytes, + spend_enc: bytes, +): + """ + :param state: transaction state + :param src_entr: Source entry + :param vini_bin: tx.vin[i] for the transaction. Contains key image, offsets, amount (usually zero) + :param vini_hmac: HMAC for the tx.vin[i] as returned from Trezor + :param pseudo_out: Pedersen commitment for the current input, uses pseudo_out_alpha + as a mask. Only applicable for RCTTypeSimple. + :param pseudo_out_hmac: HMAC for pseudo_out + :param pseudo_out_alpha_enc: alpha mask used in pseudo_out, only applicable for RCTTypeSimple. Encrypted. + :param spend_enc: one time address spending private key. Encrypted. + :return: Generated signature MGs[i] + """ + from apps.monero.signing import offloading_keys + + await confirms.transaction_step( + state.ctx, state.STEP_SIGN, state.current_input_index + 1, state.input_count + ) + + state.current_input_index += 1 + if state.current_input_index >= state.input_count: + raise ValueError("Invalid inputs count") + if state.rct_type == RctType.Simple and pseudo_out is None: + raise ValueError("SimpleRCT requires pseudo_out but none provided") + if state.rct_type == RctType.Simple and pseudo_out_alpha_enc is None: + raise ValueError("SimpleRCT requires pseudo_out's mask but none provided") + if state.current_input_index >= 1 and not state.rct_type == RctType.Simple: + raise ValueError("Two and more inputs must imply SimpleRCT") + + input_position = state.source_permutation[state.current_input_index] + + # Check input's HMAC + vini_hmac_comp = await offloading_keys.gen_hmac_vini( + state.key_hmac, src_entr, vini_bin, input_position + ) + if not crypto.ct_equals(vini_hmac_comp, vini_hmac): + raise ValueError("HMAC is not correct") + + gc.collect() + state.mem_trace(1) + + if state.rct_type == RctType.Simple: + # both pseudo_out and its mask were offloaded so we need to + # validate pseudo_out's HMAC and decrypt the alpha + pseudo_out_hmac_comp = crypto.compute_hmac( + offloading_keys.hmac_key_txin_comm(state.key_hmac, input_position), + pseudo_out, + ) + if not crypto.ct_equals(pseudo_out_hmac_comp, pseudo_out_hmac): + raise ValueError("HMAC is not correct") + + gc.collect() + state.mem_trace(2) + + from apps.monero.xmr.crypto import chacha_poly + + pseudo_out_alpha = crypto.decodeint( + chacha_poly.decrypt_pack( + offloading_keys.enc_key_txin_alpha(state.key_enc, input_position), + bytes(pseudo_out_alpha_enc), + ) + ) + pseudo_out_c = crypto.decodepoint(pseudo_out) + + # Spending secret + from apps.monero.xmr.crypto import chacha_poly + from apps.monero.xmr.serialize_messages.ct_keys import CtKey + + spend_key = crypto.decodeint( + chacha_poly.decrypt_pack( + offloading_keys.enc_key_spend(state.key_enc, input_position), + bytes(spend_enc), + ) + ) + + gc.collect() + state.mem_trace(3) + + # Basic setup, sanity check + index = src_entr.real_output + input_secret_key = CtKey(dest=spend_key, mask=crypto.decodeint(src_entr.mask)) + kLRki = None # for multisig: src_entr.multisig_kLRki + + # Private key correctness test + utils.ensure( + crypto.point_eq( + crypto.decodepoint(src_entr.outputs[src_entr.real_output].key.dest), + crypto.scalarmult_base(input_secret_key.dest), + ), + "Real source entry's destination does not equal spend key's", + ) + utils.ensure( + crypto.point_eq( + crypto.decodepoint(src_entr.outputs[src_entr.real_output].key.commitment), + crypto.gen_commitment(input_secret_key.mask, src_entr.amount), + ), + "Real source entry's mask does not equal spend key's", + ) + + gc.collect() + state.mem_trace(4) + + from apps.monero.xmr import mlsag + + if state.rct_type == RctType.Simple: + ring_pubkeys = [x.key for x in src_entr.outputs] + mg = mlsag.generate_mlsag_simple( + state.full_message, + ring_pubkeys, + input_secret_key, + pseudo_out_alpha, + pseudo_out_c, + kLRki, + index, + ) + + else: + # Full RingCt, only one input + txn_fee_key = crypto.scalarmult_h(state.fee) + ring_pubkeys = [[x.key] for x in src_entr.outputs] + mg = mlsag.generate_mlsag_full( + state.full_message, + ring_pubkeys, + [input_secret_key], + state.output_sk_masks, + state.output_pk_commitments, + kLRki, + index, + txn_fee_key, + ) + + gc.collect() + state.mem_trace(5) + + # Encode + mgs = _recode_msg([mg]) + + gc.collect() + state.mem_trace(6) + + from trezor.messages.MoneroTransactionSignInputAck import ( + MoneroTransactionSignInputAck, + ) + + return MoneroTransactionSignInputAck( + signature=serialize.dump_msg_gc(mgs[0], preallocate=488) + ) + + +def _recode_msg(mgs): + """ + Recodes MGs signatures from raw forms to bytearrays so it works with serialization + """ + for idx in range(len(mgs)): + mgs[idx].cc = crypto.encodeint(mgs[idx].cc) + if hasattr(mgs[idx], "II") and mgs[idx].II: + for i in range(len(mgs[idx].II)): + mgs[idx].II[i] = crypto.encodepoint(mgs[idx].II[i]) + + for i in range(len(mgs[idx].ss)): + for j in range(len(mgs[idx].ss[i])): + mgs[idx].ss[i][j] = crypto.encodeint(mgs[idx].ss[i][j]) + return mgs diff --git a/src/apps/monero/signing/step_10_sign_final.py b/src/apps/monero/signing/step_10_sign_final.py new file mode 100644 index 000000000..441f9f669 --- /dev/null +++ b/src/apps/monero/signing/step_10_sign_final.py @@ -0,0 +1,43 @@ +""" +Final message, signatures were already returned in the previous step. + +Here we return private tx keys in encrypted form using transaction specific key, +derived from tx hash and the private spend key. The key is deterministic, +so we can recover it just from the transaction and the spend key. + +The private tx keys are used in other numerous Monero features. +""" + +from trezor.messages.MoneroTransactionFinalAck import MoneroTransactionFinalAck + +from .state import State + +from apps.monero.xmr import crypto +from apps.monero.xmr.crypto import chacha_poly + + +async def final_msg(state: State): + tx_key, salt, rand_mult = _compute_tx_key( + state.creds.spend_key_private, state.tx_prefix_hash + ) + + key_buff = crypto.encodeint(state.tx_priv) + b"".join( + [crypto.encodeint(x) for x in state.additional_tx_private_keys] + ) + tx_enc_keys = chacha_poly.encrypt_pack(tx_key, key_buff) + + return MoneroTransactionFinalAck( + cout_key=None, salt=salt, rand_mult=rand_mult, tx_enc_keys=tx_enc_keys + ) + + +def _compute_tx_key(spend_key_private, tx_prefix_hash): + salt = crypto.random_bytes(32) + + rand_mult_num = crypto.random_scalar() + rand_mult = crypto.encodeint(rand_mult_num) + + rand_inp = crypto.sc_add(spend_key_private, rand_mult_num) + passwd = crypto.keccak_2hash(crypto.encodeint(rand_inp) + tx_prefix_hash) + tx_key = crypto.compute_hmac(salt, passwd) + return tx_key, salt, rand_mult diff --git a/src/apps/monero/xmr/__init__.py b/src/apps/monero/xmr/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/apps/monero/xmr/addresses.py b/src/apps/monero/xmr/addresses.py new file mode 100644 index 000000000..be4671879 --- /dev/null +++ b/src/apps/monero/xmr/addresses.py @@ -0,0 +1,86 @@ +from trezor.crypto import monero as tcry + +from apps.monero.xmr.networks import NetworkTypes, net_version + + +def addr_to_hash(addr): + """ + Creates hashable address representation + """ + return bytes(addr.spend_public_key + addr.view_public_key) + + +def encode_addr(version, spend_pub, view_pub, payment_id=None): + """ + Encodes public keys as versions + """ + buf = spend_pub + view_pub + if payment_id: + buf += bytes(payment_id) + return tcry.xmr_base58_addr_encode_check(ord(version), bytes(buf)) + + +def decode_addr(addr): + """ + Given address, get version and public spend and view keys. + """ + d, version = tcry.xmr_base58_addr_decode_check(bytes(addr)) + pub_spend_key = d[0:32] + pub_view_key = d[32:64] + return version, pub_spend_key, pub_view_key + + +def public_addr_encode(pub_addr, is_sub=False, net=NetworkTypes.MAINNET): + """ + Encodes public address to Monero address + """ + net_ver = net_version(net, is_sub) + return encode_addr(net_ver, pub_addr.spend_public_key, pub_addr.view_public_key) + + +def classify_subaddresses(tx_dests, change_addr): + """ + Classify destination subaddresses + """ + num_stdaddresses = 0 + num_subaddresses = 0 + single_dest_subaddress = None + addr_set = set() + for tx in tx_dests: + if change_addr and addr_eq(change_addr, tx.addr): + continue + addr_hashed = addr_to_hash(tx.addr) + if addr_hashed in addr_set: + continue + addr_set.add(addr_hashed) + if tx.is_subaddress: + num_subaddresses += 1 + single_dest_subaddress = tx.addr + else: + num_stdaddresses += 1 + return num_stdaddresses, num_subaddresses, single_dest_subaddress + + +def addr_eq(a, b): + return ( + a.spend_public_key == b.spend_public_key + and a.view_public_key == b.view_public_key + ) + + +def get_change_addr_idx(outputs, change_dts): + """ + Returns ID of the change output from the change_dts and outputs + """ + if change_dts is None: + return None + + change_idx = None + for idx, dst in enumerate(outputs): + if ( + change_dts.amount + and change_dts.amount == dst.amount + and addr_eq(change_dts.addr, dst.addr) + ): + change_idx = idx + return change_idx diff --git a/src/apps/monero/xmr/bulletproof.py b/src/apps/monero/xmr/bulletproof.py new file mode 100644 index 000000000..2265e304f --- /dev/null +++ b/src/apps/monero/xmr/bulletproof.py @@ -0,0 +1,1583 @@ +import gc + +from trezor import utils +from trezor.utils import memcpy as _memcpy + +from apps.monero.xmr import crypto +from apps.monero.xmr.serialize.int_serialize import dump_uvarint_b_into, uvarint_size + +# Constants + +BP_LOG_N = 6 +BP_N = 64 # 1 << BP_LOG_N +BP_M = 16 # maximal number of bulletproofs + +ZERO = b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" +ONE = b"\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" +TWO = b"\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" +EIGHT = b"\x08\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" +INV_EIGHT = b"\x79\x2f\xdc\xe2\x29\xe5\x06\x61\xd0\xda\x1c\x7d\xb3\x9d\xd3\x07\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x06" +MINUS_ONE = b"\xec\xd3\xf5\x5c\x1a\x63\x12\x58\xd6\x9c\xf7\xa2\xde\xf9\xde\x14\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x10" +MINUS_INV_EIGHT = b"\x74\xa4\x19\x7a\xf0\x7d\x0b\xf7\x05\xc2\xda\x25\x2b\x5c\x0b\x0d\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0a" + +# Monero H point +XMR_H = b"\x8b\x65\x59\x70\x15\x37\x99\xaf\x2a\xea\xdc\x9f\xf1\xad\xd0\xea\x6c\x72\x51\xd5\x41\x54\xcf\xa9\x2c\x17\x3a\x0d\xd3\x9c\x1f\x94" +XMR_HP = crypto.xmr_H() + +# get_exponent(Gi[i], XMR_H, i * 2 + 1) +BP_GI_PRE = b"\x0b\x48\xbe\x50\xe4\x9c\xad\x13\xfb\x3e\x01\x4f\x3f\xa7\xd6\x8b\xac\xa7\xc8\xa9\x10\x83\xdc\x9c\x59\xb3\x79\xaa\xab\x21\x8f\x15\xdf\x01\xa5\xd6\x3b\x3e\x3a\x38\x38\x2a\xfb\xd7\xbc\x68\x5f\x34\x3d\x61\x92\xda\x16\xed\x4b\x45\x1f\x15\xfd\xda\xb1\x70\xe2\x2d\x73\x69\xc8\xd5\xa7\x45\x42\x3d\x26\x06\x23\xa1\xf7\x5f\xae\x1f\xb1\xf8\x1b\x16\x9d\x42\x2a\xcd\x85\x58\xe9\xd5\x74\x25\x48\xbd\x81\xc0\x7d\x2b\xd8\x77\x1e\xb4\xbd\x84\x15\x5d\x38\xd7\x05\x31\xfe\x66\x2b\x78\xf0\xc4\x4a\x9a\xea\xea\x2e\xd2\xd6\xf0\xeb\xe1\x08\x96\xc5\xc2\x2f\x00\x70\xeb\xf0\x55\xdf\xe8\xdc\x1c\xb2\x05\x42\xef\x29\x15\x1a\xa0\x77\x1e\x58\x1e\x68\xfe\x78\x18\xef\x42\x35\xc8\xdf\x1a\x32\xae\xce\xed\xef\xcb\xdf\x6d\x91\xd5\x24\x92\x9b\x84\x02\xa0\x26\xcb\x85\x74\xe0\xe3\xa3\x34\x2c\xe2\x11\xbc\xd9\x67\xbc\x14\xe7\xab\xda\x6c\x17\xc2\xf2\x2a\x38\x1b\x84\xc2\x49\x75\x78\x52\xe9\x9d\x62\xc4\x5f\x16\x0e\x89\x15\xec\x21\xd4\xc8\xa3\x83\x1d\x7c\x2f\x24\x58\x1e\xc9\xd1\x50\x13\xdf\xcc\xb5\xeb\xa6\x9d\xf6\x91\xa0\x80\x02\xb3\x3d\x4f\x2f\xb0\x6c\xa9\xf2\x9c\xfb\xc7\x0d\xb0\x23\xa4\x8e\x45\x35\xf5\x83\x8f\x5e\xa2\x7f\x70\x98\x0d\x11\xec\xd9\x35\xb4\x78\x25\x8e\x2a\x4f\x10\x06\xb3\x2d\xa6\x38\x72\x92\x25\x9e\x69\xac\x0a\x82\x9e\xf3\x47\x69\x98\x96\x72\x8c\x0c\xc0\xca\xdc\x74\x6d\xae\x46\xfb\x31\x86\x4a\x59\xa5\xb9\xa1\x54\x9c\x77\xe4\xcf\x8a\xb8\xb2\x55\xa3\xa0\xae\xfa\xa4\xca\xd1\x25\xd2\x19\x94\x9c\x0a\xef\xf0\xc3\x56\x0a\xb1\x58\xed\x67\x17\x48\xa1\x75\x56\x41\x9e\xc9\x42\xe1\x6b\x90\x1d\xbb\x2f\xc6\xdf\x96\x60\x32\x4f\xcb\xcd\x6e\x40\xf2\x35\xd7\x5b\x76\x4f\xaf\xf6\x1c\x19\x05\x22\x2b\xaf\x87\xd5\x1d\x45\xf3\x55\x81\x38\xc8\x7c\xe5\x4c\x46\x4c\xc6\x40\xb9\x55\xe7\xfa\x33\x10\xf8\x3b\x13\xdd\x7b\x24\x73\x19\xe1\x3c\xe6\x19\x95\xbc\x77\x1e\xe1\xed\xe7\x36\x35\x99\xf0\x8f\xc5\xcf\xda\x89\x0e\xa8\x03\xe0\xec\xa7\x0a\x97\x70\x7e\x90\x56\x29\xa5\xe0\x6d\x18\x6a\x96\x4f\x32\x2f\xff\xba\xa7\xed\x2e\x78\x1d\x4d\x3f\xed\xe0\x74\x61\xf4\x4b\x2d\x98\xdb\xcc\x0c\xaa\x20\x55\x14\x6e\x13\xf5\x0e\xcf\x75\x49\x1d\xad\xd3\x6a\xd2\xba\xac\x56\xbc\x08\x56\x2e\xc6\x6c\xe1\x10\xb5\x44\x83\x1d\xbd\x34\xc6\xc2\x52\x95\x81\x51\xc4\x9a\x73\x4c\x6e\x62\x5e\x42\x60\x8c\x00\x5e\x79\x7e\xdb\x6d\x0a\x89\x34\xb3\x24\xa0\xe4\xd3\x1c\xba\x01\x57\x83\x50\x1e\xcd\xfa\x7a\x8e\xba\xe3\xa6\xbf\xd3\x2e\x6d\x1a\x36\x14\xb1\x11\x83\xc8\x09\x80\xd4\x54\x6c\xc3\xee\x5d\xb4\x7b\xfe\x97\x05\xaa\x95\xe2\xda\x29\xf2\x28\x23\x03\x53\x91\x7e\x5d\x2b\x19\x32\xfe\x48\x2f\xbc\xfe\xd7\x13\x4d\x55\x6d\x0c\x27\xf6\xcc\x6b\xf3\x01\x5c\x06\x61\x16\x25\x73\x9d\x88\x9c\x57\x89\xfa\x75\xb3\xc8\x39\x69\xcb\x88\xb1\xdf\x01\xc0\xac\xa4\x70\xf6\x65\xeb\x71\x82\xe0\x72\xbc\xa8\x9b\xc6\x69\xff\xe5\xb0\x29\x6f\xe2\x13\x43\xa8\xc3\x27\xc8\xa8\x41\x75\x02\x85\x5a\x25\xcc\xb7\x5b\x2f\x8e\xea\xc5\xd1\xdb\x25\x04\x4b\x0a\xea\xd2\xcf\x77\x02\x1e\xd9\x4f\x79\xf3\x00\x1e\x7b\x8e\x9d\xb7\x31\x1d\xb2\x8c\x45\xc9\x0d\x80\xa1\xe3\xd5\xb2\x7b\x43\xf8\xe3\x80\x21\x4d\x6a\x2c\x40\x46\xc8\xd4\x0f\x52\x4d\x47\x83\x53\x20\x4d\x01\xa1\x7c\x4f\xb7\xb1\x8c\x2f\x48\x27\x01\x50\xdb\x67\xd4\xb0\xb9\xce\x87\x86\xe0\x3c\x95\x50\xc5\x47\xfb\x18\x02\x9e\xf1\x6e\x56\x29\xe9\xa1\xc6\x68\xe1\xaa\x79\xc7\x88\x73\x55\xf5\xf5\x1b\x0c\xbb\x1f\x08\x35\xe0\x4e\x7a\xcc\x53\xac\x55\xa3\x57\x41\x97\xb5\x4c\x5a\xaa\xad\x47\xbe\x24\xdb\xbc\x11\xc1\xbd\x3e\xeb\x62\x46\x54\x2d\x2f\x5a\xe5\xf4\x39\x8d\xd4\xa7\x60\x17\x03\xcb\xbf\xd5\x9b\xad\xdd\x3a\x7c\xe6\xe3\x75\xe7\xd9\x00\x50\xe2\x71\xb1\x3f\x13\x2d\xf8\x5e\x1c\x12\xbe\x54\xfe\x66\xde\x81\xf6\x8a\x1c\x8f\x69\x6f\x3e\x77\x3c\x7e\xef\x57\xac\x13\x89\xbd\x02\x80\xd5\x58\xea\x78\x62\xf0\x1b\x64\x1e\xc6\xda\x0e\xfe\xfb\xee\xd0\x50\x9c\x53\x8a\x8c\x36\x16\x68\x1d\x76\x1a\xe5\xc6\xf9\xd2\xaa\xde\xd7\x18\x90\xda\x24\x96\x15\x60\x43\x08\x21\x82\xec\x85\x9c\x3a\xe4\x86\x93\xf9\x13\x43\xd0\xa5\xf0\xec\xbb\x7d\xec\x9b\x97\x3b\xf2\x13\x67\x8a\x65\x3b\x0d\x9d\xf5\x10\x65\x2a\x23\xc0\xb8\x06\x53\x67\x92\x4a\x4c\xfc\x78\x60\x36\xc0\x66\xca\xa7\x38\x34\x9c\xf1\xcd\xa7\x0d\xbf\xa8\x5c\xce\xb4\xa0\x9f\x85\x03\x9b\x6f\x77\x27\x4f\xa6\xe2\x79\x35\xbf\x89\xae\x37\x3a\x3b\x5a\xda\x58\x24\xbd\x4b\x2a\xec\x22\x2a\xeb\xd7\xfe\xe7\xa4\x82\xe9\xc1\x33\x58\xea\xb2\x5f\x94\x22\x36\xf3\xf4\xb6\xeb\xaf\xe1\xc3\xee\xee\xf7\x93\x83\x66\x80\x66\x7c\x66\x94\x64\xc3\xd4\xa0\x84\x7d\xf3\x02\x4b\xd5\xdf\x2a\xa4\xaa\x4d\x19\xe5\x51\xed\xe9\x3d\xd0\x75\xf7\x95\x3a\xca\xe5\x3f\x0f\x9e\x8a\x38\x4e\x49\x6c\x52\x50\xb0\x7e\x76\x17\xe8\x9e\x28\xf9\x53\xd0\x96\xec\x29\x87\xeb\xd8\xf3\xe7\x4d\x93\x39\x63\xb8\x27\x73\xd3\x7a\xb1\xb7\xa3\x60\x1d\xc8\x97\x13\x34\x82\x5d\xd1\xd6\x7e\x4c\x48\x29\x72\x92\xa0\x7a\x40\x62\x96\x75\xb3\xe8\x78\x8e\xfc\x68\x73\x85\x30\x04\x81\xae\x69\x74\x06\xd2\x4e\xf8\x8e\xbf\x9c\xa1\x97\x2c\x1d\x52\x84\x78\x85\x8e\xad\x85\x78\x2e\xd4\x10\xeb\xbc\x1f\x3d\xa4\x8b\xa8\x07\x83\x62\x36\xaa\xc0\xa8\xf0\x8a\x50\x29\x11\x5d\x57\xe7\xef\x18\xcb\x27\xcc\xe8\xd2\xc1\x57\xa9\xf4\xf5\x61\x5d\xcc\x34\x8a\xea\xc8\x0d\x0f\x28\xdf\x33\xba\xbe\x39\xf6\xec\xbd\x19\xa4\xa6\xaf\xa8\x53\xaa\x4d\xa0\x3b\x6b\xd7\xa8\x06\x22\x9d\xed\x76\xd2\xc5\xb9\xde\x11\x76\xd5\x19\xa7\x93\x94\x67\x92\xb5\x41\x7e\xaf\x7d\x2d\x51\x26\x97\x7c\x57\x04\xfc\x0f\xcd\x8e\x1b\x2f\x58\x9b\x1d\x41\x8d\x19\xdd\x28\xf7\xe9\x4c\x51\xa1\x78\x2d\x32\x2e\x03\xcb\xa4\x78\x85\x74\x24\x49\x7b\x4a\x37\x3f\xde\x0f\xba\xe4\xcc\xd9\x38\xcb\xbf\xa0\xf4\xad\x23\x97\xee\xd7\xf7\x6d\xc3\xcd\xb6\xb0\x6a\x36\x66\x0c\x07\x75\xd3\x91\xca\x47\x21\x33\x41\xf6\x59\xe9\x01\x4f\x70\x28\x4e\xfa\xa5\xfa\xab\xa4\xbb\x83\x79\xce\x02\x04\xf5\xae\xdc\x28\x26\x8d\x82\x43\x8b\x5b\x88\x1f\xdf\x2d\xee\x4a\xd7\xd4\x0e\xd1\x3d\xad\x57\xca\x92\x96\x14\xa6\x3a\x00\xfe\x3a\x78\xf3\x3b\x30\xb6\xfd\x5f\x39\xe4\x43\x70\x36\xdc\xed\x8d\x87\xaf\x43\x28\x2f\x43\xfa\x14\xab\xaf\x6c\x84\x15\xfc\x05\xee\x1a\xd1\x71\xd8\x1f\xaa\x46\x7d\xdf\xe5\xe0\x2e\xb6\x89\x5e\x56\x88\xde\xc0\x48\xf6\x66\x0e\x3a\x2f\xd8\xbd\xec\x60\x2a\xf5\x95\x90\xec\x4c\x6e\xab\x83\x4c\xc0\xde\xc8\x62\x1e\xb5\x10\xfb\xa6\xf7\xad\xf4\x76\x93\xc2\xfd\x57\x4d\x82\x20\xa2\xe7\x0e\x73\xad\x68\xe4\xc3\x32\x48\x8e\xb8\xe7\x31\xfe\x60\x0d\x1e\x9f\x6b\x8f\x5c\xbf\x69\x9c\x18\xd0\x6b\xcd\x73\xb7\xcf\xce\xf4\x2e\x68\xaf\x7a\xe6\x7f\xea\x46\xe9\x46\xde\x6a\x61\xfa\xa4\x2c\x53\x5c\xfc\xae\xaa\xd5\x33\x4f\xc1\xa9\xba\xd4\xa5\x3e\x57\xd1\x1c\x6a\xcc\xfc\xef\xd2\xe8\xab\x44\xcb\x12\xfb\x2e\x66\x4f\xcb\xdf\x5c\x82\xb2\x12\x89\x62\x6a\xc2\xa1\x40\x2b\xde\x7a\x86\x9e\xb9\xed\x78\x07\x33\x8d\xd3\xb2\xba\x82\x37\x84\x5d\xb9\x67\x71\xcc\x98\x80\x08\x1a\xcf\x05\x3d\x9b\xd5\x1c\x01\x01\x94\x1c\x4c\x26\xf6\x6a\xa5\xdb\xad\x3f\x53\x54\x60\x85\x77\xf9\xe5\x1a\xfe\x74\x3a\xdd\x50\xf1\xb5\x90\x1b\xea\x7b\xeb\x5a\xe7\x80\xb6\xec\xe9\x77\xf6\x5b\x9c\x62\x8e\x1d\xce\x0a\xd1\xe0\x78\xc7\x46\xc2\xf3\x8d\x0e\x7f\x06\xb0\x88\x70\x8a\xe9\xac\x11\x17\xe3\xa3\x79\x99\xc1\xd7\x5a\x62\xe9\xc9\xe0\x17\x01\x8e\x08\x8a\xeb\xfb\x37\x8d\xe2\x9c\x78\x93\xac\xf1\x09\x42\x58\x4b\xf5\x58\xa2\xd0\x2d\x75\x1e\x34\xf3\xf4\x84\xb0\x01\xe3\x19\x24\xcc\x21\x84\x8b\xf0\xdd\xaf\x1f\x3d\x8a\x31\x00\x49\x73\x6f\xf7\xf0\x49\x29\x4d\x8a\x59\x5f\x2c\xa7\x26\x3a\x36\x13\x84\x0c\x14\xb3\x3e\xf4\x83\xcd\xca\x5b\xbb\x8a\x4c\x70\x04\xcc\xb8\xf6\x71\x56\x26\x7e\xe3\x5f\x28\x0d\xb1\x26\x45\xde\x8e\x55\x2a\x93\x12\xdf\x57\x69\xa0\x30\xa6\xb4\x6d\x80\xdb\x2e\x6c\x06\xb3\xc7\x6c\x1a\xda\x42\x37\x3b\x29\xa0\x59\x1f\x39\x85\x67\x49\xdf\xdf\xb2\x66\x81\x16\x6a\x28\x6f\xb4\xf2\x09\x7a\x3b\x6f\x8f\xeb\xdb\xe4\x41\x3b\x67\xb5\x58\x68\x9c\x2e\x7c\x1d\x6d\x64\x08\xf4\x6a\x60\x94\xc7\x4b\x22\x81\xe7\x96\xe1\xd9\x00\xcc\x83\x53\x37\xa3\x1b\x53\x50\xca\xa9\xc4\x44\xc6\x70\xf7\x8f\x86\x6e\x03\xef\x6e\xc2\xcb\xcb\xc1\x79\x97\x41\x45\xb2\x39\xb9\x09\x12\xbb\xee\xf8\xf5\x76\x96\x1b\x5e\xfc\x69\x64\x1f\x7a\x71\x51\x70\x87\x75\xb6\x7c\x9e\x65\xed\x9b\xb9\xf5\xa8\x7b\xb7\x90\xda\x20\x35\x57\xbe\xd2\x67\x40\x55\xe8\xa6\xab\x36\x46\xc4\xe1\xa8\x45\xea\x53\xd8\x61\x4a\xe4\x90\x06\x5d\xef\x75\x76\x15\xa2\x65\xf2\xab\x98\x38\x80\x29\xae\xc3\xaf\xb5\xcc\xa3\xa6\x66\xab\x29\xb6\xd2\xc0\x02\x97\x9c\x63\x6a\x3b\x41\xb8\x83\x7a\x43\x2a\x81\xd6\xdb\x55\xcf\x40\x6b\x1f\x58\x42\xb0\xa8\x87\xfe\x6b\x2b\xd8\x8e\x46\x29\x8e\xd3\xec\xc3\x87\x4c\x98\x37\x73\x46\x33\x1f\xde\x7a\x2f\xf7\xf1\x04\x26\x5b\xbd\x2d\x02\x74\xc0\x33\xc7\x58\x38\x51\x00\x1d\xcd\xb3\xde\xd9\x0a\x9c\x09\x77\xc1\xf8\x6d\x58\x46\x47\x55\x73\x30\xce\xe5\x0a\x53\xbb\x15\xab\x2b\x5a\x8d\x8a\x2b\x5f\xb2\x9f\xfd\xa0\xe1\x54\xb2\x63\x67\xe5\xba\x1c\x67\xa8\x79\xdd\xdb\x61\xd4\x08\x67\xb6\xcd\xf5\xe3\x8e\x0f\xcb\xdb\x9a\x92\x5f\x62\x2c\x7d\xe9\x34\xa3\x08\x23\x90\x67\xda\x11\x65\xdc\x9b\xa8\x91\xfd\x29\xbb\x9d\x9d\xff\x2c\x46\xc6\x0f\x95\x39\x0d\x3d\xda\x52\xaf\x9e\xf6\x4f\xf6\x3d\x2d\xf7\x22\x25\x38\x3b\x92\xbe\x1b\xa4\x8b\x17\x40\x56\x78\xd2\x24\x35\x65\xbb\x58\x86\x9b\xc4\xb2\xb8\x79\xcc\xc8\x99\xd2\xf9\x36\xc2\xb8\x19\x5f\xd4\x74\x05\xd8\x6f\xfb\x46\x97\x55\xb7\x72\xe4\xb5\xfb\xe7\xe4\xa2\x93\xb5\xdb\x71\x00\x08\xc9\x00\x6d\xab\xd6\xa9\xab\xec\xdf\x60\x9a\x3c\x78\x9a\xcf\xe2\xf2\x33\xae\x14\x21\x93\x1e\xf6\x63\x34\xfe\x74\x3f\x48\x77\xbf\xcd\x3a\x63\x71\xfb\xe3\x78\x46\x5b\xd6\x3b\xf5\xda\xee\x92\x0b\x1a\x64\x13\xa4\x6c\x69\x69\x3f\x72\xfc\x87\x9f\xff\xe1\xa9\x17\x38\x08\x4d\xf8\x46\xbe\x95\x43\x89\x28\x93\x95\x83\x44\xa0\x01\x58\x1e\xe3\x2c\x21\xa7\x28\xe8\x05\x04\x69\x95\x3b\xda\xb7\x83\xee\xae\xc5\x56\xa2\xa5\xab\x1c\xf3\xd0\x8c\x2b\x92\x33\xf5\xf3\x64\x1b\xcd\x76\x08\x7c\xf2\x95\x5b\x60\xae\x14\x1e\xd4\xca\xde\x54\x5d\xf6\x0a\x61\x9a\x55\x60\x71\xd2\xef\x3d\xea\x09\xe7\xbf\x54\x60\x50\xe5\xdf\x3a\x8f\xdf\x04\xf6\x63\x16\xf6\xda\xa4\xdb\xb8\xcb\xd2\x42\xd0\x6a\xae\xcb\x1f\x1d\xbb\x4d\xae\x42\xc1\x58\x9c\x32\x83\x02\x77\xd2\x09\xbe\x16\x0d\xaf\x62\x8b\x5b\x19\x92\xf2\xb1\xc5\xf0\xea\xcd\xa1\xe3\x8e\x00\x87\xb7\xca\x8d\xab\x93\x50\x15\x40\x48\x5a\xd1\x8f\xf8\x69\xab\x4f\x4b\x8b\x01\x6f\x45\x43\xc6\xa7\xa9\x36\xfd\x35\x1f\xa5\xcc\x62\x7d\x24\xeb\xbb\x30\xd8\x18\x9c\x42\x58\x86\x45\xcb\xf1\xff\x87\xee\xc9\xb7\xd7\x09\xc0\x5e\x64\x9d\x9b\x64\x01\xa9\x9b\x3a\xca\xb8\xaf\xc1\xc6\x9b\x85\xea\xaf\x3f\xf3\x68\x67\xde\x12\x50\xd9\x8a\xcf\x2d\xc6\xb2\xe1\xd7\x1c\xd3\x2e\xf0\xc7\x14\x02\xcd\x66\x5f\x03\x05\x3d\x22\xa9\xc5\x8d\xe0\x81\xc4\xec\x3a\x5e\xae\x8a\x20\xeb\xe8\x08\x56\x19\x78\xae\x45\x59\x43\xc6\x54\x2d\x03\x19\x86\x4c\xf7\x75\x4f\x64\x9a\xe4\x64\x69\x69\xbe\x65\x18\x5f\xb7\x59\xe6\x98\x27\x4b\x45\x6b\x74\x8b\x9a\x51\xf7\xfd\xef\xd4\xcd\x48\x60\x9c\xbe\x64\x60\xa3\x19\x76\x52\x9f\xe4\xb6\xfc\x64\x37\x45\xfd\x2d\xd3\x63\x1c\x8f\x75\x76\xbc\x80\x52\xbd\x32\x90\x5f\xbd\x42\x65\x94\x79\xbf\x79\xde\x2d\x3b\xe3\xcb\x19\xe3\xd4\xd0\x28\x83\x44\x9b\xe1\x37\x4b\x6e\x24\x3a\xaa\x87\x14\x94\x77\xc2\x5f\xf6\x82\xe5\xc8\x4a\x03\x03\xf1\x12\x31\x08\x6e\x4a\x57\x8f\xf5\x2b\x63\x06\x00\xfc\xbb\xe7\x1f\xb7\x19\xc3\x03\xb9\x16\x31\x05\x81\xb3\x2e\xf2\xdc\x89\xf4\x09\x4e\x0b\x37\x0b\x08\x3b\xd3\x21\x4a\x0f\x3f\x45\x04\xfc\xb3\x72\xd3\xad\x4a\xe2\x06\xbd\x8e\x34\xd1\xe2\x0f\xce\x72\xa8\xab\x40\xc1\xca\x4e\x45\x46\x35\xee\xbf\xe7\x2d\xea\x80\xfd\x95\xf9\xc6\x53\xe3\x2e\xde\x81\x6b\x43\x2b\xa4\x8e\x38\xbe\x49\x57\x0c\xf5\x55\x0b\x7e\x82\x67\xfc\x2f\x15\x64\xbc\x4b\x64\xef\xee\x9b\xe2\xb5\x81\xf9\x0e\xb9\x6e\xd0\x1d\x5f\x34\xe3\x42\x62\x31\x4c\x87\xf6\x02\xc9\xb1\xd3\x1c\xce\x77\x2c\x1a\x02\x3c\x2c\x02\x95\x28\xcf\x8d\x6f\x9e\xa1\xb3\x3b\x61\x26\xc4\xa8\xc2\x87\x72\x38\x8f\x38\x2a\x7a\x87\x56\xe1\xe2\xfc\xca\x54\x9b\x00\x85\x8d\xe2\x82\x9f\x87\xaa\x20\xeb\x08\x30\xa0\x04\xbd\x8a\xe7\xd5\xb3\xe3\x36\x31\xaa\x57\xcf\x07\x18\x26\x2c\x0e\x76\x80\x62\xeb\x36\xa0\x37\xf8\xf0\xab\x7c\xca\x79\x29\xf7\x8f\x03\xc9\x17\x27\x17\x4e\x53\xe6\x02\x47\x39\xbe\x73\x24\xfe\xc9\xb4\x50\xf4\xab\x82\xd1\x05\xbb\x3a\xcb\x69\x33\x27\x55\x8f\x80\x96\x89\xd4\x52\xe9\xa4\x6e\x3d\xe7\xda\xfa\xdf\x87\x92\x1f\x17\xfa\x24\xce\x87\x4d\x3e\xf2\xa5\x56\x10\x0a\x0c\x88\x4f\x84\x40\x48\x1f\x97\x4d\xeb\x9e\x17\x86\xe5\xfa\x8e\x14\x19\x1b\x6f\x79\x7d\xbc\x0c\x86\x42\xa1\xf7\x34\xbe\xd7\xcd\x72\xb2\x15\xd0\xb2\xb5\xb9\x87\xd2\x30\xdf\x63\x74\x2b\xbc\x4b\x10\x17\x6a\x99\x15\xcf\xc7\x49\x8d\x48\xb3\xfd\x30\x47\x57\xb2\xa1\xf5\x9e\x08\x97\x88\x18\xcf\x04\x3a\x6e\x9c\xdc\xe9\xbe\xaf\x09\xd6\x15\x30\xde\x06\x1f\xd7\xad\xc9\xd4\x7d\x23\xe7\x52\x88\xd0\x92\x91\xc6\x01\xba\xd4\xa7\x6d\xa8\xdf\xf2\x62\xa7\x23\x46\xc0\x77\xee\x25\x15\x62\x1d\xf9\x07\xaa\xbc\xfd\xbc\x14\x59\x04\xff\xfd\x9f\x8b\x04\xe2\x47\x65\xa0\xfb\xb0\xb4\x34\xab\xd3\xb7\x86\xd5\x60\xec\xea\xb5\xf9\xfc\xe4\x2a\x85\xec\x05\xe8\x85\xe1\xc6\x6b\xaf\xdd\x0f\x6e\xb7\xde\x3a\xb1\x45\x11\xda\x69\xc7\x72\x3d\xec\x82\x92\xbb\x3f\xf2\x01\xff\xe6\x0d\xf6\xe2\xc5\x24\x0d\x1a\xd7\xcc\x0c\x51\xa3\xdc\xb2\xf6\xaf\x1f\x30\xe3\xa9\x7a\xf1\xf3\x2f\x30\xda\x0c\x7d\x6c\xeb\x89\x71\x99\xc9\x36\x7d\x4d\xad\xd7\xa8\x43\xcd\x2f\xbd\xa1\x36\x14\x69\x2b\xa4\x92\xbb\xb3\x7c\xea\xe1\x51\xd5\x6f\x81\x53\x2f\xeb\x37\x8a\x18\x6c\xca\x0f\xc8\xf1\x36\x63\xce\x01\x7e\x2c\x7c\x21\x99\x76\xc7\x6b\x93\xfe\xc4\x0b\x52\x72\xb2\x59\x72\xba\x14\x67\x2e\x7b\x72\x00\x05\xdf\xfd\x08\xf7\x82\x18\x8c\xc1\x6e\x37\x1a\x3b\x9e\x98\x5f\x9f\x51\x90\x7c\x49\xb0\x17\xa4\xa8\x8a\x14\xe3\x2b\x67\x36\x4e\xf0\xde\x7a\x12\xed\x65\x88\x33\x4b\xdf\xbf\xe2\xe4\xea\xd9\x9e\x7b\xe5\xbe\x31\x93\x34\xe0\x75\x52\x9a\x84\x37\xc7\x24\xf6\x1e\x6c\xc5\x16\xd3\xb2\xaa\xb6\xb4\xf9\x35\x35\x1f\x01\xa8\x37\xb0\xb3\xe6\x4e\xac\x92\xee\xb8\xa1\x46\xaf\x9b\xe9\x76\x61\x02\x2c\xf9\xc6\x6b\x98\x3b\xb1\x55\x7c\x42\xd9\xbe\xd4\x12\x34\xe9\x93\x6b\x31\x86\x64\xd8\xcb\x24\x06\xe8\xa4\xdb\x92\x67\x8a\xe1\x2f\x94\x0f\x3c\xa3\x3b\xe2\x7b\x37\x5d\x29\x65\x05\xd3\x7d\xb1\xf3\x7b\x45\x7f\xe4\x45\x6a\x92\xda\x58\x4a\x66\xd0\xbd\x4d\x57\x6e\x07\xcd\x4b\x1b\x50\x97\xf4\x01\xe1\x88\xab\x16\xbc\x92\xc8\x23\xac\x02\xf2\xae\x41\xe2\x2f\xb5\xd4\xce\x56\x25\x7c\x55\x27\xd0\x88\x92\x49\x3e\x33\xe8\xa1\xc2\x9a\x91\x55\x83\x1f\x5c\x86\x98\x96\x81\xb6\x9d\x83\xd7\x38\xb1\x5b\x75\xc6\x9e\x38\x62\xce\xdd\xb9\x0b\x96\x97\xf7\x27\xe0\xca\x28\xc8\x67\x38\xc4\x2f\x4d\x2e\xb8\x4d\xe2\x3a\x3c\x5c\x32\xc8\x6d\xdc\x8c\xe2\x6f\x0b\xa1\xca\xd9\x9d\xb4\x2e\x34\x6a\xc1\xae\x59\x23\x21\x43\xb9\x39\x24\xbf\xd2\xf1\x59\x05\x42\x03\xb8\x40\xb8\x60\x47\x24\xf9\x81\x94\xe1\x3a\x7a\x42\x07\xb4\x1b\x37\xe0\xb3\xfa\xd4\x1c\x24\x4b\x19\xe7\xc2\x5f\x22\x76\x4d\x88\x8c\x5c\x8c\x61\xe1\x15\x57\x6d\xea\x39\x9e\xf2\xa2\x73\x2f\xd8\xc2\x4c\xc6\xaa\x0f\x72\x5f\xe4\x91\x6c\x4e\x23\x4e\x92\x4c\xb4\xf7\x08\x4d\x2d\xf1\x9c\x06\xbd\xc3\x24\x86\x6d\xbd\x5b\x4a\x16\x54\x29\x61\x9f\x2e\x99\xf0\x30\xef\xb2\x23\x30\x1c\x47\x56\x5a\x48\xa9\xa2\x8f\xc5\x39\xc1\x82\xcf\xc9\x0c\x44\x06\xba\xfc\xa7\x4f\xe4\x58\x22\x85\xa3\x79\x1e\x3c\xbe\xbf\x2a\x1a\xca\xdb\xe8\x7d\xa6\x55\xc9\xce\xe9\x4d\xe6\x8f\xd1\x34\x61\xf0\x1f\x5d\x22\x5e\x06\x1f\xcf\x05\x17\x12\x26\xde\x40\xa3\x9b\xd2\xb0\x17\x7e\xa0\xde\xae\xd2\x36\x10\xad\xe4\x1e\xee\x1e\xa3\x25\xdf\xe4\x3c\xca\x28\x00\x0d\xaf\xcd\x79\x6c\xb2\x8c\x23\xcf\xd9\xbd\x9e\x8b\x6a\xfb\xb2\x1d\x33\x34\xc3\x96\xca\xfd\x23\xf6\x7d\xd8\xcd\xcd\x58\x66\xf9\x36\xf1\x29\x70\xb3\xa1\xfb\x4b\xd7\x38\x6e\xec\x4f\xaf\xff\x64\xff\x65\x8d\x14\xfc\xe9\x2d\x81\x1f\x93\xe9\xde\x6d\x01\x24\x1c\x85\x1c\xf3\xef\xbe\x72\xbb\x17\x7e\x81\x8f\x62\x45\xfc\x7e\x8f\xb8\x57\x1f\x0f\x48\x04\x01\x49\x2f\x11\x0e\xed\xe8\x4d\x5e\xe2\xf4\x25\x2c\x56\x23\x6a\x0f\x5a\x8f\x93\xdd\xfc\xf2\x22\x40\xdf\x56\xe5\x8c\xf9\xe5\xa6\x64\x7b\x69\x7e\xd1\xf5\x87\x19\x55\xa2\xfb\xce\x9e\x11\xca\x26\xc1\x3a\xde\x04\xea\xbb\x67\x5d\x69\xf4\xce\x3e\x60\x9c\xe5\x06\xf3\x7a\x03\x97\x8c\xe5\x26\x01\xba\x4a\xe9\xbb\x28\xb0\x5a\xb7\xb2\x4e\x93\xb9\xfe\x6a\x21\x59\xab\x58\x9a\xb5\xf5\xe3\x4b\x9e\xea\xc4\x6f\xd6\x7c\x97\xa5\x9d\x08\xd0\xc9\xdc\x89\x7f\x89\x45\x95\x24\x00\x7b\x62\x21\x70\x0f\xfa\x78\xf6\xfe\xdf\xb1\x73\xc0\xc3\xf5\xe4\x10\x5b\x7e\x25\xfa\x5f\xa4\x3d\x66\xe6\x6e\xd8\x0a\x0d\x0a\xbf\xab\xd9\x41\x12\xe1\x58\x31\x89\x92\x49\xef\x96\xb9\xdf\x1f\xa1\x76\xad\x3b\x44\x23\x70\x8c\x3f\xcd\x4e\xd8\x8e\x92\xe5\xcc\x9b\x69\xb7\xd4\x69\xaf\x43\x12\x07\xe0\x0a\x0f\x0f\x99\xa0\xb8\xca\x94\xbc\x12\xb6\x78\x08\xe3\xf2\xc7\xdc\xc2\x4b\xa5\x82\xcd\xb8\xcb\x33\xa8\xf4\x15\x23\x47\xf0\x49\xca\xe5\x99\x45\xd7\x02\x7a\x2c\x27\xbd\x88\x4a\xbf\x2e\x02\x13\x0b\xaf\xe9\x33\xf3\xc7\xff\xc1\x6d\x61\xf6\x8a\xd0\x6d\x01\x7b\xc6\xb1\x01\x5a\x12\xab\xf3\xb1\x4d\xf5\xb7\xe2\x1f\x34\x03\x74\x62\x05\x6c\xe8\xc3\x98\x65\x7d\xc9\x8f\x20\xbc\x62\xa5\x4f\x83\xfd\x4a\x67\x73\xc6\x54\x95\xe8\x1e\x97\x9a\x6e\x04\x40\xd1\x72\x25\x79\x4e\x05\x7f\x76\x65\x7d\x58\x19\xbf\x6c\x9a\x42\x87\x32\x13\x02\xe4\x2f\xb0\xbb\x92\xd4\x0b\xfd\xc2\x3d\x5e\x96\x04\xaa\xd6\x74\xf1\x8c\xab\xd4\xdf\x79\xf8\x57\xca\x8b\xf6\xcd\xe8\x65\x9b\xed\x37\xc9\x58\x3a\x5f\x1b\x3d\x01\xbb\xb8\x70\x9b\x5f\x5b\x90\x6f\x04\xb2\x04\x04\x98\xb0\xfc\xdb\x41\xc3\x31\xd7\xfa\x76\x46\x90\x55\x39\x0b\x09\x94\x30\x31\xc2\x14\x4c\x0b\x56\x0c\xa4\xb2\x80\xec\x38\x83\xab\x53\xb3\xa7\xf6\x79\x4c\x15\x23\x72\xef\x90\xfd\xf1\x1f\xbb\xd9\xa9\xb6\x45\xed\x91\x96\x7f\x13\xfd\x40\xc3\x7f\xfb\xbc\x4d\x91\x1f\xb9\x67\x0c\xe2\x52\x4d\xd3\xa8\x05\x54\x5b\x85\xd8\x21\x97\x86\x99\x82\x77\xf9\x0b\xea\x24\xb8\x86\x2e\x70\x6f\x57\x72\xe5\x55\x29\x3e\x5c\x7b\xaf\xfe\x5a\xaa\xc0\x15\x36\x91\x47\x06\x40\xbe\x77\x77\x8d\x00\xad\x98\xf5\x45\xc6\xc7\x52\x97\x38\x64\x58\xc2\x88\x7a\xb7\x78\x7d\xb0\x01\x92\x19\x05\x94\x29\x10\x31\x06\x8b\x98\xc1\x93\x48\x62\x1b\x2f\xc6\x55\x39\xc5\xe2\xf6\xe5\xf3\x0e\x2b\x7c\xc0\xe9\xc3\x53\xc4\x74\x4e\x42\x81\xe0\x2e\x61\xa7\x4b\x41\xc2\x80\x54\xba\x0c\xa3\x82\x67\x08\x34\xc5\x19\x7e\x89\xfa\x1d\xee\x66\xb5\xd4\x58\xf5\x40\xe9\x43\xa6\xf0\x7b\xbc\x5e\xfa\x4d\x5f\xdc\xdc\x93\x8b\x06\xdd\x8f\x24\x9d\x93\x9e\x82\x92\x13\x6e\x32\x7e\x0d\x7f\x97\xfc\x1c\xca\x48\xdc\xd0\x1d\xe5\x74\x5b\x9a\x68\x53\xe0\xf1\x81\x85\x16\x3f\xfb\xe1\x96\x7c\x00\xc6\xfb\x5e\xa6\xd5\x9f\xff\x5b\xd5\xb4\xf7\xc9\xda\x29\xa7\x8f\x93\xe3\x77\xae\x08\x24\x75\xaa\x1f\xd0\xb5\x5b\x13\xf8\xed\x58\xb9\x91\xdf\xe8\x61\xb0\xf6\xd7\x9f\x79\xcc\x8f\xdc\x3a\xd0\x06\x7b\xc3\xfc\x37\x75\xe9\x03\xde\x54\x8e\xd4\x8e\xde\xf2\x6e\x50\xd9\x30\xa6\x12\x53\x0e\x5f\x38\x5c\xe5\x83\xe3\x34\xa2\x7b\xaf\x05\xde\xf1\xd4\x91\x13\xf2\x08\x80\x95\xb9\x7c\x40\xbd\xf6\x08\xdc\xad\x81\xe0\xe9\x30\x00\x63\xc6\x5d\x74\x2b\x14\x21\xea\xc8\xc6\xdb\x74\xc5\xde\xd9\x47\x6c\x16\x94\xd5\xbc\x75\xde\xb7\x97\x6f\xb6\x93\x78\x80\x88\x3a\x04\xe6\x04\x24\xff\x67\x7c\xb2\x95\xae\x3b\x58\x93\x80\x67\x1c\xcf\x61\x86\x34\x1f\x52\x85\x30\xbd\xd2\x8f\x6a\x66\x2f\xbc\xeb\x26\xd2\x08\x91\x25\xaa\xcc\x0d\xbb\xd7\xa4\x08\xc9\x82\xe9\x38\x08\x4b\xaf\x7f\x6c\xf2\x23\x04\x33\xa7\x6b\x4b\x79\xa7\x00\x6c\xcd\xa2\x98\xfd\xff\x1b\x5f\xd1\x00\xec\x1f\xaa\x2d\xc5\x09\x52\xe4\x35\x2a\xb1\xb1\x2c\xeb\xa0\x5c\x29\x17\x78\xfe\xf2\x80\x0a\x17\xa4\x38\x31\x37\xea\x5a\x74\x1d\x54\xfa\xc4\x1e\xc2\x46\xe8\x8f\x63\x16\x61\x2e\xb1\x67\xd6\x04\x39\xe8\xb9\xde\x19\x05\xee\xb1\xb6\x08\x89\xab\xc0\x0b\x5e\xe6\x42\x72\x23\x87\x63\xc4\x91\x29\xf9\x15\xdd\xf6\xf9\xe2\x4c\xec\xff\xcf\x33\x1b\xcb\xce\xa9\x68\x49\x7f\xc0\x8f\x55\xdb\xa0\x13\xab\xa5\xe3\xaa\xbf\x96\xfb\x7d\x83\x6d\xb6\xaa\x13\xec\x80\xb0\xd5\x05\x06\x8f\x5b\x0d\x92\x65\x8e\x01\x98\x13\x78\xe4\x71\x65\xa7\xaf\x5c\x08\x65\x81\xe8\x13\xc2\x68\xd8\x51\x3a\x5e\xa6\x62\x95\xfd\x0f\x27\x00\x4a\x6e\x86\x56\x43\x23\xbc\xd8\x67\x17\x08\x8d\x7a\xaf\x6d\x7e\x03\x55\x28\xca\xbb\xfa\xb6\x5a\xb5\xd3\xeb\x9d\x6a\x92\x8b\x29\x49\xe3\xe8\xd5\x39\xd5\xcf\x85\x5d\x5a\x63\xfb\x5d\x53\xcf\xee\xcb\x21\x3a\x0a\x7c\x16\xd8\x1b\x43\xdc\xac\x3e\x70\xb3\x4f\x5a\x00\x0d\x45\x35\xc8\xd5\xa5\xe0\xd2\xbf\xb1\x03\xbf\x10\x16\x6c\xa9\xab\xb9\x3e\xe2\x0b\x50\x77\x3e\xa4\x82\xd9\xbd\xaa\xec\xe1\x8f\x7f\x57\xb8\x3e\x63\x19\xa3\x09\xe8\x2c\xf6\xc7\xab\x52\x27\xc0\x51\x98\xf4\x5a\x8c\xb7\xd7\x9f\x84\x14\x80\x69\x5a\x01\xa9\xc4\xa9\x13\x25\x97\xc6\x40\x8d\xc9\x9f\x52\x68\xe0\x8a\x6f\x44\x95\xce\x32\xd9\xba\x89\xc4\x3f\x21\x4d\x15\x89\xc7\x53\x50\x56\xaa\xa3\x8c\x47\x94\x24\xc8\xc5\x91\x9a\xe3\xd2\xc3\xd3\xee\xd1\xaf\xc1\x6c\x97\x96\xba\x48\xca\x2a\x74\x12\xce\x7c\x02\x7a\x3f\x66\x75\x17\x14\x57\x9e\x6c\x0a\x37\x08\xe6\xa9\x15\x1b\xc8\x64\x1f\x2c\x31\x86\x2d\xd1\x64\x43\xdf\xa7\x48\xc4\x34\xbc\xbc\x12\x8a\x47\x4f\xb2\xfc\x33\xd7\xe4\xe0\xf9\x6c\xaa\x74\xa9\x4a\x4b\xf2\xc7\x79\xce\xe9\xa9\x9e\x27\x66\x14\xd1\xe2\xc6\xe0\xfb\xd9\x6f\x28\xe1\x90\x81\xc0\x5b\x80\xeb\x9d\x66\xec\x39\xc5\xfc\x0d\x7a\x1e\x00\xfd\xcb\xa0\x76\x74\x74\x62\xd6\xc2\x2f\x85\x29\x28\xcc\x1c\x17\x50\x34\xf9\xf2\xd8\x8d\xbc\xbc\x32\xc6\x65\x82\x4e\x61\xa6\x15\xf4\x79\x72\x06\x39\x66\x95\xec\x94\xce\x6d\x1b\x62\x99\x18\x19\x1a\xb8\x25\xe8\xfa\x3e\x5e\xb9\x06\x28\xc1\x46\xd1\x32\x90\xff\xbe\xc1\x98\x93\x69\x1a\x26\xa0\x84\xa2\x48\xf0\x37\x06\x97\xa8\x16\x98\x46\x31\xb7\x1c\x0e\xc0\x52\x08\x80\x15\x17\x3d\xc6\x29\x29\x5a\x63\x63\xc2\x81\x3a\x59\x16\xd3\xe3\xeb\x51\xbb\xba\x08\xaa\xa0\xa5\x97\x86\x18\x39\x69\x43\x9d\x15\x38\xb7\x17\xf8\x3d\xbb\x57\x1c\x8f\x80\x9d\xbf\xf5\xd6\xbc\x5f\x20\x72\x45\x17\x70\xd6\xd9\x7d\x5e\x6b\xa3\x5a\xe4\x72\x2b\xbd\x89\x55\xd4\x1b\xae\xa6\xd4\x66\xdc\x14\xf0\xe1\x67\xdb\x9f\x7c\xec\xa7\x95\xee\x38\x46\x0c\x14\xf0\x4d\x35\x34\x18\xcf\xd4\x41\x0f\xa4\xd7\x9e\x3a\x72\x1b\xac\xf8\x23\x8a\xb6\x2b\x7f\xed\xee\xbb\x5d\xeb\xc6\xe3\xef\xd5\x80\x86\xde\x28\x6a\xdd\xeb\xf2\x7d\x4c\x80\x78\x9b\x96\x94\xf5\xce\x8b\x3b\xa8\x0f\xee\x00\x99\xe6\x81\xed\x74\xc3\xc6\xca\xc6\x10\x3f\x1c\x5d\x86\x41\x1b\xfc\x70\x5e\xa6\xd9\x79\x6d\xbe\x00\x62\xd6\xa7\x93\xaa\x20\x86\xa2\x3d\x26\x81\xe3\x99\x6b\x85\xf0\xf2\xb8\x62\x16\x70\xd6\x14\xbd\x3e\xab\xdc\x53\xbe\x0b\x0e\xbe\x6c\x4f\x3b\xfa\x56\x2d\x6a\x7a\x40\x24\x1d\xdd\x6d\x9f\xfb\x28\xa0\xb2\xbb\x6f\xa4\xe1\xf4\xd7\xc2\x12\x99\xe7\x37\xf9\x72\xd8\xe5\x35\x9d\x39\x48\x62\xfe\xbb\xf4\xaa\xe1\x5d\x68\x89\x22\x35\xc1\xf3\x4f\x31\xd7\xc6\xb0\xaa\xfe\x76\xda\xf0\x43\x79\xc6\x9d\xb5\x2a\x55\x5e\xe0\x2e\x99\x37\x62\xcd\x0b\x72\xaa\x86\x59\x6d\xc3\x11\xcf\xa4\x6b\x64\x30\x07\xee\x46\xac\x8b\x68\x67\x32\xfc\x10\xc0\x7b\x38\x9f\x23\x7d\x0b\x63\x0f\x76\x72\xb0\xf6\x3d\x63\x19\x9b\xe5\x46\x4f\x71\x8a\x71\x42\x76\xd7\xf5\x47\x1c\xa0\x69\x34\x5a\xaf\x3b\x38\xd9\x41\xc6\xb6\x4e\xde\xc9\xc0\x66\xd2\x79\x9c\x4b\x51\xbd\x04\x05\x35\x8f\x83\x4b\xd5\x98\x8c\x78\x61\x72\xb8\xf2\x17\x68\x06\x78\x77\xa5\x5e\xfe\x94\x11\x25\x9d\x24\x03\x6c\xf0\xd4\xe4\x42\xa9\x22\xc1\xe9\xfd\x70\x15\xc0\x04\xbe\xdd\x94\x06\xe9\x41\x0c\x9e\x1b\x55\xa3\xde\xa6\x7b\x11\x96\x1d\x0c\x0d\x6c\x7f\xb9\xfa\x61\xb7\xb7\x40\xe0\x5a\x58\x57\x35\x9d\xa1\x79\xff\x9e\x49\x35\xd7\xad\xe8\x2e\xb2\x0f\x30\x8b\xfc\xe2\x9d\x13\xe9\x8c\x81\x85\x3c\x22\x96\x46\x95\xdc\x18\x8b\xe9\xce\xeb\xff\x8d\xae\x55\x8f\x5f\x7b\x03\xdb\x0a\x22\x3e\x31\xb9\xad\xce\xa0\x00\x26\x32\x5f\xb8\x85\x8e\xc2\x10\x6e\x10\x20\x7d\xaf\xd4\x1e\x7a\xdd\x2a\x42\xc3\x13\xe2\xfd\xeb\x38\x09\x5f\x66\x54\x23\x46\x64\xf9\xf2\x04\x05\x3e\x7f\xe1\xa0\xdb\x12\x53\x1d\x3f\xcd\x7f\x0c\x7c\xe6\xc5\x58\x0b\xd6\x37\xd7\x35\x80\x1d\x65\x10\xcf\xfe\x72\xe0\x1a\x88\x4c\xbe\x76\xaf\xe9\x3a\x58\xc2\x23\x79\xc1\xe6\x4d\x7c\x7a\x02\xdf\xd1\x6d\xa2\x3d\x26\xc5\x84\xdb\xec\x9a\xba\xae\x15\x13\xc0\xa3\x81\xc9\x6e\x49\xf6\x3c\x04\x04\x6e\x42\x26\xd3\xcf\xa3\x2d\xdb\x98\x75\x4e\x2c\x99\x93\xf8\xa9\x7f\xb8\xa5\x93\xab\x6f\xb1\x9f\xc0\xa8\xd1\x4b\x8a\xdf\x71\xd9\x25\xef\x06\x7a\x3b\x96\x65\xa6\xba\x66\xfc\x5c\xb6\x88\xfa\x14\x86\xc2\x81\x1f\xd8\x1b\xd8\x28\xea\x49\x4d\x0d\xf1\x59\x91\x8e\xff\x1d\x38\x0b\x5c\xfd\x57\x7b\x01\x7f\x3c\xff\xc9\x0b\x0d\xc0\x74\x03\x3c\x8c\xf5\xe8\x1f\xff\xcf\xba\x0d\x0c\xc3\xfe\x1e\x36\x0f\x2b\x0c\x3d\xcc\xf0\xc2\xc4\x04\x3d\xfa\xde\xc2\xf5\xbe\xf1\x68\x5e\x77\xb6\xf0\x1b\xd8\x8f\xb5\xfc\x8b\xfe\x46\x2a\x64\x1d\xc6\x6c\xe7\xc1\x82\x4c\x2b\x4d\xdc\x73\xf3\xca\xe3\x3b\xd0\xb6\x69\xb8\xf6\x42\xf7\x77\x5e\xc3\x18\x0a\xaf\x67\x4c\xa8\x04\x2a\x2d\x76\x40\xd4\x02\x14\x9e\x1b\x64\x21\xfa\xb0\xa3\xad\x0c\x30\xe2\x48\x9b\x6c\xcd\x3a\x98\x95\x3f\x62\xb5\x5a\xfc\x3a\xfe\xb6\x05\x51\x7e\xdb\x95\xcd\x7d\xab\xb1\x32\x67\x10\x6c\xf0\x0b\xda\x79\x7c\x3a\x57\x45\x80\x0b\xc1\x7a\x7f\x31\xf1\x2d\xf4\x4f\x95\xb1\x69\x5a\xde\xbc\x35\xa7\xa9\xcc\x20\x58\x42\xe6\x03\x30\x3d\xe6\x02\x87\xfd\x1c\xe9\x24\x0d\x9d\x78\x75\xaa\x2d\x9f\x59\x1c\xdc\x45\x65\xe1\x7c\x18\x4d\x43\x8a\xf2\x9f\xfe\x02\xc2\x49\x34\x0c\xa1\x09\x19\x31\xbb\x97\x84\x96\x22\x99\x71\x8d\xab\x96\x9d\xde\xe6\xd7\x1d\x2e\x4d\x57\x12\xd9\xa1\x31\xb0\xf6\xfb\x7a\x90\x81\xcc\x3f\x69\x23\xb5\x36\x72\xd6\xe5\xda\xdc\x54\x5e\x79\xb0\x92\x62\x82\xb2\xae\x53\x48\x22\x6b\x2a\x14\x6e\x2d\x46\xc3\x56\x7b\xd3\x16\x1d\x23\x13\x30\x26\xe9\x6c\xd9\x53\xca\x96\xf5\x8b\x09\x1b\x2c\xdc\xe9\x48\xb2\xc1\x84\xfc\xc2\x4f\xfd\xbd\xb8\x14\x7d\x62\xe6\xb3\xdd\x03\x17\x5b\xb6\x8a\xea\x5b\xe5\x0e\x23\xf9\xc8\x9d\xff\xf0\xe9\x27\x89\x35\xfd\xdb\x23\x89\xb0\xfd\x8b\xd8\xea\x2f\xde\x7c\x3f\xa9\x0f\x8d\xcd\xa6\x6c\x29\x71\xa4\x31\xed\x85\xb2\x87\x1c\x54\xc8\x0c\x99\x5b\xab\x33\x97\x07\x6b\x2a\x17\xfa\x8f\x59\x57\x5b\x87\xf2\xfa\xfc\xb6\x17\x19\xcf\x77\xc3\xe6\x52\xd2\x52\xac\x14\xae\x0b\xac\x6e\xa5\xf3\x2d\xbb\x42\x65\xee\x86\xd6\x18\x02\xf5\x52\x0a\x78\x95\xf0\xcb\x69\xa1\xf2\xf6\xfa\xf4\xd8\x8b\xc2\x9a\xab\x48\x38\x06\x4a\x11\x50\x65\x59\x9b\xe8\x3f\x67\x99\xf3\x7a\x5c\x5b\x6f\xe5\x2c\x2e\xb9\x10\xf9\xe7\x15\x0e\x33\x96\xcc\x41\xa0\x40\xf4\x13\xdd\xac\xfc\xce\x34\x4d\xe2\xbb\xd1\xe6\x6f\x5a\xb6\x62\x75\x32\xca\x7d\xc2\xd1\x9d\x22\x75\x7a\x7d\x14\xa1\xca\x01\x1e\x6e\xde\x6c\x41\xcc\x3b\xb8\x30\xe9\x54\x0a\x6e\x75\x22\xa4\xa9\x72\x3d\xbb\x8c\x05\xdf\xbc\x40\xfe\x3a\xb1\x07\x05\x4f\x9f\x5e\xc8\x68\x22\xd7\x7f\x6d\xac\x7e\x4e\x17\x0a\xf8\xea\x42\x26\xea\xe3\xc2\x1c\xbd\x1d\xfb\x94\xcc\x22\x3b\x2c\x67\x09\x72\x4f\x71\xd3\xf9\x96\xd8\x10\x17\xa1\x20\x9c\x8d\xcc\xb8\x54\xf9\xf0\x6d\x0a\xac\x59\xda\x98\x9e\x31\x54\xcd\x20\x91\xd0\x1c\x89\xe2\xb4\x9f\x2e\xd3\xfa\xd6\xf4\x34\x19\xb3\x37\x74\x50\x23\x84\xc3\x7a\x02\x31\x0e\x8e\x5d\x44\x1f\x89\xcc\x9b\xd5\x9d\x09\x67\x1f\x41\xf1\x95\x9a\xad\xd2\x8b\x69\x44\xd5\x1c\x91\x08\x67\x30\x68\x25\x3d\xda\x0d\xca\x16\xa3\x37\x8f\xab\xa3\xf2\x98\x0b\x60\xe0\x8b\x11\xe7\x9e\x7a\x05\x29\x2b\x82\x44\xd1\xce\x8b\x51\x8a\x92\xcf\xff\xdd\x72\x02\x51\x75\x01\x07\x76\x38\xa9\x66\x7c\x88\x4c\xd4\xdb\x63\xdf\xc4\x79\x81\x43\xb7\xb7\xb8\x7b\x7a\x0b\x74\x30\xd0\xe6\xf3\x19\x50\xa5\x1e\xb8\x8a\x72\x2b\x6d\xf7\xcf\xb9\xab\xe9\x51\x81\x48\xd9\xe3\xf8\x7c\xe0\xc3\x2d\x96\x3a\xbb\x82\xe1\x33\x44\x8c\xf9\xc4\xcc\x42\xf0\xb6\x3c\x7c\x04\x31\xc2\xea\xe5\xdf\x57\x29\x39\x03\x62\x5d\x04\x55\xc3\xf1\x0d\x1c\x76\xb2\x52\x20\x84\x5b\x91\xd1\x5f\x9c\x9a\xd1\x2c\xf7\xb7\xe3\x74\xe7\x93\xf9\xca\x2f\xcc\xf4\xcd\xec\x2e\x67\x4f\x2f\x36\x77\xf7\xfa\x16\x5b\xb6\x66\xd1\xca\x00\x74\xa8\x50\x84\xaa\x62\x64\xb1\x19\x2c\x92\xc1\xaa\xc4\x90\xea\xdd\x00\x76\xcf\x39\xb7\xb1\xcc\xb7\x8a\xcf\xa5\x0b\x2d\x0d\x3f\x4d\x42\xda\x7f\x43\x4e\xff\x4b\x53\xb3\x4d\x3c\xcb\x2a\xe8\x29\x0e\x58\x11\x19\xf4\x7f\xc3\x3a\x52\x79\x15\x85\x07\x08\xbf\x23\x5f\xb8\x49\xf2\x17\xcd\x77\x70\xf7\x95\xa7\x1c\x6c\xcb\x7b\x0c\x98\x7d\x3d\x4f\x35\xf1\x51\x74\xa2\xdf\x6a\xb2\xd5\x0c\x3d\xd3\x7f\x0a\x7f\xa2\x3a\xf9\x51\x53\xfe\x14\xe4\x16\xd3\x17\x40\x35\x11\x27\xa6\x55\xfa\xac\xeb\x30\x70\x73\x0e\x58\xa9\x40\xbd\xc7\xb8\xfd\x7b\x16\xcf\x45\x28\x54\x44\x26\x16\xd6\x24\x69\x7c\x84\xd9\xca\xe9\x68\x99\x1a\xa0\x38\x64\xdb\xb1\xd6\x3d\x11\x83\xdf\x0d\xd7\xdc\x60\x49\xc2\x0f\x09\x02\x22\x76\x49\x83\x46\xed\xeb\xf3\x06\x3a\xe9\xc9\x90\x36\x7f\x1d\xa3\xd8\x6d\x98\xa0\x97\x56\x8d\xa7\x4e\x49\x9f\x29\x7b\x85\xe0\x55\xe9\x67\x08\x63\x9f\x7f\x5b\x98\x29\x95\xd2\x77\x00\x4b\x2d\x21\x38\xd4\x7a\xcb\xab\x8e\x89\x08\x9b\x91\x7a\x72\x28\xf6\x45\x67\xc9\x72\x76\x7f\xc9\x63\xa2\x9c\xc9\x95\xba\x9b\xf6\xd4\x99\xd4\xba\x16\x22\xa2\xc5\x8b\x08\xa2\xa7\x78\x71\x4d\x95\xed\x56\x3a\x36\xcc\x48\xa4\x6f\x7f\x76\xb6\x57\x80\x2f\x5c\xd9\x42\xfa\x0a\xa5\x17\x05\x34\x57\x22\xc3\x24\xa5\x4a\x0e\x51\xa0\xd1\x3a\x09\xf2\xb7\xe4\xa2\x14\xcf\x89\x7b\x51\xeb\x1d\x8c\x84\x70\x37\x26\x60\xa3\x19\x50\x4e\x39\xae\x4a\x2b\xc1\x92\x93\xb6\x03\x87\x6c\x4d\x4b\xfc\x04\x9d\x66\x1a\x5e\x63\xa7\xeb\xe6\xc2\x77\xa0\xe2\xb9\x25\xd0\xe0\xb1\xc5\x0f\x74\xae\xaa\x15\x1d\x42\x22\x27\xf2\x86\x19\x5d\xc0\xd8\x8d\x09\x45\xe8\x51\xbf\x8e\xdd\x52\xbf\x6f\x3b\x1e\xf3\x56\x4c\x97\x0d\x04\x9e\x90\x20\x7e\x79\x9f\x75\x5f\xbd\xfc\xd5\x23\xe3\x2b\x29\xcb\xc0\x35\xe1\x3f\x04\x05\x19\x81\x8f\x16\xdf\xc5\x5c\x59\xc1\x8d\x72\x8c\x89\xd1\xdb\x6e\x60\x3b\xae\x87\xf8\x44\x14\x95\x03\xc3\xae\xf4\xdf\x2a\x98\x89\x44\xa6\x7b\x0d\xb6\xef\x82\x71\x1c\xcf\x0f\x66\x43\x91\x0c\x4e\x5c\x50\x28\x5a\x17\x14\x28\xfa\x89\x16\x44\xdc\x35\x0e\xe5\x7e\x10\xf6\x26\x6c\xd0\x9d\x86\xd0\x0a\xba\xe7\xff\x10\x7d\x9e\xfe\x91\x19\xf4\xb4\x1a\xe4\x8c\x25\x90\xa0\x2c\x79\x22\x9c\x6a\x40\x1d\x97\x5a\xe5\x63\xd1\x68\x51\xda\x23\x06\xe5\x66\xea\x40\x57\xba\x5b\x05\x21\x1f\xc6\x75\xf7\xf1\x95\x7f\x62\x62\x7d\xc8\xf4\x5b\xeb\xc7\x88\x97\x4f\x5b\x13\x06\x79\x44\xf5\xa9\x01\x23\x99\xd1\xfb\x7d\xd6\x7d\xa3\x0f\xa8\x78\x1f\x3e\xe7\x28\xb2\x7c\x86\x05\x36\xc7\xe9\xef\x90\xe9\x6c\xdb\xec\xa6\x44\x33\xc6\x0a\x9a\x9c\x98\xfd\x0e\xb4\x4e\xd9\xa9\x34\x11\x7d\x54\xe3\xbb\x05\x3a\xee\xc2\x90\x3a\xde\x8e\x51\x08\x53\x8d\x37\x6f\xe1\x62\xfb\xb7\x1a\x37\x0c\xc0\xb4\x23\x99\xd4\x0b\x1b\x9d\x7c\xd7\x2e\xac\x1a\x5c\x07\xd6\x69\x31\xd0\x6b\xaa\x89\x00\xea\x8a\xed\x80\x04\x59\x17\x88\x13\x6d\x35\xce\xff\x6b\x09\xae\x69\x35\xd2\x83\x58\xc4\xf9\xb6\xef\x3c\x81\xc2\xf2\xf6\x16\xce\x54\xcc\x1d\x54\x24\x04\x12\x74\x3b\xb1\xfd\xe7\x0d\xbe\x1c\xf6\x2a\xda\xf2\xb2\xa3\xaa\x16\x7a\x8a\x0d\xb0\x09\xec\x42\xf2\x40\x00\xea\x2a\xb3\xde\xd5\xf0\x9a\xf3\x5c\xc8\x3a\xba\x1a\xb8\x28\xe2\xf7\x09\x70\x0d\x1e\x93\x1e\x94\x9f\x69\x2d\x38\xc5\xe3\x73\x50\x8e\xb5\xa3\x85\xd0\x84\x18\xdb\x9b\x6a\x14\xf4\x9c\xae\x9e\x6c\xeb\xf9\x88\x12\xbd\xb5\x8f\x94\x77\x2b\x8b\xc3\x52\x7c\x90\x75\x96\x5b\x62\x66\x15\x8b\xb5\x7d\x67\xac\x31\xb5\xc0\x8e\x8a\x22\x4a\xd0\x86\x01\xda\x5b\xfd\x5f\x18\xda\x9f\x54\x23\xa6\x3c\x05\x23\xe6\x03\xfc\xe0\xcd\x53\x43\xcb\x81\xbe\xb3\x73\x7d\xa5\x4b\xd3\xf6\x48\x71\xee\x89\x65\x79\x05\xfd\x66\x86\xa6\xa5\xf3\x32\x5b\x1c\xf7\xab\x3a\x22\xf8\xea\x7d\x7b\x66\xb2\x98\xc1\xc2\xda\x1f\x06\x07\x83\xe6\x4d\x66\xf0\xc9\xcc\xc7\xbf\x96\x46\x14\x2c\x5a\x09\x35\xc7\x17\xbb\x1d\xc2\x00\x15\x9b\xb9\xeb\x8a\x01\x8c\xed\x15\xc2\x56\x48\xf2\xdc\xa9\x95\x4c\xc3\xa8\x36\x7f\x9d\x85\x04\xd9\xb5\xec\x3e\x94\x9d\xdf\x8f\x98\xd8\xf2\xea\xc3\xe3\xe8\xfb\x2d\xbd\x96\x79\x06\x21\x2c\xed\x9b\x97\x56\x63\x10\x82\xb6\xb9\xd5\xe4\x68\xb2\x46\x11\x6a\xf4\xc4\xeb\xd3\xf2\xce\x84\x0b\x57\x0e\x48\xd3\xc7\x30\x84\xcb\x85\x08\xe0\x17\xf1\x01\x2f\xe5\x05\xbd\x07\xb8\x8e\x09\xe5\x8b\x18\xe1\x93\x66\xce\xb6\x4b\xd2\xac\xa3\x9b\xb8\x9f\x2a\x9c\xae\x59\x49\x56\x8f\xdd\x20\x2c\x5c\x30\x16\xd1\x62\xec\x89\xec\xdc\xf5\xb1\x3a\x84\x90\x02\xce\x69\xfb\x75\xd9\x04\xe7\x49\x8f\x74\xe9\x1d\xad\xaa\x69\x92\xdb\x89\x01\x88\x1c\xf1\x47\x8a\x65\xf8\x4b\x3e\x88\x8a\xde\x70\xc3\xb6\x9f\xef\xaf\x12\x42\x8c\x27\x64\xc7\x4b\xe0\x7c\xb5\x77\x2e\xbc\x0d\x9e\x41\xf9\x3a\xf9\x12\xda\x24\x2a\x78\xcd\x76\xb1\x3f\xf4\x67\x41\x13\xd9\x79\x60\xf6\xde\xc0\xdc\xdf\x2a\x29\x7d\xb1\xf0\x8a\x64\x43\xb5\xbb\x2f\xa8\xb0\xd4\x92\xd3\xb2\x2f\xc9\x6f\x17\x31\x40\x7d\x0a\x2e\x2e\x12\x4b\x91\xf2\x07\x6f\xd0\xf4\xc6\x61\x5c\xcb\xea\xe7\xce\x4d\xd7\x5e\xa5\xf1\x37\xa7\x06\x47\x89\xa2\x9b\x08\x2f\xd1\x42\x49\x06\x1c\xe2\x09\xda\x43\xd5\x96\x7b\xb7\x3d\x81\x96\x32\x61\xa4\x9d\x6c\x6d\x23\x32\x87\xe3\x91\x8b\x92\xb0\x05\xf4\x60\x91\xb2\xe0\x94\xe0\xf4\xf5\x52\xb1\x6a\xbd\x4c\x27\x61\xc9\x72\x55\x7e\xa7\x6a\xd2\x6f\xda\xf2\xb3\x04\x52\xab\x4a\x59\x07\x1c\xf3\xa3\x01\xfc\x37\xbc\xa4\x41\x1f\x9e\x00\x9c\x6e\xcc\x5c\x83\xe1\xe0\xb8\xb6\x9d\x58\xf6\x19\x8c\x03\xdd\x05\xf7\x98\xb5\x15\xd9\xae\xce\x42\x7e\x89\x56\x2a\x86\x78\x45\x6b\xe8\x40\xef\x8e\x5c\x07\x55\x73\x62\x6b\x3b\x26\x5b\x05\xe9\x91\x8d\x3c\x30\x8a\x5f\xa5\xd6\xad\xc3\x36\xd8\x7b\x29\xf3\xc7\xc3\xda\x97\xa2\xef\x36\x3e\x14\x76\x6f\xa1\x76\x0b\xae\x9b\xe6\xab\x73\x03\x45\xf7\x2d\xc7\x75\xd1\x8b\x72\x2b\x20\x41\xaf\x17\x4c\xeb\x82\xc9\xf9\x48\xff\x0d\x2d\x73\x5e\x4e\x04\xeb\xdc\xd0\xc1\x96\x6e\x4e\xc0\x6f\x2f\xe4\x18\xa8\x92\x66\xe1\x10\xdb\x9c\x6d\x97\x76\x39\x76\x4c\x07\x7a\x3c\x0d\x7b\x1c\xdb\xff\xe4\x2d\xf6\x8b\xc3\x24\x8e\x1e\xa2\xfb\x40\x54\x3b\x9d\xea\x96\x93\xe0\x8e\x84\xe6\x39\x77\x61\x19\x8c\xd4\x3a\xd5\x74\x37\x3e\x96\x7e\xa6\x41\xdb\xde\xcd\xcc\xa2\x46\x8f\x19\x51\x9b\xee\x24\xfd\x94\x5a\x46\xb8\xb6\x69\x77\xc0\xb6\x4b\xab\xa0\x76\xfd\x0f\x3d\xa3\x31\x8e\x29\x57\x3b\x85\x5f\xe6\xf3\x3f\x33\x86\x95\x28\xd7\xd9\xa3\x93\x70\x26\x1b\x1b\x45\x9c\xd7\x94\xdb\xf0\x8d\x2b\xf3\x27\xd2\xe3\x2e\xc2\x6f\x78\x63\x91\xcf\x29\x85\xd3\x9f\x6e\x23\x3e\xf7\xcf\x7b\x3d\xf4\x89\x1c\xc6\x14\xf7\xa3\xc9\x1e\x07\xb4\xd1\x44\xb7\x78\xcc\xa9\xef\xc4\x05\x3e\x78\xf8\x25\xa5\x4b\x28\xcd\xb9\x0e\x4e\x4b\x36\x4e\x38\x00\xc8\xb5\x65\x14\x54\xff\xe6\x7c\x32\x01\xc8\x39\x1b\x3d\xc0\xb8\x32\x1e\x41\xcf\x08\xe8\x2a\xae\xd3\xae\x81\x98\xfa\xbb\x5e\x95\x86\xda\xfb\xa0\xd0\x16\x96\x5d\x7a\x3d\xfc\x17\xc8\x21\x63\x82\xd7\xf1\xf3\x2a\x07\x86\xb6\x77\x5a\x9e\x09\x2a\x82\x07\x05\x2d\xef\x8c\xc8\x8f\x6e\x07\x68\x2d\x9d\x58\x49\xb1\x97\x63\x3a\x70\xbe\x88\x16\x95\x14\x10\xd5\x64\x42\x53\x59\x78\xfb\x5e\xe1\x7a\xfd\x0f\xf1\x01\x37\x24\x4c\x77\xd4\xf3\x53\x6f\xee\xa8\x10\x2a\x6a\x41\x0d\x0e\x2d\xe0\x92\x1b\x16\xcb\x19\xb0\x73\x3e\x63\x54\x0e\xfe\x90\xfe\x65\x54\xe2\x3f\x7e\x8f\x1e\x61\x54\x4d\x51\x89\xc9\xdc\x1e\xc1\x3c\x25\x65" + +# get_exponent(Hi[i], XMR_H, i * 2) +BP_HI_PRE = b"\x42\xba\x66\x8a\x00\x7d\x0f\xcd\x6f\xea\x40\x09\xde\x8a\x64\x37\x24\x8f\x2d\x44\x52\x30\xaf\x00\x4a\x89\xfd\x04\x27\x9b\xc2\x97\xe5\x22\x4e\xf8\x71\xee\xb8\x72\x11\x51\x1d\x2a\x5c\xb8\x1e\xea\xa1\x60\xa8\xa5\x40\x8e\xab\x5d\xea\xeb\x9d\x45\x58\x78\x09\x47\x8f\xc5\x47\xc0\xc5\x2e\x90\xe0\x1e\xcd\x2c\xe4\x1b\xfc\x62\x40\x86\xf0\xec\xdc\x26\x0c\xf3\x0e\x1b\x9c\xae\x3b\x18\xed\x6b\x2c\x9f\x11\x04\x41\x45\xda\x98\xe3\x11\x1b\x40\xa1\x07\x8e\xa9\x04\x57\xb2\x8b\x01\x46\x2c\x90\xe3\xd8\x47\x94\x9e\xd8\xc1\xd3\x1d\x17\x96\x37\xec\x75\x65\xf7\x6f\xa2\x0a\xcc\x47\x1b\x16\x94\xb7\x95\xca\x44\x61\x8e\x4c\xc6\x8e\x0a\x46\xb2\x0f\x91\xe8\x67\x77\x25\x1d\xad\x91\xf0\xd5\xd4\x51\xd7\xe9\x4b\xfc\xd4\x13\x93\x4c\x1d\xa1\x73\xa9\x2d\xdc\x0d\x5e\x0e\x4c\x2c\xfb\xe5\x92\x5b\x0b\x88\x9c\x80\x22\xf3\xa7\xe4\x2f\xcf\xd4\xea\xcd\x06\x31\x63\x15\xc8\xc0\x6c\xb6\x67\x17\x6e\x8f\xd6\x75\xe1\x8a\x22\x96\x10\x0a\xd3\x42\x06\xfc\xf4\x44\x35\x7b\xe1\xe9\x87\x2f\x59\xd7\x1c\x4e\x66\xaf\xdf\x7c\x19\x6b\x6a\x59\x6b\xe2\x89\x0c\x0a\xea\x92\x8a\x9c\x69\xd2\xc4\xdf\x3b\x9c\x52\x8b\xce\x2c\x0c\x30\x6b\x62\x91\xde\xa2\x8d\xe1\xc0\x23\x32\x87\x19\xe9\xa1\xba\x1d\x84\x9c\x1b\xb4\x46\xbc\x0b\x0d\x37\x76\x25\x0d\xd6\x6d\x97\x27\xc2\x5d\x0e\xfe\xb0\xf9\x31\xfc\x53\x7a\xb2\xbd\x9f\x89\x78\x21\x6f\x6e\xb6\xe4\x23\xfa\xe0\xd3\x74\xd3\x4a\x20\x69\x4e\x39\x7a\x70\xb8\x4b\x75\xe3\xbe\x14\xb2\xcf\x53\x01\xc7\xcb\xc6\x62\x50\x96\x71\xa5\xe5\x93\x73\x6f\x61\x13\xc3\xf2\x88\xec\x00\xa1\xcc\x2f\xc7\x15\x6f\x4f\xff\xa1\x74\x8e\x9b\x2c\x2d\xdf\x2f\x43\x03\xbb\xfe\x7f\xfc\xee\x5e\x57\xb3\xb8\x42\x06\xa9\x1b\xcf\x32\xf7\x12\xc7\x5e\x5f\xa5\x10\x87\x85\xb8\xcc\x24\x47\x99\x83\x12\xca\x31\xab\x85\x00\xc8\x2c\x62\x68\x45\x39\xa2\x70\x01\xfb\x17\xf2\xa5\x64\x9d\xb2\xe2\xd6\x4b\x6b\x88\xf0\xd6\x81\x00\x9a\xe7\x8e\xae\xce\x9c\x73\x57\x80\x2c\x6c\x1c\xd8\x1e\xf6\x24\x86\x89\x85\x40\x89\xaa\xd6\x94\x47\x33\x91\xba\xd6\x18\xef\x01\xdf\xd6\x80\x98\x1a\x78\x97\x18\xe9\xd7\xca\xef\x06\x3d\xeb\x2d\x67\x5f\xe8\x43\xea\x63\x4d\xcf\x96\x77\xc1\xd3\xee\x92\x51\x39\x71\xb7\x24\xc7\x88\xe4\x10\x7a\x42\x40\xfe\x26\xe5\xfb\x36\xcc\x00\x7e\x76\x58\x96\x48\x82\xf7\x69\xf1\x8c\x78\x6a\xb1\x52\xf2\x5c\x5d\x2a\xe4\x72\xf7\x1e\x40\x13\xc4\xb0\xc5\x78\x7d\xc1\xd7\x8b\xdc\x8d\x52\x33\x10\x39\xaf\x41\x24\x11\x2e\xe9\x34\x6f\x11\x0a\x4e\x81\x18\xe8\x64\x11\x5d\x49\xb0\x82\xc8\x38\x51\xd4\xd5\xe1\x10\xa4\xab\xda\xdd\xbd\xa9\xb0\x22\x7f\x5b\x26\xbf\x52\xd5\xa2\x25\x25\x23\x59\x72\x84\x3d\xe9\x1d\x99\xd0\x09\x1f\x17\xf4\x78\x2d\x4f\xeb\x2b\x76\x0c\xd5\x8b\x6f\x24\x76\xe8\xb0\x2d\x90\x8a\x15\x15\x07\x8a\xa8\x08\xaa\x3a\x56\x5e\xfc\xb7\x16\x9f\xe0\xcb\xf7\x2c\x12\xce\x17\x50\xf2\x86\x1f\xb6\xc6\x85\x16\x13\xcb\xe9\x74\xef\xc1\x68\x4a\xeb\xbe\x8b\x8a\x52\x2a\xbb\xe7\x82\x77\xd0\xda\xa7\x89\x2d\x9d\xa8\x7c\x27\xbe\xcd\x3e\xc0\x38\x95\x23\x3a\xd4\x66\x31\x8c\x44\x3c\x4d\x6d\x5c\xf1\x2e\xba\x7d\xbd\x3e\x84\x32\x9d\xf6\x1a\xfc\x9b\x7e\x08\xfc\x13\x32\xa6\x82\x34\x42\x73\x39\x6e\xc7\xdc\xdc\xbe\xae\x48\xff\x70\xa1\x9a\x31\xd6\x62\x44\x3c\xce\x57\xf7\x7a\xfe\x05\x0b\x81\x22\x48\x60\x25\x5b\xcb\xc8\xf4\x80\xc4\x3c\xfd\xeb\xb1\xb2\xa6\x89\x72\xb7\xd3\x32\x3b\x03\x61\xf3\xa1\x14\x2f\x8b\x45\x2e\x92\x98\x77\x3d\xef\x56\x35\xc2\xe2\xef\xa3\x70\x0e\x4c\xc9\xe5\xd8\xde\x78\x96\x7e\x57\x35\x82\xcf\x7c\x74\x97\x7c\x30\xb5\x46\x9b\x2c\x0b\xac\xe8\xec\x25\x9f\x71\xba\x25\xc8\xdd\x1c\x51\xe5\xb0\x24\x1c\xca\x7c\x86\xf7\x18\xb7\xd2\xc3\xd4\x57\xa6\xe5\xe0\xb3\x9f\x1f\x39\xeb\xaf\xbb\x08\x83\xd4\x27\xd9\x36\x47\x60\x15\xad\x88\xb7\x92\xa0\x31\xe4\xdd\x98\x37\x57\xc9\x9a\xea\x39\x12\xe8\xf8\xc2\xf6\x59\xde\x4b\xc1\xa2\x20\x4c\xea\x13\x2e\x4f\x9e\xf7\x17\x77\x11\x91\x53\x63\x9a\x71\xff\x24\x17\xf5\x22\xfe\x41\xb8\x7e\x9c\x1c\xb7\x66\x9f\x40\xf9\xd6\x85\x88\x7d\xff\x81\x92\x7a\xa4\x2e\xda\x7f\x2a\x69\x67\x89\x09\x10\x33\xcf\x5b\xe2\xfc\x1f\x5f\x3a\x2d\xe2\x27\x15\xeb\x33\xd6\x28\x28\x92\x2d\xac\x86\x2e\xfc\x7f\xc6\xd5\x4c\x99\xe6\xec\x6e\x58\xc0\xb6\x4d\xa9\x57\xe7\x36\xd3\x00\x93\xc8\x67\xa1\x20\xd5\xdb\xfc\x55\x03\xca\x27\x64\x05\xdf\x4b\x2d\xbe\x6c\xfe\x7c\x2c\x56\xbc\xd2\x66\x9f\x1b\x7d\x82\xc9\xf9\x29\x91\xbf\x41\x02\xaf\x61\x10\xbf\x1b\xf5\xbd\xae\x89\x7f\x9a\x06\x42\x09\xcf\x31\x29\x96\x53\x13\x7e\x86\x5f\x90\x5c\x89\x29\x44\x91\x39\x54\x5a\xc8\x25\x3c\x32\xbe\x19\xcc\x8b\xd8\x54\xca\x7c\xdb\x07\xc2\xae\xba\x12\xa1\x4c\xcf\xa3\x08\x5f\x9f\xfd\x9f\x75\x39\x80\xc9\xd4\x5b\x7b\x4e\x0f\x5b\xe4\x6d\xf3\xae\x5c\x10\xc1\x89\xf1\xdc\x9e\xd2\x59\x2e\x24\x6b\xd2\x44\x9a\xa0\xda\xae\x45\x8a\xe8\xbf\xbd\x52\xf9\x83\xc3\xde\x44\x12\x37\x26\x71\x9c\x08\xd4\xc3\x7c\x8c\x9b\x0b\xe1\x7b\x6b\x49\x82\x61\x36\xaa\x7b\x90\x85\x31\xbc\x91\x73\x2b\x08\x7a\x41\x36\x03\x0b\xad\x7b\x5b\x1c\xfa\x7d\x9c\x98\xa9\xdc\x34\x7a\x92\x65\x1f\x29\xc2\xe1\x10\xaf\xf8\x89\x7f\x26\x7c\x04\x22\x10\xa6\xb7\x0a\x31\x3c\xc0\x6a\xfa\x2b\xd9\xc2\x91\x15\x37\xd6\x09\xd6\x8b\xec\x94\x32\xe8\x4b\x96\x79\x52\x7d\x6a\xbb\x58\x8b\xa7\x2b\xb2\x14\x98\x70\x69\xd8\x0b\x0a\xbc\x2b\xbd\x68\xeb\xa0\x33\x1e\x3a\xe5\xf4\x10\x6f\x7f\xc1\xe2\xe7\xb8\xd6\xe5\x37\x0e\x32\x01\xcc\xe2\xa0\x36\xb6\x8e\xd3\x54\x31\x63\x39\xf0\x92\xde\xc7\x66\x2b\xce\xbd\xd2\x06\x61\x11\xd1\x6c\xe5\x5a\x93\x7e\x2c\x61\x90\x7b\xc3\x66\xc8\x85\xda\xa3\x74\x95\xbe\x67\x1e\xf6\xc2\xf2\xe5\x54\xed\xe3\xb5\x3c\xe2\x80\xcb\xe8\x8a\x48\xb9\xd9\x74\x0e\x98\x0c\xea\xf8\x04\xed\xcd\x8c\x96\x85\x81\x93\xe6\xd5\x17\x8b\xf6\x04\xcc\x73\xbd\x8f\xaa\xd5\x0d\x53\x15\x49\x99\x31\x97\xcc\x27\x28\x27\x21\x6d\x1a\xf9\xdc\xc6\xe9\x86\x2a\x6e\x53\xa0\xa2\xc7\x32\x98\xe1\xfa\xdc\x0f\x91\x48\xcb\xc8\x5e\xc0\x56\x7c\x38\x76\x9c\x27\x65\xd6\x54\xc4\x26\x9f\x6e\xf1\x39\x47\xf1\x3c\x23\x9c\xbb\x08\xb7\xcf\x67\xa2\x5b\xac\x03\x0a\xd1\xb8\x92\xc4\x34\x79\x24\x64\x49\xf5\x32\x8d\xac\x31\x41\xd3\xd7\xc8\xa9\xa2\x54\x0d\xca\xc2\xcb\xc9\x8e\x27\x84\x31\x43\xe7\xd4\xb9\x6d\xde\x75\x21\xfc\x70\xb3\x28\x0a\x2a\x4c\x5f\x39\x28\x7f\x5d\x24\xd7\xa7\x59\xea\x03\x7b\x11\x44\x87\x39\xee\x2a\x28\xfc\x4b\x16\x0e\xac\x40\x61\x08\xae\xe6\xb5\x80\x62\x13\x11\xfe\x03\x0b\xf0\x8b\x4f\x6e\xed\x3d\x7d\x3d\x86\x93\xd3\xac\x52\x4d\xa2\xb4\xeb\xf1\x9e\x25\x59\xdc\x50\xff\x35\xe6\x2d\xa6\x20\xdc\x0a\x02\xed\xcb\xe4\xf3\x98\xb1\xbd\x86\xea\x15\x4b\x6a\x94\x00\x57\x9e\x3f\x1c\xd5\x7f\xdc\x2f\x10\xbd\x8c\xdb\x16\x7c\x0b\x28\x3f\x90\x07\xe6\x20\xd9\xca\x28\x06\x7f\xe2\xb0\x15\xed\x65\x7c\x91\x53\xb8\x44\x3d\x77\xe8\xe2\x5f\xf3\x48\xf4\xdf\x78\xbb\xc1\xce\x20\xa7\xba\xbd\xe4\x0e\xd2\xbd\xbe\xaf\x2b\x5c\xd9\x8e\x52\x02\xba\xf7\xe3\xdc\xf1\x8b\xa1\x15\x62\x0c\x51\xae\x8b\x58\xb4\x92\x3b\x9a\x86\x94\xc9\x3d\xf6\x4b\x17\x8c\x4c\xd2\xf9\xf6\xef\xc5\x1f\x45\x8b\x0c\x5e\xe8\x60\xa4\x0a\xc8\xce\xc3\x50\x6e\xc8\x5b\x99\xdc\x71\x6b\x95\xcb\xb3\x42\xdb\x91\xad\xe4\xb6\x1e\x17\x7f\x60\xf9\xfa\xbb\xff\x2c\x9b\xad\xee\x04\xcf\xd7\x41\xd6\x6d\x2f\x26\x32\x1e\x2c\xf5\x0a\x3c\xd0\x21\xf6\x28\x88\x63\xde\x2d\xad\xf8\xd5\x2d\x1f\x8b\x9f\x51\x42\x43\x05\xa3\xd4\x07\x96\x29\x63\xc1\xd0\xbe\xeb\x81\x13\xf8\x03\x07\xec\xc2\x19\x23\x94\x7f\xe8\xcb\xaf\x5c\x2c\x05\xae\x63\x69\x85\x21\x99\xc5\x2a\x17\x97\xb9\xaf\xf2\xa9\x24\x5d\x7a\x8b\x91\x72\xd5\x72\xb4\x43\x2f\x63\x44\x1f\xf5\x1c\x4a\x4e\x27\x0e\x3b\x61\xea\xe6\xe1\x3e\xef\xe3\x5e\x85\x42\x7b\xc7\x58\xef\x4a\xf4\xc0\x0f\x9c\x77\x52\x1c\x03\x61\xd2\x99\x43\x1f\x9d\x8e\x29\x8c\x13\x41\x4c\x46\x17\x0a\x1d\x82\xa1\x38\x0f\xba\xfe\x53\x1c\xa7\x01\x84\xab\x89\x65\xc4\xc8\x07\x06\x0e\x80\x39\xfe\xc4\x61\x5e\x59\x09\xd2\x7a\xc5\xca\x80\x41\xe3\xf9\x5b\x27\xf1\xc3\xd4\xd4\x06\xa2\x04\x8b\x1e\x6c\xe1\xe6\x37\xcb\x87\xc0\xf9\x7d\x36\x17\xd4\x6a\xef\xfd\xd1\xe8\x13\xc2\x55\xfb\x8b\x3e\xf9\x39\xa2\xc5\xfa\xd4\xd1\x09\x73\xc0\x8c\x05\x5f\x79\x13\xc5\x16\x64\x58\x9d\xa5\x14\x5a\x9c\x59\x72\xf4\xb2\x12\xeb\xf5\x11\x71\xd9\x23\x43\x83\x3a\x08\x95\x3c\xd8\x0c\xd0\xd9\x08\x90\x4c\x56\x3e\xdc\x34\x29\x42\x21\x86\x56\x33\xd8\xcf\x6f\xf5\x04\x44\xb9\xd2\x9b\xeb\x05\xa4\x7b\x8b\xb1\x21\xcb\x11\x8d\x6c\xb1\x6b\x24\xc4\x45\x09\x8a\xa9\x0e\x6d\x5a\x10\xea\xe0\xa0\xf3\x97\x7a\x28\x08\xf7\x9c\xaf\xe8\xf8\x70\x52\x97\xbd\x91\xeb\xbf\x27\x92\xa1\x89\x2c\xb0\x09\xdb\x0b\x7a\xc3\x51\xd0\x35\x3f\x43\xfe\x3a\xa9\x71\x92\xe8\xb9\xd7\xfe\xf5\xba\xec\x41\x5b\x0c\xa4\x8c\x92\x0e\x7c\xdd\x78\xf9\x24\x6a\xd2\x54\xe8\x7e\xe1\xb0\x65\x84\xb8\x60\xb0\xb8\x80\x0a\xae\xe1\x78\x96\xf0\x29\x0c\xb7\x89\xb0\xd7\x9e\xcd\x7d\x04\xcd\xed\xa7\x10\xef\xa5\x5e\x76\xe4\x73\x14\x85\xba\x1f\xf8\x6a\x31\xfa\xad\xfa\xf5\x62\x8f\xbf\x17\x11\x34\x6d\x57\x32\x54\xba\x0f\x56\xb5\x38\xf7\x5b\x6f\xc4\x32\x17\x45\xed\x42\xa3\x19\xa8\xef\xb8\x68\xa6\xf3\x5a\x64\xa6\xc7\x44\xa6\x96\xc9\x1a\x61\x12\x5f\xad\x25\x92\x02\x02\x56\xfa\x8a\x61\x41\xa7\x4b\x9d\x49\x62\xde\xec\x53\xab\x75\x14\xca\x3a\xce\xde\xd5\x24\x99\x7a\x9a\xeb\x7c\x43\x0f\xbf\x01\xd9\x36\xfd\xc7\x6f\x55\xee\xcb\x74\x96\x32\x48\xdf\xb4\xa1\x5f\x71\xf7\xa9\x30\x35\x14\x3c\x8f\x42\xe3\x1a\xed\x71\xbd\xec\xd9\x53\x54\x24\xe7\x7d\x31\x48\x49\x1f\xf7\xe4\x63\xf6\x38\xa6\x2d\xad\x56\x95\xef\x93\x4e\x96\xfd\x5e\x21\x30\x4a\xce\x05\xc4\x2a\xf9\x86\x60\x77\x81\xab\x57\x66\xc1\x83\x83\x07\x1f\xe3\x5f\x16\xa5\x89\xd4\xe4\x81\xa9\x32\x21\x7c\x3b\xc5\x62\x8d\x67\x29\x3f\x50\xbf\xfe\x46\x98\x8e\xe1\x0c\x3e\x30\x58\x6c\x77\x45\x1b\xa7\x58\x08\x46\x4d\xc1\xac\x64\x50\x2e\x8f\xde\x87\xb5\xd3\xc8\x97\x7e\xe9\x0d\x63\x7d\x4b\x2d\xb9\x2a\xce\xd2\x0c\xdc\xaf\x5d\xa0\xad\x15\x4a\xdc\x68\x2d\xd7\x2f\x0f\x33\xbc\xf6\xbd\xcc\xe6\xfe\x17\x8c\x82\xca\x18\x3d\xce\x8c\x82\x37\xf9\xf5\x8f\x84\xb4\xb1\x08\xf7\x97\x85\x71\x08\x31\x17\x66\x84\x02\x97\xca\xcb\x0d\x7d\x2a\x28\xb4\x47\x55\x5f\x39\xa1\xda\x0d\xda\x87\x5e\x9d\x06\xe0\xa4\x8e\x2b\x63\x7a\x8f\xd3\x6f\x34\xe5\x6e\x30\x28\xde\xeb\x36\x36\xc1\x4f\x53\x87\xcc\x83\x2b\xee\xe7\xad\xd4\x1d\x8a\xbd\x1f\x6a\x50\x3d\xbf\x2e\xf9\xca\x55\xe5\xdb\x15\x9a\x02\x4f\xb4\x6f\xa8\x9c\x00\x32\x23\x30\xa0\xee\xc9\xd8\x67\x3a\x8c\xf5\x60\x0d\xb4\x77\xac\xae\xd4\xaf\xa2\x41\x13\x42\x9e\x21\x24\xda\x81\x07\xda\xe9\x46\x7c\x5f\x97\x6e\xb0\x1d\x36\xad\x57\x5e\xe4\x48\x6e\xbd\x2c\xa6\xb0\xd9\x34\xe1\xf4\xcd\x6e\xcc\x0f\x0f\x10\xa5\x44\x43\x88\x03\x15\xce\xb9\x7f\xcd\x3f\xa2\x09\x3d\xbe\xab\xef\xe5\xd4\x9b\xe6\x7e\xca\x9f\x4a\xf0\x2f\x64\xa8\x13\xc3\xa3\x75\xbe\x11\x50\xa2\x8c\x45\x27\xff\x67\x5d\xc0\x76\xf4\x4c\xe1\x9c\x6f\xa2\x81\x06\xb7\x97\xc9\xc0\xcb\x32\xc5\x90\xf7\x6c\xaa\xa9\x51\xa6\x70\x9d\xc3\x75\x26\xcc\xc3\x06\x16\x53\x94\x3d\x4b\x4f\xc6\x1a\xc6\x02\x2a\x63\xde\x69\x5a\xd2\x3d\x95\x26\x62\x9a\x6b\xf6\xff\x33\x82\xd4\xbe\x95\x49\x11\x27\x53\x1b\xb6\x9e\xa3\x69\x9c\xac\x5f\x21\xfe\x65\x2e\x10\x8e\xcc\xd0\x5d\x9a\x10\xc0\x42\x3f\x72\x2f\x97\x1c\x84\x1d\x0f\xe0\x2a\x82\xce\x3f\x65\xca\x23\xd0\xde\xde\x39\x26\x4b\xf2\x7f\x9c\x70\x52\x66\x36\xfa\x49\x3e\xbc\xe0\x6e\xd2\x90\x5b\xeb\x2d\x66\x32\xe8\x09\x7c\xc1\xc4\x6a\x48\x79\x33\xf4\x63\x30\xa0\x48\x58\x20\x21\xb0\xab\x0c\x6d\xf2\x4c\x54\x7b\x99\xe9\x8a\x19\xfe\xcd\x8d\x18\x67\xd9\x99\xf4\xf5\x9d\x7b\xca\x19\x13\xb2\xcf\x74\x6d\xd3\xbe\xa6\xec\x65\xb1\xed\xc2\x0c\xde\x46\xad\x02\xcf\x55\x59\x97\xf0\xb1\x88\xc0\xeb\x02\xab\x4c\x8a\xff\x11\xc2\x02\xc1\x9f\x16\x07\xcf\x2e\x8d\x64\xa7\xbf\x6f\x21\xc2\x4f\x1e\xb1\x25\xbc\xc6\x26\x4a\x35\xb7\x81\x72\xe4\xf9\xd3\x4b\x12\x0a\x36\xf2\x54\x9f\x6b\xac\xa4\x56\x85\x20\xb5\x7a\xe0\xd0\xb1\xce\x5d\x3b\x2b\x79\x32\xb8\x11\x0f\xae\xcd\xab\xb3\x64\x55\xeb\x5a\xd3\xec\x87\x5d\x16\x34\xf6\xc5\xb6\xb9\xa5\xb0\xd3\xa0\x32\x6a\x79\xc0\x34\x08\xb8\x18\xe7\x30\xe1\x9c\x22\xe9\xfb\xda\x0a\xd0\xee\xb7\x85\x19\x8e\x18\xad\x6e\x98\xe4\x44\xbd\xd5\xde\xda\x8c\xe9\x0b\xa9\xe8\xfd\x27\x6f\x4e\x13\x5a\xe5\xb2\x75\x1e\xc4\x0b\xf3\x5d\xb6\xc3\xd2\x75\x33\x8b\xb9\x59\xa0\x75\x0d\xe7\x00\xb0\x74\xab\xc4\x1b\x12\x6c\x69\x0c\xae\xed\x54\xa2\x96\xd9\x01\x22\xdc\xf4\x8a\x7a\x81\xe1\x3f\x40\x1b\x7a\x58\x52\x68\x58\x68\x39\xf7\xca\x16\x6a\x46\xf2\x98\x9b\x72\x25\x8d\xc8\x28\xc2\x3d\x40\xaf\x74\x9b\x4d\xb9\x45\xd3\xfa\xe8\x83\x3c\xcf\x22\xe5\xf4\xc3\xa2\xe9\xab\xf2\x27\xdc\x00\x4c\x1f\xc5\xe4\x96\x97\x65\x57\xed\xd7\x09\xd4\x1a\x7e\xd6\xaf\x6b\xe4\x8d\x7e\xde\x5e\x8e\x6d\xe3\x1d\x2f\xe8\x12\xd7\x77\xeb\x0d\x59\xa6\xe9\x15\x52\x20\xb2\x0d\xd8\xd4\x3a\xa3\xaf\x50\x99\x49\x3b\x25\xfb\xa1\x33\x7d\x4e\x79\xec\x2c\x10\xb4\x8f\xad\xbb\x4c\x93\xb9\x32\xe8\x06\xcb\x49\xd9\xfa\x17\xb1\xd8\x63\x17\xc0\x98\x03\x2e\x16\x46\x63\xc4\x66\x9a\xcb\xee\xb4\x38\x45\xdd\x2d\xed\x34\x1a\x5f\x05\x6b\x8a\x2b\x71\x02\xfe\xc1\x8c\xaf\x25\x04\xbc\x37\xc0\xd4\xc9\xb3\x8f\x1b\xf3\x62\x38\x1e\x13\xa5\xa3\x93\x9a\xe3\x06\xe8\x21\x62\x0d\x07\xa9\x00\x88\x01\xe9\x69\xad\x40\x86\xc1\x11\xa6\xa1\xfb\xd2\x29\xb8\xfd\x06\x09\xcf\xd0\x8a\x01\x9c\x0f\x46\x38\x2a\x7a\x91\x89\xe2\x96\x8f\x00\x12\xb7\x0f\xc7\x73\x45\x1c\xed\x06\x5c\x0f\x80\x48\x17\x1c\x0f\x5d\xfc\x96\x5b\x22\x60\x53\x0f\xb0\x50\x47\x5b\x00\xea\x42\xcf\x37\xf0\xd4\x28\xe0\x1e\x76\x1f\x17\x29\x29\x4f\x07\x0e\xaa\x8e\x15\x5e\x2e\xa0\xd9\x6c\x96\x54\x81\x8a\x6e\x5f\x15\x23\xac\x3b\xd8\x7b\xf2\x56\x34\x86\x1d\x6a\x9e\x28\x6a\x3f\xa3\xfb\x26\xaf\xb4\xe5\x54\x45\x85\x31\x9c\xf0\x23\x08\x73\x07\x16\x91\x5e\x70\xb3\x67\xb2\xba\xa9\x50\x2c\xb7\x1d\xe0\xcb\xc4\xc4\xde\xab\x33\xc4\xb9\x81\xd8\xf8\x0c\x95\x5d\x9e\x0d\xaf\x9b\xb6\x1b\x05\xb4\xc7\xe5\xe1\xf9\xa8\x09\x36\xd3\x4b\xb8\x64\x57\x59\x26\xe7\x46\xd7\xd3\x4e\x76\x71\xe0\xb8\x24\xb1\x8e\x7a\x31\x61\x67\xee\xc9\x87\xf1\x4b\x95\x5e\x35\x7f\x99\x1c\x89\xd2\xfa\xfa\x2a\xc6\xdb\x87\x6c\x7f\x35\x7f\x06\xe4\x82\x36\x5e\xe9\x1c\x15\xa9\x23\x95\xa1\xa7\x63\xa1\x3b\xff\x1a\x36\xde\xd2\x10\xc6\x1f\xc7\xac\x8c\xf4\x44\x8d\xbb\xb4\x93\x5e\x84\x0b\xc6\xbf\x11\x3f\xd2\x43\xbd\xf8\x74\xac\x91\x64\xa3\x14\x31\xcc\x4d\xaa\xf5\xc0\x3d\xb9\xbc\x80\xd9\x1e\x4f\x2c\xaa\x8c\xec\xe5\x8d\x41\x5f\x26\xb0\x03\xbf\x5b\x74\xa1\x7d\x8b\x8e\x52\x9a\x64\xe4\x14\xd0\xbd\x88\x49\xa6\x2a\x88\x45\xe7\x51\xcc\x3c\xba\xa4\x21\xc4\x67\x90\x0e\x52\x99\x0c\x35\x86\xda\x11\xab\x33\x77\x2d\x9e\x13\x44\x6c\x71\x4c\x66\x78\x00\x4d\x72\x5e\x02\x87\xda\xad\x19\x6d\xa1\x23\xab\x11\x38\x60\xce\x49\xf5\x0b\x12\xe6\xdf\xaf\xa0\xc0\xa8\xce\x79\x9c\x8c\x40\xd1\xe6\x8d\xef\x09\xec\x1d\x62\x63\xe5\xe8\x52\x08\x77\x28\xd5\x50\x8e\xdb\xbe\x17\xa3\x76\x87\x13\xa4\xb7\x5e\x49\xd6\x99\xd8\x30\xc7\x59\xcc\x83\xd5\xa8\x95\xe9\x6e\x42\xdd\x26\x8f\x0b\x7c\x14\x73\x3d\xba\x87\xb1\xa9\x47\xe3\xc0\x0e\x7e\x22\x8b\x63\x07\x06\xd4\x62\x93\x88\x9e\x9f\x53\x9b\x58\x93\x3b\xbd\xea\x70\x8c\x30\x10\xc1\x8a\x01\xb4\x5e\xb0\x3e\xa5\x06\x37\xb7\xb3\x3a\x07\x9f\x3c\x73\x06\x09\xab\xa4\x20\x12\xcd\xab\x7f\x79\x11\x17\xf6\xdb\xe4\x3d\x1a\x6f\x06\x1e\xee\x27\x0f\x3e\x4e\xc2\xfb\xfd\x7c\xab\x58\x45\xff\xdc\xa3\xd4\x5c\x5d\x3d\x99\xda\x7d\x74\xe9\xb5\x55\xa6\x59\x7c\x43\x72\xe1\x79\x61\x6c\x36\xdf\xe9\xcc\x48\x7a\x97\xa7\x9b\x6f\xf0\x2a\xa8\x12\xa6\xad\x55\x37\xba\x99\x15\x35\x03\x25\x4d\x00\xe2\x33\x53\xc6\x0a\xf1\xe1\xc4\x8a\xac\x02\x38\x17\x6d\xff\x63\x73\x04\x73\x35\xaa\xd4\x16\xae\x20\x50\x59\xef\xf6\x67\x14\x8d\xe2\x9d\x2a\x6d\x24\x42\xd7\x76\x1c\x91\x9b\x3d\x11\x07\x30\x4a\xa6\x54\x0a\xf0\x55\x20\x70\xd8\x3e\xa5\xe3\x49\xc3\x6e\x80\x2c\xbc\x05\x35\xdb\x5c\x55\xa0\x3c\x10\xe3\xd5\xc5\x61\xa4\xb7\x1f\xcb\x25\x03\x0f\x04\xfd\x00\x5d\x31\xfb\xe2\xc4\x89\x96\xc6\x28\x5b\x74\x3d\x8d\x62\xb1\x3b\x7f\x58\xb9\x2f\xf3\x9e\x58\xe7\x04\x4a\x8d\x39\xa0\x69\xb4\x3a\xbe\xe2\x59\x2e\x28\x71\x81\xb0\x17\xeb\x2b\x91\xa8\xb1\x48\xbd\x71\x3b\xd6\xe1\xc5\x2b\x2f\x08\x8e\x38\xec\xe1\xdc\x9c\x0f\x92\xe9\xa5\x39\x65\xa0\x8f\x19\x5e\xb5\x1a\x74\x36\xc1\x2a\x68\xd6\xc6\xee\xec\xe2\x6f\xc8\x32\xc8\x51\x81\x51\x39\x78\xfc\x4d\xa4\x63\x61\x8b\xda\x41\x27\x37\x03\xb2\xee\x10\x7c\x3c\xf0\x24\x55\xcc\x32\xc4\x4b\xc1\x8a\x7a\x1b\xb4\xf6\xf3\xff\x6f\x7d\x03\x89\x6f\x6c\xea\x1a\x8f\x5b\x1b\xf7\x49\x68\x69\x44\x7b\xaf\x5f\x8f\x34\xb8\x75\x30\x0a\x6f\xc7\xde\xb9\xf4\x67\x29\xc8\x36\xaa\xb0\xfc\x82\xb3\x24\x1b\xde\x63\x4b\x3b\x95\x78\xf8\x0f\x08\x4b\x8f\x17\xcb\x85\xee\x39\x2a\x79\xec\x92\x8a\x6a\x57\xd1\xba\x9a\x64\x8a\x00\x9f\x21\xbb\x21\xc1\xf3\x59\x68\xe9\x70\x46\x13\xa0\x9c\x53\x5a\x27\xb6\x7b\xb6\xd8\xdd\x3b\xff\xe2\xdb\x01\x10\xce\x90\x8f\x24\x70\x0b\x31\xc0\xdb\x03\x1c\x50\x98\x43\x41\xc3\xe2\x51\xf3\xea\x2d\x5b\x81\x35\xd1\x81\xd2\xb6\xd2\x62\x60\x93\x5d\x70\xe3\xcc\xf6\xa2\x2b\x54\x0f\x5c\xac\x73\x05\x91\xe0\x8c\x72\xa2\x05\x69\x8c\x9b\x2d\x3c\xf9\xa0\x4d\xf9\x94\x1c\xb4\x85\x45\x4e\xac\x12\x14\x24\xd7\xfe\x86\x56\x82\xd3\x82\xaa\x31\x1b\x38\x56\xdc\x28\x9f\x91\xf4\x5e\xef\xe0\xb8\x19\xd5\xdb\x86\xe8\xac\x5e\x8b\x54\x7e\xa2\xbe\xbf\xb8\xac\xf1\xbc\xa2\x92\xac\x1c\x84\xb6\xa3\xa3\x28\x30\xc8\x4d\xd5\x45\x14\x75\x74\xf6\x97\x8c\x04\x62\xc2\xcf\x43\x90\xe8\xe5\x94\xf1\xcf\x0e\x3c\x58\x28\xfa\xfd\xdb\x4b\x8a\x36\x82\x3d\x1c\x38\x9c\x30\x4f\x9c\x20\x5f\xda\x6a\x7e\x88\x44\x7e\xd4\xe0\xdb\xfc\xc6\x81\xb5\x86\x0b\xe0\xed\x2e\x90\x99\xc3\x0b\xfc\x57\x9f\x1a\xfb\x04\x94\xa0\x1e\xc1\x98\x88\x3f\xc2\x1c\xea\xcd\x14\x8d\x5b\x21\x1a\x87\x2f\xb3\x63\xb9\x9a\x4b\xf0\x86\x43\x02\x9d\x4c\x2f\x2c\xa1\x53\xe5\x6e\x91\xe8\xb3\x72\x75\x54\xcf\xb9\x1b\xba\xcb\xb1\xc8\x5b\x18\x18\x38\x36\x13\x58\x47\x32\x6b\xe6\x96\x29\x56\xd3\x5e\x4b\x47\xcf\x15\xd6\x26\x26\xf7\x4e\x89\x78\xea\x80\xe0\x40\x3e\xf5\x3a\xd1\xac\x48\x64\x2e\x3f\x25\x6d\x82\x66\x8c\x47\xd5\x05\x17\x5d\xdb\x7f\xdd\x83\x9a\x91\x41\xa3\xf0\xd9\x1a\x71\x51\x7a\x36\x39\x6e\x9e\x3c\x20\x55\x99\x58\x44\xdf\xed\x40\x96\xc6\xec\xd9\xfc\xef\xe1\x40\x57\x83\xe8\xa6\xd8\xc1\x86\xc9\xd4\xa6\xc9\x2d\xf7\xd2\xc6\x08\x16\xf7\xba\xbc\x34\x5c\x2e\x79\x1a\x88\x11\x39\xf4\xd7\x4c\x5d\x43\x0f\xa2\x4b\x1f\xbe\x95\xb8\x23\x7b\xce\x02\x61\x5a\x19\x27\x98\x83\x75\x3d\x41\x6d\xa9\xdc\xd5\x1a\xc6\x1b\xef\x11\x29\xf5\xc8\xd0\x6f\xba\x4a\xfa\x2a\x4b\xc7\x5a\x72\xfa\x34\xfc\x7c\xcb\x1e\x91\x09\xb0\x2b\x1d\x00\xd6\x18\x78\xc7\x14\xb0\xde\x7e\x1a\x1c\xba\xac\x36\xe6\x6c\xb5\x9f\xb3\x0e\x38\x5c\xee\x9c\xb9\xd4\x88\x2b\x40\xad\xaf\xee\xc9\xb4\x61\x87\x51\xbd\x2f\xd2\xdb\x07\xb6\x4d\x42\xcf\xd8\xf6\x73\xdc\x25\x75\x12\xfd\xd8\xf8\x62\x7b\x6a\x67\xa6\x81\x5c\xf9\x41\x31\xa2\xf0\x44\x96\xd6\xae\xd9\xed\xfd\xc9\xc3\xde\x43\x59\x46\x91\xaf\x23\x42\xd8\xb0\xc3\x7e\xb5\x26\x76\x42\x1a\xe9\xfc\xf6\x84\x57\x8b\x3f\xd7\xfb\x1b\x3e\xe0\x1a\xd5\xb2\x71\xdd\x6b\x72\xe0\x92\xf7\xfb\x99\xe7\x5c\xb8\x2d\x59\x94\x7a\x65\x70\x5a\x1a\x5e\x64\x0c\xe1\xd7\x82\x45\x0d\x2e\xa4\x9d\x4d\x80\xf1\xbb\x9e\xb0\x1c\x8c\xcc\xf2\x66\x50\x8b\xfe\x01\x24\x67\x59\x69\x1b\x03\x22\x34\x2e\xbb\x5d\x28\x66\xd5\x2d\x09\x89\xb5\x4f\xcf\xef\xc1\x07\xda\x24\x48\x5f\x83\x75\x1d\x2b\x72\x5d\x82\xde\x16\xa6\x2a\x04\x08\x30\xee\x01\x2f\x6c\x20\x88\xd9\x88\x24\xf7\xaa\xa7\x1e\xda\xd0\xa9\xe6\x8a\x33\x3b\x86\xd0\x10\x54\xba\x9c\x79\xda\xa7\x8c\x73\x33\xab\xb5\xc5\x6c\xf7\x7a\x13\x9b\x5e\x85\x6a\xa2\xe9\xfd\xf7\x8e\x5d\x35\x5e\x6e\x82\xab\xb1\x08\xbd\x4c\x2a\xd7\x32\xca\xaf\x6e\xfa\xe2\x39\x85\xb2\x4d\x6a\x4a\x02\x16\x63\x3f\x5e\x97\x0f\x16\x57\x3c\x15\xfa\x2f\xad\x51\x1e\x09\x1e\x4d\x5f\x8e\xf7\x14\xec\x22\xbb\xe6\x82\x9e\xf9\xf6\x04\xb1\x2d\x03\x2c\xcc\x1f\x5a\x3a\x83\x25\x5e\x77\x8a\x8d\x90\x02\x68\x66\x66\xf6\xde\x05\x1c\xd1\x20\x7e\x7a\xf6\xe8\x1c\xa9\x96\x39\xcd\x64\x82\xa1\x3a\xc4\x89\xd1\xa2\x5a\xca\xd6\x18\xc5\x70\x87\xab\x02\xf4\x81\xca\x50\x12\x6c\x92\x25\xdd\x0c\x37\xc2\x93\xae\x3b\x50\x15\x5c\x72\xfb\xaf\xe5\xa5\xc2\xd0\x45\xc8\x80\x0a\xaf\x0e\xa9\x4b\x89\x85\xf3\x1c\x25\x53\x8f\x73\x0d\x33\x91\xa2\x36\xa7\xd9\x08\xbf\x00\xd7\xdd\x97\x3a\x8b\x64\xc4\x34\x09\x4e\x04\x5d\xf9\xf9\x63\x50\xef\x5f\x86\x84\x3a\xfd\x10\x98\xbb\x6c\x8d\x7b\x59\x37\x53\x79\x0c\x30\x19\xd2\x7d\x3b\x9f\xe8\xe8\x6d\x1f\x95\x8d\x4b\xdf\x15\x14\x87\xa5\xbe\x3d\xfd\x74\xf5\xa3\x98\x0c\x76\x48\x94\x26\x90\x2a\x2d\xdf\xb4\x21\x66\x9c\xe2\x7b\xa3\xdb\xfd\xe4\xa4\x91\x5c\xd0\xd1\x43\x01\x55\x16\x4d\x37\xea\x70\xb1\xf4\x30\x27\x59\xda\x61\x1d\x6d\x62\x32\x83\x5d\x20\xd5\x0e\x7e\xa4\x42\x43\x5e\xea\x8c\x76\xd6\xa2\xee\x93\x4b\x7b\x86\xb4\x61\x1b\x72\x52\x71\xde\xb8\x88\x05\x00\x2c\xad\xde\xf5\xe6\x8c\xaa\x08\xa3\x02\xab\xe3\x6a\xf1\xa2\x64\x5a\x24\x08\xe2\xf3\x89\x9e\xcd\x19\x79\x1f\x54\xa0\x91\xef\x99\xe4\xf8\xfd\x1e\x7b\x37\x26\xfe\x56\xb6\xc4\xf4\x9f\x5a\x0d\x42\x6d\x8e\x81\xc0\xc8\xb8\x44\x85\x37\xbc\xe5\xa1\x9c\xff\xe5\x41\x4c\xc1\x9e\xcf\x15\x7d\x66\xbb\x39\xaf\x6c\x3a\xe1\xec\x25\x4a\x5d\x72\x65\x5c\xba\xa4\x26\x81\xab\x44\x7f\x06\x7e\x2e\x6d\x68\xf9\xb7\xaf\x80\x21\x18\xf5\x38\xd8\x50\x08\xaa\x6b\x96\x8c\x3d\x27\x87\x32\x2f\xf0\x71\x09\x89\xc6\xdb\x4d\xbc\x13\x72\x06\x91\x8c\xb5\x3b\xe1\x6d\xea\x6c\x86\x35\x41\x2e\xb0\xd7\x84\x20\xdd\x43\x2f\x83\xc2\xf2\x4d\x67\x2d\x85\x98\xf0\xab\x88\x87\x65\xee\x41\x33\xcd\xaa\x5c\x9f\x58\x4a\xb1\xd2\x43\xf4\x54\x5f\x37\x58\xbc\x49\x94\x41\x28\x61\xac\x9d\xde\x12\x46\x1c\xd4\x4c\xae\x83\xd2\x19\xfb\xb4\x70\xaf\x4a\x76\xa6\x74\xfe\x37\x9c\xfc\x98\x64\xb1\x8e\xd1\x28\x4d\xb2\x4b\xf5\x44\x61\xfe\xc7\x43\xbf\x4f\x3e\xb5\x2a\xaa\xc0\xbc\xb8\xee\xa5\x61\x8a\x2b\xb4\x41\xcf\x65\xcb\xd5\xf5\xce\x47\x02\x4a\x3a\xd6\xc7\xd3\x07\x33\xee\x0d\xfb\x2b\x4e\x91\xce\x96\xda\xcc\x4e\xc8\x9f\xc9\x6b\xa3\x77\x01\x25\x48\xf9\x21\x5b\x0a\xaf\xc3\x0a\xc3\xc1\xb1\x70\xaf\xf9\x37\x26\x57\x13\x26\xeb\x96\x41\x58\xb1\x56\x59\x01\x55\xda\x48\x25\x4e\xc2\x6c\xe9\xb3\x3e\x65\x88\x4b\x42\x01\x98\x40\xd0\xdd\x0e\x99\xc6\x13\xb3\x4b\xa4\x9c\xb6\xec\x37\x8b\x22\x71\x81\x1b\x48\x28\x9b\xf7\xe5\x66\x28\x8e\x2a\xa9\xff\x75\xfd\x22\xfa\xca\x62\x2a\x84\x32\xd7\x65\x96\x94\x90\xca\x77\x81\xaf\xc7\x83\x77\xd0\x51\x26\x7d\x61\xb1\xfb\x50\xff\xea\xcc\xc8\xb1\xfa\xcb\xb4\x7a\x9e\xa9\x78\xd7\x3c\x16\xb7\x0c\x96\x06\x06\x50\xf7\x63\xef\xc8\xdd\xb9\xac\xfd\x5a\xcb\x9c\x57\x4c\xaa\x11\x1d\x6b\x67\xb0\x9e\x64\xf5\xe5\xaa\xa5\xda\xb2\x2d\x25\x69\x2a\x34\x27\xa7\x8c\x40\x7c\xd0\x50\xee\x8d\x16\xf3\xdf\x31\x8f\x23\xfb\x62\x90\x42\xfb\x99\x8a\x88\xa9\x9f\xe6\x1b\x13\x36\x54\x40\x76\x25\x01\xb3\x08\x76\x9b\x83\x45\x1b\xf3\x5c\x56\x4b\x23\x50\xdf\x06\xc6\x92\xf6\x1d\x52\x03\x05\xc1\x64\xbd\x5a\x03\x02\xce\x9f\x7c\x4e\x3f\x8f\xb1\x2f\x76\x9d\x9a\xfb\x57\xeb\x23\xf4\x98\xff\x74\x4e\x41\xe9\x11\x14\x42\xb4\x2d\x6a\x86\x86\xee\x2b\x73\x36\x67\x0d\x33\xfc\xa0\xee\xb1\xd1\xe8\xa7\x45\x8b\xe5\x84\xab\xd5\x2b\x59\xd7\xdc\xc6\x9a\xaa\x49\x79\x47\xcc\x48\x64\x8f\xae\xd2\x41\xcf\x57\x3e\xe4\x7f\xc5\x2f\xef\x4a\x3c\x06\xf9\x4d\xe1\xaa\xd1\x45\xb4\x58\xbc\xd8\x74\x48\xc3\x74\x16\xeb\x1c\x3c\xa8\x98\xeb\xa5\xb8\x7f\x6b\x4c\xd7\x83\xd6\x07\xd0\x23\xb7\x50\x12\x38\xbd\x4f\x40\xf5\xba\x8d\x21\xd3\xe7\x1a\xa7\x5d\x49\xf9\xfa\xa9\xfc\xec\xe7\x5d\x1e\xb5\xa7\x64\x13\x95\xc1\x3c\xda\x18\x98\x07\xb0\x73\xf3\xb3\x8f\xb0\x6e\x54\xb3\xbf\x5b\x3d\x72\x60\xff\xcb\xca\xdc\xcf\x4f\xf9\x93\x6a\xbc\x79\xc3\x2f\x01\xb5\x77\xc3\x76\x6e\x12\x1b\x68\x0b\x3b\xde\x82\xbd\x70\x3a\x6a\x95\xf9\x7b\xba\x36\x9b\xbb\xb6\xfd\x82\x56\xbb\x01\x75\x83\x18\x0a\x81\xf8\x87\x80\x74\x12\x64\x1c\x7a\x3a\x5e\xfb\x1a\x92\xf8\x77\xcf\xab\x21\xba\x2d\x34\xf1\xe4\x7e\xa4\x72\xc6\xc9\x05\x89\x4a\xd0\xbc\x58\xd4\xfe\xfd\xb1\xdc\x71\xe5\x05\x00\x43\xb3\x5a\x36\x69\xe8\xdd\x13\xdc\x70\x32\x29\x10\x80\x20\x14\x0b\x01\xed\x2a\x72\x0e\x27\x1b\xd3\x0a\xe7\xf5\x33\xda\xad\x1a\x02\x25\x9b\xae\x7e\x71\x08\x95\x2d\xad\x85\x4b\xcc\x90\x6a\xb5\x60\x04\x8d\xae\xc7\x58\x4c\x0e\x4f\x69\xe4\x97\x9c\x1d\x65\xd5\x80\x5e\x4d\xc7\x60\x66\xdb\x0c\xe0\xbd\xfb\x19\x50\x21\x82\xcf\xed\x5b\x0f\x05\x60\xa6\x0f\x10\xd0\xf1\x43\x92\xe5\x5f\x63\x77\x46\x75\x27\x23\xe2\x4a\x7e\x40\x35\x0c\x25\x38\xdc\xd9\xe4\x92\xbe\x99\x71\x2c\x63\x0b\xb5\xf4\x98\xeb\x71\x95\x7e\x01\xb6\x2b\x5c\x37\xf1\x1d\x2d\x07\x00\x37\x38\xf4\x5b\x7f\x45\xd5\xb6\x18\xfa\x51\x73\xf1\x44\x1e\xaf\x3f\x00\x20\x8d\xe6\x0c\xae\xf7\x33\xaf\x14\x61\x1a\x89\x55\xf0\x4c\x44\xf9\x38\x74\xca\x17\x7e\x28\x86\xd3\x95\x13\xbd\x94\xd8\xff\x6f\x02\x35\xe5\x52\xa1\x89\x16\x30\xd6\xee\x22\x79\xf1\xe4\x6a\xe5\x46\x61\x7a\x29\xb8\x2e\x9f\xa7\xf0\x93\xf8\x45\x36\x02\x69\x1d\xb6\xd2\x3f\x70\xcc\x61\xd3\x4d\x73\x25\xb0\x15\xc1\x54\x30\xe1\x4e\xa8\x0f\x7f\x52\xf2\x37\x66\x1d\xa7\x59\x8f\x0c\x71\x18\xa5\xb9\xb4\xda\x4f\xd8\xf5\x1e\xc7\xf3\x54\xa8\x0a\x5f\x98\xe9\x82\x73\xa3\x53\x72\xbc\x63\x04\x47\xfc\xfe\x9f\x43\x63\xad\x05\x82\x4f\x64\x28\xff\x9b\xa6\x0b\x4f\xec\x62\x42\x67\xd4\xf6\x8b\xf9\x62\x37\xcd\x25\xed\x94\x0d\x10\x00\x8a\xa5\xdc\xb9\x4d\x91\x63\x68\x82\x6d\x4d\x9d\xc8\x1c\x60\xa1\xf4\x1d\xd9\xda\x7a\xde\x58\xaf\xdf\x1b\x49\x35\x7b\xe1\xcf\x67\x34\x02\xab\x99\x15\x99\xfc\xa1\xc0\x71\x4f\xe4\x43\xae\xbb\x5e\xf3\x29\xb5\xff\x5d\x19\xe4\x2c\x8e\x51\x24\x27\x92\x1b\xb4\x40\x32\x50\xcd\x5b\x1b\x09\x1d\xf1\x0a\xe0\xbc\x7c\x34\x44\x23\x6a\xb5\x4f\x49\x29\x89\xb4\x07\x13\x95\x4d\x59\x3a\xa2\x7a\xa0\xb1\xfb\x14\xe8\x9b\xc7\x96\xd1\x04\x5c\xf4\xf0\xfb\x83\x49\x88\x06\x86\x0e\x7f\x54\x10\x65\x95\xcb\x7a\xef\x4b\x81\x97\xc6\x36\x73\xbc\x01\xe9\xa7\xd2\xbd\xff\xcb\x07\x86\x00\xab\x5c\xd0\x00\x9e\xa1\xdc\x68\xa2\xa8\x24\x3f\x57\xaf\xdc\xf6\x65\xf6\x7d\x7a\x05\x68\xc8\xa8\x43\x53\x56\xee\x90\x46\xb6\x5b\xac\x0c\xd1\x0c\x78\xc9\xb2\x6a\x77\xba\x53\xcf\xdc\x7d\xdd\x99\xdf\xa4\x45\xdf\xca\xf3\x45\x2c\xb7\x22\xb4\xbb\x6c\x05\x03\xad\x5e\x86\x31\x93\x53\x72\xab\x96\x48\x42\x7a\x32\xea\x8f\x7a\xea\x9c\x94\xf2\xd7\x1b\xb9\x59\xcd\x63\xda\x6f\x9b\xe0\xe2\xd8\xc8\x1b\xb2\x12\x3e\xa8\x8a\x51\x08\x9e\xb4\xde\x60\xd4\xa4\xf6\xe9\xb8\x55\x48\xc0\x47\x4a\x86\xf1\x1f\xa7\xb5\xa7\x77\x18\xf2\x2b\x3b\x9e\xf7\xbb\x4d\x74\x82\x6c\x77\xaf\x84\xb2\xa5\x49\x1b\x7d\x9c\xbd\x87\xc7\x45\xe4\xb2\xf4\xd6\xe0\x54\x88\x64\x66\x8e\xaa\x76\xc0\x14\x4c\xed\x60\x19\x68\x67\xee\xaf\x93\x52\xf6\x7c\x1d\x31\xc6\x70\x9d\x9d\x16\xf2\xa4\xd4\x65\xba\x19\x48\x71\x54\x15\xdd\xe4\x73\x00\x92\xdf\x2a\x6a\x91\xd1\xa4\x82\x67\x36\x09\x19\x92\xd8\x00\x6c\x50\x4f\xae\xdb\x68\x4e\x5d\x46\xfd\xc6\x25\x0b\xc9\xac\x5c\x81\x6b\x0d\xb2\xc2\x5e\x8a\xf5\x2d\x95\x8b\x3e\xf6\x32\x83\xbf\x6c\xb4\x16\x06\xbf\xb6\x8b\x77\x01\xfc\xda\x7c\x8e\xdb\x5b\xd2\x10\x0d\x77\xe6\x59\xb2\x48\xec\x83\xd0\xb9\xdf\xd8\x8c\x43\x7c\x0d\x36\x48\x1c\x08\xc0\x8e\x92\x1f\x4f\x04\x39\xa4\x31\x69\x0c\x8f\x44\x84\x48\x1c\xc6\xf5\xea\x28\x18\x9e\x2a\x5c\x97\xe9\x9e\x90\xe6\x93\xcb\xa4\xdc\x7c\x22\x91\x45\x31\x23\x32\x3f\xc5\x0a\xfb\x92\xda\x8c\x29\x66\x5c\xac\xd2\x02\x35\x01\x1f\xa9\x49\xce\x0f\xb9\x83\xbc\x0e\x17\x36\x17\x5e\x3f\xbe\x7b\x2d\x28\xcb\x1b\x45\x79\xa4\xce\x88\x7c\xc3\x21\x3c\x48\x65\x5c\x30\xc4\x56\x95\x7a\xe2\xa6\xad\x59\x0a\x90\x6c\x0d\xc0\xe8\xa3\xde\xe5\xaf\xa0\xf8\x2a\xdf\x07\xc2\xae\x01\x04\x94\x21\xae\xdf\x59\x26\x5c\x9a\x2c\xe5\xcf\x91\x65\x86\xe0\x89\xa4\xba\x9e\xeb\xdf\xfd\x24\x36\x91\x4b\xdd\x04\x5d\x6b\x59\x53\x47\xe7\xda\x43\x81\xda\x75\x84\x9d\x61\x79\xd2\x1e\x38\x4b\x72\xa5\x53\x90\x0a\x29\x64\x52\x41\x1f\x99\x04\xd5\xfa\x8e\xd4\xf9\xf9\x99\x6f\xcc\x5c\xf7\x66\xb0\x15\x92\xd3\x07\xc3\x2f\x2c\xe7\x48\xd0\x58\x4d\xbf\xa6\x08\x3d\xf8\x60\x2a\xbe\xef\xa7\xa8\x10\x3a\x90\x21\xe2\x43\x0c\xc1\x57\xf4\x3f\x58\x2b\xc4\xb6\x9e\x3e\x3f\x30\x10\x1c\xfe\xee\x95\xa4\x12\xeb\xd7\x97\x07\x29\x23\x2b\x31\x61\xd1\x0a\xec\xfb\xd0\xa3\xaf\x3b\x63\xc6\x29\x79\x21\xb4\xbd\xf5\x5d\x60\x0e\x60\xaf\x39\xf1\x85\x63\x08\xd4\xd6\xf0\x71\xe2\x66\x56\xbd\x19\xc9\x64\x13\xa2\x78\x8c\x3f\xf2\x38\xa1\xbe\x89\x4e\x41\x29\x04\x48\xe4\x69\xc5\xdf\x5a\x0a\x2a\x63\xa9\xc1\x95\xf1\x2b\x36\x3b\x68\x45\x75\xa0\x76\x46\x66\x36\x18\xf4\xd9\xb7\xe8\x8a\x95\x12\x43\x34\x5d\x46\x14\xb5\xa4\xe1\x6f\x31\xdf\xc4\xfe\x64\x15\x80\xa8\xcd\x88\x12\x7e\xbd\x71\xdb\x65\xf5\x90\x97\x84\x32\x04\x81\xd7\xa8\x55\x14\xe9\x36\x23\x6f\x4c\xbe\xba\x74\x74\x0d\x22\x78\x93\x9b\x5a\x87\x1a\x72\xc8\x80\x19\xc2\x08\x39\x0e\x51\xce\xec\x46\xd1\x4d\x03\x88\xe0\x61\xa2\xd8\x4f\x58\x02\x8a\xad\x0a\xab\xfd\xe8\xba\xf0\x8a\xe0\x97\xfc\x75\x16\x17\xf9\xc2\x75\x35\x9c\x4a\xb8\x46\x40\xf1\x24\xf2\x5c\x0b\x44\x65\x28\x06\xfa\xef\xad\x46\x9c\xc4\xb5\x7c\x92\x93\x02\xa1\xd0\xa1\xb1\x16\x0a\x8c\x25\x9f\x12\x73\x9a\x7c\xa1\xc2\x04\xb8\xd3\x64\x8e\xf5\x0c\x5d\x98\x9f\x1c\xf6\xf5\xf8\xd1\x25\x01\x70\x43\xf2\x73\xd0\xce\x0f\x55\x93\x75\xd8\x85\xbe\xb3\x27\x7d\x9a\x3d\xf9\xc1\xaa\x16\x6d\x4a\x10\xad\x36\x5f\x1d\x20\xca\x4b\x09\x91\x66\xee\x27\xfd\x1a\xde\x59\x07\x1e\xde\xad\x02\xe9\x51\x2c\xd4\x84\x03\xfb\xce\xe1\x9c\x69\x60\x17\x19\x3e\x2e\xe3\x5f\x36\x6e\x85\xfc\x14\xf2\xfa\x1c\x0a\x32\xc8\x45\xbb\x5f\xd9\x15\x0d\x66\x48\x3d\x3b\x85\x7c\x33\x26\xf3\x86\x71\xab\x17\x4a\x49\x65\xab\x6c\x2f\x73\x31\xe1\xfa\xd8\x43\x4b\xee\x22\x1b\x17\xda\x60\xf5\x8c\x7d\x99\xd2\x3d\xfb\x6c\x0a\x52\x86\xf0\x0b\x86\x4d\x0c\x35\x05\xf2\x12\x2f\x9c\x8d\x02\x41\xac\xe7\xcb\x2e\x51\xb6\x07\xaa\x66\x7d\x28\x92\x6c\x8f\xc6\xc9\xdc\x15\x70\x63\x2b\xed\x12\x2a\x17\xaa\xdd\x95\xbf\x63\x6b\x40\xbe\xdb\x3f\x57\x18\xd6\x25\x31\xcb\x24\x70\x5f\x1a\x64\xc3\x9d\x0b\x62\x36\x4e\xef\x18\x57\x00\xd0\x93\x06\x2c\xb1\xc3\xc2\xf6\x53\xff\x12\xc8\x00\x42\xc7\x50\x0d\x9b\x5a\x43\x7f\xe8\x92\x8b\xc8\xe1\xa1\xcd\x85\xb9\xb2\x28\x11\x54\x26\xe7\x9c\x29\x19\xf4\x4d\x2c\x90\x69\x14\x57\x01\xe3\x76\x71\x87\x78\x01\x10\xce\x43\x79\x08\x81\x7d\x4d\x56\xb5\x68\x54\x12\xd6\x87\x94\x4a\x14\x61\xf2\xe6\x87\x0d\x4d\x11\x3b\xc2\x71\x9c\x8e\xa0\x11\x21\x16\x7a\x1c\x3e\xd0\xdf\x9c\x35\x57\x7f\xbd\xd0\x3d\xcc\xef\x63\xd2\xd4\x03\x11\xcf\x6a\xe5\x1b\x7e\x20\x15\x96\x75\x98\x0c\xf8\x4e\xd7\xdc\x69\xb8\x71\xef\xcd\x58\x4f\x45\x1e\x66\xc4\xf5\x4f\xbf\xc9\x66\xbb\xf1\x88\xd2\x22\x3c\xae\x5a\xb1\xcb\x3d\x5e\xaf\x4e\x0d\x49\x52\x9e\x12\x78\x77\xd3\x4a\x9c\xb1\x5a\xcd\x27\x4d\x6e\xd1\xc9\xb8\xe1\xb4\x6c\x23\xb1\x54\x7c\xca\x92\x1b\xea\xd9\xc0\xc9\xd2\x7a\xd8\xa6\x2f\x92\x1e\xc0\xe8\x7a\x82\x71\xfd\x14\x03\xaf\x22\x1e\xb0\x3a\x5a\xa6\x41\x8a\x56\x78\x6d\x7d\xc3\x3d\x05\x79\x2d\x9c\x80\xfa\x3a\x55\x1b\xa3\x9a\x83\x23\xf4\x88\x53\x62\x0b\x78\x04\x62\x55\x50\xfc\x22\x20\xee\xd0\xcc\xcb\x33\xca\x47\xb3\x27\xef\x53\x7d\x48\x5f\x31\x2a\x14\x9d\xe8\x66\xb1\x0e\xf9\x54\x48\x9a\x41\x38\x7f\x4f\x58\x51\xd8\xde\x6c\x01\x2b\x0f\x94\x1f\xf6\xd4\xf6\x06\x1e\x39\xae\x27\xd9\xd4\x8b\x33\xd6\x83\x5f\x72\x76\x23\x1e\x09\x17\x40\xdb\xb1\x6b\x60\xa3\x21\x0b\x3a\xe1\x08\xed\xa6\x93\xb4\x33\xed\xf5\x98\x58\x4d\xf1\x0f\xfc\x19\xf8\xe4\xdd\x9f\x57\xec\x30\x1d\xd3\xc1\xd9\x38\x49\x85\x83\x8a\x3a\x31\xf5\x19\x15\x5a\x86\xfd\x96\x5f\x53\x88\xee\x8c\x77\xea\x3f\xe8\x96\x3b\x49\x57\x17\xf5\x52\x95\xd1\x50\x3e\xee\xeb\x41\x01\x0f\x76\x87\xc0\x9e\x73\x94\xbb\xd4\x11\xed\x00\x43\x67\xf2\x75\x97\xf2\x20\x1b\x60\x57\xbf\x57\xba\xd0\x19\xc9\x9f\x1e\xcd\x45\xba\x2e\xe7\x83\x9a\x62\x34\x08\xde\x7a\x97\xd4\xe8\x5a\x49\xbf\xf8\xae\x9d\xa5\xc2\xbf\x47\x2a\x2c\x74\xa1\x5e\x96\x08\x85\xf7\x6c\x7a\x65\x44\xb8\x08\x3b\xac\x07\x6d\x4a\x95\x3d\x2e\x96\x6b\x5b\x19\xbe\x89\xa3\xdd\x17\x9c\xcb\x26\xef\x8e\x1f\x90\x12\xdd\x2b\x82\xd9\xc4\x91\x2e\x03\xc4\x86\x80\x2b\xbe\xce\x6f\x82\x96\x0e\x06\xba\x69\xb9\x86\xee\xff\xd2\x1f\x8b\xf8\x3c\x05\xc1\xac\xac\x64\x7e\x0d\xeb\xee\x97\x7d\x5f\xf3\xe9\xc2\x91\x18\x13\xeb\x9e\xda\xb9\x43\x2d\xa4\xde\x46\xae\xf8\xc7\x50\xaf\xa6\xa4\x01\xb2\x69\xfe\x8e\xea\xaf\x59\x06\x40\xa9\xf5\xfc\x77\xe5\x46\x60\xb3\x63\x8f\x9b\x67\x88\x86\xe6\xb3\x85\xef\x04\xe3\x22\xbb\x22\x42\xd0\x23\x21\x89\xc3\x3f\x32\x15\x2a\xc2\x06\xc4\xa2\x7e\x41\xf5\x15\x3c\xed\x76\x9b\xea\x6c\x3a\x33\x1a\x7e\x74\x93\xd1\x0e\xcc\xbc\xf2\x9c\x6a\x00\x24\x7f\xe6\x85\xd5\x04\x1b\x79\x7a\xd3\x44\x4d\x3b\x48\x57\x7f\x63\xec\x3d\x12\xf4\xf2\xc4\x19\x22\x85\x92\xbc\x5d\xca\xc1\x13\x48\x3d\x9a\x07\xc6\x91\xfe\x02\xea\x81\x47\x17\xc4\xdb\xf8\x22\x0c\xf5\xc2\xf9\xf9\x71\x20\x41\x08\xdd\xc4\x43\x0c\x77\x9e\x19\xab\x2e\x4c\x97\x72\x45\x81\x58\x20\x6d\x5d\x16\xad\x71\xce\x2a\xb2\xde\x0a\xff\xd0\x0e\x85\x2c\x67\xbb\x6f\x39\x88\x62\x15\xd6\x0c\xdf\x12\x6c\x94\x74\xa0\x96\x7d\x7e\xb5\xf2\x3f\xb9\x33\xf5\x9b\x30\x86\x1d\xfd\xe2\x90\x63\xf9\x57\xe0\x00\xfb\xf0\xad\xc2\x52\x07\x45\xf2\x79\xfc\xcb\xc8\xa4\x8f\xdb\xa1\xcc\xb7\x05\x1e\xc4\x11\x20\x42\x64\x42\x08\x3f\x35\x35\x6e\xd4\xb7\x30\x61\xb9\x6c\xc9\x15\xa8\xb3\x94\x64\x06\x08\xad\xa9\x72\x6d\xfd\x56\x9e\x6b\xf3\x1b\x36\x2b\x9d\x18\xa0\x4b\x7d\xa5\x96\xd7\xc0\xbf\xe3\x28\xd5\xbc\x18\x3b\xc9\x5c\x48\x0f\x88\x2c\x7f\xdc\x94\x6a\x6b\xa4\x4e\x7b\xa4\x02\xae\xf4\xfb\xa0\x16\x3d\xe3\xf7\xc3\x82\x81\x6a\xc2\x3e\x75\xef\xc7\xd5\x82\x6c\xa4\xa7\xb0\x55\xa1\xd2\x65\x0a\x97\xf6\x3f\xd9\x59\x6e\xa1\x29\xeb\x35\xbf\x0d\x33\x27\x83\x93\x1e\xd1\xf2\x7a\x9a\xd9\x8c\xe4\x45\x08\x69\x53\xb8\x57\xb2\x0b\x4e\xea\xf9\xd3\x30\xa3\x80\xee\xb0\x9e\xe9\x27\xbc\xab\xa3\x96\x66\xc6\x89\xb6\x60\x4f\xd3\xe2\xbe\xcf\x31\xe2\xa1\x17\x16\x32\xdf\xf2\x18\x61\x0f\xa1\x1e\x40\xd7\x89\xd6\x0d\x14\x5b\x94\xc4\xb8\xa7\x2b\xb5\x3f\x98\xd0\xd5\x6a\x80\x45\x64\x74\x58\x82\x2c\xcc\x29\x0b\x28\x20\x4d\x61\xd7\xc8\xda\x0f\x1a\x53\xe6\x51\x5d\x0d\x2c\x52\x41\xfa\xd6\x6a\x3d\x3b\x5c\x8f\x1b\x0f\xa8\x27\x69\x3a\xc4\xa2\x58\x76\x59\xb6\x03\xfc\x34\x3f\x29\x01\x13\xb7\x10\x6e\x83\x29\xc4\xc9\xf6\x6d\xf1\x2a\x1d\x9e\xb5\xb1\x3d\xab\x50\xde\xd6\xfd\xf2\x73\x29\x9e\xb4\x6f\x67\x16\x65\x89\x11\x0f\x1b\x18\xb6\xbd\x1d\xe4\x50\x49\x8d\xb4\x01\x50\x53\x22\x75\xd4\x29\x92\xec\x73\xbb\x4f\xa9\x13\x2a\xd3\xa0\x0a\x75\xab\x3b\x6a\xd2\x14\x87\x51\x5a\xc0\xb9\xa5\x9f\xa7\x33\x15\xbf\xed\xf5\x98\x1c\x7a\x24\x36\x32\x30\xd4\x45\xe1\x95\xe3\x8b\xd2\x97\x29\xbe\x8e\xc8\xd0\x68\xbb\xd0\xad\xa4\x87\xbd\xa2\xda\xb4\x8e\x7a\x10\xcd\x11\x7a\x78\x0d\x93\xc2\x5c\x1c\xf0\x56\x5c\x02\x9e\x8a\xd4\x3c\x25\xb1\x94\xc5\x13\x95\x2e\x89\x5e\x13\xbe\xae\x87\x8c\x15\xa0\x11\xea\xc4\x1c\x53\xbf\xb3\x16\xd4\x46\x62\xeb\xf6\xaa\xc8\x92\x73\xaf\x9f\xe6\x95\xd9\x8c\x88\x52\xeb\x78\x1c\xba\xab\xe3\x5d\xf9\x21\xd1\x91\xe1\x87\x48\x43\xc1\xbe\x60\xd4\xf3\x57\x06\x9a\xda" + +# twoN = vector_powers(TWO, BP_N); +BP_TWO_N = b"\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x08\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x20\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x40\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x08\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x20\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x40\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x08\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x20\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x40\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x08\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x20\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x40\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x08\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x20\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x40\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x08\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x20\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x40\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x08\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x20\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x40\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x08\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x20\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x40\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + +# ip12 = inner_product(oneN, twoN); +BP_IP12 = b"\xff\xff\xff\xff\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + + +# +# Rct keys operations +# tmp_x are global working registers to minimize memory allocations / heap fragmentation. +# Caution has to be exercised when using the registers and operations using the registers +# + +tmp_bf_0 = bytearray(32) +tmp_bf_1 = bytearray(32) +tmp_bf_2 = bytearray(32) +tmp_bf_exp = bytearray(11 + 32 + 4) +tmp_bf_exp_mv = memoryview(tmp_bf_exp) + +tmp_pt_1 = crypto.new_point() +tmp_pt_2 = crypto.new_point() +tmp_pt_3 = crypto.new_point() +tmp_pt_4 = crypto.new_point() + +tmp_sc_1 = crypto.new_scalar() +tmp_sc_2 = crypto.new_scalar() +tmp_sc_3 = crypto.new_scalar() +tmp_sc_4 = crypto.new_scalar() + + +def _ensure_dst_key(dst=None): + if dst is None: + dst = bytearray(32) + return dst + + +def memcpy(dst, dst_off, src, src_off, len): + if dst is not None: + _memcpy(dst, dst_off, src, src_off, len) + return dst + + +def alloc_scalars(num=1): + return (crypto.new_scalar() for _ in range(num)) + + +def copy_key(dst, src): + for i in range(32): + dst[i] = src[i] + return dst + + +def init_key(val, dst=None): + dst = _ensure_dst_key(dst) + return copy_key(dst, val) + + +def gc_iter(i): + if i & 127 == 0: + gc.collect() + + +def invert(dst, x): + dst = _ensure_dst_key(dst) + crypto.decodeint_into_noreduce(tmp_sc_1, x) + crypto.sc_inv_into(tmp_sc_2, tmp_sc_1) + crypto.encodeint_into(dst, tmp_sc_2) + return dst + + +def scalarmult_key(dst, P, s): + dst = _ensure_dst_key(dst) + crypto.decodepoint_into(tmp_pt_1, P) + crypto.decodeint_into_noreduce(tmp_sc_1, s) + crypto.scalarmult_into(tmp_pt_2, tmp_pt_1, tmp_sc_1) + crypto.encodepoint_into(dst, tmp_pt_2) + return dst + + +def scalarmultH(dst, x): + dst = _ensure_dst_key(dst) + crypto.decodeint_into(tmp_sc_1, x) + crypto.scalarmult_into(tmp_pt_1, XMR_HP, tmp_sc_1) + crypto.encodepoint_into(dst, tmp_pt_1) + return dst + + +def scalarmult_base(dst, x): + dst = _ensure_dst_key(dst) + crypto.decodeint_into_noreduce(tmp_sc_1, x) + crypto.scalarmult_base_into(tmp_pt_1, tmp_sc_1) + crypto.encodepoint_into(dst, tmp_pt_1) + return dst + + +def sc_gen(dst=None): + dst = _ensure_dst_key(dst) + crypto.random_scalar(tmp_sc_1) + crypto.encodeint_into(dst, tmp_sc_1) + return dst + + +def sc_add(dst, a, b): + dst = _ensure_dst_key(dst) + crypto.decodeint_into_noreduce(tmp_sc_1, a) + crypto.decodeint_into_noreduce(tmp_sc_2, b) + crypto.sc_add_into(tmp_sc_3, tmp_sc_1, tmp_sc_2) + crypto.encodeint_into(dst, tmp_sc_3) + return dst + + +def sc_sub(dst, a, b): + dst = _ensure_dst_key(dst) + crypto.decodeint_into_noreduce(tmp_sc_1, a) + crypto.decodeint_into_noreduce(tmp_sc_2, b) + crypto.sc_sub_into(tmp_sc_3, tmp_sc_1, tmp_sc_2) + crypto.encodeint_into(dst, tmp_sc_3) + return dst + + +def sc_mul(dst, a, b): + dst = _ensure_dst_key(dst) + crypto.decodeint_into_noreduce(tmp_sc_1, a) + crypto.decodeint_into_noreduce(tmp_sc_2, b) + crypto.sc_mul_into(tmp_sc_3, tmp_sc_1, tmp_sc_2) + crypto.encodeint_into(dst, tmp_sc_3) + return dst + + +def sc_muladd(dst, a, b, c): + dst = _ensure_dst_key(dst) + crypto.decodeint_into_noreduce(tmp_sc_1, a) + crypto.decodeint_into_noreduce(tmp_sc_2, b) + crypto.decodeint_into_noreduce(tmp_sc_3, c) + crypto.sc_muladd_into(tmp_sc_4, tmp_sc_1, tmp_sc_2, tmp_sc_3) + crypto.encodeint_into(dst, tmp_sc_4) + return dst + + +def sc_mulsub(dst, a, b, c): + dst = _ensure_dst_key(dst) + crypto.decodeint_into_noreduce(tmp_sc_1, a) + crypto.decodeint_into_noreduce(tmp_sc_2, b) + crypto.decodeint_into_noreduce(tmp_sc_3, c) + crypto.sc_mulsub_into(tmp_sc_4, tmp_sc_1, tmp_sc_2, tmp_sc_3) + crypto.encodeint_into(dst, tmp_sc_4) + return dst + + +def add_keys(dst, A, B): + dst = _ensure_dst_key(dst) + crypto.decodepoint_into(tmp_pt_1, A) + crypto.decodepoint_into(tmp_pt_2, B) + crypto.point_add_into(tmp_pt_3, tmp_pt_1, tmp_pt_2) + crypto.encodepoint_into(dst, tmp_pt_3) + return dst + + +def sub_keys(dst, A, B): + dst = _ensure_dst_key(dst) + crypto.decodepoint_into(tmp_pt_1, A) + crypto.decodepoint_into(tmp_pt_2, B) + crypto.point_sub_into(tmp_pt_3, tmp_pt_1, tmp_pt_2) + crypto.encodepoint_into(dst, tmp_pt_3) + return dst + + +def add_keys2(dst, a, b, B): + dst = _ensure_dst_key(dst) + crypto.decodeint_into_noreduce(tmp_sc_1, a) + crypto.decodeint_into_noreduce(tmp_sc_2, b) + crypto.decodepoint_into(tmp_pt_1, B) + crypto.add_keys2_into(tmp_pt_2, tmp_sc_1, tmp_sc_2, tmp_pt_1) + crypto.encodepoint_into(dst, tmp_pt_2) + return dst + + +def add_keys3(dst, a, A, b, B): + dst = _ensure_dst_key(dst) + crypto.decodeint_into_noreduce(tmp_sc_1, a) + crypto.decodeint_into_noreduce(tmp_sc_2, b) + crypto.decodepoint_into(tmp_pt_1, A) + crypto.decodepoint_into(tmp_pt_2, B) + crypto.add_keys3_into(tmp_pt_3, tmp_sc_1, tmp_pt_1, tmp_sc_2, tmp_pt_2) + crypto.encodepoint_into(dst, tmp_pt_3) + return dst + + +def hash_to_scalar(dst, data): + dst = _ensure_dst_key(dst) + crypto.hash_to_scalar_into(tmp_sc_1, data) + crypto.encodeint_into(dst, tmp_sc_1) + return dst + + +def hash_vct_to_scalar(dst, data): + dst = _ensure_dst_key(dst) + ctx = crypto.get_keccak() + for x in data: + ctx.update(x) + hsh = ctx.digest() + + crypto.decodeint_into(tmp_sc_1, hsh) + crypto.encodeint_into(tmp_bf_1, tmp_sc_1) + copy_key(dst, tmp_bf_1) + return dst + + +def get_exponent(dst, base, idx): + dst = _ensure_dst_key(dst) + salt = b"bulletproof" + idx_size = uvarint_size(idx) + final_size = len(salt) + 32 + idx_size + buff = tmp_bf_exp_mv + memcpy(buff, 0, base, 0, 32) + memcpy(buff, 32, salt, 0, len(salt)) + dump_uvarint_b_into(idx, buff, 32 + len(salt)) + crypto.keccak_hash_into(tmp_bf_1, buff[:final_size]) + crypto.hash_to_point_into(tmp_pt_1, tmp_bf_1) + crypto.encodepoint_into(dst, tmp_pt_1) + return dst + + +# +# Key Vectors +# + + +class KeyVBase: + """ + Base KeyVector object + """ + + def __init__(self, elems=64): + self.current_idx = 0 + self.size = elems + + def idxize(self, idx): + if idx < 0: + idx = self.size + idx + if idx >= self.size: + raise IndexError("Index out of bounds") + return idx + + def __getitem__(self, item): + raise ValueError("Not supported") + + def __setitem__(self, key, value): + raise ValueError("Not supported") + + def __iter__(self): + self.current_idx = 0 + return self + + def __next__(self): + if self.current_idx >= self.size: + raise StopIteration + else: + self.current_idx += 1 + return self[self.current_idx - 1] + + def __len__(self): + return self.size + + def to(self, idx, buff, offset=0): + return memcpy(buff, offset, self[self.idxize(idx)], 0, 32) + + def read(self, idx, buff, offset=0): + raise ValueError() + + def slice(self, res, start, stop): + for i in range(start, stop): + res[i - start] = self[i] + return res + + def slice_view(self, start, stop): + return KeyVSliced(self, start, stop) + + +class KeyV(KeyVBase): + """ + KeyVector abstraction + Constant precomputed buffers = bytes, frozen. Same operation as normal. + + Non-constant KeyVector is separated to 64 elements chunks to avoid problems with + the heap fragmentation. In this way the chunks are more probable to be correctly + allocated as smaller chunk of continuous memory is required. Chunk is assumed to + have 64 elements at all times to minimize corner cases handling. BP require either + multiple of 64 elements vectors or less than 64. + + Some chunk-dependent cases are not implemented as they are currently not needed in the BP. + """ + + def __init__(self, elems=64, buffer=None, const=False, no_init=False): + super().__init__(elems) + self.d = None + self.mv = None + self.const = const + self.cur = _ensure_dst_key() + self.chunked = False + if no_init: + pass + elif buffer: + self.d = buffer # can be immutable (bytes) + self.size = len(buffer) // 32 + else: + self._set_d(elems) + + if not no_init: + self._set_mv() + + def _set_d(self, elems): + if elems > 64 and elems % 64 == 0: + self.chunked = True + self.d = [bytearray(32 * 64) for _ in range(elems // 64)] + + else: + self.chunked = False + self.d = bytearray(32 * elems) + + def _set_mv(self): + if not self.chunked: + self.mv = memoryview(self.d) + + def __getitem__(self, item): + """ + Returns corresponding 32 byte array. + Creates new memoryview on access. + """ + if self.chunked: + raise ValueError("Not supported") # not needed + item = self.idxize(item) + return self.mv[item * 32 : (item + 1) * 32] + + def __setitem__(self, key, value): + if self.chunked: + raise ValueError("Not supported") # not needed + if self.const: + raise ValueError("Constant KeyV") + ck = self[key] + for i in range(32): + ck[i] = value[i] + + def to(self, idx, buff=None, offset=0): + idx = self.idxize(idx) + if self.chunked: + memcpy( + buff if buff else self.cur, + offset, + self.d[idx >> 6], + (idx & 63) << 5, + 32, + ) + else: + memcpy(buff if buff else self.cur, offset, self.d, idx << 5, 32) + return buff if buff else self.cur + + def read(self, idx, buff, offset=0): + idx = self.idxize(idx) + if self.chunked: + memcpy(self.d[idx >> 6], (idx & 63) << 5, buff, offset, 32) + else: + memcpy(self.d, idx << 5, buff, offset, 32) + + def resize(self, nsize, chop=False, realloc=False): + if self.size == nsize: + return self + + if self.chunked and nsize <= 64: + self.chunked = False # de-chunk + if self.size > nsize and realloc: + self.d = bytearray(self.d[0][: nsize << 5]) + elif self.size > nsize and not chop: + self.d = self.d[0][: nsize << 5] + else: + self.d = bytearray(nsize << 5) + + elif self.chunked: + raise ValueError("Unsupported") # not needed + + else: + if self.size > nsize and realloc: + self.d = bytearray(self.d[: nsize << 5]) + elif self.size > nsize and not chop: + self.d = self.d[: nsize << 5] + else: + self.d = bytearray(nsize << 5) + + self.size = nsize + self._set_mv() + + def realloc(self, nsize, collect=False): + self.d = None + self.mv = None + if collect: + gc.collect() # gc collect prev. allocation + + self._set_d(nsize) + self.size = nsize + self._set_mv() + + def realloc_init_from(self, nsize, src, offset=0, collect=False): + if not isinstance(src, KeyV): + raise ValueError("KeyV supported only") + self.realloc(nsize, collect) + + if not self.chunked and not src.chunked: + memcpy(self.d, 0, src.d, offset << 5, nsize << 5) + + elif self.chunked and not src.chunked: + raise ValueError("Unsupported") # not needed + + elif self.chunked and src.chunked: + raise ValueError("Unsupported") # not needed + + elif not self.chunked and src.chunked: + for i in range(nsize >> 6): + memcpy( + self.d, + i << 11, + src.d[i + (offset >> 6)], + (offset & 63) << 5 if i == 0 else 0, + nsize << 5 if i <= nsize >> 6 else (nsize & 64) << 5, + ) + + +class KeyVEval(KeyVBase): + """ + KeyVector computed / evaluated on demand + """ + + def __init__(self, elems=64, src=None): + super().__init__(elems) + self.fnc = src + self.buff = _ensure_dst_key() + self.mv = memoryview(self.buff) + + def __getitem__(self, item): + return self.fnc(self.idxize(item), self.buff) + + def to(self, idx, buff=None, offset=0): + self.fnc(self.idxize(idx), self.buff) + memcpy(buff, offset, self.buff, 0, 32) + return buff if buff else self.buff + + +class KeyVSized(KeyVBase): + """ + Resized vector, wrapping possibly larger vector + (e.g., precomputed, but has to have exact size for further computations) + """ + + def __init__(self, wrapped, new_size): + super().__init__(new_size) + self.wrapped = wrapped + + def __getitem__(self, item): + return self.wrapped[self.idxize(item)] + + def __setitem__(self, key, value): + self.wrapped[self.idxize(key)] = value + + +class KeyVConst(KeyVBase): + def __init__(self, size, elem, copy=True): + super().__init__(size) + self.elem = init_key(elem) if copy else elem + + def __getitem__(self, item): + return self.elem + + def to(self, idx, buff=None, offset=0): + memcpy(buff, offset, self.elem, 0, 32) + return buff if buff else self.elem + + +class KeyVPrecomp(KeyVBase): + """ + Vector with possibly large size and some precomputed prefix. + Usable for Gi vector with precomputed usual sizes (i.e., 2 output transactions) + but possible to compute further + """ + + def __init__(self, size, precomp_prefix, aux_comp_fnc): + super().__init__(size) + self.precomp_prefix = precomp_prefix + self.aux_comp_fnc = aux_comp_fnc + self.buff = _ensure_dst_key() + + def __getitem__(self, item): + item = self.idxize(item) + if item < len(self.precomp_prefix): + return self.precomp_prefix[item] + return self.aux_comp_fnc(item, self.buff) + + def to(self, idx, buff=None, offset=0): + item = self.idxize(idx) + if item < len(self.precomp_prefix): + return self.precomp_prefix.to(item, buff if buff else self.buff, offset) + self.aux_comp_fnc(item, self.buff) + memcpy(buff, offset, self.buff, 0, 32) + return buff if buff else self.buff + + +class KeyVSliced(KeyVBase): + """ + Sliced in-memory vector version, remapping + """ + + def __init__(self, src, start, stop): + super().__init__(stop - start) + self.wrapped = src + self.offset = start + + def __getitem__(self, item): + return self.wrapped[self.offset + self.idxize(item)] + + def __setitem__(self, key, value): + self.wrapped[self.offset + self.idxize(key)] = value + + def resize(self, nsize, chop=False): + raise ValueError("Not supported") + + def to(self, idx, buff=None, offset=0): + return self.wrapped.to(self.offset + self.idxize(idx), buff, offset) + + def read(self, idx, buff, offset=0): + return self.wrapped.read(self.offset + self.idxize(idx), buff, offset) + + +class KeyVPowers(KeyVBase): + """ + Vector of x^i. Allows only sequential access (no jumping). Resets on [0,1] access. + """ + + def __init__(self, size, x, **kwargs): + super().__init__(size) + self.x = x + self.cur = bytearray(32) + self.last_idx = 0 + + def __getitem__(self, item): + prev = self.last_idx + item = self.idxize(item) + self.last_idx = item + + if item == 0: + return copy_key(self.cur, ONE) + elif item == 1: + return copy_key(self.cur, self.x) + elif item == prev + 1: + return sc_mul(self.cur, self.cur, self.x) + else: + IndexError("Only linear scan allowed") + + +class KeyVZtwo(KeyVBase): + """ + Ztwo vector - see vector_z_two_i + """ + + def __init__(self, N, logN, M, zpow, twoN, raw=False): + super().__init__(N * M) + self.N = N + self.logN = logN + self.M = M + self.zpow = zpow + self.twoN = twoN + self.raw = raw + self.sc = crypto.new_scalar() + self.cur = bytearray(32) if not raw else None + + def __getitem__(self, item): + vector_z_two_i(self.logN, self.zpow, self.twoN, self.idxize(item), self.sc) + if self.raw: + return self.sc + + crypto.encodeint_into(self.cur, self.sc) + return self.cur + + +def _ensure_dst_keyvect(dst=None, size=None): + if dst is None: + dst = KeyV(elems=size) + return dst + if size is not None and size != len(dst): + dst.resize(size) + return dst + + +def const_vector(val, elems=BP_N, copy=True): + return KeyVConst(elems, val, copy) + + +def vector_exponent_custom(A, B, a, b, dst=None): + dst = _ensure_dst_key(dst) + crypto.identity_into(tmp_pt_2) + + for i in range(len(a)): + crypto.decodeint_into_noreduce(tmp_sc_1, a.to(i)) + crypto.decodepoint_into(tmp_pt_3, A.to(i)) + crypto.decodeint_into_noreduce(tmp_sc_2, b.to(i)) + crypto.decodepoint_into(tmp_pt_4, B.to(i)) + crypto.add_keys3_into(tmp_pt_1, tmp_sc_1, tmp_pt_3, tmp_sc_2, tmp_pt_4) + crypto.point_add_into(tmp_pt_2, tmp_pt_2, tmp_pt_1) + gc_iter(i) + crypto.encodepoint_into(dst, tmp_pt_2) + return dst + + +def vector_powers(x, n, dst=None, dynamic=False, **kwargs): + if dynamic: + return KeyVPowers(n, x, **kwargs) + dst = _ensure_dst_keyvect(dst, n) + if n == 0: + return dst + dst.read(0, ONE) + if n == 1: + return dst + dst.read(1, x) + + crypto.decodeint_into_noreduce(tmp_sc_1, x) + crypto.decodeint_into_noreduce(tmp_sc_2, x) + for i in range(2, n): + crypto.sc_mul_into(tmp_sc_1, tmp_sc_1, tmp_sc_2) + crypto.encodeint_into(tmp_bf_0, tmp_sc_1) + dst.read(i, tmp_bf_0) + gc_iter(i) + return dst + + +def vector_power_sum(x, n, dst=None): + dst = _ensure_dst_key(dst) + if n == 0: + return copy_key(dst, ZERO) + + copy_key(dst, ONE) + if n == 1: + return dst + + prev = init_key(x) + for i in range(1, n): + if i > 1: + sc_mul(prev, prev, x) + sc_add(dst, dst, prev) + gc_iter(i) + return dst + + +def inner_product(a, b, dst=None): + if len(a) != len(b): + raise ValueError("Incompatible sizes of a and b") + dst = _ensure_dst_key(dst) + crypto.sc_init_into(tmp_sc_1, 0) + + for i in range(len(a)): + crypto.decodeint_into_noreduce(tmp_sc_2, a.to(i)) + crypto.decodeint_into_noreduce(tmp_sc_3, b.to(i)) + crypto.sc_muladd_into(tmp_sc_1, tmp_sc_2, tmp_sc_3, tmp_sc_1) + gc_iter(i) + + crypto.encodeint_into(dst, tmp_sc_1) + return dst + + +def hadamard(a, b, dst=None): + dst = _ensure_dst_keyvect(dst, len(a)) + for i in range(len(a)): + sc_mul(tmp_bf_1, a.to(i), b.to(i)) + dst.read(i, tmp_bf_1) + gc_iter(i) + return dst + + +def hadamard_fold(v, a, b, into=None, into_offset=0): + """ + Folds a curvepoint array using a two way scaled Hadamard product + + ln = len(v); h = ln // 2 + v[i] = a * v[i] + b * v[h + i] + """ + h = len(v) // 2 + crypto.decodeint_into_noreduce(tmp_sc_1, a) + crypto.decodeint_into_noreduce(tmp_sc_2, b) + into = into if into else v + + for i in range(h): + crypto.decodepoint_into(tmp_pt_1, v.to(i)) + crypto.decodepoint_into(tmp_pt_2, v.to(h + i)) + crypto.add_keys3_into(tmp_pt_3, tmp_sc_1, tmp_pt_1, tmp_sc_2, tmp_pt_2) + crypto.encodepoint_into(tmp_bf_0, tmp_pt_3) + into.read(i + into_offset, tmp_bf_0) + gc_iter(i) + + return into + + +def scalar_fold(v, a, b, into=None, into_offset=0): + """ + ln = len(v); h = ln // 2 + v[i] = v[i] * a + v[h+i] * b) + """ + h = len(v) // 2 + crypto.decodeint_into_noreduce(tmp_sc_1, a) + crypto.decodeint_into_noreduce(tmp_sc_2, b) + into = into if into else v + + for i in range(h): + crypto.decodeint_into_noreduce(tmp_sc_3, v.to(i)) + crypto.decodeint_into_noreduce(tmp_sc_4, v.to(h + i)) + crypto.sc_mul_into(tmp_sc_3, tmp_sc_3, tmp_sc_1) + crypto.sc_mul_into(tmp_sc_4, tmp_sc_4, tmp_sc_2) + crypto.sc_add_into(tmp_sc_3, tmp_sc_3, tmp_sc_4) + crypto.encodeint_into(tmp_bf_0, tmp_sc_3) + into.read(i + into_offset, tmp_bf_0) + gc_iter(i) + + return into + + +def cross_inner_product(l0, r0, l1, r1): + """ + t1_1 = l0 . r1, t1_2 = l1 . r0 + t1 = t1_1 + t1_2, t2 = l1 . r1 + """ + sc_t1_1, sc_t1_2, sc_t2 = alloc_scalars(3) + cl0, cr0, cl1, cr1 = alloc_scalars(4) + + for i in range(len(l0)): + crypto.decodeint_into_noreduce(cl0, l0.to(i)) + crypto.decodeint_into_noreduce(cr0, r0.to(i)) + crypto.decodeint_into_noreduce(cl1, l1.to(i)) + crypto.decodeint_into_noreduce(cr1, r1.to(i)) + + crypto.sc_muladd_into(sc_t1_1, cl0, cr1, sc_t1_1) + crypto.sc_muladd_into(sc_t1_2, cl1, cr0, sc_t1_2) + crypto.sc_muladd_into(sc_t2, cl1, cr1, sc_t2) + gc_iter(i) + + crypto.sc_add_into(sc_t1_1, sc_t1_1, sc_t1_2) + return crypto.encodeint(sc_t1_1), crypto.encodeint(sc_t2) + + +def vector_gen(dst, size, op): + dst = _ensure_dst_keyvect(dst, size) + for i in range(size): + dst.to(i, tmp_bf_0) + op(i, tmp_bf_0) + dst.read(i, tmp_bf_0) + gc_iter(i) + return dst + + +def vector_add(a, b, dst=None): + dst = _ensure_dst_keyvect(dst, len(a)) + for i in range(len(a)): + sc_add(tmp_bf_1, a.to(i), b.to(i)) + dst.read(i, tmp_bf_1) + gc_iter(i) + return dst + + +def vector_subtract(a, b, dst=None): + dst = _ensure_dst_keyvect(dst, len(a)) + for i in range(len(a)): + sc_sub(tmp_bf_1, a.to(i), b.to(i)) + dst.read(i, tmp_bf_1) + gc_iter(i) + return dst + + +def vector_dup(x, n, dst=None): + dst = _ensure_dst_keyvect(dst, n) + for i in range(n): + dst[i] = x + gc_iter(i) + return dst + + +def vector_z_two_i(logN, zpow, twoN, i, dst_sc=None): + """ + 0...N|N+1...2N|2N+1...3N|.... + zt[i] = z^b 2^c, where + b = 2 + blockNumber. BlockNumber is idx of N block + c = i % N = i - N * blockNumber + """ + j = i >> logN + crypto.decodeint_into_noreduce(tmp_sc_1, zpow.to(j + 2)) + crypto.decodeint_into_noreduce(tmp_sc_2, twoN.to(i & ((1 << logN) - 1))) + crypto.sc_mul_into(dst_sc, tmp_sc_1, tmp_sc_2) + return dst_sc + + +def vector_z_two(N, logN, M, zpow, twoN, zero_twos=None, dynamic=False, **kwargs): + if dynamic: + return KeyVZtwo(N, logN, M, zpow, twoN, **kwargs) + else: + raise NotImplementedError() + + +def hash_cache_mash(dst, hash_cache, *args): + dst = _ensure_dst_key(dst) + ctx = crypto.get_keccak() + ctx.update(hash_cache) + + for x in args: + if x is None: + break + ctx.update(x) + hsh = ctx.digest() + + crypto.decodeint_into(tmp_sc_1, hsh) + crypto.encodeint_into(tmp_bf_1, tmp_sc_1) + + copy_key(dst, tmp_bf_1) + copy_key(hash_cache, tmp_bf_1) + return dst + + +def is_reduced(sc): + return crypto.encodeint(crypto.decodeint(sc)) == sc + + +class MultiExpSequential: + """ + MultiExp object similar to MultiExp array of [(scalar, point), ] + MultiExp computes simply: res = \\sum_i scalar_i * point_i + Straus / Pippenger algorithms are implemented in the original Monero C++ code for the speed + but the memory cost is around 1 MB which is not affordable here in HW devices. + + Moreover, Monero needs speed for very fast verification for blockchain verification which is not + priority in this use case. + + MultiExp holder with sequential evaluation + """ + + def __init__(self, size=None, points=None, point_fnc=None): + self.current_idx = 0 + self.size = size if size else None + self.points = points if points else [] + self.point_fnc = point_fnc + if points and size is None: + self.size = len(points) if points else 0 + else: + self.size = 0 + + self.acc = crypto.identity() + self.tmp = _ensure_dst_key() + + def get_point(self, idx): + return ( + self.point_fnc(idx, None) if idx >= len(self.points) else self.points[idx] + ) + + def add_pair(self, scalar, point): + self._acc(scalar, point) + + def add_scalar(self, scalar): + self._acc(scalar, self.get_point(self.current_idx)) + + def _acc(self, scalar, point): + crypto.decodeint_into_noreduce(tmp_sc_1, scalar) + crypto.decodepoint_into(tmp_pt_2, point) + crypto.scalarmult_into(tmp_pt_3, tmp_pt_2, tmp_sc_1) + crypto.point_add_into(self.acc, self.acc, tmp_pt_3) + self.current_idx += 1 + self.size += 1 + + def eval(self, dst, GiHi=False): + dst = _ensure_dst_key(dst) + return crypto.encodepoint_into(dst, self.acc) + + +def multiexp(dst=None, data=None, GiHi=False): + return data.eval(dst, GiHi) + + +class BulletProofBuilder: + def __init__(self): + self.use_det_masks = True + self.proof_sec = None + + self.Gprec = KeyV(buffer=BP_GI_PRE, const=True) + self.Hprec = KeyV(buffer=BP_HI_PRE, const=True) + self.oneN = const_vector(ONE, 64) + self.twoN = KeyV(buffer=BP_TWO_N, const=True) + self.ip12 = BP_IP12 + self.fnc_det_mask = None + + self.tmp_sc_1 = crypto.new_scalar() + self.tmp_det_buff = bytearray(64 + 1 + 4) + + self.gc_fnc = gc.collect + self.gc_trace = None + + def gc(self, *args): + if self.gc_trace: + self.gc_trace(*args) + if self.gc_fnc: + self.gc_fnc() + + def aX_vcts(self, sv, MN): + num_inp = len(sv) + + def e_xL(idx, d=None, is_a=True): + j, i = idx // BP_N, idx % BP_N + r = None + if j >= num_inp: + r = ZERO if is_a else MINUS_ONE + elif sv[j][i // 8] & (1 << i % 8): + r = ONE if is_a else ZERO + else: + r = ZERO if is_a else MINUS_ONE + if d: + memcpy(d, 0, r, 0, 32) + return r + + aL = KeyVEval(MN, lambda i, d: e_xL(i, d, True)) + aR = KeyVEval(MN, lambda i, d: e_xL(i, d, False)) + return aL, aR + + def _det_mask_init(self): + memcpy(self.tmp_det_buff, 0, self.proof_sec, 0, len(self.proof_sec)) + + def _det_mask(self, i, is_sL=True, dst=None): + dst = _ensure_dst_key(dst) + if self.fnc_det_mask: + return self.fnc_det_mask(i, is_sL, dst) + self.tmp_det_buff[64] = int(is_sL) + memcpy(self.tmp_det_buff, 65, ZERO, 0, 4) + dump_uvarint_b_into(i, self.tmp_det_buff, 65) + crypto.hash_to_scalar_into(self.tmp_sc_1, self.tmp_det_buff) + crypto.encodeint_into(dst, self.tmp_sc_1) + return dst + + def _gprec_aux(self, size): + return KeyVPrecomp( + size, self.Gprec, lambda i, d: get_exponent(d, XMR_H, i * 2 + 1) + ) + + def _hprec_aux(self, size): + return KeyVPrecomp(size, self.Hprec, lambda i, d: get_exponent(d, XMR_H, i * 2)) + + def _two_aux(self, size): + # Simple recursive exponentiation from precomputed results + lx = len(self.twoN) + + def pow_two(i, d=None): + if i < lx: + return self.twoN[i] + + d = _ensure_dst_key(d) + flr = i // 2 + + lw = pow_two(flr) + rw = pow_two(flr + 1 if flr != i / 2.0 else lw) + return sc_mul(d, lw, rw) + + return KeyVPrecomp(size, self.twoN, pow_two) + + def sL_vct(self, ln=BP_N): + return ( + KeyVEval(ln, lambda i, dst: self._det_mask(i, True, dst)) + if self.use_det_masks + else self.sX_gen(ln) + ) + + def sR_vct(self, ln=BP_N): + return ( + KeyVEval(ln, lambda i, dst: self._det_mask(i, False, dst)) + if self.use_det_masks + else self.sX_gen(ln) + ) + + def sX_gen(self, ln=BP_N): + buff = bytearray(ln * 32) + buff_mv = memoryview(buff) + sc = crypto.new_scalar() + for i in range(ln): + crypto.random_scalar(sc) + crypto.encodeint_into(buff_mv[i * 32 : (i + 1) * 32], sc) + gc_iter(i) + return KeyV(buffer=buff) + + def vector_exponent(self, a, b, dst=None): + return vector_exponent_custom(self.Gprec, self.Hprec, a, b, dst) + + def prove_testnet(self, sv, gamma): + return self.prove(sv, gamma, proof_v8=True) + + def prove(self, sv, gamma, proof_v8=False): + return self.prove_batch([sv], [gamma], proof_v8=proof_v8) + + def prove_setup(self, sv, gamma, proof_v8=False): + utils.ensure(len(sv) == len(gamma), "|sv| != |gamma|") + utils.ensure(len(sv) > 0, "sv empty") + + self.proof_sec = crypto.random_bytes(64) + self._det_mask_init() + sv = [crypto.encodeint(x) for x in sv] + gamma = [crypto.encodeint(x) for x in gamma] + + M, logM = 1, 0 + while M <= BP_M and M < len(sv): + logM += 1 + M = 1 << logM + MN = M * BP_N + + V = _ensure_dst_keyvect(None, len(sv)) + for i in range(len(sv)): + add_keys2(tmp_bf_0, gamma[i], sv[i], XMR_H) + if not proof_v8: + scalarmult_key(tmp_bf_0, tmp_bf_0, INV_EIGHT) + V.read(i, tmp_bf_0) + + aL, aR = self.aX_vcts(sv, MN) + return M, logM, aL, aR, V, gamma + + def prove_batch(self, sv, gamma, proof_v8=False): + M, logM, aL, aR, V, gamma = self.prove_setup(sv, gamma, proof_v8) + hash_cache = _ensure_dst_key() + while True: + self.gc(10) + r = self._prove_batch_main( + V, gamma, aL, aR, hash_cache, logM, BP_LOG_N, M, BP_N, proof_v8 + ) + if r[0]: + break + return r[1] + + def _prove_batch_main( + self, V, gamma, aL, aR, hash_cache, logM, logN, M, N, proof_v8=False + ): + logMN = logM + logN + MN = M * N + hash_vct_to_scalar(hash_cache, V) + + # Extended precomputed GiHi + Gprec = self._gprec_aux(MN) + Hprec = self._hprec_aux(MN) + + # PAPER LINES 38-39 + alpha = sc_gen() + ve = _ensure_dst_key() + A = _ensure_dst_key() + vector_exponent_custom(Gprec, Hprec, aL, aR, ve) + add_keys(A, ve, scalarmult_base(tmp_bf_1, alpha)) + if not proof_v8: + scalarmult_key(A, A, INV_EIGHT) + self.gc(11) + + # PAPER LINES 40-42 + sL = self.sL_vct(MN) + sR = self.sR_vct(MN) + rho = sc_gen() + vector_exponent_custom(Gprec, Hprec, sL, sR, ve) + S = _ensure_dst_key() + add_keys(S, ve, scalarmult_base(tmp_bf_1, rho)) + if not proof_v8: + scalarmult_key(S, S, INV_EIGHT) + del (ve) + self.gc(12) + + # PAPER LINES 43-45 + y = _ensure_dst_key() + hash_cache_mash(y, hash_cache, A, S) + if y == ZERO: + return (0,) + + z = _ensure_dst_key() + hash_to_scalar(hash_cache, y) + copy_key(z, hash_cache) + if z == ZERO: + return (0,) + + # Polynomial construction by coefficients + zMN = const_vector(z, MN) + l0 = _ensure_dst_keyvect(None, MN) + vector_subtract(aL, zMN, l0) + l1 = sL + self.gc(13) + + # This computes the ugly sum/concatenation from PAPER LINE 65 + # r0 = aR + z + r0 = vector_add(aR, zMN) + del (zMN) + self.gc(14) + + # r0 = r0 \odot yMN => r0[i] = r0[i] * y^i + # r1 = sR \odot yMN => r1[i] = sR[i] * y^i + yMN = vector_powers(y, MN, dynamic=False) + hadamard(r0, yMN, dst=r0) + self.gc(15) + + # r0 = r0 + zero_twos + zpow = vector_powers(z, M + 2) + twoN = self._two_aux(MN) + zero_twos = vector_z_two(N, logN, M, zpow, twoN, dynamic=True, raw=True) + vector_gen( + r0, + len(r0), + lambda i, d: crypto.encodeint_into( + d, + crypto.sc_add_into( + tmp_sc_1, + zero_twos[i], # noqa: F821 + crypto.decodeint_into_noreduce(tmp_sc_2, r0.to(i)), # noqa: F821 + ), + ), + ) + + del (zero_twos, twoN) + self.gc(15) + + # Polynomial construction before PAPER LINE 46 + # r1 = KeyVEval(MN, lambda i, d: sc_mul(d, yMN[i], sR[i])) + # r1 optimization possible, but has clashing sc registers. + # Moreover, max memory complexity is 4MN as below (while loop). + r1 = hadamard(yMN, sR, yMN) # re-use yMN vector for r1 + del (yMN, sR) + self.gc(16) + + # Inner products + # l0 = aL - z r0 = ((aR + z) \cdot ypow) + zt + # l1 = sL r1 = sR \cdot ypow + # t1_1 = l0 . r1, t1_2 = l1 . r0 + # t1 = t1_1 + t1_2, t2 = l1 . r1 + # l = l0 \odot x*l1 r = r0 \odot x*r1 + t1, t2 = cross_inner_product(l0, r0, l1, r1) + self.gc(17) + + # PAPER LINES 47-48 + tau1, tau2 = sc_gen(), sc_gen() + T1, T2 = _ensure_dst_key(), _ensure_dst_key() + + add_keys(T1, scalarmultH(tmp_bf_1, t1), scalarmult_base(tmp_bf_2, tau1)) + if not proof_v8: + scalarmult_key(T1, T1, INV_EIGHT) + + add_keys(T2, scalarmultH(tmp_bf_1, t2), scalarmult_base(tmp_bf_2, tau2)) + if not proof_v8: + scalarmult_key(T2, T2, INV_EIGHT) + del (t1, t2) + self.gc(17) + + # PAPER LINES 49-51 + x = _ensure_dst_key() + hash_cache_mash(x, hash_cache, z, T1, T2) + if x == ZERO: + return (0,) + + # PAPER LINES 52-53 + taux = _ensure_dst_key() + copy_key(taux, ZERO) + sc_mul(taux, tau1, x) + xsq = _ensure_dst_key() + sc_mul(xsq, x, x) + sc_muladd(taux, tau2, xsq, taux) + del (xsq, tau1, tau2) + for j in range(1, len(V) + 1): + sc_muladd(taux, zpow.to(j + 1), gamma[j - 1], taux) + del (zpow) + + self.gc(18) + mu = _ensure_dst_key() + sc_muladd(mu, x, rho, alpha) + del (rho, alpha) + + # PAPER LINES 54-57 + # l = l0 \odot x*l1, has to evaluated as it becomes aprime in the loop + l = vector_gen( + l0, + len(l0), + lambda i, d: sc_add(d, d, sc_mul(tmp_bf_1, l1.to(i), x)), # noqa: F821 + ) + del (l0, l1, sL) + self.gc(19) + + # r = r0 \odot x*r1, has to evaluated as it becomes bprime in the loop + r = vector_gen( + r0, + len(r0), + lambda i, d: sc_add(d, d, sc_mul(tmp_bf_1, r1.to(i), x)), # noqa: F821 + ) + t = inner_product(l, r) + del (r1, r0) + self.gc(19) + + # PAPER LINES 32-33 + x_ip = hash_cache_mash(None, hash_cache, x, taux, mu, t) + if x_ip == ZERO: + return 0, None + + # PHASE 2 + # These are used in the inner product rounds + nprime = MN + Gprime = _ensure_dst_keyvect(None, MN) + Hprime = _ensure_dst_keyvect(None, MN) + aprime = l + bprime = r + yinv = invert(None, y) + yinvpow = init_key(ONE) + self.gc(20) + + for i in range(0, MN): + Gprime.read(i, Gprec.to(i)) + scalarmult_key(tmp_bf_0, Hprec.to(i), yinvpow) + Hprime.read(i, tmp_bf_0) + sc_mul(yinvpow, yinvpow, yinv) + gc_iter(i) + self.gc(21) + + L = _ensure_dst_keyvect(None, logMN) + R = _ensure_dst_keyvect(None, logMN) + cL = _ensure_dst_key() + cR = _ensure_dst_key() + winv = _ensure_dst_key() + w_round = _ensure_dst_key() + tmp = _ensure_dst_key() + + round = 0 + _tmp_k_1 = _ensure_dst_key() + + # PAPER LINE 13 + while nprime > 1: + # PAPER LINE 15 + npr2 = nprime + nprime >>= 1 + self.gc(22) + + # PAPER LINES 16-17 + inner_product( + aprime.slice_view(0, nprime), bprime.slice_view(nprime, npr2), cL + ) + + inner_product( + aprime.slice_view(nprime, npr2), bprime.slice_view(0, nprime), cR + ) + self.gc(23) + + # PAPER LINES 18-19 + vector_exponent_custom( + Gprime.slice_view(nprime, npr2), + Hprime.slice_view(0, nprime), + aprime.slice_view(0, nprime), + bprime.slice_view(nprime, npr2), + tmp_bf_0, + ) + + sc_mul(tmp, cL, x_ip) + add_keys(tmp_bf_0, tmp_bf_0, scalarmultH(_tmp_k_1, tmp)) + if not proof_v8: + scalarmult_key(tmp_bf_0, tmp_bf_0, INV_EIGHT) + L.read(round, tmp_bf_0) + self.gc(24) + + vector_exponent_custom( + Gprime.slice_view(0, nprime), + Hprime.slice_view(nprime, npr2), + aprime.slice_view(nprime, npr2), + bprime.slice_view(0, nprime), + tmp_bf_0, + ) + + sc_mul(tmp, cR, x_ip) + add_keys(tmp_bf_0, tmp_bf_0, scalarmultH(_tmp_k_1, tmp)) + if not proof_v8: + scalarmult_key(tmp_bf_0, tmp_bf_0, INV_EIGHT) + R.read(round, tmp_bf_0) + self.gc(25) + + # PAPER LINES 21-22 + hash_cache_mash(w_round, hash_cache, L.to(round), R.to(round)) + if w_round == ZERO: + return (0,) + + # PAPER LINES 24-25 + invert(winv, w_round) + self.gc(26) + + hadamard_fold(Gprime, winv, w_round) + self.gc(27) + + hadamard_fold(Hprime, w_round, winv, Gprime, nprime) + Hprime.realloc_init_from(nprime, Gprime, nprime, round < 2) + self.gc(28) + + # PAPER LINES 28-29 + scalar_fold(aprime, w_round, winv, Gprime, nprime) + aprime.realloc_init_from(nprime, Gprime, nprime, round < 2) + self.gc(29) + + scalar_fold(bprime, winv, w_round, Gprime, nprime) + bprime.realloc_init_from(nprime, Gprime, nprime, round < 2) + self.gc(30) + + # Finally resize Gprime which was buffer for all ops + Gprime.resize(nprime, realloc=True) + round += 1 + + from apps.monero.xmr.serialize_messages.tx_rsig_bulletproof import Bulletproof + + return ( + 1, + Bulletproof( + V=V, + A=A, + S=S, + T1=T1, + T2=T2, + taux=taux, + mu=mu, + L=L, + R=R, + a=aprime.to(0), + b=bprime.to(0), + t=t, + ), + ) + + def verify_testnet(self, proof): + return self.verify(proof, proof_v8=True) + + def verify(self, proof, proof_v8=False): + return self.verify_batch([proof], proof_v8=proof_v8) + + def verify_batch(self, proofs, single_optim=True, proof_v8=False): + """ + BP batch verification + :param proofs: + :param single_optim: single proof memory optimization + :param proof_v8: previous testnet version + :return: + """ + max_length = 0 + for proof in proofs: + utils.ensure(is_reduced(proof.taux), "Input scalar not in range") + utils.ensure(is_reduced(proof.mu), "Input scalar not in range") + utils.ensure(is_reduced(proof.a), "Input scalar not in range") + utils.ensure(is_reduced(proof.b), "Input scalar not in range") + utils.ensure(is_reduced(proof.t), "Input scalar not in range") + utils.ensure(len(proof.V) >= 1, "V does not have at least one element") + utils.ensure(len(proof.L) == len(proof.R), "|L| != |R|") + utils.ensure(len(proof.L) > 0, "Empty proof") + max_length = max(max_length, len(proof.L)) + + utils.ensure(max_length < 32, "At least one proof is too large") + + maxMN = 1 << max_length + logN = 6 + N = 1 << logN + tmp = _ensure_dst_key() + + # setup weighted aggregates + is_single = len(proofs) == 1 and single_optim # ph4 + z1 = init_key(ZERO) + z3 = init_key(ZERO) + m_z4 = vector_dup(ZERO, maxMN) if not is_single else None + m_z5 = vector_dup(ZERO, maxMN) if not is_single else None + m_y0 = init_key(ZERO) + y1 = init_key(ZERO) + muex_acc = init_key(ONE) + + Gprec = self._gprec_aux(maxMN) + Hprec = self._hprec_aux(maxMN) + + for proof in proofs: + M = 1 + logM = 0 + while M <= BP_M and M < len(proof.V): + logM += 1 + M = 1 << logM + + utils.ensure(len(proof.L) == 6 + logM, "Proof is not the expected size") + MN = M * N + weight_y = crypto.encodeint(crypto.random_scalar()) + weight_z = crypto.encodeint(crypto.random_scalar()) + + # Reconstruct the challenges + hash_cache = hash_vct_to_scalar(None, proof.V) + y = hash_cache_mash(None, hash_cache, proof.A, proof.S) + utils.ensure(y != ZERO, "y == 0") + z = hash_to_scalar(None, y) + copy_key(hash_cache, z) + utils.ensure(z != ZERO, "z == 0") + + x = hash_cache_mash(None, hash_cache, z, proof.T1, proof.T2) + utils.ensure(x != ZERO, "x == 0") + x_ip = hash_cache_mash(None, hash_cache, x, proof.taux, proof.mu, proof.t) + utils.ensure(x_ip != ZERO, "x_ip == 0") + + # PAPER LINE 61 + sc_mulsub(m_y0, proof.taux, weight_y, m_y0) + zpow = vector_powers(z, M + 3) + + k = _ensure_dst_key() + ip1y = vector_power_sum(y, MN) + sc_mulsub(k, zpow[2], ip1y, ZERO) + for j in range(1, M + 1): + utils.ensure(j + 2 < len(zpow), "invalid zpow index") + sc_mulsub(k, zpow.to(j + 2), BP_IP12, k) + + # VERIFY_line_61rl_new + sc_muladd(tmp, z, ip1y, k) + sc_sub(tmp, proof.t, tmp) + + sc_muladd(y1, tmp, weight_y, y1) + weight_y8 = init_key(weight_y) + if not proof_v8: + weight_y8 = sc_mul(None, weight_y, EIGHT) + + muex = MultiExpSequential(points=[pt for pt in proof.V]) + for j in range(len(proof.V)): + sc_mul(tmp, zpow[j + 2], weight_y8) + muex.add_scalar(init_key(tmp)) + + sc_mul(tmp, x, weight_y8) + muex.add_pair(init_key(tmp), proof.T1) + + xsq = _ensure_dst_key() + sc_mul(xsq, x, x) + + sc_mul(tmp, xsq, weight_y8) + muex.add_pair(init_key(tmp), proof.T2) + + weight_z8 = init_key(weight_z) + if not proof_v8: + weight_z8 = sc_mul(None, weight_z, EIGHT) + + muex.add_pair(weight_z8, proof.A) + sc_mul(tmp, x, weight_z8) + muex.add_pair(init_key(tmp), proof.S) + + multiexp(tmp, muex, False) + add_keys(muex_acc, muex_acc, tmp) + del (muex) + + # Compute the number of rounds for the inner product + rounds = logM + logN + utils.ensure(rounds > 0, "Zero rounds") + + # PAPER LINES 21-22 + # The inner product challenges are computed per round + w = _ensure_dst_keyvect(None, rounds) + for i in range(rounds): + hash_cache_mash(tmp_bf_0, hash_cache, proof.L[i], proof.R[i]) + w.read(i, tmp_bf_0) + utils.ensure(w[i] != ZERO, "w[i] == 0") + + # Basically PAPER LINES 24-25 + # Compute the curvepoints from G[i] and H[i] + yinvpow = init_key(ONE) + ypow = init_key(ONE) + yinv = invert(None, y) + self.gc(61) + + winv = _ensure_dst_keyvect(None, rounds) + for i in range(rounds): + invert(tmp_bf_0, w.to(i)) + winv.read(i, tmp_bf_0) + self.gc(62) + + g_scalar = _ensure_dst_key() + h_scalar = _ensure_dst_key() + twoN = self._two_aux(N) + for i in range(MN): + copy_key(g_scalar, proof.a) + sc_mul(h_scalar, proof.b, yinvpow) + + for j in range(rounds - 1, -1, -1): + J = len(w) - j - 1 + + if (i & (1 << j)) == 0: + sc_mul(g_scalar, g_scalar, winv.to(J)) + sc_mul(h_scalar, h_scalar, w.to(J)) + else: + sc_mul(g_scalar, g_scalar, w.to(J)) + sc_mul(h_scalar, h_scalar, winv.to(J)) + + # Adjust the scalars using the exponents from PAPER LINE 62 + sc_add(g_scalar, g_scalar, z) + utils.ensure(2 + i // N < len(zpow), "invalid zpow index") + utils.ensure(i % N < len(twoN), "invalid twoN index") + sc_mul(tmp, zpow.to(2 + i // N), twoN.to(i % N)) + sc_muladd(tmp, z, ypow, tmp) + sc_mulsub(h_scalar, tmp, yinvpow, h_scalar) + + if not is_single: # ph4 + sc_mulsub(m_z4[i], g_scalar, weight_z, m_z4[i]) + sc_mulsub(m_z5[i], h_scalar, weight_z, m_z5[i]) + else: + sc_mul(tmp, g_scalar, weight_z) + sub_keys(muex_acc, muex_acc, scalarmult_key(tmp, Gprec.to(i), tmp)) + + sc_mul(tmp, h_scalar, weight_z) + sub_keys(muex_acc, muex_acc, scalarmult_key(tmp, Hprec.to(i), tmp)) + + if i != MN - 1: + sc_mul(yinvpow, yinvpow, yinv) + sc_mul(ypow, ypow, y) + if i & 15 == 0: + self.gc(62) + + del (g_scalar, h_scalar, twoN) + self.gc(63) + + sc_muladd(z1, proof.mu, weight_z, z1) + muex = MultiExpSequential( + point_fnc=lambda i, d: proof.L[i // 2] + if i & 1 == 0 + else proof.R[i // 2] + ) + for i in range(rounds): + sc_mul(tmp, w[i], w[i]) + sc_mul(tmp, tmp, weight_z8) + muex.add_scalar(tmp) + sc_mul(tmp, winv[i], winv[i]) + sc_mul(tmp, tmp, weight_z8) + muex.add_scalar(tmp) + + acc = multiexp(None, muex, False) + add_keys(muex_acc, muex_acc, acc) + + sc_mulsub(tmp, proof.a, proof.b, proof.t) + sc_mul(tmp, tmp, x_ip) + sc_muladd(z3, tmp, weight_z, z3) + + sc_sub(tmp, m_y0, z1) + z3p = sc_sub(None, z3, y1) + + check2 = crypto.encodepoint( + crypto.ge25519_double_scalarmult_base_vartime( + crypto.decodeint(z3p), crypto.xmr_H(), crypto.decodeint(tmp) + ) + ) + add_keys(muex_acc, muex_acc, check2) + + if not is_single: # ph4 + muex = MultiExpSequential( + point_fnc=lambda i, d: Gprec.to(i // 2) + if i & 1 == 0 + else Hprec.to(i // 2) + ) + for i in range(maxMN): + muex.add_scalar(m_z4[i]) + muex.add_scalar(m_z5[i]) + add_keys(muex_acc, muex_acc, multiexp(None, muex, True)) + + if muex_acc != ONE: + raise ValueError("Verification failure at step 2") + return True diff --git a/src/apps/monero/xmr/credentials.py b/src/apps/monero/xmr/credentials.py new file mode 100644 index 000000000..b841c42ed --- /dev/null +++ b/src/apps/monero/xmr/credentials.py @@ -0,0 +1,45 @@ +from apps.monero.xmr import crypto +from apps.monero.xmr.addresses import encode_addr +from apps.monero.xmr.networks import NetworkTypes, net_version + + +class AccountCreds: + """ + Stores account private keys + """ + + def __init__( + self, + view_key_private=None, + spend_key_private=None, + view_key_public=None, + spend_key_public=None, + address=None, + network_type=NetworkTypes.MAINNET, + ): + self.view_key_private = view_key_private + self.view_key_public = view_key_public + self.spend_key_private = spend_key_private + self.spend_key_public = spend_key_public + self.address = address + self.network_type = network_type + + @classmethod + def new_wallet( + cls, priv_view_key, priv_spend_key, network_type=NetworkTypes.MAINNET + ): + pub_view_key = crypto.scalarmult_base(priv_view_key) + pub_spend_key = crypto.scalarmult_base(priv_spend_key) + addr = encode_addr( + net_version(network_type), + crypto.encodepoint(pub_spend_key), + crypto.encodepoint(pub_view_key), + ) + return cls( + view_key_private=priv_view_key, + spend_key_private=priv_spend_key, + view_key_public=pub_view_key, + spend_key_public=pub_spend_key, + address=addr, + network_type=network_type, + ) diff --git a/src/apps/monero/xmr/crypto/__init__.py b/src/apps/monero/xmr/crypto/__init__.py new file mode 100644 index 000000000..9f746eec9 --- /dev/null +++ b/src/apps/monero/xmr/crypto/__init__.py @@ -0,0 +1,299 @@ +# Author: Dusan Klinec, ph4r05, 2018 +# +# Resources: +# https://cr.yp.to +# https://github.com/monero-project/mininero +# https://godoc.org/github.com/agl/ed25519/edwards25519 +# https://tools.ietf.org/html/draft-josefsson-eddsa-ed25519-00#section-4 +# https://github.com/monero-project/research-lab + +from trezor.crypto import hmac, monero as tcry, random +from trezor.crypto.hashlib import sha3_256 + +NULL_KEY_ENC = b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + +random_bytes = random.bytes +ct_equals = tcry.ct_equals + + +def keccak_factory(data=None): + return sha3_256(data=data, keccak=True) + + +get_keccak = keccak_factory +keccak_hash = tcry.xmr_fast_hash +keccak_hash_into = tcry.xmr_fast_hash + + +def keccak_2hash(inp): + return keccak_hash(keccak_hash(inp)) + + +def compute_hmac(key, msg=None): + h = hmac.new(key, msg=msg, digestmod=keccak_factory) + return h.digest() + + +# +# EC +# + + +new_point = tcry.ge25519_set_neutral + + +def new_scalar(): + return tcry.init256_modm(0) + + +decodepoint = tcry.ge25519_unpack_vartime +decodepoint_into = tcry.ge25519_unpack_vartime +encodepoint = tcry.ge25519_pack +encodepoint_into = tcry.ge25519_pack + +decodeint = tcry.unpack256_modm +decodeint_into_noreduce = tcry.unpack256_modm_noreduce +decodeint_into = tcry.unpack256_modm +encodeint = tcry.pack256_modm +encodeint_into = tcry.pack256_modm + +check_ed25519point = tcry.ge25519_check + +scalarmult_base = tcry.ge25519_scalarmult_base +scalarmult_base_into = tcry.ge25519_scalarmult_base +scalarmult = tcry.ge25519_scalarmult +scalarmult_into = tcry.ge25519_scalarmult + +point_add = tcry.ge25519_add +point_add_into = tcry.ge25519_add +point_sub = tcry.ge25519_sub +point_sub_into = tcry.ge25519_sub +point_eq = tcry.ge25519_eq +point_double = tcry.ge25519_double +point_double_into = tcry.ge25519_double +point_mul8 = tcry.ge25519_mul8 +point_mul8_into = tcry.ge25519_mul8 + +INV_EIGHT = b"\x79\x2f\xdc\xe2\x29\xe5\x06\x61\xd0\xda\x1c\x7d\xb3\x9d\xd3\x07\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x06" +INV_EIGHT_SC = decodeint(INV_EIGHT) + + +def sc_inv_eight(): + return INV_EIGHT_SC + + +# +# Zmod(order), scalar values field +# + + +def sc_0(): + return tcry.init256_modm(0) + + +def sc_0_into(r): + return tcry.init256_modm(r, 0) + + +def sc_init(x): + if x >= (1 << 64): + raise ValueError("Initialization works up to 64-bit only") + return tcry.init256_modm(x) + + +def sc_init_into(r, x): + if x >= (1 << 64): + raise ValueError("Initialization works up to 64-bit only") + return tcry.init256_modm(r, x) + + +sc_get64 = tcry.get256_modm +sc_check = tcry.check256_modm +check_sc = tcry.check256_modm + +sc_add = tcry.add256_modm +sc_add_into = tcry.add256_modm +sc_sub = tcry.sub256_modm +sc_sub_into = tcry.sub256_modm +sc_mul = tcry.mul256_modm +sc_mul_into = tcry.mul256_modm + + +def sc_isnonzero(c): + """ + Returns true if scalar is non-zero + """ + return not tcry.iszero256_modm(c) + + +sc_eq = tcry.eq256_modm +sc_mulsub = tcry.mulsub256_modm +sc_mulsub_into = tcry.mulsub256_modm +sc_muladd = tcry.muladd256_modm +sc_muladd_into = tcry.muladd256_modm +sc_inv_into = tcry.inv256_modm + + +def random_scalar(r=None): + return tcry.xmr_random_scalar(r if r is not None else new_scalar()) + + +# +# GE - ed25519 group +# + + +def ge25519_double_scalarmult_base_vartime(a, A, b): + """ + void ge25519_double_scalarmult_vartime(ge25519 *r, const ge25519 *p1, const bignum256modm s1, const bignum256modm s2); + r = a * A + b * B + """ + R = tcry.ge25519_double_scalarmult_vartime(A, a, b) + return R + + +ge25519_double_scalarmult_vartime2 = tcry.xmr_add_keys3 + + +def identity(byte_enc=False): + idd = tcry.ge25519_set_neutral() + return idd if not byte_enc else encodepoint(idd) + + +identity_into = tcry.ge25519_set_neutral + +""" +https://www.imperialviolet.org/2013/12/25/elligator.html +http://elligator.cr.yp.to/ +http://elligator.cr.yp.to/elligator-20130828.pdf +""" +ge_frombytes_vartime_check = tcry.ge25519_check + +# +# Monero specific +# + + +cn_fast_hash = keccak_hash + + +def hash_to_scalar(data, length=None): + """ + H_s(P) + """ + dt = data[:length] if length else data + return tcry.xmr_hash_to_scalar(dt) + + +def hash_to_scalar_into(r, data, length=None): + dt = data[:length] if length else data + return tcry.xmr_hash_to_scalar(r, dt) + + +""" +H_p(buf) + +Code adapted from MiniNero: https://github.com/monero-project/mininero +https://github.com/monero-project/research-lab/blob/master/whitepaper/ge_fromfe_writeup/ge_fromfe.pdf +http://archive.is/yfINb +""" +hash_to_point = tcry.xmr_hash_to_ec +hash_to_point_into = tcry.xmr_hash_to_ec + + +# +# XMR +# + + +xmr_H = tcry.ge25519_set_h + + +def scalarmult_h(i): + return scalarmult(xmr_H(), sc_init(i) if isinstance(i, int) else i) + + +add_keys2 = tcry.xmr_add_keys2_vartime +add_keys2_into = tcry.xmr_add_keys2_vartime +add_keys3 = tcry.xmr_add_keys3_vartime +add_keys3_into = tcry.xmr_add_keys3_vartime +gen_commitment = tcry.xmr_gen_c + + +def generate_key_derivation(pub, sec): + """ + Key derivation: 8*(key2*key1) + """ + sc_check(sec) # checks that the secret key is uniform enough... + ge_frombytes_vartime_check(pub) + return tcry.xmr_generate_key_derivation(pub, sec) + + +def derivation_to_scalar(derivation, output_index): + """ + H_s(derivation || varint(output_index)) + """ + check_ed25519point(derivation) + return tcry.xmr_derivation_to_scalar(derivation, output_index) + + +def derive_public_key(derivation, output_index, B): + """ + H_s(derivation || varint(output_index))G + B + """ + ge_frombytes_vartime_check(B) # check some conditions on the point + check_ed25519point(B) + + return tcry.xmr_derive_public_key(derivation, output_index, B) + + +def derive_secret_key(derivation, output_index, base): + """ + base + H_s(derivation || varint(output_index)) + """ + sc_check(base) + return tcry.xmr_derive_private_key(derivation, output_index, base) + + +def get_subaddress_secret_key(secret_key, major=0, minor=0): + """ + Builds subaddress secret key from the subaddress index + Hs(SubAddr || a || index_major || index_minor) + """ + return tcry.xmr_get_subaddress_secret_key(major, minor, secret_key) + + +# +# Repr invariant +# + + +def generate_signature(data, priv): + """ + Generate EC signature + crypto_ops::generate_signature(const hash &prefix_hash, const public_key &pub, const secret_key &sec, signature &sig) + """ + pub = scalarmult_base(priv) + + k = random_scalar() + comm = scalarmult_base(k) + + buff = data + encodepoint(pub) + encodepoint(comm) + c = hash_to_scalar(buff) + r = sc_mulsub(priv, c, k) + return c, r, pub + + +def check_signature(data, c, r, pub): + """ + EC signature verification + """ + check_ed25519point(pub) + if sc_check(c) != 0 or sc_check(r) != 0: + raise ValueError("Signature error") + + tmp2 = point_add(scalarmult(pub, c), scalarmult_base(r)) + buff = data + encodepoint(pub) + encodepoint(tmp2) + tmp_c = hash_to_scalar(buff) + res = sc_sub(tmp_c, c) + return not sc_isnonzero(res) diff --git a/src/apps/monero/xmr/crypto/chacha_poly.py b/src/apps/monero/xmr/crypto/chacha_poly.py new file mode 100644 index 000000000..7c90995f0 --- /dev/null +++ b/src/apps/monero/xmr/crypto/chacha_poly.py @@ -0,0 +1,40 @@ +from trezor.crypto import chacha20poly1305 as ChaCha20Poly1305, monero, random + + +def encrypt(key, plaintext, associated_data=None): + """ + Uses ChaCha20Poly1305 for encryption + """ + nonce = random.bytes(12) + cipher = ChaCha20Poly1305(key, nonce) + if associated_data: + cipher.auth(associated_data) + ciphertext = cipher.encrypt(plaintext) + tag = cipher.finish() + return nonce, ciphertext + tag, b"" + + +def decrypt(key, iv, ciphertext, tag=None, associated_data=None): + """ + ChaCha20Poly1305 decryption + """ + cipher = ChaCha20Poly1305(key, iv) + if associated_data: + cipher.auth(associated_data) + exp_tag, ciphertext = ciphertext[-16:], ciphertext[:-16] + plaintext = cipher.decrypt(ciphertext) + tag = cipher.finish() + if not monero.ct_equals(tag, exp_tag): + raise ValueError("tag invalid") + + return plaintext + + +def encrypt_pack(key, plaintext, associated_data=None): + b = encrypt(key, plaintext, associated_data) + return b[0] + b[1] + + +def decrypt_pack(key, ciphertext): + cp = memoryview(ciphertext) + return decrypt(key, cp[:12], cp[12:], None) diff --git a/src/apps/monero/xmr/keccak_hasher.py b/src/apps/monero/xmr/keccak_hasher.py new file mode 100644 index 000000000..0a3dc0001 --- /dev/null +++ b/src/apps/monero/xmr/keccak_hasher.py @@ -0,0 +1,27 @@ +from trezor.utils import HashWriter + +from apps.monero.xmr import crypto +from apps.monero.xmr.serialize import int_serialize + + +class KeccakXmrArchive: + def __init__(self, ctx=None): + self.kwriter = get_keccak_writer(ctx) + + def get_digest(self): + return self.kwriter.get_digest() + + def buffer(self, buf): + return self.kwriter.write(buf) + + def uvarint(self, i): + int_serialize.dump_uvarint(self.kwriter, i) + + def uint(self, i, width): + int_serialize.dump_uint(self.kwriter, i, width) + + +def get_keccak_writer(ctx=None): + if ctx is None: + ctx = crypto.get_keccak() + return HashWriter(ctx) diff --git a/src/apps/monero/xmr/key_image.py b/src/apps/monero/xmr/key_image.py new file mode 100644 index 000000000..5522f6685 --- /dev/null +++ b/src/apps/monero/xmr/key_image.py @@ -0,0 +1,111 @@ +from apps.monero.xmr import crypto, monero +from apps.monero.xmr.serialize.int_serialize import dump_uvarint_b + + +def compute_hash(rr): + kck = crypto.get_keccak() + kck.update(rr.out_key) + kck.update(rr.tx_pub_key) + if rr.additional_tx_pub_keys: + for x in rr.additional_tx_pub_keys: + kck.update(x) + kck.update(dump_uvarint_b(rr.internal_output_index)) + return kck.digest() + + +def export_key_image(creds, subaddresses, td): + out_key = crypto.decodepoint(td.out_key) + tx_pub_key = crypto.decodepoint(td.tx_pub_key) + additional_tx_pub_keys = [crypto.decodepoint(x) for x in td.additional_tx_pub_keys] + ki, sig = _export_key_image( + creds, + subaddresses, + out_key, + tx_pub_key, + additional_tx_pub_keys, + td.internal_output_index, + ) + return ki, sig + + +def _export_key_image( + creds, subaddresses, pkey, tx_pub_key, additional_tx_pub_keys, out_idx, test=True +): + """ + Generates key image for the TXO + signature for the key image + """ + r = monero.generate_tx_spend_and_key_image_and_derivation( + creds, subaddresses, pkey, tx_pub_key, additional_tx_pub_keys, out_idx + ) + xi, ki, recv_derivation = r[:3] + + phash = crypto.encodepoint(ki) + sig = _generate_ring_signature(phash, ki, [pkey], xi, 0, test) + + return ki, sig + + +def _generate_ring_signature(prefix_hash, image, pubs, sec, sec_idx, test=False): + """ + Generates ring signature with key image. + void crypto_ops::generate_ring_signature() + """ + from trezor.utils import memcpy + + if test: + t = crypto.scalarmult_base(sec) + if not crypto.point_eq(t, pubs[sec_idx]): + raise ValueError("Invalid sec key") + + k_i = monero.generate_key_image(crypto.encodepoint(pubs[sec_idx]), sec) + if not crypto.point_eq(k_i, image): + raise ValueError("Key image invalid") + for k in pubs: + crypto.ge_frombytes_vartime_check(k) + + buff_off = len(prefix_hash) + buff = bytearray(buff_off + 2 * 32 * len(pubs)) + memcpy(buff, 0, prefix_hash, 0, buff_off) + mvbuff = memoryview(buff) + + sum = crypto.sc_0() + k = crypto.sc_0() + sig = [] + + for i in range(len(pubs)): + sig.append([crypto.sc_0(), crypto.sc_0()]) # c, r + + for i in range(len(pubs)): + if i == sec_idx: + k = crypto.random_scalar() + tmp3 = crypto.scalarmult_base(k) + crypto.encodepoint_into(mvbuff[buff_off : buff_off + 32], tmp3) + buff_off += 32 + + tmp3 = crypto.hash_to_point(crypto.encodepoint(pubs[i])) + tmp2 = crypto.scalarmult(tmp3, k) + crypto.encodepoint_into(mvbuff[buff_off : buff_off + 32], tmp2) + buff_off += 32 + + else: + sig[i] = [crypto.random_scalar(), crypto.random_scalar()] + tmp3 = pubs[i] + tmp2 = crypto.ge25519_double_scalarmult_base_vartime( + sig[i][0], tmp3, sig[i][1] + ) + crypto.encodepoint_into(mvbuff[buff_off : buff_off + 32], tmp2) + buff_off += 32 + + tmp3 = crypto.hash_to_point(crypto.encodepoint(tmp3)) + tmp2 = crypto.ge25519_double_scalarmult_vartime2( + sig[i][1], tmp3, sig[i][0], image + ) + crypto.encodepoint_into(mvbuff[buff_off : buff_off + 32], tmp2) + buff_off += 32 + + sum = crypto.sc_add(sum, sig[i][0]) + + h = crypto.hash_to_scalar(buff) + sig[sec_idx][0] = crypto.sc_sub(h, sum) + sig[sec_idx][1] = crypto.sc_mulsub(sig[sec_idx][0], sec, k) + return sig diff --git a/src/apps/monero/xmr/mlsag.py b/src/apps/monero/xmr/mlsag.py new file mode 100644 index 000000000..9aef687cb --- /dev/null +++ b/src/apps/monero/xmr/mlsag.py @@ -0,0 +1,320 @@ +""" +Multilayer Linkable Spontaneous Anonymous Group (MLSAG) +Optimized versions with incremental hashing. +Both Simple and Full Monero tx types are supported. + +See https://eprint.iacr.org/2015/1098.pdf for details. +Also explained in From Zero to Monero section 3.3 and 5. + +---------- + +Please note, that the MLSAG code is written in a generic manner, +where it is designed for multiple public keys (aka inputs). In another +words, MLSAG should be used to sign multiple inputs, but that is currently +not the case of Monero, where the inputs are signed one by one. +So the public keys matrix has always two rows (one for public keys, +one for commitments), although the algorithm is designed for `n` rows. + +This has one unfortunate effect where `rows` is always equal to 2 and +dsRows always to 1, but the algorithm is still written as the numbers +are arbitrary. That's why there are loops such as `for i in range(dsRows)` +where it is run only once currently. + +---------- + +Also note, that the matrix of public keys is indexed by columns first. +This is because the code was ported from the official Monero client, +which is written in C++ and where it does have some memory advantages. + +For ring size = 3 and one input the matrix M will look like this: +|------------------------|------------------------|------------------------| +| public key 0 | public key 1 | public key 2 | +| cmt 0 - pseudo_out cmt | cmt 1 - pseudo_out cmt | cmt 2 - pseudo_out cmt | + +and `sk` is equal to: +|--------------|-----------------------------------------------------| +| private key* | input secret key's mask - pseudo_out's mask (alpha) | + +* corresponding to one of the public keys (`index` denotes which one) + +---------- + +Mostly ported from official Monero client, but also inspired by Mininero. +Author: Dusan Klinec, ph4r05, 2018 +""" +from apps.monero.xmr import crypto + + +def generate_mlsag_full( + message, pubs, in_sk, out_sk_mask, out_pk_commitments, kLRki, index, txn_fee_key +): + cols = len(pubs) + if cols == 0: + raise ValueError("Empty pubs") + rows = len(pubs[0]) + if rows == 0: + raise ValueError("Empty pub row") + for i in range(cols): + if len(pubs[i]) != rows: + raise ValueError("pub is not rectangular") + + if len(in_sk) != rows: + raise ValueError("Bad inSk size") + if len(out_sk_mask) != len(out_pk_commitments): + raise ValueError("Bad outsk/putpk size") + + sk = _key_vector(rows + 1) + M = _key_matrix(rows + 1, cols) + for i in range(rows + 1): + sk[i] = crypto.sc_0() + + for i in range(cols): + M[i][rows] = crypto.identity() + for j in range(rows): + M[i][j] = crypto.decodepoint(pubs[i][j].dest) + M[i][rows] = crypto.point_add( + M[i][rows], crypto.decodepoint(pubs[i][j].commitment) + ) + + sk[rows] = crypto.sc_0() + for j in range(rows): + sk[j] = in_sk[j].dest + sk[rows] = crypto.sc_add(sk[rows], in_sk[j].mask) # add masks in last row + + for i in range(cols): + for j in range(len(out_pk_commitments)): + M[i][rows] = crypto.point_sub( + M[i][rows], crypto.decodepoint(out_pk_commitments[j]) + ) # subtract output Ci's in last row + + # Subtract txn fee output in last row + M[i][rows] = crypto.point_sub(M[i][rows], txn_fee_key) + + for j in range(len(out_pk_commitments)): + sk[rows] = crypto.sc_sub( + sk[rows], out_sk_mask[j] + ) # subtract output masks in last row + + return generate_mlsag(message, M, sk, kLRki, index, rows) + + +def generate_mlsag_simple(message, pubs, in_sk, a, cout, kLRki, index): + """ + MLSAG for RctType.Simple + :param message: the full message to be signed (actually its hash) + :param pubs: vector of MoneroRctKey; this forms the ring; point values in encoded form; (dest, mask) = (P, C) + :param in_sk: CtKey; spending private key with input commitment mask (original); better_name: input_secret_key + :param a: mask from the pseudo output commitment; better name: pseudo_out_alpha + :param cout: pseudo output commitment; point, decoded; better name: pseudo_out_c + :param kLRki: used only in multisig, currently not implemented + :param index: specifies corresponding public key to the `in_sk` in the pubs array + :return: MgSig + """ + # Monero signs inputs separately, so `rows` always equals 2 (pubkey, commitment) + # and `dsRows` is always 1 (denotes where the pubkeys "end") + rows = 2 + dsRows = 1 + cols = len(pubs) + if cols == 0: + raise ValueError("Empty pubs") + + sk = _key_vector(rows) + M = _key_matrix(rows, cols) + + sk[0] = in_sk.dest + sk[1] = crypto.sc_sub(in_sk.mask, a) + + for i in range(cols): + M[i][0] = crypto.decodepoint(pubs[i].dest) + M[i][1] = crypto.point_sub(crypto.decodepoint(pubs[i].commitment), cout) + + return generate_mlsag(message, M, sk, kLRki, index, dsRows) + + +def gen_mlsag_assert(pk, xx, kLRki, index, dsRows): + """ + Conditions check + """ + cols = len(pk) + if cols <= 1: + raise ValueError("Cols == 1") + if index >= cols: + raise ValueError("Index out of range") + + rows = len(pk[0]) + if rows == 0: + raise ValueError("Empty pk") + + for i in range(cols): + if len(pk[i]) != rows: + raise ValueError("pk is not rectangular") + if len(xx) != rows: + raise ValueError("Bad xx size") + if dsRows > rows: + raise ValueError("Bad dsRows size") + if kLRki and dsRows != 1: + raise ValueError("Multisig requires exactly 1 dsRows") + if kLRki: + raise NotImplementedError("Multisig not implemented") + return rows, cols + + +def generate_first_c_and_key_images( + message, rv, pk, xx, kLRki, index, dsRows, rows, cols +): + """ + MLSAG computation - the part with secret keys + :param message: the full message to be signed (actually its hash) + :param rv: MgSig + :param pk: matrix of public keys and commitments + :param xx: input secret array composed of a private key and commitment mask + :param kLRki: used only in multisig, currently not implemented + :param index: specifies corresponding public key to the `xx`'s private key in the `pk` array + :param dsRows: row number where the pubkeys "end" (and commitments follow) + :param rows: total number of rows + :param cols: size of ring + """ + Ip = _key_vector(dsRows) + rv.II = _key_vector(dsRows) + alpha = _key_vector(rows) + rv.ss = _key_matrix(rows, cols) + + tmp_buff = bytearray(32) + hasher = _hasher_message(message) + + for i in range(dsRows): + # this is somewhat extra as compared to the Ring Confidential Tx paper + # see footnote in From Zero to Monero section 3.3 + hasher.update(crypto.encodepoint(pk[index][i])) + if kLRki: + raise NotImplementedError("Multisig not implemented") + # alpha[i] = kLRki.k + # rv.II[i] = kLRki.ki + # hash_point(hasher, kLRki.L, tmp_buff) + # hash_point(hasher, kLRki.R, tmp_buff) + + else: + Hi = crypto.hash_to_point(crypto.encodepoint(pk[index][i])) + alpha[i] = crypto.random_scalar() + # L = alpha_i * G + aGi = crypto.scalarmult_base(alpha[i]) + # Ri = alpha_i * H(P_i) + aHPi = crypto.scalarmult(Hi, alpha[i]) + # key image + rv.II[i] = crypto.scalarmult(Hi, xx[i]) + _hash_point(hasher, aGi, tmp_buff) + _hash_point(hasher, aHPi, tmp_buff) + + Ip[i] = rv.II[i] + + for i in range(dsRows, rows): + alpha[i] = crypto.random_scalar() + # L = alpha_i * G + aGi = crypto.scalarmult_base(alpha[i]) + # for some reasons we omit calculating R here, which seems + # contrary to the paper, but it is in the Monero official client + # see https://github.com/monero-project/monero/blob/636153b2050aa0642ba86842c69ac55a5d81618d/src/ringct/rctSigs.cpp#L191 + _hash_point(hasher, pk[index][i], tmp_buff) + _hash_point(hasher, aGi, tmp_buff) + + # the first c + c_old = hasher.digest() + c_old = crypto.decodeint(c_old) + return c_old, Ip, alpha + + +def generate_mlsag(message, pk, xx, kLRki, index, dsRows): + """ + Multilayered Spontaneous Anonymous Group Signatures (MLSAG signatures) + + :param message: the full message to be signed (actually its hash) + :param pk: matrix of public keys and commitments + :param xx: input secret array composed of a private key and commitment mask + :param kLRki: used only in multisig, currently not implemented + :param index: specifies corresponding public key to the `xx`'s private key in the `pk` array + :param dsRows: separates pubkeys from commitment + :return MgSig + """ + from apps.monero.xmr.serialize_messages.tx_full import MgSig + + rows, cols = gen_mlsag_assert(pk, xx, kLRki, index, dsRows) + + rv = MgSig() + c, L, R, Hi = 0, None, None, None + + # calculates the "first" c, key images and random scalars alpha + c_old, Ip, alpha = generate_first_c_and_key_images( + message, rv, pk, xx, kLRki, index, dsRows, rows, cols + ) + + i = (index + 1) % cols + if i == 0: + rv.cc = c_old + + tmp_buff = bytearray(32) + while i != index: + rv.ss[i] = _generate_random_vector(rows) + hasher = _hasher_message(message) + + for j in range(dsRows): + # L = rv.ss[i][j] * G + c_old * pk[i][j] + L = crypto.add_keys2(rv.ss[i][j], c_old, pk[i][j]) + Hi = crypto.hash_to_point(crypto.encodepoint(pk[i][j])) + # R = rv.ss[i][j] * H(pk[i][j]) + c_old * Ip[j] + R = crypto.add_keys3(rv.ss[i][j], Hi, c_old, rv.II[j]) + _hash_point(hasher, pk[i][j], tmp_buff) + _hash_point(hasher, L, tmp_buff) + _hash_point(hasher, R, tmp_buff) + + for j in range(dsRows, rows): + # again, omitting R here as discussed above + L = crypto.add_keys2(rv.ss[i][j], c_old, pk[i][j]) + _hash_point(hasher, pk[i][j], tmp_buff) + _hash_point(hasher, L, tmp_buff) + + c = crypto.decodeint(hasher.digest()) + c_old = c + i = (i + 1) % cols + + if i == 0: + rv.cc = c_old + + for j in range(rows): + rv.ss[index][j] = crypto.sc_mulsub(c, xx[j], alpha[j]) + + return rv + + +def _key_vector(rows): + return [None] * rows + + +def _key_matrix(rows, cols): + """ + first index is columns (so slightly backward from math) + """ + rv = [None] * cols + for i in range(0, cols): + rv[i] = _key_vector(rows) + return rv + + +def _generate_random_vector(n): + """ + Generates vector of random scalars + """ + return [crypto.random_scalar() for _ in range(0, n)] + + +def _hasher_message(message): + """ + Returns incremental hasher for MLSAG + """ + ctx = crypto.get_keccak() + ctx.update(message) + return ctx + + +def _hash_point(hasher, point, tmp_buff): + crypto.encodepoint_into(tmp_buff, point) + hasher.update(tmp_buff) diff --git a/src/apps/monero/xmr/mlsag_hasher.py b/src/apps/monero/xmr/mlsag_hasher.py new file mode 100644 index 000000000..7129ffd75 --- /dev/null +++ b/src/apps/monero/xmr/mlsag_hasher.py @@ -0,0 +1,112 @@ +from apps.monero.xmr import crypto +from apps.monero.xmr.keccak_hasher import KeccakXmrArchive + + +class PreMlsagHasher: + """ + Iterative construction of the pre_mlsag_hash + """ + + def __init__(self): + self.is_simple = None + self.state = 0 + self.kc_master = crypto.get_keccak() + self.rsig_hasher = crypto.get_keccak() + self.rtcsig_hasher = KeccakXmrArchive() + + def init(self, is_simple): + if self.state != 0: + raise ValueError("State error") + + self.state = 1 + self.is_simple = is_simple + + def set_message(self, message): + self.kc_master.update(message) + + def set_type_fee(self, rv_type, fee): + if self.state != 1: + raise ValueError("State error") + self.state = 2 + self.rtcsig_hasher.uint(rv_type, 1) # UInt8 + self.rtcsig_hasher.uvarint(fee) # UVarintType + + def set_pseudo_out(self, out): + if self.state != 2 and self.state != 3: + raise ValueError("State error") + self.state = 3 + + # Manual serialization of the ECKey + self.rtcsig_hasher.buffer(out) + + def set_ecdh(self, ecdh): + if self.state != 2 and self.state != 3 and self.state != 4: + raise ValueError("State error") + self.state = 4 + self.rtcsig_hasher.buffer(ecdh) + + def set_out_pk_commitment(self, out_pk_commitment): + if self.state != 4 and self.state != 5: + raise ValueError("State error") + self.state = 5 + self.rtcsig_hasher.buffer(out_pk_commitment) # ECKey + + def rctsig_base_done(self): + if self.state != 5: + raise ValueError("State error") + self.state = 6 + + c_hash = self.rtcsig_hasher.get_digest() + self.kc_master.update(c_hash) + self.rtcsig_hasher = None + + def rsig_val(self, p, bulletproof, raw=False): + if self.state == 8: + raise ValueError("State error") + + if raw: + # Avoiding problem with the memory fragmentation. + # If the range proof is passed as a list, hash each element + # as the range proof is split to multiple byte arrays while + # preserving the byte ordering + if isinstance(p, list): + for x in p: + self.rsig_hasher.update(x) + else: + self.rsig_hasher.update(p) + return + + if bulletproof: + self.rsig_hasher.update(p.A) + self.rsig_hasher.update(p.S) + self.rsig_hasher.update(p.T1) + self.rsig_hasher.update(p.T2) + self.rsig_hasher.update(p.taux) + self.rsig_hasher.update(p.mu) + for i in range(len(p.L)): + self.rsig_hasher.update(p.L[i]) + for i in range(len(p.R)): + self.rsig_hasher.update(p.R[i]) + self.rsig_hasher.update(p.a) + self.rsig_hasher.update(p.b) + self.rsig_hasher.update(p.t) + + else: + for i in range(64): + self.rsig_hasher.update(p.asig.s0[i]) + for i in range(64): + self.rsig_hasher.update(p.asig.s1[i]) + self.rsig_hasher.update(p.asig.ee) + for i in range(64): + self.rsig_hasher.update(p.Ci[i]) + + def get_digest(self): + if self.state != 6: + raise ValueError("State error") + self.state = 8 + + c_hash = self.rsig_hasher.digest() + self.rsig_hasher = None + + self.kc_master.update(c_hash) + return self.kc_master.digest() diff --git a/src/apps/monero/xmr/monero.py b/src/apps/monero/xmr/monero.py new file mode 100644 index 000000000..ef4f94a00 --- /dev/null +++ b/src/apps/monero/xmr/monero.py @@ -0,0 +1,261 @@ +from micropython import const + +from apps.monero.xmr import crypto + +if False: + from apps.monero.xmr.types import * + + +DISPLAY_DECIMAL_POINT = const(12) + + +class XmrException(Exception): + pass + + +class XmrNoSuchAddressException(XmrException): + pass + + +def get_subaddress_secret_key(secret_key, index=None, major=None, minor=None): + """ + Builds subaddress secret key from the subaddress index + Hs(SubAddr || a || index_major || index_minor) + """ + if index: + major = index.major + minor = index.minor + + if major == 0 and minor == 0: + return secret_key + + return crypto.get_subaddress_secret_key(secret_key, major, minor) + + +def get_subaddress_spend_public_key(view_private, spend_public, major, minor): + """ + Generates subaddress spend public key D_{major, minor} + """ + if major == 0 and minor == 0: + return spend_public + + m = get_subaddress_secret_key(view_private, major=major, minor=minor) + M = crypto.scalarmult_base(m) + D = crypto.point_add(spend_public, M) + return D + + +def derive_subaddress_public_key(out_key, derivation, output_index): + """ + out_key - H_s(derivation || varint(output_index))G + """ + crypto.check_ed25519point(out_key) + scalar = crypto.derivation_to_scalar(derivation, output_index) + point2 = crypto.scalarmult_base(scalar) + point4 = crypto.point_sub(out_key, point2) + return point4 + + +def generate_key_image(public_key, secret_key): + """ + Key image: secret_key * H_p(pub_key) + """ + point = crypto.hash_to_point(public_key) + point2 = crypto.scalarmult(point, secret_key) + return point2 + + +def is_out_to_account( + subaddresses: dict, + out_key: Ge25519, + derivation: Ge25519, + additional_derivations: list, + output_index: int, +): + """ + Checks whether the given transaction is sent to the account. + Searches subaddresses for the computed subaddress_spendkey. + Corresponds to is_out_to_acc_precomp() in the Monero codebase. + If found, returns (major, minor), derivation, otherwise None. + """ + subaddress_spendkey = crypto.encodepoint( + derive_subaddress_public_key(out_key, derivation, output_index) + ) + if subaddress_spendkey in subaddresses: + return subaddresses[subaddress_spendkey], derivation + + if additional_derivations and len(additional_derivations) > 0: + if output_index >= len(additional_derivations): + raise ValueError("Wrong number of additional derivations") + + subaddress_spendkey = derive_subaddress_public_key( + out_key, additional_derivations[output_index], output_index + ) + subaddress_spendkey = crypto.encodepoint(subaddress_spendkey) + if subaddress_spendkey in subaddresses: + return ( + subaddresses[subaddress_spendkey], + additional_derivations[output_index], + ) + + return None + + +def generate_tx_spend_and_key_image( + ack, out_key, recv_derivation, real_output_index, received_index: tuple +) -> Optional[Tuple[Sc25519, Ge25519]]: + """ + Generates UTXO spending key and key image. + Corresponds to generate_key_image_helper_precomp() in the Monero codebase. + + :param ack: sender credentials + :type ack: apps.monero.xmr.credentials.AccountCreds + :param out_key: real output (from input RCT) destination key + :param recv_derivation: + :param real_output_index: + :param received_index: subaddress index this payment was received to + :return: + """ + if not crypto.sc_isnonzero(ack.spend_key_private): + raise ValueError("Watch-only wallet not supported") + + # derive secret key with subaddress - step 1: original CN derivation + scalar_step1 = crypto.derive_secret_key( + recv_derivation, real_output_index, ack.spend_key_private + ) + + # step 2: add Hs(SubAddr || a || index_major || index_minor) + subaddr_sk = None + if received_index == (0, 0): + scalar_step2 = scalar_step1 + else: + subaddr_sk = get_subaddress_secret_key( + ack.view_key_private, major=received_index[0], minor=received_index[1] + ) + scalar_step2 = crypto.sc_add(scalar_step1, subaddr_sk) + + # When not in multisig, we know the full spend secret key, so the output pubkey can be obtained by scalarmultBase + pub_ver = crypto.scalarmult_base(scalar_step2) + + # , branch deactivated until implemented + # # When in multisig, we only know the partial spend secret key. But we do know the full spend public key, + # # so the output pubkey can be obtained by using the standard CN key derivation. + # pub_ver = crypto.derive_public_key( + # recv_derivation, real_output_index, ack.spend_key_public + # ) + # + # # Add the contribution from the subaddress part + # if received_index != (0, 0): + # subaddr_pk = crypto.scalarmult_base(subaddr_sk) + # pub_ver = crypto.point_add(pub_ver, subaddr_pk) + # + + if not crypto.point_eq(pub_ver, out_key): + raise ValueError( + "key image helper precomp: given output pubkey doesn't match the derived one" + ) + + ki = generate_key_image(crypto.encodepoint(pub_ver), scalar_step2) + return scalar_step2, ki + + +def generate_tx_spend_and_key_image_and_derivation( + creds, + subaddresses: dict, + out_key: Ge25519, + tx_public_key: Ge25519, + additional_tx_public_keys: list, + real_output_index: int, +) -> Tuple[Sc25519, Ge25519, Ge25519]: + """ + Generates UTXO spending key and key image and corresponding derivation. + Supports subaddresses. + Corresponds to generate_key_image_helper() in the Monero codebase. + + :param creds: + :param subaddresses: + :param out_key: real output (from input RCT) destination key + :param tx_public_key: R, transaction public key + :param additional_tx_public_keys: Additional Rs, for subaddress destinations + :param real_output_index: index of the real output in the RCT + :return: + """ + recv_derivation = crypto.generate_key_derivation( + tx_public_key, creds.view_key_private + ) + + additional_recv_derivations = [] + for add_pub_key in additional_tx_public_keys: + additional_recv_derivations.append( + crypto.generate_key_derivation(add_pub_key, creds.view_key_private) + ) + + subaddr_recv_info = is_out_to_account( + subaddresses, + out_key, + recv_derivation, + additional_recv_derivations, + real_output_index, + ) + if subaddr_recv_info is None: + raise XmrNoSuchAddressException("No such addr") + + xi, ki = generate_tx_spend_and_key_image( + creds, out_key, subaddr_recv_info[1], real_output_index, subaddr_recv_info[0] + ) + return xi, ki, recv_derivation + + +def compute_subaddresses(creds, account: int, indices, subaddresses=None): + """ + Computes subaddress public spend key for receiving transactions. + + :param creds: credentials + :param account: major index + :param indices: array of minor indices + :param subaddresses: subaddress dict. optional. + :return: + """ + if subaddresses is None: + subaddresses = {} + + for idx in indices: + if account == 0 and idx == 0: + subaddresses[crypto.encodepoint(creds.spend_key_public)] = (0, 0) + continue + + pub = get_subaddress_spend_public_key( + creds.view_key_private, creds.spend_key_public, major=account, minor=idx + ) + pub = crypto.encodepoint(pub) + subaddresses[pub] = (account, idx) + return subaddresses + + +def generate_keys(recovery_key): + pub = crypto.scalarmult_base(recovery_key) + return recovery_key, pub + + +def generate_monero_keys(seed): + """ + Generates spend key / view key from the seed in the same manner as Monero code does. + + account.cpp: + crypto::secret_key account_base::generate(const crypto::secret_key& recovery_key, bool recover, bool two_random). + """ + spend_sec, spend_pub = generate_keys(crypto.decodeint(seed)) + hash = crypto.cn_fast_hash(crypto.encodeint(spend_sec)) + view_sec, view_pub = generate_keys(crypto.decodeint(hash)) + return spend_sec, spend_pub, view_sec, view_pub + + +def generate_sub_address_keys(view_sec, spend_pub, major, minor): + if major == 0 and minor == 0: # special case, Monero-defined + return spend_pub, crypto.scalarmult_base(view_sec) + + m = get_subaddress_secret_key(view_sec, major=major, minor=minor) + M = crypto.scalarmult_base(m) + D = crypto.point_add(spend_pub, M) + C = crypto.scalarmult(D, view_sec) + return D, C diff --git a/src/apps/monero/xmr/networks.py b/src/apps/monero/xmr/networks.py new file mode 100644 index 000000000..e9f5faefa --- /dev/null +++ b/src/apps/monero/xmr/networks.py @@ -0,0 +1,51 @@ +class NetworkTypes: + MAINNET = 0 + TESTNET = 1 + STAGENET = 2 + FAKECHAIN = 3 + + +class MainNet: + PUBLIC_ADDRESS_BASE58_PREFIX = 18 + PUBLIC_INTEGRATED_ADDRESS_BASE58_PREFIX = 19 + PUBLIC_SUBADDRESS_BASE58_PREFIX = 42 + + +class TestNet: + PUBLIC_ADDRESS_BASE58_PREFIX = 53 + PUBLIC_INTEGRATED_ADDRESS_BASE58_PREFIX = 54 + PUBLIC_SUBADDRESS_BASE58_PREFIX = 63 + + +class StageNet: + PUBLIC_ADDRESS_BASE58_PREFIX = 24 + PUBLIC_INTEGRATED_ADDRESS_BASE58_PREFIX = 25 + PUBLIC_SUBADDRESS_BASE58_PREFIX = 36 + + +def net_version( + network_type=NetworkTypes.MAINNET, is_subaddr=False, is_integrated=False +): + """ + Network version bytes used for address construction + """ + if is_integrated and is_subaddr: + raise ValueError("Subaddress cannot be integrated") + + c_net = None + if network_type is None or network_type == NetworkTypes.MAINNET: + c_net = MainNet + elif network_type == NetworkTypes.TESTNET: + c_net = TestNet + elif network_type == NetworkTypes.STAGENET: + c_net = StageNet + else: + raise ValueError("Unknown network type: %s" % network_type) + + prefix = c_net.PUBLIC_ADDRESS_BASE58_PREFIX + if is_subaddr: + prefix = c_net.PUBLIC_SUBADDRESS_BASE58_PREFIX + elif is_integrated: + prefix = c_net.PUBLIC_INTEGRATED_ADDRESS_BASE58_PREFIX + + return bytes([prefix]) diff --git a/src/apps/monero/xmr/range_signatures.py b/src/apps/monero/xmr/range_signatures.py new file mode 100644 index 000000000..1e43a0418 --- /dev/null +++ b/src/apps/monero/xmr/range_signatures.py @@ -0,0 +1,144 @@ +""" +Computes range signature + +Can compute Borromean range proof or Bulletproof. +Also can verify Bulletproof, in case the computation was offloaded. + +Mostly ported from official Monero client, but also inspired by Mininero. +Author: Dusan Klinec, ph4r05, 2018 +""" + +import gc + +from apps.monero.xmr import crypto + + +def prove_range_bp_batch(amounts, masks): + """Calculates Bulletproof in batches""" + from apps.monero.xmr import bulletproof as bp + + bpi = bp.BulletProofBuilder() + bp_proof = bpi.prove_batch([crypto.sc_init(a) for a in amounts], masks) + del (bpi, bp) + gc.collect() + + return bp_proof + + +def verify_bp(bp_proof, amounts, masks): + """Verifies Bulletproof""" + from apps.monero.xmr import bulletproof as bp + + if amounts: + bp_proof.V = [] + for i in range(len(amounts)): + C = crypto.gen_commitment(masks[i], amounts[i]) + crypto.scalarmult_into(C, C, crypto.sc_inv_eight()) + bp_proof.V.append(crypto.encodepoint(C)) + + bpi = bp.BulletProofBuilder() + res = bpi.verify(bp_proof) + gc.collect() + return res + + +def prove_range_borromean(amount, last_mask): + """Calculates Borromean range proof""" + # The large chunks allocated first to avoid potential memory fragmentation issues. + ai = bytearray(32 * 64) + alphai = bytearray(32 * 64) + Cis = bytearray(32 * 64) + s0s = bytearray(32 * 64) + s1s = bytearray(32 * 64) + buff = bytearray(32) + ee_bin = bytearray(32) + + a = crypto.sc_init(0) + si = crypto.sc_init(0) + c = crypto.sc_init(0) + ee = crypto.sc_init(0) + tmp_ai = crypto.sc_init(0) + tmp_alpha = crypto.sc_init(0) + + C_acc = crypto.identity() + C_h = crypto.xmr_H() + C_tmp = crypto.identity() + L = crypto.identity() + kck = crypto.get_keccak() + + for ii in range(64): + crypto.random_scalar(tmp_ai) + if last_mask is not None and ii == 63: + crypto.sc_sub_into(tmp_ai, last_mask, a) + + crypto.sc_add_into(a, a, tmp_ai) + crypto.random_scalar(tmp_alpha) + + crypto.scalarmult_base_into(L, tmp_alpha) + crypto.scalarmult_base_into(C_tmp, tmp_ai) + + # if 0: C_tmp += Zero (nothing is added) + # if 1: C_tmp += 2^i*H + # 2^i*H is already stored in C_h + if (amount >> ii) & 1 == 1: + crypto.point_add_into(C_tmp, C_tmp, C_h) + + crypto.point_add_into(C_acc, C_acc, C_tmp) + + # Set Ci[ii] to sigs + crypto.encodepoint_into(Cis, C_tmp, ii << 5) + crypto.encodeint_into(ai, tmp_ai, ii << 5) + crypto.encodeint_into(alphai, tmp_alpha, ii << 5) + + if ((amount >> ii) & 1) == 0: + crypto.random_scalar(si) + crypto.encodepoint_into(buff, L) + crypto.hash_to_scalar_into(c, buff) + + crypto.point_sub_into(C_tmp, C_tmp, C_h) + crypto.add_keys2_into(L, si, c, C_tmp) + + crypto.encodeint_into(s1s, si, ii << 5) + + crypto.encodepoint_into(buff, L) + kck.update(buff) + + crypto.point_double_into(C_h, C_h) + + # Compute ee + tmp_ee = kck.digest() + crypto.decodeint_into(ee, tmp_ee) + del (tmp_ee, kck) + + C_h = crypto.xmr_H() + gc.collect() + + # Second pass, s0, s1 + for ii in range(64): + crypto.decodeint_into(tmp_alpha, alphai, ii << 5) + crypto.decodeint_into(tmp_ai, ai, ii << 5) + + if ((amount >> ii) & 1) == 0: + crypto.sc_mulsub_into(si, tmp_ai, ee, tmp_alpha) + crypto.encodeint_into(s0s, si, ii << 5) + + else: + crypto.random_scalar(si) + crypto.encodeint_into(s0s, si, ii << 5) + + crypto.decodepoint_into(C_tmp, Cis, ii << 5) + crypto.add_keys2_into(L, si, ee, C_tmp) + crypto.encodepoint_into(buff, L) + crypto.hash_to_scalar_into(c, buff) + + crypto.sc_mulsub_into(si, tmp_ai, c, tmp_alpha) + crypto.encodeint_into(s1s, si, ii << 5) + + crypto.point_double_into(C_h, C_h) + + crypto.encodeint_into(ee_bin, ee) + + del (ai, alphai, buff, tmp_ai, tmp_alpha, si, c, ee, C_tmp, C_h, L) + gc.collect() + + return C_acc, a, [s0s, s1s, ee_bin, Cis] diff --git a/src/apps/monero/xmr/serialize/__init__.py b/src/apps/monero/xmr/serialize/__init__.py new file mode 100644 index 000000000..93814207d --- /dev/null +++ b/src/apps/monero/xmr/serialize/__init__.py @@ -0,0 +1,27 @@ +import gc + + +def parse_msg(buf: bytes, msg_type): + from apps.monero.xmr.serialize.readwriter import MemoryReaderWriter + + reader = MemoryReaderWriter(memoryview(buf)) + return msg_type.load(reader) + + +def dump_msg(msg, preallocate: int = None, prefix: bytes = None) -> bytes: + from apps.monero.xmr.serialize.readwriter import MemoryReaderWriter + + writer = MemoryReaderWriter(preallocate=preallocate) + if prefix: + writer.write(prefix) + msg_type = msg.__class__ + msg_type.dump(writer, msg) + + return writer.get_buffer() + + +def dump_msg_gc(msg, preallocate: int = None, prefix: bytes = None) -> bytes: + buf = dump_msg(msg, preallocate, prefix) + del msg + gc.collect() + return buf diff --git a/src/apps/monero/xmr/serialize/base_types.py b/src/apps/monero/xmr/serialize/base_types.py new file mode 100644 index 000000000..b76c10b72 --- /dev/null +++ b/src/apps/monero/xmr/serialize/base_types.py @@ -0,0 +1,36 @@ +from apps.monero.xmr.serialize.int_serialize import ( + dump_uint, + dump_uvarint, + load_uint, + load_uvarint, +) + + +class XmrType: + pass + + +class UVarintType(XmrType): + @staticmethod + def load(reader) -> int: + return load_uvarint(reader) + + @staticmethod + def dump(writer, n: int): + return dump_uvarint(writer, n) + + +class IntType(XmrType): + WIDTH = 0 + + @classmethod + def load(cls, reader) -> int: + return load_uint(reader, cls.WIDTH) + + @classmethod + def dump(cls, writer, n: int): + return dump_uint(writer, n, cls.WIDTH) + + +class UInt8(IntType): + WIDTH = 1 diff --git a/src/apps/monero/xmr/serialize/int_serialize.py b/src/apps/monero/xmr/serialize/int_serialize.py new file mode 100644 index 000000000..97b17c945 --- /dev/null +++ b/src/apps/monero/xmr/serialize/int_serialize.py @@ -0,0 +1,109 @@ +_UINT_BUFFER = bytearray(1) + + +def load_uint(reader, width): + """ + Constant-width integer serialization + """ + buffer = _UINT_BUFFER + result = 0 + shift = 0 + for _ in range(width): + reader.readinto(buffer) + result += buffer[0] << shift + shift += 8 + return result + + +def dump_uint(writer, n, width): + """ + Constant-width integer serialization + """ + buffer = _UINT_BUFFER + for _ in range(width): + buffer[0] = n & 0xFF + writer.write(buffer) + n >>= 8 + + +def uvarint_size(n): + """ + Returns size in bytes n would occupy serialized as varint + """ + bts = 0 if n != 0 else 1 + while n: + n >>= 7 + bts += 1 + return bts + + +def load_uvarint_b(buffer): + """ + Variable int deserialization, synchronous from buffer. + """ + result = 0 + idx = 0 + byte = 0x80 + while byte & 0x80: + byte = buffer[idx] + result += (byte & 0x7F) << (7 * idx) + idx += 1 + return result + + +def dump_uvarint_b(n): + """ + Serializes uvarint to the buffer + """ + buffer = bytearray(uvarint_size(n)) + return dump_uvarint_b_into(n, buffer, 0) + + +def dump_uvarint_b_into(n, buffer, offset=0): + """ + Serializes n as variable size integer to the provided buffer. + """ + if n < 0: + raise ValueError("Cannot dump signed value, convert it to unsigned first.") + shifted = True + while shifted: + shifted = n >> 7 + buffer[offset] = (n & 0x7F) | (0x80 if shifted else 0x00) + offset += 1 + n = shifted + return buffer + + +def dump_uint_b_into(n, width, buffer, offset=0): + """ + Serializes fixed size integer to the buffer + """ + for idx in range(width): + buffer[idx + offset] = n & 0xFF + n >>= 8 + return buffer + + +def load_uvarint(reader): + buffer = _UINT_BUFFER + result = 0 + shift = 0 + byte = 0x80 + while byte & 0x80: + reader.readinto(buffer) + byte = buffer[0] + result += (byte & 0x7F) << shift + shift += 7 + return result + + +def dump_uvarint(writer, n): + if n < 0: + raise ValueError("Cannot dump signed value, convert it to unsigned first.") + buffer = _UINT_BUFFER + shifted = True + while shifted: + shifted = n >> 7 + buffer[0] = (n & 0x7F) | (0x80 if shifted else 0x00) + writer.write(buffer) + n = shifted diff --git a/src/apps/monero/xmr/serialize/message_types.py b/src/apps/monero/xmr/serialize/message_types.py new file mode 100644 index 000000000..f5cbbd5bf --- /dev/null +++ b/src/apps/monero/xmr/serialize/message_types.py @@ -0,0 +1,163 @@ +from trezor.utils import obj_eq, obj_repr + +from apps.monero.xmr.serialize.base_types import XmrType +from apps.monero.xmr.serialize.int_serialize import ( + dump_uint, + dump_uvarint, + load_uint, + load_uvarint, +) + + +class UnicodeType(XmrType): + """ + Unicode data in UTF-8 encoding. + """ + + @staticmethod + def dump(writer, s): + dump_uvarint(writer, len(s)) + writer.write(bytes(s)) + + @staticmethod + def load(reader): + ivalue = load_uvarint(reader) + fvalue = bytearray(ivalue) + reader.readinto(fvalue) + return str(fvalue) + + +class BlobType(XmrType): + """ + Binary data, represented as bytearray. BlobType is only a scheme + descriptor. Behaves in the same way as primitive types. + """ + + FIX_SIZE = 0 + SIZE = 0 + + @classmethod + def dump(cls, writer, elem: bytes): + if cls.FIX_SIZE: + if cls.SIZE != len(elem): + raise ValueError("Size mismatch") + else: + dump_uvarint(writer, len(elem)) + writer.write(elem) + + @classmethod + def load(cls, reader) -> bytearray: + if cls.FIX_SIZE: + size = cls.SIZE + else: + size = load_uvarint(reader) + elem = bytearray(size) + reader.readinto(elem) + return elem + + +class ContainerType(XmrType): + """ + Array of elements, represented as a list of items. ContainerType is only a + scheme descriptor. + """ + + FIX_SIZE = 0 + SIZE = 0 + ELEM_TYPE = None + + @classmethod + def dump(cls, writer, elems, elem_type=None): + if elem_type is None: + elem_type = cls.ELEM_TYPE + if cls.FIX_SIZE: + if cls.SIZE != len(elems): + raise ValueError("Size mismatch") + else: + dump_uvarint(writer, len(elems)) + for elem in elems: + elem_type.dump(writer, elem) + + @classmethod + def load(cls, reader, elem_type=None): + if elem_type is None: + elem_type = cls.ELEM_TYPE + if cls.FIX_SIZE: + size = cls.SIZE + else: + size = load_uvarint(reader) + elems = [] + for _ in range(size): + elem = elem_type.load(reader) + elems.append(elem) + return elems + + +class VariantType(XmrType): + """ + Union of types, differentiated by variant tags. VariantType is only a scheme + descriptor. + """ + + @classmethod + def dump(cls, writer, elem): + for field in cls.f_specs(): + ftype = field[1] + if isinstance(elem, ftype): + break + else: + raise ValueError("Unrecognized variant: %s" % elem) + + dump_uint(writer, ftype.VARIANT_CODE, 1) + ftype.dump(writer, elem) + + @classmethod + def load(cls, reader): + tag = load_uint(reader, 1) + for field in cls.f_specs(): + ftype = field[1] + if ftype.VARIANT_CODE == tag: + fvalue = ftype.load(reader) + break + else: + raise ValueError("Unknown tag: %s" % tag) + return fvalue + + @classmethod + def f_specs(cls): + return () + + +class MessageType(XmrType): + """ + Message composed of fields with specific types. + """ + + def __init__(self, **kwargs): + for kw in kwargs: + setattr(self, kw, kwargs[kw]) + + __eq__ = obj_eq + __repr__ = obj_repr + + @classmethod + def dump(cls, writer, msg): + defs = cls.f_specs() + for field in defs: + fname, ftype, *fparams = field + fvalue = getattr(msg, fname, None) + ftype.dump(writer, fvalue, *fparams) + + @classmethod + def load(cls, reader): + msg = cls() + defs = cls.f_specs() + for field in defs: + fname, ftype, *fparams = field + fvalue = ftype.load(reader, *fparams) + setattr(msg, fname, fvalue) + return msg + + @classmethod + def f_specs(cls): + return () diff --git a/src/apps/monero/xmr/serialize/readwriter.py b/src/apps/monero/xmr/serialize/readwriter.py new file mode 100644 index 000000000..54cd8ddaa --- /dev/null +++ b/src/apps/monero/xmr/serialize/readwriter.py @@ -0,0 +1,106 @@ +import gc + + +class MemoryReaderWriter: + def __init__( + self, + buffer=None, + read_empty=False, + threshold=None, + do_gc=False, + preallocate=None, + **kwargs + ): + self.buffer = buffer + self.nread = 0 + self.nwritten = 0 + + self.ndata = 0 + self.offset = 0 + self.woffset = 0 + + self.read_empty = read_empty + self.threshold = threshold + self.do_gc = do_gc + + if preallocate is not None: + self.preallocate(preallocate) + elif self.buffer is None: + self.buffer = bytearray(0) + else: + self.woffset = len(buffer) + + def is_empty(self): + return self.offset == len(self.buffer) or self.offset == self.woffset + + def preallocate(self, size): + self.buffer = bytearray(size) + self.offset = 0 + self.woffset = 0 + + def readinto(self, buf): + ln = len(buf) + if not self.read_empty and ln > 0 and self.offset == len(self.buffer): + raise EOFError + + nread = min(ln, len(self.buffer) - self.offset) + for idx in range(nread): + buf[idx] = self.buffer[self.offset + idx] + + self.offset += nread + self.nread += nread + self.ndata -= nread + + # Deallocation threshold triggered + if self.threshold is not None and self.offset >= self.threshold: + self.buffer = self.buffer[self.offset :] + self.woffset -= self.offset + self.offset = 0 + + if self.do_gc: + gc.collect() + + return nread + + async def areadinto(self, buf): + return self.readinto(buf) + + def write(self, buf): + nwritten = len(buf) + nall = len(self.buffer) + towrite = nwritten + bufoff = 0 + + # Fill existing place in the buffer + while towrite > 0 and nall - self.woffset > 0: + self.buffer[self.woffset] = buf[bufoff] + self.woffset += 1 + bufoff += 1 + towrite -= 1 + + # Allocate next chunk if needed + while towrite > 0: + _towrite = min(32, towrite) + chunk = bytearray(32) # chunk size typical for EC point + + for i in range(_towrite): + chunk[i] = buf[bufoff] + self.woffset += 1 + bufoff += 1 + towrite -= 1 + + self.buffer.extend(chunk) + if self.do_gc: + chunk = None # dereference + gc.collect() + + self.nwritten += nwritten + self.ndata += nwritten + return nwritten + + async def awrite(self, buf): + return self.write(buf) + + def get_buffer(self): + mv = memoryview(self.buffer) + return mv[self.offset : self.woffset] diff --git a/src/apps/monero/xmr/serialize_messages/__init__.py b/src/apps/monero/xmr/serialize_messages/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/apps/monero/xmr/serialize_messages/base.py b/src/apps/monero/xmr/serialize_messages/base.py new file mode 100644 index 000000000..2bdf1b115 --- /dev/null +++ b/src/apps/monero/xmr/serialize_messages/base.py @@ -0,0 +1,31 @@ +from micropython import const + +from apps.monero.xmr.serialize.message_types import BlobType + +_c0 = const(0) +_c1 = const(1) +_c32 = const(32) +_c64 = const(64) + +# +# cryptonote_basic.h +# + + +class Hash(BlobType): + __slots__ = ("data",) + DATA_ATTR = "data" + FIX_SIZE = _c1 + SIZE = _c32 + + +class ECKey(BlobType): + __slots__ = ("bytes",) + DATA_ATTR = "bytes" + FIX_SIZE = _c1 + SIZE = _c32 + + +ECPoint = Hash +ECPublicKey = ECPoint +KeyImage = ECPoint diff --git a/src/apps/monero/xmr/serialize_messages/ct_keys.py b/src/apps/monero/xmr/serialize_messages/ct_keys.py new file mode 100644 index 000000000..42a914fbf --- /dev/null +++ b/src/apps/monero/xmr/serialize_messages/ct_keys.py @@ -0,0 +1,24 @@ +from micropython import const + +from apps.monero.xmr.serialize.message_types import ContainerType, MessageType +from apps.monero.xmr.serialize_messages.base import ECKey + +_c0 = const(0) + + +class KeyV(ContainerType): + FIX_SIZE = _c0 + ELEM_TYPE = ECKey + + +class KeyM(ContainerType): + FIX_SIZE = _c0 + ELEM_TYPE = KeyV + + +class CtKey(MessageType): + __slots__ = ("dest", "mask") + + @classmethod + def f_specs(cls): + return (("dest", ECKey), ("mask", ECKey)) diff --git a/src/apps/monero/xmr/serialize_messages/tx_ecdh.py b/src/apps/monero/xmr/serialize_messages/tx_ecdh.py new file mode 100644 index 000000000..27a43c084 --- /dev/null +++ b/src/apps/monero/xmr/serialize_messages/tx_ecdh.py @@ -0,0 +1,5 @@ +from apps.monero.xmr.serialize.message_types import MessageType + + +class EcdhTuple(MessageType): + __slots__ = ("mask", "amount") diff --git a/src/apps/monero/xmr/serialize_messages/tx_full.py b/src/apps/monero/xmr/serialize_messages/tx_full.py new file mode 100644 index 000000000..1c98619b6 --- /dev/null +++ b/src/apps/monero/xmr/serialize_messages/tx_full.py @@ -0,0 +1,11 @@ +from apps.monero.xmr.serialize.message_types import MessageType +from apps.monero.xmr.serialize_messages.base import ECKey +from apps.monero.xmr.serialize_messages.ct_keys import KeyM + + +class MgSig(MessageType): + __slots__ = ("ss", "cc", "II") + + @classmethod + def f_specs(cls): + return (("ss", KeyM), ("cc", ECKey)) diff --git a/src/apps/monero/xmr/serialize_messages/tx_prefix.py b/src/apps/monero/xmr/serialize_messages/tx_prefix.py new file mode 100644 index 000000000..0a0ddd0d2 --- /dev/null +++ b/src/apps/monero/xmr/serialize_messages/tx_prefix.py @@ -0,0 +1,102 @@ +from micropython import const + +from apps.monero.xmr.serialize.base_types import UInt8, UVarintType +from apps.monero.xmr.serialize.message_types import ( + ContainerType, + MessageType, + VariantType, +) +from apps.monero.xmr.serialize_messages.base import ECPublicKey, Hash, KeyImage + +_c0 = const(0) +_c1 = const(1) +_c32 = const(32) +_c64 = const(64) + + +class TxoutToScript(MessageType): + __slots__ = ("keys", "script") + VARIANT_CODE = 0x0 + + @classmethod + def f_specs(cls): + return (("keys", ContainerType, ECPublicKey), ("script", ContainerType, UInt8)) + + +class TxoutToKey(MessageType): + __slots__ = ("key",) + VARIANT_CODE = 0x2 + + @classmethod + def f_specs(cls): + return (("key", ECPublicKey),) + + +class TxoutToScriptHash(MessageType): + __slots__ = ("hash",) + VARIANT_CODE = 0x1 + + @classmethod + def f_specs(cls): + return (("hash", Hash),) + + +class TxoutTargetV(VariantType): + @classmethod + def f_specs(cls): + return ( + ("txout_to_script", TxoutToScript), + ("txout_to_scripthash", TxoutToScriptHash), + ("txout_to_key", TxoutToKey), + ) + + +class TxinGen(MessageType): + __slots__ = ("height",) + VARIANT_CODE = 0xFF + + @classmethod + def f_specs(cls): + return (("height", UVarintType),) + + +class TxinToKey(MessageType): + __slots__ = ("amount", "key_offsets", "k_image") + VARIANT_CODE = 0x2 + + @classmethod + def f_specs(cls): + return ( + ("amount", UVarintType), + ("key_offsets", ContainerType, UVarintType), + ("k_image", KeyImage), + ) + + +class TxinToScript(MessageType): + __slots__ = () + VARIANT_CODE = _c0 + + +class TxinToScriptHash(MessageType): + __slots__ = () + VARIANT_CODE = _c1 + + +class TxInV(VariantType): + @classmethod + def f_specs(cls): + return ( + ("txin_gen", TxinGen), + ("txin_to_script", TxinToScript), + ("txin_to_scripthash", TxinToScriptHash), + ("txin_to_key", TxinToKey), + ) + + +class TxOut(MessageType): + __slots__ = ("amount", "target") + + @classmethod + def f_specs(cls): + return (("amount", UVarintType), ("target", TxoutTargetV)) diff --git a/src/apps/monero/xmr/serialize_messages/tx_rsig_bulletproof.py b/src/apps/monero/xmr/serialize_messages/tx_rsig_bulletproof.py new file mode 100644 index 000000000..0f0d280c6 --- /dev/null +++ b/src/apps/monero/xmr/serialize_messages/tx_rsig_bulletproof.py @@ -0,0 +1,21 @@ +from apps.monero.xmr.serialize.message_types import MessageType +from apps.monero.xmr.serialize_messages.base import ECKey +from apps.monero.xmr.serialize_messages.ct_keys import KeyV + + +class Bulletproof(MessageType): + @classmethod + def f_specs(cls): + return ( + ("A", ECKey), + ("S", ECKey), + ("T1", ECKey), + ("T2", ECKey), + ("taux", ECKey), + ("mu", ECKey), + ("L", KeyV), + ("R", KeyV), + ("a", ECKey), + ("b", ECKey), + ("t", ECKey), + ) diff --git a/src/apps/monero/xmr/types.py b/src/apps/monero/xmr/types.py new file mode 100644 index 000000000..e3523e65f --- /dev/null +++ b/src/apps/monero/xmr/types.py @@ -0,0 +1,6 @@ +if False: + from trezor.crypto import monero as tcry + from typing import * # noqa: F401 + + Ge25519 = tcry.ge25519 + Sc25519 = tcry.bignum256modm diff --git a/src/main.py b/src/main.py index f4fff50eb..c54ae0838 100644 --- a/src/main.py +++ b/src/main.py @@ -14,6 +14,7 @@ import apps.management import apps.wallet import apps.ethereum import apps.lisk +import apps.monero import apps.nem import apps.stellar import apps.ripple @@ -31,6 +32,7 @@ apps.management.boot() apps.wallet.boot() apps.ethereum.boot() apps.lisk.boot() +apps.monero.boot() apps.nem.boot() apps.stellar.boot() apps.ripple.boot() diff --git a/src/trezor/crypto/__init__.py b/src/trezor/crypto/__init__.py index c7b2f9bfa..7bc581b6a 100644 --- a/src/trezor/crypto/__init__.py +++ b/src/trezor/crypto/__init__.py @@ -6,6 +6,7 @@ from trezorcrypto import ( # noqa: F401 bip39, chacha20poly1305, crc, + monero, nem, pbkdf2, random, diff --git a/src/trezor/messages/MessageType.py b/src/trezor/messages/MessageType.py index 2355b955d..617006d4e 100644 --- a/src/trezor/messages/MessageType.py +++ b/src/trezor/messages/MessageType.py @@ -151,12 +151,10 @@ MoneroTransactionSetOutputRequest = 511 MoneroTransactionSetOutputAck = 512 MoneroTransactionAllOutSetRequest = 513 MoneroTransactionAllOutSetAck = 514 -MoneroTransactionMlsagDoneRequest = 515 -MoneroTransactionMlsagDoneAck = 516 -MoneroTransactionSignInputRequest = 517 -MoneroTransactionSignInputAck = 518 -MoneroTransactionFinalRequest = 519 -MoneroTransactionFinalAck = 520 +MoneroTransactionSignInputRequest = 515 +MoneroTransactionSignInputAck = 516 +MoneroTransactionFinalRequest = 517 +MoneroTransactionFinalAck = 518 MoneroKeyImageExportInitRequest = 530 MoneroKeyImageExportInitAck = 531 MoneroKeyImageSyncStepRequest = 532 diff --git a/src/trezor/messages/MoneroExportedKeyImage.py b/src/trezor/messages/MoneroExportedKeyImage.py index 198930912..07ddc5f19 100644 --- a/src/trezor/messages/MoneroExportedKeyImage.py +++ b/src/trezor/messages/MoneroExportedKeyImage.py @@ -8,17 +8,14 @@ class MoneroExportedKeyImage(p.MessageType): def __init__( self, iv: bytes = None, - tag: bytes = None, blob: bytes = None, ) -> None: self.iv = iv - self.tag = tag self.blob = blob @classmethod def get_fields(cls): return { 1: ('iv', p.BytesType, 0), - 2: ('tag', p.BytesType, 0), 3: ('blob', p.BytesType, 0), } diff --git a/src/trezor/messages/MoneroOutputEntry.py b/src/trezor/messages/MoneroOutputEntry.py index 12657e13e..ff9616f07 100644 --- a/src/trezor/messages/MoneroOutputEntry.py +++ b/src/trezor/messages/MoneroOutputEntry.py @@ -2,7 +2,7 @@ # fmt: off import protobuf as p -from .MoneroRctKey import MoneroRctKey +from .MoneroRctKeyPublic import MoneroRctKeyPublic class MoneroOutputEntry(p.MessageType): @@ -10,7 +10,7 @@ class MoneroOutputEntry(p.MessageType): def __init__( self, idx: int = None, - key: MoneroRctKey = None, + key: MoneroRctKeyPublic = None, ) -> None: self.idx = idx self.key = key @@ -19,5 +19,5 @@ class MoneroOutputEntry(p.MessageType): def get_fields(cls): return { 1: ('idx', p.UVarintType, 0), - 2: ('key', MoneroRctKey, 0), + 2: ('key', MoneroRctKeyPublic, 0), } diff --git a/src/trezor/messages/MoneroRctKey.py b/src/trezor/messages/MoneroRctKeyPublic.py similarity index 63% rename from src/trezor/messages/MoneroRctKey.py rename to src/trezor/messages/MoneroRctKeyPublic.py index 9f55bad5c..7bb84290c 100644 --- a/src/trezor/messages/MoneroRctKey.py +++ b/src/trezor/messages/MoneroRctKeyPublic.py @@ -3,19 +3,19 @@ import protobuf as p -class MoneroRctKey(p.MessageType): +class MoneroRctKeyPublic(p.MessageType): def __init__( self, dest: bytes = None, - mask: bytes = None, + commitment: bytes = None, ) -> None: self.dest = dest - self.mask = mask + self.commitment = commitment @classmethod def get_fields(cls): return { 1: ('dest', p.BytesType, 0), - 2: ('mask', p.BytesType, 0), + 2: ('commitment', p.BytesType, 0), } diff --git a/src/trezor/messages/MoneroTransactionAllInputsSetRequest.py b/src/trezor/messages/MoneroTransactionAllInputsSetRequest.py index 84870d165..f06c558f8 100644 --- a/src/trezor/messages/MoneroTransactionAllInputsSetRequest.py +++ b/src/trezor/messages/MoneroTransactionAllInputsSetRequest.py @@ -2,20 +2,6 @@ # fmt: off import protobuf as p -from .MoneroTransactionRsigData import MoneroTransactionRsigData - class MoneroTransactionAllInputsSetRequest(p.MessageType): MESSAGE_WIRE_TYPE = 509 - - def __init__( - self, - rsig_data: MoneroTransactionRsigData = None, - ) -> None: - self.rsig_data = rsig_data - - @classmethod - def get_fields(cls): - return { - 1: ('rsig_data', MoneroTransactionRsigData, 0), - } diff --git a/src/trezor/messages/MoneroTransactionAllOutSetAck.py b/src/trezor/messages/MoneroTransactionAllOutSetAck.py index 37d53fffc..6be052ef8 100644 --- a/src/trezor/messages/MoneroTransactionAllOutSetAck.py +++ b/src/trezor/messages/MoneroTransactionAllOutSetAck.py @@ -3,7 +3,6 @@ import protobuf as p from .MoneroRingCtSig import MoneroRingCtSig -from .MoneroTransactionRsigData import MoneroTransactionRsigData class MoneroTransactionAllOutSetAck(p.MessageType): @@ -13,19 +12,19 @@ class MoneroTransactionAllOutSetAck(p.MessageType): self, extra: bytes = None, tx_prefix_hash: bytes = None, - rsig_data: MoneroTransactionRsigData = None, rv: MoneroRingCtSig = None, + full_message_hash: bytes = None, ) -> None: self.extra = extra self.tx_prefix_hash = tx_prefix_hash - self.rsig_data = rsig_data self.rv = rv + self.full_message_hash = full_message_hash @classmethod def get_fields(cls): return { 1: ('extra', p.BytesType, 0), 2: ('tx_prefix_hash', p.BytesType, 0), - 3: ('rsig_data', MoneroTransactionRsigData, 0), 4: ('rv', MoneroRingCtSig, 0), + 5: ('full_message_hash', p.BytesType, 0), } diff --git a/src/trezor/messages/MoneroTransactionData.py b/src/trezor/messages/MoneroTransactionData.py index ff3283131..1a19a5886 100644 --- a/src/trezor/messages/MoneroTransactionData.py +++ b/src/trezor/messages/MoneroTransactionData.py @@ -26,9 +26,6 @@ class MoneroTransactionData(p.MessageType): fee: int = None, account: int = None, minor_indices: List[int] = None, - is_multisig: bool = None, - exp_tx_prefix_hash: bytes = None, - use_tx_keys: List[bytes] = None, rsig_data: MoneroTransactionRsigData = None, integrated_indices: List[int] = None, ) -> None: @@ -42,9 +39,6 @@ class MoneroTransactionData(p.MessageType): self.fee = fee self.account = account self.minor_indices = minor_indices if minor_indices is not None else [] - self.is_multisig = is_multisig - self.exp_tx_prefix_hash = exp_tx_prefix_hash - self.use_tx_keys = use_tx_keys if use_tx_keys is not None else [] self.rsig_data = rsig_data self.integrated_indices = integrated_indices if integrated_indices is not None else [] @@ -61,9 +55,6 @@ class MoneroTransactionData(p.MessageType): 8: ('fee', p.UVarintType, 0), 9: ('account', p.UVarintType, 0), 10: ('minor_indices', p.UVarintType, p.FLAG_REPEATED), - 11: ('is_multisig', p.BoolType, 0), - 12: ('exp_tx_prefix_hash', p.BytesType, 0), - 13: ('use_tx_keys', p.BytesType, p.FLAG_REPEATED), - 14: ('rsig_data', MoneroTransactionRsigData, 0), - 15: ('integrated_indices', p.UVarintType, p.FLAG_REPEATED), + 11: ('rsig_data', MoneroTransactionRsigData, 0), + 12: ('integrated_indices', p.UVarintType, p.FLAG_REPEATED), } diff --git a/src/trezor/messages/MoneroTransactionFinalAck.py b/src/trezor/messages/MoneroTransactionFinalAck.py index f01f57a1e..16ec3073a 100644 --- a/src/trezor/messages/MoneroTransactionFinalAck.py +++ b/src/trezor/messages/MoneroTransactionFinalAck.py @@ -4,7 +4,7 @@ import protobuf as p class MoneroTransactionFinalAck(p.MessageType): - MESSAGE_WIRE_TYPE = 520 + MESSAGE_WIRE_TYPE = 518 def __init__( self, diff --git a/src/trezor/messages/MoneroTransactionFinalRequest.py b/src/trezor/messages/MoneroTransactionFinalRequest.py index 43a81035d..c72a09adf 100644 --- a/src/trezor/messages/MoneroTransactionFinalRequest.py +++ b/src/trezor/messages/MoneroTransactionFinalRequest.py @@ -4,4 +4,4 @@ import protobuf as p class MoneroTransactionFinalRequest(p.MessageType): - MESSAGE_WIRE_TYPE = 519 + MESSAGE_WIRE_TYPE = 517 diff --git a/src/trezor/messages/MoneroTransactionInitAck.py b/src/trezor/messages/MoneroTransactionInitAck.py index ec962c3bf..4c53d71a1 100644 --- a/src/trezor/messages/MoneroTransactionInitAck.py +++ b/src/trezor/messages/MoneroTransactionInitAck.py @@ -16,30 +16,15 @@ class MoneroTransactionInitAck(p.MessageType): def __init__( self, - version: int = None, - status: int = None, - in_memory: bool = None, hmacs: List[bytes] = None, - many_inputs: bool = None, - many_outputs: bool = None, rsig_data: MoneroTransactionRsigData = None, ) -> None: - self.version = version - self.status = status - self.in_memory = in_memory self.hmacs = hmacs if hmacs is not None else [] - self.many_inputs = many_inputs - self.many_outputs = many_outputs self.rsig_data = rsig_data @classmethod def get_fields(cls): return { - 1: ('version', p.UVarintType, 0), - 2: ('status', p.UVarintType, 0), - 3: ('in_memory', p.BoolType, 0), - 4: ('hmacs', p.BytesType, p.FLAG_REPEATED), - 5: ('many_inputs', p.BoolType, 0), - 6: ('many_outputs', p.BoolType, 0), - 7: ('rsig_data', MoneroTransactionRsigData, 0), + 1: ('hmacs', p.BytesType, p.FLAG_REPEATED), + 2: ('rsig_data', MoneroTransactionRsigData, 0), } diff --git a/src/trezor/messages/MoneroTransactionMlsagDoneAck.py b/src/trezor/messages/MoneroTransactionMlsagDoneAck.py deleted file mode 100644 index e93bf5a8f..000000000 --- a/src/trezor/messages/MoneroTransactionMlsagDoneAck.py +++ /dev/null @@ -1,19 +0,0 @@ -# Automatically generated by pb2py -# fmt: off -import protobuf as p - - -class MoneroTransactionMlsagDoneAck(p.MessageType): - MESSAGE_WIRE_TYPE = 516 - - def __init__( - self, - full_message_hash: bytes = None, - ) -> None: - self.full_message_hash = full_message_hash - - @classmethod - def get_fields(cls): - return { - 1: ('full_message_hash', p.BytesType, 0), - } diff --git a/src/trezor/messages/MoneroTransactionMlsagDoneRequest.py b/src/trezor/messages/MoneroTransactionMlsagDoneRequest.py deleted file mode 100644 index 04fb493c0..000000000 --- a/src/trezor/messages/MoneroTransactionMlsagDoneRequest.py +++ /dev/null @@ -1,7 +0,0 @@ -# Automatically generated by pb2py -# fmt: off -import protobuf as p - - -class MoneroTransactionMlsagDoneRequest(p.MessageType): - MESSAGE_WIRE_TYPE = 515 diff --git a/src/trezor/messages/MoneroTransactionRsigData.py b/src/trezor/messages/MoneroTransactionRsigData.py index c39d50b42..bd5c57e43 100644 --- a/src/trezor/messages/MoneroTransactionRsigData.py +++ b/src/trezor/messages/MoneroTransactionRsigData.py @@ -2,8 +2,6 @@ # fmt: off import protobuf as p -from .MoneroTransactionDestinationEntry import MoneroTransactionDestinationEntry - if __debug__: try: from typing import List @@ -15,45 +13,27 @@ class MoneroTransactionRsigData(p.MessageType): def __init__( self, - version: int = None, rsig_type: int = None, offload_type: int = None, grouping: List[int] = None, - step: int = None, - operation: int = None, - seed: bytes = None, mask: bytes = None, - amount: bytes = None, rsig: bytes = None, rsig_parts: List[bytes] = None, - outputs: List[MoneroTransactionDestinationEntry] = None, ) -> None: - self.version = version self.rsig_type = rsig_type self.offload_type = offload_type self.grouping = grouping if grouping is not None else [] - self.step = step - self.operation = operation - self.seed = seed self.mask = mask - self.amount = amount self.rsig = rsig self.rsig_parts = rsig_parts if rsig_parts is not None else [] - self.outputs = outputs if outputs is not None else [] @classmethod def get_fields(cls): return { - 1: ('version', p.UVarintType, 0), - 2: ('rsig_type', p.UVarintType, 0), - 3: ('offload_type', p.UVarintType, 0), - 4: ('grouping', p.UVarintType, p.FLAG_REPEATED), - 5: ('step', p.UVarintType, 0), - 6: ('operation', p.UVarintType, 0), - 7: ('seed', p.BytesType, 0), - 8: ('mask', p.BytesType, 0), - 9: ('amount', p.BytesType, 0), - 10: ('rsig', p.BytesType, 0), - 11: ('rsig_parts', p.BytesType, p.FLAG_REPEATED), - 12: ('outputs', MoneroTransactionDestinationEntry, p.FLAG_REPEATED), + 1: ('rsig_type', p.UVarintType, 0), + 2: ('offload_type', p.UVarintType, 0), + 3: ('grouping', p.UVarintType, p.FLAG_REPEATED), + 4: ('mask', p.BytesType, 0), + 5: ('rsig', p.BytesType, 0), + 6: ('rsig_parts', p.BytesType, p.FLAG_REPEATED), } diff --git a/src/trezor/messages/MoneroTransactionSetInputAck.py b/src/trezor/messages/MoneroTransactionSetInputAck.py index 1ae34c2cd..774587431 100644 --- a/src/trezor/messages/MoneroTransactionSetInputAck.py +++ b/src/trezor/messages/MoneroTransactionSetInputAck.py @@ -12,15 +12,15 @@ class MoneroTransactionSetInputAck(p.MessageType): vini_hmac: bytes = None, pseudo_out: bytes = None, pseudo_out_hmac: bytes = None, - alpha_enc: bytes = None, - spend_enc: bytes = None, + pseudo_out_alpha: bytes = None, + spend_key: bytes = None, ) -> None: self.vini = vini self.vini_hmac = vini_hmac self.pseudo_out = pseudo_out self.pseudo_out_hmac = pseudo_out_hmac - self.alpha_enc = alpha_enc - self.spend_enc = spend_enc + self.pseudo_out_alpha = pseudo_out_alpha + self.spend_key = spend_key @classmethod def get_fields(cls): @@ -29,6 +29,6 @@ class MoneroTransactionSetInputAck(p.MessageType): 2: ('vini_hmac', p.BytesType, 0), 3: ('pseudo_out', p.BytesType, 0), 4: ('pseudo_out_hmac', p.BytesType, 0), - 5: ('alpha_enc', p.BytesType, 0), - 6: ('spend_enc', p.BytesType, 0), + 5: ('pseudo_out_alpha', p.BytesType, 0), + 6: ('spend_key', p.BytesType, 0), } diff --git a/src/trezor/messages/MoneroTransactionSetInputRequest.py b/src/trezor/messages/MoneroTransactionSetInputRequest.py index f0a9f185f..ff6b747d4 100644 --- a/src/trezor/messages/MoneroTransactionSetInputRequest.py +++ b/src/trezor/messages/MoneroTransactionSetInputRequest.py @@ -10,15 +10,12 @@ class MoneroTransactionSetInputRequest(p.MessageType): def __init__( self, - version: int = None, src_entr: MoneroTransactionSourceEntry = None, ) -> None: - self.version = version self.src_entr = src_entr @classmethod def get_fields(cls): return { - 1: ('version', p.UVarintType, 0), - 2: ('src_entr', MoneroTransactionSourceEntry, 0), + 1: ('src_entr', MoneroTransactionSourceEntry, 0), } diff --git a/src/trezor/messages/MoneroTransactionSignInputAck.py b/src/trezor/messages/MoneroTransactionSignInputAck.py index 66b239cd9..a0feb2289 100644 --- a/src/trezor/messages/MoneroTransactionSignInputAck.py +++ b/src/trezor/messages/MoneroTransactionSignInputAck.py @@ -4,19 +4,16 @@ import protobuf as p class MoneroTransactionSignInputAck(p.MessageType): - MESSAGE_WIRE_TYPE = 518 + MESSAGE_WIRE_TYPE = 516 def __init__( self, signature: bytes = None, - cout: bytes = None, ) -> None: self.signature = signature - self.cout = cout @classmethod def get_fields(cls): return { 1: ('signature', p.BytesType, 0), - 2: ('cout', p.BytesType, 0), } diff --git a/src/trezor/messages/MoneroTransactionSignInputRequest.py b/src/trezor/messages/MoneroTransactionSignInputRequest.py index 70d018fd0..54f9fc36d 100644 --- a/src/trezor/messages/MoneroTransactionSignInputRequest.py +++ b/src/trezor/messages/MoneroTransactionSignInputRequest.py @@ -6,7 +6,7 @@ from .MoneroTransactionSourceEntry import MoneroTransactionSourceEntry class MoneroTransactionSignInputRequest(p.MessageType): - MESSAGE_WIRE_TYPE = 517 + MESSAGE_WIRE_TYPE = 515 def __init__( self, @@ -15,16 +15,16 @@ class MoneroTransactionSignInputRequest(p.MessageType): vini_hmac: bytes = None, pseudo_out: bytes = None, pseudo_out_hmac: bytes = None, - alpha_enc: bytes = None, - spend_enc: bytes = None, + pseudo_out_alpha: bytes = None, + spend_key: bytes = None, ) -> None: self.src_entr = src_entr self.vini = vini self.vini_hmac = vini_hmac self.pseudo_out = pseudo_out self.pseudo_out_hmac = pseudo_out_hmac - self.alpha_enc = alpha_enc - self.spend_enc = spend_enc + self.pseudo_out_alpha = pseudo_out_alpha + self.spend_key = spend_key @classmethod def get_fields(cls): @@ -34,6 +34,6 @@ class MoneroTransactionSignInputRequest(p.MessageType): 3: ('vini_hmac', p.BytesType, 0), 4: ('pseudo_out', p.BytesType, 0), 5: ('pseudo_out_hmac', p.BytesType, 0), - 6: ('alpha_enc', p.BytesType, 0), - 7: ('spend_enc', p.BytesType, 0), + 6: ('pseudo_out_alpha', p.BytesType, 0), + 7: ('spend_key', p.BytesType, 0), } diff --git a/src/trezor/utils.py b/src/trezor/utils.py index 0b505680a..3af4e3c3d 100644 --- a/src/trezor/utils.py +++ b/src/trezor/utils.py @@ -63,15 +63,57 @@ def format_ordinal(number): class HashWriter: def __init__(self, hashfunc, *hashargs, **hashkwargs): - self.ctx = hashfunc(*hashargs, **hashkwargs) + if callable(hashfunc): + self.ctx = hashfunc(*hashargs, **hashkwargs) + else: + self.ctx = hashfunc self.buf = bytearray(1) # used in append() def extend(self, buf: bytearray): self.ctx.update(buf) + def write(self, buf: bytearray): # alias for extend() + self.ctx.update(buf) + + async def awrite(self, buf: bytearray): # AsyncWriter interface + return self.ctx.update(buf) + def append(self, b: int): self.buf[0] = b self.ctx.update(self.buf) def get_digest(self) -> bytes: return self.ctx.digest() + + +def obj_eq(l, r): + """ + Compares object contents, supports __slots__. + """ + if l.__class__ is not r.__class__: + return False + if hasattr(l, "__slots__"): + return obj_slots_dict(l) == obj_slots_dict(r) + else: + return l.__dict__ == r.__dict__ + + +def obj_repr(o): + """ + Returns a string representation of object, supports __slots__. + """ + if hasattr(o, "__slots__"): + d = obj_slots_dict(o) + else: + d = o.__dict__ + return "<%s: %s>" % (o.__class__.__name__, d) + + +def obj_slots_dict(o): + """ + Builds dict for o from defined __slots__. + """ + d = {} + for f in o.__slots__: + d[f] = getattr(o, f, None) + return d diff --git a/tests/run_tests_device_emu_monero.sh b/tests/run_tests_device_emu_monero.sh new file mode 100755 index 000000000..a8cff51bc --- /dev/null +++ b/tests/run_tests_device_emu_monero.sh @@ -0,0 +1,22 @@ +#!/bin/bash + +MICROPYTHON=../build/unix/micropython +PYOPT=0 + +# run emulator +cd ../src +$MICROPYTHON -O$PYOPT main.py >/dev/null & +upy_pid=$! +sleep 1 + +export TREZOR_PATH=udp:127.0.0.1:21324 + +# run tests +cd .. + +export EC_BACKEND_FORCE=1 +export EC_BACKEND=1 +python3 -m unittest trezor_monero_test.test_trezor +error=$? +kill $upy_pid +exit $error diff --git a/tests/test_apps.monero.bulletproof.py b/tests/test_apps.monero.bulletproof.py new file mode 100644 index 000000000..347cb981a --- /dev/null +++ b/tests/test_apps.monero.bulletproof.py @@ -0,0 +1,440 @@ +from common import * + +from apps.monero.xmr import bulletproof as bp, crypto, monero +from apps.monero.xmr.serialize_messages.tx_rsig_bulletproof import Bulletproof + + +class TestMoneroBulletproof(unittest.TestCase): + def test_1(self): + pass + + def mask_consistency_check(self, bpi): + sv = [crypto.sc_init(123)] + gamma = [crypto.sc_init(432)] + + M, logM, aL, aR, V, gamma = bpi.prove_setup(sv, gamma) + x = bp._ensure_dst_key() + y = bp._ensure_dst_key() + + sL = bpi.sL_vct(64) + sR = bpi.sR_vct(64) + + self.assertEqual(sL.to(0, x), sL.to(0, y)) + self.assertEqual(sL.to(1, x), sL.to(1, y)) + self.assertEqual(sL.to(63, x), sL.to(63, y)) + self.assertNotEqual(sL.to(1, x), sL.to(0, y)) + self.assertNotEqual(sL.to(10, x), sL.to(0, y)) + + self.assertEqual(sR.to(0, x), sR.to(0, y)) + self.assertEqual(sR.to(1, x), sR.to(1, y)) + self.assertEqual(sR.to(63, x), sR.to(63, y)) + self.assertNotEqual(sR.to(1, x), sR.to(0, y)) + + self.assertNotEqual(sL.to(0, x), sR.to(0, y)) + self.assertNotEqual(sL.to(1, x), sR.to(1, y)) + self.assertNotEqual(sL.to(63, x), sR.to(63, y)) + + ve1 = bp._ensure_dst_key() + ve2 = bp._ensure_dst_key() + bpi.vector_exponent(aL, aR, ve1) + bpi.vector_exponent(aL, aR, ve2) + + bpi.vector_exponent(sL, sR, ve1) + bpi.vector_exponent(sL, sR, ve2) + self.assertEqual(ve1, ve2) + + # fmt: off + def bproof_1(self): + return Bulletproof( + V=[ + unhexlify(b"3c705e1da4bbe43a0535a5ad3a8e6c148fb8c1a4118ba6b65412b2fe6511b261"), + ], + A=unhexlify(b"7372db75c0d9d409524924fff5dd13e867eb4c5789f3f5cc6ef860be68d5e4e5"), + S=unhexlify(b"be8f2d87ace0a528056d567881e74f44817a811e110cdb3890376262a2084ab3"), + T1=unhexlify(b"8dfc541c379efbe6000bb2339c3a52288ffa4300fcc0f0f0de777e54b5488160"), + T2=unhexlify(b"cf7d046c86c33bea6c5167bb6482c0a31332989dc9493eacc04a07deb6536953"), + taux=unhexlify(b"abaaf209cc9a800d933d51bb398b81ee7284efc9c92727066a640fdccc954009"), + mu=unhexlify(b"ec743e23abb555dca26164a86614306f117a733fcd395eb8675411cd31915608"), + L=[unhexlify(b"0ee1acc28126656eaf0934314a97e1cf2232a13f5636d319a233cedd58b2882f"), + unhexlify(b"cc3d2ec5635de569343bea37fc46a93413ae66bf803a4333f427f79f341d1696"), + unhexlify(b"518c80669bed0960fd03e802a9e837e1aa4a4910bb5853067447d7d22eaca325"), + unhexlify(b"251a586e8e79a5d767b89931e012acdae317c13c434a6f5f121e44b3b59240b2"), + unhexlify(b"09b41426e6c9808f6a58ded987cc39936f703f136b50493dd1c92c9b1ec4e7fc"), + unhexlify(b"984d1369c3c7f2687eebca26395576810c66623408958efde4f36b0bb63a2475"), + ], + R=[unhexlify(b"31768a0465315ff0dd1ea2228ae8c34d1474e873a863362feab7b050f29a211a"), + unhexlify(b"27d1b2533ed78d3dacc396afa50fa533cffc5d1563b679a4049a482436718d3c"), + unhexlify(b"a49388b042c8a4c6526054661fac1706cf450181ec1f9eed005b283614ec7f95"), + unhexlify(b"3f053243fe16f8fd302395c125ffedd93831829b13abbb195bf69fc139069de9"), + unhexlify(b"5a32d7f7132043d1f0cc8cd88cce94e5241337ed616c35a1d753436b2d1c4a93"), + unhexlify(b"bbd7f9b3031cf41b613a9ee726de9693457238b4be6317083d278e00717f8c14"), + ], + a=unhexlify(b"83d8d128f35aa02fc063792df9f4e9de0d4e58b8c6e7c449a672d6e4286ee309"), + b=unhexlify(b"741d679f1dfe749f7d1ede687f8dd48f7fd3b5a52a5e6a453488d5e25b3fff0e"), + t=unhexlify(b"88331e9fd7573135016629f337240225f9c0a5b70bad4157ad60d4260feb2b03") + ) + + def bproof_2(self): + return Bulletproof( + V=[ + unhexlify(b"3c705e1da4bbe43a0535a5ad3a8e6c148fb8c1a4118ba6b65412b2fe6511b261"), + unhexlify(b"de5b617501a37ff257e05b0cf93041253fdb85126549640891f7471d4ede167c"), + ], + A=unhexlify(b"447843c57f05fc8d68c5fdc96fe09d3599aacfe9b25e403d67482fdbe8ffbdbb"), + S=unhexlify(b"105b0186d1ec9a8e17e3f6f7909317458681275f888f6ac8a891ec3b5d51dfd5"), + T1=unhexlify(b"552c8f7b1e382842feb79b982738350b0d7aeed850ac06bc86ca7c99e43fbfcc"), + T2=unhexlify(b"2947b12ecc6c1667b0f0233ec1290893c992f655351edfd1ca877f8bcc070fc0"), + taux=unhexlify(b"8cceaccd9626c55166e8892fa6a7e200f9db27e3b46619f6c84e20b3c7ab200c"), + mu=unhexlify(b"c08a546e487b0c19e1e125c5dda6032bf198fe296d0dff52d58d091737a97b03"), + L=[unhexlify(b"4c5f56522c1e239ccc7edd45b6cc03c7ea46c3d521953bf529989f9d5935a01d"), + unhexlify(b"ba764db54e1ed9472df5d1527badd51be2a0223695a136d2114be631d135e1a9"), + unhexlify(b"7fecaae48171615c9f282c146ade72befc0f88c402a178be133b5f51afd3dbfc"), + unhexlify(b"3c66bbea3376133d8c571fced01b98ce96326fe233f311b4faf77564598d2021"), + unhexlify(b"1179c7e24a6d7655bff0b5017ccb85b21f39822c6d845cb1894737a33030e17a"), + unhexlify(b"461a200a1b5a7194c021faac7cda64a80388cea2ca26330ca06179aab409d6b1"), + unhexlify(b"5e5c377a648ac4d5c900a1ea527a9358083aa1c7777085c3ef81d0316ed16b47"), + ], + R=[unhexlify(b"110ea38dd587c1f53a8211198cd033a982d173c4d1cdbb0873685a37c7126cb5"), + unhexlify(b"960d6ef5dd857bb48148b4fb6927468d02f2a6474d535fd571b61c2c9b2b5613"), + unhexlify(b"dd6454b5e029fe4ff8f9647be237a68d0de9457e742df9dafe6e20c1f6ead444"), + unhexlify(b"ba9e3d1d9758184679283ee611144ed31d242700af13ac543bf5901472686d1a"), + unhexlify(b"05db7c85b62d95dd74f56fab6e3eee3b72b01514640601200770869616b123d1"), + unhexlify(b"b8b037b10f5647e79c7c5e7f735a554c8fb656037b304bd94383b769095bc17a"), + unhexlify(b"43f4bd0bc55b60c73ab73bb5c3f9376165f815364dc97ae62de2447e0b428632"), + ], + a=unhexlify(b"0f7696d2b23cfd84f9b62ce906458580db6fe73aaba1682e0e17e4cb9dae1b02"), + b=unhexlify(b"76541c70a127d08110a4bc09e6c6c6a0104956d089bcc0699f32dc5fde20ff03"), + t=unhexlify(b"66b4498e8980dafea640ce36c763367aba1b415c2d469b564c96d718ff009d0a") + ) + + def bproof_2_invalid(self): + return Bulletproof( + V=[ + unhexlify(b"3c705e1da4bbe43a0535a5ad3a8e6c148fb8c1a4118ba6b65412b2fe6511b261"), + unhexlify(b"de5b617501a37ff257e05b0cf93041253fdb85126549640891f7471d4ede167c"), + ], + A=unhexlify(b"447843c57f05fc8d68c5fdc96fe09d3599aacfe9b25e403d67482fdbe8ffbdbb"), + S=unhexlify(b"005b0186d1ec9a8e17e3f6f7909317458681275f888f6ac8a891ec3b5d51dfd5"), + T1=unhexlify(b"552c8f7b1e382842feb79b982738350b0d7aeed850ac06bc86ca7c99e43fbfcc"), + T2=unhexlify(b"2947b12ecc6c1667b0f0233ec1290893c992f655351edfd1ca877f8bcc070fc0"), + taux=unhexlify(b"8cceaccd9626c55166e8892fa6a7e200f9db27e3b46619f6c84e20b3c7ab200c"), + mu=unhexlify(b"c08a546e487b0c19e1e125c5dda6032bf198fe296d0dff52d58d091737a97b03"), + L=[unhexlify(b"4c5f56522c1e239ccc7edd45b6cc03c7ea46c3d521953bf529989f9d5935a01d"), + unhexlify(b"ba764db54e1ed9472df5d1527badd51be2a0223695a136d2114be631d135e1a9"), + unhexlify(b"7fecaae48171615c9f282c146ade72befc0f88c402a178be133b5f51afd3dbfc"), + unhexlify(b"3c66bbea3376133d8c571fced01b98ce96326fe233f311b4faf77564598d2021"), + unhexlify(b"1179c7e24a6d7655bff0b5017ccb85b21f39822c6d845cb1894737a33030e17a"), + unhexlify(b"461a200a1b5a7194c021faac7cda64a80388cea2ca26330ca06179aab409d6b1"), + unhexlify(b"5e5c377a648ac4d5c900a1ea527a9358083aa1c7777085c3ef81d0316ed16b47"), + ], + R=[unhexlify(b"110ea38dd587c1f53a8211198cd033a982d173c4d1cdbb0873685a37c7126cb5"), + unhexlify(b"960d6ef5dd857bb48148b4fb6927468d02f2a6474d535fd571b61c2c9b2b5613"), + unhexlify(b"dd6454b5e029fe4ff8f9647be237a68d0de9457e742df9dafe6e20c1f6ead444"), + unhexlify(b"ba9e3d1d9758184679283ee611144ed31d242700af13ac543bf5901472686d1a"), + unhexlify(b"05db7c85b62d95dd74f56fab6e3eee3b72b01514640601200770869616b123d1"), + unhexlify(b"b8b037b10f5647e79c7c5e7f735a554c8fb656037b304bd94383b769095bc17a"), + unhexlify(b"43f4bd0bc55b60c73ab73bb5c3f9376165f815364dc97ae62de2447e0b428632"), + ], + a=unhexlify(b"0f7696d2b23cfd84f9b62ce906458580db6fe73aaba1682e0e17e4cb9dae1b02"), + b=unhexlify(b"76541c70a127d08110a4bc09e6c6c6a0104956d089bcc0699f32dc5fde20ff03"), + t=unhexlify(b"66b4498e8980dafea640ce36c763367aba1b415c2d469b564c96d718ff009d0a") + ) + + def bproof_4(self): + return Bulletproof( + V=[ + unhexlify(b"8bb0da134d14ad399af3b3ab476afbf3a9ad39c610d770ad86be8f8fcf4d5334"), + unhexlify(b"5321769a89359b519df85e8aaf9d310920641a09796b1c07917c505dfea3c638"), + unhexlify(b"4b7dfc193c8e717f66f8811aa30ed5aa27cde9f5b64826346c96040be6311256"), + unhexlify(b"e3a5474501cef576428521ab71c17676477ea75ca2de0f1950cc62a91831bb4b"), + ], + A=unhexlify(b"d5be7a928f686ac09eaa8d18c3329e587d6e8e8cc9a35f50a747a128c94da69a"), + S=unhexlify(b"35654816c07d7537e1091bbe5768eb5733c986b642aad9e1ecf8e2d9dca5894e"), + T1=unhexlify(b"b0fcf5c8e6b23bbcbbc7e31776ba08166b01a3fb22930f871c5afae01c5bfa30"), + T2=unhexlify(b"a918af0bfb87b142ef86ef4cebff56e7ff372ac554f5bb50e11ef9cf730eb984"), + taux=unhexlify(b"f8596b4f35387c2b7bbda10bc668f4233c1c34ec9ab702e5064476182de38405"), + mu=unhexlify(b"5ebde106b2c6096808e359dddaec2d9e0dc558a38f9958fabb60dd90ba3a1701"), + L=[unhexlify(b"a98b5961c6988f9a31a9fd982e5e992f0c899edf91ba09d87f254eff45e20c88"), + unhexlify(b"81e161c3b3573fcf8f5e365a01b2882b1dbacc1dbf273eba984eb8ae575794e6"), + unhexlify(b"0b3c65d81b2d0384aa2d3ec128e880b2385f6c7de942a5f906d84d930f458798"), + unhexlify(b"38fc712591ca80d106e0d207a9342d7fea1be529909de7aeb3df1e6e805520f5"), + unhexlify(b"8cc71b0aa59c67f1f9c3f0f6f64e8feb3622406a45f9575cd96697fdfce98ba8"), + unhexlify(b"8456936b65204ec32bc5e378485d6d7931581cf9f5d734c5af34a3dde67de785"), + unhexlify(b"4fca68547aea92ab546e33d43151821b94c153cc045388a4b409276c8c52110b"), + unhexlify(b"197f85a00316bee804a89f215b91edb5e259e92b002bf7a410174fc8b5987e6c"), + ], + R=[unhexlify(b"02a06fa825460b77fb3bdc6724da7849b81ecd98602cd666720235319133673a"), + unhexlify(b"bc633534483f4e6a86133281d6d841c81d75e3305785463d55b1991c2e2d5492"), + unhexlify(b"c816c4d05b92288d0d6431513bfbcbdefd15e39cfc665ea6445ebb8903811931"), + unhexlify(b"27def4cc98f1c7c43aed78968aacc3fb06394ebf305de4495998cd3e6cbb515a"), + unhexlify(b"9a54fba6a21aafc95c3e80639558b6608257e3289dc005855b37245f6f5a0d85"), + unhexlify(b"495dd5d57df30aff8be48f538142c2c50d04675953286dbd82095cb7e9ec45f7"), + unhexlify(b"d504b4927875bf39651c4593a4dc27d78a14ff0ddc46b056c0bcd1d6ab5dce90"), + unhexlify(b"80a87eb25f02539fbb44649c477ce0044e7ec8e99410d16242796aad168f6731"), + ], + a=unhexlify(b"ae3789e27324e3d4ecc48993b83052a8843fdcc67e1e4d30221e2dba4dd3c205"), + b=unhexlify(b"4206e54ee16aaba98c43ab34ce7b094c05bc1d5c89c2cfc15436346f808fc305"), + t=unhexlify(b"91ac52bd644dd0cf47064c340a0fb7e87f66eee3f9286af0f75a910260f46406") + ) + + def bproof_8(self): + return Bulletproof( + V=[ + unhexlify(b"8968230f6104ecadab81a61b71d7e5d35b62fb5e983ef0fa143f399e1b455556"), + unhexlify(b"3ad4c8fc2476f239767b2c98b8adbd613e1d48290577ac2f060e5eae4578bc07"), + unhexlify(b"5bfa33de351ec800057b9a94009cbe3c4b2207f8518adf338db39a4a541f99fb"), + unhexlify(b"65ad56ed1e1253f3f6d912e46ce76a59f1e0ce76133e94c6fcff06aa8e57847f"), + unhexlify(b"0b6c65c33a06a5fe402c735c5e58981e9cc5ed6d7020df746d828b203566010f"), + unhexlify(b"eb01d17406d4b71b5b01358c3a8187da02de64cd6a18dcaaff107e1c0310283b"), + unhexlify(b"5826183d16cc353b8b07778354b4d5e4bec71c9c915b8db4cd314e1a4fc8515c"), + unhexlify(b"cf980756a69c3535f9a52897e13cb3649211bc9870246b8456a55311b9d47b65"), + ], + A=unhexlify(b"9a8aa683d90464a9a02ab9d002bdaf04d306c271285caa916d0275bfed5786a8"), + S=unhexlify(b"6eee7bf4b3b9fd00b0018c4a95d9e0dbdf4a4d8d68891c212a99ae040fad12db"), + T1=unhexlify(b"d8b154556661544b0967529ddbb1650d8e82f6c43a2698f4191e36f815c44106"), + T2=unhexlify(b"21a987b97cebcc51116dcdffc0576a8d727970bc5e075d4c885c9612ec53f01a"), + taux=unhexlify(b"3f7b2ea15183fed911215d05d839907d057f324c01e630bb66dfb7f2e939f601"), + mu=unhexlify(b"86440d5b853a4d39b43a05346535f6c62d434d3543eda161c2415f8a62387403"), + L=[unhexlify(b"110ddcd1c72e576fa2a7388a31e5632779a15394a8f82c6db4a16aa12ee8c673"), + unhexlify(b"0013e290ca6453f4327d79a010d7158588e4df5da07e64913ec460ebf1992729"), + unhexlify(b"1f4af7b9e76c84fdd28ed1419f9b7d90f42c87399f8aca81b52dcf4f22dcf4db"), + unhexlify(b"2e05fcc9835dac5e6a3124068726fdcfab49e6c5215dcb0d6ccf55befacbcc10"), + unhexlify(b"0a8564bb2c7938382541e2f51996eb0d8c9a944a1c4c7abba51938fd3a2498b1"), + unhexlify(b"a3b02cdbe3af1bb9f5961cdee787b1a55ce1e083cd4377543e7c11b3aa3a4789"), + unhexlify(b"1d3301fe9b7438dcced3ca052a364aa442b1abe189f4013003e7ec8245331e5a"), + unhexlify(b"31a0387da1091c18618ffc85ae1c84774ddd3885e6f9a525e108dab92333151a"), + unhexlify(b"550e00179778a332d960438d443caab60b571c878787c3dc90a056cb102c47bb"), + ], + R=[unhexlify(b"b8d336c3521a854856bd1f78d8c7566f4cfe1441af74f38562bca947d98ab884"), + unhexlify(b"b5514c68e2765ee4c39af1c65360a42f76e2538cea04de97e4e5cfb159fa46bc"), + unhexlify(b"9f41d7fa770cae09b83600a5852ed36ef3418ec9bca566881046db6ceddaa87a"), + unhexlify(b"b9dfcd2f889ccff138b98e84aad4cbbcb1723db0722950b421f0c7f7ca550312"), + unhexlify(b"6fdcc16d2f6c202a2c386eb7d3b61dad86b8fe7d2c4d87c73a87fd87fa931828"), + unhexlify(b"90aa28db8b75a86c7a867ae8323b5e327086047fe88131230c874fcae818a711"), + unhexlify(b"398bfe592022ec1c801e13bd35577d563faa727d37f37daea5977c8abe584d69"), + unhexlify(b"7dd85aff7c63f98b65384e0439407db98df9428f0375b7d7581291e519b097ec"), + unhexlify(b"27a37d34a9f0fc5e1b7f20256a2b59b19954b52ae39f29870731750dab52bb1f"), + ], + a=unhexlify(b"de7d5bebe81a3ca0cb151e187e078ce98e7c19cc3f4ba448c2ccadee969a0f0a"), + b=unhexlify(b"f8f88ee64bc03c68fa40d855f96b3d9f0cf16e3035aef3a129f76c89196b7207"), + t=unhexlify(b"e9f0f74b2efcff21ac16c842e27b79d6615a31873be399d38e257a85bcc7c00b") + ) + + def bproof_16(self): + return Bulletproof( + V=[ + unhexlify(b"fcb9064e19894c5703f49d515ab0e6e98c87f9ec4230f0b898bc22061bb8d39f"), + unhexlify(b"507ff564c127cc2beb83c9a539408ffd2ec5f648dda711724bba1a8b79d66e32"), + unhexlify(b"dbd4de0ef0184378c483b9e821a6da2d80ed24d0c7444104efa4bbc710ed14ef"), + unhexlify(b"8baeb7c9d69946547a8f73cdb0e6fae3134fe5e1734e8bbefe8bb452da1ae59e"), + unhexlify(b"7f28196a49a4130b68b5589df47d9a2a08dc27518809016a3753ab2db4d8c453"), + unhexlify(b"708ad84f91f702dbcdeee179f7e94328314190935ada0f85eddd5db35d631c8c"), + unhexlify(b"3925935ecd1d2bb5fda9a15a822db8128c045b1b9fa017f4913231289329ff41"), + unhexlify(b"d75438e7d308bb758e68d6af7d80087755dcffc0a47255ce17c2f653501997ef"), + unhexlify(b"dd24a43fc1c31240c7d64248bd100e4ed7cefd9bd80ac05471ce947a71176de6"), + unhexlify(b"c0011f8ae31c76f3658eb971bb520cac0d051fee4e5cf3ba833d55e093643f08"), + unhexlify(b"c7b7edc4c584a1cc41b079550ce9e6ff7bc781b52d16c0c4b667988f422d66a8"), + unhexlify(b"483d8df224106bbb6f45f50becbc70b55bbcbd262c0447a42d16f62ad57c057d"), + unhexlify(b"9af16713061f6a43112092a3221a07ab7dd377145dc705611ad03f7cc407626a"), + unhexlify(b"74b066e1866043d736bde08a790879e5387838e793fbdfb1e05d11404ad4c08f"), + unhexlify(b"add0f5a30bf8111541ce5ab176bebf77f0b470f5ccac57ec5fda1dd5da641dfd"), + unhexlify(b"d74989bc336a0557a027ad440683406273ca4d04d4138eac69107074ea7e18ed"), + ], + A=unhexlify(b"a66ce244c86883c8b8ef5ad8e38b3cb8db00306698813f9858edb226d294e52d"), + S=unhexlify(b"34be5df1324bd561fd94695eb4b6e084068f188039831ca81be0e77849b639a7"), + T1=unhexlify(b"6a86c12f2c1bd2c2c43193da1a1b4c13e8829c0583318977417a6e1144e3c1e9"), + T2=unhexlify(b"f16761fd77f03a3d74854fe33143e2b02274747f27c6a4b45973622a0d1cec73"), + taux=unhexlify(b"272decc39da7103cddfa0dc7ea4042ea313ab3f740c9234d060d35db04b9700b"), + mu=unhexlify(b"acaa00e33078217cc1ee795e2886b771dded4da964ea8db682b7c4a9039c4906"), + L=[unhexlify(b"f964f4a86362e788978371d38052e825e22e17e8e52a82a5b61cff4447516d1d"), + unhexlify(b"ea7d1b2e10fb0aa15b8bf4be7300c0619036a0846bc0ae4ef62eca61fd2545f3"), + unhexlify(b"d4cdfb68a899503f317edd6050da54e85a2979c7b145c10c76f69899a1dee450"), + unhexlify(b"9c56aadad7366addf6c7ea3e39bde810056dd59ee9a6020109c92e8939734583"), + unhexlify(b"56685894507fdbb994ff007f94ac16ad5b5d7e4cec2a2de6bbc0c0532b5cb190"), + unhexlify(b"023f99b43ae8509446f625241bf263052c141a49f02356ff65d9eeec287ecd05"), + unhexlify(b"eee0982bd28d2e85f0712d043879c00c1899de69ab1362cc0ac5369b3b17f32d"), + unhexlify(b"8d4f9f498d376fda7b06a5dd95be852b32b65d6e2e38363f3e805477e996eec4"), + unhexlify(b"e5d9ee5f8910b67c1cc02232e8bb018f0c39966f76b34aee2ce3441fd2737da2"), + unhexlify(b"620ab92c82b294a97815d06548a4ac669228d04551c24d174902db1cbb8f10aa"), + ], + R=[unhexlify(b"a7b55c962351bd40dffe898a5192a3f05a6a89a0e3e61d6a2c4af27640c9cafe"), + unhexlify(b"010a1259e188677332b3e3b60030f9b8fe57ff95d4ff4a3c110a147f26a0bf07"), + unhexlify(b"891a65627a950b55428d86e2face1543b3297ec5d13839d7de684d92a9c7626b"), + unhexlify(b"382e4354aad61f42223c9ff903aee83557716130bb159645ac48e8d27485feb9"), + unhexlify(b"e8cd6098fdf7fba0f03db32e0fd41b549bfeaf489bfccf72c391fba9d5046737"), + unhexlify(b"ed15099d4d39a73dc48a338246828c6ce60c8f68b648a0d205bf65b3d5a3623b"), + unhexlify(b"8d1deb97bbfb33bec7dda8f56428a588a92512ee41ebdcb7b2f7631fc45d1a12"), + unhexlify(b"35bd424cef108222b900b58078b90387a65c70f3fd1e1367dd827ee172f393fd"), + unhexlify(b"63ecb848be5fab31d355c976876abd892cdc9d0ebc90e90cd8dab46e7f417740"), + unhexlify(b"9189a79c56400ac08d8821d283d94f1f5ebebaf6f9660a62c995fccb192431d6"), + ], + a=unhexlify(b"e43b7890911f413e0d870f099961da40d8e053d9ddd21a56f7eb3308828dbc04"), + b=unhexlify(b"dfea0fe39d9a7c5497fd01e92fc7fa8b39cda75b340322f77e0cac15194aa007"), + t=unhexlify(b"0de43b393686af8dd0d89f4832a2995cda14e6288de9ecd2b4bf2fa39baba408") + ) + # fmt: on + + def test_masks(self): + bpi = bp.BulletProofBuilder() + self.mask_consistency_check(bpi) + + # Randomized masks + bpi.use_det_masks = False + self.mask_consistency_check(bpi) + + def test_verify_testnet(self): + bpi = bp.BulletProofBuilder() + + # fmt: off + bp_proof = Bulletproof( + V=[bytes( + [0x67, 0x54, 0xbf, 0x40, 0xcb, 0x45, 0x63, 0x0d, 0x4b, 0xea, 0x08, 0x9e, 0xd7, 0x86, 0xec, 0x3c, 0xe5, + 0xbd, 0x4e, 0xed, 0x8f, 0xf3, 0x25, 0x76, 0xae, 0xca, 0xb8, 0x9e, 0xf2, 0x5e, 0x41, 0x16])], + A=bytes( + [0x96, 0x10, 0x17, 0x66, 0x87, 0x7e, 0xef, 0x97, 0xb3, 0x82, 0xfb, 0x8e, 0x0c, 0x2a, 0x93, 0x68, 0x9e, + 0x05, 0x22, 0x07, 0xe3, 0x30, 0x94, 0x20, 0x58, 0x6f, 0x5d, 0x01, 0x6d, 0x4e, 0xd5, 0x88]), + S=bytes( + [0x50, 0x51, 0x38, 0x32, 0x96, 0x20, 0x7c, 0xc9, 0x60, 0x4d, 0xac, 0x7c, 0x7c, 0x21, 0xf9, 0xad, 0x1c, + 0xc2, 0x2d, 0xee, 0x88, 0x7b, 0xa2, 0xe2, 0x61, 0x81, 0x46, 0xf5, 0x99, 0xc3, 0x12, 0x57]), + T1=bytes( + [0x1a, 0x7d, 0x06, 0x51, 0x41, 0xe6, 0x12, 0xbe, 0xad, 0xd7, 0x68, 0x60, 0x85, 0xfc, 0xc4, 0x86, 0x0b, + 0x39, 0x4b, 0x06, 0xf7, 0xca, 0xb3, 0x29, 0xdf, 0x1d, 0xbf, 0x96, 0x5f, 0xbe, 0x8c, 0x87]), + T2=bytes( + [0x57, 0xae, 0x91, 0x04, 0xfa, 0xac, 0xf3, 0x73, 0x75, 0xf2, 0x83, 0xd6, 0x9a, 0xcb, 0xef, 0xe4, 0xfc, + 0xe5, 0x37, 0x55, 0x52, 0x09, 0xb5, 0x60, 0x6d, 0xab, 0x46, 0x85, 0x01, 0x23, 0x9e, 0x47]), + taux=bytes( + [0x44, 0x7a, 0x87, 0xd9, 0x5f, 0x1b, 0x17, 0xed, 0x53, 0x7f, 0xc1, 0x4f, 0x91, 0x9b, 0xca, 0x68, 0xce, + 0x20, 0x43, 0xc0, 0x88, 0xf1, 0xdf, 0x12, 0x7b, 0xd7, 0x7f, 0xe0, 0x27, 0xef, 0xef, 0x0d]), + mu=bytes( + [0x32, 0xf9, 0xe4, 0xe1, 0xc2, 0xd8, 0xe4, 0xb0, 0x0d, 0x49, 0xd1, 0x02, 0xbc, 0xcc, 0xf7, 0xa2, 0x5a, + 0xc7, 0x28, 0xf3, 0x05, 0xb5, 0x64, 0x2e, 0xde, 0xcf, 0x01, 0x61, 0xb8, 0x62, 0xfb, 0x0d]), + L=[ + bytes([0xde, 0x71, 0xca, 0x09, 0xf9, 0xd9, 0x1f, 0xa2, 0xae, 0xdf, 0x39, 0x49, 0x04, 0xaa, 0x6b, 0x58, + 0x67, 0x9d, 0x61, 0xa6, 0xfa, 0xec, 0x81, 0xf6, 0x4c, 0x15, 0x09, 0x9d, 0x10, 0x21, 0xff, 0x39]), + bytes([0x90, 0x47, 0xbf, 0xf0, 0x1f, 0x72, 0x47, 0x4e, 0xd5, 0x58, 0xfb, 0xc1, 0x16, 0x43, 0xb7, 0xd8, + 0xb1, 0x00, 0xa4, 0xa3, 0x19, 0x9b, 0xda, 0x5b, 0x27, 0xd3, 0x6c, 0x5a, 0x87, 0xf8, 0xf0, 0x28]), + bytes([0x03, 0x45, 0xef, 0x57, 0x19, 0x8b, 0xc7, 0x38, 0xb7, 0xcb, 0x9c, 0xe7, 0xe8, 0x23, 0x27, 0xbb, + 0xd3, 0x54, 0xcb, 0x38, 0x3c, 0x24, 0x8a, 0x60, 0x11, 0x20, 0x92, 0x99, 0xec, 0x35, 0x71, 0x9f]), + bytes([0x7a, 0xb6, 0x36, 0x42, 0x36, 0x83, 0xf3, 0xa6, 0xc1, 0x24, 0xc5, 0x63, 0xb0, 0x4c, 0x8b, 0xef, + 0x7c, 0x77, 0x25, 0x83, 0xa8, 0xbb, 0x8b, 0x57, 0x75, 0x1c, 0xb6, 0xd7, 0xca, 0xc9, 0x0d, 0x78]), + bytes([0x9d, 0x79, 0x66, 0x21, 0x64, 0x72, 0x97, 0x08, 0xa0, 0x5a, 0x94, 0x5a, 0x94, 0x7b, 0x11, 0xeb, + 0x4e, 0xe9, 0x43, 0x2f, 0x08, 0xa2, 0x57, 0xa5, 0xd5, 0x99, 0xb0, 0xa7, 0xde, 0x78, 0x80, 0xb7]), + bytes([0x9f, 0x88, 0x5c, 0xa5, 0xeb, 0x08, 0xef, 0x1a, 0xcf, 0xbb, 0x1d, 0x04, 0xc5, 0x47, 0x24, 0x37, + 0x49, 0xe4, 0x4e, 0x9c, 0x5d, 0x56, 0xd0, 0x97, 0xfd, 0x8a, 0xe3, 0x23, 0x1d, 0xab, 0x16, 0x03]), + ], + R=[ + bytes([0xae, 0x89, 0xeb, 0xa8, 0x5b, 0xd5, 0x65, 0xd6, 0x9f, 0x2a, 0xfd, 0x04, 0x66, 0xad, 0xb1, 0xf3, + 0x5e, 0xf6, 0x60, 0xa7, 0x26, 0x94, 0x3b, 0x72, 0x5a, 0x5c, 0x80, 0xfa, 0x0f, 0x75, 0x48, 0x27]), + bytes([0xc9, 0x1a, 0x61, 0x70, 0x6d, 0xea, 0xea, 0xb2, 0x42, 0xff, 0x27, 0x3b, 0x8e, 0x94, 0x07, 0x75, + 0x40, 0x7d, 0x33, 0xde, 0xfc, 0xbd, 0x53, 0xa0, 0x2a, 0xf9, 0x0c, 0x36, 0xb0, 0xdd, 0xbe, 0x8d]), + bytes([0xb7, 0x39, 0x7a, 0x0e, 0xa1, 0x42, 0x0f, 0x94, 0x62, 0x24, 0xcf, 0x54, 0x75, 0xe3, 0x0b, 0x0f, + 0xfb, 0xcb, 0x67, 0x7b, 0xbc, 0x98, 0x36, 0x01, 0x9f, 0x73, 0xa0, 0x70, 0xa1, 0x7e, 0xf0, 0xcf]), + bytes([0x40, 0x06, 0xd4, 0xfa, 0x22, 0x7c, 0x82, 0xbf, 0xe8, 0xe0, 0x35, 0x13, 0x28, 0xa2, 0xb9, 0x51, + 0xa3, 0x37, 0x34, 0xc0, 0xa6, 0x43, 0xd6, 0xb7, 0x7a, 0x40, 0xae, 0xf9, 0x36, 0x0e, 0xe3, 0xcc]), + bytes([0x88, 0x38, 0x64, 0xe9, 0x63, 0xe3, 0x33, 0xd9, 0xf6, 0xca, 0x47, 0xc4, 0xc7, 0x36, 0x70, 0x01, + 0xd2, 0xe4, 0x8c, 0x9f, 0x25, 0xc2, 0xce, 0xcf, 0x81, 0x89, 0x4f, 0x24, 0xcb, 0xb8, 0x40, 0x73]), + bytes([0xdc, 0x35, 0x65, 0xed, 0x6b, 0xb0, 0xa7, 0x1a, 0x1b, 0xf3, 0xd6, 0xfb, 0x47, 0x00, 0x48, 0x00, + 0x20, 0x6d, 0xd4, 0xeb, 0xff, 0xb9, 0xdc, 0x43, 0x30, 0x8a, 0x90, 0xfe, 0x43, 0x74, 0x75, 0x68]), + ], + a=bytes( + [0xb4, 0x8e, 0xc2, 0x31, 0xce, 0x05, 0x9a, 0x7a, 0xbc, 0x82, 0x8c, 0x30, 0xb3, 0xe3, 0x80, 0x86, 0x05, + 0xb8, 0x4c, 0x93, 0x9a, 0x8e, 0xce, 0x39, 0x0f, 0xb6, 0xee, 0x28, 0xf6, 0x7e, 0xd5, 0x07]), + b=bytes( + [0x47, 0x10, 0x62, 0xc2, 0xad, 0xc7, 0xe2, 0xc9, 0x14, 0x6f, 0xf4, 0xd1, 0xfe, 0x52, 0xa9, 0x1a, 0xe4, + 0xb6, 0xd0, 0x25, 0x4b, 0x19, 0x80, 0x7c, 0xcd, 0x62, 0x62, 0x1d, 0x97, 0x20, 0x71, 0x0b]), + t=bytes( + [0x47, 0x06, 0xea, 0x76, 0x8f, 0xdb, 0xa3, 0x15, 0xe0, 0x2c, 0x6b, 0x25, 0xa1, 0xf7, 0x3c, 0xc8, 0x1d, + 0x97, 0xa6, 0x52, 0x48, 0x75, 0x37, 0xf9, 0x1e, 0x14, 0xac, 0xb1, 0x2a, 0x34, 0xc6, 0x06]) + ) + # fmt: on + + self.assertTrue(bpi.verify_testnet(bp_proof)) + + def test_verify(self): + bpi = bp.BulletProofBuilder() + self.assertTrue(bpi.verify(self.bproof_1())) + self.assertTrue(bpi.verify(self.bproof_2())) + self.assertTrue(bpi.verify(self.bproof_4())) + + def test_prove_testnet(self): + bpi = bp.BulletProofBuilder() + val = crypto.sc_init(123) + mask = crypto.sc_init(432) + + bp_res = bpi.prove_testnet(val, mask) + bpi.verify_testnet(bp_res) + + try: + bp_res.S[0] += 1 + bpi.verify(bp_res) + self.fail("Verification should have failed") + except: + pass + + def test_prove_testnet_2(self): + bpi = bp.BulletProofBuilder() + val = crypto.sc_init((1 << 30) - 1 + 16) + mask = crypto.random_scalar() + + bp_res = bpi.prove_testnet(val, mask) + bpi.verify_testnet(bp_res) + + def test_verify_batch_1(self): + bpi = bp.BulletProofBuilder() + bpi.verify_batch([self.bproof_1()]) + bpi.verify_batch([self.bproof_2()]) + bpi.verify_batch([self.bproof_4()]) + bpi.verify_batch([self.bproof_8()]) + bpi.verify_batch([self.bproof_16()]) + with self.assertRaises(Exception): + bpi.verify_batch([self.bproof_2_invalid()]) + with self.assertRaises(Exception): + bpi.verify_batch([self.bproof_2_invalid()]) + + def test_prove_random_masks(self): + bpi = bp.BulletProofBuilder() + bpi.use_det_masks = False # trully randomly generated mask vectors + val = crypto.sc_init((1 << 30) - 1 + 16) + mask = crypto.random_scalar() + + bp_res = bpi.prove(val, mask) + bpi.verify(bp_res) + + def test_prove_testnet_random_masks(self): + bpi = bp.BulletProofBuilder() + bpi.use_det_masks = False # trully randomly generated mask vectors + val = crypto.sc_init((1 << 30) - 1 + 16) + mask = crypto.random_scalar() + + bp_res = bpi.prove_testnet(val, mask) + bpi.verify_testnet(bp_res) + + def ctest_multiexp(self): + scalars = [0, 1, 2, 3, 4, 99] + point_base = [0, 2, 4, 7, 12, 18] + scalar_sc = [crypto.sc_init(x) for x in scalars] + points = [crypto.scalarmult_base(crypto.sc_init(x)) for x in point_base] + + muex = bp.MultiExp(scalars=[crypto.encodeint(x) for x in scalar_sc], + point_fnc=lambda i, d: crypto.encodepoint(points[i])) + + self.assertEqual(len(muex), len(scalars)) + res = bp.multiexp(None, muex) + res2 = bp.vector_exponent_custom( + A=bp.KeyVEval(3, lambda i, d: crypto.encodepoint_into(crypto.scalarmult_base(crypto.sc_init(point_base[i])), d)), + B=bp.KeyVEval(3, lambda i, d: crypto.encodepoint_into(crypto.scalarmult_base(crypto.sc_init(point_base[3+i])), d)), + a=bp.KeyVEval(3, lambda i, d: crypto.encodeint_into(crypto.sc_init(scalars[i]), d),), + b=bp.KeyVEval(3, lambda i, d: crypto.encodeint_into(crypto.sc_init(scalars[i+3]), d)), + ) + self.assertEqual(res, res2) + + def test_prove_batch(self): + bpi = bp.BulletProofBuilder() + sv = [crypto.sc_init(123), crypto.sc_init(768)] + gamma = [crypto.sc_init(456), crypto.sc_init(901)] + proof = bpi.prove_batch(sv, gamma) + bpi.verify_batch([proof]) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_apps.monero.crypto.py b/tests/test_apps.monero.crypto.py new file mode 100644 index 000000000..fb589fba2 --- /dev/null +++ b/tests/test_apps.monero.crypto.py @@ -0,0 +1,238 @@ +from common import * + +from apps.monero.xmr import crypto, monero +from apps.monero.xmr.addresses import encode_addr +from apps.monero.xmr.credentials import AccountCreds +from apps.monero.xmr.networks import NetworkTypes, net_version + + +class TestMoneroCrypto(unittest.TestCase): + def test_encoding(self): + point = unhexlify( + b"2486224797d05cae3cba4be043be2db0df381f3f19cfa113f86ab38e3d8d2bd0" + ) + self.assertEqual(point, crypto.encodepoint(crypto.decodepoint(point))) + self.assertTrue( + crypto.point_eq( + crypto.decodepoint(point), + crypto.decodepoint(crypto.encodepoint(crypto.decodepoint(point))), + ) + ) + + def test_scalarmult_base(self): + scalar = crypto.decodeint( + unhexlify( + b"a0eea49140a3b036da30eacf64bd9d56ce3ef68ba82ef13571ec511edbcf8303" + ) + ) + exp = unhexlify( + b"16bb4a3c44e2ced511fc0d4cd86b13b3af21efc99fb0356199fac489f2544c09" + ) + + res = crypto.scalarmult_base(scalar) + self.assertEqual(exp, crypto.encodepoint(res)) + self.assertTrue(crypto.point_eq(crypto.decodepoint(exp), res)) + + scalar = crypto.decodeint( + unhexlify( + b"fd290dce39f781aebbdbd24584ed6d48bd300de19d9c3decfda0a6e2c6751d0f" + ) + ) + exp = unhexlify( + b"123daf90fc26f13c6529e6b49bfed498995ac383ef19c0db6771143f24ba8dd5" + ) + + res = crypto.scalarmult_base(scalar) + self.assertEqual(exp, crypto.encodepoint(res)) + self.assertTrue(crypto.point_eq(crypto.decodepoint(exp), res)) + + def test_scalarmult(self): + priv = unhexlify( + b"3482fb9735ef879fcae5ec7721b5d3646e155c4fb58d6cc11c732c9c9b76620a" + ) + pub = unhexlify( + b"2486224797d05cae3cba4be043be2db0df381f3f19cfa113f86ab38e3d8d2bd0" + ) + exp = unhexlify( + b"adcd1f5881f46f254900a03c654e71950a88a0236fa0a3a946c9b8daed6ef43d" + ) + + res = crypto.scalarmult(crypto.decodepoint(pub), crypto.decodeint(priv)) + self.assertEqual(exp, crypto.encodepoint(res)) + self.assertTrue(crypto.point_eq(crypto.decodepoint(exp), res)) + + def test_cn_fast_hash(self): + inp = unhexlify( + b"259ef2aba8feb473cf39058a0fe30b9ff6d245b42b6826687ebd6b63128aff6405" + ) + res = crypto.cn_fast_hash(inp) + self.assertEqual( + res, + unhexlify( + b"86db87b83fb1246efca5f3b0db09ce3fa4d605b0d10e6507cac253dd31a3ec16" + ), + ) + + def test_hash_to_scalar(self): + inp = unhexlify( + b"259ef2aba8feb473cf39058a0fe30b9ff6d245b42b6826687ebd6b63128aff6405" + ) + + res = crypto.hash_to_scalar(inp) + exp = crypto.decodeint( + unhexlify( + b"9907925b254e12162609fc0dfd0fef2aa4d605b0d10e6507cac253dd31a3ec06" + ) + ) + self.assertTrue(crypto.sc_eq(res, exp)) + + def test_hash_to_point(self): + data = unhexlify( + b"42f6835bf83114a1f5f6076fe79bdfa0bd67c74b88f127d54572d3910dd09201" + ) + res = crypto.hash_to_point(data) + res_p = crypto.encodepoint(res) + self.assertEqual( + res_p, + unhexlify( + b"54863a0464c008acc99cffb179bc6cf34eb1bbdf6c29f7a070a7c6376ae30ab5" + ), + ) + + def test_derivation_to_scalar(self): + derivation = unhexlify( + b"e720a09f2e3a0bbf4e4ba7ad93653bb296885510121f806acb2a5f9168fafa01" + ) + scalar = unhexlify( + b"25d08763414c379aa9cf989cdcb3cadd36bd5193b500107d6bf5f921f18e470e" + ) + + sc_int = crypto.derivation_to_scalar(crypto.decodepoint(derivation), 0) + self.assertEqual(scalar, crypto.encodeint(sc_int)) + + def test_generate_key_derivation(self): + key_pub = crypto.decodepoint( + unhexlify( + b"7739c95d3298e2f87362dba9e0e0b3980a692ae8e2f16796b0e382098cd6bd83" + ) + ) + key_priv = crypto.decodeint( + unhexlify( + b"3482fb9735ef879fcae5ec7721b5d3646e155c4fb58d6cc11c732c9c9b76620a" + ) + ) + deriv_exp = unhexlify( + b"fa188a45a0e4daccc0e6d4f6f6858fd46392104be74183ec0047e7e9f4eaf739" + ) + + self.assertEqual( + deriv_exp, + crypto.encodepoint(crypto.generate_key_derivation(key_pub, key_priv)), + ) + + def test_h(self): + H = unhexlify( + b"8b655970153799af2aeadc9ff1add0ea6c7251d54154cfa92c173a0dd39c1f94" + ) + self.assertEqual(crypto.encodepoint(crypto.xmr_H()), H) + + def test_sc_inversion(self): + res = crypto.new_scalar() + inp = crypto.decodeint( + unhexlify( + b"3482fb9735ef879fcae5ec7721b5d3646e155c4fb58d6cc11c732c9c9b76620a" + ) + ) + + crypto.sc_inv_into(res, inp) + self.assertEqual( + hexlify(crypto.encodeint(res)), + b"bcf365a551e6358f3f281a6241d4a25eded60230b60a1d48c67b51a85e33d70e", + ) + + def test_wallet_addr(self): + addr = encode_addr( + net_version(), + unhexlify( + b"3bec484c5d7f0246af520aab550452b5b6013733feabebd681c4a60d457b7fc1" + ), + unhexlify( + b"2d5918e31d3c003da3c778592c07b398ad6f961a67082a75fd49394d51e69bbe" + ), + ) + + self.assertEqual( + addr, + b"43tpGG9PKbwCpjRvNLn1jwXPpnacw2uVUcszAtgmDiVcZK4VgHwjJT9BJz1WGF9eMxSYASp8yNMkuLjeQfWqJn3CNWdWfzV", + ) + + w = AccountCreds.new_wallet( + crypto.decodeint( + unhexlify( + b"4ce88c168e0f5f8d6524f712d5f8d7d83233b1e7a2a60b5aba5206cc0ea2bc08" + ) + ), + crypto.decodeint( + unhexlify( + b"f2644a3dd97d43e87887e74d1691d52baa0614206ad1b0c239ff4aa3b501750a" + ) + ), + network_type=NetworkTypes.TESTNET, + ) + self.assertEqual( + w.address, + b"9vacMKaj8JJV6MnwDzh2oNVdwTLJfTDyNRiB6NzV9TT7fqvzLivH2dB8Tv7VYR3ncn8vCb3KdNMJzQWrPAF1otYJ9cPKpkr", + ) + + def test_derive_subaddress_public_key(self): + out_key = crypto.decodepoint( + unhexlify( + b"f4efc29da4ccd6bc6e81f52a6f47b2952966442a7efb49901cce06a7a3bef3e5" + ) + ) + deriv = crypto.decodepoint( + unhexlify( + b"259ef2aba8feb473cf39058a0fe30b9ff6d245b42b6826687ebd6b63128aff64" + ) + ) + res = crypto.encodepoint(monero.derive_subaddress_public_key(out_key, deriv, 5)) + self.assertEqual( + res, + unhexlify( + b"5a10cca900ee47a7f412cd661b29f5ab356d6a1951884593bb170b5ec8b6f2e8" + ), + ) + + def test_get_subaddress_secret_key(self): + a = crypto.decodeint( + unhexlify( + b"4ce88c168e0f5f8d6524f712d5f8d7d83233b1e7a2a60b5aba5206cc0ea2bc08" + ) + ) + m = monero.get_subaddress_secret_key(secret_key=a, major=0, minor=1) + self.assertEqual( + crypto.encodeint(m), + unhexlify( + b"b6ff4d689b95e3310efbf683850c075bcde46361923054e42ef30016b287ff0c" + ), + ) + + def test_public_spend(self): + derivation = unhexlify( + b"e720a09f2e3a0bbf4e4ba7ad93653bb296885510121f806acb2a5f9168fafa01" + ) + base = unhexlify( + b"7d996b0f2db6dbb5f2a086211f2399a4a7479b2c911af307fdc3f7f61a88cb0e" + ) + pkey_ex = unhexlify( + b"0846cae7405077b6b7800f0b932c10a186448370b6db318f8c9e13f781dab546" + ) + + pkey_comp = crypto.derive_public_key( + crypto.decodepoint(derivation), 0, crypto.decodepoint(base) + ) + self.assertEqual(pkey_ex, crypto.encodepoint(pkey_comp)) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_apps.monero.serializer.py b/tests/test_apps.monero.serializer.py new file mode 100644 index 000000000..2dc0f2ea1 --- /dev/null +++ b/tests/test_apps.monero.serializer.py @@ -0,0 +1,158 @@ +import utest +from common import * +from trezor import log, loop, utils + +from apps.monero.xmr.serialize.int_serialize import ( + dump_uint, + dump_uvarint, + load_uint, + load_uvarint, +) +from apps.monero.xmr.serialize.readwriter import MemoryReaderWriter +from apps.monero.xmr.serialize_messages.base import ECPoint +from apps.monero.xmr.serialize_messages.tx_prefix import ( + TxinGen, + TxinToKey, + TxInV, + TxOut, + TxoutToKey, +) + + +class XmrTstData(object): + """Simple tests data generator""" + + def __init__(self, *args, **kwargs): + super(XmrTstData, self).__init__() + self.ec_offset = 0 + + def reset(self): + self.ec_offset = 0 + + def generate_ec_key(self, use_offset=True): + """ + Returns test EC key, 32 element byte array + :param use_offset: + :return: + """ + offset = 0 + if use_offset: + offset = self.ec_offset + self.ec_offset += 1 + + return bytearray(range(offset, offset + 32)) + + def gen_transaction_prefix(self): + """ + Returns test transaction prefix + :return: + """ + vin = [ + TxinToKey( + amount=123, key_offsets=[1, 2, 3, 2 ** 76], k_image=bytearray(range(32)) + ), + TxinToKey( + amount=456, key_offsets=[9, 8, 7, 6], k_image=bytearray(range(32, 64)) + ), + TxinGen(height=99), + ] + + vout = [ + TxOut(amount=11, target=TxoutToKey(key=bytearray(range(32)))), + TxOut(amount=34, target=TxoutToKey(key=bytearray(range(64, 96)))), + ] + + msg = TransactionPrefix( + version=2, unlock_time=10, vin=vin, vout=vout, extra=list(range(31)) + ) + return msg + + +class TestMoneroSerializer(unittest.TestCase): + def __init__(self, *args, **kwargs): + super(TestMoneroSerializer, self).__init__(*args, **kwargs) + self.tdata = XmrTstData() + + def setUp(self): + self.tdata.reset() + + def test_varint(self): + """ + Var int + :return: + """ + # fmt: off + test_nums = [0, 1, 12, 44, 32, 63, 64, 127, 128, 255, 256, 1023, 1024, 8191, 8192, + 2**16, 2**16 - 1, 2**32, 2**32 - 1, 2**64, 2**64 - 1, 2**72 - 1, 2**112] + # fmt: on + + for test_num in test_nums: + writer = MemoryReaderWriter() + + dump_uvarint(writer, test_num) + test_deser = load_uvarint(MemoryReaderWriter(writer.get_buffer())) + + self.assertEqual(test_num, test_deser) + + def test_ecpoint(self): + """ + Ec point + :return: + """ + ec_data = bytearray(range(32)) + writer = MemoryReaderWriter() + + ECPoint.dump(writer, ec_data) + self.assertTrue(len(writer.get_buffer()), ECPoint.SIZE) + + test_deser = ECPoint.load(MemoryReaderWriter(writer.get_buffer())) + self.assertEqual(ec_data, test_deser) + + def test_simple_msg(self): + """ + TxinGen + :return: + """ + msg = TxinGen(height=42) + + writer = MemoryReaderWriter() + TxinGen.dump(writer, msg) + test_deser = TxinGen.load(MemoryReaderWriter(writer.get_buffer())) + + self.assertEqual(msg.height, test_deser.height) + + def test_txin_to_key(self): + """ + TxinToKey + :return: + """ + msg = TxinToKey( + amount=123, key_offsets=[1, 2, 3, 2 ** 76], k_image=bytearray(range(32)) + ) + + writer = MemoryReaderWriter() + TxinToKey.dump(writer, msg) + test_deser = TxinToKey.load(MemoryReaderWriter(writer.get_buffer())) + + self.assertEqual(msg.amount, test_deser.amount) + self.assertEqual(msg, test_deser) + + def test_txin_variant(self): + """ + TxInV + :return: + """ + msg1 = TxinToKey( + amount=123, key_offsets=[1, 2, 3, 2 ** 76], k_image=bytearray(range(32)) + ) + + writer = MemoryReaderWriter() + TxInV.dump(writer, msg1) + test_deser = TxInV.load(MemoryReaderWriter(writer.get_buffer())) + + self.assertEqual(test_deser.__class__, TxinToKey) + self.assertEqual(msg1, test_deser) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/unittest.py b/tests/unittest.py index 508104188..0c52a08da 100644 --- a/tests/unittest.py +++ b/tests/unittest.py @@ -126,6 +126,15 @@ class TestCase: return raise + def assertListEqual(self, x, y, msg=''): + if len(x) != len(y): + if not msg: + msg = "List lengths not equal" + ensure(False, msg) + + for i in range(len(x)): + self.assertEqual(x[i], y[i], msg) + def skip(msg): def _decor(fun): diff --git a/tools/build_mocks b/tools/build_mocks index 1fb8fb711..749a7df27 100755 --- a/tools/build_mocks +++ b/tools/build_mocks @@ -13,6 +13,16 @@ def split_to_parts(line, mod_desc=None): global current_indent global current_class global current_method + global current_package + + if line.startswith("package: "): + current_package = line[9:].strip() + return + + if line.startswith("mock:global"): + current_indent = 0 + current_class = None + return if line.startswith("class "): current_class = line[6:].split("(")[0].strip(":") @@ -38,15 +48,16 @@ def split_to_parts(line, mod_desc=None): def store_to_file(dest, parts): for package, line in parts: - dirpath = os.path.abspath(dest) - filename = package + package = package.replace(".", "/") + dirpath = os.path.join(os.path.abspath(dest), os.path.dirname(package)) + filename = os.path.basename(package) + ".py" if not os.path.exists(dirpath): os.makedirs(dirpath) open(os.path.join(dirpath, "__init__.py"), "w").close() open(os.path.join(dirpath, ".mock-generated"), "w").close() - filepath = os.path.join(dirpath, filename + ".py") + filepath = os.path.join(dirpath, filename) if not os.path.exists(filepath): with open(filepath, "a") as f: @@ -83,7 +94,9 @@ def build_module(mod_file, dest): def build_directory(dir, dest): print("Building mocks for", dir, "to", dest) - for pkg in sorted([x for x in os.listdir(dir) if os.path.isdir(os.path.join(dir, x))]): + for pkg in sorted( + [x for x in os.listdir(dir) if os.path.isdir(os.path.join(dir, x))] + ): for mod in sorted(os.listdir(os.path.join(dir, pkg))): build_module(os.path.join(dir, pkg, mod), dest) diff --git a/travis-install-libsodium.sh b/travis-install-libsodium.sh new file mode 100755 index 000000000..3e73215fd --- /dev/null +++ b/travis-install-libsodium.sh @@ -0,0 +1,21 @@ +#!/bin/sh +# libsodium-dev replacement +# +# The purpose of this file is to install libsodium in +# the Travis CI environment. Outside this environment, +# you would probably not want to install it like this. + +set -e +export LIBSODIUM_VER="1.0.16" + +# check if libsodium is already installed +if [ ! -d "$HOME/libsodium/lib" ]; then + wget "https://github.com/jedisct1/libsodium/releases/download/${LIBSODIUM_VER}/libsodium-${LIBSODIUM_VER}.tar.gz" + tar xvfz "libsodium-${LIBSODIUM_VER}.tar.gz" + cd "libsodium-${LIBSODIUM_VER}" + ./configure --prefix=$HOME/libsodium + make + make install +else + echo 'Using cached directory.' +fi diff --git a/vendor/trezor-common b/vendor/trezor-common index ea0262266..588f8e03f 160000 --- a/vendor/trezor-common +++ b/vendor/trezor-common @@ -1 +1 @@ -Subproject commit ea0262266d9c42315617e9abbcf0b539c373ebc6 +Subproject commit 588f8e03f5ac111adf719f0a437de67481a26aed