-
Notifications
You must be signed in to change notification settings - Fork 52
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
Add support for "relative" BIP32 derivation paths #49
Add support for "relative" BIP32 derivation paths #49
Comments
Thanks for opening an issue @rnbrady! You're definitely right, this was an oversight in the original API. I like your idea for denoting the "relative" derivation too. Since it's not part of the BIP32 spec, I think using a unique character to denote relative derivation might be clearer, so I think I'd prefer the One minor issue: we can't check that the provided node is actually the correct one for the designated part of the path: const masterNode = deriveHdPrivateNodeFromSeed(crypto, hexToBin("000102030405060708090a0b0c0d0e0f"));
if (!masterNode.valid) return console.error(`Error with initial derivation.`);
console.log('Master node depth:', masterNode.depth); // 0
const depth2Path = "m/1'/0";
const depth2Node = deriveHdPath(crypto, masterNode, depth2Path);
if(typeof depth2Node === 'string') return console.error(depth2Node);
// depth2Node is "m/1'/0"
const depth4Path = "m/2'/0/0/0";
const depth4Node = deriveHdPath(crypto, depth2Node, depth4Path);
if(typeof depth4Node === 'string') return console.error(depth4Node);
// depth4Node is "m/1'/0/0/0" rather than "m/2'/0/0/0" Though I think we can just note that prominently in the tsdocs. It also looks like we're not validating I'll try to get this in the next major release. 👍 (Happy to take a PR as well!) |
That is a good point re not being able to detect whether the path matches the node. The alternative would be to return an error if an absolute path is requested with a non-master starting node. |
Today I learned that by convention wallet apps which provide a xpub for export (e.g. Electron Cash, Bitcoin.com Wallet, Crescent) will provide the xpub for the account level node (depth 3). This makes sense as the first 3 levels are usually hardened (m/44'/145'/0') per BIP44 which makes the master xpub useless (these apps seems to support only one such "account", there is not way to add more other than by creating whole new HDWs at mnemonic / seed / master node level, which I found counterintuitive). So it looks like the depth 3 import followed by relative path derivation would be a common use case. I'll ask around for thoughts on how devs would expect to handle. |
To summarize some out-of-band discussion: no matter how we design this API, some unintuitive-ness is going to remain due to choices in BIP32. (You can see the same excessive complexity in how HD keys are currently defined for the compiler.) Most notably, developers often don't understand that a path like In the BIP32 spec, this is clarified by wrapping the "neutered" node in This all to say: I think these baked-in "gotchas" and inconsistencies might warrant defining a more consistent dialect of the BIP32 path syntax. Some goals:
It might also be useful to choose a different prefix character (other than One proposal, example:
"Absolute" paths – paths which define the path to a public or private key from a "level 0" node – should be prefixed with "Relative" paths would be prefixed with Instead of codifying the "neutered" terminology with the character By convention, both Because there are cases where capital characters are useful (e.g. QR code alphanumeric mode), we should also standardize an uppercase syntax, where we also replace Thoughts? With this path syntax, we'd be able to simplify |
Thinking about it more: using the
Maybe a better option is to use a colon (
This makes depth much clearer (and reduces the visual noise of extra
Note, it's still possible to indicate in the path whether the final result should be the public or private node:
(Also considered separating with |
One remaining ambiguity with using E.g. for absolute path (This sort of became a stream of consciousness – we may have to settle on the exact syntax after trying some implementations.) Option 1: always prefix with
|
Maybe the best option: don't support relative paths. YAGNI There are probably exceptionally few applications where it would be a good idea to even use a relative path. For the most part, explicitly-defined absolute paths are the best choice: they're easy to parse, extremely clear, and can offer built-in safeguards (like sanity-checking the depth of the provided node against the depth of the requested absolute path). With an absolute path, you can also easily determine whether a public or private node is needed to complete the derivation. E.g. for the path In the unusual case that the user wants a "relative path", it's probably safer for them to compute an absolute path from the relative components they expect to use, then provide that absolute path to our safer, absolute-paths-only derivation method (which will sanity-check their result and error if something unexpected happens). |
Wow, thanks for the great write-up. A few observations in response: On the necessity of relative paths
I think there are some significant use cases where it is needed. For example imagine a business owner operates a BIP44 style HDW with the following accounts:
The business owner would provide his webmaster and each store manager with a unique extended public key with depth 3. They would load those xpubs into their point-of-sale (POS) system or payment backend, which would then do a relative derivation The combination of derivation path In the BIP32 spec this use case is referred to as Unsecure money receiver. There is also a Recurrent business-to-business transactions use case which would have similar requirements for relative paths. The developer of such a POS system needs to do a relative derivation to generate the payment addresses. It seems strange to expect them to add an (arbitrary) prefix to form an absolute path, only so that the library can strip it back off again to perform the relative derivation. Some opinions on the options presentedI don't see the benefit of introducing a new prefix, especially now that I understand the meaning of I don't see the benefit of adding a positional neuter symbol such as
Having considered all of this, the scheme that now makes most sense to me is:
Unfortunately my understanding of BitAuth templates and compilers is minimal so I wasn't able to take their requirements into consideration. |
Ya, after this discussion, I think you're right – we should probably leave the API mostly as-is, and just allow support for "relative" paths without any new character scheme, e.g. For future completeness of this discussion, this BIP proposal now exists too: BIP32 Path Templates. Though I don't think Libauth needs to support it at all – it's easy to for a downstream library to create an implementation with the primitives provided by Libauth's BIP32 support. |
I'm submitting a ... question about the decisions made in the repository / feature request.
Summary
When using
deriveHdPath
to derive a node using a "relative" path, the prefixm
orM
must be included even when the source node is not a master(depth 0) node.For example if I have a source node with absolute path
m/1/2
and I want to reach a destination node with absolute pathm/1/2/3/4
, I must specify the relative path between them asm/3/4
. I think it would be more intuitive to accept the relative path as/3/4
or3/4
or./3/4
asm
should be reserved for nodes with depth 0 andm/3/4
should result in a node with depth 2.It could also accept
m/1/2/3/4
and then, observing that the starting node has depth 2 and index 2, drop the appropriate prefix from the derivation path and proceed from there.See here for an executable demo of the code above.
The text was updated successfully, but these errors were encountered: