README update, including "Why MMGen is not a BIP32 wallet"
A more compact TX view, accessible via:
'mmgen-tool txview <tx file> terse=1'
'mmgen-txsign -t <tx file>'
This commit is contained in:
parent
7602425b4a
commit
b7a7d666e8
7 changed files with 111 additions and 64 deletions
68
README.md
68
README.md
|
|
@ -5,20 +5,14 @@ MMGen = Multi-Mode GENerator
|
|||
### Description
|
||||
|
||||
MMGen is a Bitcoin cold-storage system implemented as a suite of Python
|
||||
command-line scripts that require only a bare minimum of system resources. The
|
||||
scripts work in tandem with the reference Bitcoin Core daemon (bitcoind) running
|
||||
command-line scripts requiring only a bare minimum of system resources. The
|
||||
scripts work in tandem with a reference Bitcoin Core daemon (bitcoind) running
|
||||
on both an online and an offline computer to provide a robust solution for
|
||||
securely storing, tracking, sending and receiving Bitcoins. "Non-MMGen"
|
||||
addresses can be tracked and spent as well, creating an easy migration path from
|
||||
other wallets.
|
||||
|
||||
To track address balances, MMGen relies on Bitcoin Core's newly included support
|
||||
for watch-only addresses. Binary builds with this feature will become available
|
||||
with the next release of Bitcoin Core. In the meantime, users can download the
|
||||
Bitcoin source from the project's official repository on Github and compile it,
|
||||
a trivial task on Linux. Compilation instructions for Windows are also
|
||||
included, though Windows users may find it easier to wait for the binary from
|
||||
the upcoming release.
|
||||
securely storing, tracking, sending and receiving Bitcoins. To track address
|
||||
balances without exposing keys on the online computer, MMGen relies on Bitcoin
|
||||
Core's newly included watch-only address support. Ordinary Bitcoin addresses
|
||||
can also be tracked and spent, creating an easy migration path from other
|
||||
wallets.
|
||||
|
||||
MMGen is designed for reliability by having the reference Bitcoin Core daemon,
|
||||
rather than less-tested third-party software, do all the "heavy lifting" of
|
||||
|
|
@ -35,26 +29,57 @@ back it up only once. Transactions are signed offline: your seed and private
|
|||
keys never touch an online computer.
|
||||
|
||||
At the heart of the MMGen system is the seed, the "master key" providing access
|
||||
to all your Bitcoins. The seed can be stored in four different ways:
|
||||
to all your Bitcoins. The seed can be stored in five different ways:
|
||||
|
||||
1. as a password-encrypted wallet. For password hashing, the crack-resistant
|
||||
scrypt hash function is used. Scrypt's parameters can be customized on the
|
||||
scrypt hash function is used. Scrypt's parameters can be tuned on the
|
||||
command line to make your wallet's password virtually impossible to crack
|
||||
should it fall into the wrong hands. The wallet is a tiny, six-line text
|
||||
file suitable for printing or even writing out by hand;
|
||||
|
||||
2. as a seed file: a one-line base-58 representation of your unencrypted seed
|
||||
with a checksum;
|
||||
2. as a seed file: a one-line, conveniently formatted base-58 representation
|
||||
of your unencrypted seed plus a checksum;
|
||||
|
||||
3. as an Electrum-like mnemonic of 12, 18 or 24 words; or
|
||||
3. as an Electrum-like mnemonic of 12, 18 or 24 words;
|
||||
|
||||
4. as a brainwallet password (this option is recommended for expert users
|
||||
only).
|
||||
4. as a brainwallet passphrase (this option is recommended only for users who
|
||||
understand the risks of brainwallets and know how to create a strong
|
||||
brainwallet passphrase). The brainwallet is hashed using scrypt with
|
||||
tunable parameters, making it much harder to crack than standard SHA-256
|
||||
brainwallets; or
|
||||
|
||||
5. as "incognito data", an MMGen wallet encrypted to make it indistinguishable
|
||||
from random data. This data can be hidden in and retrieved from a
|
||||
random-data filled disk partition or file at an offset of your choice.
|
||||
This makes it possible to hide a wallet in a public location -- on cloud
|
||||
storage, for example. Incognito wallet hiding/retrieval is seamlessly
|
||||
integrated into MMGen, making its use nearly as easy as that of the
|
||||
standard wallet.
|
||||
|
||||
The best part is that all these methods can be combined. If you forget your
|
||||
mnemonic, for example, you can regenerate it and your keys from the stored
|
||||
wallet or seed file. Correspondingly, a lost wallet can be regenerated from the
|
||||
mnemonic or seed or a lost seed from the wallet or mnemonic.
|
||||
mnemonic or seed or a lost seed from the wallet or mnemonic. Keys from a
|
||||
forgotten brainwallet can be recovered from the brainwallet's corresponding
|
||||
wallet file.
|
||||
|
||||
#### Why MMGen is not a BIP32 wallet
|
||||
|
||||
Most popular deterministic wallets use the elliptic-curve-based BIP32 or
|
||||
Electrum protocols to generate their key/address pairs. MMGen, on the other
|
||||
hand, uses a much simpler system: a simple SHA-512 hash chain with double
|
||||
SHA-256 branches. One advantage of this system is that you can recover your
|
||||
keys from an MMGen seed without the MMGen program itself using standard
|
||||
command-line utilities. But the most important advantage is security:
|
||||
elliptic-curve wallets are not only cryptographically weaker than hash-bashed
|
||||
ones but have a dangerous flaw -- their 'master public key' feature allows an
|
||||
attacker to recover any key in the wallet from a single compromised key (for a
|
||||
detailed discussion of this problem, see Vitalik Buterin's article
|
||||
[Deterministic Wallets, Their Advantages and Their Understated Flaws][7]).
|
||||
Though the master public key feature of BIP32 and Electrum wallets is undeniably
|
||||
convenient, MMGen makes up for its absence by allowing you to save a virtually
|
||||
unlimited number of Bitcoin addresses for future use in an address file, which
|
||||
addresses may safely be made public.
|
||||
|
||||
|
||||
### Download/Install
|
||||
|
|
@ -82,3 +107,4 @@ Donate: 15TLdmi5NYLdqmtCqczUs5pBPkJDXRs83w
|
|||
[4]: https://bitcointalk.org/index.php?topic=567069.0
|
||||
[5]: https://github.com/mmgen/mmgen/wiki/MMGen-Signing-Key
|
||||
[6]: https://github.com/mmgen/mmgen/wiki/MMGen-command-help
|
||||
[7]: http://bitcoinmagazine.com/8396/deterministic-wallets-advantages-flaw/
|
||||
|
|
|
|||
|
|
@ -465,16 +465,12 @@ else:
|
|||
tx_hex = c.createrawtransaction(tx_in,tx_out)
|
||||
qmsg("Transaction successfully created")
|
||||
|
||||
prompt = "View decoded transaction? (y)es, (N)o, (v)iew in pager"
|
||||
reply = prompt_and_get_char(prompt,"YyNnVv",enter_ok=True)
|
||||
|
||||
amt = send_amt or change
|
||||
tx_id = make_chksum_6(unhexlify(tx_hex)).upper()
|
||||
metadata = tx_id, amt, make_timestamp()
|
||||
|
||||
if reply and reply in "YyVv":
|
||||
view_tx_data(c,[i.__dict__ for i in sel_unspent],tx_hex,b2m_map,
|
||||
comment,metadata,reply in "Vv")
|
||||
prompt_and_view_tx_data(c,"View decoded transaction?",
|
||||
[i.__dict__ for i in sel_unspent],tx_hex,b2m_map,comment,metadata)
|
||||
|
||||
prompt = "Save transaction?"
|
||||
if keypress_confirm(prompt,default_yes=True):
|
||||
|
|
|
|||
|
|
@ -57,10 +57,8 @@ qmsg("Signed transaction file '%s' is valid" % infile)
|
|||
|
||||
c = connect_to_bitcoind()
|
||||
|
||||
prompt = "View transaction data? (y)es, (N)o, (v)iew in pager"
|
||||
reply = prompt_and_get_char(prompt,"YyNnVv",enter_ok=True)
|
||||
if reply and reply in "YyVv":
|
||||
view_tx_data(c,inputs_data,tx_hex,b2m_map,comment,metadata,reply in "Vv")
|
||||
prompt_and_view_tx_data(c,"View transaction data?",
|
||||
inputs_data,tx_hex,b2m_map,comment,metadata)
|
||||
|
||||
if keypress_confirm("Edit transaction comment?"):
|
||||
comment = get_tx_comment_from_user(comment)
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ help_data = {
|
|||
-d, --outdir= d Specify an alternate directory 'd' for output
|
||||
-e, --echo-passphrase Print passphrase to screen when typing it
|
||||
-i, --info Display information about the transaction and exit
|
||||
-t, --terse-info Like '--info', but produce more concise output
|
||||
-I, --tx-id Display transaction ID and exit
|
||||
-k, --keys-from-file= f Provide additional keys for non-{MMG} addresses
|
||||
-K, --no-keyconv Force use of internal libraries for address gener-
|
||||
|
|
@ -284,7 +285,8 @@ def get_keys_from_keylist(kldata,other_addrs):
|
|||
opts,infiles = parse_opts(sys.argv,help_data)
|
||||
|
||||
for l in (
|
||||
('tx_id', 'info')
|
||||
('tx_id', 'info'),
|
||||
('tx_id', 'terse_info'),
|
||||
): warn_incompatible_opts(opts,l)
|
||||
|
||||
if 'from_incog_hex' in opts or 'from_incog_hidden' in opts:
|
||||
|
|
@ -299,7 +301,8 @@ saved_seeds = {}
|
|||
tx_files = [i for i in infiles if get_extension(i) == g.rawtx_ext]
|
||||
seed_files = [i for i in infiles if get_extension(i) != g.rawtx_ext]
|
||||
|
||||
if not "info" in opts: do_license_msg(immed=True)
|
||||
if not "info" in opts and not "terse_info" in opts:
|
||||
do_license_msg(immed=True)
|
||||
|
||||
from_file = { 'mmdata':{}, 'kldata':{} }
|
||||
if 'mmgen_keys_from_file' in opts:
|
||||
|
|
@ -317,20 +320,19 @@ for tx_num,tx_file in enumerate(tx_files,1):
|
|||
tx_data = get_lines_from_file(tx_file,m)
|
||||
|
||||
metadata,tx_hex,inputs_data,b2m_map,comment = parse_tx_file(tx_data,tx_file)
|
||||
qmsg("Successfully opened transaction file '%s'" % tx_file)
|
||||
vmsg("Successfully opened transaction file '%s'" % tx_file)
|
||||
|
||||
if 'tx_id' in opts:
|
||||
msg(metadata[0])
|
||||
sys.exit(0)
|
||||
|
||||
if 'info' in opts:
|
||||
view_tx_data(c,inputs_data,tx_hex,b2m_map,comment,metadata)
|
||||
if 'info' in opts or 'terse_info' in opts:
|
||||
view_tx_data(c,inputs_data,tx_hex,b2m_map,comment,metadata,pause=False,
|
||||
terse='terse_info' in opts)
|
||||
sys.exit(0)
|
||||
|
||||
p = "View data for transaction{}? (y)es, (N)o, (v)iew in pager"
|
||||
reply = prompt_and_get_char(p.format(tx_num_str),"YyNnVv",enter_ok=True)
|
||||
if reply and reply in "YyVv":
|
||||
view_tx_data(c,inputs_data,tx_hex,b2m_map,comment,metadata,reply in "Vv")
|
||||
prompt_and_view_tx_data(c,"View data for transaction{}?".format(tx_num_str),
|
||||
inputs_data,tx_hex,b2m_map,comment,metadata)
|
||||
|
||||
# Start
|
||||
other_addrs = list(set([i['address'] for i in inputs_data
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@ commands = {
|
|||
"str2id6": ['<string (spaces are ignored)> [str]'],
|
||||
"listaddresses":['minconf [int=1]','showempty [bool=False]','pager [bool=False]'],
|
||||
"getbalance": ['minconf [int=1]'],
|
||||
"txview": ['<MMGen tx file> [str]','pager [bool=False]'],
|
||||
"txview": ['<MMGen tx file> [str]','pager [bool=False]','terse [bool=False]'],
|
||||
"addrfile_chksum": ['<MMGen addr file> [str]'],
|
||||
"keyaddrfile_chksum": ['<MMGen addr file> [str]'],
|
||||
"find_incog_data": ['<file or device name> [str]','<Incog ID> [str]','keep_searching [bool=False]'],
|
||||
|
|
@ -424,12 +424,12 @@ def getbalance(minconf=1):
|
|||
for key in sorted(accts.keys()):
|
||||
print fs.format(key+":", *[str(trim_exponent(a))+" BTC" for a in accts[key]])
|
||||
|
||||
def txview(infile,pager=False):
|
||||
def txview(infile,pager=False,terse=False):
|
||||
c = connect_to_bitcoind()
|
||||
tx_data = get_lines_from_file(infile,"transaction data")
|
||||
|
||||
metadata,tx_hex,inputs_data,b2m_map,comment = parse_tx_file(tx_data,infile)
|
||||
view_tx_data(c,inputs_data,tx_hex,b2m_map,comment,metadata,pager,pause=False)
|
||||
view_tx_data(c,inputs_data,tx_hex,b2m_map,comment,metadata,pager,pause=False,terse=terse)
|
||||
|
||||
def addrfile_chksum(infile): parse_addrfile(infile,{})
|
||||
def keyaddrfile_chksum(infile): parse_keyaddr_file(infile,{})
|
||||
|
|
|
|||
61
mmgen/tx.py
61
mmgen/tx.py
|
|
@ -132,15 +132,28 @@ Only ASCII printable characters are permitted.
|
|||
""".strip() % (ch,label))
|
||||
sys.exit(3)
|
||||
|
||||
def prompt_and_view_tx_data(c,prompt,inputs_data,tx_hex,b2m_map,comment,metadata):
|
||||
|
||||
def view_tx_data(c,inputs_data,tx_hex,b2m_map,comment,metadata,pager=False,pause=True):
|
||||
prompt += " (y)es, (N)o, pager (v)iew, (t)erse view"
|
||||
|
||||
reply = prompt_and_get_char(prompt,"YyNnVvTt",enter_ok=True)
|
||||
|
||||
if reply and reply in "YyVvTt":
|
||||
view_tx_data(c,inputs_data,tx_hex,b2m_map,comment,metadata,
|
||||
pager=reply in "Vv",terse=reply in "Tt")
|
||||
|
||||
|
||||
def view_tx_data(c,inputs_data,tx_hex,b2m_map,comment,metadata,pager=False,pause=True,terse=False):
|
||||
|
||||
td = c.decoderawtransaction(tx_hex)
|
||||
|
||||
out = "TRANSACTION DATA\n\n"
|
||||
out += "Header: [Tx ID: {}] [Amount: {} BTC] [Time: {}]\n\n".format(*metadata)
|
||||
if comment: out += "Comment: %s\n\n" % comment
|
||||
out += "Inputs:\n\n"
|
||||
fs = "Transaction {} - {} BTC - {} GMT\n" if terse else \
|
||||
"TRANSACTION DATA\n\nHeader: [Tx ID: {}] [Amount: {} BTC] [Time: {}]\n\n"
|
||||
out = fs.format(*metadata)
|
||||
|
||||
enl = "" if terse else "\n"
|
||||
if comment: out += "Comment: %s\n%s" % (comment,enl)
|
||||
out += "Inputs:\n" + enl
|
||||
|
||||
total_in = 0
|
||||
for n,i in enumerate(td['vin']):
|
||||
|
|
@ -154,41 +167,53 @@ def view_tx_data(c,inputs_data,tx_hex,b2m_map,comment,metadata,pager=False,pause
|
|||
if not mmid: mmid = "non-%s address" % g.proj_name
|
||||
mmid_str = " ({:>{l}})".format(mmid,l=34-len(j['address']))
|
||||
|
||||
for d in (
|
||||
if terse:
|
||||
out += " %s: %-54s %s BTC" % (n+1,j['address'] + mmid_str,
|
||||
trim_exponent(j['amount']))
|
||||
else:
|
||||
for d in (
|
||||
(n+1, "tx,vout:", "%s,%s" % (i['txid'], i['vout'])),
|
||||
("", "address:", j['address'] + mmid_str),
|
||||
("", "label:", label),
|
||||
("", "amount:", "%s BTC" % trim_exponent(j['amount'])),
|
||||
("", "confirmations:", "%s (around %s days)" % (j['confirmations'], days))
|
||||
):
|
||||
if d[2]: out += ("%3s %-8s %s\n" % d)
|
||||
if d[2]: out += ("%3s %-8s %s\n" % d)
|
||||
out += "\n"
|
||||
|
||||
break
|
||||
total_out = 0
|
||||
out += "Outputs:\n\n"
|
||||
out += "Outputs:\n" + enl
|
||||
for n,i in enumerate(td['vout']):
|
||||
addr = i['scriptPubKey']['addresses'][0]
|
||||
mmid,label = b2m_map[addr] if addr in b2m_map else ("","")
|
||||
if not mmid: mmid = "non-%s address" % g.proj_name
|
||||
mmid_str = " ({:>{l}})".format(mmid,l=34-len(j['address']))
|
||||
total_out += i['value']
|
||||
for d in (
|
||||
(n+1, "address:", addr + mmid_str),
|
||||
("", "label:", label),
|
||||
("", "amount:", trim_exponent(i['value']))
|
||||
):
|
||||
if d[2]: out += ("%3s %-8s %s\n" % d)
|
||||
if terse:
|
||||
out += " %s: %-54s %s BTC" % (n+1,addr + mmid_str,
|
||||
trim_exponent(i['value']))
|
||||
else:
|
||||
for d in (
|
||||
(n+1, "address:", addr + mmid_str),
|
||||
("", "label:", label),
|
||||
("", "amount:", trim_exponent(i['value']))
|
||||
):
|
||||
if d[2]: out += ("%3s %-8s %s\n" % d)
|
||||
out += "\n"
|
||||
|
||||
out += "Total input: %s BTC\n" % trim_exponent(total_in)
|
||||
out += "Total output: %s BTC\n" % trim_exponent(total_out)
|
||||
out += "TX fee: %s BTC\n" % trim_exponent(total_in-total_out)
|
||||
fs = "In %s BTC - Out %s BTC - Fee %s BTC\n" if terse else \
|
||||
"Total input: %s BTC\nTotal output: %s BTC\nTX fee: %s BTC\n"
|
||||
out += fs % (
|
||||
trim_exponent(total_in),
|
||||
trim_exponent(total_out),
|
||||
trim_exponent(total_in-total_out)
|
||||
)
|
||||
|
||||
o = out.encode("utf8")
|
||||
if pager: do_pager(o)
|
||||
else:
|
||||
print "\n"+o
|
||||
print o
|
||||
if pause:
|
||||
get_char("Press any key to continue: ")
|
||||
msg("")
|
||||
|
|
|
|||
|
|
@ -391,7 +391,7 @@ class MMGenExpect(object):
|
|||
self.expect("Exiting at user request")
|
||||
|
||||
def tx_view(self):
|
||||
my_expect(self.p,r"View .*?transaction.*? \(y\)es, \(N\)o, \(v\)iew in pager: ","\n",regex=True)
|
||||
my_expect(self.p,r"View .*?transaction.*? \(y\)es, \(N\)o, pager \(v\)iew.*?: ","\n",regex=True)
|
||||
|
||||
def expect_getend(self,s,regex=False):
|
||||
ret = self.expect(s,regex=regex,nonl=True)
|
||||
|
|
@ -595,7 +595,7 @@ class MMGenTestSuite(object):
|
|||
c = rebuild_list[cmd]
|
||||
m = "Rebuild" if (c[0] and c[1]) else "Build" if c[0] else "OK"
|
||||
msg("cmd {:<{w}} {}".format(cmd+":", m, w=w))
|
||||
# msgrepr(cmd,c)
|
||||
# msgrepr(cmd,c)
|
||||
|
||||
|
||||
def clean(self,name,dirs=[]):
|
||||
|
|
@ -878,7 +878,7 @@ class MMGenTestSuite(object):
|
|||
t.hash_preset("key-address file",'1')
|
||||
t.passphrase("key-address file",cfg['kapasswd'])
|
||||
t.expect("Check key-to-address validity? (y/N): ","y")
|
||||
t.expect("View data for transaction? (y)es, (N)o, (v)iew in pager: ","\n")
|
||||
t.tx_view()
|
||||
t.expect("Signing transaction...OK")
|
||||
t.expect("Edit transaction comment? (y/N): ","\n")
|
||||
t.written_to_file("Signed transaction")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue