#+title: Ethereum SLIP-39 Account Generation
#+date: 2021-12-20 10:55:00
#+draft: false
#+EXPORT_FILE_NAME: README.pdf
#+STARTUP: org-startup-with-inline-images inlineimages
#+STARTUP: org-latex-tables-centered nil
#+OPTIONS: ^:nil # Disable sub/superscripting with bare _; _{...} still works
#+OPTIONS: toc:nil
#+LATEX_HEADER: \usepackage[margin=1.0in]{geometry}

#+BEGIN_SRC emacs-lisp :noweb no-export :exports results
;; Tables not centered
(
 setq org-latex-tables-centered nil
      org-src-preserve-indentation t
      org-edit-src-content-indentation 0
)
nil
#+END_SRC

#+RESULTS:

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

A BIP-39 seed recovery phrase helps, but a *single* lapse in security dooms the account (and all
derived accounts, in fact).  If someone finds your recovery phrase (or you lose it), the accounts
derived from that seed are /gone/.

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

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
documentation of [[https://wolovim.medium.com/ethereum-201-hd-wallets-11d0c93c87][Ethereum HD Wallet]] seeds and derived accounts, with various SLIP-39 sharing
parameters.  It generates the new 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]] =m/44'/60'/0'/0/0= by default) and Bitcoin accounts (at Bech32
derivation path =m/84'/0'/0'/0/0= by default), with wallet address and QR code (compatible with
Trezor derivations).  It produces the required SLIP-39 phrases, and outputs a single PDF containing
all the required printable cards to document the seed (and the specified derived accounts).

Output of BIP-38 or JSON encrypted Paper Wallets is supported, for import into standard software
cryptocurrency wallets.

On an secure (ideally air-gapped) computer, new seeds can safely be generated and the PDF saved to a
USB drive for printing (or directly printed without the file being saved to disk.).  Presently,
=slip39= can output example ETH, BTC, LTC and DOGE addresses derived from the seed, to illustrate
what accounts are associated with the backed-up seed.  Recovery of the seed to a Trezor is simple,
by entering the mnemonics right on the device.
#+END_ABSTRACT
#+TOC: headlines 2

* Security with Availability

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

  Losing this seed means that all of the HD Wallet accounts are permanently lost.  It must be /both/
  backed up securely, /and/ be readily accessible.

  Therefore, we must:

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

  How can we address these conflicting requirements?

** Shamir's Secret Sharing System (SSSS)

   [[https://github.com/satoshilabs/slips/blob/master/slip-0039.md][Satoshi Lab's (Trezor) SLIP-39]] uses SSSS to distribute the ability to recover the key to 1 or
   more "groups".  Collecting the mnemonics from the required number of groups allows recovery of
   the seed.  For BIP-39, the number of groups is always 1, and the number of mnemonics required for
   that group is always 1.

   For SLIP-39, a "group_threshold" of how many groups must bet successfully collected to recover
   the key.  Then key is (conceptually) split between 1 or more groups (not really; each group's
   data alone gives away no information about the key).

   For example, you might have First, Second, Fam and Frens groups, and decide that any 2 groups can
   be combined to recover the key.  Each group has members with varying levels of trust and
   persistence, so have different number of Members, and differing numbers Required to recover that
   group's data:

   #+LATEX: {\scriptsize
   | Group  | Required |   | Members | Description                           |
   |--------+----------+---+---------+---------------------------------------|
   |        |      <r> |   | <l>     |                                       |
   | First  |        1 | / | 1       | Stored at home                        |
   | Second |        1 | / | 1       | Stored in office safe                 |
   | Fam    |        2 | / | 4       | Distributed to family members         |
   | Frens  |        3 | / | 6       | Distributed to friends and associates |
   #+LATEX: }

   The account owner might store their First and Second group data in their home and office safes.
   These are 1/1 groups (1 required, and only 1 member, so each of these are3 1-card groups.)

   If the account needs to be recovered, collecting the First and Second cards from the home and
   office safe is sufficient to recover the seed, and re-generate the HD Wallet accounts.

   Only 2 Fam member's cards must be collected to recover the Fam group's data.  So, if the HD
   Wallet owner loses their home and First group card in a fire, they could get the Second group
   card from the office safe, and 2 cards from Fam group members, and recover the wallet.

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

* SLIP-39 Account Creation, Recovery and Address Generation

  Generating a new SLIP-39 encoded seed is easy, with results available as PDF and text.  Any number
  of accounts can be generated from this seed, and it can be recovered by collecting the desired
  groups of recover card phrases.  The default recovery groups are as described above.

** Creating New SLIP-39 Recoverable Seeds

   This is what the first page of the output SLIP-39 mnemonic cards PDF looks like:

   #+CAPTION: SLIP-39 Cards PDF (from =--secret ffff...=)
   #+ATTR_LATEX: :width 6in :options angle=0
   [[./images/slip39-cards.png]]

   Run the following to obtain a PDF file containing index cards with the default SLIP-39 groups for
   a new account seed named "Personal"; insert a USB drive to collect the output, and run:

   #+LATEX: {\scriptsize
   #+BEGIN_EXAMPLE
   $ python3 -m pip install slip39        # Install slip39 in Python3
   $ cd /Volumes/USBDRIVE/                # Change current directory to USB
   $ python3 -m slip39 Personal           # Or just run "slip39 Personal"
   2021-12-25 11:10:38 slip39           ETH m/44'/60'/0'/0/0    : 0xb44A2011A99596671d5952CdC22816089f142FB3
   2021-12-25 11:10:38 slip39           Wrote SLIP-39-encoded wallet for 'Personal' to:\
     Personal-2021-12-22+15.45.36-0xb44A2011A99596671d5952CdC22816089f142FB3.pdf
   #+END_EXAMPLE
   #+LATEX: }
  
   The resultant PDF will be output into the designated file.
  
   This PDF file can be printed on 3x5 index cards, or on regular paper or card stock and the cards
   can be cut out (=--card credit= , =business=, =half= (page) and =third= (page) are also
   available, as well as custom ="(<h>,<w>),<margin>"=).
  
   To get the data printed on the terminal as in this example (so you could write it down on cards
   instead), add a =-v= (to see it logged in a tabular format), or =--text= to have it printed to
   stdout in full lines (ie. for pipelining to other programs).

*** Paper Wallets

    The Trezor hardware wallet natively supports the input of SLIP-39 Mnemonics.  However, most
    software wallets do not (yet) support SLIP-39.  So, how do we load the Crypto wallets produced
    from our Seed into software wallets such as the Metamask plugin or the Brave browser, for
    example?

    The =slip39.gui= (and the macOS SLIP-39.App) support output of standard BIP-38 encrypted wallets
    for Bitcoin-like cryptocurrencies such as BTC, LTC and DOGE.  It also outputs encrypted Ethereum
    JSON wallets for ETH.  Here is how to produce them (from a test secret Seed; exclude =--secret
    ffff...= for yours!):

    #+LATEX: {\scriptsize
    #+BEGIN_EXAMPLE
    $ slip39 -c ETH -c BTC -c DOGE -c LTC --secret ffffffffffffffffffffffffffffffff \
        --wallet password --wallet-hint 'bad:pass...'
    #+END_EXAMPLE
    #+LATEX: }

    And what they look like:
    
    #+CAPTION: Paper Wallets (from =--secret ffff...=)
    #+ATTR_LATEX: :width 6in :options angle=0
   [[./images/slip39-wallets.png]]

** The macOS =SLIP-39.app= GUI App

   If you prefer a graphical user-interface, try the macOS SLIP-39.App.  You can run it directly if
   you install Python 3.9+ from [[https://python.org/downloads][python.org/downloads]] or using homebrew =brew install
   python-tk@3.10=.  Then, start the GUI in a variety of ways:

   #+LATEX: {\scriptsize
   #+BEGIN_EXAMPLE
   slip39-gui
   python3 -m slip39.gui
   #+END_EXAMPLE
   #+LATEX: }

   Alternatively, download and install the macOS GUI App .zip, .pkg or .dmg installer from
   [[https://github.com/pjkundert/python-slip39/releases/latest][github.com/pjkundert/python-slip-39/releases]].

** The Python =slip39= CLI

   From the command line, you can create SLIP-39 seed Mnemonic card PDFs.

*** =slip39= Synopsis

    The full command-line argument synopsis for =slip39= is:

    #+LATEX: {\scriptsize
    #+BEGIN_SRC bash :exports both :results raw
    slip39 --help               | sed 's/^/: /' # (just for output formatting)
    #+END_SRC

    #+RESULTS:
    : usage: slip39 [-h] [-v] [-q] [-o OUTPUT] [-t THRESHOLD] [-g GROUP] [-f FORMAT]
    :               [-c CRYPTOCURRENCY] [-p PATH] [-j JSON] [-w WALLET]
    :               [--wallet-hint WALLET_HINT] [--wallet-format WALLET_FORMAT]
    :               [-s SECRET] [--bits BITS] [--passphrase PASSPHRASE] [-C CARD]
    :               [--paper PAPER] [--no-card] [--text]
    :               [names ...]
    : 
    : Create and output SLIP-39 encoded Seeds and Paper Wallets to a PDF file.
    : 
    : positional arguments:
    :   names                 Account names to produce
    : 
    : options:
    :   -h, --help            show this help message and exit
    :   -v, --verbose         Display logging information.
    :   -q, --quiet           Reduce logging output.
    :   -o OUTPUT, --output OUTPUT
    :                         Output PDF to file or '-' (stdout); formatting w/
    :                         name, date, time, crypto, path, address allowed
    :   -t THRESHOLD, --threshold THRESHOLD
    :                         Number of groups required for recovery (default: half
    :                         of groups, rounded up)
    :   -g GROUP, --group GROUP
    :                         A group name[[<require>/]<size>] (default: <size> = 1,
    :                         <require> = half of <size>, rounded up, eg.
    :                         'Frens(3/5)' ).
    :   -f FORMAT, --format FORMAT
    :                         Specify crypto address formats: legacy, segwit,
    :                         bech32; default BTC:bech32, DOGE:legacy, ETH:legacy,
    :                         LTC:bech32
    :   -c CRYPTOCURRENCY, --cryptocurrency CRYPTOCURRENCY
    :                         A crypto name and optional derivation path (eg.
    :                         '../<range>/<range>'); defaults: BTC:m/84'/0'/0'/0/0,
    :                         DOGE:m/44'/3'/0'/0/0, ETH:m/44'/60'/0'/0/0,
    :                         LTC:m/84'/2'/0'/0/0
    :   -p PATH, --path PATH  Modify all derivation paths by replacing the final
    :                         segment(s) w/ the supplied range(s), eg. '.../1/-'
    :                         means .../1/[0,...)
    :   -j JSON, --json JSON  Save an encrypted JSON wallet for each Ethereum
    :                         address w/ this password, '-' reads it from stdin
    :                         (default: None)
    :   -w WALLET, --wallet WALLET
    :                         Produce paper wallets in output PDF; each wallet
    :                         private key is encrypted this password
    :   --wallet-hint WALLET_HINT
    :                         Paper wallets password hint
    :   --wallet-format WALLET_FORMAT
    :                         Paper wallet size; third or '(<h>,<w>),<margin>'
    :                         (default: third)
    :   -s SECRET, --secret SECRET
    :                         Use the supplied 128-, 256- or 512-bit hex value as
    :                         the secret seed; '-' reads it from stdin (eg. output
    :                         from slip39.recover)
    :   --bits BITS           Ensure that the seed is of the specified bit length;
    :                         128, 256, 512 supported.
    :   --passphrase PASSPHRASE
    :                         Encrypt the master secret w/ this passphrase, '-'
    :                         reads it from stdin (default: None/'')
    :   -C CARD, --card CARD  Card size; index, credit, business, half, third or
    :                         '(<h>,<w>),<margin>' (default: index)
    :   --paper PAPER         Paper size (default: Letter)
    :   --no-card             Disable PDF SLIP-39 mnemonic card output
    :   --text                Enable textual SLIP-39 mnemonic output to stdout
    #+LATEX: }

** Recovery & Re-Creation

  Later, if you need to recover the wallet seed, keep entering SLIP-39 mnemonics into
  =slip39-recovery= until the secret is recovered (invalid/duplicate mnemonics will be ignored):

  #+LATEX: {\scriptsize
  #+BEGIN_EXAMPLE
  $ python3 -m slip39.recovery   # (or just "slip39-recovery")
  Enter 1st SLIP-39 mnemonic: ab c
  Enter 2nd SLIP-39 mnemonic: veteran guilt acrobat romp burden campus purple webcam uncover ...
  Enter 3rd SLIP-39 mnemonic: veteran guilt acrobat romp burden campus purple webcam uncover ...
  Enter 4th SLIP-39 mnemonic: veteran guilt beard romp dragon island merit burden aluminum worthy ...
  2021-12-25 11:03:33 slip39.recovery  Recovered SLIP-39 secret; Use:  python3 -m slip39 --secret ...
  383597fd63547e7c9525575decd413f7
  #+END_EXAMPLE
  #+LATEX: }

  Finally, re-create the wallet seed, perhaps including an encrypted JSON wallet file for import of
  some accounts into a software wallet:

  #+LATEX: {\scriptsize
  #+BEGIN_SRC bash :exports both :results output
  slip39 --secret 383597fd63547e7c9525575decd413f7 --json password 2>&1
  #+END_SRC

  #+RESULTS:
  : 2022-02-21 13:10:45 slip39           It is recommended to not use '-s|--secret <hex>'; specify '-' to read from input
  : 2022-02-21 13:10:45 slip39.layout    ETH    m/44'/60'/0'/0/0    : 0xb44A2011A99596671d5952CdC22816089f142FB3
  : 2022-02-21 13:10:45 slip39.layout    BTC    m/84'/0'/0'/0/0     : bc1qcupw7k8enymvvsa7w35j5hq4ergtvus3zk8a8s
  : 2022-02-21 13:10:45 slip39.layout    It is recommended to not use '-j|--json <password>'; specify '-' to read from input
  : 2022-02-21 13:10:45 slip39.layout    Wrote JSON SLIP39's encrypted ETH wallet 0xb44A2011A99596671d5952CdC22816089f142FB3 derived at m/44'/60'/0'/0/0 to: SLIP39-2022-02-21+13.10.45-ETH-0xb44A2011A99596671d5952CdC22816089f142FB3.json
  : 2022-02-21 13:10:46 slip39.layout    Wrote SLIP39-encoded wallet for 'SLIP39' to: SLIP39-2022-02-21+13.10.45-ETH-0xb44A2011A99596671d5952CdC22816089f142FB3.pdf

  #+LATEX: }

*** =slip39.recovery= Synopsis

    #+LATEX: {\scriptsize
    #+BEGIN_SRC bash :exports both :results raw
    slip39-recovery --help         | sed 's/^/: /' # (just for output formatting)
    #+END_SRC

    #+RESULTS:
    : usage: slip39-recovery [-h] [-v] [-q] [-b] [-m MNEMONIC] [-p PASSPHRASE]
    : 
    : Recover and output secret seed from SLIP39 or BIP39 mnemonics
    : 
    : options:
    :   -h, --help            show this help message and exit
    :   -v, --verbose         Display logging information.
    :   -q, --quiet           Reduce logging output.
    :   -b, --bip39           Recover 512-bit secret seed from BIP-39 mnemonics
    :   -m MNEMONIC, --mnemonic MNEMONIC
    :                         Supply another SLIP-39 (or a BIP-39) mnemonic phrase
    :   -p PASSPHRASE, --passphrase PASSPHRASE
    :                         Decrypt the master secret w/ this passphrase, '-'
    :                         reads it from stdin (default: None/'')
    : 
    : If you obtain a threshold number of SLIP-39 mnemonics, you can recover the original
    : secret seed, and re-generate one or more Ethereum wallets from it.
    : 
    : Enter the mnemonics when prompted and/or via the command line with -m |--mnemonic "...".
    : 
    : The master secret seed can then be used to generate a new SLIP-39 encoded wallet:
    : 
    :     python3 -m slip39 --secret = "ab04...7f"
    : 
    : BIP-39 wallets can be backed up as SLIP-39 wallets, but only at the cost of 59-word SLIP-39
    : mnemonics.  This is because the *output* 512-bit BIP-39 seed must be stored in SLIP-39 -- not the
    : *input* 128-, 160-, 192-, 224-, or 256-bit entropy used to create the original BIP-39 mnemonic
    : phrase.

*** Pipelining =slip39.recovery | slip39 --secret -=

   The tools can be used in a pipeline to avoid printing the secret.  Here we generate some
   mnemonics, sorting them in reverse order so we need more than just the first couple to recover.
   Observe the Ethereum wallet address generated.

   Then, we recover the master secret seed in hex with =slip39-recovery=, and finally send it to
   =slip39 --secret -= to re-generate the same wallet as we originally created.

   #+LATEX: {\scriptsize
   #+BEGIN_SRC bash :exports both :results output
   ( python3 -m slip39 --text --no-card -v \
       | sort -r \
       | python3 -m slip39.recovery \
       | python3 -m slip39 --secret - --no-card -q ) 2>&1
   #+END_SRC
   #+RESULTS:
   #+begin_example
   2022-02-21 13:11:01 slip39           First(1/1): Recover w/ 2 of 4 groups First(1), Second(1), Fam(2/4), Frens(3/6)
   2022-02-21 13:11:01 slip39           1st  1 species    8 pumps     15 maximum   
   2022-02-21 13:11:01 slip39                2 pipeline   9 timber    16 crazy     
   2022-02-21 13:11:01 slip39                3 acrobat   10 epidemic  17 spill     
   2022-02-21 13:11:01 slip39                4 romp      11 lawsuit   18 surprise  
   2022-02-21 13:11:01 slip39                5 cage      12 coding    19 forecast  
   2022-02-21 13:11:01 slip39                6 umbrella  13 process   20 born      
   2022-02-21 13:11:01 slip39                7 mandate   14 ordinary  
   2022-02-21 13:11:01 slip39           Second(1/1): Recover w/ 2 of 4 groups First(1), Second(1), Fam(2/4), Frens(3/6)
   2022-02-21 13:11:01 slip39           1st  1 species    8 wavy      15 wits      
   2022-02-21 13:11:01 slip39                2 pipeline   9 family    16 smoking   
   2022-02-21 13:11:01 slip39                3 beard     10 style     17 receiver  
   2022-02-21 13:11:01 slip39                4 romp      11 boundary  18 cleanup   
   2022-02-21 13:11:01 slip39                5 dictate   12 endless   19 avoid     
   2022-02-21 13:11:01 slip39                6 easy      13 equation  20 penalty   
   2022-02-21 13:11:01 slip39                7 random    14 inherit   
   2022-02-21 13:11:01 slip39           Fam(2/4): Recover w/ 2 of 4 groups First(1), Second(1), Fam(2/4), Frens(3/6)
   2022-02-21 13:11:01 slip39           1st  1 species    8 segment   15 hawk      
   2022-02-21 13:11:01 slip39                2 pipeline   9 slice     16 percent   
   2022-02-21 13:11:01 slip39                3 ceramic   10 hamster   17 discuss   
   2022-02-21 13:11:01 slip39                4 roster    11 boundary  18 adapt     
   2022-02-21 13:11:01 slip39                5 ancestor  12 entrance  19 cleanup   
   2022-02-21 13:11:01 slip39                6 lunch     13 glimpse   20 stadium   
   2022-02-21 13:11:01 slip39                7 criminal  14 diet      
   2022-02-21 13:11:01 slip39           2nd  1 species    8 taste     15 garden    
   2022-02-21 13:11:01 slip39                2 pipeline   9 briefing  16 careful   
   2022-02-21 13:11:01 slip39                3 ceramic   10 duckling  17 adjust    
   2022-02-21 13:11:01 slip39                4 scared    11 aircraft  18 prepare   
   2022-02-21 13:11:01 slip39                5 dragon    12 process   19 biology   
   2022-02-21 13:11:01 slip39                6 filter    13 install   20 prize     
   2022-02-21 13:11:01 slip39                7 solution  14 submit    
   2022-02-21 13:11:01 slip39           3rd  1 species    8 timely    15 jury      
   2022-02-21 13:11:01 slip39                2 pipeline   9 pile      16 impulse   
   2022-02-21 13:11:01 slip39                3 ceramic   10 segment   17 shrimp    
   2022-02-21 13:11:01 slip39                4 shadow    11 dictate   18 username  
   2022-02-21 13:11:01 slip39                5 database  12 diet      19 cleanup   
   2022-02-21 13:11:01 slip39                6 cowboy    13 enlarge   20 evening   
   2022-02-21 13:11:01 slip39                7 sunlight  14 vintage   
   2022-02-21 13:11:01 slip39           4th  1 species    8 wrist     15 idle      
   2022-02-21 13:11:01 slip39                2 pipeline   9 impulse   16 shaped    
   2022-02-21 13:11:01 slip39                3 ceramic   10 method    17 username  
   2022-02-21 13:11:01 slip39                4 sister    11 clay      18 froth     
   2022-02-21 13:11:01 slip39                5 carbon    12 software  19 biology   
   2022-02-21 13:11:01 slip39                6 vexed     13 froth     20 deploy    
   2022-02-21 13:11:01 slip39                7 civil     14 broken    
   2022-02-21 13:11:01 slip39           Frens(3/6): Recover w/ 2 of 4 groups First(1), Second(1), Fam(2/4), Frens(3/6)
   2022-02-21 13:11:01 slip39           1st  1 species    8 true      15 main      
   2022-02-21 13:11:01 slip39                2 pipeline   9 thorn     16 tofu      
   2022-02-21 13:11:01 slip39                3 decision  10 viral     17 nylon     
   2022-02-21 13:11:01 slip39                4 round     11 alpha     18 length    
   2022-02-21 13:11:01 slip39                5 become    12 charity   19 health    
   2022-02-21 13:11:01 slip39                6 listen    13 short     20 browser   
   2022-02-21 13:11:01 slip39                7 main      14 recover   
   2022-02-21 13:11:01 slip39           2nd  1 species    8 glasses   15 elevator  
   2022-02-21 13:11:01 slip39                2 pipeline   9 triumph   16 canyon    
   2022-02-21 13:11:01 slip39                3 decision  10 large     17 home      
   2022-02-21 13:11:01 slip39                4 scatter   11 slice     18 pile      
   2022-02-21 13:11:01 slip39                5 both      12 acrobat   19 declare   
   2022-02-21 13:11:01 slip39                6 upstairs  13 universe  20 thank     
   2022-02-21 13:11:01 slip39                7 gross     14 hospital  
   2022-02-21 13:11:01 slip39           3rd  1 species    8 always    15 secret    
   2022-02-21 13:11:01 slip39                2 pipeline   9 quick     16 oral      
   2022-02-21 13:11:01 slip39                3 decision  10 magazine  17 increase  
   2022-02-21 13:11:01 slip39                4 shaft     11 density   18 victim    
   2022-02-21 13:11:01 slip39                5 desert    12 ting      19 forbid    
   2022-02-21 13:11:01 slip39                6 analysis  13 agree     20 emphasis  
   2022-02-21 13:11:01 slip39                7 work      14 goat      
   2022-02-21 13:11:01 slip39           4th  1 species    8 leaf      15 august    
   2022-02-21 13:11:01 slip39                2 pipeline   9 revenue   16 grumpy    
   2022-02-21 13:11:01 slip39                3 decision  10 advance   17 luck      
   2022-02-21 13:11:01 slip39                4 skin      11 walnut    18 sniff     
   2022-02-21 13:11:01 slip39                5 density   12 scroll    19 adult     
   2022-02-21 13:11:01 slip39                6 kitchen   13 cause     20 romantic  
   2022-02-21 13:11:01 slip39                7 bedroom   14 raisin    
   2022-02-21 13:11:01 slip39           5th  1 species    8 inside    15 group     
   2022-02-21 13:11:01 slip39                2 pipeline   9 glance    16 clothes   
   2022-02-21 13:11:01 slip39                3 decision  10 submit    17 mixture   
   2022-02-21 13:11:01 slip39                4 snake     11 muscle    18 jump      
   2022-02-21 13:11:01 slip39                5 debris    12 guard     19 typical   
   2022-02-21 13:11:01 slip39                6 airport   13 merchant  20 cinema    
   2022-02-21 13:11:01 slip39                7 society   14 rhyme     
   2022-02-21 13:11:01 slip39           6th  1 species    8 wrist     15 problem   
   2022-02-21 13:11:01 slip39                2 pipeline   9 heat      16 swing     
   2022-02-21 13:11:01 slip39                3 decision  10 extra     17 gross     
   2022-02-21 13:11:01 slip39                4 spider    11 fragment  18 garbage   
   2022-02-21 13:11:01 slip39                5 dictate   12 energy    19 reaction  
   2022-02-21 13:11:01 slip39                6 hybrid    13 railroad  20 strike    
   2022-02-21 13:11:01 slip39                7 dryer     14 guitar    
   2022-02-21 13:11:01 slip39.layout    ETH    m/44'/60'/0'/0/0    : 0x1742650B2396B6B968E9D40C4765c7d48de90851
   2022-02-21 13:11:01 slip39.layout    BTC    m/84'/0'/0'/0/0     : bc1qk6y6kqufdv4vfuxatkray0vmtdkzl637zxwnnz
   2022-02-21 13:11:01 slip39.recovery  Recovered 128-bit SLIP-39 secret with 5 (1st, 2nd, 3rd, 7th, 8th) of 8 supplied mnemonics
   #+end_example
   #+LATEX: }

** Generation of Addresses

   For systems that require a stream of groups of wallet Addresses (eg. for preparing invoices for
   clients, with a choice of cryptocurrency payment options), =slip-generator= can produce a stream
   of groups of addresses.

*** =slip39-generator= Synopsis
   
    #+LATEX: {\scriptsize
    #+BEGIN_SRC bash :exports both :results raw
    slip39-generator --help --version         | sed 's/^/: /' # (just for output formatting)
    #+END_SRC

    #+RESULTS:
    : usage: slip39-generator [-h] [-v] [-q] [-s SECRET] [-f FORMAT]
    :                         [-c CRYPTOCURRENCY] [-p PATH] [-d DEVICE]
    :                         [-b BAUDRATE] [-e ENCRYPT] [--decrypt ENCRYPT]
    :                         [--enumerated] [--no-enumerate] [--receive]
    :                         [--corrupt CORRUPT]
    : 
    : Generate public wallet address(es) from a secret seed
    : 
    : options:
    :   -h, --help            show this help message and exit
    :   -v, --verbose         Display logging information.
    :   -q, --quiet           Reduce logging output.
    :   -s SECRET, --secret SECRET
    :                         Use the supplied 128-, 256- or 512-bit hex value as
    :                         the secret seed; '-' (default) reads it from stdin
    :                         (eg. output from slip39.recover)
    :   -f FORMAT, --format FORMAT
    :                         Specify crypto address formats: legacy, segwit,
    :                         bech32; default BTC:bech32, DOGE:legacy, ETH:legacy,
    :                         LTC:bech32
    :   -c CRYPTOCURRENCY, --cryptocurrency CRYPTOCURRENCY
    :                         A crypto name and optional derivation path (default:
    :                         "ETH:{Account.path_default('ETH')}"), optionally w/
    :                         ranges, eg: ETH:../0/-
    :   -p PATH, --path PATH  Modify all derivation paths by replacing the final
    :                         segment(s) w/ the supplied range(s), eg. '.../1/-'
    :                         means .../1/[0,...)
    :   -d DEVICE, --device DEVICE
    :                         Use this serial device to transmit (or --receive)
    :                         records
    :   -b BAUDRATE, --baudrate BAUDRATE
    :                         Set the baud rate of the serial device (default:
    :                         115200)
    :   -e ENCRYPT, --encrypt ENCRYPT
    :                         Secure the channel from errors and/or prying eyes with
    :                         ChaCha20Poly1305 encryption w/ this password; '-'
    :                         reads from stdin
    :   --decrypt ENCRYPT
    :   --enumerated          Include an enumeration in each record output (required
    :                         for --encrypt)
    :   --no-enumerate        Disable enumeration of output records
    :   --receive             Receive a stream of slip.generator output
    :   --corrupt CORRUPT     Corrupt a percentage of output symbols
    : 
    : Once you have a secret seed (eg. from slip39.recovery), you can generate a sequence
    : of HD wallet addresses from it.  Emits rows in the form:
    : 
    :     <enumeration> [<address group(s)>]
    : 
    : If the output is to be transmitted by an insecure channel (eg. a serial port), which may insert
    : errors or allow leakage, it is recommended that the records be encrypted with a cryptographic
    : function that includes a message authentication code.  We use ChaCha20Poly1305 with a password and a
    : random nonce generated at program start time.  This nonce is incremented for each record output.
    : 
    : Since the receiver requires the nonce to decrypt, and we do not want to separately transmit the
    : nonce and supply it to the receiver, the first record emitted when --encrypt is specified is the
    : random nonce, encrypted with the password, itself with a known nonce of all 0 bytes.  The plaintext
    : data is random, while the nonce is not, but since this construction is only used once, it should be
    : satisfactory.  This first nonce record is transmitted with an enumeration prefix of "nonce".

*** Producing Addresses

    Addresses can be produced in plaintext or encrypted, and output to stdout or to a serial port.

    #+LATEX: {\scriptsize
    #+BEGIN_SRC bash :exports both :results raw
    slip39-generator --secret ffffffffffffffffffffffffffffffff --path '../-3' | sed 's/^/: /' # (just for output formatting)
    #+END_SRC

    #+RESULTS:
    :     0: [["ETH", "m/44'/60'/0'/0/0", "0x824b174803e688dE39aF5B3D7Cd39bE6515A19a1"], ["BTC", "m/84'/0'/0'/0/0", "bc1q9yscq3l2yfxlvnlk3cszpqefparrv7tk24u6pl"]]
    :     1: [["ETH", "m/44'/60'/0'/0/1", "0x8D342083549C635C0494d3c77567860ee7456963"], ["BTC", "m/84'/0'/0'/0/1", "bc1qnec684yvuhfrmy3q856gydllsc54p2tx9w955c"]]
    :     2: [["ETH", "m/44'/60'/0'/0/2", "0x52787E24965E1aBd691df77827A3CfA90f0166AA"], ["BTC", "m/84'/0'/0'/0/2", "bc1q2snj0zcg23dvjpw7m9lxtu0ap0hfl5tlddq07j"]]
    :     3: [["ETH", "m/44'/60'/0'/0/3", "0xc2442382Ae70c77d6B6840EC6637dB2422E1D44e"], ["BTC", "m/84'/0'/0'/0/3", "bc1qxwekjd46aa5n0s3dtsynvtsjwsne7c5f5w5dsd"]]

    To produce accounts from a BIP-39 or SLIP-39 seed, recover it using slip39-recovery.

    Here's an example of recovering a test BIP-39 seed; note that it yields the well-known ETH
    =0xfc20...1B5E= and BTC =bc1qk0...gnn2= accounts associated with this test Mnemonic:

    #+LATEX: {\scriptsize
    #+BEGIN_SRC bash :exports both :results raw
    slip39-recovery --bip39 --mnemonic 'zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo wrong' \
        | slip39-generator --secret - --path '../-3'                          | sed 's/^/: /' # (just for output formatting)
    #+END_SRC

    #+RESULTS:
    :     0: [["ETH", "m/44'/60'/0'/0/0", "0xfc2077CA7F403cBECA41B1B0F62D91B5EA631B5E"], ["BTC", "m/84'/0'/0'/0/0", "bc1qk0a9hr7wjfxeenz9nwenw9flhq0tmsf6vsgnn2"]]
    :     1: [["ETH", "m/44'/60'/0'/0/1", "0xd1a7451beB6FE0326b4B78e3909310880B781d66"], ["BTC", "m/84'/0'/0'/0/1", "bc1qkd33yck74lg0kaq4tdcmu3hk4yruhjayxpe9ug"]]
    :     2: [["ETH", "m/44'/60'/0'/0/2", "0x578270B5E5B53336baC354756b763b309eCA90Ef"], ["BTC", "m/84'/0'/0'/0/2", "bc1qvr7e5aytd0hpmtaz2d443k364hprvqpm3lxr8w"]]
    :     3: [["ETH", "m/44'/60'/0'/0/3", "0x909f59835A5a120EafE1c60742485b7ff0e305da"], ["BTC", "m/84'/0'/0'/0/3", "bc1q6t9vhestkcfgw4nutnm8y2z49n30uhc0kyjl0d"]]

    We can encrypt the output, to secure the sequence (and due to integrated MACs, ensures no errors
    occur over an insecure channel like a serial cable):

    #+LATEX: {\scriptsize
    #+BEGIN_SRC bash :exports both :results raw
    slip39-recovery --bip39 --mnemonic 'zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo wrong' \
        | slip39-generator --secret - --path '../-3' --encrypt 'password'     | sed 's/^/: /' # (just for output formatting)
    #+END_SRC

    #+RESULTS:
    : 
    : 
    : nonce: 464460a9bd6230fb395dbf9c30c0c2af8ee55a0c8384f014be4cf0a7
    :     0: 900868048e5876d7e8f889160009eb22aedd142d462d84be15f54ace315b66fc3022f4368d7175fc3c80f69e156afcd79fff40a2a21936fc787e07efc28195e553e22824acb3538dabb4be42f8ebb417670298c8ce9f5d6a19b350ccdb00d18082911320e0ca873d80b4bf23f24202713a469125cbda24345fe967213e9277eb4658f4786cf2bcebd37a83970aadde5fdd321be020490d80e5bca402d889d5b8ba74705ed8
    :     1: 4bb48193c23c310bc5549f38ef5455e2f534c799fb05f99de1fb3d5e6cc382fb6421efc2578fdf9fff67e9679f1b8d0558c9f112127da414419c8c8a72d0c52c6104de80e4f00c4eee6ec67dbdd4b313aac02dc27c604e89c76f7d9364e990dc3afc82be75cd1f0082440b6115cb974959bfcd10d7b513036cea6dd642149bcd85a5eff57f52079c4e61b38770ec1774f21fc1d592db2797c0c40635eaa88aa0a142733743
    :     2: 340366fe3e00818f905cff2359fbe247536cad4c453c3cce592466211de4092f789d75076788a22014d0a7503884e808345214495fbd7f29685c2bfb913ecf38562aad347c2a10d8ebfd4479dad3075bae8bb0298299e527399c2ac3eb60d1d0cb26ec66900814f32062fdffbecb1c813d426ada6478993f1c74de9cb0f1d0d8c22cd323b7e6971598bada2342439f55d91dccd02e61aaef1e26068dfe4bf5d2cd82cf2fa4
    :     3: 6a2b0afb8a7a3466ca3229d9d6ec08aba9c6cebeed04f0a917083da314a2a7fae70bec59e79121570cd466d6081265f33e044dfec5cd3e747c6415fadbf18dab4ad0e5e68ec51eb29d12e74db3c0470ac5c670a126031d10b754c112511b65e48df6aec2f68c5826285b6608fa3e5653c10409d504cb311982257778ea1c3a2d6e0843e29ef79978474b07a19abe8b9d4d5653c6571f9ba76fec725bda1e1a61caa7553b7c

    On the receiving computer, we can decrypt and recover the stream of accounts from the wallet
    seed; any rows with errors are ignored:
    #+LATEX: {\scriptsize
    #+BEGIN_SRC bash :exports both :results raw
    slip39-recovery --bip39 --mnemonic 'zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo wrong' \
        | slip39-generator --secret - --path '../-3' --encrypt 'password' \
        | slip39-generator --receive --decrypt 'password'                     | sed 's/^/: /' # (just for output formatting)
    #+END_SRC

    #+RESULTS:
    :     0: [["ETH", "m/44'/60'/0'/0/0", "0xfc2077CA7F403cBECA41B1B0F62D91B5EA631B5E"], ["BTC", "m/84'/0'/0'/0/0", "bc1qk0a9hr7wjfxeenz9nwenw9flhq0tmsf6vsgnn2"]]
    :     1: [["ETH", "m/44'/60'/0'/0/1", "0xd1a7451beB6FE0326b4B78e3909310880B781d66"], ["BTC", "m/84'/0'/0'/0/1", "bc1qkd33yck74lg0kaq4tdcmu3hk4yruhjayxpe9ug"]]
    :     2: [["ETH", "m/44'/60'/0'/0/2", "0x578270B5E5B53336baC354756b763b309eCA90Ef"], ["BTC", "m/84'/0'/0'/0/2", "bc1qvr7e5aytd0hpmtaz2d443k364hprvqpm3lxr8w"]]
    :     3: [["ETH", "m/44'/60'/0'/0/3", "0x909f59835A5a120EafE1c60742485b7ff0e305da"], ["BTC", "m/84'/0'/0'/0/3", "bc1q6t9vhestkcfgw4nutnm8y2z49n30uhc0kyjl0d"]]

** The =slip39= module API
   
   Provide SLIP-39 Mnemonic set creation from a 128-bit master secret, and recovery of the secret
   from a subset of the provided Mnemonic set.
   
*** =slip39.create=

    Creates a set of SLIP-39 groups and their mnemonics.

    #+LATEX: {\scriptsize
    | Key                | Description                                                                |
    |--------------------+----------------------------------------------------------------------------|
    | name               | Who/what the account is for                                                |
    | group_threshold    | How many groups' data is required to recover the account(s)                |
    | groups             | Each group's description, as {"<group>":(<required>, <members>), ...}      |
    | master_secret      | 128-bit secret (default: from secrets.token_bytes)                         |
    | passphrase         | An optional additional passphrase required to recover secret (default: "") |
    | iteration_exponent | For encrypted secret, exponentially increase PBKDF2 rounds (default: 1)    |
    | cryptopaths        | A number of crypto names, and their derivation paths ]                     |
    | strength           | Desired master_secret strength, in bits (default: 128)                     |
    #+LATEX: }

    Outputs a =slip39.Details= namedtuple containing:
    
    #+LATEX: {\scriptsize
    | Key             | Description                                       |
    |-----------------+---------------------------------------------------|
    | name            | (same)                                            |
    | group_threshold | (same)                                            |
    | groups          | Like groups, w/ <members> =  ["<mnemonics>", ...] |
    | accounts        | Resultant list of groups of accounts              |
    #+LATEX: }

    This is immediately usable to pass to =slip39.output=.

    #+LATEX: {\scriptsize
    #+BEGIN_SRC ipython :session :exports both :results raw drawer
    import codecs
    import random

    #
    # NOTE:
    #
    # We turn off randomness here during SLIP-39 generation to get deterministic phrases;
    # during normal operation, secure entropy is used during mnemonic generation, yielding
    # random phrases, even when the same seed is used multiple times.
    # 
    import shamir_mnemonic
    shamir_mnemonic.shamir.RANDOM_BYTES = lambda n: b'\00' * n

    import slip39

    cryptopaths         = [("ETH","m/44'/60'/0'/0/-2"), ("BTC","m/44'/0'/0'/0/-2")]
    master_secret       = b'\xFF' * 16
    passphrase          = b""
    create_details      = slip39.create(
        "Test", 2, { "Mine": (1,1), "Fam": (2,3) },
        master_secret=master_secret, passphrase=passphrase, cryptopaths=cryptopaths )
    [
        [
            f"{g_name}({g_of}/{len(g_mnems)}) #{g_n+1}:" if l_n == 0 else ""
        ] + words
        for g_name,(g_of,g_mnems) in create_details.groups.items()
        for g_n,mnem in enumerate( g_mnems )
        for l_n,(line,words) in enumerate(slip39.organize_mnemonic(
                mnem, label=f"{g_name}({g_of}/{len(g_mnems)}) #{g_n+1}:" ))
    ]
    #+END_SRC

    #+RESULTS:
    :results:
    # Out[2]:
    | 0             | 1          | 2           | 3           |
    |---------------+------------+-------------+-------------|
    | Mine(1/1) #1: | 1 academic | 8 safari    | 15 standard |
    |               | 2 acid     | 9 drug      | 16 angry    |
    |               | 3 acrobat  | 10 browser  | 17 similar  |
    |               | 4 easy     | 11 trash    | 18 aspect   |
    |               | 5 change   | 12 fridge   | 19 smug     |
    |               | 6 injury   | 13 busy     | 20 violence |
    |               | 7 painting | 14 finger   |             |
    | Fam(2/3) #1:  | 1 academic | 8 prevent   | 15 dwarf    |
    |               | 2 acid     | 9 mouse     | 16 dream    |
    |               | 3 beard    | 10 daughter | 17 flavor   |
    |               | 4 echo     | 11 ancient  | 18 oral     |
    |               | 5 crystal  | 12 fortune  | 19 chest    |
    |               | 6 machine  | 13 ruin     | 20 marathon |
    |               | 7 bolt     | 14 warmth   |             |
    | Fam(2/3) #2:  | 1 academic | 8 prune     | 15 briefing |
    |               | 2 acid     | 9 pickup    | 16 often    |
    |               | 3 beard    | 10 device   | 17 escape   |
    |               | 4 email    | 11 device   | 18 sprinkle |
    |               | 5 dive     | 12 peanut   | 19 segment  |
    |               | 6 warn     | 13 enemy    | 20 devote   |
    |               | 7 ranked   | 14 graduate |             |
    | Fam(2/3) #3:  | 1 academic | 8 dining    | 15 intimate |
    |               | 2 acid     | 9 invasion  | 16 satoshi  |
    |               | 3 beard    | 10 bumpy    | 17 hobo     |
    |               | 4 entrance | 11 identify | 18 ounce    |
    |               | 5 alarm    | 12 anxiety  | 19 both     |
    |               | 6 health   | 13 august   | 20 award    |
    |               | 7 discuss  | 14 sunlight |             |
    :end:

    #+LATEX: }

    Add the resultant HD Wallet addresses:

    #+LATEX: {\scriptsize
    #+BEGIN_SRC ipython :session :exports both :results raw drawer
    [
        [ account.path, account.address ]
        for group in create_details.accounts
        for account in group
    ]
    #+END_SRC

    #+RESULTS:
    :results:
    # Out[3]:
    | 0                | 1                                          |
    |------------------+--------------------------------------------|
    | m/44'/60'/0'/0/0 | 0x824b174803e688dE39aF5B3D7Cd39bE6515A19a1 |
    | m/44'/0'/0'/0/0  | bc1qm5ua96hx30snwrwsfnv97q96h53l86ded7wmjl |
    | m/44'/60'/0'/0/1 | 0x8D342083549C635C0494d3c77567860ee7456963 |
    | m/44'/0'/0'/0/1  | bc1qwz6v9z49z8mk5ughj7r78hjsp45jsxgzh29lnh |
    | m/44'/60'/0'/0/2 | 0x52787E24965E1aBd691df77827A3CfA90f0166AA |
    | m/44'/0'/0'/0/2  | bc1q690m430qu29auyefarwfrvfumncunvyw6v53n9 |
    :end:

    #+LATEX: }

*** =slip39.produce_pdf=
    
    #+LATEX: {\scriptsize
    | Key             | Description                                       |
    |-----------------+---------------------------------------------------|
    | name            | (same as =slip39.create=)                         |
    | group_threshold | (same as =slip39.create=)                         |
    | groups          | Like groups, w/ <members> =  ["<mnemonics>", ...] |
    | accounts        | Resultant { "path": Account, ...}                 |
    | card_format     | 'index', '(<h>,<w>),<margin>', ...                |
    | paper_format    | 'Letter', ...                                     |
    #+LATEX: }

    Layout and produce a PDF containing all the SLIP-39 details on cards for the crypto accounts, on
    the paper_format provided.  Returns the paper (orientation,format) used, the FPDF, and passes
    through the supplied cryptocurrency accounts derived.

    #+LATEX: {\scriptsize
    #+BEGIN_SRC ipython :session :exports both :results raw drawer
    (paper_format,orientation),pdf,accounts = slip39.produce_pdf( *create_details )
    pdf_binary = pdf.output()
    [
        [ "Orientation:",	orientation ],
        [ "Paper:",		paper_format ],
        [ "PDF Pages:",		pdf.pages_count ],
        [ "PDF Size:",		len( pdf_binary )],
    ]
    #+END_SRC

    #+RESULTS:
    :results:
    # Out[20]:
    | 0            | 1         |
    |--------------+-----------|
    | Orientation: | landscape |
    | Paper:       | Letter    |
    | PDF Pages:   | 1         |
    | PDF Size:    | 12985     |
    :end:

    #+LATEX: }

*** =slip39.write_pdfs=

    #+LATEX: {\scriptsize
    | Key             | Description                                                                                           |
    |-----------------+-------------------------------------------------------------------------------------------------------|
    | names           | A sequence of Seed names, or a dict of { name: <details> } (from slip39.create)                       |
    | master_secret   | A Seed secret (only appropriate if exactly one name supplied)                                         |
    | passphrase      | A SLIP-39 passphrase (not Trezor compatible; use "hidden wallet" phrase on device instead)            |
    | group           | A dict of {"<group>":(<required>, <members>), ...}                                                    |
    | group_threshold | How many groups are required to recover the Seed                                                      |
    | cryptocurrency  | A sequence of [ "<crypto>", "<crypto>:<derivation>", ... ] w/ optional ranges                         |
    | edit            | Derivation range(s) for each cryptocurrency, eg. "../0-4/-9" is 9 accounts first 5 change addresses   |
    | card_format     | Card size (eg. "credit"); False specifies no SLIP-39 cards (ie. only BIP-39 or JSON paper wallets)    |
    | paper_format    | Paper size (eg. "letter")                                                                             |
    | filename        | A filename; may contain "...{name}..." formatting, for name, date, time, crypto path and address      |
    | filepath        | A file path, if PDF output to file is desired; empty implies current dir.                             |
    | printer         | A printer name (or True for default), if output to printer is desired                                 |
    | json_pwd        | If password supplied, encrypted Ethereum JSON wallet files will be saved, and produced into PDF       |
    | text            | If True, outputs SLIP-39 phrases to stdout                                                            |
    | wallet_pwd      | If password supplied, produces encrypted BIP-38 or JSON Paper Wallets to PDF (preferred vs. json_pwd) |
    | wallet_pwd_hint | An optional passphrase hint, printed on paper wallet                                                  |
    | wallet_format   | Paper wallet size, (eg. "third"); the default is 1/3 letter size                                      |
    #+LATEX: }

    For each of the names provided, produces a separate PDF containing all the SLIP-39 details and
    optionally encrypted BIP-38 paper wallets and Ethereum JSON wallets for the specified
    cryptocurrency accounts derived from the seed, and writes the PDF and JSON wallets to the
    specified file name(s).

    #+LATEX: {\scriptsize
    #+BEGIN_EXAMPLE
    slip39.write_pdfs( ... )
    #+END_EXAMPLE
    #+LATEX: }

*** =slip39.recover=

    Takes a number of SLIP-39 mnemonics, and if sufficient =group_threshold= groups' mnemonics are
    present (and the options =passphrase= is supplied), the =master_secret= is recovered.  This can
    be used with =slip39.accounts= to directly obtain any =Account= data.

    Note that the passphrase is *not* checked; entering a different passphrase for the same set of
    mnemonics will recover a *different* wallet!  This is by design; it allows the holder of the
    SLIP-39 mnemonic phrases to recover a "decoy" wallet by supplying a specific passphrase, while
    protecting the "primary" wallet.

    Therefore, it is *essential* to remember any non-default (empty) passphrase used, separately and
    securely.  Take great care in deciding if you wish to use a passphrase with your SLIP-39 wallet!
    
    #+LATEX: {\scriptsize
    | Key        | Description                                       |
    |------------+---------------------------------------------------|
    | mnemonics  | ["<mnemonics>", ...]                              |
    | passphrase | Optional passphrase to decrypt secret
    #+LATEX: }
    
    #+LATEX: {\scriptsize
    #+BEGIN_SRC ipython :session :exports both :results raw drawer
    recoverydecoy       = slip39.recover(
        create_details.groups['Mine'][1][:] + create_details.groups['Fam'][1][:2],
        passphrase=b"wrong!"
    )
    recoverydecoyhex    = codecs.encode( recoverydecoy, 'hex_codec' ).decode( 'ascii' )

    recoveryvalid       = slip39.recover(
        create_details.groups['Mine'][1][:] + create_details.groups['Fam'][1][:2],
        passphrase=passphrase
    )
    recoveryvalidhex    = codecs.encode( recoveryvalid, 'hex_codec' ).decode( 'ascii' )

    [[ f"{len(recoverydecoy)*8}-bit secret w/ decoy password recovered:" ]] + [
     [ f"{recoverydecoyhex[b*32:b*32+32]}" ]
        for b in range( len( recoverydecoyhex ) // 32 )
    ] +  [[ f"{len(recoveryvalid)*8}-bit secret recovered:" ]] + [
     [ f"{recoveryvalidhex[b*32:b*32+32]}" ]
        for b in range( len( recoveryvalidhex ) // 32 )
    ]
    #+END_SRC

    #+RESULTS:
    :results:
    # Out[20]:
    | 0                                           |
    |---------------------------------------------|
    | 128-bit secret w/ decoy password recovered: |
    | 2e522cea2b566840495c220cf79c756e            |
    | 128-bit secret recovered:                   |
    | ffffffffffffffffffffffffffffffff            |
    :end:

    #+LATEX: }

* Conversion from BIP-39 to SLIP-39

  If we already have a BIP-39 wallet, it would certainly be nice to be able to create nice, safe
  SLIP-39 mnemonics for it, and discard the unsafe BIP-39 mnemonics we have lying around, just
  waiting to be accidentally discovered and the account compromised!

** BIP-39 vs. SLIP-39 Incompatibility

   Unfortunately, it is *not possible* to cleanly convert a BIP-39 derived wallet into a SLIP-39
   wallet.  Both of these techniques preserve "entropy" (random) bits, but these bits are used
   *differently* -- and incompatibly -- to derive the resultant Ethereum wallets.

   The best we can do is to preserve the 512-bit *output* of the BIP-39 mnemonic phrase as a set of
   512-bit SLIP-39 mnemonics.

*** BIP-39 Entropy to Mnemonic

    BIP-39 uses a single set of 12, 15, 18, 21 or 24 BIP-39 words to carefully preserve a specific
    128 to 256 bits of initial entropy.  Here's a 128-bit (12-word) example using some fixed
    "entropy" =0xFFFF..FFFF=:

    #+LATEX: {\scriptsize
    #+BEGIN_SRC ipython :session :exports both :results raw drawer
    from mnemonic import Mnemonic
    bip39_english       = Mnemonic("english")
    entropy             = b'\xFF' * 16
    entropy_mnemonic    = bip39_english.to_mnemonic( entropy )
    [
     [ entropy_mnemonic ]
    ]
    #+END_SRC

    #+RESULTS:
    :results:
    # Out[9]:
    | 0                                                 |
    |---------------------------------------------------|
    | zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo wrong |
    :end:

    #+LATEX: }

    Each word is one of a corpus of 2048 words; therefore, each word encodes 11 bits (2048 == 2**11)
    of entropy.  So, we provided 128 bits, but 12*11 == 132.  So where does the extra 4 bits of data
    come from?

    It comes from the first few bits of a SHA256 hash of the entropy, which is added to the end of
    the supplied 128 bits, to reach the required 132 bits: 132 / 11 == 12 words.

    This last 4 bits (up to 8 bits, for a 256-bit 24-word BIP-39) is checked, when validating the
    BIP-39 mnemonic.  Therefore, making up a random BIP-39 mnemonic will succeed only 1 / 16 times on
    average, due to an incorrect checksum 4-bit (16 == 2**4) .  Lets check:

    #+LATEX: {\scriptsize
    #+BEGIN_SRC ipython :session :exports both :results raw drawer
    def random_words( n, count=100 ):
        for _ in range( count ):
            yield ' '.join( random.choice( bip39_english.wordlist ) for _ in range( n ))

    successes           = sum(
        bip39_english.check( m )
        for i,m in enumerate( random_words( 12, 10000 ))) / 100

    [[ f"Valid random 12-word mnemonics:" ]] + [
     [ f"{successes}%" ]] + [
     [ f"~ 1/{100/successes:.3}" ]]
    #+END_SRC

    #+RESULTS:
    :results:
    # Out[10]:
    | 0                               |
    |---------------------------------|
    | Valid random 12-word mnemonics: |
    | 6.2%                            |
    | ~ 1/16.1                        |
    :end:

    #+LATEX: }

    Sure enough, about 1/16 random 12-word phrases are valid BIP-39 mnemonics.  OK, we've got the
    contents of the BIP-39 phrase dialed in.  How is it used to generate accounts?

*** BIP-39 Mnemonic to Seed

    Unfortunately, we do *not* use the carefully preserved 128-bit entropy to generate the wallet!
    Nope, it is stretched to a 512-bit seed using PBKDF2 HMAC SHA512.  The normalized *text* (/not
    the entropy bytes/) of the 12-word mnemonic is then used (with a salt of "mnemonic" plus an
    optional passphrase, "" by default), to obtain the seed:

    #+LATEX: {\scriptsize
    #+BEGIN_SRC ipython :session :exports both :results raw drawer
    seed                = bip39_english.to_seed( entropy_mnemonic )
    seedhex             = codecs.encode( seed, 'hex_codec' ).decode( 'ascii' )
    [
     [ f"{len(seed)*8}-bit seed:" ]] + [
     [ f"{seedhex[b*32:b*32+32]}" ]
     for b in range( len( seedhex ) // 32 )
    ]
    #+END_SRC

    #+RESULTS:
    :results:
    # Out[13]:
    | 0                                |
    |----------------------------------|
    | 512-bit seed:                    |
    | b6a6d8921942dd9806607ebc2750416b |
    | 289adea669198769f2e15ed926c3aa92 |
    | bf88ece232317b4ea463e84b0fcd3b53 |
    | 577812ee449ccc448eb45e6f544e25b6 |
    :end:

    #+LATEX: }

*** BIP-39 Seed to Address

    Finally, this 512-bit seed is used to derive HD wallet(s).  The HD Wallet key derivation process
    consumes whatever seed entropy is provided (512 bits in the case of BIP-39), and uses HMAC SHA512
    with a prefix of b"Bitcoin seed" to stretch the supplied seed entropy to 64 bytes (512 bits).
    Then, the HD Wallet *path* segments are iterated through, permuting the first 32 bytes of this
    material as the key with the second 32 bytes of material as the chain node, until finally the
    32-byte (256-bit) Ethereum account private key is produced.  We then use this private key to
    compute the rest of the Ethereum account details, such as its public address.

    #+LATEX: {\scriptsize
    #+BEGIN_SRC ipython :session :exports both :results raw drawer
    path                = "m/44'/60'/0'/0/0"
    eth_hd		= slip39.account( seed, 'ETH', path )
    [
     [ f"{len(eth_hd.key)*4}-bit derived key at path {path!r}:" ]] + [
     [ f"{eth_hd.key}" ]] + [
     [ "... yields ..." ]] + [
     [ f"Ethereum address: {eth_hd.address}" ]
    ]
    #+END_SRC

    #+RESULTS:
    :results:
    # Out[14]:
    | 0                                                                |
    |------------------------------------------------------------------|
    | 256-bit derived key at path "m/44'/60'/0'/0/0":                  |
    | 7af65ba4dd53f23495dcb04995e96f47c243217fc279f10795871b725cd009ae |
    | ... yields ...                                                   |
    | Ethereum address: 0xfc2077CA7F403cBECA41B1B0F62D91B5EA631B5E     |
    :end:

    #+LATEX: }

    Thus, we see that while the 12-word BIP-39 mnemonic careful preserves the original 128-bit
    entropy, this data is not directly used to derive the wallet private key and address.  Also,
    since an irreversible hash is used to derive the seed from the mnemonic, we can't reverse the
    process on the seed to arrive back at the BIP-39 mnemonic phrase.

*** SLIP-39 Entropy to Mnemonic

    Just like BIP-39 carefully preserves the original 128-bit entropy bytes in a single 12-word
    mnemonic phrase, SLIP-39 preserves the original 128-bit entropy in a /set/ of 30-word mnemonic
    phrases.

    #+LATEX: {\scriptsize
    #+BEGIN_SRC ipython :session :exports both :results raw drawer
    name,thrs,grps,acct = slip39.create(
        "Test", 2, { "Mine": (1,1), "Fam": (2,3) }, entropy )
    [
     [ f"{g_name}({g_of}/{len(g_mnems)}) #{g_n+1}:" if l_n == 0 else "" ] + words
     for g_name,(g_of,g_mnems) in grps.items()
     for g_n,mnem in enumerate( g_mnems )
     for l_n,(line,words) in enumerate(slip39.organize_mnemonic(
             mnem, rows=7, cols=3, label=f"{g_name}({g_of}/{len(g_mnems)}) #{g_n+1}:" ))
    ]
    #+END_SRC

    #+RESULTS:
    :results:
    # Out[15]:
    | 0             | 1          | 2           | 3           |
    |---------------+------------+-------------+-------------|
    | Mine(1/1) #1: | 1 academic | 8 safari    | 15 standard |
    |               | 2 acid     | 9 drug      | 16 angry    |
    |               | 3 acrobat  | 10 browser  | 17 similar  |
    |               | 4 easy     | 11 trash    | 18 aspect   |
    |               | 5 change   | 12 fridge   | 19 smug     |
    |               | 6 injury   | 13 busy     | 20 violence |
    |               | 7 painting | 14 finger   |             |
    | Fam(2/3) #1:  | 1 academic | 8 prevent   | 15 dwarf    |
    |               | 2 acid     | 9 mouse     | 16 dream    |
    |               | 3 beard    | 10 daughter | 17 flavor   |
    |               | 4 echo     | 11 ancient  | 18 oral     |
    |               | 5 crystal  | 12 fortune  | 19 chest    |
    |               | 6 machine  | 13 ruin     | 20 marathon |
    |               | 7 bolt     | 14 warmth   |             |
    | Fam(2/3) #2:  | 1 academic | 8 prune     | 15 briefing |
    |               | 2 acid     | 9 pickup    | 16 often    |
    |               | 3 beard    | 10 device   | 17 escape   |
    |               | 4 email    | 11 device   | 18 sprinkle |
    |               | 5 dive     | 12 peanut   | 19 segment  |
    |               | 6 warn     | 13 enemy    | 20 devote   |
    |               | 7 ranked   | 14 graduate |             |
    | Fam(2/3) #3:  | 1 academic | 8 dining    | 15 intimate |
    |               | 2 acid     | 9 invasion  | 16 satoshi  |
    |               | 3 beard    | 10 bumpy    | 17 hobo     |
    |               | 4 entrance | 11 identify | 18 ounce    |
    |               | 5 alarm    | 12 anxiety  | 19 both     |
    |               | 6 health   | 13 august   | 20 award    |
    |               | 7 discuss  | 14 sunlight |             |
    :end:

    #+LATEX: }

    Since there is some randomness used in the SLIP-39 mnemonics generation process, we would get a
    *different* set of words each time for the fixed "entropy" =0xFFFF..FF= used in this example (if
    we hadn't manually disabled entropy for =shamir_mnemonic=, above), but we will *always* derive
    the same Ethereum account =0x824b..19a1= at the specified HD Wallet derivation path.

    #+LATEX: {\scriptsize
    #+BEGIN_SRC ipython :session :exports both :results raw drawer
    [
     [ "Crypto", "HD Wallet Path:", "Ethereum Address:" ]
    ] + [
     [ account.crypto, account.path, account.address ]
     for group in create_details.accounts
     for account in group
    ]
    #+END_SRC

    #+RESULTS:
    :results:
    # Out[16]:
    | 0      | 1                | 2                                          |
    |--------+------------------+--------------------------------------------|
    | Crypto | HD Wallet Path:  | Ethereum Address:                          |
    | ETH    | m/44'/60'/0'/0/0 | 0x824b174803e688dE39aF5B3D7Cd39bE6515A19a1 |
    | BTC    | m/44'/0'/0'/0/0  | bc1qm5ua96hx30snwrwsfnv97q96h53l86ded7wmjl |
    | ETH    | m/44'/60'/0'/0/1 | 0x8D342083549C635C0494d3c77567860ee7456963 |
    | BTC    | m/44'/0'/0'/0/1  | bc1qwz6v9z49z8mk5ughj7r78hjsp45jsxgzh29lnh |
    | ETH    | m/44'/60'/0'/0/2 | 0x52787E24965E1aBd691df77827A3CfA90f0166AA |
    | BTC    | m/44'/0'/0'/0/2  | bc1q690m430qu29auyefarwfrvfumncunvyw6v53n9 |
    :end:

    #+LATEX: }

*** SLIP-39 Mnemonic to Seed

    Lets prove that we can actually recover the *original* entropy from the SLIP-39 recovery
    mnemonics; in this case, we've specified a SLIP-39 group_threshold of 2 groups, so we'll use 1
    mnemonic from Mine, and 2 from Fam:

    #+LATEX: {\scriptsize
    #+BEGIN_SRC ipython :session :exports both :results raw drawer
    _,mnem_mine         = grps['Mine']
    _,mnem_fam          = grps['Fam']
    recseed             = slip39.recover( mnem_mine + mnem_fam[:2] )
    recseedhex          = codecs.encode( recseed, 'hex_codec' ).decode( 'ascii' )
    [
     [ f"{len(recseed)*8}-bit seed:" ]
    ] + [
     [ f"{recseedhex[b*32:b*32+32]}" ]
        for b in range( len( recseedhex ) // 32 )
    ]
    #+END_SRC

    #+RESULTS:
    :results:
    # Out[17]:
    | 0                                |
    |----------------------------------|
    | 128-bit seed:                    |
    | ffffffffffffffffffffffffffffffff |
    :end:

    #+LATEX: }

*** SLIP-39 Seed to Address

    And we'll use the same style of code as for the BIP-39 example above, to derive the Ethereum
    address *directly* from this recovered 128-bit seed:

    #+LATEX: {\scriptsize
    #+BEGIN_SRC ipython :session :exports both :results raw drawer
    receth		= slip39.account( recseed, 'ETH', path )
    [
     [ f"{len(receth.key)*4}-bit derived key at path {path!r}:" ]] + [
     [ f"{receth.key}" ]] + [
     [ "... yields ..." ]] + [
     [ f"Ethereum address: {receth.address}" ]
    ]
    #+END_SRC

    #+RESULTS:
    :results:
    # Out[19]:
    | 0                                                                |
    |------------------------------------------------------------------|
    | 256-bit derived key at path "m/44'/60'/0'/0/0":                  |
    | 6a2ec39aab88ec0937b79c8af6aaf2fd3c909e9a56c3ddd32ab5354a06a21a2b |
    | ... yields ...                                                   |
    | Ethereum address: 0x824b174803e688dE39aF5B3D7Cd39bE6515A19a1     |
    :end:

    #+LATEX: }

    And we see that we obtain the same Ethereum address =0x824b..1a2b= as we originally got from
    =slip39.create= above.  However, this is *not* the Ethereum wallet address obtained from BIP-39
    with exactly the same =0xFFFF...FF= entropy, which was =0xfc20..1B5E=.  This is due to the fact
    that BIP-39 does not use the recovered entropy to produce the seed like SLIP-39 does, but
    applies additional one-way hashing of the mnemonic to produce the seed.

** BIP-39 vs SLIP-39 Key Derivation Summary

   At no time in BIP-39 account derivation is the original 128-bit mnemonic entropy used directly in
   the derivation of the wallet key.  This differs from SLIP-39, which directly uses the 128-bit mnemonic
   entropy recovered from the SLIP-39 Shamir's Secret Sharing System recovery process to generate
   each HD Wallet account's private key.

   Furthermore, there is no point in the BIP-39 entropy to account generation where we *could*
   introduce a known 128-bit seed and produce a known Ethereum wallet from it, other than as the
   very beginning.

*** BIP-39 Backup via SLIP-39

    There is one approach which can preserve an original BIP-39 wallet address, using SLIP-39 mnemonics.

    It is clumsy, as it preserves the BIP-39 *output* 512-bit stretched seed, and the resultant
    59-word SLIP-39 mnemonics cannot be used (at present) with the Trezor hardware wallet.  They
    can, however, be used to recover the HD wallet private keys without access to the original
    BIP-39 mnemonic phrase -- you could generate and distribute a set of more secure SLIP-39
    mnemonic phrases, instead of trying to secure the original BIP-39 mnemonic.

    We'll use =slip39.recovery --bip39 ...= to recover the 512-bit stretched seed from BIP-39:

   #+LATEX: {\scriptsize
   #+BEGIN_SRC bash :exports both :results output
   ( python3 -m slip39.recovery --bip39 \
       --mnemonic "zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo wrong" 
   ) 2>&1
   #+END_SRC
   #+RESULTS:
   : 2022-02-17 05:10:23 slip39.recovery  Recovered 512-bit BIP-39 secret from english mnemonic
   : b6a6d8921942dd9806607ebc2750416b289adea669198769f2e15ed926c3aa92bf88ece232317b4ea463e84b0fcd3b53577812ee449ccc448eb45e6f544e25b6
   #+LATEX: }

   Then we can generate a 59-word SLIP-39 mnemonic set from the 512-bit secret:
   
   #+LATEX: {\scriptsize
   #+BEGIN_SRC bash :exports both :results output
   ( python3 -m slip39.recovery --bip39 \
       --mnemonic "zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo wrong" \
     | python3 -m slip39 --secret - --no-card
   ) 2>&1
   #+END_SRC
   #+RESULTS:
   : 2022-02-17 05:10:25 slip39.recovery  Recovered 512-bit BIP-39 secret from english mnemonic
   : 2022-02-17 05:10:25 slip39           ETH    m/44'/60'/0'/0/0    : 0xfc2077CA7F403cBECA41B1B0F62D91B5EA631B5E
   : 2022-02-17 05:10:25 slip39           BTC    m/84'/0'/0'/0/0     : bc1qk0a9hr7wjfxeenz9nwenw9flhq0tmsf6vsgnn2
   #+LATEX: }

   This =0xfc20..1B5E= address is the same Ethereum address as is recovered on a Trezor using this
   BIP-39 mnemonic phrase.

* Building & Installing

  The =python-slip39= project is tested under both homebrew:
  : $ brew install python-tk@3.9
  and using [[https://www.python.org/downloads/][the official python.org/downloads installer]].

  Either of these methods will get you a =python3= executable running version 3.9+, usable for
  running the =slip39= module, and the =slip39.gui= GUI.

** The =slip39= Module

   To build the wheel and install =slip39= manually:
   : $ git clone git@github.com:pjkundert/python-slip39.git
   : $ make -C python-slip39 install

   To install from Pypi, including the optional requirements to run the PySimpleGUI/tkinter GUI,
   support serial I/O, and to support creating encrypted Ethereum JSON wallets:
   : $ python3 -m pip install slip39[gui,serial,json]

** The =slip39= GUI

   To install from Pypi, including the optional requirements to run the PySimpleGUI/tkinter GUI:
   : $ python3 -m pip install slip39[gui]

   Then, there are several ways to run the GUI:
   : $ python3 -m slip39.gui     # Execute the python slip39.gui module main method
   : $ slip39-gui                # Run the main function provided by the slip39.gui module
   
*** The macOS =SLIP-39.app= GUI

    You can build the native macOS =SLIP-39.app= App.

    This requires the official [[https://python.org/downloads][python.org/downloads]] installer; the homebrew python-tk@3.9 will not
    work for building the native app using either =PyInstaller=. (The =py2app= approach doesn't work
    in either version of Python).

    : $ git clone git@github.com:pjkundert/python-slip39.git
    : $ make -C python-slip39 app

*** The Windows 10 =SLIP-39= GUI

    Install Python from https://python.org/downloads, and the [[https://visualstudio.microsoft.com/visual-cpp-build-tools][Microsoft C++ Build Tools]] via the
    Visual Studio Installer (required for installing some =slip39= package dependencies).

    To run the GUI, just install =slip39= package from Pypi using pip, including the =gui= and
    =wallet= options.  Building the Windows =SLIP-39= executable GUI application requires the =dev=
    option.
    : PS C:\Users\IEUser> pip install slip39[gui,wallet,dev]

    To work with the [[https://github.com/pjkundert/python-slip39.git][python-slip39 Git repo on Github]], you'll also need to install [[https://git-scm.com/download/win][Git from
    git-scm.com]]. Once installed, run "Git bash", and
    : $ ssh-keygen.exe -t ed25519
    to create an =id_ed25519.pub= SSH identity, and import it into your Git Settings SSH keys.  Then,
    : $ mkdir src
    : $ cd src
    : $ git clone git@github.com:pjkundert/python-slip39.git

* Dependencies

  Internally, python-slip39 project uses Trezor's [[https://gihub.com/trezor/python-shamir-mnemonic.git][python-shamir-mnemonic]] to encode the seed data to
  SLIP-39 phrases, [[https://github.com/meherett/python-hdwallet.git][python-hdwallet]] to convert seeds to ETH, BTC, LTC and DOGE wallets, and the
  Ethereum project's [[https://github.com/ethereum/eth-account][eth-account]] to produce encrypted JSON wallets for specified Ethereum accounts.

** The =python-shamir-mnemonic= API

   To use it directly, obtain , and install it, or run =python3 -m pip install shamir-mnemonic=.

#+LATEX: {\scriptsize
#+BEGIN_EXAMPLE
$ shamir create custom --group-threshold 2 --group 1 1 --group 1 1 --group 2 5 --group 3 6
Using master secret: 87e39270d1d1976e9ade9cc15a084c62
Group 1 of 4 - 1 of 1 shares required:
merit aluminum acrobat romp capacity leader gray dining thank rhyme escape genre havoc furl breathe class pitch location render beard
Group 2 of 4 - 1 of 1 shares required:
merit aluminum beard romp briefing email member flavor disaster exercise cinema subject perfect facility genius bike include says ugly package
Group 3 of 4 - 2 of 5 shares required:
merit aluminum ceramic roster already cinema knit cultural agency intimate result ivory makeup lobe jerky theory garlic ending symbolic endorse
merit aluminum ceramic scared beam findings expand broken smear cleanup enlarge coding says destroy agency emperor hairy device rhythm reunion
merit aluminum ceramic shadow cover smith idle vintage mixture source dish squeeze stay wireless likely privacy impulse toxic mountain medal
merit aluminum ceramic sister duke relate elite ruler focus leader skin machine mild envelope wrote amazing justice morning vocal injury
merit aluminum ceramic smug buyer taxi amazing marathon treat clinic rainbow destroy unusual keyboard thumb story literary weapon away move
Group 4 of 4 - 3 of 6 shares required:
merit aluminum decision round bishop wrote belong anatomy spew hour index fishing lecture disease cage thank fantasy extra often nail
merit aluminum decision scatter carpet spine ruin location forward priest cage security careful emerald screw adult jerky flame blanket plot
merit aluminum decision shaft arcade infant argue elevator imply obesity oral venture afraid slice raisin born nervous universe usual racism
merit aluminum decision skin already fused tactics skunk work floral very gesture organize puny hunting voice python trial lawsuit machine
merit aluminum decision snake cage premium aide wealthy viral chemical pharmacy smoking inform work cubic ancestor clay genius forward exotic
merit aluminum decision spider boundary lunar staff inside junior tendency sharp editor trouble legal visual tricycle auction grin spit index
#+END_EXAMPLE
#+LATEX: }
