> For the complete documentation index, see [llms.txt](https://bsv.brc.dev/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://bsv.brc.dev/key-derivation/0157.md).

# Entropy-Rooted Backup and Recovery with Mnemonics and Backup Shares

Deggen (<d.kellenschwiler@bsvassociation.org>)

## Abstract

This standard defines a unified backup and recovery scheme in which **entropy** — not a private key and not a mnemonic phrase — is the fundamental object being backed up. A 32-byte entropy value is represented as a secp256k1 private key so that it can be split into [BRC-140](/key-derivation/0140.md) backup shares, and simultaneously encoded as a BIP-39 mnemonic sentence so that it can be written down as words. Either backup artifact — a quorum of shares, or the mnemonic — recovers the identical entropy, from which a canonical BIP-32 derivation produces the wallet's root key (the private counterparty to the [BRC-100](/wallet/0100.md) identity key) and any number of additional profile keys. This makes the two most widely deployed backup methods interchangeable rather than mutually exclusive, and codifies the `toEntropy()` / `fromEntropy()` methods of the `Mnemonic` class together with the `toBackupShares()` / `fromBackupShares()` methods of the `PrivateKey` class in the BSV TypeScript SDK.

## Motivation

Wallet users fall into two camps. Some prefer mnemonic backups — twelve or twenty-four words on paper, a habit built over a decade of BIP-39 wallets — and some arrive with a mnemonic they already have. Others prefer threshold backup shares ([BRC-140](/key-derivation/0140.md)) — resilient to the loss or compromise of a minority of shares — and some arrive with shares they already hold.

As deployed today these two methods are incompatible, because they anchor to different objects:

* Backup shares assume the thing being backed up is a **private key** (`PrivateKey.toBackupShares()` splits a key; `PrivateKey.fromBackupShares()` reconstructs one).
* Mnemonics assume the thing being backed up is a **string of words** encoding entropy, from which a seed and then keys are derived.

A wallet that generated its master key directly cannot later hand the user a mnemonic for it, because an arbitrary 32-byte key is not the output of a mnemonic derivation. A wallet restored from a mnemonic cannot cut backup shares that would round-trip back to the same mnemonic. Users are forced to pick one method at wallet creation and are locked in forever.

The resolution is to back up **entropy**. Entropy converts losslessly in both directions: it maps to a private key by interpretation (the bytes *are* the scalar), and it maps to a mnemonic by BIP-39 encoding (which is reversible via the wordlist and checksum). If the wallet's key hierarchy is derived *from* the entropy — rather than the entropy being an afterthought — then shares and mnemonic are two serializations of the same backup, and either one alone is a complete recovery path.

## Specification

### Terminology

The key words "MUST", "MUST NOT", "SHOULD", "SHOULD NOT", and "MAY" in this document are to be interpreted as described in RFC 2119.

* **Entropy** — the 32-byte secret value that is the sole object of backup and recovery.
* **Entropy key** — the secp256k1 private key whose big-endian 32-byte scalar encoding equals the entropy. It exists only to carry the entropy through [BRC-140](/key-derivation/0140.md) share operations; it MUST NOT be used to sign or to derive anything except as specified below.
* **Root key** — the wallet's master private key, derived from the entropy as specified below; its public key is the wallet's [BRC-100](/wallet/0100.md) identity key.
* **Profile key** — an additional root-level private key for a distinct wallet profile, derived from the same entropy at a different index.

### Entropy generation

New wallets MUST generate entropy as a uniformly random secp256k1 private key scalar, i.e. a 32-byte value in the range `[1, n − 1]` where `n` is the secp256k1 group order:

```
n = 0xFFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE BAAEDCE6 AF48A03B BFD25E8C D0364141
```

Generating within `[1, n − 1]` rather than `[0, 2²⁵⁶ − 1]` introduces a bias, but a negligible one: `n` is within `2⁻¹²⁸` of `2²⁵⁶`, so the entropy loss is far below one bit. This trade-off is accepted deliberately — it keeps the entropy directly usable as a BRC-140 share subject with no additional mapping, and the resulting ≈256-bit strength still doubles that of the 128-bit entropy behind the twelve-word mnemonics most users rely on today.

The entropy is serialized as exactly 32 bytes, big-endian, zero-padded on the left. Implementations MUST request the fixed-width encoding explicitly — in the TypeScript SDK this is `privateKey.toArray('be', 32)`; the zero-argument `toArray()` returns a *minimal-length* encoding and silently drops leading zero bytes.

### Validation

Implementations MUST validate both artifacts at every entry point:

**Mnemonic validation** (on import and on recovery):

1. Every word MUST appear in the active BIP-39 wordlist.
2. The word count MUST be 12, 15, 18, 21, or 24.
3. The BIP-39 checksum MUST verify: after mapping words to 11-bit indices and splitting off the final `ENT / 32` checksum bits, those bits MUST equal the leading bits of `SHA-256(entropy)`. A sentence failing any of these MUST be rejected — `toEntropy()` MUST throw rather than return unverified bytes.

**Entropy validation** (on generation, on mnemonic decode, and on share recovery):

4. The entropy, interpreted as a big-endian integer, MUST lie in `[1, n − 1]`. Zero MUST be rejected — note that zero is reachable through *valid* BIP-39 sentences (e.g. the twelve-word sentence consisting of eleven repetitions of `abandon` followed by `about` decodes to sixteen `0x00` bytes), and that `PrivateKey` constructors in current SDKs accept a zero scalar without complaint, so this check is the implementation's responsibility. Values `≥ n` are only reachable with 32-byte entropy and MUST likewise be rejected; a generator that produces one MUST redraw.

### Mnemonic encoding

The entropy MUST be encoded to and decoded from a mnemonic sentence exactly as specified by BIP-39, using the English wordlist by default:

* **Encoding** (`Mnemonic.fromEntropy(entropy)`): append the checksum (first `ENT / 32` bits of `SHA-256(entropy)`), split into 11-bit groups, and map each group to a word. 32 bytes of entropy yields a 24-word sentence.
* **Decoding** (`mnemonic.toEntropy()`): map words back to 11-bit indices, verify the checksum, and return the entropy bytes. Implementations MUST reject sentences with invalid checksums or words outside the wordlist.

Because BIP-39 encoding is a bijection between valid entropy and valid sentences (for a given wordlist), the mnemonic is a faithful serialization of the entropy — this is the property the entire scheme rests on.

### Backup shares

The entropy is interpreted as the entropy key and split with the [BRC-140](/key-derivation/0140.md) threshold scheme:

```
shares = entropyKey.toBackupShares(threshold, total)
```

Recovery from any `threshold` shares reconstructs the entropy key, whose 32-byte serialization is the entropy:

```
entropy = PrivateKey.fromBackupShares(shares).toArray()   // 32 bytes, zero-padded
```

The BRC-140 integrity tag (HASH160 of the entropy key's compressed public key) binds all shares of one entropy value together and lets recovery software confirm that the reconstructed entropy matches, exactly as in ordinary BRC-140 use.

### Root key derivation

The root key MUST be derived from the entropy as follows:

1. Encode the entropy as a BIP-39 mnemonic (above).
2. Derive the BIP-39 seed: `PBKDF2-HMAC-SHA512(password = sentence, salt = "mnemonic" + passphrase, 2048 iterations, 64 bytes)`, with an **empty passphrase** (`""`).
3. Construct a BIP-32 ([BRC-32](/key-derivation/0032.md)) master node from the seed.
4. Derive the hardened path `m/0'/0'`; the resulting private key is the root key.

```
rootKey = HD.fromSeed(Mnemonic.fromEntropy(entropy).toSeed()).derive("m/0'/0'").privKey
```

The root key's compressed public key is the wallet's [BRC-100](/wallet/0100.md) identity key, and all operational keys are derived from the root key using [BRC-42](/key-derivation/0042.md) as usual. The entropy key itself MUST NOT be used as the root key: keeping the backup subject and the spending root separated by a hardened derivation means the recovery artifacts never directly expose a key that has appeared on-chain or in signatures.

A non-empty BIP-39 passphrase MAY be supported as an additional secret, but implementations MUST default to the empty passphrase so that shares and mnemonic alone are sufficient for recovery; any wallet offering a passphrase MUST make clear to the user that it is a third secret which neither shares nor mnemonic can recover.

### Profiles

Additional wallet profiles under the same backup are derived from the same BIP-32 master node at successive hardened indices:

```
profileKey(i) = HD.fromSeed(Mnemonic.fromEntropy(entropy).toSeed()).derive(`m/0'/${i}'`).privKey
```

Profile 0 (`m/0'/0'`) is the root key. Profiles 1, 2, 3, … are peers of it — each with its own identity key — all recoverable from the single backed-up entropy with no per-profile backup material.

### Recovery

A conforming implementation MUST accept either recovery artifact and MUST arrive at the same entropy:

* **From shares**: `entropy = PrivateKey.fromBackupShares(shares).toArray()` (zero-padded to 32 bytes).
* **From mnemonic**: `entropy = Mnemonic.fromString(sentence).toEntropy()`.

After recovery, the wallet MUST be able to re-emit both artifacts — cut fresh shares (BRC-140 splits are non-deterministic, so new shares will differ textually but reconstruct the same entropy) and re-display the identical mnemonic sentence.

### Imported mnemonics of fewer than 24 words

Users bringing an existing BIP-39 mnemonic of 12–21 words carry entropy of 16–28 bytes. Such mnemonics MUST be accepted: `toEntropy()` yields the shorter entropy, which is zero-padded on the left to 32 bytes to form the entropy key, and derivation proceeds identically (the mnemonic seed in step 2 of root key derivation is computed from the sentence the user actually holds).

Recovering such a wallet from *shares* reconstructs the padded 32-byte value, so the original entropy length must be restored before re-encoding — naively re-encoding all 32 bytes would produce a different, 24-word sentence. Round-trip fidelity (shares back to the *exact original words*) is therefore governed by the following rules:

1. **Record the length.** Implementations SHOULD store the entropy length (equivalently, the word count) alongside the wallet and inside any share vault metadata ([BRC-154](/wallet/0154.md)). At recovery time the user can also simply be asked how many words their mnemonic had — they know.
2. **Trim to length.** On share recovery, take the reconstructed value as 32 bytes big-endian (`toArray('be', 32)`), drop the leading `32 − length` zero bytes, and re-encode the remaining `length` bytes with `Mnemonic.fromEntropy`. Because BIP-39 encoding at a fixed length is deterministic and bijective, this reproduces the identical sentence.
3. **Fallback heuristic.** If the length was not recorded and cannot be asked, count the leading zero bytes `L` of the 32-byte value and take `length = max(16, roundUpToMultipleOf4(32 − L))`. This guesses correctly unless the user's genuine entropy itself began with 4 or more zero bytes (probability `2⁻³²` per wallet), which is why rule 1 is a SHOULD and this is only a fallback.

New wallets avoid the machinery entirely by generating full 32-byte entropy (24 words).

#### Worked example

Take the BIP-39 test-vector mnemonic:

```
legal winner thank year wave sausage worth useful legal winner thank yellow
```

Decoding (12 words × 11 bits = 132 bits = 128 entropy bits + 4 checksum bits) yields 16 bytes of entropy:

```
entropy       = 7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f
```

Zero-padding to 32 bytes gives the entropy key:

```
entropyKey    = 000000000000000000000000000000007f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f
```

Splitting with `toBackupShares(2, 3)` and recombining any two shares with `fromBackupShares` reconstructs exactly that scalar. Serialized fixed-width:

```
recovered     = 000000000000000000000000000000007f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f
```

The recorded length is 16 (or the fallback heuristic observes `L = 16` leading zero bytes and computes `max(16, 32 − 16) = 16`). Dropping the 16 zero bytes and re-encoding the remaining `7f7f…7f` at 16 bytes reproduces, word for word:

```
legal winner thank year wave sausage worth useful legal winner thank yellow
```

Had the implementation instead re-encoded the full 32 bytes, the user would be shown this unrelated-looking 24-word sentence — a different backup that would derive a different root key:

```
abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon
abandon abstract wave sausage worth useful legal winner thank year wave sausage
worth upgrade
```

(The eleven leading `abandon`s are the encoding of the padding zeros — a red flag reviewers and test suites can watch for.)

Either way the entropy round-trips; only the trim step decides whether the *presentation* matches the user's paper backup. The root key is unaffected by any of this at recovery-from-mnemonic time, since it is derived from the sentence the user holds; with correct trimming, share recovery converges on the same sentence and therefore the same root key:

```
rootKey (m/0'/0') = 27e442c8015fc055789d6628f3b30461e8b2598aff74dc87ceef00dd8e670e55
```

## Reference implementation

Using `@bsv/sdk`:

```ts
import { PrivateKey, Mnemonic, HD } from '@bsv/sdk'

// --- Wallet creation ---
const entropy = PrivateKey.fromRandom()            // 32 bytes of entropy, carried as a key
const mnemonic = Mnemonic.fromEntropy(entropy.toArray('be', 32))
const hd = HD.fromSeed(mnemonic.toSeed())
const rootKey = hd.derive("m/0'/0'").privKey       // private counterparty to the identity key

// --- Backup: hand the user EITHER (or both) ---
const shares = entropy.toBackupShares(2, 3)        // any 2 of 3 recover
const words = mnemonic.mnemonic                    // 24 words

// --- Recovery path A: from shares ---
const entropyA = PrivateKey.fromBackupShares(shares.slice(0, 2))
const mnemonicA = Mnemonic.fromEntropy(entropyA.toArray('be', 32))

// --- Recovery path B: from mnemonic ---
const mnemonicB = Mnemonic.fromString(words)       // throws on bad word / count / checksum
const entropyB = new PrivateKey(mnemonicB.toEntropy())

// Both paths converge on the same entropy, hence the same root key:
// entropyA == entropyB == entropy, and
// HD.fromSeed(mnemonicA.toSeed()).derive("m/0'/0'").privKey == rootKey

// --- Profiles ---
const profile2 = hd.derive("m/0'/2'").privKey      // second additional profile, same backup
```

Importing a 12-word mnemonic and round-tripping through shares back to the exact same words:

```ts
// --- Import: user brings their own 12-word mnemonic ---
const words12 = 'legal winner thank year wave sausage worth useful legal winner thank yellow'
const imported = Mnemonic.fromString(words12)      // validates wordlist membership + checksum
const ent = imported.toEntropy()                   // 16 bytes: 7f7f…7f
const entLength = ent.length                       // 16 — RECORD THIS (or the word count, 12)

const entropyKey = new PrivateKey(ent)             // scalar 0x0000…00007f7f…7f
const shares = entropyKey.toBackupShares(2, 3)

// --- Recovery from shares, back to the exact original words ---
const recovered = PrivateKey.fromBackupShares(shares.slice(0, 2))
const full = recovered.toArray('be', 32)           // MUST be fixed-width: 32 bytes
const trimmed = full.slice(32 - entLength)         // drop the 16 padding zero bytes
const again = Mnemonic.fromEntropy(trimmed)
// again.mnemonic === words12                      // exact same 12 words

// Root key is identical from either artifact:
const rootKey = HD.fromSeed(again.toSeed()).derive("m/0'/0'").privKey
// 27e442c8015fc055789d6628f3b30461e8b2598aff74dc87ceef00dd8e670e55
```

## Security considerations

* **Entropy is the master secret.** Anyone holding the mnemonic, or a quorum of shares, controls every profile of the wallet. Both artifacts warrant the same handling users already apply to seed phrases.
* **Two live backup channels widen the attack surface.** A wallet offering both methods gives an attacker two independent artifacts to hunt for. Users who want only one method should be permitted to use only one; this standard makes them interchangeable, not both mandatory.
* **The entropy key never signs.** Because the entropy key is separated from all operational keys by hardened BIP-32 derivation and BRC-42, no signature or on-chain data reveals information about the backup subject.
* **Share recovery reassembles the secret in one place.** All BRC-140 caveats apply: recovery software sees the full entropy, and the integrity tag is a checksum, not an authentication mechanism.
* **Bias from the group order.** Restricting entropy to `[1, n − 1]` forfeits under `2⁻¹²⁸` of a bit of entropy relative to the full 256-bit space — cryptographically irrelevant.

## Relationship to other BRCs

* [BRC-140](/key-derivation/0140.md) supplies the threshold sharing scheme and share serialization; this standard changes only *what* is shared — entropy rather than an operational key.
* [BRC-75](/key-derivation/0075.md) maps a mnemonic to a single master key via `SHA-256(seed)`; this standard supersedes that construction for new wallets by making entropy recoverable from the mnemonic (BRC-75's hash is one-way, so it cannot interoperate with shares) and by using standard BIP-32 hardened derivation to reach the root key.
* [BRC-32](/key-derivation/0032.md) specifies the BIP-32 derivation used for `m/0'/0'` and profile paths.
* [BRC-42](/key-derivation/0042.md) governs all key derivation below the root key.
* [BRC-100](/wallet/0100.md) defines the wallet interface whose identity key is the public counterparty of the root key derived here.
* [BRC-154](/wallet/0154.md) backup services can vault the shares produced under this scheme unchanged.

## Implementations

* The BSV TypeScript SDK (`@bsv/sdk`) implements `PrivateKey.toBackupShares` / `fromBackupShares` ([BRC-140](/key-derivation/0140.md)) and the `Mnemonic` class with `fromEntropy`, `toEntropy`, `fromString`, and `toSeed`.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://bsv.brc.dev/key-derivation/0157.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
