MetacoSA / NBitcoin

Comprehensive Bitcoin library for the .NET framework.
MIT License
1.88k stars 848 forks source link

NBitcoin support "ypub" (BIP49) XPUBs? #508

Closed bazooka70 closed 6 years ago

bazooka70 commented 6 years ago

Does NBitcoin supports ypub (BIP49) XPUBs?

This is the XPUB I see in my Trezor interface on `trezor.io and I don't know what do make of it, since NBitcoin can't parse it. (I assume it is a SegWit derivation path?)

The ypub I get on Trezor.io is:

ypub6Y9f5qAWkh1afWD79VmE3os1geV2BRqM7HPiqNeYVEVWUsk9U8ci28BZ4CEd8cSNWZXJ2hGEo3PtgK1SVKrNf5px3wPDmJBQ1VV8yB121RN

What I'm trying also is ExtKey extKey = extMasterKey.Derive(new KeyPath("m/49'/0'/0'")); and I get xpub when I derive ExtPubKey from it, which does not match the ypub I see in Trezor wallet in trezor.io.

On the other hand when I query the device directly via the API, it returns an xpub. with the following data:

"xpubkey":"xpub6DKPnAVbc1U6pD1zK8ybqimWWgLaEoqrCAsW3ykf7E7dRmvvDUT9Q4XR2zH38hnT6vQVHDfgLP3Lo2PsmdSMrr9MBbgoBPMujmRVaa1pC1L", 
"chainCode": "2e998420629890e54888c52399888fabadb68c32e171496eb9a65381779e5bfc",
"publicKey": "0366ce79daba1725b279600d491a2d9bc8b82611a88ad9c95ffcd8ee46883604fd",
"path": [
    2147483697,
    2147483648,
    2147483648
  ],
  "serializedPath": "49'/0'/0'"

For legacy accounts the serialized path returned is 44'/0'/0' (BIP44)

So my basic question is, how do I know that this xpub is BIP49 or BIP44?

How can I get back the serialized path from ExtKey or ExtPubKey? e.g. the m/49'/0'/0'? or 2147483697, 2147483648, 2147483648 which I get back from Trezor api?

What I see for the ExtKey is only:

Child = 2147483648
Depth = 3
IsHardened = true

ExtKey or ExtPubKey does not hold a reference to the KeyPath.

Thanks for any help on this subject!

bazooka70 commented 6 years ago

BTW, Does is make sense to keep a reference to the KeyPath inside ExtKey/ExtPubKey in case we derive with KeyPath i.e.:

                /// <summary>
        /// Derives a new extended key in the hierarchy at the given path below the current key,
        /// by deriving the specified child at each step.
        /// </summary>
        public ExtKey Derive(KeyPath derivation)
        {
                        ExtKey result = derivation.Indexes.Aggregate(this, (current, index) => current.Derive(index));
                        result.KeyPath = derivation; // New property (default null)
            return result;
        }

If yes, I can make a PR.

NicolasDorier commented 6 years ago

Not an easy matter. ExtPubKey represent a key, not a way to derive addresses. This is why NBXplorer has the concept of DerivationScheme which is for this exact purpose.

Please read https://github.com/MetacoSA/NBitcoin/issues/456 and take my DerivatoinSchemeParser from BTCPayServer. This correctly parse it. Use DerivationScheme to derive addresses.

This has been quite messy, with multiple wallet not agreeing on the right standard. My DerivationSchemeParser class try to make the best guess.

bazooka70 commented 6 years ago

@NicolasDorier Tanks! that seem to work. I haven't tested it against my real Trezor yet (will do on sunday). My test was at (select BIP 49 tab): https://iancoleman.io/bip39/

So yes, DerivatoinSchemeParser seems to parse ypub just fine, and I get the inner xpub which matches also the derived addresses.

However I still can't seem to find a way to get back the derivation path e.g. m/49'/0'/0'. Is that even possible?

NicolasDorier commented 6 years ago

@bazooka70 so the first address generated by the ypub of Trezor is 0/0. The part m/49'/0'/0' correspond to the path of your xpub from the root key. You can discard it.

bazooka70 commented 6 years ago

@NicolasDorier , I tested DerivatoinSchemeParser with the actual ypub shown by Trezor in trezor.io and it parses ypub just fine! so thanks for that!

Trezor has the concept of Accounts. by default it creates segwit BIP49 key/xpub but you can also create legacy accounts BIP44.

I can examine the Child property to determine which Account it is, for example m/49'/0'/1' is account #1 for BTC BIP49, and m/44'/0'/1 is Account #1 for BTC BIP44 legacy etc...

So you see, I must extract the serialized path in order to determine what kind of Account it is (segwit/legacy/coin type).

Feature more, I want to create my own wallet per user based on that concept, where I have a single master key, and I derive a Key for each account, based on the coin type, segwit/legacy, account number.

Now each wallet per user has it's own Master key. and I would like to have only one master key just like Trezor.

NicolasDorier commented 6 years ago

So here is the thing:

m/44'/0'/1'/0/0 is the first address non segwit of account 1. As far as NBXplorer is concerned, you track the x/ypub of m/44'/0'/1', then NBXplorer will return the keypath of the first address as 0/0.

This mean that the code signing your UTXOs, need to know m/44'/0'/1' and the info returned by NBXplorer.

NBXplorer can't do anything else, because without the private key of m/44'/0', NBXplorer can't by itself derive m/44'/0'/2' (this is what the ' actually mean!). And no hardware wallet will ever give you the private key,

bazooka70 commented 6 years ago

So if I have a Master key (ExtKey derived from mnemonic) and I derive a new ExtKey for account 1: m/44'/0'/1' from it (and derive ExtPubKey and the addresses) I will need the root Master key to sign transactions for m/44'/0'/1'?

NicolasDorier commented 6 years ago

For signing m/44'/0'/1'/0/0 you need the private key of m/44'/0'/1'/0/0 which you can derive either from the master root private key or from the private key of m/44'/0'/1'.

From NBXplorer perspective, it does not know nothing about m/44'/0'/1', it knows only about the extpubkey you give to it, and paths 0/x, 1/x, and x.

bazooka70 commented 6 years ago

Cool. Many thanks!