Architecture¶
btclib is a library that runs inside its caller’s process. This page is its high-level design: the major components, how they depend on one another, and the properties those dependencies keep. What a user can expect of it in terms of security is SECURITY, and why those expectations hold is the assurance case.
The curve arithmetic is btclib_ecc’s¶
Elliptic-curve arithmetic and the cryptography built on it are
btclib_ecc, a required
dependency: curves, number_theory, kdf, ecc and the SwiftEC map
of ecc.ellswift. btclib imports each name it uses from
btclib_ecc and publishes none of them: btclib_ecc.curves.mult is spelt
that way and no other, and tests/all_test.py’s
test_no_module_exports_another_distributions_name holds every __all__
of btclib to it. What those names raise is btclib_ecc’s exception
classes, which derive from the built-in classes and not from
BTClibException, and a caller names them from btclib_ecc.exceptions.
btclib_ecc delegates secp256k1 to btclib-secp256k1, the cffi bindings to Bitcoin Core’s libsecp256k1, conditionally, and btclib delegates its own few operations on the same condition.
One switch.
btclib_ecc.curves.is_libsecp256k1_servinganswers whether the process delegates, andbtclib_ecc.curves.set_libsecp256k1_servingsets it;BTCLIB_ECC_NO_LIBSECP256K1in the environment sets it off at import. btclib’s own delegating calls,ecc.ellswift.xdh, the script engine’s signature checks andscript.taproot’s tweaks, ask it first and add their own conditions to it. SECURITY.md’s Limitations, not vulnerabilities states those conditions operation by operation.The Python arithmetic. Whatever the switch and the call site decline runs btclib_ecc’s Python arithmetic. That path is not dead code: it serves every other curve, every other hash function and a caller-imposed nonce, it answers for the point at infinity, which libsecp256k1 has no public key for, and it is the whole library in an install without the
secp256k1extra. It is not constant-time, which SECURITY.md publishes as a known limitation.The bindings are the authority on the answer. The suite validates btclib’s own Python arms against them.
tests/no_bindings_test.pyimports btclib in an interpreter with the bindings out of reach and compares what it computes with what the bindings compute, andtests/py_arm_authority_test.pyrecords, for each of btclib’s Python arms, which vectors published by somebody else reach it..github/workflows/py-arm-authority.ymlre-measures that record.
Layers¶
Roughly bottom-up. The directions that hold without exception are the
substrate, ecc without ecc.bms, the pairs in the next section and the edge to
btclib_wallet below, each held by a test; elsewhere a module reaches up
where its subject asks for it, as ecc.bms does for the address a
message signature names.
the substrate:
alias,exceptions,utilsthe curve and what is built on it: btclib_ecc’s
curves,number_theory,kdfandecc, in a distribution of their own, with btclib’secc.ellswiftbeside thembitcoin’s hashes, integers and constants:
hashes,var_int,var_bytes,consensus,amountnetworks, keys and addresses:
network,base58,bech32,key,b58,b32the chain:
script,tx,block,fee,policy,coinstats,muhashthe wire:
p2p,electrum
alias holds the types the public API accepts, much of it taking anything
convertible rather than one type, and exceptions the errors it raises.
consensus holds the consensus constants and the per-network table, and
importing it imports nothing else of btclib, which tests/imports_test.py
asserts.
What of the curve’s layer is btclib’s¶
Two modules under ecc are btclib’s own, and btclib.ecc holds no other:
ecc.bmsnames its signer by an address, so it importsb32,b58,keyandnetwork, andeccimports it on demand rather than eagerly.ecc.ellswiftisxdh,XDH_TAGandELL_SIZE, BIP324’s agreement on btclib_ecc’s SwiftEC map, which is btclib’s.
Each pair is one idea split in two¶
the codec or the arithmetic |
the bitcoin semantics on top |
|---|---|
|
|
|
|
The right column imports the left one, and the left never imports the
right. So btclib.b58 for an address, and btclib.base58 for the
encoding on its own. Each of these modules states its direction in its
docstring, and tests/imports_test.py holds btclib.base58 and
btclib.bech32 to closures that do not reach the other column.
The same split lies between btclib_ecc’s curves and its ecc, which
btclib imports in place of a pair of its own.
Where a key’s spelling is read¶
A caller states which half of a pair it holds, rather than leaving a size
or a format to decide it. key.PubKeyData is the SEC octets and the
network an address builder takes, and key.PrvKeyData the scalar, the
network and the compression flag ecc.bms signs with.
Each spelling of a key is read by the module that defines it:
the scalar and the curve point in their octet spellings are facts about the curve, read by
btclib_ecc.curves.scalar_from_prv_keyandbtclib_ecc.curves.point_from_pub_keythe network and the compression, which a record carries and a key does not, are
key’sa WIF is Base58Check with a prefix and a flag, so
b58.prv_key_data_from_wifreads it andb58.wif_from_prv_keywrites itan extended key is BIP32’s format, parsed by
btclib_wallet.bip32; a caller holding one parses it there and passes on the scalar or the point
The chain and the wire¶
script, tx and block build and validate what goes on the chain, and
script.engine verifies a transaction against the consensus rules.
p2p is the wire format peers speak: the message envelope, its framing,
the message start of each network, and the payloads. electrum is the
Electrum server protocol. Both turn octets into objects and objects into
octets, and neither opens a socket: a caller reading from a connection
hands them what it read.
What sits outside the package¶
The wallet side (key derivation, mnemonics, PSBTs, output descriptors, signers and chain backends) is the
btclib-walletdistribution, imported asbtclib_wallet. It imports btclib, and nothing in btclib imports it:tests/imports_test.pyholds that edge to one direction.bitcoin-core-rpc is a required dependency.
btclib.p2p.magictakes the message start of each network from itschainsmodule, which depends on nothing beyond the standard library.btclib.p2p.magicis the one module that imports that package, and no module loadsurllib.requeston its way to anything else, so a caller who parses messages pays nothing for a client it never uses.tests/imports_test.py’stest_the_codec_does_not_pay_for_the_rpc_packageandtest_electrum_codec_stays_stdlib_lighthold both codecs to that.Package data. The networks are JSON under
src/btclib/_data/, read once at import bysrc/btclib/network.py; the catalogued curves are btclib_ecc’s package data.
The public surface¶
Every module and every package declares __all__, and a public function
validates its inputs before a private twin does the work.
CONTRIBUTING.md’s The public surface states both rules and names the
tests that hold them.