Skip to content

bip32: Add support for ypubs and zpubs#279

Closed
shesek wants to merge 1 commit intorust-bitcoin:masterfrom
shesek:bip32-yzpub
Closed

bip32: Add support for ypubs and zpubs#279
shesek wants to merge 1 commit intorust-bitcoin:masterfrom
shesek:bip32-yzpub

Conversation

@shesek
Copy link
Copy Markdown
Contributor

@shesek shesek commented Jun 7, 2019

This commit adds a new ScriptType enum, with three possible values: P2pkh for xpubs, P2wpkh for zpubs and P2shP2wpkh for ypubs. It also adds a new Version type and parses it into the network and script type when deserializing extended keys from string.

I think that ExtendedPrivKey::new_master() should ideally be changed to take a Version instead of a Network, but I wasn't sure if breaking the backwards compatibility of the public API is considered acceptable. Let me know if you prefer that.

@stevenroose
Copy link
Copy Markdown
Collaborator

stevenroose commented Jun 7, 2019

Haha, I literally had an error yesterday where I tried parsing an xpub that turned out to be a ypub instead (Trezkr returns it). I asked around and they're not part of BIP-32 and are only really supported by Trezor and Electrum.

I have to say I personally don't like them. I think BIP-32 is about "key derivation" and shouldn't have anything to do with addresses. Also, script descriptors do that a lot better and make these weird subtypes obsolete, like p2wsh(xpub.../0'/4) f.e.

So I have mixed feelings because I don't like them in principle but then I could have used them yesterday for the Trezor crate. Eventually ended up creating the BIP32 ext key with the ext key info fields instead of the serialized zpub and that worked as well.

@shesek
Copy link
Copy Markdown
Contributor Author

shesek commented Jun 8, 2019

I agree that SLIP-0132 and the ypub/zpub convention are problematic in many ways, but it seems like its getting adopted nonetheless (its not just Electrum and Trezor, I believe Edge, Samurai and Eclair all do that too, to name a few -- there are probably more). It was around since before script descriptors, when people needed some solution and didn't really have any good alternatives (that I'm aware of), so we kinda got stuck with this.

For my particular case, an electrum personal server implementation in Rust that I'm working on, I need to be able to process [xyz]pubs that users get out of their electrum wallets. How would you suggest to go about doing this? Perhaps a separate crate that uses rust-bitcoin::util::bip32 internally and swaps out the versions to get it to accept it, then keep track of the real versions somewhere outside? Or just maintain a fork of the code? Both options sounds kind of messy. :-\

@shesek
Copy link
Copy Markdown
Contributor Author

shesek commented Jun 8, 2019

Perhaps rust-bitcoin::util::bip32 could simply be relaxed to accept any version bytes and just store them as-is on a field, without attempting to parse it into a network? That would make it easier to implement [yz]pub support externally.

