From 956eeab186286eaaf1f3ffeb131e7679b841be47 Mon Sep 17 00:00:00 2001 From: philemon Date: Sun, 28 Feb 2016 16:41:43 +0300 Subject: [PATCH] MMGen version 0.8.3 New features/improvements: * New native Bitcoin RPC library. * Support for cookie-based RPC authentication (new in Bitcoin Core v0.12.0). * Batch mode available when listing and importing addresses. * mmgen-tool listaddresses: 'addrs' argument allows you to specify an address or range of addresses. NOTE: if MMGen is already installed on your system, you must remove your existing installation by hand before installing this new version. On Linux, this means deleting everything under the directory '/usr/local/lib/python2.7/dist-packages/mmgen/'. Also, if you did a 'git pull' instead of a fresh clone, you must delete the 'build' directory in the repository root before installing. The 'mmgen-pywallet' utility has been removed. It's no longer needed, as the 'bitcoin-cli dumpwallet' command (available since Core v0.9.0) provides equivalent functionality. The Windows port isn't being actively maintained at the moment. Use at your own risk, and report any problems on the Bitcointalk forum. --- ...Install-MMGen-on-Debian-or-Ubuntu-Linux.md | 19 +- .../using-mmgen/Getting-Started-with-MMGen.md | 79 +- mmgen-addrgen | 2 +- mmgen-addrimport | 2 +- mmgen-keygen | 2 +- mmgen-passchg | 2 +- mmgen-pywallet | 24 - mmgen-tool | 2 +- mmgen-txcreate | 2 +- mmgen-txsend | 2 +- mmgen-txsign | 2 +- mmgen-walletchk | 2 +- mmgen-walletconv | 2 +- mmgen-walletgen | 2 +- mmgen/__init__.py | 2 +- mmgen/addr.py | 158 +- mmgen/bitcoin.py | 42 +- mmgen/{opt.py => common.py} | 11 +- mmgen/crypto.py | 122 +- mmgen/filename.py | 4 +- mmgen/globalvars.py | 78 +- mmgen/license.py | 4 +- mmgen/main.py | 16 +- mmgen/main_addrgen.py | 55 +- mmgen/main_addrimport.py | 81 +- mmgen/main_pywallet.py | 1684 ----------------- mmgen/main_tool.py | 26 +- mmgen/main_txcreate.py | 281 ++- mmgen/main_txsend.py | 43 +- mmgen/main_txsign.py | 159 +- mmgen/main_wallet.py | 81 +- mmgen/mn_electrum.py | 2 +- mmgen/mn_tirosh.py | 2 +- mmgen/obj.py | 39 +- mmgen/opts.py | 136 +- mmgen/rpc.py | 128 ++ mmgen/rpc/__init__.py | 53 - mmgen/rpc/config.py | 75 - mmgen/rpc/connection.py | 766 -------- mmgen/rpc/data.py | 168 -- mmgen/rpc/exceptions.py | 203 -- mmgen/rpc/proxy.py | 142 -- mmgen/rpc/util.py | 49 - mmgen/seed.py | 438 ++--- mmgen/share/Opts.py | 68 +- mmgen/share/__init__.py | 2 +- mmgen/term.py | 59 +- mmgen/test.py | 16 +- mmgen/tool.py | 333 ++-- mmgen/tx.py | 201 +- mmgen/util.py | 367 ++-- setup.py | 21 +- test/gentest.py | 57 +- test/test.py | 1361 ++++++------- test/tooltest.py | 255 ++- 55 files changed, 2447 insertions(+), 5485 deletions(-) delete mode 100755 mmgen-pywallet rename mmgen/{opt.py => common.py} (76%) delete mode 100755 mmgen/main_pywallet.py create mode 100755 mmgen/rpc.py delete mode 100755 mmgen/rpc/__init__.py delete mode 100755 mmgen/rpc/config.py delete mode 100755 mmgen/rpc/connection.py delete mode 100755 mmgen/rpc/data.py delete mode 100755 mmgen/rpc/exceptions.py delete mode 100755 mmgen/rpc/proxy.py delete mode 100755 mmgen/rpc/util.py diff --git a/doc/wiki/install-linux/Install-MMGen-on-Debian-or-Ubuntu-Linux.md b/doc/wiki/install-linux/Install-MMGen-on-Debian-or-Ubuntu-Linux.md index 06682815..57d41a93 100644 --- a/doc/wiki/install-linux/Install-MMGen-on-Debian-or-Ubuntu-Linux.md +++ b/doc/wiki/install-linux/Install-MMGen-on-Debian-or-Ubuntu-Linux.md @@ -12,17 +12,16 @@ Install the pexpect Python module: $ sudo pip install pexpect - Note: pexpect v4.0.1 (the latest version as of this writing) is - BROKEN and will cause errors when running the test suite! - If this is the version you just installed on your system (examine the - output of 'pip freeze' to find out), then you must downgrade. Note - that newer versions may be broken as well. Version 3.1 is known to work - and can be found [here][03]. First, uninstall the broken pexpect: +>> Note: pexpect v4.0.1 (the latest version as of this writing) is BROKEN and +>> will cause errors when running the test suite! If this is the version you +>> just installed on your system (examine the output of 'pip freeze' to find +>> out), then you must downgrade. Note that newer versions may be broken as +>> well. Version 3.1 is known to work. If this is the version on your system, +>> then you may skip the next step. Otherwise, download the [v3.1 tarball][03], +>> unpack it, cd to the archive root and run: - $ sudo pip uninstall pexpect - - Then download and unpack the v3.1 tarball, cd to the archive root and - run 'sudo python setup.py install' to install it. + $ sudo pip uninstall pexpect + $ sudo python setup.py install Install MMGen: diff --git a/doc/wiki/using-mmgen/Getting-Started-with-MMGen.md b/doc/wiki/using-mmgen/Getting-Started-with-MMGen.md index 97e0520f..aad44dae 100644 --- a/doc/wiki/using-mmgen/Getting-Started-with-MMGen.md +++ b/doc/wiki/using-mmgen/Getting-Started-with-MMGen.md @@ -229,55 +229,66 @@ confirmations, your transaction will be saved: Note that the transaction has a unique ID, and the non-change spend amount of 6.6 BTC is included in the filename. +#### Create a keylist file (online computer): + +To sign your transaction, you'll need the Bitcoin private key corresponding to +its input address, '1F93Znz....' + +If the key in question is in a bitcoind wallet ('wallet.dat'), you'll want to +extract the key (along with all the other keys in the wallet) to a keylist +file. This is done using the 'bitcoin-cli dumpwallet' command with bitcoind +running. + + $ bitcoin-cli dumpwallet my_secret.keys + +This will write the keylist file 'my_secret.keys' (or whatever filename you've +chosen) to your home directory (or maybe to your Bitcoin data directory, results +may vary). If you want it written to another location, provide an absolute +path. + +Note that the keylist file lists your private keys in *unencrypted* form, even +if your 'wallet.dat' was encrypted. Therefore, it should be backed up to a safe +location—to a USB stick, say, or to your offline computer. After you've backed +it up, securely delete all copies of it on your online computer. + +You'll use this keylist file to sign all transactions that spend from addresses +in your bitcoind wallet. + +If the key/address pair in question came from another source, you may create +your own file 'my_secret.keys' (or whatever) in a plain text editor and just +list the key in this file. In the case of multiple keys, just list them all, +one key per line. In our example, the file will have one line containing a +single private key corresponding to the address '1F93Znz....' + #### Sign a transaction (offline computer): -Now copy the raw transaction you've just created to a USB stick and transfer it -to your offline computer for signing. For this you'll need the key corresponding -to the transaction's input address, 1F93Znz.... If the key in question is in a -bitcoind 'wallet.dat', copy 'wallet.dat' to your offline machine as well and use -the included command 'mmgen-pywallet' (a modified version of the well-known -pywallet utility) to extract the required key from it. +Now transfer the the raw transaction file and just-created keylist file to your +offline computer and run: - $ mmgen-pywallet -k wallet.dat - ... - Private keys written to file 'wd_EDBC983A[102].keys' - -You've in fact extracted a list of all of the wallet's 102 keys here, but that's -not a problem, since the unused keys will be ignored (if you wish, you can -extract only the one key you need using the '--keys-for-addrs' option). Now go -ahead and sign the transaction using this key list: - - $ mmgen-txsign -k wd_EDBC983A[102].keys tx_FEDCBA[6.6].raw + $ mmgen-txsign -k my_secret.keys tx_FEDCBA[6.6].raw ... Signed transaction written to file 'tx_FEDCBA[6.6].sig' -Note that 'mmgen-pywallet's output is just a flat list of keys. So if you have -several Bitcoin wallets with balances, you can just concatenate these lists into -a single file which you can use to sign all future transactions with -'wallet.dat' inputs: +The signed transaction is written to a new file whose name differs from the raw +transaction file only by its '.sig' extension. - $ mmgen-pywallet -k wallet1.dat - $ mmgen-pywallet -k wallet2.dat - $ mmgen-pywallet -k wallet3.dat - $ cat wd_*.keys > all_keys - -Once you've migrated your funds to MMGen, such key files will no longer be +NOTE: once you've migrated your funds to MMGen, the keylist file will no longer be needed. Instead, you'll sign transactions by listing an MMGen seed source -(wallet, mnemonic or seed file) on the command line after the transaction, -and the required keys will be generated on the fly, as in this example: +(wallet, mnemonic or seed file) on the command line after the transaction, and +the required keys will be generated on the fly, as in the following example: - $ mmgen-txsign tx_ABCDE[1.2345].raw 1234567A-BCDEF123[256,3].mmdat + $ mmgen-txsign tx_ABCDE[1].raw my_mmgen_wallet.mmdat -Transactions may contain a mixture of MMGen and non-MMGen inputs, as well as -inputs with more than one MMGen Seed ID. Just list a seed source for each +NOTE: transactions may contain a mixture of MMGen and non-MMGen inputs, as well +as inputs with more than one MMGen Seed ID. Just list a seed source for each MMGen input on the command line after the transaction, as in this example: - $ mmgen-txsign -k key_list my_tx.raw a.mmdat b.mmwords c.mmseed + $ mmgen-txsign -k my_secret.keys my_tx.raw a.mmdat b.mmwords c.mmseed #### Send a transaction (online computer): Now you're ready for the final step: broadcasting the transaction to the -network. Copy just-created signed transaction file to your online computer, +network. Copy the just-created signed transaction file to your online computer, start bitcoind and issue the command: $ mmgen-txsend tx_FEDCBA[6.6].sig @@ -301,7 +312,7 @@ Your total MMGen balance will also now be visible: 89ABCDEF: 0 BTC 0 BTC 9.99995 BTC TOTAL: 0 BTC 0 BTC 9.99995 BTC -To verify that your transaction's received its first, second, third and so forth +To verify that your transaction's received its first, second, third, and so on, confirmation, increase the 'minconf' value to 1, 2, 3 and so forth. Congratulations! You've performed your first MMGen transaction and placed your diff --git a/mmgen-addrgen b/mmgen-addrgen index a1ef5d3e..d9ac7db7 100755 --- a/mmgen-addrgen +++ b/mmgen-addrgen @@ -1,7 +1,7 @@ #!/usr/bin/env python # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution -# Copyright (C)2013-2015 Philemon +# Copyright (C)2013-2016 Philemon # # 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 diff --git a/mmgen-addrimport b/mmgen-addrimport index d67bb8d3..508b47a4 100755 --- a/mmgen-addrimport +++ b/mmgen-addrimport @@ -1,7 +1,7 @@ #!/usr/bin/env python # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution -# Copyright (C)2013-2015 Philemon +# Copyright (C)2013-2016 Philemon # # 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 diff --git a/mmgen-keygen b/mmgen-keygen index 53fcd82f..4f1c1535 100755 --- a/mmgen-keygen +++ b/mmgen-keygen @@ -1,7 +1,7 @@ #!/usr/bin/env python # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution -# Copyright (C)2013-2015 Philemon +# Copyright (C)2013-2016 Philemon # # 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 diff --git a/mmgen-passchg b/mmgen-passchg index 5ca7f88f..3ad0f14b 100755 --- a/mmgen-passchg +++ b/mmgen-passchg @@ -1,7 +1,7 @@ #!/usr/bin/env python # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution -# Copyright (C)2013-2015 Philemon +# Copyright (C)2013-2016 Philemon # # 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 diff --git a/mmgen-pywallet b/mmgen-pywallet deleted file mode 100755 index 4cbdb531..00000000 --- a/mmgen-pywallet +++ /dev/null @@ -1,24 +0,0 @@ -#!/usr/bin/env python - -# mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution -# Copyright (C)2013-2015 Philemon -# -# 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 . - -""" -mmgen-pywallet: Dump contents of a bitcoind wallet to file -""" - -from mmgen.main import launch -launch("pywallet") diff --git a/mmgen-tool b/mmgen-tool index b25d30d1..d273d6dd 100755 --- a/mmgen-tool +++ b/mmgen-tool @@ -1,7 +1,7 @@ #!/usr/bin/env python # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution -# Copyright (C)2013-2015 Philemon +# Copyright (C)2013-2016 Philemon # # 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 diff --git a/mmgen-txcreate b/mmgen-txcreate index 466cfa11..2056bb07 100755 --- a/mmgen-txcreate +++ b/mmgen-txcreate @@ -1,7 +1,7 @@ #!/usr/bin/env python # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution -# Copyright (C)2013-2015 Philemon +# Copyright (C)2013-2016 Philemon # # 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 diff --git a/mmgen-txsend b/mmgen-txsend index 0f3cad4f..24a92656 100755 --- a/mmgen-txsend +++ b/mmgen-txsend @@ -1,7 +1,7 @@ #!/usr/bin/env python # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution -# Copyright (C)2013-2015 Philemon +# Copyright (C)2013-2016 Philemon # # 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 diff --git a/mmgen-txsign b/mmgen-txsign index 1071156a..c9cd4f3a 100755 --- a/mmgen-txsign +++ b/mmgen-txsign @@ -1,7 +1,7 @@ #!/usr/bin/env python # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution -# Copyright (C)2013-2015 Philemon +# Copyright (C)2013-2016 Philemon # # 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 diff --git a/mmgen-walletchk b/mmgen-walletchk index f96c8ecb..f3782ab4 100755 --- a/mmgen-walletchk +++ b/mmgen-walletchk @@ -1,7 +1,7 @@ #!/usr/bin/env python # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution -# Copyright (C)2013-2015 Philemon +# Copyright (C)2013-2016 Philemon # # 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 diff --git a/mmgen-walletconv b/mmgen-walletconv index b18b4adf..48bab7c5 100755 --- a/mmgen-walletconv +++ b/mmgen-walletconv @@ -1,7 +1,7 @@ #!/usr/bin/env python # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution -# Copyright (C)2013-2015 Philemon +# Copyright (C)2013-2016 Philemon # # 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 diff --git a/mmgen-walletgen b/mmgen-walletgen index 736c7290..dfcad274 100755 --- a/mmgen-walletgen +++ b/mmgen-walletgen @@ -1,7 +1,7 @@ #!/usr/bin/env python # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution -# Copyright (C)2013-2015 Philemon +# Copyright (C)2013-2016 Philemon # # 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 diff --git a/mmgen/__init__.py b/mmgen/__init__.py index 49fac675..bfb07aec 100755 --- a/mmgen/__init__.py +++ b/mmgen/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution -# Copyright (C)2013-2015 Philemon +# Copyright (C)2013-2016 Philemon # # 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 diff --git a/mmgen/addr.py b/mmgen/addr.py index 2f3a03ee..29e7b4d4 100755 --- a/mmgen/addr.py +++ b/mmgen/addr.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution -# Copyright (C)2013-2015 Philemon +# Copyright (C)2013-2016 Philemon # # 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 @@ -20,17 +20,13 @@ addr.py: Address generation/display routines for the MMGen suite """ -import sys -from hashlib import sha256, sha512 -from hashlib import new as hashlib_new +from hashlib import sha256, sha512, new as hashlib_new from binascii import hexlify, unhexlify +from mmgen.common import * from mmgen.bitcoin import numtowif -from mmgen.util import * from mmgen.tx import * from mmgen.obj import * -import mmgen.globalvars as g -import mmgen.opt as opt pnm = g.proj_name @@ -54,7 +50,7 @@ addrmsgs = { Executable '{kconv}' unavailable. Falling back on (slow) internal ECDSA library. Please install '{kconv}' from the {vgen} package on your system for much faster address generation. -""".format(kconv=g.keyconv_exec, vgen="vanitygen") +""".format(kconv=g.keyconv_exec, vgen='vanitygen') } def test_for_keyconv(silent=False): @@ -69,19 +65,19 @@ def test_for_keyconv(silent=False): return True -def generate_addrs(seed, addrnums, source="addrgen"): +def generate_addrs(seed, addrnums, source='addrgen'): from util import make_chksum_8 seed_id = make_chksum_8(seed) # Must do this before seed gets clobbered if 'a' in opt.gen_what: if opt.no_keyconv or test_for_keyconv() == False: - msg("Using (slow) internal ECDSA library for address generation") + msg('Using (slow) internal ECDSA library for address generation') from mmgen.bitcoin import privnum2addr keyconv = False else: from subprocess import check_output - keyconv = "keyconv" + keyconv = 'keyconv' addrnums = sorted(set(addrnums)) # don't trust the calling function t_addrs,num,pos,out = len(addrnums),0,0,[] @@ -102,7 +98,7 @@ def generate_addrs(seed, addrnums, source="addrgen"): pos += 1 - qmsg_r("\rGenerating %s #%s (%s of %s)" % (w[0],num,pos,t_addrs)) + qmsg_r('\rGenerating %s #%s (%s of %s)' % (w[0],num,pos,t_addrs)) e = AddrInfoEntry() e.idx = num @@ -123,7 +119,7 @@ def generate_addrs(seed, addrnums, source="addrgen"): out.append(e) m = w[0] if t_addrs == 1 else w[0]+w[1] - qmsg("\r%s: %s %s generated%s" % (seed_id,t_addrs,m," "*15)) + qmsg('\r%s: %s %s generated%s' % (seed_id,t_addrs,m,' '*15)) a = AddrInfo(has_keys='k' in opt.gen_what, source=source) a.initialize(seed_id,out) return a @@ -131,7 +127,7 @@ def generate_addrs(seed, addrnums, source="addrgen"): def _parse_addrfile_body(lines,has_keys=False,check=False): if has_keys and len(lines) % 2: - return "Key-address file has odd number of lines" + return 'Key-address file has odd number of lines' ret = [] while lines: @@ -145,7 +141,7 @@ def _parse_addrfile_body(lines,has_keys=False,check=False): return "'%s': invalid Bitcoin address" % d[1] if len(d) == 3: check_addr_label(d[2]) - else: d.append("") + else: d.append('') a.idx,a.addr,a.comment = int(d[0]),unicode(d[1]),unicode(d[2]) @@ -153,7 +149,7 @@ def _parse_addrfile_body(lines,has_keys=False,check=False): l = lines.pop(0) d = l.split(None,2) - if d[0] != "wif:": + if d[0] != 'wif:': return "Invalid key line in file: '%s'" % l if not is_wif(d[1]): return "'%s': invalid Bitcoin key" % d[1] @@ -162,14 +158,14 @@ def _parse_addrfile_body(lines,has_keys=False,check=False): ret.append(a) - if has_keys and keypress_confirm("Check key-to-address validity?"): + if has_keys and keypress_confirm('Check key-to-address validity?'): wif2addr_f = get_wif2addr_f() llen = len(ret) for n,e in enumerate(ret): - msg_r("\rVerifying keys %s/%s" % (n+1,llen)) + msg_r('\rVerifying keys %s/%s' % (n+1,llen)) if e.addr != wif2addr_f(e.wif): return "Key doesn't match address!\n %s\n %s" % (e.wif,e.addr) - msg(" - done") + msg(' - done') return ret @@ -177,7 +173,7 @@ def _parse_addrfile_body(lines,has_keys=False,check=False): def _parse_addrfile(fn,buf=[],has_keys=False,exit_on_error=True): if buf: lines = remove_comments(buf.splitlines()) # DOS-safe - else: lines = get_lines_from_file(fn,"address data",trim_comments=True) + else: lines = get_lines_from_file(fn,'address data',trim_comments=True) try: sid,obrace = lines[0].split() @@ -196,17 +192,14 @@ def _parse_addrfile(fn,buf=[],has_keys=False,exit_on_error=True): if type(ret) == list: return sid,ret else: errmsg = ret - if exit_on_error: - msg(errmsg) - sys.exit(3) - else: - return False + if exit_on_error: die(3,errmsg) + else: return False def _parse_keyaddr_file(fn): from mmgen.crypto import mmgen_decrypt_file_maybe - d = mmgen_decrypt_file_maybe(fn,"key-address file") - return _parse_addrfile("",buf=d,has_keys=True,exit_on_error=False) + d = mmgen_decrypt_file_maybe(fn,'key-address file') + return _parse_addrfile('',buf=d,has_keys=True,exit_on_error=False) class AddrInfoList(MMGenObject): @@ -225,31 +218,31 @@ class AddrInfoList(MMGenObject): return self.data[sid] def mmaddr2btcaddr(self,mmaddr): - btcaddr = "" - sid,idx = mmaddr.split(":") + btcaddr = '' + sid,idx = mmaddr.split(':') if sid in self.seed_ids(): btcaddr = self.addrinfo(sid).btcaddr(int(idx)) return btcaddr def add_wallet_data(self,c): - vmsg_r("Getting account data from wallet...") - data,accts,i = {},c.listaccounts(minconf=0,includeWatchonly=True),0 + vmsg_r('Getting account data from wallet...') + accts = c.listaccounts(0,True) + data,i = {},0 for acct in accts: ma,comment = parse_mmgen_label(acct) if ma: i += 1 addrlist = c.getaddressesbyaccount(acct) if len(addrlist) != 1: - msg(wmsg['too_many_acct_addresses'] % acct) - sys.exit(2) - seed_id,idx = ma.split(":") + die(2,wmsg['too_many_acct_addresses'] % acct) + seed_id,idx = ma.split(':') if seed_id not in data: data[seed_id] = [] a = AddrInfoEntry() a.idx,a.addr,a.comment = \ int(idx),unicode(addrlist[0]),unicode(comment) data[seed_id].append(a) - vmsg("{n} {pnm} addresses found, {m} accounts total".format( + vmsg('{n} {pnm} addresses found, {m} accounts total'.format( n=i,pnm=pnm,m=len(accts))) for sid in data: self.add(AddrInfo(sid=sid,adata=data[sid])) @@ -259,8 +252,7 @@ class AddrInfoList(MMGenObject): self.data[addrinfo.seed_id] = addrinfo return True else: - msg("Error: object %s is not of type AddrInfo" % repr(addrinfo)) - sys.exit(1) + die(1,'Error: object %s is not of type AddrInfo' % repr(addrinfo)) def make_reverse_dict(self,btcaddrs): d = {} @@ -274,53 +266,57 @@ class AddrInfoEntry(MMGenObject): class AddrInfo(MMGenObject): - def __init__(self,addrfile="",has_keys=False,sid="",adata=[], source=""): + def __init__(self,addrfile='',has_keys=False,sid='',adata=[],source='',caller=''): self.has_keys = has_keys + self.caller = caller do_chksum = True if addrfile: - f = _parse_keyaddr_file if has_keys else _parse_addrfile + f = (_parse_addrfile,_parse_keyaddr_file)[bool(has_keys)] sid,adata = f(addrfile) - self.source = "addrfile" + self.source = 'addrfile' elif sid and adata: # data from wallet - self.source = "wallet" + self.source = 'wallet' elif sid or adata: - die(3,"Must specify address file, or seed_id + adata") + die(3,'Must specify address file, or seed_id + adata') else: - self.source = source if source else "unknown" + self.source = source if source else 'unknown' return self.initialize(sid,adata) def initialize(self,seed_id,addrdata): if seed_id in self.__dict__: - msg("Seed ID already set for object %s" % self) + msg('Seed ID already set for object %s' % self) return False self.seed_id = seed_id self.addrdata = addrdata self.num_addrs = len(addrdata) - if self.source in ("wallet","txsign"): + if self.source in ('wallet','txsign'): self.checksum = None self.idxs_fmt = None - elif self.source == "addrgen" and opt.gen_what == "k": + elif self.source == 'addrgen' and opt.gen_what == 'k': self.checksum = None self.fmt_addr_idxs() else: # self.source in addrfile, addrgen self.make_addrdata_chksum() - self.fmt_addr_idxs() - w = "key-address" if self.has_keys else "address" - qmsg("Checksum for %s data %s[%s]: %s" % - (w,self.seed_id,self.idxs_fmt,self.checksum)) - if self.source == "addrgen": - qmsg( -"Record this checksum: it will be used to verify the address file in the future") - elif self.source == "addrfile": - qmsg("Check this value against your records") + if self.caller == 'tool': + Msg(self.checksum) + else: + self.fmt_addr_idxs() + w = ('address','key-address')[bool(self.has_keys)] + qmsg('Checksum for %s data %s[%s]: %s' % + (w,self.seed_id,self.idxs_fmt,self.checksum)) + if self.source == 'addrgen': + qmsg( + 'Record this checksum: it will be used to verify the address file in the future') + elif self.source == 'addrfile': + qmsg('Check this value against your records') def idxs(self): return [e.idx for e in self.addrdata] def addrs(self): - return ["%s:%s"%(self.seed_id,e.idx) for e in self.addrdata] + return ['%s:%s'%(self.seed_id,e.idx) for e in self.addrdata] def addrpairs(self): return [(e.idx,e.addr) for e in self.addrdata] @@ -355,15 +351,15 @@ class AddrInfo(MMGenObject): d,b = {},btcaddrs for e in self.addrdata: try: - d[b[b.index(e.addr)]] = ("%s:%s"%(self.seed_id,e.idx),e.comment) + d[b[b.index(e.addr)]] = ('%s:%s'%(self.seed_id,e.idx),e.comment) except: pass return d def make_addrdata_chksum(self): - lines=[" ".join([str(e.idx),e.addr]+([e.wif] if self.has_keys else [])) + lines=[' '.join([str(e.idx),e.addr]+([e.wif] if self.has_keys else [])) for e in self.addrdata] - self.checksum = make_chksum_N(" ".join(lines), nchars=24, sep=True) + self.checksum = make_chksum_N(' '.join(lines), nchars=24, sep=True) def fmt_data(self,enable_comments=False): @@ -377,53 +373,51 @@ class AddrInfo(MMGenObject): for i,s in enumerate(status): if s != 0 and s != self.num_addrs: - msg("%s missing %s in addr data"% (self.num_addrs-s,attrs[i])) - sys.exit(3) + die(3,'%s missing %s in addr data'% (self.num_addrs-s,attrs[i])) if status[0] == status[1] == 0: - msg("Addr data contains neither addresses nor keys") - sys.exit(3) + die(3,'Addr data contains neither addresses nor keys') # Header out = [] from mmgen.addr import addrmsgs - k = ('addrfile_header','keyfile_header')[int(status[0]==0)] - out.append(addrmsgs[k]+"\n") + k = ('addrfile_header','keyfile_header')[status[0]==0] + out.append(addrmsgs[k]+'\n') if self.checksum: - w = ("Key-address","Address")[int(status[1]==0)] - out.append("# {} data checksum for {}[{}]: {}".format( + w = ('Key-address','Address')[status[1]==0] + out.append('# {} data checksum for {}[{}]: {}'.format( w, self.seed_id, self.idxs_fmt, self.checksum)) - out.append("# Record this value to a secure location.\n") - out.append("%s {" % self.seed_id) + out.append('# Record this value to a secure location.\n') + out.append('%s {' % self.seed_id) # Body - fs = " {:<%s} {:<34}{}" % len(str(self.addrdata[-1].idx)) + fs = ' {:<%s} {:<34}{}' % len(str(self.addrdata[-1].idx)) for e in self.addrdata: - c = "" + c = '' if enable_comments: - try: c = " "+e.comment + try: c = ' '+e.comment except: pass if status[0]: # First line with idx out.append(fs.format(e.idx, e.addr,c)) else: - out.append(fs.format(e.idx, "wif: "+e.wif,c)) + out.append(fs.format(e.idx, 'wif: '+e.wif,c)) if status[1]: # Subsequent lines if status[2]: - out.append(fs.format("", "hex: "+e.sec,c)) + out.append(fs.format('', 'hex: '+e.sec,c)) if status[0]: - out.append(fs.format("", "wif: "+e.wif,c)) + out.append(fs.format('', 'wif: '+e.wif,c)) - out.append("}") + out.append('}') - return "\n".join([l.rstrip() for l in out]) + "\n" + return '\n'.join([l.rstrip() for l in out]) + '\n' def fmt_addr_idxs(self): try: int(self.addrdata[0].idx) except: - self.idxs_fmt = "(no idxs)" + self.idxs_fmt = '(no idxs)' return addr_idxs = [e.idx for e in self.addrdata] @@ -432,10 +426,10 @@ class AddrInfo(MMGenObject): for i in addr_idxs[1:]: if i == prev + 1: - if i == addr_idxs[-1]: ret += "-", i + if i == addr_idxs[-1]: ret += '-', i else: - if prev != ret[-1]: ret += "-", prev - ret += ",", i + if prev != ret[-1]: ret += '-', prev + ret += ',', i prev = i - self.idxs_fmt = "".join([str(i) for i in ret]) + self.idxs_fmt = ''.join([str(i) for i in ret]) diff --git a/mmgen/bitcoin.py b/mmgen/bitcoin.py index 2dd3b659..7e71358c 100755 --- a/mmgen/bitcoin.py +++ b/mmgen/bitcoin.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # # MMGen = Multi-Mode GENerator, command-line Bitcoin cold storage solution -# Copyright (C)2013-2015 Philemon +# Copyright (C)2013-2016 Philemon # # 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 @@ -37,7 +37,7 @@ _Gy = 0x483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8L curve_secp256k1 = ecdsa.ellipticcurve.CurveFp( _p, _a, _b ) generator_secp256k1 = ecdsa.ellipticcurve.Point( curve_secp256k1, _Gx, _Gy, _r ) oid_secp256k1 = (1,3,132,0,10) -secp256k1 = ecdsa.curves.Curve("secp256k1", curve_secp256k1, generator_secp256k1, oid_secp256k1) +secp256k1 = ecdsa.curves.Curve('secp256k1', curve_secp256k1, generator_secp256k1, oid_secp256k1) b58a='123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz' @@ -49,8 +49,8 @@ b58a='123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz' # # Test: 5JbQQTs3cnoYN9vDYaGY6nhQ1DggVsY4FJNBUfEfpSQqrEp3srk # -# The "zero address": -# 1111111111111111111114oLvT2 (use step2 = ("0" * 40) to generate) +# The 'zero address': +# 1111111111111111111114oLvT2 (use step2 = ('0' * 40) to generate) # def pubhex2hexaddr(pubhex): @@ -63,8 +63,8 @@ def hexaddr2addr(hexaddr, vers_num='00'): step1 = sha256(unhexlify(hexaddr2)).digest() step2 = sha256(step1).hexdigest() pubkey = hexaddr2 + step2[:8] - lzeroes = (len(hexaddr2) - len(hexaddr2.lstrip("0"))) / 2 - return ("1" * lzeroes) + _numtob58(int(pubkey,16)) + lzeroes = (len(hexaddr2) - len(hexaddr2.lstrip('0'))) / 2 + return ('1' * lzeroes) + _numtob58(int(pubkey,16)) def verify_addr(addr,verbose=False,return_hex=False): @@ -72,7 +72,7 @@ def verify_addr(addr,verbose=False,return_hex=False): if addr[0] != ldigit: continue num = _b58tonum(addr) if num == False: break - addr_hex = "{:050x}".format(num) + addr_hex = '{:050x}'.format(num) if addr_hex[:2] != vers_num: continue step1 = sha256(unhexlify(addr_hex[:42])).digest() step2 = sha256(step1).hexdigest() @@ -101,7 +101,7 @@ def _b58tonum(b58num): return sum([b58a.index(n) * (58**i) for i,n in enumerate(list(b58num[::-1]))]) def numtowif(numpriv): - step1 = '80' + "{:064x}".format(numpriv) + step1 = '80' + '{:064x}'.format(numpriv) step2 = sha256(unhexlify(step1)).digest() step3 = sha256(step2).hexdigest() key = step1 + step3[:8] @@ -113,17 +113,17 @@ def numtowif(numpriv): # (well, not exactly: they yield numeric but not bytewise equivalence) def b58encode(s): - if s == "": return "" + if s == '': return '' num = int(hexlify(s),16) return _numtob58(num) def b58decode(b58num): - if b58num == "": return "" + if b58num == '': return '' # Zap all spaces: num = _b58tonum(b58num.translate(None,' \t\n\r')) if num == False: return False - out = "{:x}".format(num) - return unhexlify("0"*(len(out)%2) + out) + out = '{:x}'.format(num) + return unhexlify('0'*(len(out)%2) + out) # These yield bytewise equivalence in our special cases: @@ -134,36 +134,36 @@ def _b58_pad(s,a,b,pad,f,w): try: outlen = b[a.index(len(s))] except: - Msg("_b58_pad() accepts only %s %s bytes long "\ - "(input was %s bytes)" % (w,",".join([str(i) for i in a]),len(s))) + Msg('_b58_pad() accepts only %s %s bytes long '\ + '(input was %s bytes)' % (w,','.join([str(i) for i in a]),len(s))) return False out = f(s) if out == False: return False - return "%s%s" % (pad * (outlen - len(out)), out) + return '%s%s' % (pad * (outlen - len(out)), out) def b58encode_pad(s): return _b58_pad(s, - a=bin_lens,b=b58_lens,pad="1",f=b58encode,w="binary strings") + a=bin_lens,b=b58_lens,pad='1',f=b58encode,w='binary strings') def b58decode_pad(s): return _b58_pad(s, - a=b58_lens,b=bin_lens,pad='\0',f=b58decode,w="base 58 numbers") + a=b58_lens,b=bin_lens,pad='\0',f=b58decode,w='base 58 numbers') # Compressed address support: def wiftohex(wifpriv,compressed=False): - idx = 68 if compressed else 66 + idx = (66,68)[bool(compressed)] num = _b58tonum(wifpriv) if num == False: return False - key = "{:x}".format(num) + key = '{:x}'.format(num) if compressed and key[66:68] != '01': return False round1 = sha256(unhexlify(key[:idx])).digest() round2 = sha256(round1).hexdigest() return key[2:66] if (key[:2] == '80' and key[idx:] == round2[:8]) else False def hextowif(hexpriv,compressed=False): - step1 = '80' + hexpriv + ('01' if compressed else '') + step1 = '80' + hexpriv + ('','01')[bool(compressed)] step2 = sha256(unhexlify(step1)).digest() step3 = sha256(step2).hexdigest() key = step1 + step3[:8] @@ -173,7 +173,7 @@ def privnum2pubhex(numpriv,compressed=False): pko = ecdsa.SigningKey.from_secret_exponent(numpriv,secp256k1) pubkey = hexlify(pko.get_verifying_key().to_string()) if compressed: - p = '02' if pubkey[-1] in "02468ace" else '03' + p = ('03','02')[pubkey[-1] in '02468ace'] return p+pubkey[:64] else: return '04'+pubkey diff --git a/mmgen/opt.py b/mmgen/common.py similarity index 76% rename from mmgen/opt.py rename to mmgen/common.py index 0fca1719..0627ccbf 100755 --- a/mmgen/opt.py +++ b/mmgen/common.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution -# Copyright (C)2013-2015 Philemon +# Copyright (C)2013-2016 Philemon # # 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 @@ -17,6 +17,11 @@ # along with this program. If not, see . """ -opt.py: a namespace for the global opt variables +common.py: Common imports for all MMGen scripts """ -import opts + +import sys +import mmgen.globalvars as g +import mmgen.opts as opts +from mmgen.opts import opt +from mmgen.util import * diff --git a/mmgen/crypto.py b/mmgen/crypto.py index b6bae59d..13c1aa3b 100755 --- a/mmgen/crypto.py +++ b/mmgen/crypto.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution -# Copyright (C)2013-2015 Philemon +# Copyright (C)2013-2016 Philemon # # 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 @@ -20,13 +20,10 @@ crypto.py: Cryptographic and related routines for the MMGen suite """ -import sys from binascii import hexlify from hashlib import sha256 -import mmgen.globalvars as g -import mmgen.opt as opt -from mmgen.util import * +from mmgen.common import * from mmgen.term import get_char crmsg = { @@ -58,73 +55,72 @@ keystrokes will also be used as a source of randomness. } def encrypt_seed(seed, key): - return encrypt_data(seed, key, iv=1, desc="seed") + return encrypt_data(seed, key, iv=1, desc='seed') def decrypt_seed(enc_seed, key, seed_id, key_id): - vmsg_r("Checking key...") + vmsg_r('Checking key...') chk1 = make_chksum_8(key) if key_id: - if not compare_chksums(key_id,"key ID",chk1,"computed"): - msg("Incorrect passphrase or hash preset") + if not compare_chksums(key_id,'key ID',chk1,'computed'): + msg('Incorrect passphrase or hash preset') return False - dec_seed = decrypt_data(enc_seed, key, iv=1, desc="seed") + dec_seed = decrypt_data(enc_seed, key, iv=1, desc='seed') chk2 = make_chksum_8(dec_seed) if seed_id: - if compare_chksums(seed_id,"Seed ID",chk2,"decrypted seed"): - qmsg("Passphrase is OK") + if compare_chksums(seed_id,'Seed ID',chk2,'decrypted seed'): + qmsg('Passphrase is OK') else: if not opt.debug: - msg_r("Checking key ID...") - if compare_chksums(key_id,"key ID",chk1,"computed"): - msg("Key ID is correct but decryption of seed failed") + msg_r('Checking key ID...') + if compare_chksums(key_id,'key ID',chk1,'computed'): + msg('Key ID is correct but decryption of seed failed') else: - msg("Incorrect passphrase or hash preset") + msg('Incorrect passphrase or hash preset') - vmsg("") + vmsg('') return False # else: -# qmsg("Generated IDs (Seed/Key): %s/%s" % (chk2,chk1)) +# qmsg('Generated IDs (Seed/Key): %s/%s' % (chk2,chk1)) - dmsg("Decrypted seed: %s" % hexlify(dec_seed)) + dmsg('Decrypted seed: %s' % hexlify(dec_seed)) return dec_seed -def encrypt_data(data, key, iv=1, desc="data", verify=True): +def encrypt_data(data, key, iv=1, desc='data', verify=True): # 192-bit seed is 24 bytes -> not multiple of 16. Must use MODE_CTR from Crypto.Cipher import AES from Crypto.Util import Counter - vmsg("Encrypting %s" % desc) + vmsg('Encrypting %s' % desc) c = AES.new(key, AES.MODE_CTR, counter=Counter.new(g.aesctr_iv_len*8,initial_value=iv)) enc_data = c.encrypt(data) if verify: - vmsg_r("Performing a test decryption of the %s..." % desc) + vmsg_r('Performing a test decryption of the %s...' % desc) c = AES.new(key, AES.MODE_CTR, counter=Counter.new(g.aesctr_iv_len*8,initial_value=iv)) dec_data = c.decrypt(enc_data) - if dec_data == data: vmsg("done") + if dec_data == data: vmsg('done') else: - msg("ERROR.\nDecrypted %s doesn't match original %s" % (desc,desc)) - sys.exit(2) + die(2,"ERROR.\nDecrypted %s doesn't match original %s" % (desc,desc)) return enc_data -def decrypt_data(enc_data, key, iv=1, desc="data"): +def decrypt_data(enc_data, key, iv=1, desc='data'): - vmsg_r("Decrypting %s with key..." % desc) + vmsg_r('Decrypting %s with key...' % desc) from Crypto.Cipher import AES from Crypto.Util import Counter @@ -147,66 +143,66 @@ def scrypt_hash_passphrase(passwd, salt, hash_preset, buflen=32): def make_key(passwd,salt,hash_preset, - desc="encryption key",from_what="passphrase",verbose=False): + desc='encryption key',from_what='passphrase',verbose=False): - if from_what: desc += " from " + if from_what: desc += ' from ' if opt.verbose or verbose: - msg_r("Generating %s%s..." % (desc,from_what)) + msg_r('Generating %s%s...' % (desc,from_what)) key = scrypt_hash_passphrase(passwd, salt, hash_preset) - if opt.verbose or verbose: msg("done") - dmsg("Key: %s" % hexlify(key)) + if opt.verbose or verbose: msg('done') + dmsg('Key: %s' % hexlify(key)) return key def _get_random_data_from_user(uchars): - if opt.quiet: msg("Enter %s random symbols" % uchars) + if opt.quiet: msg('Enter %s random symbols' % uchars) else: msg(crmsg['usr_rand_notice'] % uchars) - prompt = "You may begin typing. %s symbols left: " + prompt = 'You may begin typing. %s symbols left: ' msg_r(prompt % uchars) import time # time.clock() always returns zero, so we'll use time.time() saved_time = time.time() - key_data,time_data,pp = "",[],True + key_data,time_data,pp = '',[],True for i in range(uchars): - key_data += get_char(immed_chars="ALL",prehold_protect=pp) + key_data += get_char(immed_chars='ALL',prehold_protect=pp) pp = False - msg_r("\r" + prompt % (uchars - i - 1)) + msg_r('\r' + prompt % (uchars - i - 1)) now = time.time() time_data.append(now - saved_time) saved_time = now - if opt.quiet: msg_r("\r") - else: msg_r("\rThank you. That's enough.%s\n\n" % (" "*18)) + if opt.quiet: msg_r('\r') + else: msg_r("\rThank you. That's enough.%s\n\n" % (' '*18)) - fmt_time_data = ["{:.22f}".format(i) for i in time_data] + fmt_time_data = ['{:.22f}'.format(i) for i in time_data] - dmsg("\nUser input:\n%s\nKeystroke time intervals:\n%s\n" % - (key_data,"\n".join(fmt_time_data))) + dmsg('\nUser input:\n%s\nKeystroke time intervals:\n%s\n' % + (key_data,'\n'.join(fmt_time_data))) - prompt = "User random data successfully acquired. Press ENTER to continue" - prompt_and_get_char(prompt,"",enter_ok=True) + prompt = 'User random data successfully acquired. Press ENTER to continue' + prompt_and_get_char(prompt,'',enter_ok=True) - return key_data+"".join(fmt_time_data) + return key_data+''.join(fmt_time_data) def get_random(length): from Crypto import Random os_rand = Random.new().read(length) if g.use_urandchars and opt.usr_randchars: - from_what = "OS random data" + from_what = 'OS random data' if not g.user_entropy: g.user_entropy = \ sha256(_get_random_data_from_user(opt.usr_randchars)).digest() - from_what += " plus user-supplied entropy" + from_what += ' plus user-supplied entropy' else: - from_what += " plus saved user-supplied entropy" - key = make_key(g.user_entropy, "", '2', from_what=from_what, verbose=True) - return encrypt_data(os_rand,key,desc="random data",verify=False) + from_what += ' plus saved user-supplied entropy' + key = make_key(g.user_entropy, '', '2', from_what=from_what, verbose=True) + return encrypt_data(os_rand,key,desc='random data',verify=False) else: return os_rand @@ -214,13 +210,13 @@ def get_random(length): # Vars for mmgen_*crypt functions only salt_len,sha256_len,nonce_len = 32,32,32 -def mmgen_encrypt(data,desc="data",hash_preset=''): +def mmgen_encrypt(data,desc='data',hash_preset=''): salt,iv,nonce = get_random(salt_len),\ get_random(g.aesctr_iv_len), \ get_random(nonce_len) hp = hash_preset or get_hash_preset_from_user('3',desc) - m = "default" if hp == '3' else "user-requested" - vmsg("Encrypting %s" % desc) + m = ('user-requested','default')[hp=='3'] + vmsg('Encrypting %s' % desc) qmsg("Using %s hash preset of '%s'" % (m,hp)) passwd = get_new_passphrase(desc, {}) key = make_key(passwd, salt, hp) @@ -229,34 +225,34 @@ def mmgen_encrypt(data,desc="data",hash_preset=''): return salt+iv+enc_d -def mmgen_decrypt(data,desc="data",hash_preset=""): +def mmgen_decrypt(data,desc='data',hash_preset=''): dstart = salt_len + g.aesctr_iv_len salt,iv,enc_d = data[:salt_len],data[salt_len:dstart],data[dstart:] - vmsg("Preparing to decrypt %s" % desc) + vmsg('Preparing to decrypt %s' % desc) hp = hash_preset or get_hash_preset_from_user('3',desc) - m = "default" if hp == '3' else "user-requested" + m = ('user-requested','default')[hp=='3'] qmsg("Using %s hash preset of '%s'" % (m,hp)) passwd = get_mmgen_passphrase(desc) key = make_key(passwd, salt, hp) dec_d = decrypt_data(enc_d, key, int(hexlify(iv),16), desc) if dec_d[:sha256_len] == sha256(dec_d[sha256_len:]).digest(): - vmsg("OK") + vmsg('OK') return dec_d[sha256_len+nonce_len:] else: - msg("Incorrect passphrase or hash preset") + msg('Incorrect passphrase or hash preset') return False def mmgen_decrypt_file_maybe(fn,desc): - d = get_data_from_file(fn,"{} data".format(desc),binary=True) + d = get_data_from_file(fn,'{} data'.format(desc),binary=True) have_enc_ext = get_extension(fn) == g.mmenc_ext if have_enc_ext or not is_ascii(d): - m = ("Attempting to decrypt","Decrypting")[int(have_enc_ext)] - msg("%s %s %s" % (m,desc,fn)) + m = ('Attempting to decrypt','Decrypting')[have_enc_ext] + msg('%s %s %s' % (m,desc,fn)) d = mmgen_decrypt_retry(d,desc) return d -def mmgen_decrypt_retry(d,desc="data"): +def mmgen_decrypt_retry(d,desc='data'): while True: d_dec = mmgen_decrypt(d,desc) if d_dec: return d_dec - msg("Trying again...") + msg('Trying again...') diff --git a/mmgen/filename.py b/mmgen/filename.py index dfdbf050..bd2903ce 100755 --- a/mmgen/filename.py +++ b/mmgen/filename.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution -# Copyright (C)2013-2015 Philemon +# Copyright (C)2013-2016 Philemon # # 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 @@ -41,7 +41,7 @@ class Filename(MMGenObject): die(2,"Unrecognized extension '.%s' for file '%s'" % (self.ext,fn)) # TODO: Check for Windows - mode = (os.O_RDONLY,os.O_RDWR)[int(write)] + mode = (os.O_RDONLY,os.O_RDWR)[bool(write)] import stat if stat.S_ISBLK(os.stat(fn).st_mode): try: diff --git a/mmgen/globalvars.py b/mmgen/globalvars.py index d755e785..26e87c86 100755 --- a/mmgen/globalvars.py +++ b/mmgen/globalvars.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution -# Copyright (C)2013-2015 Philemon +# Copyright (C)2013-2016 Philemon # # 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 @@ -17,82 +17,82 @@ # along with this program. If not, see . """ -config.py: Constants and configuration options for the MMGen suite +globalvars.py: Constants and configuration options for the MMGen suite """ import sys, os # Variables - these might be altered at runtime: -user_entropy = "" +user_entropy = '' hash_preset = '3' usr_randchars = 30 use_urandchars = False # returns None if env var unset -debug = os.getenv("MMGEN_DEBUG") -no_license = os.getenv("MMGEN_NOLICENSE") -bogus_wallet_data = os.getenv("MMGEN_BOGUS_WALLET_DATA") -disable_hold_protect = os.getenv("MMGEN_DISABLE_HOLD_PROTECT") +debug = os.getenv('MMGEN_DEBUG') +no_license = os.getenv('MMGEN_NOLICENSE') +bogus_wallet_data = os.getenv('MMGEN_BOGUS_WALLET_DATA') +disable_hold_protect = os.getenv('MMGEN_DISABLE_HOLD_PROTECT') from decimal import Decimal -tx_fee = Decimal("0.00005") -max_tx_fee = Decimal("0.01") +tx_fee = Decimal('0.00005') +max_tx_fee = Decimal('0.01') seed_len = 256 -http_timeout = 30 +http_timeout = 60 # Constants - these don't change at runtime -proj_name = "MMGen" +proj_name = 'MMGen' prog_name = os.path.basename(sys.argv[0]) -author = "Philemon" -email = "" -Cdates = '2013-2015' -version = '0.8.2' +author = 'Philemon' +email = '' +Cdates = '2013-2016' +version = '0.8.3' required_opts = [ - "quiet","verbose","debug","outdir","echo_passphrase","passwd_file", - "usr_randchars","stdout","show_hash_presets","label", - "keep_passphrase","keep_hash_preset" + 'quiet','verbose','debug','outdir','echo_passphrase','passwd_file', + 'usr_randchars','stdout','show_hash_presets','label', + 'keep_passphrase','keep_hash_preset' ] incompatible_opts = ( - ("quiet","verbose"), - ("label","keep_label"), - ("tx_id", "info"), - ("tx_id", "terse_info"), + ('quiet','verbose'), + ('label','keep_label'), + ('tx_id', 'info'), + ('tx_id', 'terse_info'), ) min_screen_width = 80 -wallet_ext = "mmdat" -seed_ext = "mmseed" -mn_ext = "mmwords" -brain_ext = "mmbrain" -incog_ext = "mmincog" -incog_hex_ext = "mmincox" +wallet_ext = 'mmdat' +seed_ext = 'mmseed' +mn_ext = 'mmwords' +brain_ext = 'mmbrain' +incog_ext = 'mmincog' +incog_hex_ext = 'mmincox' seedfile_exts = ( wallet_ext, seed_ext, mn_ext, brain_ext, incog_ext, incog_hex_ext ) -rawtx_ext = "raw" -sigtx_ext = "sig" -addrfile_ext = "addrs" -addrfile_chksum_ext = "chk" -keyfile_ext = "keys" -keyaddrfile_ext = "akeys" -mmenc_ext = "mmenc" +rawtx_ext = 'raw' +sigtx_ext = 'sig' +addrfile_ext = 'addrs' +addrfile_chksum_ext = 'chk' +keyfile_ext = 'keys' +keyaddrfile_ext = 'akeys' +mmenc_ext = 'mmenc' -default_wordlist = "electrum" -#default_wordlist = "tirosh" +default_wordlist = 'electrum' +#default_wordlist = 'tirosh' # Global value sets user opt -dfl_vars = "seed_len","hash_preset","usr_randchars","debug" +dfl_vars = 'seed_len','hash_preset','usr_randchars','debug' seed_lens = 128,192,256 mn_lens = [i / 32 * 3 for i in seed_lens] -keyconv_exec = "keyconv" +keyconv_exec = 'keyconv' mins_per_block = 9 passwd_max_tries = 5 diff --git a/mmgen/license.py b/mmgen/license.py index c0070047..77381cbd 100755 --- a/mmgen/license.py +++ b/mmgen/license.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution -# Copyright (C)2013-2015 Philemon +# Copyright (C)2013-2016 Philemon # # 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 @@ -17,7 +17,7 @@ # along with this program. If not, see . """ -license.py: Text of GPLv3 +license.py: Copyright notice and text of GPLv3 """ import mmgen.globalvars as g diff --git a/mmgen/main.py b/mmgen/main.py index e2713cc2..b46dae5c 100755 --- a/mmgen/main.py +++ b/mmgen/main.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution -# Copyright (C)2013-2015 Philemon +# Copyright (C)2013-2016 Philemon # # 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 @@ -22,15 +22,15 @@ main.py - Script launcher for the MMGen suite def launch(what): - if what in ("walletgen","walletchk","walletconv","passchg"): - what = "wallet" - if what == "keygen": what = "addrgen" + if what in ('walletgen','walletchk','walletconv','passchg'): + what = 'wallet' + if what == 'keygen': what = 'addrgen' try: import termios except: # Windows from mmgen.util import start_mscolor start_mscolor() - __import__("mmgen.main_" + what) + __import__('mmgen.main_' + what) else: import sys,atexit fd = sys.stdin.fileno() @@ -38,8 +38,8 @@ def launch(what): def at_exit(): termios.tcsetattr(fd, termios.TCSADRAIN, old) atexit.register(at_exit) - try: __import__("mmgen.main_" + what) + try: __import__('mmgen.main_' + what) except KeyboardInterrupt: - sys.stderr.write("\nUser interrupt\n") + sys.stderr.write('\nUser interrupt\n') except EOFError: - sys.stderr.write("\nEnd of file\n") + sys.stderr.write('\nEnd of file\n') diff --git a/mmgen/main_addrgen.py b/mmgen/main_addrgen.py index fa9481de..3a0cdcc0 100755 --- a/mmgen/main_addrgen.py +++ b/mmgen/main_addrgen.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution -# Copyright (C)2013-2015 Philemon +# Copyright (C)2013-2016 Philemon # # 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 @@ -21,24 +21,20 @@ mmgen-addrgen: Generate a series or range of addresses from an MMGen deterministic wallet """ -import sys - -import mmgen.globalvars as g -import mmgen.opt as opt -from mmgen.util import * +from mmgen.common import * from mmgen.crypto import * from mmgen.addr import * from mmgen.seed import SeedSource -if sys.argv[0].split("-")[-1] == "keygen": - gen_what = "keys" +if sys.argv[0].split('-')[-1] == 'keygen': + gen_what = 'keys' opt_filter = None note1 = """ By default, both addresses and secret keys are generated. """.strip() else: - gen_what = "addresses" - opt_filter = "hcdeiHOKlpzPqSv" + gen_what = 'addresses' + opt_filter = 'hcdeiHOKlpzPqSv' note1 = """ If available, the external 'keyconv' program will be used for address generation. @@ -48,7 +44,7 @@ opts_data = { 'sets': [('print_checksum',True,'quiet',True)], 'desc': """Generate a range or list of {what} from an {pnm} wallet, mnemonic, seed or password""".format(what=gen_what,pnm=g.proj_name), - 'usage':"[opts] [infile]
", + 'usage':'[opts] [infile]
', 'options': """ -h, --help Print this help message. -A, --no-addresses Print only secret keys, no addresses. @@ -73,7 +69,7 @@ opts_data = { -v, --verbose Produce more verbose output. -x, --b16 Print secret keys in hexadecimal too. """.format( - seed_lens=", ".join([str(i) for i in g.seed_lens]), + seed_lens=', '.join([str(i) for i in g.seed_lens]), pnm=g.proj_name, what=gen_what,g=g ), @@ -92,21 +88,20 @@ FMT CODES: {f} """.format( n=note1, - f="\n ".join(SeedSource.format_fmt_codes().splitlines()), - o=opt.opts + f='\n '.join(SeedSource.format_fmt_codes().splitlines()), + o=opts ) } - -cmd_args = opt.opts.init(opts_data,add_opts=["b16"],opt_filter=opt_filter) +cmd_args = opts.init(opts_data,add_opts=['b16'],opt_filter=opt_filter) nargs = 2 if len(cmd_args) < nargs \ and not opt.hidden_incog_input_params and not opt.in_fmt: - opt.opts.usage() + opts.usage() elif len(cmd_args) > nargs \ or (len(cmd_args) == nargs and opt.hidden_incog_input_params): - opt.opts.usage() + opts.usage() addrlist_arg = cmd_args.pop() addr_idxs = parse_addr_idxs(addrlist_arg) @@ -115,8 +110,8 @@ if not addr_idxs: do_license_msg() -opt.gen_what = "a" if gen_what == "addresses" \ - else "k" if opt.no_addresses else "ka" +opt.gen_what = 'a' if gen_what == 'addresses' \ + else 'k' if opt.no_addresses else 'ka' # Generate data: ss = SeedSource(*cmd_args) @@ -124,19 +119,19 @@ ss = SeedSource(*cmd_args) ainfo = generate_addrs(ss.seed.data,addr_idxs) addrdata_str = ainfo.fmt_data() -outfile_base = "{}[{}]".format(ss.seed.sid, ainfo.idxs_fmt) +outfile_base = '{}[{}]'.format(ss.seed.sid, ainfo.idxs_fmt) if 'a' in opt.gen_what and opt.print_checksum: Die(0,ainfo.checksum) -if 'k' in opt.gen_what and keypress_confirm("Encrypt key list?"): - addrdata_str = mmgen_encrypt(addrdata_str,"new key list","") - enc_ext = "." + g.mmenc_ext -else: enc_ext = "" +if 'k' in opt.gen_what and keypress_confirm('Encrypt key list?'): + addrdata_str = mmgen_encrypt(addrdata_str,'new key list','') + enc_ext = '.' + g.mmenc_ext +else: enc_ext = '' -ext = (g.keyfile_ext,g.keyaddrfile_ext)[int("ka" in opt.gen_what)] -ext = (g.addrfile_ext,ext)[int("k" in opt.gen_what)] -outfile = "%s.%s%s" % (outfile_base, ext, enc_ext) -ask_tty = "k" in opt.gen_what and not opt.quiet -if gen_what == "keys": gen_what = "secret keys" +ext = (g.keyfile_ext,g.keyaddrfile_ext)['ka' in opt.gen_what] +ext = (g.addrfile_ext,ext)['k' in opt.gen_what] +outfile = '%s.%s%s' % (outfile_base, ext, enc_ext) +ask_tty = 'k' in opt.gen_what and not opt.quiet +if gen_what == 'keys': gen_what = 'secret keys' write_data_to_file(outfile,addrdata_str,gen_what,ask_tty=ask_tty) diff --git a/mmgen/main_addrimport.py b/mmgen/main_addrimport.py index 562bbcc6..97ca28b8 100755 --- a/mmgen/main_addrimport.py +++ b/mmgen/main_addrimport.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution -# Copyright (C)2013-2015 Philemon +# Copyright (C)2013-2016 Philemon # # 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 @@ -20,19 +20,19 @@ mmgen-addrimport: Import addresses into a MMGen bitcoind tracking wallet """ -import sys, time -import mmgen.globalvars as g -import mmgen.opt as opt -from mmgen.util import * +import time + +from mmgen.common import * from mmgen.tx import connect_to_bitcoind from mmgen.addr import AddrInfo,AddrInfoEntry opts_data = { 'desc': """Import addresses (both {pnm} and non-{pnm}) into a bitcoind tracking wallet""".format(pnm=g.proj_name), - 'usage':"[opts] [mmgen address file]", + 'usage':'[opts] [mmgen address file]', 'options': """ -h, --help Print this help message +-b, --batch Batch mode. Import all addresses in one RPC call -l, --addrlist Address source is a flat list of addresses -k, --keyaddr-file Address source is a key-address file -q, --quiet Suppress warnings @@ -46,14 +46,14 @@ in the tracking wallet. """ } -cmd_args = opt.opts.init(opts_data) +cmd_args = opts.init(opts_data) if len(cmd_args) == 1: infile = cmd_args[0] check_infile(infile) if opt.addrlist: lines = get_lines_from_file( - infile,"non-{pnm} addresses".format(pnm=g.proj_name),trim_comments=True) + infile,'non-{pnm} addresses'.format(pnm=g.proj_name),trim_comments=True) ai,adata = AddrInfo(),[] for btcaddr in lines: a = AddrInfoEntry() @@ -63,23 +63,19 @@ if len(cmd_args) == 1: else: ai = AddrInfo(infile,has_keys=opt.keyaddr_file) else: - msg(""" + die(1,""" You must specify an {pnm} address file (or a list of non-{pnm} addresses with the '--addrlist' option) """.strip().format(pnm=g.proj_name)) - sys.exit(1) from mmgen.bitcoin import verify_addr -qmsg_r("Validating addresses...") +qmsg_r('Validating addresses...') for e in ai.addrdata: if not verify_addr(e.addr,verbose=True): - msg("%s: invalid address" % e.addr) - sys.exit(2) + die(2,'%s: invalid address' % e.addr) -m = (" from Seed ID %s" % ai.seed_id) if ai.seed_id else "" -qmsg("OK. %s addresses%s" % (ai.num_addrs,m)) - -g.http_timeout = 3600 +m = (' from Seed ID %s' % ai.seed_id) if ai.seed_id else '' +qmsg('OK. %s addresses%s' % (ai.num_addrs,m)) if not opt.test: c = connect_to_bitcoind() @@ -97,35 +93,40 @@ program now and rerun it using the '--rescan' option. Otherwise you may ignore this message and continue. """.strip() -if not opt.quiet: confirm_or_exit(m, "continue", expect="YES") +if not opt.quiet: confirm_or_exit(m, 'continue', expect='YES') err_flag = False def import_address(addr,label,rescan): try: if not opt.test: - c.importaddress(addr,label,rescan) + c.importaddress(addr,label,rescan,timeout=(False,3600)[rescan]) except: global err_flag err_flag = True w_n_of_m = len(str(ai.num_addrs)) * 2 + 2 -w_mmid = "" if opt.addrlist else len(str(max(ai.idxs()))) + 12 +w_mmid = '' if opt.addrlist else len(str(max(ai.idxs()))) + 12 if opt.rescan: import threading - msg_fmt = "\r%s %-{}s %-34s %s".format(w_n_of_m) + msg_fmt = '\r%s %-{}s %-34s %s'.format(w_n_of_m) else: - msg_fmt = "\r%-{}s %-34s %s".format(w_n_of_m, w_mmid) + msg_fmt = '\r%-{}s %-34s %s'.format(w_n_of_m, w_mmid) -msg("Importing addresses") +msg("Importing %s addresses from '%s'%s" % + (len(ai.addrdata),infile,('',' (batch mode)')[bool(opt.batch)])) + +arg_list = [] for n,e in enumerate(ai.addrdata): if e.idx: - label = "%s:%s" % (ai.seed_id,e.idx) - if e.comment: label += " " + e.comment - else: label = "non-{pnm}".format(pnm=g.proj_name) + label = '%s:%s' % (ai.seed_id,e.idx) + if e.comment: label += ' ' + e.comment + else: label = 'non-{pnm}'.format(pnm=g.proj_name) - if opt.rescan: + if opt.batch: + arg_list.append((e.addr,label,False)) + elif opt.rescan: t = threading.Thread(target=import_address, args=(e.addr,label,True)) t.daemon = True t.start() @@ -135,18 +136,22 @@ for n,e in enumerate(ai.addrdata): while True: if t.is_alive(): elapsed = int(time.time() - start) - count = "%s/%s:" % (n+1, ai.num_addrs) - msg_r(msg_fmt % (secs_to_hms(elapsed),count,e.addr,"(%s)"%label)) + count = '%s/%s:' % (n+1, ai.num_addrs) + msg_r(msg_fmt % (secs_to_hms(elapsed),count,e.addr,'(%s)'%label)) time.sleep(1) else: - if err_flag: msg("\nImport failed"); sys.exit(2) - msg("\nOK") + if err_flag: die(2,'\nImport failed') + msg('\nOK') break else: - import_address(e.addr,label,rescan=False) - count = "%s/%s:" % (n+1, ai.num_addrs) - msg_r(msg_fmt % (count, e.addr, "(%s)"%label)) - if err_flag: - msg("\nImport failed") - sys.exit(2) - msg(" - OK") + import_address(e.addr,label,False) + count = '%s/%s:' % (n+1, ai.num_addrs) + msg_r(msg_fmt % (count, e.addr, '(%s)'%label)) + if err_flag: die(2,'\nImport failed') + msg(' - OK') + +if opt.batch: + if opt.rescan: + msg('Warning: this command may take a long time to complete!') + ret = c.importaddress(arg_list,batch=True,timeout=(False,3600)[bool(opt.rescan)]) + msg('OK: %s addresses imported' % len(ret)) diff --git a/mmgen/main_pywallet.py b/mmgen/main_pywallet.py deleted file mode 100755 index fc853975..00000000 --- a/mmgen/main_pywallet.py +++ /dev/null @@ -1,1684 +0,0 @@ -#!/usr/bin/env python -# -# mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution -# Copyright (C)2013-2015 Philemon -# -# 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 . - -""" -mmgen-pywallet: Dump contents of a bitcoind wallet to file -""" - -# Changes by Philemon: -# password entry at prompt -# dump keys, addresses or keys for specified addresses (output in flat list) - -# PyWallet 1.2.1 (Public Domain) -# http://github.com/joric/pywallet -# Most of the actual PyWallet code placed in the public domain. -# PyWallet includes portions of free software, listed below. - -# BitcoinTools (wallet.dat handling code, MIT License) -# https://github.com/gavinandresen/bitcointools -# Copyright (c) 2010 Gavin Andresen - -# python-ecdsa (EC_KEY implementation, MIT License) -# http://github.com/warner/python-ecdsa -# "python-ecdsa" Copyright (c) 2010 Brian Warner -# Portions written in 2005 by Peter Pearson and placed in the public domain. - -# SlowAES (aes.py code, Apache 2 License) -# http://code.google.com/p/slowaes/ -# Copyright (c) 2008, Josh Davis (http://www.josh-davis.org), -# Alex Martelli (http://www.aleax.it) -# Ported from C code written by Laurent Haan (http://www.progressive-coding.com) - -from bsddb.db import * -import sys, time -import json -import logging -import struct -import StringIO -import traceback -import socket -import types -import string -import exceptions -import hashlib -import random -import math - -import mmgen.globalvars as g -import mmgen.opt as opt -from mmgen.util import msg - -max_version = 60000 -addrtype = 0 -json_db = {} -private_keys = [] -password = None - -opts_data = { - 'desc': "Dump contents of a bitcoind wallet to file", - 'usage': "[opts] ", - 'options': """ --h, --help Print this help message. --d, --outdir= d Specify an alternate directory 'd' for output. --e, --echo-passphrase Display passphrase on screen upon entry. --j, --json Dump wallet in json format. --k, --keys Dump all private keys (flat list). --a, --addrs Dump all addresses (flat list). --K, --keysforaddrs= f Dump private keys for addresses listed in file 'f'. --P, --passwd-file= f Get passphrase from file 'f'. --q, --quiet Produce quieter output; suppress some warnings. --S, --stdout Dump to stdout rather than file. -""" -} - -cmd_args = opt.opts.init(opts_data) -opt.opts.die_on_incompatible_opts(['json','keys','addrs','keysforaddrs']) - -if len(cmd_args) == 1: - from mmgen.util import check_infile - check_infile(cmd_args[0]) -else: - opt.opts.usage(opts_data) - -if (not opt.json and not opt.keys and not opt.addrs and not opt.keysforaddrs): - opt.opts.usage(opts_data) - -# from the SlowAES project, http://code.google.com/p/slowaes (aes.py) - -def append_PKCS7_padding(s): - """return s padded to a multiple of 16-bytes by PKCS7 padding""" - numpads = 16 - (len(s)%16) - return s + numpads*chr(numpads) - -def strip_PKCS7_padding(s): - """return s stripped of PKCS7 padding""" - if len(s)%16 or not s: - raise ValueError("String of len %d can't be PCKS7-padded" % len(s)) - numpads = ord(s[-1]) - if numpads > 16: - raise ValueError("String ending with %r can't be PCKS7-padded" % s[-1]) - return s[:-numpads] - -class AES(object): - # valid key sizes - keySize = dict(SIZE_128=16, SIZE_192=24, SIZE_256=32) - - # Rijndael S-box - sbox = [0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, - 0x2b, 0xfe, 0xd7, 0xab, 0x76, 0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, - 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0, 0xb7, - 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, - 0x71, 0xd8, 0x31, 0x15, 0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, - 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75, 0x09, 0x83, - 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, - 0xe3, 0x2f, 0x84, 0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, - 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf, 0xd0, 0xef, 0xaa, - 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, - 0x9f, 0xa8, 0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, - 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2, 0xcd, 0x0c, 0x13, 0xec, - 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, - 0x73, 0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, - 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb, 0xe0, 0x32, 0x3a, 0x0a, 0x49, - 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79, - 0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, - 0xea, 0x65, 0x7a, 0xae, 0x08, 0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, - 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a, 0x70, - 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, - 0x86, 0xc1, 0x1d, 0x9e, 0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, - 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf, 0x8c, 0xa1, - 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, - 0x54, 0xbb, 0x16] - - # Rijndael Inverted S-box - rsbox = [0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, 0xbf, 0x40, 0xa3, - 0x9e, 0x81, 0xf3, 0xd7, 0xfb , 0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f, - 0xff, 0x87, 0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde, 0xe9, 0xcb , 0x54, - 0x7b, 0x94, 0x32, 0xa6, 0xc2, 0x23, 0x3d, 0xee, 0x4c, 0x95, 0x0b, - 0x42, 0xfa, 0xc3, 0x4e , 0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24, - 0xb2, 0x76, 0x5b, 0xa2, 0x49, 0x6d, 0x8b, 0xd1, 0x25 , 0x72, 0xf8, - 0xf6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xd4, 0xa4, 0x5c, 0xcc, 0x5d, - 0x65, 0xb6, 0x92 , 0x6c, 0x70, 0x48, 0x50, 0xfd, 0xed, 0xb9, 0xda, - 0x5e, 0x15, 0x46, 0x57, 0xa7, 0x8d, 0x9d, 0x84 , 0x90, 0xd8, 0xab, - 0x00, 0x8c, 0xbc, 0xd3, 0x0a, 0xf7, 0xe4, 0x58, 0x05, 0xb8, 0xb3, - 0x45, 0x06 , 0xd0, 0x2c, 0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02, 0xc1, - 0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b , 0x3a, 0x91, 0x11, 0x41, - 0x4f, 0x67, 0xdc, 0xea, 0x97, 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6, - 0x73 , 0x96, 0xac, 0x74, 0x22, 0xe7, 0xad, 0x35, 0x85, 0xe2, 0xf9, - 0x37, 0xe8, 0x1c, 0x75, 0xdf, 0x6e , 0x47, 0xf1, 0x1a, 0x71, 0x1d, - 0x29, 0xc5, 0x89, 0x6f, 0xb7, 0x62, 0x0e, 0xaa, 0x18, 0xbe, 0x1b , - 0xfc, 0x56, 0x3e, 0x4b, 0xc6, 0xd2, 0x79, 0x20, 0x9a, 0xdb, 0xc0, - 0xfe, 0x78, 0xcd, 0x5a, 0xf4 , 0x1f, 0xdd, 0xa8, 0x33, 0x88, 0x07, - 0xc7, 0x31, 0xb1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xec, 0x5f , 0x60, - 0x51, 0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d, 0x2d, 0xe5, 0x7a, 0x9f, - 0x93, 0xc9, 0x9c, 0xef , 0xa0, 0xe0, 0x3b, 0x4d, 0xae, 0x2a, 0xf5, - 0xb0, 0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61 , 0x17, 0x2b, - 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26, 0xe1, 0x69, 0x14, 0x63, 0x55, - 0x21, 0x0c, 0x7d] - - def getSBoxValue(self,num): - """Retrieves a given S-Box Value""" - return self.sbox[num] - - def getSBoxInvert(self,num): - """Retrieves a given Inverted S-Box Value""" - return self.rsbox[num] - - def rotate(self, word): - """ Rijndael's key schedule rotate operation. - - Rotate a word eight bits to the left: eg, rotate(1d2c3a4f) == 2c3a4f1d - Word is an char list of size 4 (32 bits overall). - """ - return word[1:] + word[:1] - - # Rijndael Rcon - Rcon = [0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, - 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, - 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, - 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, - 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, - 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, - 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, - 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, - 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, - 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, - 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, - 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, - 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, - 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, - 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, - 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, - 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, - 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, - 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08, - 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, - 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, - 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, - 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, - 0xe8, 0xcb ] - - def getRconValue(self, num): - """Retrieves a given Rcon Value""" - return self.Rcon[num] - - def core(self, word, iteration): - """Key schedule core.""" - # rotate the 32-bit word 8 bits to the left - word = self.rotate(word) - # apply S-Box substitution on all 4 parts of the 32-bit word - for i in range(4): - word[i] = self.getSBoxValue(word[i]) - # XOR the output of the rcon operation with i to the first part - # (leftmost) only - word[0] = word[0] ^ self.getRconValue(iteration) - return word - - def expandKey(self, key, size, expandedKeySize): - """Rijndael's key expansion. - - Expands an 128,192,256 key into an 176,208,240 bytes key - - expandedKey is a char list of large enough size, - key is the non-expanded key. - """ - # current expanded keySize, in bytes - currentSize = 0 - rconIteration = 1 - expandedKey = [0] * expandedKeySize - - # set the 16, 24, 32 bytes of the expanded key to the input key - for j in range(size): - expandedKey[j] = key[j] - currentSize += size - - while currentSize < expandedKeySize: - # assign the previous 4 bytes to the temporary value t - t = expandedKey[currentSize-4:currentSize] - - # every 16,24,32 bytes we apply the core schedule to t - # and increment rconIteration afterwards - if currentSize % size == 0: - t = self.core(t, rconIteration) - rconIteration += 1 - # For 256-bit keys, we add an extra sbox to the calculation - if size == self.keySize["SIZE_256"] and ((currentSize % size) == 16): - for l in range(4): t[l] = self.getSBoxValue(t[l]) - - # We XOR t with the four-byte block 16,24,32 bytes before the new - # expanded key. This becomes the next four bytes in the expanded - # key. - for m in range(4): - expandedKey[currentSize] = expandedKey[currentSize - size] ^ \ - t[m] - currentSize += 1 - - return expandedKey - - def addRoundKey(self, state, roundKey): - """Adds (XORs) the round key to the state.""" - for i in range(16): - state[i] ^= roundKey[i] - return state - - def createRoundKey(self, expandedKey, roundKeyPointer): - """Create a round key. - Creates a round key from the given expanded key and the - position within the expanded key. - """ - roundKey = [0] * 16 - for i in range(4): - for j in range(4): - roundKey[j*4+i] = expandedKey[roundKeyPointer + i*4 + j] - return roundKey - - def galois_multiplication(self, a, b): - """Galois multiplication of 8 bit characters a and b.""" - p = 0 - for counter in range(8): - if b & 1: p ^= a - hi_bit_set = a & 0x80 - a <<= 1 - # keep a 8 bit - a &= 0xFF - if hi_bit_set: - a ^= 0x1b - b >>= 1 - return p - - # - # substitute all the values from the state with the value in the SBox - # using the state value as index for the SBox - # - def subBytes(self, state, isInv): - if isInv: getter = self.getSBoxInvert - else: getter = self.getSBoxValue - for i in range(16): state[i] = getter(state[i]) - return state - - # iterate over the 4 rows and call shiftRow() with that row - def shiftRows(self, state, isInv): - for i in range(4): - state = self.shiftRow(state, i*4, i, isInv) - return state - - # each iteration shifts the row to the left by 1 - def shiftRow(self, state, statePointer, nbr, isInv): - for i in range(nbr): - if isInv: - state[statePointer:statePointer+4] = \ - state[statePointer+3:statePointer+4] + \ - state[statePointer:statePointer+3] - else: - state[statePointer:statePointer+4] = \ - state[statePointer+1:statePointer+4] + \ - state[statePointer:statePointer+1] - return state - - # galois multiplication of the 4x4 matrix - def mixColumns(self, state, isInv): - # iterate over the 4 columns - for i in range(4): - # construct one column by slicing over the 4 rows - column = state[i:i+16:4] - # apply the mixColumn on one column - column = self.mixColumn(column, isInv) - # put the values back into the state - state[i:i+16:4] = column - - return state - - # galois multiplication of 1 column of the 4x4 matrix - def mixColumn(self, column, isInv): - if isInv: mult = [14, 9, 13, 11] - else: mult = [2, 1, 1, 3] - cpy = list(column) - g = self.galois_multiplication - - column[0] = g(cpy[0], mult[0]) ^ g(cpy[3], mult[1]) ^ \ - g(cpy[2], mult[2]) ^ g(cpy[1], mult[3]) - column[1] = g(cpy[1], mult[0]) ^ g(cpy[0], mult[1]) ^ \ - g(cpy[3], mult[2]) ^ g(cpy[2], mult[3]) - column[2] = g(cpy[2], mult[0]) ^ g(cpy[1], mult[1]) ^ \ - g(cpy[0], mult[2]) ^ g(cpy[3], mult[3]) - column[3] = g(cpy[3], mult[0]) ^ g(cpy[2], mult[1]) ^ \ - g(cpy[1], mult[2]) ^ g(cpy[0], mult[3]) - return column - - # applies the 4 operations of the forward round in sequence - def aes_round(self, state, roundKey): - state = self.subBytes(state, False) - state = self.shiftRows(state, False) - state = self.mixColumns(state, False) - state = self.addRoundKey(state, roundKey) - return state - - # applies the 4 operations of the inverse round in sequence - def aes_invRound(self, state, roundKey): - state = self.shiftRows(state, True) - state = self.subBytes(state, True) - state = self.addRoundKey(state, roundKey) - state = self.mixColumns(state, True) - return state - - # Perform the initial operations, the standard round, and the final - # operations of the forward aes, creating a round key for each round - def aes_main(self, state, expandedKey, nbrRounds): - state = self.addRoundKey(state, self.createRoundKey(expandedKey, 0)) - i = 1 - while i < nbrRounds: - state = self.aes_round(state, self.createRoundKey(expandedKey, 16*i)) - i += 1 - state = self.subBytes(state, False) - state = self.shiftRows(state, False) - state = self.addRoundKey(state, self.createRoundKey(expandedKey, 16*nbrRounds)) - return state - - # Perform the initial operations, the standard round, and the final - # operations of the inverse aes, creating a round key for each round - def aes_invMain(self, state, expandedKey, nbrRounds): - state = self.addRoundKey(state, self.createRoundKey(expandedKey, 16*nbrRounds)) - i = nbrRounds - 1 - while i > 0: - state = self.aes_invRound(state, self.createRoundKey(expandedKey, 16*i)) - i -= 1 - state = self.shiftRows(state, True) - state = self.subBytes(state, True) - state = self.addRoundKey(state, self.createRoundKey(expandedKey, 0)) - return state - - # encrypts a 128 bit input block against the given key of size specified - def encrypt(self, iput, key, size): - output = [0] * 16 - # the number of rounds - nbrRounds = 0 - # the 128 bit block to encode - block = [0] * 16 - # set the number of rounds - if size == self.keySize["SIZE_128"]: nbrRounds = 10 - elif size == self.keySize["SIZE_192"]: nbrRounds = 12 - elif size == self.keySize["SIZE_256"]: nbrRounds = 14 - else: return None - - # the expanded keySize - expandedKeySize = 16*(nbrRounds+1) - - # Set the block values, for the block: - # a0,0 a0,1 a0,2 a0,3 - # a1,0 a1,1 a1,2 a1,3 - # a2,0 a2,1 a2,2 a2,3 - # a3,0 a3,1 a3,2 a3,3 - # the mapping order is a0,0 a1,0 a2,0 a3,0 a0,1 a1,1 ... a2,3 a3,3 - # - # iterate over the columns - for i in range(4): - # iterate over the rows - for j in range(4): - block[(i+(j*4))] = iput[(i*4)+j] - - # expand the key into an 176, 208, 240 bytes key - # the expanded key - expandedKey = self.expandKey(key, size, expandedKeySize) - - # encrypt the block using the expandedKey - block = self.aes_main(block, expandedKey, nbrRounds) - - # unmap the block again into the output - for k in range(4): - # iterate over the rows - for l in range(4): - output[(k*4)+l] = block[(k+(l*4))] - return output - - # decrypts a 128 bit input block against the given key of size specified - def decrypt(self, iput, key, size): - output = [0] * 16 - # the number of rounds - nbrRounds = 0 - # the 128 bit block to decode - block = [0] * 16 - # set the number of rounds - if size == self.keySize["SIZE_128"]: nbrRounds = 10 - elif size == self.keySize["SIZE_192"]: nbrRounds = 12 - elif size == self.keySize["SIZE_256"]: nbrRounds = 14 - else: return None - - # the expanded keySize - expandedKeySize = 16*(nbrRounds+1) - - # Set the block values, for the block: - # a0,0 a0,1 a0,2 a0,3 - # a1,0 a1,1 a1,2 a1,3 - # a2,0 a2,1 a2,2 a2,3 - # a3,0 a3,1 a3,2 a3,3 - # the mapping order is a0,0 a1,0 a2,0 a3,0 a0,1 a1,1 ... a2,3 a3,3 - - # iterate over the columns - for i in range(4): - # iterate over the rows - for j in range(4): - block[(i+(j*4))] = iput[(i*4)+j] - # expand the key into an 176, 208, 240 bytes key - expandedKey = self.expandKey(key, size, expandedKeySize) - # decrypt the block using the expandedKey - block = self.aes_invMain(block, expandedKey, nbrRounds) - # unmap the block again into the output - for k in range(4): - # iterate over the rows - for l in range(4): - output[(k*4)+l] = block[(k+(l*4))] - return output - -class AESModeOfOperation(object): - - aes = AES() - - # structure of supported modes of operation - modeOfOperation = dict(OFB=0, CFB=1, CBC=2) - - # converts a 16 character string into a number array - def convertString(self, string, start, end, mode): - if end - start > 16: end = start + 16 - if mode == self.modeOfOperation["CBC"]: ar = [0] * 16 - else: ar = [] - - i = start - j = 0 - while len(ar) < end - start: - ar.append(0) - while i < end: - ar[j] = ord(string[i]) - j += 1 - i += 1 - return ar - - # Mode of Operation Encryption - # stringIn - Input String - # mode - mode of type modeOfOperation - # hexKey - a hex key of the bit length size - # size - the bit length of the key - # hexIV - the 128 bit hex Initilization Vector - def encrypt(self, stringIn, mode, key, size, IV): - if len(key) % size: - return None - if len(IV) % 16: - return None - # the AES input/output - plaintext = [] - iput = [0] * 16 - output = [] - ciphertext = [0] * 16 - # the output cipher string - cipherOut = [] - # char firstRound - firstRound = True - if stringIn != None: - for j in range(int(math.ceil(float(len(stringIn))/16))): - start = j*16 - end = j*16+16 - if end > len(stringIn): - end = len(stringIn) - plaintext = self.convertString(stringIn, start, end, mode) - # print 'PT@%s:%s' % (j, plaintext) - if mode == self.modeOfOperation["CFB"]: - if firstRound: - output = self.aes.encrypt(IV, key, size) - firstRound = False - else: - output = self.aes.encrypt(iput, key, size) - for i in range(16): - if len(plaintext)-1 < i: - ciphertext[i] = 0 ^ output[i] - elif len(output)-1 < i: - ciphertext[i] = plaintext[i] ^ 0 - elif len(plaintext)-1 < i and len(output) < i: - ciphertext[i] = 0 ^ 0 - else: - ciphertext[i] = plaintext[i] ^ output[i] - for k in range(end-start): - cipherOut.append(ciphertext[k]) - iput = ciphertext - elif mode == self.modeOfOperation["OFB"]: - if firstRound: - output = self.aes.encrypt(IV, key, size) - firstRound = False - else: - output = self.aes.encrypt(iput, key, size) - for i in range(16): - if len(plaintext)-1 < i: - ciphertext[i] = 0 ^ output[i] - elif len(output)-1 < i: - ciphertext[i] = plaintext[i] ^ 0 - elif len(plaintext)-1 < i and len(output) < i: - ciphertext[i] = 0 ^ 0 - else: - ciphertext[i] = plaintext[i] ^ output[i] - for k in range(end-start): - cipherOut.append(ciphertext[k]) - iput = output - elif mode == self.modeOfOperation["CBC"]: - for i in range(16): - if firstRound: - iput[i] = plaintext[i] ^ IV[i] - else: - iput[i] = plaintext[i] ^ ciphertext[i] - # print 'IP@%s:%s' % (j, iput) - firstRound = False - ciphertext = self.aes.encrypt(iput, key, size) - # always 16 bytes because of the padding for CBC - for k in range(16): - cipherOut.append(ciphertext[k]) - return mode, len(stringIn), cipherOut - - # Mode of Operation Decryption - # cipherIn - Encrypted String - # originalsize - The unencrypted string length - required for CBC - # mode - mode of type modeOfOperation - # key - a number array of the bit length size - # size - the bit length of the key - # IV - the 128 bit number array Initilization Vector - def decrypt(self, cipherIn, originalsize, mode, key, size, IV): - # cipherIn = unescCtrlChars(cipherIn) - if len(key) % size: - return None - if len(IV) % 16: - return None - # the AES input/output - ciphertext = [] - iput = [] - output = [] - plaintext = [0] * 16 - # the output plain text string - stringOut = '' - # char firstRound - firstRound = True - if cipherIn != None: - for j in range(int(math.ceil(float(len(cipherIn))/16))): - start = j*16 - end = j*16+16 - if j*16+16 > len(cipherIn): - end = len(cipherIn) - ciphertext = cipherIn[start:end] - if mode == self.modeOfOperation["CFB"]: - if firstRound: - output = self.aes.encrypt(IV, key, size) - firstRound = False - else: - output = self.aes.encrypt(iput, key, size) - for i in range(16): - if len(output)-1 < i: - plaintext[i] = 0 ^ ciphertext[i] - elif len(ciphertext)-1 < i: - plaintext[i] = output[i] ^ 0 - elif len(output)-1 < i and len(ciphertext) < i: - plaintext[i] = 0 ^ 0 - else: - plaintext[i] = output[i] ^ ciphertext[i] - for k in range(end-start): - stringOut += chr(plaintext[k]) - iput = ciphertext - elif mode == self.modeOfOperation["OFB"]: - if firstRound: - output = self.aes.encrypt(IV, key, size) - firstRound = False - else: - output = self.aes.encrypt(iput, key, size) - for i in range(16): - if len(output)-1 < i: - plaintext[i] = 0 ^ ciphertext[i] - elif len(ciphertext)-1 < i: - plaintext[i] = output[i] ^ 0 - elif len(output)-1 < i and len(ciphertext) < i: - plaintext[i] = 0 ^ 0 - else: - plaintext[i] = output[i] ^ ciphertext[i] - for k in range(end-start): - stringOut += chr(plaintext[k]) - iput = output - elif mode == self.modeOfOperation["CBC"]: - output = self.aes.decrypt(ciphertext, key, size) - for i in range(16): - if firstRound: - plaintext[i] = IV[i] ^ output[i] - else: - plaintext[i] = iput[i] ^ output[i] - firstRound = False - if originalsize is not None and originalsize < end: - for k in range(originalsize-start): - stringOut += chr(plaintext[k]) - else: - for k in range(end-start): - stringOut += chr(plaintext[k]) - iput = ciphertext - return stringOut - -# end of aes.py code - -# pywallet crypter implementation - -crypter = None - -try: - from Crypto.Cipher import AES - crypter = 'pycrypto' -except: - pass - -class Crypter_pycrypto( object ): - def SetKeyFromPassphrase(self, vKeyData, vSalt, nDerivIterations, nDerivationMethod): - if nDerivationMethod != 0: - return 0 - data = vKeyData + vSalt - for i in xrange(nDerivIterations): - data = hashlib.sha512(data).digest() - self.SetKey(data[0:32]) - self.SetIV(data[32:32+16]) - return len(data) - - def SetKey(self, key): - self.chKey = key - - def SetIV(self, iv): - self.chIV = iv[0:16] - - def Encrypt(self, data): - return AES.new(self.chKey,AES.MODE_CBC,self.chIV).encrypt(data)[0:32] - - def Decrypt(self, data): - return AES.new(self.chKey,AES.MODE_CBC,self.chIV).decrypt(data)[0:32] - -try: - if not crypter: - import ctypes - import ctypes.util - ssl = ctypes.cdll.LoadLibrary (ctypes.util.find_library ('ssl') or 'libeay32') - crypter = 'ssl' -except: - pass - -class Crypter_ssl(object): - def __init__(self): - self.chKey = ctypes.create_string_buffer (32) - self.chIV = ctypes.create_string_buffer (16) - - def SetKeyFromPassphrase(self, vKeyData, vSalt, nDerivIterations, nDerivationMethod): - if nDerivationMethod != 0: - return 0 - strKeyData = ctypes.create_string_buffer (vKeyData) - chSalt = ctypes.create_string_buffer (vSalt) - return ssl.EVP_BytesToKey(ssl.EVP_aes_256_cbc(), ssl.EVP_sha512(), chSalt, strKeyData, - len(vKeyData), nDerivIterations, ctypes.byref(self.chKey), ctypes.byref(self.chIV)) - - def SetKey(self, key): - self.chKey = ctypes.create_string_buffer(key) - - def SetIV(self, iv): - self.chIV = ctypes.create_string_buffer(iv) - - def Encrypt(self, data): - buf = ctypes.create_string_buffer(len(data) + 16) - written = ctypes.c_int(0) - final = ctypes.c_int(0) - ctx = ssl.EVP_CIPHER_CTX_new() - ssl.EVP_CIPHER_CTX_init(ctx) - ssl.EVP_EncryptInit_ex(ctx, ssl.EVP_aes_256_cbc(), None, self.chKey, self.chIV) - ssl.EVP_EncryptUpdate(ctx, buf, ctypes.byref(written), data, len(data)) - output = buf.raw[:written.value] - ssl.EVP_EncryptFinal_ex(ctx, buf, ctypes.byref(final)) - output += buf.raw[:final.value] - return output - - def Decrypt(self, data): - buf = ctypes.create_string_buffer(len(data) + 16) - written = ctypes.c_int(0) - final = ctypes.c_int(0) - ctx = ssl.EVP_CIPHER_CTX_new() - ssl.EVP_CIPHER_CTX_init(ctx) - ssl.EVP_DecryptInit_ex(ctx, ssl.EVP_aes_256_cbc(), None, self.chKey, self.chIV) - ssl.EVP_DecryptUpdate(ctx, buf, ctypes.byref(written), data, len(data)) - output = buf.raw[:written.value] - ssl.EVP_DecryptFinal_ex(ctx, buf, ctypes.byref(final)) - output += buf.raw[:final.value] - return output - -class Crypter_pure(object): - def __init__(self): - self.m = AESModeOfOperation() - self.cbc = self.m.modeOfOperation["CBC"] - self.sz = self.m.aes.keySize["SIZE_256"] - - def SetKeyFromPassphrase(self, vKeyData, vSalt, nDerivIterations, nDerivationMethod): - if nDerivationMethod != 0: - return 0 - data = vKeyData + vSalt - for i in xrange(nDerivIterations): - data = hashlib.sha512(data).digest() - self.SetKey(data[0:32]) - self.SetIV(data[32:32+16]) - return len(data) - - def SetKey(self, key): - self.chKey = [ord(i) for i in key] - - def SetIV(self, iv): - self.chIV = [ord(i) for i in iv] - - def Encrypt(self, data): - mode, size, cypher = self.m.encrypt(data, self.cbc, self.chKey, self.sz, self.chIV) - return ''.join(map(chr, cypher)) - - def Decrypt(self, data): - chData = [ord(i) for i in data] - return self.m.decrypt(chData, self.sz, self.cbc, self.chKey, self.sz, self.chIV) - -# secp256k1 - -_p = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2FL -_r = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141L -_b = 0x0000000000000000000000000000000000000000000000000000000000000007L -_a = 0x0000000000000000000000000000000000000000000000000000000000000000L -_Gx = 0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798L -_Gy = 0x483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8L - -# python-ecdsa code (EC_KEY implementation) - -class CurveFp( object ): - def __init__( self, p, a, b ): - self.__p = p - self.__a = a - self.__b = b - - def p( self ): - return self.__p - - def a( self ): - return self.__a - - def b( self ): - return self.__b - - def contains_point( self, x, y ): - return ( y * y - ( x * x * x + self.__a * x + self.__b ) ) % self.__p == 0 - -class Point( object ): - def __init__( self, curve, x, y, order = None ): - self.__curve = curve - self.__x = x - self.__y = y - self.__order = order - if self.__curve: assert self.__curve.contains_point( x, y ) - if order: assert self * order == INFINITY - - def __add__( self, other ): - if other == INFINITY: return self - if self == INFINITY: return other - assert self.__curve == other.__curve - if self.__x == other.__x: - if ( self.__y + other.__y ) % self.__curve.p() == 0: - return INFINITY - else: - return self.double() - - p = self.__curve.p() - l = ( ( other.__y - self.__y ) * \ - inverse_mod( other.__x - self.__x, p ) ) % p - x3 = ( l * l - self.__x - other.__x ) % p - y3 = ( l * ( self.__x - x3 ) - self.__y ) % p - return Point( self.__curve, x3, y3 ) - - def __mul__( self, other ): - def leftmost_bit( x ): - assert x > 0 - result = 1L - while result <= x: result = 2 * result - return result / 2 - - e = other - if self.__order: e = e % self.__order - if e == 0: return INFINITY - if self == INFINITY: return INFINITY - assert e > 0 - e3 = 3 * e - negative_self = Point( self.__curve, self.__x, -self.__y, self.__order ) - i = leftmost_bit( e3 ) / 2 - result = self - while i > 1: - result = result.double() - if ( e3 & i ) != 0 and ( e & i ) == 0: result = result + self - if ( e3 & i ) == 0 and ( e & i ) != 0: result = result + negative_self - i = i / 2 - return result - - def __rmul__( self, other ): - return self * other - - def __str__( self ): - if self == INFINITY: return "infinity" - return "(%d,%d)" % ( self.__x, self.__y ) - - def double( self ): - if self == INFINITY: - return INFINITY - - p = self.__curve.p() - a = self.__curve.a() - l = ( ( 3 * self.__x * self.__x + a ) * \ - inverse_mod( 2 * self.__y, p ) ) % p - x3 = ( l * l - 2 * self.__x ) % p - y3 = ( l * ( self.__x - x3 ) - self.__y ) % p - return Point( self.__curve, x3, y3 ) - - def x( self ): - return self.__x - - def y( self ): - return self.__y - - def curve( self ): - return self.__curve - - def order( self ): - return self.__order - -INFINITY = Point( None, None, None ) - -def inverse_mod( a, m ): - if a < 0 or m <= a: a = a % m - c, d = a, m - uc, vc, ud, vd = 1, 0, 0, 1 - while c != 0: - q, c, d = divmod( d, c ) + ( c, ) - uc, vc, ud, vd = ud - q*uc, vd - q*vc, uc, vc - assert d == 1 - if ud > 0: return ud - else: return ud + m - -class Signature( object ): - def __init__( self, r, s ): - self.r = r - self.s = s - -class Public_key( object ): - def __init__( self, generator, point ): - self.curve = generator.curve() - self.generator = generator - self.point = point - n = generator.order() - if not n: - raise RuntimeError, "Generator point must have order." - if not n * point == INFINITY: - raise RuntimeError, "Generator point order is bad." - if point.x() < 0 or n <= point.x() or point.y() < 0 or n <= point.y(): - raise RuntimeError, "Generator point has x or y out of range." - - def verifies( self, hash, signature ): - G = self.generator - n = G.order() - r = signature.r - s = signature.s - if r < 1 or r > n-1: return False - if s < 1 or s > n-1: return False - c = inverse_mod( s, n ) - u1 = ( hash * c ) % n - u2 = ( r * c ) % n - xy = u1 * G + u2 * self.point - v = xy.x() % n - return v == r - -class Private_key( object ): - def __init__( self, public_key, secret_multiplier ): - self.public_key = public_key - self.secret_multiplier = secret_multiplier - - def der( self ): - hex_der_key = '06052b8104000a30740201010420' + \ - '%064x' % self.secret_multiplier + \ - 'a00706052b8104000aa14403420004' + \ - '%064x' % self.public_key.point.x() + \ - '%064x' % self.public_key.point.y() - return hex_der_key.decode('hex') - - def sign( self, hash, random_k ): - G = self.public_key.generator - n = G.order() - k = random_k % n - p1 = k * G - r = p1.x() - if r == 0: raise RuntimeError, "amazingly unlucky random number r" - s = ( inverse_mod( k, n ) * \ - ( hash + ( self.secret_multiplier * r ) % n ) ) % n - if s == 0: raise RuntimeError, "amazingly unlucky random number s" - return Signature( r, s ) - -class EC_KEY(object): - def __init__( self, secret ): - curve = CurveFp( _p, _a, _b ) - generator = Point( curve, _Gx, _Gy, _r ) - self.pubkey = Public_key( generator, generator * secret ) - self.privkey = Private_key( self.pubkey, secret ) - self.secret = secret - -# end of python-ecdsa code - -# pywallet openssl private key implementation - -def i2d_ECPrivateKey(pkey, compressed=False): - if compressed: - key = '3081d30201010420' + \ - '%064x' % pkey.secret + \ - 'a081a53081a2020101302c06072a8648ce3d0101022100' + \ - '%064x' % _p + \ - '3006040100040107042102' + \ - '%064x' % _Gx + \ - '022100' + \ - '%064x' % _r + \ - '020101a124032200' - else: - key = '308201130201010420' + \ - '%064x' % pkey.secret + \ - 'a081a53081a2020101302c06072a8648ce3d0101022100' + \ - '%064x' % _p + \ - '3006040100040107044104' + \ - '%064x' % _Gx + \ - '%064x' % _Gy + \ - '022100' + \ - '%064x' % _r + \ - '020101a144034200' - - return key.decode('hex') + i2o_ECPublicKey(pkey, compressed) - -def i2o_ECPublicKey(pkey, compressed=False): - # public keys are 65 bytes long (520 bits) - # 0x04 + 32-byte X-coordinate + 32-byte Y-coordinate - # 0x00 = point at infinity, 0x02 and 0x03 = compressed, 0x04 = uncompressed - # compressed keys: where is 0x02 if y is even and 0x03 if y is odd - if compressed: - if pkey.pubkey.point.y() & 1: - key = '03' + '%064x' % pkey.pubkey.point.x() - else: - key = '02' + '%064x' % pkey.pubkey.point.x() - else: - key = '04' + \ - '%064x' % pkey.pubkey.point.x() + \ - '%064x' % pkey.pubkey.point.y() - - return key.decode('hex') - -# bitcointools hashes and base58 implementation - -def hash_160(public_key): - md = hashlib.new('ripemd160') - md.update(hashlib.sha256(public_key).digest()) - return md.digest() - -def public_key_to_bc_address(public_key): - h160 = hash_160(public_key) - return hash_160_to_bc_address(h160) - -def hash_160_to_bc_address(h160): - vh160 = chr(addrtype) + h160 - h = Hash(vh160) - addr = vh160 + h[0:4] - return b58encode(addr) - -def bc_address_to_hash_160(addr): - bytes = b58decode(addr, 25) - return bytes[1:21] - -__b58chars = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz' -__b58base = len(__b58chars) - -def b58encode(v): - """ encode v, which is a string of bytes, to base58. - """ - - long_value = 0L - for (i, c) in enumerate(v[::-1]): - long_value += (256**i) * ord(c) - - result = '' - while long_value >= __b58base: - div, mod = divmod(long_value, __b58base) - result = __b58chars[mod] + result - long_value = div - result = __b58chars[long_value] + result - - # Bitcoin does a little leading-zero-compression: - # leading 0-bytes in the input become leading-1s - nPad = 0 - for c in v: - if c == '\0': nPad += 1 - else: break - - return (__b58chars[0]*nPad) + result - -def b58decode(v, length): - """ decode v into a string of len bytes - """ - long_value = 0L - for (i, c) in enumerate(v[::-1]): - long_value += __b58chars.find(c) * (__b58base**i) - - result = '' - while long_value >= 256: - div, mod = divmod(long_value, 256) - result = chr(mod) + result - long_value = div - result = chr(long_value) + result - - nPad = 0 - for c in v: - if c == __b58chars[0]: nPad += 1 - else: break - - result = chr(0)*nPad + result - if length is not None and len(result) != length: - return None - - return result - -# end of bitcointools base58 implementation - - -# address handling code - -def Hash(data): - return hashlib.sha256(hashlib.sha256(data).digest()).digest() - -def EncodeBase58Check(secret): - hash = Hash(secret) - return b58encode(secret + hash[0:4]) - -def DecodeBase58Check(sec): - vchRet = b58decode(sec, None) - secret = vchRet[0:-4] - csum = vchRet[-4:] - hash = Hash(secret) - cs32 = hash[0:4] - if cs32 != csum: - return None - else: - return secret - -def PrivKeyToSecret(privkey): - if len(privkey) == 279: - return privkey[9:9+32] - else: - return privkey[8:8+32] - -def SecretToASecret(secret, compressed=False): - vchIn = chr((addrtype+128)&255) + secret - if compressed: vchIn += '\01' - return EncodeBase58Check(vchIn) - -def ASecretToSecret(sec): - vch = DecodeBase58Check(sec) - if vch and vch[0] == chr((addrtype+128)&255): - return vch[1:] - else: - return False - -def regenerate_key(sec): - b = ASecretToSecret(sec) - if not b: - return False - b = b[0:32] - secret = int('0x' + b.encode('hex'), 16) - return EC_KEY(secret) - -def GetPubKey(pkey, compressed=False): - return i2o_ECPublicKey(pkey, compressed) - -def GetPrivKey(pkey, compressed=False): - return i2d_ECPrivateKey(pkey, compressed) - -def GetSecret(pkey): - return ('%064x' % pkey.secret).decode('hex') - -def is_compressed(sec): - b = ASecretToSecret(sec) - return len(b) == 33 - -# bitcointools wallet.dat handling code - -def create_env(db_dir): - db_env = DBEnv(0) - r = db_env.open(db_dir, (DB_CREATE|DB_INIT_LOCK|DB_INIT_LOG|DB_INIT_MPOOL|DB_INIT_TXN|DB_THREAD|DB_RECOVER)) - return db_env - -def parse_CAddress(vds): - d = {'ip':'0.0.0.0','port':0,'nTime': 0} - try: - d['nVersion'] = vds.read_int32() - d['nTime'] = vds.read_uint32() - d['nServices'] = vds.read_uint64() - d['pchReserved'] = vds.read_bytes(12) - d['ip'] = socket.inet_ntoa(vds.read_bytes(4)) - d['port'] = vds.read_uint16() - except: - pass - return d - -def deserialize_CAddress(d): - return d['ip']+":"+str(d['port']) - -def parse_BlockLocator(vds): - d = { 'hashes' : [] } - nHashes = vds.read_compact_size() - for i in xrange(nHashes): - d['hashes'].append(vds.read_bytes(32)) - return d - -def deserialize_BlockLocator(d): - result = "Block Locator top: "+d['hashes'][0][::-1].encode('hex_codec') - return result - -def parse_setting(setting, vds): - if setting[0] == "f": # flag (boolean) settings - return str(vds.read_boolean()) - elif setting[0:4] == "addr": # CAddress - d = parse_CAddress(vds) - return deserialize_CAddress(d) - elif setting == "nTransactionFee": - return vds.read_int64() - elif setting == "nLimitProcessors": - return vds.read_int32() - return 'unknown setting' - -class SerializationError(Exception): - """ Thrown when there's a problem deserializing or serializing """ - -class BCDataStream(object): - def __init__(self): - self.input = None - self.read_cursor = 0 - - def clear(self): - self.input = None - self.read_cursor = 0 - - def write(self, bytes): # Initialize with string of bytes - if self.input is None: - self.input = bytes - else: - self.input += bytes - - def map_file(self, file, start): # Initialize with bytes from file - self.input = mmap.mmap(file.fileno(), 0, access=mmap.ACCESS_READ) - self.read_cursor = start - def seek_file(self, position): - self.read_cursor = position - def close_file(self): - self.input.close() - - def read_string(self): - # Strings are encoded depending on length: - # 0 to 252 : 1-byte-length followed by bytes (if any) - # 253 to 65,535 : byte'253' 2-byte-length followed by bytes - # 65,536 to 4,294,967,295 : byte '254' 4-byte-length followed by bytes - # ... and the Bitcoin client is coded to understand: - # greater than 4,294,967,295 : byte '255' 8-byte-length followed by bytes of string - # ... but I don't think it actually handles any strings that big. - if self.input is None: - raise SerializationError("call write(bytes) before trying to deserialize") - - try: - length = self.read_compact_size() - except IndexError: - raise SerializationError("attempt to read past end of buffer") - - return self.read_bytes(length) - - def write_string(self, string): - # Length-encoded as with read-string - self.write_compact_size(len(string)) - self.write(string) - - def read_bytes(self, length): - try: - result = self.input[self.read_cursor:self.read_cursor+length] - self.read_cursor += length - return result - except IndexError: - raise SerializationError("attempt to read past end of buffer") - - return '' - - def read_boolean(self): return self.read_bytes(1)[0] != chr(0) - def read_int16(self): return self._read_num(' max_version: - print "Version mismatch (must be <= %d)" % max_version - exit(1) - -wallet_addrs = [i['addr'] for i in json_db['keys']] - -if opt.json: - data = [json.dumps(json_db, sort_keys=True, indent=4)] - ext,what = "json","json dump" - -elif opt.keys: - data = sorted([i['sec'] for i in json_db['keys']]) - ext,what = "keys","private keys" - -elif opt.addrs: - data = sorted([i['addr'] for i in json_db['keys']]) - ext,what = "addrs","addresses" - -elif opt.keysforaddrs: - from mmgen.util import get_lines_from_file - usr_addrs = set(get_lines_from_file(opt.keysforaddrs,"addresses",trim_comments=True)) - data = [i['sec'] for i in json_db['keys'] if i['addr'] in usr_addrs] - ext,what = "keys","private keys" - if len(data) < len(usr_addrs): - msg("Warning: not all requested keys found") - -len_arg = "%s" % len(wallet_addrs) \ - if len(data) == len(wallet_addrs) or ext == "json" \ - else "%s:%s" % (len(data),len(wallet_addrs)) - -from mmgen.util import make_chksum_8,write_data_to_file -wallet_id = make_chksum_8(str(sorted(wallet_addrs))) - -data = "\n".join(data) + "\n" - -# Output data -of = "wd_%s[%s].%s" % (wallet_id,len_arg,ext) -write_data_to_file(of, data, what, ask_overwrite=not opt.quiet) -# outfile, -# data, -# desc="data", -# ask_write=False, -# ask_write_prompt="", -# ask_write_default_yes=False, -# ask_overwrite=True, -# ask_tty=True, -# no_tty=False, -# silent=False diff --git a/mmgen/main_tool.py b/mmgen/main_tool.py index a8f2e681..1a7d673a 100755 --- a/mmgen/main_tool.py +++ b/mmgen/main_tool.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution -# Copyright (C)2013-2015 Philemon +# Copyright (C)2013-2016 Philemon # # 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 @@ -21,14 +21,12 @@ mmgen-tool: Perform various MMGen- and Bitcoin-related operations. Part of the MMGen suite """ -import sys -import mmgen.globalvars as g -import mmgen.opt as opt +from mmgen.common import * import mmgen.tool as tool opts_data = { - 'desc': "Perform various {pnm}- and Bitcoin-related operations".format(pnm=g.proj_name), - 'usage': "[opts] ", + 'desc': 'Perform various {pnm}- and Bitcoin-related operations'.format(pnm=g.proj_name), + 'usage': '[opts] ', 'options': """ -d, --outdir= d Specify an alternate directory 'd' for output -h, --help Print this help message @@ -46,27 +44,25 @@ command """.format(tool.cmd_help,g.prog_name) } -cmd_args = opt.opts.init(opts_data, +cmd_args = opts.init(opts_data, add_opts=[ - "no_keyconv", - "hidden_incog_input_params", - "in_fmt" + 'no_keyconv', + 'hidden_incog_input_params', + 'in_fmt' ]) if len(cmd_args) < 1: - opt.opts.usage() + opts.usage() sys.exit(1) command = cmd_args.pop(0) if command not in tool.cmd_data: - from mmgen.util import msg - msg("'%s': No such command" % command) - sys.exit(1) + die(1,"'%s': no such command" % command) if cmd_args and cmd_args[0] == '--help': tool.tool_usage(g.prog_name, command) - sys.exit(0) + sys.exit() args,kwargs = tool.process_args(g.prog_name, command, cmd_args) diff --git a/mmgen/main_txcreate.py b/mmgen/main_txcreate.py index 5c847606..92e3f420 100755 --- a/mmgen/main_txcreate.py +++ b/mmgen/main_txcreate.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution -# Copyright (C)2013-2015 Philemon +# Copyright (C)2013-2016 Philemon # # 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 @@ -21,19 +21,17 @@ mmgen-txcreate: Create a Bitcoin transaction to and from MMGen- or non-MMGen inputs and outputs """ -import sys from decimal import Decimal -import mmgen.globalvars as g -import mmgen.opt as opt -from mmgen.util import dmsg +from mmgen.common import * from mmgen.tx import * +from mmgen.term import get_char pnm = g.proj_name opts_data = { - 'desc': "Create a BTC transaction with outputs to specified addresses", - 'usage': "[opts] ... [change addr] [addr file] ...", + 'desc': 'Create a BTC transaction with outputs to specified addresses', + 'usage': '[opts] ... [change addr] [addr file] ...', 'options': """ -h, --help Print this help message -c, --comment-file= f Source the transaction's comment from file 'f' @@ -63,8 +61,8 @@ with no amount on the command line. wmsg = { 'too_many_acct_addresses': """ -ERROR: More than one address found for account: "%s". -Your "wallet.dat" file appears to have been altered by a non-{pnm} program. +ERROR: More than one address found for account: '%s'. +Your 'wallet.dat' file appears to have been altered by a non-{pnm} program. Please restore your tracking wallet from a backup or create a new one and re-import your addresses. """.strip().format(pnm=pnm), @@ -106,44 +104,44 @@ was specified. def format_unspent_outputs_for_printing(out,sort_info,total): - pfs = " %-4s %-67s %-34s %-12s %-13s %-8s %-10s %s" - pout = [pfs % ("Num","TX id,Vout","Address","{pnm} ID".format(pnm=pnm), - "Amount (BTC)","Conf.","Age (days)", "Comment")] + pfs = ' %-4s %-67s %-34s %-12s %-13s %-8s %-10s %s' + pout = [pfs % ('Num','TX id,Vout','Address','{pnm} ID'.format(pnm=pnm), + 'Amount (BTC)','Conf.','Age (days)', 'Comment')] for n,i in enumerate(out): - addr = "=" if i.skip == "addr" and "grouped" in sort_info else i.address - tx = " " * 63 + "=" \ - if i.skip == "txid" and "grouped" in sort_info else str(i.txid) + addr = '=' if i['skip'] == 'addr' and 'grouped' in sort_info else i['address'] + tx = ' ' * 63 + '=' \ + if i['skip'] == 'txid' and 'grouped' in sort_info else str(i['txid']) - s = pfs % (str(n+1)+")", tx+","+str(i.vout),addr, - i.mmid,i.amt,i.confirmations,i.days,i.comment) + s = pfs % (str(n+1)+')', tx+','+str(i['vout']),addr, + i['mmid'],i['amt'],i['confirmations'],i['days'],i['comment']) pout.append(s.rstrip()) return \ -"Unspent outputs ({} UTC)\nSort order: {}\n\n{}\n\nTotal BTC: {}\n".format( - make_timestr(), " ".join(sort_info), "\n".join(pout), total +'Unspent outputs ({} UTC)\nSort order: {}\n\n{}\n\nTotal BTC: {}\n'.format( + make_timestr(), ' '.join(sort_info), '\n'.join(pout), total ) def sort_and_view(unspent): - def s_amt(i): return i.amount - def s_txid(i): return "%s %03s" % (i.txid,i.vout) - def s_addr(i): return i.address - def s_age(i): return i.confirmations + def s_amt(i): return i['amount'] + def s_txid(i): return '%s %03s' % (i['txid'],i['vout']) + def s_addr(i): return i['address'] + def s_age(i): return i['confirmations'] def s_mmgen(i): - if i.mmid: - return "{}:{:>0{w}}".format( - *i.mmid.split(":"), w=g.mmgen_idx_max_digits) - else: return "G" + i.comment + if i['mmid']: + return '{}:{:>0{w}}'.format( + *i['mmid'].split(':'), w=g.mmgen_idx_max_digits) + else: return 'G' + i['comment'] - sort,group,show_days,show_mmaddr,reverse = "age",False,False,True,True + sort,group,show_days,show_mmaddr,reverse = 'age',False,False,True,True unspent.sort(key=s_age,reverse=reverse) # Reverse age sort by default - total = trim_exponent(sum([i.amount for i in unspent])) - max_acct_len = max([len(i.mmid+" "+i.comment) for i in unspent]) + total = trim_exponent(sum([i['amount'] for i in unspent])) + max_acct_len = max([len(i['mmid']+' '+i['comment']) for i in unspent]) - hdr_fmt = "UNSPENT OUTPUTS (sort order: %s) Total BTC: %s" + hdr_fmt = 'UNSPENT OUTPUTS (sort order: %s) Total BTC: %s' options_msg = """ Sort options: [t]xid, [a]mount, a[d]dress, [A]ge, [r]everse, [M]mgen addr Display options: show [D]ays, [g]roup, show [m]mgen addr, r[e]draw screen @@ -151,85 +149,84 @@ Display options: show [D]ays, [g]roup, show [m]mgen addr, r[e]draw screen prompt = \ "('q' = quit sorting, 'p' = print to file, 'v' = pager view, 'w' = wide view): " - mmid_w = max(len(i.mmid) for i in unspent) + mmid_w = max(len(i['mmid']) for i in unspent) from copy import deepcopy from mmgen.term import get_terminal_size - written_to_file_msg = "" - msg("") + written_to_file_msg = '' + msg('') while True: cols = get_terminal_size()[0] if cols < g.min_screen_width: - msg( - "{pnl}-txcreate requires a screen at least {w} characters wide".format( + die(2, + '{pnl}-txcreate requires a screen at least {w} characters wide'.format( pnl=pnm.lower(),w=g.min_screen_width)) - sys.exit(2) addr_w = min(34+((1+max_acct_len) if show_mmaddr else 0),cols-46) acct_w = min(max_acct_len, max(24,int(addr_w-10))) btaddr_w = addr_w - acct_w - 1 tx_w = max(11,min(64, cols-addr_w-32)) - txdots = "..." if tx_w < 64 else "" - fs = " %-4s %-" + str(tx_w) + "s %-2s %-" + str(addr_w) + "s %-13s %-s" - table_hdr = fs % ("Num","TX id Vout","","Address","Amount (BTC)", - "Age(d)" if show_days else "Conf.") + txdots = ('','...')[tx_w < 64] + fs = ' %-4s %-' + str(tx_w) + 's %-2s %-' + str(addr_w) + 's %-13s %-s' + table_hdr = fs % ('Num','TX id Vout','','Address','Amount (BTC)', + ('Conf.','Age(d)')[show_days]) unsp = deepcopy(unspent) - for i in unsp: i.skip = "" - if group and (sort == "address" or sort == "txid"): + for i in unsp: i['skip'] = '' + if group and (sort == 'address' or sort == 'txid'): for a,b in [(unsp[i],unsp[i+1]) for i in range(len(unsp)-1)]: - if sort == "address" and a.address == b.address: b.skip = "addr" - elif sort == "txid" and a.txid == b.txid: b.skip = "txid" + if sort == 'address' and a['address'] == b['address']: b['skip'] = 'addr' + elif sort == 'txid' and a['txid'] == b['txid']: b['skip'] = 'txid' for i in unsp: - amt = str(trim_exponent(i.amount)) - lfill = 3 - len(amt.split(".")[0]) if "." in amt else 3 - len(amt) - i.amt = " "*lfill + amt - i.days = int(i.confirmations * g.mins_per_block / (60*24)) - i.age = i.days if show_days else i.confirmations + amt = str(trim_exponent(i['amount'])) + lfill = 3 - len(amt.split('.')[0]) if '.' in amt else 3 - len(amt) + i['amt'] = ' '*lfill + amt + i['days'] = int(i['confirmations'] * g.mins_per_block / (60*24)) + i['age'] = i['days'] if show_days else i['confirmations'] - if i.skip == "addr": - i.addr = "|" + "." * 33 + addr_disp = (i['address'],'|' + '.'*33)[i['skip']=='addr'] + mmid_disp = (i['mmid'],'.'*len(i['mmid']))[i['skip']=='addr'] + + if show_mmaddr: + dots = ('','..')[btaddr_w < len(i['address'])] + i['addr'] = '%s%s %s' % ( + addr_disp[:btaddr_w-len(dots)], + dots, ( + ('{:<{w}} '.format(mmid_disp,w=mmid_w) if i['mmid'] else '') + + i['comment'])[:acct_w] + ) else: - if show_mmaddr: - dots = ".." if btaddr_w < len(i.address) else "" - i.addr = "%s%s %s" % ( - i.address[:btaddr_w-len(dots)], - dots, ( - ("{:<{w}} ".format(i.mmid,w=mmid_w) if i.mmid else "") - + i.comment)[:acct_w] - ) - else: - i.addr = i.address + i['addr'] = addr_disp - i.tx = " " * (tx_w-4) + "|..." if i.skip == "txid" \ - else i.txid[:tx_w-len(txdots)]+txdots + i['tx'] = ' ' * (tx_w-4) + '|...' if i['skip'] == 'txid' \ + else i['txid'][:tx_w-len(txdots)]+txdots - sort_info = ["reverse"] if reverse else [] - sort_info.append(sort if sort else "unsorted") - if group and (sort == "address" or sort == "txid"): - sort_info.append("grouped") + sort_info = ([],['reverse'])[reverse] + sort_info.append(sort if sort else 'unsorted') + if group and (sort == 'address' or sort == 'txid'): + sort_info.append('grouped') - out = [hdr_fmt % (" ".join(sort_info), total), table_hdr] - out += [fs % (str(n+1)+")",i.tx,i.vout,i.addr,i.amt,i.age) + out = [hdr_fmt % (' '.join(sort_info), total), table_hdr] + out += [fs % (str(n+1)+')',i['tx'],i['vout'],i['addr'],i['amt'],i['age']) for n,i in enumerate(unsp)] - msg("\n".join(out) +"\n\n" + written_to_file_msg + options_msg) - written_to_file_msg = "" + msg('\n'.join(out) +'\n\n' + written_to_file_msg + options_msg) + written_to_file_msg = '' skip_prompt = False while True: - reply = get_char(prompt, immed_chars="atDdAMrgmeqpvw") + reply = get_char(prompt, immed_chars='atDdAMrgmeqpvw') - if reply == 'a': unspent.sort(key=s_amt); sort = "amount" - elif reply == 't': unspent.sort(key=s_txid); sort = "txid" + if reply == 'a': unspent.sort(key=s_amt); sort = 'amount' + elif reply == 't': unspent.sort(key=s_txid); sort = 'txid' elif reply == 'D': show_days = not show_days - elif reply == 'd': unspent.sort(key=s_addr); sort = "address" - elif reply == 'A': unspent.sort(key=s_age); sort = "age" + elif reply == 'd': unspent.sort(key=s_addr); sort = 'address' + elif reply == 'A': unspent.sort(key=s_age); sort = 'age' elif reply == 'M': - unspent.sort(key=s_mmgen); sort = "mmgen" + unspent.sort(key=s_mmgen); sort = 'mmgen' show_mmaddr = True elif reply == 'r': unspent.reverse() @@ -240,23 +237,23 @@ Display options: show [D]ays, [g]roup, show [m]mgen addr, r[e]draw screen elif reply == 'q': pass elif reply == 'p': d = format_unspent_outputs_for_printing(unsp,sort_info,total) - of = "listunspent[%s].out" % ",".join(sort_info) - write_data_to_file(of,d,"unspent outputs listing") + of = 'listunspent[%s].out' % ','.join(sort_info) + write_data_to_file(of,d,'unspent outputs listing') written_to_file_msg = "Data written to '%s'\n\n" % of elif reply == 'v': - do_pager("\n".join(out)) + do_pager('\n'.join(out)) continue elif reply == 'w': data = format_unspent_outputs_for_printing(unsp,sort_info,total) do_pager(data) continue else: - msg("\nInvalid input") + msg('\nInvalid input') continue break - msg("\n") + msg('\n') if reply == 'q': break return tuple(unspent) @@ -274,24 +271,23 @@ def select_outputs(unspent,prompt): if not selected: continue if selected[-1] > len(unspent): - msg("Inputs must be less than %s" % len(unspent)) + msg('Inputs must be less than %s' % len(unspent)) continue return selected def mmaddr2btcaddr_unspent(unspent,mmaddr): - vmsg_r("Searching for {pnm} address {m} in wallet...".format(pnm=pnm,m=mmaddr)) - m = [u for u in unspent if u.mmid == mmaddr] + vmsg_r('Searching for {pnm} address {m} in wallet...'.format(pnm=pnm,m=mmaddr)) + m = [u for u in unspent if u['mmid'] == mmaddr] if len(m) == 0: - vmsg("not found") - return "","" + vmsg('not found') + return '','' elif len(m) > 1: - msg(wmsg['too_many_acct_addresses'] % acct); sys.exit(2) + die(2,wmsg['too_many_acct_addresses'] % acct) else: - vmsg("success (%s)" % m[0].address) + vmsg('success (%s)' % m[0].address) return m[0].address, m[0].comment - sys.exit() def mmaddr2btcaddr(c,mmaddr,ail_w,ail_f): @@ -304,14 +300,12 @@ def mmaddr2btcaddr(c,mmaddr,ail_w,ail_f): btcaddr = ail_f.mmaddr2btcaddr(mmaddr) if btcaddr: msg(wmsg['addr_in_addrfile_only'].format(mmgenaddr=mmaddr)) - if not keypress_confirm("Continue anyway?"): + if not keypress_confirm('Continue anyway?'): sys.exit(1) else: - msg(wmsg['addr_not_found'].format(pnm=pnm,mmgenaddr=mmaddr)) - sys.exit(2) + die(2,wmsg['addr_not_found'].format(pnm=pnm,mmgenaddr=mmaddr)) else: - msg(wmsg['addr_not_found_no_addrfile'].format(pnm=pnm,mmgenaddr=mmaddr)) - sys.exit(2) + die(2,wmsg['addr_not_found_no_addrfile'].format(pnm=pnm,mmgenaddr=mmaddr)) return btcaddr @@ -323,7 +317,7 @@ def make_b2m_map(inputs_data,tx_out,ail_w,ail_f): d.update(ail_f.make_reverse_dict(tx_out.keys())) return d -cmd_args = opt.opts.init(opts_data) +cmd_args = opts.init(opts_data) if opt.comment_file: comment = get_tx_comment_from_file(opt.comment_file) @@ -333,7 +327,7 @@ c = connect_to_bitcoind() if not opt.info: do_license_msg(immed=True) - tx_out,change_addr = {},"" + tx_out,change_addr = {},'' addrfiles = [a for a in cmd_args if get_extension(a) == g.addrfile_ext] cmd_args = set(cmd_args) - set(addrfiles) @@ -347,131 +341,122 @@ if not opt.info: ail_w = AddrInfoList(bitcoind_connection=c) for a in cmd_args: - if "," in a: - a1,a2 = split2(a,",") + if ',' in a: + a1,a2 = split2(a,',') if is_btc_addr(a1): btcaddr = a1 elif is_mmgen_addr(a1): btcaddr = mmaddr2btcaddr(c,a1,ail_w,ail_f) else: - msg("%s: unrecognized subargument in argument '%s'" % (a1,a)) - sys.exit(2) + die(2,"%s: unrecognized subargument in argument '%s'" % (a1,a)) ret = normalize_btc_amt(a2) if ret: tx_out[btcaddr] = ret else: - msg("%s: invalid amount in argument '%s'" % (a2,a)) - sys.exit(2) + die(2,"%s: invalid amount in argument '%s'" % (a2,a)) elif is_mmgen_addr(a) or is_btc_addr(a): if change_addr: - msg("ERROR: More than one change address specified: %s, %s" % + die(2,'ERROR: More than one change address specified: %s, %s' % (change_addr, a)) - sys.exit(2) - change_addr = a if is_btc_addr(a) else \ - mmaddr2btcaddr(c,a,ail_w,ail_f) + change_addr = a if is_btc_addr(a) else mmaddr2btcaddr(c,a,ail_w,ail_f) tx_out[change_addr] = 0 else: - msg("%s: unrecognized argument" % a) - sys.exit(2) + die(2,'%s: unrecognized argument' % a) if not tx_out: - msg("At least one output must be specified on the command line") - sys.exit(2) + die(2,'At least one output must be specified on the command line') - tx_fee = opt.tx_fee if opt.tx_fee else g.tx_fee + tx_fee = (g.tx_fee,opt.tx_fee)[bool(opt.tx_fee)] tx_fee = normalize_btc_amt(tx_fee) if tx_fee > g.max_tx_fee: - msg("Transaction fee too large: %s > %s" % (tx_fee,g.max_tx_fee)) - sys.exit(2) + die(2,'Transaction fee too large: %s > %s' % (tx_fee,g.max_tx_fee)) if g.bogus_wallet_data: # for debugging purposes only - import mmgen.rpc.data us = eval(get_data_from_file(g.bogus_wallet_data)) else: us = c.listunspent() -# write_data_to_file("bogus_unspent.json", repr(us), "bogus unspent data") +# write_data_to_file('bogus_unspent.json', repr(us), 'bogus unspent data') # sys.exit() -if not us: msg(wmsg['no_spendable_outputs']); sys.exit(2) +if not us: + die(2,wmsg['no_spendable_outputs']) for o in us: - o.mmid,o.comment = parse_mmgen_label(o.account) - del o.account + o['mmid'],o['comment'] = parse_mmgen_label(o['account']) + del o['account'] unspent = sort_and_view(us) -total = trim_exponent(sum([i.amount for i in unspent])) +total = trim_exponent(sum([i['amount'] for i in unspent])) -msg("Total unspent: %s BTC (%s outputs)" % (total, len(unspent))) -if opt.info: sys.exit(0) +msg('Total unspent: %s BTC (%s outputs)' % (total, len(unspent))) +if opt.info: sys.exit() send_amt = sum([tx_out[i] for i in tx_out.keys()]) -msg("Total amount to spend: %s%s" % ( - (send_amt or "Unknown")," BTC" if send_amt else "")) +msg('Total amount to spend: %s' % ('%s BTC'%send_amt,'Unknown')[bool(send_amt)]) while True: sel_nums = select_outputs(unspent, - "Enter a range or space-separated list of outputs to spend: ") - msg("Selected output%s: %s" % - (("" if len(sel_nums) == 1 else "s"), " ".join(str(i) for i in sel_nums)) - ) + 'Enter a range or space-separated list of outputs to spend: ') + msg('Selected output%s: %s' % ( + ('s','')[len(sel_nums)==1], + ' '.join(str(i) for i in sel_nums) + )) sel_unspent = [unspent[i-1] for i in sel_nums] - mmaddrs = set([i.mmid for i in sel_unspent]) - mmaddrs.discard("") + mmaddrs = set([i['mmid'] for i in sel_unspent]) + mmaddrs.discard('') if mmaddrs and len(mmaddrs) < len(sel_unspent): - msg(wmsg['mixed_inputs'] % ", ".join(sorted(mmaddrs))) - if not keypress_confirm("Accept?"): + msg(wmsg['mixed_inputs'] % ', '.join(sorted(mmaddrs))) + if not keypress_confirm('Accept?'): continue - total_in = trim_exponent(sum([i.amount for i in sel_unspent])) + total_in = trim_exponent(sum([i['amount'] for i in sel_unspent])) change = trim_exponent(total_in - (send_amt + tx_fee)) if change >= 0: - prompt = "Transaction produces %s BTC in change. OK?" % change + prompt = 'Transaction produces %s BTC in change. OK?' % change if keypress_confirm(prompt,default_yes=True): break else: msg(wmsg['not_enough_btc'] % change) if change > 0 and not change_addr: - msg(wmsg['throwaway_change'] % change) - sys.exit(2) + die(2,wmsg['throwaway_change'] % change) if change_addr in tx_out and not change: - msg("Warning: Change address will be unused as transaction produces no change") + msg('Warning: Change address will be unused as transaction produces no change') del tx_out[change_addr] for k,v in tx_out.items(): tx_out[k] = float(v) if change > 0: tx_out[change_addr] = float(change) -tx_in = [{"txid":i.txid, "vout":i.vout} for i in sel_unspent] +tx_in = [{'txid':i['txid'], 'vout':i['vout']} for i in sel_unspent] -dmsg("tx_in: %s\ntx_out: %s" % (repr(tx_in),repr(tx_out))) +dmsg('tx_in: %s\ntx_out: %s' % (repr(tx_in),repr(tx_out))) if opt.comment_file: - if keypress_confirm("Edit comment?",False): + if keypress_confirm('Edit comment?',False): comment = get_tx_comment_from_user(comment) else: - if keypress_confirm("Add a comment to transaction?",False): + if keypress_confirm('Add a comment to transaction?',False): comment = get_tx_comment_from_user() else: comment = False tx_hex = c.createrawtransaction(tx_in,tx_out) -qmsg("Transaction successfully created") +qmsg('Transaction successfully created') amt = send_amt or change tx_id = make_chksum_6(unhexlify(tx_hex)).upper() metadata = tx_id, amt, make_timestamp() -sel_unspent = [i.__dict__ for i in sel_unspent] b2m_map = make_b2m_map(sel_unspent,tx_out,ail_w,ail_f) -prompt_and_view_tx_data(c,"View decoded transaction?", +prompt_and_view_tx_data(c,'View decoded transaction?', sel_unspent,tx_hex,b2m_map,comment,metadata) -outfile = "tx_%s[%s].%s" % (tx_id,amt,g.rawtx_ext) -data = make_tx_data("{} {} {}".format(*metadata), +outfile = 'tx_%s[%s].%s' % (tx_id,amt,g.rawtx_ext) +data = make_tx_data('{} {} {}'.format(*metadata), tx_hex,sel_unspent,b2m_map,comment) -write_data_to_file(outfile,data,"transaction",ask_write_default_yes=False) +write_data_to_file(outfile,data,'transaction',ask_write_default_yes=False) diff --git a/mmgen/main_txsend.py b/mmgen/main_txsend.py index 3623f247..495e045b 100755 --- a/mmgen/main_txsend.py +++ b/mmgen/main_txsend.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution -# Copyright (C)2013-2015 Philemon +# Copyright (C)2013-2016 Philemon # # 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 @@ -20,17 +20,13 @@ mmgen-txsend: Broadcast a transaction signed by 'mmgen-txsign' to the network """ -import sys - -import mmgen.globalvars as g -import mmgen.opt as opt +from mmgen.common import * from mmgen.tx import * -from mmgen.util import * opts_data = { - 'desc': "Send a Bitcoin transaction signed by {pnm}-txsign".format( + 'desc': 'Send a Bitcoin transaction signed by {pnm}-txsign'.format( pnm=g.proj_name.lower()), - 'usage': "[opts] ", + 'usage': '[opts] ', 'options': """ -h, --help Print this help message -d, --outdir= d Specify an alternate directory 'd' for output @@ -38,17 +34,17 @@ opts_data = { """ } -cmd_args = opt.opts.init(opts_data) +cmd_args = opts.init(opts_data) if len(cmd_args) == 1: infile = cmd_args[0]; check_infile(infile) -else: opt.opts.usage() +else: opts.usage() # Begin execution do_license_msg() -tx_data = get_lines_from_file(infile,"signed transaction data") +tx_data = get_lines_from_file(infile,'signed transaction data') metadata,tx_hex,inputs_data,b2m_map,comment = parse_tx_file(tx_data,infile) @@ -56,32 +52,31 @@ qmsg("Signed transaction file '%s' is valid" % infile) c = connect_to_bitcoind() -prompt_and_view_tx_data(c,"View transaction data?", +prompt_and_view_tx_data(c,'View transaction data?', inputs_data,tx_hex,b2m_map,comment,metadata) -if keypress_confirm("Edit transaction comment?"): +if keypress_confirm('Edit transaction comment?'): comment = get_tx_comment_from_user(comment) - data = make_tx_data("{} {} {}".format(*metadata), tx_hex, + data = make_tx_data('{} {} {}'.format(*metadata), tx_hex, inputs_data, b2m_map, comment) - write_data_to_file(infile,data,"signed transaction with edited comment") + write_data_to_file(infile,data,'signed transaction with edited comment') warn = "Once this transaction is sent, there's no taking it back!" -action = "broadcast this transaction to the network" -expect = "YES, I REALLY WANT TO DO THIS" +action = 'broadcast this transaction to the network' +expect = 'YES, I REALLY WANT TO DO THIS' -if opt.quiet: warn,expect = "","YES" +if opt.quiet: warn,expect = '','YES' confirm_or_exit(warn, action, expect) -msg("Sending transaction") +msg('Sending transaction') try: tx_id = c.sendrawtransaction(tx_hex) except: - msg("Unable to send transaction") - sys.exit(3) + die(3,'Unable to send transaction') -msg("Transaction sent: %s" % tx_id) +msg('Transaction sent: %s' % tx_id) -of = "tx_{}[{}].txid".format(*metadata[:2]) -write_data_to_file(of, tx_id+"\n","transaction ID",ask_overwrite=True) +of = 'tx_{}[{}].txid'.format(*metadata[:2]) +write_data_to_file(of, tx_id+'\n','transaction ID',ask_overwrite=True) diff --git a/mmgen/main_txsign.py b/mmgen/main_txsign.py index 2c4d9faf..27690b32 100755 --- a/mmgen/main_txsign.py +++ b/mmgen/main_txsign.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution -# Copyright (C)2013-2015 Philemon +# Copyright (C)2013-2016 Philemon # # 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 @@ -20,21 +20,16 @@ mmgen-txsign: Sign a transaction generated by 'mmgen-txcreate' """ -import sys - -import mmgen.globalvars as g -import mmgen.opt as opt +from mmgen.common import * from mmgen.tx import * -from mmgen.util import do_license_msg,dmsg from mmgen.seed import SeedSource - pnm = g.proj_name pnl = pnm.lower() opts_data = { - 'desc': "Sign Bitcoin transactions generated by {pnl}-txcreate".format(pnl=pnl), - 'usage': "[opts] ... [seed source]...", + 'desc': 'Sign Bitcoin transactions generated by {pnl}-txcreate'.format(pnl=pnl), + 'usage': '[opts] ... [seed source]...', 'options': """ -h, --help Print this help message. -b, --brain-params=l,p Use seed length 'l' and hash preset 'p' for brain- @@ -101,7 +96,7 @@ Seed data supplied in files must have the following extensions: FMT CODES: {f} """.format( - f="\n ".join(SeedSource.format_fmt_codes().splitlines()), + f='\n '.join(SeedSource.format_fmt_codes().splitlines()), g=g,pnm=pnm,pnl=pnl ) } @@ -126,12 +121,11 @@ def get_seed_for_seed_id(seed_id,infiles,saved_seeds): if infiles: ss = SeedSource(infiles.pop(0),ignore_in_fmt=True) elif opt.in_fmt: - qmsg("Need seed data for Seed ID %s" % seed_id) + qmsg('Need seed data for Seed ID %s' % seed_id) ss = SeedSource() - msg("User input produced Seed ID %s" % make_chksum_8(seed)) + msg('User input produced Seed ID %s' % make_chksum_8(seed)) else: - msg("ERROR: No seed source found for Seed ID: %s" % seed_id) - sys.exit(2) + die(2,'ERROR: No seed source found for Seed ID: %s' % seed_id) saved_seeds[ss.seed.sid] = ss.seed.data @@ -141,7 +135,7 @@ def get_seed_for_seed_id(seed_id,infiles,saved_seeds): def get_keys_for_mmgen_addrs(mmgen_addrs,infiles,saved_seeds): seed_ids = set([i[:8] for i in mmgen_addrs]) - vmsg("Need seed%s: %s" % (suf(seed_ids,"k")," ".join(seed_ids))) + vmsg('Need seed%s: %s' % (suf(seed_ids,'k'),' '.join(seed_ids))) d = [] from mmgen.addr import generate_addrs @@ -149,25 +143,25 @@ def get_keys_for_mmgen_addrs(mmgen_addrs,infiles,saved_seeds): # Returns only if seed is found seed = get_seed_for_seed_id(seed_id,infiles,saved_seeds) addr_nums = [int(i[9:]) for i in mmgen_addrs if i[:8] == seed_id] - opt.gen_what = "ka" - ai = generate_addrs(seed,addr_nums,source="txsign") - d += [("{}:{}".format(seed_id,e.idx),e.addr,e.wif) for e in ai.addrdata] + opt.gen_what = 'ka' + ai = generate_addrs(seed,addr_nums,source='txsign') + d += [('{}:{}'.format(seed_id,e.idx),e.addr,e.wif) for e in ai.addrdata] return d def sign_transaction(c,tx_hex,tx_num_str,sig_data,keys=None): if keys: - qmsg("Passing %s key%s to bitcoind" % (len(keys),suf(keys,"k"))) - dmsg("Keys:\n %s" % "\n ".join(keys)) + qmsg('Passing %s key%s to bitcoind' % (len(keys),suf(keys,'k'))) + dmsg('Keys:\n %s' % '\n '.join(keys)) - msg_r("Signing transaction{}...".format(tx_num_str)) - from mmgen.rpc import exceptions + msg_r('Signing transaction{}...'.format(tx_num_str)) +# from mmgen.rpc import exceptions try: sig_tx = c.signrawtransaction(tx_hex,sig_data,keys) - except exceptions.InvalidAddressOrKey: - msg("failed\nInvalid address or key") - sys.exit(3) +# except exceptions.InvalidAddressOrKey: + except: # TODO + die(3,'failed\nInvalid address or key') return sig_tx @@ -177,26 +171,27 @@ def sign_tx_with_bitcoind_wallet(c,tx_hex,tx_num_str,sig_data,keys): try: sig_tx = sign_transaction(c,tx_hex,tx_num_str,sig_data,keys) except: - from mmgen.rpc import exceptions - msg("Using keys in wallet.dat as per user request") - prompt = "Enter passphrase for bitcoind wallet: " +# from mmgen.rpc import exceptions + msg('Using keys in wallet.dat as per user request') + prompt = 'Enter passphrase for bitcoind wallet: ' while True: passwd = get_bitcoind_passphrase(prompt) try: c.walletpassphrase(passwd, 9999) - except exceptions.WalletPassphraseIncorrect: - msg("Passphrase incorrect") +# except exceptions.WalletPassphraseIncorrect: + except: # TODO + msg('Passphrase incorrect (or some other error)') else: - msg("Passphrase OK"); break + msg('Passphrase OK'); break sig_tx = sign_transaction(c,tx_hex,tx_num_str,sig_data,keys) - msg("Locking wallet") + msg('Locking wallet') try: c.walletlock() except: - msg("Failed to lock wallet") + msg('Failed to lock wallet') return sig_tx @@ -204,55 +199,57 @@ def sign_tx_with_bitcoind_wallet(c,tx_hex,tx_num_str,sig_data,keys): def check_maps_from_seeds(maplist,desc,infiles,saved_seeds,return_keys=False): if not maplist: return [] - qmsg("Checking {pnm} -> BTC address mappings for {w}s (from seed(s))".format( + qmsg('Checking {pnm} -> BTC address mappings for {w}s (from seed(s))'.format( pnm=pnm,w=desc)) d = get_keys_for_mmgen_addrs(maplist.keys(),infiles,saved_seeds) # 0=mmaddr 1=addr 2=wif m = dict([(e[0],e[1]) for e in d]) for a,b in zip(sorted(m),sorted(maplist)): if a != b: - al,bl = "generated seed:","tx file:" - msg(wmsg['mm2btc_mapping_error'] % (al,a,m[a],bl,b,maplist[b])) - sys.exit(3) + al,bl = 'generated seed:','tx file:' + die(3,wmsg['mm2btc_mapping_error'] % (al,a,m[a],bl,b,maplist[b])) if return_keys: ret = [e[2] for e in d] - vmsg("Added %s wif key%s from seeds" % (len(ret),suf(ret,"k"))) + vmsg('Added %s wif key%s from seeds' % (len(ret),suf(ret,'k'))) return ret def missing_keys_errormsg(addrs): Msg(""" A key file must be supplied (or use the '--use-wallet-dat' option) for the following non-{pnm} address{suf}:\n {l}""".format( - pnm=pnm, suf=suf(addrs,"a"), l="\n ".join(addrs)).strip()) + pnm=pnm, suf=suf(addrs,'a'), l='\n '.join(addrs)).strip()) def parse_mmgen_keyaddr_file(): from mmgen.addr import AddrInfo ai = AddrInfo(opt.mmgen_keys_from_file,has_keys=True) - vmsg("Found %s wif key%s for Seed ID %s" % - (ai.num_addrs, suf(ai.num_addrs,"k"), ai.seed_id)) + vmsg('Found %s wif key%s for Seed ID %s' % + (ai.num_addrs, suf(ai.num_addrs,'k'), ai.seed_id)) # idx: (0=addr, 1=comment 2=wif) -> mmaddr: (0=addr, 1=wif) return dict( - [("%s:%s"%(ai.seed_id,e.idx), (e.addr,e.wif)) for e in ai.addrdata]) + [('%s:%s'%(ai.seed_id,e.idx), (e.addr,e.wif)) for e in ai.addrdata]) def parse_keylist(from_file): fn = opt.keys_from_file from mmgen.crypto import mmgen_decrypt_file_maybe - dec = mmgen_decrypt_file_maybe(fn,"non-{} keylist file".format(pnm)) - # Remove possible dups from key-address file - keys_all = set(remove_comments(dec.splitlines())) # DOS-safe + dec = mmgen_decrypt_file_maybe(fn,'non-{} keylist file'.format(pnm)) + keys_all = remove_comments(dec.splitlines()) # DOS-safe + # Key list could be bitcoind dump, so remove first space and everything following + keys_all = [k.split()[0] for k in keys_all] + keys_all = set(keys_all) # Remove possible dups + dmsg(repr(keys_all)) d = from_file['mmdata'] kawifs = [d[k][1] for k in d.keys()] keys = [k for k in keys_all if k not in kawifs] removed = len(keys_all) - len(keys) - if removed: vmsg(wmsg['removed_dups'] % (removed,suf(removed,"k"))) + if removed: vmsg(wmsg['removed_dups'] % (removed,suf(removed,'k'))) addrs = [] wif2addr_f = get_wif2addr_f() for n,k in enumerate(keys,1): - qmsg_r("\rGenerating addresses from keylist: %s/%s" % (n,len(keys))) + qmsg_r('\rGenerating addresses from keylist: %s/%s' % (n,len(keys))) addrs.append(wif2addr_f(k)) - qmsg("\rGenerated addresses from keylist: %s/%s " % (n,len(keys))) + qmsg('\rGenerated addresses from keylist: %s/%s ' % (n,len(keys))) return dict(zip(addrs,keys)) @@ -260,7 +257,7 @@ def parse_keylist(from_file): # Check inputs and outputs maps against key-address file, deleting entries: def check_maps_from_kafile(imap,desc,kadata,return_keys=False): if not kadata: return [] - qmsg("Checking {pnm} -> BTC address mappings for {w}s (from key-address file)".format(pnm=pnm,w=desc)) + qmsg('Checking {pnm} -> BTC address mappings for {w}s (from key-address file)'.format(pnm=pnm,w=desc)) ret = [] for k in imap.keys(): if k in kadata.keys(): @@ -268,12 +265,11 @@ def check_maps_from_kafile(imap,desc,kadata,return_keys=False): del imap[k] ret += [kadata[k][1]] else: - kl,il = "key-address file:","tx file:" - msg(wmsg['mm2btc_mapping_error']%(kl,k,kadata[k][0],il,k,imap[k])) - sys.exit(2) - if ret: vmsg("Removed %s address%s from %ss map" % (len(ret),suf(ret,"a"),desc)) + kl,il = 'key-address file:','tx file:' + die(2,wmsg['mm2btc_mapping_error']%(kl,k,kadata[k][0],il,k,imap[k])) + if ret: vmsg('Removed %s address%s from %ss map' % (len(ret),suf(ret,'a'),desc)) if return_keys: - vmsg("Added %s wif key%s from %ss map" % (len(ret),suf(ret,"k"),desc)) + vmsg('Added %s wif key%s from %ss map' % (len(ret),suf(ret,'k'),desc)) return ret @@ -283,16 +279,16 @@ def get_keys_from_keylist(kldata,other_addrs): if addr in kldata.keys(): ret += [kldata[addr]] other_addrs.remove(addr) - vmsg("Added %s wif key%s from user-supplied keylist" % - (len(ret),suf(ret,"k"))) + vmsg('Added %s wif key%s from user-supplied keylist' % + (len(ret),suf(ret,'k'))) return ret -infiles = opt.opts.init(opts_data,add_opts=["b16"]) +infiles = opts.init(opts_data,add_opts=['b16']) #if opt.from_incog_hex or opt.from_incog_hidden: opt.from_incog = True -if not infiles: opt.opts.usage() +if not infiles: opts.usage() for i in infiles: check_infile(i) c = connect_to_bitcoind() @@ -310,28 +306,26 @@ if opt.mmgen_keys_from_file: if opt.keys_from_file: from_file['kldata'] = parse_keylist(from_file) or {} -tx_num_str = "" +tx_num_str = '' for tx_num,tx_file in enumerate(tx_files,1): if len(tx_files) > 1: - msg("\nTransaction #%s of %s:" % (tx_num,len(tx_files))) - tx_num_str = " #%s" % tx_num + msg('\nTransaction #%s of %s:' % (tx_num,len(tx_files))) + tx_num_str = ' #%s' % tx_num - m = "" if opt.tx_id else "transaction data" + m = ('transaction data','')[bool(opt.tx_id)] tx_data = get_lines_from_file(tx_file,m) metadata,tx_hex,inputs_data,b2m_map,comment = parse_tx_file(tx_data,tx_file) vmsg("Successfully opened transaction file '%s'" % tx_file) - if opt.tx_id: - msg(metadata[0]) - sys.exit(0) + if opt.tx_id: die(0,metadata[0]) if opt.info or opt.terse_info: view_tx_data(c,inputs_data,tx_hex,b2m_map,comment,metadata,pause=False, terse=opt.terse_info) - sys.exit(0) + sys.exit() - prompt_and_view_tx_data(c,"View data for transaction{}?".format(tx_num_str), + prompt_and_view_tx_data(c,'View data for transaction{}?'.format(tx_num_str), inputs_data,tx_hex,b2m_map,comment,metadata) # Start @@ -347,20 +341,20 @@ for tx_num,tx_file in enumerate(tx_files,1): omap = dict([(j[0],i) for i,j in b2m_map.items()]) sids = set([i[:8] for i in imap.keys()]) - keys += check_maps_from_kafile(imap,"input",from_file['mmdata'],True) - check_maps_from_kafile(omap,"output",from_file['mmdata']) + keys += check_maps_from_kafile(imap,'input',from_file['mmdata'],True) + check_maps_from_kafile(omap,'output',from_file['mmdata']) - keys += check_maps_from_seeds(imap,"input",seed_files,saved_seeds,True) - check_maps_from_seeds(omap,"output",seed_files,saved_seeds) + keys += check_maps_from_seeds(imap,'input',seed_files,saved_seeds,True) + check_maps_from_seeds(omap,'output',seed_files,saved_seeds) extra_sids = set(saved_seeds.keys()) - sids if extra_sids: - msg("Unused Seed ID%s: %s" % - (suf(extra_sids,"k")," ".join(extra_sids))) + msg('Unused Seed ID%s: %s' % + (suf(extra_sids,'k'),' '.join(extra_sids))) # Begin signing sig_data = [ - {"txid":i['txid'],"vout":i['vout'],"scriptPubKey":i['scriptPubKey']} + {'txid':i['txid'],'vout':i['vout'],'scriptPubKey':i['scriptPubKey']} for i in inputs_data] if opt.use_wallet_dat: @@ -370,21 +364,20 @@ for tx_num,tx_file in enumerate(tx_files,1): sig_tx = sign_transaction(c,tx_hex,tx_num_str,sig_data,keys) if sig_tx['complete']: - msg("OK") - if keypress_confirm("Edit transaction comment?"): + msg('OK') + if keypress_confirm('Edit transaction comment?'): comment = get_tx_comment_from_user(comment) - outfile = "tx_%s[%s].%s" % (metadata[0],metadata[1],g.sigtx_ext) + outfile = 'tx_%s[%s].%s' % (metadata[0],metadata[1],g.sigtx_ext) data = make_tx_data( - "{} {} {t}".format(*metadata[:2], + '{} {} {t}'.format(*metadata[:2], t=make_timestamp()), sig_tx['hex'], inputs_data, b2m_map, comment ) write_data_to_file( outfile,data, - "signed transaction{}".format(tx_num_str), - ask_write_prompt="Save signed transaction?" + 'signed transaction{}'.format(tx_num_str), + ask_write_prompt='Save signed transaction?' ) else: - msg_r("failed\nSome keys were missing. ") - msg("Transaction %scould not be signed." % tx_num_str) - sys.exit(3) + msg_r('failed\nSome keys were missing. ') + die(3,'Transaction %scould not be signed.' % tx_num_str) diff --git a/mmgen/main_wallet.py b/mmgen/main_wallet.py index 4dabefea..b7b96d5e 100755 --- a/mmgen/main_wallet.py +++ b/mmgen/main_wallet.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution -# Copyright (C)2013-2015 Philemon +# Copyright (C)2013-2016 Philemon # # 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 @@ -20,40 +20,39 @@ mmgen/main_wallet: Entry point for MMGen wallet-related scripts """ -import sys,os,re -import mmgen.globalvars as g -import mmgen.opt as opt -from mmgen.util import die,msg,green,do_license_msg,check_infile,mdie,mmsg,qmsg,capfirst +import os,re + +from mmgen.common import * from mmgen.seed import SeedSource bn = os.path.basename(sys.argv[0]) -invoked_as = re.sub(r'^wallet','',bn.split("-")[-1]) +invoked_as = re.sub(r'^wallet','',bn.split('-')[-1]) -usage = "[opts] [infile]" +usage = '[opts] [infile]' nargs = 1 -iaction = "convert" -oaction = "convert" -bw_note = opt.opts.bw_note -pw_note = opt.opts.pw_note +iaction = 'convert' +oaction = 'convert' +bw_note = opts.bw_note +pw_note = opts.pw_note -if invoked_as == "gen": - desc = "Generate an {pnm} wallet from a random seed" - opt_filter = "ehdoJlLpPqrSvz" - usage = "[opts]" - oaction = "output" +if invoked_as == 'gen': + desc = 'Generate an {pnm} wallet from a random seed' + opt_filter = 'ehdoJlLpPqrSvz' + usage = '[opts]' + oaction = 'output' nargs = 0 -elif invoked_as == "conv": - desc = "Convert an {pnm} wallet from one format to another" +elif invoked_as == 'conv': + desc = 'Convert an {pnm} wallet from one format to another' opt_filter = None -elif invoked_as == "chk": - desc = "Check validity of an {pnm} wallet" - opt_filter = "ehiHOlpPqrvz" - iaction = "input" -elif invoked_as == "passchg": - desc = "Change the password, hash preset or label of an {pnm} wallet" - opt_filter = "ehdiHkKOlLmpPqrSvz" - iaction = "input" - bw_note = "" +elif invoked_as == 'chk': + desc = 'Check validity of an {pnm} wallet' + opt_filter = 'ehiHOlpPqrvz' + iaction = 'input' +elif invoked_as == 'passchg': + desc = 'Change the password, hash preset or label of an {pnm} wallet' + opt_filter = 'ehdiHkKOlLmpPqrSvz' + iaction = 'input' + bw_note = '' else: die(1,"'%s': unrecognized invocation" % bn) @@ -103,36 +102,36 @@ opts_data = { FMT CODES: {f} """.format( - f="\n ".join(SeedSource.format_fmt_codes().splitlines()), + f='\n '.join(SeedSource.format_fmt_codes().splitlines()), pw_note=pw_note, - bw_note=("","\n\n" + bw_note)[int(bool(bw_note))] + bw_note=('','\n\n' + bw_note)[bool(bw_note)] ) } -cmd_args = opt.opts.init(opts_data,opt_filter=opt_filter) +cmd_args = opts.init(opts_data,opt_filter=opt_filter) if len(cmd_args) < nargs \ and not opt.hidden_incog_input_params and not opt.in_fmt: - die(1,"An input file or input format must be specified") + die(1,'An input file or input format must be specified') elif len(cmd_args) > nargs \ or (len(cmd_args) == nargs and opt.hidden_incog_input_params): - msg("No input files may be specified" if invoked_as == "gen" - else "Too many input files specified") - opt.opts.usage() + msg('No input files may be specified' if invoked_as == 'gen' + else 'Too many input files specified') + opts.usage() if cmd_args: check_infile(cmd_args[0]) -if not invoked_as == "chk": do_license_msg() +if not invoked_as == 'chk': do_license_msg() -if invoked_as in ("conv","passchg"): msg(green("Processing input wallet")) +if invoked_as in ('conv','passchg'): msg(green('Processing input wallet')) -ss_in = None if invoked_as == "gen" \ - else SeedSource(*cmd_args,passchg=invoked_as=="passchg") +ss_in = None if invoked_as == 'gen' \ + else SeedSource(*cmd_args,passchg=invoked_as=='passchg') -if invoked_as == "chk": +if invoked_as == 'chk': sys.exit() -if invoked_as in ("conv","passchg"): msg(green("Processing output wallet")) +if invoked_as in ('conv','passchg'): msg(green('Processing output wallet')) -ss_out = SeedSource(ss=ss_in,passchg=invoked_as=="passchg") +ss_out = SeedSource(ss=ss_in,passchg=invoked_as=='passchg') ss_out.write_to_file() diff --git a/mmgen/mn_electrum.py b/mmgen/mn_electrum.py index 1926038c..93b272e2 100755 --- a/mmgen/mn_electrum.py +++ b/mmgen/mn_electrum.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution -# Copyright (C)2013-2015 Philemon +# Copyright (C)2013-2016 Philemon # # 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 diff --git a/mmgen/mn_tirosh.py b/mmgen/mn_tirosh.py index 48179e96..0ae54f00 100755 --- a/mmgen/mn_tirosh.py +++ b/mmgen/mn_tirosh.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution -# Copyright (C)2013-2015 Philemon +# Copyright (C)2013-2016 Philemon # # 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 diff --git a/mmgen/obj.py b/mmgen/obj.py index 8263f738..02faba1b 100755 --- a/mmgen/obj.py +++ b/mmgen/obj.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution -# Copyright (C)2013-2015 Philemon +# Copyright (C)2013-2016 Philemon # # 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 @@ -20,7 +20,6 @@ obj.py: The MMGenObject class and methods """ import mmgen.globalvars as g -from mmgen.util import mdie,mmsg lvl = 0 @@ -29,16 +28,16 @@ class MMGenObject(object): # Pretty-print any object of type MMGenObject, recursing into sub-objects def __str__(self): global lvl - indent = lvl * " " + indent = lvl * ' ' def fix_linebreaks(v,fixed_indent=None): - if "\n" in v: - i = indent+" " if fixed_indent == None else fixed_indent*" " - return "\n"+i + v.replace("\n","\n"+i) + if '\n' in v: + i = indent+' ' if fixed_indent == None else fixed_indent*' ' + return '\n'+i + v.replace('\n','\n'+i) else: return repr(v) def conv(v,col_w): - vret = "" + vret = '' if type(v) == str: if not (set(list(v)) <= set(list(g.printable))): vret = repr(v) @@ -47,30 +46,30 @@ class MMGenObject(object): elif type(v) == int or type(v) == long: vret = str(v) elif type(v) == dict: - sep = "\n{}{}".format(indent," "*4) + sep = '\n{}{}'.format(indent,' '*4) cw = max(len(k) for k in v) + 2 - t = sep.join(["{:<{w}}: {}".format( + t = sep.join(['{:<{w}}: {}'.format( repr(k), (fix_linebreaks(v[k],fixed_indent=0) if type(v[k]) == str else v[k]), w=cw) for k in sorted(v)]) - vret = "{" + sep + t + "\n" + indent + "}" + vret = '{' + sep + t + '\n' + indent + '}' elif type(v) in (list,tuple): - sep = "\n{}{}".format(indent," "*4) - t = " ".join([repr(e) for e in sorted(v)]) - o,c = ("[","]") if type(v) == list else ("(",")") - vret = o + sep + t + "\n" + indent + c + sep = '\n{}{}'.format(indent,' '*4) + t = ' '.join([repr(e) for e in sorted(v)]) + o,c = (('(',')'),('[',']'))[type(v)==list] + vret = o + sep + t + '\n' + indent + c elif repr(v)[:14] == '" + vret = ' '.join(repr(v).split()[0:3]) + '>' # vret = repr(v) return vret or type(v) out = [] - def f(k): return k[:2] != "__" + def f(k): return k[:2] != '__' keys = filter(f, dir(self)) col_w = max(len(k) for k in keys) - fs = "{}%-{}s: %s".format(indent,col_w) + fs = '{}%-{}s: %s'.format(indent,col_w) methods = [k for k in keys if repr(getattr(self,k))[:14] == ' +# Copyright (C)2013-2016 Philemon # # 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 @@ -21,10 +21,11 @@ opts.py: MMGen-specific options processing after generic processing by share.Op """ import sys +class opt(object): pass + import mmgen.globalvars as g import mmgen.share.Opts -import opt -from mmgen.util import msg,msg_r,mdie,mmsg,Msg,die,is_mmgen_wallet_label +from mmgen.util import * pw_note = """ For passphrases all combinations of whitespace are equal and leading and @@ -41,7 +42,7 @@ seed, the same seed length and hash preset parameters must always be used. """.strip() def usage(): - Msg("USAGE: %s %s" % (g.prog_name, usage_txt)) + Msg('USAGE: %s %s' % (g.prog_name, usage_txt)) sys.exit(2) def print_version_info(): @@ -55,7 +56,7 @@ def die_on_incompatible_opts(incompat_list): for group in incompat_list: bad = [k for k in opt.__dict__ if opt.__dict__[k] and k in group] if len(bad) > 1: - die(1,"Conflicting options: %s" % ", ".join([fmt_opt(b) for b in bad])) + die(1,'Conflicting options: %s' % ', '.join([fmt_opt(b) for b in bad])) def _typeconvert_from_dfl(key): @@ -66,7 +67,7 @@ def _typeconvert_from_dfl(key): gtype = type(gval) try: - opt.__dict__[key] = gtype(opt.__dict__[key]) + setattr(opt,key,gtype(uval)) except: d = { 'int': 'an integer', @@ -75,44 +76,45 @@ def _typeconvert_from_dfl(key): 'bool': 'a boolean value', } die(1, "'%s': invalid parameter for '--%s' option (not %s)" % ( - opt.__dict__[key], - key.replace("_","-"), + uval, + key.replace('_','-'), d[gtype.__name__] )) if g.debug: - Msg("Opt overriden by user:\n %-18s: %s" % ( - key, ("%s -> %s" % (gval,uval)) + Msg('Opt overriden by user:\n %-18s: %s' % ( + key, ('%s -> %s' % (gval,uval)) )) -def fmt_opt(o): return "--" + o.replace("_","-") +def fmt_opt(o): return '--' + o.replace('_','-') def _show_hash_presets(): - fs = " {:<7} {:<6} {:<3} {}" - msg("Available parameters for scrypt.hash():") - msg(fs.format("Preset","N","r","p")) + fs = ' {:<7} {:<6} {:<3} {}' + msg('Available parameters for scrypt.hash():') + msg(fs.format('Preset','N','r','p')) for i in sorted(g.hash_presets.keys()): msg(fs.format("'%s'" % i, *g.hash_presets[i])) - msg("N = memory usage (power of two), p = iterations (rounds)") + msg('N = memory usage (power of two), p = iterations (rounds)') def init(opts_data,add_opts=[],opt_filter=None): if len(sys.argv) == 2 and sys.argv[1] == '--version': - print_version_info(); sys.exit() + print_version_info() + sys.exit() uopts,args,short_opts,long_opts,skipped_opts = \ mmgen.share.Opts.parse_opts(sys.argv,opts_data,opt_filter=opt_filter) if g.debug: d = ( - ("Short opts", short_opts), - ("Long opts", long_opts), - ("Skipped opts", skipped_opts), - ("User-selected opts", uopts), - ("Cmd args", args), + ('Short opts', short_opts), + ('Long opts', long_opts), + ('Skipped opts', skipped_opts), + ('User-selected opts', uopts), + ('Cmd args', args), ) - Msg("\n### BEGIN OPTS.PY ###") - for e in d: Msg("{:<20}: {}".format(*e)) + Msg('\n=== opts.py debug ===') + for e in d: Msg(' {:<20}: {}'.format(*e)) # Save this for usage() global usage_txt @@ -123,61 +125,53 @@ def init(opts_data,add_opts=[],opt_filter=None): for k in 'prog_name','desc','usage','options','notes': if k in opts_data: del opts_data[k] - # Remove all unneeded attributes from opt, our special global namespace - for k in dir(opt): - if k[:2] == "__": del opt.__dict__[k] - # Transfer uopts into opt, setting required opts to None if not set by user - for o in [s.rstrip("=") for s in long_opts] + \ + for o in [s.rstrip('=') for s in long_opts] + \ g.required_opts + add_opts + skipped_opts: - opt.__dict__[o] = uopts[o] if o in uopts else None + setattr(opt,o,uopts[o] if o in uopts else None) # A special case - do this here, before opt gets set from g.dfl_vars if opt.usr_randchars: g.use_urandchars = True # If user opt is set, convert its type based on value in mmgen.globalvars # If unset, set it to default value in mmgen.globalvars (g): - opt.__dict__['set_by_user'] = [] + setattr(opt,'set_by_user',[]) for k in g.dfl_vars: if k in opt.__dict__ and opt.__dict__[k] != None: _typeconvert_from_dfl(k) opt.set_by_user.append(k) else: - opt.__dict__[k] = g.__dict__[k] + setattr(opt,k,g.__dict__[k]) # Check user-set opts without modifying them - if not check_opts(uopts): sys.exit(1) + if not check_opts(uopts): + sys.exit(1) if opt.show_hash_presets: - _show_hash_presets(); sys.exit(0) + _show_hash_presets() + sys.exit() if opt.debug: opt.verbose = True if g.debug: - Msg("Opts after processing:") - for k in opt.__dict__: - v = opt.__dict__[k] - if v != None and k != "opts": - Msg(" %-18s: %-6s [%s]" % (k,v,type(v).__name__)) - Msg("### END OPTS.PY ###\n") + a = [k for k in dir(opt) if k[:2] != '__' and getattr(opt,k) != None] + b = [k for k in dir(opt) if k[:2] != '__' and getattr(opt,k) == None] + Msg(' Opts after processing:') + for k in a: + v = getattr(opt,k) + Msg(' %-18s: %-6s [%s]' % (k,v,type(v).__name__)) + Msg(" Opts set to 'None':") + Msg(' %s\n' % '\n '.join(b)) die_on_incompatible_opts(g.incompatible_opts) return args -# save for debugging -def show_all_opts(): - msg("Processed options:") - d = opt.__dict__ - for k in [o for o in d if o != "opts"]: - tstr = type(d[k]) if d[k] not in (None,False,True) else "" - msg("%-20s: %-8s %s" % (k, d[k], tstr)) def check_opts(usr_opts): # Returns false if any check fails def opt_splits(val,sep,n,desc): - sepword = "comma" if sep == "," else ( - "colon" if sep == ":" else ("'"+sep+"'")) + sepword = 'comma' if sep == ',' else 'colon' if sep == ':' else "'%s'" % sep try: l = val.split(sep) except: msg("'%s': invalid %s (not %s-separated list)" % (val,desc,sepword)) @@ -189,10 +183,10 @@ def check_opts(usr_opts): # Returns false if any check fails (val,desc,n,sepword)) return False - def opt_compares(val,op,target,desc,what=""): - if what: what += " " - if not eval("%s %s %s" % (val, op, target)): - msg("%s: invalid %s (%snot %s %s)" % (val,desc,what,op,target)) + def opt_compares(val,op,target,desc,what=''): + if what: what += ' ' + if not eval('%s %s %s' % (val, op, target)): + msg('%s: invalid %s (%snot %s %s)' % (val,desc,what,op,target)) return False return True @@ -205,8 +199,8 @@ def check_opts(usr_opts): # Returns false if any check fails def opt_is_in_list(val,lst,desc): if val not in lst: - q,sep = ("'","','") if type(lst[0]) == str else ("",",") - msg("{q}{v}{q}: invalid {w}\nValid choices: {q}{o}{q}".format( + q,sep = (('',','),("'","','"))[type(lst[0]) == str] + msg('{q}{v}{q}: invalid {w}\nValid choices: {q}{o}{q}'.format( v=val,w=desc,q=q, o=sep.join([str(i) for i in sorted(lst)]) )) @@ -218,8 +212,8 @@ def check_opts(usr_opts): # Returns false if any check fails % (val,desc,fmt_opt(key))) return False - def opt_display(key,val='',beg="For selected",end=":\n"): - s = "%s=%s" % (fmt_opt(key),val) if val else fmt_opt(key) + def opt_display(key,val='',beg='For selected',end=':\n'): + s = '%s=%s' % (fmt_opt(key),val) if val else fmt_opt(key) msg_r("%s option '%s'%s" % (beg,s,end)) global opt @@ -245,23 +239,23 @@ def check_opts(usr_opts): # Returns false if any check fails from mmgen.seed import SeedSource,IncogWallet,Brainwallet,IncogWalletHidden sstype = SeedSource.fmt_code_to_sstype(val) if not sstype: - return opt_unrecognized(key,val,"format code") + return opt_unrecognized(key,val,'format code') if key == 'out_fmt': p = 'hidden_incog_output_params' if sstype == IncogWalletHidden and not getattr(opt,p): - die(1,"Hidden incog format output requested. You must supply" + die(1,'Hidden incog format output requested. You must supply' + " a file and offset with the '%s' option" % fmt_opt(p)) if issubclass(sstype,IncogWallet) and opt.old_incog_fmt: - opt_display(key,val,beg="Selected",end=" ") - opt_display('old_incog_fmt',beg="conflicts with",end=":\n") - die(1,"Export to old incog wallet format unsupported") + opt_display(key,val,beg='Selected',end=' ') + opt_display('old_incog_fmt',beg='conflicts with',end=':\n') + die(1,'Export to old incog wallet format unsupported') elif issubclass(sstype,Brainwallet): - die(1,"Output to brainwallet format unsupported") + die(1,'Output to brainwallet format unsupported') elif key in ('hidden_incog_input_params','hidden_incog_output_params'): - a = val.split(",") + a = val.split(',') if len(a) != 2: opt_display(key,val) - msg("Option requires two comma-separated arguments") + msg('Option requires two comma-separated arguments') return False if not opt_is_int(a[1],desc): return False if key == 'hidden_incog_input_params': @@ -280,7 +274,7 @@ def check_opts(usr_opts): # Returns false if any check fails from mmgen.seed import IncogWalletHidden if val2 and val2 not in IncogWalletHidden.fmt_codes: die(1, - "Option conflict:\n %s, with\n %s=%s" % ( + 'Option conflict:\n %s, with\n %s=%s' % ( fmt_opt(key),fmt_opt(key2),val2 )) elif key == 'seed_len': @@ -289,21 +283,21 @@ def check_opts(usr_opts): # Returns false if any check fails elif key == 'hash_preset': if not opt_is_in_list(val,g.hash_presets.keys(),desc): return False elif key == 'brain_params': - a = val.split(",") + a = val.split(',') if len(a) != 2: opt_display(key,val) - msg("Option requires two comma-separated arguments") + msg('Option requires two comma-separated arguments') return False - d = "seed length " + desc + d = 'seed length ' + desc if not opt_is_int(a[0],d): return False if not opt_is_in_list(int(a[0]),g.seed_lens,d): return False - d = "hash preset " + desc + d = 'hash preset ' + desc if not opt_is_in_list(a[1],g.hash_presets.keys(),d): return False elif key == 'usr_randchars': if val == 0: continue if not opt_is_int(val,desc): return False - if not opt_compares(val,">=",g.min_urandchars,desc): return False - if not opt_compares(val,"<=",g.max_urandchars,desc): return False + if not opt_compares(val,'>=',g.min_urandchars,desc): return False + if not opt_compares(val,'<=',g.max_urandchars,desc): return False else: if g.debug: Msg("check_opts(): No test for opt '%s'" % key) diff --git a/mmgen/rpc.py b/mmgen/rpc.py new file mode 100755 index 00000000..6105b7ae --- /dev/null +++ b/mmgen/rpc.py @@ -0,0 +1,128 @@ +#!/usr/bin/env python +# +# mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution +# Copyright (C)2013-2016 Philemon +# +# 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 . + +""" +rpc.py: Bitcoin RPC library for the MMGen suite +""" + +import httplib,base64,json,decimal + +from mmgen.common import * + +class BitcoinRPCConnection(object): + + def __init__( + self, + host='localhost',port=8332, + user=None,passwd=None,auth_cookie=None, + ): + + if auth_cookie: + self.auth_str = auth_cookie + elif user and passwd: + self.auth_str = '{}:{}'.format(user,passwd) + else: + msg('Error: no Bitcoin RPC authentication method found') + if passwd: die(1,"'rpcuser' entry missing in bitcoin.conf") + elif user: die(1,"'rpcpassword' entry missing in bitcoin.conf") + else: + m1 = 'Either provide rpcuser/rpcpassword in bitcoin.conf' + m2 = '(or, alternatively, copy the authentication cookie to Bitcoin data dir' + m3 = 'if {} and Bitcoin are running as different users)'.format(g.proj_name) + die(1,'\n'.join((m1,m2,m3))) + + self.host = host + self.port = port + + # Normal mode: call with arg list unrolled, exactly as with 'bitcoin-cli' + # Batch mode: call with list of arg lists as first argument + # kwargs are for local use and are not passed to server + def request(self,cmd,*args,**kwargs): + + cf = { 'timeout': g.http_timeout, 'batch': False } + + for k in cf: + if k in kwargs and kwargs[k]: cf[k] = kwargs[k] + + c = httplib.HTTPConnection(self.host, self.port, False, cf['timeout']) + + if cf['batch']: + p = [{'method':cmd,'params':r,'id':n} for n,r in enumerate(args[0],1)] + else: + p = {'method':cmd,'params':args,'id':1} + + dmsg('=== rpc.py debug ===') + dmsg(' RPC POST data ==> %s\n' % p) + + try: + c.request('POST', '/', json.dumps(p), { + 'Host': self.host, + 'Authorization': 'Basic ' + base64.b64encode(self.auth_str) + }) + except Exception as e: + die(2,'%s\nUnable to connect to bitcoind' % e) + + r = c.getresponse() # returns HTTPResponse instance + + if r.status == 401: + m1 = 'RPC authentication error' + m2 = 'Check that rpcuser/rpcpassword in Bitcoin config file are correct' + m3 = '(or, alternatively, copy the authentication cookie to Bitcoin data dir' + m4 = 'if {} and Bitcoin are running as different users)'.format(g.proj_name) + die(1,'\n'.join((m1,m2,m3,m4))) + elif r.status != 200: + die(1,'RPC error: %s %s\n%s' % (r.status, r.reason, r.read())) + + r2 = r.read() + + dmsg(' RPC REPLY data ==> %s\n' % r2) + + if not r2: + die(2,'Error: empty reply') + + r3 = json.loads(r2.decode('utf8'), parse_float=decimal.Decimal) + ret = [] + + for resp in r3 if cf['batch'] else [r3]: + if 'error' in resp and resp['error'] != None: + die(1,'Bitcoind returned an error: %s' % resp['error']) + elif 'result' not in resp: + die(1, 'Missing JSON-RPC result\n' + repr(resps)) + else: + ret.append(resp['result']) + + return ret if cf['batch'] else ret[0] + + + rpcmethods = ( + 'getinfo', + 'getbalance', + 'getaddressesbyaccount', + 'listunspent', + 'listaccounts', + 'importaddress', + 'decoderawtransaction', + 'createrawtransaction', + 'signrawtransaction', + 'sendrawtransaction', + 'walletpassphrase', + 'walletlock', + ) + + for name in rpcmethods: + exec "def {n}(self,*a,**k):return self.request('{n}',*a,**k)\n".format(n=name) diff --git a/mmgen/rpc/__init__.py b/mmgen/rpc/__init__.py deleted file mode 100755 index e1dc4dd2..00000000 --- a/mmgen/rpc/__init__.py +++ /dev/null @@ -1,53 +0,0 @@ -# Copyright (c) 2010 Witchspace -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. -""" -bitcoin-python - Easy-to-use Bitcoin API client -""" - - -def connect_to_local(filename=None): - """ - Connect to default bitcoin instance owned by this user, on this machine. - - Returns a :class:`~mmgen.rpc.connection.BitcoinConnection` object. - - Arguments: - - - `filename`: Path to a configuration file in a non-standard location (optional) - """ - from mmgen.rpc.connection import BitcoinConnection - from mmgen.rpc.config import read_default_config - - cfg = read_default_config(filename) - port = int(cfg.get('rpcport', '18332' if cfg.get('testnet') else '8332')) - rcpuser = cfg.get('rpcuser', '') - - return BitcoinConnection(rcpuser, cfg['rpcpassword'], 'localhost', port) - - -def connect_to_remote(user,password,host='localhost',port=8332,use_https=False): - """ - Connect to remote or alternative local bitcoin client instance. - - Returns a :class:`~mmgen.rpc.connection.BitcoinConnection` object. - """ - from mmgen.rpc.connection import BitcoinConnection - - return BitcoinConnection(user, password, host, port, use_https) diff --git a/mmgen/rpc/config.py b/mmgen/rpc/config.py deleted file mode 100755 index 86f75c20..00000000 --- a/mmgen/rpc/config.py +++ /dev/null @@ -1,75 +0,0 @@ -# Copyright (c) 2010 Witchspace -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. -""" -Utilities for reading bitcoin configuration files. -""" - - -def read_config_file(filename): - """ - Read a simple ``'='``-delimited config file. - Raises :const:`IOError` if unable to open file, or :const:`ValueError` - if an parse error occurs. - """ - f = open(filename) - try: - cfg = {} - for line in f: - line = line.strip() - if line and not line.startswith("#"): - try: - (key, value) = line.split('=', 1) - cfg[key] = value - except ValueError: - pass # Happens when line has no '=', ignore - finally: - f.close() - return cfg - - -def read_default_config(filename=None): - """ - Read bitcoin default configuration from the current user's home directory. - - Arguments: - - - `filename`: Path to a configuration file in a non-standard location (optional) - """ - if filename is None: - import os - import platform - home = os.getenv("HOME") - if not home: - raise IOError("Home directory not defined, don't know where to look for config file") - - if platform.system() == "Darwin": - location = 'Library/Application Support/Bitcoin/bitcoin.conf' - else: - location = '.bitcoin/bitcoin.conf' - filename = os.path.join(home, location) - - elif filename.startswith("~"): - import os - filename = os.path.expanduser(filename) - - try: - return read_config_file(filename) - except (IOError, ValueError): - pass # Cannot read config file, ignore diff --git a/mmgen/rpc/connection.py b/mmgen/rpc/connection.py deleted file mode 100755 index 1a0c8584..00000000 --- a/mmgen/rpc/connection.py +++ /dev/null @@ -1,766 +0,0 @@ -# Copyright (C) 2013 by philemon -# Added methods for sendrawtransaction(), importaddress() -# -# Previous copyright from bitcoin-python/connection.py: -# Copyright (c) 2010 Witchspace -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. -""" -Connect to Bitcoin server via JSON-RPC. -""" -from mmgen.rpc.proxy import JSONRPCException, AuthServiceProxy -from mmgen.rpc.exceptions import _wrap_exception, WalletPassphraseIncorrect, WalletAlreadyUnlocked -from mmgen.rpc.data import (ServerInfo, AccountInfo, AddressInfo, TransactionInfo, - AddressValidation, WorkItem, MiningInfo) - - -class BitcoinConnection(object): - """ - A BitcoinConnection object defines a connection to a bitcoin server. - It is a thin wrapper around a JSON-RPC API connection. - - Up-to-date for SVN revision 198. - - Arguments to constructor: - - - *user* -- Authenticate as user. - - *password* -- Authentication password. - - *host* -- Bitcoin JSON-RPC host. - - *port* -- Bitcoin JSON-RPC port. - """ - def __init__(self, user, password, host='localhost', port=8332, use_https=False): - """ - Create a new bitcoin server connection. - """ - url = 'http{s}://{user}:{password}@{host}:{port}/'.format( - s='s' if use_https else '', - user=user, password=password, host=host, port=port) - self.url = url - try: - self.proxy = AuthServiceProxy(url) - except JSONRPCException as e: - raise _wrap_exception(e.error) - -# importaddress
[label] [rescan=true] - def importaddress(self,address,label=None,rescan=True): - try: -# return self.proxy.badmethod(address,label) # DEBUG - return self.proxy.importaddress(address,label,rescan) - except JSONRPCException as e: - if e.error['message'] == "Method not found": - from mmgen.util import msg - msg(""" -******************************************************************************* -******************************************************************************* -ERROR: 'importaddress' not found. Does your bitcoind support watch-only addrs? -******************************************************************************* -******************************************************************************* -""") - raise _wrap_exception(e.error) - -# sendrawtransaction [allowhighfees=false] - def sendrawtransaction(self,tx): - try: - return self.proxy.sendrawtransaction(tx) - except JSONRPCException as e: - raise _wrap_exception(e.error) - - def stop(self): - """ - Stop bitcoin server. - """ - try: - self.proxy.stop() - except JSONRPCException as e: - raise _wrap_exception(e.error) - - def getblock(self, hash): - """ - Returns information about the given block hash. - """ - try: - return self.proxy.getblock(hash) - except JSONRPCException as e: - raise _wrap_exception(e.error) - - def getblockcount(self): - """ - Returns the number of blocks in the longest block chain. - """ - try: - return self.proxy.getblockcount() - except JSONRPCException as e: - raise _wrap_exception(e.error) - - def getblockhash(self, index): - """ - Returns hash of block in best-block-chain at index. - - :param index: index ob the block - - """ - try: - return self.proxy.getblockhash(index) - except JSONRPCException as e: - raise _wrap_exception(e.error) - - def getblocknumber(self): - """ - Returns the block number of the latest block in the longest block chain. - Deprecated. Use getblockcount instead. - """ - return self.getblockcount() - - def getconnectioncount(self): - """ - Returns the number of connections to other nodes. - """ - try: - return self.proxy.getconnectioncount() - except JSONRPCException as e: - raise _wrap_exception(e.error) - - def getdifficulty(self): - """ - Returns the proof-of-work difficulty as a multiple of the minimum difficulty. - """ - try: - return self.proxy.getdifficulty() - except JSONRPCException as e: - raise _wrap_exception(e.error) - - def getgenerate(self): - """ - Returns :const:`True` or :const:`False`, depending on whether - generation is enabled. - """ - try: - return self.proxy.getgenerate() - except JSONRPCException as e: - raise _wrap_exception(e.error) - - def setgenerate(self, generate, genproclimit=None): - """ - Enable or disable generation (mining) of coins. - - Arguments: - - - *generate* -- is :const:`True` or :const:`False` to turn generation - on or off. - - *genproclimit* -- Number of processors that are used for generation, - -1 is unlimited. - - """ - try: - if genproclimit is None: - return self.proxy.setgenerate(generate) - else: - return self.proxy.setgenerate(generate, genproclimit) - except JSONRPCException as e: - raise _wrap_exception(e.error) - - def gethashespersec(self): - """ - Returns a recent hashes per second performance measurement while generating. - """ - try: - return self.proxy.gethashespersec() - except JSONRPCException as e: - raise _wrap_exception(e.error) - - def getinfo(self): - """ - Returns an :class:`~mmgen.rpc.data.ServerInfo` object containing - various state info. - """ - try: - return ServerInfo(**self.proxy.getinfo()) - except JSONRPCException as e: - raise _wrap_exception(e.error) - - def getmininginfo(self): - """ - Returns an :class:`~mmgen.rpc.data.MiningInfo` object containing various - mining state info. - """ - try: - return MiningInfo(**self.proxy.getmininginfo()) - except JSONRPCException as e: - raise _wrap_exception(e.error) - - def getnewaddress(self, account=None): - """ - Returns a new bitcoin address for receiving payments. - - Arguments: - - - *account* -- If account is specified (recommended), it is added to the - address book so that payments received with the address will be - credited to it. - - """ - try: - if account is None: - return self.proxy.getnewaddress() - else: - return self.proxy.getnewaddress(account) - except JSONRPCException as e: - raise _wrap_exception(e.error) - - def getaccountaddress(self, account): - """ - Returns the current bitcoin address for receiving payments to an account. - - Arguments: - - - *account* -- Account for which the address should be returned. - - """ - try: - return self.proxy.getaccountaddress(account) - except JSONRPCException as e: - raise _wrap_exception(e.error) - - def setaccount(self, bitcoinaddress, account): - """ - Sets the account associated with the given address. - - Arguments: - - - *bitcoinaddress* -- Bitcoin address to associate. - - *account* -- Account to associate the address to. - - """ - try: - return self.proxy.setaccount(bitcoinaddress, account) - except JSONRPCException as e: - raise _wrap_exception(e.error) - - def getaccount(self, bitcoinaddress): - """ - Returns the account associated with the given address. - - Arguments: - - - *bitcoinaddress* -- Bitcoin address to get account for. - """ - try: - return self.proxy.getaccount(bitcoinaddress) - except JSONRPCException as e: - raise _wrap_exception(e.error) - - def getaddressesbyaccount(self, account): - """ - Returns the list of addresses for the given account. - - Arguments: - - - *account* -- Account to get list of addresses for. - """ - try: - return self.proxy.getaddressesbyaccount(account) - except JSONRPCException as e: - raise _wrap_exception(e.error) - - def sendtoaddress(self, bitcoinaddress, amount, comment=None, comment_to=None): - """ - Sends *amount* from the server's available balance to *bitcoinaddress*. - - Arguments: - - - *bitcoinaddress* -- Bitcoin address to send to. - - *amount* -- Amount to send (float, rounded to the nearest 0.01). - - *minconf* -- Minimum number of confirmations required for transferred - balance. - - *comment* -- Comment for transaction. - - *comment_to* -- Comment for to-address. - - """ - try: - if comment is None: - return self.proxy.sendtoaddress(bitcoinaddress, amount) - elif comment_to is None: - return self.proxy.sendtoaddress(bitcoinaddress, amount, comment) - else: - return self.proxy.sendtoaddress(bitcoinaddress, amount, comment, comment_to) - except JSONRPCException as e: - raise _wrap_exception(e.error) - - def getreceivedbyaddress(self, bitcoinaddress, minconf=1): - """ - Returns the total amount received by a bitcoin address in transactions - with at least a certain number of confirmations. - - Arguments: - - - *bitcoinaddress* -- Address to query for total amount. - - - *minconf* -- Number of confirmations to require, defaults to 1. - """ - try: - return self.proxy.getreceivedbyaddress(bitcoinaddress, minconf) - except JSONRPCException as e: - raise _wrap_exception(e.error) - - def getreceivedbyaccount(self, account, minconf=1): - """ - Returns the total amount received by addresses with an account in - transactions with at least a certain number of confirmations. - - Arguments: - - - *account* -- Account to query for total amount. - - *minconf* -- Number of confirmations to require, defaults to 1. - - """ - try: - return self.proxy.getreceivedbyaccount(account, minconf) - except JSONRPCException as e: - raise _wrap_exception(e.error) - - def gettransaction(self, txid): - """ - Get detailed information about transaction - - Arguments: - - - *txid* -- Transactiond id for which the info should be returned - - """ - try: - return TransactionInfo(**self.proxy.gettransaction(txid)) - except JSONRPCException as e: - raise _wrap_exception(e.error) - - def getrawtransaction(self, txid, verbose=True): - """ - Get transaction raw info - - Arguments: - - - *txid* -- Transactiond id for which the info should be returned. - - *verbose* -- If False, return only the "hex" of the transaction. - - """ - try: - if verbose: - return TransactionInfo(**self.proxy.getrawtransaction(txid, 1)) - return self.proxy.getrawtransaction(txid, 0) - except JSONRPCException as e: - raise _wrap_exception(e.error) - - def createrawtransaction(self, inputs, outputs): - """ - Creates a raw transaction spending given inputs - (a list of dictionaries, each containing a transaction id and an output - number), sending to given address(es). - - Returns hex-encoded raw transaction. - - Example usage: - >>> conn.createrawtransaction( - [{"txid": "a9d4599e15b53f3eb531608ddb31f48c695c3d0b3538a6bda871e8b34f2f430c", - "vout": 0}], - {"mkZBYBiq6DNoQEKakpMJegyDbw2YiNQnHT":50}) - - - Arguments: - - - *inputs* -- A list of {"txid": txid, "vout": n} dictionaries. - - *outputs* -- A dictionary mapping (public) addresses to the amount - they are to be paid. - """ - try: - return self.proxy.createrawtransaction(inputs, outputs) - except JSONRPCException as e: - raise _wrap_exception(e.error) - - def signrawtransaction(self, hexstring, previous_transactions=None, private_keys=None): - """ - Sign inputs for raw transaction (serialized, hex-encoded). - - Returns a dictionary with the keys: - "hex": raw transaction with signature(s) (hex-encoded string) - "complete": 1 if transaction has a complete set of signature(s), 0 if not - - Arguments: - - - *hexstring* -- A hex string of the transaction to sign. - - *previous_transactions* -- A (possibly empty) list of dictionaries of - the form: - {"txid": txid, "vout": n, "scriptPubKey": hex, "redeemScript": hex}, - representing previous transaction outputs that this transaction depends - on but may not yet be in the block chain. - - *private_keys* -- A (possibly empty) list of base58-encoded private - keys that, if given, will be the only keys used to sign the transaction. - """ - try: - return dict(self.proxy.signrawtransaction(hexstring, - previous_transactions, private_keys)) - except JSONRPCException as e: - raise _wrap_exception(e.error) - - def decoderawtransaction(self, hexstring): - """ - Produces a human-readable JSON object for a raw transaction. - - Arguments: - - - *hexstring* -- A hex string of the transaction to be decoded. - """ - try: - return dict(self.proxy.decoderawtransaction(hexstring)) - except JSONRPCException as e: - raise _wrap_exception(e.error) - - def listsinceblock(self, block_hash): - try: - res = self.proxy.listsinceblock(block_hash) - res['transactions'] = [TransactionInfo(**x) for x in res['transactions']] - return res - except JSONRPCException as e: - raise _wrap_exception(e.error) - - def listreceivedbyaddress(self, minconf=1, includeempty=False): - """ - Returns a list of addresses. - - Each address is represented with a - :class:`~mmgen.rpc.data.AddressInfo` object. - - Arguments: - - - *minconf* -- Minimum number of confirmations before payments are included. - - *includeempty* -- Whether to include addresses that haven't received - any payments. - - """ - try: - return [AddressInfo(**x) for x in - self.proxy.listreceivedbyaddress(minconf, includeempty)] - except JSONRPCException as e: - raise _wrap_exception(e.error) - - def listaccounts(self, minconf=1, includeWatchonly=False, as_dict=False): - """ - Returns a list of account names. - - Arguments: - - - *minconf* -- Minimum number of confirmations before payments are included. - - *as_dict* -- Returns a dictionary of account names, with their balance as values. - """ - try: - if as_dict: - return dict(self.proxy.listaccounts(minconf,includeWatchonly)) - else: - return self.proxy.listaccounts(minconf,includeWatchonly).keys() - except JSONRPCException as e: - from mmgen.util import msg - msg(""" -******************************************************************************* -******************************************************************************* -ERROR: 'listaccounts' failed. Does your bitcoind support watch-only addresses? -******************************************************************************* -******************************************************************************* -""") - raise _wrap_exception(e.error) - - def listreceivedbyaccount(self, minconf=1, includeempty=False): - """ - Returns a list of accounts. - - Each account is represented with a :class:`~mmgen.rpc.data.AccountInfo` object. - - Arguments: - - - *minconf* -- Minimum number of confirmations before payments are included. - - - *includeempty* -- Whether to include addresses that haven't received any payments. - """ - try: - return [AccountInfo(**x) for x in - self.proxy.listreceivedbyaccount(minconf, includeempty)] - except JSONRPCException as e: - raise _wrap_exception(e.error) - - def listtransactions(self, account=None, count=10, from_=0, address=None): - """ - Returns a list of the last transactions for an account. - - Each transaction is represented with a :class:`~mmgen.rpc.data.TransactionInfo` object. - - Arguments: - - - *account* -- Account to list transactions from. Return transactions from - all accounts if None. - - *count* -- Number of transactions to return. - - *from_* -- Skip the first transactions. - - *address* -- Receive address to consider - """ - accounts = [account] if account is not None else self.listaccounts(as_dict=True).iterkeys() - try: - return [TransactionInfo(**tx) for acc in accounts for - tx in self.proxy.listtransactions(acc, count, from_) if - address is None or tx["address"] == address] - except JSONRPCException as e: - raise _wrap_exception(e.error) - - def backupwallet(self, destination): - """ - Safely copies ``wallet.dat`` to *destination*, which can be a directory or a path - with filename. - - Arguments: - - *destination* -- directory or path with filename to backup wallet to. - - """ - try: - return self.proxy.backupwallet(destination) - except JSONRPCException as e: - raise _wrap_exception(e.error) - - def validateaddress(self, validateaddress): - """ - Validate a bitcoin address and return information for it. - - The information is represented by a :class:`~mmgen.rpc.data.AddressValidation` object. - - Arguments: -- Address to validate. - - - - *validateaddress* - """ - try: - return AddressValidation(**self.proxy.validateaddress(validateaddress)) - except JSONRPCException as e: - raise _wrap_exception(e.error) - - def getbalance(self, account=None, minconf=None): - """ - Get the current balance, either for an account or the total server balance. - - Arguments: - - *account* -- If this parameter is specified, returns the balance in the account. - - *minconf* -- Minimum number of confirmations required for transferred balance. - - """ - args = [] - if account: - args.append(account) - if minconf is not None: - args.append(minconf) - try: - return self.proxy.getbalance(*args) - except JSONRPCException as e: - raise _wrap_exception(e.error) - - def move(self, fromaccount, toaccount, amount, minconf=1, comment=None): - """ - Move from one account in your wallet to another. - - Arguments: - - - *fromaccount* -- Source account name. - - *toaccount* -- Destination account name. - - *amount* -- Amount to transfer. - - *minconf* -- Minimum number of confirmations required for transferred balance. - - *comment* -- Comment to add to transaction log. - - """ - try: - if comment is None: - return self.proxy.move(fromaccount, toaccount, amount, minconf) - else: - return self.proxy.move(fromaccount, toaccount, amount, minconf, comment) - except JSONRPCException as e: - raise _wrap_exception(e.error) - - def sendfrom(self, fromaccount, tobitcoinaddress, amount, minconf=1, comment=None, - comment_to=None): - """ - Sends amount from account's balance to bitcoinaddress. This method will fail - if there is less than amount bitcoins with minconf confirmations in the account's - balance (unless account is the empty-string-named default account; it - behaves like the sendtoaddress method). Returns transaction ID on success. - - Arguments: - - - *fromaccount* -- Account to send from. - - *tobitcoinaddress* -- Bitcoin address to send to. - - *amount* -- Amount to send (float, rounded to the nearest 0.01). - - *minconf* -- Minimum number of confirmations required for transferred balance. - - *comment* -- Comment for transaction. - - *comment_to* -- Comment for to-address. - - """ - try: - if comment is None: - return self.proxy.sendfrom(fromaccount, tobitcoinaddress, amount, minconf) - elif comment_to is None: - return self.proxy.sendfrom(fromaccount, tobitcoinaddress, amount, minconf, comment) - else: - return self.proxy.sendfrom(fromaccount, tobitcoinaddress, amount, minconf, comment, comment_to) - except JSONRPCException as e: - raise _wrap_exception(e.error) - - def sendmany(self, fromaccount, todict, minconf=1, comment=None): - """ - Sends specified amounts from account's balance to bitcoinaddresses. - This method will fail if there is less than total amount bitcoins with - minconf confirmations in the account's balance (unless account is the - empty-string-named default account; Returns transaction ID on - success. - - Arguments: - - - *fromaccount* -- Account to send from. - - *todict* -- Dictionary with Bitcoin addresses as keys and amounts as - values. - - *minconf* -- Minimum number of confirmations required for transferred - balance. - - *comment* -- Comment for transaction. - - """ - try: - if comment is None: - return self.proxy.sendmany(fromaccount, todict, minconf) - else: - return self.proxy.sendmany(fromaccount, todict, minconf, comment) - except JSONRPCException as e: - raise _wrap_exception(e.error) - - def verifymessage(self, bitcoinaddress, signature, message): - """ - Verifies a signature given the bitcoinaddress used to sign, - the signature itself, and the message that was signed. - Returns :const:`True` if the signature is valid, and :const:`False` if it is invalid. - - Arguments: - - - *bitcoinaddress* -- the bitcoinaddress used to sign the message - - *signature* -- the signature to be verified - - *message* -- the message that was originally signed - - """ - try: - return self.proxy.verifymessage(bitcoinaddress, signature, message) - except JSONRPCException as e: - raise _wrap_exception(e.error) - - def getwork(self, data=None): - """ - Get work for remote mining, or submit result. - If data is specified, the server tries to solve the block - using the provided data and returns :const:`True` if it was successful. - If not, the function returns formatted hash data (:class:`~mmgen.rpc.data.WorkItem`) - to work on. - - Arguments: - - - *data* -- Result from remote mining. - - """ - try: - if data is None: - # Only if no data provided, it returns a WorkItem - return WorkItem(**self.proxy.getwork()) - else: - return self.proxy.getwork(data) - except JSONRPCException as e: - raise _wrap_exception(e.error) - - def listunspent(self, minconf=1, maxconf=999999): - """ - Returns a list of unspent transaction inputs in the wallet. - - Arguments: - - - *minconf* -- Minimum number of confirmations required to be listed. - - - *maxconf* -- Maximal number of confirmations allowed to be listed. - - - """ - try: - return [TransactionInfo(**tx) for tx in - self.proxy.listunspent(minconf, maxconf)] - except JSONRPCException as e: - raise _wrap_exception(e.error) - - def keypoolrefill(self): - "Fills the keypool, requires wallet passphrase to be set." - try: - self.proxy.keypoolrefill() - except JSONRPCException as e: - raise _wrap_exception(e.error) - - def walletpassphrase(self, passphrase, timeout, dont_raise=False): - """ - Stores the wallet decryption key in memory for seconds. - - - *passphrase* -- The wallet passphrase. - - - *timeout* -- Time in seconds to keep the wallet unlocked - (by keeping the passphrase in memory). - - - *dont_raise* -- instead of raising `~mmgen.rpc.exceptions.WalletPassphraseIncorrect` - return False. - """ - try: - self.proxy.walletpassphrase(passphrase, timeout) - return True - except JSONRPCException as e: - json_exception = _wrap_exception(e.error) - if dont_raise: - if isinstance(json_exception, WalletPassphraseIncorrect): - return False - elif isinstance(json_exception, WalletAlreadyUnlocked): - return True - raise json_exception - - def walletlock(self): - """ - Removes the wallet encryption key from memory, locking the wallet. - After calling this method, you will need to call walletpassphrase - again before being able to call any methods which require the wallet - to be unlocked. - """ - try: - return self.proxy.walletlock() - except JSONRPCException as e: - raise _wrap_exception(e.error) - - def walletpassphrasechange(self, oldpassphrase, newpassphrase, dont_raise=False): - """ - Changes the wallet passphrase from to . - - Arguments: - - - *dont_raise* -- instead of raising - `~mmgen.rpc.exceptions.WalletPassphraseIncorrect` return False. - """ - try: - self.proxy.walletpassphrasechange(oldpassphrase, newpassphrase) - return True - except JSONRPCException as e: - json_exception = _wrap_exception(e.error) - if dont_raise and isinstance(json_exception, WalletPassphraseIncorrect): - return False - raise json_exception diff --git a/mmgen/rpc/data.py b/mmgen/rpc/data.py deleted file mode 100755 index b8577b2c..00000000 --- a/mmgen/rpc/data.py +++ /dev/null @@ -1,168 +0,0 @@ -# Copyright (c) 2010 Witchspace -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. -""" -Bitcoin RPC service, data objects. -""" -from mmgen.rpc.util import DStruct - - -class ServerInfo(DStruct): - """ - Information object returned by :func:`~mmgen.rpc.connection.BitcoinConnection.getinfo`. - - - *errors* -- Number of errors. - - - *blocks* -- Number of blocks. - - - *paytxfee* -- Amount of transaction fee to pay. - - - *keypoololdest* -- Oldest key in keypool. - - - *genproclimit* -- Processor limit for generation. - - - *connections* -- Number of connections to other clients. - - - *difficulty* -- Current generating difficulty. - - - *testnet* -- True if connected to testnet, False if on real network. - - - *version* -- Bitcoin client version. - - - *proxy* -- Proxy configured in client. - - - *hashespersec* -- Number of hashes per second (if generation enabled). - - - *balance* -- Total current server balance. - - - *generate* -- True if generation enabled, False if not. - - - *unlocked_until* -- Timestamp (seconds since epoch) after which the wallet - will be/was locked (if wallet encryption is enabled). - - """ - - -class AccountInfo(DStruct): - """ - Information object returned by :func:`~mmgen.rpc.connection.BitcoinConnection.listreceivedbyaccount`. - - - *account* -- The account of the receiving address. - - - *amount* -- Total amount received by the address. - - - *confirmations* -- Number of confirmations of the most recent transaction included. - - """ - - -class AddressInfo(DStruct): - """ - Information object returned by :func:`~mmgen.rpc.connection.BitcoinConnection.listreceivedbyaddress`. - - - *address* -- Receiving address. - - - *account* -- The account of the receiving address. - - - *amount* -- Total amount received by the address. - - - *confirmations* -- Number of confirmations of the most recent transaction included. - - """ - - -class TransactionInfo(DStruct): - """ - Information object returned by :func:`~mmgen.rpc.connection.BitcoinConnection.listtransactions`. - - - *account* -- account name. - - - *address* -- the address bitcoins were sent to, or received from. - - - *category* -- will be generate, send, receive, or move. - - - *amount* -- amount of transaction. - - - *fee* -- Fee (if any) paid (only for send transactions). - - - *confirmations* -- number of confirmations (only for generate/send/receive). - - - *txid* -- transaction ID (only for generate/send/receive). - - - *otheraccount* -- account funds were moved to or from (only for move). - - - *message* -- message associated with transaction (only for send). - - - *to* -- message-to associated with transaction (only for send). - """ - - -class AddressValidation(DStruct): - """ - Information object returned by :func:`~mmgen.rpc.connection.BitcoinConnection.validateaddress`. - - - *isvalid* -- Validatity of address (:const:`True` or :const:`False`). - - - *ismine* -- :const:`True` if the address is in the server's wallet. - - - *address* -- Bitcoin address. - - """ - - -class WorkItem(DStruct): - """ - Information object returned by :func:`~mmgen.rpc.connection.BitcoinConnection.getwork`. - - - *midstate* -- Precomputed hash state after hashing the first half of the data. - - - *data* -- Block data. - - - *hash1* -- Formatted hash buffer for second hash. - - - *target* -- Little endian hash target. - - """ - - -class MiningInfo(DStruct): - """ - Information object returned by :func:`~mmgen.rpc.connection.BitcoinConnection.getmininginfo`. - - - *blocks* -- Number of blocks. - - - *currentblocksize* -- Size of current block. - - - *currentblocktx* -- Number of transactions in current block. - - - *difficulty* -- Current generating difficulty. - - - *errors* -- Number of errors. - - - *generate* -- True if generation enabled, False if not. - - - *genproclimit* -- Processor limit for generation. - - - *hashespersec* -- Number of hashes per second (if generation enabled). - - - *pooledtx* -- Number of pooled transactions. - - - *testnet* -- True if connected to testnet, False if on real network. - - """ diff --git a/mmgen/rpc/exceptions.py b/mmgen/rpc/exceptions.py deleted file mode 100755 index 873fe4cc..00000000 --- a/mmgen/rpc/exceptions.py +++ /dev/null @@ -1,203 +0,0 @@ -# Copyright (c) 2010 Witchspace -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. -""" -Exception definitions. -""" - - -class BitcoinException(Exception): - """ - Base class for exceptions received from Bitcoin server. - - - *code* -- Error code from ``bitcoind``. - """ - # Standard JSON-RPC 2.0 errors - INVALID_REQUEST = -32600, - METHOD_NOT_FOUND = -32601, - INVALID_PARAMS = -32602, - INTERNAL_ERROR = -32603, - PARSE_ERROR = -32700, - - # General application defined errors - MISC_ERROR = -1 # std::exception thrown in command handling - FORBIDDEN_BY_SAFE_MODE = -2 # Server is in safe mode, and command is not allowed in safe mode - TYPE_ERROR = -3 # Unexpected type was passed as parameter - INVALID_ADDRESS_OR_KEY = -5 # Invalid address or key - OUT_OF_MEMORY = -7 # Ran out of memory during operation - INVALID_PARAMETER = -8 # Invalid, missing or duplicate parameter - DATABASE_ERROR = -20 # Database error - DESERIALIZATION_ERROR = -22 # Error parsing or validating structure in raw format - - # P2P client errors - CLIENT_NOT_CONNECTED = -9 # Bitcoin is not connected - CLIENT_IN_INITIAL_DOWNLOAD = -10 # Still downloading initial blocks - - # Wallet errors - WALLET_ERROR = -4 # Unspecified problem with wallet (key not found etc.) - WALLET_INSUFFICIENT_FUNDS = -6 # Not enough funds in wallet or account - WALLET_INVALID_ACCOUNT_NAME = -11 # Invalid account name - WALLET_KEYPOOL_RAN_OUT = -12 # Keypool ran out, call keypoolrefill first - WALLET_UNLOCK_NEEDED = -13 # Enter the wallet passphrase with walletpassphrase first - WALLET_PASSPHRASE_INCORRECT = -14 # The wallet passphrase entered was incorrect - WALLET_WRONG_ENC_STATE = -15 # Command given in wrong wallet encryption state (encrypting an encrypted wallet etc.) - WALLET_ENCRYPTION_FAILED = -16 # Failed to encrypt the wallet - WALLET_ALREADY_UNLOCKED = -17 # Wallet is already unlocked - - def __init__(self, error): - Exception.__init__(self, error['message']) - self.code = error['code'] - - -##### General application defined errors -class SafeMode(BitcoinException): - """ - Operation denied in safe mode (run ``bitcoind`` with ``-disablesafemode``). - """ - - -class JSONTypeError(BitcoinException): - """ - Unexpected type was passed as parameter - """ -InvalidAmount = JSONTypeError # Backwards compatibility - - -class InvalidAddressOrKey(BitcoinException): - """ - Invalid address or key. - """ -InvalidTransactionID = InvalidAddressOrKey # Backwards compatibility - - -class OutOfMemory(BitcoinException): - """ - Out of memory during operation. - """ - - -class InvalidParameter(BitcoinException): - """ - Invalid parameter provided to RPC call. - """ - - -##### Client errors -class ClientException(BitcoinException): - """ - P2P network error. - This exception is never raised but functions as a superclass - for other P2P client exceptions. - """ - - -class NotConnected(ClientException): - """ - Not connected to any peers. - """ - - -class DownloadingBlocks(ClientException): - """ - Client is still downloading blocks. - """ - - -##### Wallet errors -class WalletError(BitcoinException): - """ - Unspecified problem with wallet (key not found etc.) - """ -SendError = WalletError # Backwards compatibility - -class InsufficientFunds(WalletError): - """ - Insufficient funds to complete transaction in wallet or account - """ - -class InvalidAccountName(WalletError): - """ - Invalid account name - """ - - -class KeypoolRanOut(WalletError): - """ - Keypool ran out, call keypoolrefill first - """ - - -class WalletUnlockNeeded(WalletError): - """ - Enter the wallet passphrase with walletpassphrase first - """ - - -class WalletPassphraseIncorrect(WalletError): - """ - The wallet passphrase entered was incorrect - """ - - -class WalletWrongEncState(WalletError): - """ - Command given in wrong wallet encryption state (encrypting an encrypted wallet etc.) - """ - - -class WalletEncryptionFailed(WalletError): - """ - Failed to encrypt the wallet - """ - - -class WalletAlreadyUnlocked(WalletError): - """ - Wallet is already unlocked - """ - - -# For convenience, we define more specific exception classes -# for the more common errors. -_exception_map = { - BitcoinException.FORBIDDEN_BY_SAFE_MODE: SafeMode, - BitcoinException.TYPE_ERROR: JSONTypeError, - BitcoinException.WALLET_ERROR: WalletError, - BitcoinException.INVALID_ADDRESS_OR_KEY: InvalidAddressOrKey, - BitcoinException.WALLET_INSUFFICIENT_FUNDS: InsufficientFunds, - BitcoinException.OUT_OF_MEMORY: OutOfMemory, - BitcoinException.INVALID_PARAMETER: InvalidParameter, - BitcoinException.CLIENT_NOT_CONNECTED: NotConnected, - BitcoinException.CLIENT_IN_INITIAL_DOWNLOAD: DownloadingBlocks, - BitcoinException.WALLET_INSUFFICIENT_FUNDS: InsufficientFunds, - BitcoinException.WALLET_INVALID_ACCOUNT_NAME: InvalidAccountName, - BitcoinException.WALLET_KEYPOOL_RAN_OUT: KeypoolRanOut, - BitcoinException.WALLET_UNLOCK_NEEDED: WalletUnlockNeeded, - BitcoinException.WALLET_PASSPHRASE_INCORRECT: WalletPassphraseIncorrect, - BitcoinException.WALLET_WRONG_ENC_STATE: WalletWrongEncState, - BitcoinException.WALLET_ENCRYPTION_FAILED: WalletEncryptionFailed, - BitcoinException.WALLET_ALREADY_UNLOCKED: WalletAlreadyUnlocked, -} - - -def _wrap_exception(error): - """ - Convert a JSON error object to a more specific Bitcoin exception. - """ - return _exception_map.get(error['code'], BitcoinException)(error) diff --git a/mmgen/rpc/proxy.py b/mmgen/rpc/proxy.py deleted file mode 100755 index 342f6adb..00000000 --- a/mmgen/rpc/proxy.py +++ /dev/null @@ -1,142 +0,0 @@ -""" - Copyright (C) 2013 by philemon - Added http_timeout from mmgen.globalvars - - Previous copyright from bitcoin-python/proxy.py: - - Copyright 2011 Jeff Garzik - - AuthServiceProxy has the following improvements over python-jsonrpc's - ServiceProxy class: - - - HTTP connections persist for the life of the AuthServiceProxy object - (if server supports HTTP/1.1) - - sends protocol 'version', per JSON-RPC 1.1 - - sends proper, incrementing 'id' - - sends Basic HTTP authentication headers - - parses all JSON numbers that look like floats as Decimal - - uses standard Python json lib - - Previous copyright, from python-jsonrpc/jsonrpc/proxy.py: - - Copyright (c) 2007 Jan-Klaas Kollhof - - This file is part of jsonrpc. - - jsonrpc is free software; you can redistribute it and/or modify - it under the terms of the GNU Lesser General Public License as published by - the Free Software Foundation; either version 2.1 of the License, or - (at your option) any later version. - - This software 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 Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public License - along with this software; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -""" - -try: - import http.client as httplib -except ImportError: - import httplib -import base64 -import json -import decimal -try: - import urllib.parse as urlparse -except ImportError: - import urlparse - -USER_AGENT = "AuthServiceProxy/0.1" - -class JSONRPCException(Exception): - def __init__(self, rpcError): - Exception.__init__(self) - self.error = rpcError - - -import mmgen.globalvars as g - -class AuthServiceProxy(object): - def __init__(self, serviceURL, serviceName = None): - - self.__serviceURL = serviceURL - self.__serviceName = serviceName - self.__url = urlparse.urlparse(serviceURL) - if self.__url.port is None: - port = 80 - else: - port = self.__url.port - self.__idcnt = 0 - authpair = "%s:%s" % (self.__url.username, self.__url.password) - authpair = authpair.encode('utf8') - self.__authhdr = "Basic ".encode('utf8') + base64.b64encode(authpair) - - http_timeout = g.http_timeout - - if self.__url.scheme == 'https': - self.__conn = httplib.HTTPSConnection(self.__url.hostname, port, - None, None, False, timeout=http_timeout) - else: - self.__conn = httplib.HTTPConnection(self.__url.hostname, port, - False, timeout=http_timeout) - - def __getattr__(self, name): - if self.__serviceName != None: - name = "%s.%s" % (self.__serviceName, name) - return AuthServiceProxy(self.__serviceURL, name) - - def __call__(self, *args): - self.__idcnt += 1 - - postdata = json.dumps({ - 'version': '1.1', - 'method': self.__serviceName, - 'params': args, - 'id': self.__idcnt}) - try: - self.__conn.request('POST', self.__url.path, postdata, - { 'Host' : self.__url.hostname, - 'User-Agent' : USER_AGENT, - 'Authorization' : self.__authhdr, - 'Content-type' : 'application/json' }) - except: - from mmgen.util import die,red - die(1,red("Unable to connect to bitcoind")) - - httpresp = self.__conn.getresponse() - if httpresp is None: - raise JSONRPCException({ - 'code' : -342, 'message' : 'missing HTTP response from server'}) - - resp = httpresp.read() - resp = resp.decode('utf8') - resp = json.loads(resp, parse_float=decimal.Decimal) - if 'error' in resp and resp['error'] != None: - raise JSONRPCException(resp['error']) - elif 'result' not in resp: - raise JSONRPCException({ - 'code' : -343, 'message' : 'missing JSON-RPC result'}) - else: - return resp['result'] - - def _batch(self, rpc_call_list): - postdata = json.dumps(list(rpc_call_list)) - self.__conn.request('POST', self.__url.path, postdata, - { 'Host' : self.__url.hostname, - 'User-Agent' : USER_AGENT, - 'Authorization' : self.__authhdr, - 'Content-type' : 'application/json' }) - - httpresp = self.__conn.getresponse() - if httpresp is None: - raise JSONRPCException({ - 'code' : -342, 'message' : 'missing HTTP response from server'}) - - resp = httpresp.read() - resp = resp.decode('utf8') - resp = json.loads(resp, parse_float=decimal.Decimal) - return resp diff --git a/mmgen/rpc/util.py b/mmgen/rpc/util.py deleted file mode 100755 index 35f71ce9..00000000 --- a/mmgen/rpc/util.py +++ /dev/null @@ -1,49 +0,0 @@ -# Copyright (c) 2010 Witchspace -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. -"""Generic utilities used by bitcoin client library.""" -from copy import copy - - -class DStruct(object): - """ - Simple dynamic structure, like :const:`collections.namedtuple` but more flexible - (and less memory-efficient) - """ - # Default arguments. Defaults are *shallow copied*, to allow defaults such as []. - _fields = [] - _defaults = {} - - def __init__(self, *args_t, **args_d): - # order - if len(args_t) > len(self._fields): - raise TypeError("Number of arguments is larger than of predefined fields") - # Copy default values - for (k, v) in self._defaults.iteritems(): - self.__dict__[k] = copy(v) - # Set pass by value arguments - self.__dict__.update(zip(self._fields, args_t)) - # dict - self.__dict__.update(args_d) - - def __repr__(self): - return '{module}.{classname}({slots})'.format( - module=self.__class__.__module__, classname=self.__class__.__name__, - slots=", ".join('{k}={v!r}'.format(k=k, v=v) for k, v in - self.__dict__.iteritems())) diff --git a/mmgen/seed.py b/mmgen/seed.py index d6041138..13a4825f 100755 --- a/mmgen/seed.py +++ b/mmgen/seed.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution -# Copyright (C)2013-2015 Philemon +# Copyright (C)2013-2016 Philemon # # 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 @@ -19,22 +19,21 @@ """ seed.py: Seed-related classes and methods for the MMGen suite """ -import sys,os + +import os from binascii import hexlify,unhexlify -import mmgen.globalvars as g -import mmgen.opt as opt +from mmgen.common import * from mmgen.bitcoin import b58encode_pad,b58decode_pad,b58_lens from mmgen.obj import * from mmgen.filename import * -from mmgen.util import * from mmgen.crypto import * pnm = g.proj_name def check_usr_seed_len(seed_len): if opt.seed_len != seed_len and 'seed_len' in opt.set_by_user: - m = "ERROR: requested seed length (%s) " + \ + m = 'ERROR: requested seed length (%s) ' + \ "doesn't match seed length of source (%s)" die(1, m % (opt.seed_len,seed_len)) @@ -45,7 +44,7 @@ class Seed(MMGenObject): # Truncate random data for smaller seed lengths seed_bin = sha256(get_random(1033)).digest()[:opt.seed_len/8] elif len(seed_bin)*8 not in g.seed_lens: - die(3,"%s: invalid seed length" % len(seed_bin)) + die(3,'%s: invalid seed length' % len(seed_bin)) self.data = seed_bin self.hexdata = hexlify(seed_bin) @@ -55,8 +54,8 @@ class Seed(MMGenObject): class SeedSource(MMGenObject): - desc = g.proj_name + " seed source" - file_mode = "text" + desc = g.proj_name + ' seed source' + file_mode = 'text' stdin_ok = False ask_tty = True no_tty = False @@ -71,8 +70,8 @@ class SeedSource(MMGenObject): def die_on_opt_mismatch(opt,sstype): opt_sstype = cls.fmt_code_to_sstype(opt) compare_or_die( - opt_sstype.__name__, "input format requested on command line", - sstype.__name__, "input file format" + opt_sstype.__name__, 'input format requested on command line', + sstype.__name__, 'input file format' ) if ss: @@ -83,31 +82,31 @@ class SeedSource(MMGenObject): me = super(cls,cls).__new__(sstype or Wallet) # default: Wallet me.seed = ss.seed me.ss_in = ss - me.op = ("conv","pwchg_new")[int(passchg)] + me.op = ('conv','pwchg_new')[bool(passchg)] elif fn or opt.hidden_incog_input_params: if fn: f = Filename(fn) sstype = cls.ext_to_sstype(f.ext) else: - fn = opt.hidden_incog_input_params.split(",")[0] - f = Filename(fn,ftype="hincog") - sstype = cls.fmt_code_to_sstype("hincog") + fn = opt.hidden_incog_input_params.split(',')[0] + f = Filename(fn,ftype='hincog') + sstype = cls.fmt_code_to_sstype('hincog') if opt.in_fmt and not ignore_in_fmt: die_on_opt_mismatch(opt.in_fmt,sstype) me = super(cls,cls).__new__(sstype) me.infile = f - me.op = ("old","pwchg_old")[int(passchg)] + me.op = ('old','pwchg_old')[bool(passchg)] elif opt.in_fmt: # Input format sstype = cls.fmt_code_to_sstype(opt.in_fmt) me = super(cls,cls).__new__(sstype) - me.op = ("old","pwchg_old")[int(passchg)] + me.op = ('old','pwchg_old')[bool(passchg)] else: # Called with no inputs - initialize with random seed sstype = cls.fmt_code_to_sstype(opt.out_fmt) me = super(cls,cls).__new__(sstype or Wallet) # default: Wallet me.seed = Seed(seed_bin=seed or None) - me.op = "new" + me.op = 'new' return me @@ -130,38 +129,38 @@ class SeedSource(MMGenObject): self._decrypt_retry() else: if not self.stdin_ok: - die(1,"Reading from standard input not supported for %s format" + die(1,'Reading from standard input not supported for %s format' % self.desc) self._deformat_retry() self._decrypt_retry() - m = ("",", seed length %s" % self.seed.length)[int(self.seed.length != 256)] - qmsg("Valid %s for Seed ID %s%s" % (self.desc,self.seed.sid,m)) + m = ('',', seed length %s' % self.seed.length)[self.seed.length!=256] + qmsg('Valid %s for Seed ID %s%s' % (self.desc,self.seed.sid,m)) def _get_data(self): if hasattr(self,'infile'): self.fmt_data = get_data_from_file(self.infile.name,self.desc, - binary=self.file_mode=="binary") + binary=self.file_mode=='binary') else: self.fmt_data = get_data_from_user(self.desc) def _deformat_once(self): self._get_data() if not self._deformat(): - die(2,"Invalid format for input data") + die(2,'Invalid format for input data') def _deformat_retry(self): while True: self._get_data() if self._deformat(): break - msg("Trying again...") + msg('Trying again...') def _decrypt_retry(self): while True: if self._decrypt(): break if opt.passwd_file: - die(2,"Passphrase from password file, so exiting") - msg("Trying again...") + die(2,'Passphrase from password file, so exiting') + msg('Trying again...') subclasses = [] @@ -185,7 +184,7 @@ class SeedSource(MMGenObject): def fmt_code_to_sstype(cls,fmt_code): if not fmt_code: return None for c in cls._get_subclasses(): - if hasattr(c,"fmt_codes") and fmt_code in c.fmt_codes: + if hasattr(c,'fmt_codes') and fmt_code in c.fmt_codes: return c return None @@ -193,20 +192,20 @@ class SeedSource(MMGenObject): def ext_to_sstype(cls,ext): if not ext: return None for c in cls._get_subclasses(): - if hasattr(c,"ext") and ext == c.ext: + if hasattr(c,'ext') and ext == c.ext: return c return None @classmethod def format_fmt_codes(cls): - d = [(c.__name__,",".join(c.fmt_codes)) for c in cls._get_subclasses() - if hasattr(c,"fmt_codes")] + d = [(c.__name__,','.join(c.fmt_codes)) for c in cls._get_subclasses() + if hasattr(c,'fmt_codes')] w = max([len(a) for a,b in d]) - ret = ["{:<{w}} {}".format(a,b,w=w) for a,b in [ - ("Format","Valid codes"), - ("------","-----------") + ret = ['{:<{w}} {}'.format(a,b,w=w) for a,b in [ + ('Format','Valid codes'), + ('------','-----------') ] + sorted(d)] - return "\n".join(ret) + "\n" + return '\n'.join(ret) + '\n' def get_fmt_data(self): self._format() @@ -218,7 +217,7 @@ class SeedSource(MMGenObject): 'desc': self.desc, 'ask_tty': self.ask_tty, 'no_tty': self.no_tty, - 'binary': self.file_mode == "binary" + 'binary': self.file_mode == 'binary' } write_data_to_file(self._filename(),self.fmt_data,**kwargs) @@ -240,17 +239,17 @@ an empty passphrase, just hit ENTER twice. """.strip() } - def _get_hash_preset_from_user(self,hp,desc_suf=""): + def _get_hash_preset_from_user(self,hp,desc_suf=''): # hp=a, - n = ("","old ")[int(self.op=="pwchg_old")] - m,n = (("to accept the default",n),("to reuse the old","new "))[ - int(self.op=="pwchg_new")] + n = ('','old ')[self.op=='pwchg_old'] + m,n = (('to accept the default',n),('to reuse the old','new '))[ + int(self.op=='pwchg_new')] fs = "Enter {}hash preset for {}{}{},\n or hit ENTER {} value ('{}'): " p = fs.format( n, - ("","new ")[int(self.op=="new")], + ('','new ')[self.op=='new'], self.desc, - (""," "+desc_suf)[int(bool(desc_suf))], + ('',' '+desc_suf)[bool(desc_suf)], m, hp ) @@ -261,14 +260,14 @@ an empty passphrase, just hit ENTER twice. self.ssdata.hash_preset = ret return ret else: - msg("Invalid input. Valid choices are %s" % - ", ".join(sorted(g.hash_presets.keys()))) + msg('Invalid input. Valid choices are %s' % + ', '.join(sorted(g.hash_presets.keys()))) else: self.ssdata.hash_preset = hp return hp - def _get_hash_preset(self,desc_suf=""): - if hasattr(self,"ss_in") and hasattr(self.ss_in.ssdata,"hash_preset"): + def _get_hash_preset(self,desc_suf=''): + if hasattr(self,'ss_in') and hasattr(self.ss_in.ssdata,'hash_preset'): old_hp = self.ss_in.ssdata.hash_preset if opt.keep_hash_preset: qmsg("Reusing hash preset '%s' at user request" % old_hp) @@ -280,9 +279,9 @@ an empty passphrase, just hit ENTER twice. else: # Prompt, using old value as default hp = self._get_hash_preset_from_user(old_hp,desc_suf) - if (not opt.keep_hash_preset) and self.op == "pwchg_new": - m = ("changed to '%s'" % hp,"unchanged")[int(hp==old_hp)] - qmsg("Hash preset %s" % m) + if (not opt.keep_hash_preset) and self.op == 'pwchg_new': + m = ("changed to '%s'" % hp,'unchanged')[hp==old_hp] + qmsg('Hash preset %s' % m) elif 'hash_preset' in opt.set_by_user: self.ssdata.hash_preset = opt.hash_preset qmsg("Using hash preset '%s' requested on command line"%opt.hash_preset) @@ -290,44 +289,43 @@ an empty passphrase, just hit ENTER twice. self._get_hash_preset_from_user(opt.hash_preset,desc_suf) def _get_new_passphrase(self): - desc = "{}passphrase for {}{}".format( - ("","new ")[int(self.op=="pwchg_new")], - ("","new ")[int(self.op in ("new","conv"))], + desc = '{}passphrase for {}{}'.format( + ('','new ')[self.op=='pwchg_new'], + ('','new ')[self.op in ('new','conv')], self.desc ) if opt.passwd_file: w = pwfile_reuse_warning() - pw = " ".join(get_words_from_file(opt.passwd_file,desc,silent=w)) + pw = ' '.join(get_words_from_file(opt.passwd_file,desc,silent=w)) elif opt.echo_passphrase: - pw = " ".join(get_words_from_user("Enter %s: " % desc)) + pw = ' '.join(get_words_from_user('Enter %s: ' % desc)) else: for i in range(g.passwd_max_tries): - pw = " ".join(get_words_from_user("Enter %s: " % desc)) - pw2 = " ".join(get_words_from_user("Repeat passphrase: ")) - dmsg("Passphrases: [%s] [%s]" % (pw,pw2)) + pw = ' '.join(get_words_from_user('Enter %s: ' % desc)) + pw2 = ' '.join(get_words_from_user('Repeat passphrase: ')) + dmsg('Passphrases: [%s] [%s]' % (pw,pw2)) if pw == pw2: - vmsg("Passphrases match"); break - else: msg("Passphrases do not match. Try again.") + vmsg('Passphrases match'); break + else: msg('Passphrases do not match. Try again.') else: - msg("User failed to duplicate passphrase in %s attempts" % + die(2,'User failed to duplicate passphrase in %s attempts' % g.passwd_max_tries) - sys.exit(2) - if pw == "": qmsg("WARNING: Empty passphrase") + if pw == '': qmsg('WARNING: Empty passphrase') self.ssdata.passwd = pw return pw - def _get_passphrase(self,desc_suf=""): - desc ="{}passphrase for {}{}".format( - ("","old ")[int(self.op=="pwchg_old")], + def _get_passphrase(self,desc_suf=''): + desc ='{}passphrase for {}{}'.format( + ('','old ')[self.op=='pwchg_old'], self.desc, - (""," "+desc_suf)[int(bool(desc_suf))] + ('',' '+desc_suf)[bool(desc_suf)] ) if opt.passwd_file: w = pwfile_reuse_warning() - ret = " ".join(get_words_from_file(opt.passwd_file,desc,silent=w)) + ret = ' '.join(get_words_from_file(opt.passwd_file,desc,silent=w)) else: - ret = " ".join(get_words_from_user("Enter %s: " % desc)) + ret = ' '.join(get_words_from_user('Enter %s: ' % desc)) self.ssdata.passwd = ret def _get_first_pw_and_hp_and_encrypt_seed(self): @@ -338,12 +336,12 @@ an empty passphrase, just hit ENTER twice. old_pw = self.ss_in.ssdata.passwd if opt.keep_passphrase: d.passwd = old_pw - qmsg("Reusing passphrase at user request") + qmsg('Reusing passphrase at user request') else: pw = self._get_new_passphrase() - if self.op == "pwchg_new": - m = ("changed","unchanged")[int(pw==old_pw)] - qmsg("Passphrase %s" % m) + if self.op == 'pwchg_new': + m = ('changed','unchanged')[pw==old_pw] + qmsg('Passphrase %s' % m) else: qmsg(self.msg['choose_passphrase'] % (self.desc,d.hash_preset)) self._get_new_passphrase() @@ -357,12 +355,12 @@ an empty passphrase, just hit ENTER twice. class Mnemonic (SeedSourceUnenc): stdin_ok = True - fmt_codes = "mmwords","words","mnemonic","mnem","mn","m" - desc = "mnemonic data" - ext = "mmwords" + fmt_codes = 'mmwords','words','mnemonic','mnem','mn','m' + desc = 'mnemonic data' + ext = 'mmwords' wl_checksums = { - "electrum": '5ca31424', - "tirosh": '1a5faeff' + 'electrum': '5ca31424', + 'tirosh': '1a5faeff' } mn_base = 1626 wordlists = sorted(wl_checksums) @@ -377,7 +375,7 @@ class Mnemonic (SeedSourceUnenc): def baseNtohex(base,words,wl,pad=0): deconv = [wl.index(words[::-1][i])*(base**i) for i in range(len(words))] - ret = ("{:0%sx}" % pad).format(sum(deconv)) + ret = ('{:0%sx}' % pad).format(sum(deconv)) return ('','0')[len(ret) % 2] + ret @staticmethod @@ -402,28 +400,28 @@ class Mnemonic (SeedSourceUnenc): def get_wordlist(cls,wordlist=None): wordlist = wordlist or g.default_wordlist if wordlist not in cls.wordlists: - die(1,'"%s": invalid wordlist. Valid choices: %s' % - (wordlist,'"'+'" "'.join(cls.wordlists)+'"')) + die(1,"'%s': invalid wordlist. Valid choices: '%s'" % + (wordlist,"' '".join(cls.wordlists))) - return __import__("mmgen.mn_"+wordlist,fromlist=["words"]).words.split() + return __import__('mmgen.mn_'+wordlist,fromlist=['words']).words.split() @classmethod def check_wordlist(cls,wlname): wl = cls.get_wordlist(wlname) - Msg("Wordlist: %s\nLength: %i words" % (capfirst(wlname),len(wl))) - new_chksum = sha256(" ".join(wl)).hexdigest()[:8] + Msg('Wordlist: %s\nLength: %i words' % (capfirst(wlname),len(wl))) + new_chksum = sha256(' '.join(wl)).hexdigest()[:8] if (sorted(wl) == wl): - Msg("List is sorted") + Msg('List is sorted') else: - die(3,"ERROR: List is not sorted!") + die(3,'ERROR: List is not sorted!') compare_chksums( - new_chksum,"generated checksum", - cls.wl_checksums[wlname],"saved checksum", + new_chksum,'generated checksum', + cls.wl_checksums[wlname],'saved checksum', die_on_fail=True) - Msg("Checksum %s matches" % new_chksum) + Msg('Checksum %s matches' % new_chksum) def _format(self): wl = self.get_wordlist() @@ -432,11 +430,11 @@ class Mnemonic (SeedSourceUnenc): ret = self.baseNtohex(self.mn_base,mn,wl,self._mn2hex_pad(mn)) # Internal error, so just die on fail - compare_or_die(ret,"recomputed seed", - seed_hex,"original",e="Internal error") + compare_or_die(ret,'recomputed seed', + seed_hex,'original',e='Internal error') self.ssdata.mnemonic = mn - self.fmt_data = " ".join(mn) + "\n" + self.fmt_data = ' '.join(mn) + '\n' def _deformat(self): @@ -444,13 +442,13 @@ class Mnemonic (SeedSourceUnenc): wl = self.get_wordlist() if len(mn) not in g.mn_lens: - msg("Invalid mnemonic (%i words). Allowed numbers of words: %s" % - (len(mn),", ".join([str(i) for i in g.mn_lens]))) + msg('Invalid mnemonic (%i words). Allowed numbers of words: %s' % + (len(mn),', '.join([str(i) for i in g.mn_lens]))) return False for n,w in enumerate(mn,1): if w not in wl: - msg("Invalid mnemonic: word #%s is not in the wordlist" % n) + msg('Invalid mnemonic: word #%s is not in the wordlist' % n) return False seed_hex = self.baseNtohex(self.mn_base,mn,wl,self._mn2hex_pad(mn)) @@ -458,8 +456,8 @@ class Mnemonic (SeedSourceUnenc): ret = self.hextobaseN(self.mn_base,seed_hex,wl,self._hex2mn_pad(seed_hex)) # Internal error, so just die - compare_or_die(" ".join(ret),"recomputed mnemonic", - " ".join(mn),"original",e="Internal error") + compare_or_die(' '.join(ret),'recomputed mnemonic', + ' '.join(mn),'original',e='Internal error') self.seed = Seed(unhexlify(seed_hex)) self.ssdata.mnemonic = mn @@ -469,21 +467,21 @@ class Mnemonic (SeedSourceUnenc): return True def _filename(self): - return "%s[%s].%s" % (self.seed.sid,self.seed.length,self.ext) + return '%s[%s].%s' % (self.seed.sid,self.seed.length,self.ext) class SeedFile (SeedSourceUnenc): stdin_ok = True - fmt_codes = "mmseed","seed","s" - desc = "seed data" - ext = "mmseed" + fmt_codes = 'mmseed','seed','s' + desc = 'seed data' + ext = 'mmseed' def _format(self): b58seed = b58encode_pad(self.seed.data) self.ssdata.chksum = make_chksum_6(b58seed) self.ssdata.b58seed = b58seed - self.fmt_data = "%s %s\n" % ( + self.fmt_data = '%s %s\n' % ( self.ssdata.chksum, split_into_cols(4,b58seed) ) @@ -493,10 +491,10 @@ class SeedFile (SeedSourceUnenc): ld = self.fmt_data.split() if not (7 <= len(ld) <= 12): # 6 <= padded b58 data (ld[1:]) <= 11 - msg("Invalid data length (%s) in %s" % (len(ld),desc)) + msg('Invalid data length (%s) in %s' % (len(ld),desc)) return False - a,b = ld[0],"".join(ld[1:]) + a,b = ld[0],''.join(ld[1:]) if not is_chksum_6(a): msg("'%s': invalid checksum format in %s" % (a, desc)) @@ -506,16 +504,16 @@ class SeedFile (SeedSourceUnenc): msg("'%s': not a base 58 string, in %s" % (b, desc)) return False - vmsg_r("Validating %s checksum..." % desc) + vmsg_r('Validating %s checksum...' % desc) if not compare_chksums( - a,"checksum",make_chksum_6(b),"base 58 data"): + a,'checksum',make_chksum_6(b),'base 58 data'): return False ret = b58decode_pad(b) if ret == False: - msg("Invalid base-58 encoded seed: %s" % val) + msg('Invalid base-58 encoded seed: %s' % val) return False self.seed = Seed(ret) @@ -527,27 +525,27 @@ class SeedFile (SeedSourceUnenc): return True def _filename(self): - return "%s[%s].%s" % (self.seed.sid,self.seed.length,self.ext) + return '%s[%s].%s' % (self.seed.sid,self.seed.length,self.ext) class Wallet (SeedSourceEnc): - fmt_codes = "wallet","w" - desc = g.proj_name + " wallet" - ext = "mmdat" + fmt_codes = 'wallet','w' + desc = g.proj_name + ' wallet' + ext = 'mmdat' - def _get_label_from_user(self,old_lbl=""): - d = ("to reuse the label '%s'" % old_lbl) if old_lbl else "for no label" - p = "Enter a wallet label, or hit ENTER %s: " % d + def _get_label_from_user(self,old_lbl=''): + d = ("to reuse the label '%s'" % old_lbl) if old_lbl else 'for no label' + p = 'Enter a wallet label, or hit ENTER %s: ' % d while True: ret = my_raw_input(p) if ret: if is_mmgen_wallet_label(ret): self.ssdata.label = ret; return ret else: - msg("Invalid label. Trying again...") + msg('Invalid label. Trying again...') else: - ret = old_lbl or "No Label" + ret = old_lbl or 'No Label' self.ssdata.label = ret; return ret # nearly identical to _get_hash_preset() - factor? @@ -563,9 +561,9 @@ class Wallet (SeedSourceEnc): else: # Prompt, using old value as default lbl = self._get_label_from_user(old_lbl) - if (not opt.keep_label) and self.op == "pwchg_new": - m = ("changed to '%s'" % lbl,"unchanged")[int(lbl==old_lbl)] - qmsg("Label %s" % m) + if (not opt.keep_label) and self.op == 'pwchg_new': + m = ("changed to '%s'" % lbl,'unchanged')[lbl==old_lbl] + qmsg('Label %s' % m) elif opt.label: qmsg("Using label '%s' requested on command line" % opt.label) self.ssdata.label = opt.label @@ -576,7 +574,7 @@ class Wallet (SeedSourceEnc): self._get_first_pw_and_hp_and_encrypt_seed() self._get_label() d = self.ssdata - d.pw_status = ("NE","E")[int(len(d.passwd)==0)] + d.pw_status = ('NE','E')[len(d.passwd)==0] d.timestamp = make_timestamp() def _format(self): @@ -586,32 +584,32 @@ class Wallet (SeedSourceEnc): es_fmt = b58encode_pad(d.enc_seed) lines = ( d.label, - "{} {} {} {} {}".format(s.sid.lower(), d.key_id.lower(), + '{} {} {} {} {}'.format(s.sid.lower(), d.key_id.lower(), s.length, d.pw_status, d.timestamp), - "{}: {} {} {}".format(d.hash_preset,*get_hash_params(d.hash_preset)), - "{} {}".format(make_chksum_6(slt_fmt),split_into_cols(4,slt_fmt)), - "{} {}".format(make_chksum_6(es_fmt), split_into_cols(4,es_fmt)) + '{}: {} {} {}'.format(d.hash_preset,*get_hash_params(d.hash_preset)), + '{} {}'.format(make_chksum_6(slt_fmt),split_into_cols(4,slt_fmt)), + '{} {}'.format(make_chksum_6(es_fmt), split_into_cols(4,es_fmt)) ) - chksum = make_chksum_6(" ".join(lines)) - self.fmt_data = "%s\n" % "\n".join((chksum,)+lines) + chksum = make_chksum_6(' '.join(lines)) + self.fmt_data = '%s\n' % '\n'.join((chksum,)+lines) def _deformat(self): def check_master_chksum(lines,desc): if len(lines) != 6: - msg("Invalid number of lines (%s) in %s data" % + msg('Invalid number of lines (%s) in %s data' % (len(lines),desc)) return False if not is_chksum_6(lines[0]): - msg("Incorrect master checksum (%s) in %s data" % + msg('Incorrect master checksum (%s) in %s data' % (lines[0],desc)) return False - chk = make_chksum_6(" ".join(lines[1:])) - if not compare_chksums(lines[0],"master",chk,"computed", - hdr="For wallet master checksum"): + chk = make_chksum_6(' '.join(lines[1:])) + if not compare_chksums(lines[0],'master',chk,'computed', + hdr='For wallet master checksum'): return False return True @@ -641,26 +639,26 @@ class Wallet (SeedSourceEnc): if hash_params != get_hash_params(d.hash_preset): msg("Hash parameters '%s' don't match hash preset '%s'" % - (" ".join(hash_params), d.hash_preset)) + (' '.join(hash_params), d.hash_preset)) return False lmin,lmax = b58_lens[0],b58_lens[-1] # 22,33,44 - for i,key in (4,"salt"),(5,"enc_seed"): - l = lines[i].split(" ") + for i,key in (4,'salt'),(5,'enc_seed'): + l = lines[i].split(' ') chk = l.pop(0) - b58_val = "".join(l) + b58_val = ''.join(l) if len(b58_val) < lmin or len(b58_val) > lmax: - msg("Invalid format for %s in %s: %s" % (key,self.desc,l)) + msg('Invalid format for %s in %s: %s' % (key,self.desc,l)) return False if not compare_chksums(chk,key, - make_chksum_6(b58_val),"computed checksum"): + make_chksum_6(b58_val),'computed checksum'): return False val = b58decode_pad(b58_val) if val == False: - msg("Invalid base 58 number: %s" % b58_val) + msg('Invalid base 58 number: %s' % b58_val) return False setattr(d,key,val) @@ -670,7 +668,7 @@ class Wallet (SeedSourceEnc): def _decrypt(self): d = self.ssdata # Needed for multiple transactions with {}-txsign - suf = ("",self.infile.name)[int(bool(opt.quiet))] + suf = ('',self.infile.name)[bool(opt.quiet)] self._get_passphrase(desc_suf=suf) key = make_key(d.passwd, d.salt, d.hash_preset) ret = decrypt_seed(d.enc_seed, key, d.seed_id, d.key_id) @@ -681,7 +679,7 @@ class Wallet (SeedSourceEnc): return False def _filename(self): - return "{}-{}[{},{}].{}".format( + return '{}-{}[{},{}].{}'.format( self.seed.sid, self.ssdata.key_id, self.seed.length, @@ -689,80 +687,48 @@ class Wallet (SeedSourceEnc): self.ext ) -# def __str__(self): -## label,metadata,hash_preset,salt,enc_seed): -# d = self.ssdata -# s = self.seed -# out = ["WALLET DATA"] -# fs = " {:18} {}" -# pw_empty = "Yes" if d.metadata[3] == "E" else "No" -# for i in ( -# ("Label:", d.label), -# ("Seed ID:", s.sid), -# ("Key ID:", d.key_id), -# ("Seed length:", "%s bits (%s bytes)" % (s.length,s.length/8)), -# ("Scrypt params:", "Preset '%s' (%s)" % (opt.hash_preset, -# " ".join([str(i) for i in get_hash_params(opt.hash_preset)]) -# ) -# ), -# ("Passphrase empty?", pw_empty), -# ("Timestamp:", "%s UTC" % d.metadata[4]), -# ): out.append(fs.format(*i)) -# -# fs = " {:6} {}" -# for i in ( -# ("Salt:", ""), -# (" b58:", b58encode_pad(d.salt)), -# (" hex:", hexlify(d.salt)), -# ("Encrypted seed:", ""), -# (" b58:", b58encode_pad(d.enc_seed)), -# (" hex:", hexlify(d.enc_seed)) -# ): out.append(fs.format(*i)) -# -# return "\n".join(out) - class Brainwallet (SeedSourceEnc): stdin_ok = True - fmt_codes = "mmbrain","brainwallet","brain","bw","b" - desc = "brainwallet" - ext = "mmbrain" + fmt_codes = 'mmbrain','brainwallet','brain','bw','b' + desc = 'brainwallet' + ext = 'mmbrain' # brainwallet warning message? TODO def get_bw_params(self): # already checked - a = opt.brain_params.split(",") + a = opt.brain_params.split(',') return int(a[0]),a[1] def _deformat(self): - self.brainpasswd = " ".join(self.fmt_data.split()) + self.brainpasswd = ' '.join(self.fmt_data.split()) return True def _decrypt(self): d = self.ssdata - if hasattr(opt,"brain_params"): + if hasattr(opt,'brain_params'): seed_len,d.hash_preset = self.get_bw_params() else: self._get_hash_preset() seed_len = opt.seed_len - vmsg_r("Hashing brainwallet data. Please wait...") + vmsg_r('Hashing brainwallet data. Please wait...') # Use buflen arg of scrypt.hash() to get seed of desired length - seed = scrypt_hash_passphrase(self.brainpasswd, "", + seed = scrypt_hash_passphrase(self.brainpasswd, '', d.hash_preset, buflen=seed_len/8) - vmsg("Done") + vmsg('Done') self.seed = Seed(seed) - msg("Seed ID: %s" % self.seed.sid) - qmsg("Check this value against your records") + msg('Seed ID: %s' % self.seed.sid) + qmsg('Check this value against your records') return True class IncogWallet (SeedSourceEnc): - file_mode = "binary" - fmt_codes = "mmincog","incog","icg","i" - desc = "incognito data" - ext = "mmincog" + file_mode = 'binary' + fmt_codes = 'mmincog','incog','icg','i' + desc = 'incognito data' + ext = 'mmincog' no_tty = True _msg = { @@ -788,7 +754,7 @@ to exit and re-run the program with the '--old-incog-fmt' option. def _make_iv_chksum(self,s): return sha256(s).hexdigest()[:8].upper() def _get_incog_data_len(self,seed_len): - e = (g.hincog_chk_len,0)[int(bool(opt.old_incog_fmt))] + e = (g.hincog_chk_len,0)[bool(opt.old_incog_fmt)] return g.aesctr_iv_len + g.salt_len + e + seed_len/8 def _incog_data_size_chk(self): @@ -799,38 +765,38 @@ to exit and re-run the program with the '--old-incog-fmt' option. return True else: if opt.old_incog_fmt: - msg("WARNING: old-style incognito format requested. " + - "Are you sure this is correct?") - msg(("Invalid incognito data size (%s bytes) for this " + - "seed length (%s bits)") % (dlen,opt.seed_len)) - msg("Valid data size for this seed length: %s bytes" % valid_dlen) + msg('WARNING: old-style incognito format requested. ' + + 'Are you sure this is correct?') + msg(('Invalid incognito data size (%s bytes) for this ' + + 'seed length (%s bits)') % (dlen,opt.seed_len)) + msg('Valid data size for this seed length: %s bytes' % valid_dlen) for sl in g.seed_lens: if dlen == self._get_incog_data_len(sl): - die(1,"Valid seed length for this data size: %s bits" % sl) - msg(("This data size (%s bytes) is invalid for all available " + - "seed lengths") % dlen) + die(1,'Valid seed length for this data size: %s bits' % sl) + msg(('This data size (%s bytes) is invalid for all available ' + + 'seed lengths') % dlen) return False def _encrypt (self): self._get_first_pw_and_hp_and_encrypt_seed() if opt.old_incog_fmt: - die(1,"Writing old-format incog wallets is unsupported") + die(1,'Writing old-format incog wallets is unsupported') d = self.ssdata # IV is used BOTH to initialize counter and to salt password! d.iv = get_random(g.aesctr_iv_len) d.iv_id = self._make_iv_chksum(d.iv) - msg("New Incog Wallet ID: %s" % d.iv_id) - qmsg("Make a record of this value") + msg('New Incog Wallet ID: %s' % d.iv_id) + qmsg('Make a record of this value') vmsg(self.msg['record_incog_id']) d.salt = get_random(g.salt_len) - key = make_key(d.passwd, d.salt, d.hash_preset, "incog wallet key") + key = make_key(d.passwd, d.salt, d.hash_preset, 'incog wallet key') chk = sha256(self.seed.data).digest()[:8] - d.enc_seed = encrypt_data(chk + self.seed.data, key, 1, "seed") + d.enc_seed = encrypt_data(chk + self.seed.data, key, 1, 'seed') - d.wrapper_key = make_key(d.passwd, d.iv, d.hash_preset, "incog wrapper key") + d.wrapper_key = make_key(d.passwd, d.iv, d.hash_preset, 'incog wrapper key') d.key_id = make_chksum_8(d.wrapper_key) - vmsg("Key ID: %s" % d.key_id) + vmsg('Key ID: %s' % d.key_id) d.target_data_len = self._get_incog_data_len(self.seed.length) def _format(self): @@ -845,7 +811,7 @@ to exit and re-run the program with the '--old-incog-fmt' option. def _filename(self): s = self.seed d = self.ssdata - return "{}-{}-{}[{},{}].{}".format( + return '{}-{}-{}[{},{}].{}'.format( s.sid, d.key_id, d.iv_id, @@ -862,8 +828,8 @@ to exit and re-run the program with the '--old-incog-fmt' option. d.iv = self.fmt_data[0:g.aesctr_iv_len] d.incog_id = self._make_iv_chksum(d.iv) d.enc_incog_data = self.fmt_data[g.aesctr_iv_len:] - msg("Incog Wallet ID: %s" % d.incog_id) - qmsg("Check this value against your records") + msg('Incog Wallet ID: %s' % d.incog_id) + qmsg('Check this value against your records') vmsg(self.msg['check_incog_id']) return True @@ -871,14 +837,14 @@ to exit and re-run the program with the '--old-incog-fmt' option. def _verify_seed_newfmt(self,data): chk,seed = data[:8],data[8:] if sha256(seed).digest()[:8] == chk: - qmsg("Passphrase%s are correct" % (self.msg['dec_chk'] % "and")) + qmsg('Passphrase%s are correct' % (self.msg['dec_chk'] % 'and')) return seed else: - msg("Incorrect passphrase%s" % (self.msg['dec_chk'] % "or")) + msg('Incorrect passphrase%s' % (self.msg['dec_chk'] % 'or')) return False def _verify_seed_oldfmt(self,seed): - m = "Seed ID: %s. Is the Seed ID correct?" % make_chksum_8(seed) + m = 'Seed ID: %s. Is the Seed ID correct?' % make_chksum_8(seed) if keypress_confirm(m, True): return seed else: @@ -890,24 +856,24 @@ to exit and re-run the program with the '--old-incog-fmt' option. self._get_passphrase(desc_suf=d.incog_id) # IV is used BOTH to initialize counter and to salt password! - key = make_key(d.passwd, d.iv, d.hash_preset, "wrapper key") + key = make_key(d.passwd, d.iv, d.hash_preset, 'wrapper key') dd = decrypt_data(d.enc_incog_data, key, - int(hexlify(d.iv),16), "incog data") + int(hexlify(d.iv),16), 'incog data') d.salt = dd[0:g.salt_len] d.enc_seed = dd[g.salt_len:] - key = make_key(d.passwd, d.salt, d.hash_preset, "main key") - qmsg("Key ID: %s" % make_chksum_8(key)) + key = make_key(d.passwd, d.salt, d.hash_preset, 'main key') + qmsg('Key ID: %s' % make_chksum_8(key)) - verify_seed = getattr(self,"_verify_seed_"+ - ("newfmt","oldfmt")[int(bool(opt.old_incog_fmt))]) + verify_seed = getattr(self,'_verify_seed_'+ + ('newfmt','oldfmt')[bool(opt.old_incog_fmt)]) - seed = verify_seed(decrypt_seed(d.enc_seed, key, "", "")) + seed = verify_seed(decrypt_seed(d.enc_seed, key, '', '')) if seed: self.seed = Seed(seed) - msg("Seed ID: %s" % self.seed.sid) + msg('Seed ID: %s' % self.seed.sid) return True else: return False @@ -915,10 +881,10 @@ to exit and re-run the program with the '--old-incog-fmt' option. class IncogWalletHex (IncogWallet): - file_mode = "text" - desc = "hex incognito data" - fmt_codes = "mmincox","incox","incog_hex","xincog","ix","xi" - ext = "mmincox" + file_mode = 'text' + desc = 'hex incognito data' + fmt_codes = 'mmincox','incox','incog_hex','xincog','ix','xi' + ext = 'mmincox' no_tty = False def _deformat(self): @@ -936,8 +902,8 @@ class IncogWalletHex (IncogWallet): class IncogWalletHidden (IncogWallet): - desc = "hidden incognito data" - fmt_codes = "incog_hidden","hincog","ih","hi" + desc = 'hidden incognito data' + fmt_codes = 'incog_hidden','hincog','ih','hi' ext = None _msg = { @@ -957,18 +923,18 @@ harder to find, you're advised to choose a much larger file size than this. identify the incog wallet data in the future and to locate the offset where the data is hidden in the event you forget it. """, - 'dec_chk': ", hash preset, offset %s seed length" + 'dec_chk': ', hash preset, offset %s seed length' } def _get_hincog_params(self,wtype): p = getattr(opt,'hidden_incog_'+ wtype +'_params') - a,b = p.split(",") + a,b = p.split(',') return a,int(b) def _check_valid_offset(self,fn,action): d = self.ssdata - m = ("Input","Destination")[int(action=="write")] + m = ('Input','Destination')[action=='write'] if fn.size < d.hincog_offset + d.target_data_len: die(1, "%s file '%s' has length %s, too short to %s %s bytes of data at offset %s" @@ -976,29 +942,29 @@ harder to find, you're advised to choose a much larger file size than this. def _get_data(self): d = self.ssdata - d.hincog_offset = self._get_hincog_params("input")[1] + d.hincog_offset = self._get_hincog_params('input')[1] qmsg("Getting hidden incog data from file '%s'" % self.infile.name) # Already sanity-checked: d.target_data_len = self._get_incog_data_len(opt.seed_len) - self._check_valid_offset(self.infile,"read") + self._check_valid_offset(self.infile,'read') fh = os.open(self.infile.name,os.O_RDONLY) os.lseek(fh,int(d.hincog_offset),os.SEEK_SET) self.fmt_data = os.read(fh,d.target_data_len) os.close(fh) qmsg("Data read from file '%s' at offset %s" % - (self.infile.name,d.hincog_offset), "Data read from file") + (self.infile.name,d.hincog_offset), 'Data read from file') # overrides method in SeedSource def write_to_file(self): d = self.ssdata self._format() - compare_or_die(d.target_data_len, "target data length", - len(self.fmt_data),"length of formatted " + self.desc) + compare_or_die(d.target_data_len, 'target data length', + len(self.fmt_data),'length of formatted ' + self.desc) - k = ("output","input")[int(self.op=="pwchg_new")] + k = ('output','input')[self.op=='pwchg_new'] fn,d.hincog_offset = self._get_hincog_params(k) check_offset = True @@ -1010,25 +976,25 @@ harder to find, you're advised to choose a much larger file size than this. min_fsize = d.target_data_len + d.hincog_offset msg(self.msg['choose_file_size'].format(min_fsize)) while True: - fsize = parse_nbytes(my_raw_input("Enter file size: ")) + fsize = parse_nbytes(my_raw_input('Enter file size: ')) if fsize >= min_fsize: break - msg("File size must be an integer no less than %s" % + msg('File size must be an integer no less than %s' % min_fsize) from mmgen.tool import rand2file rand2file(fn, str(fsize)) check_offset = False else: - die(1,"Exiting at user request") + die(1,'Exiting at user request') self.outfile = f = Filename(fn,ftype=self.fmt_codes[0],write=True) - dmsg("%s data len %s, offset %s" % ( + dmsg('%s data len %s, offset %s' % ( capfirst(self.desc),d.target_data_len,d.hincog_offset)) if check_offset: - self._check_valid_offset(f,"write") - if not opt.quiet: confirm_or_exit("","alter file '%s'" % f.name) + self._check_valid_offset(f,'write') + if not opt.quiet: confirm_or_exit('',"alter file '%s'" % f.name) fh = os.open(f.name,os.O_RDWR) os.lseek(fh, int(d.hincog_offset), os.SEEK_SET) diff --git a/mmgen/share/Opts.py b/mmgen/share/Opts.py index 6654853a..62b5697b 100755 --- a/mmgen/share/Opts.py +++ b/mmgen/share/Opts.py @@ -1,6 +1,6 @@ #!/usr/bin/env python # -# Opts.py, an options parsing library for Python. Copyright (C) 2014 by +# Opts.py, an options parsing library for Python. Copyright (C) 2014-2016 # Philemon . # # This program is free software: you can redistribute it and/or modify @@ -16,28 +16,32 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . +""" +Opts.py: Generic options handling +""" + import sys, getopt def usage(opts_data): - print "USAGE: %s %s" % (opts_data['prog_name'], opts_data['usage']) + print 'USAGE: %s %s' % (opts_data['prog_name'], opts_data['usage']) sys.exit(2) def print_help(opts_data): pn = opts_data['prog_name'] pn_len = str(len(pn)+2) - print (" %-"+pn_len+"s %s") % (pn.upper()+":", opts_data['desc'].strip()) - print (" %-"+pn_len+"s %s %s")%("USAGE:", pn, opts_data['usage'].strip()) - sep = "\n " - print " OPTIONS:"+sep+"%s" % sep.join(opts_data['options'].strip().splitlines()) - if "notes" in opts_data: - print " %s" % "\n ".join(opts_data['notes'][1:-1].splitlines()) + print (' %-'+pn_len+'s %s') % (pn.upper()+':', opts_data['desc'].strip()) + print (' %-'+pn_len+'s %s %s')%('USAGE:', pn, opts_data['usage'].strip()) + sep = '\n ' + print ' OPTIONS:' + sep + sep.join(opts_data['options'].strip().splitlines()) + if 'notes' in opts_data: + print ' ' + '\n '.join(opts_data['notes'][1:-1].splitlines()) def process_opts(argv,opts_data,short_opts,long_opts): import os opts_data['prog_name'] = os.path.basename(sys.argv[0]) - long_opts = [i.replace("_","-") for i in long_opts] + long_opts = [i.replace('_','-') for i in long_opts] try: cl_opts,args = getopt.getopt(argv[1:], short_opts, long_opts) except getopt.GetoptError as err: @@ -46,21 +50,21 @@ def process_opts(argv,opts_data,short_opts,long_opts): opts,short_opts_l = {},[] for i in short_opts: - if i == ":": short_opts_l[-1] += i + if i == ':': short_opts_l[-1] += i else: short_opts_l += i for opt, arg in cl_opts: - if opt in ("-h","--help"): print_help(opts_data); sys.exit() - elif opt[:2] == "--" and opt[2:] in long_opts: - opts[opt[2:].replace("-","_")] = True - elif opt[:2] == "--" and opt[2:]+"=" in long_opts: - opts[opt[2:].replace("-","_")] = arg - elif opt[0] == "-" and opt[1] in short_opts_l: - opts[long_opts[short_opts_l.index(opt[1:])].replace("-","_")] = True - elif opt[0] == "-" and opt[1:]+":" in short_opts_l: + if opt in ('-h','--help'): print_help(opts_data); sys.exit() + elif opt[:2] == '--' and opt[2:] in long_opts: + opts[opt[2:].replace('-','_')] = True + elif opt[:2] == '--' and opt[2:]+'=' in long_opts: + opts[opt[2:].replace('-','_')] = arg + elif opt[0] == '-' and opt[1] in short_opts_l: + opts[long_opts[short_opts_l.index(opt[1:])].replace('-','_')] = True + elif opt[0] == '-' and opt[1:]+':' in short_opts_l: opts[long_opts[short_opts_l.index( - opt[1:]+":")][:-1].replace("-","_")] = arg - else: assert False, "Invalid option" + opt[1:]+':')][:-1].replace('-','_')] = arg + else: assert False, 'Invalid option' if 'sets' in opts_data: for o_in,v_in,o_out,v_out in opts_data['sets']: @@ -69,9 +73,9 @@ def process_opts(argv,opts_data,short_opts,long_opts): if (v and v_in == bool) or v == v_in: if o_out in opts and opts[o_out] != v_out: sys.stderr.write( - "Option conflict:\n --%s=%s, with\n --%s=%s\n" % ( - o_out.replace("_","-"),opts[o_out], - o_in.replace("_","-"),opts[o_in] + 'Option conflict:\n --%s=%s, with\n --%s=%s\n' % ( + o_out.replace('_','-'),opts[o_out], + o_in.replace('_','-'),opts[o_in] )) sys.exit(1) else: @@ -83,24 +87,24 @@ def process_opts(argv,opts_data,short_opts,long_opts): def parse_opts(argv,opts_data,opt_filter=None): import re - pat = r"^-([a-zA-Z0-9]), --([a-zA-Z0-9-]{2,64})(=| )(.+)" + pat = r'^-([a-zA-Z0-9]), --([a-zA-Z0-9-]{2,64})(=| )(.+)' od,skip = [],True for l in opts_data['options'].strip().splitlines(): m = re.match(pat,l) if m: - skip = True if (opt_filter and m.group(1) not in opt_filter) else False - app = [':','='] if (m.group(3) == '=') else ['',''] + skip = (False,True)[bool(opt_filter) and m.group(1) not in opt_filter] + app = (['',''],[':','='])[m.group(3) == '='] od.append(list(m.groups()) + app + [skip]) else: - if not skip: od[-1][3] += "\n" + l + if not skip: od[-1][3] += '\n' + l - opts_data['options'] = "\n".join( - ["-{}, --{} {}".format(d[0],d[1],d[3]) for d in od if d[6] == False] + opts_data['options'] = '\n'.join( + ['-{}, --{} {}'.format(d[0],d[1],d[3]) for d in od if d[6] == False] ) - short_opts = "".join([d[0]+d[4] for d in od if d[6] == False]) - long_opts = [d[1].replace("-","_")+d[5] for d in od if d[6] == False] - skipped_opts = [d[1].replace("-","_") for d in od if d[6] == True] + short_opts = ''.join([d[0]+d[4] for d in od if d[6] == False]) + long_opts = [d[1].replace('-','_')+d[5] for d in od if d[6] == False] + skipped_opts = [d[1].replace('-','_') for d in od if d[6] == True] opts,args = process_opts(argv,opts_data,short_opts,long_opts) diff --git a/mmgen/share/__init__.py b/mmgen/share/__init__.py index c9f5d8e7..17127479 100755 --- a/mmgen/share/__init__.py +++ b/mmgen/share/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution -# Copyright (C) 2013-2015 by philemon +# Copyright (C) 2013-2016 Philemon # # 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 diff --git a/mmgen/term.py b/mmgen/term.py index f19be1d6..ab92dc08 100755 --- a/mmgen/term.py +++ b/mmgen/term.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution -# Copyright (C)2013-2015 Philemon +# Copyright (C)2013-2016 Philemon # # 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 @@ -20,10 +20,9 @@ term.py: Terminal-handling routines for the MMGen suite """ -import sys, os, struct -import mmgen.globalvars as g -import opt -from mmgen.util import msg, msg_r +import os,struct + +from mmgen.common import * def _kb_hold_protect_unix(): @@ -42,7 +41,7 @@ def _kb_hold_protect_unix(): def _kb_hold_protect_unix_raw(): pass -def _get_keypress_unix(prompt="",immed_chars="",prehold_protect=True): +def _get_keypress_unix(prompt='',immed_chars='',prehold_protect=True): msg_r(prompt) timeout = float(0.3) @@ -57,8 +56,8 @@ def _get_keypress_unix(prompt="",immed_chars="",prehold_protect=True): ch = sys.stdin.read(1) if prehold_protect: if key: continue - if immed_chars == "ALL" or ch in immed_chars: break - if immed_chars == "ALL_EXCEPT_ENTER" and not ch in "\n\r": break + if immed_chars == 'ALL' or ch in immed_chars: break + if immed_chars == 'ALL_EXCEPT_ENTER' and not ch in '\n\r': break # Protect against long keypress key = select([sys.stdin], [], [], timeout)[0] if not key: break @@ -67,7 +66,7 @@ def _get_keypress_unix(prompt="",immed_chars="",prehold_protect=True): return ch -def _get_keypress_unix_raw(prompt="",immed_chars="",prehold_protect=None): +def _get_keypress_unix_raw(prompt='',immed_chars='',prehold_protect=None): msg_r(prompt) @@ -98,7 +97,7 @@ def _kb_hold_protect_mswin(): def _kb_hold_protect_mswin_raw(): pass -def _get_keypress_mswin(prompt="",immed_chars="",prehold_protect=True): +def _get_keypress_mswin(prompt='',immed_chars='',prehold_protect=True): msg_r(prompt) timeout = float(0.5) @@ -109,9 +108,9 @@ def _get_keypress_mswin(prompt="",immed_chars="",prehold_protect=True): if ord(ch) == 3: raise KeyboardInterrupt - if immed_chars == "ALL" or ch in immed_chars: + if immed_chars == 'ALL' or ch in immed_chars: return ch - if immed_chars == "ALL_EXCEPT_ENTER" and not ch in "\n\r": + if immed_chars == 'ALL_EXCEPT_ENTER' and not ch in '\n\r': return ch hit_time = time.time() @@ -121,7 +120,7 @@ def _get_keypress_mswin(prompt="",immed_chars="",prehold_protect=True): if float(time.time() - hit_time) > timeout: return ch -def _get_keypress_mswin_raw(prompt="",immed_chars="",prehold_protect=None): +def _get_keypress_mswin_raw(prompt='',immed_chars='',prehold_protect=None): msg_r(prompt) ch = msvcrt.getch() @@ -160,7 +159,7 @@ def _get_terminal_size_linux(): def _get_terminal_size_mswin(): try: - from ctypes import windll, create_string_buffer + from ctypes import windll,create_string_buffer # stdin handle is -10 # stdout handle is -11 # stderr handle is -12 @@ -169,7 +168,7 @@ def _get_terminal_size_mswin(): res = windll.kernel32.GetConsoleScreenBufferInfo(h, csbi) if res: (bufx, bufy, curx, cury, wattr, left, top, right, bottom, - maxx, maxy) = struct.unpack("hhhhHhhhhhh", csbi.raw) + maxx, maxy) = struct.unpack('hhhhHhhhhhh', csbi.raw) sizex = right - left + 1 sizey = bottom - top + 1 return sizex, sizey @@ -179,7 +178,7 @@ def _get_terminal_size_mswin(): def mswin_dummy_flush(fd,termconst): pass try: - import tty, termios + import tty,termios from select import select if g.disable_hold_protect: get_char = _get_keypress_unix_raw @@ -192,7 +191,7 @@ try: # call: myflush(sys.stdin, termios.TCIOFLUSH) except: try: - import msvcrt, time + import msvcrt,time if g.disable_hold_protect: get_char = _get_keypress_mswin_raw kb_hold_protect = _kb_hold_protect_mswin_raw @@ -202,18 +201,18 @@ except: get_terminal_size = _get_terminal_size_mswin myflush = mswin_dummy_flush except: - if not sys.platform.startswith("linux") \ - and not sys.platform.startswith("win"): - msg("Unsupported platform: %s" % sys.platform) - msg("This program currently runs only on Linux and Windows") + if not sys.platform.startswith('linux') \ + and not sys.platform.startswith('win'): + msg('Unsupported platform: %s' % sys.platform) + msg('This program currently runs only on Linux and Windows') else: - msg("Unable to set terminal mode") + msg('Unable to set terminal mode') sys.exit(2) def do_pager(text): - pagers = ["less","more"] + pagers = ['less','more'] shell = False from os import environ @@ -223,23 +222,23 @@ def do_pager(text): # not found. # When 'shell' is false, an exception is raised, invoking the fallback # 'print' instead of the pager. -# We risk assuming that "more" will always be available on a stock +# We risk assuming that 'more' will always be available on a stock # Windows installation. - if sys.platform.startswith("win") and 'HOME' not in environ: + if sys.platform.startswith('win') and 'HOME' not in environ: shell = True - pagers = ["more"] + pagers = ['more'] if 'PAGER' in environ and environ['PAGER'] != pagers[0]: pagers = [environ['PAGER']] + pagers for pager in pagers: - end = "" if pager == "less" else "\n(end of text)\n" + end = ('\n(end of text)\n','')[pager=='less'] try: - from subprocess import Popen, PIPE, STDOUT + from subprocess import Popen,PIPE,STDOUT p = Popen([pager], stdin=PIPE, shell=shell) except: pass else: - p.communicate(text+end+"\n") - msg_r("\r") + p.communicate(text+end+'\n') + msg_r('\r') break else: Msg(text+end) diff --git a/mmgen/test.py b/mmgen/test.py index df225923..0d9a9cfb 100755 --- a/mmgen/test.py +++ b/mmgen/test.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution -# Copyright (C)2013-2015 Philemon +# Copyright (C)2013-2016 Philemon # # 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 @@ -20,10 +20,10 @@ test.py: Shared routines for the test suites """ -import sys,os +import os from binascii import hexlify -from mmgen.util import msg,write_data_to_file,red,green -import mmgen.opt as opt + +from mmgen.common import * def cleandir(d): try: files = os.listdir(d) @@ -38,7 +38,7 @@ def getrandhex(n): return hexlify(os.urandom(n)) def getrandstr(num_chars,no_space=False): n,m = 95,32 if no_space: n,m = 94,33 - return "".join([chr(ord(i)%n+m) for i in list(os.urandom(num_chars))]) + return ''.join([chr(ord(i)%n+m) for i in list(os.urandom(num_chars))]) def mk_tmpdir(cfg): try: os.mkdir(cfg['tmpdir'],0755) @@ -66,8 +66,8 @@ def read_from_tmpfile(cfg,fn,binary=False): def ok(): if opt.verbose or opt.exact_output: - sys.stderr.write(green("OK\n")) - else: msg(" OK") + sys.stderr.write(green('OK\n')) + else: msg(' OK') def ok_or_die(val,chk_func,s,skip_ok=False): try: ret = chk_func(val) @@ -83,6 +83,6 @@ def cmp_or_die(s,t,skip_ok=False): if not skip_ok: ok() else: sys.stderr.write(red( - "ERROR: recoded data:\n%s\ndiffers from original data:\n%s\n" % + 'ERROR: recoded data:\n%s\ndiffers from original data:\n%s\n' % (repr(t),repr(s)))) sys.exit(3) diff --git a/mmgen/tool.py b/mmgen/tool.py index 943c066a..102b7d73 100755 --- a/mmgen/tool.py +++ b/mmgen/tool.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution -# Copyright (C)2013-2015 Philemon +# Copyright (C)2013-2016 Philemon # # 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 @@ -20,74 +20,71 @@ tool.py: Routines and data for the 'mmgen-tool' utility """ -import sys -import mmgen.bitcoin as bitcoin import binascii as ba -import mmgen.globalvars as g -import mmgen.opt as opt +import mmgen.bitcoin as bitcoin +from mmgen.common import * from mmgen.crypto import * -from mmgen.util import * from mmgen.tx import * pnm = g.proj_name from collections import OrderedDict cmd_data = OrderedDict([ - ("help", [' [str]']), - ("usage", [' [str]']), - ("strtob58", [' [str]']), - ("b58tostr", [' [str]']), - ("hextob58", [' [str]']), - ("b58tohex", [' [str]']), - ("b58randenc", []), - ("b32tohex", [' [str]']), - ("hextob32", [' [str]']), - ("randhex", ['nbytes [int=32]']), - ("id8", [' [str]']), - ("id6", [' [str]']), - ("sha256x2", [' [str]', + ('help', [' [str]']), + ('usage', [' [str]']), + ('strtob58', [' [str]']), + ('b58tostr', [' [str]']), + ('hextob58', [' [str]']), + ('b58tohex', [' [str]']), + ('b58randenc', []), + ('b32tohex', [' [str]']), + ('hextob32', [' [str]']), + ('randhex', ['nbytes [int=32]']), + ('id8', [' [str]']), + ('id6', [' [str]']), + ('sha256x2', [' [str]', 'hex_input [bool=False]','file_input [bool=False]']), - ("str2id6", [' [str]']), - ("hexdump", [' [str]', 'cols [int=8]', 'line_nums [bool=True]']), - ("unhexdump", [' [str]']), - ("hexreverse", [' [str]']), - ("hexlify", [' [str]']), - ("rand2file", [' [str]',' [str]','threads [int=4]','silent [bool=False']), + ('str2id6', [' [str]']), + ('hexdump', [' [str]', 'cols [int=8]', 'line_nums [bool=True]']), + ('unhexdump', [' [str]']), + ('hexreverse', [' [str]']), + ('hexlify', [' [str]']), + ('rand2file', [' [str]',' [str]','threads [int=4]','silent [bool=False]']), - ("randwif", ['compressed [bool=False]']), - ("randpair", ['compressed [bool=False]']), - ("hex2wif", [' [str]', 'compressed [bool=False]']), - ("wif2hex", [' [str]', 'compressed [bool=False]']), - ("wif2addr", [' [str]', 'compressed [bool=False]']), - ("hexaddr2addr", [' [str]']), - ("addr2hexaddr", [' [str]']), - ("pubkey2addr", [' [str]']), - ("pubkey2hexaddr", [' [str]']), - ("privhex2addr", [' [str]','compressed [bool=False]']), + ('randwif', ['compressed [bool=False]']), + ('randpair', ['compressed [bool=False]']), + ('hex2wif', [' [str]', 'compressed [bool=False]']), + ('wif2hex', [' [str]', 'compressed [bool=False]']), + ('wif2addr', [' [str]', 'compressed [bool=False]']), + ('hexaddr2addr', [' [str]']), + ('addr2hexaddr', [' [str]']), + ('pubkey2addr', [' [str]']), + ('pubkey2hexaddr', [' [str]']), + ('privhex2addr', [' [str]','compressed [bool=False]']), - ("hex2mn", [' [str]','wordlist [str="electrum"]']), - ("mn2hex", [' [str]', 'wordlist [str="electrum"]']), - ("mn_rand128", ['wordlist [str="electrum"]']), - ("mn_rand192", ['wordlist [str="electrum"]']), - ("mn_rand256", ['wordlist [str="electrum"]']), - ("mn_stats", ['wordlist [str="electrum"]']), - ("mn_printlist", ['wordlist [str="electrum"]']), + ('hex2mn', [' [str]',"wordlist [str='electrum']"]), + ('mn2hex', [' [str]', "wordlist [str='electrum']"]), + ('mn_rand128', ["wordlist [str='electrum']"]), + ('mn_rand192', ["wordlist [str='electrum']"]), + ('mn_rand256', ["wordlist [str='electrum']"]), + ('mn_stats', ["wordlist [str='electrum']"]), + ('mn_printlist', ["wordlist [str='electrum']"]), - ("listaddresses",['addrs [str='']','minconf [int=1]','showempty [bool=False]','pager [bool=False]','showbtcaddrs [bool=False]']), - ("getbalance", ['minconf [int=1]']), - ("txview", ['<{pnm} tx file> [str]','pager [bool=False]','terse [bool=False]'.format(pnm=pnm)]), + ('listaddresses',["addrs [str='']",'minconf [int=1]','showempty [bool=False]','pager [bool=False]','showbtcaddrs [bool=False]']), + ('getbalance', ['minconf [int=1]']), + ('txview', ['<{} TX file> [str]'.format(pnm),'pager [bool=False]','terse [bool=False]']), - ("add_label", ['<{pnm} address> [str]'.format(pnm=pnm),'