Skip to content

WIP: Add descriptor key#93

Closed
sgeisler wants to merge 10 commits intorust-bitcoin:masterfrom
sgeisler:2020-06-descriptor-key
Closed

WIP: Add descriptor key#93
sgeisler wants to merge 10 commits intorust-bitcoin:masterfrom
sgeisler:2020-06-descriptor-key

Conversation

@sgeisler
Copy link
Copy Markdown
Contributor

@sgeisler sgeisler commented Jun 4, 2020

Keys in script descriptors can come in a wide variety of formats:

  • simple public or private keys
  • xpub, xpriv

all with or without origin information.

The xpub/xpriv variety can also allow further derivations by ending its derivation path with *. This PR allows all of theses keys to be represented and Descriptors to be derived. Deriving a descriptor leaves all keys as they are except wildcard keys. They are derived using a supplied path resulting in a Descriptor without wildcard keys/with the respective derived keys.

For example to derive the first 5 addresses of a 2-of-3 multisig wallet with one static key (for some reason) and two derived keys the resulting code would look like this:

et policy = "thresh(2,\
pk(xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/1/*),\
pk(xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/2/*),\
pk(03f28773c2d975288bc7d1d205c3748651b075fbc6610e58cddeeddf8f19405aa8))";

let descriptor = Descriptor::Wsh(
    policy
        .parse::<Concrete<DescriptorKey>>()
        .unwrap()
        .compile()
        .unwrap(),
);

let base_deriv = DerivationPath::from(Vec::<ChildNumber>::new());

for child in base_deriv.normal_children().take(5) {
    println!(
        "child {}: {}",
        child,
        descriptor
            .derive(child.as_ref())
            .address(Network::Regtest)
            .unwrap()
    );
}

This would generate

child m/0: bcrt1qyaqn5jangsa8jt92n6evj7fzhwkttsh7prn6vaswhhekmr78a8vqedj0uw
child m/1: bcrt1q2m3lqldudthalkhukt595zv79awvdy2mmvkrnlck030fhccpyd5sqpt0cq
child m/2: bcrt1qjulpj2f0cfftvc3ae4jcfdrdfzvh58tf9vahj4uaqwgl2sfuc49ql0wc9h
child m/3: bcrt1qsvlq87een4pk76jq394j4tuc6058j93zy2nnjqj9m84am82ksw0qc5tjf7
child m/4: bcrt1qnz7346r9u7utq4srct06hrsqkcgy6paglx3qdcy9p8upzl30xt8qsex053

As in the title, this is still heavily WIP, I mainly want to get concept feedback.


#[derive(Debug, Eq, PartialEq, Clone, Ord, PartialOrd, Hash)]
pub struct DescriptorXPub {
source: Option<([u8; 4], DerivationPath)>,
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

All key types seem to allow a source description. Move this to a DescriptorKey struct.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Might be preferable to use use bitcoin::util::bip32::Fingerprint instead of [u8; 4]

source: Option<([u8; 4], DerivationPath)>,
xpub: bitcoin::util::bip32::ExtendedPubKey,
derivation_path: DerivationPath,
is_wildcard: bool,
Copy link
Copy Markdown
Contributor

@afilini afilini Jun 30, 2020

Choose a reason for hiding this comment

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

Do you think it would make sense to also support a "hardened wildcard"? Something like /*'.

Currently the parser in magical-bitcoin-wallet supports it, even tough I don't really see many use-cases that could use it.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

In principle yes. I just didn't implement any private key types so far, so this didn't make any sense (no hardened derivations are currently supported).

I'm still not sure if we should even support anything priv-key related in rust-miniscript. The only use case would be recovering old funds automatically by searching a lot of possible (private) paths. But if you view miniscript more as a exchange format for policies/scripts private keys in it become somewhat hazardous. xpubs should be still seen as confidential, but to a way lesser degree than actual xprivs.

One use case I can imagine is to setup a new multisig wallet client for one of your cosigners you just copy the descriptor of your multisig wallet over. If it accidentally contains your xpriv instead of your xpub that would be really, really bad.

Maybe I'm just to paranoid. Maybe two different parses would help, one returns an error if the descriptor contains an xpriv.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Well if you are trying to build a wallet with rust-miniscript and not just a "monitor" then yeah I think it's pretty useful to have private keys in a descriptor, just like Core does.

But yeah, I agree with you that it should be an "explicit" parser/type to avoid the issues you described.

One thing that I have in Magical which I could probably upstream if this PR is merged is a way to transform a descriptor into its "public" version. It's very useful if you have a descriptor with some private keys inside and you just want to make a watch-only out of it, so instead of manually converting the keys you can just use that method.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I think core compatibility as a good reason to do it. But I'd still argue that it's better to have private keys separate from the descriptor. They are used in two completely different steps of tx creation and more often than not there might not even be a private key on the device the wallet is running on.

That's how I imagine wallet/miniscript/PSBT interaction:

wallet_flow

Imo it's not a big advantage to have a config only consisting of a descriptor with private keys instead of a descriptor and a separate set of private keys.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I thought about adding private key support again and I have to be damn careful when implementing Ord (required by the MiniscriptKey trait) as it could easily lead to side channels that leak the key. So I'll probably implement inefficiently by first computing the public key (should be constant time in libsecp) and then using it for comparisons.

@apoelstra / @sanket1729 do you have any opinions on implementing MiniscriptKey for private key types?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

cc @sipa

@sgeisler the way that Core works when parsing a descriptor with private keys is that it outputs a descriptor containing public keys, along with a map from pubkeys to privkeys. I think we should do something analogous, and have our map use the Satisfier trait to allow users to produce signatures etc with it.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

...so I think we (a) should not impl Ord on any private key types; (b) not directly support private keys in descriptors. Instead we should write a function that parses a privkey-containing descriptor, converts all the privkeys to pubkeys, and outputs a mapping. I can offer guidance on how to do this.

@sgeisler sgeisler force-pushed the 2020-06-descriptor-key branch 2 times, most recently from 48a3512 to 50d0183 Compare July 18, 2020 21:50
@darosior
Copy link
Copy Markdown
Contributor

darosior commented Sep 3, 2020

Hi @sgeisler, what's the status of this PR ? I'd happily help you polish it if this is not a priority for you right now :-)

@sgeisler
Copy link
Copy Markdown
Contributor Author

sgeisler commented Sep 3, 2020

Yeah, I'd actually like to get this moving again too to not rely on my custom patched version of rust-miniscript anymore. @afilini took over in #116 so I haven't been paying that much attention to this PR lately. I'll take a closer look again this evening/tomorrow.

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.

5 participants