remove ccc confirm with (4) key; remove ccc tests

This commit is contained in:
scgbckbone 2024-10-24 18:09:36 +02:00
parent e14fb64904
commit 8bf4731cf5
8 changed files with 105 additions and 56 deletions

View File

@ -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

View File

@ -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

View File

@ -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()

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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)