-
Notifications
You must be signed in to change notification settings - Fork 1
add round-trip for share derived via interpolation #2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
scgbckbone
wants to merge
3
commits into
BenWestgate:master
Choose a base branch
from
scgbckbone:test
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 1 commit
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| from src.codex32.codex32 import Codex32String | ||
|
|
||
|
|
||
| # secret share from seed | ||
| s = Codex32String.from_seed(bytes.fromhex("68f14219957131d21b615271058437e8"), "ms13k00ls") | ||
| assert s.s == "ms13k00lsdrc5yxv4wycayxmp2fcstppharks8z0r84pf3uj" | ||
|
|
||
| # derive 'a' via proposed BIP-85 | ||
| a = Codex32String.from_seed(bytes.fromhex("641be1cb12c97ede1c6bad8edf067760"), "ms13k00la") | ||
| assert a.s == "ms13k00lavsd7rjcje9ldu8rt4k8d7pnhvppyrt5gpff9wwl" | ||
|
|
||
| # derive 'c' via proposed BIP-85 | ||
| c = Codex32String.from_seed(bytes.fromhex("61b3c4052f7a31dc2b425c843a13c9b4"), "ms13k00lc") | ||
| assert c.s == "ms13k00lcvxeugpf00gcac26ztjzr5y7fkjl7fx7nx7ykhkr" | ||
|
|
||
| # derive next share via interpolation | ||
| d = Codex32String.interpolate_at([s, a, c], "d") | ||
| assert d.s == "ms13k00ldp4v5nw8lph96x47mjxzgwjexe44p32swkq99e0w" | ||
|
|
||
| # now round-trip d share ('d' is derived via interpolation, NOT via 'from_seed') | ||
| dd = Codex32String.from_seed(d.data, "ms13k00ld") | ||
| # they are NOT equal after round-trip - seem we miss padding at interpolation level | ||
| assert dd.s != d.s # FAIL (should equal) | ||
|
|
||
| # irrelevant | ||
| # e = Codex32String.interpolate_at([s, a, c], "e") | ||
| # assert e.s == "ms13k00lezuknydaaygk5u20zs4fm736vj909mdj6xqp8pc2" | ||
| # | ||
| # f = Codex32String.interpolate_at([s, a, c], "f") | ||
| # assert f.s == "ms13k00lf0ehe53zsu6vrxcjjh9v7wzsa83mqfvku3fd8kem" | ||
|
|
||
| # recover from shares, use 'd' without round-trip | ||
| rec_s = Codex32String.interpolate_at([a, d, c], "s") | ||
| # recover from shares, use 'd' after round-trip | ||
| rec_ss = Codex32String.interpolate_at([a, dd, c], "s") | ||
|
|
||
| print(" s:", s.data.hex()) | ||
| print(" rec_s:", rec_s.data.hex()) | ||
| print("rec_ss:", rec_ss.data.hex()) | ||
| assert s.data == rec_s.data | ||
| assert s.data == rec_ss.data # FAIL | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can't do this. You can only
.from_seedwithout passingpad_valfor thekinitial strings, derived strings MUST be passed padding to round-trip..You needed to be able to do this:
This version's
Codex32Stringlacks apad_valproperty, I'm working on an update which does.No matter what padding style we use, since it's less than a full 5-bit value, so not in field GF(32), it will not interpolate into derived shares and maintain any linear relationship that allows round-tripping from bytes, GF(256), to GF(32) interpolated strings without passing the padding.
The only string you should care about
dataof after construction is "s" so the fact other share index values can returndatais more of a curiosity and maybe.datashouldRaise InvalidShareIndexorreturn Noneif share_idx != "s"to this misuse.What is your exact use case where you really need to store ALL the shares as bytes and recover back to codex32?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm able to do this which fixes this test case:
dd = Codex32String.from_seed(d.data, "ms13k00ld", pad_val=1)but I have no idea how did I get to the
pad_val=1besides grinding it against the string which I already know (which won't be the case in real life)not really... besides grinding correct
pad_valright after construction of derived share via round-trips (very meh)So my general idea is that I can use individual shares as normal secrets, load them on HWW, sign with them, etc. For instance user uses one HWW device to do the shamir split, while having N devices ready to export generated/derived shares as QR codes for instance. Load these derived shares on devices and geo-distribute the devices. These then serve as decoy, fully functional signers. When S secret is needed user just collect K devices & does some QR scanning to recover the S on empty HWW.
For this I thought I can use this from_seed/to_seed round-trips. Secure element storage is limited so for me byte encoding is more desired instead of u5.
But now, it seems this was never intended purpose of the non-secret shares, which seems more as just recovery tools, aka data with one and only one purpose - to recover share S (which is kind of pity tbh). Am I reading this correctly?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I also think that if round-trips with derived shares can be achieved somehow, even if passing padding is necessary, it should be desired.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You had to grind it because you discarded the
pad_val. You might recover a different last data character if you don't know the last character without padding.interpolate_atoperates on 5-bit values not bytes.It may be possible to do it if you give up being able construct "non-encoded" shares from bytes data and instead accept construction of a
Codex32ShareSetobject with afrom_bytes(orfrom_seeds) factory. And then use aninterpolate_at(share_idx)method of that share set object.Make sure to skim this compact CodexQR discussion before speccing a QR design, it's the analog of compact SeedQR. I found a fun way to fit 128-bit codex32 share data into 21x21 QR codes by dropping some of the identifier.
Whatever solution we find for
Codex32ShareSet.from_bytes(header, dict)would be very helpful there, as well as here.This seems useful!
You may be able to round trip the share set
from_seeds/to_seedsor.dataof individual shares but we need to define the correct Codex32ShareSetfrom_seedsclass method to make this possible.The source of truth in a Codex32ShareSet should be the common header and the byte payloads of "s", "a", "c" for k = 3 or maybe "a", "c", "d". CRC padding, which does not interpolate, is slightly more useful on a share you can actually find and verify it on, than trying to interpolate to an unknown share to check if it validates.
A 21x21 QR has only 137.2 bits if using base45 alphanumeric encoding, 138.2 bits if also using kanji, bytes and numeric modes. So it'd be excellent for us to define a compact encoding of share data. The bare minimum needed to always recover the correct secret and with what's left: prevent user errors.
Yes, this is not their intended purpose but they do contain randomness and I think your idea is a cool and efficient use of that otherwise wasted random data needed for SSS so worth pursuing IF it can be done securely (not revealing any more info about "s" than, at most, its padding bits with
k-1shares.)I agree. The solution to recover seeds from bytes alone is non-trivial but it should exist, lets find it. You'll find this bytes vs 130-bits question tripped up Andrew in the QR discussion, it's always surprising how padding behaves as the finite field changes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@scgbckbone do you still want me to come up with a way to recover the same seed from any share's (not just initial strings) bytes without passing padding? I think it's technically possible but I haven't thought about how to do it yet.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Working for your vectors at least.
This approach takes the
share_idx_listandshare_bytes_list,header, then for every possible combination of pad values, interpolates the initial k shares, checks they round trip, returns thetargetstring at "s" if all initial shares round trip.I haven't proven yet this always finds exactly one solution but my intuition says it will especially when the default padding bits checksum the missing last character data bits.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd try it, but missing definitions (like
encodein above snippet). Do you have some implementation, even if just POC, you can post ?Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can use
Codex32String.from_seed()in place ofencodejust passheader_strinstead of hrp and header separately.IDX_ORDER='sacdefghjklmnpqrstuvwxyz023456789'The last tip is you must generate the 'a', 'c', ... 'd' etc shares with default padding as shown in my screenshot because it recovers by solving what padding values on its given share data recovers initial shares with correct padding.
Try and see if it always recovers the correct seed from shares interpolated from initial shares that used the default padding. I think it should.
That it recovered the right seed for your vector is a bad omen because the d share was interpolated and per bip85 and bip93 "for a fresh secret" d should be an initial share (with default padding) for threshold 3, but by chance it passed the share d padding check and recovered the correct secret.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not sure what I'm missing, I can see that in your screenshot you're using
dthat is not round-tripped. I usefrom_seedthat uses your CRC checksum as padding.I updated the vector:
dd4767a
still does not recover correct secret
sThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we're solving the wrong issue.
How do you know which device has which share index? Presumably with your metadata that has the threshold and identifier. Why not just store the padding bits there?
They are secrets though so if you store every shares padding in one place, you likely leak most or all of the last secret character. So it makes sense to encode them with payload bytes.
I like a solution where 2 bytes are added to store metadata and padding as 1) it's similar to what Compact CodexQR needs, and 2) you intend for these "secrets" to be QR scannable, which is a related problem to solve.
Does an 18-byte seed ruin the plausible deniability of these decoys?
If yes, then why not store the padding and extra bits to make them look like 192- or 256-bit seeds or something "plausible". And if you can store a 32-byte seed you can store a codex32 string data part (sans checksum) as one u5 per byte.
If neither of those are satisfactory, we could tweak how Compact CodexQR works so the share index and threshold is not encoded to maintain plausible deniability about whether a CodexQR was produced from a codex32 secret or a codex32 share.
It's a UX regression but decoy QRs that meet this system's requirements may be worth it. Especially since Compact CodexQR is opt in, normal undeniable shares can always be encoded in alphanumeric mode in a larger QR.
I'm envisioning a recovery process where:
It is possible to have decoy wallets at any and every threshold and it's relatively cheap (seconds to minutes) to grind a decoy share whose only purpose is to recover a decoy "secret" with the desired fingerprint.