MMGen Wallet adds offline transaction autosigning support for Monero

0
0
Expand Up
@@ -9,15 +9,15 @@
# https://gitlab.com/mmgen/mmgen
"""
autosign: Auto-sign MMGen transactions and message files
autosign: Auto-sign MMGen transactions, message files and XMR wallet output files
"""
import sys,os,asyncio
from subprocess import run,PIPE,DEVNULL
from collections import namedtuple
from .cfg import Config
from .util import msg,msg_r,ymsg,rmsg,gmsg,bmsg,die,suf,fmt,fmt_list
from .util import msg,msg_r,ymsg,rmsg,gmsg,bmsg,die,suf,fmt,fmt_list,async_run
from .color import yellow,red,orange
from .wallet import Wallet,get_wallet_cls
from .filename import find_file_in_dir
Expand All
@@ -39,6 +39,10 @@ def __init__(self,parent):
def unsigned(self):
return self._unprocessed( '_unsigned', self.rawext, self.sigext )
@property
def unsubmitted(self):
return self._unprocessed( '_unsubmitted', self.sigext, self.subext )
def _unprocessed(self,attrname,rawext,sigext):
if not hasattr(self,attrname):
dirlist = tuple(os.scandir(self.dir))
Expand Down
Expand Up
@@ -116,6 +120,58 @@ def gen_bad_list(self,bad_files):
for f in bad_files:
yield red(f.path)
class xmr_transaction(transaction):
dir_name = 'xmr_tx_dir'
desc = 'Monero transaction'
subext = 'subtx'
def __init__(self,*args,**kwargs):
super().__init__(*args,**kwargs)
if len(self.unsigned) > 1:
die('AutosignTXError', 'Only one unsigned XMR transaction allowed at a time!')
async def sign(self,f):
from .xmrwallet import MoneroMMGenTX,MoneroWalletOps,xmrwallet_uargs
tx1 = MoneroMMGenTX.Completed( self.parent.xmrwallet_cfg, f.path )
m = MoneroWalletOps.sign(
self.parent.xmrwallet_cfg,
xmrwallet_uargs(
infile = self.parent.wallet_files[0], # MMGen wallet file
wallets = str(tx1.src_wallet_idx),
spec = None ),
)
tx2 = await m.main(f.path) # TODO: stop wallet daemon?
tx2.write(ask_write=False)
return tx2
def print_summary(self,txs):
bmsg('\nAutosign summary:\n')
msg_r('\n'.join(tx.get_info() for tx in txs))
class xmr_wallet_outputs_file(transaction):
desc = 'Monero wallet outputs file'
rawext = 'raw'
sigext = 'sig'
dir_name = 'xmr_outputs_dir'
async def sign(self,f):
from .xmrwallet import MoneroWalletOps,xmrwallet_uargs
wallet_idx = MoneroWalletOps.wallet.get_idx_from_fn(f.name)
m = MoneroWalletOps.export_key_images(
self.parent.xmrwallet_cfg,
xmrwallet_uargs(
infile = self.parent.wallet_files[0], # MMGen wallet file
wallets = str(wallet_idx),
spec = None ),
)
obj = await m.main( f, wallet_idx )
obj.write()
return obj
def print_summary(self,txs):
bmsg('\nAutosign summary:')
msg(' ' + '\n '.join(tx.get_info() for tx in txs) + '\n')
class message(base):
desc = 'message file'
rawext = 'rawmsg.json'
Expand Down
Expand Up
@@ -200,10 +256,18 @@ def __init__(self,cfg):
self.coins = cfg.coins.upper().split(',') if cfg.coins else []
if cfg.xmrwallets and not 'XMR' in self.coins:
self.coins.append('XMR')
if not self.coins:
ymsg('Warning: no coins specified, defaulting to BTC')
self.coins = ['BTC']
if 'XMR' in self.coins:
self.xmr_dir = os.path.join( self.mountpoint, 'xmr' )
self.xmr_tx_dir = os.path.join( self.mountpoint, 'xmr', 'tx' )
self.xmr_outputs_dir = os.path.join( self.mountpoint, 'xmr', 'outputs' )
async def check_daemons_running(self):
from .protocol import init_proto
for coin in self.coins:
Expand Down
Expand Up
@@ -239,7 +303,7 @@ def wallet_files(self):
return self._wallet_files
def do_mount(self):
def do_mount(self,no_xmr_chk=False):
from stat import S_ISDIR,S_IWUSR,S_IRUSR
Expand Down
Expand Up
@@ -272,6 +336,9 @@ def do_die(m):
if self.have_msg_dir:
check_dir(self.msg_dir)
if 'XMR' in self.coins and not no_xmr_chk:
check_dir(self.xmr_tx_dir)
def do_umount(self):
if os.path.ismount(self.mountpoint):
run( ['sync'], check=True )
Expand Down
Expand Up
@@ -330,7 +397,10 @@ async def do_sign(self):
self.led.set('busy')
ret1 = await self.sign_all('transaction')
ret2 = await self.sign_all('message') if self.have_msg_dir else True
ret = ret1 and ret2
# import XMR wallet outputs BEFORE signing transactions:
ret3 = await self.sign_all('xmr_wallet_outputs_file') if 'XMR' in self.coins else True
ret4 = await self.sign_all('xmr_transaction') if 'XMR' in self.coins else True
ret = ret1 and ret2 and ret3 and ret4
self.do_umount()
self.led.set(('standby','off','error')[(not ret)*2 or bool(self.cfg.stealth_led)])
return ret
Expand Down
Expand Up
@@ -365,7 +435,7 @@ def gen_key(self,no_unmount=False):
self.create_wallet_dir()
if not self.get_insert_status():
die(1,'Removable device not present!')
self.do_mount()
self.do_mount(no_xmr_chk=True)
self.wipe_existing_key()
self.create_key()
if not no_unmount:
Expand Down
Expand Up
@@ -398,6 +468,53 @@ def setup(self):
ss_out = Wallet( self.cfg, ss=ss_in )
ss_out.write_to_file( desc='autosign wallet', outdir=self.wallet_dir )
@property
def xmrwallet_cfg(self):
if not hasattr(self,'_xmrwallet_cfg'):
from .cfg import Config
self._xmrwallet_cfg = Config({
'coin': 'xmr',
'wallet_rpc_user': 'autosigner',
'wallet_rpc_password': 'my very secret password',
'passwd_file': self.cfg.passwd_file,
'wallet_dir': self.wallet_dir,
'autosign': True,
'autosign_mountpoint': self.mountpoint,
'outdir': self.xmr_dir, # required by vkal.write()
})
return self._xmrwallet_cfg
def xmr_setup(self):
import shutil
try: shutil.rmtree(self.xmr_outputs_dir)
except: pass
os.makedirs(self.xmr_outputs_dir)
os.makedirs(self.xmr_tx_dir,exist_ok=True)
from .addrfile import ViewKeyAddrFile
from .fileutil import shred_file
for f in os.scandir(self.xmr_dir):
if f.name.endswith(ViewKeyAddrFile.ext):
msg(f'Shredding old viewkey-address file {f.name!r}')
shred_file(f.path)
if len(self.wallet_files) > 1:
ymsg(f'Warning: more that one wallet file, using the first ({self.wallet_files[0]}) for xmrwallet generation')
from .xmrwallet import MoneroWalletOps,xmrwallet_uargs
m = MoneroWalletOps.create_offline(
self.xmrwallet_cfg,
xmrwallet_uargs(
infile = self.wallet_files[0], # MMGen wallet file
wallets = self.cfg.xmrwallets, # XMR wallet idxs
spec = None ),
)
async_run(m.main())
async_run(m.stop_wallet_daemon())
def get_insert_status(self):
if self.cfg.no_insert_check:
return True
Expand Down