blockchainkit.structures#
Authenticated data, signed transactions, immutable blocks, and fork state.
Merkle trees and proofs, signed transfers, blocks, ledger state, and cumulative-work fork selection.
Every public name below is re-exported by the subpackage: import it as
bk.structures.<name>. The plotting helpers are the exception: import them
explicitly from blockchainkit.structures.visualizers, which loads Matplotlib.
Types and results#
Result containers for blockchainkit.structures.
- class blockchainkit.structures.core.base.MerkleProof(index, leaf_count, siblings)[source]#
Bases:
objectLeaf position, leaf count, and bottom-up siblings (None for promotion).
- class blockchainkit.structures.core.base.ProofStep(side, sibling, digest)[source]#
Bases:
objectOne level of Merkle-proof verification, from the leaf toward the root.
- Variables:
side (
{"left", "right", "promoted"}) – Where the sibling sits:"left"means hash(sibling || current),"right"means hash(current || sibling), and"promoted"means the node had no sibling and moved up unchanged.sibling (
bytesorNone) – The sibling digest from the proof (Nonewhen promoted).digest (
bytes) – The node digest after this step.
- Parameters:
- class blockchainkit.structures.core.base.MerkleTrace(leaf_digest, steps, root, valid)[source]#
Bases:
objectEvery step of reconstructing a root from a leaf and its proof.
- Variables:
leaf_digest (
bytes) – The domain-separated hash of the leaf payload.steps (
tupleofblockchainkit.structures.core.base.ProofStep) – Bottom-up reconstruction steps.root (
bytes) – The count-bound root the proof reconstructs.valid (
bool) – Whether that root equals the trusted root.
- Parameters:
- class blockchainkit.structures.core.base.OutPoint(txid, index)[source]#
Bases:
objectA reference to one coin: the creating transaction’s id and the output index.
- class blockchainkit.structures.core.base.Coin(owner, amount)[source]#
Bases:
objectAn unspent output: who owns it and how much it holds.
- class blockchainkit.structures.core.base.SparseMerkleProof(key, value, siblings)[source]#
Bases:
objectSiblings from the leaf up, and the value at the key (None if absent).
Constructions and protocols#
Domain-separated Merkle trees with explicit leaf-count commitments.
Odd nodes are promoted unchanged. Roots bind the leaf count to avoid ambiguity between differently shaped trees. This is not Bitcoin’s format.
- class blockchainkit.structures.systems.merkle.MerkleTree(leaves)[source]#
Bases:
objectBuild a tree from an iterable of byte strings.
- Parameters:
leaves (
collections.abc.Iterableofbytes) – Ordered payloads, hashed as SHA256(0x00 || payload).
Examples
>>> from blockchainkit.structures import MerkleTree, verify_proof >>> tree = MerkleTree([b"alice", b"bob", b"carol"]) >>> verify_proof(b"bob", tree.proof(1), tree.root) True
- consistency_proof(old_size)[source]#
Prove that this tree extends its first
old_sizeleaves (RFC 6962).
- property levels: tuple[tuple[bytes, ...], ...]#
Return every level’s digests, from leaf hashes up to the top digest.
The top digest is not yet the root: the root also binds the leaf count. An empty tree has a single empty level.
- blockchainkit.structures.systems.merkle.verify_proof(leaf, proof, root)[source]#
Verify payload, position, shape, count, and root; reject malformed proofs.
- Parameters:
leaf (bytes)
proof (MerkleProof)
root (bytes)
- Return type:
- blockchainkit.structures.systems.merkle.trace_proof(leaf, proof, root)[source]#
Reconstruct the root step by step, recording each level.
Unlike
verify_proof(), a well-formed proof that reconstructs the wrong root still returns its full trace (withvalid=False), so an experiment can show where a tampered leaf diverges.- Raises:
ValueError – The proof is malformed: wrong shape, count, index, or sibling type.
- Parameters:
leaf (bytes)
proof (MerkleProof)
root (bytes)
- Return type:
Examples
>>> from blockchainkit.structures import MerkleTree, trace_proof >>> tree = MerkleTree([b"a", b"b", b"c"]) >>> [step.side for step in trace_proof(b"c", tree.proof(2), tree.root).steps] ['promoted', 'left']
- blockchainkit.structures.systems.merkle.consistency_proof(tree, old_size)[source]#
Prove that the first
old_sizeleaves oftreeform an earlier tree.The proof (RFC 6962, section 2.1.2) lists the subtree digests needed to rebuild both the old and the new top digest from shared parts, so a verifier can check that the log only appended. Because blockchainkit’s roots also bind the leaf count, the verifier cannot read the old top digest off the old root; when
old_sizeis a power of two (the case where RFC 6962 omits it) the proof starts with it. Also available asMerkleTree.consistency_proof().- Parameters:
tree (MerkleTree)
old_size (int)
- Return type:
- blockchainkit.structures.systems.merkle.verify_consistency(old_size, old_root, new_size, new_root, proof)[source]#
Check that the tree with
old_rootis a prefix of the tree withnew_root.Follows RFC 9162, section 2.1.4.2, on top digests, then checks both count-bound roots. Malformed or mismatched proofs return False.
Examples
>>> from blockchainkit.structures import MerkleTree, verify_consistency >>> leaves = [bytes([i]) for i in range(7)] >>> old, new = MerkleTree(leaves[:3]), MerkleTree(leaves) >>> verify_consistency(3, old.root, 7, new.root, new.consistency_proof(3)) True
- blockchainkit.structures.systems.merkle.bitcoin_merkle_root(leaves)[source]#
Return a root in Bitcoin’s convention: double SHA-256, odd nodes duplicated.
Shown for contrast with
MerkleTree. With no domain separation and no leaf count, the lists[a, b, c]and[a, b, c, c]share a root (CVE-2012-2459), which let an attacker make nodes reject a valid block.MerkleTreepromotes odd nodes and binds the count instead.>>> from blockchainkit.structures import bitcoin_merkle_root >>> bitcoin_merkle_root([b"a", b"b", b"c"]) == bitcoin_merkle_root([b"a", b"b", b"c", b"c"]) True
Merkle mountain ranges (2016): an append-only log with cheap updates.
A Merkle tree over n leaves is rebuilt along a whole path when a leaf is added. A mountain range keeps a list of perfect binary trees (“peaks”) of decreasing size, one per 1-bit of n. Appending a leaf adds a peak of size one and merges equal-sized neighbours, like binary addition: on average a constant number of merges. Old peaks are never rewritten, so proofs about old leaves need only the peaks to be refreshed. The root “bags” the peaks.
- class blockchainkit.structures.systems.mmr.MerkleMountainRange[source]#
Bases:
objectAn immutable append-only accumulator of byte strings.
Examples
>>> from blockchainkit.structures import MerkleMountainRange, verify_mmr_proof >>> mmr = MerkleMountainRange() >>> for leaf in (b"a", b"b", b"c"): ... mmr = mmr.append(leaf) >>> len(mmr.peaks), verify_mmr_proof(b"c", mmr.proof(2), mmr.root) (2, True)
- append(leaf)[source]#
Return a range with one more leaf, merging equal-sized peaks.
Only the new leaf’s hash and the merged nodes are computed: about two hashes per append on average, however long the range.
- Parameters:
leaf (bytes)
- Return type:
- blockchainkit.structures.systems.mmr.verify_mmr_proof(leaf, proof, root)[source]#
Rebuild the leaf’s peak, find it among the peaks, and check the bagged root.
Sparse Merkle trees (2016): a key-value map with membership and non-membership proofs.
Picture a Merkle tree with one leaf for every possible 256-bit key, almost all empty. Each key’s position is fixed by its hash, so the root is independent of insertion order, and absence is provable: show that the leaf at the key’s position is empty. Empty subtrees have precomputed default digests, so only paths to non-empty leaves are ever hashed.
- class blockchainkit.structures.systems.sparse_merkle.SparseMerkleTree(depth=256, items=None)[source]#
Bases:
objectAn immutable map from byte keys to byte values with a Merkle root.
- Parameters:
Examples
>>> from blockchainkit.structures import SparseMerkleTree, verify_sparse_proof >>> tree = SparseMerkleTree().set(b"alice", b"100") >>> verify_sparse_proof(tree.prove(b"bob"), tree.root) # Proof that bob is absent. True
- blockchainkit.structures.systems.sparse_merkle.verify_sparse_proof(proof, root)[source]#
Recompute the root from a key, its claimed value (or absence), and siblings.
The number of siblings is the tree depth. Leaf and node digests carry different prefixes, so a shorter proof cannot pass for a longer one.
- Parameters:
proof (SparseMerkleProof)
root (bytes)
- Return type:
Bloom filters (1970): compact set membership with false positives but no false negatives.
Bitcoin’s lightweight clients (BIP 37, 2012) sent full nodes a Bloom filter of their addresses, so nodes could forward matching transactions without the client listing its addresses outright. The false positives were meant to give privacy; in practice they leaked much of it.
- class blockchainkit.structures.systems.bloom.BloomFilter(size, hashes)[source]#
Bases:
objectAn array of
sizebits, set athashespositions per added item.An item is reported present if all its positions are set. Items that were added are always found; others are wrongly found with probability about
(1 - exp(-k n / m))**k.Examples
>>> from blockchainkit.structures import BloomFilter >>> bloom = BloomFilter(256, 3) >>> bloom.add(b"alice") >>> b"alice" in bloom True
Lamport’s hash chains (1981): one-time passwords from repeated hashing.
Hash a secret seed n times. The server stores only the last link. To log in, the user reveals the link before it; the server hashes it once, compares, and stores the revealed link as the new anchor. An eavesdropper who captures a password learns a value the server will never accept again, and cannot compute the next one without inverting the hash. This became S/KEY (1995).
- blockchainkit.structures.systems.hash_chain.hash_chain(seed, length)[source]#
Return
(H(seed), H(H(seed)), ..., H^length(seed)).>>> from blockchainkit.structures import hash_chain >>> len(hash_chain(b"seed", 3)) 3
- blockchainkit.structures.systems.hash_chain.verify_one_time_password(password, anchor)[source]#
Return whether
H(password)equals the stored anchor (constant-time).
Immutable signed account transfers and deterministic teaching encodings.
- blockchainkit.structures.systems.transaction.address(public)[source]#
Return SHA-256(uncompressed public key) as a 64-character account ID.
- class blockchainkit.structures.systems.transaction.Transaction(sender, recipient, amount, nonce, chain_id='blockchainkit-demo', signature=None)[source]#
Bases:
objectAn integer-valued transfer, signed over network ID and account nonce.
- Parameters:
recipient (
str) – Lowercase 64-character hexadecimal account identifier.amount (
int) – Positive integer units (no floating-point currency amounts).nonce (
int) – Sender sequence number, starting at zero.chain_id (
str) – Domain that prevents replay onto a different teaching network.signature (
blockchainkit.crypto.core.base.SchnorrSignature, optional) – Signature over the canonical unsigned payload.
- signature: SchnorrSignature | None = None#
- property txid: bytes#
Return a digest of the signed transaction encoding.
Because it covers the signature, re-signing the same payment changes it: the malleability that segregated witness removed from Bitcoin’s transaction ids. Compare
unsigned_id.
- property unsigned_id: bytes#
Return a digest of the unsigned payload only, as SegWit’s txid does.
Any valid signature over the same payment gives the same value, so a later transaction can refer to this one before it is confirmed.
- is_valid()[source]#
Check the signature; account balance and nonce are checked by Ledger.
- Return type:
The unspent-transaction-output (UTXO) model of Bitcoin (2008).
There are no accounts. Value lives in coins, outputs of earlier transactions, each owned by an address. A transaction consumes whole coins as inputs and creates new ones as outputs; any value not assigned to an output is the miner’s fee. A coin can be spent once: double spending is the attempt to use one input twice.
- class blockchainkit.structures.systems.utxo.UTXOTransaction(inputs, outputs, witnesses=())[source]#
Bases:
objectSpend whole coins and create new ones.
- Parameters:
inputs (
tupleofblockchainkit.structures.core.base.OutPoint) – Coins to consume, each at most once.outputs (
tuple) –(owner_address, amount)pairs; amounts are positive integers.witnesses (
tuple) – One(public_key, SchnorrSignature)per input, added bysigned().
- signed(privates)[source]#
Return a copy with one signature per input, keys given in input order.
Each witness is the signer’s public key and a Schnorr signature over
payload()with an RFC 6979 nonce. Whether the key owns the coin is checked byUTXOSet.apply().- Parameters:
- Return type:
- class blockchainkit.structures.systems.utxo.UTXOSet(coins)[source]#
Bases:
objectAn immutable set of unspent coins; applying a transaction returns a new set.
Examples
>>> import blockchainkit as bk >>> alice = bk.structures.address(bk.crypto.public_key(7)) >>> bk.structures.UTXOSet.genesis({alice: 50}).balance(alice) 50
- apply(tx)[source]#
Validate a transaction and return the set after it.
- Raises:
ValueError – An input is spent or unknown, a witness is missing or does not match the coin’s owner, or outputs exceed inputs.
- Parameters:
tx (UTXOTransaction)
- Return type:
Immutable blocks with canonical headers and transaction commitments.
- class blockchainkit.structures.systems.block.BlockHeader(previous_hash=b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', merkle_root=b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', height=0, timestamp=0, difficulty=8, nonce=0)[source]#
Bases:
objectThe 80-byte-style summary a light client downloads instead of a block.
It commits to the block’s transactions through
merkle_rootalone, so its hash, and therefore its proof of work, can be checked without them.Block.to_header().hash == Block.hash.- Parameters:
- previous_hash: bytes = b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'#
- class blockchainkit.structures.systems.block.Block(previous_hash=b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', transactions=(), height=0, timestamp=0, difficulty=8, nonce=0)[source]#
Bases:
objectA teaching block, not a Bitcoin/Ethereum wire-format block.
- Parameters:
previous_hash (
bytes) – Parent digest, or 32 zero bytes for genesis.transactions (
tupleofblockchainkit.structures.systems.transaction.Transaction) – Ordered signed transfers; copied into an immutable tuple.height (
int) – Nonnegative 64-bit integers; timestamps use simulation units.timestamp (
int) – Nonnegative 64-bit integers; timestamps use simulation units.nonce (
int) – Nonnegative 64-bit integers; timestamps use simulation units.difficulty (
int) – Number of required leading zero hash bits, between 0 and 256.
Notes
The Merkle root and the block hash are computed once and cached. A miner never rebuilds the transaction tree: it calls
header(nonce=...)with candidate nonces, as real miners vary only the header.- previous_hash: bytes = b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'#
- transactions: tuple[Transaction, ...] = ()#
Simplified payment verification: checking a chain of headers without blocks.
Nakamoto’s whitepaper (section 8) observed that a client can verify a payment without downloading the blockchain: keep only the block headers, check that they link and carry proof of work, and ask for a Merkle proof that the transaction is in one of them. Trust shifts to an assumption: the heaviest header chain is the one honest miners extended.
- blockchainkit.structures.systems.headers.verify_header_chain(headers)[source]#
Check that headers link by hash and carry their proof of work.
- Parameters:
headers (
collections.abc.Sequenceofblockchainkit.structures.systems.block.BlockHeader) – Consecutive headers, oldest first (not necessarily from genesis).- Returns:
The expected work they represent, the sum of
2**difficulty.- Return type:
- Raises:
ValueError – The list is empty, a header does not link to its predecessor, or a hash misses its target.
Examples
>>> import blockchainkit as bk >>> genesis = bk.consensus.mine(bk.structures.Block(difficulty=4)).block >>> bk.structures.verify_header_chain([genesis.to_header()]) 16
Immutable account balances and nonces, updated atomically by signed transfers.
- class blockchainkit.structures.systems.ledger.Ledger(balances=None, nonces=None, *, chain_id='blockchainkit-demo')[source]#
Bases:
objectAn immutable view of account balances and next expected nonces.
Initial balances are a shared simulation configuration, not a minting transaction. All peers must start with the same allocation and chain ID. Applying a batch returns a new state; failures leave the old state intact.
- property total_supply: int#
Return the sum of all balances, which transfers never change.
Every transfer debits one account and credits another by the same amount: Pacioli’s double-entry rule. A change in total supply would mean money was created or destroyed.
- property nonces: Mapping[str, int]#
Read-only next expected sequence numbers (missing accounts start at 0).
- apply(transactions)[source]#
Validate and atomically apply transfers in order, returning a new ledger.
Rejects invalid signatures, wrong chain IDs, replayed/out-of-order nonces, and insufficient funds. Self-transfers still consume a nonce.
- Parameters:
transactions (Iterable[Transaction])
- Return type:
Cumulative-work fork selection for a fixed-difficulty chain.
- class blockchainkit.structures.systems.chain.Blockchain(genesis, initial_state=None)[source]#
Bases:
objectStore valid forks and select the tip with greatest cumulative work.
- Parameters:
genesis (
blockchainkit.structures.systems.block.Block) – Mined, empty height-zero block with a zero parent hash.initial_state (
blockchainkit.structures.systems.ledger.Ledger, optional) – Shared initial allocation and chain ID.
Notes
Difficulty is fixed by genesis; a child cannot lower its own target. Equal-work ties select the lexicographically smaller hash, so peers with the same block set converge independent of arrival order. This teaching tie-break is not Bitcoin’s first-seen behavior. Each fork retains its own ledger snapshot, so reorganizations restore balances and nonces.
Keeping a full snapshot per block makes reorganizations easy to inspect, at a memory cost proportional to blocks times accounts. Real nodes keep one state and undo data instead.
- property cumulative_work: int#
Return total expected hash trials represented by the canonical chain.
- property blocks: Mapping[bytes, Block]#
Read-only view of every stored block by hash, including side forks.
- tips()[source]#
Return the blocks that have no child: the tip of every fork.
Ordered as fork choice ranks them: most cumulative work first, ties broken by the smaller hash. The first tip is always
tip.
- work_at(block_hash)[source]#
Return the cumulative expected work of the chain ending at a stored block.
Raises KeyError for an unknown hash.
- state_at(block_hash)[source]#
Return the ledger snapshot after a stored block, on whichever fork it lies.
Raises KeyError for an unknown hash.
Helpers#
Deterministic byte encodings for hashing and signing records.
- blockchainkit.structures.utils.encoding.canonical_json(value)[source]#
Encode internal integer/string records as sorted compact UTF-8 JSON.
This is a package-specific encoding, not a general canonical-JSON standard.
Account identifiers: 64 lowercase hex characters, the SHA-256 of a public key.
Plotting#
Plotting helpers for blockchainkit.structures: Merkle trees, proof traces, block trees.
- blockchainkit.structures.visualizers.plots.plot_merkle_tree(tree, *, highlight=None, ax=None)[source]#
Draw every level of a Merkle tree, optionally highlighting one proof.
- Parameters:
tree (
blockchainkit.structures.systems.merkle.MerkleTree) – A tree with at least one leaf.highlight (
int, optional) – Leaf index whose authentication path (blue) and proof siblings (orange) are colored: the siblings are exactly what a proof sends.ax (
matplotlib.axes.Axes, optional) – Axes to draw on; a new figure is created if omitted.
- Return type:
- blockchainkit.structures.visualizers.plots.plot_proof_trace(trace, ax=None)[source]#
Tabulate each step of reconstructing a root from a leaf and its proof.
- Parameters:
trace (
blockchainkit.structures.core.base.MerkleTrace) – Fromtrace_proof().ax (
matplotlib.axes.Axes, optional) – Axes to draw on; a new figure is created if omitted.
- Return type:
- blockchainkit.structures.visualizers.plots.plot_block_tree(chain, ax=None)[source]#
Draw every stored block by height, one lane per fork, canonical chain highlighted.
- Parameters:
chain (
blockchainkit.structures.systems.chain.Blockchain) – A chain, possibly holding side forks.ax (
matplotlib.axes.Axes, optional) – Axes to draw on; a new figure is created if omitted.
- Return type: