Skip to content

Commit 10a7ea4

Browse files
committed
Update README.org/pdf, and properly set up logging
1 parent d69e5eb commit 10a7ea4

File tree

4 files changed

+74
-60
lines changed

4 files changed

+74
-60
lines changed

README.org

Lines changed: 72 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -16,32 +16,38 @@ nil
1616
#+RESULTS:
1717

1818
#+BEGIN_ABSTRACT
19-
Creating Ethereum accounts is complex and fraught with potential for loss of funds.
19+
Creating Ethereum, Bitcoin and other accounts is complex and fraught with potential for loss of funds.
2020

21-
A BIP-39 seed recovery phrase helps, but a *single* lapse in security dooms the account. If someone
22-
finds your recovery phrase, the account is /gone/.
21+
A BIP-39 seed recovery phrase helps, but a *single* lapse in security dooms the account (and all
22+
derived accounts, in fact). If someone finds your recovery phrase, the accounts derived from that
23+
seed are /gone/.
2324

24-
The SLIP-39 standard allows you to split the seed between 1 or more groups of several mnemonic
25+
The SLIP-39 standard allows you to split the seed between 1, 2, or more groups of several mnemonic
2526
recovery phrases. This is better, but creating such accounts is difficult; presently, only the
2627
Trezor supports these, and they can only be created "manually". Writing down 5 or more sets of 20
2728
words is difficult, error-prone and time consuming.
2829

2930
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
30-
Wallet]] accounts, with various SLIP-39 sharing parameters. It generates the new random wallet seed,
31-
generates standard Ethereum account(s) (at [[https://medium.com/myetherwallet/hd-wallets-and-derivation-paths-explained-865a643c7bf2][derivation path]] =m/44'/60'/0'/0/0= by default) with
32-
Ethereum wallet address and QR code, produces the required SLIP-39 phrases, and outputs a single PDF
33-
containing all the required printable cards to document the account.
34-
35-
On an secure (ideally air-gapped) computer, new accounts can safely be generated and the PDF saved
36-
to a USB drive for printing (or directly printed without the file being saved to disk.)
31+
Wallet]] seeds and derived accounts, with various SLIP-39 sharing parameters. It generates the new
32+
random wallet seed, and generates standard Ethereum account(s) (at [[https://medium.com/myetherwallet/hd-wallets-and-derivation-paths-explained-865a643c7bf2][derivation path]]
33+
=m/44'/60'/0'/0/0= by default) and Bitcoin accounts (at derivation path =m/44'/0'/0'/0/0 by
34+
default), with wallet address and QR code, produces the required SLIP-39 phrases, and outputs a
35+
single PDF containing all the required printable cards to document the seed (and the specified
36+
derived accounts).
37+
38+
On an secure (ideally air-gapped) computer, new seeds can safely be generated and the PDF saved to a
39+
USB drive for printing (or directly printed without the file being saved to disk.). Presently,
40+
=slip39= can output example ETH, BTC, LTC and DOGE addresses derived from the seed, to illustrate
41+
what accounts are associated with the backed-up seed. Recovery of the seed to a Trezor is simple,
42+
by entering the mnemonics right on the device.
3743
#+END_ABSTRACT
3844
#+TOC: headlines 2
3945

4046
* Security with Availability
4147

4248
For both BIP-39 and SLIP-39, a 128-bit random "seed" is the source of an unlimited sequence of
43-
Ethereum HD Wallet accounts. Anyone who can obtain this seed gains control of all Ethereum
44-
accounts derived from it, so it must be securely stored.
49+
Ethereum HD Wallet accounts. Anyone who can obtain this seed gains control of all Ethereum,
50+
Bitcoin (and other) accounts derived from it, so it must be securely stored.
4551

4652
Losing this seed means that all of the HD Wallet accounts are permanently lost. Therefore, it
4753
must be backed up reliably, and be readily accessible.
@@ -145,13 +151,14 @@ to a USB drive for printing (or directly printed without the file being saved to
145151
#+LATEX: {\scriptsize
146152
#+BEGIN_EXAMPLE
147153
$ python3 -m slip39 --secret 383597fd63547e7c9525575decd413f7 --json -
148-
2021-12-25 11:09:57 slip39 ETH m/44'/60'/0'/0/0 : 0xb44A2011A99596671d5952CdC22816089f142FB3
149-
...
150-
JSON key file password: <enter JSON wallet password>
151-
2021-12-25 11:10:38 slip39 Wrote JSON encrypted wallet for '' to:\
152-
SLIP39-2021-12-25+11.09.57-0xb44A2011A99596671d5952CdC22816089f142FB3.json
153-
2021-12-25 11:10:39 slip39 Wrote SLIP39-encoded wallet for '' to:\
154-
SLIP39-2021-12-25+11.09.57-0xb44A2011A99596671d5952CdC22816089f142FB3.pdf
154+
2022-01-12 19:19:21 slip39 It is recommended to not use '-s|--secret <hex>'; specify '-' to read from input
155+
2022-01-12 19:19:21 slip39 ETH m/44'/60'/0'/0/0 : 0xb44A2011A99596671d5952CdC22816089f142FB3
156+
2022-01-12 19:19:21 slip39 BTC m/44'/0'/0'/0/0 : 1LHhWXKn4PVXRiKfr39qyreBxd4qTLmZa
157+
JSON key file password: <enter password for JSON file...>
158+
2022-01-12 19:19:28 slip39 Wrote JSON 'SLIP39''s encrypted ETH wallet 0xb44A2011A99596671d5952CdC22816089f142FB3 to:\
159+
SLIP39-2022-01-12+19.19.21-ETH-0xb44A2011A99596671d5952CdC22816089f142FB3.json
160+
2022-01-12 19:19:28 slip39 Wrote SLIP39-encoded wallet for '' to:\
161+
SLIP39-2022-01-12+19.19.21-ETH-0xb44A2011A99596671d5952CdC22816089f142FB3.pdf
155162
#+END_EXAMPLE
156163
#+LATEX: }
157164

@@ -172,9 +179,7 @@ to a USB drive for printing (or directly printed without the file being saved to
172179
| python3 -m slip39 --secret - --no-card -q ) 2>&1
173180
#+END_SRC
174181
#+RESULTS:
175-
: 2022-01-03 10:37:06 slip39 ETH m/44'/60'/0'/0/0 : 0xe751A1C06e7FaaEd7054F47f88145C21AFD46A15
176-
: 2022-01-03 10:37:07 slip39.recovery Recovered 128-bit SLIP-39 secret with 4 (1st, 2nd, 7th, 8th) of 8 supplied mnemonics
177-
: 2022-01-03 10:37:07 slip39 ETH m/44'/60'/0'/0/0 : 0xe751A1C06e7FaaEd7054F47f88145C21AFD46A15
182+
: 2022-01-12 17:08:16 slip39.recovery Recovered 128-bit SLIP-39 secret with 4 (1st, 2nd, 7th, 8th) of 8 supplied mnemonics
178183
#+LATEX: }
179184

180185
** Recovery Mnemonic Cards PDF
@@ -202,7 +207,7 @@ to a USB drive for printing (or directly printed without the file being saved to
202207
| master_secret | 128-bit secret (default: from secrets.token_bytes) |
203208
| passphrase | An optional additional passphrase required to recover secret (default: "") |
204209
| iteration_exponent | For encrypted secret, exponentially increase PBKDF2 rounds (default: 1) |
205-
| paths | A number of HD Wallet derivation paths (default: ["m/60'/44'/0'/0/0"] |
210+
| cryptopaths | A number of crypto names, and their derivation paths ] |
206211
#+LATEX: }
207212

208213
Outputs a =slip39.Details= namedtuple containing:
@@ -213,7 +218,7 @@ to a USB drive for printing (or directly printed without the file being saved to
213218
| name | (same) |
214219
| group_threshold | (same) |
215220
| groups | Like groups, w/ <members> = ["<mnemonics>", ...] |
216-
| accounts | Resultant { "path": eth_account.Account, ...} |
221+
| accounts | Resultant list of groups of accounts |
217222
#+LATEX: }
218223

219224
This is immediately usable to pass to =slip39.output=.
@@ -236,12 +241,12 @@ to a USB drive for printing (or directly printed without the file being saved to
236241
import eth_account
237242
import slip39
238243

239-
paths = [ "m/44'/60'/0'/0/0", "m/44'/60'/0'/0/1" ]
244+
cryptopaths = [("ETH","m/44'/60'/0'/0/-2"), ("BTC","m/44'/0'/0'/0/-2")]
240245
master_secret = b'\xFF' * 16
241246
passphrase = b""
242247
create_details = slip39.create(
243248
"Test", 2, { "Mine": (1,1), "Fam": (2,3) },
244-
master_secret=master_secret, passphrase=passphrase, paths=paths )
249+
master_secret=master_secret, passphrase=passphrase, cryptopaths=cryptopaths )
245250
[
246251
[
247252
f"{g_name}({g_of}/{len(g_mnems)}) #{g_n+1}:" if l_n == 0 else ""
@@ -255,7 +260,7 @@ to a USB drive for printing (or directly printed without the file being saved to
255260

256261
#+RESULTS:
257262
:results:
258-
# Out[308]:
263+
# Out[8]:
259264
| 0 | 1 | 2 | 3 |
260265
|---------------+------------+-------------+-------------|
261266
| Mine(1/1) #1: | 1 academic | 8 safari | 15 standard |
@@ -295,18 +300,23 @@ to a USB drive for printing (or directly printed without the file being saved to
295300
#+LATEX: {\scriptsize
296301
#+BEGIN_SRC ipython :session :exports both :results raw drawer
297302
[
298-
[ path, eth.address ]
299-
for path,eth in create_details.accounts.items()
303+
[ account.path, account.address ]
304+
for group in create_details.accounts
305+
for account in group
300306
]
301307
#+END_SRC
302308

303309
#+RESULTS:
304310
:results:
305-
# Out[283]:
311+
# Out[9]:
306312
| 0 | 1 |
307313
|------------------+--------------------------------------------|
308314
| m/44'/60'/0'/0/0 | 0x824b174803e688dE39aF5B3D7Cd39bE6515A19a1 |
315+
| m/44'/0'/0'/0/0 | 1MAjc529bjmkC1iCXTw2XMHL2zof5StqdQ |
309316
| m/44'/60'/0'/0/1 | 0x8D342083549C635C0494d3c77567860ee7456963 |
317+
| m/44'/0'/0'/0/1 | 1BGwDuVPJeXDG9upaHvVPds5MXwkTjZoav |
318+
| m/44'/60'/0'/0/2 | 0x52787E24965E1aBd691df77827A3CfA90f0166AA |
319+
| m/44'/0'/0'/0/2 | 1L64uW2jKB3d1mWvfzTGwZPTGg9qPCaQFM |
310320
:end:
311321

312322
#+LATEX: }
@@ -378,7 +388,7 @@ to a USB drive for printing (or directly printed without the file being saved to
378388

379389
#+RESULTS:
380390
:results:
381-
# Out[284]:
391+
# Out[11]:
382392
| 0 |
383393
|---------------------------------------------|
384394
| 128-bit secret w/ decoy password recovered: |
@@ -421,7 +431,7 @@ to a USB drive for printing (or directly printed without the file being saved to
421431

422432
#+RESULTS:
423433
:results:
424-
# Out[285]:
434+
# Out[12]:
425435
| 0 |
426436
|---------------------------------------------------|
427437
| zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo wrong |
@@ -457,11 +467,11 @@ to a USB drive for printing (or directly printed without the file being saved to
457467

458468
#+RESULTS:
459469
:results:
460-
# Out[245]:
470+
# Out[13]:
461471
| 0 |
462472
|---------------------------------|
463473
| Valid random 12-word mnemonics: |
464-
| 6.2% |
474+
| 6.23% |
465475
| ~ 1/16.1 |
466476
:end:
467477

@@ -489,7 +499,7 @@ to a USB drive for printing (or directly printed without the file being saved to
489499

490500
#+RESULTS:
491501
:results:
492-
# Out[271]:
502+
# Out[14]:
493503
| 0 |
494504
|----------------------------------|
495505
| 512-bit seed: |
@@ -527,7 +537,7 @@ to a USB drive for printing (or directly printed without the file being saved to
527537

528538
#+RESULTS:
529539
:results:
530-
# Out[295]:
540+
# Out[15]:
531541
| 0 |
532542
|------------------------------------------------------------------|
533543
| 256-bit derived key at path "m/44'/60'/0'/0/0": |
@@ -552,7 +562,7 @@ to a USB drive for printing (or directly printed without the file being saved to
552562
#+LATEX: {\scriptsize
553563
#+BEGIN_SRC ipython :session :exports both :results raw drawer
554564
name,thrs,grps,acct = slip39.create(
555-
"Test", 2, { "Mine": (1,1), "Fam": (2,3) }, entropy, paths=[path] )
565+
"Test", 2, { "Mine": (1,1), "Fam": (2,3) }, entropy )
556566
[[ f"{g_name}({g_of}/{len(g_mnems)}) #{g_n+1}:" if l_n == 0 else "" ] + words
557567
for g_name,(g_of,g_mnems) in grps.items()
558568
for g_n,mnem in enumerate( g_mnems )
@@ -563,7 +573,7 @@ to a USB drive for printing (or directly printed without the file being saved to
563573

564574
#+RESULTS:
565575
:results:
566-
# Out[289]:
576+
# Out[17]:
567577
| 0 | 1 | 2 | 3 |
568578
|---------------+------------+-------------+-------------|
569579
| Mine(1/1) #1: | 1 academic | 8 safari | 15 standard |
@@ -606,19 +616,25 @@ to a USB drive for printing (or directly printed without the file being saved to
606616
#+LATEX: {\scriptsize
607617
#+BEGIN_SRC ipython :session :exports both :results raw drawer
608618

609-
[[ "HD Wallet Path:", "Ethereum Address:" ]] + [
610-
[ path, eth.address ]
611-
for path,eth in acct.items()
619+
[[ "Crypto", "HD Wallet Path:", "Ethereum Address:" ]] + [
620+
[ account.crypto, account.path, account.address ]
621+
for group in create_details.accounts
622+
for account in group
612623
]
613624
#+END_SRC
614625

615626
#+RESULTS:
616627
:results:
617-
# Out[292]:
618-
| 0 | 1 |
619-
|------------------+--------------------------------------------|
620-
| HD Wallet Path: | Ethereum Address: |
621-
| m/44'/60'/0'/0/0 | 0x824b174803e688dE39aF5B3D7Cd39bE6515A19a1 |
628+
# Out[20]:
629+
| 0 | 1 | 2 |
630+
|--------+------------------+--------------------------------------------|
631+
| Crypto | HD Wallet Path: | Ethereum Address: |
632+
| ETH | m/44'/60'/0'/0/0 | 0x824b174803e688dE39aF5B3D7Cd39bE6515A19a1 |
633+
| BTC | m/44'/0'/0'/0/0 | 1MAjc529bjmkC1iCXTw2XMHL2zof5StqdQ |
634+
| ETH | m/44'/60'/0'/0/1 | 0x8D342083549C635C0494d3c77567860ee7456963 |
635+
| BTC | m/44'/0'/0'/0/1 | 1BGwDuVPJeXDG9upaHvVPds5MXwkTjZoav |
636+
| ETH | m/44'/60'/0'/0/2 | 0x52787E24965E1aBd691df77827A3CfA90f0166AA |
637+
| BTC | m/44'/0'/0'/0/2 | 1L64uW2jKB3d1mWvfzTGwZPTGg9qPCaQFM |
622638
:end:
623639

624640
#+LATEX: }
@@ -631,8 +647,8 @@ to a USB drive for printing (or directly printed without the file being saved to
631647

632648
#+LATEX: {\scriptsize
633649
#+BEGIN_SRC ipython :session :exports both :results raw drawer
634-
_,mnem_mine = entropy_slip39['Mine']
635-
_,mnem_fam = entropy_slip39['Fam']
650+
_,mnem_mine = grps['Mine']
651+
_,mnem_fam = grps['Fam']
636652
recseed = slip39.recover( mnem_mine + mnem_fam[:2] )
637653
recseedhex = codecs.encode( recseed, 'hex_codec' ).decode( 'ascii' )
638654

@@ -644,7 +660,7 @@ to a USB drive for printing (or directly printed without the file being saved to
644660

645661
#+RESULTS:
646662
:results:
647-
# Out[293]:
663+
# Out[24]:
648664
| 0 |
649665
|----------------------------------|
650666
| 128-bit seed: |
@@ -672,7 +688,7 @@ to a USB drive for printing (or directly printed without the file being saved to
672688

673689
#+RESULTS:
674690
:results:
675-
# Out[294]:
691+
# Out[26]:
676692
| 0 |
677693
|------------------------------------------------------------------|
678694
| 256-bit derived key at path "m/44'/60'/0'/0/0": |
@@ -719,7 +735,7 @@ to a USB drive for printing (or directly printed without the file being saved to
719735
) 2>&1
720736
#+END_SRC
721737
#+RESULTS:
722-
: 2022-01-03 09:35:05 slip39.recovery Recovered 512-bit BIP-39 secret from english mnemonic
738+
: 2022-01-12 19:14:50 slip39.recovery Recovered 512-bit BIP-39 secret from english mnemonic
723739
: b6a6d8921942dd9806607ebc2750416b289adea669198769f2e15ed926c3aa92bf88ece232317b4ea463e84b0fcd3b53577812ee449ccc448eb45e6f544e25b6
724740
#+LATEX: }
725741

@@ -729,11 +745,12 @@ to a USB drive for printing (or directly printed without the file being saved to
729745
#+BEGIN_SRC bash :exports both :results output
730746
( python3 -m slip39.recovery --bip39 \
731747
--mnemonic "zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo wrong" \
732-
| python3 -m slip39 --secret - --no-card -q ) 2>&1
748+
| python3 -m slip39 --secret - --no-card ) 2>&1
733749
#+END_SRC
734750
#+RESULTS:
735-
: 2022-01-03 09:35:07 slip39.recovery Recovered 512-bit BIP-39 secret from english mnemonic
736-
: 2022-01-03 09:35:07 slip39 ETH m/44'/60'/0'/0/0 : 0xfc2077CA7F403cBECA41B1B0F62D91B5EA631B5E
751+
: 2022-01-12 19:16:34 slip39.recovery Recovered 512-bit BIP-39 secret from english mnemonic
752+
: 2022-01-12 19:16:34 slip39 ETH m/44'/60'/0'/0/0 : 0xfc2077CA7F403cBECA41B1B0F62D91B5EA631B5E
753+
: 2022-01-12 19:16:34 slip39 BTC m/44'/0'/0'/0/0 : 1EjnS13zBgN6tUgy6U64qFeh53fyAeUsqE
737754
#+LATEX: }
738755

739756
This =0xfc20..1B5E= address is the same Ethereum address as is recovered on a Trezor using this

README.pdf

2.3 KB
Binary file not shown.

slip39/api.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,7 @@ def create(
187187
cryptopaths = cryptopaths or [('ETH',None), ('BTC',None)],
188188
allow_unbounded = False,
189189
))
190-
print( f"{accts!r}" )
190+
191191
groups = {
192192
g_name: (g_of, g_mnems)
193193
for (g_name,(g_of, _),g_mnems) in zip( g_names, g_dims, mnems )

slip39/main.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -194,11 +194,10 @@ def main( argv=None ):
194194
ap.add_argument( 'names', nargs="*",
195195
help="Account names to produce")
196196
args = ap.parse_args( argv )
197-
logging.debug( f"{args}" )
198-
log_cfg['level'] = log_level( args.verbose - args.quiet )
199197

200198
# Set up logging; also, handle the degenerate case where logging has *already* been set up (and
201199
# basicConfig is a NO-OP), by (also) setting the logging level
200+
log_cfg['level'] = log_level( args.verbose - args.quiet )
202201
logging.basicConfig( **log_cfg )
203202
if args.verbose:
204203
logging.getLogger().setLevel( log_cfg['level'] )
@@ -308,8 +307,6 @@ def main( argv=None ):
308307
for account in group
309308
if account.crypto == 'ETH'
310309
):
311-
if account.crypto != 'ETH':
312-
continue
313310
json_str = json.dumps( eth_account.Account.encrypt( eth.key, json_pwd ), indent=4 )
314311
json_name = args.output.format(
315312
name = name or "SLIP39",

0 commit comments

Comments
 (0)