Beersy
BRC-157

Entropy-Rooted Backup and Recovery with Mnemonics and Backup Shares

Wallet users end up locked into one backup method forever. If your wallet was set up with a written-down word phrase, you cannot later split your backup into shares you hand to trusted people, and if it was set up with shares, you cannot get a word phrase out of it. Someone switching wallets or wanting both options at once has been stuck picking one at creation time.

Darren Kellenschwiler12 min read

Reference for an AI

Everything an assistant needs to answer questions about BRC-157 accurately, including what it depends on.

Summary

Why
Two popular wallet backup methods, word phrases and split shares, have been built to to different underlying secrets, so a wallet set up with one method can never produce the other.
What
BRC-157 defines a scheme where a single 32-byte random value, called entropy, is the one thing backed up, and from it both a BIP-39 word mnemonic and BRC-140 threshold shares can be produced and recovered interchangeably.
How
A developer generates entropy as a random scalar, encodes it as a mnemonic and/or splits it into shares, then derives the wallet's root key and any profile keys from that same entropy via a fixed BIP-32 path so either recovery artifact reconstructs the identical wallet.

What this lets you do

  • Generate one entropy value and back it up as words, shares, or both
  • Recover the same wallet from either a mnemonic or a quorum of shares
  • Derive the wallet's root key and from entropy via a fixed path
  • Derive multiple independent wallet profiles from one backup
  • Import an existing shorter mnemonic and still round-trip through shares correctly

Written by claude-sonnet-5 from the specification text. Where the two differ, the original is correct.

mnemonicsharesentropyroot key

The specification

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 private key so that it can be split into BRC-140 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 to the BRC-100 ) 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) — 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 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 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 share operations; it MUST NOT be used to sign or to derive anything except as specified below.
  • Root key — the wallet's , derived from the entropy as specified below; its public key is the wallet's BRC-100 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):

  1. The entropy, interpreted as a big-endian integer, MUST lie in [1, n − 1]. Zero MUST be rejected — 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 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 ( 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) 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 identity key, and all operational keys are derived from the root key using BRC-42 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). 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 abandons 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:

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:

// --- 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 supplies the threshold sharing scheme and share serialization; this standard changes only what is shared — entropy rather than an operational key.
  • BRC-75 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 specifies the BIP-32 derivation used for m/0'/0' and profile paths.
  • BRC-42 governs all key derivation below the root key.
  • BRC-100 defines the whose identity key is the public counterparty of the root key derived here.
  • BRC-154 backup services can vault the shares produced under this scheme unchanged.

Implementations

  • The BSV TypeScript SDK (@bsv/sdk) implements PrivateKey.toBackupShares / fromBackupShares (BRC-140) and the Mnemonic class with fromEntropy, toEntropy, fromString, and toSeed.
Was this helpful?

Search Beersy

Search standards by number, title, author or topic