Edit: getting the network could be a method on Version, so it would still be possible to get it with something like extkey.version.network() (which returns an Err if it doesn't recognize the version bytes).

@stevenroose
Copy link
Copy Markdown
Collaborator

In that case I would remove the script_type and network fields from the Extended*Key structs and have the Version type return them like it does now. (Btw, an Option<> is enough, doesn't have to be a Result with only a single error variant.)

That would also make it a lot easier to use this with Liquid, f.e.

@stevenroose
Copy link
Copy Markdown
Collaborator

So I'd just have Version have network() and script_type() inspectors. And leave them out of the structs.

@shesek
Copy link
Copy Markdown
Contributor Author

shesek commented Jun 8, 2019

So keep the SLIP-0132 version bytes and script types parser, but only trigger it when explicitly calling the methods on Version?

How would you prefer to handle the different bytes for public/private versions? I see a few options:

  • Have network() and script_type() methods that can parse version bytes for both public and private extended keys

  • Create separate private_{network,script_type}() and public_{network,script_type}() methods on Version (what currently exists basically, but as separate methods instead of a single method that returns both)

  • Make Version an enum with two variants, one for public and one for private. Or just two entirely separate structs? Public/private versions aren't being used interchangeably, so this doesn't really have to be an enum.

@stevenroose
Copy link
Copy Markdown
Collaborator

Yeah there's many issues. I was indeed not thinking about the issue when someone parses an ExtendedPrivKey that starts with a public prefix. We could have the methods on the ExtendedPubKey and ExtendedPrivKey and don't have the Version type but just a private [u8; 4] field internally.

@stevenroose
Copy link
Copy Markdown
Collaborator

In that case by default it's possible to parse an invalid one, though. So personally I think we should have the default parsing code error when it's an unknown prefix and have a special method to parse ignoring the prefix.

@shesek
Copy link
Copy Markdown
Contributor Author

shesek commented Jun 8, 2019

After a discussion with @stevenroose, I'm trying an alternative approach where the version prefix constraints are relaxed if the keys are initialized using a new from_str_any_prefix method, making it possible to implement something external that deals with SLIP-0132's custom [yz]pubs version bytes, without implementing it into rust-bitcoin itself (and without forking the entire bip32 code...).

An implementation of this can be seen here: master...shesek:bip32-relax-version

This has a few caveats:

  1. Converting an xpriv into an xpub requires knowing the xpub version prefix matching the xpriv version prefix. This means that from_private() now returns a Result and fails on unknown versions.

  2. Getting the identifier of an xpriv requires converting it into an xpub first. This means that identifier() and fingerprint() now return a Result and fails on unknown versions. ckd_priv() will also fail on unknown versions (but already returned a Result before). The asymmetry between the fingerprint()/identifier() methods on ExtendedPrivKey and ExtendedPubKey is pretty annoying... (but seems technically correct? you really can't get an xpriv identifier without knowing its matching xpub version)

  3. The network of the inner PrivateKey is defaulted to bitcoin mainnet if the network of the version prefix is not recognized. This only effects WIF encoding, so possibly not too terrible?

@shesek
Copy link
Copy Markdown
Contributor Author

shesek commented Jun 8, 2019

Regarding (1)/(2): another option is to have a new xpub_version field on ExtendedPrivKey, which can be explicitly set to the matching xpub version to be used for conversion. This would allow removing the dependency of having a known version prefix from the identifier(), fingerprint() and ckd_priv() methods and change them back to not returning a Result.

@stevenroose
Copy link
Copy Markdown
Collaborator

re Result/Option: I'd return Result but document the method saying it only returns an error if a non-BIP32 prefix is used, so that strict BIP-32 users can safely unwrap.

re (3):

The network of the inner PrivateKey is defaulted to bitcoin mainnet if the network of the version prefix is not recognized. This only effects WIF encoding, so possibly not too terrible?

I've always argued against a PrivateKey type that has a network field. I suppose the existence of bitcoin::PrivateKey means we shouldn't use secp256k1::SecretKey directly anymore? @apoelstra @dongcarl .. In that case, I'd say it doesn't matter that much..

shesek added a commit to bwt-dev/bwt that referenced this pull request May 13, 2020
So that we can use the mainline rust-bitcoin without rust-bitcoin/rust-bitcoin#279.

For now, only support regular xpubs with p2pkh. Support for
p2wpkh/p2shp2wpkh will be added back later.
Copy link
Copy Markdown
Collaborator

@dr-orlovsky dr-orlovsky left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I found this PR while was working on PSBT implementation for the new key/values from BIP-174. It has a global xpub key, which must be serialized according to BIP32 binary serialization; however it is not being implemented for ExtendedPubkey in rust-bitcoin yet. Why? B/c it requires implementation of this version byte serialization and deserialization.

My proposal is the following: we need a single BIP32 key version type, that is able (a) to serialize/deserialize with consensus binary encoding, (b) to a human-readable form, (c) produce Network value and (d) be compatible with miniscript descriptors (i.e. it must return whether BIP 32 key may be applied for a given miniscript descriptor).

Probably version may be a trait, with two concrete types for public and private key.


/// Script type for addresses
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
pub enum ScriptType {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think ScriptType is a very confusing name. First, it's scriptPubkey type, second, it may mix with other cases where script type name may be used in the future. So I propose to rename it to something like UsageScenario

Comment on lines +53 to +57
P2pkh,
/// Pay to witness pubkey hash
P2wpkh,
/// Pay to witness pubkey hash, wrapped in pay to script hash
P2shP2wpkh,
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, quite confusing names. May be Legacy, SegWitV0, and SegWitLegacy will work better?

@dr-orlovsky
Copy link
Copy Markdown
Collaborator

dr-orlovsky commented Sep 8, 2020

I am working on draft PR which will propose the following API, which seem to cover all discussed cases:

#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub struct KeyVersion([u8; 4]);

#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub enum KeyApplications {
    /// xprv/xpub: keys that can be used for P2PKH and multisig P2SH scriptPubkey
    /// descriptors.
    Legacy,
    /// zprv/zpub: keys that can be used for P2WPKH scriptPubkey descriptors
    SegWitV0Singlesig,
    /// yprv/ypub: keys that can be used for P2WPKH-in-P2SH scriptPubkey descriptors
    SegWitLegacySinglesig,
    /// Zprv/Zpub: keys that can be used for multisig P2WSH scriptPubkey descriptors
    SegWitV0Miltisig,
    /// Yprv/Ypub: keys that can be used for multisig P2WSH-in-P2SH scriptPubkey descriptors
    SegWitLegacyMultisig,
}

pub trait VersionResolver {
    type Network;
    type Applications;

    fn resolve(network: Self::Network, applicable_for: Self::Applications) -> Option<KeyVersion>;
    
    fn network(_: &KeyVersion) -> Option<Self::Network>;
    fn applications(_: &KeyVersion) -> Option<Self::Applications>;
    fn derivation_path(_: &KeyVersion) -> Option<DerivationPath>;
}

impl KeyVersion {
    fn network<R: VersionResolver>(&self) -> Option<R::Network> { R::network(&self) }
    fn applications<R: VersionResolver>(&self) -> Option<R::Applications> { R::applications(&self) }
    fn derivation_path<R: VersionResolver>(&self) -> Option<DerivationPath> { R::derivation_path(&self) }
}

pub struct DefaultResolver;

impl VersionResolver for DefaultResolver {
    type Network = Network;
    type Applications = KeyApplications;

    fn resolve(network: Self::Network, applicable_for: Self::Applications) -> Option<KeyVersion> {
        match (network, applicable_for) {
            (Network::Bitcoin, KeyApplications::Legacy) => Some(KeyVersion(VERSION_MAGIC_XPUB)),
            // ....
            _ => None
        }
    }
    // ....
}

/// Extended private key
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub struct ExtendedPrivKey {
    /// Version bytes specifying to which network the key belongs
    /// and for which types of scriptPubkey it may be used
    pub version: KeyVersion,
    /// How many derivations this key is from the master (which is 0)
    pub depth: u8,
    /// Fingerprint of the parent key (0 for master)
    pub parent_fingerprint: Fingerprint,
    /// Child number of the key used to derive from parent (0 for master)
    pub child_number: ChildNumber,
    /// Private key
    pub private_key: PrivateKey,
    /// Chain code
    pub chain_code: ChainCode
}

Downstream crates will be able to provide their concrete implementations for VersionResolver supporting other networks and cases and still use them with extended keys from bitcoin crate.

@dr-orlovsky
Copy link
Copy Markdown
Collaborator

Have completed my variant of implementation: https://github.com/rust-bitcoin/rust-bitcoin/pull/469/files

@apoelstra
Copy link
Copy Markdown
Member

concept NACK. If descriptors aren't in rust-bitcoin then ypubs/zpubs definitely shouldn't be.

@RCasatta
Copy link
Copy Markdown
Collaborator

RCasatta commented Nov 23, 2020

Made a separate, simple, stand-alone tool for electrum [?]pubs to descriptors if someone is interested https://github.com/RCasatta/electrum2descriptors

@dr-orlovsky
Copy link
Copy Markdown
Collaborator

Agree with concept nACK and propose to close this PR as I did with the alternative #469

@sgeisler
Copy link
Copy Markdown
Contributor

The concept NACK from #469 applies here too, there's also https://crates.io/crates/slip132 now.

@sgeisler sgeisler closed this Jan 28, 2021
PastaPastaPasta pushed a commit to PastaPastaPasta/rust-dashcore that referenced this pull request Feb 2, 2026
* removed version from PersistentSyncState

* removed persist and recovery logic of teh ChainState

* dropped dead code in sync_state

* removed logic to store sync state

* sync_state.rs deleted

* ffi docs udpated
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants