Skip to content

Commit 8f4eaf1

Browse files
committed
Improve card layouts and colors
o Change default Frens group to 3/6 o Add card size selection radio buttons
1 parent 4cb729a commit 8f4eaf1

File tree

11 files changed

+299
-286
lines changed

11 files changed

+299
-286
lines changed

GNUmakefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ dist/slip39-$(VERSION)-py3-none-any.whl: build-check FORCE
5858

5959
# Install from wheel, including all optional extra dependencies
6060
install: dist/slip39-$(VERSION)-py3-none-any.whl FORCE
61-
$(PY3) -m pip install --force-reinstall $^[gui,serial,json]
61+
$(PY3) -m pip install --force-reinstall $<[gui,serial,json]
6262

6363
# Generate, Sign and Zip the macOS SLIP39.app GUI package
6464
app: dist/SLIP39.app-$(VERSION).zip

README.org

Lines changed: 126 additions & 124 deletions
Large diffs are not rendered by default.

README.pdf

-273 Bytes
Binary file not shown.

README.txt

Lines changed: 114 additions & 113 deletions
Large diffs are not rendered by default.

slip39/api.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -247,14 +247,16 @@ def mnemonics(
247247
master_secret: Union[str,bytes] = None,
248248
passphrase: bytes = b"",
249249
iteration_exponent: int = 1,
250+
strength: int = 128,
250251
) -> List[List[str]]:
251252
"""Generate SLIP39 mnemonics for the supplied group_threshold of the given groups. Will generate a
252253
random master_secret, if necessary.
253254
254255
"""
255256
if master_secret is None:
256-
master_secret = random_secret()
257-
if len( master_secret ) not in (16, 32, 64):
257+
assert strength in BITS, f"Invalid {strength}-bit secret length specified"
258+
master_secret = random_secret( strength // 8 )
259+
if len( master_secret ) * 8 not in BITS:
258260
raise ValueError(
259261
f"Only 128-, 256- and 512bit seeds supported; {len(master_secret)*8}-bit master_secret supplied" )
260262
return generate_mnemonics(

slip39/api_passphrase_test.py

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -81,14 +81,14 @@ def test_passphrase():
8181
]
8282
),
8383
"Frens": (
84-
2,
84+
3,
8585
[
86-
"academic acid decision roster alto density aircraft wavy pistol capacity receiver username dismiss famous walnut lunch acquire counter fused step",
87-
"academic acid decision scared bike sunlight explain render emphasis activity exotic verdict machine enforce facility grasp husband warmth famous club",
88-
"academic acid decision shadow deadline wildlife warmth smell device clogs craft volume cargo item scramble easy grumpy budget easy forward",
89-
"academic acid decision sister dragon belong python marathon stay dismiss snapshot victim process holiday hesitate presence both sidewalk exclude petition",
90-
"academic acid decision smug beard resident modify much minister hesitate memory expand depend alarm unwrap analysis envelope pickup raspy lunar",
91-
"academic acid decision spew aircraft false stilt stadium lawsuit idle intend example library bike energy ticket discuss island resident jerky"
86+
"academic acid decision round academic academic academic academic academic academic academic academic academic academic academic academic academic ranked flame amount",
87+
"academic acid decision scatter biology trial escape element unfair cage wavy afraid provide blind pitch ultimate hybrid gravity formal voting",
88+
"academic acid decision shaft crunch glance exclude stilt grill numb smug stick obtain raisin force theater duke taught license scramble",
89+
"academic acid decision skin disaster mama alive nylon mansion listen cowboy suitable crisis pancake velvet aviation exhaust decent medal dominant",
90+
"academic acid decision snake aunt frozen flip crystal crystal observe equip maximum maiden dragon wine crazy nervous crystal profile fiction",
91+
"academic acid decision spider bulge receiver behavior humidity ruler luck public math distance cylinder fantasy seafood training withdraw payment playoff"
9292
]
9393
)
9494
}
@@ -101,7 +101,7 @@ def test_passphrase():
101101

102102
# Ensure we can recover it w/ no passphrase
103103
assert recover(
104-
details_nonpass.groups['Fam'][1][:2] + details_nonpass.groups['Frens'][1][:2],
104+
details_nonpass.groups['Fam'][1][:2] + details_nonpass.groups['Frens'][1][:-3],
105105
) == SEED_FF
106106

107107
# Now, ensure that we see a different set of Mnemonics w/ a SLIP39 passphrase.
@@ -116,7 +116,7 @@ def test_passphrase():
116116
badpass = "password".encode( 'UTF-8' )
117117

118118
assert recover(
119-
details_nonpass.groups['Fam'][1][:2] + details_nonpass.groups['Frens'][1][:2],
119+
details_nonpass.groups['Fam'][1][:2] + details_nonpass.groups['Frens'][1][:-3],
120120
passphrase = badpass,
121121
) != SEED_FF
122122

@@ -151,14 +151,14 @@ def test_passphrase():
151151
]
152152
),
153153
"Frens": (
154-
2,
154+
3,
155155
[
156-
"academic acid decision roster alien carbon machine home valuable parking soul ounce disaster smart much hour prisoner script alpha extend",
157-
"academic acid decision scared both episode phantom award pacific spirit clock index memory evaluate mule epidemic very eraser evaluate lying",
158-
"academic acid decision shadow closet prospect result primary hawk equation fawn thorn soldier taste welcome mild blimp blue glad crush",
159-
"academic acid decision sister desert tracks metric sister again drink peaceful analysis grin forecast webcam reaction entrance oasis damage victim",
160-
"academic acid decision smug alarm focus medical treat season harvest style silver hobo ultimate exercise deal garbage harvest paper junior",
161-
"academic acid decision spew boundary auction peanut luxury platform carbon cultural cluster sugar hamster exclude bracelet adequate ting uncover party"
156+
"academic acid decision round academic academic academic academic academic academic academic academic academic academic academic academic academic ranked flame amount",
157+
"academic acid decision scatter become flavor crystal genuine hour infant voice unfair recall living afraid company froth distance bundle soldier",
158+
"academic acid decision shaft display headset level prevent verdict genius preach glad makeup element twin scene hunting smoking rival mortgage",
159+
"academic acid decision skin course dress prisoner skunk ordinary blind freshman member drink scroll traffic thank deliver frequent velvet evoke",
160+
"academic acid decision snake burden unknown guard peaceful artwork snapshot teaspoon literary move spray saver voice august maximum hazard negative",
161+
"academic acid decision spider ancestor regular evaluate salon glasses penalty blue guitar check extra roster snapshot fantasy adjust coastal eclipse"
162162
]
163163
)
164164
}
@@ -169,22 +169,22 @@ def test_passphrase():
169169
# design -- you can supply an attacker with a "duress" seed passphrase leading to wallet(s) with
170170
# a small sacrificial amount of funds.
171171
assert recover(
172-
details_badpass.groups['Fam'][1][:2] + details_badpass.groups['Frens'][1][:2],
172+
details_badpass.groups['Fam'][1][:2] + details_badpass.groups['Frens'][1][:3],
173173
) != SEED_FF
174174
assert recover(
175-
details_badpass.groups['Fam'][1][:2] + details_badpass.groups['Frens'][1][:2],
175+
details_badpass.groups['Fam'][1][:2] + details_badpass.groups['Frens'][1][:3],
176176
passphrase = badpass,
177177
) == SEED_FF
178178

179179
# Mixing SLIP39 recovery groups should fail to recover, both without and with the password,
180180
# since SLIP39 confirms the digest of the recovered "encrypted" seed, before decryption.
181181
with pytest.raises( shamir_mnemonic.utils.MnemonicError ):
182182
assert recover(
183-
details_nonpass.groups['Fam'][1][:2] + details_badpass.groups['Frens'][1][:2],
183+
details_nonpass.groups['Fam'][1][:2] + details_badpass.groups['Frens'][1][:3],
184184
) != SEED_FF
185185
with pytest.raises( shamir_mnemonic.utils.MnemonicError ):
186186
assert recover(
187-
details_nonpass.groups['Fam'][1][:2] + details_badpass.groups['Frens'][1][:2],
187+
details_nonpass.groups['Fam'][1][:2] + details_badpass.groups['Frens'][1][:3],
188188
passphrase = badpass,
189189
) != SEED_FF
190190

slip39/defaults.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
"First1",
3333
"Second(1/1)",
3434
"Fam(4)",
35-
"Frens2/6"
35+
"Frens3/6"
3636
]
3737

