remove ccc confirm with (4) key; remove ccc tests
This commit is contained in:
parent
e14fb64904
commit
8bf4731cf5
@ -584,12 +584,10 @@ async def clear_seed(*a):
|
||||
'Saved temporary seed settings and Seed Vault are lost.'):
|
||||
return await ux_aborted()
|
||||
|
||||
ch = await ux_show_story('''Are you REALLY sure though???\n\n\
|
||||
if not await ux_confirm('''Are you REALLY sure though???\n\n\
|
||||
This action will certainly cause you to lose all funds associated with this wallet, \
|
||||
unless you have a backup of the seed words and know how to import them into a \
|
||||
new wallet.\n\nPress (4) to prove you read to the end of this message and accept all \
|
||||
consequences.''', escape='4')
|
||||
if ch != '4':
|
||||
new wallet.''', confirm_key='4'):
|
||||
return await ux_aborted()
|
||||
|
||||
# clear settings, address cache, settings from tmp seeds / seedvault seeds
|
||||
|
||||
@ -6,7 +6,7 @@ import gc, chains, version, ngu, web2fa, bip39, re
|
||||
from chains import NLOCK_IS_TIME
|
||||
from utils import swab32, a2b_hex, b2a_hex, xfp2str, truncate_address, pad_raw_secret
|
||||
from glob import settings
|
||||
from ux import ux_confirm, ux_show_story, the_ux, OK, ux_dramatic_pause, ux_enter_number
|
||||
from ux import ux_confirm, ux_show_story, the_ux, OK, ux_dramatic_pause, ux_enter_number, ux_aborted
|
||||
from menu import MenuSystem, MenuItem, start_chooser
|
||||
from seed import seed_words_to_encoded_secret
|
||||
from stash import SecretStash
|
||||
@ -53,6 +53,11 @@ class CCCFeature:
|
||||
ccc = settings.get('ccc')
|
||||
return ccc['c_xfp'] if ccc else None
|
||||
|
||||
@classmethod
|
||||
def get_master_xpub(cls):
|
||||
ccc = settings.get('ccc')
|
||||
return ccc['c_xpub'] if ccc else None
|
||||
|
||||
@classmethod
|
||||
def init_setup(cls, words):
|
||||
# Encode 12 or 24 words into the secret to held as key C.
|
||||
@ -290,13 +295,14 @@ class CCCConfigMenu(MenuSystem):
|
||||
|
||||
async def remove_ccc(self, *a):
|
||||
# disable and remove feature
|
||||
if not await ux_confirm('''Key C will be lost, and policy settings forgotten. \
|
||||
This unit will only be able to partly sign transactions. To completely remove this \
|
||||
wallet, proceed to the multisig menu and remove related wallet entry.'''):
|
||||
if not await ux_confirm('Key C will be lost, and policy settings forgotten.'
|
||||
' This unit will only be able to partly sign transactions.'
|
||||
' To completely remove this wallet, proceed to the multisig'
|
||||
' menu and remove related wallet entries.'):
|
||||
return
|
||||
|
||||
if not await ux_confirm("Last chance. Funds in this wallet may be impacted."):
|
||||
return
|
||||
if not await ux_confirm("Funds in related wallet/s may be impacted.", confirm_key='4'):
|
||||
return await ux_aborted()
|
||||
|
||||
CCCFeature.remove_ccc()
|
||||
the_ux.pop()
|
||||
@ -347,7 +353,11 @@ be ready to show it as a QR, before proceeding.'''
|
||||
async def show_ident(self, *a):
|
||||
# give some background? or just KISS for now?
|
||||
xfp = xfp2str(CCCFeature.get_xfp())
|
||||
await ux_show_story("XFP (Extended Finger Print) of key C is:\n\n %s" % xfp)
|
||||
xpub = CCCFeature.get_master_xpub()
|
||||
await ux_show_story(
|
||||
"Key C:\n\n"
|
||||
"XFP (Master Fingerprint):\n\n %s\n\n"
|
||||
"Master Extended Public Key:\n\n %s " % (xfp, xpub))
|
||||
|
||||
async def enter_temp_mode(self, *a):
|
||||
# apply key C as temp seed, so you can do anything with it
|
||||
|
||||
@ -1119,17 +1119,14 @@ class EphemeralSeedMenu(MenuSystem):
|
||||
async def make_ephemeral_seed_menu(*a):
|
||||
if (not pa.tmp_value) and (not settings.master_get("seedvault", False)):
|
||||
# force a warning on them, unless they are already doing it.
|
||||
ch = await ux_show_story(
|
||||
if not await ux_confirm(
|
||||
"Temporary seed is a secret completely separate "
|
||||
"from the master seed, typically held in device RAM and "
|
||||
"not persisted between reboots in the Secure Element. "
|
||||
"Enable the Seed Vault feature to store these secrets longer-term."
|
||||
"\n\nPress (4) to prove you read to the end"
|
||||
" of this message and accept all consequences.",
|
||||
"Enable the Seed Vault feature to store these secrets longer-term.",
|
||||
title="WARNING",
|
||||
escape="4"
|
||||
)
|
||||
if ch != "4":
|
||||
confirm_key="4"
|
||||
):
|
||||
return
|
||||
|
||||
rv = EphemeralSeedMenu.construct()
|
||||
|
||||
20
shared/ux.py
20
shared/ux.py
@ -19,7 +19,7 @@ if version.has_qwerty:
|
||||
CH_PER_W = CHARS_W
|
||||
STORY_H = CHARS_H
|
||||
from ux_q1 import PressRelease, ux_enter_number, ux_input_text, ux_show_pin
|
||||
from ux_q1 import ux_login_countdown, ux_confirm, ux_dice_rolling, ux_render_words
|
||||
from ux_q1 import ux_login_countdown, ux_dice_rolling, ux_render_words
|
||||
from ux_q1 import ux_show_phish_words
|
||||
OK = "ENTER"
|
||||
X = "CANCEL"
|
||||
@ -32,7 +32,7 @@ else:
|
||||
CH_PER_W = 19
|
||||
STORY_H = 5
|
||||
from ux_mk4 import PressRelease, ux_enter_number, ux_input_text, ux_show_pin
|
||||
from ux_mk4 import ux_login_countdown, ux_confirm, ux_dice_rolling, ux_render_words
|
||||
from ux_mk4 import ux_login_countdown, ux_dice_rolling, ux_render_words
|
||||
from ux_mk4 import ux_show_phish_words
|
||||
OK = "OK"
|
||||
X = "X"
|
||||
@ -248,7 +248,21 @@ async def ux_show_story(msg, title=None, escape=None, sensitive=False,
|
||||
if ch in { KEY_NFC, KEY_QR }:
|
||||
return ch
|
||||
|
||||
|
||||
async def ux_confirm(msg, title="Are you SURE ?!?", confirm_key=None):
|
||||
# confirmation screen, with stock title and Y=of course.
|
||||
if not version.has_qwerty and len(title) > 12:
|
||||
msg = title + "\n\n" + msg
|
||||
title = None
|
||||
|
||||
suffix = ""
|
||||
if confirm_key:
|
||||
suffix = ("\n\nPress (%s) to prove you read to the end of this message"
|
||||
" and accept all consequences.") % confirm_key
|
||||
|
||||
msg += suffix
|
||||
r = await ux_show_story(msg, title=title, escape=confirm_key)
|
||||
|
||||
return r == (confirm_key or 'y')
|
||||
|
||||
async def idle_logout():
|
||||
import glob
|
||||
|
||||
@ -58,15 +58,7 @@ class PressRelease:
|
||||
else:
|
||||
self.last_key = ch
|
||||
return ch
|
||||
|
||||
|
||||
async def ux_confirm(msg):
|
||||
# confirmation screen, with stock title and Y=of course.
|
||||
from ux import ux_show_story
|
||||
|
||||
resp = await ux_show_story("Are you SURE ?!?\n\n" + msg)
|
||||
|
||||
return resp == 'y'
|
||||
|
||||
async def ux_enter_number(prompt, max_value, can_cancel=False, value=''):
|
||||
# return the decimal number which the user has entered
|
||||
|
||||
@ -77,14 +77,6 @@ class PressRelease:
|
||||
else:
|
||||
self.last_key = ch
|
||||
return ch
|
||||
|
||||
async def ux_confirm(msg):
|
||||
# confirmation screen, with stock title and Y=of course.
|
||||
from ux import ux_show_story
|
||||
|
||||
resp = await ux_show_story(msg, title="Are you SURE ?!?")
|
||||
|
||||
return resp == 'y'
|
||||
|
||||
async def ux_enter_number(prompt, max_value, can_cancel=False, value=''):
|
||||
# return the decimal number which the user has entered
|
||||
@ -601,6 +593,7 @@ async def seed_word_entry(prompt, num_words, has_checksum=True, done_cb=None):
|
||||
# - max word length is 8, min is 3
|
||||
# - useful: simulator.py --q1 --eff --seq 'aa ee 4i '
|
||||
from glob import dis
|
||||
from ux import ux_confirm
|
||||
|
||||
assert num_words and prompt and done_cb
|
||||
|
||||
@ -692,8 +685,7 @@ async def seed_word_entry(prompt, num_words, has_checksum=True, done_cb=None):
|
||||
elif ch == KEY_CANCEL:
|
||||
if word_num >= 2:
|
||||
tmp = dis.save_state()
|
||||
ok = await ux_confirm("Everything you've entered will be lost.")
|
||||
if not ok:
|
||||
if not await ux_confirm("Everything you've entered will be lost."):
|
||||
dis.restore_state(tmp)
|
||||
continue
|
||||
return None
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
# run simulator without --eff
|
||||
#
|
||||
#
|
||||
import pytest, pdb, requests, re, time, random, json, glob, os, hashlib, base64
|
||||
import pytest, pdb, requests, re, time, random, json, glob, os, hashlib, base64, uuid
|
||||
from base64 import urlsafe_b64encode
|
||||
from onetimepass import get_totp
|
||||
from helpers import prandom, slip132undo
|
||||
@ -489,7 +489,7 @@ def bitcoind_create_watch_only_wallet(pick_menu_item, need_keypress, microsd_pat
|
||||
res = json.loads(res)
|
||||
|
||||
bitcoind_wo = bitcoind.create_wallet(
|
||||
wallet_name=f"watch_only_ccc_{ms_menu_item.split()[-1]}", disable_private_keys=True,
|
||||
wallet_name=f"wo_ccc_{str(uuid.uuid4())}", disable_private_keys=True,
|
||||
blank=True, passphrase=None, avoid_reuse=False, descriptors=True
|
||||
)
|
||||
res = bitcoind_wo.importdescriptors(res)
|
||||
@ -507,7 +507,7 @@ def bitcoind_create_watch_only_wallet(pick_menu_item, need_keypress, microsd_pat
|
||||
|
||||
@pytest.fixture
|
||||
def policy_sign(start_sign, end_sign, cap_story, get_last_violation):
|
||||
def doit(wallet, psbt, violation=None, num_warn=1, warn_list=None):
|
||||
def doit(wallet, psbt, violation=None, num_warn=1, warn_list=None, ccc_disabled=False):
|
||||
start_sign(base64.b64decode(psbt))
|
||||
time.sleep(.1)
|
||||
title, story = cap_story()
|
||||
@ -525,20 +525,23 @@ def policy_sign(start_sign, end_sign, cap_story, get_last_violation):
|
||||
signed = end_sign(accept=True)
|
||||
po = BasicPSBT().parse(signed)
|
||||
|
||||
tx_hex = None
|
||||
if violation is None:
|
||||
assert not get_last_violation()
|
||||
if ccc_disabled:
|
||||
assert len(po.inputs[0].part_sigs) == 1 # only A signed
|
||||
else:
|
||||
assert not get_last_violation()
|
||||
|
||||
assert len(po.inputs[0].part_sigs) == 2 # CC key signed
|
||||
res = wallet.finalizepsbt(base64.b64encode(signed).decode())
|
||||
assert res["complete"]
|
||||
tx_hex = res["hex"]
|
||||
res = wallet.testmempoolaccept([tx_hex])
|
||||
assert res[0]["allowed"]
|
||||
res = wallet.sendrawtransaction(tx_hex)
|
||||
assert len(res) == 64 # tx id
|
||||
assert len(po.inputs[0].part_sigs) == 2 # CC key signed
|
||||
res = wallet.finalizepsbt(base64.b64encode(signed).decode())
|
||||
assert res["complete"]
|
||||
tx_hex = res["hex"]
|
||||
res = wallet.testmempoolaccept([tx_hex])
|
||||
assert res[0]["allowed"]
|
||||
res = wallet.sendrawtransaction(tx_hex)
|
||||
assert len(res) == 64 # tx id
|
||||
else:
|
||||
assert len(po.inputs[0].part_sigs) == 1 # CC key did NOT sign
|
||||
tx_hex = None
|
||||
|
||||
return signed, tx_hex
|
||||
|
||||
@ -1028,7 +1031,7 @@ def test_multiple_multisig_wallets(settings_set, setup_ccc, enter_enabled_ccc, c
|
||||
|
||||
# export one of the wallets
|
||||
w_mn, w_name = ami.rsplit(" ", 1)
|
||||
new_name = "new name"
|
||||
new_name = "new"
|
||||
pick_menu_item(ami) # just another ms wallet
|
||||
pick_menu_item("Coldcard Export")
|
||||
ms_conf = load_export("sd", label="Coldcard multisig setup", is_json=False, sig_check=False)
|
||||
@ -1049,6 +1052,48 @@ def test_multiple_multisig_wallets(settings_set, setup_ccc, enter_enabled_ccc, c
|
||||
assert f"{w_mn} {new_name}" in m
|
||||
|
||||
|
||||
def test_remove_ccc(settings_set, setup_ccc, enter_enabled_ccc, ccc_ms_setup, settings_get,
|
||||
pick_menu_item, cap_story, press_select, need_keypress, policy_sign,
|
||||
bitcoind_create_watch_only_wallet, bitcoind):
|
||||
settings_set("ccc", None)
|
||||
settings_set("multisig", [])
|
||||
|
||||
words = setup_ccc(c_words=None, mag=2, vel='6 blocks (hour)')
|
||||
enter_enabled_ccc(words, first_time=True)
|
||||
_, mi = ccc_ms_setup(N=3)
|
||||
|
||||
w0 = bitcoind_create_watch_only_wallet(mi)
|
||||
|
||||
ccc_ms_setup(N=5)
|
||||
|
||||
assert len(settings_get("multisig")) == 2
|
||||
|
||||
pick_menu_item("Remove CCC") # start remove
|
||||
time.sleep(.1)
|
||||
title, story = cap_story()
|
||||
assert "Key C will be lost, and policy settings forgotten" in story
|
||||
assert "unit will only be able to partly sign transactions" in story
|
||||
assert "proceed to the multisig menu and remove related wallet entries" in story
|
||||
press_select()
|
||||
time.sleep(.1)
|
||||
title, story = cap_story()
|
||||
assert "Press (4)" in story
|
||||
assert "accept all consequences" in story
|
||||
assert "Funds in related wallet/s may be impacted" in story
|
||||
need_keypress("4")
|
||||
|
||||
# multisig wallets are not impacted by removal of ccc
|
||||
assert len(settings_get("multisig")) == 2
|
||||
|
||||
bitcoind.supply_wallet.sendtoaddress(address=w0.getnewaddress(), amount=5)
|
||||
bitcoind.supply_wallet.generatetoaddress(1, bitcoind.supply_wallet.getnewaddress())
|
||||
psbt = w0.walletcreatefundedpsbt([], [{bitcoind.supply_wallet.getnewaddress(): 4}],
|
||||
bitcoind.supply_wallet.getblockchaininfo()["blocks"])["psbt"]
|
||||
# below should be magnitude violation, BUT we removed CCC
|
||||
policy_sign(w0, psbt, ccc_disabled=True)
|
||||
|
||||
|
||||
|
||||
# TODO
|
||||
# - policy-fail reason submenu; check display
|
||||
|
||||
|
||||
@ -98,12 +98,12 @@ def get_seed_value_ux(goto_home, pick_menu_item, need_keypress, cap_story,
|
||||
pick_menu_item("Danger Zone")
|
||||
pick_menu_item("Seed Functions")
|
||||
pick_menu_item('View Seed Words')
|
||||
time.sleep(.01)
|
||||
time.sleep(.1)
|
||||
title, body = cap_story()
|
||||
assert ('Are you SURE' in body) or ('Are you SURE' in title)
|
||||
assert 'can control all funds' in body
|
||||
press_select() # skip warning
|
||||
time.sleep(0.01)
|
||||
time.sleep(0.1)
|
||||
title, story = cap_story()
|
||||
|
||||
if nfc:
|
||||
@ -149,7 +149,7 @@ def get_identity_story(goto_home, pick_menu_item, cap_story):
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def goto_eph_seed_menu(goto_home, pick_menu_item, cap_story, need_keypress):
|
||||
def goto_eph_seed_menu(goto_home, pick_menu_item, cap_story, need_keypress, is_q1):
|
||||
def _doit():
|
||||
goto_home()
|
||||
pick_menu_item("Advanced/Tools")
|
||||
@ -397,6 +397,7 @@ def generate_ephemeral_words(goto_eph_seed_menu, pick_menu_item, press_select,
|
||||
assert len(e_seed_words) == num_words
|
||||
|
||||
need_keypress("6") # skip quiz
|
||||
time.sleep(.1)
|
||||
press_select() # yes - I'm sure
|
||||
confirm_tmp_seed(seedvault=seed_vault)
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user