Skip to content

Conversation

@apoelstra
Copy link
Member

This ELIP describes CT descriptors, as needed to attach blinding keys to existing descriptors and therefore create confidential addresses.

It does not cover:

  • Ordinary Elements descriptors, e.g. eltr; these need a separate standard preceding this one
  • Pegins or pegouts
  • Anything related to PAK keys

Dupliicate of #1 created because of Github bugs.

==Design==

===Overview===
We propose a new `ct` descriptor which wraps any other descriptor type in the form `ct(<BLINDING_KEY>, <DESCRIPTOR>)`.
Copy link
Contributor

Choose a reason for hiding this comment

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

github does not render ct(<BLINDING_KEY>, <DESCRIPTOR>) correctly,
see https://github.com/apoelstra/ELIPs/blob/2022-10--ct-descriptors/elip-ct-descriptors.mediawiki#overview

Also in the "specification" section the bullet points have few rendering issues.

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah, I see, it's because I'm using MD rather than mediawiki tags. I need to go over the entire doc.


However, this has the following drawbacks:
* The mismatch between "public" keys being used in a "secret" context may lead to user confusion; though we argue this is no worse than the "secret" chaincode value being included in xpubs used by Bitcoin wallets.
* Any party who has a copy of an addresses' descriptor is able to see the blinding key and unblind coins sent to that address, or *any* derived address in the case of a non-concrete descriptor.
Copy link
Contributor

Choose a reason for hiding this comment

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

non-concrete descriptor

What is a "concrete" descriptor?

Copy link
Member Author

Choose a reason for hiding this comment

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

One that is not derived. I should change this to "non-derived" which I think would be clearer.

Here `DESCRIPTOR` refers to any existing descriptor, e.g. `wsh(...)` or `tr(...)` and `BLINDING_KEY` is a new expression which must be one of
* `slip77(<64-character hex>)` which indicates that blinding keys for these addresses are derived via SLIP77; or
* `hash_to_private(<KEY_1>, <KEY_2>, ...)` which indicates that the blinding key is produced by a hash of the sorted list of one or more public keys; or
* `<KEY>` which is an ordinary (extended) public key which will be used as a blinding key
Copy link
Contributor

Choose a reason for hiding this comment

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

(extended) public key

What do you mean by extended here?
Does this mean we can have xpubs here?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes, it means an xpub.


In both the `hash_to_private` and `<KEY>` expressions, all keys must be compressed; x-only and uncompressed keys are invalid.

We note that even if the descriptor includes private keys, as it will when used for wallet backups, the keys included in the `hash_to_private` combinator will be converted to public keys before sorting and hashing.
Copy link
Contributor

Choose a reason for hiding this comment

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

as it will when used for wallet backups,

nit: This is a bit misleading to me,
why a wallet should include private keys there?

I'm not against allowing to have private keys in hash_to_private,
but I don't understand how setting private keys set in hash_to_private helps the wallet backup and why a wallet should do that.

Anyway this is a nit, so feel free to ignore.

Copy link
Member Author

Choose a reason for hiding this comment

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

Bitcoin Core's wallet backups consist of descriptors where every public key slot is filled by a private key. It can also import descriptors of this form, in which case it is able to sign with them.

I agree that in the case of hash_to_private there's no value in making these keys private, since they're never interpreted as private keys. But when dealing with import/export it is easiest if all the keys have the same datatype, which means that all keys, including these, would end up being private ones.

@apoelstra
Copy link
Member Author

@LeoComandini thanks for your review! I have fixed the formatting (I think/hope), and changed the "non-concrete descriptor" text to be clearer.

@apoelstra apoelstra force-pushed the 2022-10--ct-descriptors branch 3 times, most recently from dbf2055 to 24098ba Compare March 8, 2023 15:40
@apoelstra apoelstra force-pushed the 2022-10--ct-descriptors branch 4 times, most recently from ccb486e to ce30581 Compare March 29, 2023 16:12
* <code>slip77(<64-character hex>)</code> which indicates that blinding keys for these addresses are derived via SLIP77; or
* <code><KEY></code> which is an ordinary (extended) public key; the blinding key is derived by a Taproot-style tweak of the key with the data of <code><DESCRIPTOR></code>.

In both the <code><KEY></code> expression, all key(s) must be compressed; x-only and uncompressed keys are invalid.
Copy link
Contributor

Choose a reason for hiding this comment

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

nit:

both

now we have single variant with <KEY>

We propose a new <code>ct</code> descriptor which wraps any other descriptor type in the form <code>ct(<BLINDING_KEY>, <DESCRIPTOR>)</code>.
Here <code>DESCRIPTOR</code> refers to any existing descriptor, e.g. <code>elwsh(...)</code> or <code>eltr(...)</code> and <code>BLINDING_KEY</code> is a new expression which must be one of
* <code>slip77(<64-character hex>)</code> which indicates that blinding keys for these addresses are derived via SLIP77; or
* <code><KEY></code> which is an ordinary (extended) public key; the blinding key is derived by a Taproot-style tweak of the key with the data of <code><DESCRIPTOR></code>.
Copy link
Contributor

