Refuse the type at the coercion, not three modules later (#776) - #809
Merged
Conversation
Five coercions took "anything convertible", handled the str spelling and passed everything else through untouched, trusting the annotation. The value then failed elsewhere as a native TypeError or AttributeError -- about a builtin, out of a module the caller never called -- which is the bug #744 fixed in bytes_from_octets, five times over. The input-validation gate held fifty-nine public functions open on them, and one line of base58.decode accounted for more than half: every key and address converter in the library decodes there. bytesio_from_binarydata is the one that did not raise at all. It returned its argument unchanged, so a None came back a None and the six parse functions above it failed on .read or on .getbuffer; it now wraps octets in a BytesIO and hands a BytesIO back as it came. script_pub_key.address is the other: it answered "" for a None, which is the answer a nulldata output has. utils.str_from_string is the String half of bytes_from_octets, and the four places that spelled that coercion out by hand call it. Two of them needed it before their length bound rather than at the codec: len of a float is a complaint about a builtin, where the codec would have named the argument. to_prv_key's WIF and xkey attempts catch a TypeError beside the ValueError, as its two "it must be octets" fallbacks already did, so an argument of the wrong type still ends as "not a private key". What stays open is ecc.dleq.verify_proof, and it is not a coercion: what answers False is to_pub_key refusing anything that is not a public key as a BTClibValueError whatever was wrong with it, which is what keeps a boolean verification total. That is #745's decision and #143's test -- dsa.verify answers False for a private key passed as a public one -- so closing the entry reverses those rather than plugging a hole, and it waits for that call. _classify is the gate's verdict as a function of its own: no function under btclib/ leaks a native exception any more, so the branch that names one went uncovered, and test_the_walk_names_what_escapes is what provokes all three answers now.
Contributor
There was a problem hiding this comment.
Sorry @fametrano, you have reached your weekly rate limit of 500000 diff characters.
Please try again later or upgrade to continue using Sourcery
Contributor
Reviewer's GuideThis PR tightens btclib’s input validation by moving type/encoding checks to the coercion boundaries, reusing a new Sequence diagram for address decoding coercion with str_from_stringsequenceDiagram
actor Caller
participant keys_from_address
participant str_from_string
participant decode
participant bech32__decode
Caller->>keys_from_address: keys_from_address(address)
keys_from_address->>str_from_string: str_from_string(address, "address")
alt invalid_type_or_non_ascii
str_from_string-->>keys_from_address: BTClibTypeError/BTClibValueError
keys_from_address-->>Caller: exception
else valid_ascii_string
str_from_string-->>keys_from_address: addr
keys_from_address->>decode: decode(addr, _BECH32_M_CONST)
decode->>bech32__decode: _decode(bech)
bech32__decode-->>decode: hrp, data
decode-->>keys_from_address: hrp, data
keys_from_address-->>Caller: Point, Point, NetworkType
end
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
This was referenced Aug 14, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes fifty-eight of the fifty-nine entries the input-validation gate
holds open in #776. The five
roots of §5, the seven one-offs of §6 and two of the three of §7.
What was wrong
Each of the five is a coercion that takes "anything convertible",
handles the
strspelling and passes everything else throughuntouched, trusting the annotation. The value then fails later and
somewhere else, as a native
TypeErrororAttributeErrorabout abuiltin, out of a module the caller never called. It is the bug
#766 fixed in
bytes_from_octets, five times over.base58.decodereachedlen(v)with whatever it was handed. Everykey and address converter in the library decodes there, which is why
one line accounted for more than half the list
utils.bytesio_from_binarydatadid not raise at all: it returned itsargument unchanged, so a
Nonecame back aNoneand the sixparsefunctions above it failed on.reador on.getbufferbip32.der_path's_indexes_from_der_pathhanded a float tolist()b32.has_segwit_prefixassumed bytes in theelseof its one linecurves.curve_group.is_on_curveaskedlenof what is not sizedWhat the fix reuses
utils.str_from_stringis theStringhalf ofbytes_from_octets,and the four places that spelled that coercion out by hand now call it:
bech32._decode,b32.has_segwit_prefix,b32.witness_from_addressand
silent_payments.keys_from_address. Two of them needed it beforetheir length bound rather than at the codec —
lenof a float is acomplaint about a builtin, where the codec below would have named the
argument — and it carries to all four the non-ascii refusal only
bech32had.What is left, and why it is a decision rather than a fix
ecc.dleq.verify_proofstays in_OPEN, and it is not a coercion.What answers False is
to_pub_keyrefusing anything that is not apublic key as a
BTClibValueErrorwhatever was wrong with it, andfolding the type in is what keeps a boolean verification total —
which is the position #745
settled on, and which
#143 test pins:
dsa.verifyanswers False for a private key passed as a public one.I did try the other way, and the suite refused it: making
point_from_pub_keyraiseBTClibTypeErrorbroketest_prv_key_is_not_a_pub_keyandtest_from_pub_key. So closingthat entry reverses #745 rather than plugging a hole, and it waits for
that call. The comment above
_OPENsays so, in place of the linebeing deleted on a reading of the code.
§8 of the issue — raising the ceiling above the 138 functions the walk
can drive — is untouched and belongs to a separate branch.
Note on the gate itself
_classifyis the gate's verdict as a function of its own. No functionunder
btclib/leaks a native exception any more, so the branch thatnames one went uncovered by the 100% ratchet, and an unrun branch is a
poor thing to be relying on the day something does leak.
test_the_walk_names_what_escapesprovokes all three answers.Gates
uv run pytest— 26722 passed, coverage 100.00%uv run pre-commit run --all-files— exit 0sphinx-build -W --keep-going— exit 0🤖 Generated with Claude Code
Summary by Sourcery
Tighten input validation across key/address codecs and curve utilities so invalid argument types fail early with BTClib-specific exceptions instead of leaking native errors.
New Features:
Bug Fixes:
Enhancements:
Documentation:
Tests: