From fbbf80819608303d2d89e4873f865e1406b212cc Mon Sep 17 00:00:00 2001 From: kewde Date: Wed, 18 Dec 2024 17:19:11 +0100 Subject: [PATCH 1/8] feat: initial draft of unspendable key path for multisig --- bip-xxxx.mediawiki | 62 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 bip-xxxx.mediawiki diff --git a/bip-xxxx.mediawiki b/bip-xxxx.mediawiki new file mode 100644 index 0000000000..1b07528c01 --- /dev/null +++ b/bip-xxxx.mediawiki @@ -0,0 +1,62 @@ +
+  BIP: ?
+  Layer: Applications
+  Title: unspendable() Descriptor Key Expression
+  Author: Andrew Toth 
+          Kewde 
+  Comments-Summary: No comments yet.
+  Comments-URI: https://github.com/bitcoin/bips/wiki/Comments:BIP-?
+  Status: Draft
+  Type: Standards Track
+  Created: 2024-12-12
+  License: BSD-2-Clause
+
+ +==Abstract== +This document specifies a `unspendable()` key expression for output script descriptors. The `unspendable()` expression takes multiple public keys as input and produces an unspendable public key that can be independently verified by anyone with knowledge of all the constituent public keys. + +==Copyright== + +This BIP is licensed under the BSD 2-clause license. + +==Motivation== + +This document introduces a mechanism to compute a NUMS (Nothing Up My Sleeve) point for use in the Taproot key path that: +* Allows active participants involved in constructing the output script to independently verify the unspendable key. +* Prevents passive observers from recognizing that the key path is unspendable. +* Enables signers with limited information, such as hardware wallets, to verify unspendability without requiring user interaction. + +==Specification== + +A new key expression is defined: unspendable(). + +===unspendable(KEY, KEY, ..., KEY)=== + +The vector of keys is processed in the following sequence: deduplication, compression, sorting, concatenation, and finally, SHA256 hashing to generate a chaincode `r`. +A new unspendable key is constructed by taking the NUMS point `H`, suggested in BIP-0341, and attaching the chaincode `r` we previously computed. +`H + r*G` where `H = lift_x(0x50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0)` + +Q: should we add a prefix like the "BIPXXXX" before we SHA256 the keys to ensure the preimage was not previously leaked before this BIP? +Q: is it desireable to specify the keys in the expressions or to extract it from the script expression indirectly? +Q: compress first and then sort, or sort and then compress? + +==Test Vectors== + +TBD Andew + +==Rationale== + +This proposal ensures: +* Compatibility with existing Taproot functionality by leveraging NUMS points. +* Verifiability of unspendable constructions by participants, without exposing this property to outside observers. +* Security and simplicity for signers with limited information (e.g., hardware wallets). + + +==Reference Implementation== + +TBD + +==Acknowledgements== + +Thanks to Salvatore Ingala, Pieter Wuille, Antoine Poinsot, Andrew Kozlik and all others who +participated in discussions on this topic. \ No newline at end of file From 8201dad92ca02ed1c6dec5e54c49a85b38a8a02c Mon Sep 17 00:00:00 2001 From: kewde Date: Wed, 18 Dec 2024 17:26:28 +0100 Subject: [PATCH 2/8] fix: code formatting --- bip-xxxx.mediawiki | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/bip-xxxx.mediawiki b/bip-xxxx.mediawiki index 1b07528c01..896d2914ef 100644 --- a/bip-xxxx.mediawiki +++ b/bip-xxxx.mediawiki @@ -13,7 +13,7 @@ ==Abstract== -This document specifies a `unspendable()` key expression for output script descriptors. The `unspendable()` expression takes multiple public keys as input and produces an unspendable public key that can be independently verified by anyone with knowledge of all the constituent public keys. +This document specifies a unspendable() key expression for output script descriptors. The unspendable() expression takes multiple public keys as input and produces an unspendable public key that can be independently verified by anyone with knowledge of all the constituent public keys. ==Copyright== @@ -32,9 +32,9 @@ A new key expression is defined: unspendable(). ===unspendable(KEY, KEY, ..., KEY)=== -The vector of keys is processed in the following sequence: deduplication, compression, sorting, concatenation, and finally, SHA256 hashing to generate a chaincode `r`. -A new unspendable key is constructed by taking the NUMS point `H`, suggested in BIP-0341, and attaching the chaincode `r` we previously computed. -`H + r*G` where `H = lift_x(0x50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0)` +The vector of keys is processed in the following sequence: deduplication, compression, sorting, concatenation, and finally, SHA256 hashing to generate a chaincode r. +A new unspendable key is constructed by taking the NUMS point H, suggested in BIP-0341, and attaching the chaincode r we previously computed. +H + r*G where H = lift_x(0x50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0) Q: should we add a prefix like the "BIPXXXX" before we SHA256 the keys to ensure the preimage was not previously leaked before this BIP? Q: is it desireable to specify the keys in the expressions or to extract it from the script expression indirectly? From 4fd1272375217b309c4a0676d17127066c9ed7c2 Mon Sep 17 00:00:00 2001 From: Andrew Toth Date: Tue, 14 Jan 2025 12:36:32 -0500 Subject: [PATCH 3/8] Update specification and add test vectors --- bip-xxxx.mediawiki | 63 +++++++++++++++++++++++++++++++++++----------- 1 file changed, 48 insertions(+), 15 deletions(-) diff --git a/bip-xxxx.mediawiki b/bip-xxxx.mediawiki index 896d2914ef..19bf67a7c8 100644 --- a/bip-xxxx.mediawiki +++ b/bip-xxxx.mediawiki @@ -28,21 +28,59 @@ This document introduces a mechanism to compute a NUMS (Nothing Up My Sleeve) po ==Specification== -A new key expression is defined: unspendable(). +A new key expression is defined: unspendable()/NUM/.../*. -===unspendable(KEY, KEY, ..., KEY)=== +===unspendable()/NUM/.../*=== -The vector of keys is processed in the following sequence: deduplication, compression, sorting, concatenation, and finally, SHA256 hashing to generate a chaincode r. -A new unspendable key is constructed by taking the NUMS point H, suggested in BIP-0341, and attaching the chaincode r we previously computed. -H + r*G where H = lift_x(0x50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0) +The unspendable expression can only be used as the first argument of a BIP386 tr(KEY, TREE) expression. All other KEY expressions in the descriptor must be xpub encoded extended public keys with exactly 2 unhardened derivation steps. The derivation steps may include /* or a BIP389 multipath expression, but still must have only unhardened steps. -Q: should we add a prefix like the "BIPXXXX" before we SHA256 the keys to ensure the preimage was not previously leaked before this BIP? -Q: is it desireable to specify the keys in the expressions or to extract it from the script expression indirectly? -Q: compress first and then sort, or sort and then compress? +The unspendable expression resolves to an extended public key, which is then further derived. As there is no aggregate private key for an unspendable key, only unhardened derivation is allowed. + +The extended public key is computed by first collecting the public key from all the extended public keys in all the KEY expressions. The collection of public keys then has all duplicates removed and the remaining public keys are sorted lexicographically. +The vector of keys is processed in the following sequence: deduplication, compression, sorting, concatenation, and finally, SHA256 hashing to generate a chaincode c. +The unspendable BIP32 extended key is constructed by using the NUMS point H, suggested in BIP341, as the public key, and the chaincode c is computed as follows: +* The public keys are collected from all extended public keys in all KEY expressions. +* All duplicate public keys are removed from the collection and the public keys are sorted lexicographically. +* Let ''P0 ... Pn'' be the sorted and deduplicated public keys. Using the notation from BIP340, ''c = hashBIP0???/chaincode(bytes(P0) || ... || bytes(Pn))''. ==Test Vectors== -TBD Andew +Valid descriptors containing the unspendable expression followed by the chaincode of the unspendable extended public key they expand to and then the scripts they produce. +Todo: These will be filled in when the BIP number is assigned for the tagged hash. + +The following produce identical extended public keys and scripts: +* tr(unspendable()/0, pk(xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/0/0)) +* tr(unspendable()/0, pk(xpub661MyMwAqRbcFXsZHGwUFzya6zhjaLUoKt2jKZTsWEoHAPjUERUbW215Fy6DGNLZdNDyMo8WJLgouGNRypxvDFc3MgW8TvRJdpbzsxuyfvr/0/0)) +* tr(unspendable()/0, pk(xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/<0;1>/*)) + +The following has two identical public keys which are deduplicated: +* tr(unspendable()/0, {pk(xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/0/0),pk(xpub661MyMwAqRbcFXsZHGwUFzya6zhjaLUoKt2jKZTsWEoHAPjUERUbW215Fy6DGNLZdNDyMo8WJLgouGNRypxvDFc3MgW8TvRJdpbzsxuyfvr/0/0)}) + +The following has two identical public keys which are deduplicated, and then the remaining two public keys are sorted:xpub68Gmy5EdvgibQVfPdqkBBCHxA5htiqg55crXYuXoQRKfDBFA1WEjWgP6LHhwBZeNK1VTsfTFUHCdrfp1bgwQ9xv5ski8PX9rL2dZXvgGDnw +* tr(unspendable()/0, {pk(xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/0/0),{pk(xpub68Gmy5EdvgibQVfPdqkBBCHxA5htiqg55crXYuXoQRKfDBFA1WEjWgP6LHhwBZeNK1VTsfTFUHCdrfp1bgwQ9xv5ski8PX9rL2dZXvgGDnw),pk(xpub661MyMwAqRbcFXsZHGwUFzya6zhjaLUoKt2jKZTsWEoHAPjUERUbW215Fy6DGNLZdNDyMo8WJLgouGNRypxvDFc3MgW8TvRJdpbzsxuyfvr/0/0)}}) + +Invalid descriptors: + +No TREE expression: +* tr(unspendable()/0) + +No derivation path for the unspendable key: +* tr(unspendable(), pk(xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/0/0)) + +Hardened derivation path for the unspendable key: +* tr(unspendable()/0'/0, pk(xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/0/0)) + +Hardened derivation path in a KEY expression: +* tr(unspendable()/0, pk(xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/0'/0)) + +Greater than two derivation paths in a KEY expression: +* tr(unspendable()/0, pk(xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/0/0/0)) + +Less than two derivation paths in a KEY expression: +* tr(unspendable()/0, pk(xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/0)) + +A KEY expression is not an xpub: +* tr(unspendable()/0, pk(0260b2003c386519fc9eadf2b5cf124dd8eea4c4e68d5e154050a9346ea98ce600)) ==Rationale== @@ -51,12 +89,7 @@ This proposal ensures: * Verifiability of unspendable constructions by participants, without exposing this property to outside observers. * Security and simplicity for signers with limited information (e.g., hardware wallets). - -==Reference Implementation== - -TBD - ==Acknowledgements== Thanks to Salvatore Ingala, Pieter Wuille, Antoine Poinsot, Andrew Kozlik and all others who -participated in discussions on this topic. \ No newline at end of file +participated in discussions on this topic. From 3ef6f88e330177a73f9a6742de6e28b28a64a475 Mon Sep 17 00:00:00 2001 From: Andrew Toth Date: Tue, 14 Jan 2025 13:02:03 -0500 Subject: [PATCH 4/8] Forbid musig expressions --- bip-xxxx.mediawiki | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bip-xxxx.mediawiki b/bip-xxxx.mediawiki index 19bf67a7c8..4bd36d760b 100644 --- a/bip-xxxx.mediawiki +++ b/bip-xxxx.mediawiki @@ -32,7 +32,7 @@ A new key expression is defined: unspendable()/NUM/.../*. ===unspendable()/NUM/.../*=== -The unspendable expression can only be used as the first argument of a BIP386 tr(KEY, TREE) expression. All other KEY expressions in the descriptor must be xpub encoded extended public keys with exactly 2 unhardened derivation steps. The derivation steps may include /* or a BIP389 multipath expression, but still must have only unhardened steps. +The unspendable expression can only be used as the first argument of a BIP386 tr(KEY, TREE) expression. All other KEY expressions in the descriptor must be xpub encoded extended public keys with exactly 2 unhardened derivation steps. The derivation steps may include /* or a BIP389 multipath expression, but still must have only unhardened steps. Additionally, BIP390 musig expressions are forbidden. The unspendable expression resolves to an extended public key, which is then further derived. As there is no aggregate private key for an unspendable key, only unhardened derivation is allowed. From 7a3704836d6fed226d0b1e4edec10fd238d2fd58 Mon Sep 17 00:00:00 2001 From: Andrew Toth Date: Tue, 14 Jan 2025 14:59:56 -0500 Subject: [PATCH 5/8] Add backwards compatibility section --- bip-xxxx.mediawiki | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/bip-xxxx.mediawiki b/bip-xxxx.mediawiki index 4bd36d760b..e32e6b10b1 100644 --- a/bip-xxxx.mediawiki +++ b/bip-xxxx.mediawiki @@ -89,6 +89,10 @@ This proposal ensures: * Verifiability of unspendable constructions by participants, without exposing this property to outside observers. * Security and simplicity for signers with limited information (e.g., hardware wallets). +==Backwards Compatibility== + +This is backwards compatible with BIP386 by computing the unspendable key as a BIP380 KEY expression and replacing the unspendable expression as the first argument of the tr() expression. + ==Acknowledgements== Thanks to Salvatore Ingala, Pieter Wuille, Antoine Poinsot, Andrew Kozlik and all others who From d375d8d2e98047d7a1ed789a5b7eeb0eff32ffec Mon Sep 17 00:00:00 2001 From: Andrew Toth Date: Fri, 17 Jan 2025 09:22:28 -0500 Subject: [PATCH 6/8] Specify which musig expression is forbidden --- bip-xxxx.mediawiki | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bip-xxxx.mediawiki b/bip-xxxx.mediawiki index e32e6b10b1..2ed45bb196 100644 --- a/bip-xxxx.mediawiki +++ b/bip-xxxx.mediawiki @@ -32,7 +32,7 @@ A new key expression is defined: unspendable()/NUM/.../*. ===unspendable()/NUM/.../*=== -The unspendable expression can only be used as the first argument of a BIP386 tr(KEY, TREE) expression. All other KEY expressions in the descriptor must be xpub encoded extended public keys with exactly 2 unhardened derivation steps. The derivation steps may include /* or a BIP389 multipath expression, but still must have only unhardened steps. Additionally, BIP390 musig expressions are forbidden. +The unspendable expression can only be used as the first argument of a BIP386 tr(KEY, TREE) expression. All other KEY expressions in the descriptor must be xpub encoded extended public keys with exactly 2 unhardened derivation steps. The derivation steps may include /* or a BIP389 multipath expression, but still must have only unhardened steps. BIP390 musig(KEY, KEY, ..., KEY) expressions are allowed, but the variant with derivation after the expression musig(KEY, KEY, ..., KEY)/NUM/.../* is forbidden. The unspendable expression resolves to an extended public key, which is then further derived. As there is no aggregate private key for an unspendable key, only unhardened derivation is allowed. From a33c7035b10bfef10aeab2072f776fa8d37d45a1 Mon Sep 17 00:00:00 2001 From: Andrew Toth Date: Fri, 17 Jan 2025 09:48:27 -0500 Subject: [PATCH 7/8] Added more rationale --- bip-xxxx.mediawiki | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/bip-xxxx.mediawiki b/bip-xxxx.mediawiki index 2ed45bb196..279e314664 100644 --- a/bip-xxxx.mediawiki +++ b/bip-xxxx.mediawiki @@ -13,6 +13,7 @@ ==Abstract== + This document specifies a unspendable() key expression for output script descriptors. The unspendable() expression takes multiple public keys as input and produces an unspendable public key that can be independently verified by anyone with knowledge of all the constituent public keys. ==Copyright== @@ -21,6 +22,8 @@ This BIP is licensed under the BSD 2-clause license. ==Motivation== +When creating a multi-party Taproot transaction spending only from the script path, it is useful to be able to prove to all cosigners that they keypath is unspendable. Otherwise a malicious participant could use an internal key which they have the private key for and spend the transaction out from the rest of the participants. + This document introduces a mechanism to compute a NUMS (Nothing Up My Sleeve) point for use in the Taproot key path that: * Allows active participants involved in constructing the output script to independently verify the unspendable key. * Prevents passive observers from recognizing that the key path is unspendable. @@ -82,8 +85,15 @@ Less than two derivation paths in a KEY expression: A KEY expression is not an xpub: * tr(unspendable()/0, pk(0260b2003c386519fc9eadf2b5cf124dd8eea4c4e68d5e154050a9346ea98ce600)) +A musig expression with derivation paths is used: +* tr(unspendable()/0, musig(xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/0/0)/0/0) + ==Rationale== +The restrictions on KEY expressions is necessary to not allow multiple TREE expressions which would all produce the same merkle root to produce different internal keys. +* Using different lengths of derivation paths would allow a descriptor using a child xpub to generate a different key, while the merkle root would be identical. +* Not sorting the public keys would allow sortedmulti expressions to generate different keys depending on the order specified in the descriptor, while the merkle roots would be identical. + This proposal ensures: * Compatibility with existing Taproot functionality by leveraging NUMS points. * Verifiability of unspendable constructions by participants, without exposing this property to outside observers. @@ -93,6 +103,8 @@ This proposal ensures: This is backwards compatible with BIP386 by computing the unspendable key as a BIP380 KEY expression and replacing the unspendable expression as the first argument of the tr() expression. +This is backwards compatible with BIP388, since the public keys are deduplicated. The key information vector will contain all the necessary public keys. + ==Acknowledgements== Thanks to Salvatore Ingala, Pieter Wuille, Antoine Poinsot, Andrew Kozlik and all others who From a2e38654501e755372b7f6ac3b9487635b91ce21 Mon Sep 17 00:00:00 2001 From: Andrew Toth Date: Fri, 17 Jan 2025 10:53:10 -0500 Subject: [PATCH 8/8] Remove reference to inputs and aggregate keys --- bip-xxxx.mediawiki | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bip-xxxx.mediawiki b/bip-xxxx.mediawiki index 279e314664..c95a4faa59 100644 --- a/bip-xxxx.mediawiki +++ b/bip-xxxx.mediawiki @@ -14,7 +14,7 @@ ==Abstract== -This document specifies a unspendable() key expression for output script descriptors. The unspendable() expression takes multiple public keys as input and produces an unspendable public key that can be independently verified by anyone with knowledge of all the constituent public keys. +This document specifies a unspendable() key expression for output script descriptors. The unspendable() expression operates on the root TREE expression and produces an unspendable public key that can be independently verified by anyone with knowledge of all the constituent public keys. ==Copyright== @@ -37,7 +37,7 @@ A new key expression is defined: unspendable()/NUM/.../*. The unspendable expression can only be used as the first argument of a BIP386 tr(KEY, TREE) expression. All other KEY expressions in the descriptor must be xpub encoded extended public keys with exactly 2 unhardened derivation steps. The derivation steps may include /* or a BIP389 multipath expression, but still must have only unhardened steps. BIP390 musig(KEY, KEY, ..., KEY) expressions are allowed, but the variant with derivation after the expression musig(KEY, KEY, ..., KEY)/NUM/.../* is forbidden. -The unspendable expression resolves to an extended public key, which is then further derived. As there is no aggregate private key for an unspendable key, only unhardened derivation is allowed. +The unspendable expression resolves to an extended public key, which is then further derived. As there is no private key for an unspendable key, only unhardened derivation is allowed. The extended public key is computed by first collecting the public key from all the extended public keys in all the KEY expressions. The collection of public keys then has all duplicates removed and the remaining public keys are sorted lexicographically. The vector of keys is processed in the following sequence: deduplication, compression, sorting, concatenation, and finally, SHA256 hashing to generate a chaincode c.