whitelist import by qr

This commit is contained in:
Peter D. Gray 2024-10-08 11:39:36 -04:00 committed by scgbckbone
parent fc5e99c226
commit d21515e2cf
2 changed files with 120 additions and 8 deletions

View File

@ -10,6 +10,7 @@ from ux import ux_confirm, ux_show_story, the_ux, OK, ux_dramatic_pause, ux_ente
from menu import MenuSystem, MenuItem
from seed import seed_words_to_encoded_secret
from stash import SecretStash, len_from_marker, len_to_numwords
from charcodes import KEY_QR
class CCCFeature:
@classmethod
@ -219,6 +220,96 @@ class PolCheckedMenuItem(MenuItem):
assert isinstance(m, CCCPolicyMenu)
return bool(m.policy.get(self.polkey, False))
class CCCAddrWhitelist(MenuSystem):
# simulator arg: --seq tcENTERENTERsENTERwENTER
def __init__(self):
items = self.construct()
super().__init__(items)
def update_contents(self):
tmp = self.construct()
self.replace_items(tmp)
@classmethod
async def be_a_submenu(cls, *a):
return cls()
def construct(self):
# list of addresses
addrs = CCCFeature.get_policy().get('addr', [])
if version.has_qr:
def shorten4menu(a):
return a[:12]+''+a[-16:]
else:
def shorten4menu(a):
# good for segwit
# TODO: test on classics
return a[:6]+''+a[-8:]
items = [MenuItem(shorten4menu(a), f=self.edit_addr, arg=a) for a in addrs]
if not items:
items.append(MenuItem("(none yet)"))
if version.has_qr:
items.append(MenuItem('Scan QR', f=self.scan_qr, shortcut=KEY_QR))
items.append(MenuItem('Import from SD'))
return items
async def edit_addr(self, menu, idx, item):
# show detail and offer delete
addr = item.arg
msg = 'Spends to this address will be permitted:\n\n%s\n\nPress (4) to delete.' % addr
ch = await ux_show_story(msg, escape='4')
if ch == '4':
self.delete_addr(addr)
def delete_addr(self, addr):
# no confirm, stakes are low
addrs = CCCFeature.get_policy().get('addr', [])
addrs.remove(addr)
CCCFeature.update_policy_key(addrs=addrs)
self.update_contents()
async def scan_qr(self, *a):
# Scan and return a text string. For things like BIP-39 passphrase
# and perhaps they are re-using a QR from something else. Don't act on contents.
from ux_q1 import QRScannerInteraction
q = QRScannerInteraction()
got = []
ln = ''
while 1:
here = await q.scan_for_addresses("Bitcoin Address(es) to Whitelist", line2=ln)
if not here: break
got.extend(here)
ln = 'Got %d so far. CANCEL to save.' % len(got) # XXX ENTER would be better
if got:
# import them
await self.add_addresses(got)
async def add_addresses(self, more_addrs):
# add new entries, if unique; preserve ordering
addrs = CCCFeature.get_policy().get('addr', [])
new = []
for a in more_addrs:
if a not in addrs:
addrs.append(a)
new.append(a)
if not new:
await ux_show_story("Already in whitelist:\n\n" + '\n\n'.join(more_addrs))
return
CCCFeature.update_policy_key(addrs=addrs)
self.update_contents()
class CCCPolicyMenu(MenuSystem):
# Build menu stack that allows edit of all features of the spending
# policy. Key C is set already at this point.
@ -236,7 +327,6 @@ class CCCPolicyMenu(MenuSystem):
@classmethod
async def be_a_submenu(cls, *a):
print("here")
return cls()
def construct(self):
@ -245,7 +335,7 @@ class CCCPolicyMenu(MenuSystem):
PolCheckedMenuItem('Max Magnitude', 'mag', f=self.set_magnitude),
PolCheckedMenuItem('Limit Velocity', 'vel', chooser=self.velocity_chooser),
PolCheckedMenuItem('Whitelist' + (' Addresses' if version.has_qr else ''),
'addr', f=self.edit_whitelist),
'addr', menu=CCCAddrWhitelist.be_a_submenu),
PolCheckedMenuItem('Web 2FA', 'web2fa', f=self.toggle_2fa),
]
@ -269,9 +359,6 @@ class CCCPolicyMenu(MenuSystem):
ss = self.policy.get('web2fa')
assert ss
await web2fa.web2fa_enroll('CCC', ss)
async def edit_whitelist(self, *a):
pass
async def set_magnitude(self, *a):
was = self.policy.get('mag', 0)

View File

@ -830,10 +830,10 @@ class QRScannerInteraction:
return data
async def scan_general(self, prompt, convertor):
async def scan_general(self, prompt, convertor, line2=None):
# Scan stuff, and parse it .. raise QRDecodeExplained if you don't like it
# continues until something is accepted
problem = None
problem = line2
while 1:
try:
@ -847,7 +847,7 @@ class QRScannerInteraction:
problem = str(exc)
continue
except Exception as exc:
#import sys; sys.print_exception(exc)
import sys; sys.print_exception(exc)
problem = "Unable to decode QR"
continue
@ -877,6 +877,31 @@ class QRScannerInteraction:
return await self.scan_general(prompt, convertor)
async def scan_for_addresses(self, prompt, line2=None):
# accept only payment addresses; strips BIP-21 junk that might be there
# - always a list result, might be size one
from utils import decode_bip21_text
def addr_taster(got):
# could be muliple-line text file via BBQR or single line
got = decode_qr_result(got, expect_text=True)
try:
rv = []
for ln in got.split():
what, args = decode_bip21_text(ln)
if what == 'addr':
rv.append(args[1])
if rv:
return rv
except QRDecodeExplained:
raise
except:
pass
raise QRDecodeExplained("Not a payment address?")
return await self.scan_general(prompt, addr_taster, line2=line2)
async def scan_anything(self, expect_secret=False, tmp=False):
# start a QR scan, and act on what we find, whatever it may be.