From 915590780f385d7f268307286b3e0814479bd718 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Sa=C5=82aban?= Date: Thu, 11 Oct 2018 17:19:26 +0200 Subject: [PATCH] Add methods for exporting/importing outputs and key images --- monero/backends/jsonrpc.py | 19 ++++++++++++++- monero/exceptions.py | 3 +++ monero/wallet.py | 34 +++++++++++++++++++++++++++ tests/test_jsonrpcwallet.py | 47 +++++++++++++++++++++++++++++++++++++ 4 files changed, 102 insertions(+), 1 deletion(-) diff --git a/monero/backends/jsonrpc.py b/monero/backends/jsonrpc.py index 908e713..d7c5efd 100644 --- a/monero/backends/jsonrpc.py +++ b/monero/backends/jsonrpc.py @@ -1,4 +1,3 @@ -import binascii from datetime import datetime import operator import json @@ -263,6 +262,23 @@ class JSONRPCWallet(object): 'blob': data.get('blob', None), }) + def export_outputs(self): + return self.raw_request('export_outputs')['outputs_data_hex'] + + def import_outputs(self, outputs_hex): + return self.raw_request( + 'import_outputs', + {'outputs_data_hex': outputs_hex})['num_imported'] + + def export_key_images(self): + return self.raw_request('export_key_images')['signed_key_images'] + + def import_key_images(self, key_images): + _data = self.raw_request( + 'import_key_images', + {'signed_key_images': key_images}) + return (_data['height'], from_atomic(_data['spent']), from_atomic(_data['unspent'])) + def transfer(self, destinations, priority, ringsize, payment_id=None, unlock_time=0, account=0, relay=True): @@ -341,6 +357,7 @@ _err2exc = { -2: exceptions.WrongAddress, -5: exceptions.WrongPaymentId, -8: exceptions.TransactionNotFound, + -9: exceptions.SignatureCheckFailed, -16: exceptions.TransactionNotPossible, -17: exceptions.NotEnoughMoney, -20: exceptions.AmountIsZero, diff --git a/monero/exceptions.py b/monero/exceptions.py index 07f1949..ef2bca3 100644 --- a/monero/exceptions.py +++ b/monero/exceptions.py @@ -35,3 +35,6 @@ class TransactionBroadcastError(BackendException): class TransactionNotFound(AccountException): pass + +class SignatureCheckFailed(MoneroException): + pass diff --git a/monero/wallet.py b/monero/wallet.py index 919db1c..e75c3f6 100644 --- a/monero/wallet.py +++ b/monero/wallet.py @@ -104,6 +104,40 @@ class Wallet(object): except TypeError: return 0 + def export_outputs(self): + """ + Exports outputs in hexadecimal format. + + :rtype: str + """ + return self._backend.export_outputs() + + def import_outputs(self, outputs_hex): + """ + Imports outputs in hexadecimal format. Returns number of imported outputs. + + :rtype: int + + """ + return self._backend.import_outputs(outputs_hex) + + def export_key_images(self): + """ + Exports signed key images as a list of dicts. + + :rtype: [dict, dict, ...] + """ + return self._backend.export_key_images() + + def import_key_images(self, key_images_hex): + """ + Imports key images from a list of dicts. Returns tuple of (height, spent, unspent). + + :rtype: (int, Decimal, Decimal) + + """ + return self._backend.import_key_images(key_images_hex) + # Following methods operate on default account (index=0) def balances(self): """ diff --git a/tests/test_jsonrpcwallet.py b/tests/test_jsonrpcwallet.py index d842842..73313e2 100644 --- a/tests/test_jsonrpcwallet.py +++ b/tests/test_jsonrpcwallet.py @@ -1004,3 +1004,50 @@ class SubaddrWalletTestCase(unittest.TestCase): 3)], ringsize=11) self.assertEqual(len(w), 2) + + @patch('monero.backends.jsonrpc.requests.post') + def test_export_import_outputs(self, mock_post): + mock_post.return_value.status_code = 200 + mock_post.return_value.json.return_value = self.accounts_result + self.wallet = Wallet(JSONRPCWallet()) + mock_post.return_value.status_code = 200 + mock_post.return_value.json.return_value = {'id': 0, + 'jsonrpc': '2.0', + 'result': {'outputs_data_hex': '4d6f6e65726f206f7574707574206578706f72740393332736be3f7d08c6a00a99f3f14f933f6e9b266ee5a84738a4331a446eb9c16415cb7932dae2730bd713c58bc286fcfc257fad6349ae2346242d5f4cf0dda5c14b3acd335bd157b4f8d101ee95a169e6cf9ae8d1ef0f2fb98b23c0841889cf6017f405d3a097a8d7b3089026dcf969cba8df8140e798a9a8c6204e66ddfe22c945702c1b84fb6ecf3484070ed53622b38cd5dcae3441168219d1637a6a7bf8114c03b8ace240ca90a7dce92ee5d22fd302a623fc98d59503debdeb16f096f8f0b4fd5c5585143f7adb9d8a918ad2ff01d47cbd8c3d27b1278b28446ad3782d68a7d42dfbf7959710239eafad6476a36ed02bcd563b780572980a9ab7340d850bd489a415f71da64b7c51c9cbfcfb4556c97ad9d7617b27e0bc128995dea5b6c8b67cc160c2b001bf9750b719cbd46e1d4e9e2f7c654948ee775820501e53bea9383d16a7e8b0de60ca15eec3cc132f869f704887fa78415fd4da9d77c7592d76ddff5bc0f13c1f2dfcc4a9086e1e38d90b13b8aecb91778f3d187513ffd9c543c0551f43020c74340c6b795c84e9cc8c85b0f6e6d649201c0ba4c738afb8b984c25244e69a66b799f145cb3dd1378df1f59767a884eced4779ca9b6d3919d5b635427f2cc2db7dd064a84d4e1e31f707ce593cbeec9066dafea07e3086522a0d099e15bdc7cde66f02e352e2e5b5d493bbc8ff4a43d4ac1956cb70697793a8856428bc77d4894912871a177b275e5f1dac55becb150981205b0723ab2137549b8ae39a665a24485fae048288012b7937a032c3b617eed0b10504ea43b00fc70cb4b935cebe09adb163e9806aa22edefe7921c90dbf6db8a0541e03b73420a0615fefb11a93c608c0985a865436bc1f865a97fb6ef77d8fd4d8db202c53374f06b65ce251fd200c7323aa017b407d06c3ede254d82e39a149b74dd70a89b892704aa19b5eec9bf5610e014f3175dc3f13e79a64d1bcdf2c492494e813e5df157f40a5d796d1414e0e6c65b11e8fb9e63b26aae3f4c4367b0a6333b819e7ed4a2b2687b292c1611d7efe638033da44a4d9ecf12417beaee8c05b978c0eda0808be4cdb0ee76ed5d54dbf4c61f99e6cef60c5baecb615015036d6113890b8827d3b41f21273c3a860531cd2ea1d1ecff3656740c69702d0d6ea34160cc9cd5d3c58bd2ca53320fef77c384aaa8214a30d315c8d94174d45759fd13fdb8d0c94823964dc669ad23eb85426a6f316931c1e42c839b5b0713fffd34890165cee2656f63b7fee0949849368fc0ca2dff74340cca7dd724c06d2e7ec40195535ec43c2b8977cb0bd0955d56b540fb7eb7103d5f432edd63f9d426b464ec3f531633a86487c27660662fa5164b8402ca1535933cf2e55868a8cc3a4fd19a8a42d8878008ae6ddf1e17236e7f816da9aceb5baa381c3727604516df20557281d9ebc1bd9f27effbf1375680b910f8fcd2cf9799eb4845e0aa17653525fdcdef87278d9b069265850e39e6c6598ec370a8ac372638aa781c6d71cf4d9b65115034d01b24cdfd4d02c97aac431ccd192c25de4e4feefc119a6ea55d477566c79761113a04cdd0a5a1e161279795efc5e0f8fe3c2cebceb5dd6f6a0383a18fedd5b83d404239ce6ecab6e6265a9d4678f8ec10445d2b0e790fd414b071d3fe1076a18c6a90f4e7ba1bdbfcd363d7e3aef31a502d5f262ad54777d001d14b3a809de213b379929e4308a0b0f45e1aa0c6b3b483a4867f2827c316e85223cb191fcd546600d96f32e3ff9377cff408d475f59b385e0d78bbbf640e5f180fc64622d9536a8ab1f89309044813cc9e0840e57b1e2c5e392cca2cbd3bac1b2ac4c12ce11f2e7e48e644ebb0936002d2e2c554fb52fb7dc02b4371f87c5175af7ea0b101e2486b6331f5887604b4245c8b71c56edce095483d6fc4b3d90b642f53973e1ed0af6de3d0143a10a442668e4ac1db0c321ced61d17ac559a61000e643d62762fa41249f5d2712938e224555dfca61352e5a288db6eaa2fa2f3cbad48b45780c6fd84a772cd16e3922825125969f9ac5fa1e75a1faaea9350895ec0c5286a3d7cdfe99d6ee5d290e95bfeb7f35a28d4f6bb5996fd40bd18bbf1138b6a86ab74fa2228abd72c69646cd6fd0b53562c5e41104b5b5ac047cff45240169703282f09872f1152cffd4ad5bf13e3ee8123d8510154a7fdfa435c0ff92464db01b7fe9d41bdd63fb01178093d2f5f1f83cbff2674f509e45ed2e359faa2549259d849c74b1ca5b15ad9bd20a0121c4af360782cb260a021e4e6dc680429394eda423910105936f8d3c9ff29c6b5d48b42ad3f0e0318647d3f2d5d17cb7fe37cfe4b947a0f9ffdf84adb53dbd5b2a228f73ca79396703290f8111863dfa1de9a7c91dc42107d5cdee5c7234d25476f0b3500966a6ef2df343721fdfa57631ea3d5df939b7ff88be4f7738d829cbd0f4dc6ea5c6799ce0747da631fba84a5c0d29c905e377675171a040b4311c13940c78240b5157d4fb17b27e94ace0b29b7629955d35a3a7fbccc977f82f272253bcaaf458340b394b6263af39146ea8b20938c3259bc641d7b5f47fad1ffeb251aa40e97c54f112cd0b571dbfafe83a1776add24c8ad5c363eea5d420f869e05e32d0f926c5d8d1a0a533b0ed60f1cf3104765a27c11dd18e74a62cd329594e22d75d357f10afa56f1cd51220a5165c75d7bf1779d904d1f5715a3db1607c01bf266676db7055813db92522a49fff7ff96042c6476b7fb00487b484ed1675b390efab7d3b40221ab3d0f4c8872189644197c1e6b0b9a5a66e7fc32aeff38539a63db449a5233f8a4be230324d8ac99efa472ec4e7fde53f616efae90ad84ee88c3afe5df6a1eae471c2928692ebc896c28eb3934308d1ba01ef9d209dee589adc5ccce2ad658828080037a64656ecff5209f157cbd57f37bc25461771ca549d44ceaa2178bae4acb1dfe5367503575a8bee605f9e3ed629dbcdeee58ba732819ec818e776c9f66ebc2757c3c0c7a1afd37b8113c322e62d826169c560f5a7c9456b30307db0ec2e594b0ecb6580067fda2f5196ad8ff6b460ebbc8879fbf41559e03c109f2f610d0611899cdbd33ed3a718b0c875073d972aaf1f440702fef37983abc9845df067a52b3697ae6f722f4f743de910e68f13ecb3645a0a9a1e0ef9f71c02bfc7aa22743c8175ed767ec12d042bf7ec32edc2bc5d3c54e9969bf22c434db5bdbbba21fd827fe084963344411e61291bf1ed92432f9de0af8f68c73343bf8baf8205754bdcfe5a540cc870e45d634e3d84281ff70a3d619cd60da558d106b669f3c1a8786eb258a2816e19064418e4b410b47ca6d2f07f75acf507417d1872c4e3ccb797435a1f3dcf3877627cecbcd3d7bea19712b4ad9363363c177153a71b81e4eeb9048a4dd3531daf81e23a8a165eb20ee03d02994157fff693ed9900b67876654ec0ba974eac6c4b8d223c834a2e31d6a9e785c6ac066c4cb8291f859a4f77346dc9133d93f94abca5d9b16bf93d9c0879aa44bcbb4c29c0726db1e905823531abb9e2a8dad4a1d3eea6814530f8d4da4290a64cb83dc6c21bbdcb3665124a3fe54a93d58f64048ccca4fe832bae27747955d5b11b9e636e5a38c3f5f4d93b2b711de020c99ffa147d2e755504995a4790b4e7432a4bf33187a08179e4e3232040914be7a7b98221576f2bf05ee2a8d4a55a6788a4e44bb7c5047dcbb41b01aeff3d0604b70118ed7c74fff089b467f0059da8ebaf7ebe9afdd746eefedcd63103ce88f0fa8234e512b64e742d12a9046690a0cb7be124b4294b3249695702c57f0c7c07ad9d0ee41cef44cdea99478671fd7ebeef353082cbd93956c96deeee0c91b66e8899b8537294b30cc0b7e0d3958c4b5dc9954c7ba04fe4addc9bd3c4040a09418a9ef3dbd59a4a445f7cadbd8ff004afa75133c56692995626148c096ac74a3cdc1fbb746d0b55900df0deb466ad93b7986f1652b42cf5d0de98001709423b233eb3612f1774e54d0939ef2c7e8aefd6e0a72c00597baf85617041c3639f69f5ab628ece848d8cc183a250df634673c51e96c0b6edb232c29cb05a7b6d465e729f3a250a4dbd90aba8ed4d3a9ce783af1c755a224d583e3639ebd383eed58667461d13f19ef20a8ee9e968fbab7cdd580e1899639d1f92ada3c4332a3d561807b3b85b52436143aa97a49c177e9acb0dd4c1d2e5f8fe0c2f55c9810309e422a65cfdd9d16cc84fad5d6019c61c120c02461b512cf9a507a61b5205f431e6646f622891af9c918e841e5f64b37c2071f6f1c5a43953f2ec79cef7ca57413ef55555b256e9cb181f5b456fd45760f818abc2edccfbe3abebe2ed01439c530c79dfd1d62df609d4e1ed663f76dae50853ab121c50d2b5f55ec51ddcb1efee19722e946f049f6ba52a33986adf0bc9bdba057d94e3ebf542b6b10288d4c37c3d913bbaa8fa54972733bdec93b839adf2417f9a6d270ff793afb239086642862fa7c362f5c381263047a605674927e7fbd093e8a1d0ec66f60a89b48bab5f37f832d4643ac0c91caea78e1bc08d9255c1a569effe7a690bc4a174f6e8a4153999b83468c146007951f885fb08fa5ba68bef63b759d41b3c03a5493940793dbf414ca54b6114c39644cd667c3dd214db0dd261cb1367d1abfc4fe96ef233c43e8efbb080f89835c3c4d68754c3623d562d164a24fe48c5e38059ced6e69486661664351a7574113b686b79c9559c62452336cdd7543563213e5316407fd231cfa6dc95a1fe66ad40db5d1dc081a9fd2898b78def10dc835b189b6c7002da379336f1cadbfe04579ae0aee067b84eb7814a98224e1ece9035b4c4f35d9d1680f271182356ce57d3dd2db12f965a783af83dab107d8a2276ea8d43e38f1e842046c97ac09f4142f84c77a7bdfd9d36e3844f80209'}} + outs_hex = self.wallet.export_outputs() + mock_post.return_value.json.return_value = {u'id': 0, u'jsonrpc': u'2.0', u'result': {u'num_imported': 9}} + self.assertEqual(self.wallet.import_outputs(outs_hex), 9) + + @patch('monero.backends.jsonrpc.requests.post') + def test_export_import_key_images(self, mock_post): + mock_post.return_value.status_code = 200 + mock_post.return_value.json.return_value = self.accounts_result + self.wallet = Wallet(JSONRPCWallet()) + mock_post.return_value.status_code = 200 + mock_post.return_value.json.return_value = {u'id': 0, + u'jsonrpc': u'2.0', + u'result': {u'signed_key_images': [{u'key_image': u'0100000000000000000000000000000000000000000000000000000000000000', + u'signature': u'ffe0d3216dc7a9f44dd997e7412ecb4a5e03641bba27119bbc0d5b6a145955048f4a8f301826c0d5999264cbc67817a6643143c49195a56fd135936667db0e08'}, + {u'key_image': u'0100000000000000000000000000000000000000000000000000000000000000', + u'signature': u'0ed0b9752eb5d2466985174c50453f3fc9bd271b1f95bfd50d484b958976c40e8e1a7584cde52a85d40edc4a1bf9684003436492364af0ad9e1d4add1e158108'}, + {u'key_image': u'0100000000000000000000000000000000000000000000000000000000000000', + u'signature': u'b54632f4e95f70eb1af6459c94694c65ff1b8c822b40a62e6ef7cc1c6a055b0bb4b3832d48330c863993bf46bf87e1ed2971bf6a5d94a9a333a59017fb83630e'}, + {u'key_image': u'0100000000000000000000000000000000000000000000000000000000000000', + u'signature': u'57f85fd761ecb780a877034cef57449df7c3ad492d803711d1b2a47bf192d00af2f2341388120364dba0d7496c0a5d041032602812995c11268253618713040a'}, + {u'key_image': u'0100000000000000000000000000000000000000000000000000000000000000', + u'signature': u'9d22b52da3b835afbf29de7ba2dd86fb805435492691799debd7d37dd932dc0e4b07e9b4744da38e2b38fac63475522112804627227756bc37dbc57ea7e59701'}, + {u'key_image': u'0100000000000000000000000000000000000000000000000000000000000000', + u'signature': u'664c7ac1c1f4c86e56ebc5298ef4acf46c2c154263c5dbc69b5d4d08b9f31a001db8845dbe37a81e7289e33f24ab5b0f17bbfe00612e4624c7b27ca0c27e5c0c'}, + {u'key_image': u'0100000000000000000000000000000000000000000000000000000000000000', + u'signature': u'a6bf6c5248b00f946ced5d21d03311fa6335b0ddf37f8e46d89d4b0a73de7b040d7ceee31da144693a8f86b1e61908014783e139061c0a4015b0ef13a967060c'}, + {u'key_image': u'0100000000000000000000000000000000000000000000000000000000000000', + u'signature': u'2d6792c9c438657a5a2d7068f16fe80ca7995144ec1f12f1a37c091690cf260323bbe866e88d725dc791344e6af29b0f22d0792b9f60a5020c6e54bad7fe1404'}, + {u'key_image': u'0100000000000000000000000000000000000000000000000000000000000000', + u'signature': u'a5fe1c57e83442856b1e3cefc67814c908f2485269adbe808c013c7ddb489c0592278fec3c7cc32b2d16135e627fe7bf42ab896e0c7a17333f176ff8bf267f04'}]}} + kimgs = self.wallet.export_key_images() + mock_post.return_value.json.return_value = {u'id': 0, + u'jsonrpc': u'2.0', + u'result': {u'height': 125578, u'spent': 322437994530000, u'unspent': 0}} + self.assertEqual( + self.wallet.import_key_images(kimgs), + (125578, Decimal('322.437994530000'), Decimal('0')))