Skip to content

BIP93: Fix checksum selection bounds and restrict ms sizes - #2258

Merged
murchandamus merged 2 commits into
bitcoin:masterfrom
BenWestgate:bip93-checksum-boundary
Sep 11, 2026
Merged

murchandamus merged 2 commits into
bitcoin:masterfrom
BenWestgate:bip93-checksum-boundary

Conversation

@BenWestgate

@BenWestgate BenWestgate commented Aug 17, 2026 •

Copy link
Copy Markdown
Contributor

Motivation

BIP93 currently admits expanded codewords beyond the checksum’s stated period, so its detection guarantee no longer applies to the complete codeword. This is a problem for a generic codex32_decode function in later PRs. Both regular and long checksums permitted codewords in excess of their period.

Meanwhile, fixing the above will invalidate very uncommon ms strings or leave an ugly disjoint checksum selection rule for len(hrp) == 2. A better solution is immediately follow up with by restricting master seeds to the most commonly used seed and entropy sizes. This reduces target length ambiguity for optional insert/delete correcting wallets and my benchmarks show improvements in both false-correction risk and performance at the same indel search depths.

Changes

  • ms32_verify_checksum and ms32_create_checksum now include the expanded "ms" HRP length when selecting regular vs. long checksums.
  • ms32_create_checksum uses if 5 + len(data) > 80: as the switch to ms32_create_long_checksum.
  • added ms32_create_regular_checksum and ms32_verify_regular_checksum.
  • The Python reference code, specification, rationale, and vectors are updated.
  • ms32_decode enforces the new ms string lengths.
  • Obsolete tests/vectors from the seed size restrictions are removed.

codex32 lengths:

  • Regular codewords will now be <= 93 expanded values
  • Long codewords will now be >= 96 and <= 1023 expanded values

invalid codex32 lengths:

  • Expanded values 94, 95 and > 1023.

codex32-encoded master seed sizes:

  • 16-, 20-, 24-, 28-, 32- and 64-bytes

unsupported ms sizes:

  • All from 16- to 64-bytes besides the six above.

Compatibility

This is a breaking change for old strings encoding deprecated byte-length seeds. However the authors do not see this is a problem due to the exceedingly rare possibility these have ever been created and used.

Testing

Thoroughly reviewed the complete diff.
Checked the new vectors.
Checked that expanded length 1023 verifies and 1024 fails.
Checked that the legacy short-checksum vectors should fail.
Checked that ms32_decode rejects newly invalid lengths, even when they have valid header, incomplete group and checksum.

Discussion

Proposal: #2040 (comment)
cACK #2040 (comment)
Reference impl issue: BlockstreamResearch/codex32#75
rust-codex32 PR: BlockstreamResearch/codex32#76
Restrict ms sizes suggestion: #2258 (comment)

@vitrixLab

This comment was marked as low quality.

@murchandamus murchandamus added Proposed BIP modification PR by non-owner to update BIP content Pending acceptance This BIP modification requires sign-off by the champion of the BIP being modified labels Aug 17, 2026
@murchandamus

Copy link
Copy Markdown
Member

cc: @roconnor-blockstream, @apoelstra

@BenWestgate

BenWestgate commented Aug 20, 2026 •

Copy link
Copy Markdown
Contributor Author

Remaining gate: reproducible boundary vectors.

I added "reproducible boundary vectors."

Edit: Unsure if I should have done this to satisfy a review comment that was later marked "low quality".

For vectors, we need at minimum:

  • add regular checksum 44-, 45-, and 46-byte seeds to These examples use the wrong checksum for their given data sizes
  • add Expanded values 94, 95 and > 1023 to These examples have improper lengths
  • remove any vectors that are now accepted

Willing to revert the "reproducible" vectors added in a9d8246 if reviews also find it overkill or open to another suggestion.

What I would keep:
Separated checksum verification into regular/long primitives that check expanded length is within the checksum period and residue is valid. While ms32_verify_checksum owns the switch to long primitive at 96 expanded symbol codewords and calls the above.

Similarly ms32_create_regular_checksum creates the short checksum for data, while ms32_create_checksum selects the required checksum by expanded codewode length and creates it.

It should be easier to generate test vectors and test them now, as well as to reuse our checksums for non-codex32 data.

BenWestgate added a commit to BenWestgate/codex32 that referenced this pull request Aug 20, 2026
Account for the HRP expansion when selecting and slicing regular and long
checksums. Enforce the invalid 94–95 value gap and update the affected test
vectors.

Aligns the reference implementation with bitcoin/bips#2258.
@roconnor

roconnor commented Aug 25, 2026 •

Copy link
Copy Markdown

Based on my comment at #2040 (comment) which received some thumbs up by all parties here, I wonder if it everyone would find it preferable to simply restrict BIP-93 to seed lengths 128, 160, 192, 224, 256 and 512. I think I slightly prefer that, and if we are going to make such a change it would be better to do it from the get go.

@BenWestgate

Copy link
Copy Markdown
Contributor Author

cACK

simply restrict BIP-93 to seed lengths 128, 160, 192, 224, 256 and 512. I think I slightly prefer that, and if we are going to make such a change it would be better to do it from the get go.

To prepare to generalize BIP-93 we still must also correct the checksum-period coverage or it leaves the unsafe checksum-selection question for every later application:

For an ms application to restrict to these lengths, codex32_decode must verify the separator, header and checksum, then return HRP and u5 data. If expanded codewords may exceed the checksum period, detection guarantees won't apply, risking undetected cross-application/profile confusion.

Since restricting ms seed lengths can simplify the test vectors, rationale and compatibility sections I will add it here as a separate commit rather than revive #2077.

  • Checksum verification primitives enforce their maximum 93/1023 periods.
  • Checksum verification selects Long from 96 expanded length.
  • Checksum creation selects Long above 80 expanded hrp plus data length.
  • Master seed format rules will update and state its valid lengths.
  • “Long codex32 only for 512 bits” is an ms-profile rule, not a general rule.
  • Compatibility mentions the removed previously valid non-multiple of 4-byte and 33-63-byte sizes and their previous checksum selections.
  • SLIP-0039 permits additional lengths, compatibility needs "interconvertible with SLIP-0039 master seeds of supported lengths".
  • 43–47-byte master-seed vectors will be removed.
  • Checksum-layer assertions for 93/94/95/96/1023/1024 will be added while ms tests will enforce the six seed lengths.

Include the expanded human-readable part when selecting the codex32
checksum. This keeps regular codewords within the 93-symbol BCH period
and rejects the 94- and 95-symbol gap before the long checksum starts
at 96 symbols.

Split regular and long checksum construction and verification so their
periods can be tested independently. Add assertions for both selection
boundaries and the long checksum upper limit.

Refs: bitcoin#2258 (comment)
BenWestgate added a commit to BenWestgate/bips that referenced this pull request Aug 26, 2026
Include the expanded human-readable part when selecting the codex32
checksum. This keeps regular codewords within the 93-symbol BCH period
and rejects the 94- and 95-symbol gap before the long checksum starts
at 96 symbols.

Split regular and long checksum construction and verification so their
periods can be tested independently. Add assertions for both selection
boundaries and the long checksum upper limit.

Refs: bitcoin#2258 (comment)
@BenWestgate
BenWestgate force-pushed the bip93-checksum-boundary branch 2 times, most recently from 53203a9 to c201536 Compare August 26, 2026 20:18
@BenWestgate

Copy link
Copy Markdown
Contributor Author

c201536 is ready for review. I drafted the next commit "BIP93: Restrict ms seed lengths", working on the vectors.

Each commit is stand alone complete.

Restrict codex32-encoded BIP32 master seeds to 16, 20, 24, 28,
32, or 64 bytes. These sizes correspond to the BIP39 entropy
sizes and the 512-bit BIP32 seed produced by BIP39 recovery and provide
minimum six-character length gaps to reduce target length ambiguity
during optional insertion/deletion correction.

Move payload decoding out of the generic codex32 secret definition
and make it application-specific. Define the byte conversion and
length requirements under the master seed format instead.

Additionally:
- Enforce the new valid encoded lengths (48, 54, 61, 67, 74, 127) in
  the Python `ms32_decode` in-line reference.
- Remove obsolete short-checksum backward compatibility constraints and
  unsupported length test vectors.
- Add test vectors for 160, 192, and 224-bit master seeds
- Update rationale, compatibility, and reference implementation links.
@BenWestgate BenWestgate changed the title BIP93: Fix checksum selection boundaries BIP93: Fix checksum selection bounds and restrict ms sizes Aug 26, 2026
@apoelstra

Copy link
Copy Markdown
Contributor

ACK 5117f58

This looks good to me! Lol I think my hideous BIP39->codex32 conversion scheme is now permissible under "application-specific rules".

@jonatack jonatack removed the Pending acceptance This BIP modification requires sign-off by the champion of the BIP being modified label Aug 28, 2026
@BenWestgate

Copy link
Copy Markdown
Contributor Author

This looks good to me! Lol I think my hideous BIP39->codex32 conversion scheme is now permissible under "application-specific rules".

It indeed is! I used your bip39_12w profile's non-byte aligned payload as a major consideration here and in my rewrite of #1958. (payload length parameter, not a byte length) And for bip39_24w our regular checksum fully covers the codewords.

@roconnor

roconnor commented Aug 29, 2026 •

Copy link
Copy Markdown

I had thought that SLIP-0039 supported just 128 and 256, but now I see that it supports lengths [128,144,160,176,192,208,224,240,256].

This PR for BIP-93 supports [128, 160, 192, 224, 256] (and 512).

I think we should at give some consideration in our discussion here in supporting all SLIP-39 lengths. It would cause no problems for us regarding long and short codex32 checksums. It would make insertion and deletion recovery somewhat more difficult.

With the full SLIP-93 lengths [128,144,160,176,192,208,224,240,256] we would have codex32 encodings of lengths [48,51,54,58,61,64,67,70,74] with gaps of 3 or 4 characters between valid lengths.

Thinking out loud, if you take a 160 bit, 54 length ms codex32 string and delete 3 random characters, can you actually able to recover that by trial and error inserting 3 characters? There are 54 choose 3 positions to try inserting, and 32^3 combinations to try in each position which is a total of 812 777 472 possible combinations to try. That does seem to be a feasible number to try, especially since you are looking for one with a valid checksum.

So we are considering a trade off here of enhanced SLIP-39 compatibility for secret lengths that probably no one ever uses versus allowing recovering an from up to 3 characters being omitted from a codex32 string, which feels like also an unlikely number of characters to forget to jot down.

I'm genuinely torn here. What are the thoughts of other folks here?

@roconnor

roconnor commented Aug 29, 2026 •

Copy link
Copy Markdown

Maybe another angle to consider is, would users want to try 144 bit secrets with codex32? The argument is that Bitcoin has 128-bit security so you really want to debias your dice / entropy source when making a 128 bit secret. An alternative would be to make a longer secret and avoid debiasing your entropy with the idea that your longer secret would still contain 128-bits of entropy. This was my main motivation for considering 160 bit secrets (which was the next step up in BIP-39 secret sizes). But shorter is better because it is less work to create checksums and fewer characters means fewer errors to correct.

The question is can users use regular biased dice to get 128 bits of entropy in 144 bits?

The AI machine suggests an air bubble trapped in a cheap opaque die could bias one face up to 25% of the time. If the distribution were [25%, 16.75%, 16.75%, 16.75%, 16.75%, 8%], that would reduce the entropy of the dice from 2.58 bits per roll to 2.518 bits per roll. If the distribution were even more unreasonable at [33%, 16.66%, 16.66%, 16.66%, 16.66%, 0%] (this is also a fair die with one number repeated on a face). We'd have 2.25 bits per roll. Treating that die as fair would leave you with only than 125 bits of entropy in a 144 bit secret, but would give you 138 bits for a 160 bit secret.

So I don't know. Maybe 144 bits is still to small to eliminate debiasing?

@roconnor

roconnor commented Aug 29, 2026 •

Copy link
Copy Markdown

As a point of information, as a child I had a PC game of monopoly where the RNG had an off by 1 error and 6s were never rolled. This gives a "five" sided dice with entropy of 2.32 per roll, which gives just over 128 bits of entropy for a 144 bit secret. We did eventually notice the missing 6, but it took quite a surprising amount of time to realize.

I think I'd argue that 144 bit is inadequate for generating 128 bits of entropy using dice without debiasing.

@roconnor

roconnor commented Aug 29, 2026 •

Copy link
Copy Markdown

