-
Notifications
You must be signed in to change notification settings - Fork 9
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
Derive within spending key alphanet v5 compat pr #317
base: master
Are you sure you want to change the base?
Derive within spending key alphanet v5 compat pr #317
Conversation
Primary features: 1. SpendingKey can now derive its own children without any wallet state. This enables RPC clients to derive keys themselves. 2. Derivation iterators. SpendingKeyIter and SpendingKeyParallelIter. The latter leverages rayon to utilize all cpu cores. 3. /known_keys rpc now returns a SpendingKeyRange instead of a list of addresses. This is a big perf win for large wallets. changelog: refactor spending-key types so each key can derive its own children known_keys rpcs now return iterators instead of Vec DerivationIndex: u64 -> u128. fix premine_distribution(). separate test vs non-test. make it aware of current network impl IntoIterator for SpendingKey GenerationReceivingAddress::derive_from_seed() -> from_seed() impl rayon parallel iterators SpendingKeyIter now starts at child 0, instead of parent_key. fix some DoubleEndedIter issues iteration tests add test double_ended_range_iterator_meet_middle add test double_ended_iterator_to_first_elem add tests double_ended_range_iterator_to_first_elem, range_iterator_to_last_elem, iterator_nth separate SpendingKeyParallelIter from SpendingKeyIter add par_iter tests iterator accepts any impl RangeBounds tests to validate RangeBounds conversions known_keys() rpc returns SpendingKeyRange instead of SpendingKeyIter only perform extra GenerationSpendingKey assertions for debug build. add comments
fixes a panic in SpendingKeyParallelIter::collect(). also adds a test assertion that count() is correct. An incorrect count was another symptom of the bug. big thanks to @cuviper for spotting the problem. rayon-rs/rayon#1224 (comment)
This modifies generation_address so that the same addresses will be derived from a given wallet secret as happened at tag alphanet-v5. A test exists which verifies this: wallet_tests::generation_key_derivation::verify_derived_generation_addrs() Significant changes: * compatibility with alphanet-v5 addresses: * DerivationIndex: u128 --> u64 * replace GenerationSpendingKey::seed with secret, index * rename GenerationSpendingKey::from_seed() --> from_secret_and_index() * add GenerationSpendingKey::seed(secret, index) which uses same hash(secret, generation_flag, index) construction as WalletSecret::nth_generation_key() used to. * WalletSecret::master_generation_key is now DerivationIndex::MAX so that the 0th key can have a parent. * GenerationSpendingKey::derive_child() now uses wrapping_add() so that master key (index = DerivationIndex::MAX) can derive 0th key. * add KeyTypeSeed to abstract over seed types Minor changes: * update a couple Block tests to use mainnet. * rename SpendingKeyIter::curr --> curr_fwd * rename SpendingKeyIter::curr_back --> curr_rev * updates test verify_derived_generation_keys with newly generated keys because serialized key format has changed. * add some missing doc-comments for key/addr in generation_address.rs * update GenerationSpendingKey deserialization impl * add impl From<&GenerationSpendingKey> for GenerationReceivingAddress * remove GenerationReceivingAddress::from_spending_key() * update Arbitrary impls for GenerationSpendingKey seeds * add a couple of Arbitrary impl to the feature flag. * adapt tests to changes
I have another idea how to impl self-deriving keys. first, a review of the implementations thus far. historical impl, in master (not self deriving):
This has the property that if any single key is compromised, no other key is compromised. No key can be derived without access to wallet-secret, which is never supplied to client(s) via RPC. first self-deriving impl from #304:
This has property/problem that any key can derive all its children. So if key with index = 5 is compromised then all index > 5 are likewise compromised. Also, these keys/addrs do not match legacy keys from alphanet-v5. 2nd self-deriving impl, in this pr:
This has property/problem that any key can derive all keys. So if key with index = 2005 is compromised then all index <= 2005 and >= 2005 are compromised. these keys/addrs match legacy keys from alphanet-v5. finally, a possible 3rd construction, master-key-only derivation:
Here the master-key would store wallet_secret while the sub-keys would store only a digest. This is the original/legacy impl except that the master-key would be a serializable type intended for use by client software. This has the property that if the master key is compromised all keys are compromised. However if any spending key is compromised, no other key is compromised. WIth this construction spending keys are not self-deriving, but the master-key can derive all child keys. I believe this is similar to xprv in bip32. @aszepieniec I leave it to you to consider. Please let me know if any q's. |
I can't pass judgment because I'm confused about this summary. Please clarify and/or confirm.
Based on abstract consideration of the properties (ignoring how they arise from the concrete implementations) I tend towards this position. (But I reserve the right to change my mind after understanding the matter better.)
|
I wonder whether you ever considered this scheme? If so, what what made you decide against it?
It arranges all keys into a balanced binary tree such that every key is the root of some branch, and every key has two immediate children. If a key is compromised, only its branch is compromised. |
Supersedes #304
This modifies generation_address so that the same addresses will
be derived from a given wallet secret as happened at tag alphanet-v5 (Jan 6, 2024)
A test exists which verifies this:
wallet_tests::generation_key_derivation::verify_derived_generation_addrs()
Significant changes since #304:
compatibility with alphanet-v5 addresses:
hash(secret, generation_flag, index) construction as
WalletSecret::nth_generation_key() used to.
that the 0th key can have a parent.
master key (index = DerivationIndex::MAX) can derive 0th key.
add KeyTypeSeed to abstract over seed types
Minor changes:
because serialized key format has changed.
Of special note for review:
note the fields that changed in GenerationSpendingKey and the method from_seed() became from_secret_and_index().
consider security impiications that every key now embeds the wallet master secret. This means that if any key is compromised every key in the wallet is compromised. If this is not acceptable (and it probably shouldn't be), I believe it is a deal-breaker for backwards compatibility. I don't believe there's any other way to have both self-deriving keys and backwards compatibility, but feel free to prove me wrong....
compare impl of GenerationSpendingKey::seed() with WalletSecret::nth_generation_spending_key() in master.
also see GenerationSpendingKey::derive_child()
note how the master generation key in WalletSecret is DerivationIndex::MAX, and that GenerationSpendingKey::derive_child() allows wrapping add, so that the master key can be parent of the 0th key. I initially made an impl that did not do this, and instead the master-key is the 0th key, but this complicated the implementation of the nth_* methods and made them a bit ugly and non-obvious. I feel this impl is overall more elegant.
the DerivationIndex is a u64 instead of u128 -- for all key types. I think it's fine.... that's still a huge amount of possible addresses for a wallet.
review test: wallet_tests::generation_key_derivation::verify_derived_generation_addrs()
with regards to point 2, see #317 (comment)
With regards to point 2: my conclusion after some reflection is that the weakened security risk is not worth it, and we should prefer #304 rather than this PR. If we are not willing to accept the weakened security, we have a choice:a. implement self-derived keys in a backwards incompatible way, eg #304.
b. prioritize backwards compatiiblity and do not use self-deriving keys at all.
I very much prefer (a) even if it means some short-term pain because we have to obtain new addresses from 3rd parties. I think that self-deriving keys bring a lot of value for the ecosystem that will be lost if every piece of software must have access to the wallet secret directly or via RPC.