Test UTF-8 passwords; add release notes

This commit is contained in:
The MMGen Project 2018-05-14 16:36:08 +00:00
commit 6972153248
Signed by: mmgen
GPG key ID: 3F8B1861E32B7DA2
23 changed files with 278 additions and 50 deletions

View file

@ -1,10 +0,0 @@
MMGen MS Windows Notes
The following MMGen features are unsupported or broken on the MSWin/MinGW platform:
- Autosign (not supported)
- Zcash z-address generation (requires libsodium)
- Monero wallet creation/syncing* (IO stream issues with pexpect and the password prompt)
- UTF-8 label, filename and path support (may work on versions of Windows with native UTF-8 support)
*Monero address and viewkey generation works fine.

10
doc/README.mswin.md Normal file
View file

@ -0,0 +1,10 @@
### MMGen MS Windows Notes
The following MMGen features are unsupported or broken on the MSWin/MinGW platform:
- Autosign (not supported)
- Zcash z-address generation (requires libsodium)
- Monero wallet creation/syncing\* (IO stream issues with pexpect and the password prompt)
- UTF-8 filename and path support
\*Monero address and viewkey generation are fully supported.

View file

@ -0,0 +1,23 @@
MMGen version 0.8.3
New features/improvements:
* New native Bitcoin RPC library.
* Support for cookie-based RPC authentication (new in Bitcoin Core v0.12.0).
* Batch mode available when listing and importing addresses.
* mmgen-tool listaddresses: 'addrs' argument allows you to specify an
address or range of addresses.
NOTE: if MMGen is already installed on your system, you must remove your
existing installation by hand before installing this new version. On Linux,
this means deleting everything under the directory
'/usr/local/lib/python2.7/dist-packages/mmgen/'. Also, if you did a 'git pull'
instead of a fresh clone, you must delete the 'build' directory in the
repository root before installing.
The 'mmgen-pywallet' utility has been removed. It's no longer needed, as the
'bitcoin-cli dumpwallet' command (available since Core v0.9.0) provides
equivalent functionality.
The Windows port isn't being actively maintained at the moment. Use at your own
risk, and report any problems on the Bitcointalk forum.

View file

@ -0,0 +1,16 @@
MMGen version 0.8.5
New features/improvements:
* Colored output
* Label editing in mmgen-txcreate
This release includes a major object-oriented rewrite of much of the code.
NOTE: The transaction file format has changed. Since TX files are temporary, this
shouldn't be a problem for most. However, the script 'tx-old2new.py' in the
scripts directory will convert old old TX files to the new format for those who
need to do so.
The Windows implementation is functional again. Use at your own risk, and
report any problems on the Bitcointalk forum.

View file

@ -0,0 +1,7 @@
**New features/improvements**
- Address generation using secp256k1 library (Linux only)
Instructions for installing the secp256k1 library on your system can be found at doc/wiki/install-linux/Install-MMGen-on-Debian-or-Ubuntu-Linux.md
If secp256k1 is not installed on the system, MMGen will still be usable. It just falls back to 'keyconv', or failing that, python-ecdsa for generating addresses.

View file

@ -0,0 +1,12 @@
*Assorted fixes/improvements*
- Importing addresses with `--rescan` working again
- Tracking and spending non-MMGen addresses now fully functional
- `mmgen-txcreate`: improvements in unspent outputs display
- `mmgen-txsign`: use bitcoind wallet dump as keylist fixed
- Testnet support:
- Practice sending transactions without risking funds
(free testnet coins: https://tpfaucet.appspot.com/)
- Test suite fully supported
- To enable, set `MMGEN_TESTNET` environment variable

View file

@ -0,0 +1,12 @@
Assorted fixes/improvements:
- Importing addresses with --rescan working again
- Tracking and spending non-MMGen addresses fully functional
- mmgen-txcreate: improvements in unspent outputs display
- mmgen-txsign: use bitcoind wallet dump as keylist without modification
- Testnet support:
- Practice sending transactions without risking real funds
(free testnet coins: https://tpfaucet.appspot.com/)
- Test suite fully supported
- To enable, set MMGEN_TESTNET environment variable

View file

@ -0,0 +1,20 @@
**Data directory, config file and default wallet support**
- Data directory is `~/.mmgen`; config file is `mmgen.cfg`.
- When default wallet is present in data directory, specifying the wallet
on the command line is optional.
- Datadir structure mirrors that of Bitcoin Core: mainnet and testnet share
a common config file, with testnet putting its own files, including the
default wallet, in the subdirectory 'testnet3'.
- Global vars are now overriden in this order:
1) config file
2) environmental variables beginning with `MMGEN_` (listed in globalvars.py)
3) command line
- Long (common) opts added for setting global vars; display with `--longhelp`.
The test suite has been updated to test these new features.
**Other changes**
- Always get user entropy, even for non-critical randomness, unless `-r0`.
- rpcuser,rpcpassword now override cookie authentication, as with Core.
- Communication with remote bitcoind supported with `--rpc-host` option.
- Testnet use can be overridden with the `--testnet=0|1` option.

View file

@ -0,0 +1,15 @@
This release brings full functionality and wider testing to the MS Windows port. MMGen now works with both WinXP/MinGW32 and Win7+/MinGW64, and separate, updated installation instructions for both platforms have been added to the wiki. A working MinGW environment is now required to run MMGen.
New Windows features:
- Full non-interactive test suite support with pexpect (PopenSpawn)
- secp256k1 address generation support
- Secure wallet deletion with sdelete
Windows bugfixes:
- A critical bug in writing the encrypted keyaddrfile has been fixed. This bug
would have affected only online wallet use and would not have led to the loss
of coins
- Cookie filename fixed; RPC cookie authentication now functional
General features:
- --bitcoin-data-dir, --rpc-port, --rpc-user, and --rpc-password options

View file

@ -0,0 +1,11 @@
New features:
- New `mmgen-txdo` command creates, signs and sends transactions in one operation
- Exporting seed to hexadecimal (mmhex) format now supported
- Support for 8-color terminals, better default colors on 256-color terminals
- `--force-256-color` option overrides terminfo entry and $TERM environment variable
- Selected commands of `mmgen-tool` now accept stdin input
- Transaction file format change: TXID appended to file after tx is broadcast
A new tutorial, [Recovering Keys Without MMGen][01], has been added to the wiki
[01]: https://github.com/mmgen/mmgen/wiki/Recovering-Keys-Without-MMGen

View file

@ -0,0 +1,25 @@
BIP 125 replace-by-fee (RBF) support:
- Create replaceable transactions using the `--rbf` switch to `mmgen-txcreate` and `mmgen-txdo`
- Create, and optionally sign and send, replacement transactions with the new `mmgen-txbump` command
Satoshis-per-byte format:
- Tx fees, both on the command line and at the interactive prompt, may be specified either as absolute BTC amounts or in satoshis-per-byte format (an integer followed by the letter 's')
Improved fee handling:
- Completely reworked fee-handling code with better fee checking
- default tx fee eliminated, max_tx_fee configurable in mmgen.cfg
Command scriptability:
- New `--yes` switch makes `mmgen-txbump` and `mmgen-txsign` fully non-interactive and `mmgen-txcreate` and `mmgen-txsend` mostly non-interactive
Bugfixes and usability improvements:
- 'mmgen-tool listaddresses' now list addresses from multiple seeds correctly
- Improved user interaction with all 'mmgen-tx*' commands
The RBF and new fee functionality are documented in the [Getting Started][01] guide.
The guide has also been updated with a new [Preliminaries][03] section and a new [Hot wallets and key-address files][02] section.
[01]: https://github.com/mmgen/mmgen/wiki/Getting-Started-with-MMGen#a_fee
[02]: https://github.com/mmgen/mmgen/wiki/Getting-Started-with-MMGen#a_hw
[03]: https://github.com/mmgen/mmgen/wiki/Getting-Started-with-MMGen#a_i

View file