As counter argument for why 144 bits could be adequate, if you are building a 2 out of n secret share, you are xoring 2 biases secrets together which, generally speaking, reduces the bias of the resulting master secret.

That is to say that while generating a 144 bit master secret form a plausibly biased die seems too close to borderline unsafe, generating a 144-bit master secret from 2 randomly generated shares feels a lot safer.

(As a counter-counter argument, leaking k-1 shares reduces the security back down to a single share, though that involves the attacker both loading the dice and accessing k-1 shares).

@roconnor

Copy link
Copy Markdown

We could consider a compromise where ms secrets are limited to between 128 and 256 bits (i.e. 16-32 bytes) (plus the 512 bit long format), but generating bit sizes outside [128, 160, 192, 224, 256] is discouraged? I don't know if that is sensible.

@BenWestgate

BenWestgate commented Aug 29, 2026 •

Copy link
Copy Markdown
Contributor Author

We could consider a compromise where ms secrets are limited to between 128 and 256 bits (i.e. 16-32 bytes) (plus the 512 bit long format), but generating bit sizes outside [128, 160, 192, 224, 256] is discouraged? I don't know if that is sensible.

Adding 160, 192 and 224 already required my single threaded python indel correction to search 160, 192 and 224 one indel less deeply to stay under 10 seconds on my laptop.

It can fill 4 omissions in 44 character observed in about 5 seconds. Since omissions are marked as erasures and algebraically solved, its only 149k combinations of 4 in 45 since deletions are marked as erasures and algebraically solved. 250k for 4 in 51 and 1.1 million for 4 in 71. (128, 160 and 256-bit respectively)

The limit for filling omissions is false-correction. Intuitively it feels wrong to correct more deletions than substitutions so I prefer the symmetric "up to 4 extra or omitted characters, and mixtures" I.e. I+O <= 4.

Removing inserted characters is faster and CPU limited rather than accuracy limited.

I will simulate how much adding the 4 extra lengths increases ambiguity assuming non 128 and 256 remain searched to 3 indels.

Lastly we say "we do not recommend interconversion and suggest users generate a fresh master seed" so this potentially harms an optional feature to better support a not recommended feature.

We said wallets may assume the true length is 48 or 74. But if weird lengths get corrected to 48 or 74 they're not well supported so they need to be checked at some depth before "assuming 48 or 74".

For avoiding transcription errors and having fast corrections of them, the best recommendation is avoid generating sizes besides 128-bit, the 4-indel correction is necessarily slower on 256-bit and they've more chances to make mistakes, we already say " shorter strings are better."

However if not generating 128-bit, 256-bit is the next best size to reduce search ambiguity.

SLIP 39 requires 128 and 256 and requires 16-bit multiples so if the argument is slip39 compatibility, its either 128 and 256 alone or every even byte size 16-64, not just those 16-32.

@BenWestgate

Copy link
Copy Markdown
Contributor Author

I had thought that SLIP-0039 supported just 128 and 256, but now I see that it supports lengths [128,144,160,176,192,208,224,240,256].

It supports all even byte lengths 16-64 inclusive.

It would cause no problems for us regarding long and short codex32 checksums. It would make insertion and deletion recovery somewhat more difficult.

One of these lengths switched checksums in my first commit here.

I will test indel recovery, if it's not meaningfully worse than the 6 lengths in my 2nd commit, I have no objection.

gaps of 3 or 4 characters between valid lengths.

Thinking out loud, if you take a 160 bit, 54 length ms codex32 string and delete 3 random characters, can you actually able to recover that by trial and error inserting 3 characters? There are 54 choose 3 positions to try inserting, and 32^3 combinations to try in each position which is a total of 812 777 472 possible combinations to try.

Only the positions need search, the characters are marked erasures and filled without brute force.

3 is very fast, 4 omitted is seconds on a laptop.

So we are considering a trade off here of secret lengths that probably no one ever uses versus allowing recovering an from up to 3 characters being omitted from a codex32 string, which feels like also an unlikely number of characters to forget to jot down.

Its possible to recover from 5 omitted characters too if 0.1% chance of false corrections is acceptable. Limiting to 4 is safer.

We dont recommend interconversion, indel recovery is optional.

We could use a new profile SLIP39_EMS and let that one support every even byte length and also the passphrase feature from SLIP39, while using our SSS and checksums.

I also don't oppose every even byte length if indel recovery is unharmed.

@roconnor

roconnor commented Sep 6, 2026

Copy link
Copy Markdown

I got a bunch of emails from @BenWestgate last Wednesday on this PR but that do not appear here. I'm very confused.

@BenWestgate

BenWestgate commented Sep 7, 2026 •

Copy link
Copy Markdown
Contributor Author

@roconnor Sorry for the confusion. I clicked something in GitHub Copilot thinking it would offer a second opinion on your review, but instead it posted several You're Absolutely Right™ AI-slop replies. I deleted them; they were prohibited by Bitcoin Core's AI policy. That's why they appeared in email notifications but no longer appear here.

For the remaining review comments, I plan to open a cleanup PR based on 5117f58 containing the refactor, typo fixes, and your bits-vs-bytes consistency improvements.

#2258 (comment) is already covered by #2040, so I can rebase #2040 onto that cleanup and reopen it afterward.

That leaves ms32_encode as the only unresolved review:
#2258 (comment)

You suggested returning None when the constructed string has an invalid length. I think the low-level encoder should remain permissive, like bech32_encode, with application-specific validity enforced by the decoder or a higher-level encoder. Keeping the low-level encoder permissive is also useful for generating invalid test vectors.

The Bech32 reference implementation follows this pattern: bech32_encode simply constructs the checksummed string, while the higher-level Segwit encode function validates its result by decoding it.

We could similarly add a higher-level master-seed encoder in the Master seed section:

def encode(threshold, identifier, seed):
    """Encode a master seed in codex32."""
    header = threshold + identifier + "s"
    data = [CHARSET.find(x) for x in header.lower()]
    ret = ms32_encode(data + convertbits(seed, 8, 5))
    return ret if ms32_decode(ret) is not None else None

That function would be more useful alongside #2040, where a generalized low-level codex32_decode(codex) can no longer enforce application-specific ms length rules. Shares need a different encoder, though, so I'd rather include both or omit both encoders from the BIP text.

BIP173 takes the latter approach: its text specifies the checksum primitives but leaves bech32_encode and bech32_decode to the reference implementations. Perhaps BIP93 should do the same.

@BenWestgate

BenWestgate commented Sep 7, 2026 •

Copy link
Copy Markdown
Contributor Author

Shares need a different encoder, though

This is a subtle property @apoelstra and I missed in our first implementations, if API users have a way to construct shares from bytes, or decode bytes from shares, they abuse it.

If shares are constructed from bytes with deterministic or otherwise non-random padding, the final u5 value is not uniformly random. This can leak information about the secret given k - 1 shares.

There need not really be a share_encode function. A create_share or generate_share function would be safer:

import secrets


def create_share(threshold, identifier, share_idx, payload_len):
    """Generate a codex32 share with a uniformly random payload."""
    header = threshold + identifier + share_idx
    payload = [secrets.randbelow(32) for _ in range(payload_len)]
    data = [CHARSET.find(x) for x in header.lower()] + payload
    ret = ms32_encode(data)
    return ret if ms32_decode(ret) is not None else None

@roconnor

roconnor commented Sep 7, 2026

Copy link
Copy Markdown

Thanks. I have no further comments.

@apoelstra

Copy link
Copy Markdown
Contributor

This is a subtle property @apoelstra and I missed in our first implementations, if API users have a way to construct shares from bytes, or decode bytes from shares, they abuse it.

Yeah, we probably want to update the BIP with some cautionary text about this. But the existing BIP has no mention of converting bytes to shares (or to seeds for that matter), only of converting seeds to bytes. So I think we can defer this to a later PR.

