diff --git a/bip-0138.md b/bip-0138.md index 8c5d6cda..0801acc8 100644 --- a/bip-0138.md +++ b/bip-0138.md @@ -10,8 +10,8 @@ Discussion: https://delvingbitcoin.org/t/a-simple-backup-scheme-for-wallet-accounts/1607 https://groups.google.com/g/bitcoindev/c/5NgJbpVDgEc/m/TtGK9sF9BgAJ https://github.com/bitcoin/bips/pull/1951 - Version: 0.1.2 - Requires: 32, 340, 380, 388, 389 + Version: 0.1.3 + Requires: 32, 340, 380, 388, 389, 390 ``` ## Introduction @@ -98,7 +98,8 @@ Note: in the following sections, the operator ⊕ refers to the bitwise XOR oper ### Secret generation -- Let $p_1, p_2, \dots, p_n$, be the public keys in the descriptor/wallet policy, in +- Let $p_1, p_2, \dots, p_n$, be the distinct public keys across all descriptor + and policy sets and all content items in the payload, in increasing lexicographical order. The scheme is defined for any $n \geq 1$, in particular it supports single-signature descriptors ($n = 1$). Each $p_i$ is the x-only-normalized root public key of one *allowed* key expression @@ -113,8 +114,8 @@ hash construction from [BIP340](bip-0340.mediawiki#design). Because $s$ and $s_i$ use distinct domain-separation tags, $s \neq s_i$ and therefore $c_i$ is never the all-zero string. -Keys published on chain are already kept out of $\{p_1, \dots, p_n\}$ by the -[Descriptor key requirements](#descriptor-key-requirements). +The [Descriptor key requirements](#descriptor-key-requirements) exclude +keys exposed by literal or bare-xpub expressions in the backed-up scripts. Implementations MUST exclude the key with x coordinate `50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0` (the BIP341 NUMS point). Applications MAY exclude any further key they know to be public. @@ -138,8 +139,8 @@ accounts whose xpub was never sent to such a server, such as BIP48 or BIP87 acco ### Descriptor key requirements -Only key expressions of the extended-public-key type with a trailing -derivation step or wildcard contribute to $\{p_1, \dots, p_n\}$. Without +Outside `musig()`, only key expressions of the extended-public-key type with a +trailing derivation step or wildcard contribute to $\{p_1, \dots, p_n\}$. Without that derivation, the xpub's root pubkey, used to seed $s$, would also be the on-chain pubkey of every spend, so observing one spend would let anyone recompute $s$ and decrypt the backup.[^trailing-derivation] The @@ -150,9 +151,9 @@ rule generalizes to any network prefix (`xpub`/`tpub`/`Vpub`/...). a wildcard, breaks the identity between the xpub root pubkey and the on-chain pubkey. Fixed-derivation expressions like `xpub.../0/5` are allowed because the on-chain key (`xpub/0/5`) already differs from the - encryption seed (the xpub root). Literal pubkeys and bare xpubs are - disallowed for the same reason: the literal value is exactly what goes - on-chain, so $s$ would become recoverable from a single observed spend. + encryption seed (the xpub root). Outside `musig()`, literal pubkeys and + bare xpubs are disallowed for the same reason: the literal value is exactly + what goes on-chain, so $s$ would become recoverable from a single observed spend. Allowed forms (origin information `[...]` is optional and orthogonal): @@ -163,15 +164,27 @@ Allowed forms (origin information `[...]` is optional and orthogonal): | `//*` (fixed derivation followed by a wildcard) | `xpub.../0/*` | | `/` (multipath, no wildcard) | `xpub.../<0;1>` | | `//*` (multipath followed by a wildcard) | `xpub.../<0;1>/*` | +| `` (only as a `musig()` participant) | `xpub...` | + +Within a [BIP390](bip-0390.mediawiki) `musig()` expression, participant xpubs +are allowed with or without trailing derivation: the aggregate on-chain key +does not reveal the individual participant keys. For encryption, use each +participant's xpub root rather than the aggregate key. Literal public-key +participants remain excluded. + +A candidate root MUST be excluded if its x-coordinate matches a literal key +or a bare xpub root used outside `musig()` in any descriptor or resolved +policy in the payload. For example, +`wsh(or_i(pk(X/0/*),pk(X)))` exposes the root of xpub `X` on spending, so that +root must be excluded even though `X/0/*` is otherwise eligible. Implementations: -- MUST exclude every other form (literal pubkeys, bare xpubs, ...) from - $\{p_1, \dots, p_n\}$. +- MUST exclude every other form (literal pubkeys, bare xpubs outside `musig()`, + ...) from $\{p_1, \dots, p_n\}$. - MUST refuse to encode a backup if the resulting set is empty. -- MUST make the user aware of each excluded expression, since the - cosigner holding that key will be unable to decrypt the backup with - their key. +- MUST make the user aware of each excluded expression and candidate root, + since its holder may be unable to decrypt the backup with that key. ### Key Normalization @@ -590,8 +603,11 @@ The version field enables possible future enhancements: ### Test Vectors -[key_types.json](./bip-0138/test_vectors/keys_types.json) contains test -vectors for key serialisations. +[recipient_keys.json](./bip-0138/test_vectors/recipient_keys.json) contains +test vectors for recipient selection. `descriptors` lists the payload's descriptor +strings; `expected_keys` lists the sorted, deduplicated x-only recipient roots +before application-specific exclusions. `null` requires refusing to encode. + [content_type.json](./bip-0138/test_vectors/content_type.json) contains test vectors for content types serialisations. [payload.json](./bip-0138/test_vectors/payload.json) contains test vectors for payload parsing. @@ -633,6 +649,10 @@ their reviews and suggestions. ## Changelog +* __0.1.3__ (2026-09-24): + * Rename, generalize and expand the key-selection test vectors. + * Clarify MuSig participant eligibility and recipient selection across the payload. + * Exclude roots exposed by another key expression. * __0.1.2__ (2026-09-24): * Specify empty AEAD associated data. * Clarify that `0x00` ends payload parsing and add test vectors. diff --git a/bip-0138/test_vectors/keys_types.json b/bip-0138/test_vectors/keys_types.json deleted file mode 100644 index a2346e0b..00000000 --- a/bip-0138/test_vectors/keys_types.json +++ /dev/null @@ -1,47 +0,0 @@ -[ - { - "description": "Xpub with origin and multipath wildcard", - "key": "[58b7f8dc/48'/1'/0'/2']tpubDEPBvXvhta3pjVaKokqC3eeMQnszj9ehFaA2zD5nSdkaccwGAizu8jVB2NeSpvmP2P52MBoZvNCixqXRJnTyXx51FQzARR63tjxQSyP3Btw/<0;1>/*", - "expected": "ebd252ca0877aae09b9d058219682775aa3cbcd049c12f07832f2cf6a3b51708" - }, - { - "description": "Xpub with single-path wildcard", - "key": "[58b7f8dc/48'/1'/0'/2']tpubDEPBvXvhta3pjVaKokqC3eeMQnszj9ehFaA2zD5nSdkaccwGAizu8jVB2NeSpvmP2P52MBoZvNCixqXRJnTyXx51FQzARR63tjxQSyP3Btw/0/*", - "expected": "ebd252ca0877aae09b9d058219682775aa3cbcd049c12f07832f2cf6a3b51708" - }, - { - "description": "Xpub with bare wildcard (no preceding path)", - "key": "[58b7f8dc/48'/1'/0'/2']tpubDEPBvXvhta3pjVaKokqC3eeMQnszj9ehFaA2zD5nSdkaccwGAizu8jVB2NeSpvmP2P52MBoZvNCixqXRJnTyXx51FQzARR63tjxQSyP3Btw/*", - "expected": "ebd252ca0877aae09b9d058219682775aa3cbcd049c12f07832f2cf6a3b51708" - }, - { - "description": "Xpub with fixed deep derivation, no wildcard", - "key": "[58b7f8dc/48'/1'/0'/2']tpubDEPBvXvhta3pjVaKokqC3eeMQnszj9ehFaA2zD5nSdkaccwGAizu8jVB2NeSpvmP2P52MBoZvNCixqXRJnTyXx51FQzARR63tjxQSyP3Btw/0/5", - "expected": "ebd252ca0877aae09b9d058219682775aa3cbcd049c12f07832f2cf6a3b51708" - }, - { - "description": "Xpub with multipath, no wildcard", - "key": "[58b7f8dc/48'/1'/0'/2']tpubDEPBvXvhta3pjVaKokqC3eeMQnszj9ehFaA2zD5nSdkaccwGAizu8jVB2NeSpvmP2P52MBoZvNCixqXRJnTyXx51FQzARR63tjxQSyP3Btw/<0;1>", - "expected": "ebd252ca0877aae09b9d058219682775aa3cbcd049c12f07832f2cf6a3b51708" - }, - { - "description": "Bare xpub (no derivation, no wildcard); DISALLOWED", - "key": "[d4ab66f1/48'/1'/1'/2']tpubDFTxBKyUCgkwp5enwZh3t2FJ5AMJqmCWoh1NRT13qNYQb1iKTUrAG6u5gpsDYhG8cZGXouYWuQtzcuSVjPStTc4dwU6JqPMFtgaLGvSQXhi", - "expected": null - }, - { - "description": "Compressed literal pubkey; DISALLOWED", - "key": "02ebd252ca0877aae09b9d058219682775aa3cbcd049c12f07832f2cf6a3b51708", - "expected": null - }, - { - "description": "X-only literal pubkey; DISALLOWED", - "key": "ebd252ca0877aae09b9d058219682775aa3cbcd049c12f07832f2cf6a3b51708", - "expected": null - }, - { - "description": "Uncompressed literal pubkey; DISALLOWED", - "key": "04ebd252ca0877aae09b9d058219682775aa3cbcd049c12f07832f2cf6a3b517089e956909c4c07e8529f45f3ff8904d28df5a181619e21bdf748a896322530039", - "expected": null - } -] diff --git a/bip-0138/test_vectors/recipient_keys.json b/bip-0138/test_vectors/recipient_keys.json new file mode 100644 index 00000000..f12adb0b --- /dev/null +++ b/bip-0138/test_vectors/recipient_keys.json @@ -0,0 +1,131 @@ +[ + { + "description": "Xpub with origin and multipath wildcard", + "descriptors": [ + "tr([58b7f8dc/48'/1'/0'/2']tpubDEPBvXvhta3pjVaKokqC3eeMQnszj9ehFaA2zD5nSdkaccwGAizu8jVB2NeSpvmP2P52MBoZvNCixqXRJnTyXx51FQzARR63tjxQSyP3Btw/<0;1>/*)" + ], + "expected_keys": [ + "ebd252ca0877aae09b9d058219682775aa3cbcd049c12f07832f2cf6a3b51708" + ] + }, + { + "description": "Xpub with single-path wildcard", + "descriptors": [ + "tr([58b7f8dc/48'/1'/0'/2']tpubDEPBvXvhta3pjVaKokqC3eeMQnszj9ehFaA2zD5nSdkaccwGAizu8jVB2NeSpvmP2P52MBoZvNCixqXRJnTyXx51FQzARR63tjxQSyP3Btw/0/*)" + ], + "expected_keys": [ + "ebd252ca0877aae09b9d058219682775aa3cbcd049c12f07832f2cf6a3b51708" + ] + }, + { + "description": "Xpub with bare wildcard (no preceding path)", + "descriptors": [ + "tr([58b7f8dc/48'/1'/0'/2']tpubDEPBvXvhta3pjVaKokqC3eeMQnszj9ehFaA2zD5nSdkaccwGAizu8jVB2NeSpvmP2P52MBoZvNCixqXRJnTyXx51FQzARR63tjxQSyP3Btw/*)" + ], + "expected_keys": [ + "ebd252ca0877aae09b9d058219682775aa3cbcd049c12f07832f2cf6a3b51708" + ] + }, + { + "description": "Xpub with fixed deep derivation, no wildcard", + "descriptors": [ + "tr([58b7f8dc/48'/1'/0'/2']tpubDEPBvXvhta3pjVaKokqC3eeMQnszj9ehFaA2zD5nSdkaccwGAizu8jVB2NeSpvmP2P52MBoZvNCixqXRJnTyXx51FQzARR63tjxQSyP3Btw/0/5)" + ], + "expected_keys": [ + "ebd252ca0877aae09b9d058219682775aa3cbcd049c12f07832f2cf6a3b51708" + ] + }, + { + "description": "Xpub with multipath, no wildcard", + "descriptors": [ + "tr([58b7f8dc/48'/1'/0'/2']tpubDEPBvXvhta3pjVaKokqC3eeMQnszj9ehFaA2zD5nSdkaccwGAizu8jVB2NeSpvmP2P52MBoZvNCixqXRJnTyXx51FQzARR63tjxQSyP3Btw/<0;1>)" + ], + "expected_keys": [ + "ebd252ca0877aae09b9d058219682775aa3cbcd049c12f07832f2cf6a3b51708" + ] + }, + { + "description": "Bare xpub (no derivation, no wildcard); DISALLOWED", + "descriptors": [ + "tr([d4ab66f1/48'/1'/1'/2']tpubDFTxBKyUCgkwp5enwZh3t2FJ5AMJqmCWoh1NRT13qNYQb1iKTUrAG6u5gpsDYhG8cZGXouYWuQtzcuSVjPStTc4dwU6JqPMFtgaLGvSQXhi)" + ], + "expected_keys": null + }, + { + "description": "Compressed literal pubkey; DISALLOWED", + "descriptors": [ + "tr(02ebd252ca0877aae09b9d058219682775aa3cbcd049c12f07832f2cf6a3b51708)" + ], + "expected_keys": null + }, + { + "description": "X-only literal pubkey; DISALLOWED", + "descriptors": [ + "tr(ebd252ca0877aae09b9d058219682775aa3cbcd049c12f07832f2cf6a3b51708)" + ], + "expected_keys": null + }, + { + "description": "Uncompressed literal pubkey; DISALLOWED", + "descriptors": [ + "pk(04ebd252ca0877aae09b9d058219682775aa3cbcd049c12f07832f2cf6a3b517089e956909c4c07e8529f45f3ff8904d28df5a181619e21bdf748a896322530039)" + ], + "expected_keys": null + }, + { + "description": "Bare MuSig participant xpubs", + "descriptors": [ + "tr(musig(tpubDEPBvXvhta3pjVaKokqC3eeMQnszj9ehFaA2zD5nSdkaccwGAizu8jVB2NeSpvmP2P52MBoZvNCixqXRJnTyXx51FQzARR63tjxQSyP3Btw,tpubDC5FSnBiZDMmhiuCmWAYsLwgLYrrT9rAqvTySfuCCrgsWz8wxMXUS9Tb9iVMvcRbvFcAHGkMD5Kx8koh4GquNGNTfohfk7pgjhaPCdXpoba))" + ], + "expected_keys": [ + "8b42cd4776376c82791b494155151f56c2d7b471e0c7a526a7ce60dd872e3867", + "ebd252ca0877aae09b9d058219682775aa3cbcd049c12f07832f2cf6a3b51708" + ] + }, + { + "description": "Derivation after MuSig aggregation", + "descriptors": [ + "tr(musig(tpubDEPBvXvhta3pjVaKokqC3eeMQnszj9ehFaA2zD5nSdkaccwGAizu8jVB2NeSpvmP2P52MBoZvNCixqXRJnTyXx51FQzARR63tjxQSyP3Btw,tpubDC5FSnBiZDMmhiuCmWAYsLwgLYrrT9rAqvTySfuCCrgsWz8wxMXUS9Tb9iVMvcRbvFcAHGkMD5Kx8koh4GquNGNTfohfk7pgjhaPCdXpoba)/<0;1>/*)" + ], + "expected_keys": [ + "8b42cd4776376c82791b494155151f56c2d7b471e0c7a526a7ce60dd872e3867", + "ebd252ca0877aae09b9d058219682775aa3cbcd049c12f07832f2cf6a3b51708" + ] + }, + { + "description": "Same root in several descriptor sets contributes once", + "descriptors": [ + "tr(tpubDEPBvXvhta3pjVaKokqC3eeMQnszj9ehFaA2zD5nSdkaccwGAizu8jVB2NeSpvmP2P52MBoZvNCixqXRJnTyXx51FQzARR63tjxQSyP3Btw/0/*)", + "tr(tpubDEPBvXvhta3pjVaKokqC3eeMQnszj9ehFaA2zD5nSdkaccwGAizu8jVB2NeSpvmP2P52MBoZvNCixqXRJnTyXx51FQzARR63tjxQSyP3Btw/1/*)", + "tr(tpubDC5FSnBiZDMmhiuCmWAYsLwgLYrrT9rAqvTySfuCCrgsWz8wxMXUS9Tb9iVMvcRbvFcAHGkMD5Kx8koh4GquNGNTfohfk7pgjhaPCdXpoba/*)" + ], + "expected_keys": [ + "8b42cd4776376c82791b494155151f56c2d7b471e0c7a526a7ce60dd872e3867", + "ebd252ca0877aae09b9d058219682775aa3cbcd049c12f07832f2cf6a3b51708" + ] + }, + { + "description": "Root used both bare and derived is excluded", + "descriptors": [ + "wsh(or_i(pk(tpubDEPBvXvhta3pjVaKokqC3eeMQnszj9ehFaA2zD5nSdkaccwGAizu8jVB2NeSpvmP2P52MBoZvNCixqXRJnTyXx51FQzARR63tjxQSyP3Btw/0/*),pk(tpubDEPBvXvhta3pjVaKokqC3eeMQnszj9ehFaA2zD5nSdkaccwGAizu8jVB2NeSpvmP2P52MBoZvNCixqXRJnTyXx51FQzARR63tjxQSyP3Btw)))" + ], + "expected_keys": null + }, + { + "description": "Xpub root (03ebd252ca0877aae09b9d058219682775aa3cbcd049c12f07832f2cf6a3b51708) excluded by an opposite-parity literal in another descriptor set", + "descriptors": [ + "tr(tpubDEPBvXvhta3pjVaKokqC3eeMQnszj9ehFaA2zD5nSdkaccwGAizu8jVB2NeSpvmP2P52MBoZvNCixqXRJnTyXx51FQzARR63tjxQSyP3Btw/*)", + "wpkh(02ebd252ca0877aae09b9d058219682775aa3cbcd049c12f07832f2cf6a3b51708)" + ], + "expected_keys": null + }, + { + "description": "MuSig participant exposed in a script leaf", + "descriptors": [ + "tr(musig(tpubDC5FSnBiZDMmhiuCmWAYsLwgLYrrT9rAqvTySfuCCrgsWz8wxMXUS9Tb9iVMvcRbvFcAHGkMD5Kx8koh4GquNGNTfohfk7pgjhaPCdXpoba,tpubDEPBvXvhta3pjVaKokqC3eeMQnszj9ehFaA2zD5nSdkaccwGAizu8jVB2NeSpvmP2P52MBoZvNCixqXRJnTyXx51FQzARR63tjxQSyP3Btw)/0/*,pk(tpubDC5FSnBiZDMmhiuCmWAYsLwgLYrrT9rAqvTySfuCCrgsWz8wxMXUS9Tb9iVMvcRbvFcAHGkMD5Kx8koh4GquNGNTfohfk7pgjhaPCdXpoba))" + ], + "expected_keys": [ + "ebd252ca0877aae09b9d058219682775aa3cbcd049c12f07832f2cf6a3b51708" + ] + } +]