Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 26 additions & 3 deletions src/embit/slip39.py
Original file line number Diff line number Diff line change
Expand Up @@ -320,8 +320,18 @@ def split_secret(cls, secret, k, n, randint=secure_randint):
def generate_shares(
cls, mnemonic, k, n, passphrase=b"", exponent=0, randint=secure_randint
):
"""Takes a BIP39 mnemonic along with k, n, passphrase and exponent.
Returns a list of SLIP39 mnemonics, any k of of which, along with the passphrase, recover the secret
"""
Takes a BIP39 mnemonic along with k, n, passphrase and exponent.
Returns a list of SLIP39 mnemonics, any k of which, along with the passphrase, recover the secret.

NOTE: This function is designed for creating NEW wallets from scratch using the BIP39
mnemonic as the source entropy. It does NOT convert an existing BIP39-backed wallet
to SLIP39 format while preserving the same addresses.

For existing BIP39 wallets, converting to SLIP39 while maintaining the same addresses
would require using the BIP39 seed (from mnemonic_to_seed) as input, which generates
512-bit seeds requiring 59-word SLIP39 shares. This conversion is not supported by
embit and is not recommended.
"""
# convert mnemonic to a shared secret
secret = mnemonic_to_bytes(mnemonic)
Expand Down Expand Up @@ -352,7 +362,20 @@ def generate_shares(

@classmethod
def recover_mnemonic(cls, share_mnemonics, passphrase=b""):
"""Recovers the BIP39 mnemonic from a bunch of SLIP39 mnemonics"""
"""
WARNING: This function will be removed in a future version as it is misleading.

This function recovers the original BIP32 seed in BIP39 mnemonic format from
SLIP39 shares, but this is NOT the same as a proper BIP39 mnemonic recovery.
Using the returned mnemonic with standard BIP39 seed derivation (mnemonic_to_seed)
will generate a different seed than the original BIP32 seed, resulting in
a completely different wallet with different addresses.

