From 7f8851adc268ccdbb8503991b37bc1373e8459ec Mon Sep 17 00:00:00 2001 From: The MMGen Project Date: Tue, 19 Nov 2019 21:33:38 +0000 Subject: [PATCH] =?UTF-8?q?deleted:=20=20=20=20N-of-N-Seed-Splitting,-Theo?= =?UTF-8?q?ry-and-Practice.md=20new=20file:=20=20=20N=E2=80=90of=E2=80=90N?= =?UTF-8?q?-Seed-Splitting,-Theory-and-Practice.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- N-of-N-Seed-Splitting,-Theory-and-Practice.md | 119 --------- N‐of‐N-Seed-Splitting,-Theory-and-Practice.md | 239 ++++++++++++++++++ 2 files changed, 239 insertions(+), 119 deletions(-) delete mode 100644 N-of-N-Seed-Splitting,-Theory-and-Practice.md create mode 100644 N‐of‐N-Seed-Splitting,-Theory-and-Practice.md diff --git a/N-of-N-Seed-Splitting,-Theory-and-Practice.md b/N-of-N-Seed-Splitting,-Theory-and-Practice.md deleted file mode 100644 index ed47ab2..0000000 --- a/N-of-N-Seed-Splitting,-Theory-and-Practice.md +++ /dev/null @@ -1,119 +0,0 @@ -### Contents -+ [N-of-N Seed Splitting: A Theoretical Introduction](#a_nn) -+ [Seed Splitting with MMGen](#a_ss) - -#### N-of-N Seed Splitting: A Theoretical Introduction - -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, *share1* is -a random value with the same bit length as *seed,* and *share2* is -the resulting “ciphertext”. Just as the ciphertext reveals nothing about the -plaintext in the previous example, *share2* reveals nothing about -*seed* without knowledge of *share1.* And *share1,* 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. - -#### Seed Splitting with MMGen - -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 diff --git a/N‐of‐N-Seed-Splitting,-Theory-and-Practice.md b/N‐of‐N-Seed-Splitting,-Theory-and-Practice.md new file mode 100644 index 0000000..41e53ec --- /dev/null +++ b/N‐of‐N-Seed-Splitting,-Theory-and-Practice.md @@ -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) + +### N-of-N Seed Splitting: A Theoretical Introduction + +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*1 is a random +value with the same bit length as *seed,* and *share*2 is the +resulting ciphertext. Just as the *C* reveals nothing about *P* in the previous +example, *share*2 reveals nothing about *seed* without knowledge of +*share*1. 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. + +#### Deterministic Shares + +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*1 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')"] + +Using these deterministic pseudorandom values, we can now split and rejoin our +seed in the manner described at the end of the previous section. + +#### Named Splits + +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. + +#### Master Shares + +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. + +### Seed Splitting with MMGen + +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')"]: https://mmgen.github.io/images/ss/hmac.svg "share1 = HMAC(seed,'share1'), share2 = HMAC(seed,'share2'), ... shareN-1 = HMAC(seed,'share')" +["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" + + +[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