renamed and modified: XOR-Seed-Splitting:-Theory-and-Practice.md
parent
7f8851adc2
commit
ec87b538a9
2 changed files with 276 additions and 239 deletions
|
|
@ -1,239 +0,0 @@
|
|||
## 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
|
||||
276
XOR-Seed-Splitting:-Theory-and-Practice.md
Normal file
276
XOR-Seed-Splitting:-Theory-and-Practice.md
Normal file
|
|
@ -0,0 +1,276 @@
|
|||
## 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 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 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 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 your memory, 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 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>')"
|
||||
["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
|
||||
Loading…
Add table
Add a link
Reference in a new issue