I'd really like to get this one in. It's small and targeted and I'd like not to have another multi-month conversation with 1000s of words of tricky/subtle analysis that aren't even directly related to the issue at hand. (And I'm sure our BIP maintainers agree.)

@apoelstra

apoelstra commented Sep 10, 2026 •

Copy link
Copy Markdown
Contributor

Oh, I see, the existing text does say "Set the payload to a bech32 encoding of the secret data, padded with arbitrary bits" for "converting from an existing share".

We should maybe update this to say "pad uniformly", which will be safe if users blindly reuse this logic to convert bytes to shares, and we should also add some cautionary text saying not to convert bytes to shares and not to provide APIs to do this.

But different PR please.

@roconnor

Copy link
Copy Markdown

LGTM

@apoelstra

Copy link
Copy Markdown
Contributor

FYI my ACK above applies to the latest commit. I think we are good to merge.

@murchandamus

Copy link
Copy Markdown
Member

Great! Thanks @BenWestgate for your persistence as well as for the review @roconnor and @apoelstra.

@murchandamus
murchandamus merged commit 86d96b6 into bitcoin:master Sep 11, 2026
4 checks passed
@murchandamus

Copy link
Copy Markdown
Member

@roconnor Sorry for the confusion. I clicked something in GitHub Copilot thinking it would offer a second opinion on your review, but instead it posted several You're Absolutely Right™ AI-slop replies. I deleted them; they were prohibited by Bitcoin Core's AI policy. That's why they appeared in email notifications but no longer appear here.

Thanks for removing the slop comments. I figured that I should point out that while for historical reasons the BIPs repository is part of the Bitcoin Core organization, BIPs are not considered part of Bitcoin Core and its policy does not apply here.

@murchandamus

Copy link
Copy Markdown
Member

Also, while this proposal is currently published in Draft status and therefore doesn’t yet require a Changelog, you could consider adding one to mention these recent changes, if you feel that this was significant enough to record.

@BenWestgate
BenWestgate deleted the bip93-checksum-boundary branch September 11, 2026 23:37
BenWestgate added a commit to BenWestgate/bips that referenced this pull request Sep 12, 2026
Group the regular and long checksum definitions in the codex32 format
section. Move the master-seed application profile to the end of the
specification and keep seed-specific checksum motivation in the
rationale.

This is a behavior-neutral organization change on top of the bitcoin#2258
profile commit.
BenWestgate added a commit to BenWestgate/bips that referenced this pull request Sep 14, 2026
Keep the common format rules and checksum properties in one place, and replace the broad application-validity sentence with a concrete reference to the master seed requirements.

Remove the redundant symbol encoder and decoder examples. Preserve their validation rules in the format and master seed text, including the encoded-length restriction for both secrets and shares. Retained checksum and interpolation code is unchanged.

Checked retained Python ASTs against eb7bb6c, all 36 valid and 55 invalid vector occurrences, all six size mappings, legacy sizes, 1024 header combinations, checksum boundaries, share recovery, and nonzero padding. Link-format, README table, and whitespace checks pass.

Refs: bitcoin#2258, bitcoin#2285
BenWestgate added a commit to BenWestgate/bips that referenced this pull request Sep 14, 2026
Describe seed sizes in bits in the encoding instructions, checksum
rationale, and retained-size list, matching generation and the vectors.
Keep bytes for decoded output and the historical contiguous byte-size
range, and retain both units in the size table.

The supported sizes and all numeric constraints are unchanged. The
existing rationale already explains that BIP39 produces 512-bit seeds.

Checked retained Python ASTs, all existing vector occurrences, size
mappings, header combinations, checksum boundaries, recovery, padding,
and rendered markup. Link-format, README table, and whitespace checks
pass.

Refs: bitcoin#2258, bitcoin#2285
BenWestgate added a commit to BenWestgate/bips that referenced this pull request Sep 14, 2026
Add a reverse-chronological draft changelog and matching Version header
so readers can distinguish the earlier checksum-boundary and seed-size
changes from this behavior-neutral reorganization.

Assign retrospective versions to significant revisions and use their
upstream integration dates, rather than individual patch author dates.
Keep the Draft status and BSD-3-Clause license unchanged.

Checked the historical entries against first-parent upstream history,
the version and date ordering, and the metadata-only diff. Python ASTs,
existing vectors, size and checksum boundaries, header combinations,
recovery, padding, rendered markup, link formatting, README table, and
whitespace checks pass.

Refs: bitcoin#2258, bitcoin#2285
BenWestgate added a commit to BenWestgate/bips that referenced this pull request Sep 22, 2026
Specify the human-readable part as in BIP-0173 instead of requiring
"ms", so other applications, such as the registered "cl", can use the
codex32 format. Master seeds and their shares keep "ms".

Pass the human-readable part to the checksum functions and cover its
BIP-0173 expansion, which already counts toward the checksum length
limits. Rename the ms32 functions and constants that now serve every
human-readable part to codex32. Results for "ms" are unchanged.

Require every share in a set to have the same human-readable part, state
that the interpolation helpers do not check the set conditions, and limit
fresh-secret generation to applications that accept every payload.
Implementations should not correct the human-readable part unless its
application specifies how.

Checked that the new functions match the previous ms32 functions for
"ms" on random data of every length from 0 to 1029, that interpolation
is unchanged, and that all 36 valid and 55 invalid vector occurrences
decode as before. Link-format, README table, and whitespace checks pass.

Refs: bitcoin#2040, bitcoin#2258, bitcoin#2285

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
BenWestgate added a commit to BenWestgate/bips that referenced this pull request Sep 22, 2026
Specify the human-readable part as in BIP-0173 instead of requiring
"ms", so other applications, such as the registered "cl", can use the
codex32 format. Master seeds and their shares keep "ms".

Pass the human-readable part to the checksum functions and cover its
BIP-0173 expansion, which already counts toward the checksum length
limits. Rename the ms32 functions and constants that now serve every
human-readable part to codex32. Results for "ms" are unchanged.

Require every share in a set to have the same human-readable part, state
that the interpolation helpers do not check the set conditions, and limit
fresh-secret generation to applications that accept every payload.
Implementations should not correct the human-readable part unless its
application specifies how.

Checked that the new functions match the previous ms32 functions for
"ms" on random data parts of every length from 0 to 1029 symbols
(expanded lengths 5 to 1034), covering both checksums, the 94-95 gap,
and lengths past 1023. Also checked that interpolation is unchanged and
that all 36 valid and 55 invalid vector occurrences decode as before.
Link-format, README table, and whitespace checks pass.

Refs: bitcoin#2040, bitcoin#2258, bitcoin#2285

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
BenWestgate added a commit to BenWestgate/bips that referenced this pull request Sep 22, 2026
Specify the human-readable part as in BIP-0173 instead of requiring
"ms", so other applications, such as the registered "cl", can use the
codex32 format. Master seeds and their shares keep "ms".

Pass the human-readable part to the checksum functions and cover its
BIP-0173 expansion, which already counts toward the checksum length
limits. Rename the ms32 functions and constants that now serve every
human-readable part to codex32. Results for "ms" are unchanged.

Require every share in a set to have the same human-readable part, state
that the interpolation helpers do not check the set conditions, and limit
fresh-secret generation to applications that accept every payload.
Implementations should not correct the human-readable part unless its
application specifies how.

Checked that the new functions match the previous ms32 functions for
"ms" on random data parts of every length from 0 to 1029 symbols
(expanded lengths 5 to 1034), covering both checksums, the 94-95 gap,
and lengths past 1023. Also checked that interpolation is unchanged and
that all 36 valid and 55 invalid vector occurrences decode as before.
Link-format, README table, and whitespace checks pass.

Refs: bitcoin#2040, bitcoin#2258, bitcoin#2285

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
BenWestgate added a commit to BenWestgate/bips that referenced this pull request Sep 22, 2026
Specify the human-readable part as in BIP-0173 instead of requiring
"ms", so other applications, such as the registered "cl", can use the
codex32 format. Master seeds and their shares keep "ms".

Pass the human-readable part to the checksum functions and cover its
BIP-0173 expansion, which already counts toward the checksum length
limits. Rename the ms32 functions and constants that now serve every
human-readable part to codex32. Results for "ms" are unchanged.

Require every share in a set to have the same human-readable part, state
that the interpolation helpers do not check the set conditions, and limit
fresh-secret generation to applications that accept every payload, with
rejection sampling allowed for those that do not. Implementations should
not correct the human-readable part unless its application specifies how.

Checked that the new functions match the previous ms32 functions for
"ms" on random data parts of every length from 0 to 1029 symbols
(expanded lengths 5 to 1034), covering both checksums, the 94-95 gap,
and lengths past 1023. Also checked that interpolation is unchanged and
that all 36 valid and 55 invalid vector occurrences decode as before.
Link-format, README table, and whitespace checks pass.

Refs: bitcoin#2040, bitcoin#2258, bitcoin#2285
baslabofhd777-lab

This comment was marked as low quality.

BenWestgate added a commit to BenWestgate/bips that referenced this pull request Oct 1, 2026
Specify the human-readable part as in BIP-0173 instead of requiring
"ms", so other applications, such as the registered "cl", can use the
codex32 format. Master seeds and their shares keep "ms".

Pass the human-readable part to the checksum functions and cover its
BIP-0173 expansion, which already counts toward the checksum length
limits. Rename the ms32 functions and constants that now serve every
human-readable part to codex32. Results for "ms" are unchanged.

Require every share in a set to have the same human-readable part, state
that the interpolation helpers do not check the set conditions, and limit
fresh-secret generation to applications that accept every payload, with
rejection sampling allowed for those that do not. Implementations should
not correct the human-readable part unless its application specifies how.

Update the existing invalid-vector classifications for generalized human-
readable parts. Add vectors for a Core Lightning "cl" secret, an
11-character human-readable part requiring the long checksum, the
83-character maximum, the 94-95 expanded-length gap, an uppercase human-
readable-part checksum, and an overlong human-readable part.

Checked the checksum functions against the previous ms32 functions for
"ms" across data-part lengths 0 through 1029, and checked interpolation
is unchanged. Checked every new vector with the specification's Python
code and python-codex32, and checked each invalid group fails for its
stated reason. Link-format, README table, and whitespace checks pass.

Refs: bitcoin#2040, bitcoin#2258, bitcoin#2285
apoelstra added a commit to BlockstreamResearch/codex32 that referenced this pull request Oct 16, 2026
c4d425a rust: align BIP-0093 seed lengths (Ben Westgate)
9860634 rust: fix unchecksummed string checksums (Ben Westgate)
581a472 rust: select checksum by expanded codeword length (Ben Westgate)

Pull request description:

  Account for the HRP expansion when selecting and slicing regular and long checksums. Enforce the invalid 94–95 value gap and update the affected test vectors.

  Aligns the reference implementation with bitcoin/bips#2258.

  Closes #75

ACKs for top commit:
  apoelstra:
    utACK c4d425a

Tree-SHA512: 9c5b6ef1d350e68e4f4ea1dbed129b45ca3524aac0593cec1f7c9f10def0802af5254c4ea3cea59b0c8510f82a9f5ff9f0c0f895f8ddd44336bc623cf3cd5712
BenWestgate added a commit to BenWestgate/bips that referenced this pull request Oct 22, 2026
Keep the common format rules and checksum properties in one place, and replace the broad application-validity sentence with a concrete reference to the master seed requirements.

Retained checksum, encoding, decoding and interpolation code is unchanged.

Checked retained Python ASTs against eb7bb6c, all 36 valid and 55 invalid vector occurrences, all six size mappings, legacy sizes, 1024 header combinations, checksum boundaries, share recovery, and nonzero padding. Link-format, README table, and whitespace checks pass.

Refs: bitcoin#2258, bitcoin#2285
BenWestgate added a commit to BenWestgate/bips that referenced this pull request Oct 22, 2026
Describe seed sizes in bits in the encoding instructions, checksum
rationale, and retained-size list, matching generation and the vectors.
Keep bytes for decoded output and the historical contiguous byte-size
range, and retain both units in the size table.

The supported sizes and all numeric constraints are unchanged. The
existing rationale already explains that BIP39 produces 512-bit seeds.

Checked retained Python ASTs, all existing vector occurrences, size
mappings, header combinations, checksum boundaries, recovery, padding,
and rendered markup. Link-format, README table, and whitespace checks
pass.

Refs: bitcoin#2258, bitcoin#2285
BenWestgate added a commit to BenWestgate/bips that referenced this pull request Oct 22, 2026
Add a reverse-chronological draft changelog and matching Version header
so readers can distinguish the earlier checksum-boundary and seed-size
changes from this behavior-neutral reorganization.

Assign retrospective versions to significant revisions and use their
upstream integration dates, rather than individual patch author dates.
Keep the Draft status and BSD-3-Clause license unchanged.

Checked the historical entries against first-parent upstream history,
the version and date ordering, and the metadata-only diff. Python ASTs,
existing vectors, size and checksum boundaries, header combinations,
recovery, padding, rendered markup, link formatting, README table, and
whitespace checks pass.

Refs: bitcoin#2258, bitcoin#2285
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Proposed BIP modification PR by non-owner to update BIP content

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants