Skip to content

Commit d91b9c1

Browse files
committed
Add PySimpleGUI App, begin work toward PyInstaller
1 parent 349cbc8 commit d91b9c1

File tree

12 files changed

+250
-17
lines changed

12 files changed

+250
-17
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,4 @@ dist/
2424
.vagrant
2525
.pytest_cache/
2626
.cache/
27+
*.spec

README.org

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ by entering the mnemonics right on the device.
7474
the key. Then key is (conceptually) split between 1 or more groups (not really; each group's
7575
data alone gives away no information about the key).
7676

77-
For example, you might have First, Second, Fam and Fren groups, and decide that any 2 groups can
77+
For example, you might have First, Second, Fam and Frens groups, and decide that any 2 groups can
7878
be combined to recover the key. Each group has members with varying levels of trust and
7979
persistence, so have different number of Members, and differing numbers Required to recover that
8080
group's data:
@@ -86,7 +86,7 @@ by entering the mnemonics right on the device.
8686
| First | 1 | / | 1 | Stored at home |
8787
| Second | 1 | / | 1 | Stored in office safe |
8888
| Fam | 2 | / | 4 | Distributed to family members |
89-
| Fren | 2 | / | 6 | Distributed to friends and associates |
89+
| Frens | 2 | / | 6 | Distributed to friends and associates |
9090
#+LATEX: }
9191

9292
The account owner might store their First and Second group data in their home and office safes.
@@ -100,8 +100,8 @@ by entering the mnemonics right on the device.
100100
card from the office safe, and 2 cards from Fam group members, and recover the wallet.
101101

102102
If catastrophe strikes and the owner dies, and the heirs don't have access to either the First
103-
(at home) or Second (at the office), they can collect 2 Fam cards and 2 Fren cards (at the
104-
funeral, for example), completing the Fam and Fren groups' data, and recover the HD Wallet
103+
(at home) or Second (at the office), they can collect 2 Fam cards and 2 Frens cards (at the
104+
funeral, for example), completing the Fam and Frens groups' data, and recover the HD Wallet
105105
account. Since Frens are less likely to persist long term (and are also less likely to know
106106
each-other), we'll require a lower proportion of them to be collected.
107107

