Browse Source

add 3 wiki pages

Added:

- doc/wiki/using-mmgen/Test-Suite.md
- doc/wiki/using-mmgen/Tool-API.md
- doc/wiki/using-mmgen/XOR-Seed-Splitting:-Theory-and-Practice.md
The MMGen Project 2 years ago
parent
commit
59368fd7c1

+ 193 - 0
doc/wiki/using-mmgen/Test-Suite.md

@@ -0,0 +1,193 @@
+## Introduction
+
+In addition to low-level subsystems, the suite tests the overall operation of
+MMGen’s commands by running them interactively as a user would.  Thus the test
+suite is useful not only for ensuring the MMGen system is correctly installed
+and working on your platform but also for learning how it works.
+
+BTC-only testing requires only Bitcoin Core to be installed.  Altcoin testing
+requires various helper programs and libraries.  Installation instructions for
+these are provided below.  Non-standard RPC ports and data directories are
+always used, so there’s no need to stop your running node or nodes.
+
+On Linux/x86\_64 with a reasonably fast processor, the full suite should run in
+under 15 minutes when invoked with the `-F` option.  Execution times on other
+platforms may be much slower.
+
+## Quick start
+
+### Container setup (if applicable)
+
+The test suite requires the `/dev/loopX` devices to exist and be enabled.  If
+you’re running in an LXC container, note that only privileged containers allow
+loop devices.  You may enable them in the config file as follows:
+
+	lxc.cgroup2.devices.allow = b 7:0 rwm # /dev/loop0
+	lxc.cgroup2.devices.allow = b 7:1 rwm # /dev/loop1
+	lxc.cgroup2.devices.allow = b 7:2 rwm # /dev/loop2
+
+Every time the container is started, you may need to create the files afresh:
+
+	# mknod /dev/loop0 b 7 0
+	# mknod /dev/loop1 b 7 1
+	# mknod /dev/loop2 b 7 2
+
+### BTC-only testing
+
+Clone the Bitcoin Core repo somewhere on your system:
+
+	$ git clone https://github.com/bitcoin/bitcoin
+
+Install the Bitcoin Core daemon [(source)][sd] [(binaries)][bd].
+
+Point the test suite to your copy of the Bitcoin Core repo:
+
+	$ export CORE_REPO_ROOT=/path/to/bitcoin/core/repo
+
+Install Pycoin:
+
+	$ python3 -m pip install --user pycoin
+
+CD to the MMGen repository root and build without installing:
+
+	$ cd path/to/mmgen/repo
+	$ python3 setup.py build_ext --inplace
+
+Run the following if upgrading from a previous version of MMGen:
+
+	$ test/test.py clean
+
+Run the test suite in fast mode, skipping altcoin tests:
+
+	$ test/test-release.sh -F noalt
+
+### Complete testing (BTC plus all supported altcoins)
+
+Complete the BTC-only installation steps above, without running the test.
+
+Make sure the [Bitcoin Cash Node][cnd], [Litecoin][ld] and [Monero][md]
+daemons are installed on your system.
+
+Install [OpenEthereum, Parity, Geth, the Ethereum dependencies][oe] and
+optionally the [Solidity compiler][sc] as described on the
+Altcoin-and-Forkcoin-Support page.
+
+In addition, you must install the following helper programs and libraries (MSYS2
+users can omit Zcash-Mini and leave out `sudo` in commands):
+
+#### SSH daemon setup (MSYS2 only)
+
+The XMR test sets up a local SOCKS proxy to test transaction relaying.  This
+requires the SSH daemon to be set up and running.  On MSYS2 systems, SSHD
+is not configured by default, but it may be easily set up with the following
+steps:
+
+Open PowerShell as administrator, and at the DOS prompt, execute:
+
+	system32> net user administrator /active:yes
+	system32> C:\\msys64\usr\bin\bash.exe --login
+
+Now, at the MSYS2 prompt, cd to the MMGen repository root and run the setup
+script:
+
+	$ scripts/msys2-sshd-setup.sh
+
+The daemon should now start automatically every time the system is booted. It
+may also be started and stopped manually at the DOS or MSYS2 prompt as follows
+(PowerShell must be running with admin privileges):
+
+	# net start msys2_sshd
+	# net stop msys2_sshd
+
+#### MoneroPy
+
+	$ git clone https://github.com/bigreddmachine/MoneroPy
+	$ cd MoneroPy
+	$ python3 setup.py install --user
+	$ cd ..
+
+#### Vanitygen PlusPlus (forked from Vanitygen Plus)
+
+    $ git clone https://github.com/10gic/vanitygen-plusplus
+	$ cd vanitygen-plusplus
+	$ git checkout -b vanitygen-plus e7858035d092  # rewind to fork commit
+	$ make keyconv # ‘mingw32-make.exe keyconv’ for MSYS2
+	$ sudo install --strip keyconv /usr/local/bin  # ‘keyconv.exe’ for MSYS2
+	$ cd ..
+
+#### Zcash-Mini
+
+	$ sudo apt-get install golang  # skip this if Go is already installed
+    $ git clone https://github.com/FiloSottile/zcash-mini
+    $ cd zcash-mini
+	$ go mod init zcash-mini
+	$ go mod tidy
+	$ go build -mod=mod # or just ’go build’
+	$ sudo install --strip ./zcash-mini /usr/local/bin
+	$ cd ..
+
+#### Ethkey
+
+On Arch Linux systems, ethkey is included in the OpenEthereum package:
+
+	$ pacman -S openethereum
+
+For other systems, you may have to build ethkey from source:
+
+	$ sudo apt-get install rustc  # skip this if Rust is already installed
+	$ git clone https://github.com/openethereum/openethereum
+	$ cd openethereum
+	$ git checkout v2.6.6 # this version builds on ARM boards - your mileage may vary
+	$ cargo build -p ethkey-cli --release
+	$ sudo install --strip ./target/release/ethkey /usr/local/bin
+	$ cd ..
+
+#### Monero note
+
+The Monero test (`test/test-release.sh xmr`) creates a private network and
+mines coins, so is therefore non-deterministic and prone to random failures.
+If you experience such a failure, just restart the test.
+
+### Run the tests
+
+Now you can run the test suite for all coins:
+
+	$ test/test-release.sh -F
+
+## Overview of the individual tests
+
+`test/test-release.sh` is just a simple shell script that invokes the test
+scripts with various options and arguments to ensure complete coverage of
+MMGen’s functionality.  Launch the script with the `-t` option to view the
+invocations without running them.
+
+The test scripts themselves are all located in the `test/` directory and bear
+the `.py` extension.  They may be run individually if desired.  Options and
+arguments required by the tests are described in detail on their help screens.
+
+High-level testing of the MMGen system is performed by `test/test.py`, which
+uses the `pexpect` library to simulate interactive operation of MMGen user
+commands.  Running `test/test.py` with the `-e` option will display the
+commands’ output on the screen as they’re being run.
+
+| Test                  | What it tests                                        |
+|:----------------------|:-----------------------------------------------------|
+| `test/colortest.py`   | terminfo parsing; terminal colors                    |
+| `test/gentest.py`     | key/address generation - profiling and data validity |
+| `test/hashfunc.py`    | native SHA2 and Keccak implementations               |
+| `test/objtest.py`     | MMGen data objects - creation and error handling     |
+| `test/objattrtest.py` | MMGen data objects - immutable attributes            |
+| `test/scrambletest.py`| HMAC scramble strings used in key/password derivation|
+| `test/test.py`        | overall operation of MMGen commands                  |
+| `test/tooltest.py`    | the `mmgen-tool` utility - overall operation         |
+| `test/tooltest2.py`   | the `mmgen-tool` utility - data validity             |
+| `test/unit_tests.py`  | low-level subsystems                                 |
+
+[sd]: Install-Bitcoind-from-Source-on-Debian-or-Ubuntu-Linux
+[bd]: Install-Bitcoind
+[md]: https://getmonero.org/downloads/#linux
+[ad]: https://download.bitcoinabc.org/
+[cnd]: https://bitcoincashnode.org/
+[ld]: https://download.litecoin.org/litecoin-0.17.1/
+[oe]: Altcoin-and-Forkcoin-Support#a_oe
+[sc]: Altcoin-and-Forkcoin-Support#a_dt

