From aa66a440668c9984eeed0bdc4889ae65939eda0a Mon Sep 17 00:00:00 2001 From: philemon Date: Sun, 8 Dec 2013 07:36:49 +0400 Subject: [PATCH] TX scripts updates; import address feature added new file: mmgen-addrimport new file: mmgen/connection.py new file: mmgen/proxy.py --- MANIFEST | 3 + mmgen-addrgen | 174 +++++++++- mmgen-addrimport | 69 ++++ mmgen-keygen | 175 +--------- mmgen-passchg | 5 +- mmgen-txcreate | 28 +- mmgen-txsend | 2 +- mmgen-txsign | 2 +- mmgen-walletchk | 6 +- mmgen/bitcoin.py | 37 ++- mmgen/connection.py | 737 +++++++++++++++++++++++++++++++++++++++++++ mmgen/proxy.py | 130 ++++++++ mmgen/tx.py | 35 +- mmgen/utils.py | 65 +++- scripts/deinstall.sh | 3 + setup.py | 18 +- tests/bitcoin.py | 5 + 17 files changed, 1278 insertions(+), 216 deletions(-) mode change 120000 => 100755 mmgen-addrgen create mode 100755 mmgen-addrimport mode change 100755 => 120000 mmgen-keygen create mode 100755 mmgen/connection.py create mode 100755 mmgen/proxy.py create mode 100755 scripts/deinstall.sh diff --git a/MANIFEST b/MANIFEST index 8a9389a3..ca43e95b 100644 --- a/MANIFEST +++ b/MANIFEST @@ -1,8 +1,10 @@ # file GENERATED by distutils, do NOT edit mmgen-addrgen +mmgen-addrimport mmgen-keygen mmgen-passchg mmgen-txcreate +mmgen-txsend mmgen-txsign mmgen-walletchk mmgen-walletgen @@ -12,6 +14,7 @@ mmgen/__init__.py mmgen/addr.py mmgen/bitcoin.py mmgen/config.py +mmgen/connection.py mmgen/license.py mmgen/mn_electrum.py mmgen/mn_tirosh.py diff --git a/mmgen-addrgen b/mmgen-addrgen deleted file mode 120000 index cd93f1ad..00000000 --- a/mmgen-addrgen +++ /dev/null @@ -1 +0,0 @@ -mmgen-keygen \ No newline at end of file diff --git a/mmgen-addrgen b/mmgen-addrgen new file mode 100755 index 00000000..1980a64e --- /dev/null +++ b/mmgen-addrgen @@ -0,0 +1,173 @@ +#!/usr/bin/env python +# +# mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution +# Copyright (C) 2013 by 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-addrgen: Generate a range of addresses from a mmgen deterministic + wallet. + Call as 'btc-keygen' to allow key generation as well. +""" + +import sys + +from mmgen.Opts import * +from mmgen.config import * +from mmgen.license import * +from mmgen.utils import * +from mmgen.addr import * + +invoked_as = sys.argv[0].split("-")[-1] +gen_what = "addresses" if invoked_as == "addrgen" else "keys" + +if invoked_as == "keygen": + extra_help_data = ( + "\n-A, --no-addresses Print only secret keys, no addresses", + "\n-x, --b16 Print secret keys in hexadecimal too", + ".\nBy default, both addresses and secret keys are generated" + ) +else: extra_help_data = ("","","") + +help_data = { + 'prog_name': sys.argv[0].split("/")[-1], + 'desc': """Generate a range of {} from a {} wallet, mnemonic, + seed or password""".format(gen_what,proj_name), + 'usage':"[opts] [infile]
", + 'options': """ +-h, --help Print this help message{} +-d, --outdir d Specify an alternate directory 'd' for output +-e, --echo-passphrase Display passphrase or mnemonic on screen upon entry +-K, --no-keyconv Use internal libraries for address generation + instead of 'keyconv' +-l, --seed-len N Length of seed. Options: {} + (default: {}) +-p, --hash-preset p Use scrypt.hash() parameters from preset 'p' + when hashing password (default: '{}') +-P, --show-hash-presets Show information on available hash presets +-q, --quiet Suppress warnings; overwrite files without asking +-S, --stdout Print {W} to stdout +-v, --verbose Produce more verbose output{} + +-b, --from-brain l,p Generate {W} from a user-created password, + i.e. a "brainwallet", using seed length 'l' and + hash preset 'p' (comma-separated) +-m, --from-mnemonic Generate {W} from an electrum-like mnemonic +-s, --from-seed Generate {W} from a seed in .{S} format + +Address range may be a single number or a range in the form XXX-YYY{} + +If available, external 'keyconv' program will be used for address generation + +Data for the --from- options will be taken from if +is specified. Otherwise, the user will be prompted to enter the data. Note +that passphrase data in a file may be arranged in free-form fashion, using +any combination of spaces, tabs or newlines to separate words + +BRAINWALLET NOTE: + +As brainwallets require especially strong hashing to thwart dictionary +attacks, the brainwallet hash preset must be specified by the user, using +the 'p' parameter of the '--from-brain' option + +The '--from-brain' option also requires the user to specify a seed length +(the 'l' parameter) + +For a brainwallet passphrase to always generate the same keys and addresses, +the same 'l' and 'p' parameters to '--from-brain' must be used in all future +invocations with that passphrase +""".format( + extra_help_data[0], + ", ".join([str(i) for i in seed_lens]), + seed_len, + hash_preset, + extra_help_data[1], + extra_help_data[2], + W=gen_what, + S=seed_ext + ) +} + +so = "h","A","d:","e","K","l:","p:","P","q","S","v","x","b:","m","s" +lo = "help","no_addresses","outdir=","echo_passphrase","no_keyconv",\ + "seed_len=","hash_preset=","show_hash_presets","quiet","stdout",\ + "verbose","b16","from_brain=","from_mnemonic","from_seed" + +if invoked_as == "addrgen": + short_opts = so[0:1] + so[2:10] + so[11:] + long_opts = lo[0:1] + lo[2:10] + lo[11:] +else: + short_opts,long_opts = so,lo + +opts,cmd_args = process_opts(sys.argv,help_data,"".join(short_opts),long_opts) + +if 'show_hash_presets' in opts: show_hash_presets() + +# Sanity checking and processing of command-line arguments: + +opts['gen_what'] = gen_what + +set_if_unset_and_typeconvert(opts,( + ('hash_preset',hash_preset,'str'), + ('seed_len',seed_len,'int') +)) + +# Exits on invalid input +check_opts(opts,('hash_preset','seed_len','outdir','from_brain')) + +if debug: + print "Processed options: %s" % repr(opts) + print "Cmd args: %s" % repr(cmd_args) + +if len(cmd_args) == 1 and ( + 'from_mnemonic' in opts or + 'from_brain' in opts or + 'from_seed' in opts + ): infile,addr_range = "",cmd_args[0] +elif len(cmd_args) == 2: + infile,addr_range = cmd_args + check_infile(infile) +else: usage(help_data) + +start,end = parse_address_range(addr_range) + +if not 'quiet' in opts: do_license_msg() + +# Interact with user: +if invoked_as == "keygen" and not 'quiet' in opts: + confirm_or_exit(cmessages['unencrypted_secret_keys'], 'continue') + +# Generate data: + +if invoked_as == "addrgen": + opts['print_addresses_only'] = True +else: + if not 'no_addresses' in opts: opts['print_secret'] = True + +seed = get_seed(infile,opts) +seed_id = make_chksum_8(seed) +addr_data = generate_addrs(seed, start, end, opts) +addr_data_str = format_addr_data(addr_data, seed_id, opts) + +# Output data: +if 'stdout' in opts: + if invoked_as == "keygen" and not 'quiet' in opts: + confirm = True + else: confirm = False + write_to_stdout(addr_data_str,"secret keys",confirm) +elif not sys.stdout.isatty(): + write_to_stdout(addr_data_str,"secret keys",confirm=False) +else: + write_addr_data_to_file(seed, addr_data_str, start, end, opts) diff --git a/mmgen-addrimport b/mmgen-addrimport new file mode 100755 index 00000000..dae87be8 --- /dev/null +++ b/mmgen-addrimport @@ -0,0 +1,69 @@ +#!/usr/bin/env python +# +# mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution +# Copyright (C) 2013 by 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-addrimport: Import addresses generated by mmgen-addrgen into an + online bitcoind watching wallet. +""" + +import sys +from mmgen.Opts import * +from mmgen.config import * +from mmgen.license import * +from mmgen.utils import check_infile,parse_addrs_file,confirm_or_exit + +help_data = { + 'prog_name': sys.argv[0].split("/")[-1], + 'desc': """Import addresses generated by mmgen-addrgen into an + online bitcoind watching wallet""", + 'usage':"[opts] [infile]", + 'options': """ +-h, --help Print this help message +-q, --quiet Suppress warnings +-v, --verbose Produce more verbose output +""" +} + +short_opts = "hqv" +long_opts = "help", "quiet", "verbose" + +opts,cmd_args = process_opts(sys.argv,help_data,"".join(short_opts),long_opts) + +if len(cmd_args) != 1: usage(help_data) + +check_infile(cmd_args[0]) + +seed_id,addr_data = parse_addrs_file(cmd_args[0]) + +from mmgen.tx import connect_to_bitcoind +c = connect_to_bitcoind(mmgen=True) + +message = """ +Importing addresses can take a long time, up to 30 min. per address on a +low-powered computer such as a netbook. +""" +confirm_or_exit(message, "continue", expect="YES") + +for n,i in enumerate(addr_data): + comment = " " + " ".join(i[2:]) if len(i) > 2 else "" + label = "%s:%s%s" % (seed_id,str(i[0]),comment) + msg("Importing %-6s %-34s (%s)" % ( + ("%s/%s:" % (n+1,len(addr_data))), + i[1], label) + ) + c.importaddress(i[1],label) diff --git a/mmgen-keygen b/mmgen-keygen deleted file mode 100755 index f18a5e80..00000000 --- a/mmgen-keygen +++ /dev/null @@ -1,174 +0,0 @@ -#!/usr/bin/env python -# -# mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution -# Copyright (C) 2013 by 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-keygen: Generate addresses/secret keys from a mmgen deterministic - wallet for a range of addresses. - Call as 'btc-addrgen' for safe, reduced functionality - with no output of secret keys. -""" - -import sys - -from mmgen.Opts import * -from mmgen.config import * -from mmgen.license import * -from mmgen.utils import * -from mmgen.addr import * - -invoked_as = sys.argv[0].split("-")[-1] -gen_what = "addresses" if invoked_as == "addrgen" else "keys" - -if invoked_as == "keygen": - extra_help_data = ( - "\n-A, --no-addresses Print only secret keys, no addresses", - "\n-x, --b16 Print secret keys in hexadecimal too", - ".\nBy default, both addresses and secret keys are generated" - ) -else: extra_help_data = ("","","") - -help_data = { - 'prog_name': sys.argv[0].split("/")[-1], - 'desc': """Generate a range of {} from a {} wallet, mnemonic, - seed or password""".format(gen_what,proj_name), - 'usage':"[opts] [infile]
", - 'options': """ --h, --help Print this help message{} --d, --outdir d Specify an alternate directory 'd' for output --e, --echo-passphrase Display passphrase or mnemonic on screen upon entry --K, --no-keyconv Use internal libraries for address generation - instead of 'keyconv' --l, --seed-len N Length of seed. Options: {} - (default: {}) --p, --hash-preset p Use scrypt.hash() parameters from preset 'p' - when hashing password (default: '{}') --P, --show-hash-presets Show information on available hash presets --q, --quiet Suppress warnings; overwrite files without asking --S, --stdout Print {W} to stdout --v, --verbose Produce more verbose output{} - --b, --from-brain l,p Generate {W} from a user-created password, - i.e. a "brainwallet", using seed length 'l' and - hash preset 'p' (comma-separated) --m, --from-mnemonic Generate {W} from an electrum-like mnemonic --s, --from-seed Generate {W} from a seed in .{S} format - -Address range may be a single number or a range in the form XXX-YYY{} - -If available, external 'keyconv' program will be used for address generation - -Data for the --from- options will be taken from if -is specified. Otherwise, the user will be prompted to enter the data. Note -that passphrase data in a file may be arranged in free-form fashion, using -any combination of spaces, tabs or newlines to separate words - -BRAINWALLET NOTE: - -As brainwallets require especially strong hashing to thwart dictionary -attacks, the brainwallet hash preset must be specified by the user, using -the 'p' parameter of the '--from-brain' option - -The '--from-brain' option also requires the user to specify a seed length -(the 'l' parameter) - -For a brainwallet passphrase to always generate the same keys and addresses, -the same 'l' and 'p' parameters to '--from-brain' must be used in all future -invocations with that passphrase -""".format( - extra_help_data[0], - ", ".join([str(i) for i in seed_lens]), - seed_len, - hash_preset, - extra_help_data[1], - extra_help_data[2], - W=gen_what, - S=seed_ext - ) -} - -so = "h","A","d:","e","K","l:","p:","P","q","S","v","x","b:","m","s" -lo = "help","no_addresses","outdir=","echo_passphrase","no_keyconv",\ - "seed_len=","hash_preset=","show_hash_presets","quiet","stdout",\ - "verbose","b16","from_brain=","from_mnemonic","from_seed" - -if invoked_as == "addrgen": - short_opts = so[0:1] + so[2:10] + so[11:] - long_opts = lo[0:1] + lo[2:10] + lo[11:] -else: - short_opts,long_opts = so,lo - -opts,cmd_args = process_opts(sys.argv,help_data,"".join(short_opts),long_opts) - -if 'show_hash_presets' in opts: show_hash_presets() - -# Sanity checking and processing of command-line arguments: - -opts['gen_what'] = gen_what - -set_if_unset_and_typeconvert(opts,( - ('hash_preset',hash_preset,'str'), - ('seed_len',seed_len,'int') -)) - -# Exits on invalid input -check_opts(opts,('hash_preset','seed_len','outdir','from_brain')) - -if debug: - print "Processed options: %s" % repr(opts) - print "Cmd args: %s" % repr(cmd_args) - -if len(cmd_args) == 1 and ( - 'from_mnemonic' in opts or - 'from_brain' in opts or - 'from_seed' in opts - ): infile,addr_range = "",cmd_args[0] -elif len(cmd_args) == 2: - infile,addr_range = cmd_args - check_infile(infile) -else: usage(help_data) - -start,end = parse_address_range(addr_range) - -if not 'quiet' in opts: do_license_msg() - -# Interact with user: -if invoked_as == "keygen" and not 'quiet' in opts: - confirm_or_exit(cmessages['unencrypted_secret_keys'], 'continue') - -# Generate data: - -if invoked_as == "addrgen": - opts['print_addresses_only'] = True -else: - if not 'no_addresses' in opts: opts['print_secret'] = True - -seed = get_seed(infile,opts) -seed_id = make_chksum_8(seed) -addr_data = generate_addrs(seed, start, end, opts) -addr_data_str = format_addr_data(addr_data, seed_id, opts) - -# Output data: -if 'stdout' in opts: - if invoked_as == "keygen" and not 'quiet' in opts: - confirm = True - else: confirm = False - write_to_stdout(addr_data_str,"secret keys",confirm) -elif not sys.stdout.isatty(): - write_to_stdout(addr_data_str,"secret keys",confirm=False) -else: - write_addr_data_to_file(seed, addr_data_str, start, end, opts) diff --git a/mmgen-keygen b/mmgen-keygen new file mode 120000 index 00000000..89090086 --- /dev/null +++ b/mmgen-keygen @@ -0,0 +1 @@ +mmgen-addrgen \ No newline at end of file diff --git a/mmgen-passchg b/mmgen-passchg index 6dbd58a2..5d96f440 100755 --- a/mmgen-passchg +++ b/mmgen-passchg @@ -35,7 +35,7 @@ help_data = { -d, --outdir d Specify an alternate directory 'd' for output -k, --keep-old-passphrase Keep old passphrase (use when changing hash strength or label only) --L, --label Change the wallet's label +-L, --label l Change the wallet's label to 'l' -p, --hash-preset p Change scrypt.hash() parameters to preset 'p' (default: '{}') -P, --show-hash-presets Show information on available hash presets @@ -65,7 +65,8 @@ infile = cmd_args[0] # Old key: label,metadata,hash_preset,salt,enc_seed = get_data_from_wallet(infile,opts) seed_id,key_id = metadata[:2] -passwd = " ".join(get_words("","","Enter old passphrase: ",opts)) +oldp = "" if 'keep_old_passphrase' in opts else "old " +passwd = " ".join(get_words("","",("Enter %spassphrase: " % oldp),opts)) key = make_key(passwd, salt, hash_preset) seed = decrypt_seed(enc_seed, key, seed_id, key_id) diff --git a/mmgen-txcreate b/mmgen-txcreate index 3983fb01..7bde0f0a 100755 --- a/mmgen-txcreate +++ b/mmgen-txcreate @@ -16,7 +16,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . """ -mmgen-txcreate: Send BTC from specified outputs to specified addresses +mmgen-txcreate: Send BTC to specified addresses """ import sys @@ -33,8 +33,8 @@ prog_name = sys.argv[0].split("/")[-1] help_data = { 'prog_name': prog_name, - 'desc': "Send BTC from specified outputs to specified addresses", - 'usage': "[opts] ", + 'desc': "Send BTC to specified addresses", + 'usage': "[opts] [,...] ", 'options': """ -h, --help Print this help message -d, --outdir d Specify an alternate directory 'd' for output @@ -42,6 +42,8 @@ help_data = { -i, --info Display unspent outputs and exit -q, --quiet Suppress warnings; overwrite files without asking +Outputs to spend are chosen by the user via a menu. + Ages of transactions are approximate based on an estimated block discovery time of %s minutes. """ % mins_per_block @@ -59,19 +61,20 @@ if debug: print "Processed options: %s" % repr(opts) print "Cmd args: %s" % repr(cmd_args) -if len(cmd_args) == 4: - rcpt_addr,send_amt,tx_fee,change_addr = cmd_args +if len(cmd_args) == 3: + rcpt_arg,tx_fee,change_addr = cmd_args check_address(change_addr) -elif len(cmd_args) == 3: - rcpt_addr,send_amt,tx_fee = cmd_args +elif len(cmd_args) == 2: + rcpt_arg,tx_fee = cmd_args change_addr = "" elif len(cmd_args) == 0 and 'info' in opts: pass else: usage(help_data) if not 'info' in opts: - check_address(rcpt_addr) - send_amt = check_btc_amt(send_amt) + tx_out = make_tx_out(rcpt_arg) + for i in tx_out.keys(): check_address(i) + for i in tx_out.values(): check_btc_amt(i) tx_fee = check_btc_amt(tx_fee) # Begin execution @@ -86,10 +89,10 @@ total = trim_exponent(sum([i.amount for i in unspent])) msg("Total unspent: %s BTC" % total) if 'info' in opts: sys.exit(0) -msg("Amount to spend: %s BTC" % send_amt) +send_amt = sum(tx_out.values()) +msg("Total amount to spend: %s BTC" % send_amt) msg("%s unspent outputs total" % len(unspent)) - while True: sel_unspent = select_outputs(unspent,"Choose the outputs to spend: ") total_in = trim_exponent(sum([o.amount for o in sel_unspent])) @@ -107,7 +110,8 @@ if change > 0 and not change_addr: sys.exit(2) tx_in = [{"txid":i.txid, "vout":i.vout} for i in sel_unspent] -tx_out = {rcpt_addr:float(send_amt), change_addr:float(change)} +for i in tx_out.keys(): tx_out[i] = float(tx_out[i]) +if change: tx_out[change_addr] = float(change) tx_hex = c.createrawtransaction(tx_in,tx_out) msg("Transaction successfully created") diff --git a/mmgen-txsend b/mmgen-txsend index 00c383ec..df481415 100755 --- a/mmgen-txsend +++ b/mmgen-txsend @@ -83,7 +83,7 @@ confirm_or_exit(warn, what, expect) msg("Sending transaction") -c = connect_to_bitcoind() +c = connect_to_bitcoind(mmgen=True) try: tx = c.sendrawtransaction(tx_sig) diff --git a/mmgen-txsign b/mmgen-txsign index be4f22b8..557e4638 100755 --- a/mmgen-txsign +++ b/mmgen-txsign @@ -118,7 +118,7 @@ if wallet_enc: if sig_tx['complete']: msg("Signing completed") else: - msg("signrawtransaction() returned failure") + msg("Signing failed: 'complete=%s'" % sig_tx['complete']) sys.exit(3) prompt = "Save signed transaction?" diff --git a/mmgen-walletchk b/mmgen-walletchk index 37b15bf4..c38708c8 100755 --- a/mmgen-walletchk +++ b/mmgen-walletchk @@ -49,9 +49,9 @@ opts,cmd_args = Opts.process_opts(sys.argv,help_data,short_opts,long_opts) check_opts(opts, ('outdir',)) -if len(cmd_args) != 1: - msg("One input file must be specified") - sys.exit(2) +if len(cmd_args) != 1: usage(help_data) + +check_infile(cmd_args[0]) if 'export_mnemonic' in opts: msg("Exporting mnemonic data to file by user request") diff --git a/mmgen/bitcoin.py b/mmgen/bitcoin.py index c39f0b05..69456ced 100755 --- a/mmgen/bitcoin.py +++ b/mmgen/bitcoin.py @@ -51,7 +51,7 @@ b58a='123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz' # The "zero address": # 1111111111111111111114oLvT2 (use step2 = ("0" * 40) to generate) # -def _pubhex2addr(pubhex): +def pubhex2addr(pubhex): step1 = sha256(unhexlify(pubhex)).digest() step2 = hashlib_new('ripemd160',step1).hexdigest() # See above: @@ -64,28 +64,32 @@ def _pubhex2addr(pubhex): def privnum2addr(numpriv): pko = ecdsa.SigningKey.from_secret_exponent(numpriv,secp256k1) pubkey = hexlify(pko.get_verifying_key().to_string()) - return _pubhex2addr('04'+pubkey) + return pubhex2addr('04'+pubkey) def verify_addr(addr): if addr[0] != "1": - print "%s Invalid address" % addr + print "%s: Invalid address" % addr return False - addr,lz = addr[1:],0 - while addr[0] == "1": addr = addr[1:]; lz += 1 - - addr_hex = lz * "00" + hex(_b58tonum(addr))[2:].rstrip("L") +# addr,lz = addr[1:],0 +# while addr[0] == "1": addr = addr[1:]; lz += 1 +# +# addr_hex = lz * "00" + hex(_b58tonum(addr))[2:].rstrip("L") - if len(addr_hex) != 48: - print "%s Invalid address" % addr - return False +# if len(addr_hex) != 48: +# print "%s: Invalid address hex length: %s" % ("1"+addr, len(addr_hex)) +# return False + + num = _b58tonum(addr[1:]) + if num == False: return False + addr_hex = hex(num)[2:].rstrip("L").zfill(48) step1 = sha256(unhexlify('00'+addr_hex[:40])).digest() step2 = sha256(step1).hexdigest() if step2[:8] != addr_hex[40:]: - print "Invalid checksum in address %s" % addr + print "Invalid checksum in address %s" % ("1" + addr) return False return True @@ -104,7 +108,7 @@ def _b58tonum(b58num): for i in b58num: if not i in b58a: print "Invalid symbol in b58 number: '%s'" % i - sys.exit(9) + return False b58deconv = [] b58num_r = b58num[::-1] @@ -135,6 +139,7 @@ def b58decode(b58num): if b58num == "": return "" # Zap all spaces: num = _b58tonum(b58num.translate(None,' \t\n\r')) + if num == False: return False out = hex(num)[2:].rstrip('L') return unhexlify("0" + out if len(out) % 2 else out) @@ -149,9 +154,10 @@ def _b58_pad(s,a,b,pad,f,w): except: print "_b58_pad() accepts only %s %s bytes long "\ "(input was %s bytes)" % (w,",".join([str(i) for i in a]),len(s)) - sys.exit(9) + return False out = f(s) + if out == False: return False return "%s%s" % (pad * (outlen - len(out)), out) def b58encode_pad(s): @@ -168,10 +174,13 @@ def b58decode_pad(s): # To check validity, recode with numtowif() def wiftonum(wifpriv): num = _b58tonum(wifpriv) + if num == False: return False return (num % (1<<288)) >> 32 def wiftohex(wifpriv): - key = hex(_b58tonum(wifpriv))[2:].rstrip('L') + num = _b58tonum(wifpriv) + if num == False: return False + key = hex(num)[2:].rstrip('L') round1 = sha256(unhexlify(key[:66])).digest() round2 = sha256(round1).hexdigest() return key[2:66] if (key[:2] == '80' and key[66:] == round2[:8]) else False diff --git a/mmgen/connection.py b/mmgen/connection.py new file mode 100755 index 00000000..5e109a99 --- /dev/null +++ b/mmgen/connection.py @@ -0,0 +1,737 @@ +# 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.proxy import JSONRPCException, AuthServiceProxy +from bitcoinrpc.exceptions import (_wrap_exception, WalletPassphraseIncorrect, + WalletAlreadyUnlocked) +from bitcoinrpc.data import (ServerInfo, AccountInfo, AddressInfo, + TransactionInfo, AddressValidation, WorkItem, MiningInfo) + + +class MMGenBitcoinConnection(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): + """ + """ + try: + return self.proxy.importaddress(address,label) + except JSONRPCException as e: + 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 getbalance(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:`~bitcoinrpc.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:`~bitcoinrpc.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:`~bitcoinrpc.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, 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)) +# else: +# return self.proxy.listaccounts(minconf).keys() +# except JSONRPCException as e: +# raise _wrap_exception(e.error) +# +# def listreceivedbyaccount(self, minconf=1, includeempty=False): +# """ +# Returns a list of accounts. +# +# Each account is represented with a :class:`~bitcoinrpc.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:`~bitcoinrpc.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:`~bitcoinrpc.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:`~bitcoinrpc.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 `~bitcoinrpc.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 `~bitcoinrpc.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/proxy.py b/mmgen/proxy.py new file mode 100755 index 00000000..629e5802 --- /dev/null +++ b/mmgen/proxy.py @@ -0,0 +1,130 @@ +""" + 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" + +HTTP_TIMEOUT = 7200 + + +class JSONRPCException(Exception): + def __init__(self, rpcError): + Exception.__init__(self) + self.error = rpcError + + +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) + if self.__url.scheme == 'https': + self.__conn = httplib.HTTPSConnection(self.__url.hostname, port, None, None,False, + HTTP_TIMEOUT) + else: + self.__conn = httplib.HTTPConnection(self.__url.hostname, port, False, + 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}) + 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) + 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/tx.py b/mmgen/tx.py index e964530b..ccea36c6 100755 --- a/mmgen/tx.py +++ b/mmgen/tx.py @@ -22,7 +22,6 @@ tx.py: Bitcoin transaction routines from binascii import unhexlify from mmgen.utils import msg,msg_r,write_to_file,my_raw_input,get_char,make_chksum_8,make_timestamp import sys, os -from bitcoinrpc.connection import * from decimal import Decimal from mmgen.config import * @@ -36,13 +35,21 @@ specified recipient address. """.strip() } -def connect_to_bitcoind(): + +def connect_to_bitcoind(mmgen=False): host,port,user,passwd = "localhost",8332,"rpcuser","rpcpassword" cfg = get_cfg_options((user,passwd)) + if mmgen: + import mmgen.connection + f = mmgen.connection.MMGenBitcoinConnection + else: + import bitcoinrpc.connection + f = bitcoinrpc.connection.BitcoinConnection + try: - c = BitcoinConnection(cfg[user],cfg[passwd],host,port) + c = f(cfg[user],cfg[passwd],host,port) except: msg("Unable to establish RPC connection with bitcoind") sys.exit(2) @@ -55,11 +62,13 @@ def trim_exponent(d): ''' return d.quantize(Decimal(1)) if d == d.to_integral() else d.normalize() + def check_address(rcpt_address): from mmgen.bitcoin import verify_addr if not verify_addr(rcpt_address): sys.exit(3) + def check_btc_amt(send_amt): from decimal import Decimal @@ -298,3 +307,23 @@ def select_outputs(unspent,prompt): msg("'%s': Invalid input" % reply) return [unspent[i] for i in selected] + + +def make_tx_out(rcpt_arg): + + import decimal + try: + tx_out = dict([(i.split(":")[0],i.split(":")[1]) + for i in rcpt_arg.split(",")]) + except: + msg("Invalid format: %s" % rcpt_arg) + sys.exit(3) + + try: + for i in tx_out.keys(): + tx_out[i] = trim_exponent(Decimal(tx_out[i])) + except decimal.InvalidOperation: + msg("Decimal conversion error in suboption '%s:%s'" % (i,tx_out[i])) + sys.exit(3) + + return tx_out diff --git a/mmgen/utils.py b/mmgen/utils.py index 1fa78681..061a959a 100755 --- a/mmgen/utils.py +++ b/mmgen/utils.py @@ -22,6 +22,7 @@ utils.py: Shared routines for the mmgen suite import sys from mmgen.config import * from binascii import hexlify,unhexlify +from mmgen.bitcoin import b58decode_pad def msg(s): sys.stderr.write(s + "\n") def msg_r(s): sys.stderr.write(s) @@ -628,12 +629,14 @@ def get_data_from_wallet(infile,opts): sys.exit(9) res = {} - from mmgen.bitcoin import b58decode_pad for i,key in (4,"salt"),(5,"enc_seed"): l = lines[i].split() val = "".join(l[1:]) _check_chksum_6(l[0], val, key, infile) res[key] = b58decode_pad(val) + if res[key] == False: + msg("Invalid b58 number: %s" % val) + sys.exit(9) _check_chksum_6(lines[0], " ".join(lines[1:]), "Master", infile) @@ -683,8 +686,11 @@ def get_seed_from_seed_data(words): msg_r("Validating %s checksum..." % seed_ext) if compare_checksums(chk, "from seed", stored_chk, "from input"): - from mmgen.bitcoin import b58decode_pad seed = b58decode_pad(seed_b58) + if seed == False: + msg("Invalid b58 number: %s" % val) + sys.exit(9) + msg("%s data produces seed ID: %s" % (seed_ext,make_chksum_8(seed))) return seed else: @@ -778,5 +784,60 @@ def get_seed(infile,opts,no_wallet=False): else: return get_seed_from_wallet(infile, opts) +def remove_blanks_comments(lines): + import re +# re.sub(pattern, repl, string, count=0, flags=0) + ret = [] + for i in lines: + i = re.sub('#.*','',i,1) + i = re.sub('\s+$','',i) + if i: ret.append(i) + + return ret + +def parse_addrs_file(f): + lines = get_lines_from_file(f,"address data") + lines = remove_blanks_comments(lines) + + seed_id,obrace = lines[0].split() + cbrace = lines[-1] + + if obrace != '{': + msg("'%s': invalid first line" % lines[0]) + elif cbrace != '}': + msg("'%s': invalid last line" % cbrace) + elif len(seed_id) != 8: + msg("'%s': invalid Seed ID" % seed_id) + else: + try: + unhexlify(seed_id) + except: + msg("'%s': invalid Seed ID" % seed_id) + sys.exit(3) + + ret = [] + for i in lines[1:-1]: + d = i.split() + + try: d[0] = int(d[0]) + except: + msg("'%s': invalid address num. in line: %s" % (d[0],d)) + sys.exit(3) + + from mmgen.bitcoin import verify_addr + if not verify_addr(d[1]): + msg("'%s': invalid address" % d[1]) + sys.exit(3) + + ret.append(d) + + return seed_id,ret + + sys.exit(3) + + + + + if __name__ == "__main__": print get_lines_from_file("/tmp/lines","test file") diff --git a/scripts/deinstall.sh b/scripts/deinstall.sh new file mode 100755 index 00000000..dae4b507 --- /dev/null +++ b/scripts/deinstall.sh @@ -0,0 +1,3 @@ +#!/bin/bash +set -x +sudo rm -r /usr/local/bin/mmgen-* /usr/local/lib/python2.7/dist-packages/mmgen* diff --git a/setup.py b/setup.py index 7870097c..1f076a35 100755 --- a/setup.py +++ b/setup.py @@ -3,7 +3,7 @@ from distutils.core import setup setup( name = 'mmgen', - version = '0.6.1', + version = '0.6.2', author = 'Philemon', author_email = 'mmgen-py@yandex.com', url = 'https://github.com/mmgen/mmgen', @@ -19,15 +19,27 @@ setup( 'mmgen.Opts', 'mmgen.tx', 'mmgen.utils', - 'mmgen.walletgen' + 'mmgen.walletgen', + 'mmgen.connection', + 'mmgen.proxy', + 'tests.addr', + 'tests.bitcoin', + 'tests.mn_electrum', + 'tests.mnemonic', + 'tests.mn_tirosh', + 'tests.test', + 'tests.utils', + 'tests.walletgen' ], data_files=[('/usr/local/bin', [ 'mmgen-addrgen', + 'mmgen-addrimport', 'mmgen-keygen', 'mmgen-passchg', 'mmgen-walletchk', 'mmgen-walletgen', 'mmgen-txcreate', - 'mmgen-txsign' + 'mmgen-txsign', + 'mmgen-txsend' ])] ) diff --git a/tests/bitcoin.py b/tests/bitcoin.py index 5f7c0fb8..020ffd95 100755 --- a/tests/bitcoin.py +++ b/tests/bitcoin.py @@ -168,6 +168,10 @@ def hextosha256(s_in): s_enc = sha256(unhexlify(s_in)).hexdigest() print "Encoded data: %s" % s_enc +def pubhextoaddr(s_in): + print "Entered data: %s" % s_in + s_enc = pubhex2addr(s_in) + print "Encoded data: %s" % s_enc tests = { "keyconv_compare": ['wif [str]','quiet [bool=False]'], @@ -183,6 +187,7 @@ tests = { "numtowif_rand": ['quiet [bool=False]'], "hextosha256": ['hexnum [str]','quiet [bool=False]'], "hextowiftopubkey": ['hexnum [str]','quiet [bool=False]'], + "pubhextoaddr": ['hexnum [str]','quiet [bool=False]'], }