3838
FONTS = dict(
@@ -48,8 +48,8 @@
4848

4949
CARD = 'index'
5050
CARD_SIZES = dict(
51-
credit = CREDIT_CARD,
5251
index = INDEX_CARD,
52+
credit = CREDIT_CARD,
5353
business = BUSINESS_CARD,
5454
half = HALF_LETTER,
5555
)

slip39/gui/main.py

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
from ..recovery import recover, recover_bip39
1313
from ..util import log_level, log_cfg
1414
from ..layout import write_pdfs
15-
from ..defaults import GROUPS, GROUP_THRESHOLD_RATIO, CRYPTO_PATHS
15+
from ..defaults import GROUPS, GROUP_THRESHOLD_RATIO, CRYPTO_PATHS, CARD, CARD_SIZES
1616

1717
log = logging.getLogger( __package__ )
1818

@@ -48,7 +48,7 @@ def groups_layout( names, group_threshold, groups, passphrase=None ):
4848
], key='-GROUP-NUMBER-' ) ]], **F_kwds
4949
),
5050
sg.Frame(
51-
'Group Recovery', [[ sg.Column( [
51+
'Recovery Group', [[ sg.Column( [
5252
[ sg.Input( f'{g_name}', key=f'-G-NAME-{g}', **I_kwds ) ]
5353
for g,(g_name,(g_need,g_size)) in enumerate( groups.items() )
5454
], key='-GROUP-NAMES-' ) ]], **F_kwds
@@ -79,17 +79,20 @@ def groups_layout( names, group_threshold, groups, passphrase=None ):
7979
],
8080
[
8181
sg.Text( "Seed Name(s): ", size=prefix, **T_kwds ),
82-
sg.Input( f"{', '.join( names )}", key='-NAMES-', size=inputs, **I_kwds ),
82+
sg.Input( f"{', '.join( names )}", key='-NAMES-', size=inputs, **I_kwds ),
8383
sg.Text( "(default is 'SLIP39...'; comma-separated)", **T_kwds ),
8484
],
8585
[
86-
sg.Button( 'Save', **B_kwds ), sg.Button( 'Exit', **B_kwds ),
86+
sg.Text( "Card size: ", size=prefix, **T_kwds ),
87+
] + [
88+
sg.Radio( f"{card}", "CS", key=f"-CS-{card}", default=(card == CARD), **B_kwds )
89+
for card in CARD_SIZES
8790
],
8891
], key='-OUTPUT-F-', **F_kwds ),
8992
],
9093
] + [
9194
[
92-
sg.Frame( 'Seed Data Source', [
95+
sg.Frame( 'Seed Data Source (256-bit seeds produce more Mnemonic words to type into your Trezor; 512-bit seeds aren\'t Trezor compatible)', [
9396
[
9497
sg.Radio( "128-bit Random", "SD", key='-SD-128-RND-', default=True, **B_kwds ),
9598
sg.Radio( "256-bit Random", "SD", key='-SD-256-RND-', default=False, **B_kwds ),
@@ -101,7 +104,7 @@ def groups_layout( names, group_threshold, groups, passphrase=None ):
101104
sg.Radio( "512-bit Fixed ", "SD", key='-SD-512-FIX-', default=False, **B_kwds ),
102105
],
103106
[
104-
sg.Radio( "BIP-39", "SD", key='-SD-BIP-', default=False, **B_kwds ),
107+
sg.Radio( "512-bit BIP-39", "SD", key='-SD-BIP-', default=False, **B_kwds ),
105108
sg.Radio( "SLIP-39", "SD", key='-SD-SLIP-', default=False, **B_kwds ),
106109
],
107110
[
@@ -113,11 +116,10 @@ def groups_layout( names, group_threshold, groups, passphrase=None ):
113116
], key='-SD-DATA-F-', visible=False, **F_kwds ),
114117
],
115118
[
116-
sg.Frame( 'Passphrase (optional, if provided when Mnemonic was created)', [
119+
sg.Frame( 'Passphrase (if provided when Mnemonic was created)', [
117120
[
118121
sg.Text( "Passphrase (decrypt): ", size=prefix, **T_kwds ),
119122
sg.Input( "", key='-SD-PASS-', size=inputs, **I_kwds ),
120-
sg.Text( "(NOT Trezor compatible)", **T_kwds ),
121123
],
122124
], key='-SD-PASS-F-', visible=False, **F_kwds ),
123125
],
@@ -170,10 +172,10 @@ def groups_layout( names, group_threshold, groups, passphrase=None ):
170172
key='-PASSPHRASE-', size=inputs, **I_kwds ), # noqa: E127
171173
sg.Text( "(NOT Trezor compatible, and must be saved separately!!)", **T_kwds ),
172174
],
173-
group_body,
174175
[
175176
sg.Button( '+', **B_kwds ),
176177
],
178+
group_body,
177179
] ),
178180
],
179181
], key='-GROUPS-F-', **F_kwds ),
@@ -186,6 +188,9 @@ def groups_layout( names, group_threshold, groups, passphrase=None ):
186188
]
187189
], key='-SUMMARY-F-', **F_kwds ),
188190
],
191+
[
192+
sg.Button( 'Save', **B_kwds ), sg.Button( 'Exit', **B_kwds ),
193+
],
189194
[
190195
sg.Frame( 'Status', [
191196
[
@@ -443,10 +448,10 @@ def app(
443448
if not values or event in events_termination:
444449
continue
445450

446-
if event == '+':
447-
# Add a SLIP39 Groups row
448-
g = len(groups)
449-
name = f"Group {g+1}"
451+
if event == '+' and len( groups ) < 16:
452+
# Add a SLIP39 Groups row (if not already at limit)
453+
g = len( groups )
454+
name = f"Group{g+1}"
450455
needs = (2,3)
451456
groups[name] = needs
452457
values[f"-G-NAME-{g}"] = name
@@ -567,8 +572,10 @@ def app(
567572
# details is now { "<filename>": <details> })
568573
if event == 'Save':
569574
try:
575+
card = next( c for c in CARD_SIZES if values[f"-CS-{c}"] )
570576
details = write_pdfs(
571577
names = details,
578+
card = card,
572579
)
573580
except Exception as exc:
574581
status = f"Error saving PDF(s): {exc}"
@@ -611,7 +618,8 @@ def main( argv=None ):
611618
help="A group name[[<require>/]<size>] (default: <size> = 1, <require> = half of <size>, rounded up, eg. 'Frens(3/5)' )." )
612619
ap.add_argument( '-c', '--cryptocurrency', action='append',
613620
default=[],
614-
help=f"A crypto name and optional derivation path ('../<range>/<range>' allowed); defaults: {', '.join( f'{c}:{Account.path_default(c)}' for c in Account.CRYPTOCURRENCIES)}" )
621+
help="A crypto name and optional derivation path ('../<range>/<range>' allowed); defaults:" \
622+
f" {', '.join( f'{c}:{Account.path_default(c)}' for c in Account.CRYPTOCURRENCIES)}" )
615623
ap.add_argument( '--passphrase',
616624
default=None,
617625
help="Encrypt the master secret w/ this passphrase, '-' reads it from stdin (default: None/'')" )

slip39/layout.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -171,28 +171,28 @@ def layout_card(
171171
f = "FF"
172172
color = [
173173
# Primary
174-
f"0x{f}{o}{o}", # Red
175-
f"0x{o}{f}{o}", # Green
176174
f"0x{o}{o}{f}", # Blue
175+
f"0x{o}{f}{o}", # Green
176+
f"0x{f}{o}{o}", # Red
177177
# Secondary
178178
f"0x{o}{f}{f}", # Cyan,
179179
f"0x{f}{o}{f}", # Magenta
180180
f"0x{f}{f}{o}", # Yellow
181181
# Tertiary
182-
f"0x{f}{h}{o}", # Orange
183-
f"0x{h}{f}{o}", # Lime
184-
f"0x{h}{o}{f}", # Violet
185-
f"0x{f}{o}{h}", # Red-Magenta
186182
f"0x{o}{h}{f}", # Ocean
187183
f"0x{o}{f}{h}", # Turquoise
184+
f"0x{f}{o}{h}", # Red-Magenta
185+
f"0x{h}{o}{f}", # Violet
186+
f"0x{h}{f}{o}", # Lime
187+
f"0x{f}{h}{o}", # Orange
188188
# Other
189189
f"0x{o}{h}{h}", # Light Cyan
190190
f"0x{h}{o}{h}", # Light Magenta
191191
f"0x{h}{h}{o}", # Light Yellow
192192
f"0x{h}{h}{h}", # Light grey,
193193
]
194194
card_interior.add_region_proportional(
195-
Text( f'card-g{g_n}', x1=1/8, y1=-1/8, x2=7/8, y2=3/8, foreground=int( color[g_n], 16 ), rotate=-45 )
195+
Text( f'card-g{g_n}', x1=1/8, y1=-1/16, x2=7/8, y2=5/16, foreground=int( color[g_n], 16 ), rotate=-45 )
196196
)
197197
card_top = card_interior.add_region_proportional(
198198
Region( 'card-top', x1=0, y1=0, x2=1, y2=1/4 )
@@ -359,7 +359,7 @@ def output_pdf(
359359
if len( accounts[0] ) > 1:
360360
tpl['card-crypto2'] = f"{accounts[0][1].crypto} {accounts[0][1].path}: {accounts[0][1].address}"
361361
tpl['card-qr2'] = qr[1].get_image()
362-
tpl[f'card-g{g_n}'] = f"{g_name:5.5}..{mn_n+1}" if len(g_name) > 6 else f"{g_name} {mn_n+1}"
362+
tpl[f'card-g{g_n}'] = f"{g_name:6.6}..{mn_n+1}" if len(g_name) > 7 else f"{g_name} {mn_n+1}"
363363

364364
for n,m in enumerate_mnemonic( mnem ).items():
365365
tpl[f"mnem-{n}"] = m

slip39/main.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ def main( argv=None ):
2929
help="Reduce logging output." )
3030
ap.add_argument( '-o', '--output',
3131
default=FILENAME_FORMAT,
32-
help="Output PDF to file or '-' (stdout); formatting w/ {', '.join( FILENAME_KEYWORDS )} allowed" )
32+
help=f"Output PDF to file or '-' (stdout); formatting w/ {', '.join( FILENAME_KEYWORDS )} allowed" )
3333
ap.add_argument( '-t', '--threshold',
3434
default=None,
3535
help="Number of groups required for recovery (default: half of groups, rounded up)" )

0 commit comments

Comments
 (0)