Skip to content

Commit c4a630a

Browse files
committed
Handle output PDF file directory location better
o Better paper format handling for cards, paper wallets o Multiple Seeds enables Extra Entropy collection
1 parent f1d5359 commit c4a630a

File tree

11 files changed

+224
-209
lines changed

11 files changed

+224
-209
lines changed

README.org

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -32,16 +32,16 @@ recovery phrases. This is better, but creating such accounts is difficult; pres
3232
Trezor supports these, and they can only be created "manually". Writing down 5 or more sets of 20
3333
words is difficult, error-prone and time consuming.
3434

35-
The [[https://github.com/pjkundert/python-slip39.git][python-slip39]] project exists to assist in the safe creation and documentation of [[https://wolovim.medium.com/ethereum-201-hd-wallets-11d0c93c87][Ethereum HD
36-
Wallet]] seeds and derived accounts, with various SLIP-39 sharing parameters. It generates the new
37-
random wallet seed, and generates the expected standard Ethereum account(s) (at [[https://medium.com/myetherwallet/hd-wallets-and-derivation-paths-explained-865a643c7bf2][derivation path]]
38-
=m/44'/60'/0'/0/0= by default) and Bitcoin accounts (at derivation path =m/84'/0'/0'/0/0= by
39-
default), with wallet address and QR code (compatible with Trezor derivations). It produces the
40-
required SLIP-39 phrases, and outputs a single PDF containing all the required printable cards to
41-
document the seed (and the specified derived accounts).
35+
The [[https://github.com/pjkundert/python-slip39.git][python-slip39]] project (and the [[https://slip39.kundert.ca/macos][SLIP-39 macOS App]]) exists to assist in the safe creation and
36+
documentation of [[https://wolovim.medium.com/ethereum-201-hd-wallets-11d0c93c87][Ethereum HD Wallet]] seeds and derived accounts, with various SLIP-39 sharing
37+
parameters. It generates the new random wallet seed, and generates the expected standard Ethereum
38+
account(s) (at [[https://medium.com/myetherwallet/hd-wallets-and-derivation-paths-explained-865a643c7bf2][derivation path]] =m/44'/60'/0'/0/0= by default) and Bitcoin accounts (at Bech32
39+
derivation path =m/84'/0'/0'/0/0= by default), with wallet address and QR code (compatible with
40+
Trezor derivations). It produces the required SLIP-39 phrases, and outputs a single PDF containing
41+
all the required printable cards to document the seed (and the specified derived accounts).
4242

43-
Output of BIP-38 or JSON encrypted paper wallets is supported, to support software cryptocurrency
44-
wallets.
43+
Output of BIP-38 or JSON encrypted Paper Wallets is supported, for import into standard software
44+
cryptocurrency wallets.
4545

4646
On an secure (ideally air-gapped) computer, new seeds can safely be generated and the PDF saved to a
4747
USB drive for printing (or directly printed without the file being saved to disk.). Presently,
@@ -54,16 +54,17 @@ by entering the mnemonics right on the device.
5454
* Security with Availability
5555

5656
For both BIP-39 and SLIP-39, a 128-bit random "seed" is the source of an unlimited sequence of
57-
Ethereum HD Wallet accounts. Anyone who can obtain this seed gains control of all Ethereum,
58-
Bitcoin (and other) accounts derived from it, so it must be securely stored.
57+
Ethereum and Bitcoin HD (Heirarchical Deterministic) derived Wallet accounts. Anyone who can
58+
obtain this seed gains control of all Ethereum, Bitcoin (and other) accounts derived from it, so
59+
it must be securely stored.
5960

60-
Losing this seed means that all of the HD Wallet accounts are permanently lost. Therefore, it
61-
must be backed up reliably, and be readily accessible.
61+
Losing this seed means that all of the HD Wallet accounts are permanently lost. It must be /both/
62+
backed up securely, /and/ be readily accessible.
6263

6364
Therefore, we must:
6465

6566
- Ensure that nobody untrustworthy can recover the seed, but
66-
- Store the seed in many places with several (some perhaps untrustworthy) people.
67+
- Store the seed in many places, probably with several (some perhaps untrustworthy) people.
6768

6869
How can we address these conflicting requirements?
6970

README.pdf

1.16 KB
Binary file not shown.

README.txt

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

macOS.org

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -116,15 +116,15 @@ of the cards and regain access to all of the cryptocurrency accounts related to
116116
** Protecting your SLIP-39 Cards
117117

118118
Protect your printed SLIP-39 cards from water damage by laminating them in plastic or storing
119-
them in [[https://amzn.to/346xBU8][zip-loc bags]] before [[https://amzn.to/35m4][mailing them]].
119+
them in [[https://amzn.to/346xBU8][zip-loc bags]] before [[https://amzn.to/3HCX8lv][mailing them]].
120120
#+BEGIN_EXPORT html
121121
<iframe style="width:120px;height:240px;" marginwidth="0" marginheight="0" scrolling="no" frameborder="0" src="//rcm-na.amazon-adsystem.com/e/cm?lt1=_blank&bc1=000000&IS2=1&bg1=FFFFFF&fc1=000000&lc1=0000FF&t=pjkundert-20&language=en_CA&o=15&p=8&l=as4&m=amazon&f=ifr&ref=as_ss_li_til&asins=B07X9GWPH1&linkId=69a3c4648067d4268b83e20be281ebb4"></iframe>
122122
<iframe style="width:120px;height:240px;" marginwidth="0" marginheight="0" scrolling="no" frameborder="0" src="//rcm-na.amazon-adsystem.com/e/cm?lt1=_blank&bc1=000000&IS2=1&bg1=FFFFFF&fc1=000000&lc1=0000FF&t=pjkundert-20&language=en_CA&o=15&p=8&l=as4&m=amazon&f=ifr&ref=as_ss_li_til&asins=B07WXMYX87&linkId=06e0f5d889c93f5427c379ddc5fa6857"></iframe>
123123
#+END_EXPORT
124124

125125
Print the SLIP-39 cards and cut them out, and then lay them out with 1/2" margins (so you can cut
126126
them out after lamination and retain 1/4" borders), either with [[https://amzn.to/3K6wp2p][self-adhesive full-page
127-
laminating sheets - no laminating machine required]] (or [[https://amzn.to/3vyyKPw][index-card size]]),
127+
laminating sheets]] - no machine required (or [[https://amzn.to/3vyyKPw][index-card size]]),
128128
#+BEGIN_EXPORT html
129129
<iframe style="width:120px;height:240px;" marginwidth="0" marginheight="0" scrolling="no" frameborder="0" src="//rcm-na.amazon-adsystem.com/e/cm?lt1=_blank&bc1=000000&IS2=1&bg1=FFFFFF&fc1=000000&lc1=0000FF&t=pjkundert-20&language=en_CA&o=15&p=8&l=as4&m=amazon&f=ifr&ref=as_ss_li_til&asins=B00007E7D2&linkId=608ce5dd44a7a227327c9000d6442c92"></iframe>
130130
<iframe style="width:120px;height:240px;" marginwidth="0" marginheight="0" scrolling="no" frameborder="0" src="//rcm-na.amazon-adsystem.com/e/cm?lt1=_blank&bc1=000000&IS2=1&bg1=FFFFFF&fc1=000000&lc1=0000FF&t=pjkundert-20&language=en_CA&o=15&p=8&l=as4&m=amazon&f=ifr&ref=as_ss_li_til&asins=B00ENFRAX8&linkId=4ef3861c37b523826fcf6d3a87349890"></iframe>

macOS.pdf

-28 Bytes
Binary file not shown.

macOS.txt

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -146,18 +146,17 @@ Table of Contents
146146
them].
147147
Print the SLIP-39 cards and cut them out, and then lay them out with
148148
1/2" margins (so you can cut them out after lamination and retain 1/4"
149-
borders), either with [self-adhesive full-page laminating sheets - no
150-
laminating machine required] (or [index-card size]),
149+
borders), either with [self-adhesive full-page laminating sheets] - no
150+
machine required (or [index-card size]),
151151
or with a [heat-laminating machine] in [full-page pouches] (or in
152152
[index-card size pouches]).
153153

154154

155155
[zip-loc bags] <https://amzn.to/346xBU8>
156156

157-
[mailing them] <https://amzn.to/35m4>
157+
[mailing them] <https://amzn.to/3HCX8lv>
158158

159-
[self-adhesive full-page laminating sheets - no laminating machine
160-
required] <https://amzn.to/3K6wp2p>
159+
[self-adhesive full-page laminating sheets] <https://amzn.to/3K6wp2p>
161160

162161
[index-card size] <https://amzn.to/3vyyKPw>
163162

setup.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,9 @@
7171
required SLIP-39 phrases, and outputs a single PDF containing all the required printable cards to
7272
document the seed (and the specified derived accounts).
7373
74+
Output of BIP-38 or JSON encrypted Paper Wallets is supported, for import into standard software
75+
cryptocurrency wallets.
76+
7477
On an secure (ideally air-gapped) computer, new seeds can safely be generated and the PDF saved to a
7578
USB drive for printing (or directly printed without the file being saved to disk.). Presently,
7679
=slip39= can output example ETH, BTC, LTC and DOGE addresses derived from the seed, to illustrate

slip39/defaults.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,12 +53,12 @@
5353
CARD = 'index'
5454
CARD_SIZES = dict(
5555
index = INDEX_CARD,
56-
photo = PHOTO_CARD,
5756
credit = CREDIT_CARD,
5857
business = BUSINESS_CARD,
5958
half = HALF_LETTER,
6059
third = THIRD_LETTER,
6160
quarter = THIRD_LETTER,
61+
photo = PHOTO_CARD,
6262
)
6363

6464
# Paper Wallet Bill Sizes, by default on PAPER format paper

slip39/gui/main.py

Lines changed: 45 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -114,25 +114,21 @@ def groups_layout(
114114

115115
layout = [
116116
[
117-
sg.Frame( '1. Location for output PDF File(s) (Preferably removable media, such as a USB drive)', [
118-
[
119-
sg.Text( "Save PDF(s) to: ", size=prefix, **T_kwds ),
120-
sg.Input( sg.user_settings_get_entry( "-target folder-", ""),
121-
key='-TARGET-', size=inputs, **I_kwds ), # noqa: E127
122-
sg.FolderBrowse( **B_kwds ),
123-
sg.Text( "Card size: ", **T_kwds ),
124-
] + [
125-
sg.Radio( f"{cs}", "CS", key=f"-CS-{cs}", default=(cs == CARD), **B_kwds )
126-
for cs in CARD_SIZES
127-
],
117+
sg.Frame( '1. Name(s) and formats of SLIP-39 Cards for output PDF File(s)', [
128118
[
129119
sg.Text( "Seed Name(s): ", size=prefix, **T_kwds ),
130120
sg.Input( f"{', '.join( names )}", key='-NAMES-', size=inputs, **I_kwds ),
131-
sg.Text( ", ... Paper size: ", **T_kwds ),
121+
sg.Text( ", ... on Paper: ", **T_kwds ),
132122
] + [
133-
sg.Radio( f"{pf}", "PF", key=f"-PF-{pf}", default=(pf == PAPER), **B_kwds )
123+
sg.Radio( f"{pf}", "PF", key=f"-PF-{pf}-", default=(pf == PAPER), **B_kwds )
134124
for pf in PAPER_FORMATS
135125
],
126+
[
127+
sg.Text( "SLIP-39 Card size: ", **T_kwds ),
128+
] + [
129+
sg.Radio( f"{cs}", "CS", key=f"-CS-{cs}-", default=(cs == CARD), **B_kwds )
130+
for cs in CARD_SIZES
131+
],
136132
], key='-OUTPUT-F-', **F_kwds ),
137133
],
138134
] + [
@@ -270,7 +266,9 @@ def groups_layout(
270266
],
271267
] + [
272268
[
273-
sg.Button( 'Save', key='-SAVE-', **B_kwds ),
269+
sg.Input( sg.user_settings_get_entry( "-target folder-", ""),
270+
key='-SAVE-', visible=False, **I_kwds ), # noqa: E127
271+
sg.FolderBrowse( 'Save', target='-SAVE-', **B_kwds ),
274272
sg.Button( 'Exit', **B_kwds ),
275273
sg.Frame( '6. Summary of your SLIP-39 Recovery Groups. When this looks good, hit Save!', [
276274
[
@@ -720,11 +718,11 @@ def app(
720718
groups_recovered = None # The last SLIP-39 groups recovered from user input
721719
details = None # ..and the SLIP-39 details produced; make None to force SLIP-39 Mnemonic update
722720
cryptopaths = None
721+
timeout = 0 # First time thru; refresh immediately
723722
while event not in events_termination:
724723
# Create window (for initial window.read()), or update status
725724
if window:
726-
window['-STATUS-'].update( f"{status or 'OK':.145}{'...' if len(status or 'OK')>145 else ''}", font=font_bold if status_error else font )
727-
window['-SAVE-'].update( disabled=status_error )
725+
window['-STATUS-'].update( f"{status or 'OK'}", font=font_bold if status_error else font )
728726
window['-RECOVERY-'].update( f"of {len(groups)}" )
729727
window['-SD-SEED-F-'].expand( expand_x=True )
730728
window['-SE-SEED-F-'].expand( expand_x=True )
@@ -736,14 +734,14 @@ def app(
736734
window['-RECOVERED-F-'].expand( expand_x=True )
737735
window['-MNEMONICS-F-'].expand( expand_x=True )
738736
window['-GROUPS-F-'].expand( expand_x=True )
739-
timeout = None # Subsequently, block indefinitely
740737
else:
741738
window = sg.Window( f"{', '.join( names or [ 'SLIP-39' ] )} Mnemonic Cards", layout )
742-
timeout = 0 # First time through, refresh immediately
739+
timeout = 0 # First time through w/ new window, refresh immediately
743740

744741
# Block (except for first loop) and obtain current event and input values. Until we get a
745742
# new event, retain the current status
746743
event, values = window.read( timeout=timeout )
744+
timeout = None # Subsequently, default; block indefinitely
747745
logging.debug( f"{event}, {values}" )
748746
if not values or event in events_termination or event in events_ignored:
749747
continue
@@ -765,6 +763,23 @@ def app(
765763
window.extend_layout( window['-GROUP-NEEDS-'], [[ sg.Input( f"{needs[0]}", key=f"-G-NEED-{g}", size=shorty, **I_kwds ) ]] ) # noqa: 241
766764
window.extend_layout( window['-GROUP-SIZES-'], [[ sg.Input( f"{needs[1]}", key=f"-G-SIZE-{g}", size=shorty, **I_kwds ) ]] ) # noqa: 241
767765

766+
# Deduce the desired Seed names, defaulting to "SLIP39"; if any change, force update of
767+
# details. If the user specifies more than one, ensure that Extra Seed Entropy is enabled.
768+
names_now = [
769+
name.strip()
770+
for name in ( values['-NAMES-'].strip() or "SLIP39" ).split( ',' )
771+
if name and name.strip()
772+
]
773+
if names != names_now:
774+
names = names_now
775+
details = None
776+
if names and len( names ) > 1:
777+
if values['-SE-NON-']:
778+
# Multiple names specified, but NO Extra Seed Entropy selected!
779+
window['-SE-SHA-'].update( True )
780+
timeout = 0
781+
continue
782+
768783
# Attempt to compute the 1st Master Secret Seed, collecting any failure status detected.
769784
# Compute the Master Secret Seed, from the supplied Seed Data and any extra Seed Entropy.
770785
# We are displaying the 1st extra Seed Entropy, used to produce the first Seed, for the
@@ -910,16 +925,6 @@ def app(
910925

911926
window['-SUMMARY-'].update( summary )
912927

913-
# Deduce the desired Seed names, defaulting to "SLIP39"; if any change, force update
914-
names_now = [
915-
name.strip()
916-
for name in ( values['-NAMES-'].strip() or "SLIP39" ).split( ',' )
917-
if name and name.strip()
918-
]
919-
if names != names_now:
920-
names = names_now
921-
details = None
922-
923928
# Re-compute the SLIP39 Seed details. For multiple names, each subsequent slip39.create
924929
# uses a master_secret produced by hashing the prior master_secret entropy. For the 1st
925930
# Seed, we've computed the master_secret, above. Each master_secret_n contains the computed
@@ -962,43 +967,38 @@ def app(
962967
# If all has gone well -- display the resultant <name>/<filename>, and some derived account details
963968
name_len = max( len( name ) for name in details )
964969
status = '\n'.join(
965-
f"{name:>{name_len}} == " + ', '.join( f'{a.crypto} @ {a.path}: {a.address}' for a in details[name].accounts[0] )
970+
f"{name:>{name_len}} == {', '.join( f'{a.crypto} @ {a.path}: {a.address}' for a in details[name].accounts[0] ):.130}..."
966971
for name in details
967972
)
968973

969-
# We have a complete SLIP-39 Mnemonic set. A target directory must be selected; use it, if
970-
# possible. This is where any output will be written. It should usually be a removable
971-
# volume, but we do not check for this.
972-
try:
973-
os.chdir( values['-TARGET-'] )
974-
except Exception as exc:
975-
status = f"Error changing to target directory {values['-TARGET-']!r}: {exc}"
976-
logging.exception( f"{status}" )
977-
update_seed_recovered( window, values, None )
978-
continue
979-
980-
# If we get here, no failure status has been detected, and SLIP39 mnemonic and account
981-
# details { "name": <details> } have been created; we can now save the PDFs; converted
982-
# details is now { "<filename>": <details> })
974+
# We have a complete SLIP-39 Mnemonic set. If we get here, no failure status has been
975+
# detected, and SLIP39 mnemonic and account details { "name": <details> } have been created;
976+
# we can now save the PDFs; converted details is now { "<filename>": <details> })
983977
if event == '-SAVE-':
978+
# A -SAVE- target directory has been selected; use it, if possible. This is where any
979+
# output will be written. It should usually be a removable volume, but we do not check.
980+
# An empty path implies the current directory.
984981
try:
985-
card_format = next( c for c in CARD_SIZES if values[f"-CS-{c}"] )
982+
card_format = next( c for c in CARD_SIZES if values[f"-CS-{c}-"] )
983+
paper_format = next( pf for pn,pf in PAPER_FORMATS.items() if values[f"-PF-{pn}-"] )
986984
details = write_pdfs(
987985
names = details,
988986
card_format = card_format,
987+
paper_format = paper_format,
989988
cryptocurrency = cryptocurrency,
990989
edit = edit,
991990
wallet_pwd = values['-WALLET-PASS-'], # Produces Paper Wallet(s) iff set
992991
wallet_pwd_hint = values['-WALLET-HINT-'],
993992
wallet_format = next( (f for f in WALLET_SIZES if values.get( f"-WALLET-SIZE-{f}-" )), None ),
993+
filepath = values['-SAVE-'] or None,
994994
)
995995
except Exception as exc:
996996
status = f"Error saving PDF(s): {exc}"
997997
logging.exception( f"{status}" )
998998
continue
999999
name_len = max( len( name ) for name in details )
10001000
status = '\n'.join(
1001-
f"{name:>{name_len}} saved to {values['-TARGET-']}"
1001+
f"Saved {name}"
10021002
for name in details
10031003
)
10041004
# Finally, success has been assured; turn off emboldened status line

0 commit comments

Comments
 (0)