mirror of
https://github.com/bitcoin/bips.git
synced 2026-10-19 19:29:18 +00:00
bip138: address review
This commit is contained in:
100
bip-0138.md
100
bip-0138.md
@@ -9,6 +9,7 @@
|
||||
License: BSD-2-Clause
|
||||
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
|
||||
```
|
||||
|
||||
## Introduction
|
||||
@@ -16,7 +17,7 @@
|
||||
### Abstract
|
||||
|
||||
This BIP defines a compact encryption scheme for **output script descriptors** (BIP-0380),
|
||||
**wallet policies** (BIP-0388), **labels** (BIP-0329), and **wallet backup metadata** (BIP-0139).
|
||||
**wallet policies** (BIP-0388), and other wallet metadata.
|
||||
The payload must not contain any private key material.
|
||||
|
||||
Users can store encrypted backups on untrusted media or cloud services without leaking
|
||||
@@ -38,18 +39,24 @@ in all copies.
|
||||
|
||||
Losing the **wallet descriptor** (or **wallet policy**) is almost as catastrophic as
|
||||
losing the seed itself. The seed lets you sign, but the descriptor maps you to your coins.
|
||||
For multisig or miniscript wallets, keys alone won't help: without the descriptor, you
|
||||
For multisig or miniscript wallets, keys alone are insufficient: without the descriptor, you
|
||||
can't reconstruct the script.
|
||||
|
||||
Multisig setups can be designed to retain access to coins even if one of the
|
||||
seeds is lost, for example a 3-of-3 MuSig2 keypath with a script path that lets
|
||||
2-of-3 recover. But when one seed is lost, its public key may also be lost,
|
||||
rendering the coins inaccessible. This risk is not intuitive.
|
||||
|
||||
Offline storage of descriptors has two practical obstacles:
|
||||
|
||||
1. **Descriptors are hard to store offline.**
|
||||
Descriptors can be much longer than a 12/24-word seed. Paper and steel backups
|
||||
become impractical or error-prone.
|
||||
become impractical or error-prone, while USB sticks and other offline digital
|
||||
storage solutions degrade over time and are unlikely to survive flood or fire.
|
||||
|
||||
2. **Online redundancy carries privacy risk.**
|
||||
USB drives, phones, and cloud storage solve the length problem but expose your
|
||||
wallet structure. Plaintext descriptors leak your pubkeys and script details.
|
||||
Phones and cloud storage solve the length problem but expose your wallet
|
||||
structure. Plaintext descriptors leak your pubkeys and script details.
|
||||
Cloud storage is often unencrypted, and even cloud encryption could be compromised,
|
||||
depending on (often opaque) implementation details. Its security also reduces to
|
||||
that of the weakest device with cloud access. Each copy increases the attack surface.
|
||||
@@ -57,7 +64,7 @@ Offline storage of descriptors has two practical obstacles:
|
||||
This BIP therefore proposes an **encrypted**, compact backup format that:
|
||||
|
||||
* can be **safely stored in multiple places**, including untrusted online services,
|
||||
* can be **decrypted only by intended holders** of specified public keys,
|
||||
* can be **decrypted only by intended holders** of specified _public_ keys,
|
||||
|
||||
See the original [Delving post](https://delvingbitcoin.org/t/a-simple-backup-scheme-for-wallet-accounts/1607/31)
|
||||
for more background.
|
||||
@@ -65,8 +72,12 @@ for more background.
|
||||
### Expected properties
|
||||
|
||||
* **Encrypted**: safe to store with untrusted cloud providers or backup services
|
||||
* **Access controlled**: only designated cosigners can decrypt
|
||||
* **Easy to implement**: it should not require any sophisticated tools/libraries.
|
||||
* **Access controlled**:
|
||||
* only designated cosigners can decrypt, using their xpub
|
||||
* can't be decrypted using public keys revealed on chain
|
||||
* **Easy to implement**:
|
||||
* it should not require any sophisticated tools/libraries
|
||||
* it should not require access to private key material, which secure signing systems may not yield
|
||||
* **Vendor-neutral**: works with any hardware signer
|
||||
|
||||
### Scope
|
||||
@@ -76,6 +87,8 @@ scheme also works for labels (BIP-0329) and other wallet metadata like
|
||||
wallet backup metadata (BIP-0139).
|
||||
|
||||
Private key material MUST be removed before encrypting any payload.
|
||||
This is because the use of _public key_ material for decryption makes the
|
||||
scheme unsuitable for storing private key material.
|
||||
|
||||
## Specification
|
||||
|
||||
@@ -88,10 +101,13 @@ Note: in the following sections, the operator ⊕ refers to the bitwise XOR oper
|
||||
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
|
||||
(see [Descriptor key requirements](#descriptor-key-requirements)).
|
||||
- Let $s$ = sha256(sha256("BIP138_DECRYPTION_SECRET") | sha256("BIP138_DECRYPTION_SECRET") | $p_1$ | $p_2$ | ... | $p_n$)
|
||||
- Let $s_i$ = sha256(sha256("BIP138_INDIVIDUAL_SECRET") | sha256("BIP138_INDIVIDUAL_SECRET") | $p_i$)
|
||||
- Let $s$ = tagged_hash("BIP138_DECRYPTION_SECRET", $p_1$ | $p_2$ | ... | $p_n$)
|
||||
- Let $s_i$ = tagged_hash("BIP138_INDIVIDUAL_SECRET", $p_i$)
|
||||
- Let $c_i$ = $s$ ⊕ $s_i$
|
||||
|
||||
where tagged_hash(tag, m) = sha256(sha256(tag) | sha256(tag) | m) is the tagged
|
||||
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.
|
||||
|
||||
@@ -175,7 +191,7 @@ the encoder MUST generate a new one.
|
||||
In order to decrypt the payload of a backup, the owner of a certain public key p
|
||||
computes:
|
||||
|
||||
* let $s_i$ = sha256(sha256("BIP138_INDIVIDUAL_SECRET") ‖ sha256("BIP138_INDIVIDUAL_SECRET") ‖ $p$)
|
||||
* let $s_i$ = tagged_hash("BIP138_INDIVIDUAL_SECRET", $p$)
|
||||
* for each `individual_secret_i` generate `reconstructed_secret_i` =
|
||||
`individual_secret_i` ⊕ `si`
|
||||
* for each `reconstructed_secret_i` process $payload$ =
|
||||
@@ -228,8 +244,8 @@ at least classify these:[^common-derivation-paths]
|
||||
- BitBox02: `m/44h/<coin>h/{0..9}h`, `m/87h/<coin>h/{0..9}h`
|
||||
- Ledger: `m/87h/<coin>h/{0..9}h`
|
||||
|
||||
If fetching an xpub for one of these common paths fails or times out,
|
||||
implementations SHOULD warn the user.
|
||||
If fetching an xpub for one of these common paths fails or times out and
|
||||
decryption fails, implementations SHOULD warn the user.
|
||||
|
||||
[^derivation-optional]: **Why are derivation paths optional?**
|
||||
When standard derivation paths are used, they are easily discoverable, making
|
||||
@@ -312,15 +328,15 @@ Implementations MUST reject empty payloads.
|
||||
`CIPHERTEXT` is the encrypted data resulting from encryption of `PAYLOAD` with algorithm
|
||||
defined in `ENCRYPTION` where `PAYLOAD` is encoded following this format:
|
||||
|
||||
`CONTENT_TYPE` `LENGTH` `PLAINTEXT` (`CONTENT_TYPE` `LENGTH` `PLAINTEXT` ...) (`PADDING`)
|
||||
`CONTENT_TYPE` `CONTENT_LENGTH` `CONTENT` (`CONTENT_TYPE` `CONTENT_LENGTH` `CONTENT` ...) (`PADDING`)
|
||||
|
||||
`LENGTH`: variable-length integer representing the length of `PLAINTEXT` in bytes. It MUST
|
||||
be present.
|
||||
`CONTENT_LENGTH`: variable-length integer representing the length of `CONTENT` in bytes.
|
||||
It MUST be present.
|
||||
|
||||
`PLAINTEXT`: the `LENGTH` bytes of payload data.
|
||||
`CONTENT`: the `CONTENT_LENGTH` bytes of payload data.
|
||||
|
||||
`PADDING`: OPTIONAL bytes after the final `PLAINTEXT`, up to the end of the decrypted
|
||||
`PAYLOAD`. Parsers MUST consume exactly `LENGTH` bytes of each `PLAINTEXT`. The padding
|
||||
`PADDING`: OPTIONAL bytes after the final `CONTENT`, up to the end of the decrypted
|
||||
`PAYLOAD`. Parsers MUST consume exactly `CONTENT_LENGTH` bytes of each `CONTENT`. The padding
|
||||
bytes are reserved for size padding (see Padding) and/or vendor-specific data, the same
|
||||
way trailing bytes after `CIPHERTEXT` are reserved and ignored.
|
||||
|
||||
@@ -333,7 +349,7 @@ for mutable payloads such as BIP-329 or BIP-0139.[^padding-privacy]
|
||||
|
||||
Padding is OPTIONAL and is an encoder concern only: decoders ignore the `PADDING` bytes and
|
||||
MUST NOT validate any bucket. Because the padding sits inside `CIPHERTEXT`, the
|
||||
`ENCRYPTED_PAYLOAD` `LENGTH` reveals only the padded size, not the real `PLAINTEXT` size.
|
||||
`ENCRYPTED_PAYLOAD` `LENGTH` reveals only the padded size, not the real `CONTENT` size.
|
||||
|
||||
Encoders that pad SHOULD zero-fill `PAYLOAD` up to the smallest bucket `S_n` that fits it,
|
||||
with `S_0 = 10240` (10 KiB) and `S_n = floor(S_0 * (5 / 4)^n)`. Payloads of 10 KiB or less
|
||||
@@ -344,7 +360,7 @@ unnecessary for static ones such as BIP-380 descriptors and BIP-388 wallet polic
|
||||
[^padding-privacy]: **Why pad inside the ciphertext?**
|
||||
Padding the encrypted payload, rather than appending bytes after the backup, hides the
|
||||
real payload size from anyone who parses the backup, not just from someone who measures
|
||||
the file size. The size field a parser sees is the padded `LENGTH`.
|
||||
the file size. The size field a parser sees is the padded `ENCRYPTED_PAYLOAD` `LENGTH`.
|
||||
|
||||
#### Integer Encodings
|
||||
|
||||
@@ -353,18 +369,18 @@ All variable-length integers are encoded as
|
||||
|
||||
#### Content Type
|
||||
|
||||
`CONTENT_TYPE` is a variable length field defining the type of the `PLAINTEXT` that follows
|
||||
`CONTENT_TYPE` is a variable length field defining the type of the `CONTENT` that follows
|
||||
it. It follows this format:
|
||||
|
||||
`TYPE` (`LENGTH`) `DATA`
|
||||
`TYPE` (`TYPE_LENGTH`) `TYPE_PARAMS`
|
||||
|
||||
A `PAYLOAD` carries one or more `CONTENT_TYPE LENGTH PLAINTEXT` items, each `CONTENT_TYPE`
|
||||
describing the `PLAINTEXT` immediately following it. The sequence ends at the first
|
||||
A `PAYLOAD` carries one or more `CONTENT_TYPE CONTENT_LENGTH CONTENT` items, each `CONTENT_TYPE`
|
||||
describing the `CONTENT` immediately following it. The sequence ends at the first
|
||||
`TYPE` byte equal to `0x00`, or at the end of the `PAYLOAD`, whichever comes first; all
|
||||
remaining bytes are `PADDING`. A `PAYLOAD` MUST contain at least one item (see
|
||||
[Payload Size Limits](#payload-size-limits)).
|
||||
|
||||
`TYPE`: 1-byte unsigned integer identifying how to interpret `DATA`.
|
||||
`TYPE`: 1-byte unsigned integer identifying how to interpret `TYPE_PARAMS`.
|
||||
|
||||
| Value | Definition |
|
||||
|:-------|:---------------------------------------|
|
||||
@@ -373,36 +389,36 @@ remaining bytes are `PADDING`. A `PAYLOAD` MUST contain at least one item (see
|
||||
| 0x02 | Vendor-Specific Opaque Tag |
|
||||
| 0x03 | String |
|
||||
|
||||
`LENGTH`: variable-length integer representing the length of `DATA` in bytes.
|
||||
`TYPE_LENGTH`: variable-length integer representing the length of `TYPE_PARAMS` in bytes.
|
||||
|
||||
For all `TYPE` values except `0x01`, `LENGTH` MUST be present.[^length-required]
|
||||
For all `TYPE` values except `0x01`, `TYPE_LENGTH` MUST be present.[^length-required]
|
||||
|
||||
[^length-required]: **Why is `LENGTH` mandatory?**
|
||||
`LENGTH` is what lets a parser skip a content type it does not implement and still
|
||||
read the items that follow; without it, the parser cannot know where `DATA` ends.
|
||||
[^length-required]: **Why is `TYPE_LENGTH` mandatory?**
|
||||
`TYPE_LENGTH` is what lets a parser skip a content type it does not implement and still
|
||||
read the items that follow; without it, the parser cannot know where `TYPE_PARAMS` ends.
|
||||
A string note placed before a descriptor, for example, must not stop an
|
||||
implementation that only handles descriptors from recovering that descriptor. A
|
||||
type whose `DATA` is always empty still carries a zero `LENGTH` for this reason.
|
||||
type whose `TYPE_PARAMS` is always empty still carries a zero `TYPE_LENGTH` for this reason.
|
||||
`0x01` is exempt because every implementation handles it, so it is never skipped
|
||||
blindly.
|
||||
|
||||
`DATA`: variable-length field whose encoding depends on `TYPE`.
|
||||
`TYPE_PARAMS`: variable-length field whose encoding depends on `TYPE`.
|
||||
|
||||
For `TYPE` values defined above:
|
||||
- 0x00: parsers MUST stop reading content items and treat the remaining bytes as
|
||||
padding.
|
||||
- 0x01: `LENGTH` MUST be omitted and `DATA` is a 2-byte big-endian unsigned integer
|
||||
- 0x01: `TYPE_LENGTH` MUST be omitted and `TYPE_PARAMS` is a 2-byte big-endian unsigned integer
|
||||
representing the BIP number that defines it.
|
||||
- 0x02: `DATA` MUST be `LENGTH` bytes of opaque, vendor-specific data.
|
||||
- 0x03: `DATA` MUST be empty, and the following `PLAINTEXT` is the string itself,
|
||||
- 0x02: `TYPE_PARAMS` MUST be `TYPE_LENGTH` bytes of opaque, vendor-specific data.
|
||||
- 0x03: `TYPE_PARAMS` MUST be empty, and the following `CONTENT` is the string itself,
|
||||
which MUST be valid UTF-8.
|
||||
|
||||
For all `TYPE` values except `0x01`, parsers MUST reject `CONTENT_TYPE` if `LENGTH` exceeds
|
||||
For all `TYPE` values except `0x01`, parsers MUST reject `CONTENT_TYPE` if `TYPE_LENGTH` exceeds
|
||||
the remaining payload bytes.
|
||||
|
||||
For an unknown `TYPE` less than `0x80`, parsers MUST consume its `LENGTH` bytes of
|
||||
`DATA`, treat the content type as unknown, consume the following payload `LENGTH` and
|
||||
`PLAINTEXT`, and continue with the next item.
|
||||
For an unknown `TYPE` less than `0x80`, parsers MUST consume its `TYPE_LENGTH` bytes of
|
||||
`TYPE_PARAMS`, treat the content type as unknown, consume the following payload
|
||||
`CONTENT_LENGTH` and `CONTENT`, and continue with the next item.
|
||||
|
||||
For an unknown `TYPE` greater than or equal to `0x80`, parsers MUST reject the
|
||||
payload.[^type-upgrade]
|
||||
@@ -414,7 +430,7 @@ payload.[^type-upgrade]
|
||||
|
||||
#### BIP380 Descriptor Backup Content
|
||||
|
||||
When `CONTENT_TYPE` is `TYPE = 0x01` with `DATA = 0x017c` (BIP380), `PLAINTEXT` is a
|
||||
When `CONTENT_TYPE` is `TYPE = 0x01` with `TYPE_PARAMS = 0x017c` (BIP380), `CONTENT` is a
|
||||
UTF-8 BIP380 descriptor backup. It is either a single bare descriptor string, or
|
||||
a JSON descriptor backup document (for several descriptors) when the first
|
||||
character is `{`.
|
||||
@@ -457,7 +473,7 @@ wallets MAY use this value as a scanning hint.
|
||||
|
||||
#### BIP388 Wallet Policy Backup Content
|
||||
|
||||
When `CONTENT_TYPE` is `TYPE = 0x01` with `DATA = 0x0184` (BIP388), `PLAINTEXT` is a
|
||||
When `CONTENT_TYPE` is `TYPE = 0x01` with `TYPE_PARAMS = 0x0184` (BIP388), `CONTENT` is a
|
||||
UTF-8 JSON wallet policy backup document. A BIP388 wallet policy is a descriptor
|
||||
template plus a vector of key information items, as defined in BIP388.
|
||||
|
||||
|
||||
@@ -60,7 +60,7 @@
|
||||
"content": "0201"
|
||||
},
|
||||
{
|
||||
"description": "Invalid proprietary (LENGTH exceeds payload)",
|
||||
"description": "Invalid proprietary (TYPE_LENGTH exceeds payload)",
|
||||
"valid": false,
|
||||
"content": "020500"
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user