firmware/testing/test_ephemeral.py

237 lines
7.5 KiB
Python

# (c) Copyright 2022 by Coinkite Inc. This file is covered by license found in COPYING-CC.
#
# Ephemeral Seeds tests
#
import pytest, time, re
from constants import simulator_fixed_xpub
from ckcc.protocol import CCProtocolPacker
from txn import fake_txn
from test_ux import word_menu_entry
def truncate_seed_words(words):
if isinstance(words, str):
words = words.split(" ")
return ' '.join(w[0:4] for w in words)
def seed_story_to_words(story: str):
# filter those that starts with space, number and colon --> actual words
words = [
line.strip().split(":")[1].strip()
for line in story.split("\n")
if re.search(r"\s\d:", line) or re.search(r"\d{2}:", line)
]
return words
@pytest.fixture
def get_seed_value_ux(goto_home, pick_menu_item, need_keypress, cap_story, nfc_read_text):
def doit(nfc=False):
goto_home()
pick_menu_item("Advanced/Tools")
pick_menu_item("Danger Zone")
pick_menu_item("Seed Functions")
pick_menu_item('View Seed Words')
time.sleep(.01)
title, body = cap_story()
assert 'Are you SURE' in body
assert 'can control all funds' in body
need_keypress('y') # skip warning
time.sleep(0.01)
title, story = cap_story()
if nfc:
need_keypress("1") # show QR code
time.sleep(.1)
need_keypress("3") # any QR can be exported via NFC
time.sleep(.1)
str_words = nfc_read_text()
time.sleep(.1)
need_keypress("y") # exit NFC animation
return str_words.split(" ") # always truncated
words = seed_story_to_words(story)
return words
return doit
@pytest.fixture
def get_identity_story(goto_home, pick_menu_item, cap_story):
def doit():
goto_home()
pick_menu_item("Advanced/Tools")
pick_menu_item("View Identity")
time.sleep(0.1)
title, story = cap_story()
return story
return doit
@pytest.fixture
def goto_eph_seed_menu(goto_home, pick_menu_item, cap_story, need_keypress):
def doit():
goto_home()
pick_menu_item("Advanced/Tools")
pick_menu_item("Ephemeral Seed")
title, story = cap_story()
if title == "WARNING":
assert "temporary secret stored solely in device RAM" in story
assert "Press (4) to prove you read to the end of this message and accept all consequences." in story
need_keypress("4") # understand consequences
return doit
@pytest.mark.parametrize("num_words", [12, 24])
@pytest.mark.parametrize("dice", [False, True])
def test_ephemeral_seed_generate(num_words, cap_menu, pick_menu_item, goto_home, cap_story, need_keypress,
reset_seed_words, get_seed_value_ux, get_identity_story, fake_txn, dev, try_sign, goto_eph_seed_menu, dice):
reset_seed_words()
try:
goto_eph_seed_menu()
except:
time.sleep(.1)
goto_eph_seed_menu()
menu = cap_menu()
# no ephemeral seed chosen (yet)
assert len(menu) == 2
pick_menu_item("Generate Seed")
if not dice:
pick_menu_item(f"{num_words} Words")
time.sleep(0.1)
else:
pick_menu_item(f"{num_words} Word Dice Roll")
for ch in '123456yy':
need_keypress(ch)
title, story = cap_story()
assert f"Record these {num_words} secret words!" in story
assert "Press (6) to skip word quiz" in story
# filter those that starts with space, number and colon --> actual words
e_seed_words = seed_story_to_words(story)
assert len(e_seed_words) == num_words
need_keypress("6") # skip quiz
need_keypress("y") # yes - I'm sure
time.sleep(0.1)
need_keypress("4") # understand consequences
time.sleep(0.1)
title, story = cap_story()
in_effect_xfp = story[1:9]
assert "key in effect until next power down." in story
need_keypress("y") # just confirm new master key message
menu = cap_menu()
assert menu[0] == "Ready To Sign" # returned to main menu
seed_words = get_seed_value_ux()
assert e_seed_words == seed_words
ident_story = get_identity_story()
assert "Ephemeral seed is in effect" in ident_story
ident_xfp = ident_story.split("\n\n")[1].strip()
assert ident_xfp == in_effect_xfp
e_master_xpub = dev.send_recv(CCProtocolPacker.get_xpub(), timeout=5000)
assert e_master_xpub != simulator_fixed_xpub
psbt = fake_txn(3, 3, master_xpub=e_master_xpub, segwit_in=True)
try_sign(psbt, accept=True, finalize=True) # MUST NOT raise
goto_home()
pick_menu_item("Advanced/Tools")
pick_menu_item("Ephemeral Seed")
menu = cap_menu()
# ephemeral seed chosen -> [xfp] will be visible
assert len(menu) == 3
assert menu[0] == f"[{ident_xfp}]"
reset_seed_words()
goto_eph_seed_menu()
menu = cap_menu()
assert len(menu) == 2
@pytest.mark.parametrize("num_words", [12, 18, 24])
@pytest.mark.parametrize("nfc", [False, True])
@pytest.mark.parametrize("truncated", [False, True])
def test_ephemeral_seed_import(nfc, num_words, cap_menu, pick_menu_item, goto_home, cap_story,
need_keypress, reset_seed_words, get_seed_value_ux, get_identity_story, fake_txn,
dev, try_sign, goto_eph_seed_menu, word_menu_entry, nfc_write_text, truncated
):
if truncated and not nfc: return
wordlists = {
12: ( 'abandon ' * 11 + 'about', 0x0adac573),
18: ( 'abandon ' * 17 + 'agent', 0xc38a8be0),
24: ( 'abandon ' * 23 + 'art', 0x24d73654 ),
}
words, expect_xfp = wordlists[num_words]
reset_seed_words()
try:
goto_eph_seed_menu()
except:
time.sleep(.1)
goto_eph_seed_menu()
menu = cap_menu()
# no ephemeral seed chosen (yet)
assert len(menu) == 2
pick_menu_item("Import Seed")
if not nfc:
pick_menu_item(f"{num_words} Words")
time.sleep(0.1)
word_menu_entry(words.split())
else:
menu = cap_menu()
if 'Import via NFC' not in menu:
raise pytest.xfail("NFC not enabled")
pick_menu_item('Import via NFC')
if truncated:
truncated_words = truncate_seed_words(words)
nfc_write_text(truncated_words)
else:
nfc_write_text(words)
need_keypress("4") # understand consequences
time.sleep(0.4)
title, story = cap_story()
in_effect_xfp = story[1:9]
assert "key in effect until next power down." in story
need_keypress("y") # just confirm new master key message
menu = cap_menu()
assert menu[0] == "Ready To Sign" # returned to main menu
seed_words = get_seed_value_ux()
assert words == " ".join(seed_words)
ident_story = get_identity_story()
assert "Ephemeral seed is in effect" in ident_story
ident_xfp = ident_story.split("\n\n")[1].strip()
assert ident_xfp == in_effect_xfp
e_master_xpub = dev.send_recv(CCProtocolPacker.get_xpub(), timeout=5000)
assert e_master_xpub != simulator_fixed_xpub
psbt = fake_txn(2, 2, master_xpub=e_master_xpub, segwit_in=True)
try_sign(psbt, accept=True, finalize=True) # MUST NOT raise
goto_home()
pick_menu_item("Advanced/Tools")
pick_menu_item("Ephemeral Seed")
menu = cap_menu()
# ephemeral seed chosen -> [xfp] will be visible
assert len(menu) == 3
assert menu[0] == f"[{ident_xfp}]"
nfc_seed = get_seed_value_ux(nfc=True) # export seed via NFC (always truncated)
assert " ".join(nfc_seed) == truncate_seed_words(seed_words)
# EOF