You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Document BIP93 reference-helper edge cases and add reproducible examples #1
The inline BIP93 helpers can return misleading results when their input preconditions are missed, including recovery of an unintended payload that still passes checksum and header validation. The prose already states the recovery conditions and fresh-target requirement, but the helpers do not enforce them.
Our checked Lean API already handles these cases. This issue tracks reproducible reference-code examples, documentation improvements, and precise links to the existing tests and proofs.
Points 5 and 6 are already documented upstream; point 9 is covered by an open PR. They are crossed out below. Local reproducer tasks remain in the acceptance criteria.
1. Make recovery-set validation explicit
ms32_interpolate / ms32_recover do not check matching thresholds, identifiers or lengths, distinct indices, or exact share count. With equal-length inputs, distinct indices and a fresh target, the weights sum to one and preserve checksum validity even when identifiers differ. A common valid threshold also survives, so the result can pass decoding.
Document that callers MUST validate the individual inputs and all stated set conditions before using the helpers. Include invalid recovery-set vectors for mismatched identifiers and duplicate indices. Identifier agreement is a consistency check; reused or colliding identifiers cannot establish common provenance.
Verified example: these two synthetic strings individually pass ms32_decode, but have different identifiers:
Passing their complete data parts, including checksums, to the inline ms32_recover returns:
ms12sssssnnnnnnnnnnnnnnnnnnnnnnnnnnwsmcmf8m75wg5
That result passes both checksum verification and ms32_decode, despite the invalid recovery set. Our checked recovery API should reject the inputs with mismatchedIdentifier.
Duplicate indices require a separate explanation: the inline expression compares index values in i == j, so duplicate values do not create zero denominators at a fresh target. For example, bech32_lagrange([29, 29], 16) returns [1, 1]; two identical input strings then cancel to zero. Duplicates violate the interpolation preconditions and must be rejected.
2. Explain existing-index interpolation
bech32_lagrange([29, 24], 29) returns [0, 0]. At an existing target, the numerator vanishes and the inverse table maps zero to zero, so interpolation returns an all-zero data part. A conforming decoder rejects its header.
Put the existing fresh-target restriction beside the helper. Alternatively, use the standard Lagrange product excluding the current point, which agrees at fresh targets and correctly evaluates existing targets when input indices are pairwise distinct.
Our Field.lagrange uses the standard product; Shares.interpolate returns the validated existing message, while Shares.derive enforces a fresh target.
3. Check the checksum constructor's upper bound
Merged bitcoin/bips#2258 fixes the checksum selection bounds and verifier limit. The constructor guard below remains open.
The inline ms32_create_checksum returns a 15-symbol checksum for both 1,003 and 1,004 input symbols. Its verifier accepts the first completed codeword and rejects the second.
The limit counts the five HRP expansion values, the data, and the checksum. At most 1,003 symbols may precede the checksum, including the six header symbols; the maximum payload is 997 symbols. Our checked Checksum.create already enforces this bound.
4. Explain why header interpolation works
Constant threshold and identifier columns are preserved because the weights sum to one. The index column represents f(x) = x, so reproducing the target requires at least two distinct input points. Sharing already requires a threshold of at least two. Add this explanation alongside the header-inheritance rationale.
5. Distinguish variant-only checksum verification from format validation
Addressed upstream: merged bitcoin/bips#2258 already documents variant-only verification versus automatic variant selection. The local reproducer remains to be added.
ms32_verify_long_checksum checks the long polymod residue and upper bound, but does not enforce the lower bound for selecting the long variant. Callers must use ms32_verify_checksum to select the variant required by the complete expanded length.
Concrete example: take the 32-symbol body consisting of 0qqqqs followed by 26 q symbols and append a long checksum. This gives 47 data symbols, 52 expanded values, and 50 printed characters. The long-only verifier accepts it, while the automatic selector rejects it.
Local follow-up: add this example to the pinned-snippet reproducer:
The equivalent Lean calls were checked: Checksum.verifyLong returns true and Checksum.verify returns false. This is a low-level helper contract, not a newly found implementation defect.
6. Distinguish generic codex32 from master-seed decoding
Addressed upstream: merged bitcoin/bips#2258 explicitly identifies ms32_decode as a master-seed parser. Open bitcoin/bips#2285 further separates the format and application sections and removes the inline encode/decode helpers.
The reference ms32_decode is an application parser: it accepts only the six printed master-seed lengths in MS32_VALID_LENGTHS. Generic codex32 permits other payload lengths. Accordingly, ms32_encode and ms32_decode are not unrestricted generic-format inverses.
For example, a valid regular string with a 69-symbol payload has 91 printed characters and 93 expanded values. Our Encoding.parse accepts it; Seed.parse and the reference ms32_decode reject its application size intentionally.
Local follow-up: add a generic-size example to the reproducer. The two API levels are already documented, and existing Lean generic-length regression coverage exercises the distinction.
7. Make random-share entropy and padding requirements explicit
Random initial payloads must consist of independent, uniform GF(32) symbols across all shares and columns, including padding. Uniform random bytes followed by zero padding do not meet this requirement: encoding 16 bytes produces 26 symbols, with the final symbol's low two bits fixed.
Distinguish this from a supplied existing secret, whose padding may be zero or any other permitted value. Add a usage example showing full-symbol sampling and link the secrecy theorems' exact entropy assumptions. This clarifies the BIP's instruction to choose payload characters uniformly at random; it does not identify a new mandatory feature missing from the Lean library.
8. Record the complete-codeword reference-equivalence proof boundary
The BIP's ms32_interpolate interpolates every input data position, including the checksum when complete codewords are supplied. Our checked API evaluates payload columns, constructs the header, and regenerates the checksum during serialization.
The official vectors establish tested interoperability. A general Lean theorem equating these full results remains unproved. Record this separately from the existing caveat about directly proving the optimized BIP weights equal the standard Lagrange product at pairwise-distinct source indices and a fresh target.
A follow-up theorem should compare the complete serialized data parts for admissible common-metadata, equal-length, checksum-valid sources and fresh targets, for both checksum variants. Neither missing equivalence theorem is evidence that the existing recovery or secrecy results are incorrect: those results concern the actual Lean implementation.
9. Note the apparent probability-expression typo
Covered by an existing open PR:bitcoin/bips#2285 removes the sentence containing 1 - 2^65; the earlier probability statement is retained. No separate upstream proposal is needed. The vendored snapshot stays unchanged.
This does not prove a failure probability. Formalizing the larger-random-error probability claims and the 13/15 consecutive-erasure guarantees remains separate proof work.
Completed proof coverage
The following are now proved and are not outstanding gaps:
Perfect secrecy under independently uniform full-symbol input entropy for fixed fewer-than-threshold non-secret observations: existing-secret distribution equality and fresh-secret joint-count symmetry. Public metadata and payload length are held fixed; external entropy quality is not proved.
Add a reproducible example against the pinned, unmodified BIP Python snippets covering mixed identifiers, duplicate indices, existing targets, and the 1,003/1,004-symbol boundary.
Include the concrete invalid recovery sets as fixtures and verify the checked Lean API returns the appropriate errors, reusing existing coverage where possible.
Expand the implementation documentation with the caller-validation warning, corrected duplicate-index explanation, proposed constructor guard, and constant/linear header argument.
Link the completed initialization, parser, and secrecy proofs, retaining their assumptions and distinguishing them from missing reference-equivalence theorems.
Document variant selection, generic versus master-seed parsing, and full-symbol random-share padding requirements.
Extend the pinned-snippet reproducer with the short long-checksum example and a valid generic payload rejected by the master-seed decoder.
Add a full-symbol sampling usage example contrasting random-share padding with existing-secret padding.
Prove the optimized BIP weights agree with the standard product for distinct source indices and a fresh target, then prove complete-codeword interpolation agrees with payload interpolation followed by checksum generation for both variants.
Propose the probability-expression typo correction upstream. Covered by open bitcoin/bips#2285; retain the unmodified pinned snapshot.
Verification of the original findings: the mixed-identifier example, existing-target weights, duplicate weights/cancellation, and checksum boundary behavior above were reproduced by executing the exact Python blocks extracted from the vendored specification.
Verification of the added findings: the short long-checksum and 69-symbol generic-payload examples were evaluated with the Lean implementation. Their permanent reproductions against the pinned Python snippets remain open above. Independent review checked the entropy assumptions and the remaining theorem boundaries against the source and current proofs.
Some of these are already addressed in existing PRs to the BIPs repo.
I agree we should update the BIP to fix point 1 at least. Though before PRing anything please rewrite this stuff tersely and without LLM tone.
The TOC adjustment to put sharing under heading SSSS-awareness gives it an intro section to define the functions used by recovery and interpolation. bitcoin/bips#2285
create_checksum intentionally does not verify anything, the data over 1023 expanded symbols will be rejected by ms32_verify_long_checksum, so high-level encoders should decode their output to verify this (along with header and valid lengths) before returning it.
Add a reproducible example against the pinned, unmodified BIP Python snippets covering mixed identifiers, duplicate indices, existing targets, and the 1,003/1,004-symbol boundary.
This would be an interesting invalid test vector: valid codex32 strings which form invalid sets. @apoelstra do you think generalize HRP PR should include it? It has a secret sharing valid vector using a non-"ms" HRP already. I agree, it belongs if we add a helper function for 1 that validates the set. My current draft just says implementations MUST verify the prefix and length match, and the share indices are unique and the following functions do not verify.
Document BIP93 reference-helper edge cases
Problem
The inline BIP93 helpers can return misleading results when their input preconditions are missed, including recovery of an unintended payload that still passes checksum and header validation. The prose already states the recovery conditions and fresh-target requirement, but the helpers do not enforce them.
Our checked Lean API already handles these cases. This issue tracks reproducible reference-code examples, documentation improvements, and precise links to the existing tests and proofs.
Reference: vendored BIP93 revision 55083d36.
Findings and proposed changes
Points 5 and 6 are already documented upstream; point 9 is covered by an open PR. They are crossed out below. Local reproducer tasks remain in the acceptance criteria.
1. Make recovery-set validation explicit
ms32_interpolate/ms32_recoverdo not check matching thresholds, identifiers or lengths, distinct indices, or exact share count. With equal-length inputs, distinct indices and a fresh target, the weights sum to one and preserve checksum validity even when identifiers differ. A common valid threshold also survives, so the result can pass decoding.Document that callers MUST validate the individual inputs and all stated set conditions before using the helpers. Include invalid recovery-set vectors for mismatched identifiers and duplicate indices. Identifier agreement is a consistency check; reused or colliding identifiers cannot establish common provenance.
Verified example: these two synthetic strings individually pass
ms32_decode, but have different identifiers:Passing their complete data parts, including checksums, to the inline
ms32_recoverreturns:That result passes both checksum verification and
ms32_decode, despite the invalid recovery set. Our checked recovery API should reject the inputs withmismatchedIdentifier.Duplicate indices require a separate explanation: the inline expression compares index values in
i == j, so duplicate values do not create zero denominators at a fresh target. For example,bech32_lagrange([29, 29], 16)returns[1, 1]; two identical input strings then cancel to zero. Duplicates violate the interpolation preconditions and must be rejected.2. Explain existing-index interpolation
bech32_lagrange([29, 24], 29)returns[0, 0]. At an existing target, the numerator vanishes and the inverse table maps zero to zero, so interpolation returns an all-zero data part. A conforming decoder rejects its header.Put the existing fresh-target restriction beside the helper. Alternatively, use the standard Lagrange product excluding the current point, which agrees at fresh targets and correctly evaluates existing targets when input indices are pairwise distinct.
Our
Field.lagrangeuses the standard product;Shares.interpolatereturns the validated existing message, whileShares.deriveenforces a fresh target.3. Check the checksum constructor's upper bound
Merged bitcoin/bips#2258 fixes the checksum selection bounds and verifier limit. The constructor guard below remains open.
The inline
ms32_create_checksumreturns a 15-symbol checksum for both 1,003 and 1,004 input symbols. Its verifier accepts the first completed codeword and rejects the second.Proposed guard:
The limit counts the five HRP expansion values, the data, and the checksum. At most 1,003 symbols may precede the checksum, including the six header symbols; the maximum payload is 997 symbols. Our checked
Checksum.createalready enforces this bound.4. Explain why header interpolation works
Constant threshold and identifier columns are preserved because the weights sum to one. The index column represents
f(x) = x, so reproducing the target requires at least two distinct input points. Sharing already requires a threshold of at least two. Add this explanation alongside the header-inheritance rationale.5.
Distinguish variant-only checksum verification from format validationAddressed upstream: merged bitcoin/bips#2258 already documents variant-only verification versus automatic variant selection. The local reproducer remains to be added.
ms32_verify_long_checksumchecks the long polymod residue and upper bound, but does not enforce the lower bound for selecting the long variant. Callers must usems32_verify_checksumto select the variant required by the complete expanded length.Concrete example: take the 32-symbol body consisting of
0qqqqsfollowed by 26qsymbols and append a long checksum. This gives 47 data symbols, 52 expanded values, and 50 printed characters. The long-only verifier accepts it, while the automatic selector rejects it.Local follow-up: add this example to the pinned-snippet reproducer:
The equivalent Lean calls were checked:
Checksum.verifyLongreturnstrueandChecksum.verifyreturnsfalse. This is a low-level helper contract, not a newly found implementation defect.6.
Distinguish generic codex32 from master-seed decodingAddressed upstream: merged bitcoin/bips#2258 explicitly identifies
ms32_decodeas a master-seed parser. Open bitcoin/bips#2285 further separates the format and application sections and removes the inline encode/decode helpers.The reference
ms32_decodeis an application parser: it accepts only the six printed master-seed lengths inMS32_VALID_LENGTHS. Generic codex32 permits other payload lengths. Accordingly,ms32_encodeandms32_decodeare not unrestricted generic-format inverses.For example, a valid regular string with a 69-symbol payload has 91 printed characters and 93 expanded values. Our
Encoding.parseaccepts it;Seed.parseand the referencems32_decodereject its application size intentionally.Local follow-up: add a generic-size example to the reproducer. The two API levels are already documented, and existing Lean generic-length regression coverage exercises the distinction.
7. Make random-share entropy and padding requirements explicit
Still open: the bytes-to-shares warning was discussed but explicitly deferred from bitcoin/bips#2285.
Random initial payloads must consist of independent, uniform GF(32) symbols across all shares and columns, including padding. Uniform random bytes followed by zero padding do not meet this requirement: encoding 16 bytes produces 26 symbols, with the final symbol's low two bits fixed.
Distinguish this from a supplied existing secret, whose padding may be zero or any other permitted value. Add a usage example showing full-symbol sampling and link the secrecy theorems' exact entropy assumptions. This clarifies the BIP's instruction to choose payload characters uniformly at random; it does not identify a new mandatory feature missing from the Lean library.
8. Record the complete-codeword reference-equivalence proof boundary
The BIP's
ms32_interpolateinterpolates every input data position, including the checksum when complete codewords are supplied. Our checked API evaluates payload columns, constructs the header, and regenerates the checksum during serialization.The official vectors establish tested interoperability. A general Lean theorem equating these full results remains unproved. Record this separately from the existing caveat about directly proving the optimized BIP weights equal the standard Lagrange product at pairwise-distinct source indices and a fresh target.
A follow-up theorem should compare the complete serialized data parts for admissible common-metadata, equal-length, checksum-valid sources and fresh targets, for both checksum variants. Neither missing equivalence theorem is evidence that the existing recovery or secrecy results are incorrect: those results concern the actual Lean implementation.
9.
Note the apparent probability-expression typoCovered by an existing open PR: bitcoin/bips#2285 removes the sentence containing
1 - 2^65; the earlier probability statement is retained. No separate upstream proposal is needed. The vendored snapshot stays unchanged.This does not prove a failure probability. Formalizing the larger-random-error probability claims and the 13/15 consecutive-erasure guarantees remains separate proof work.
Completed proof coverage
The following are now proved and are not outstanding gaps:
initializeFresh_valid,initializeExisting_valid, and recovery theorems.parse_iff_spec,seed_parse_iff_spec, and uppercase results.These proofs were added in 8d27167. The four-substitution/eight-erasure uniqueness proofs are also complete; they do not implement a correction algorithm.
Acceptance criteria
Propose the probability-expression typo correction upstream.Covered by open bitcoin/bips#2285; retain the unmodified pinned snapshot.Existing coverage: share validation and checked APIs, regression tests, checksum proofs, and documented specification boundaries.
Verification of the original findings: the mixed-identifier example, existing-target weights, duplicate weights/cancellation, and checksum boundary behavior above were reproduced by executing the exact Python blocks extracted from the vendored specification.
Verification of the added findings: the short long-checksum and 69-symbol generic-payload examples were evaluated with the Lean implementation. Their permanent reproductions against the pinned Python snippets remain open above. Independent review checked the entropy assumptions and the remaining theorem boundaries against the source and current proofs.