mmgen-autosign: support signing of TXs with non-MMGen inputs

This commit is contained in:
The MMGen Project 2025-06-21 11:14:09 +00:00
commit b12fd879bf
Signed by: mmgen
GPG key ID: 3F8B1861E32B7DA2
8 changed files with 111 additions and 10 deletions

View file

@ -275,6 +275,7 @@ class Signable:
self.cfg,
tx1,
seedfiles = self.parent.wallet_files[:],
keylist = self.parent.keylist,
passwdfile = str(self.parent.keyfile),
autosign = True).keys)
if tx2:
@ -424,6 +425,7 @@ class Autosign:
macOS_ramdisk_name = 'AutosignRamDisk'
wallet_subdir = 'autosign'
linux_blkid_cmd = 'sudo blkid -s LABEL -o value'
keylist_fn = 'keylist.mmenc'
cmds = ('setup', 'xmr_setup', 'sign', 'wait')
@ -672,6 +674,7 @@ class Autosign:
self.led.set('busy')
self.do_mount()
key_ok = self.decrypt_wallets()
self.init_non_mmgen_keys()
if key_ok:
if self.cfg.stealth_led:
self.led.set('busy')
@ -780,6 +783,9 @@ class Autosign:
ss_out = Wallet(self.cfg, ss=ss_in, passwd_file=str(self.keyfile))
ss_out.write_to_file(desc='autosign wallet', outdir=self.wallet_dir)
if self.cfg.keys_from_file:
self.setup_non_mmgen_keys()
@property
def xmrwallet_cfg(self):
if not hasattr(self, '_xmrwallet_cfg'):
@ -908,3 +914,34 @@ class Autosign:
enabled = self.cfg.led,
simulate = self.cfg.test_suite_autosign_led_simulate)
self.led.set('off')
def setup_non_mmgen_keys(self):
from .fileutil import get_lines_from_file, write_data_to_file
from .crypto import Crypto
lines = get_lines_from_file(self.cfg, self.cfg.keys_from_file, desc='keylist data')
write_data_to_file(
self.cfg,
str(self.wallet_dir / self.keylist_fn),
Crypto(self.cfg).mmgen_encrypt(
data = '\n'.join(lines).encode(),
passwd = self.keyfile.read_text()),
desc = 'encrypted keylist data',
binary = True)
if keypress_confirm(self.cfg, 'Securely delete original keylist file?'):
shred_file(self.cfg, self.cfg.keys_from_file)
def init_non_mmgen_keys(self):
if not hasattr(self, 'keylist'):
path = self.wallet_dir / self.keylist_fn
if path.exists():
from .crypto import Crypto
from .fileutil import get_data_from_file
self.keylist = Crypto(self.cfg).mmgen_decrypt(
get_data_from_file(
self.cfg,
path,
desc = 'encrypted keylist data',
binary = True),
passwd = self.keyfile.read_text()).decode().split()
else:
self.keylist = None

View file

@ -1 +1 @@
15.1.dev48
15.1.dev49

View file

@ -36,6 +36,12 @@ opts_data = {
--, --longhelp Print help message for long (global) options
-c, --coins=c Coins to sign for (comma-separated list)
-I, --no-insert-check Dont check for device insertion
-k, --keys-from-file=F Use wif keys listed in file F for signing non-MMGen
inputs. The file may be MMGen encrypted if desired. The
setup operation creates a temporary encrypted copy of
the file in volatile memory for use during the signing
session, thus permitting the deletion of the original
file for increased security.
-l, --seed-len=N Specify wallet seed length of N bits (for setup only)
-L, --led Use status LED to signal standby, busy and error
-m, --mountpoint=M Specify an alternate mountpoint 'M'

View file

@ -95,8 +95,7 @@ class Base(MMGenObject):
This transaction includes inputs with non-{gc.proj_name} addresses. When
signing the transaction, private keys for the addresses listed below must
be supplied using the --keys-from-file option. The key file must contain
one key per line. Please note that this transaction cannot be autosigned,
as autosigning does not support the use of key files.
one key per line.
Non-{gc.proj_name} addresses found in inputs:
{{}}
@ -213,6 +212,7 @@ class Base(MMGenObject):
quiet = True)
def check_non_mmgen_inputs(self, *, caller, non_mmaddrs=None):
assert caller in ('txcreate', 'txdo', 'txsign', 'autosign')
non_mmaddrs = non_mmaddrs or self.get_non_mmaddrs('inputs')
if non_mmaddrs:
indent = ' '
@ -223,7 +223,7 @@ class Base(MMGenObject):
die('UserOptError', f'\n{indent}ERROR: {m}\n')
else:
msg(f'\n{indent}WARNING: {m}\n')
if not self.cfg.yes:
if not (caller == 'autosign' or self.cfg.yes):
from ..ui import keypress_confirm
keypress_confirm(self.cfg, 'Continue?', default_yes=True, do_exit=True)

View file