+ 73 - 0
doc/wiki/using-mmgen/Tool-API.md

@@ -0,0 +1,73 @@
+The tool API provides a convenient interface to selected methods in the
+mmgen.tool module.  Type `pydoc3 mmgen.tool.api` for available methods and
+call signatures.
+
+## Examples
+
+### Initialize:
+
+	from mmgen.tool.api import tool_api
+	tool = tool_api()
+
+### Key/address generation:
+
+	# List available coins:
+	print(' '.join(tool.coins))
+
+	# Initialize a coin/network pair:
+	proto = tool.init_coin('btc','mainnet')
+
+	# Print the available address types for current coin/network, along with a
+	# description.  If tool.addrtype is unset, the first-listed will be used:
+	tool.print_addrtypes()
+
+	# Set the address type to P2PKH with compressed public key:
+	tool.addrtype = 'compressed'
+
+	# Skip user entropy gathering (not recommended)
+	tool.usr_randchars = 0
+
+	# Generate a random hex secret:
+	hexsec = tool.randhex()
+
+	# Generate the key and address:
+	wif = tool.hex2wif(hexsec)
+	addr = tool.wif2addr(wif)
+
+	# Generate an LTC regtest Segwit key and address:
+	proto = tool.init_coin('ltc','regtest')
+	tool.addrtype = 'segwit'
+	wif = tool.hex2wif(hexsec)
+	addr = tool.wif2addr(wif)
+
+	# Generate a random LTC regtest Bech32 key/address pair:
+	tool.addrtype = 'bech32'
+	wif,addr = tool.randpair()
+
+### Mnemonic seed phrase generation:
+
+	# Generate an MMGen native mnemonic seed phrase:
+	mmgen_seed = tool.hex2mn(hexsec)
+
+	# Generate a BIP39 mnemonic seed phrase:
+	bip39_seed = tool.hex2mn(hexsec,fmt='bip39')
+
+### Utility methods:
+
+	# Reverse the hex string:
+	hexsec_rev = tool.hexreverse(hexsec)
+
+	# Get the HASH160 of the value:
+	sec_hash160 = tool.hash160(hexsec)
+
+	# Convert the value to base58 format:
+	sec_b58 = tool.hextob58(hexsec)
+
+	# Convert the value to base58 check format:
+	sec_b58chk = tool.hextob58chk(hexsec)
+
+	# Convert the byte specification '4G' to an integer:
+	four_g = tool.bytespec('4G')
+
+	# Convert the byte specification '4GB' to an integer:
+	four_gb = tool.bytespec('4GB')

+ 342 - 0
doc/wiki/using-mmgen/XOR-Seed-Splitting:-Theory-and-Practice.md

@@ -0,0 +1,342 @@
+## Contents
+
++ [XOR Seed Splitting: A Theoretical Introduction](#a_xor)
+   - [Deterministic Shares](#a_ds)
+   - [Named Splits](#a_ns)
+   - [Master Shares](#a_ms)
++ [Seed Splitting with MMGen](#a_ss)
+
+### <a name="a_xor">XOR Seed Splitting: A Theoretical Introduction</a>
+
+The bitwise exclusive-or operation (usually denoted as `XOR`, or “![⊕]”)
+has interesting properties that make it very useful in cryptography.
+
+Suppose we have two bytes, *a* and *b*:
+
+![]["a: 1 0 0 1 0 1 0 0"]
+
+![]["b: 0 1 0 1 1 1 1 0"]
+
+To XOR the two bytes, we compare each of their bits.  If the bit is the same for
+both *a* and *b,* the corresponding bit of the result is 0.  If it differs, the
+result is 1:
+
+![]["a ⊖ b: 1 1 0 0 1 0 1 0"]
+
+Thus XOR can be thought of logically as “one or the other but not both”, or
+arithmetically as addition [modulo][wm] 2 without carry, since 1 plus 1 equals 0
+in base-2 arithmetic.
+
+As is clear from our above example, switching the order of *a* and *b* has no
+effect on the result. So XOR, like addition, is commutative:
+
+![]["a ⊕ b = b ⊕ a"]
+
+And like addition, grouping has no effect on the result.  XOR is associative:
+
+![]["a ⊕ (b ⊕ c) = (a ⊕ b) ⊕ c"]
+
+But unlike addition, XOR has an extra property: *invertibility.* The result can
+be switched with any of the operands, making XOR sort of like addition and
+subtraction rolled into one.  Thus, if
+
+![]["a ⊕ b = c"]
+
+then
+
+![]["c ⊕ a = b"]
+
+![]["b ⊕ c = a"]
+
+and so forth.
+
+This last property makes XOR very handy for encryption and decryption.  Given
+a plaintext *P* and a random value *r* with the same bit length as *P*, we
+encrypt *P* by XOR’ing it with *r* to obtain the ciphertext *C:*
+
+![]["P ⊕ r = C"]
+
+To decrypt, we just XOR the ciphertext with *r* to recover the plaintext:
+
+![]["C ⊕ r = P"]
+
+The randomness of the ciphertext is guaranteed to be no less than that of the
+random value.  Thus if *r* is perfectly random, then *C* is perfectly
+undecipherable, given no knowledge of *r.*  This is the principle underlying the
+[one-time pads][otp] used by spies and diplomats before the computer age, as
+well as modern [stream ciphers][sc].
+
+To demonstrate how this can be used for seed splitting, all we do is change
+the names of the variables:
+
+![]["seed ⊕ share1 = share2"]
+
+Here *seed* is analogous to the plaintext *P,* *share*<sub>1</sub> is a random
+value with the same bit length as *seed,* and *share*<sub>2</sub> is the
+resulting ciphertext.  Just as the *C* reveals nothing about *P* in the previous
+example, *share*<sub>2</sub> reveals nothing about *seed* without knowledge of
+*share*<sub>1</sub>. To recover the seed, we just XOR the two shares.  Since XOR
+is commutative, the order in which we combine them isn’t important:
+
+![]["share2 ⊕ share1 = seed"]
+
+Thanks to XOR’s associativity, splits of any arbitrary length *n* can be created
+by using *n*-1 random shares, with the *n*-th share being the result of the
+chained XOR operations:
+
+Perform an *n*-way split:
+
+![]["seed ⊕ share1 ⊕ share2 ... ⊕ shareN-1 = shareN"]
+
+Join shares 1 through *n* to recover the seed:
+
+![]["share1 ⊕ share2 ... ⊕ shareN = seed"]
+
+Knowledge of any combination of *n*-1 shares reveals nothing about the seed.
+
+#### <a name="a_ds">Deterministic Shares</a>
+
+So we’ve seen that the mathematics behind XOR seed splitting is basically
+trivial.  In practice, though, there are several issues that need to be
+resolved.  For example, how do we obtain the random values for the shares?
+The easy answer is to just use the random number generator provided by our
+operating system.  Generating the values deterministically is a better solution,
+however, providing us with two key advantages: 1) we avoid reliance on such
+factors as the quality of our OS RNG, the underlying hardware, and the entropy
+pool; and 2) we gain reproducibility—the ability to generate identical shares
+repeatedly, and as a consequence, to generate shares independently of each
+other.
+
+This latter feature is especially useful.  Suppose I want to do a 2-way split
+of my seed for backup purposes, giving one share to my friend Bob and storing
+the other share at some location accessible to me but unknown to Bob.  With
+deterministic shares, I can generate Bob’s share now, giving it to Bob, and my
+own share later, once I’ve determined a good location for its safekeeping.  And
+if either of us loses our share, it can just be regenerated.
+
+OK, so now that we’re sold on the idea of deterministic shares, how do we go
+about creating them?  A naive approach would be to just generate a secure
+cryptographic hash of our seed using the SHA256 algorithm and use that directly
+as the first share:
+
+![]["share1 = SHA256(seed)"]
+
+This would work fine for a 2-way split: assuming SHA256 is secure and our seed
+is strongly random (if either assumption is false, we’re in big trouble anyway),
+then *share*<sub>1</sub> is strongly random too and reveals nothing about our
+seed.  And being derived from the seed alone, it’s regenerable on demand by the
+seed’s owner.
+
+For *n*-way splits where *n* is greater than 2, however, we run into a problem
+when attempting to generate the additional random values.  We might be tempted
+to just keep hashing:
+
+![]["share2 = SHA256(share1), share3 = SHA256(share2), ..."]
+
+But you may have already spotted the mistake here: the owner of the first share
+can generate all the successive shares up to *n*-1.  Without the final *n*’th
+share he can’t recover the seed, but the whole benefit of having the additional
+shares has been nullified.
+
+***Important disclaimer:*** *there are other reasons, beyond the scope of this
+discussion, why using a bare hash of the seed as our random number source might
+not be a good idea.  Bear in mind that this is a simplified **theoretical**
+introduction, and the examples presented herein are not suitable for
+implementation in real production code.*
+
+The above example illustrates what happens when we violate the golden rule of
+the wallet developer: *never derive a secret from another secret that someone
+besides the wallet’s owner could potentially gain access to.*  This goes for
+the private keys of the addresses in a wallet, which could be compromised in a
+security breach.  And it certainly goes for seed shares, which are intended for
+distribution to others from the outset.
+
+The solution to this problem is to derive the shares directly from the seed, but
+with an added identifier that’s unique to each share.  The [HMAC] message-digest
+algorithm is ideally suited for this:
+
+![]["share1 = HMAC(seed,'share1'), share2 = HMAC(seed,'share2'), ... shareN-1 = HMAC(seed,'share<N-1>')"]
+
+Using these unique pseudorandom values, we can now split and rejoin our seed in
+the manner described at the end of the previous section.
+
+#### <a name="a_ns">Named Splits</a>
+
+Now, we’d like to use seed splitting as part of our backup strategy, entrusting
+shares of our seed with various people we know.  Multiple 2-way splits seems
+like the best approach—if one of our trustees loses their share, moves to
+another city, or gets run over by a bus, then we have the others left as a
+fallback.
+
+However, we have no mechanism as yet for generating multiple splits: using the
+deterministic method outlined above, we can only generate the same set of
+pseudorandom shares over and over again.  The obvious solution here is to give
+each split a name: for our split with Bob, we’ll add “bob” to the share
+identifier, for our split with Alice, we’ll add “alice”, and so forth.  Using
+this approach, we can create an arbitrary number of uniquely named splits.
+
+Create a 2-way split with Bob:
+
+![]["share_me = HMAC(seed,'bob:share1'), share_bob = seed ⊕ share_me"]
+
+Create a 2-way split with Alice:
+
+![]["share_me = HMAC(seed,'alice:share1'), share_alice = seed ⊕ share_me"]
+
+In addition, we should handle the case of multiple splits with different length
+but the same name.  To exclude the reuse of shares in this case, we’ll add an
+additional identifier field specifying the total number of shares in the split.
+
+Create a 3-way split “friends” with Bob and Alice:
+
+![]["share_me = HMAC(seed,'friends:share1:of3'), share_bob = HMAC(seed,'friends:share2:of3'), share_alice = seed ⊕ share_me ⊕ share_bob"]
+
+Create a 4-way split “friends” with Bob, Alice and Carol:
+
+![]["share_me = HMAC(seed,'friends:share1:of4'), share_bob = HMAC(seed,'friends:share2:of4'), share_alice = HMAC(seed,'friends:share3:of4'), share_carol = seed ⊕ share_me ⊕ share_bob ⊕ share_alice"]
+
+Thus we’ve ensured the uniqueness of all shares across all possible splits.
+
+#### <a name="a_ms">Master Shares</a>
+
+As the number of splits we create grows, the question of how to store our shares
+becomes especially problematic.  Each new split creates another new share that
+must be securely stored somewhere.  What we need is some mechanism to generate
+our share of each split from a single master share.  This master share would
+need to be generated and stored just once, saving us a great deal of trouble.
+
+Having multiple master shares could be useful too.  For example, if we ever
+wanted to revoke our splits, all we’d have to do is destroy our copy of the
+current master share, ensuring that none of the splits made with its
+participation could be joined.  To create new splits, we’d use the next master
+share.
+
+This is all easy to implement using the tools we’re already familiar with from
+the preceding sections.  Using HMAC-SHA256, we’ll generate a range of indexed
+master shares from our seed as follows:
+
+![]["master1 = HMAC(seed,'master1'), master2 = HMAC(seed,'master2'), ..."]
+
+Using master share #1 as our share, our 4-way split “friends” with Bob, Alice
+and Carol now looks like this:
+
+![]["share_me = master1, share_bob = HMAC(seed,'friends:share2:of4:master1'), share_alice = HMAC(seed,'friends:share2:of4:master1'), share_carol = seed ⊕ HMAC(master1,'friends:share1:of4') ⊕ share_bob ⊕ share_alice"]
+
+And rejoining the seed looks like this:
+
+![]["seed = HMAC(master1,'friends:share1:of4') ⊕ share_bob ⊕ share_alice ⊕ share_carol"]
+
+The rejoining process now involves more than a simple XOR’ing of shares: The
+name of the split must be input to the join function, so it should something
+that’s easy to memorize.  If you *really* don’t trust yourself to remember
+the word “friends”, you could write it down somewhere.
+
+Also note that an additional field, `master<n>`, has been appended to the share
+identifiers.  This is to ensure that the shares of each master share split are
+unique, and differ from their non-master-share counterparts.
+
+### <a name="a_ss">Seed Splitting with MMGen</a>
+
+The MMGen wallet implements the seed splitting and joining functionality
+described above via the commands [`mmgen-seedsplit`][SS] and
+[`mmgen-seedjoin`][SJ].  Usage examples can be found on the `mmgen-seedsplit`
+help screen.
+
+Shares can be made from and exported to all supported MMGen wallet formats.
+This means you can split a BIP39 seed phrase, for example, and output the share
+back to BIP39 in one easy command:
+
+	# Create share 1 of a 2-way split of the provided BIP39 seed phrase:
+	$ mmgen-seedsplit -o bip39 sample.bip39 1:2
+
+Each share of a split has a unique share ID.  The share IDs are displayed by
+`mmgen-seedsplit` so that the user may record them for later reference.  They
+may also be viewed with the `mmgen-tool list_shares` command:
+
+	# List the share IDs of a 2-way named split 'alice' of your default wallet:
+	$ mmgen-tool list_shares 2 id_str=alice
+
+	Seed: 71CA5049 (256 bits)
+	Split Type: 2-of-2 (XOR)
+	ID String: alice
+
+	Shares
+	------
+	1: D0BBD210
+	2: 25F0BD65
+
+	# List the share IDs of a 3-way default split of provided BIP39 seed phrase:
+	$ mmgen-tool list_shares 3 wallet=sample.bip39
+
+	Seed: 03BAE887 (128 bits)
+	Split Type: 3-of-3 (XOR)
+	ID String: default
+
+	Shares
+	------
+	1: 83B9AF74
+	2: 109485F4
+	3: 424522DC
+
+Share IDs are handy for checking the correctness of shares when rejoining a
+split.  Let’s say you’ve decided to rejoin your 2-way split with Alice, whose
+share you exported to BIP39 format.  This can be done by contacting Alice by
+phone, for example, and having her read the mnemonic phrase to you.  If the ID
+of Alice’s share as displayed by `mmgen-seedjoin` matches the value you recorded
+when making the split, then you know Alice has given you the correct phrase.
+
+***Note:*** *when recovering shares over an insecure channel like the telephone,
+it’s advisable to destroy all copies of your share once you’ve rejoined the
+seed to safeguard against a possible eavesdropping attacker.*
+
+Ordinary named splits can easily be rejoined even without the MMGen software.
+First, each share must be converted to hexadecimal data.  If your shares are in
+BIP39 format, for example, there are command-line tools available to do this.
+Then a single line of Python code is all that’s required to finish the job:
+
+	$ python3
+	>>> seed_hex = hex(int(share1_hex,16) ^ int(share2_hex,16)) # rejoin a 2-way split
+
+(Note that the XOR operator in Python is `^`.)
+
+Unfortunately, rejoining master-share splits is considerably harder to do at
+the Python command prompt.  This is because converting the master share into the
+temporary share used to make the split involves an additional step, as you’ll
+recall from the above discussion.  In addition, this step is implemented by
+MMGen somewhat differently than as described above.  For advanced users, an
+example will be provided in a future version of this document.
+
+[⊕]: https://mmgen.github.io/images/ss/o_xor.svg "⊕"
+["a: 1 0 0 1 0 1 0 0"]: https://mmgen.github.io/images/ss/byte_a.svg "a: 1 0 0 1 0 1 0 0"
+["b: 0 1 0 1 1 1 1 0"]: https://mmgen.github.io/images/ss/byte_b.svg "b: 0 1 0 1 1 1 1 0"
+["a ⊖ b: 1 1 0 0 1 0 1 0"]: https://mmgen.github.io/images/ss/byte_ab.svg "a ⊖ b: 1 1 0 0 1 0 1 0"
+["a ⊕ b = b ⊕ a"]: https://mmgen.github.io/images/ss/ab-ba.svg "a ⊕ b = b ⊕ a"
+["a ⊕ (b ⊕ c) = (a ⊕ b) ⊕ c"]: https://mmgen.github.io/images/ss/abc.svg "a ⊕ (b ⊕ c) = (a ⊕ b) ⊕ c"
+["a ⊕ b = c"]: https://mmgen.github.io/images/ss/ab-c.svg "a ⊕ b = c"
+["c ⊕ a = b"]: https://mmgen.github.io/images/ss/ca-b.svg "c ⊕ a = b"
+["b ⊕ c = a"]: https://mmgen.github.io/images/ss/bc-a.svg "b ⊕ c = a"
+["P ⊕ r = C"]: https://mmgen.github.io/images/ss/Pr-C.svg "P ⊕ r = C"
+["C ⊕ r = P"]: https://mmgen.github.io/images/ss/Cr-P.svg "C ⊕ r = P"
+["seed ⊕ share1 = share2"]: https://mmgen.github.io/images/ss/ss-enc.svg "seed ⊕ share1 = share2"
+["share2 ⊕ share1 = seed"]: https://mmgen.github.io/images/ss/ss-dec.svg "share2 ⊕ share1 = seed"
+["seed ⊕ share1 ⊕ share2 ... ⊕ shareN-1 = shareN"]: https://mmgen.github.io/images/ss/ssN-enc.svg "seed ⊕ share1 ⊕ share2 ... ⊕ shareN-1 = shareN"
+["share1 ⊕ share2 ... ⊕ shareN = seed"]: https://mmgen.github.io/images/ss/ssN-dec.svg "share1 ⊕ share2 ... ⊕ shareN = seed"
+["share1 = SHA256(seed)"]: https://mmgen.github.io/images/ss/sha256.svg "share1 = SHA256(seed)"
+["share2 = SHA256(share1), share3 = SHA256(share2), ..."]: https://mmgen.github.io/images/ss/sha256b.svg "share2 = SHA256(share1), share3 = SHA256(share2), ..."
+["share1 = HMAC(seed,'share1'), share2 = HMAC(seed,'share2'), ... shareN-1 = HMAC(seed,'share<N-1>')"]: https://mmgen.github.io/images/ss/hmac.svg "share1 = HMAC(seed,'share1'), share2 = HMAC(seed,'share2'), ... shareN-1 = HMAC(seed,'share<N-1>')"
+["share_me = HMAC(seed,'bob:share1'), share_bob = seed ⊕ share_me"]: https://mmgen.github.io/images/ss/bob.svg "share_me = HMAC(seed,'bob:share1'), share_bob = seed ⊕ share_me"
+["share_me = HMAC(seed,'alice:share1'), share_alice = seed ⊕ share_me"]: https://mmgen.github.io/images/ss/alice.svg "share_me = HMAC(seed,'alice:share1'), share_alice = seed ⊕ share_me"
+["share_me = HMAC(seed,'friends:share1:of3'), share_bob = HMAC(seed,'friends:share2:of3'), share_alice = seed ⊕ share_me ⊕ share_bob"]: https://mmgen.github.io/images/ss/friends1.svg "share_me = HMAC(seed,'friends:share1:of3'), share_bob = HMAC(seed,'friends:share2:of3'), share_alice = seed ⊕ share_me ⊕ share_bob"
+["share_me = HMAC(seed,'friends:share1:of4'), share_bob = HMAC(seed,'friends:share2:of4'), share_alice = HMAC(seed,'friends:share3:of4'), share_carol = seed ⊕ share_me ⊕ share_bob ⊕ share_alice"]: https://mmgen.github.io/images/ss/friends2.svg "share_me = HMAC(seed,'friends:share1:of4'), share_bob = HMAC(seed,'friends:share2:of4'), share_alice = HMAC(seed,'friends:share3:of4'), share_carol = seed ⊕ share_me ⊕ share_bob ⊕ share_alice"
+["master1 = HMAC(seed,'master1'), master2 = HMAC(seed,'master2'), ..."]: https://mmgen.github.io/images/ss/master.svg "master1 = HMAC(seed,'master1'), master2 = HMAC(seed,'master2'), ..."
+["share_me = master1, share_bob = HMAC(seed,'friends:share2:of4:master1'), share_alice = HMAC(seed,'friends:share2:of4:master1'), share_carol = seed ⊕ HMAC(master1,'friends:share1:of4') ⊕ share_bob ⊕ share_alice"]: https://mmgen.github.io/images/ss/friends3.svg "share_me = master1, share_bob = HMAC(seed,'friends:share2:of4:master1'), share_alice = HMAC(seed,'friends:share2:of4:master1'), share_carol = seed ⊕ HMAC(master1,'friends:share1:of4') ⊕ share_bob ⊕ share_alice"
+["seed = HMAC(master1,'friends:share1:of4') ⊕ share_bob ⊕ share_alice ⊕ share_carol"]: https://mmgen.github.io/images/ss/friends4.svg "seed = HMAC(master1,'friends:share1:of4') ⊕ share_bob ⊕ share_alice ⊕ share_carol"
+
+<!-- https://mmgen.github.io/images/ss/ -->
+
+[HMAC]: https://en.wikipedia.org/wiki/HMAC
+[wm]: https://en.wikipedia.org/wiki/Modular_arithmetic
+[otp]: https://en.wikipedia.org/wiki/One-time_pad
+[sc]: https://en.wikipedia.org/wiki/Stream_cipher
+[SS]: seedsplit-[MMGen-command-help]
+[SJ]: seedjoin-[MMGen-command-help]