1662 lines
64 KiB
Python
1662 lines
64 KiB
Python
import sys
|
|
sys.path.append("../shared")
|
|
import pytest, time, pdb, os, random, hashlib, base64
|
|
from constants import simulator_fixed_tprv
|
|
from charcodes import KEY_NFC
|
|
from bsms import CoordinatorSession, Signer
|
|
from bsms.encryption import key_derivation_function, decrypt, encrypt
|
|
from bsms.util import bitcoin_msg, str2path
|
|
from bsms.bip32 import PrvKeyNode, PubKeyNode
|
|
from bsms.ecdsa import ecdsa_verify, ecdsa_recover
|
|
from bsms.address import p2wsh_address, p2sh_p2wsh_address
|
|
from descriptor import MultisigDescriptor, append_checksum
|
|
from msg import sign_message
|
|
from bip32 import BIP32Node
|
|
|
|
|
|
BSMS_VERSION = "BSMS 1.0"
|
|
ALLOWED_PATH_RESTRICTIONS = "/0/*,/1/*"
|
|
|
|
|
|
# keys in settings object
|
|
BSMS_SETTINGS = "bsms"
|
|
BSMS_SIGNER_SETTINGS = "s"
|
|
BSMS_COORD_SETTINGS = "c"
|
|
|
|
|
|
et_map = {
|
|
"1": "STANDARD",
|
|
"2": "EXTENDED",
|
|
"3": "NO_ENCRYPTION"
|
|
}
|
|
|
|
af_map = {
|
|
"p2wsh": 14,
|
|
"p2sh-p2wsh": 26
|
|
}
|
|
|
|
|
|
def coordinator_label(M, N, addr_fmt, et, index=None):
|
|
fmt_str = "%dof%d_%s_%s" % (M, N, "native" if addr_fmt == "p2wsh" else "nested", et)
|
|
if index:
|
|
fmt_str = "%d %s" % (index, fmt_str)
|
|
return fmt_str
|
|
|
|
|
|
def assert_coord_summary(title, story, M, N, addr_fmt, et):
|
|
assert title == "SUMMARY"
|
|
assert f"{M} of {N}" in story
|
|
assert f"Address format:\n{addr_fmt}" in story
|
|
assert f"Encryption type:\n{et_map[et].replace('_', ' ')}" in story
|
|
tokens = story.split("\n\n")[3:-1]
|
|
if et == "1":
|
|
assert len(tokens) == 1
|
|
elif et == "2":
|
|
assert len(tokens) == N
|
|
else:
|
|
assert len(tokens) == 0
|
|
return tokens
|
|
|
|
@pytest.fixture
|
|
def make_coordinator_round1(settings_remove, settings_get, settings_set, microsd_path, virtdisk_path):
|
|
def doit(M, N, addr_fmt, et, way, purge_bsms=True, tokens_only=False):
|
|
if purge_bsms:
|
|
settings_remove(BSMS_SETTINGS) # clear bsms
|
|
bsms = settings_get(BSMS_SETTINGS) or {}
|
|
tokens = []
|
|
if et == "1":
|
|
tokens = [os.urandom(8).hex()]
|
|
elif et == "2":
|
|
tokens = [os.urandom(16).hex() for _ in range(N)]
|
|
coord_tuple = (M, N, af_map[addr_fmt], et, tokens)
|
|
if BSMS_COORD_SETTINGS in bsms:
|
|
bsms[BSMS_COORD_SETTINGS].append(coord_tuple)
|
|
else:
|
|
bsms[BSMS_COORD_SETTINGS] = [coord_tuple]
|
|
settings_set(BSMS_SETTINGS, bsms)
|
|
if tokens_only:
|
|
return tokens
|
|
if way == "sd":
|
|
path_fn = microsd_path
|
|
elif way == "vdisk":
|
|
path_fn = virtdisk_path
|
|
else:
|
|
return tokens
|
|
for token_hex in tokens:
|
|
basename = "bsms_%s.token" % token_hex[:4]
|
|
with open(path_fn(basename), "w") as f:
|
|
f.write(token_hex)
|
|
return tokens
|
|
return doit
|
|
|
|
|
|
def bsms_sr1_fname(token, is_extended, suffix, index=None):
|
|
fname = "bsms_sr1"
|
|
if is_extended:
|
|
fname += "_" + token[:4]
|
|
else:
|
|
if index: # ignores index = 0
|
|
fname += "-" + str(index)
|
|
return fname + suffix
|
|
|
|
|
|
@pytest.fixture
|
|
def make_signer_round1(settings_get, settings_set, settings_remove, microsd_path, virtdisk_path):
|
|
def doit(token, way, root_xprv=None, bsms_version=BSMS_VERSION, description=None, purge_bsms=True,
|
|
add_to_settings=False, data_only=False, index=None, wrong_sig=False, wrong_encryption=False):
|
|
is_extended = len(token) == 32
|
|
if purge_bsms:
|
|
settings_remove(BSMS_SETTINGS) # clear bsms
|
|
if add_to_settings:
|
|
bsms = settings_get(BSMS_SETTINGS) or {}
|
|
if BSMS_SIGNER_SETTINGS in bsms:
|
|
bsms[BSMS_COORD_SETTINGS].append(token)
|
|
else:
|
|
bsms[BSMS_SIGNER_SETTINGS] = [token]
|
|
|
|
if root_xprv:
|
|
wk = BIP32Node.from_wallet_key(root_xprv)
|
|
else:
|
|
wk = BIP32Node.from_master_secret(os.urandom(32), netcode="XTN")
|
|
root_xfp = wk.fingerprint().hex()
|
|
paths = ["48'/1'/0'/2'", "48'/1'/0'/1'", "0'/1'/0'/0'", "0'", "100'/0'"]
|
|
path = random.choice(paths)
|
|
sk = wk.subkey_for_path(path)
|
|
xpub = sk.hwif(as_private=False)
|
|
key_expr = "[%s/%s]%s" % (root_xfp, path, xpub)
|
|
data = "%s\n" % bsms_version
|
|
data += "%s\n" % token
|
|
data += "%s\n" % key_expr
|
|
if description is None:
|
|
description = "Coldcard Signer %s" % root_xfp
|
|
data += "%s" % description
|
|
sig = sign_message(bytes(sk.node.private_key),
|
|
data.encode()+b"ff" if wrong_sig else data.encode(),
|
|
b64=True)
|
|
data += "\n%s" % sig
|
|
suffix = ".txt"
|
|
mode = "wt"
|
|
if token != "00":
|
|
suffix = ".dat"
|
|
mode = "wb"
|
|
dkey = key_derivation_function(token)
|
|
if wrong_encryption:
|
|
wrong = "ffff" + token[4:]
|
|
dkey = key_derivation_function(wrong)
|
|
data = encrypt(dkey, token, data)
|
|
data = bytes.fromhex(data)
|
|
if data_only:
|
|
return data
|
|
if way != "nfc":
|
|
if way == "sd":
|
|
path_fn = microsd_path
|
|
else:
|
|
# vdisk
|
|
path_fn = virtdisk_path
|
|
basename = bsms_sr1_fname(token, is_extended, suffix, index)
|
|
with open(path_fn(basename), mode) as f:
|
|
f.write(data)
|
|
return data
|
|
|
|
return doit
|
|
|
|
|
|
def ms_address_from_descriptor_bsms(desc_obj: MultisigDescriptor, subpath="0/0", network="XTN"):
|
|
testnet = True if network == "XTN" else False
|
|
nodes = [
|
|
PubKeyNode.parse(ek).derive_path(str2path(subpath))
|
|
for _, _, ek in desc_obj.keys
|
|
]
|
|
secs = [node.sec() for node in nodes]
|
|
secs.sort()
|
|
if desc_obj.addr_fmt == af_map["p2wsh"]:
|
|
address = p2wsh_address(secs, desc_obj.M, sortedmulti=True, testnet=testnet)
|
|
else:
|
|
address = p2sh_p2wsh_address(secs, desc_obj.M, sortedmulti=True, testnet=testnet)
|
|
return address
|
|
|
|
|
|
def bsms_cr2_fname(token, is_extended, suffix):
|
|
fname = "bsms_cr2"
|
|
if is_extended:
|
|
fname += "_" + token[:4]
|
|
return fname + suffix
|
|
|
|
|
|
@pytest.fixture
|
|
def make_coordinator_round2(make_coordinator_round1, settings_get, settings_set, microsd_path, virtdisk_path):
|
|
def doit(M, N, addr_fmt, et, way, has_ours=True, ours_no=1, path_restrictions=ALLOWED_PATH_RESTRICTIONS,
|
|
bsms_version=BSMS_VERSION, sortedmulti=True, wrong_address=False, wrong_encryption=False,
|
|
wrong_chain=False, add_checksum=False, wrong_checksum=False):
|
|
tokens = make_coordinator_round1(M, N, addr_fmt, et, way=way, purge_bsms=True, tokens_only=True)
|
|
range_num = N if has_ours is False else N - ours_no
|
|
keys = []
|
|
for _ in range(range_num):
|
|
wk = BIP32Node.from_master_secret(os.urandom(32), netcode="BTC" if wrong_chain else "XTN")
|
|
root_xfp = wk.fingerprint().hex()
|
|
paths = ["48'/1'/0'/2'", "48'/1'/0'/1'", "0'/1'/0'/0'", "0'", "100'/0'"]
|
|
path = random.choice(paths)
|
|
sk = wk.subkey_for_path(path)
|
|
xpub = sk.hwif(as_private=False)
|
|
keys.append((root_xfp, "m/" + path, xpub))
|
|
if has_ours:
|
|
for _ in range(ours_no):
|
|
wk = BIP32Node.from_wallet_key(simulator_fixed_tprv)
|
|
root_xfp = wk.fingerprint().hex()
|
|
paths = ["48'/1'/0'/2'", "48'/1'/0'/1'", "0'/1'/0'/0'", "0'", "100'/0'"]
|
|
path = random.choice(paths)
|
|
sk = wk.subkey_for_path(path)
|
|
xpub = sk.hwif(as_private=False)
|
|
keys.append((root_xfp, "m/" + path, xpub))
|
|
|
|
desc_obj = MultisigDescriptor(M=M, N=N, addr_fmt=af_map[addr_fmt], keys=keys)
|
|
desc = desc_obj._serialize(int_ext=True)
|
|
wcs = append_checksum(desc).split("#")[-1]
|
|
desc = desc.replace("/<0;1>/*", "/**")
|
|
if add_checksum:
|
|
desc = append_checksum(desc)
|
|
elif wrong_checksum:
|
|
desc = desc + "#" + wcs
|
|
if not sortedmulti:
|
|
desc = desc.replace("sortedmulti", "multi")
|
|
desc_template = "%s\n" % bsms_version
|
|
desc_template += "%s\n" % desc
|
|
desc_template += "%s\n" % path_restrictions
|
|
if wrong_address:
|
|
addr = ms_address_from_descriptor_bsms(desc_obj, subpath="1000/100")
|
|
else:
|
|
addr = ms_address_from_descriptor_bsms(desc_obj)
|
|
desc_template += "%s" % addr
|
|
|
|
# create signer artificialy and produce correct descriptor template file
|
|
bsms = settings_get(BSMS_SETTINGS) or {}
|
|
bsms[BSMS_SIGNER_SETTINGS] = [] # purge
|
|
if not tokens:
|
|
token = "00"
|
|
bsms[BSMS_SIGNER_SETTINGS].append(token)
|
|
res = desc_template
|
|
else:
|
|
token = tokens[0]
|
|
# same for STANDARD and EXTENDED --> encrypt
|
|
bsms[BSMS_SIGNER_SETTINGS].append(token)
|
|
if wrong_encryption:
|
|
res = encrypt(key_derivation_function(os.urandom(16).hex()), token, desc_template)
|
|
else:
|
|
res = encrypt(key_derivation_function(token), token, desc_template)
|
|
res = bytes.fromhex(res)
|
|
|
|
settings_set(BSMS_SETTINGS, bsms)
|
|
if way != "nfc":
|
|
if way == "sd":
|
|
path_fn = microsd_path
|
|
else:
|
|
# vdisk
|
|
path_fn = virtdisk_path
|
|
mode = "wb" if et in ["1", "2"] else "wt"
|
|
suffix = ".dat" if et in ["1", "2"] else ".txt"
|
|
basename = bsms_cr2_fname(token, et == "2", suffix)
|
|
with open(path_fn(basename), mode) as f:
|
|
f.write(res)
|
|
|
|
return res, token
|
|
|
|
return doit
|
|
|
|
|
|
@pytest.mark.parametrize("way", ["sd", "nfc", "vdisk"])
|
|
@pytest.mark.parametrize("encryption_type", ["1", "2", "3"])
|
|
@pytest.mark.parametrize("M_N", [(2,2), (3, 5), (15, 15)])
|
|
@pytest.mark.parametrize("addr_fmt", ["p2wsh", "p2sh-p2wsh"])
|
|
def test_coordinator_round1(way, encryption_type, M_N, addr_fmt, clear_miniscript, goto_home, need_keypress,
|
|
pick_menu_item, cap_menu, cap_story, microsd_path, settings_remove,
|
|
nfc_read_text, request, settings_get, microsd_wipe, press_select,
|
|
is_q1, press_cancel):
|
|
if way == "vdisk":
|
|
virtdisk_wipe = request.getfixturevalue("virtdisk_wipe")
|
|
virtdisk_path = request.getfixturevalue("virtdisk_path")
|
|
virtdisk_wipe()
|
|
|
|
M, N = M_N
|
|
|
|
microsd_wipe()
|
|
settings_remove(BSMS_SETTINGS) # clear bsms
|
|
goto_home()
|
|
pick_menu_item('Settings')
|
|
pick_menu_item('Multisig/Miniscript')
|
|
pick_menu_item('BSMS (BIP-129)')
|
|
title, story = cap_story()
|
|
assert "Bitcoin Secure Multisig Setup (BIP-129) is a mechanism to securely create multisig wallets." in story
|
|
assert "WARNING: BSMS is an EXPERIMENTAL and BETA feature" in story
|
|
press_select()
|
|
pick_menu_item('Coordinator')
|
|
menu = cap_menu()
|
|
assert len(menu) == 1 # nothing should be in menu at this point but round 1
|
|
pick_menu_item('Create BSMS')
|
|
# choose number of signers N
|
|
for num in str(N):
|
|
need_keypress(num)
|
|
press_select()
|
|
# choose threshold M
|
|
for num in str(M):
|
|
need_keypress(num)
|
|
press_select()
|
|
if addr_fmt == "p2wsh":
|
|
press_select()
|
|
else:
|
|
need_keypress("2")
|
|
time.sleep(0.1)
|
|
title, story = cap_story()
|
|
assert story == "Choose encryption type. Press (1) for STANDARD encryption, (2) for EXTENDED, and (3) for no encryption"
|
|
need_keypress(encryption_type)
|
|
time.sleep(0.1)
|
|
title, story = cap_story()
|
|
tokens = assert_coord_summary(title, story, M, N, addr_fmt, encryption_type)
|
|
press_select() # confirm summary
|
|
time.sleep(0.1)
|
|
title, story = cap_story()
|
|
assert "Press (1) to participate as co-signer in this BSMS" in story
|
|
press_select() # continue normally
|
|
time.sleep(0.1)
|
|
title, story = cap_story()
|
|
if encryption_type == "3":
|
|
assert story == "Success. Coordinator round 1 saved."
|
|
else:
|
|
if way == "sd":
|
|
if "Press (1) to save BSMS token file(s) to SD Card" in story:
|
|
need_keypress("1")
|
|
# else no prompt if both NFC and vdisk disabled
|
|
elif way == "nfc":
|
|
|
|
if f"press {KEY_NFC if is_q1 else '(3)'} to share via NFC" not in story:
|
|
pytest.skip("NFC disabled")
|
|
else:
|
|
need_keypress(KEY_NFC if is_q1 else "3")
|
|
time.sleep(0.2)
|
|
bsms_tokens = nfc_read_text()
|
|
time.sleep(0.2)
|
|
press_cancel() # exit NFC UI simulation
|
|
time.sleep(0.5)
|
|
else:
|
|
# virtual disk
|
|
if "press (2) to save to Virtual Disk" not in story:
|
|
pytest.skip("Vdisk disabled")
|
|
else:
|
|
need_keypress("2")
|
|
|
|
read_tokens = []
|
|
if way == "nfc" and encryption_type != "3":
|
|
read_tokens = bsms_tokens.split("\n\n")
|
|
else:
|
|
time.sleep(0.2)
|
|
_, story = cap_story()
|
|
assert 'BSMS token file(s) written' in story
|
|
fnames = story.split('\n\n')[2:]
|
|
# check token files contains first 4 chars of token
|
|
try:
|
|
token_start = set([tok.split(" ")[1][:4] for tok in tokens])
|
|
except IndexError:
|
|
# only one token - special case without numbering
|
|
assert len(tokens) == 1
|
|
token_start = set([tokens[0].split("\n")[1][:4]])
|
|
token_fnames_start = set([fn.replace(".token", "").split("_")[-1].split("-")[0] for fn in fnames])
|
|
assert token_start == token_fnames_start
|
|
read_tokens = []
|
|
for fname in fnames:
|
|
if way == "vdisk":
|
|
path = virtdisk_path(fname)
|
|
else:
|
|
path = microsd_path(fname)
|
|
with open(path, 'rt') as f:
|
|
token = f.read().strip()
|
|
read_tokens.append(token)
|
|
|
|
if encryption_type == "1":
|
|
assert len(read_tokens) == 1
|
|
elif encryption_type == "2":
|
|
assert len(read_tokens) == N
|
|
else:
|
|
assert len(tokens) == 0
|
|
|
|
press_select() # confirm success or files written story
|
|
time.sleep(0.1)
|
|
menu = cap_menu()
|
|
assert len(menu) == 2
|
|
current_coord_menu_item = coordinator_label(M, N, addr_fmt, encryption_type, index=1)
|
|
assert menu[0] == current_coord_menu_item
|
|
assert menu[1] == "Create BSMS"
|
|
# check correct summary in detail
|
|
pick_menu_item(menu[0])
|
|
time.sleep(0.1)
|
|
menu = cap_menu()
|
|
assert len(menu) == 3
|
|
assert menu[0] == "Round 2"
|
|
assert menu[1] == "Detail"
|
|
assert menu[2] == "Delete"
|
|
pick_menu_item("Detail")
|
|
time.sleep(0.1)
|
|
title, story = cap_story()
|
|
assert_coord_summary(title, story, M, N, addr_fmt, encryption_type)
|
|
press_select()
|
|
# check correct coord tuple saved
|
|
bsms_settings = settings_get(BSMS_SETTINGS)
|
|
if BSMS_SIGNER_SETTINGS in bsms_settings:
|
|
assert bsms_settings[BSMS_SIGNER_SETTINGS] == []
|
|
coord_settings = bsms_settings[BSMS_COORD_SETTINGS]
|
|
assert len(coord_settings) == 1
|
|
assert coord_settings[0] == (
|
|
M, N, af_map[addr_fmt], encryption_type,
|
|
[tok.split(" ")[-1].replace("Tokens:\n", "") for tok in tokens] if tokens else []
|
|
)
|
|
# delete coordinator settings
|
|
pick_menu_item("Delete")
|
|
time.sleep(0.1)
|
|
menu = cap_menu()
|
|
assert len(menu) == 1
|
|
assert menu[0] == "Create BSMS"
|
|
bsms_settings = settings_get(BSMS_SETTINGS)
|
|
coord_settings = bsms_settings[BSMS_COORD_SETTINGS]
|
|
assert coord_settings == []
|
|
|
|
|
|
@pytest.mark.parametrize("way", ["sd", "nfc", "vdisk"])
|
|
@pytest.mark.parametrize("encryption_type", ["1", "2", "3"])
|
|
@pytest.mark.parametrize("M_N", [(2,2), (3, 5), (15, 15)])
|
|
@pytest.mark.parametrize("addr_fmt", ["p2wsh", "p2sh-p2wsh"])
|
|
def test_signer_round1(way, encryption_type, M_N, addr_fmt, clear_miniscript, goto_home, need_keypress,
|
|
cap_story, microsd_path, settings_remove, nfc_read_text, request, settings_get,
|
|
make_coordinator_round1, nfc_write_text, microsd_wipe, press_select,
|
|
is_q1, pick_menu_item, cap_menu, press_cancel):
|
|
if way == "vdisk":
|
|
virtdisk_wipe = request.getfixturevalue("virtdisk_wipe")
|
|
virtdisk_path = request.getfixturevalue("virtdisk_path")
|
|
virtdisk_wipe()
|
|
|
|
M, N = M_N
|
|
microsd_wipe()
|
|
tokens = make_coordinator_round1(M, N, addr_fmt, encryption_type, way)
|
|
if encryption_type != "3":
|
|
assert tokens
|
|
else:
|
|
assert tokens == []
|
|
goto_home()
|
|
pick_menu_item('Settings')
|
|
pick_menu_item('Multisig/Miniscript')
|
|
pick_menu_item('BSMS (BIP-129)')
|
|
title, story = cap_story()
|
|
assert "Bitcoin Secure Multisig Setup (BIP-129) is a mechanism to securely create multisig wallets." in story
|
|
assert "WARNING: BSMS is an EXPERIMENTAL and BETA feature" in story
|
|
press_select()
|
|
pick_menu_item('Signer')
|
|
menu = cap_menu()
|
|
assert len(menu) == 1 # nothing should be in menu at this point but round 1
|
|
pick_menu_item('Round 1')
|
|
time.sleep(0.1)
|
|
title, story = cap_story()
|
|
if encryption_type == "3":
|
|
token = "00"
|
|
need_keypress("3") # no token (unencrypted BSMS)
|
|
else:
|
|
token = random.choice(tokens)
|
|
if way == "sd":
|
|
if "Press (1) to import token file from SD Card" in story:
|
|
need_keypress("1")
|
|
# else no prompt if both NFC and vdisk disabled
|
|
elif way == "nfc":
|
|
if f"{KEY_NFC if is_q1 else '(4)'} to import via NFC" not in story:
|
|
pytest.skip("NFC disabled")
|
|
else:
|
|
need_keypress(KEY_NFC if is_q1 else "4")
|
|
time.sleep(0.1)
|
|
nfc_write_text(token)
|
|
time.sleep(0.4)
|
|
else:
|
|
# virtual disk
|
|
if "(6) to import from Virtual Disk" not in story:
|
|
pytest.skip("Vdisk disabled")
|
|
else:
|
|
need_keypress("6")
|
|
|
|
if way != "nfc":
|
|
time.sleep(0.2)
|
|
fname = "bsms_%s.token" % token[:4]
|
|
pick_menu_item(fname)
|
|
|
|
time.sleep(0.1)
|
|
title, story = cap_story()
|
|
assert "You have entered token:\n%s" % token in story
|
|
press_select()
|
|
time.sleep(0.1)
|
|
_, story = cap_story()
|
|
# address format a.k.a. SLIP derivation path - ignore and use SLIP agnostic
|
|
assert "Choose co-signer address format for correct SLIP derivation path" in story
|
|
press_select() # default
|
|
# account number prompt
|
|
press_select()
|
|
time.sleep(0.1)
|
|
_, story = cap_story()
|
|
# textual key description
|
|
assert "Choose key description" in story
|
|
press_select() # default
|
|
time.sleep(0.1)
|
|
title, story = cap_story()
|
|
suffix = ".txt" if encryption_type == "3" else ".dat"
|
|
mode = "rt" if encryption_type == "3" else "rb"
|
|
if way == "sd":
|
|
if "Press (1) to save BSMS signer round 1 file to SD Card" in story:
|
|
need_keypress("1")
|
|
elif way == "nfc":
|
|
if f"press {KEY_NFC if is_q1 else '(3)'} to share via NFC" not in story:
|
|
pytest.skip("NFC disabled")
|
|
else:
|
|
need_keypress(KEY_NFC if is_q1 else "3")
|
|
time.sleep(0.2)
|
|
signer_r1 = nfc_read_text()
|
|
time.sleep(0.2)
|
|
press_cancel() # exit NFC UI simulation
|
|
time.sleep(0.5)
|
|
else:
|
|
# virtual disk
|
|
if "press (2) to save to Virtual Disk" not in story:
|
|
pytest.skip("Vdisk disabled")
|
|
else:
|
|
need_keypress("2")
|
|
|
|
if way != "nfc":
|
|
time.sleep(0.2)
|
|
_, story = cap_story()
|
|
assert 'BSMS signer round 1 file written' in story
|
|
fname = story.split('\n\n')[-1]
|
|
assert suffix in fname
|
|
if encryption_type == "2":
|
|
# check token files contains first 4 chars of token or just 00
|
|
assert token[:4] == fname.split(".")[0][-4:]
|
|
if way == "vdisk":
|
|
path = virtdisk_path(fname)
|
|
else:
|
|
path = microsd_path(fname)
|
|
with open(path, mode) as f:
|
|
signer_r1 = f.read()
|
|
|
|
bsms = settings_get(BSMS_SETTINGS)
|
|
assert len(bsms[BSMS_SIGNER_SETTINGS]) == 1
|
|
assert bsms[BSMS_SIGNER_SETTINGS][0] == token
|
|
|
|
if encryption_type in ["1", "2"]:
|
|
# decrypt
|
|
if isinstance(signer_r1, bytes):
|
|
signer_r1 = signer_r1.hex()
|
|
signer_r1 = decrypt(key_derivation_function(token), signer_r1)
|
|
|
|
version, tok, key_exp, description, sig = signer_r1.strip().split("\n")
|
|
assert version == BSMS_VERSION
|
|
assert tok == token
|
|
close_index = key_exp.find("]")
|
|
assert key_exp[0] == "[" and close_index != -1
|
|
key_orig_info = key_exp[1:close_index] # remove brackets
|
|
xpub = key_exp[close_index + 1:]
|
|
assert xpub[:4] in ["xpub", "tpub"]
|
|
xfp, path = key_orig_info.split("/", 1)
|
|
# pycoin xpub check
|
|
mk = BIP32Node.from_wallet_key(simulator_fixed_tprv)
|
|
sk = mk.subkey_for_path(path)
|
|
pycoin_xpub = sk.hwif(as_private=False)
|
|
assert xpub == pycoin_xpub
|
|
# bsms lib xpub check
|
|
mk0 = PrvKeyNode.parse(simulator_fixed_tprv, testnet=True)
|
|
sk0 = mk0.derive_path(str2path(path))
|
|
bsms_xpub = sk0.extended_public_key()
|
|
assert xpub == bsms_xpub
|
|
signed_data = "\n".join([version, tok, key_exp, description])
|
|
# verify msg bsms lib (pure python ecdsa)
|
|
signed_digest = bitcoin_msg(signed_data)
|
|
decoded_sig = base64.b64decode(sig)
|
|
recovered_sec = ecdsa_recover(signed_digest, decoded_sig)
|
|
assert ecdsa_verify(signed_digest, decoded_sig, recovered_sec), "Signature invalid"
|
|
|
|
|
|
@pytest.mark.parametrize("way", ["sd", "nfc", "vdisk"])
|
|
@pytest.mark.parametrize("encryption_type", ["1", "2", "3"])
|
|
@pytest.mark.parametrize("M_N", [(2,2), (3, 5), (15, 15)])
|
|
@pytest.mark.parametrize("addr_fmt", ["p2wsh", "p2sh-p2wsh"])
|
|
@pytest.mark.parametrize("auto_collect", [True, False])
|
|
def test_coordinator_round2(way, encryption_type, M_N, addr_fmt, auto_collect, clear_miniscript, goto_home,
|
|
cap_menu, cap_story, microsd_path, settings_remove, nfc_read_text, request,
|
|
settings_get, make_coordinator_round1, make_signer_round1, nfc_write_text,
|
|
microsd_wipe, pick_menu_item, press_select, is_q1, need_keypress, press_cancel):
|
|
def get_token(index):
|
|
if len(tokens) == 1 and encryption_type == "1":
|
|
token = tokens[0]
|
|
elif len(tokens) == N and encryption_type == "2":
|
|
token = tokens[index]
|
|
else:
|
|
token = "00"
|
|
return token
|
|
|
|
if way == "vdisk":
|
|
virtdisk_wipe = request.getfixturevalue("virtdisk_wipe")
|
|
virtdisk_path = request.getfixturevalue("virtdisk_path")
|
|
virtdisk_wipe()
|
|
|
|
M, N = M_N
|
|
microsd_wipe()
|
|
tokens = make_coordinator_round1(M, N, addr_fmt, encryption_type, way=way, tokens_only=True)
|
|
all_data = []
|
|
for i in range(N):
|
|
token = get_token(i)
|
|
index = None
|
|
if encryption_type != "2":
|
|
index = i + 1
|
|
|
|
all_data.append(make_signer_round1(token, way, purge_bsms=False, index=index))
|
|
|
|
goto_home()
|
|
pick_menu_item('Settings')
|
|
pick_menu_item('Multisig/Miniscript')
|
|
pick_menu_item('BSMS (BIP-129)')
|
|
title, story = cap_story()
|
|
assert "Bitcoin Secure Multisig Setup (BIP-129) is a mechanism to securely create multisig wallets." in story
|
|
assert "WARNING: BSMS is an EXPERIMENTAL and BETA feature" in story
|
|
press_select()
|
|
pick_menu_item('Coordinator')
|
|
menu = cap_menu()
|
|
assert len(menu) == 2
|
|
coord_menu_item = coordinator_label(M, N, addr_fmt, encryption_type, index=1)
|
|
assert coord_menu_item in menu
|
|
pick_menu_item(coord_menu_item)
|
|
pick_menu_item("Round 2")
|
|
time.sleep(0.1)
|
|
_, story = cap_story()
|
|
if way == "sd":
|
|
if "Press (1) to import co-signer round 1 files from SD Card" in story:
|
|
need_keypress("1")
|
|
# else no prompt if both NFC and vdisk disabled
|
|
elif way == "vdisk":
|
|
if "(2) to import from Virtual Disk" not in story:
|
|
pytest.skip("Vdisk disabled")
|
|
else:
|
|
need_keypress("2")
|
|
else:
|
|
# NFC
|
|
if f"{KEY_NFC if is_q1 else '(3)'} to import via NFC" not in story:
|
|
pytest.skip("NFC disabled")
|
|
else:
|
|
need_keypress(KEY_NFC if is_q1 else "3")
|
|
|
|
if way == "nfc":
|
|
if auto_collect is True:
|
|
pytest.skip("No auto-collection for NFC")
|
|
for i, data in enumerate(all_data):
|
|
time.sleep(0.1)
|
|
title, story = cap_story()
|
|
token = get_token(i)
|
|
if encryption_type == "2":
|
|
expect = "Share co-signer #%d round-1 data for token starting with %s" % (i + 1, token[:4])
|
|
else:
|
|
expect = "Share co-signer #%d round-1 data" % (i + 1)
|
|
assert expect in story
|
|
press_select()
|
|
time.sleep(.2)
|
|
nfc_write_text(data.hex() if isinstance(data, bytes) else data)
|
|
time.sleep(0.3)
|
|
else:
|
|
suffix = ".txt" if encryption_type == "3" else ".dat"
|
|
time.sleep(0.1)
|
|
title, story = cap_story()
|
|
assert "Press OK to pick co-signer round 1 files manually, or press (1) to attempt auto-collection." in story
|
|
assert "For auto-collection to succeed all filenames have to start with 'bsms_sr1'" in story
|
|
suffix_target = "and end with extension '%s'" % suffix
|
|
assert suffix_target in story
|
|
if encryption_type == "2":
|
|
assert "In addition for EXTENDED encryption all files must contain first four characters of respective token." in story
|
|
elif encryption_type == "3":
|
|
assert ("In addition for NO ENCRYPTION cases, number of files with above mentioned"
|
|
" pattern and suffix must equal number of signers (N).") in story
|
|
assert "If above is not respected auto-collection fails and defaults to manual selection of files." in story
|
|
if auto_collect:
|
|
need_keypress("1")
|
|
else:
|
|
press_select() # continue with manual selection
|
|
for i, _ in enumerate(all_data, start=1):
|
|
token = get_token(i - 1)
|
|
time.sleep(0.1)
|
|
title, story = cap_story()
|
|
if encryption_type == "2":
|
|
expect = 'Select co-signer #%d file containing round 1 data for token starting with %s' % (i, token[:4])
|
|
else:
|
|
expect = 'Select co-signer #%d file containing round 1 data' % i
|
|
expect += '. File extension has to be "%s"' % suffix
|
|
assert expect in story
|
|
press_select()
|
|
menu_item = bsms_sr1_fname(token, encryption_type == "2", suffix, i)
|
|
pick_menu_item(menu_item)
|
|
|
|
time.sleep(0.1)
|
|
_, story = cap_story()
|
|
if way == "sd":
|
|
if "Press (1) to save BSMS descriptor template file(s) to SD Card" in story:
|
|
need_keypress("1")
|
|
# else no prompt if both NFC and vdisk disabled
|
|
elif way == "nfc":
|
|
if f"{KEY_NFC if is_q1 else '(3)'} to share via NFC" not in story:
|
|
pytest.skip("NFC disabled")
|
|
else:
|
|
need_keypress(KEY_NFC if is_q1 else "3")
|
|
else:
|
|
# virtual disk
|
|
if "(2) to save to Virtual Disk" not in story:
|
|
pytest.skip("Vdisk disabled")
|
|
else:
|
|
need_keypress("2")
|
|
|
|
descriptor_templates = []
|
|
if way == "nfc":
|
|
# not implemented because of the fake nfc limit
|
|
# pytest skip will be raised before we can get here
|
|
if encryption_type == "2":
|
|
for i, token in enumerate(tokens, start=1):
|
|
time.sleep(.1)
|
|
title, story = cap_story()
|
|
expect = "Exporting data for co-signer #%d with token %s" % (i, token[:4])
|
|
assert expect in story
|
|
press_select()
|
|
time.sleep(.5)
|
|
rv = nfc_read_text()
|
|
time.sleep(.5)
|
|
descriptor_templates.append(rv)
|
|
press_cancel() # exit animation
|
|
|
|
time.sleep(.1)
|
|
title, story = cap_story()
|
|
assert "All done" in story
|
|
press_select()
|
|
else:
|
|
time.sleep(.5)
|
|
rv = nfc_read_text()
|
|
time.sleep(.5)
|
|
descriptor_templates.append(rv)
|
|
press_cancel() # exit animation
|
|
else:
|
|
if way == "sd":
|
|
path_fn = microsd_path
|
|
else:
|
|
path_fn = virtdisk_path
|
|
time.sleep(0.1)
|
|
_, story = cap_story()
|
|
assert "BSMS descriptor template file(s) written." in story
|
|
fnames = story.split("\n\n")[1:]
|
|
if encryption_type == "2":
|
|
for fname, token in zip(fnames, tokens):
|
|
assert token[:4] in fname
|
|
|
|
for fname in fnames:
|
|
with open(path_fn(fname), "rt" if encryption_type == "3" else "rb") as f:
|
|
desc_temp = f.read()
|
|
descriptor_templates.append(desc_temp)
|
|
|
|
assert descriptor_templates
|
|
if encryption_type == "2":
|
|
# each file encrypted with different token/key
|
|
templates = set()
|
|
for token, desc_template in zip(tokens, descriptor_templates):
|
|
plaintext = decrypt(
|
|
key_derivation_function(token),
|
|
desc_template if isinstance(desc_template, str) else desc_template.hex()
|
|
)
|
|
assert plaintext
|
|
templates.add(plaintext)
|
|
assert len(templates) == 1
|
|
# pick last to be the template
|
|
the_template = plaintext
|
|
elif encryption_type == "1":
|
|
# just one template but encrypted
|
|
assert len(descriptor_templates) == 1
|
|
plaintext = decrypt(
|
|
key_derivation_function(get_token(0)),
|
|
descriptor_templates[0] if isinstance(descriptor_templates[0], str) else descriptor_templates[0].hex()
|
|
)
|
|
assert plaintext
|
|
the_template = plaintext
|
|
else:
|
|
assert len(descriptor_templates) == 1
|
|
the_template = descriptor_templates[0]
|
|
|
|
version, descriptor, pth_restrictions, addr = the_template.split("\n")
|
|
assert version == BSMS_VERSION
|
|
try:
|
|
MultisigDescriptor.checksum_check(descriptor)
|
|
descriptor = descriptor.split("#")[0]
|
|
except ValueError:
|
|
pass
|
|
# replace /** so we can parse it
|
|
descriptor = descriptor.replace("/**", "/0/*")
|
|
descriptor = append_checksum(descriptor)
|
|
desc_obj = MultisigDescriptor.parse(descriptor)
|
|
assert len(desc_obj.keys) == N
|
|
assert pth_restrictions == ALLOWED_PATH_RESTRICTIONS
|
|
# bsms lib test ms address
|
|
address = ms_address_from_descriptor_bsms(desc_obj)
|
|
assert addr == address
|
|
|
|
|
|
@pytest.mark.parametrize("refuse", [True, False])
|
|
@pytest.mark.parametrize("way", ["sd", "nfc", "vdisk"])
|
|
@pytest.mark.parametrize("encryption_type", ["1", "2", "3"])
|
|
@pytest.mark.parametrize("with_checksum", [True, False])
|
|
@pytest.mark.parametrize("M_N", [(2,2), (3, 5), (15, 15)])
|
|
@pytest.mark.parametrize("addr_fmt", ["p2wsh", "p2sh-p2wsh"])
|
|
def test_signer_round2(refuse, way, encryption_type, M_N, addr_fmt, clear_miniscript, goto_home, need_keypress, pick_menu_item,
|
|
cap_menu, cap_story, microsd_path, settings_remove, nfc_read_text, request, settings_get,
|
|
make_coordinator_round2, nfc_write_text, microsd_wipe, with_checksum,
|
|
press_select, press_cancel, is_q1):
|
|
if way == "vdisk":
|
|
virtdisk_wipe = request.getfixturevalue("virtdisk_wipe")
|
|
virtdisk_path = request.getfixturevalue("virtdisk_path")
|
|
virtdisk_wipe()
|
|
M, N = M_N
|
|
clear_miniscript()
|
|
microsd_wipe()
|
|
desc_template, token = make_coordinator_round2(M, N, addr_fmt, encryption_type, way=way, add_checksum=with_checksum)
|
|
goto_home()
|
|
pick_menu_item('Settings')
|
|
pick_menu_item('Multisig/Miniscript')
|
|
pick_menu_item('BSMS (BIP-129)')
|
|
title, story = cap_story()
|
|
assert "Bitcoin Secure Multisig Setup (BIP-129) is a mechanism to securely create multisig wallets." in story
|
|
assert "WARNING: BSMS is an EXPERIMENTAL and BETA feature" in story
|
|
press_select()
|
|
pick_menu_item('Signer')
|
|
menu = cap_menu()
|
|
assert len(menu) == 2
|
|
assert "Round 1" in menu
|
|
menu_item = "1 %s" % token[:4]
|
|
assert menu_item in menu
|
|
pick_menu_item(menu_item)
|
|
menu = cap_menu()
|
|
assert len(menu) == 3
|
|
assert "Detail" in menu
|
|
assert "Delete" in menu
|
|
assert "Round 2" in menu
|
|
pick_menu_item("Detail")
|
|
time.sleep(0.1)
|
|
_, story = cap_story()
|
|
assert token in story
|
|
assert str(int(token, 16)) in story
|
|
press_select()
|
|
pick_menu_item("Round 2")
|
|
time.sleep(0.1)
|
|
_, story = cap_story()
|
|
if way == "sd":
|
|
if "Press (1) to import descriptor template file from SD Card" in story:
|
|
need_keypress("1")
|
|
# else no prompt if both NFC and vdisk disabled
|
|
elif way == "vdisk":
|
|
if "(2) to import from Virtual Disk" not in story:
|
|
pytest.skip("Vdisk disabled")
|
|
else:
|
|
need_keypress("2")
|
|
else:
|
|
# NFC
|
|
if f"{KEY_NFC if is_q1 else '(3)'} to import via NFC" not in story:
|
|
pytest.skip("NFC disabled")
|
|
else:
|
|
need_keypress(KEY_NFC if is_q1 else "3")
|
|
|
|
if way == "nfc":
|
|
time.sleep(0.1)
|
|
nfc_write_text(desc_template.hex() if isinstance(desc_template, bytes) else desc_template)
|
|
time.sleep(0.3)
|
|
else:
|
|
suffix = ".txt" if encryption_type == "3" else ".dat"
|
|
time.sleep(0.1)
|
|
menu_item = bsms_cr2_fname(token, encryption_type == "2", suffix)
|
|
pick_menu_item(menu_item)
|
|
|
|
time.sleep(0.5)
|
|
_, story = cap_story()
|
|
assert "Create new multisig wallet?" in story
|
|
assert "bsms" in story # part of the name
|
|
policy = "Policy: %d of %d" % (M, N)
|
|
assert policy in story
|
|
assert addr_fmt.upper() in story
|
|
ms_wal_name = story.split("\n\n")[1].split("\n")[-1].strip()
|
|
if refuse:
|
|
press_cancel()
|
|
time.sleep(0.1)
|
|
menu = cap_menu()
|
|
assert ms_wal_name not in menu
|
|
bsms_settings = settings_get(BSMS_SETTINGS)
|
|
# signer round 2 NOT removed
|
|
assert bsms_settings.get(BSMS_SIGNER_SETTINGS)
|
|
else:
|
|
press_select()
|
|
time.sleep(0.1)
|
|
menu = cap_menu()
|
|
assert ms_wal_name in menu
|
|
bsms_settings = settings_get(BSMS_SETTINGS)
|
|
# signer round 2 removed
|
|
assert not bsms_settings.get(BSMS_SIGNER_SETTINGS, None)
|
|
|
|
|
|
@pytest.mark.parametrize("token", [
|
|
"f" * 15,
|
|
"f" * 17,
|
|
"0" * 31,
|
|
"0" * 33,
|
|
])
|
|
@pytest.mark.parametrize("way", ["sd", "nfc", "vdisk", "manual"])
|
|
def test_invalid_token_signer_round1(token, way, pick_menu_item, cap_story, need_keypress,
|
|
nfc_write_text, microsd_path, virtdisk_path, goto_home,
|
|
press_select, is_q1):
|
|
goto_home()
|
|
pick_menu_item('Settings')
|
|
pick_menu_item('Multisig/Miniscript')
|
|
pick_menu_item('BSMS (BIP-129)')
|
|
title, story = cap_story()
|
|
assert "Bitcoin Secure Multisig Setup (BIP-129) is a mechanism to securely create multisig wallets." in story
|
|
assert "WARNING: BSMS is an EXPERIMENTAL and BETA feature" in story
|
|
press_select()
|
|
pick_menu_item('Signer')
|
|
pick_menu_item('Round 1')
|
|
time.sleep(0.1)
|
|
title, story = cap_story()
|
|
if way == "manual":
|
|
need_keypress("2") # manual
|
|
need_keypress("2") # decimal
|
|
for num in str(int(token, 16)):
|
|
need_keypress(num)
|
|
press_select()
|
|
else:
|
|
if way != "nfc":
|
|
token_fname = "error.token"
|
|
path_func = virtdisk_path if way == "vdisk" else microsd_path
|
|
with open(path_func(token_fname), "w") as f:
|
|
f.write(token)
|
|
if way == "sd":
|
|
if "Press (1) to import token file from SD Card" in story:
|
|
need_keypress("1")
|
|
# else no prompt if both NFC and vdisk disabled
|
|
elif way == "nfc":
|
|
if f"{KEY_NFC if is_q1 else '(4)'} to import via NFC" not in story:
|
|
pytest.skip("NFC disabled")
|
|
else:
|
|
need_keypress(KEY_NFC if is_q1 else "4")
|
|
time.sleep(0.1)
|
|
nfc_write_text(token)
|
|
time.sleep(0.4)
|
|
else:
|
|
# virtual disk
|
|
if "(6) to import from Virtual Disk" not in story:
|
|
pytest.skip("Vdisk disabled")
|
|
else:
|
|
need_keypress("6")
|
|
|
|
if way != "nfc":
|
|
time.sleep(0.2)
|
|
pick_menu_item(token_fname)
|
|
|
|
time.sleep(0.1)
|
|
title, story = cap_story()
|
|
assert title == "FAILURE"
|
|
assert "BSMS signer round1 failed" in story
|
|
assert "Invalid token length. Expected 64 or 128 bits (16 or 32 hex characters)" in story
|
|
|
|
|
|
@pytest.mark.parametrize("failure", ["wrong_sig", "bsms_version"])
|
|
@pytest.mark.parametrize("encryption_type", ["1", "2", "3"])
|
|
def test_failure_coordinator_round2(encryption_type, make_coordinator_round1, make_signer_round1, microsd_wipe, cap_menu,
|
|
pick_menu_item, press_select, goto_home, cap_story, failure,
|
|
need_keypress):
|
|
microsd_wipe()
|
|
|
|
def get_token(index):
|
|
if len(tokens) == 1 and encryption_type == "1":
|
|
token = tokens[0]
|
|
elif len(tokens) == 2 and encryption_type == "2":
|
|
token = tokens[index]
|
|
else:
|
|
token = "00"
|
|
return token
|
|
|
|
if failure == "bsms_version":
|
|
kws = {failure: "BSMS 1.1"}
|
|
else:
|
|
kws = {failure: True}
|
|
tokens = make_coordinator_round1(2, 2, "p2wsh", encryption_type, way="sd", tokens_only=True)
|
|
for i in range(2):
|
|
token = get_token(i)
|
|
index = None
|
|
if encryption_type != "2":
|
|
index = i + 1
|
|
make_signer_round1(token, "sd", purge_bsms=False, index=index, **kws)
|
|
goto_home()
|
|
pick_menu_item('Settings')
|
|
pick_menu_item('Multisig/Miniscript')
|
|
pick_menu_item('BSMS (BIP-129)')
|
|
title, story = cap_story()
|
|
assert "Bitcoin Secure Multisig Setup (BIP-129) is a mechanism to securely create multisig wallets." in story
|
|
assert "WARNING: BSMS is an EXPERIMENTAL and BETA feature" in story
|
|
press_select()
|
|
pick_menu_item('Coordinator')
|
|
menu = cap_menu()
|
|
assert len(menu) == 2
|
|
coord_menu_item = coordinator_label(2, 2, "p2wsh", encryption_type, index=1)
|
|
assert coord_menu_item in menu
|
|
pick_menu_item(coord_menu_item)
|
|
pick_menu_item("Round 2")
|
|
time.sleep(0.1)
|
|
_, story = cap_story()
|
|
if "Press (1) to import co-signer round 1 files from SD Card" in story:
|
|
need_keypress("1")
|
|
press_select() # continue with manual file selection
|
|
suffix = ".txt" if encryption_type == "3" else ".dat"
|
|
for i, _ in enumerate(range(2), start=1):
|
|
token = get_token(i - 1)
|
|
time.sleep(0.1)
|
|
title, story = cap_story()
|
|
if encryption_type == "2":
|
|
expect = 'Select co-signer #%d file containing round 1 data for token starting with %s' % (i, token[:4])
|
|
else:
|
|
expect = 'Select co-signer #%d file containing round 1 data' % i
|
|
expect += '. File extension has to be "%s"' % suffix
|
|
assert expect in story
|
|
press_select()
|
|
menu_item = bsms_sr1_fname(token, encryption_type == "2", suffix, i)
|
|
pick_menu_item(menu_item)
|
|
time.sleep(0.1)
|
|
title, story = cap_story()
|
|
assert title == "FAILURE"
|
|
assert "BSMS coordinator round2 failed" in story
|
|
if failure == "wrong_sig":
|
|
failure_msg = "Recovered key from signature does not equal key provided. Wrong signature?"
|
|
else:
|
|
failure_msg = "Incompatible BSMS version. Need BSMS 1.0 got BSMS 1.1"
|
|
assert failure_msg in story
|
|
|
|
|
|
# TODO do this for NFC too when length requirements are lifted from 250
|
|
@pytest.mark.parametrize("encryption_type", ["1", "2"])
|
|
def test_wrong_encryption_coordinator_round2(encryption_type, make_coordinator_round1, make_signer_round1, microsd_wipe,
|
|
cap_menu, pick_menu_item, need_keypress, goto_home, cap_story,
|
|
press_cancel, press_select):
|
|
def get_token(index):
|
|
if len(tokens) == 1 and encryption_type == "1":
|
|
token = tokens[0]
|
|
elif len(tokens) == 2 and encryption_type == "2":
|
|
token = tokens[index]
|
|
else:
|
|
token = "00"
|
|
return token
|
|
|
|
microsd_wipe()
|
|
tokens = make_coordinator_round1(2, 2, "p2wsh", encryption_type, way="sd", tokens_only=True)
|
|
for i in range(2):
|
|
token = get_token(i)
|
|
index = None
|
|
if encryption_type == "1":
|
|
index = i + 1
|
|
make_signer_round1(token, "sd", purge_bsms=False, index=index, wrong_encryption=True)
|
|
goto_home()
|
|
pick_menu_item('Settings')
|
|
pick_menu_item('Multisig/Miniscript')
|
|
pick_menu_item('BSMS (BIP-129)')
|
|
title, story = cap_story()
|
|
assert "Bitcoin Secure Multisig Setup (BIP-129) is a mechanism to securely create multisig wallets." in story
|
|
assert "WARNING: BSMS is an EXPERIMENTAL and BETA feature" in story
|
|
press_select()
|
|
pick_menu_item('Coordinator')
|
|
menu = cap_menu()
|
|
assert len(menu) == 2
|
|
coord_menu_item = coordinator_label(2, 2, "p2wsh", encryption_type, index=1)
|
|
assert coord_menu_item in menu
|
|
pick_menu_item(coord_menu_item)
|
|
pick_menu_item("Round 2")
|
|
time.sleep(0.1)
|
|
_, story = cap_story()
|
|
if "Press (1) to import co-signer round 1 files from SD Card" in story:
|
|
need_keypress("1")
|
|
press_select() # continue with manual file selection
|
|
suffix = ".txt" if encryption_type == "3" else ".dat"
|
|
for i, _ in enumerate(range(2), start=1):
|
|
for attempt in range(2):
|
|
token = get_token(i - 1)
|
|
time.sleep(0.1)
|
|
title, story = cap_story()
|
|
if encryption_type == "2":
|
|
expect = 'Select co-signer #%d file containing round 1 data for token starting with %s' % (i, token[:4])
|
|
else:
|
|
expect = 'Select co-signer #%d file containing round 1 data' % i
|
|
expect += '. File extension has to be "%s"' % suffix
|
|
assert expect in story
|
|
press_select()
|
|
menu_item = bsms_sr1_fname(token, encryption_type == "2", suffix, i)
|
|
pick_menu_item(menu_item)
|
|
time.sleep(0.1)
|
|
_, story = cap_story()
|
|
expect_story = "Decryption failed for co-signer #%d" % i
|
|
if encryption_type == 2:
|
|
expect_story += " with token %s" % token[:4]
|
|
assert expect_story in story
|
|
if attempt == 0:
|
|
assert "Try again?" in story
|
|
press_select()
|
|
else:
|
|
assert "Try again?" not in story
|
|
press_cancel()
|
|
break
|
|
break
|
|
|
|
|
|
@pytest.mark.parametrize("failure", [
|
|
"wrong_address", "path_restrictions", "bsms_version", "sortedmulti", "has_ours", "ours_no",
|
|
"wrong_encryption", "wrong_chain", "wrong_checksum"
|
|
])
|
|
@pytest.mark.parametrize("encryption_type", ["1", "2", "3"])
|
|
def test_failure_signer_round2(encryption_type, goto_home, press_select, pick_menu_item, cap_menu, cap_story,
|
|
microsd_path, settings_remove, nfc_read_text, virtdisk_path, settings_get, microsd_wipe,
|
|
make_coordinator_round2, failure, need_keypress):
|
|
microsd_wipe()
|
|
if failure == "wrong_address":
|
|
kws = {failure: True}
|
|
failure_msg = "Address mismatch!"
|
|
elif failure == "path_restrictions":
|
|
kws = {failure: "5/*,4/*"}
|
|
failure_msg = "Only '/0/*,/1/*' allowed as path restrictions."
|
|
elif failure == "bsms_version":
|
|
kws = {failure: "BSMS 2.0"}
|
|
failure_msg = "Incompatible BSMS version. Need BSMS 1.0 got BSMS 2.0"
|
|
elif failure == "sortedmulti":
|
|
kws = {failure: False}
|
|
failure_msg = "sortedmulti required"
|
|
elif failure == "has_ours":
|
|
kws = {failure: False}
|
|
failure_msg = "My key 0F056943 missing in descriptor."
|
|
elif failure == "ours_no":
|
|
kws = {failure: 2}
|
|
failure_msg = "Multiple 0F056943 keys in descriptor (2)"
|
|
elif failure == "wrong_chain":
|
|
kws = {failure: True}
|
|
failure_msg = "wrong chain"
|
|
elif failure == "wrong_checksum":
|
|
kws = {failure: True}
|
|
failure_msg = "Wrong checksum"
|
|
else:
|
|
assert failure == "wrong_encryption"
|
|
if encryption_type == "3":
|
|
pytest.skip("Cannot test wrong encryption on unencrypted BSMS")
|
|
kws = {failure: True}
|
|
failure_msg = "Decryption with token {token} failed."
|
|
|
|
desc_template, token = make_coordinator_round2(2, 2, "p2wsh", encryption_type, way="sd", **kws)
|
|
failure_msg = failure_msg.format(token=token[:4])
|
|
goto_home()
|
|
pick_menu_item('Settings')
|
|
pick_menu_item('Multisig/Miniscript')
|
|
pick_menu_item('BSMS (BIP-129)')
|
|
title, story = cap_story()
|
|
assert "Bitcoin Secure Multisig Setup (BIP-129) is a mechanism to securely create multisig wallets." in story
|
|
assert "WARNING: BSMS is an EXPERIMENTAL and BETA feature" in story
|
|
press_select()
|
|
pick_menu_item('Signer')
|
|
menu_item = "1 %s" % token[:4]
|
|
pick_menu_item(menu_item)
|
|
pick_menu_item("Round 2")
|
|
time.sleep(0.1)
|
|
_, story = cap_story()
|
|
if "Press (1) to import descriptor template file from SD Card" in story:
|
|
need_keypress("1")
|
|
|
|
suffix = ".txt" if encryption_type == "3" else ".dat"
|
|
time.sleep(0.1)
|
|
menu_item = bsms_cr2_fname(token, encryption_type == "2", suffix)
|
|
pick_menu_item(menu_item)
|
|
time.sleep(0.1)
|
|
title, story = cap_story()
|
|
assert title == "FAILURE"
|
|
assert "BSMS signer round2 failed" in story
|
|
assert failure_msg in story
|
|
|
|
|
|
@pytest.mark.parametrize("encryption_type", ["1", "2", "3"])
|
|
@pytest.mark.parametrize("M_N", [(2,2), (3, 5), (15, 15)])
|
|
@pytest.mark.parametrize("addr_fmt", ["p2wsh", "p2sh-p2wsh"])
|
|
def test_integration_signer(encryption_type, M_N, addr_fmt, clear_miniscript, microsd_wipe, goto_home, pick_menu_item, cap_story,
|
|
press_select, settings_remove, microsd_path, settings_get, cap_menu, use_mainnet,
|
|
need_keypress):
|
|
# test CC signer full with bsms lib coordinator (test just SD card no need to retest IO paths again - tested above)
|
|
def get_token(index):
|
|
if len(tokens) == 1 and encryption_type == "1":
|
|
token = tokens[0]
|
|
elif len(tokens) == N and encryption_type == "2":
|
|
token = tokens[index]
|
|
else:
|
|
token = "00"
|
|
return token
|
|
|
|
M, N = M_N
|
|
settings_remove(BSMS_SETTINGS)
|
|
use_mainnet()
|
|
clear_miniscript()
|
|
microsd_wipe()
|
|
coordinator = CoordinatorSession(M, N, addr_fmt, et_map[encryption_type])
|
|
session_data = coordinator.generate_token_key_pairs()
|
|
tokens = [x[0] for x in session_data]
|
|
cc_token = get_token(0)
|
|
other_signers = []
|
|
for i in range(1, N):
|
|
other_signers.append(Signer(token=get_token(i), key_description="Other signer %d" % i))
|
|
# ROUND 1
|
|
goto_home()
|
|
pick_menu_item('Settings')
|
|
pick_menu_item('Multisig/Miniscript')
|
|
pick_menu_item('BSMS (BIP-129)')
|
|
title, story = cap_story()
|
|
assert "Bitcoin Secure Multisig Setup (BIP-129) is a mechanism to securely create multisig wallets." in story
|
|
assert "WARNING: BSMS is an EXPERIMENTAL and BETA feature" in story
|
|
press_select()
|
|
pick_menu_item('Signer')
|
|
pick_menu_item('Round 1')
|
|
time.sleep(0.1)
|
|
_, story = cap_story()
|
|
if encryption_type == "3":
|
|
need_keypress("3") # no token (unencrypted BSMS)
|
|
else:
|
|
fname = "bsms_%s.token" % cc_token[:4] if cc_token != "00" else "1"
|
|
with open(microsd_path(fname), "w") as f:
|
|
f.write(cc_token)
|
|
if "Press (1) to import token file from SD Card" in story:
|
|
need_keypress("1")
|
|
time.sleep(0.2)
|
|
fname = "bsms_%s.token" % cc_token[:4]
|
|
pick_menu_item(fname)
|
|
|
|
time.sleep(0.1)
|
|
title, story = cap_story()
|
|
assert "You have entered token:\n%s" % cc_token in story
|
|
press_select()
|
|
time.sleep(0.1)
|
|
_, story = cap_story()
|
|
# address format a.k.a. SLIP derivation path - ignore and use SLIP agnostic
|
|
assert "Choose co-signer address format for correct SLIP derivation path" in story
|
|
press_select()
|
|
# account number prompt
|
|
press_select()
|
|
time.sleep(0.1)
|
|
_, story = cap_story()
|
|
# textual key description
|
|
assert "Choose key description" in story
|
|
press_select() # default
|
|
time.sleep(0.1)
|
|
title, story = cap_story()
|
|
suffix = ".txt" if encryption_type == "3" else ".dat"
|
|
mode = "rt" if encryption_type == "3" else "rb"
|
|
if "Press (1) to save BSMS signer round 1 file to SD Card" in story:
|
|
need_keypress("1")
|
|
time.sleep(0.2)
|
|
_, story = cap_story()
|
|
assert 'BSMS signer round 1 file written' in story
|
|
fname = story.split('\n\n')[-1]
|
|
assert suffix in fname
|
|
path = microsd_path(fname)
|
|
with open(path, mode) as f:
|
|
signer_r1 = f.read()
|
|
|
|
bsms = settings_get(BSMS_SETTINGS)
|
|
assert len(bsms[BSMS_SIGNER_SETTINGS]) == 1
|
|
assert bsms[BSMS_SIGNER_SETTINGS][0] == cc_token
|
|
|
|
# ROUND 2
|
|
all_r1_data = [signer_r1.hex() if encryption_type != "3" else signer_r1]
|
|
for s in other_signers:
|
|
all_r1_data.append(s.round_1())
|
|
|
|
descriptor_templates = coordinator.round_2(all_r1_data)
|
|
if encryption_type == "2":
|
|
assert len(descriptor_templates) == N
|
|
for signer, tmplt in zip(other_signers, descriptor_templates[1:]):
|
|
signer.round_2(tmplt)
|
|
else:
|
|
assert len(descriptor_templates) == 1
|
|
for signer in other_signers:
|
|
signer.round_2(descriptor_templates[0])
|
|
|
|
cc_desc_template = descriptor_templates[0] # zeroeth as our token is zero too
|
|
suffix = ".txt" if encryption_type == "3" else ".dat"
|
|
mode = "wt" if encryption_type == "3" else "wb"
|
|
fname = bsms_cr2_fname(cc_token, encryption_type == "2", suffix)
|
|
with open(microsd_path(fname), mode) as f:
|
|
f.write(bytes.fromhex(cc_desc_template) if mode == "wb" else cc_desc_template)
|
|
time.sleep(0.1)
|
|
goto_home()
|
|
pick_menu_item('Settings')
|
|
pick_menu_item('Multisig/Miniscript')
|
|
pick_menu_item('BSMS (BIP-129)')
|
|
title, story = cap_story()
|
|
assert "Bitcoin Secure Multisig Setup (BIP-129) is a mechanism to securely create multisig wallets." in story
|
|
assert "WARNING: BSMS is an EXPERIMENTAL and BETA feature" in story
|
|
press_select()
|
|
pick_menu_item('Signer')
|
|
menu_item = "1 %s" % cc_token[:4]
|
|
pick_menu_item(menu_item)
|
|
pick_menu_item("Round 2")
|
|
time.sleep(0.1)
|
|
_, story = cap_story()
|
|
if "Press (1) to import descriptor template file from SD Card" in story:
|
|
need_keypress("1")
|
|
time.sleep(0.1)
|
|
menu_item = bsms_cr2_fname(cc_token, encryption_type == "2", suffix)
|
|
pick_menu_item(menu_item)
|
|
time.sleep(0.1)
|
|
title, story = cap_story()
|
|
assert "Create new multisig wallet?" in story
|
|
assert "bsms" in story # part of the name
|
|
policy = "Policy: %d of %d" % (M, N)
|
|
assert policy in story
|
|
assert addr_fmt.upper() in story
|
|
ms_wal_name = story.split("\n\n")[1].split("\n")[-1].strip()
|
|
press_select()
|
|
time.sleep(0.1)
|
|
menu = cap_menu()
|
|
assert ms_wal_name in menu
|
|
bsms_settings = settings_get(BSMS_SETTINGS)
|
|
# signer round 2 removed
|
|
assert not bsms_settings.get(BSMS_SIGNER_SETTINGS, None)
|
|
|
|
|
|
@pytest.mark.parametrize("encryption_type", ["1", "2", "3"])
|
|
@pytest.mark.parametrize("M_N", [(2,2), (3, 5), (15, 15)])
|
|
@pytest.mark.parametrize("addr_fmt", ["p2wsh", "p2sh-p2wsh"])
|
|
@pytest.mark.parametrize("cr1_shortcut", [True, False])
|
|
def test_integration_coordinator(encryption_type, M_N, addr_fmt, clear_miniscript, microsd_wipe, goto_home, pick_menu_item,
|
|
cap_story, need_keypress, settings_remove, microsd_path, settings_get, cap_menu,
|
|
use_mainnet, cr1_shortcut, press_select):
|
|
M, N = M_N
|
|
settings_remove(BSMS_SETTINGS)
|
|
use_mainnet()
|
|
clear_miniscript()
|
|
microsd_wipe()
|
|
goto_home()
|
|
pick_menu_item('Settings')
|
|
pick_menu_item('Multisig/Miniscript')
|
|
pick_menu_item('BSMS (BIP-129)')
|
|
title, story = cap_story()
|
|
assert "Bitcoin Secure Multisig Setup (BIP-129) is a mechanism to securely create multisig wallets." in story
|
|
assert "WARNING: BSMS is an EXPERIMENTAL and BETA feature" in story
|
|
press_select()
|
|
pick_menu_item('Coordinator')
|
|
menu = cap_menu()
|
|
assert len(menu) == 1 # nothing should be in menu at this point but round 1
|
|
pick_menu_item('Create BSMS')
|
|
# choose number of signers N
|
|
for num in str(N):
|
|
need_keypress(num)
|
|
press_select()
|
|
# choose threshold M
|
|
for num in str(M):
|
|
need_keypress(num)
|
|
press_select()
|
|
if addr_fmt == "p2wsh":
|
|
press_select()
|
|
else:
|
|
need_keypress("2")
|
|
time.sleep(0.1)
|
|
title, story = cap_story()
|
|
assert story == "Choose encryption type. Press (1) for STANDARD encryption, (2) for EXTENDED, and (3) for no encryption"
|
|
need_keypress(encryption_type)
|
|
time.sleep(0.1)
|
|
title, story = cap_story()
|
|
assert_coord_summary(title, story, M, N, addr_fmt, encryption_type)
|
|
press_select() # confirm summary
|
|
time.sleep(0.1)
|
|
title, story = cap_story()
|
|
assert "Press (1) to participate as co-signer in this BSMS" in story
|
|
if cr1_shortcut:
|
|
_start_idx = 1
|
|
need_keypress("1")
|
|
press_select() # slip
|
|
press_select() # acct num 0
|
|
press_select() # default textual key description
|
|
time.sleep(0.1)
|
|
_, story = cap_story()
|
|
if "Press (1) to save BSMS signer round 1 file to SD Card" in story:
|
|
need_keypress("1")
|
|
time.sleep(0.2)
|
|
_, story = cap_story()
|
|
shortcut_fname = story.split("\n\n")[-1]
|
|
press_select() # looking at save sr1 filename
|
|
else:
|
|
_start_idx = 0
|
|
press_select() # continue normally
|
|
|
|
time.sleep(0.1)
|
|
title, story = cap_story()
|
|
read_tokens = []
|
|
if encryption_type == "3":
|
|
assert story == "Success. Coordinator round 1 saved."
|
|
else:
|
|
if "Press (1) to save BSMS token file(s) to SD Card" in story:
|
|
need_keypress("1")
|
|
time.sleep(0.2)
|
|
_, story = cap_story()
|
|
assert 'BSMS token file(s) written' in story
|
|
fnames = story.split('\n\n')[2:]
|
|
for fname in fnames:
|
|
path = microsd_path(fname)
|
|
with open(path, 'rt') as f:
|
|
tok = f.read().strip()
|
|
read_tokens.append(tok)
|
|
|
|
all_signers = []
|
|
if encryption_type == "1":
|
|
assert len(read_tokens) == 1
|
|
for i in range(_start_idx, N):
|
|
all_signers.append(Signer(read_tokens[0], "key %d" % i))
|
|
elif encryption_type == "2":
|
|
assert len(read_tokens) == (N - _start_idx)
|
|
for i in range(N - _start_idx):
|
|
all_signers.append(Signer(read_tokens[i], "key %d" % i))
|
|
else:
|
|
assert len(read_tokens) == 0
|
|
for i in range(N - _start_idx):
|
|
all_signers.append(Signer("00", "key %d" % i))
|
|
|
|
press_select() # confirm success or files written story
|
|
time.sleep(0.1)
|
|
menu = cap_menu()
|
|
assert len(menu) == 2
|
|
current_coord_menu_item = coordinator_label(M, N, addr_fmt, encryption_type, index=1)
|
|
assert menu[0] == current_coord_menu_item
|
|
# check correct coord tuple saved
|
|
bsms_settings = settings_get(BSMS_SETTINGS)
|
|
if BSMS_SIGNER_SETTINGS in bsms_settings:
|
|
if cr1_shortcut:
|
|
assert len(bsms_settings[BSMS_SIGNER_SETTINGS]) == 1
|
|
shortcut_token = bsms_settings[BSMS_SIGNER_SETTINGS][0]
|
|
else:
|
|
assert bsms_settings[BSMS_SIGNER_SETTINGS] == []
|
|
shortcut_token = None
|
|
coord_settings = bsms_settings[BSMS_COORD_SETTINGS]
|
|
assert len(coord_settings) == 1
|
|
if read_tokens:
|
|
expect_tokens = [tok.split(" ")[-1] for tok in read_tokens]
|
|
if cr1_shortcut and encryption_type == "2":
|
|
expect_tokens = [shortcut_token] + expect_tokens
|
|
else:
|
|
expect_tokens = []
|
|
assert coord_settings[0] == (M, N, af_map[addr_fmt], encryption_type, expect_tokens)
|
|
|
|
# ROUND 2
|
|
def get_token(index):
|
|
if len(read_tokens) == 1 and encryption_type == "1":
|
|
token = read_tokens[0]
|
|
elif encryption_type == "2":
|
|
token = read_tokens[index]
|
|
else:
|
|
token = "00"
|
|
return token
|
|
|
|
all_r1_signer_data = [s.round_1() for s in all_signers]
|
|
mode = "wt" if encryption_type == "3" else "wb"
|
|
suffix = ".txt" if encryption_type == "3" else ".dat"
|
|
for i, data in enumerate(all_r1_signer_data, start=1):
|
|
token = get_token(i - 1)
|
|
fname = bsms_sr1_fname(token, encryption_type == "2", suffix, i)
|
|
with open(microsd_path(fname), mode) as f:
|
|
f.write(bytes.fromhex(data) if mode == "wb" else data)
|
|
|
|
goto_home()
|
|
pick_menu_item('Settings')
|
|
pick_menu_item('Multisig/Miniscript')
|
|
pick_menu_item('BSMS (BIP-129)')
|
|
title, story = cap_story()
|
|
assert "Bitcoin Secure Multisig Setup (BIP-129) is a mechanism to securely create multisig wallets." in story
|
|
assert "WARNING: BSMS is an EXPERIMENTAL and BETA feature" in story
|
|
press_select()
|
|
pick_menu_item('Coordinator')
|
|
menu = cap_menu()
|
|
assert len(menu) == 2
|
|
coord_menu_item = coordinator_label(M, N, addr_fmt, encryption_type, index=1)
|
|
assert coord_menu_item in menu
|
|
pick_menu_item(coord_menu_item)
|
|
pick_menu_item("Round 2")
|
|
time.sleep(0.1)
|
|
_, story = cap_story()
|
|
if "Press (1) to import co-signer round 1 files from SD Card" in story:
|
|
need_keypress("1")
|
|
press_select() # continue with manual file selection
|
|
if cr1_shortcut:
|
|
time.sleep(0.1)
|
|
title, story = cap_story()
|
|
if encryption_type == "2":
|
|
expect = 'Select co-signer #1 file containing round 1 data for token starting with %s' % shortcut_token[:4]
|
|
else:
|
|
expect = 'Select co-signer #1 file containing round 1 data'
|
|
assert expect in story
|
|
press_select()
|
|
pick_menu_item(shortcut_fname)
|
|
for i in range(_start_idx, N):
|
|
token = get_token(i - _start_idx)
|
|
time.sleep(0.1)
|
|
title, story = cap_story()
|
|
if encryption_type == "2":
|
|
expect = 'Select co-signer #%d file containing round 1 data for token starting with %s' % (i + 1, token[:4])
|
|
else:
|
|
expect = 'Select co-signer #%d file containing round 1 data' % (i + 1)
|
|
expect += '. File extension has to be "%s"' % suffix
|
|
assert expect in story
|
|
press_select()
|
|
fname = bsms_sr1_fname(token, encryption_type == "2", suffix, i + 1 - _start_idx)
|
|
pick_menu_item(fname)
|
|
|
|
time.sleep(0.1)
|
|
_, story = cap_story()
|
|
if "Press (1) to save BSMS descriptor template file(s) to SD Card" in story:
|
|
need_keypress("1")
|
|
time.sleep(0.1)
|
|
_, story = cap_story()
|
|
assert "BSMS descriptor template file(s) written." in story
|
|
fnames = story.split("\n\n")[1:]
|
|
if encryption_type == "2":
|
|
if cr1_shortcut:
|
|
read_tokens = [shortcut_token] + read_tokens
|
|
for fname, token in zip(fnames, read_tokens):
|
|
assert token[:4] in fname
|
|
descriptor_templates = []
|
|
for fname in fnames:
|
|
with open(microsd_path(fname), "rt" if encryption_type == "3" else "rb") as f:
|
|
desc_temp = f.read()
|
|
descriptor_templates.append(desc_temp)
|
|
if len(descriptor_templates) == 1:
|
|
target = descriptor_templates[0]
|
|
if isinstance(target, bytes):
|
|
target = target.hex()
|
|
for signer in all_signers:
|
|
signer.round_2(target)
|
|
else:
|
|
if cr1_shortcut:
|
|
_, descriptor_templates = descriptor_templates[0], descriptor_templates[1:]
|
|
for signer, desc_tmplt in zip(all_signers, descriptor_templates):
|
|
if isinstance(desc_tmplt, bytes):
|
|
desc_tmplt = desc_tmplt.hex()
|
|
signer.round_2(desc_tmplt)
|
|
if cr1_shortcut:
|
|
# still need to add our signer
|
|
goto_home()
|
|
pick_menu_item('Settings')
|
|
pick_menu_item('Multisig/Miniscript')
|
|
pick_menu_item('BSMS (BIP-129)')
|
|
press_select()
|
|
pick_menu_item('Signer')
|
|
menu_item = "1 %s" % shortcut_token[:4]
|
|
pick_menu_item(menu_item)
|
|
pick_menu_item("Round 2")
|
|
time.sleep(0.1)
|
|
_, story = cap_story()
|
|
if "Press (1) to import descriptor template file from SD Card" in story:
|
|
need_keypress("1")
|
|
time.sleep(0.1)
|
|
pick_menu_item(fnames[0])
|
|
time.sleep(0.1)
|
|
title, story = cap_story()
|
|
assert "Create new multisig wallet?" in story
|
|
assert "bsms" in story # part of the name
|
|
policy = "Policy: %d of %d" % (M, N)
|
|
assert policy in story
|
|
assert addr_fmt.upper() in story
|
|
ms_wal_name = story.split("\n\n")[1].split("\n")[-1].strip()
|
|
press_select()
|
|
time.sleep(0.1)
|
|
menu = cap_menu()
|
|
assert ms_wal_name in menu
|
|
bsms_settings = settings_get(BSMS_SETTINGS)
|
|
# signer round 2 removed
|
|
assert not bsms_settings.get(BSMS_SIGNER_SETTINGS, None)
|
|
|
|
|
|
|
|
@pytest.mark.parametrize("encryption_type", ["1", "2", "3"])
|
|
@pytest.mark.parametrize("M_N", [(2, 2), (3, 5), (15, 15)])
|
|
def test_auto_collection_coordinator_r2(encryption_type, M_N, goto_home, need_keypress, pick_menu_item, microsd_wipe,
|
|
cap_story, microsd_path,make_coordinator_round1, make_signer_round1,
|
|
press_select):
|
|
M, N = M_N
|
|
microsd_wipe()
|
|
|
|
def get_token(index):
|
|
if len(tokens) == 1 and encryption_type == "1":
|
|
token = tokens[0]
|
|
elif len(tokens) == N and encryption_type == "2":
|
|
token = tokens[index]
|
|
else:
|
|
token = "00"
|
|
return token
|
|
|
|
# add twice as many files with different tokens - should be still able to collect the correct ones
|
|
f_pattern = "bsms_sr1"
|
|
if encryption_type == "2":
|
|
suffix = ".dat"
|
|
for i in range(N):
|
|
token = os.urandom(16).hex()
|
|
s = Signer(token=token, key_description="key%d" % i)
|
|
r1 = s.round_1()
|
|
fname = "%s_%s%s" % (f_pattern, token[:4], suffix)
|
|
with open(microsd_path(fname), "wb") as f:
|
|
f.write(bytes.fromhex(r1))
|
|
|
|
elif encryption_type == "1":
|
|
suffix = ".dat"
|
|
for i in range(N):
|
|
token = os.urandom(8).hex()
|
|
s = Signer(token=token, key_description="key%d" % i)
|
|
r1 = s.round_1()
|
|
fname = "%s%s" % (f_pattern, suffix)
|
|
with open(microsd_path(fname), "wb") as f:
|
|
f.write(bytes.fromhex(r1))
|
|
|
|
else:
|
|
suffix = ".txt"
|
|
for i in range(N):
|
|
s = Signer(token="00", key_description="key%d" % i)
|
|
r1 = s.round_1()
|
|
fname = "%s%s" % (f_pattern, suffix)
|
|
with open(microsd_path(fname), "w") as f:
|
|
f.write(r1)
|
|
|
|
tokens = make_coordinator_round1(M, N, "p2wsh", encryption_type, way="sd", tokens_only=True)
|
|
all_data = []
|
|
for i in range(N):
|
|
token = get_token(i)
|
|
index = None
|
|
if encryption_type == "1":
|
|
index = i + 1
|
|
all_data.append(make_signer_round1(token, "sd", purge_bsms=False, index=index))
|
|
goto_home()
|
|
pick_menu_item('Settings')
|
|
pick_menu_item('Multisig/Miniscript')
|
|
pick_menu_item('BSMS (BIP-129)')
|
|
title, story = cap_story()
|
|
assert "Bitcoin Secure Multisig Setup (BIP-129) is a mechanism to securely create multisig wallets." in story
|
|
assert "WARNING: BSMS is an EXPERIMENTAL and BETA feature" in story
|
|
press_select()
|
|
pick_menu_item('Coordinator')
|
|
coord_menu_item = coordinator_label(M, N, "p2wsh", encryption_type, index=1)
|
|
pick_menu_item(coord_menu_item)
|
|
pick_menu_item("Round 2")
|
|
time.sleep(0.1)
|
|
_, story = cap_story()
|
|
if "Press (1) to import co-signer round 1 files from SD Card" in story:
|
|
need_keypress("1")
|
|
need_keypress("1") # auto-collection
|
|
time.sleep(0.1)
|
|
title, story = cap_story()
|
|
if encryption_type == "3":
|
|
# we need exact number of files for unencrypted as we would have no idea which are part of this multisig setup
|
|
assert "Auto-collection failed. Defaulting to manual selection of files." in story
|
|
else:
|
|
if "Press (1) to save BSMS descriptor template file(s) to SD Card" in story:
|
|
# if NFC or Vdisk enabled - but means auto-collection was successful and we are prompted where to
|
|
# save the resulting descriptor (coordinator round2 data)
|
|
assert True
|
|
else:
|
|
# NFC and Vdisk disabled, automatically written to SD card - success
|
|
assert "BSMS descriptor template file(s) written" in story
|