@@ -177,7 +177,7 @@ by entering the mnemonics right on the device.
177177
: -g GROUP, --group GROUP
178178
: A group name[[<require>/]<size>] (default: <size> = 1,
179179
: <require> = half of <size>, rounded up, eg.
180-
: 'Fren(3/5)' ).
180+
: 'Frens(3/5)' ).
181181
: -f FORMAT, --format FORMAT
182182
: Specify default crypto address formats: legacy,
183183
: segwit, bech32; default ETH:legacy, BTC:bech32,
@@ -296,23 +296,23 @@ by entering the mnemonics right on the device.
296296
#+END_SRC
297297
#+RESULTS:
298298
#+begin_example
299-
2022-01-26 13:15:04 slip39 First(1/1): Recover w/ 2 of 4 groups First(1), Second(1), Fam(2/4), Fren(2/6)
299+
2022-01-26 13:15:04 slip39 First(1/1): Recover w/ 2 of 4 groups First(1), Second(1), Fam(2/4), Frens(2/6)
300300
2022-01-26 13:15:04 slip39 1st 1 painting 8 luxury 15 training
301301
2022-01-26 13:15:04 slip39 2 flexible 9 apart 16 welcome
302302
2022-01-26 13:15:04 slip39 3 acrobat 10 mild 17 birthday
303303
2022-01-26 13:15:04 slip39 4 romp 11 deploy 18 involve
304304
2022-01-26 13:15:04 slip39 5 column 12 machine 19 single
305305
2022-01-26 13:15:04 slip39 6 firm 13 predator 20 deny
306306
2022-01-26 13:15:04 slip39 7 stick 14 pencil
307-
2022-01-26 13:15:04 slip39 Second(1/1): Recover w/ 2 of 4 groups First(1), Second(1), Fam(2/4), Fren(2/6)
307+
2022-01-26 13:15:04 slip39 Second(1/1): Recover w/ 2 of 4 groups First(1), Second(1), Fam(2/4), Frens(2/6)
308308
2022-01-26 13:15:04 slip39 1st 1 painting 8 voting 15 genius
309309
2022-01-26 13:15:04 slip39 2 flexible 9 starting 16 group
310310
2022-01-26 13:15:04 slip39 3 beard 10 crucial 17 alive
311311
2022-01-26 13:15:04 slip39 4 romp 11 fangs 18 switch
312312
2022-01-26 13:15:04 slip39 5 airline 12 intend 19 texture
313313
2022-01-26 13:15:04 slip39 6 envelope 13 envy 20 herd
314314
2022-01-26 13:15:04 slip39 7 trial 14 eraser
315-
2022-01-26 13:15:04 slip39 Fam(2/4): Recover w/ 2 of 4 groups First(1), Second(1), Fam(2/4), Fren(2/6)
315+
2022-01-26 13:15:04 slip39 Fam(2/4): Recover w/ 2 of 4 groups First(1), Second(1), Fam(2/4), Frens(2/6)
316316
2022-01-26 13:15:04 slip39 1st 1 painting 8 realize 15 furl
317317
2022-01-26 13:15:04 slip39 2 flexible 9 guilt 16 fused
318318
2022-01-26 13:15:04 slip39 3 ceramic 10 slush 17 decision
@@ -341,7 +341,7 @@ by entering the mnemonics right on the device.
341341
2022-01-26 13:15:04 slip39 5 dining 12 starting 19 thorn
342342
2022-01-26 13:15:04 slip39 6 decrease 13 inherit 20 response
343343
2022-01-26 13:15:04 slip39 7 grocery 14 visitor
344-
2022-01-26 13:15:04 slip39 Fren(2/6): Recover w/ 2 of 4 groups First(1), Second(1), Fam(2/4), Fren(2/6)
344+
2022-01-26 13:15:04 slip39 Frens(2/6): Recover w/ 2 of 4 groups First(1), Second(1), Fam(2/4), Frens(2/6)
345345
2022-01-26 13:15:04 slip39 1st 1 painting 8 frost 15 puny
346346
2022-01-26 13:15:04 slip39 2 flexible 9 shelter 16 trip
347347
2022-01-26 13:15:04 slip39 3 decision 10 dragon 17 careful

SLIP39.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
#! /usr/bin/env python3
2+
3+
import sys
4+
5+
from slip39.App.main import main
6+
7+
sys.exit( main() )

requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,6 @@ fpdf2
33
hdwallet
44
mnemonic>=0.19,<1
55
pyserial>=3.5
6+
pysimplegui
67
qrcode
78
shamir-mnemonic

setup.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
'slip39 = slip39.main:main',
2020
'slip39-recovery = slip39.recovery.main:main',
2121
'slip39-generator = slip39.generator.main:main',
22+
'slip39-App = slip39.App.main:main',
2223
]
2324

2425
entry_points = {
@@ -32,6 +33,7 @@
3233
"slip39": "./slip39",
3334
"slip39.recovery": "./slip39/recovery",
3435
"slip39.generator": "./slip39/generator",
36+
"slip39.App": "./slip39/App",
3537
}
3638