@ -89,6 +89,7 @@ class TxKeys:
self.keylist = keylist if autosign else keylist or get_keylist(cfg)
self.keyaddrlist = keyaddrlist if autosign else keyaddrlist or get_keyaddrlist(cfg, tx.proto)
self.passwdfile = passwdfile
self.autosign = autosign
self.saved_seeds = {}
def get_keys_for_non_mmgen_inputs(self):
@ -96,7 +97,7 @@ class TxKeys:
sep = '\n '
if addrs := self.tx.get_non_mmaddrs('inputs'):
self.tx.check_non_mmgen_inputs(
caller = 'txsign',
caller = 'autosign' if self.autosign and self.keylist else 'txsign',
non_mmaddrs = addrs)
kal = KeyAddrList(
cfg = self.cfg,

View file

@ -15,7 +15,7 @@ import time
from .autosign import CmdTestAutosignThreaded
from .regtest import CmdTestRegtest, rt_pw
from ..include.common import gr_uc
from ..include.common import gr_uc, create_addrpairs
class CmdTestAutosignAutomount(CmdTestAutosignThreaded, CmdTestRegtest):
'automounted transacting operations via regtest mode'
@ -23,21 +23,29 @@ class CmdTestAutosignAutomount(CmdTestAutosignThreaded, CmdTestRegtest):
networks = ('btc', 'bch', 'ltc')
tmpdir_nums = [49]
bdb_wallet = True
keylist_passwd = 'abc'
rt_data = {
'rtFundAmt': {'btc':'500', 'bch':'500', 'ltc':'5500'},
}
bal1_chk = {
'btc': '502.46',
'bch': '502.46',
'ltc': '5502.46'}
bal2_chk = {
'btc': '491.11002204',
'bch': '498.7653392',
'ltc': '5491.11002204'}
'btc': '493.56992828',
'bch': '501.22524576',
'ltc': '5493.56992828'}
cmd_group = (
('setup', 'regtest mode setup'),
('walletgen_alice', 'wallet generation (Alice)'),
('addrgen_alice', 'address generation (Alice)'),
('addrimport_alice', 'importing Alice’s addresses'),
('addrimport_alice_non_mmgen', 'importing Alice’s non-MMGen addresses'),
('fund_alice', 'funding Alice’s wallet'),
('fund_alice_non_mmgen1', 'funding Alice’s wallet (non-MMGen addr #1)'),
('fund_alice_non_mmgen2', 'funding Alice’s wallet (non-MMGen addr #2)'),
('generate', 'mining a block'),
('alice_bal1', 'checking Alice’s balance'),
('alice_txcreate1', 'creating a transaction'),
@ -92,9 +100,30 @@ class CmdTestAutosignAutomount(CmdTestAutosignThreaded, CmdTestRegtest):
self.opts.append('--alice')
self.non_mmgen_addrs = create_addrpairs(self.proto, 'C', 2)
def addrimport_alice_non_mmgen(self):
self.write_to_tmpfile(
'non_mmgen_addrs',
'\n'.join(e.addr for e in self.non_mmgen_addrs))
return self.spawn(
'mmgen-addrimport',
['--alice', '--quiet', '--addrlist', f'{self.tmpdir}/non_mmgen_addrs'])
def fund_alice_non_mmgen1(self):
return self.fund_wallet('alice', '1.23', addr=self.non_mmgen_addrs[0].addr)
def fund_alice_non_mmgen2(self):
return self.fund_wallet('alice', '1.23', addr=self.non_mmgen_addrs[1].addr)
def alice_bal1(self):
return self._user_bal_cli('alice', chk=self.bal1_chk[self.coin])
def alice_txcreate1(self):
return self._user_txcreate(
'alice',
inputs = '1-3',
tweaks = ['confirm_non_mmgen'],
chg_addr = 'C:5',
data_arg = 'data:'+gr_uc[:24])
@ -147,7 +176,18 @@ class CmdTestAutosignAutomount(CmdTestAutosignThreaded, CmdTestRegtest):
return self._user_txcreate('alice', chg_addr='C:5', exit_val=2, expect_str='unsent transaction')
def alice_run_autosign_setup(self):
return self.run_setup(mn_type='default', use_dfl_wallet=True, passwd=rt_pw)
from mmgen.crypto import Crypto
from mmgen.cfg import Config
new_cfg = Config({'_clone': self.cfg, 'usr_randchars': 0, 'hash_preset': '1'})
enc_data = Crypto(new_cfg).mmgen_encrypt(
'\n'.join(e.wif for e in self.non_mmgen_addrs).encode(), passwd=self.keylist_passwd)
self.write_to_tmpfile('non_mmgen_keys.mmenc', enc_data, binary=True)
return self.run_setup(
mn_type = 'default',
use_dfl_wallet = True,
wallet_passwd = rt_pw,
add_opts = [f'--keys-from-file={self.tmpdir}/non_mmgen_keys.mmenc'],
keylist_passwd = self.keylist_passwd)
def alice_txsend1(self):
return self._user_txsend('alice', comment='This one’s worth a comment', no_wait=True)

View file

@ -197,6 +197,7 @@ class CmdTestAutosignBase(CmdTestBase):
seed_len = None,
usr_entry_modes = False,
wallet_passwd = None,
keylist_passwd = None,
add_opts = [],
expect_args = []):
@ -244,6 +245,10 @@ class CmdTestAutosignBase(CmdTestBase):
if expect_args:
t.expect(*expect_args)
if keylist_passwd:
t.passphrase('keylist data', keylist_passwd)
t.expect('(y/N): ', 'y')
t.read()
self.remove_device()

View file

@ -23,6 +23,7 @@ test.include.common: Shared routines and data for the MMGen test suites
import sys, os, re, atexit
from subprocess import run, PIPE, DEVNULL
from pathlib import Path
from collections import namedtuple
from mmgen.cfg import gv
from mmgen.color import yellow, green, orange
@ -362,6 +363,17 @@ def make_burn_addr(proto, mmtype='compressed', hexdata=None):
proto = proto,
mmtype = mmtype).pubhash2addr(hexdata or '00'*20)
def create_addrpairs(proto, mmtype, num):
ap = namedtuple('addrpair', ['wif', 'addr'])
from mmgen.tool.coin import tool_cmd
n = 123456789123456789
return [ap(*tool_cmd(
cfg = cfg,
cmdname = 'privhex2pair',
proto = proto,
mmtype = mmtype).privhex2pair(f'{n+m:064x}'))
for m in range(num)]
def VirtBlockDevice(img_path, size):
if sys.platform == 'linux':
return VirtBlockDeviceLinux(img_path, size)