@ -0,0 +1,12 @@
**New features:**
- Bob and Alice regtest mode for testing MMGen in a mock two-user environment.
[Documented](https://github.com/mmgen/mmgen/wiki/MMGen-Quick-Start-with-Regtest-Mode) in the wiki, tests added to test suite
- Compressed P2PKH address support (address type 'C')
- BCH (Bitcoin Cash) support
- Segwit (address type 'S') support deployed after Segwit activation on mainnet
- and, of course, numerous bugfixes and small improvements

View file

@ -0,0 +1,8 @@
URL: https://github.com/mmgen/mmgen/releases/tag/v0.9.5
**New features:**
- Altcoin framework + full Litecoin support [(commit)](https://github.com/mmgen/mmgen/commit/35d10911596c76227b8cd6318681c6ac9dc02a42)
- Offline transaction autosigning [(commit)](https://github.com/mmgen/mmgen/commit/8fb3efd99cc2f62e7f12f0e1f9893dd287064035) [(VIDEO)](https://github.com/mmgen/mmgen/releases/tag/autosign)

View file

@ -0,0 +1,8 @@
**New features:**
- Key/address generation support for ETH, ETC, ZEC, XMR and 144 Bitcoin-derived altcoins
- Zcash z-address support (Linux only)
- Monero wallet generation utility (Linux only): `mmgen-tool keyaddrlist2monerowallet`
- 32-byte hexadecimal password generation with `mmgen-passgen --hex`
Altcoin support is EXPERIMENTAL. Use at your own risk

View file

@ -0,0 +1,6 @@
**New features:**
- Monero wallet syncing utility (258651a)
- mmgen-tool listaddresses: add 'show_age','show_days' options (f7e54cc)
This release closes a serious exploit (6b9df0e). Upgrading is advised. In particular, the **offline** MMGen installation in an online/offline setup should be upgraded.

View file

@ -0,0 +1,46 @@
### MMGen Version 0.9.8 Release Notes
**Interesting new features:**
- Bech32 address support (BTC: e4114ee, LTC: 2cb4df7)
- Stealth mnemonic entry (90ebc94)
**New comprehensive UTF-8 support:**
- UTF-8 filenames and paths (896c7fe)
- UTF-8 tracking wallet comments (d49c862)
- UTF-8 wallet labels (2104273)
- Proper formatting of CJK strings (ea6629d)
**Security/bugfixes:**
- `max_tx_file_size` and other TX file checks (cf20311)
- TX size estimation fixes (ed2b94c)
- Require brainwallet and passwords to be UTF-8 encoded (9f2153c)
Coin daemons used for testing:
- Bitcoin Core v0.16.0
- Litecoin Core v0.16.0rc1
- Bitcoin-ABC v0.17.1
- Monero v0.12.0.0 (Lithium Luna)
Tools used for testing:
- Zcash-Mini (a2b3504)
- Pycoin v0.90a
- Pyethereum v2.1.2
- Vanitygen-Plus (5ca3d22)
Note that some features, notably UTF-8 filename and path support, do not work
on the MS Windows/MinGW platform. See the file [doc/README.mswin.md][1] for
details.
All user input is now required to be UTF-8 encoded. This will break backward
compatibility in the unlikely event that a) you're using a non-ASCII wallet
password or brainwallet, and b) your native charset is non-UTF-8. If this is
the case, you must change your wallet password to an ASCII one (or export your
brainwallet to another MMGen wallet format) using an older version of MMGen
before upgrading.
[1]: ../doc/README.mswin

View file

@ -304,7 +304,7 @@ an empty passphrase, just hit ENTER twice.
for i in range(g.passwd_max_tries):
pw = ' '.join(get_words_from_user('Enter {}: '.format(desc)))
pw2 = ' '.join(get_words_from_user('Repeat passphrase: '))
dmsg('Passphrases: [{}] [{}]'.format(pw,pw2))
dmsg(u'Passphrases: [{}] [{}]'.format(pw,pw2))
if pw == pw2:
vmsg('Passphrases match'); break
else: msg('Passphrases do not match. Try again.')

View file

@ -42,6 +42,11 @@ def cleandir(d):
def getrandnum(n): return int(hexlify(os.urandom(n)),16)
def getrandhex(n): return hexlify(os.urandom(n))
def getrandnum_range(nbytes,rn_max):
while True:
rn = int(hexlify(os.urandom(nbytes)),16)
if rn < rn_max: return rn
def getrandstr(num_chars,no_space=False):
n,m = 95,32
if no_space: n,m = 94,33

View file

@ -328,9 +328,9 @@ Display options: show [D]ays, [g]roup, show [m]mgen addr, r[e]draw screen
try:
if not is_mmgen_id(arg1):
assert coinaddr,"Invalid coin address for this chain: {}".format(arg1)
assert coinaddr,"{pn} address '{ma}' not found in tracking wallet"
assert coinaddr.is_in_tracking_wallet(),"Address '{ca}' not found in tracking wallet"
assert coinaddr,u"Invalid coin address for this chain: {}".format(arg1)
assert coinaddr,u"{pn} address '{ma}' not found in tracking wallet"
assert coinaddr.is_in_tracking_wallet(),u"Address '{ca}' not found in tracking wallet"
except Exception as e:
msg(e[0].format(pn=g.proj_name,ma=mmaddr,ca=coinaddr))
return False

View file

@ -629,7 +629,7 @@ def get_words_from_file(infile,desc,silent=False):
try: words = f.read().decode('utf8').split() # split() also strips
except: die(1,'{} data must be UTF-8 encoded.'.format(capfirst(desc)))
f.close()
dmsg('Sanitized input: [{}]'.format(' '.join(words)))
dmsg(u'Sanitized input: [{}]'.format(' '.join(words)))
return words
def get_words(infile,desc,prompt):

View file

@ -72,7 +72,11 @@ done
shift $((OPTIND-1))
RED="\e[31;1m" GREEN="\e[32;1m" YELLOW="\e[33;1m" RESET="\e[0m"
REFDIR='test/ref'
if uname -a | grep -qi mingw; then SUDO='' MINGW=1; else SUDO='sudo' MINGW=''; fi
[ "$MINGW" ] || RED="\e[31;1m" GREEN="\e[32;1m" YELLOW="\e[33;1m" RESET="\e[0m"
set -e
[ "$NO_INSTALL" ] || {
BRANCH=$1; shift
@ -81,11 +85,6 @@ RED="\e[31;1m" GREEN="\e[32;1m" YELLOW="\e[33;1m" RESET="\e[0m"
[ "$FOUND_BRANCH" ] || { echo "Branch '$BRANCH' not found!"; exit; }
}
set -e
REFDIR=test/ref
if uname -a | grep -qi mingw; then SUDO='' MINGW=1; else SUDO='sudo' MINGW=''; fi
check() {
[ "$BRANCH" ] || { echo 'No branch specified. Exiting'; exit; }
[ "$(git diff $BRANCH)" == "" ] || {

View file

@ -147,6 +147,9 @@ class MMGenPexpect(object):
cmd_str = g.traceback_cmd + ' ' + cmd_str
# Msg('\ncmd_str: {}'.format(cmd_str))
if opt.popen_spawn:
# PopenSpawn() requires cmd string to be bytes. However, it autoconverts unicode
# input to bytes, though this behavior seems to be undocumented. Setting 'encoding'
# to 'UTF-8' will cause pexpect to reject non-unicode string input.
self.p = PopenSpawn(cmd_str.encode('utf8'))
else:
self.p = pexpect.spawn(cmd,args)

View file

@ -49,6 +49,7 @@ pwfile = u'passwd_file'
ref_dir = os.path.join(u'test',u'ref')
rt_pw = u'abc-α'
ref_wallet_brainpass = 'abc'
ref_wallet_hash_preset = '1'
ref_wallet_incog_offset = 123
@ -164,7 +165,7 @@ altcoin_pfx = '' if g.proto.base_coin == 'BTC' else '-'+g.proto.base_coin
tn_ext = ('','.testnet')[g.testnet]
coin_sel = g.coin.lower()
if g.coin == 'B2X': coin_sel = 'btc'
# if g.coin == 'B2X': coin_sel = 'btc'
fork = {'bch':'btc','btc':'btc','ltc':'ltc'}[coin_sel]
tx_fee = {'btc':'0.0001','bch':'0.001','ltc':'0.01'}[coin_sel]
@ -207,7 +208,7 @@ def restore_debug():
cfgs = {
'15': {
'tmpdir': os.path.join(u'test',u'tmp15'),
'wpasswd': 'Dorian',
'wpasswd': 'Dorian',
'kapasswd': 'Grok the blockchain',
'addr_idx_list': '12,99,5-10,5,12', # 8 addresses
'dep_generators': {
@ -231,10 +232,10 @@ cfgs = {
},
'17': { 'tmpdir': os.path.join(u'test',u'tmp17') },
'18': { 'tmpdir': os.path.join(u'test',u'tmp18') },
'19': { 'tmpdir': os.path.join(u'test',u'tmp19'), 'wpasswd':'abc' },
# '19': { 'tmpdir': os.path.join(u'test',u'tmp19'), 'wpasswd':'abc' }, B2X
'1': {
'tmpdir': os.path.join(u'test',u'tmp1'),
'wpasswd': 'Dorian',
'wpasswd': u'Dorian',
'kapasswd': 'Grok the blockchain',
'addr_idx_list': '12,99,5-10,5,12', # 8 addresses
'dep_generators': {
@ -339,7 +340,7 @@ cfgs = {
},
'5': {
'tmpdir': os.path.join(u'test',u'tmp5'),
'wpasswd': 'My changed password',
'wpasswd': 'My changed password',
'hash_preset': '2',
'dep_generators': {
'mmdat': 'passchg',
@ -1180,7 +1181,7 @@ def write_fake_data_to_file(d):
if opt.print_cmdline: msg(bwd_msg)
if opt.log: log_fd.write(bwd_msg + ' ')
if opt.verbose or opt.exact_output:
sys.stderr.write(u"Fake transaction wallet data written to file '{}'\n".format(unspent_data_file))
sys.stderr.write(u"Fake transaction wallet data written to file {!r}\n".format(unspent_data_file))
def create_tx_data(sources):
tx_data,ad = {},AddrData()
@ -1241,24 +1242,23 @@ def add_comments_to_addr_file(addrfile,outfile,use_labels=False):
# 100 words chosen randomly from here:
# https://github.com/bitcoin/bips/pull/432/files/6332230d63149a950d05db78964a03bfd344e6b0
rwords = [
'ампула','арест','арка','архив','атлас','афера','багаж','башмак','бежать','бидон','брюки','вена',
'взвод','виски','волна','вспышка','встреча','гавань','гамма','гора','горшок','депутат','динамика',
'доверие','доза','документ','жених','жюри','зависть','заслуга','зато','зацепка','заявка','здание',
'зеркало','зефир','зрачок','изнутри','исход','кедр','киоск','кирпич','комната','концерт','косой',
'кубок','лачуга','лужа','мелодия','металл','механизм','механизм','механизм','мост','мощность','мыло',
'некий','нижний','новый','няня','овощ','ограда','опыт','орел','падение','петля','пила','поцелуй',
'пощечина','проект','путем','пыль','роман','рюкзак','сауна','сбыт','север','сейчас','сержант','след',
'слуга','снижение','сокол','соус','стакан','статус','сущность','табак','тело','тень','техника','ужин',
'упор','уровень','фирма','франция','фуражка','чучело','шрифт','элемент']
rwords = """
алфавит алый амнезия амфора артист баян белый биатлон брат бульвар веревка вернуть весть возраст
восток горло горный десяток дятел ежевика жест жизнь жрать заговор здание зона изделие итог кабина
кавалер каждый канал керосин класс клятва князь кривой крыша крючок кузнец кукла ландшафт мальчик
масса масштаб матрос мрак муравей мычать негодяй носок ночной нрав оборот оружие открытие оттенок
палуба пароход период пехота печать письмо позор полтора понятие поцелуй почему приступ пруд пятно
ранее режим речь роса рынок рябой седой сердце сквозь смех снимок сойти соперник спичка стон
сувенир сугроб суть сцена театр тираж толк удивить улыбка фирма читатель эстония эстрада юность
"""
def make_brainwallet_file(fn):
# Print random words with random whitespace in between
wl = rwords.split()
nwords,ws_list,max_spaces = 10,' \n',5
def rand_ws_seq():
nchars = getrandnum(1) % max_spaces + 1
return ''.join([ws_list[getrandnum(1)%len(ws_list)] for i in range(nchars)])
rand_pairs = [rwords[getrandnum(4) % len(rwords)] + rand_ws_seq() for i in range(nwords)]
return ''.join([ws_list[getrandnum_range(1,200)%len(ws_list)] for i in range(nchars)])
rand_pairs = [wl[getrandnum(1) % len(wl)] + rand_ws_seq() for i in range(nwords)]
d = ''.join(rand_pairs).rstrip() + '\n'
if opt.verbose: msg_r('Brainwallet password:\n{}'.format(cyan(d)))
write_data_to_file(fn,d,'brainwallet password',silent=True)
@ -2419,7 +2419,7 @@ class MMGenTestSuite(object):
def regtest_walletgen(self,name,user):
t = MMGenExpect(name,'mmgen-walletgen',['-q','-r0','-p1','--'+user])
t.passphrase_new('new MMGen wallet','abc')
t.passphrase_new('new MMGen wallet',rt_pw)
t.label()
t.expect('move it to the data directory? (Y/n): ','y')
t.written_to_file('MMGen wallet')
@ -2435,7 +2435,7 @@ class MMGenTestSuite(object):
def regtest_user_sid(self,user):
return os.path.basename(get_file_with_ext('mmdat',self.regtest_user_dir(user)))[:8]
def regtest_addrgen(self,name,user,wf=None,passwd='abc',addr_range='1-5'):
def regtest_addrgen(self,name,user,wf=None,passwd=rt_pw,addr_range='1-5'):
from mmgen.addr import MMGenAddrType
for mmtype in g.proto.mmtypes:
t = MMGenExpect(name,'mmgen-addrgen',
@ -2523,7 +2523,7 @@ class MMGenTestSuite(object):
outputs_prompt,
extra_args=[],
wf=None,
pw='abc',
pw=rt_pw,
no_send=False,
do_label=False,
bad_locktime=False,
@ -2544,7 +2544,7 @@ class MMGenTestSuite(object):
t.expect('OK? (Y/n): ','y') # change OK?
t.expect('Add a comment to transaction? (y/N): ',('\n','y')[do_label])
if do_label:
t.expect('Comment: ',ref_tx_label_jp.encode('utf8')+'\n')
t.expect('Comment: ',ref_tx_label_jp+'\n')
t.expect('View decoded transaction\? .*?: ',('t','v')[full_tx_view],regex=True)
if not do_label: t.expect('to continue: ','\n')
t.passphrase('MMGen wallet',pw)
@ -2608,7 +2608,7 @@ class MMGenTestSuite(object):
t.expect('OK? (Y/n): ','y') # output OK?
t.expect('OK? (Y/n): ','y') # fee OK?
t.expect('Add a comment to transaction? (y/N): ','n')
t.passphrase('MMGen wallet','abc')
t.passphrase('MMGen wallet',rt_pw)
t.written_to_file('Signed transaction')
if not no_send:
t.expect('to confirm: ','YES, I REALLY WANT TO DO THIS\n')
@ -2736,11 +2736,11 @@ class MMGenTestSuite(object):
def regtest_alice_add_label_badaddr(self,name,addr,reply):
t = MMGenExpect(name,'mmgen-tool',['--alice','add_label',addr,'(none)'])
t.expect(reply,regex=True)
t.expect(reply.encode('utf8'),regex=True)
t.ok()
def regtest_alice_add_label_badaddr1(self,name):
return self.regtest_alice_add_label_badaddr(name,'abc','Invalid coin address for this chain: abc')
return self.regtest_alice_add_label_badaddr(name,rt_pw,u'Invalid coin address for this chain: '+rt_pw)
def regtest_alice_add_label_badaddr2(self,name):
addr = g.proto.pubhash2addr('00'*20,False) # mainnet zero address
@ -2876,7 +2876,7 @@ class MMGenTestSuite(object):
opt.coin = coin
sid = self.regtest_user_sid('bob')
self.regtest_user_txdo(
name,'bob','0.0001',[sid+':S:5'],'1',pw='abc',
name,'bob','0.0001',[sid+':S:5'],'1',pw=rt_pw,
extra_args=['--locktime='+str(locktime)],
bad_locktime=bad_locktime)
@ -2889,7 +2889,7 @@ class MMGenTestSuite(object):
def regtest_split_txdo_timelock_good_b2x(self,name):
self.regtest_split_txdo_timelock(name,'B2X',locktime=1321009871,bad_locktime=False)
# def regtest_user_txdo(self,name,user,fee,outputs_cl,outputs_prompt,extra_args=[],wf=None,pw='abc',no_send=False,do_label=False):
# def regtest_user_txdo(self,name,user,fee,outputs_cl,outputs_prompt,extra_args=[],wf=None,pw=rt_pw,no_send=False,do_label=False):
# undocumented admin commands
ref_tx_setup = regtest_setup
@ -2935,7 +2935,7 @@ class MMGenTestSuite(object):
t.expect('OK? (Y/n): ','y') # fee OK?
t.expect('OK? (Y/n): ','y') # change OK?
t.expect('Add a comment to transaction? (y/N): ','y')
t.expect('Comment: ',ref_tx_label_zh.encode('utf8')+'\n')
t.expect('Comment: ',ref_tx_label_zh+'\n')
t.expect('View decoded transaction\? .*?: ','n',regex=True)
t.expect('Save transaction? (y/N): ','y')
fn = t.written_to_file('Transaction')