3739
long_description_content_type = 'text/markdown'
@@ -62,23 +64,23 @@
6264
by entering the mnemonics right on the device.
6365
6466
$ python3 -m slip39 -v Personal # or run: slip39 -v Personal
65-
2022-01-26 13:55:30 slip39 First(1/1): Recover w/ 2 of 4 groups First(1), Second(1), Fam(2/4), Fren(2/6)
67+
2022-01-26 13:55:30 slip39 First(1/1): Recover w/ 2 of 4 groups First(1), Second(1), Fam(2/4), Frens(2/6)
6668
2022-01-26 13:55:30 slip39 1st 1 sister 8 cricket 15 unhappy
6769
2022-01-26 13:55:30 slip39 2 acid 9 mental 16 ocean
6870
2022-01-26 13:55:30 slip39 3 acrobat 10 veteran 17 mayor
6971
2022-01-26 13:55:30 slip39 4 romp 11 phantom 18 promise
7072
2022-01-26 13:55:30 slip39 5 anxiety 12 grownup 19 wrote
7173
2022-01-26 13:55:30 slip39 6 laser 13 skunk 20 romp
7274
2022-01-26 13:55:30 slip39 7 cricket 14 anatomy
73-
2022-01-26 13:55:30 slip39 Second(1/1): Recover w/ 2 of 4 groups First(1), Second(1), Fam(2/4), Fren(2/6)
75+
2022-01-26 13:55:30 slip39 Second(1/1): Recover w/ 2 of 4 groups First(1), Second(1), Fam(2/4), Frens(2/6)
7476
2022-01-26 13:55:30 slip39 1st 1 sister 8 belong 15 spirit
7577
2022-01-26 13:55:30 slip39 2 acid 9 survive 16 royal
7678
2022-01-26 13:55:30 slip39 3 beard 10 home 17 often
7779
2022-01-26 13:55:30 slip39 4 romp 11 herd 18 silver
7880
2022-01-26 13:55:30 slip39 5 again 12 mountain 19 grocery
7981
2022-01-26 13:55:30 slip39 6 orbit 13 august 20 antenna
8082
2022-01-26 13:55:30 slip39 7 very 14 evening
81-
2022-01-26 13:55:30 slip39 Fam(2/4): Recover w/ 2 of 4 groups First(1), Second(1), Fam(2/4), Fren(2/6)
83+
2022-01-26 13:55:30 slip39 Fam(2/4): Recover w/ 2 of 4 groups First(1), Second(1), Fam(2/4), Frens(2/6)
8284
2022-01-26 13:55:30 slip39 1st 1 sister 8 rainbow 15 husky
8385
2022-01-26 13:55:30 slip39 2 acid 9 swing 16 crowd
8486
2022-01-26 13:55:30 slip39 3 ceramic 10 credit 17 learn

slip39/App/__init__.py

Whitespace-only changes.

slip39/App/__main__.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import sys
2+
3+
from .main import main
4+
5+
sys.exit( main() )

