Switch hardened derivation marker to h#26076
Conversation
|
The following sections might be updated with supplementary metadata relevant to reviewers and maintainers. ReviewsSee the guideline for information on the review process.
If your review is incorrectly listed, please react with 👎 to this comment and the bot will ignore it on the next update. ConflictsReviewers, this pull request conflicts with the following ones:
If you consider this pull request important, please also help to review the conflicting pull requests. Ideally, start with the one that should be merged first. |
doc/release-notes-26076.md
Outdated
There was a problem hiding this comment.
The enclosing single quotes are shell escaping, not part of the actual RPC call (which has no issue at present).
There was a problem hiding this comment.
By "manually" I mean using bitcoin-cli from the shell. Afaik the GUI console - probably used more often - has the same problem.
|
concept ack |
|
ACK fc5594d036a47434ea301f5e6adff06c59649fd2 |
|
I'm in favor of using Would it be unreasonable to make the descriptor classes remember which of the two was used when parsing, and reuse the same one when serializing? That guarantees roundtripping, but defaults could still prefer Not a NACK, but I'd be more comfortable with just honoring whatever was parsed. |
|
My apologies for the accidental close. That was unintentional. |
008df84 to
9df0fd2
Compare
|
Made a slight documentation change to the first commit and then pushed three more which change the following behaviour:
I could squash the new behavior into the original commit, but I'll wait for concept ACK. |
|
Concept ACK Please squash |
|
@achow101 squashed |
9df0fd2 to
80f2031
Compare
src/script/descriptor.cpp
Outdated
There was a problem hiding this comment.
Should this consider normalized as well?
There was a problem hiding this comment.
I intentionally didn't normalize the private-key versions. Though I could.
There was a problem hiding this comment.
What was the rationale for only normalizing the public-key version with h?
There was a problem hiding this comment.
We don't normalize the private key version of descriptors in general. I'm not sure if it matters a lot, but would prefer to keep such a change to followup.
src/script/descriptor.cpp
Outdated
There was a problem hiding this comment.
Does apostrophe need to be optional? Either the path uses an apostrophe, or it doesn't. And it doesn't matter if the path is not hardened at all.
There was a problem hiding this comment.
In one of the call sites it first checks the origin and then the rest of the path. The optional is necessary to combine those results, especially in the case where the origin doesn't have any hardened derivation (one of the test cases).
There was a problem hiding this comment.
I'm not seeing how it is necessary. It could initialized to false in ParsePubKey and I think that would achieve the same result. Nothing is checking whether the optional has a value, except at the very end with value_or(false), and that's equivalent to initializing as false.
There was a problem hiding this comment.
If I initialise to false, it would be set to true when checking the origin and then back to false when checking the path.
(at minimum this confusion suggests I should add more comments)
There was a problem hiding this comment.
This seems to work:
diff --git a/src/script/descriptor.cpp b/src/script/descriptor.cpp
index e639e494582..877d2538b2c 100644
--- a/src/script/descriptor.cpp
+++ b/src/script/descriptor.cpp
@@ -1056,7 +1056,7 @@ enum class ParseScriptContext {
};
/** Parse a key path, being passed a split list of elements (the first element is ignored). */
-[[nodiscard]] bool ParseKeyPath(const std::vector<Span<const char>>& split, KeyPath& out, std::optional<bool>& apostrophe, std::string& error)
+[[nodiscard]] bool ParseKeyPath(const std::vector<Span<const char>>& split, KeyPath& out, bool& apostrophe, std::string& error)
{
for (size_t i = 1; i < split.size(); ++i) {
Span<const char> elem = split[i];
@@ -1083,7 +1083,7 @@ enum class ParseScriptContext {
}
/** Parse a public key that excludes origin information. */
-std::unique_ptr<PubkeyProvider> ParsePubkeyInner(uint32_t key_exp_index, const Span<const char>& sp, ParseScriptContext ctx, FlatSigningProvider& out, std::optional<bool>& apostrophe, std::string& error)
+std::unique_ptr<PubkeyProvider> ParsePubkeyInner(uint32_t key_exp_index, const Span<const char>& sp, ParseScriptContext ctx, FlatSigningProvider& out, bool& apostrophe, std::string& error)
{
using namespace spanparsing;
@@ -1149,7 +1149,7 @@ std::unique_ptr<PubkeyProvider> ParsePubkeyInner(uint32_t key_exp_index, const S
extpubkey = extkey.Neuter();
out.keys.emplace(extpubkey.pubkey.GetID(), extkey.key);
}
- return std::make_unique<BIP32PubkeyProvider>(key_exp_index, extpubkey, std::move(path), type, apostrophe.value_or(false));
+ return std::make_unique<BIP32PubkeyProvider>(key_exp_index, extpubkey, std::move(path), type, apostrophe);
}
/** Parse a public key including origin information (if enabled). */
@@ -1163,7 +1163,7 @@ std::unique_ptr<PubkeyProvider> ParsePubkey(uint32_t key_exp_index, const Span<c
return nullptr;
}
// This is set if either the origin or path suffix contains a hardened deriviation.
- std::optional<bool> apostrophe;
+ bool apostrophe = false;
if (origin_split.size() == 1) {
return ParsePubkeyInner(key_exp_index, origin_split[0], ctx, out, apostrophe, error);
}
@@ -1190,7 +1190,7 @@ std::unique_ptr<PubkeyProvider> ParsePubkey(uint32_t key_exp_index, const Span<c
if (!ParseKeyPath(slash_split, info.path, apostrophe, error)) return nullptr;
auto provider = ParsePubkeyInner(key_exp_index, origin_split[1], ctx, out, apostrophe, error);
if (!provider) return nullptr;
- return std::make_unique<OriginPubkeyProvider>(key_exp_index, std::move(info), std::move(provider), apostrophe.value_or(false));
+ return std::make_unique<OriginPubkeyProvider>(key_exp_index, std::move(info), std::move(provider), apostrophe);
}
std::unique_ptr<PubkeyProvider> InferPubkey(const CPubKey& pubkey, ParseScriptContext, const SigningProvider& provider)
There was a problem hiding this comment.
Ah yes, because ParseKeyPath leaves &apostrophe alone if it doesn't find hardened derivation. Fixed and added some documentation.
src/test/descriptor_tests.cpp
Outdated
There was a problem hiding this comment.
It's not clear to me why this and the change in Check are necessary?
There was a problem hiding this comment.
Could be a left-over from the squash. Will look into this once we decide whether or not to normalise private keys, because that might change things again: #26076 (comment)
There was a problem hiding this comment.
Not a left-over; the test fails without it. The behaviour is quite different now that we echo the original descriptor rather than always put ' in it.
|
it might make sense to have new wallets only generate |
|
@JeremyRubin is there software that can't parse BIP32 paths with Rebased. |
80f2031 to
5df4d99
Compare
|
ah nope i misremembered that rust-bitcoin didn't support |
darosior
left a comment
There was a problem hiding this comment.
light ACK bd13dc2. Code looks correct to me but i find the OP and release note misleading (stated purpose is already possible and not what this PR implements) and i'm a bit confused about the choice of where to make breaking changes:
- Why not honour what was parsed for normalized serialization?
- Why keep serializing
hdkeypath's hardened paths with apostrophe for legacy wallet but not descriptor wallets or elsewhere (like indecodepsbt)?
Just to be clear: no strong opinion, and i don't mean to be blocking this. I think it's safe. Just i don't get the rationale for where to (slightly) break the API or not.
| // In legacy wallets hdkeypath has always used an apostrophe for | ||
| // hardened derivation. Perhaps some external tool depends on that. | ||
| ret.pushKV("hdkeypath", WriteHDKeypath(meta->key_origin.path, /*apostrophe=*/!desc_spk_man)); |
There was a problem hiding this comment.
But so has it in descriptor wallets? Why would it be more ok to make a breaking change for descriptor wallets than for legacy wallet?
There was a problem hiding this comment.
Descriptors have always required parsers to handle both h and ', and I'm not aware of any that don't. For old BIP32 derivation parsing software I'm less sure about that.
doc/release-notes-26076.md
Outdated
| wallets use `h`. This change makes it easier to handle descriptor strings manually. | ||
| E.g. an RPC call that takes an array of descriptors can now use '["desc": ".../0h/..."]'. |
There was a problem hiding this comment.
This is already possible on master (and also at least on 24.0.1). This PR does not change that. For instance on a 24.0.1 regtest node:
importdescriptors '[{"desc":"pk([00aabbcc/0h/1h]cSWVo8YMehg6STxqw6xGgmMJE6Ac6RCJ7owcrBZRAT7QANtyAAEj)#uqdekwuc","timestamp":"now"}]'
works fine. So does:
scantxoutset start '["pk([00aabbcc/0h/1h]cSWVo8YMehg6STxqw6xGgmMJE6Ac6RCJ7owcrBZRAT7QANtyAAEj)#uqdekwuc"]'
There was a problem hiding this comment.
I'll clarify what I meant here: you can copy-paste the result of listdescriptors when doing such an import, because this now uses h instead of '
doc/release-notes-26076.md
Outdated
| RPC | ||
| --- | ||
|
|
||
| - The `listdescriptors` RPC now shows `h` rather than apostrophe (`'`) to indicate |
|
Thanks for the review! The reason for not breaking legacy wallet RPC calls is that I'm assuming any code that uses legacy wallets is really old and nobody wants to touch it. On our side eventually we want to get rid of the legacy wallet - or at least freeze it - so there's no need to modernise it. I updated the title, description and release note. Pushed the latter as a new commit to not lose ACKs. |
|
ACK fe49f06 |
| For legacy wallets the `hdkeypath` field in `getaddressinfo` is unchanged, | ||
| nor is the serialization format of wallet dumps. (#26076) |
There was a problem hiding this comment.
The “nor” here seems misplaced:
| For legacy wallets the `hdkeypath` field in `getaddressinfo` is unchanged, | |
| nor is the serialization format of wallet dumps. (#26076) | |
| For legacy wallets the `hdkeypath` field in `getaddressinfo` | |
| and the serialization format of wallet dumps remain unchanged. (#26076) |
This makes it easier to handle descriptor strings manually, especially when importing from another Bitcoin Core wallet.
For example the
importdescriptorsRPC call is easiest to usehas the marker:'["desc": ".../0h/..."]', avoiding the need for escape characters. With this changelistdescriptorswill useh, so you can copy-paste the result, without having to add escape characters or switch'to 'h' manually.Both markers can still be parsed.
The
hdkeypathfield ingetaddressinfois also impacted by this change, except for legacy wallets. The latter is to prevent accidentally breaking ancient software that uses our legacy wallet.See discussion in #15740