def test_backup_slip39_basic(client): assert client.features.needs_backup is True mnemonics = [] def input_flow(): # 1. Checklist # 2. Number of shares (5) # 3. Checklist # 4. Threshold (3) # 5. Checklist # 6. Confirm show seeds yield from click_through(client.debug, screens=6, code=B.ResetDevice) # Mnemonic phrases for _ in range(5): yield # Phrase screen mnemonic = read_and_confirm_mnemonic(client.debug, words=20) mnemonics.append(mnemonic) yield # Confirm continue to next client.debug.press_yes() # Confirm backup yield client.debug.press_yes() with client: client.set_input_flow(input_flow) client.set_expected_responses([ messages.ButtonRequest(code=B.ResetDevice), messages.ButtonRequest(code=B.ResetDevice), messages.ButtonRequest(code=B.ResetDevice), messages.ButtonRequest(code=B.ResetDevice), messages.ButtonRequest(code=B.ResetDevice), messages.ButtonRequest(code=B.ResetDevice), messages.ButtonRequest(code=B.ResetDevice), messages.ButtonRequest(code=B.Success), messages.ButtonRequest(code=B.ResetDevice), messages.ButtonRequest(code=B.Success), messages.ButtonRequest(code=B.ResetDevice), messages.ButtonRequest(code=B.Success), messages.ButtonRequest(code=B.ResetDevice), messages.ButtonRequest(code=B.Success), messages.ButtonRequest(code=B.ResetDevice), messages.ButtonRequest(code=B.Success), messages.ButtonRequest(code=B.Success), messages.Success(), messages.Features(), ]) device.backup(client) client.init_device() assert client.features.initialized is True assert client.features.needs_backup is False assert client.features.unfinished_backup is False assert client.features.no_backup is False assert client.features.backup_type is messages.BackupType.Slip39_Basic expected_ms = shamir.combine_mnemonics(MNEMONIC_SLIP39_BASIC_20_3of6) actual_ms = shamir.combine_mnemonics(mnemonics[:3]) assert expected_ms == actual_ms
def test_backup_slip39_basic(client: Client, click_info: bool): assert client.features.needs_backup is True mnemonics = [] def input_flow(): yield # Checklist client.debug.press_yes() if click_info: yield from click_info_button(client.debug) yield # Number of shares (5) client.debug.press_yes() yield # Checklist client.debug.press_yes() if click_info: yield from click_info_button(client.debug) yield # Threshold (3) client.debug.press_yes() yield # Checklist client.debug.press_yes() yield # Confirm show seeds client.debug.press_yes() # Mnemonic phrases for _ in range(5): # Phrase screen mnemonic = yield from read_and_confirm_mnemonic(client.debug) mnemonics.append(mnemonic) yield # Confirm continue to next client.debug.press_yes() yield # Confirm backup client.debug.press_yes() with client: client.set_input_flow(input_flow) client.set_expected_responses( [messages.ButtonRequest(code=B.ResetDevice)] * (8 if click_info else 6) # intro screens (and optional info) + [ messages.ButtonRequest(code=B.ResetDevice), messages.ButtonRequest(code=B.Success), ] * 5 # individual shares + [ messages.ButtonRequest(code=B.Success), messages.Success, messages.Features, ]) device.backup(client) client.init_device() assert client.features.initialized is True assert client.features.needs_backup is False assert client.features.unfinished_backup is False assert client.features.no_backup is False assert client.features.backup_type is messages.BackupType.Slip39_Basic expected_ms = shamir.combine_mnemonics(MNEMONIC_SLIP39_BASIC_20_3of6) actual_ms = shamir.combine_mnemonics(mnemonics[:3]) assert expected_ms == actual_ms
def test_iteration_exponent(): mnemonics = shamir.generate_mnemonics(1, [(3, 5)], MS, b"TREZOR", 1)[0] assert MS == shamir.combine_mnemonics(mnemonics[1:4], b"TREZOR") assert MS != shamir.combine_mnemonics(mnemonics[1:4]) mnemonics = shamir.generate_mnemonics(1, [(3, 5)], MS, b"TREZOR", 2)[0] assert MS == shamir.combine_mnemonics(mnemonics[1:4], b"TREZOR") assert MS != shamir.combine_mnemonics(mnemonics[1:4])
def validate_mnemonics(mnemonics, threshold, expected_secret): # We expect these combinations to recreate the secret properly for test_group in combinations(mnemonics, threshold): secret = shamir.combine_mnemonics(test_group) assert secret == expected_secret # We expect these combinations to raise MnemonicError for test_group in combinations(mnemonics, threshold - 1): with pytest.raises( MnemonicError, match=r".*Expected {} mnemonics.*".format(threshold) ): secret = shamir.combine_mnemonics(test_group)
def validate_mnemonics(mnemonics, threshold, expected_ems): # We expect these combinations to recreate the secret properly for test_group in combinations(mnemonics, threshold): # TODO: HOTFIX, we should fix this properly by modifying and unifying the python-shamir-mnemonic API ms = shamir.combine_mnemonics(test_group) identifier, iteration_exponent, _, _, _ = shamir._decode_mnemonics(test_group) ems = shamir._encrypt(ms, b"", iteration_exponent, identifier) assert ems == expected_ems # We expect these combinations to raise MnemonicError for test_group in combinations(mnemonics, threshold - 1): with pytest.raises( MnemonicError, match=r".*Expected {} mnemonics.*".format(threshold) ): shamir.combine_mnemonics(test_group)
def test_vectors(): with open("vectors.json", "r") as f: vectors = json.load(f) for description, mnemonics, secret in vectors: if secret: assert bytes.fromhex(secret) == shamir.combine_mnemonics( mnemonics, b"TREZOR"), 'Incorrect secret for test vector "{}".'.format( description) else: with pytest.raises(MnemonicError): shamir.combine_mnemonics(mnemonics) pytest.fail( 'Failed to raise exception for test vector "{}".'.format( description))
def split_mnemonic(m, n): phrase = input('Enter BIP39 phrase:\n') is_valid = check_bip39_phrase(phrase) if not is_valid: print('The BIP39 phrase is invalid.', file=sys.stderr) sys.exit(2) seed = phrase_to_seed(phrase) print_seed(seed) print('\nGenerating shares...') mnemonics = shamir_mnemonic.generate_mnemonics(1, [(m, n)], seed)[0] combinations = [combo for combo in itertools.combinations(mnemonics, m)] for combo in combinations: secret = shamir_mnemonic.combine_mnemonics(combo) if secret != seed: print( 'Error: a combination of shares failed to verify. Try again.', file=sys.stderr) sys.exit(2) return mnemonics
def backup_flow_slip39_advanced(client): mnemonics = [] def input_flow(): # 1. Confirm Reset # 2. shares info # 3. Set & Confirm number of groups # 4. threshold info # 5. Set & confirm group threshold value # 6-15: for each of 5 groups: # 1. Set & Confirm number of shares # 2. Set & confirm share threshold value # 16. Confirm show seeds yield from click_through(client.debug, screens=16, code=B.ResetDevice) # show & confirm shares for all groups for _ in range(5): for _ in range(5): # mnemonic phrases btn_code = yield assert btn_code == B.ResetDevice mnemonic = read_and_confirm_mnemonic(client.debug, words=20) mnemonics.append(mnemonic) # Confirm continue to next share btn_code = yield assert btn_code == B.Success client.debug.press_yes() # safety warning btn_code = yield assert btn_code == B.Success client.debug.press_yes() with client: client.set_input_flow(input_flow) client.set_expected_responses( [messages.ButtonRequest(code=B.ResetDevice)] * 6 # intro screens + [ messages.ButtonRequest(code=B.ResetDevice), messages.ButtonRequest(code=B.ResetDevice), ] * 5 # group thresholds + [ messages.ButtonRequest(code=B.ResetDevice), messages.ButtonRequest(code=B.Success), ] * 25 # individual shares + [ messages.ButtonRequest(code=B.Success), messages.Success(), messages.Features(), ]) device.backup(client) mnemonics = mnemonics[0:3] + mnemonics[5:8] + mnemonics[10:13] ms = shamir.combine_mnemonics(mnemonics) identifier, iteration_exponent, _, _, _ = shamir._decode_mnemonics( mnemonics) secret = shamir._encrypt(ms, b"", iteration_exponent, identifier) return secret
def validate_mnemonics(mnemonics, expected_ems): # We expect these combinations to recreate the secret properly # In case of click tests the mnemonics are always XofX so no need for combinations ms = shamir.combine_mnemonics(mnemonics) identifier, iteration_exponent, _, _, _ = shamir._decode_mnemonics( mnemonics) ems = shamir._encrypt(ms, b"", iteration_exponent, identifier) assert ems == expected_ems
def validate_mnemonics(mnemonics, threshold, expected_ems): # 3of5 shares 3of5 groups # TODO: test all possible group+share combinations? test_combination = mnemonics[0:3] + mnemonics[5:8] + mnemonics[10:13] ms = shamir.combine_mnemonics(test_combination) identifier, iteration_exponent, _, _, _ = shamir._decode_mnemonics(test_combination) ems = shamir._encrypt(ms, b"", iteration_exponent, identifier) assert ems == expected_ems
def test_group_sharing(): group_threshold = 2 group_sizes = (5, 3, 5, 1) member_thresholds = (3, 2, 2, 1) mnemonics = shamir.generate_mnemonics( group_threshold, list(zip(member_thresholds, group_sizes)), MS) # Test all valid combinations of mnemonics. for groups in combinations(zip(mnemonics, member_thresholds), group_threshold): for group1_subset in combinations(groups[0][0], groups[0][1]): for group2_subset in combinations(groups[1][0], groups[1][1]): mnemonic_subset = list(group1_subset + group2_subset) shuffle(mnemonic_subset) assert MS == shamir.combine_mnemonics(mnemonic_subset) # Minimal sets of mnemonics. assert MS == shamir.combine_mnemonics( [mnemonics[2][0], mnemonics[2][2], mnemonics[3][0]]) assert MS == shamir.combine_mnemonics( [mnemonics[2][3], mnemonics[3][0], mnemonics[2][4]]) # One complete group and one incomplete group out of two groups required. with pytest.raises(MnemonicError): shamir.combine_mnemonics(mnemonics[0][2:] + [mnemonics[1][0]]) # One group of two required. with pytest.raises(MnemonicError): shamir.combine_mnemonics(mnemonics[0][1:4])
def test_split_ems(): identifier = 42 exponent = 1 encrypted_master_secret = shamir.encrypt(MS, b"TREZOR", exponent, identifier) mnemonics = shamir.split_ems(1, [(3, 5)], identifier, exponent, encrypted_master_secret) recovered = shamir.combine_mnemonics(mnemonics[0][:3], b"TREZOR") assert recovered == MS
def test_split_ems(): identifier = 42 exponent = 1 encrypted_master_secret = shamir.EncryptedMasterSecret.from_master_secret( MS, b"TREZOR", identifier, exponent) grouped_shares = shamir.split_ems(1, [(3, 5)], encrypted_master_secret) mnemonics = [share.mnemonic() for share in grouped_shares[0]] recovered = shamir.combine_mnemonics(mnemonics[:3], b"TREZOR") assert recovered == MS
def test_group_sharing_threshold_1(): group_threshold = 1 group_sizes = (5, 3, 5, 1) member_thresholds = (3, 2, 2, 1) mnemonics = shamir.generate_mnemonics( group_threshold, list(zip(member_thresholds, group_sizes)), MS) # Test all valid combinations of mnemonics. for group, threshold in zip(mnemonics, member_thresholds): for group_subset in combinations(group, threshold): mnemonic_subset = list(group_subset) shuffle(mnemonic_subset) assert MS == shamir.combine_mnemonics(mnemonic_subset)
def combine_mnemonics(m): shares = [] for i in range(m): print('\n=========================') share = input(f'Enter mnemonic share #{i+1}:\n').strip() print('=========================') shares.append(share) try: seed = shamir_mnemonic.combine_mnemonics(shares) except shamir_mnemonic.MnemonicError as e: print() print(e, file=sys.stderr) sys.exit(2) phrase = seed_to_phrase(seed) return seed, phrase
def backup_flow_slip39_basic(client): mnemonics = [] def input_flow(): # 1. Checklist # 2. Number of shares (5) # 3. Checklist # 4. Threshold (3) # 5. Checklist # 6. Confirm show seeds yield from click_through(client.debug, screens=6, code=B.ResetDevice) # Mnemonic phrases for _ in range(5): yield # Phrase screen mnemonic = read_and_confirm_mnemonic(client.debug, words=20) mnemonics.append(mnemonic) yield # Confirm continue to next client.debug.press_yes() # Confirm backup yield client.debug.press_yes() with client: client.set_input_flow(input_flow) client.set_expected_responses( [messages.ButtonRequest(code=B.ResetDevice)] * 6 # intro screens + [ messages.ButtonRequest(code=B.ResetDevice), messages.ButtonRequest(code=B.Success), ] * 5 # individual shares + [ messages.ButtonRequest(code=B.Success), messages.Success(), messages.Features(), ]) device.backup(client) mnemonics = mnemonics[:3] ms = shamir.combine_mnemonics(mnemonics) identifier, iteration_exponent, _, _, _ = shamir._decode_mnemonics( mnemonics) secret = shamir._encrypt(ms, b"", iteration_exponent, identifier) return secret
def test_basic_sharing_random(): secret = secrets.token_bytes(16) mnemonics = shamir.generate_mnemonics(1, [(3, 5)], secret)[0] assert shamir.combine_mnemonics(mnemonics[:3]) == shamir.combine_mnemonics( mnemonics[2:])
def test_backup_slip39_advanced(client): assert client.features.needs_backup is True mnemonics = [] def input_flow(): # 1. Checklist # 2. Set and confirm group count # 3. Checklist # 4. Set and confirm group threshold # 5. Checklist # 6-15: for each of 5 groups: # 1. Set & Confirm number of shares # 2. Set & confirm share threshold value # 16. Confirm show seeds yield from click_through(client.debug, screens=16, code=B.ResetDevice) # Mnemonic phrases for _ in range(5): for _ in range(5): yield # Phrase screen mnemonic = read_and_confirm_mnemonic(client.debug, words=20) mnemonics.append(mnemonic) yield # Confirm continue to next client.debug.press_yes() # Confirm backup yield client.debug.press_yes() with client: client.set_input_flow(input_flow) client.set_expected_responses([ messages.ButtonRequest(code=B.ResetDevice), messages.ButtonRequest(code=B.ResetDevice), messages.ButtonRequest(code=B.ResetDevice), messages.ButtonRequest(code=B.ResetDevice), messages.ButtonRequest(code=B.ResetDevice), messages.ButtonRequest(code=B.ResetDevice), messages.ButtonRequest(code=B.ResetDevice), # group #1 counts messages.ButtonRequest(code=B.ResetDevice), messages.ButtonRequest(code=B.ResetDevice), # group #2 counts messages.ButtonRequest(code=B.ResetDevice), messages.ButtonRequest(code=B.ResetDevice), # group #3 counts messages.ButtonRequest(code=B.ResetDevice), messages.ButtonRequest(code=B.ResetDevice), # group #4 counts messages.ButtonRequest(code=B.ResetDevice), messages.ButtonRequest(code=B.ResetDevice), # group #5 counts messages.ButtonRequest(code=B.ResetDevice), messages.ButtonRequest(code=B.ResetDevice), # show seeds messages.ButtonRequest(code=B.Success), messages.ButtonRequest(code=B.ResetDevice), messages.ButtonRequest(code=B.Success), messages.ButtonRequest(code=B.ResetDevice), messages.ButtonRequest(code=B.Success), messages.ButtonRequest(code=B.ResetDevice), messages.ButtonRequest(code=B.Success), messages.ButtonRequest(code=B.ResetDevice), messages.ButtonRequest(code=B.Success), messages.ButtonRequest(code=B.ResetDevice), messages.ButtonRequest(code=B.Success), messages.ButtonRequest(code=B.ResetDevice), messages.ButtonRequest(code=B.Success), messages.ButtonRequest(code=B.ResetDevice), messages.ButtonRequest(code=B.Success), messages.ButtonRequest(code=B.ResetDevice), messages.ButtonRequest(code=B.Success), messages.ButtonRequest(code=B.ResetDevice), messages.ButtonRequest(code=B.Success), messages.ButtonRequest(code=B.ResetDevice), messages.ButtonRequest(code=B.Success), messages.ButtonRequest(code=B.ResetDevice), messages.ButtonRequest(code=B.Success), messages.ButtonRequest(code=B.ResetDevice), messages.ButtonRequest(code=B.Success), messages.ButtonRequest(code=B.ResetDevice), messages.ButtonRequest(code=B.Success), messages.ButtonRequest(code=B.ResetDevice), messages.ButtonRequest(code=B.Success), messages.ButtonRequest(code=B.ResetDevice), messages.ButtonRequest(code=B.Success), messages.ButtonRequest(code=B.ResetDevice), messages.ButtonRequest(code=B.Success), messages.ButtonRequest(code=B.ResetDevice), messages.ButtonRequest(code=B.Success), messages.ButtonRequest(code=B.ResetDevice), messages.ButtonRequest(code=B.Success), messages.ButtonRequest(code=B.ResetDevice), messages.ButtonRequest(code=B.Success), messages.ButtonRequest(code=B.ResetDevice), messages.ButtonRequest(code=B.Success), messages.ButtonRequest(code=B.ResetDevice), messages.ButtonRequest(code=B.Success), messages.ButtonRequest(code=B.ResetDevice), messages.ButtonRequest(code=B.Success), messages.ButtonRequest(code=B.ResetDevice), messages.ButtonRequest(code=B.Success), messages.ButtonRequest(code=B.ResetDevice), messages.ButtonRequest(code=B.Success), # show seeds ends here messages.ButtonRequest(code=B.Success), messages.Success(), ]) device.backup(client) client.init_device() assert client.features.initialized is True assert client.features.needs_backup is False assert client.features.unfinished_backup is False assert client.features.no_backup is False assert client.features.backup_type is messages.BackupType.Slip39_Advanced expected_ms = shamir.combine_mnemonics(MNEMONIC_SLIP39_ADVANCED_20) actual_ms = shamir.combine_mnemonics(mnemonics[:3] + mnemonics[5:8] + mnemonics[10:13]) assert expected_ms == actual_ms
def combine_shares(shares): msg = shamir.combine_mnemonics(shares) return msg
def test_passphrase(): mnemonics = shamir.generate_mnemonics(1, [(3, 5)], MS, b"TREZOR")[0] assert MS == shamir.combine_mnemonics(mnemonics[1:4], b"TREZOR") assert MS != shamir.combine_mnemonics(mnemonics[1:4])
def test_basic_sharing_fixed(): mnemonics = shamir.generate_mnemonics(1, [(3, 5)], MS)[0] assert MS == shamir.combine_mnemonics(mnemonics[:3]) assert MS == shamir.combine_mnemonics(mnemonics[1:4]) with pytest.raises(MnemonicError): shamir.combine_mnemonics(mnemonics[1:3])
addr = btc.address.for_p2s(script) if path[0:2] == "84": addr = btc.address.for_p2pkh_wit(hash) xpub = acct.hwif(as_private=False) xprv = acct.hwif(as_private=True) print(f"\n{xprv}\n{xpub}\n{addr}") def main(seed, sss, passphrase): print("Passphrase:", f'"{passphrase}"') print("Master Seed:", seed.hex()) print(sss[-1][-1]) print_keys(seed, ACCT44, None) print_keys(seed, ACCT49, HW49) print_keys(seed, ACCT84, HW84) if __name__ == "__main__": if len(argv) < 3: print(f"Usage:\t{argv[0]}", "<passphrase> <master_seed | shamir-mnemonic>") print('\tHint: Use "" as your passphrase if you have none') else: passphrase = argv[1] args = " ".join(argv[2:]).replace("0x","").strip() if all(c in hexdigits for c in args): sss = generate_mnemonics(1, [(1,1)], bytes.fromhex(args), passphrase.encode(), 0) else: sss = [[args]] seed = combine_mnemonics(sss[-1],passphrase.encode()) main(seed, sss, passphrase)
def test_basic_sharing_random(): mnemonics = shamir.generate_mnemonics_random(1, [(3, 5)])[0] assert shamir.combine_mnemonics(mnemonics[:3]) == shamir.combine_mnemonics( mnemonics[2:])
# Nonsense Passphrase and Master-Seed PP = "Pa55w0rd" MSEC = "0660cc198330660cc198330660cc1983" # SLIP-14 entryopy for fun ACCT44 = '44H/0H/0H' ACCT49 = '49H/0H/0H' ACCT84 = '84H/0H/0H' HW49 = dict(bip32_pub_prefix_hex="049d7cb2", bip32_prv_prefix_hex="049d7878") HW84 = dict(bip32_pub_prefix_hex="04b24746", bip32_prv_prefix_hex="04b2430c") # Can be done MS -> shamir-mnemonic or shamir-mnemonic -> MS SSS = generate_mnemonics(1, [(1,1)], bytes.fromhex(MSEC), PP.encode(), 0) #SSS = [["armed husband academic academic document aquatic wisdom " + # "pleasure lilac response axle parking shaft crazy cargo " + # "dish diet dramatic together unfold"]] seed = combine_mnemonics(SSS[-1],PP.encode()) print(seed.hex()) print(SSS[-1][-1], "\n") # BIP44 derivations and xpub/xprv prefix acct44 = btc.keys.bip32_seed(seed).subkey_for_path(ACCT44) key44 = acct44.subkey_for_path('0/0') addr44 = key44.address() xprv44 = acct44.hwif(as_private=True) xpub44 = acct44.hwif(as_private=False) print(xprv44) print(xpub44) print(addr44, "\n") # BIP49 derivations and ypub/yprv prefix
def test_backup_slip39_advanced(client, click_info: bool): assert client.features.needs_backup is True mnemonics = [] def input_flow(): yield # Checklist client.debug.press_yes() if click_info: yield from click_info_button(client.debug) yield # Set and confirm group count client.debug.press_yes() yield # Checklist client.debug.press_yes() if click_info: yield from click_info_button(client.debug) yield # Set and confirm group threshold client.debug.press_yes() yield # Checklist client.debug.press_yes() for _ in range(5): # for each of 5 groups if click_info: yield from click_info_button(client.debug) yield # Set & Confirm number of shares client.debug.press_yes() if click_info: yield from click_info_button(client.debug) yield # Set & confirm share threshold value client.debug.press_yes() yield # Confirm show seeds client.debug.press_yes() # Mnemonic phrases for _ in range(5): for _ in range(5): # Phrase screen mnemonic = yield from read_and_confirm_mnemonic(client.debug) mnemonics.append(mnemonic) yield # Confirm continue to next client.debug.press_yes() yield # Confirm backup client.debug.press_yes() with client: if click_info: client.watch_layout() client.set_input_flow(input_flow) client.set_expected_responses( [messages.ButtonRequest(code=B.ResetDevice)] * (8 if click_info else 6) # intro screens (and optional info) + [ (click_info, messages.ButtonRequest(code=B.ResetDevice)), messages.ButtonRequest(code=B.ResetDevice), (click_info, messages.ButtonRequest(code=B.ResetDevice)), messages.ButtonRequest(code=B.ResetDevice), ] * 5 # group thresholds (and optional info) + [ messages.ButtonRequest(code=B.ResetDevice), messages.ButtonRequest(code=B.Success), ] * 25 # individual shares + [ messages.ButtonRequest(code=B.Success), messages.Success, messages.Features, ] ) device.backup(client) client.init_device() assert client.features.initialized is True assert client.features.needs_backup is False assert client.features.unfinished_backup is False assert client.features.no_backup is False assert client.features.backup_type is messages.BackupType.Slip39_Advanced expected_ms = shamir.combine_mnemonics(MNEMONIC_SLIP39_ADVANCED_20) actual_ms = shamir.combine_mnemonics( mnemonics[:3] + mnemonics[5:8] + mnemonics[10:13] ) assert expected_ms == actual_ms