slip39/App/main.py

Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,217 @@
1+
import argparse
2+
import logging
3+
import math
4+
import sys
5+
6+
import PySimpleGUI as sg
7+
8+
from ..types import Account
9+
from ..api import create, group_parser
10+
from ..util import log_level, log_cfg
11+
from ..defaults import GROUPS, GROUP_THRESHOLD_RATIO
12+
13+
font = ('Sans', 12)
14+
15+
I_kwds = dict(
16+
change_submits = True,
17+
font = font,
18+
)
19+
20+
T_kwds = dict(
21+
font = font,
22+
)
23+
24+
25+
def groups_layout( names, group_threshold, groups ):
26+
"""Return a layout for the specified number of SLIP-39 groups.
27+
28+
"""
29+
group_body = [
30+
sg.Frame(
31+
'#', [[ sg.Column( [
32+
[ sg.Text( f'{g+1}', **T_kwds ) ]
33+
for g,(g_name,(g_need,g_size)) in enumerate( groups.items() )
34+
], key='-GROUP-NUMBER-' ) ]]
35+
),
36+
sg.Frame(
37+
'Group Recovery', [[ sg.Column( [
38+
[ sg.Input( f'{g_name}', key=f'-G-NAME-{g}', **I_kwds ) ]
39+
for g,(g_name,(g_need,g_size)) in enumerate( groups.items() )
40+
], key='-GROUP-NAMES-' ) ]]
41+
),
42+
sg.Frame(
43+
'Needs at least', [[ sg.Column( [
44+
[ sg.Input( f'{g_need}', key=f'-G-NEED-{g}', **I_kwds ) ]
45+
for g,(g_name,(g_need,g_size)) in enumerate( groups.items() )
46+
], key='-GROUP-NEEDS-' ) ]]
47+
),
48+
sg.Frame(
49+
'of Cards in Group', [[ sg.Column( [
50+
[ sg.Input( f'{g_size}', key=f'-G-SIZE-{g}', **I_kwds ) ]
51+
for g,(g_name,(g_need,g_size)) in enumerate( groups.items() )
52+
], key='-GROUP-SIZES-' ) ]]
53+
),
54+
]
55+
56+
layout = [
57+
[
58+
sg.Input( f"{', '.join( names )}", key='-NAMES-', **I_kwds ),
59+
sg.Text( "Requires collection of at least", **T_kwds ),
60+
sg.Input( f"{group_threshold}", key='-THRESHOLD-', **I_kwds ),
61+
sg.Text( f" of {len(groups)} SLIP-39 Recovery Groups", **T_kwds ),
62+
],
63+
] + [
64+
[
65+
sg.Frame( 'Groups', [group_body], key='-GROUPS-' ),
66+
],
67+
] + [
68+
[
69+
sg.Button( '+' ), sg.Button( 'Generate' ), sg.Exit()
70+
],
71+
[
72+
sg.Frame(
73+
'Summary',
74+
[[ sg.Text( key='-SUMMARY-', **T_kwds ), ]]
75+
),
76+
],
77+
[
78+
sg.Frame(
79+
'Status',
80+
[[ sg.Text( key='-STATUS-', **T_kwds ), ]]
81+
),
82+
],
83+
84+
]
85+
return layout
86+
87+
88+
def app( names, group_threshold, groups, cryptopaths ):
89+
sg.theme( 'DarkAmber' )
90+
91+
layout = groups_layout( names, group_threshold, groups )
92+
window = None
93+
status = None
94+
event = False
95+
while event not in (sg.WIN_CLOSED, 'Exit',):
96+
if window:
97+
window['-STATUS-'].update( status or 'OK' )
98+
else:
99+
window = sg.Window( f"{', '.join( names )} Mnemonic Cards", layout )
100+
101+
status = None
102+
event, values = window.read()
103+
logging.info( f"{event}, {values}" )
104+
105+
if event == '+':
106+
g = len(groups)
107+
name = f"Group {g+1}"
108+
needs = (1,2)
109+
groups[name] = needs
110+
window.extend_layout( window['-GROUP-NUMBER-'], [[ sg.Text( f"{g+1}", **T_kwds ) ]] ) # noqa: 241
111+
window.extend_layout( window['-GROUP-NAMES-'], [[ sg.Input( f"{name}", key=f"-G-NAME-{g}", **I_kwds ) ]] ) # noqa: 241
112+
window.extend_layout( window['-GROUP-NEEDS-'], [[ sg.Input( f"{needs[0]}", key=f"-G-NEED-{g}", **I_kwds ) ]] ) # noqa: 241
113+
window.extend_layout( window['-GROUP-SIZES-'], [[ sg.Input( f"{needs[1]}", key=f"-G-SIZE-{g}", **I_kwds ) ]] ) # noqa: 241
114+
115+
if event == 'Generate':
116+
try:
117+
g_thr_val = values['-THRESHOLD-']
118+
g_thr = int( g_thr_val )
119+
except Exception as exc:
120+
status = f"Error defining group threshold {g_thr_val}: {exc}"
121+
logging.exception( f"{status}" )
122+
continue
123+
124+
g_rec = {}
125+
status = None
126+
for g in range( 16 ):
127+
nam_idx = f"-G-NAME-{g}"
128+
if nam_idx not in values:
129+
break
130+
try:
131+
nam = values[nam_idx]
132+
req,siz = int( values[f"-G-NEED-{g}"] ), int( values[f"-G-SIZE-{g}"] )
133+
g_rec[nam] = (req, siz)
134+
except Exception as exc:
135+
status = f"Error defining group {g+1}: {exc}"
136+
logging.exception( f"{status}" )
137+
continue
138+
139+
summary = f"Require {g_thr}/{len(g_rec)} Groups, from: {f', '.join( f'{n}({need}/{size})' for n,(need,size) in g_rec.items())}"
140+
window['-SUMMARY-'].update( summary )
141+
if status is None:
142+
details = {}
143+
try:
144+
for name in names:
145+
details[name] = create(
146+
name = name,
147+
group_threshold = group_threshold,
148+
groups = g_rec,
149+
cryptopaths = cryptopaths,
150+
)
151+
except Exception as exc:
152+
status = f"Error creating: {exc}"
153+
logging.exception( f"{status}" )
154+
continue
155+
156+
status = 'OK'
157+
for n in names:
158+
accts = ', '.join( f'{a.crypto} @ {a.path}: {a.address}' for a in details[n].accounts[0] )
159+
status += f"; {accts}"
160+
161+
window.close()
162+
163+
return 0
164+
165+
166+
def main( argv=None ):
167+
ap = argparse.ArgumentParser(
168+
description = "Create and output SLIP39 encoded Ethereum wallet(s) to a PDF file.",
169+
epilog = "" )
170+
ap.add_argument( '-v', '--verbose', action="count",
171+
default=0,
172+
help="Display logging information." )
173+
ap.add_argument( '-q', '--quiet', action="count",
174+
default=0,
175+
help="Reduce logging output." )
176+
ap.add_argument( '-t', '--threshold',
177+
default=None,
178+
help="Number of groups required for recovery (default: half of groups, rounded up)" )
179+
ap.add_argument( '-g', '--group', action='append',
180+
help="A group name[[<require>/]<size>] (default: <size> = 1, <require> = half of <size>, rounded up, eg. 'Frens(3/5)' )." )
181+
ap.add_argument( '-c', '--cryptocurrency', action='append',
182+
default=[],
183+
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)}" )
184+
ap.add_argument( 'names', nargs="*",
185+
help="Account names to produce")
186+
args = ap.parse_args( argv )
187+
188+
# Set up logging; also, handle the degenerate case where logging has *already* been set up (and
189+
# basicConfig is a NO-OP), by (also) setting the logging level
190+
log_cfg['level'] = log_level( args.verbose - args.quiet )
191+
logging.basicConfig( **log_cfg )
192+
if args.verbose:
193+
logging.getLogger().setLevel( log_cfg['level'] )
194+
195+
names = args.names or [ 'SLIP-39' ]
196+
groups = dict(
197+
group_parser( g )
198+
for g in args.group or GROUPS
199+
)
200+
group_threshold = args.threshold or math.ceil( len( groups ) * GROUP_THRESHOLD_RATIO )
201+
202+
cryptopaths = []
203+
for crypto in args.cryptocurrency or ['ETH', 'BTC']:
204+
try:
205+
crypto,paths = crypto.split( ':' )
206+
except ValueError:
207+
crypto,paths = crypto,None
208+
cryptopaths.append( (crypto,paths) )
209+
210+
sys.exit(
211+
app(
212+
names = names,
213+
group_threshold = group_threshold,
214+
groups = groups,
215+
cryptopaths = cryptopaths,
216+
)
217+
)

slip39/api.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -163,8 +163,8 @@ def organize_mnemonic( mnemonic, rows=None, cols=None, label="" ):
163163
def group_parser( group_spec ):
164164
"""Parse a SLIP-39 group specification.
165165
166-
Fren6, Fren 6, Fren(6) - A 3/6 group (default is 1/2 of group size, rounded up)
167-
Fren2/6, Fren(2/6) - A 2/6 group
166+
Frens6, Frens 6, Frens(6) - A 3/6 group (default is 1/2 of group size, rounded up)
167+
Frens2/6, Frens(2/6) - A 2/6 group
168168
169169
"""
170170
match = group_parser.RE.match( group_spec )

slip39/defaults.py

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

3838
FONTS = dict(

0 commit comments

Comments
 (0)