Choose a reason for hiding this comment

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

extended

Why a wallet should use a xpub/xprv as a bare blinding key?

Now that the key is tweaked with the scriptpubkey (which in most cases should be derived from some xpubs),
I can't think of a reason why a wallet should use xpub/xprv as "master" blinding key rather than a public/private key.

Does it make sense to disallow extended keys for the "bare" variant?

Copy link
Member Author

Choose a reason for hiding this comment

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

I don't think so. We definitely should support xpubs/xprivs so that they can specify things that have BIP32 paths starting from an existing master seed (otherwise they will need to do a bunch of extra work to derive a public key and then remember how they got it).

I agree that supporting ranged xpubs no longer has a clear utility ... but I see no reason to ban it either.

Copy link
Contributor

Choose a reason for hiding this comment

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

which is an ordinary (extended) public key;

This should probably read something like:

<code><KEY></code> which is an ordinary (or extended) private or public key;

An example bare key with xpub/xprv would be useful in the examples, as would a raw private rather than public key.

@apoelstra apoelstra force-pushed the 2022-10--ct-descriptors branch from 9b065a4 to 3d4d49b Compare June 11, 2023 19:08
@apoelstra
Copy link
Member Author

Rebased, fixed both nit, added test vectors.

** These 32 bytes are the same for both public and private descriptors; they have no "public" equivalent.
** This mode is not recommended because there is no way to express this descriptor without revealing the SLIP77 key, which can be used to unblind every single output received by the wallet.
* '''bare key''', which simply encodes a single key in the same way as the keys in <code><DESCRIPTOR></code> are encoded. The blinding key is derived from the address by taking the public key ''K'' and tweaking it to form a new key ''K<sub>blind</sub>'' as follows:
** ''K' = K + H<sub>tag</sub>(K, <code>scriptPubKey</code>)'', where
Copy link
Contributor

Choose a reason for hiding this comment

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

Should this be H<sub>tag</sub>(K, <code>scriptPubKey</code>) * G? or K is a private key?

Copy link
Member Author

Choose a reason for hiding this comment

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

Nope, you're right.

** Unconfidential address: <code>ert1pdp6fxh3mq3wtqdtkja2tvsudh9nxy85m67m25agayx92ztz9guvs9wr5lg</code>
* Invalid Descriptor 1
** <code>ct(slip77(b2396b3ee20509cdb64fe24180a14a72dbd671728eaa49bac69d2bdecb5f5a04),elsh(wpkh(03774eec7a3d550d18e9f89414152025b3b0ad6a342b19481f702d843cff06dfc4)))#xxxxxxxx</code>
** Reason:
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: missing reason here and below

Copy link
Member Author

Choose a reason for hiding this comment

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

oops, I was supposed to fill those in by hand :P

** Blinding key: <code>02dce16018bbbb8e36de7b394df5b5166e9adb7498be7d881a85a09aeecf76b623</code>
** Confidential address: <code>AzpsK7uqP1KVEMfDQvBXYUkpHmFagD3W4vaLe1X7uy8MS6nj41kNYnaexuXgx14PcbNnYAqBdCSWcbga</code>
** Unconfidential address: <code>XQ7ffnJkhMwj1H8Ma6N1vcU9mqAa96wB9w</code>
* Valid Descriptor 4: <code>ct(02dce16018bbbb8e36de7b394df5b5166e9adb7498be7d881a85a09aeecf76b623,eltr(03774eec7a3d550d18e9f89414152025b3b0ad6a342b19481f702d843cff06dfc4))#ytq9w7f3</code>
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we please have a valid test vector for the bare variant specifying a private key?
That should be the most practical use case, i.e. allowing unblinding

Copy link
Member Author

Choose a reason for hiding this comment

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

Sure, though I'd need to implement that :P. Give me a day or two. I think I can do it before ElementsProject/rust-elements#166 (which is blocking the reference implementation) gets in.

@apoelstra apoelstra force-pushed the 2022-10--ct-descriptors branch from 3d4d49b to 530cbf4 Compare June 30, 2023 17:40
@apoelstra
Copy link
Member Author

Sorry for the long delay. Switching to xpubs was easy but the view key support was pretty annoying because our elements-miniscript implementation doesn't directly relate secret key types to public key types, making it difficult to elegantly have "one key private, others public" for view keys. In the end I hacked it in, but as implemented it does not support e.g. ranged blinding keys.

I have updated the test vectors in the ELIP, and the reference implementation at ElementsProject/elements-miniscript#31


Using the <code>ct(slip77(<64-byte hex>), <DESCRIPTOR>)</code> construction, any wallet should be able to express its existing confidential addresses using this new scheme.

==Test Vectors==
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we add test vectors with non-extended public and private blinding keys?

The following CT descriptors should be parseable and generate the given addresses:

