Added transaction creation/signing scripts
modified: MANIFEST new file: mmgen-txcreate new file: mmgen-txsign modified: mmgen/__init__.py modified: mmgen/bitcoin.py modified: mmgen/config.py modified: mmgen/license.py new file: mmgen/tx.py modified: mmgen/utils.py new file: scripts/bitcoind-walletunlock.py modified: setup.py
This commit is contained in:
parent
e9e0e700ab
commit
35ddbc029c
11 changed files with 708 additions and 74 deletions
3
MANIFEST
3
MANIFEST
|
|
@ -2,6 +2,8 @@
|
|||
mmgen-addrgen
|
||||
mmgen-keygen
|
||||
mmgen-passchg
|
||||
mmgen-txcreate
|
||||
mmgen-txsign
|
||||
mmgen-walletchk
|
||||
mmgen-walletgen
|
||||
setup.py
|
||||
|
|
@ -14,5 +16,6 @@ mmgen/license.py
|
|||
mmgen/mn_electrum.py
|
||||
mmgen/mn_tirosh.py
|
||||
mmgen/mnemonic.py
|
||||
mmgen/tx.py
|
||||
mmgen/utils.py
|
||||
mmgen/walletgen.py
|
||||
|
|
|
|||
106
mmgen-txcreate
Executable file
106
mmgen-txcreate
Executable file
|
|
@ -0,0 +1,106 @@
|
|||
#!/usr/bin/env python
|
||||
#
|
||||
# mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
|
||||
# Copyright (C) 2013 by philemon <mmgen-py@yandex.com>
|
||||
#
|
||||
# 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 <http://www.gnu.org/licenses/>.
|
||||
"""
|
||||
mmgen-txcreate: Send BTC from specified outputs to specified addresses
|
||||
"""
|
||||
|
||||
import sys
|
||||
#from hashlib import sha256
|
||||
|
||||
from mmgen.Opts import *
|
||||
from mmgen.license import *
|
||||
from mmgen.config import *
|
||||
from mmgen.tx import *
|
||||
from mmgen.utils import check_opts, msg, user_confirm
|
||||
from decimal import Decimal
|
||||
|
||||
prog_name = sys.argv[0].split("/")[-1]
|
||||
|
||||
help_data = {
|
||||
'prog_name': prog_name,
|
||||
'desc': "Send BTC from specified outputs to specified addresses",
|
||||
'usage': "[opts] <recipient address> <amount> <transaction fee> <change address>",
|
||||
'options': """
|
||||
-h, --help Print this help message
|
||||
-d, --outdir d Specify an alternate directory 'd' for output
|
||||
-e, --echo-passphrase Print passphrase to screen when typing it
|
||||
-q, --quiet Suppress warnings; overwrite files without asking
|
||||
"""
|
||||
}
|
||||
|
||||
short_opts = "hd:eq"
|
||||
long_opts = "help","outdir=","echo_passphrase","quiet"
|
||||
|
||||
opts,cmd_args = process_opts(sys.argv,help_data,short_opts,long_opts)
|
||||
|
||||
# Exits on invalid input
|
||||
check_opts(opts, ('outdir',))
|
||||
|
||||
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
|
||||
check_address(change_addr)
|
||||
elif len(cmd_args) == 3:
|
||||
rcpt_addr,send_amt,tx_fee = cmd_args
|
||||
change_addr = ""
|
||||
else: usage(help_data)
|
||||
|
||||
check_address(rcpt_addr)
|
||||
send_amt = check_btc_amt(send_amt)
|
||||
tx_fee = check_btc_amt(tx_fee)
|
||||
|
||||
# Begin execution
|
||||
c = connect_to_bitcoind()
|
||||
|
||||
if not 'quiet' in opts: do_license_msg()
|
||||
|
||||
unspent = sort_and_view(c.listunspent())
|
||||
|
||||
total = remove_exponent(sum([i.amount for i in unspent]))
|
||||
|
||||
msg("Total unspent: %s BTC" % total)
|
||||
msg("Amount to spend: %s BTC" % send_amt)
|
||||
msg("%s unspent outputs total" % len(unspent))
|
||||
|
||||
sel_unspent = select_outputs(unspent,"Choose the outputs to spend: ")
|
||||
|
||||
total_in = remove_exponent(sum([o.amount for o in sel_unspent]))
|
||||
change = remove_exponent(total_in - (send_amt + tx_fee))
|
||||
|
||||
if change < 0:
|
||||
msg(txmsg['not_enough_btc'] % change)
|
||||
sys.exit(2)
|
||||
elif change > 0 and not change_addr:
|
||||
msg(txmsg['throwaway_change'] % (change, total_in-tx_fee))
|
||||
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)}
|
||||
tx_hex = c.createrawtransaction(tx_in,tx_out)
|
||||
|
||||
msg("Transaction successfully created\n")
|
||||
prompt = "View decoded transaction?"
|
||||
if user_confirm(prompt,default_yes=False):
|
||||
view_tx_data(c,[i.__dict__ for i in sel_unspent],tx_hex)
|
||||
|
||||
prompt = "Save transaction?"
|
||||
if user_confirm(prompt,default_yes=True):
|
||||
print_tx_to_file(tx_hex,sel_unspent,send_amt,opts)
|
||||
118
mmgen-txsign
Executable file
118
mmgen-txsign
Executable file
|
|
@ -0,0 +1,118 @@
|
|||
#!/usr/bin/env python
|
||||
#
|
||||
# mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
|
||||
# Copyright (C) 2013 by philemon <mmgen-py@yandex.com>
|
||||
#
|
||||
# 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 <http://www.gnu.org/licenses/>.
|
||||
"""
|
||||
mmgen-txsign: Sign a Bitcoin transaction generated by mmgen-txcreate
|
||||
"""
|
||||
|
||||
import sys
|
||||
#from hashlib import sha256
|
||||
|
||||
from mmgen.Opts import *
|
||||
from mmgen.license import *
|
||||
from mmgen.config import *
|
||||
from mmgen.tx import *
|
||||
from mmgen.utils import check_opts, msg, user_confirm, check_infile, get_lines_from_file, my_getpass, my_raw_input
|
||||
|
||||
prog_name = sys.argv[0].split("/")[-1]
|
||||
|
||||
help_data = {
|
||||
'prog_name': prog_name,
|
||||
'desc': "Sign a Bitcoin transaction generated by mmgen-txcreate",
|
||||
'usage': "[opts] <transaction file>",
|
||||
'options': """
|
||||
-h, --help Print this help message
|
||||
-d, --outdir d Specify an alternate directory 'd' for output
|
||||
-e, --echo-passphrase Print passphrase to screen when typing it
|
||||
-i, --info Just show info about the transaction and exit
|
||||
-q, --quiet Suppress warnings; overwrite files without asking
|
||||
"""
|
||||
}
|
||||
|
||||
short_opts = "hd:eiq"
|
||||
long_opts = "help","outdir=","echo_passphrase","info","quiet"
|
||||
|
||||
opts,cmd_args = process_opts(sys.argv,help_data,short_opts,long_opts)
|
||||
|
||||
# Exits on invalid input
|
||||
check_opts(opts, ('outdir',))
|
||||
|
||||
if debug:
|
||||
print "Processed options: %s" % repr(opts)
|
||||
print "Cmd args: %s" % repr(cmd_args)
|
||||
|
||||
if len(cmd_args) == 1:
|
||||
infile = cmd_args[0]
|
||||
check_infile(infile)
|
||||
else: usage(help_data)
|
||||
|
||||
# Begin execution
|
||||
|
||||
c = connect_to_bitcoind()
|
||||
|
||||
tx_data = get_lines_from_file(infile,"transaction data")
|
||||
|
||||
timestamp,tx_hex,sig_data,inputs_data = parse_tx_data(tx_data)
|
||||
|
||||
if 'info' in opts:
|
||||
view_tx_data(c,inputs_data,tx_hex,timestamp)
|
||||
sys.exit(0)
|
||||
|
||||
if not 'quiet' in opts and not 'info' in opts: do_license_msg()
|
||||
|
||||
msg("Successfully opened transaction file '%s'" % infile)
|
||||
|
||||
if user_confirm("View transaction data? ",default_yes=False):
|
||||
view_tx_data(c,inputs_data,tx_hex,timestamp)
|
||||
|
||||
prompt = "Enter bitcoind passphrase: "
|
||||
if 'echo_passphrase' in opts:
|
||||
password = my_raw_input(prompt)
|
||||
else:
|
||||
password = my_getpass(prompt)
|
||||
|
||||
wallet_enc = True
|
||||
from bitcoinrpc import exceptions
|
||||
|
||||
try:
|
||||
c.walletpassphrase(password, 9999)
|
||||
except exceptions.WalletWrongEncState:
|
||||
msg("Wallet is unencrypted")
|
||||
wallet_enc = False
|
||||
except exceptions.WalletPassphraseIncorrect:
|
||||
msg("Passphrase incorrect")
|
||||
sys.exit(3)
|
||||
except exceptions.WalletAlreadyUnlocked:
|
||||
msg("WARNING: Wallet already unlocked!")
|
||||
|
||||
# signrawtransaction <hex string> [{"txid":txid,"vout":n,"scriptPubKey":hex,"redeemScript":hex},...] [<privatekey1>,...] [sighashtype="ALL"]
|
||||
try:
|
||||
sig_tx = c.signrawtransaction(tx_hex,sig_data)
|
||||
except:
|
||||
msg("Failed to sign transaction")
|
||||
if wallet_enc: c.walletlock()
|
||||
sys.exit(3)
|
||||
finally:
|
||||
if wallet_enc: c.walletlock()
|
||||
|
||||
if not sig_tx['complete']:
|
||||
msg("signrawtransaction() returned failure")
|
||||
sys.exit(3)
|
||||
|
||||
prompt = "Save signed transaction?"
|
||||
if user_confirm(prompt,default_yes=True):
|
||||
print_signed_tx_to_file(tx_hex,sig_tx['hex'],opts)
|
||||
|
|
@ -28,6 +28,7 @@ __all__ = [
|
|||
'mnemonic.py',
|
||||
'mn_tirosh.py',
|
||||
'Opts.py',
|
||||
'tx.py',
|
||||
'utils.py',
|
||||
'walletgen.py'
|
||||
]
|
||||
|
|
|
|||
|
|
@ -66,6 +66,30 @@ def privnum2addr(numpriv):
|
|||
pubkey = hexlify(pko.get_verifying_key().to_string())
|
||||
return _pubhex2addr('04'+pubkey)
|
||||
|
||||
def verify_addr(addr):
|
||||
|
||||
if addr[0] != "1":
|
||||
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")
|
||||
|
||||
if len(addr_hex) != 48:
|
||||
print "%s Invalid address" % addr
|
||||
return False
|
||||
|
||||
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
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
# Reworked code from here:
|
||||
|
||||
def _numtob58(num):
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ mnemonic_lens = [i / 32 * 3 for i in seed_lens]
|
|||
from os import getenv
|
||||
debug = True if getenv("MMGEN_DEBUG") else False
|
||||
|
||||
mins_per_block = 9
|
||||
passwd_max_tries = 5
|
||||
max_randlen,min_randlen = 80,5
|
||||
usr_randlen = 20
|
||||
|
|
|
|||
|
|
@ -21,18 +21,16 @@ license.py: Show the license
|
|||
|
||||
import sys
|
||||
from mmgen.config import proj_name
|
||||
from mmgen.utils import msg, msg_r
|
||||
from mmgen.utils import msg, msg_r, get_char
|
||||
|
||||
gpl = {
|
||||
'warning': """
|
||||
{} Copyright (C) 2013 by philemon <mmgen-py@yandex.com>
|
||||
This program comes with ABSOLUTELY NO WARRANTY.
|
||||
This is free software, and you are welcome to
|
||||
redistribute it under certain conditions.
|
||||
{} Copyright (C) 2013 by Philemon <mmgen-py@yandex.com>. This program
|
||||
comes with ABSOLUTELY NO WARRANTY. This is free software, and you are
|
||||
welcome to redistribute it under certain conditions.
|
||||
""".format(proj_name),
|
||||
'prompt': """
|
||||
Press 'c' for conditions, 'w' for warranty info,
|
||||
or ENTER to continue:
|
||||
Press 'c' for conditions, 'w' for warranty info, or ENTER to continue:
|
||||
""",
|
||||
'conditions': """
|
||||
TERMS AND CONDITIONS
|
||||
|
|
@ -620,30 +618,24 @@ copy of the Program in return for a fee.
|
|||
"""
|
||||
}
|
||||
|
||||
def do_pager(text):
|
||||
import os
|
||||
pager = os.environ['PAGER'] if 'PAGER' in os.environ else 'more'
|
||||
|
||||
p = os.popen(pager, 'w')
|
||||
p.write(text)
|
||||
p.close()
|
||||
msg_r("\r")
|
||||
|
||||
|
||||
def do_license_msg():
|
||||
ls = "\n "
|
||||
msg(" " + ls.join(gpl['warning'].strip().split("\n")))
|
||||
msg("%s\n" % gpl['warning'].strip())
|
||||
|
||||
try:
|
||||
import os
|
||||
os.system(
|
||||
"stty -icanon min 1 time 0 -echo -echoe -echok -echonl -crterase noflsh"
|
||||
)
|
||||
pager = os.environ['PAGER'] if 'PAGER' in os.environ else 'more'
|
||||
while True:
|
||||
|
||||
while True:
|
||||
msg_r(ls + ls.join(gpl['prompt'].strip().split("\n")) + " ")
|
||||
reply = sys.stdin.read(1)
|
||||
if reply == 'c':
|
||||
m = gpl['conditions']
|
||||
elif reply == 'w':
|
||||
m = gpl['warranty']
|
||||
else: break
|
||||
p = os.popen(pager, 'w')
|
||||
p.write(m)
|
||||
p.close()
|
||||
except:
|
||||
msg("\nInterrupted by user")
|
||||
sys.exit(1)
|
||||
finally:
|
||||
os.system("stty sane")
|
||||
prompt = "%s " % gpl['prompt'].strip()
|
||||
reply = get_char(prompt)
|
||||
|
||||
if reply == 'c': do_pager(gpl['conditions'])
|
||||
elif reply == 'w': do_pager(gpl['warranty'])
|
||||
else: msg("\n"); break
|
||||
|
|
|
|||
285
mmgen/tx.py
Executable file
285
mmgen/tx.py
Executable file
|
|
@ -0,0 +1,285 @@
|
|||
#!/usr/bin/env python
|
||||
#
|
||||
# mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
|
||||
# Copyright (C) 2013 by philemon <mmgen-py@yandex.com>
|
||||
#
|
||||
# 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 <http://www.gnu.org/licenses/>.
|
||||
"""
|
||||
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 *
|
||||
|
||||
txmsg = {
|
||||
'not_enough_btc': "Not enough BTC in the inputs for this transaction (%s BTC)",
|
||||
'throwaway_change': """
|
||||
ERROR: This transaction produces change (%s BTC); however, no change
|
||||
address was specified. Total inputs - transaction fee = %s BTC.
|
||||
To create a valid transaction with no change address, send this sum to the
|
||||
specified recipient address.
|
||||
""".strip()
|
||||
}
|
||||
|
||||
def connect_to_bitcoind():
|
||||
|
||||
host,port,user,passwd = "localhost",8332,"rpcuser","rpcpassword"
|
||||
cfg = get_cfg_options((user,passwd))
|
||||
|
||||
try:
|
||||
c = BitcoinConnection(cfg[user],cfg[passwd],host,port)
|
||||
except:
|
||||
msg("Unable to establish RPC connection with bitcoind")
|
||||
sys.exit(2)
|
||||
|
||||
return c
|
||||
|
||||
|
||||
def remove_exponent(d):
|
||||
'''Remove exponent and trailing zeros.
|
||||
'''
|
||||
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
|
||||
try:
|
||||
retval = Decimal(send_amt)
|
||||
except:
|
||||
msg("%s: Invalid amount" % send_amt)
|
||||
sys.exit(3)
|
||||
|
||||
if retval.as_tuple()[-1] < -8:
|
||||
msg("%s: Too many decimal places in amount" % send_amt)
|
||||
sys.exit(3)
|
||||
|
||||
return remove_exponent(retval)
|
||||
|
||||
|
||||
def get_cfg_options(cfg_keys):
|
||||
|
||||
cfg_file = "%s/%s" % (os.environ["HOME"], ".bitcoin/bitcoin.conf")
|
||||
try:
|
||||
f = open(cfg_file)
|
||||
except:
|
||||
msg("Unable to open file '%s' for reading" % cfg_file)
|
||||
sys.exit(2)
|
||||
|
||||
cfg = {}
|
||||
|
||||
for line in f.readlines():
|
||||
s = line.translate(None,"\n\t ").split("=")
|
||||
for k in cfg_keys:
|
||||
if s[0] == k: cfg[k] = s[1]
|
||||
|
||||
f.close()
|
||||
|
||||
for k in cfg_keys:
|
||||
if not k in cfg:
|
||||
msg("Configuration option '%s' must be set in %s" % (k,cfg_file))
|
||||
sys.exit(2)
|
||||
|
||||
return cfg
|
||||
|
||||
|
||||
def print_tx_to_file(tx,sel_unspent,send_amt,opts):
|
||||
sig_data = [{"txid":i.txid,"vout":i.vout,"scriptPubKey":i.scriptPubKey}
|
||||
for i in sel_unspent]
|
||||
tx_id = make_chksum_8(unhexlify(tx))
|
||||
outfile = "%s[%s].tx" % (tx_id,send_amt)
|
||||
if 'outdir' in opts:
|
||||
outfile = "%s/%s" % (opts['outdir'], outfile)
|
||||
input_data = sel_unspent
|
||||
data = "%s\n%s\n%s\n%s\n" % (
|
||||
make_timestamp(), tx, repr(sig_data),
|
||||
repr([i.__dict__ for i in sel_unspent])
|
||||
)
|
||||
write_to_file(outfile,data,confirm=False)
|
||||
msg("Transaction data saved to file '%s'" % outfile)
|
||||
|
||||
|
||||
def print_signed_tx_to_file(tx,sig_tx,opts):
|
||||
tx_id = make_chksum_8(unhexlify(tx))
|
||||
outfile = "%s.txsig" % tx_id
|
||||
if 'outdir' in opts:
|
||||
outfile = "%s/%s" % (opts['outdir'], outfile)
|
||||
write_to_file(outfile,sig_tx+"\n",confirm=False)
|
||||
msg("Signed transaction saved to file '%s'" % outfile)
|
||||
|
||||
|
||||
def print_sent_tx_to_file(tx):
|
||||
outfile = "tx.out"
|
||||
write_to_file(outfile,tx+"\n",confirm=False)
|
||||
msg("Transaction ID saved to file '%s'" % outfile)
|
||||
|
||||
def sort_and_view(unspent):
|
||||
|
||||
def s_amt(a,b): return cmp(a.amount,b.amount)
|
||||
def s_txid(a,b):
|
||||
return cmp("%s %03s" % (a.txid,a.vout), "%s %03s" % (b.txid,b.vout))
|
||||
def s_addr(a,b): return cmp(a.address,b.address)
|
||||
def s_age(a,b): return cmp(b.confirmations,a.confirmations)
|
||||
|
||||
fs = "%-4s %-11s %-2s %-34s %13s"
|
||||
sort,group,reverse = "",False,False
|
||||
|
||||
from copy import deepcopy
|
||||
while True:
|
||||
out = deepcopy(unspent)
|
||||
for i in out: i.skip = ""
|
||||
for n in range(len(out)):
|
||||
if group and n < len(out)-1:
|
||||
a,b = out[n],out[n+1]
|
||||
if sort == "address" and a.address == b.address:
|
||||
out[n+1].skip = "d"
|
||||
elif sort == "txid" and a.txid == b.txid:
|
||||
out[n+1].skip = "t"
|
||||
|
||||
output = []
|
||||
output.append("Sort order: %s%s%s" % (
|
||||
"reverse " if reverse else "",
|
||||
sort if sort else "None",
|
||||
" (grouped)" if group and (sort == "address" or sort == "txid") else ""
|
||||
))
|
||||
output.append(fs % ("Num","TX id","Vout","Address","Amount "))
|
||||
|
||||
for n,i in enumerate(out):
|
||||
amt = str(remove_exponent(i.amount))
|
||||
fill = 8 - len(amt.split(".")[-1]) if "." in amt else 9
|
||||
addr = " |" + "-"*32 if i.skip == "d" else i.address
|
||||
txid = " |---" if i.skip == "t" else i.txid[:8]+"..."
|
||||
|
||||
output.append(fs % (str(n+1)+")", txid,i.vout,addr,amt+(" "*fill)))
|
||||
|
||||
while True:
|
||||
reply = get_char("\n".join(output) +
|
||||
"""\n
|
||||
Sort options: [t]xid, [a]mount, a[d]dress, [A]ge, [r]everse, [g]roup
|
||||
(Type 'q' to quit sorting): """).strip()
|
||||
if reply == 'a': unspent.sort(s_amt); sort = "amount"; break
|
||||
elif reply == 't': unspent.sort(s_txid); sort = "txid"; break
|
||||
elif reply == 'd': unspent.sort(s_addr); sort = "address"; break
|
||||
elif reply == 'A': unspent.sort(s_age); sort = "age"; break
|
||||
elif reply == 'r':
|
||||
reverse = False if reverse else True
|
||||
unspent.reverse()
|
||||
break
|
||||
elif reply == 'g': group = False if group else True; break
|
||||
elif reply == 'q': break
|
||||
else: msg("Invalid input")
|
||||
|
||||
msg("\n")
|
||||
if reply == 'q': break
|
||||
|
||||
return unspent
|
||||
|
||||
|
||||
def view_tx_data(c,inputs_data,tx_hex,timestamp=""):
|
||||
|
||||
td = c.decoderawtransaction(tx_hex)
|
||||
|
||||
msg("TRANSACTION DATA:\n")
|
||||
|
||||
if timestamp: msg("Timestamp: %s\n" % timestamp)
|
||||
|
||||
msg("Inputs:")
|
||||
total_in = 0
|
||||
for n,i in enumerate(td['vin']):
|
||||
for j in inputs_data:
|
||||
if j['txid'] == i['txid'] and j['vout'] == i['vout']:
|
||||
days = j['confirmations'] * mins_per_block / (60*24)
|
||||
total_in += j['amount']
|
||||
msg("""
|
||||
%-3s tx,vout: %s,%s
|
||||
address: %s
|
||||
amount: %s BTC
|
||||
confirmations: %s (around %s days)
|
||||
""".strip() %
|
||||
(n+1,i['txid'],i['vout'],j['address'],
|
||||
remove_exponent(j['amount']),j['confirmations'],days)+"\n")
|
||||
break
|
||||
|
||||
msg("Total input: %s BTC\n" % remove_exponent(total_in))
|
||||
|
||||
total_out = 0
|
||||
msg("Outputs:")
|
||||
for n,i in enumerate(td['vout']):
|
||||
total_out += i['value']
|
||||
msg("""
|
||||
%-3s address: %s
|
||||
amount: %s BTC
|
||||
""".strip() % (
|
||||
n,
|
||||
i['scriptPubKey']['addresses'][0],
|
||||
remove_exponent(i['value']))
|
||||
+ "\n")
|
||||
msg("Total output: %s BTC" % remove_exponent(total_out))
|
||||
msg("TX fee: %s BTC\n" % remove_exponent(total_in-total_out))
|
||||
|
||||
|
||||
def parse_tx_data(tx_data):
|
||||
|
||||
if len(tx_data) != 4:
|
||||
msg("'%s': not a transaction file" % infile)
|
||||
sys.exit(2)
|
||||
|
||||
try: unhexlify(tx_data[1])
|
||||
except:
|
||||
msg("Transaction data is invalid")
|
||||
sys.exit(2)
|
||||
|
||||
try:
|
||||
sig_data = eval(tx_data[2])
|
||||
except:
|
||||
msg("Signature data is invalid")
|
||||
sys.exit(2)
|
||||
|
||||
try:
|
||||
inputs_data = eval(tx_data[3])
|
||||
except:
|
||||
msg("Inputs data is invalid")
|
||||
sys.exit(2)
|
||||
|
||||
return tx_data[0],tx_data[1],sig_data,inputs_data
|
||||
|
||||
|
||||
def select_outputs(unspent,prompt):
|
||||
|
||||
while True:
|
||||
reply = my_raw_input(prompt).strip()
|
||||
if reply:
|
||||
selected = ()
|
||||
try:
|
||||
selected = [int(i) - 1 for i in reply.split()]
|
||||
except: pass
|
||||
else:
|
||||
for i in selected:
|
||||
if i < 0 or i >= len(unspent):
|
||||
msg(
|
||||
"Input must be a number or numbers between 1 and %s" % len(unspent))
|
||||
break
|
||||
else: break
|
||||
|
||||
msg("'%s': Invalid input" % reply)
|
||||
|
||||
return [unspent[i] for i in selected]
|
||||
118
mmgen/utils.py
118
mmgen/utils.py
|
|
@ -28,27 +28,44 @@ def msg_r(s): sys.stderr.write(s)
|
|||
|
||||
def bail(): sys.exit(9)
|
||||
|
||||
def _my_getpass(prompt):
|
||||
def my_getpass(prompt):
|
||||
|
||||
from getpass import getpass
|
||||
# getpass prompts to stderr, so no trickery required as with raw_input()
|
||||
try: pw = getpass(prompt)
|
||||
except:
|
||||
msg("\nInterrupted by user")
|
||||
msg("\nUser interrupt")
|
||||
sys.exit(1)
|
||||
|
||||
return pw
|
||||
|
||||
def get_char(prompt):
|
||||
|
||||
def _my_raw_input(prompt):
|
||||
import os
|
||||
msg_r(prompt)
|
||||
os.system(
|
||||
"stty -icanon min 1 time 0 -echo -echoe -echok -echonl -crterase noflsh"
|
||||
)
|
||||
try: ch = sys.stdin.read(1)
|
||||
except:
|
||||
os.system("stty sane")
|
||||
msg("\nUser interrupt")
|
||||
sys.exit(1)
|
||||
else:
|
||||
os.system("stty sane")
|
||||
|
||||
return ch
|
||||
|
||||
|
||||
def my_raw_input(prompt):
|
||||
|
||||
msg_r(prompt)
|
||||
try: pw = raw_input()
|
||||
try: reply = raw_input()
|
||||
except:
|
||||
msg("\nInterrupted by user")
|
||||
msg("\nUser interrupt")
|
||||
sys.exit(1)
|
||||
|
||||
return pw
|
||||
return reply
|
||||
|
||||
|
||||
def _get_hash_params(hash_preset):
|
||||
|
|
@ -175,7 +192,8 @@ future, you must continue using these same parameters
|
|||
|
||||
def confirm_or_exit(message, question):
|
||||
|
||||
if message.strip(): msg(message.strip())
|
||||
m = message.strip()
|
||||
if m: msg(m)
|
||||
msg("")
|
||||
|
||||
conf_msg = "Type uppercase 'YES' to confirm: "
|
||||
|
|
@ -185,13 +203,28 @@ def confirm_or_exit(message, question):
|
|||
else:
|
||||
prompt = "Are you sure you want to %s?\n%s" % (question,conf_msg)
|
||||
|
||||
if _my_raw_input(prompt).strip() != "YES":
|
||||
if my_raw_input(prompt).strip() != "YES":
|
||||
msg("Program aborted by user")
|
||||
sys.exit(2)
|
||||
|
||||
msg("")
|
||||
|
||||
|
||||
def user_confirm(prompt,default_yes=False):
|
||||
|
||||
q = "(Y/n)" if default_yes else "(y/N)"
|
||||
|
||||
while True:
|
||||
reply = get_char("%s %s: " % (prompt, q)).strip()
|
||||
msg("")
|
||||
|
||||
if not reply:
|
||||
return True if default_yes else False
|
||||
elif reply in 'yY': return True
|
||||
elif reply in 'nN': return False
|
||||
else: msg("Invalid reply")
|
||||
|
||||
|
||||
def set_if_unset_and_typeconvert(opts,item):
|
||||
|
||||
for opt,var,dtype in item:
|
||||
|
|
@ -282,10 +315,10 @@ just hit ENTER twice.
|
|||
|
||||
for i in range(passwd_max_tries):
|
||||
if 'echo_passphrase' in opts:
|
||||
return _my_raw_input("Enter %s: " % what)
|
||||
return my_raw_input("Enter %s: " % what)
|
||||
else:
|
||||
ret = _my_getpass("Enter %s: " % what)
|
||||
if ret == _my_getpass("Repeat %s: " % what):
|
||||
ret = my_getpass("Enter %s: " % what)
|
||||
if ret == my_getpass("Repeat %s: " % what):
|
||||
s = " (empty)" if not len(ret) else ""
|
||||
msg("%ss match%s" % (what.capitalize(),s))
|
||||
return ret
|
||||
|
|
@ -359,6 +392,16 @@ def get_default_wordlist():
|
|||
return wl.strip().split("\n")
|
||||
|
||||
|
||||
def open_file_or_exit(filename,mode):
|
||||
try:
|
||||
f = open(filename, mode)
|
||||
except:
|
||||
what = "reading" if mode == 'r' else "writing"
|
||||
msg("Unable to open file '%s' for %s" % (infile,what))
|
||||
sys.exit(2)
|
||||
return f
|
||||
|
||||
|
||||
def write_to_file(outfile,data,confirm=False):
|
||||
|
||||
if confirm:
|
||||
|
|
@ -370,11 +413,7 @@ def write_to_file(outfile,data,confirm=False):
|
|||
else:
|
||||
confirm_or_exit("","File '%s' already exists\nOverwrite?" % outfile)
|
||||
|
||||
try:
|
||||
f = open(outfile,'w')
|
||||
except:
|
||||
msg("Failed to open file '%s' for writing" % outfile)
|
||||
sys.exit(2)
|
||||
f = open_file_or_exit(outfile,'w')
|
||||
|
||||
try:
|
||||
f.write(data)
|
||||
|
|
@ -446,12 +485,12 @@ def col4(s):
|
|||
nondiv = 1 if len(s) % 4 else 0
|
||||
return " ".join([s[4*i:4*i+4] for i in range(len(s)/4 + nondiv)])
|
||||
|
||||
|
||||
def write_wallet_to_file(seed, passwd, key_id, salt, enc_seed, opts):
|
||||
|
||||
def make_timestamp():
|
||||
import time
|
||||
tv = time.gmtime(time.time())[:6]
|
||||
ts_hdr = "{:04d}{:02d}{:02d}_{:02d}{:02d}{:02d}".format(*tv)
|
||||
return "{:04d}{:02d}{:02d}_{:02d}{:02d}{:02d}".format(*tv)
|
||||
|
||||
def write_wallet_to_file(seed, passwd, key_id, salt, enc_seed, opts):
|
||||
|
||||
seed_id = make_chksum_8(seed)
|
||||
seed_len = str(len(seed)*8)
|
||||
|
|
@ -470,7 +509,8 @@ def write_wallet_to_file(seed, passwd, key_id, salt, enc_seed, opts):
|
|||
sf = b58encode_pad(salt)
|
||||
esf = b58encode_pad(enc_seed)
|
||||
|
||||
metadata = seed_id.lower(),key_id.lower(),seed_len,pw_status,ts_hdr
|
||||
metadata = seed_id.lower(),key_id.lower(),\
|
||||
seed_len,pw_status,make_timestamp()
|
||||
|
||||
lines = (
|
||||
label,
|
||||
|
|
@ -560,11 +600,7 @@ def get_data_from_wallet(infile,opts):
|
|||
|
||||
msg("Getting {} wallet data from file: {}".format(proj_name,infile))
|
||||
|
||||
try:
|
||||
f = open(infile, 'r')
|
||||
except:
|
||||
msg("Unable to open file '" + infile + "' for reading")
|
||||
sys.exit(2)
|
||||
f = open_file_or_exit(infile, 'r')
|
||||
|
||||
lines = [i.strip() for i in f.readlines()]
|
||||
f.close()
|
||||
|
|
@ -602,23 +638,27 @@ def get_data_from_wallet(infile,opts):
|
|||
def _get_words_from_user(opts, prompt):
|
||||
# split() also strips
|
||||
if 'echo_passphrase' in opts:
|
||||
return _my_raw_input(prompt).split()
|
||||
return my_raw_input(prompt).split()
|
||||
else:
|
||||
return _my_getpass(prompt).split()
|
||||
return my_getpass(prompt).split()
|
||||
|
||||
|
||||
def _get_words_from_file(infile,what):
|
||||
msg("Getting %s data from file '%s'" % (what,infile))
|
||||
try:
|
||||
f = open(infile, 'r')
|
||||
except:
|
||||
msg("Unable to open file '%s' for reading" % infile)
|
||||
sys.exit(2)
|
||||
lines = f.readlines()
|
||||
f.close()
|
||||
msg("Getting %s from file '%s'" % (what,infile))
|
||||
f = open_file_or_exit(infile, 'r')
|
||||
lines = f.readlines(); f.close()
|
||||
# split() also strips
|
||||
return [w for l in lines for w in l.split()]
|
||||
|
||||
|
||||
def get_lines_from_file(infile,what):
|
||||
msg("Getting %s from file '%s'" % (what,infile))
|
||||
f = open_file_or_exit(infile,'r')
|
||||
data = f.read(); f.close()
|
||||
# split() doesn't strip final newline
|
||||
return data.strip().split("\n")
|
||||
|
||||
|
||||
def get_words(infile,what,prompt,opts):
|
||||
if infile:
|
||||
words = _get_words_from_file(infile,what)
|
||||
|
|
@ -709,7 +749,7 @@ def decrypt_seed(enc_seed, key, seed_id, key_id):
|
|||
def get_seed(infile,opts,no_wallet=False):
|
||||
if 'from_mnemonic' in opts:
|
||||
prompt = "Enter mnemonic: "
|
||||
words = get_words(infile,"mnemonic",prompt,opts)
|
||||
words = get_words(infile,"mnemonic data",prompt,opts)
|
||||
|
||||
wl = get_default_wordlist()
|
||||
from mmgen.mnemonic import get_seed_from_mnemonic
|
||||
|
|
@ -723,11 +763,11 @@ def get_seed(infile,opts,no_wallet=False):
|
|||
*_get_from_brain_opt_params(opts)),
|
||||
"continue")
|
||||
prompt = "Enter brainwallet passphrase: "
|
||||
words = get_words(infile,"brainwallet",prompt,opts)
|
||||
words = get_words(infile,"brainwallet data",prompt,opts)
|
||||
return _get_seed_from_brain_passphrase(words,opts)
|
||||
elif 'from_seed' in opts:
|
||||
prompt = "Enter seed in %s format: " % seed_ext
|
||||
words = get_words(infile,"seed",prompt,opts)
|
||||
words = get_words(infile,"seed data",prompt,opts)
|
||||
return get_seed_from_seed_data(words)
|
||||
elif no_wallet:
|
||||
return False
|
||||
|
|
|
|||
61
scripts/bitcoind-walletunlock.py
Executable file
61
scripts/bitcoind-walletunlock.py
Executable file
|
|
@ -0,0 +1,61 @@
|
|||
#!/usr/bin/env python
|
||||
#
|
||||
# mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
|
||||
# Copyright (C) 2013 by philemon <mmgen-py@yandex.com>
|
||||
#
|
||||
# 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 <http://www.gnu.org/licenses/>.
|
||||
"""
|
||||
bitcoind-walletunlock.py: Unlock a Bitcoin wallet securely
|
||||
"""
|
||||
|
||||
import sys
|
||||
from mmgen.Opts import *
|
||||
from mmgen.tx import *
|
||||
from mmgen.utils import msg, my_getpass, my_raw_input
|
||||
|
||||
prog_name = sys.argv[0].split("/")[-1]
|
||||
|
||||
help_data = {
|
||||
'prog_name': prog_name,
|
||||
'desc': "Unlock a Bitcoin wallet securely",
|
||||
'usage': "[opts]",
|
||||
'options': """
|
||||
-h, --help Print this help message
|
||||
-e, --echo-passphrase Print passphrase to screen when typing it
|
||||
"""
|
||||
}
|
||||
|
||||
short_opts = "he"
|
||||
long_opts = "help","echo_passphrase"
|
||||
|
||||
opts,cmd_args = process_opts(sys.argv,help_data,short_opts,long_opts)
|
||||
|
||||
c = connect_to_bitcoind()
|
||||
|
||||
prompt = "Enter passphrase: "
|
||||
if 'echo_passphrase' in opts:
|
||||
password = my_raw_input(prompt)
|
||||
else:
|
||||
password = my_getpass(prompt)
|
||||
|
||||
from bitcoinrpc import exceptions
|
||||
|
||||
try:
|
||||
c.walletpassphrase(password, 9999)
|
||||
except exceptions.WalletWrongEncState:
|
||||
msg("Wallet is unencrypted")
|
||||
except exceptions.WalletPassphraseIncorrect:
|
||||
msg("Passphrase incorrect")
|
||||
except exceptions.WalletAlreadyUnlocked:
|
||||
msg("WARNING: Wallet already unlocked!")
|
||||
7
setup.py
7
setup.py
|
|
@ -3,7 +3,7 @@ from distutils.core import setup
|
|||
|
||||
setup(
|
||||
name = 'mmgen',
|
||||
version = '0.6',
|
||||
version = '0.6.1',
|
||||
author = 'Philemon',
|
||||
author_email = 'mmgen-py@yandex.com',
|
||||
url = 'https://github.com/mmgen/mmgen',
|
||||
|
|
@ -17,6 +17,7 @@ setup(
|
|||
'mmgen.mnemonic',
|
||||
'mmgen.mn_tirosh',
|
||||
'mmgen.Opts',
|
||||
'mmgen.tx',
|
||||
'mmgen.utils',
|
||||
'mmgen.walletgen'
|
||||
],
|
||||
|
|
@ -25,6 +26,8 @@ setup(
|
|||
'mmgen-keygen',
|
||||
'mmgen-passchg',
|
||||
'mmgen-walletchk',
|
||||
'mmgen-walletgen'
|
||||
'mmgen-walletgen',
|
||||
'mmgen-txcreate',
|
||||
'mmgen-txsign'
|
||||
])]
|
||||
)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue