Skip to content

Commit c622213

Browse files
committed
Working BIP-39 backup and recovery
1 parent d9dc3f1 commit c622213

30 files changed

+1761
-1083
lines changed

App.pdf

131 Bytes
Binary file not shown.

App.txt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@ The best practice for using these wallets is to load this "Seed" into a
1919
secure hardware device, like a [Trezor "Model T"] hardware wallet.
2020
SLIP-39 Mnemonic cards contain the recovery words, which are typed
2121
directly into the Trezor device to recover the Seed, and all of its
22-
Cryptocurrency accounts.
22+
Cryptocurrency accounts. For the [Ledger Nano] and other hardware
23+
wallets supporting only BIP-39 Mnemonics, you can now use the SLIP-39
24+
App to securely and reliably back up these BIP-39 phrases.
2325

2426
The [macOS and win32 SLIP-39 App (download here – .dmg for macOS, .msi
2527
for Windows)] helps you generate Mnemonic cards and back up this Seed,
@@ -54,6 +56,9 @@ Table of Contents
5456
[Trezor "Model T"]
5557
<https://shop.trezor.io/product/trezor-model-t?offer_id=15&aff_id=10388>
5658

59+
[Ledger Nano]
60+
<https://shop.ledger.com/pages/ledger-nano-x?r=2cd1cb6ae51f>
61+
5762
[macOS and win32 SLIP-39 App (download here – .dmg for macOS, .msi for
5863
Windows)] <https://github.com/pjkundert/python-slip39/releases/latest>
5964

GNUmakefile

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ else
4141
endif
4242

4343
# To see all pytest output, uncomment --capture=no
44-
PYTESTOPTS = -vv --doctest-modules # --capture=no --log-cli-level=INFO
44+
PYTESTOPTS = -vv --doctest-modules --capture=no --log-cli-level=INFO
4545

4646
PY3TEST = $(PY3) -m pytest $(PYTESTOPTS)
4747

@@ -102,20 +102,25 @@ build: clean wheel
102102
#
103103
# org-mode products.
104104
#
105-
# deps-txt: All of the gui/.txt files needed to built, before the sdist, wheel or app
105+
# deps: All of the gui/.txt files needed to built, before the sdist, wheel or app
106106
#
107107
%.txt: %.org
108108
emacs $< --batch -f org-ascii-export-to-ascii --kill
109109

110-
GUI_TXT = $(patsubst %.org,%.txt,$(wildcard slip39/gui/*.org))
110+
TXT = $(patsubst %.org,%.txt,$(wildcard slip39/*/*.org))
111111

112112
slip39/gui/SLIP-39.txt:
113113
toilet --font ascii12 SLIP-39 > $@
114114
@echo " Safe & Effective (tm) Crypto Wallet Backup and Recovery" >> $@
115115
@echo " (explanations and instructions will appear here)" >> $@
116116

117+
slip39/layout/COVER.txt:
118+
toilet --width 200 https://slip39.com > $@
119+
@echo " Safe & Effective (tm) Crypto Wallet Backup and Recovery" >> $@
120+
@echo " (explanations and instructions will appear here)" >> $@
121+
117122
# Any build dependencies that are dynamically generated, and may need updating from time to time
118-
build-deps: $(GUI_TXT) slip39/gui/SLIP-39.txt
123+
deps: $(TXT) slip39/gui/SLIP-39.txt slip39/layout/COVER.txt
119124

120125
#
121126
# VirtualEnv build, install and activate
@@ -149,7 +154,7 @@ $(VENV_LOCAL)/$(VENV_NAME)-activate: $(VENV_LOCAL)/$(VENV_NAME)
149154
@bash --init-file $</venv-activate.sh -i
150155

151156

152-
wheel: build-deps dist/slip39-$(VERSION)-py3-none-any.whl
157+
wheel: deps dist/slip39-$(VERSION)-py3-none-any.whl
153158

154159
dist/slip39-$(VERSION)-py3-none-any.whl: build-check FORCE
155160
$(PY3) -m build
@@ -167,10 +172,10 @@ install: dist/slip39-$(VERSION)-py3-none-any.whl FORCE
167172
# o TODO: no signed and notarized package yet accepted for upload by macOS App Store
168173
installer: $(INSTALLER)
169174

