240 lines
7.6 KiB
Python
240 lines
7.6 KiB
Python
# (c) Copyright 2020 by Coinkite Inc. This file is covered by license found in COPYING-CC.
|
|
#
|
|
# test Seed XOR features
|
|
#
|
|
import time
|
|
import pytest
|
|
from mnemonic import Mnemonic
|
|
from test_ux import word_menu_entry, pass_word_quiz
|
|
|
|
wordlist = Mnemonic('english').wordlist
|
|
|
|
zero32 = ' '.join('abandon' for _ in range(23)) + ' art'
|
|
ones32 = ' '.join('zoo' for _ in range(23)) + ' vote'
|
|
|
|
@pytest.mark.parametrize('incl_self', [False, True])
|
|
@pytest.mark.parametrize('parts, expect', [
|
|
( [ 'romance wink lottery autumn shop bring dawn tongue range crater truth ability miss spice fitness easy legal release recall obey exchange recycle dragon room',
|
|
'lion misery divide hurry latin fluid camp advance illegal lab pyramid unaware eager fringe sick camera series noodle toy crowd jeans select depth lounge',
|
|
'vault nominee cradle silk own frown throw leg cactus recall talent worry gadget surface shy planet purpose coffee drip few seven term squeeze educate',],
|
|
'silent toe meat possible chair blossom wait occur this worth option bag nurse find fish scene bench asthma bike wage world quit primary indoor'),
|
|
( [zero32]*2, zero32),
|
|
( [ones32]*7, ones32),
|
|
( [ones32]*4, zero32),
|
|
])
|
|
def test_import_xor(incl_self, parts, expect, goto_home, pick_menu_item, cap_story, need_keypress, cap_menu, word_menu_entry, get_secrets, reset_seed_words, set_seed_words):
|
|
|
|
# values from docs/seed-xor.md, and some easy cases
|
|
|
|
if incl_self:
|
|
set_seed_words(parts[0])
|
|
|
|
goto_home()
|
|
pick_menu_item('Advanced/Tools')
|
|
pick_menu_item('Danger Zone')
|
|
pick_menu_item('Seed Functions')
|
|
pick_menu_item('Seed XOR')
|
|
pick_menu_item('Restore Seed XOR')
|
|
time.sleep(.01)
|
|
title, body = cap_story()
|
|
|
|
assert 'all the parts' in body
|
|
need_keypress('y')
|
|
time.sleep(0.01)
|
|
|
|
title, body = cap_story()
|
|
assert 'you have a seed already' in body
|
|
assert '(1) to include this Coldcard' in body
|
|
if incl_self:
|
|
need_keypress('1')
|
|
else:
|
|
need_keypress('y')
|
|
|
|
#time.sleep(0.01)
|
|
|
|
for n, part in enumerate(parts):
|
|
if n == 0 and incl_self:
|
|
continue
|
|
|
|
word_menu_entry(part.split())
|
|
|
|
time.sleep(0.01)
|
|
title, body = cap_story()
|
|
assert f"You've entered {n} parts so far"
|
|
assert "or (2) if done"
|
|
|
|
if n != len(parts)-1:
|
|
need_keypress('1')
|
|
else:
|
|
# correct anticipated checksum word
|
|
chk_word = expect.split()[-1]
|
|
assert f"24: {chk_word}" in body
|
|
if expect == zero32:
|
|
assert 'ZERO WARNING' in body
|
|
|
|
need_keypress('2')
|
|
|
|
time.sleep(0.01)
|
|
title, body = cap_story()
|
|
assert 'New master key in effect' in body
|
|
|
|
assert get_secrets()['mnemonic'] == expect
|
|
reset_seed_words()
|
|
|
|
@pytest.mark.parametrize('qty', [2, 3, 4])
|
|
@pytest.mark.parametrize('trng', [False, True])
|
|
def test_xor_split(qty, trng, goto_home, pick_menu_item, cap_story, need_keypress, cap_menu, word_menu_entry, get_secrets, pass_word_quiz):
|
|
|
|
goto_home()
|
|
pick_menu_item('Advanced/Tools')
|
|
pick_menu_item('Danger Zone')
|
|
pick_menu_item('Seed Functions')
|
|
pick_menu_item('Seed XOR')
|
|
pick_menu_item('Split Existing')
|
|
time.sleep(.01)
|
|
title, body = cap_story()
|
|
|
|
assert 'Seed XOR Split' in body
|
|
assert 'ANY ONE' in body
|
|
assert 'ALL FUNDS' in body
|
|
assert str(qty) in body
|
|
need_keypress(str(qty))
|
|
|
|
time.sleep(.01)
|
|
title, body = cap_story()
|
|
assert f"Split Into {qty} Parts" in body
|
|
assert f"{qty*24} words" in body
|
|
|
|
need_keypress('2' if trng else 'y')
|
|
time.sleep(.01)
|
|
title, body = cap_story()
|
|
|
|
assert f'Record these {qty} lists of 24-words' in body
|
|
assert all((f'Part {chr(n+65)}:' in body) for n in range(qty))
|
|
|
|
words = [ln[4:] for ln in body.split('\n') if ln[2:4] == ': ']
|
|
assert len(words) == (24 * qty)+1
|
|
|
|
chk_word = words[-1]
|
|
parts = [words[pos:pos+24] for pos in range(0, 24*qty, 24)]
|
|
|
|
expect = get_secrets()['mnemonic'].split()
|
|
assert expect[-1] == chk_word
|
|
|
|
for part in parts[1:]:
|
|
assert part != parts[0]
|
|
assert all(parts[0][n] != part[n] for n in range(24))
|
|
|
|
x = [0]*24
|
|
for part in parts:
|
|
for n, word in enumerate(part):
|
|
x[n] ^= wordlist.index(word)
|
|
|
|
assert len(set(x)) > 4
|
|
|
|
#x[-1] &= 0x700
|
|
got = [wordlist[i] for i in x[:-1]]
|
|
assert len(got) == 23
|
|
assert got == expect[0:-1]
|
|
|
|
count, title, body = pass_word_quiz(parts[0], prefix='A')
|
|
assert count == 24
|
|
for n, part in enumerate(parts[1:]):
|
|
count, title, body = pass_word_quiz(part, prefix=chr(65+n+1), preload=(title, body))
|
|
assert count == 24
|
|
|
|
assert 'Quiz Passed' in body
|
|
|
|
def test_import_zero_set(goto_home, pick_menu_item, cap_story, need_keypress, cap_menu, word_menu_entry, get_secrets, reset_seed_words, set_seed_words):
|
|
|
|
# look for a warning
|
|
goto_home()
|
|
pick_menu_item('Advanced/Tools')
|
|
pick_menu_item('Danger Zone')
|
|
pick_menu_item('Seed Functions')
|
|
pick_menu_item('Seed XOR')
|
|
pick_menu_item('Restore Seed XOR')
|
|
time.sleep(.01)
|
|
title, body = cap_story()
|
|
|
|
assert 'all the parts' in body
|
|
need_keypress('y')
|
|
time.sleep(0.01)
|
|
|
|
title, body = cap_story()
|
|
assert 'you have a seed already' in body
|
|
assert '(1) to include this Coldcard' in body
|
|
need_keypress('y')
|
|
|
|
#time.sleep(0.01)
|
|
|
|
for n in range(2):
|
|
word_menu_entry(ones32.split())
|
|
|
|
time.sleep(0.01)
|
|
title, body = cap_story()
|
|
assert f"You've entered {n} parts so far"
|
|
assert "or (2) if done"
|
|
|
|
if n == 1:
|
|
assert 'ZERO WARNING' in body
|
|
return
|
|
|
|
need_keypress('1')
|
|
|
|
raise pytest.fail("reached")
|
|
|
|
@pytest.mark.parametrize('parts, expect', [
|
|
( [ 'romance wink lottery autumn shop bring dawn tongue range crater truth ability miss spice fitness easy legal release recall obey exchange recycle dragon room',
|
|
'lion misery divide hurry latin fluid camp advance illegal lab pyramid unaware eager fringe sick camera series noodle toy crowd jeans select depth lounge',
|
|
'vault nominee cradle silk own frown throw leg cactus recall talent worry gadget surface shy planet purpose coffee drip few seven term squeeze educate',],
|
|
'silent toe meat possible chair blossom wait occur this worth option bag nurse find fish scene bench asthma bike wage world quit primary indoor'),
|
|
])
|
|
def test_xor_import_empty(parts, expect, goto_home, pick_menu_item, cap_story, need_keypress, cap_menu, word_menu_entry, get_secrets, reset_seed_words, unit_test, expect_ftux):
|
|
|
|
# test import when wallet empty
|
|
unit_test('devtest/clear_seed.py')
|
|
|
|
m = cap_menu()
|
|
assert m[0] == 'New Seed Words'
|
|
pick_menu_item('Import Existing')
|
|
pick_menu_item('Seed XOR')
|
|
|
|
time.sleep(0.01)
|
|
title, body = cap_story()
|
|
assert 'all the parts' in body
|
|
need_keypress('y')
|
|
time.sleep(0.01)
|
|
|
|
for n, part in enumerate(parts):
|
|
word_menu_entry(part.split())
|
|
|
|
time.sleep(0.01)
|
|
title, body = cap_story()
|
|
assert f"You've entered {n} parts so far"
|
|
assert "or (2) if done"
|
|
|
|
if n != len(parts)-1:
|
|
assert 'ZERO WARNING' not in body
|
|
need_keypress('1')
|
|
else:
|
|
# correct anticipated checksum word
|
|
chk_word = expect.split()[-1]
|
|
assert f"24: {chk_word}" in body
|
|
if expect == zero32:
|
|
assert 'ZERO WARNING' in body
|
|
|
|
# install seed ... causes reset on real device
|
|
need_keypress('2')
|
|
|
|
time.sleep(0.01)
|
|
|
|
# main menu should be "ready to sign" now
|
|
expect_ftux()
|
|
|
|
assert get_secrets()['mnemonic'] == expect
|
|
reset_seed_words()
|
|
|
|
|
|
# EOF
|