* Valid Descriptor 1: <code>ct(xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,elpkh(xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH))#y0lg3d5y</code>
** Blinding public key: <code>xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL</code>
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: the name here is slightly confusing, "blinding public key" is the one encoded in the confidential address, this is more like the "SLIP77 master blinding key", perhaps we should name it "master blinding public key" or "descriptor blinding public key". But any name is fine for me, as long we have different one wrt the derived (public) blinding keys

Copy link
Member Author

Choose a reason for hiding this comment

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

Oh, good point. I think I'll change this to "Descriptor blinding [public] key". Agreed it's confusing now that the key in the descriptor is different from the key in the address.

Copy link

@sanket1729 sanket1729 left a comment

Choose a reason for hiding this comment

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

ACK 530cbf4. Sorry for my confusion on the PR review.

* <code>slip77(<64-character hex>)</code> which indicates that blinding keys for these addresses are derived via SLIP77; or
* <code><KEY></code> which is an ordinary (extended) public key; the blinding key is derived by a Taproot-style tweak of the key with the data of <code><DESCRIPTOR></code>.

In the <code><KEY></code> expression, all key(s) must be compressed; x-only and uncompressed keys are invalid.
Copy link

@sanket1729 sanket1729 Jul 13, 2023

Choose a reason for hiding this comment

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

nit: It would be great if this specifically mentions KEY expression in descriptor spec. Reading the words compressed key confused me a bit.

another nit: This also mentions multi-path KEY expressions. I don't think we want to support that.

Copy link
Member Author

Choose a reason for hiding this comment

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

Where does it mention multi-path KEY expressions? I did not even know those existed when I was writing this.

Choose a reason for hiding this comment

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

Perhaps the "all key(s)" is confusing. There's just a single key.

Copy link
Member Author

Choose a reason for hiding this comment

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

It might be a musig of multiple keys.

Choose a reason for hiding this comment

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

in the <KEY> expression?

Choose a reason for hiding this comment

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

Key expressions are defined as follows. https://github.com/bitcoin/bips/blob/master/bip-0380.mediawiki#key-expressions. I think what we mean to say that all KEY expressions that are not uncompressed keys are allowed.

Choose a reason for hiding this comment

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

I'm not sure. We say <code><KEY></code> which is an ordinary (extended) public key. We don't state anywhere that this is a "key expression" as in BIP380.

If it's supposed to be a key expression as in BIP380, we should say this. If not, we should consider renaming KEY.

Copy link
Member Author

Choose a reason for hiding this comment

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

Ok, I will reword this. Yes, it is supposed to be a key expression.

Copy link
Member Author

Choose a reason for hiding this comment

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

Though it would be nice if we had our own ELIP for key expressions so that we can extend it to support musig() eventually. Though I have zero interest in doing that. This ELIP has been more than enough standards work for one lifetime.

@apoelstra
Copy link
Member Author

I apologize for not doing the test vectors yet. I will try to get them before the end of this week.

Meanwhile we should really give this a number. I propose 150. If nobody complains I will assign this and update the PR tomorrow.


===Overview===
We propose a new <code>ct</code> descriptor which wraps any other descriptor type in the form <code>ct(<BLINDING_KEY>, <DESCRIPTOR>)</code>.
Here <code>DESCRIPTOR</code> refers to any existing descriptor, e.g. <code>elwsh(...)</code> or <code>eltr(...)</code> and <code>BLINDING_KEY</code> is a new expression which must be one of
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
Here <code>DESCRIPTOR</code> refers to any existing descriptor, e.g. <code>elwsh(...)</code> or <code>eltr(...)</code> and <code>BLINDING_KEY</code> is a new expression which must be one of
Here <code>DESCRIPTOR</code> refers to any existing descriptor, e.g. <code>elwsh(...)</code> or <code>eltr(...)</code>

Is this recursive - can a ct descriptor include another ct descriptor?

Copy link
Member Author

Choose a reason for hiding this comment

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

No.

Copy link
Member

@delta1 delta1 Aug 8, 2023

Choose a reason for hiding this comment

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

Cool, might be worth specifying (edit: sorry didn't realize you already had a reference implementation)

Copy link
Member Author

Choose a reason for hiding this comment

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

How can I be more specific than "any existing descriptor"?

@apoelstra
Copy link
Member Author

Updated exposition, added new test vectors, and assigned ELIP 150.

Copy link

@sanket1729 sanket1729 left a comment

Choose a reason for hiding this comment

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

ACK 5726891 . One small missing thing is that test vectors don't mention the address was created for elements regtest network.

    pub const ELEMENTS: AddressParams = AddressParams {
        p2pkh_prefix: 235,
        p2sh_prefix: 75,
        blinded_prefix: 4,
        bech_hrp: "ert",
        blech_hrp: "el",
    };

@apoelstra
Copy link
Member Author

Thanks! Going to merge this. The test network thing is somewhat implicit by the prefix of the addresses, but we should mention it nonetheless. Can do this in a followup PR.

@apoelstra apoelstra merged commit 80759c9 into ElementsProject:main Aug 23, 2023
@apoelstra apoelstra deleted the 2022-10--ct-descriptors branch August 23, 2023 17:53
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