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
This commit is contained in:
The MMGen Project 2022-06-09 11:18:08 +00:00
commit 59368fd7c1
Signed by: mmgen
GPG key ID: 3F8B1861E32B7DA2
3 changed files with 608 additions and 0 deletions

View file

@ -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

View file

@ -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')

View file

@ -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]