170-
dmg: build-deps app-dmg-valid
171-
msi: build-deps dist/slip39-$(VERSION)-win64.msi
172-
exe: build-deps build/exe.$(CXFREEZE_EXT)/SLIP-39.exe
173-
app: build-deps dist/SLIP-39.app
175+
dmg: deps app-dmg-valid
176+
msi: deps dist/slip39-$(VERSION)-win64.msi
177+
exe: deps build/exe.$(CXFREEZE_EXT)/SLIP-39.exe
178+
app: deps dist/SLIP-39.app
174179

175180
app-packages: app-zip-valid app-dmg-valid app-pkg-valid
176181
app-upload: app-dmg-upload

README.org

Lines changed: 509 additions & 321 deletions
Large diffs are not rendered by default.

README.pdf

-70 Bytes
Binary file not shown.

README.txt

Lines changed: 646 additions & 469 deletions
Large diffs are not rendered by default.

images/BIP-39-backup-entropy.png

33.7 KB
Loading

setup.py

Lines changed: 61 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -48,18 +48,18 @@
4848
icon_win = "images/SLIP-39.ico"
4949

5050
shortcut = (
51-
"DesktopShortcut", # Shortcut
52-
"DesktopFolder", # Directory_
53-
"SLIP-39", # Name
54-
"TARGETDIR", # Component_
55-
"[TARGETDIR]SLIP-39.exe", # Target
56-
None, # Arguments
57-
None, # Description
58-
None, # Hotkey
59-
None, # Icon
60-
None, # IconIndex
61-
None, # ShowCmd
62-
"TARGETDIR", # WkDir
51+
"DesktopShortcut", # Shortcut
52+
"DesktopFolder", # Directory_
53+
"SLIP-39", # Name
54+
"TARGETDIR", # Component_
55+
"[TARGETDIR]SLIP-39.exe", # Target
56+
None, # Arguments
57+
None, # Description
58+
None, # Hotkey
59+
None, # Icon
60+
None, # IconIndex
61+
None, # ShowCmd
62+
"TARGETDIR", # WkDir
6363
)
6464

6565
msi_data = dict(
@@ -171,34 +171,57 @@
171171

172172
long_description_content_type = 'text/markdown'
173173
long_description = """\
174-
Creating Ethereum, Bitcoin and other accounts is complex and fraught with potential for loss of funds.
175-
176-
A BIP-39 seed recovery phrase helps, but a *single* lapse in security dooms the account (and all
177-
derived accounts, in fact). If someone finds your recovery phrase (or you lose it), the accounts
178-
derived from that seed are /gone/.
179-
180-
The SLIP-39 standard allows you to split the seed between 1, 2, or more groups of several mnemonic
181-
recovery phrases. This is better, but creating such accounts is difficult; presently, only the
182-
Trezor supports these, and they can only be created "manually". Writing down 5 or more sets of 20
183-
words is difficult, error-prone and time consuming.
184-
185-
The python-slip39 project exists to assist in the safe creation and documentation of Ethereum HD
186-
Wallet seeds and derived accounts, with various SLIP-39 sharing parameters. It generates the new
187-
random wallet seed, and generates the expected standard Ethereum account(s) (at derivation path
188-
=m/44'/60'/0'/0/0= by default) and Bitcoin accounts (at Bech32 derivation path =m/84'/0'/0'/0/0= by
189-
default), with wallet address and QR code (compatible with Trezor derivations). It produces the
190-
required SLIP-39 phrases, and outputs a single PDF containing all the required printable cards to
191-
document the seed (and the specified derived accounts).
192-
193-
Output of BIP-38 or JSON encrypted Paper Wallets is supported, for import into standard software
194-
cryptocurrency wallets.
195-
196-
On an secure (ideally air-gapped) computer, new seeds can safely be generated and the PDF saved to a
197-
USB drive for printing (or directly printed without the file being saved to disk.). Presently,
198-
=slip39= can output example ETH, BTC, LTC and DOGE addresses derived from the seed, to illustrate
199-
what accounts are associated with the backed-up seed. Recovery of the seed to a Trezor is simple,
174+
Creating Ethereum, Bitcoin and other accounts is complex and fraught
175+
with potential for loss of funds.
176+
177+
A BIP-39 seed recovery phrase helps, but a *single* lapse in security
178+
dooms the account (and all derived accounts, in fact). If someone finds
179+
your recovery phrase (or you lose it), the accounts derived from that
180+
seed are /gone/.
181+
182+
The SLIP-39 standard allows you to split the seed between 1, 2, or more
183+
groups of several mnemonic recovery phrases. This is better, but
184+
creating such accounts is difficult; presently, only the Trezor supports
185+
these, and they can only be created "manually". Writing down 5 or more
186+
sets of 20 words is difficult, error-prone and time consuming.
187+
188+
The [python-slip39] project (and the SLIP-39-app macOS/win32 App) exists
189+
to assist in the safe creation and documentation of Ethereum HD Wallet
190+
seeds and derived accounts, with various SLIP-39 sharing parameters. It
191+
generates the new random wallet seed, and generates the expected
192+
standard Ethereum account(s) (at derivation path `m/44'/60'/0'/0/0' by
193+
default) and Bitcoin accounts (at Bech32 derivation path
194+
`m/84'/0'/0'/0/0' by default), with wallet address and QR code
195+
(compatible with Trezor derivations). It produces the required SLIP-39
196+
phrases, and outputs a single PDF containing all the required printable
197+
cards to document the seed (and the specified derived accounts).
198+
199+
Output of BIP-38 or JSON encrypted Paper Wallets is supported, for
200+
import into standard software cryptocurrency wallets.
201+
202+
On an secure (ideally air-gapped) computer, new seeds can safely be
203+
generated and the PDF saved to a USB drive for printing (or directly
204+
printed without the file being saved to disk.). Presently, `slip39' can
205+
output example ETH, BTC, LTC, DOGE, BNB, CRO and XRP addresses derived
206+
from the seed, to /illustrate/ what accounts are associated with the
207+
backed-up seed. Recovery of the seed to a [trezor-model-t] is simple,
200208
by entering the mnemonics right on the device.
201209
210+
We also support backup of existing insecure and unreliable BIP-39
211+
recover phrases as SLIP-39 Mnemonic cards, for existing BIP-39 hardware
212+
wallets like the [ledger-nano]! Recover from your existing BIP-39
213+
Mnemonic, select "Using BIP-39", and generate a set of SLIP-39 Mnemonic
214+
cards. Later, use the SLIP-39 App to recover from your SLIP-39 Mnemonic
215+
cards, click "Using BIP-39" to get your BIP-39 Mnemonic back, and use it
216+
to recover your accounts to your Ledger (or other) hardware wallet.
217+
218+
[python-slip39]: https://github.com/pjkundert/python-slip39.git "python-slip39"
219+
220+
[trezor-model-t]: https://shop.trezor.io/product/trezor-model-t?offer_id=15&aff_id=10388 "Trezor Model T"
221+
222+
[ledger-nano]: <https://shop.ledger.com/pages/ledger-nano-x?r=2cd1cb6ae51f> "Ledger Nano"
223+
224+
202225
$ python3 -m slip39 -v Personal # or run: slip39 -v Personal
203226
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)
204227
2022-01-26 13:55:30 slip39 1st 1 sister 8 cricket 15 unhappy

slip39/api.py

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -723,6 +723,10 @@ def random_secret(
723723

724724

725725
def enumerate_mnemonic( mnemonic ):
726+
"""Return a dict containing the supplied mnemonics stored by their indexed, starting from 0.
727+
Each Mnemonic is labelled with its ordinal index (ie. beginning at 1).
728+
729+
"""
726730
if isinstance( mnemonic, str ):
727731
mnemonic = mnemonic.split( ' ' )
728732
return dict(
@@ -786,9 +790,9 @@ def create(
786790
name: str,
787791
group_threshold: int,
788792
groups: Dict[str,Tuple[int, int]],
789-
master_secret: bytes = None, # Default: 128-bit seeds
793+
master_secret: bytes = None, # Default: 128-bit Seed Entropy
790794
passphrase: bytes = b"",
791-
using_bip39: bool = False, # Generate wallet Seed from master_secret Entropy using BIP-39 generation
795+
using_bip39: bool = False, # Produce wallet Seed from master_secret Entropy using BIP-39 generation
792796
iteration_exponent: int = 1,
793797
cryptopaths: Optional[Sequence[Union[str,Tuple[str,str]]]] = None, # default: ETH, BTC at default paths
794798
strength: int = 128,
@@ -801,11 +805,10 @@ def create(
801805
used in the SLIP-39 standard fashion (not recommended -- not Trezor "Model T" compatible).
802806
803807
If using_bip39, creates the Cryptocurrency accountgroups from the supplied master_secret
804-
Entropy, but by generating the Seed from a BIP-38 Mnemonic produced from the provided entropy
805-
(or generated, default 128 bits), plus the supplied passphrase.
808+
Entropy, by generating the Seed from a BIP-38 Mnemonic produced from the provided entropy
809+
(or generated, default 128 bits), plus any supplied passphrase.
806810
807811
"""
808-
809812
if master_secret is None:
810813
assert strength in BITS, f"Invalid {strength}-bit secret length specified"
811814
master_secret = random_secret( strength // 8 )
@@ -822,6 +825,12 @@ def create(
822825
mnemonic = bip39_mnem,
823826
passphrase = passphrase,
824827
)
828+
log.info(
829+
f"SLIP-39 for {name} from {len(master_secret)*8}-bit Entropy using BIP-39 Mnemonic" + (
830+
f": {bip39_mnem:.10}... (w/ BIP-39 Passphrase: {passphrase!r:.2}..." # WARNING: Reveals partial Secret!
831+
if log.isEnabledFor( logging.DEBUG ) else ""
832+
)
833+
)
825834
accts = list( accountgroups(
826835
master_secret = bip39_seed,
827836
cryptopaths = cryptopaths,
@@ -831,13 +840,22 @@ def create(
831840
else:
832841
# For SLIP-39, accounts are generated directly from supplied Entropy, and passphrase
833842
# encrypts the SLIP-39 Mnemonics, below.
843+
log.info(
844+
f"SLIP-39 for {name} from {len(master_secret)*8}-bit Entropy directly" + (
845+
f": {codecs.encode( master_secret, 'hex_codec' ).decode( 'ascii' ):.10}... (w/ SLIP-39 Passphrase: {passphrase!r:.2}..." # WARNING: Reveals partial Secret!
846+
if log.isEnabledFor( logging.DEBUG ) else ""
847+
)
848+
)
834849
accts = list( accountgroups(
835850
master_secret = master_secret,
836851
cryptopaths = cryptopaths,
837852
allow_unbounded = False,
838853
))
839854

840-
# Generate the SLIP-39 Mnemonics representing the supplied
855+
# Generate the SLIP-39 Mnemonics representing the supplied master_secret Seed Entropy. This
856+
# always recovers the Seed Entropy; if not using_bip39, this is also the wallet derivation Seed;
857+
# if using_bip39, the wallet derivation Seed was produced from the BIP-39 Seed generation
858+
# process (and the SLIP-39 password is always b"", here).
841859
mnems = mnemonics(
842860
group_threshold = group_threshold,
843861
groups = g_dims,

slip39/generator/main.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,6 @@ def main( argv=None ):
7171
data is random, while the nonce is not, but since this construction is only used once, it should be
7272
satisfactory. This first nonce record is transmitted with an enumeration prefix of "nonce".
7373
74-
7574
""" )
7675

7776
ap.add_argument( '-v', '--verbose', action="count",
@@ -91,13 +90,13 @@ def main( argv=None ):
9190
ap.add_argument( '-c', '--cryptocurrency', action='append',
9291
default=[],
9392
help="A crypto name and optional derivation path (default: \"ETH:{Account.path_default('ETH')}\"), optionally w/ ranges, eg: ETH:../0/-" )
94-
ap.add_argument( '-p', '--path',
93+
ap.add_argument( '--path',
9594
default=None,
9695
help="Modify all derivation paths by replacing the final segment(s) w/ the supplied range(s), eg. '.../1/-' means .../1/[0,...)")
9796
ap.add_argument( '-d', '--device', type=str,
9897
default=None,
9998
help="Use this serial device to transmit (or --receive) records" )
100-
ap.add_argument( '-b', '--baudrate', type=int,
99+
ap.add_argument( '--baudrate', type=int,
101100
default=None,
102101
help="Set the baud rate of the serial device (default: 115200)" )
103102
ap.add_argument( '-e', '--encrypt',

0 commit comments

Comments
 (0)