Skip to content

Commit fa02f5f

Browse files
committed
Merge branch 'feature/extendable'; v13.1.0
2 parents 012598e + a427590 commit fa02f5f

File tree

14 files changed

+531
-334
lines changed

14 files changed

+531
-334
lines changed

README.org

Lines changed: 156 additions & 153 deletions
Large diffs are not rendered by default.

README.pdf

271 Bytes
Binary file not shown.

README.txt

Lines changed: 162 additions & 153 deletions
Large diffs are not rendered by default.

images/SLIP39-Example.pdf

172 Bytes
Binary file not shown.

slip39/api.py

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333

3434
from shamir_mnemonic import EncryptedMasterSecret, split_ems
3535
from shamir_mnemonic.shamir import _random_identifier, RANDOM_BYTES
36+
from shamir_mnemonic.constants import ID_LENGTH_BITS
3637

3738
import hdwallet
3839
from hdwallet import cryptocurrencies
@@ -981,15 +982,16 @@ def group_parser( group_spec ):
981982

982983
def create(
983984
name: str,
984-
group_threshold: Optional[Union[int,float]] = None, # Default: 1/2 of groups, rounded up
985+
group_threshold: Optional[Union[int,float]] = None, # Default: 1/2 of groups, rounded up
985986
groups: Optional[Union[List[str],Dict[str,Tuple[int, int]]]] = None, # Default: 4 groups (see defaults.py)
986-
master_secret: Optional[Union[str,bytes]] = None, # Default: generate 128-bit Seed Entropy
987+
master_secret: Optional[Union[str,bytes]] = None, # Default: generate 128-bit Seed Entropy
987988
passphrase: Optional[Union[bytes,str]] = None,
988989
using_bip39: Optional[bool] = None, # Produce wallet Seed from master_secret Entropy using BIP-39 generation
989990
iteration_exponent: int = 1,
990991
cryptopaths: Optional[Sequence[Union[str,Tuple[str,str],Tuple[str,str,str]]]] = None, # default: ETH, BTC at default path, format
991-
strength: Optional[int] = None, # Default: 128
992-
extendable: Optional[Union[bool,int]] = None, # Default: True w/ random identifier
992+
strength: Optional[int] = None, # Default: 128
993+
extendable: Optional[bool] = None, # Default: True
994+
identifier: Optional[int] = None, # Default: random identifier
993995
) -> Tuple[str,int,Dict[str,Tuple[int,List[str]]], Sequence[Sequence[Account]], bool]:
994996
"""Creates a SLIP-39 encoding for supplied master_secret Entropy, and 1 or more Cryptocurrency
995997
accounts. Returns the Details, in a form directly compatible with the layout.produce_pdf API.
@@ -1092,7 +1094,8 @@ def create(
10921094
master_secret = master_secret,
10931095
passphrase = passphrase,
10941096
iteration_exponent= iteration_exponent,
1095-
extendable = extendable
1097+
extendable = extendable,
1098+
identifier = identifier,
10961099
)
10971100

10981101
groups = {
@@ -1120,7 +1123,8 @@ def mnemonics(
11201123
passphrase: Optional[Union[bytes,str]] = None,
11211124
iteration_exponent: int = 1,
11221125
strength: int = BITS_DEFAULT,
1123-
extendable: Optional[Tuple[bool,int]] = None,
1126+
extendable: Optional[bool] = None, # Default: True
1127+
identifier: Optional[int] = None, # Default: random identifier
11241128
) -> List[List[str]]:
11251129
"""Generate SLIP39 mnemonics for the supplied master_secret for group_threshold of the given
11261130
groups. Will generate a random master_secret, if necessary.
@@ -1151,11 +1155,15 @@ def mnemonics(
11511155
passphrase = ""
11521156
if isinstance( passphrase, str ):
11531157
passphrase = passphrase.encode( 'UTF-8' )
1158+
extendable = False if extendable is False else True
1159+
identifier = _random_identifier() if identifier is None else int( identifier )
1160+
assert isinstance( identifier, int) and 0 <= identifier < (1 << ID_LENGTH_BITS), \
1161+
"Identifier must be an {ID_LENGTH_BITS}-bit unsigned integer: {identifier}"
11541162
encrypted_secret = EncryptedMasterSecret.from_master_secret(
11551163
master_secret = master_secret,
11561164
passphrase = passphrase,
1157-
identifier = _random_identifier() if extendable in (None, False, True) else extendable,
1158-
extendable = False if extendable is False else True,
1165+
identifier = identifier,
1166+
extendable = extendable,
11591167
iteration_exponent = iteration_exponent,
11601168
)
11611169

slip39/gui/SLIP-39-EXTENDABLE.org

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
#+title: Extendable
2+
#+OPTIONS: toc:nil title:nil author:nil
3+
4+
#+BEGIN_ABSTRACT
5+
SLIP-39 Mnemonics recover a unique (but "valid") Seed and derived wallets, no matter what
6+
/alternative/ passphrase you use! Your /original/ Seed can only be recovered with the originally
7+
specified "correct" passphrase.
8+
9+
Extendable SLIP-39 Mnemonics ensures that all SLIP-39 Mnemonic sets generated from the /original/
10+
Seed and original "correct" passphrase will /always/ result in the same unique Seed for each
11+
/alternative/ passphrase.
12+
13+
Non-Extendable SLIP-39 Mnemonics recover the /original/ Seed with the "correct" passphrase, but
14+
*different* unique Seeds for all /alternative/ passphrases.
15+
#+END_ABSTRACT
16+
17+
* Extendable
18+
19+
The default is now /Extendable/ -- does /not/ use the Identifier to salt the encryption passphrase.
20+
21+
** The Purpose for Multiple Passphrases
22+
23+
Recovering different Seeds for different passphrases is a valuable feature, because you may use
24+
the same SLIP-39 Mnemonic cards, and supply different passphrases to recover different (but valid)
25+
Seeds and sets of derived HD wallets!
26+
27+
- You could have a "distress" passphrase that recovers a decoy wallet containing a
28+
small amount of sacrificial funds, while your real savings are under a different passphrase.
29+
- One password for your personal accounts and another for business accounts.
30+
31+
** Non-Extendable Encoding
32+
33+
Historically, the SLIP-39 encoding used the randomly assigned Identifier to both 1) associate groups
34+
of Mnemonics belonging to the same set, but /also/ 2) to salt the Seed encryption.
35+
36+
This meant that: if you created 2 sets of SLIP-39 Mnemonics for the same Seed -- each set would
37+
lead to */same/* Seed with the "correct" original passphrase, but to */different/* Seeds with
38+
each "distress" passphrase!
39+
40+
Unless all sets of SLIP-39 Mnemonics lead to the same Seeds for each passphrase, you are
41+
restricted to ever issue /only one/ set of SLIP-39 Mnemonics for each Seed! You lose the ability
42+
to recover other "distress" passphrase Seeds from the new sets of Mnemonics!
43+
44+
** Issuing Multiple SLIP-39 Mnemonic Sets
45+
46+
You may want to issue a simple set of SLIP-39 Mnemonics for your Seed to begin with, and then
47+
(later) decide to issue a more elaborate set of SLIP-39 Mnenmonic cards.
48+
49+
Only with Extendable SLIP-39 Mnemonics, will the /alternative/ passphrase Seeds and derived
50+
wallets be consistent.
51+
52+
* Recovery
53+
54+
The SLIP-39 App supports recovery from both Extendable and (historic) non-Extendable SLIP-39
55+
Mnemonics.
56+
57+
** Using [[https://iancoleman.io/slip39]]
58+
59+
Until the website is updated, you cannot (as of Dec 2024) use it to recover your Seed from
60+
Extendable SLIP-39 Mnemonics.

slip39/gui/SLIP-39-EXTENDABLE.txt

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
SLIP-39 Mnemonics recover a unique (but "valid") Seed and derived
2+
wallets, no matter what /alternative/ passphrase you use! Your
3+
/original/ Seed can only be recovered with the originally specified
4+
"correct" passphrase.
5+
6+
Extendable SLIP-39 Mnemonics ensures that all SLIP-39 Mnemonic sets
7+
generated from the /original/ Seed and original "correct" passphrase
8+
will /always/ result in the same unique Seed for each /alternative/
9+
passphrase.
10+
11+
Non-Extendable SLIP-39 Mnemonics recover the /original/ Seed with the
12+
"correct" passphrase, but *different* unique Seeds for all /alternative/
13+
passphrases.
14+
15+
16+
1 Extendable
17+
════════════
18+
19+
The default is now /Extendable/ – does /not/ use the Identifier to
20+
salt the encryption passphrase.
21+
22+
23+
1.1 The Purpose for Multiple Passphrases
24+
────────────────────────────────────────
25+
26+
Recovering different Seeds for different passphrases is a valuable
27+
feature, because you may use the same SLIP-39 Mnemonic cards, and
28+
supply different passphrases to recover different (but valid) Seeds
29+
and sets of derived HD wallets!
30+
31+
• You could have a "distress" passphrase that recovers a decoy wallet
32+
containing a small amount of sacrificial funds, while your real
33+
savings are under a different passphrase.
34+
• One password for your personal accounts and another for business
35+
accounts.
36+
37+
38+
1.2 Non-Extendable Encoding
39+
───────────────────────────
40+
41+
Historically, the SLIP-39 encoding used the randomly assigned
42+
Identifier to both 1) associate groups of Mnemonics belonging to the
43+
same set, but /also/ 2) to salt the Seed encryption.
44+
45+
This meant that: if you created 2 sets of SLIP-39 Mnemonics for the
46+
same Seed – each set would lead to */same/* Seed with the "correct"
47+
original passphrase, but to */different/* Seeds with each "distress"
48+
passphrase!
49+
50+
Unless all sets of SLIP-39 Mnemonics lead to the same Seeds for each
51+
passphrase, you are restricted to ever issue /only one/ set of SLIP-39
52+
Mnemonics for each Seed! You lose the ability to recover other
53+
"distress" passphrase Seeds from the new sets of Mnemonics!
54+
55+
56+
1.3 Issuing Multiple SLIP-39 Mnemonic Sets
57+
──────────────────────────────────────────
58+
59+
You may want to issue a simple set of SLIP-39 Mnemonics for your Seed
60+
to begin with, and then (later) decide to issue a more elaborate set
61+
of SLIP-39 Mnenmonic cards.
62+
63+
Only with Extendable SLIP-39 Mnemonics, will the /alternative/
64+
passphrase Seeds and derived wallets be consistent.
65+
66+
67+
2 Recovery
68+
══════════
69+
70+
The SLIP-39 App supports recovery from both Extendable and (historic)
71+
non-Extendable SLIP-39 Mnemonics.
72+
73+
74+
2.1 Using <https://iancoleman.io/slip39>
75+
────────────────────────────────────────
76+
77+
Until the website is updated, you cannot (as of Dec 2024) use it to
78+
recover your Seed from Extendable SLIP-39 Mnemonics.

slip39/gui/SLIP-39-SE-SIGS.org

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
#+OPTIONS: toc:nil title:nil author:nil
33

44
#+BEGIN_ABSTRACT
5-
Bad Entropy is risk to Cryptocurrency HD Wallet Seed Secrets!
5+
Bad Entropy is a risk to Cryptocurrency HD Wallet Seed Secrets!
66

77
Avoid Harmonic and Shannon Entropy Deficiencies:
88
- Use strong cryptographically secure randomness for your Seed Data

slip39/gui/SLIP-39-SE-SIGS.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
Bad Entropy is risk to Cryptocurrency HD Wallet Seed Secrets!
1+
Bad Entropy is a risk to Cryptocurrency HD Wallet Seed Secrets!
22

33
Avoid Harmonic and Shannon Entropy Deficiencies:
44
• Use strong cryptographically secure randomness for your Seed Data

slip39/gui/main.py

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@
5757
"academic acid beard romp believe impulse species holiday demand building" \
5858
" earth warn lunar olympic clothes piece campus alpha short endless"
5959

60-
SD_SEED_FRAME = 'Seed Source: Create your Seed Entropy here'
60+
SD_SEED_FRAME = 'Seed Source: Create/Recover Seed Entropy here'
6161
SE_SEED_FRAME = 'Seed Extra Randomness'
6262
SS_SEED_FRAME = 'Seed Secret & SLIP-39 Recovery Groups'
6363

@@ -269,16 +269,17 @@ def groups_layout(
269269
],
270270
] + [
271271
[
272-
# SLIP-39 only available in Recovery; SLIP-39 Passphrase only in Pro; BIP-39 and Fixed Hex only in Pro
272+
# SLIP-39 only available in Recovery; SLIP-39 Passphrase only in Pro
273273
sg.Frame( SD_SEED_FRAME, [
274274
[
275275
sg.Text( "Random:" if not LO_BAK else "Source:", visible=LO_CRE, **T_hue( T_kwds, 0/20 )),
276-
sg.Radio( "128-bit", "SD", key='-SD-128-RND-', default=LO_CRE,
276+
sg.Radio( "128-bit", "SD", key='-SD-128-RND-', visible=LO_CRE, **T_hue( B_kwds, 0/20 )),
277+
sg.Radio( "256-bit", "SD", key='-SD-256-RND-', default=LO_CRE and not LO_REC,
277278
visible=LO_CRE, **T_hue( B_kwds, 0/20 )),
278-
sg.Radio( "256-bit", "SD", key='-SD-256-RND-', visible=LO_CRE, **T_hue( B_kwds, 0/20 )),
279279
sg.Radio( "512-bit", "SD", key='-SD-512-RND-', visible=LO_PRO, **T_hue( B_kwds, 0/20 )),
280280
sg.Text( "Recover:", visible=LO_CRE, **T_hue( T_kwds, 2/20 )),
281-
sg.Radio( "SLIP-39", "SD", key='-SD-SLIP-', visible=LO_REC, **T_hue( B_kwds, 2/20 )),
281+
sg.Radio( "SLIP-39", "SD", key='-SD-SLIP-', default=LO_REC,
282+
visible=LO_REC, **T_hue( B_kwds, 2/20 )),
282283
sg.Radio( "BIP-39", "SD", key='-SD-BIP-', default=LO_BAK,
283284
visible=LO_CRE, **T_hue( B_kwds, 2/20 )),
284285
sg.Radio( "BIP-39 Seed", "SD", key='-SD-BIP-SEED-', visible=LO_PRO, **T_hue( B_kwds, 2/20 )),
@@ -367,6 +368,9 @@ def groups_layout(
367368
sg.Text( f"of {len(groups)}", key='-RECOVERY-', **T_kwds ),
368369
sg.Button( '+', **B_kwds ),
369370
sg.Text( "Mnemonic Card Groups", **T_kwds ),
371+
sg.Checkbox( "Extendable", key='-EXTENDABLE-', visible=LO_CRE,
372+
default=True,
373+
size=prefix, **T_hue( B_kwds, +1/20 )),
370374
],
371375
group_body,
372376
] ),
@@ -595,12 +599,12 @@ def update_seed_data( event, window, values ):
595599
# We're recovering the BIP-39 Seed Phrase *Entropy*, NOT the derived (decrypted) 512-bit
596600
# Seed Data! So, we don't deal in Passphrases, here. The Passphrase (to encrypt the Seed,
597601
# when "Using BIP-39") is only required to display the correct wallet addresses.
598-
window['-SD-DATA-F-'].update( "BIP-39 Mnemonic to Back Up: " )
602+
window['-SD-DATA-F-'].update( "BIP-39 Mnemonic to Recover or Back Up: " )
599603
window['-SD-DATA-F-'].update( visible=True )
600604
window['-SD-PASS-C-'].update( visible=False )
601605
window['-SD-PASS-F-'].update( visible=False )
602606
elif 'SLIP' in update_seed_data.src:
603-
window['-SD-DATA-F-'].update( "SLIP-39 Mnemonics to Back Up: " )
607+
window['-SD-DATA-F-'].update( "SLIP-39 Mnemonics to Recover or Back Up: " )
604608
window['-SD-DATA-F-'].update( visible=True )
605609
window['-SD-PASS-C-'].update( visible=True )
606610
window['-SD-PASS-F-'].update(
@@ -1073,6 +1077,7 @@ def app(
10731077
events_ignored = ('-MNEMONICS-'+sg.WRITE_ONLY_KEY,)
10741078
master_secret = None # default to produce randomly
10751079
details = None # The SLIP-39 details produced from groups; make None to force SLIP-39 Mnemonic update
1080+
extendable = True
10761081
cryptopaths = None
10771082
timeout = 0 # First time thru; refresh immediately; functions req. refresh may adjust via values['__TIMEOUT__']
10781083
instructions = '' # The last instructions .txt payload found
@@ -1441,6 +1446,11 @@ def deficiency( *deficiencies ):
14411446
# We avoid recomputing this unless something about the seed or the recovered groups changes;
14421447
# each time we recompute -- even without any changes -- the SLIP-39 Mnemonics will change,
14431448
# due to the use of entropy in the SLIP-39 process.
1449+
extendable_rec = values['-EXTENDABLE-']
1450+
if extendable_rec != extendable:
1451+
extendable = extendable_rec
1452+
details = None
1453+
14441454
if not details or names[0] not in details:
14451455
log.info( f"SLIP39 details for {names}..." )
14461456
try:
@@ -1457,6 +1467,7 @@ def deficiency( *deficiencies ):
14571467
passphrase = passphrase,
14581468
using_bip39 = using_bip39,
14591469
cryptopaths = cryptopaths,
1470+
extendable = extendable,
14601471
)
14611472
except Exception as exc:
14621473
status = f"Error creating: {exc}"
@@ -1532,7 +1543,7 @@ def deficiency( *deficiencies ):
15321543
continue
15331544
name_len = max( len( name ) for name in details )
15341545
status = '\n'.join(
1535-
f"Saved {name}"
1546+
f"{'Print' if printer else 'Saved'}: {name}"
15361547
for name in details
15371548
)
15381549
# Finally, success has been assured; turn off emboldened status line

0 commit comments

Comments
 (0)