def test_basic_sharing_random(self): ems = random.bytes(32) identifier = slip39.generate_random_identifier() mnemonics = slip39.split_ems(1, [(3, 5)], identifier, 1, ems) mnemonics = mnemonics[0] self.assertEqual(slip39.recover_ems(mnemonics[:3]), slip39.recover_ems(mnemonics[2:]))
def test_vectors(self): for mnemonics, secret in vectors: if secret: identifier, exponent, ems = slip39.recover_ems(mnemonics) self.assertEqual( slip39.decrypt(ems, b"TREZOR", exponent, identifier), unhexlify(secret)) else: with self.assertRaises(slip39.MnemonicError): slip39.recover_ems(mnemonics)
def test_basic_sharing_fixed(self): generated_identifier = slip39.generate_random_identifier() mnemonics = slip39.split_ems(1, [(3, 5)], generated_identifier, 1, self.EMS) mnemonics = mnemonics[0] identifier, exponent, ems = slip39.recover_ems(mnemonics[:3]) self.assertEqual(ems, self.EMS) self.assertEqual(generated_identifier, identifier) self.assertEqual(slip39.recover_ems(mnemonics[1:4])[2], ems) with self.assertRaises(slip39.MnemonicError): slip39.recover_ems(mnemonics[1:3])
def test_iteration_exponent(self): identifier = slip39.generate_random_identifier() mnemonics = slip39.split_ems(1, [(3, 5)], identifier, 1, self.EMS) mnemonics = mnemonics[0] identifier, exponent, ems = slip39.recover_ems(mnemonics[1:4]) self.assertEqual(ems, self.EMS) identifier = slip39.generate_random_identifier() mnemonics = slip39.split_ems(1, [(3, 5)], identifier, 2, self.EMS) mnemonics = mnemonics[0] identifier, exponent, ems = slip39.recover_ems(mnemonics[1:4]) self.assertEqual(ems, self.EMS)
def test_group_sharing(self): group_threshold = 2 group_sizes = (5, 3, 5, 1) member_thresholds = (3, 2, 2, 1) identifier = slip39.generate_random_identifier() mnemonics = slip39.split_ems(group_threshold, list(zip(member_thresholds, group_sizes)), identifier, 1, self.EMS) # 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) random.shuffle(mnemonic_subset) identifier, exponent, ems = slip39.recover_ems( mnemonic_subset) self.assertEqual(ems, self.EMS) # Minimal sets of mnemonics. identifier, exponent, ems = slip39.recover_ems( [mnemonics[2][0], mnemonics[2][2], mnemonics[3][0]]) self.assertEqual(ems, self.EMS) self.assertEqual( slip39.recover_ems( [mnemonics[2][3], mnemonics[3][0], mnemonics[2][4]])[2], ems) # One complete group and one incomplete group out of two groups required. with self.assertRaises(slip39.MnemonicError): slip39.recover_ems(mnemonics[0][2:] + [mnemonics[1][0]]) # One group of two required. with self.assertRaises(slip39.MnemonicError): slip39.recover_ems(mnemonics[0][1:4])
def test_slip39_128(self): mnemonics = [ "extra extend academic bishop cricket bundle tofu goat apart victim " "enlarge program behavior permit course armed jerky faint language modern", "extra extend academic acne away best indicate impact square oasis " "prospect painting voting guest either argue username racism enemy eclipse", "extra extend academic arcade born dive legal hush gross briefing " "talent drug much home firefly toxic analysis idea umbrella slice" ] passphrase = b"TREZOR" identifier, exponent, ems = slip39.recover_ems(mnemonics) master_secret = slip39.decrypt(ems, passphrase, exponent, identifier) node = bip32.from_seed(master_secret, "ed25519 cardano seed") node.derive_cardano(0x80000000 | 44) node.derive_cardano(0x80000000 | 1815) keychain = Keychain([0x80000000 | 44, 0x80000000 | 1815], node) # 44'/1815'/0'/0/i derivation_paths = [ [0x80000000 | 44, 0x80000000 | 1815, 0x80000000, 0, 0], [0x80000000 | 44, 0x80000000 | 1815, 0x80000000, 0, 1], [0x80000000 | 44, 0x80000000 | 1815, 0x80000000, 0, 2] ] public_keys = [ b'bc043d84b8b891d49890edb6aced6f2d78395f255c5b6aea8878b913f83e8579', b'24c4fe188a39103db88818bc191fd8571eae7b284ebcbdf2462bde97b058a95c', b'831a63d381a8dab1e6e1ee991a4300fc70687aae5f97f4fcf92ed1b6c2bd99de' ] chain_codes = [ b"dc3f0d2b5cccb822335ef6213fd133f4ca934151ec44a6000aee43b8a101078c", b"6f7a744035f4b3ddb8f861c18446169643cc3ae85e271b4b4f0eda05cf84c65b", b"672d6af4707aba201b7940231e83dd357f92f8851b3dfdc224ef311e1b64cdeb" ] xpub_keys = [ "bc043d84b8b891d49890edb6aced6f2d78395f255c5b6aea8878b913f83e8579dc3f0d2b5cccb822335ef6213fd133f4ca934151ec44a6000aee43b8a101078c", "24c4fe188a39103db88818bc191fd8571eae7b284ebcbdf2462bde97b058a95c6f7a744035f4b3ddb8f861c18446169643cc3ae85e271b4b4f0eda05cf84c65b", "831a63d381a8dab1e6e1ee991a4300fc70687aae5f97f4fcf92ed1b6c2bd99de672d6af4707aba201b7940231e83dd357f92f8851b3dfdc224ef311e1b64cdeb" ] for index, derivation_path in enumerate(derivation_paths): key = _get_public_key(keychain, derivation_path) self.assertEqual(hexlify(key.node.public_key), public_keys[index]) self.assertEqual(hexlify(key.node.chain_code), chain_codes[index]) self.assertEqual(key.xpub, xpub_keys[index])
def test_slip39_256(self): mnemonics = [ "hobo romp academic axis august founder knife legal recover alien expect " "emphasis loan kitchen involve teacher capture rebuild trial numb spider forward " "ladle lying voter typical security quantity hawk legs idle leaves gasoline", "hobo romp academic agency ancestor industry argue sister scene midst graduate " "profile numb paid headset airport daisy flame express scene usual welcome " "quick silent downtown oral critical step remove says rhythm venture aunt" ] passphrase = b"TREZOR" identifier, exponent, ems = slip39.recover_ems(mnemonics) master_secret = slip39.decrypt(ems, passphrase, exponent, identifier) node = bip32.from_seed(master_secret, "ed25519 cardano seed") node.derive_cardano(0x80000000 | 44) node.derive_cardano(0x80000000 | 1815) keychain = Keychain([0x80000000 | 44, 0x80000000 | 1815], node) # 44'/1815'/0'/0/i derivation_paths = [ [0x80000000 | 44, 0x80000000 | 1815, 0x80000000, 0, 0], [0x80000000 | 44, 0x80000000 | 1815, 0x80000000, 0, 1], [0x80000000 | 44, 0x80000000 | 1815, 0x80000000, 0, 2] ] public_keys = [ b"967a9a041ad1379e31c2c7f2aa4bc2b3f7769341c0ea89ccfb12a904f2e10877", b"6f3805bbc1b7a75afa95dffec331671f3c4662800615e80d2ec1202a9d874c86", b"7f145b50ef07fb9accc40ee07a01fe93ceb6fa07d5a9f20fc3c8a48246dd4d02", ] chain_codes = [ b"7b15d8d9006afe3cd7e04f375a1126a8c7c7c07c59a6f0c5b0310f4245f4edbb", b"44baf30fd549e6a1e05f99c2a2c8971aea8894ee8d9c5fc2c5ae6ee839a56b2d", b"e67d2864614ada5eec8fb8ee1225a94a6fb0a1b3c347c854ec3037351c6a0fc7", ] xpub_keys = [ "967a9a041ad1379e31c2c7f2aa4bc2b3f7769341c0ea89ccfb12a904f2e108777b15d8d9006afe3cd7e04f375a1126a8c7c7c07c59a6f0c5b0310f4245f4edbb", "6f3805bbc1b7a75afa95dffec331671f3c4662800615e80d2ec1202a9d874c8644baf30fd549e6a1e05f99c2a2c8971aea8894ee8d9c5fc2c5ae6ee839a56b2d", "7f145b50ef07fb9accc40ee07a01fe93ceb6fa07d5a9f20fc3c8a48246dd4d02e67d2864614ada5eec8fb8ee1225a94a6fb0a1b3c347c854ec3037351c6a0fc7", ] for index, derivation_path in enumerate(derivation_paths): key = _get_public_key(keychain, derivation_path) self.assertEqual(hexlify(key.node.public_key), public_keys[index]) self.assertEqual(hexlify(key.node.chain_code), chain_codes[index]) self.assertEqual(key.xpub, xpub_keys[index])
def test_group_sharing_threshold_1(self): group_threshold = 1 group_sizes = (5, 3, 5, 1) member_thresholds = (3, 2, 2, 1) identifier = slip39.generate_random_identifier() mnemonics = slip39.split_ems(group_threshold, list(zip(member_thresholds, group_sizes)), identifier, 1, self.EMS) # 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) random.shuffle(mnemonic_subset) identifier, exponent, ems = slip39.recover_ems(mnemonic_subset) self.assertEqual(ems, self.EMS)
async def load_device(ctx, msg): word_count = _validate(msg) is_slip39 = backup_types.is_slip39_word_count(word_count) if not is_slip39 and not msg.skip_checksum and not bip39.check( msg.mnemonics[0]): raise wire.ProcessError("Mnemonic is not valid") await _warn(ctx) if not is_slip39: # BIP-39 secret = msg.mnemonics[0].encode() backup_type = BackupType.Bip39 else: identifier, iteration_exponent, secret = slip39.recover_ems( msg.mnemonics) # this must succeed if the recover_ems call succeeded share = slip39.decode_mnemonic(msg.mnemonics[0]) if share.group_count == 1: backup_type = BackupType.Slip39_Basic elif share.group_count > 1: backup_type = BackupType.Slip39_Advanced else: raise wire.ProcessError("Invalid group count") storage.device.set_slip39_identifier(identifier) storage.device.set_slip39_iteration_exponent(iteration_exponent) storage.device.store_mnemonic_secret( secret, backup_type, needs_backup=msg.needs_backup is True, no_backup=msg.no_backup is True, ) storage.device.set_passphrase_enabled(msg.passphrase_protection) storage.device.set_label(msg.label or "") if msg.pin: config.change_pin(pin_to_int(""), pin_to_int(msg.pin), None, None) return Success(message="Device loaded")
def process_slip39(words: str) -> Tuple[Optional[bytes], slip39.Share]: """ Processes a single mnemonic share. Returns the encrypted master secret (or None if more shares are needed) and the share's group index and member index. """ share = slip39.decode_mnemonic(words) remaining = storage.recovery.fetch_slip39_remaining_shares() # if this is the first share, parse and store metadata if not remaining: storage.recovery.set_slip39_group_count(share.group_count) storage.recovery.set_slip39_iteration_exponent( share.iteration_exponent) storage.recovery.set_slip39_identifier(share.identifier) storage.recovery.set_slip39_remaining_shares(share.threshold - 1, share.group_index) storage.recovery_shares.set(share.index, share.group_index, words) # if share threshold and group threshold are 1 # we can calculate the secret right away if share.threshold == 1 and share.group_threshold == 1: identifier, iteration_exponent, secret = slip39.recover_ems( [words]) return secret, share else: # we need more shares return None, share # These should be checked by UI before so it's a Runtime exception otherwise if share.identifier != storage.recovery.get_slip39_identifier(): raise RuntimeError("Slip39: Share identifiers do not match") if share.iteration_exponent != storage.recovery.get_slip39_iteration_exponent( ): raise RuntimeError("Slip39: Share exponents do not match") if storage.recovery_shares.get(share.index, share.group_index): raise RuntimeError("Slip39: This mnemonic was already entered") if share.group_count != storage.recovery.get_slip39_group_count(): raise RuntimeError("Slip39: Group count does not match") remaining_for_share = (storage.recovery.get_slip39_remaining_shares( share.group_index) or share.threshold) storage.recovery.set_slip39_remaining_shares(remaining_for_share - 1, share.group_index) remaining[share.group_index] = remaining_for_share - 1 storage.recovery_shares.set(share.index, share.group_index, words) if remaining.count(0) < share.group_threshold: # we need more shares return None, share if share.group_count > 1: mnemonics = [] for i, r in enumerate(remaining): # if we have multiple groups pass only the ones with threshold reached if r == 0: group = storage.recovery_shares.fetch_group(i) mnemonics.extend(group) else: # in case of slip39 basic we only need the first and only group mnemonics = storage.recovery_shares.fetch_group(0) identifier, iteration_exponent, secret = slip39.recover_ems(mnemonics) return secret, share
def test_slip39_256(self): mnemonics = [ "hobo romp academic axis august founder knife legal recover alien expect " "emphasis loan kitchen involve teacher capture rebuild trial numb spider forward " "ladle lying voter typical security quantity hawk legs idle leaves gasoline", "hobo romp academic agency ancestor industry argue sister scene midst graduate " "profile numb paid headset airport daisy flame express scene usual welcome " "quick silent downtown oral critical step remove says rhythm venture aunt" ] passphrase = b"TREZOR" identifier, exponent, ems = slip39.recover_ems(mnemonics) master_secret = slip39.decrypt(ems, passphrase, exponent, identifier) node = bip32.from_seed(master_secret, "ed25519 cardano seed") # Check root node. root_priv = b"90633724b5daf770a8b420b8658e7d8bc21e066b60ec8cd4d5730681cc294e4f" root_ext = b"f9d99bf3cd9c7e12663e8646afa40cb3aecf15d91f2abc15d21056c6bccb3414" root_pub = b"eea170f0ef97b59d22907cb429888029721ed67d3e7a1b56b81731086ab7db64" root_chain = b"04f1de750b62725fcc1ae1b93ca4063acb53c486b959cadaa100ebd7828e5460" self.assertEqual(hexlify(node.private_key()), root_priv) self.assertEqual(hexlify(node.private_key_ext()), root_ext) self.assertEqual(hexlify(seed.remove_ed25519_prefix(node.public_key())), root_pub) self.assertEqual(hexlify(node.chain_code()), root_chain) # Check derived nodes and addresses. node.derive_cardano(0x80000000 | 44) node.derive_cardano(0x80000000 | 1815) keychain = Keychain([0x80000000 | 44, 0x80000000 | 1815], node) nodes = [ ( "Ae2tdPwUPEYyDD1C2FbVJFAE3FuAxLspfMYt29TJ1urnSKr57cVhEcioSCC", b"38e8a4b17ca07b6a309f1cee83f87593e34a1fc3a289785ea451ef65df294e4f", b"405d10ef71c2b0019250d11837de8db825d8556bf1e57f8866920af6d8c90002", b"967a9a041ad1379e31c2c7f2aa4bc2b3f7769341c0ea89ccfb12a904f2e10877", b"7b15d8d9006afe3cd7e04f375a1126a8c7c7c07c59a6f0c5b0310f4245f4edbb", ), ( "Ae2tdPwUPEZHJGtyz47F6wD7qAegt1JNRJWuiE36QLvFzeqJPBZ2EBvhr8M", b"a09f90e3f76a7bdb7f8721cc0c142dbd6398fd704b83455e123fa886dc294e4f", b"917e4166bb404def9f12634e84ecbcb98afdea051ba7c38745e208178a9e9baf", b"6f3805bbc1b7a75afa95dffec331671f3c4662800615e80d2ec1202a9d874c86", b"44baf30fd549e6a1e05f99c2a2c8971aea8894ee8d9c5fc2c5ae6ee839a56b2d", ), ( "Ae2tdPwUPEYxD9xNPBJTzYmtFVVWEPB6KW4TCDijQ4pDwU11wt5621PyCi4", b"78dd824aea33bed5c1502d1a17f11a4adbe923aac1cd1f7ae98c9506db294e4f", b"ddfe7f27e2894b983df773d8ac2a07973fc37ff36e93a2f2d71fb7327d4e18f4", b"7f145b50ef07fb9accc40ee07a01fe93ceb6fa07d5a9f20fc3c8a48246dd4d02", b"e67d2864614ada5eec8fb8ee1225a94a6fb0a1b3c347c854ec3037351c6a0fc7", ) ] for i, (address, priv, ext, pub, chain) in enumerate(nodes): # 44'/1815'/0'/0/i a, n = derive_address_and_node(keychain, [0x80000000 | 44, 0x80000000 | 1815, 0x80000000, 0, i]) self.assertEqual(a, address) self.assertEqual(hexlify(n.private_key()), priv) self.assertEqual(hexlify(n.private_key_ext()), ext) self.assertEqual(hexlify(seed.remove_ed25519_prefix(n.public_key())), pub) self.assertEqual(hexlify(n.chain_code()), chain)
def test_slip39_128(self): mnemonics = [ "extra extend academic bishop cricket bundle tofu goat apart victim " "enlarge program behavior permit course armed jerky faint language modern", "extra extend academic acne away best indicate impact square oasis " "prospect painting voting guest either argue username racism enemy eclipse", "extra extend academic arcade born dive legal hush gross briefing " "talent drug much home firefly toxic analysis idea umbrella slice" ] passphrase = b"TREZOR" identifier, exponent, ems = slip39.recover_ems(mnemonics) master_secret = slip39.decrypt(ems, passphrase, exponent, identifier) node = bip32.from_seed(master_secret, "ed25519 cardano seed") # Check root node. root_priv = b"c0fe4a6973df4de06262693fc9186f71faf292960350882d49456bf108d13954" root_ext = b"4064253ffefc4127489bce1b825a47329010c5afb4d21154ef949ef786204405" root_pub = b"83e3ecaf57f90f022c45e10d1b8cb78499c30819515ad9a81ad82139fdb12a90" root_chain = b"22c12755afdd192742613b3062069390743ea232bc1b366c8f41e37292af9305" self.assertEqual(hexlify(node.private_key()), root_priv) self.assertEqual(hexlify(node.private_key_ext()), root_ext) self.assertEqual(hexlify(seed.remove_ed25519_prefix(node.public_key())), root_pub) self.assertEqual(hexlify(node.chain_code()), root_chain) # Check derived nodes and addresses. node.derive_cardano(0x80000000 | 44) node.derive_cardano(0x80000000 | 1815) keychain = Keychain([0x80000000 | 44, 0x80000000 | 1815], node) nodes = [ ( "Ae2tdPwUPEYxF9NAMNdd3v2LZoMeWp7gCZiDb6bZzFQeeVASzoP7HC4V9s6", b"e0acfe234aa6e1219ce7d3d8d91853e0808bab92ecb8a0ff0f345ff31ad13954", b"ff89dc71365c4b67bb7bb75d566e65b8a95f16e4d70cce51c25937db15614530", b"bc043d84b8b891d49890edb6aced6f2d78395f255c5b6aea8878b913f83e8579", b"dc3f0d2b5cccb822335ef6213fd133f4ca934151ec44a6000aee43b8a101078c", ), ( "Ae2tdPwUPEZ1TjYcvfkWAbiHtGVxv4byEHHZoSyQXjPJ362DifCe1ykgqgy", b"d0ce3e7a6445bc91801319b9bbaf47fdfca9364257295fb13bc5046a20d13954", b"c800359abdc875944754ae7368bab7ef75184d48816c368f5a28af4bcf1d1ee8", b"24c4fe188a39103db88818bc191fd8571eae7b284ebcbdf2462bde97b058a95c", b"6f7a744035f4b3ddb8f861c18446169643cc3ae85e271b4b4f0eda05cf84c65b", ), ( "Ae2tdPwUPEZGXmSbda1kBNfyhRQGRcQxJFdk7mhWZXAGnapyejv2b2U3aRb", b"e8320644cce22a6e9fc33865fc5a598b1cda061c47a548aead3af4ed1cd13954", b"9e2ece5d7fe8119cb76090009be926a84fc5d3b95855b5962ffe2f880836cf09", b"831a63d381a8dab1e6e1ee991a4300fc70687aae5f97f4fcf92ed1b6c2bd99de", b"672d6af4707aba201b7940231e83dd357f92f8851b3dfdc224ef311e1b64cdeb" ) ] for i, (address, priv, ext, pub, chain) in enumerate(nodes): # 44'/1815'/0'/0/i a, n = derive_address_and_node(keychain, [0x80000000 | 44, 0x80000000 | 1815, 0x80000000, 0, i]) self.assertEqual(a, address) self.assertEqual(hexlify(n.private_key()), priv) self.assertEqual(hexlify(n.private_key_ext()), ext) self.assertEqual(hexlify(seed.remove_ed25519_prefix(n.public_key())), pub) self.assertEqual(hexlify(n.chain_code()), chain)