deleted: N-of-N-Seed-Splitting,-Theory-and-Practice.md

new file:   N‐of‐N-Seed-Splitting,-Theory-and-Practice.md
The MMGen Project 2019-11-19 21:33:38 +00:00
commit 7f8851adc2
Signed by: mmgen
GPG key ID: 3F8B1861E32B7DA2
2 changed files with 239 additions and 119 deletions

@ -1,119 +0,0 @@
### Contents
+ [N-of-N Seed Splitting: A Theoretical Introduction](#a_nn)
+ [Seed Splitting with MMGen](#a_ss)
#### <a name='a_nn'>N-of-N 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 without 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 *P* in the previous example, *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 ciphertext reveals nothing about the
plaintext in the previous example, *share<sub>2</sub>* reveals nothing about
*seed* without knowledge of *share<sub>1</sub>.* And *share<sub>1</sub>,* of
course, being a random value, reveals nothing about *seed* either. 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 arbitrary length can be created by
using an arbitrary number of random shares:
Perform an *n*-way split:
![]["seed ⊕ share1 ⊕ share2 ... ⊕ shareN-1 = shareN"]
Recover the seed:
![]["share1 ⊕ share2 ... ⊕ shareN = seed"]
Knowledge of anything less than *n* shares reveals nothing about the seed.
#### <a name='a_ss'>Seed Splitting with MMGen</a>
The MMGen wallet implements seed-splitting functionality via the commands
`mmgen-seedsplit` and `mmgen-seedjoin`.
TBD
[⊕]: 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"
[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

@ -0,0 +1,239 @@
## Contents
+ [N-of-N Seed Splitting: A Theoretical Introduction](#a_nn)
- [Deterministic Shares](#a_ds)
- [Named Splits](#a_ns)
- [Master Shares](#a_ms)
+ [Seed Splitting with MMGen](#a_ss)
### <a name="a_nn">N-of-N 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 XOR
operation:
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 N-of-N 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?
An easy solution would be to just use our OS’s random number generator. A
better one, however, is to generate the values deterministically. This provides
two key advantages: 1) we avoid reliance on OS random numbers, the quality of
which can depend on the hardware, operating system and such factors as the lack
of a good entropy pool on machines without a network connection; and 2) we gain
reproducibility—the ability to identical 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
generate them? A naïve solution is to just create a 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 (and if either assumption is false, we’re in big trouble
anyway), then *share*<sub>1</sub> is strongly random 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 two, however, the problem arises
of how 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 problem 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 point of having a greater than
2-way split has been nullified.
This example illustrates what happens when we violate the golden rule of the
wallet developer: *never derive one secret from another, except from the wallet
seed itself.* The seed belongs solely to the wallet’s owner and is never made
public, but other secrets might belong to others (including by being
accidentally revealed), giving those others access to any secrets that might
be derived from the secrets they possess.
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] algorithm was
created just for this purpose:
![]["share1 = HMAC(seed,'share1'), share2 = HMAC(seed,'share2'), ... shareN-1 = HMAC(seed,'share<N-1>')"]
Using these deterministic 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>
By now it might seem that we’ve laid the necessary theoretical groundwork and
can now proceed to the implementation stage. But actually, we’re just getting
started.
Consider the following situation: 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 share 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 for generating multiple splits: using the
deterministic approach 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 each share’s
unique identifier, for our split with Alice, we’ll add “alice”, and so forth.
To keep things simple for demonstration purposes, we’ll just separate the parts
of the identifier with colons:
![]["share1_bob = HMAC(seed,'bob:share1')"]
![]["2-way split with Bob: seed ⊕ share1_bob = share2_bob"]
In addition, we should handle the case where we have multiple identically named splits
of different length. To avoid reusing shares between the splits, we’ll add an
additional identifier element specifying the total number of shares:
![]["share1of3_friends = HMAC(seed,'friends:share1:of3') ..."]
![]["share1of4_friends = HMAC(seed,'friends:share1:of4') ..."]
![]["Split with 3 friends: seed ⊕ share1of3_friends ⊕ share2of3_friends = share3of3_friends"]
![]["Split with 4 friends: seed ⊕ share1of4_friends ⊕ share2of4_friends ⊕ share3of4_friends = share4of4_friends"]
So now we can create multiple named splits with various people and groups, and
we’ve ensured that all shares of all possible splits are unique.
#### <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 means another new share that we
have to store somewhere. If there were some mechanism to generate our share of
each split from a single master share, this would simplify things enormously for
us. The ability to create several such master shares might come in handy too.
### <a name="a_ss">Seed Splitting with MMGen</a>
The MMGen wallet implements seed-splitting functionality via the commands
`mmgen-seedsplit` and `mmgen-seedjoin`.
TBD
[⊕]: 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>')"
["share1_bob = HMAC(seed,'bob:share1')"]: https://mmgen.github.io/images/ss/bob.svg "share1_bob = HMAC(seed,'bob:share1')"
["2-way split with Bob: seed ⊕ share1_bob = share2_bob"]: https://mmgen.github.io/images/ss/bob2.svg "2-way split with Bob: seed ⊕ share1_bob = share2_bob"
["share1of3_friends = HMAC(seed,'friends:share1:of3') ..."]: https://mmgen.github.io/images/ss/friends1.svg "share1of3_friends = HMAC(seed,'friends:share1:of3') ..."
["share1of4_friends = HMAC(seed,'friends:share1:of4') ..."]: https://mmgen.github.io/images/ss/friends2.svg "share1of4_friends = HMAC(seed,'friends:share1:of4') ..."
["Split with 3 friends: seed ⊕ share1of3_friends ⊕ share2of3_friends = share3of3_friends"]: https://mmgen.github.io/images/ss/friends1a.svg "Split with 3 friends: seed ⊕ share1of3_friends ⊕ share2of3_friends = share3of3_friends"
["Split with 4 friends: seed ⊕ share1of4_friends ⊕ share2of4_friends ⊕ share3of4_friends = share4of4_friends"]: https://mmgen.github.io/images/ss/friends2a.svg "Split with 4 friends: seed ⊕ share1of4_friends ⊕ share2of4_friends ⊕ share3of4_friends = share4of4_friends"
<!-- 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