Browse Source

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>'
philemon 10 years ago
parent
commit
b7a7d666e8
7 changed files with 111 additions and 64 deletions
  1. 47 21
      README.md
  2. 2 6
      mmgen/main_txcreate.py
  3. 2 4
      mmgen/main_txsend.py
  4. 11 9
      mmgen/main_txsign.py
  5. 3 3
      mmgen/tool.py
  6. 43 18
      mmgen/tx.py
  7. 3 3
      test/test.py

+ 47 - 21
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/

+ 2 - 6
mmgen/main_txcreate.py

@@ -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):

+ 2 - 4
mmgen/main_txsend.py

@@ -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)

+ 11 - 9
mmgen/main_txsign.py

@@ -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

+ 3 - 3
mmgen/tool.py

@@ -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,{})

+ 43 - 18
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("")

+ 3 - 3
test/test.py

@@ -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")