The function technically returns the correct raw seed data formatted as a BIP39
mnemonic, but this creates confusion as users expect BIP39 mnemonics to work with
standard BIP39 derivation processes. This will cause users to lose access to their
original wallet addresses.
"""
shares = [Share.parse(m) for m in share_mnemonics]
share_set = ShareSet(shares)
secret = share_set.recover(passphrase)
Expand Down
18 changes: 16 additions & 2 deletions tests/tests/test_slip39.py
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,7 @@ def test_recover(self):
"duckling enlarge academic academic agency result length solution fridge kidney coal piece deal husband erode duke ajar critical decision keyboard"
],
"bb54aac4b89dc868ba37d9cc21b2cece",
"xprv9s21ZrQH143K4QViKpwKCpS2zVbz8GrZgpEchMDg6KME9HZtjfL7iThE9w5muQA4YPHKN1u5VM1w8D4pvnjxa2BmpGMfXr7hnRrRHZ93awZ",
],
[
"4. Basic sharing 2-of-3 (128 bits)",
Expand All @@ -270,6 +271,7 @@ def test_recover(self):
"shadow pistol academic acid actress prayer class unknown daughter sweater depict flip twice unkind craft early superior advocate guest smoking",
],
"b43ceb7e57a0ea8766221624d01b0864",
"xprv9s21ZrQH143K2nNuAbfWPHBtfiSCS14XQgb3otW4pX655q58EEZeC8zmjEUwucBu9dPnxdpbZLCn57yx45RBkwJHnwHFjZK4XPJ8SyeYjYg",
],
[
"17. Threshold number of groups and members in each group (128 bits, case 1)",
Expand All @@ -281,6 +283,7 @@ def test_recover(self):
"eraser senior decision smug corner ruin rescue cubic angel tackle skin skunk program roster trash rumor slush angel flea amazing",
],
"7c3397a292a5941682d7a4ae2d898d11",
"xprv9s21ZrQH143K3dzDLfeY3cMp23u5vDeFYftu5RPYZPucKc99mNEddU4w99GxdgUGcSfMpVDxhnR1XpJzZNXRN1m6xNgnzFS5MwMP6QyBRKV",
],
[
"18. Threshold number of groups and members in each group (128 bits, case 2)",
Expand All @@ -290,6 +293,7 @@ def test_recover(self):
"eraser senior decision scared cargo theory device idea deliver modify curly include pancake both news skin realize vitamins away join",
],
"7c3397a292a5941682d7a4ae2d898d11",
"xprv9s21ZrQH143K3dzDLfeY3cMp23u5vDeFYftu5RPYZPucKc99mNEddU4w99GxdgUGcSfMpVDxhnR1XpJzZNXRN1m6xNgnzFS5MwMP6QyBRKV",
],
[
"19. Threshold number of groups and members in each group (128 bits, case 3)",
Expand All @@ -298,13 +302,15 @@ def test_recover(self):
"eraser senior acrobat romp bishop medical gesture pumps secret alive ultimate quarter priest subject class dictate spew material endless market",
],
"7c3397a292a5941682d7a4ae2d898d11",
"xprv9s21ZrQH143K3dzDLfeY3cMp23u5vDeFYftu5RPYZPucKc99mNEddU4w99GxdgUGcSfMpVDxhnR1XpJzZNXRN1m6xNgnzFS5MwMP6QyBRKV",
],
[
"20. Valid mnemonic without sharing (256 bits)",
[
"theory painting academic academic armed sweater year military elder discuss acne wildlife boring employer fused large satoshi bundle carbon diagnose anatomy hamster leaves tracks paces beyond phantom capital marvel lips brave detect luck"
],
"989baf9dcaad5b10ca33dfd8cc75e42477025dce88ae83e75a230086a0e00e92",
"xprv9s21ZrQH143K41mrxxMT2FpiheQ9MFNmWVK4tvX2s28KLZAhuXWskJCKVRQprq9TnjzzzEYePpt764csiCxTt22xwGPiRmUjYUUdjaut8RM",
],
[
"23. Basic sharing 2-of-3 (256 bits)",
Expand All @@ -313,6 +319,7 @@ def test_recover(self):
"humidity disease academic agency actress jacket gross physics cylinder solution fake mortgage benefit public busy prepare sharp friar change work slow purchase ruler again tricycle involve viral wireless mixture anatomy desert cargo upgrade",
],
"c938b319067687e990e05e0da0ecce1278f75ff58d9853f19dcaeed5de104aae",
"xprv9s21ZrQH143K3a4GRMgK8WnawupkwkP6gyHxRsXnMsYPTPH21fWwNcAytijtfyftqNfiaY8LgQVdBQvHZ9FBvtwdjC7LCYxjYruJFuLzyMQ",
],
[
"36. Threshold number of groups and members in each group (256 bits, case 1)",
Expand All @@ -324,6 +331,7 @@ def test_recover(self):
"wildlife deal decision shadow analysis adjust bulb skunk muscle mandate obesity total guitar coal gravity carve slim jacket ruin rebuild ancestor numerous hour mortgage require herd maiden public ceiling pecan pickup shadow club",
],
"5385577c8cfc6c1a8aa0f7f10ecde0a3318493262591e78b8c14c6686167123b",
"xprv9s21ZrQH143K2UspC9FRPfQC9NcDB4HPkx1XG9UEtuceYtpcCZ6ypNZWdgfxQ9dAFVeD1F4Zg4roY7nZm2LB7THPD6kaCege3M7EuS8v85c",
],
[
"37. Threshold number of groups and members in each group (256 bits, case 2)",
Expand All @@ -333,6 +341,7 @@ def test_recover(self):
"wildlife deal decision smug ancestor genuine move huge cubic strategy smell game costume extend swimming false desire fake traffic vegan senior twice timber submit leader payroll fraction apart exact forward pulse tidy install",
],
"5385577c8cfc6c1a8aa0f7f10ecde0a3318493262591e78b8c14c6686167123b",
"xprv9s21ZrQH143K2UspC9FRPfQC9NcDB4HPkx1XG9UEtuceYtpcCZ6ypNZWdgfxQ9dAFVeD1F4Zg4roY7nZm2LB7THPD6kaCege3M7EuS8v85c",
],
[
"38. Threshold number of groups and members in each group (256 bits, case 3)",
Expand All @@ -341,12 +350,17 @@ def test_recover(self):
"wildlife deal acrobat romp anxiety axis starting require metric flexible geology game drove editor edge screw helpful have huge holy making pitch unknown carve holiday numb glasses survive already tenant adapt goat fangs",
],
"5385577c8cfc6c1a8aa0f7f10ecde0a3318493262591e78b8c14c6686167123b",
"xprv9s21ZrQH143K2UspC9FRPfQC9NcDB4HPkx1XG9UEtuceYtpcCZ6ypNZWdgfxQ9dAFVeD1F4Zg4roY7nZm2LB7THPD6kaCege3M7EuS8v85c",
],
]
for test_name, mnemonics, expected in test_cases:
for test_name, mnemonics, expected_secret, expected_xprv in test_cases:
share_set = ShareSet([Share.parse(m) for m in mnemonics])
recovered_secret = share_set.recover(b"TREZOR")
self.assertEqual(
share_set.recover(b"TREZOR"), unhexlify(expected), test_name
recovered_secret, unhexlify(expected_secret), test_name
)
self.assertEqual(
HDKey.from_seed(recovered_secret).to_base58(), expected_xprv, test_name
)

def test_split(self):
Expand Down