├── .gitignore ├── code ├── latency │ ├── env_check.py │ ├── orderbook_io.py │ └── latencies_and_slippage.py ├── entropy │ ├── log2_choice_count.py │ ├── passphrase_entropy_estimate.py │ └── combined_entropy_bits.py ├── aead │ ├── signing_protocol_message.py │ ├── chunked_aead_encryption.py │ └── envelope_encryption.py ├── transaction │ ├── cli_wrapper.py │ └── transaction_surgery.py ├── incident │ ├── node_health_snapshot.py │ ├── withdrawal_queue.py │ └── underpriced_transactions.py ├── wallet │ ├── check_restored_addresses.py │ └── wallet_lab_metadata.py ├── hashing │ ├── verify_download.py │ ├── hash_diagnostics.py │ ├── pbkdf2_helper.py │ └── toy_hash_explorer.py ├── failures │ ├── mac_test_harness.py │ └── mac_helpers.py ├── mining │ ├── verify_bitcoin_core_download.py │ └── generate_block_regtest.py ├── risk │ └── amm_helpers.py ├── chaindata │ └── provenance_smoke_test.py ├── compliance │ ├── collect_artefacts.py │ └── write_binder_seed.py ├── system │ └── version_snapshots.py └── policy │ └── fee_policy.py ├── notebooks ├── 4_book │ ├── chapter06_observability_and_metric_design.ipynb │ ├── chapter04_code_review_and_safety_linters.ipynb │ ├── chapter11_data_management_and_reproducibility.ipynb │ ├── chapter10_controls_monitoring_and_runbooks.ipynb │ ├── chapter12_change_management_and_upgrade_readiness.ipynb │ ├── chapter07_monitoring_dashboards_and_freshness.ipynb │ ├── chapter05_testing_strategies_and_ci_gates.ipynb │ ├── chapter03_architecture_and_trust_boundaries.ipynb │ ├── chapter02_key_and_secret_lifecycle_management.ipynb │ ├── chapter08_incident_response_and_post_mortems.ipynb │ ├── chapter01_crypto_system_design_and_threat_modelling.ipynb │ └── chapter09_governance_risk_and_compliance_memos.ipynb ├── 3_book │ ├── appendixB_additional_risk_and_scenario_patterns.ipynb │ ├── chapter06_leverage_liquidations_and_risk_limits.ipynb │ ├── chapter04_returns_volatility_and_drawdowns.ipynb │ ├── appendixC_extended_checklists_for_builders.ipynb │ ├── chapter08_portfolios_and_risk_metrics.ipynb │ ├── chapter11_nfts_and_tokenised_assets.ipynb │ ├── chapter07_market_making_and_liquidity.ipynb │ ├── chapter05_execution_costs_and_slippage.ipynb │ ├── appendixA_data_formats_and_apis.ipynb │ ├── chapter10_defi_primitives_lending_and_amm_state.ipynb │ ├── chapter09_smart_contract_platforms_and_gas.ipynb │ ├── chapter12_stablecoins_bridges_and_l2_plumbing.ipynb │ ├── chapter03_on_chain_data_and_block_level_metrics.ipynb │ ├── chapter02_centralised_exchanges_and_custody.ipynb │ └── chapter01_cryptomarkets_in_context.ipynb ├── 2_book │ ├── appendixC_wallet_and_seed_handling_checklists.ipynb │ ├── appendixB_difficulty_and_hash_rate_calculations.ipynb │ ├── appendixA_bitcoin_data_formats.ipynb │ ├── chapter07_wallets_keys_and_operational_safety.ipynb │ ├── chapter08_fees_mempools_and_confirmation_strategy.ipynb │ ├── chapter09_privacy_attacks_and_real_world_risks.ipynb │ ├── chapter02_hashes_addresses_and_identities.ipynb │ ├── chapter03_utxos_and_the_transaction_model.ipynb │ ├── chapter04_scripts_and_spending_conditions.ipynb │ ├── chapter05_blocks_merkle_trees_and_chain_structure.ipynb │ ├── chapter01_bitcoin_in_context.ipynb │ └── chapter06_proof_of_work_and_mining_economics.ipynb └── 1_book │ ├── appendixD_tooling_quickstart.ipynb │ ├── chapter07_public_key_tools.ipynb │ ├── appendixC_implementation_crib_sheets.ipynb │ ├── chapter04_symmetric_encryption_basics.ipynb │ ├── chapter02_math_snack_bar.ipynb │ ├── appendixA_math_foundations_refresher.ipynb │ ├── chapter06_keys_randomness_and_kdfs.ipynb │ ├── chapter08_secure_transport_and_storage.ipynb │ └── appendixB_probability_entropy_and_collision_intuition.ipynb └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | # Python bytecode and caches 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # Virtual environments 7 | .venv/ 8 | venv/ 9 | env/ 10 | 11 | # Packaging / build artefacts 12 | build/ 13 | dist/ 14 | *.egg-info/ 15 | .eggs/ 16 | 17 | # Testing and coverage 18 | .pytest_cache/ 19 | .coverage 20 | coverage.xml 21 | htmlcov/ 22 | .mypy_cache/ 23 | .ruff_cache/ 24 | 25 | # Notebook checkpoints 26 | .ipynb_checkpoints/ 27 | 28 | # Editors / IDEs 29 | .vscode/ 30 | .idea/ 31 | *.swp 32 | 33 | # macOS metadata 34 | .DS_Store 35 | .AppleDouble 36 | .LSOverride 37 | Icon? 38 | 39 | # Logs 40 | *.log 41 | 42 | # Misc 43 | *.bak 44 | *.tmp 45 | -------------------------------------------------------------------------------- /code/latency/env_check.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | Crypto Engineering Notes – Latency 4 | 5 | Minimal environment check for Python and pandas installation. 6 | 7 | © Dr. Yves J. Hilpisch | The Python Quants GmbH 8 | The Crypto Engineer | AI-Powered by GPT 5.1. 9 | """ 10 | 11 | import sys 12 | 13 | 14 | def main() -> None: 15 | print("Python version:", sys.version) 16 | try: 17 | import pandas as pd # type: ignore[import-not-found] 18 | 19 | print("pandas version:", pd.__version__) 20 | except ImportError: 21 | print("Please install pandas, for example via `pip install pandas`.") 22 | 23 | 24 | if __name__ == "__main__": 25 | main() 26 | 27 | -------------------------------------------------------------------------------- /code/entropy/log2_choice_count.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | Crypto Engineering Notes – Entropy 4 | 5 | Computing log2 of a choice count to estimate bits of entropy. 6 | 7 | © Dr. Yves J. Hilpisch | The Python Quants GmbH 8 | The Crypto Engineer | AI-Powered by GPT 5.1. 9 | """ 10 | 11 | import math 12 | 13 | 14 | def bits_for_choices(n: int) -> float: 15 | """Return log2(n) for a positive choice count.""" 16 | if n <= 0: 17 | raise ValueError("choice count must be positive") 18 | return math.log2(n) 19 | 20 | 21 | def main() -> None: 22 | for n in (6, 10, 2048, 2**128): 23 | print(f"N={n:>8}: {bits_for_choices(n):6.2f} bits") 24 | 25 | 26 | if __name__ == "__main__": 27 | main() 28 | 29 | -------------------------------------------------------------------------------- /code/aead/signing_protocol_message.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | Crypto Engineering Notes – AEAD 4 | 5 | Signing a protocol message with HMAC over header and body fields. 6 | 7 | © Dr. Yves J. Hilpisch | The Python Quants GmbH 8 | The Crypto Engineer | AI-Powered by GPT 5.1. 9 | """ 10 | 11 | import hmac 12 | from hashlib import sha256 13 | 14 | 15 | def sign_message(key: bytes, header: bytes, body: bytes) -> bytes: 16 | """Compute an HMAC tag over header and body.""" 17 | mac = hmac.new(key, digestmod=sha256) 18 | mac.update(header) 19 | mac.update(body) 20 | return mac.digest() 21 | 22 | 23 | def main() -> None: 24 | tag = sign_message(b"lab-key", b"hdr", b"body") 25 | print("tag:", tag.hex()) 26 | 27 | 28 | if __name__ == "__main__": 29 | main() 30 | 31 | -------------------------------------------------------------------------------- /code/entropy/passphrase_entropy_estimate.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | Crypto Engineering Notes – Entropy 4 | 5 | Estimating passphrase entropy from word count and effective dictionary size. 6 | 7 | © Dr. Yves J. Hilpisch | The Python Quants GmbH 8 | The Crypto Engineer | AI-Powered by GPT 5.1. 9 | """ 10 | 11 | from code.entropy.log2_choice_count import bits_for_choices 12 | 13 | 14 | def passphrase_bits(words: int, effective_dict_size: int = 2048) -> float: 15 | """Return an entropy estimate in bits for a passphrase.""" 16 | return words * bits_for_choices(effective_dict_size) 17 | 18 | 19 | def main() -> None: 20 | for k in (3, 4, 6, 8): 21 | bits = passphrase_bits(k) 22 | print(f"{k} words from 2048 entries -> {bits:5.1f} bits") 23 | 24 | 25 | if __name__ == "__main__": 26 | main() 27 | 28 | -------------------------------------------------------------------------------- /code/transaction/cli_wrapper.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | Crypto Engineering Notes – Transaction 4 | 5 | Thin wrapper around bitcoin-cli for regtest experiments. 6 | 7 | © Dr. Yves J. Hilpisch | The Python Quants GmbH 8 | The Crypto Engineer | AI-Powered by GPT 5.1. 9 | """ 10 | 11 | import json 12 | import subprocess 13 | from typing import Any 14 | 15 | 16 | def cli(*args: str) -> Any: 17 | """Call `bitcoin-cli -regtest` and return parsed JSON where possible.""" 18 | out = subprocess.check_output( 19 | ["bitcoin-cli", "-regtest", *args], 20 | text=True, 21 | ) 22 | try: 23 | return json.loads(out) 24 | except json.JSONDecodeError: 25 | return out.strip() 26 | 27 | 28 | def main() -> None: 29 | info = cli("getblockchaininfo") 30 | print("blocks:", info["blocks"]) 31 | 32 | 33 | if __name__ == "__main__": 34 | main() 35 | 36 | -------------------------------------------------------------------------------- /code/incident/node_health_snapshot.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | Crypto Engineering Notes – Incident 4 | 5 | Quick node health snapshot combining blockchain and mempool information. 6 | 7 | © Dr. Yves J. Hilpisch | The Python Quants GmbH 8 | The Crypto Engineer | AI-Powered by GPT 5.1. 9 | """ 10 | 11 | import json 12 | 13 | from code.policy.mempool import mempool_info # type: ignore[import-not-found] 14 | from code.transaction.cli_wrapper import cli # type: ignore[import-not-found] 15 | 16 | 17 | def node_health() -> dict: 18 | info = cli("getblockchaininfo") 19 | mem = mempool_info() 20 | return { 21 | "blocks": info["blocks"], 22 | "initial_block_download": info.get("initialblockdownload", False), 23 | "mempool_size": mem["size"], 24 | "mempool_min_fee": mem.get("mempoolminfee", 0.0), 25 | } 26 | 27 | 28 | def main() -> None: 29 | print(json.dumps(node_health(), indent=2)) 30 | 31 | 32 | if __name__ == "__main__": 33 | main() 34 | 35 | -------------------------------------------------------------------------------- /code/wallet/check_restored_addresses.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | Crypto Engineering Notes – Wallet 4 | 5 | Checking restored addresses against the lab wallet metadata file. 6 | 7 | © Dr. Yves J. Hilpisch | The Python Quants GmbH 8 | The Crypto Engineer | AI-Powered by GPT 5.1. 9 | """ 10 | 11 | import json 12 | from pathlib import Path 13 | from typing import List 14 | 15 | 16 | def expected_addresses(store: Path, label: str) -> List[str]: 17 | data = json.loads(store.read_text()) 18 | return data[label]["first_addresses"] 19 | 20 | 21 | def main() -> None: 22 | lab_file = Path("wallet_lab.json") 23 | label = "mobile_testnet_1" 24 | expected = expected_addresses(lab_file, label) 25 | restored = [ 26 | "tb1qexampleaddress1", 27 | "tb1qexampleaddress2", 28 | ] 29 | matches = expected == restored 30 | print("expected:", expected) 31 | print("restored:", restored) 32 | print("match:", matches) 33 | 34 | 35 | if __name__ == "__main__": 36 | main() 37 | 38 | -------------------------------------------------------------------------------- /code/entropy/combined_entropy_bits.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | Crypto Engineering Notes – Entropy 4 | 5 | Combining dice-roll and hardware entropy estimates for a seed. 6 | 7 | © Dr. Yves J. Hilpisch | The Python Quants GmbH 8 | The Crypto Engineer | AI-Powered by GPT 5.1. 9 | """ 10 | 11 | from code.entropy.log2_choice_count import bits_for_choices 12 | 13 | 14 | def combined_entropy_bits( 15 | dice_rolls: int, 16 | hardware_bits: float, 17 | key_size_bits: int = 256, 18 | ) -> float: 19 | """Return a conservative combined entropy estimate, capped at key size.""" 20 | dice_bits = dice_rolls * bits_for_choices(6) 21 | total = dice_bits + hardware_bits 22 | return min(total, float(key_size_bits)) 23 | 24 | 25 | def main() -> None: 26 | for rolls in (10, 20, 40): 27 | est = combined_entropy_bits(dice_rolls=rolls, hardware_bits=64.0) 28 | print(f"{rolls:2d} dice rolls + 64 hardware bits -> {est:6.2f} effective bits") 29 | 30 | 31 | if __name__ == "__main__": 32 | main() 33 | 34 | -------------------------------------------------------------------------------- /code/hashing/verify_download.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | Crypto Engineering Notes – Hashing 4 | 5 | File integrity helper streaming a file and checking a published digest. 6 | 7 | © Dr. Yves J. Hilpisch | The Python Quants GmbH 8 | The Crypto Engineer | AI-Powered by GPT 5.1. 9 | """ 10 | 11 | from hashlib import sha256 12 | from pathlib import Path 13 | 14 | 15 | EXPECTED_SHA256 = ( 16 | "ab407e4bf408c44dd7e3fdd52d1de99a7f6e5286c8b3ad21fb3d8a6c6e3b4c9a" 17 | ) 18 | 19 | 20 | def file_digest(path: Path, chunk_size: int = 65_536) -> str: 21 | h = sha256() 22 | with path.open("rb") as handle: 23 | for chunk in iter(lambda: handle.read(chunk_size), b""): 24 | h.update(chunk) 25 | return h.hexdigest() 26 | 27 | 28 | def main() -> None: 29 | artifact = Path("dataset.zip") 30 | digest = file_digest(artifact) 31 | status = "OK" if digest == EXPECTED_SHA256 else "MISMATCH" 32 | print(f"{artifact.name}: {digest} -> {status}") 33 | 34 | 35 | if __name__ == "__main__": 36 | main() 37 | 38 | -------------------------------------------------------------------------------- /code/hashing/hash_diagnostics.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | Crypto Engineering Notes – Hashing 4 | 5 | Hashing diagnostics across SHA-256, SHA3-256, and BLAKE2b-256. 6 | 7 | © Dr. Yves J. Hilpisch | The Python Quants GmbH 8 | The Crypto Engineer | AI-Powered by GPT 5.1. 9 | """ 10 | 11 | from hashlib import blake2b, sha256, sha3_256 12 | from time import perf_counter 13 | 14 | 15 | def digest_report(message: bytes) -> None: 16 | cases = { 17 | "sha256": sha256, 18 | "sha3_256": sha3_256, 19 | "blake2b-256": lambda: blake2b(digest_size=32), 20 | } 21 | print(f"Message ({len(message)} bytes): {message!r}") 22 | for name, factory in cases.items(): 23 | h = factory() 24 | start = perf_counter() 25 | h.update(message) 26 | digest = h.hexdigest() 27 | elapsed = (perf_counter() - start) * 1e6 28 | print(f"{name:>12} {digest} ({elapsed:8.2f} µs)") 29 | 30 | 31 | def main() -> None: 32 | digest_report(b"TPQ Hash Clinic") 33 | 34 | 35 | if __name__ == "__main__": 36 | main() 37 | 38 | -------------------------------------------------------------------------------- /code/failures/mac_test_harness.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | Crypto Engineering Notes – Failures 4 | 5 | Minimal MAC test harness for truncation length and canonical JSON equivalence. 6 | 7 | © Dr. Yves J. Hilpisch | The Python Quants GmbH 8 | The Crypto Engineer | AI-Powered by GPT 5.1. 9 | """ 10 | 11 | from code.failures.mac_helpers import ( 12 | hmac_sha256, 13 | truncated_mac, 14 | mac_over_payload, 15 | ) 16 | 17 | 18 | def test_truncation_length() -> None: 19 | key = b"lab-key-3" 20 | mac = hmac_sha256(key, b"message-1") 21 | short = truncated_mac(mac, 8) 22 | assert len(short) == 8 23 | 24 | 25 | def test_canonical_json_equivalence() -> None: 26 | key = b"lab-key-3" 27 | p1 = {"user": "alice", "amount": 10} 28 | p2 = {"amount": 10, "user": "alice"} 29 | assert mac_over_payload(key, p1) == mac_over_payload(key, p2) 30 | 31 | 32 | def main() -> None: 33 | test_truncation_length() 34 | test_canonical_json_equivalence() 35 | print("basic MAC checks passed") 36 | 37 | 38 | if __name__ == "__main__": 39 | main() 40 | 41 | -------------------------------------------------------------------------------- /code/mining/verify_bitcoin_core_download.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | Crypto Engineering Notes – Mining 4 | 5 | Verifying a Bitcoin Core download via SHA-256 checksum. 6 | 7 | © Dr. Yves J. Hilpisch | The Python Quants GmbH 8 | The Crypto Engineer | AI-Powered by GPT 5.1. 9 | """ 10 | 11 | import hashlib 12 | from pathlib import Path 13 | 14 | 15 | EXPECTED_SHA256 = "PUT_REAL_CHECKSUM_HERE" # TODO: replace with the real checksum 16 | 17 | 18 | def sha256_file(path: Path) -> str: 19 | """Return the hex-encoded SHA-256 digest of a file.""" 20 | h = hashlib.sha256() 21 | with path.open("rb") as f: 22 | for chunk in iter(lambda: f.read(65536), b""): 23 | h.update(chunk) 24 | return h.hexdigest() 25 | 26 | 27 | def main() -> None: 28 | archive = Path("bitcoin--x86_64-apple-darwin.dmg") 29 | if not archive.exists(): 30 | raise SystemExit(f"Archive not found: {archive}") 31 | digest = sha256_file(archive) 32 | print("digest :", digest) 33 | print("matches:", digest == EXPECTED_SHA256) 34 | 35 | 36 | if __name__ == "__main__": 37 | main() 38 | 39 | -------------------------------------------------------------------------------- /code/hashing/pbkdf2_helper.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | Crypto Engineering Notes – Hashing 4 | 5 | PBKDF2 helper for password hashing labs. 6 | 7 | © Dr. Yves J. Hilpisch | The Python Quants GmbH 8 | The Crypto Engineer | AI-Powered by GPT 5.1. 9 | """ 10 | 11 | import os 12 | from hashlib import pbkdf2_hmac 13 | from typing import Tuple 14 | 15 | 16 | def derive_password_hash( 17 | password: str, salt: bytes | None = None, iterations: int = 200_000 18 | ) -> Tuple[bytes, bytes]: 19 | if salt is None: 20 | salt = os.urandom(16) 21 | dk = pbkdf2_hmac("sha256", password.encode("utf-8"), salt, iterations, dklen=32) 22 | return salt, dk 23 | 24 | 25 | def verify_password( 26 | password: str, salt: bytes, expected: bytes, iterations: int = 200_000 27 | ) -> bool: 28 | _, candidate = derive_password_hash(password, salt, iterations) 29 | return candidate == expected 30 | 31 | 32 | def main() -> None: 33 | salt, digest = derive_password_hash("correct horse battery staple") 34 | print("salt:", salt.hex()) 35 | print("digest:", digest.hex()) 36 | 37 | 38 | if __name__ == "__main__": 39 | main() 40 | 41 | -------------------------------------------------------------------------------- /code/mining/generate_block_regtest.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | Crypto Engineering Notes – Mining 4 | 5 | Generate blocks on Bitcoin Core regtest and print the new block hashes. 6 | 7 | © Dr. Yves J. Hilpisch | The Python Quants GmbH 8 | The Crypto Engineer | AI-Powered by GPT 5.1. 9 | """ 10 | 11 | import json 12 | import subprocess 13 | from typing import Any, Dict, List 14 | 15 | 16 | def cli(*args: str) -> Dict[str, Any]: 17 | """Call bitcoin-cli with -regtest and parse JSON output when possible.""" 18 | out = subprocess.check_output(["bitcoin-cli", "-regtest", *args], text=True) 19 | try: 20 | return json.loads(out) 21 | except json.JSONDecodeError: 22 | return {"raw": out.strip()} 23 | 24 | 25 | def main() -> None: 26 | addr_info = cli("getnewaddress") 27 | addr = addr_info.get("raw") or addr_info 28 | if isinstance(addr, dict): 29 | raise SystemExit(f"Unexpected address structure: {addr!r}") 30 | print("mining to:", addr) 31 | result: List[str] = cli("generatetoaddress", "1", addr).get("raw") or [] 32 | print("new blocks:", result) 33 | 34 | 35 | if __name__ == "__main__": 36 | main() 37 | 38 | -------------------------------------------------------------------------------- /code/risk/amm_helpers.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | Crypto Engineering Notes – Risk 4 | 5 | Helpers for AMM reserve rebalancing, impermanent loss, and fee comparisons. 6 | 7 | © Dr. Yves J. Hilpisch | The Python Quants GmbH 8 | The Crypto Engineer | AI-Powered by GPT 5.1. 9 | """ 10 | 11 | import math 12 | 13 | 14 | def rebalance_to_price(x0: float, y0: float, new_price: float) -> tuple[float, float]: 15 | k = x0 * y0 16 | x1 = (k / new_price) ** 0.5 17 | y1 = new_price * x1 18 | return x1, y1 19 | 20 | 21 | def impermanent_loss(alpha: float) -> float: 22 | if alpha <= 0: 23 | raise ValueError("alpha must be positive") 24 | numerator = 2 * math.sqrt(alpha) 25 | denominator = 1 + alpha 26 | return 1.0 - numerator / denominator 27 | 28 | 29 | def fee_revenue(notional: float, fee_rate: float, volume_turns: float) -> float: 30 | """Approximate fee revenue over a horizon.""" 31 | return notional * fee_rate * volume_turns 32 | 33 | 34 | def main() -> None: 35 | x0, y0 = 10.0, 10_000.0 36 | x1, y1 = rebalance_to_price(x0, y0, new_price=1500.0) 37 | print("after move: x =", x1, "y =", y1) 38 | 39 | 40 | if __name__ == "__main__": 41 | main() 42 | 43 | -------------------------------------------------------------------------------- /code/incident/withdrawal_queue.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | Crypto Engineering Notes – Incident 4 | 5 | Tiny in-memory withdrawal queue for incident-simulation labs. 6 | 7 | © Dr. Yves J. Hilpisch | The Python Quants GmbH 8 | The Crypto Engineer | AI-Powered by GPT 5.1. 9 | """ 10 | 11 | from dataclasses import dataclass, field 12 | from enum import Enum 13 | from typing import List 14 | 15 | 16 | class Status(str, Enum): 17 | PENDING = "pending" 18 | SENT = "sent" 19 | CONFIRMED = "confirmed" 20 | 21 | 22 | @dataclass 23 | class Withdrawal: 24 | wid: str 25 | address: str 26 | amount_btc: float 27 | status: Status = Status.PENDING 28 | txid: str | None = None 29 | 30 | 31 | @dataclass 32 | class Queue: 33 | items: List[Withdrawal] = field(default_factory=list) 34 | 35 | def pending(self) -> list[Withdrawal]: 36 | return [w for w in self.items if w.status == Status.PENDING] 37 | 38 | 39 | def main() -> None: 40 | q = Queue() 41 | q.items.append(Withdrawal("w1", "bcrt1example", 0.1)) 42 | q.items.append(Withdrawal("w2", "bcrt1example2", 0.2)) 43 | for w in q.pending(): 44 | print("pending:", w.wid, w.address, w.amount_btc) 45 | 46 | 47 | if __name__ == "__main__": 48 | main() 49 | 50 | -------------------------------------------------------------------------------- /code/aead/chunked_aead_encryption.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | Crypto Engineering Notes – AEAD 4 | 5 | Chunked AEAD encryption with derived nonces and associated data. 6 | 7 | © Dr. Yves J. Hilpisch | The Python Quants GmbH 8 | The Crypto Engineer | AI-Powered by GPT 5.1. 9 | """ 10 | 11 | from typing import Iterable, Iterator, Tuple 12 | 13 | from cryptography.hazmat.primitives.ciphers.aead import AESGCM # type: ignore[import-not-found] 14 | 15 | 16 | def derive_nonce(stream_id: bytes, index: int) -> bytes: 17 | return (stream_id + index.to_bytes(4, "big"))[:12] 18 | 19 | 20 | def encrypt_chunks( 21 | dek: bytes, stream_id: bytes, chunks: Iterable[bytes] 22 | ) -> Iterator[Tuple[int, bytes]]: 23 | aesgcm = AESGCM(dek) 24 | for index, chunk in enumerate(chunks): 25 | nonce = derive_nonce(stream_id, index) 26 | aad = b"stream:" + stream_id + b";index:" + index.to_bytes(4, "big") 27 | ciphertext = aesgcm.encrypt(nonce, chunk, aad) 28 | yield index, ciphertext 29 | 30 | 31 | def main() -> None: 32 | dek = b"\x00" * 32 33 | stream_id = b"stream-1" 34 | for idx, ct in encrypt_chunks(dek, stream_id, [b"a", b"b", b"c"]): 35 | print(idx, ct.hex()) 36 | 37 | 38 | if __name__ == "__main__": 39 | main() 40 | 41 | -------------------------------------------------------------------------------- /code/wallet/wallet_lab_metadata.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | Crypto Engineering Notes – Wallet 4 | 5 | Tracking lab wallet metadata in a JSON file. 6 | 7 | © Dr. Yves J. Hilpisch | The Python Quants GmbH 8 | The Crypto Engineer | AI-Powered by GPT 5.1. 9 | """ 10 | 11 | import json 12 | from pathlib import Path 13 | from typing import List 14 | 15 | 16 | def add_wallet_record( 17 | store: Path, label: str, seed_words: str, network: str, first_addresses: List[str] 18 | ) -> None: 19 | if store.exists(): 20 | data = json.loads(store.read_text()) 21 | else: 22 | data = {} 23 | data[label] = { 24 | "seed_words": seed_words, 25 | "network": network, 26 | "first_addresses": first_addresses, 27 | } 28 | store.write_text(json.dumps(data, indent=2)) 29 | 30 | 31 | def main() -> None: 32 | lab_file = Path("wallet_lab.json") 33 | add_wallet_record( 34 | lab_file, 35 | label="mobile_testnet_1", 36 | seed_words="abandon abandon abandon ... (lab only!)", 37 | network="testnet", 38 | first_addresses=[ 39 | "tb1qexampleaddress1", 40 | "tb1qexampleaddress2", 41 | ], 42 | ) 43 | print(lab_file.read_text()) 44 | 45 | 46 | if __name__ == "__main__": 47 | main() 48 | 49 | -------------------------------------------------------------------------------- /code/latency/orderbook_io.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | Crypto Engineering Notes – Latency 4 | 5 | Loading orderbook_ticks.csv and deriving best bid/ask and mid time series. 6 | 7 | © Dr. Yves J. Hilpisch | The Python Quants GmbH 8 | The Crypto Engineer | AI-Powered by GPT 5.1. 9 | """ 10 | 11 | from typing import Optional 12 | 13 | import pandas as pd # type: ignore[import-not-found] 14 | 15 | 16 | def load_orderbook(path: str, venue: Optional[str] = None) -> pd.DataFrame: 17 | df = pd.read_csv(path) 18 | df["ts"] = pd.to_datetime(df["ts_ns"], unit="ns", utc=True) 19 | if venue is not None: 20 | df = df[df["venue_id"] == venue].copy() 21 | return df.set_index("ts").sort_index() 22 | 23 | 24 | def top_of_book(ob: pd.DataFrame) -> pd.DataFrame: 25 | bids = ob[ob["side"] == "B"].groupby(ob.index)["price"].max() 26 | asks = ob[ob["side"] == "A"].groupby(ob.index)["price"].min() 27 | tob = pd.concat({"best_bid": bids, "best_ask": asks}, axis=1).ffill() 28 | tob["mid"] = (tob["best_bid"] + tob["best_ask"]) / 2 29 | return tob 30 | 31 | 32 | def main() -> None: 33 | ob = load_orderbook("orderbook_ticks.csv", venue="CEX_A") 34 | print(ob.head()) 35 | tob = top_of_book(ob) 36 | print(tob.head()) 37 | 38 | 39 | if __name__ == "__main__": 40 | main() 41 | 42 | -------------------------------------------------------------------------------- /code/aead/envelope_encryption.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | Crypto Engineering Notes – AEAD 4 | 5 | Envelope encryption with AESGCM and a placeholder KEK wrapper. 6 | 7 | © Dr. Yves J. Hilpisch | The Python Quants GmbH 8 | The Crypto Engineer | AI-Powered by GPT 5.1. 9 | """ 10 | 11 | import os 12 | from typing import Dict 13 | 14 | from cryptography.hazmat.primitives.ciphers.aead import AESGCM # type: ignore[import-not-found] 15 | 16 | 17 | def generate_dek() -> bytes: 18 | return os.urandom(32) 19 | 20 | 21 | def wrap_dek_with_kek(dek: bytes) -> bytes: 22 | """Placeholder: in production, call your KMS or HSM here.""" 23 | return dek # do not copy this line into real systems 24 | 25 | 26 | def encrypt_record(plaintext: bytes, aad: bytes) -> Dict[str, str]: 27 | dek = generate_dek() 28 | aesgcm = AESGCM(dek) 29 | nonce = os.urandom(12) 30 | ciphertext = aesgcm.encrypt(nonce, plaintext, aad) 31 | wrapped_dek = wrap_dek_with_kek(dek) 32 | return { 33 | "nonce": nonce.hex(), 34 | "ciphertext": ciphertext.hex(), 35 | "wrapped_dek": wrapped_dek.hex(), 36 | "aad": aad.hex(), 37 | } 38 | 39 | 40 | def main() -> None: 41 | rec = encrypt_record(b"example document contents", b"lab-aad") 42 | print(rec) 43 | 44 | 45 | if __name__ == "__main__": 46 | main() 47 | 48 | -------------------------------------------------------------------------------- /code/chaindata/provenance_smoke_test.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | Crypto Engineering Notes – Chaindata 4 | 5 | Provenance smoke test comparing local row counts to chain data. 6 | 7 | © Dr. Yves J. Hilpisch | The Python Quants GmbH 8 | The Crypto Engineer | AI-Powered by GPT 5.1. 9 | """ 10 | 11 | from datetime import datetime 12 | import os 13 | 14 | import requests 15 | 16 | 17 | RPC_ENDPOINT = os.environ.get("RPC_ENDPOINT", "https://rpc.testnet.example") 18 | LOCAL_COUNT = int(os.environ.get("LOCAL_COUNT", "123456")) 19 | BLOCK_HEIGHT = int(os.environ.get("BLOCK_HEIGHT", "10500")) 20 | EXPECTED_TX_PER_BLOCK = float(os.environ.get("EXPECTED_TX_PER_BLOCK", "42")) 21 | 22 | 23 | def main() -> None: 24 | payload = requests.post( 25 | RPC_ENDPOINT, 26 | json={"method": "eth_getBlockByNumber", "params": [hex(BLOCK_HEIGHT), False]}, 27 | timeout=10, 28 | ) 29 | payload.raise_for_status() 30 | chain_hash = payload.json()["result"]["hash"] 31 | 32 | print("Checked at :", datetime.utcnow().isoformat() + "Z") 33 | print("Chain hash :", chain_hash) 34 | print("Local row count :", LOCAL_COUNT) 35 | 36 | if LOCAL_COUNT < BLOCK_HEIGHT * EXPECTED_TX_PER_BLOCK: 37 | raise SystemExit("Row count looks stale—rerun the ingestion job.") 38 | 39 | 40 | if __name__ == "__main__": 41 | main() 42 | 43 | -------------------------------------------------------------------------------- /code/incident/underpriced_transactions.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | Crypto Engineering Notes – Incident 4 | 5 | Sketch for creating deliberately underpriced transactions using fee policy helpers. 6 | 7 | © Dr. Yves J. Hilpisch | The Python Quants GmbH 8 | The Crypto Engineer | AI-Powered by GPT 5.1. 9 | """ 10 | 11 | from code.policy.fee_policy import suggest_fee # type: ignore[import-not-found] 12 | from code.transaction.cli_wrapper import cli # type: ignore[import-not-found] 13 | from code.incident.withdrawal_queue import Withdrawal 14 | 15 | 16 | def broadcast_underpriced(withdrawal: Withdrawal) -> None: 17 | """Compute an intentionally low fee rate for a withdrawal. 18 | 19 | This is a structural sketch; filling in raw transaction construction 20 | is left to the lab notebook and transaction helpers. 21 | """ 22 | rate, _ = suggest_fee("low") 23 | bad_rate = max(1.0, rate / 5.0) # far below normal suggestion 24 | print("suggested low-band rate:", rate, "sat/vB; using underpriced:", bad_rate) 25 | # Build, fund, and sign a transaction here (omitted), 26 | # then broadcast via cli("sendrawtransaction", raw_hex). 27 | 28 | 29 | def main() -> None: 30 | w = Withdrawal(wid="lab-1", address="bcrt1example", amount_btc=0.1) 31 | broadcast_underpriced(w) 32 | 33 | 34 | if __name__ == "__main__": 35 | main() 36 | 37 | -------------------------------------------------------------------------------- /code/hashing/toy_hash_explorer.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | Crypto Engineering Notes – Hashing 4 | 5 | Toy hash explorer for tracing a simple ARX-style compression function. 6 | 7 | © Dr. Yves J. Hilpisch | The Python Quants GmbH 8 | The Crypto Engineer | AI-Powered by GPT 5.1. 9 | """ 10 | 11 | from textwrap import wrap 12 | 13 | 14 | INIT = (0x61707865, 0x3320646E, 0x79622D32, 0x6B206574) 15 | 16 | 17 | def rol(x: int, k: int) -> int: 18 | return ((x << k) | (x >> (32 - k))) & 0xFFFFFFFF 19 | 20 | 21 | def toy_hash(msg: bytes) -> bytes: 22 | pad_len = (4 - len(msg) % 4) % 4 23 | padded = msg + b"\x00" * pad_len 24 | words = [ 25 | int.from_bytes(padded[i : i + 4], "little") 26 | for i in range(0, len(padded), 4) 27 | ] 28 | a, b, c, d = INIT 29 | for idx, word in enumerate(words): 30 | a = (a + word) & 0xFFFFFFFF 31 | b = rol(b ^ a, 5) 32 | c = (c + b) & 0xFFFFFFFF 33 | d = rol(d ^ c, 8) 34 | print(f"step {idx:02d}: a={a:08x} b={b:08x} c={c:08x} d={d:08x}") 35 | return ( 36 | a.to_bytes(4, "little") 37 | + b.to_bytes(4, "little") 38 | + c.to_bytes(4, "little") 39 | + d.to_bytes(4, "little") 40 | ) 41 | 42 | 43 | def main() -> None: 44 | digest = toy_hash(b"TPQ") 45 | print("digest:", " ".join(wrap(digest.hex(), 8))) 46 | 47 | 48 | if __name__ == "__main__": 49 | main() 50 | 51 | -------------------------------------------------------------------------------- /code/failures/mac_helpers.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | Crypto Engineering Notes – Failures 4 | 5 | HMAC helpers for truncation, encoding, and canonical JSON MAC computation. 6 | 7 | © Dr. Yves J. Hilpisch | The Python Quants GmbH 8 | The Crypto Engineer | AI-Powered by GPT 5.1. 9 | """ 10 | 11 | import hmac 12 | import json 13 | import unicodedata 14 | from hashlib import sha256 15 | from typing import Any, Dict 16 | 17 | 18 | def hmac_sha256(key: bytes, message: bytes) -> bytes: 19 | mac = hmac.new(key, message, sha256) 20 | return mac.digest() 21 | 22 | 23 | def truncated_mac(mac: bytes, length_bytes: int) -> bytes: 24 | return mac[:length_bytes] 25 | 26 | 27 | def mac_of_text(key: bytes, text: str) -> bytes: 28 | data = text.encode("utf-8") 29 | return hmac_sha256(key, data) 30 | 31 | 32 | def canonical_json(payload: Dict[str, Any]) -> bytes: 33 | return json.dumps(payload, separators=(",", ":"), sort_keys=True).encode("utf-8") 34 | 35 | 36 | def mac_over_payload(key: bytes, payload: Dict[str, Any]) -> bytes: 37 | return hmac_sha256(key, canonical_json(payload)) 38 | 39 | 40 | def main() -> None: 41 | key = b"lab-key-1" 42 | s1 = "café" 43 | s2 = unicodedata.normalize("NFD", s1) 44 | print("len(s1):", len(s1), "len(s2):", len(s2)) 45 | print("mac(s1):", mac_of_text(key, s1).hex()) 46 | print("mac(s2):", mac_of_text(key, s2).hex()) 47 | 48 | 49 | if __name__ == "__main__": 50 | main() 51 | 52 | -------------------------------------------------------------------------------- /code/compliance/collect_artefacts.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | Crypto Engineering Notes – Compliance 4 | 5 | Collect candidate documentation, tests, and monitoring artefacts from a repo tree. 6 | 7 | © Dr. Yves J. Hilpisch | The Python Quants GmbH 8 | The Crypto Engineer | AI-Powered by GPT 5.1. 9 | """ 10 | 11 | from dataclasses import dataclass 12 | from pathlib import Path 13 | from typing import Iterable, Iterator 14 | 15 | 16 | ROOT = Path(".") 17 | 18 | 19 | @dataclass 20 | class Artefact: 21 | kind: str 22 | path: str 23 | 24 | 25 | def iter_artefacts(root: Path) -> Iterator[Artefact]: 26 | """Yield Artefact records discovered under root.""" 27 | for path in root.rglob("*"): 28 | if not path.is_file(): 29 | continue 30 | rel = path.relative_to(root).as_posix() 31 | if rel.startswith(".git/"): 32 | continue 33 | if rel.endswith((".md", ".rst")): 34 | yield Artefact(kind="doc", path=rel) 35 | elif "tests" in rel and rel.endswith(".py"): 36 | yield Artefact(kind="test", path=rel) 37 | elif "dashboards" in rel or "grafana" in rel: 38 | yield Artefact(kind="dashboard", path=rel) 39 | elif "alerts" in rel or "prometheus" in rel: 40 | yield Artefact(kind="alert", path=rel) 41 | 42 | 43 | def main() -> None: 44 | for art in iter_artefacts(ROOT): 45 | print(art.kind, "->", art.path) 46 | 47 | 48 | if __name__ == "__main__": 49 | main() 50 | 51 | -------------------------------------------------------------------------------- /code/compliance/write_binder_seed.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | Crypto Engineering Notes – Compliance 4 | 5 | Seed a controls evidence binder as CSV from discovered artefacts. 6 | 7 | © Dr. Yves J. Hilpisch | The Python Quants GmbH 8 | The Crypto Engineer | AI-Powered by GPT 5.1. 9 | """ 10 | 11 | import csv 12 | from pathlib import Path 13 | 14 | from code.compliance.collect_artefacts import ROOT, iter_artefacts 15 | 16 | 17 | def write_seed(path: Path) -> None: 18 | """Write a binder.csv skeleton with one row per discovered artefact.""" 19 | headers = [ 20 | "control_id", 21 | "question", 22 | "domain", 23 | "evidence_links", 24 | "owner", 25 | "last_reviewed_at", 26 | ] 27 | with path.open("w", newline="") as handle: 28 | writer = csv.DictWriter(handle, fieldnames=headers) 29 | writer.writeheader() 30 | for idx, art in enumerate(iter_artefacts(ROOT), start=1): 31 | writer.writerow( 32 | { 33 | "control_id": f"C-{idx:04d}", 34 | "question": "", 35 | "domain": "", 36 | "evidence_links": art.path, 37 | "owner": "", 38 | "last_reviewed_at": "", 39 | } 40 | ) 41 | 42 | 43 | def main() -> None: 44 | target = Path("binder.csv") 45 | write_seed(target) 46 | print(f"wrote {target}") 47 | 48 | 49 | if __name__ == "__main__": 50 | main() 51 | 52 | -------------------------------------------------------------------------------- /code/latency/latencies_and_slippage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | Crypto Engineering Notes – Latency 4 | 5 | Per-order latency calculations and a simple top-of-book slippage estimator. 6 | 7 | © Dr. Yves J. Hilpisch | The Python Quants GmbH 8 | The Crypto Engineer | AI-Powered by GPT 5.1. 9 | """ 10 | 11 | import pandas as pd # type: ignore[import-not-found] 12 | 13 | 14 | def load_events(path: str) -> pd.DataFrame: 15 | df = pd.read_csv(path) 16 | df["ts"] = pd.to_datetime(df["ts_ns"], unit="ns", utc=True) 17 | return df 18 | 19 | 20 | def order_latencies(events: pd.DataFrame) -> pd.DataFrame: 21 | sent = events[events["event"] == "sent"].set_index("order_id") 22 | ack = events[events["event"] == "ack"].set_index("order_id") 23 | joined = sent[["venue_id", "ts"]].join( 24 | ack[["ts"]].rename(columns={"ts": "ts_ack"}), 25 | how="inner", 26 | lsuffix="_sent", 27 | ) 28 | joined["latency_ms"] = ( 29 | joined["ts_ack"] - joined["ts_sent"] 30 | ).dt.total_seconds() * 1000 31 | return joined.reset_index() 32 | 33 | 34 | def join_with_tob(latencies: pd.DataFrame, tob: pd.DataFrame) -> pd.DataFrame: 35 | l = latencies.copy() 36 | l = l.set_index("ts_sent").join(tob, how="left").rename( 37 | columns={"mid": "mid_at_send"} 38 | ) 39 | l = l.reset_index().rename(columns={"index": "ts_sent"}) 40 | l = l.set_index("ts_ack").join(tob, how="left").rename( 41 | columns={"best_bid": "best_bid_ack", "best_ask": "best_ask_ack"} 42 | ) 43 | return l.reset_index().rename(columns={"index": "ts_ack"}) 44 | 45 | 46 | def estimate_slippage(lat_tob: pd.DataFrame, side: str) -> pd.Series: 47 | if side == "buy": 48 | exec_price = lat_tob["best_ask_ack"] 49 | else: 50 | exec_price = lat_tob["best_bid_ack"] 51 | ref = lat_tob["mid_at_send"] 52 | return (exec_price - ref) / ref * 10_000 # basis points 53 | 54 | 55 | def main() -> None: 56 | events = load_events("client_events.csv") 57 | lat = order_latencies(events) 58 | print(lat[["order_id", "venue_id", "latency_ms"]].head()) 59 | 60 | 61 | if __name__ == "__main__": 62 | main() 63 | 64 | -------------------------------------------------------------------------------- /code/transaction/transaction_surgery.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | Crypto Engineering Notes – Transaction 4 | 5 | Helpers for selecting UTXOs, inspecting transactions, building templates, 6 | funding/signing, and inspecting the mempool via bitcoin-cli. 7 | 8 | © Dr. Yves J. Hilpisch | The Python Quants GmbH 9 | The Crypto Engineer | AI-Powered by GPT 5.1. 10 | """ 11 | 12 | import json 13 | from typing import Dict, List, Tuple, TypedDict 14 | 15 | from code.transaction.cli_wrapper import cli 16 | 17 | 18 | class UTXO(TypedDict): 19 | txid: str 20 | vout: int 21 | amount: float 22 | address: str 23 | 24 | 25 | def first_utxo() -> UTXO: 26 | utxos: List[Dict] = cli("listunspent", "1", "9999999") 27 | if not utxos: 28 | raise RuntimeError("No UTXOs found; mine a few blocks first.") 29 | u = utxos[0] 30 | return { 31 | "txid": u["txid"], 32 | "vout": u["vout"], 33 | "amount": u["amount"], 34 | "address": u["address"], 35 | } 36 | 37 | 38 | def fetch_tx(txid: str) -> Tuple[str, Dict]: 39 | raw = cli("getrawtransaction", txid) 40 | decoded = cli("decoderawtransaction", raw) 41 | return raw, decoded 42 | 43 | 44 | def build_template(utxo: UTXO, dest_address: str, send_amount: float) -> str: 45 | inputs = [{"txid": utxo["txid"], "vout": utxo["vout"]}] 46 | outputs = {dest_address: send_amount} 47 | return cli("createrawtransaction", json.dumps(inputs), json.dumps(outputs)) 48 | 49 | 50 | def fund_and_sign(raw_hex: str) -> str: 51 | funded = cli("fundrawtransaction", raw_hex) 52 | signed = cli("signrawtransactionwithwallet", funded["hex"]) 53 | if not signed.get("complete", False): 54 | raise RuntimeError("Signing failed") 55 | return signed["hex"] 56 | 57 | 58 | def mempool_snapshot() -> List[str]: 59 | return cli("getrawmempool") 60 | 61 | 62 | def main() -> None: 63 | utxo = first_utxo() 64 | dest = cli("getnewaddress") 65 | template = build_template(utxo, dest, send_amount=0.1) 66 | final = fund_and_sign(template) 67 | txid = cli("sendrawtransaction", final) 68 | print("broadcast txid:", txid) 69 | print("mempool txids:", mempool_snapshot()) 70 | 71 | 72 | if __name__ == "__main__": 73 | main() 74 | 75 | -------------------------------------------------------------------------------- /code/system/version_snapshots.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | Crypto Engineering Notes – System / Upgrade 4 | 5 | Capture and diff node version snapshots around upgrades. 6 | 7 | © Dr. Yves J. Hilpisch | The Python Quants GmbH 8 | The Crypto Engineer | AI-Powered by GPT 5.1. 9 | """ 10 | 11 | import json 12 | import subprocess 13 | from dataclasses import dataclass 14 | from pathlib import Path 15 | from typing import Dict, List 16 | 17 | 18 | @dataclass 19 | class NodeVersion: 20 | name: str 21 | command: List[str] 22 | 23 | 24 | NODES: List[NodeVersion] = [ 25 | NodeVersion( 26 | name="bitcoin", command=["bitcoin-cli", "-regtest", "getnetworkinfo"] 27 | ), 28 | ] 29 | 30 | 31 | def capture_versions() -> Dict[str, str]: 32 | versions: Dict[str, str] = {} 33 | for node in NODES: 34 | try: 35 | raw = subprocess.check_output(node.command, text=True) 36 | except Exception as exc: 37 | versions[node.name] = f"error: {exc}" 38 | continue 39 | try: 40 | info = json.loads(raw) 41 | versions[node.name] = info.get("subversion", "").strip("/") 42 | except json.JSONDecodeError: 43 | versions[node.name] = raw.strip() 44 | return versions 45 | 46 | 47 | def write_snapshot(path: Path) -> None: 48 | data = { 49 | "versions": capture_versions(), 50 | } 51 | path.write_text(json.dumps(data, indent=2)) 52 | 53 | 54 | def load_versions(path: Path) -> Dict[str, str]: 55 | data = json.loads(path.read_text()) 56 | return data.get("versions", {}) 57 | 58 | 59 | def diff_versions(before: Path, after: Path) -> None: 60 | v_before = load_versions(before) 61 | v_after = load_versions(after) 62 | all_names = sorted(set(v_before) | set(v_after)) 63 | for name in all_names: 64 | old = v_before.get(name, "") 65 | new = v_after.get(name, "") 66 | marker = "OK" if old == new else "CHANGED" 67 | print(f"{name:10} {old:20} -> {new:20} [{marker}]") 68 | 69 | 70 | def main() -> None: 71 | snapshot_path = Path("upgrade_snapshot.json") 72 | write_snapshot(snapshot_path) 73 | print(f"wrote snapshot to {snapshot_path}") 74 | 75 | 76 | if __name__ == "__main__": 77 | main() 78 | 79 | -------------------------------------------------------------------------------- /notebooks/4_book/chapter06_observability_and_metric_design.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "\"The
\n" 8 | ] 9 | }, 10 | { 11 | "cell_type": "markdown", 12 | "metadata": {}, 13 | "source": [ 14 | "# Crypto Engineering\n", 15 | "## Chapter 6 – Observability and Metric Design\n", 16 | "\n", 17 | "© Dr. Yves J. Hilpisch | The Python Quants GmbH
\n", 18 | "The Crypto Engineer | AI-Powered by GPT 5.1.\n" 19 | ] 20 | }, 21 | { 22 | "cell_type": "markdown", 23 | "metadata": {}, 24 | "source": [ 25 | "This helper finds a “metric aggregator” configuration in a small in-memory registry. It reflects the chapter’s encouragement to keep metric definitions and ownership centralised so you can audit which dashboards and alerts depend on which signals." 26 | ] 27 | }, 28 | { 29 | "cell_type": "code", 30 | "execution_count": null, 31 | "metadata": { 32 | "trusted": true 33 | }, 34 | "outputs": [], 35 | "source": [ 36 | "metrics = [\n", 37 | " {\"name\": \"wallet.sign.latency\", \"owner\": \"wallet_team\", \"agg\": \"p95\"},\n", 38 | " {\"name\": \"exchange.depth.btcusd\", \"owner\": \"markets\", \"agg\": \"min\"},\n", 39 | "]\n", 40 | "\n", 41 | "target = \"wallet.sign.latency\"\n", 42 | "for m in metrics:\n", 43 | " if m[\"name\"] == target:\n", 44 | " print(\"Aggregator for\", target, \"is\", m[\"agg\"])\n", 45 | " break\n" 46 | ] 47 | }, 48 | { 49 | "cell_type": "markdown", 50 | "metadata": {}, 51 | "source": [ 52 | "\"The
\n" 53 | ] 54 | } 55 | ], 56 | "metadata": { 57 | "colab": { 58 | "name": "Crypto Engineering – Chapter 6 – Observability and Metric Design", 59 | "provenance": [] 60 | }, 61 | "kernelspec": { 62 | "display_name": "Python 3", 63 | "language": "python", 64 | "name": "python3" 65 | }, 66 | "language_info": { 67 | "name": "python", 68 | "pygments_lexer": "ipython3" 69 | } 70 | }, 71 | "nbformat": 4, 72 | "nbformat_minor": 5 73 | } 74 | 75 | -------------------------------------------------------------------------------- /notebooks/4_book/chapter04_code_review_and_safety_linters.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "\"The
\n" 8 | ] 9 | }, 10 | { 11 | "cell_type": "markdown", 12 | "metadata": {}, 13 | "source": [ 14 | "# Crypto Engineering\n", 15 | "## Chapter 4 – Code Review and Safety Linters\n", 16 | "\n", 17 | "© Dr. Yves J. Hilpisch | The Python Quants GmbH
\n", 18 | "The Crypto Engineer | AI-Powered by GPT 5.1.\n" 19 | ] 20 | }, 21 | { 22 | "cell_type": "markdown", 23 | "metadata": {}, 24 | "source": [ 25 | "This notebook sketches a very small “crypto-safety linter” that scans source lines for red-flag patterns such as hard-coded keys or insecure randomness. It shows how you can start turning qualitative review checklists into repeatable, automated checks." 26 | ] 27 | }, 28 | { 29 | "cell_type": "code", 30 | "execution_count": null, 31 | "metadata": { 32 | "trusted": true 33 | }, 34 | "outputs": [], 35 | "source": [ 36 | "source_lines = [\n", 37 | " \"SECRET_KEY = 'abc123'\",\n", 38 | " \"nonce = random.random()\",\n", 39 | " \"token = secrets.token_hex(16)\",\n", 40 | "]\n", 41 | "\n", 42 | "bad_patterns = [\"SECRET_KEY\", \"random.random()\"]\n", 43 | "\n", 44 | "for i, line in enumerate(source_lines, start=1):\n", 45 | " for pat in bad_patterns:\n", 46 | " if pat in line:\n", 47 | " print(f\"Line {i}: potential issue -> {line.strip()}\")\n" 48 | ] 49 | }, 50 | { 51 | "cell_type": "markdown", 52 | "metadata": {}, 53 | "source": [ 54 | "\"The
\n" 55 | ] 56 | } 57 | ], 58 | "metadata": { 59 | "colab": { 60 | "name": "Crypto Engineering – Chapter 4 – Code Review and Safety Linters", 61 | "provenance": [] 62 | }, 63 | "kernelspec": { 64 | "display_name": "Python 3", 65 | "language": "python", 66 | "name": "python3" 67 | }, 68 | "language_info": { 69 | "name": "python", 70 | "pygments_lexer": "ipython3" 71 | } 72 | }, 73 | "nbformat": 4, 74 | "nbformat_minor": 5 75 | } 76 | 77 | -------------------------------------------------------------------------------- /notebooks/4_book/chapter11_data_management_and_reproducibility.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "\"The
\n" 8 | ] 9 | }, 10 | { 11 | "cell_type": "markdown", 12 | "metadata": {}, 13 | "source": [ 14 | "# Crypto Engineering\n", 15 | "## Chapter 11 – Data Management and Reproducibility\n", 16 | "\n", 17 | "© Dr. Yves J. Hilpisch | The Python Quants GmbH
\n", 18 | "The Crypto Engineer | AI-Powered by GPT 5.1.\n" 19 | ] 20 | }, 21 | { 22 | "cell_type": "markdown", 23 | "metadata": {}, 24 | "source": [ 25 | "This notebook validates a tiny dataset manifest: it checks required keys and prints any missing fields. That pattern scales to larger data catalogues and embodies the chapter’s push for reproducible, well-documented datasets feeding your crypto experiments." 26 | ] 27 | }, 28 | { 29 | "cell_type": "code", 30 | "execution_count": null, 31 | "metadata": { 32 | "trusted": true 33 | }, 34 | "outputs": [], 35 | "source": [ 36 | "manifest = {\n", 37 | " \"name\": \"wallet_sign_events\",\n", 38 | " \"path\": \"data/wallet_sign_events.csv\",\n", 39 | " \"owner\": \"analytics\",\n", 40 | "}\n", 41 | "\n", 42 | "required_keys = {\"name\", \"path\", \"owner\"}\n", 43 | "missing = required_keys - manifest.keys()\n", 44 | "if missing:\n", 45 | " print(\"Missing required fields:\", missing)\n", 46 | "else:\n", 47 | " print(\"Manifest OK:\", manifest)\n" 48 | ] 49 | }, 50 | { 51 | "cell_type": "markdown", 52 | "metadata": {}, 53 | "source": [ 54 | "\"The
\n" 55 | ] 56 | } 57 | ], 58 | "metadata": { 59 | "colab": { 60 | "name": "Crypto Engineering – Chapter 11 – Data Management and Reproducibility", 61 | "provenance": [] 62 | }, 63 | "kernelspec": { 64 | "display_name": "Python 3", 65 | "language": "python", 66 | "name": "python3" 67 | }, 68 | "language_info": { 69 | "name": "python", 70 | "pygments_lexer": "ipython3" 71 | } 72 | }, 73 | "nbformat": 4, 74 | "nbformat_minor": 5 75 | } 76 | 77 | -------------------------------------------------------------------------------- /code/policy/fee_policy.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | Crypto Engineering Notes – Fee Policy 4 | 5 | Fee estimation wrapper and urgency-band-based fee suggestion helper. 6 | 7 | © Dr. Yves J. Hilpisch | The Python Quants GmbH 8 | The Crypto Engineer | AI-Powered by GPT 5.1. 9 | """ 10 | 11 | import json 12 | import subprocess 13 | from dataclasses import dataclass 14 | 15 | 16 | def cli(*args: str): 17 | """Call `bitcoin-cli -regtest` and parse JSON when possible.""" 18 | out = subprocess.check_output( 19 | ["bitcoin-cli", "-regtest", *args], 20 | text=True, 21 | ) 22 | try: 23 | return json.loads(out) 24 | except json.JSONDecodeError: 25 | return out.strip() 26 | 27 | 28 | def mempool_info() -> dict: 29 | return cli("getmempoolinfo") 30 | 31 | 32 | def smart_fee(conf_target: int) -> float | None: 33 | """Return estimated fee rate in sat/vByte for a given confirmation target. 34 | 35 | Returns None when the node lacks enough data. 36 | """ 37 | res = cli("estimatesmartfee", str(conf_target)) 38 | feerate_btc_per_kvbyte = res.get("feerate") 39 | if feerate_btc_per_kvbyte is None: 40 | return None 41 | return feerate_btc_per_kvbyte * 1e8 / 1000.0 42 | 43 | 44 | @dataclass 45 | class FeeBand: 46 | conf_target: int 47 | max_sat_vbyte: float 48 | 49 | 50 | POLICY = { 51 | "low": FeeBand(conf_target=24, max_sat_vbyte=5.0), 52 | "normal": FeeBand(conf_target=6, max_sat_vbyte=25.0), 53 | "high": FeeBand(conf_target=2, max_sat_vbyte=100.0), 54 | } 55 | 56 | 57 | def suggest_fee(band_name: str) -> tuple[float, str]: 58 | band = POLICY[band_name] 59 | est = smart_fee(band.conf_target) 60 | info = mempool_info() 61 | floor = info.get("mempoolminfee", 0.0) * 1e8 / 1000.0 # BTC/kvB -> sat/vByte 62 | 63 | if est is None: 64 | rate = max(floor, 1.0) 65 | reason = "no estimate available; using floor with 1 sat/vByte minimum" 66 | else: 67 | rate = max(floor, est) 68 | reason = ( 69 | f"estimatesmartfee for {band.conf_target} blocks, floored at mempool minimum" 70 | ) 71 | 72 | if rate > band.max_sat_vbyte: 73 | rate = band.max_sat_vbyte 74 | reason += f" and capped at policy maximum for {band_name} urgency" 75 | 76 | return rate, reason 77 | 78 | 79 | def main() -> None: 80 | for name in ("low", "normal", "high"): 81 | rate, why = suggest_fee(name) 82 | print(f"{name:6} -> {rate:5.1f} sat/vByte ({why})") 83 | 84 | 85 | if __name__ == "__main__": 86 | main() 87 | 88 | -------------------------------------------------------------------------------- /notebooks/4_book/chapter10_controls_monitoring_and_runbooks.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "\"The
\n" 8 | ] 9 | }, 10 | { 11 | "cell_type": "markdown", 12 | "metadata": {}, 13 | "source": [ 14 | "# Crypto Engineering\n", 15 | "## Chapter 10 – Controls, Monitoring, and Runbooks\n", 16 | "\n", 17 | "© Dr. Yves J. Hilpisch | The Python Quants GmbH
\n", 18 | "The Crypto Engineer | AI-Powered by GPT 5.1.\n" 19 | ] 20 | }, 21 | { 22 | "cell_type": "markdown", 23 | "metadata": {}, 24 | "source": [ 25 | "This helper builds a small manifest of controls, metrics, and runbooks in memory and prints it in a readable way. It shows how to keep the relationships between controls and evidence explicit, just as the chapter’s \"control/monitoring manifest\" template suggests." 26 | ] 27 | }, 28 | { 29 | "cell_type": "code", 30 | "execution_count": null, 31 | "metadata": { 32 | "trusted": true 33 | }, 34 | "outputs": [], 35 | "source": [ 36 | "manifest = {\n", 37 | " \"controls\": [\n", 38 | " {\"id\": \"c_sign_latency\", \"metric\": \"wallet.sign.latency\", \"runbook\": \"runbooks/sign_latency.md\"},\n", 39 | " {\"id\": \"c_queue_depth\", \"metric\": \"wallet.queue.depth\", \"runbook\": \"runbooks/queue_depth.md\"},\n", 40 | " ]\n", 41 | "}\n", 42 | "\n", 43 | "for c in manifest[\"controls\"]:\n", 44 | " print(f\"Control {c['id']}: metric={c['metric']}, runbook={c['runbook']}\")\n" 45 | ] 46 | }, 47 | { 48 | "cell_type": "markdown", 49 | "metadata": {}, 50 | "source": [ 51 | "\"The
\n" 52 | ] 53 | } 54 | ], 55 | "metadata": { 56 | "colab": { 57 | "name": "Crypto Engineering – Chapter 10 – Controls, Monitoring, and Runbooks", 58 | "provenance": [] 59 | }, 60 | "kernelspec": { 61 | "display_name": "Python 3", 62 | "language": "python", 63 | "name": "python3" 64 | }, 65 | "language_info": { 66 | "name": "python", 67 | "pygments_lexer": "ipython3" 68 | } 69 | }, 70 | "nbformat": 4, 71 | "nbformat_minor": 5 72 | } 73 | 74 | -------------------------------------------------------------------------------- /notebooks/4_book/chapter12_change_management_and_upgrade_readiness.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "\"The
\n" 8 | ] 9 | }, 10 | { 11 | "cell_type": "markdown", 12 | "metadata": {}, 13 | "source": [ 14 | "# Crypto Engineering\n", 15 | "## Chapter 12 – Change Management and Upgrade Readiness\n", 16 | "\n", 17 | "© Dr. Yves J. Hilpisch | The Python Quants GmbH
\n", 18 | "The Crypto Engineer | AI-Powered by GPT 5.1.\n" 19 | ] 20 | }, 21 | { 22 | "cell_type": "markdown", 23 | "metadata": {}, 24 | "source": [ 25 | "This final lab encodes a short upgrade-readiness checklist in a dictionary and prints whether a hypothetical service is \"ready\" to roll. It gives you a tangible pattern for capturing pre-flight checks—tests, monitoring, runbooks—in a form that’s easy to wire into CI or deployment tooling." 26 | ] 27 | }, 28 | { 29 | "cell_type": "code", 30 | "execution_count": null, 31 | "metadata": { 32 | "trusted": true 33 | }, 34 | "outputs": [], 35 | "source": [ 36 | "service = {\n", 37 | " \"name\": \"wallet-api\",\n", 38 | " \"tests_green\": True,\n", 39 | " \"monitoring_ready\": True,\n", 40 | " \"runbooks_updated\": False,\n", 41 | "}\n", 42 | "\n", 43 | "ready = all(service.values() - {service[\"name\"]}) if False else (\n", 44 | " service[\"tests_green\"] and service[\"monitoring_ready\"] and service[\"runbooks_updated\"]\n", 45 | ")\n", 46 | "print(\"Upgrade ready?\", ready)\n" 47 | ] 48 | }, 49 | { 50 | "cell_type": "markdown", 51 | "metadata": {}, 52 | "source": [ 53 | "\"The
\n" 54 | ] 55 | } 56 | ], 57 | "metadata": { 58 | "colab": { 59 | "name": "Crypto Engineering – Chapter 12 – Change Management and Upgrade Readiness", 60 | "provenance": [] 61 | }, 62 | "kernelspec": { 63 | "display_name": "Python 3", 64 | "language": "python", 65 | "name": "python3" 66 | }, 67 | "language_info": { 68 | "name": "python", 69 | "pygments_lexer": "ipython3" 70 | } 71 | }, 72 | "nbformat": 4, 73 | "nbformat_minor": 5 74 | } 75 | 76 | -------------------------------------------------------------------------------- /notebooks/3_book/appendixB_additional_risk_and_scenario_patterns.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "\"The
\n" 8 | ] 9 | }, 10 | { 11 | "cell_type": "markdown", 12 | "metadata": {}, 13 | "source": [ 14 | "# Cryptomarkets and Systems\n", 15 | "## Appendix B – Additional Risk and Scenario Patterns\n", 16 | "\n", 17 | "© Dr. Yves J. Hilpisch | The Python Quants GmbH
\n", 18 | "The Crypto Engineer | AI-Powered by GPT 5.1.\n" 19 | ] 20 | }, 21 | { 22 | "cell_type": "markdown", 23 | "metadata": {}, 24 | "source": [ 25 | "This appendix does not define specific code, but you can still practice by encoding a tiny \"risk memo\" structure as nested dictionaries—system, scenario, checklist items—and printing it. That keeps incident and scenario thinking close to code, just as the text encourages." 26 | ] 27 | }, 28 | { 29 | "cell_type": "code", 30 | "execution_count": null, 31 | "metadata": { 32 | "trusted": true 33 | }, 34 | "outputs": [], 35 | "source": [ 36 | "risk_memo = {\n", 37 | " \"system\": \"example_cex_integration\",\n", 38 | " \"scenario\": \"exchange withdrawal freeze\",\n", 39 | " \"checks\": [\n", 40 | " \"max exposure per venue defined\",\n", 41 | " \"read-only fallback mode documented\",\n", 42 | " \"proof-of-reserves usage understood\",\n", 43 | " ],\n", 44 | "}\n", 45 | "\n", 46 | "for k, v in risk_memo.items():\n", 47 | " print(f\"{k.upper()}:\", v)\n" 48 | ] 49 | }, 50 | { 51 | "cell_type": "markdown", 52 | "metadata": {}, 53 | "source": [ 54 | "\"The
\n" 55 | ] 56 | } 57 | ], 58 | "metadata": { 59 | "colab": { 60 | "name": "Cryptomarkets and Systems – Appendix B – Additional Risk and Scenario Patterns", 61 | "provenance": [] 62 | }, 63 | "kernelspec": { 64 | "display_name": "Python 3", 65 | "language": "python", 66 | "name": "python3" 67 | }, 68 | "language_info": { 69 | "name": "python", 70 | "pygments_lexer": "ipython3" 71 | } 72 | }, 73 | "nbformat": 4, 74 | "nbformat_minor": 5 75 | } 76 | 77 | -------------------------------------------------------------------------------- /notebooks/4_book/chapter07_monitoring_dashboards_and_freshness.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "\"The
\n" 8 | ] 9 | }, 10 | { 11 | "cell_type": "markdown", 12 | "metadata": {}, 13 | "source": [ 14 | "# Crypto Engineering\n", 15 | "## Chapter 7 – Monitoring Dashboards and Freshness\n", 16 | "\n", 17 | "© Dr. Yves J. Hilpisch | The Python Quants GmbH
\n", 18 | "The Crypto Engineer | AI-Powered by GPT 5.1.\n" 19 | ] 20 | }, 21 | { 22 | "cell_type": "markdown", 23 | "metadata": {}, 24 | "source": [ 25 | "This snippet encodes a few metrics with last-update timestamps and flags any that look stale relative to a freshness budget. It captures the chapter’s idea that monitoring quality depends not just on what you measure but on how fresh and trustworthy those measurements are." 26 | ] 27 | }, 28 | { 29 | "cell_type": "code", 30 | "execution_count": null, 31 | "metadata": { 32 | "trusted": true 33 | }, 34 | "outputs": [], 35 | "source": [ 36 | "from datetime import datetime, timedelta\n", 37 | "\n", 38 | "now = datetime.utcnow()\n", 39 | "metrics = [\n", 40 | " {\"name\": \"wallet.sign.latency\", \"last\": now - timedelta(seconds=30)},\n", 41 | " {\"name\": \"exchange.orderbook.depth\", \"last\": now - timedelta(minutes=10)},\n", 42 | "]\n", 43 | "\n", 44 | "budget = timedelta(minutes=5)\n", 45 | "for m in metrics:\n", 46 | " age = now - m[\"last\"]\n", 47 | " if age > budget:\n", 48 | " print(\"Stale metric:\", m[\"name\"], \"age\", age)\n" 49 | ] 50 | }, 51 | { 52 | "cell_type": "markdown", 53 | "metadata": {}, 54 | "source": [ 55 | "\"The
\n" 56 | ] 57 | } 58 | ], 59 | "metadata": { 60 | "colab": { 61 | "name": "Crypto Engineering – Chapter 7 – Monitoring Dashboards and Freshness", 62 | "provenance": [] 63 | }, 64 | "kernelspec": { 65 | "display_name": "Python 3", 66 | "language": "python", 67 | "name": "python3" 68 | }, 69 | "language_info": { 70 | "name": "python", 71 | "pygments_lexer": "ipython3" 72 | } 73 | }, 74 | "nbformat": 4, 75 | "nbformat_minor": 5 76 | } 77 | 78 | -------------------------------------------------------------------------------- /notebooks/3_book/chapter06_leverage_liquidations_and_risk_limits.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "\"The
\n" 8 | ] 9 | }, 10 | { 11 | "cell_type": "markdown", 12 | "metadata": {}, 13 | "source": [ 14 | "# Cryptomarkets and Systems\n", 15 | "## Chapter 6 – Leverage, Liquidations, and Risk Limits\n", 16 | "\n", 17 | "© Dr. Yves J. Hilpisch | The Python Quants GmbH
\n", 18 | "The Crypto Engineer | AI-Powered by GPT 5.1.\n" 19 | ] 20 | }, 21 | { 22 | "cell_type": "markdown", 23 | "metadata": {}, 24 | "source": [ 25 | "This cell explores a toy liquidation threshold: given collateral, debt, and a maintenance margin, you compute the critical price at which a leveraged position is at risk. It turns the chapter’s formulas into a small, tweakable calculator you can play with directly in a notebook." 26 | ] 27 | }, 28 | { 29 | "cell_type": "code", 30 | "execution_count": null, 31 | "metadata": { 32 | "trusted": true 33 | }, 34 | "outputs": [], 35 | "source": [ 36 | "collateral_value = 10_000.0 # current USD value of collateral\n", 37 | "debt = 5_000.0 # borrowed amount\n", 38 | "maintenance_margin = 1.3 # collateral/debt ratio required\n", 39 | "\n", 40 | "# Critical collateral value where collateral/debt == maintenance_margin\n", 41 | "critical_collateral = maintenance_margin * debt\n", 42 | "drawdown_needed = 1 - critical_collateral / collateral_value\n", 43 | "\n", 44 | "print(\"Critical collateral value:\", critical_collateral)\n", 45 | "print(\"Drawdown until risk (fraction):\", round(drawdown_needed, 4))\n" 46 | ] 47 | }, 48 | { 49 | "cell_type": "markdown", 50 | "metadata": {}, 51 | "source": [ 52 | "\"The
\n" 53 | ] 54 | } 55 | ], 56 | "metadata": { 57 | "colab": { 58 | "name": "Cryptomarkets and Systems – Chapter 6 – Leverage, Liquidations, and Risk Limits", 59 | "provenance": [] 60 | }, 61 | "kernelspec": { 62 | "display_name": "Python 3", 63 | "language": "python", 64 | "name": "python3" 65 | }, 66 | "language_info": { 67 | "name": "python", 68 | "pygments_lexer": "ipython3" 69 | } 70 | }, 71 | "nbformat": 4, 72 | "nbformat_minor": 5 73 | } 74 | 75 | -------------------------------------------------------------------------------- /notebooks/4_book/chapter05_testing_strategies_and_ci_gates.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "\"The
\n" 8 | ] 9 | }, 10 | { 11 | "cell_type": "markdown", 12 | "metadata": {}, 13 | "source": [ 14 | "# Crypto Engineering\n", 15 | "## Chapter 5 – Testing Strategies and CI Gates\n", 16 | "\n", 17 | "© Dr. Yves J. Hilpisch | The Python Quants GmbH
\n", 18 | "The Crypto Engineer | AI-Powered by GPT 5.1.\n" 19 | ] 20 | }, 21 | { 22 | "cell_type": "markdown", 23 | "metadata": {}, 24 | "source": [ 25 | "This lab simulates a miniature CI gate: given a list of recent tests and their outcomes, it decides whether to allow a crypto-sensitive change to merge. It highlights how to encode “must-pass” suites as data rather than tribal knowledge." 26 | ] 27 | }, 28 | { 29 | "cell_type": "code", 30 | "execution_count": null, 31 | "metadata": { 32 | "trusted": true 33 | }, 34 | "outputs": [], 35 | "source": [ 36 | "tests = [\n", 37 | " {\"name\": \"test_aead_roundtrip\", \"status\": \"pass\"},\n", 38 | " {\"name\": \"test_kdf_parameters\", \"status\": \"pass\"},\n", 39 | " {\"name\": \"test_randomness_source\", \"status\": \"pass\"},\n", 40 | "]\n", 41 | "\n", 42 | "required = {\"test_aead_roundtrip\", \"test_kdf_parameters\"}\n", 43 | "passed = {t[\"name\"] for t in tests if t[\"status\"] == \"pass\"}\n", 44 | "\n", 45 | "missing = required - passed\n", 46 | "if missing:\n", 47 | " print(\"CI gate FAILED; missing:\", missing)\n", 48 | "else:\n", 49 | " print(\"CI gate PASSED; all required crypto tests green.\")\n" 50 | ] 51 | }, 52 | { 53 | "cell_type": "markdown", 54 | "metadata": {}, 55 | "source": [ 56 | "\"The
\n" 57 | ] 58 | } 59 | ], 60 | "metadata": { 61 | "colab": { 62 | "name": "Crypto Engineering – Chapter 5 – Testing Strategies and CI Gates", 63 | "provenance": [] 64 | }, 65 | "kernelspec": { 66 | "display_name": "Python 3", 67 | "language": "python", 68 | "name": "python3" 69 | }, 70 | "language_info": { 71 | "name": "python", 72 | "pygments_lexer": "ipython3" 73 | } 74 | }, 75 | "nbformat": 4, 76 | "nbformat_minor": 5 77 | } 78 | 79 | -------------------------------------------------------------------------------- /notebooks/4_book/chapter03_architecture_and_trust_boundaries.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "\"The
\n" 8 | ] 9 | }, 10 | { 11 | "cell_type": "markdown", 12 | "metadata": {}, 13 | "source": [ 14 | "# Crypto Engineering\n", 15 | "## Chapter 3 – Architecture and Trust Boundaries\n", 16 | "\n", 17 | "© Dr. Yves J. Hilpisch | The Python Quants GmbH
\n", 18 | "The Crypto Engineer | AI-Powered by GPT 5.1.\n" 19 | ] 20 | }, 21 | { 22 | "cell_type": "markdown", 23 | "metadata": {}, 24 | "source": [ 25 | "This lab encodes a minimal list of services and the trust boundaries between them, then prints where sensitive assets cross contexts. It turns the chapter’s call for explicit boundary mapping into a concrete data structure you can diff and extend with your own systems." 26 | ] 27 | }, 28 | { 29 | "cell_type": "code", 30 | "execution_count": null, 31 | "metadata": { 32 | "trusted": true 33 | }, 34 | "outputs": [], 35 | "source": [ 36 | "services = [\n", 37 | " {\"name\": \"api_gateway\", \"trust_zone\": \"edge\", \"assets\": [\"access_tokens\"]},\n", 38 | " {\"name\": \"wallet_backends\", \"trust_zone\": \"core\", \"assets\": [\"signing_keys\"]},\n", 39 | " {\"name\": \"analytics\", \"trust_zone\": \"analytics\", \"assets\": [\"aggregated_metrics\"]},\n", 40 | "]\n", 41 | "\n", 42 | "boundaries = [\n", 43 | " (\"edge\", \"core\"),\n", 44 | " (\"core\", \"analytics\"),\n", 45 | "]\n", 46 | "\n", 47 | "print(\"Trust boundaries:\")\n", 48 | "for src, dst in boundaries:\n", 49 | " print(f\" {src} -> {dst}\")\n" 50 | ] 51 | }, 52 | { 53 | "cell_type": "markdown", 54 | "metadata": {}, 55 | "source": [ 56 | "\"The
\n" 57 | ] 58 | } 59 | ], 60 | "metadata": { 61 | "colab": { 62 | "name": "Crypto Engineering – Chapter 3 – Architecture and Trust Boundaries", 63 | "provenance": [] 64 | }, 65 | "kernelspec": { 66 | "display_name": "Python 3", 67 | "language": "python", 68 | "name": "python3" 69 | }, 70 | "language_info": { 71 | "name": "python", 72 | "pygments_lexer": "ipython3" 73 | } 74 | }, 75 | "nbformat": 4, 76 | "nbformat_minor": 5 77 | } 78 | 79 | -------------------------------------------------------------------------------- /notebooks/3_book/chapter04_returns_volatility_and_drawdowns.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "\"The
\n" 8 | ] 9 | }, 10 | { 11 | "cell_type": "markdown", 12 | "metadata": {}, 13 | "source": [ 14 | "# Cryptomarkets and Systems\n", 15 | "## Chapter 4 – Returns, Volatility, and Drawdowns\n", 16 | "\n", 17 | "© Dr. Yves J. Hilpisch | The Python Quants GmbH
\n", 18 | "The Crypto Engineer | AI-Powered by GPT 5.1.\n" 19 | ] 20 | }, 21 | { 22 | "cell_type": "markdown", 23 | "metadata": {}, 24 | "source": [ 25 | "This lab takes a small price series and computes simple returns, volatility, and rolling-window statistics by hand using only standard-library tools. It reinforces the chapter’s core point: even without heavy libraries, you can get a feel for risk by working carefully with well-labelled price and return arrays." 26 | ] 27 | }, 28 | { 29 | "cell_type": "code", 30 | "execution_count": null, 31 | "metadata": { 32 | "trusted": true 33 | }, 34 | "outputs": [], 35 | "source": [ 36 | "prices = [100, 102, 101, 105, 107] # toy closing prices\n", 37 | "\n", 38 | "returns = []\n", 39 | "for i in range(1, len(prices)):\n", 40 | " r = (prices[i] / prices[i - 1]) - 1.0\n", 41 | " returns.append(r)\n", 42 | "\n", 43 | "print(\"Prices:\", prices)\n", 44 | "print(\"Returns:\", [round(r, 4) for r in returns])\n", 45 | "\n", 46 | "# Compute simple (unbiased) sample volatility of returns\n", 47 | "mean_r = sum(returns) / len(returns)\n", 48 | "var_r = sum((r - mean_r) ** 2 for r in returns) / (len(returns) - 1)\n", 49 | "vol = var_r ** 0.5\n", 50 | "print(\"Volatility (per period):\", round(vol, 4))\n" 51 | ] 52 | }, 53 | { 54 | "cell_type": "markdown", 55 | "metadata": {}, 56 | "source": [ 57 | "\"The
\n" 58 | ] 59 | } 60 | ], 61 | "metadata": { 62 | "colab": { 63 | "name": "Cryptomarkets and Systems – Chapter 4 – Returns, Volatility, and Drawdowns", 64 | "provenance": [] 65 | }, 66 | "kernelspec": { 67 | "display_name": "Python 3", 68 | "language": "python", 69 | "name": "python3" 70 | }, 71 | "language_info": { 72 | "name": "python", 73 | "pygments_lexer": "ipython3" 74 | } 75 | }, 76 | "nbformat": 4, 77 | "nbformat_minor": 5 78 | } 79 | 80 | -------------------------------------------------------------------------------- /notebooks/3_book/appendixC_extended_checklists_for_builders.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "\"The
\n" 8 | ] 9 | }, 10 | { 11 | "cell_type": "markdown", 12 | "metadata": {}, 13 | "source": [ 14 | "# Cryptomarkets and Systems\n", 15 | "## Appendix C – Extended Checklists for Builders\n", 16 | "\n", 17 | "© Dr. Yves J. Hilpisch | The Python Quants GmbH
\n", 18 | "The Crypto Engineer | AI-Powered by GPT 5.1.\n" 19 | ] 20 | }, 21 | { 22 | "cell_type": "markdown", 23 | "metadata": {}, 24 | "source": [ 25 | "To turn the text-only checklists into something you can run, this snippet models a short list of \"exchange-integration\" checks and prints out any that are still marked incomplete. It follows the same pattern as other checklist notebooks in this project, keeping operational habits close to your code." 26 | ] 27 | }, 28 | { 29 | "cell_type": "code", 30 | "execution_count": null, 31 | "metadata": { 32 | "trusted": true 33 | }, 34 | "outputs": [], 35 | "source": [ 36 | "from dataclasses import dataclass\n", 37 | "from typing import List\n", 38 | "\n", 39 | "\n", 40 | "@dataclass\n", 41 | "class Check:\n", 42 | " description: str\n", 43 | " done: bool = False\n", 44 | "\n", 45 | "\n", 46 | "checks: List[Check] = [\n", 47 | " Check(\"Read-only vs trading API keys separated\"),\n", 48 | " Check(\"Withdrawal limits configured and documented\"),\n", 49 | " Check(\"Test environment clearly distinguished from production\"),\n", 50 | "]\n", 51 | "\n", 52 | "print(\"Exchange integration checklist (incomplete):\")\n", 53 | "for c in checks:\n", 54 | " if not c.done:\n", 55 | " print(\" -\", c.description)\n" 56 | ] 57 | }, 58 | { 59 | "cell_type": "markdown", 60 | "metadata": {}, 61 | "source": [ 62 | "\"The
\n" 63 | ] 64 | } 65 | ], 66 | "metadata": { 67 | "colab": { 68 | "name": "Cryptomarkets and Systems – Appendix C – Extended Checklists for Builders", 69 | "provenance": [] 70 | }, 71 | "kernelspec": { 72 | "display_name": "Python 3", 73 | "language": "python", 74 | "name": "python3" 75 | }, 76 | "language_info": { 77 | "name": "python", 78 | "pygments_lexer": "ipython3" 79 | } 80 | }, 81 | "nbformat": 4, 82 | "nbformat_minor": 5 83 | } 84 | 85 | -------------------------------------------------------------------------------- /notebooks/3_book/chapter08_portfolios_and_risk_metrics.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "\"The
\n" 8 | ] 9 | }, 10 | { 11 | "cell_type": "markdown", 12 | "metadata": {}, 13 | "source": [ 14 | "# Cryptomarkets and Systems\n", 15 | "## Chapter 8 – Portfolios and Risk Metrics\n", 16 | "\n", 17 | "© Dr. Yves J. Hilpisch | The Python Quants GmbH
\n", 18 | "The Crypto Engineer | AI-Powered by GPT 5.1.\n" 19 | ] 20 | }, 21 | { 22 | "cell_type": "markdown", 23 | "metadata": {}, 24 | "source": [ 25 | "This lab works with a toy table of asset returns and a vector of portfolio weights. You compute portfolio returns and a basic volatility estimate, echoing the chapter’s emphasis on building an intuition for diversification and risk before reaching for heavier quantitative toolkits." 26 | ] 27 | }, 28 | { 29 | "cell_type": "code", 30 | "execution_count": null, 31 | "metadata": { 32 | "trusted": true 33 | }, 34 | "outputs": [], 35 | "source": [ 36 | "# rows: time, columns: assets A, B\n", 37 | "returns_A = [0.01, -0.02, 0.015, 0.0]\n", 38 | "returns_B = [0.005, 0.01, -0.005, 0.02]\n", 39 | "weights = [0.6, 0.4]\n", 40 | "\n", 41 | "portfolio_returns = []\n", 42 | "for ra, rb in zip(returns_A, returns_B):\n", 43 | " rp = weights[0] * ra + weights[1] * rb\n", 44 | " portfolio_returns.append(rp)\n", 45 | "\n", 46 | "mean_rp = sum(portfolio_returns) / len(portfolio_returns)\n", 47 | "var_rp = sum((r - mean_rp) ** 2 for r in portfolio_returns) / (len(portfolio_returns) - 1)\n", 48 | "vol_p = var_rp ** 0.5\n", 49 | "\n", 50 | "print(\"Portfolio returns:\", [round(r, 4) for r in portfolio_returns])\n", 51 | "print(\"Mean return:\", round(mean_rp, 4))\n", 52 | "print(\"Volatility:\", round(vol_p, 4))\n" 53 | ] 54 | }, 55 | { 56 | "cell_type": "markdown", 57 | "metadata": {}, 58 | "source": [ 59 | "\"The
\n" 60 | ] 61 | } 62 | ], 63 | "metadata": { 64 | "colab": { 65 | "name": "Cryptomarkets and Systems – Chapter 8 – Portfolios and Risk Metrics", 66 | "provenance": [] 67 | }, 68 | "kernelspec": { 69 | "display_name": "Python 3", 70 | "language": "python", 71 | "name": "python3" 72 | }, 73 | "language_info": { 74 | "name": "python", 75 | "pygments_lexer": "ipython3" 76 | } 77 | }, 78 | "nbformat": 4, 79 | "nbformat_minor": 5 80 | } 81 | 82 | -------------------------------------------------------------------------------- /notebooks/2_book/appendixC_wallet_and_seed_handling_checklists.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "\"The
\n" 8 | ] 9 | }, 10 | { 11 | "cell_type": "markdown", 12 | "metadata": {}, 13 | "source": [ 14 | "# Bitcoin Foundations\n", 15 | "## Appendix C – Wallet and Seed Handling Checklists\n", 16 | "\n", 17 | "© Dr. Yves J. Hilpisch | The Python Quants GmbH
\n", 18 | "The Crypto Engineer | AI-Powered by GPT 5.1.\n" 19 | ] 20 | }, 21 | { 22 | "cell_type": "markdown", 23 | "metadata": {}, 24 | "source": [ 25 | "This final appendix example encodes a basic wallet-hygiene checklist as dataclass instances. Running it prints any items not yet marked complete, turning the prose checklist into a light-weight prompt you can run regularly as part of your operational habits." 26 | ] 27 | }, 28 | { 29 | "cell_type": "code", 30 | "execution_count": null, 31 | "metadata": { 32 | "trusted": true 33 | }, 34 | "outputs": [], 35 | "source": [ 36 | "from dataclasses import dataclass # structured records\n", 37 | "from typing import List # type hints\n", 38 | "\n", 39 | "\n", 40 | "@dataclass\n", 41 | "class ChecklistItem:\n", 42 | " description: str\n", 43 | " done: bool = False\n", 44 | "\n", 45 | "\n", 46 | "items: List[ChecklistItem] = [\n", 47 | " ChecklistItem(\"Seed phrase stored offline in durable form\"),\n", 48 | " ChecklistItem(\"Test restore performed in the last 6 months\"),\n", 49 | " ChecklistItem(\"Runbook for lost device or suspected compromise\"),\n", 50 | "] # end of checklist\n", 51 | "\n", 52 | "print(\"Wallet hygiene checklist (incomplete items):\")\n", 53 | "for item in items:\n", 54 | " if not item.done:\n", 55 | " print(\" -\", item.description)\n" 56 | ] 57 | }, 58 | { 59 | "cell_type": "markdown", 60 | "metadata": {}, 61 | "source": [ 62 | "\"The
\n" 63 | ] 64 | } 65 | ], 66 | "metadata": { 67 | "colab": { 68 | "name": "Bitcoin Foundations – Appendix C – Wallet and Seed Handling Checklists", 69 | "provenance": [] 70 | }, 71 | "kernelspec": { 72 | "display_name": "Python 3", 73 | "language": "python", 74 | "name": "python3" 75 | }, 76 | "language_info": { 77 | "name": "python", 78 | "pygments_lexer": "ipython3" 79 | } 80 | }, 81 | "nbformat": 4, 82 | "nbformat_minor": 5 83 | } 84 | 85 | -------------------------------------------------------------------------------- /notebooks/3_book/chapter11_nfts_and_tokenised_assets.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "\"The
\n" 8 | ] 9 | }, 10 | { 11 | "cell_type": "markdown", 12 | "metadata": {}, 13 | "source": [ 14 | "# Cryptomarkets and Systems\n", 15 | "## Chapter 11 – NFTs and Tokenised Assets\n", 16 | "\n", 17 | "© Dr. Yves J. Hilpisch | The Python Quants GmbH
\n", 18 | "The Crypto Engineer | AI-Powered by GPT 5.1.\n" 19 | ] 20 | }, 21 | { 22 | "cell_type": "markdown", 23 | "metadata": {}, 24 | "source": [ 25 | "This example inspects a tiny piece of NFT-style metadata stored as JSON. By looking at fields like `name`, `description`, and `image`, you connect the chapter’s discussion of on-chain vs. off-chain content to a concrete document you can tweak and extend." 26 | ] 27 | }, 28 | { 29 | "cell_type": "code", 30 | "execution_count": null, 31 | "metadata": { 32 | "trusted": true 33 | }, 34 | "outputs": [], 35 | "source": [ 36 | "import json\n", 37 | "from pathlib import Path\n", 38 | "\n", 39 | "meta_path = Path(\"toy_nft_metadata.json\")\n", 40 | "\n", 41 | "if not meta_path.exists():\n", 42 | " sample_meta = {\n", 43 | " \"name\": \"Example NFT #1\",\n", 44 | " \"description\": \"Tiny demo metadata for Cryptomarkets and Systems.\",\n", 45 | " \"image\": \"ipfs://examplehash\",\n", 46 | " \"attributes\": [{\"trait_type\": \"rarity\", \"value\": \"common\"}],\n", 47 | " }\n", 48 | " meta_path.write_text(json.dumps(sample_meta, indent=2), encoding=\"utf-8\")\n", 49 | "\n", 50 | "meta = json.loads(meta_path.read_text(encoding=\"utf-8\"))\n", 51 | "print(\"Name:\", meta.get(\"name\"))\n", 52 | "print(\"Image URI:\", meta.get(\"image\"))\n", 53 | "print(\"Attributes:\", meta.get(\"attributes\"))\n" 54 | ] 55 | }, 56 | { 57 | "cell_type": "markdown", 58 | "metadata": {}, 59 | "source": [ 60 | "\"The
\n" 61 | ] 62 | } 63 | ], 64 | "metadata": { 65 | "colab": { 66 | "name": "Cryptomarkets and Systems – Chapter 11 – NFTs and Tokenised Assets", 67 | "provenance": [] 68 | }, 69 | "kernelspec": { 70 | "display_name": "Python 3", 71 | "language": "python", 72 | "name": "python3" 73 | }, 74 | "language_info": { 75 | "name": "python", 76 | "pygments_lexer": "ipython3" 77 | } 78 | }, 79 | "nbformat": 4, 80 | "nbformat_minor": 5 81 | } 82 | 83 | -------------------------------------------------------------------------------- /notebooks/4_book/chapter02_key_and_secret_lifecycle_management.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "\"The
\n" 8 | ] 9 | }, 10 | { 11 | "cell_type": "markdown", 12 | "metadata": {}, 13 | "source": [ 14 | "# Crypto Engineering\n", 15 | "## Chapter 2 – Key and Secret Lifecycle Management\n", 16 | "\n", 17 | "© Dr. Yves J. Hilpisch | The Python Quants GmbH
\n", 18 | "The Crypto Engineer | AI-Powered by GPT 5.1.\n" 19 | ] 20 | }, 21 | { 22 | "cell_type": "markdown", 23 | "metadata": {}, 24 | "source": [ 25 | "This helper walks a toy key inventory and performs a few simple sanity checks: missing owners, stale rotation dates, and unclear storage locations. It mirrors the chapter’s recommendation to keep key inventories explicit and machine-readable so that audits and reviews can flag drift early." 26 | ] 27 | }, 28 | { 29 | "cell_type": "code", 30 | "execution_count": null, 31 | "metadata": { 32 | "trusted": true 33 | }, 34 | "outputs": [], 35 | "source": [ 36 | "from datetime import date\n", 37 | "\n", 38 | "keys = [\n", 39 | " {\"id\": \"signing_key_hot\", \"owner\": \"security\", \"last_rotated\": \"2024-01-10\", \"location\": \"hsm_cluster_a\"},\n", 40 | " {\"id\": \"api_token_analytics\", \"owner\": \"data\", \"last_rotated\": \"2023-06-01\", \"location\": \"env_var\"},\n", 41 | "]\n", 42 | "\n", 43 | "today = date.today()\n", 44 | "\n", 45 | "def days_since(d: str) -> int:\n", 46 | " y, m, d_ = map(int, d.split(\"-\"))\n", 47 | " return (today - date(y, m, d_)).days\n", 48 | "\n", 49 | "print(\"Keys needing attention:\")\n", 50 | "for k in keys:\n", 51 | " age = days_since(k[\"last_rotated\"])\n", 52 | " if age > 365:\n", 53 | " print(\" -\", k[\"id\"], \"rotation overdue (days:\", age, \")\")\n" 54 | ] 55 | }, 56 | { 57 | "cell_type": "markdown", 58 | "metadata": {}, 59 | "source": [ 60 | "\"The
\n" 61 | ] 62 | } 63 | ], 64 | "metadata": { 65 | "colab": { 66 | "name": "Crypto Engineering – Chapter 2 – Key and Secret Lifecycle Management", 67 | "provenance": [] 68 | }, 69 | "kernelspec": { 70 | "display_name": "Python 3", 71 | "language": "python", 72 | "name": "python3" 73 | }, 74 | "language_info": { 75 | "name": "python", 76 | "pygments_lexer": "ipython3" 77 | } 78 | }, 79 | "nbformat": 4, 80 | "nbformat_minor": 5 81 | } 82 | 83 | -------------------------------------------------------------------------------- /notebooks/3_book/chapter07_market_making_and_liquidity.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "\"The
\n" 8 | ] 9 | }, 10 | { 11 | "cell_type": "markdown", 12 | "metadata": {}, 13 | "source": [ 14 | "# Cryptomarkets and Systems\n", 15 | "## Chapter 7 – Market Making and Liquidity\n", 16 | "\n", 17 | "© Dr. Yves J. Hilpisch | The Python Quants GmbH
\n", 18 | "The Crypto Engineer | AI-Powered by GPT 5.1.\n" 19 | ] 20 | }, 21 | { 22 | "cell_type": "markdown", 23 | "metadata": {}, 24 | "source": [ 25 | "This toy simulation sketches a symmetric market maker posting bid and ask quotes around a mid-price. It tracks inventory and cash as random buy/sell hits arrive, illustrating how spread revenue and inventory risk trade off over time without invoking any external data or libraries." 26 | ] 27 | }, 28 | { 29 | "cell_type": "code", 30 | "execution_count": null, 31 | "metadata": { 32 | "trusted": true 33 | }, 34 | "outputs": [], 35 | "source": [ 36 | "import random\n", 37 | "\n", 38 | "mid = 100.0\n", 39 | "spread = 1.0\n", 40 | "bid = mid - spread / 2\n", 41 | "ask = mid + spread / 2\n", 42 | "\n", 43 | "inventory = 0.0\n", 44 | "cash = 0.0\n", 45 | "\n", 46 | "for _ in range(20): # 20 random hits\n", 47 | " side = random.choice([\"buy\", \"sell\"]) # from the taker's perspective\n", 48 | " size = 1.0\n", 49 | " if side == \"buy\": # taker buys from us at ask\n", 50 | " inventory -= size\n", 51 | " cash += size * ask\n", 52 | " else: # taker sells to us at bid\n", 53 | " inventory += size\n", 54 | " cash -= size * bid\n", 55 | "\n", 56 | "pnl = cash + inventory * mid\n", 57 | "print(\"Final inventory:\", inventory)\n", 58 | "print(\"Final cash:\", cash)\n", 59 | "print(\"Approx P&L at mid:\", pnl)\n" 60 | ] 61 | }, 62 | { 63 | "cell_type": "markdown", 64 | "metadata": {}, 65 | "source": [ 66 | "\"The
\n" 67 | ] 68 | } 69 | ], 70 | "metadata": { 71 | "colab": { 72 | "name": "Cryptomarkets and Systems – Chapter 7 – Market Making and Liquidity", 73 | "provenance": [] 74 | }, 75 | "kernelspec": { 76 | "display_name": "Python 3", 77 | "language": "python", 78 | "name": "python3" 79 | }, 80 | "language_info": { 81 | "name": "python", 82 | "pygments_lexer": "ipython3" 83 | } 84 | }, 85 | "nbformat": 4, 86 | "nbformat_minor": 5 87 | } 88 | 89 | -------------------------------------------------------------------------------- /notebooks/3_book/chapter05_execution_costs_and_slippage.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "\"The
\n" 8 | ] 9 | }, 10 | { 11 | "cell_type": "markdown", 12 | "metadata": {}, 13 | "source": [ 14 | "# Cryptomarkets and Systems\n", 15 | "## Chapter 5 – Execution Costs and Slippage\n", 16 | "\n", 17 | "© Dr. Yves J. Hilpisch | The Python Quants GmbH
\n", 18 | "The Crypto Engineer | AI-Powered by GPT 5.1.\n" 19 | ] 20 | }, 21 | { 22 | "cell_type": "markdown", 23 | "metadata": {}, 24 | "source": [ 25 | "Here you simulate a simple market buy that walks up a toy order book. By computing the volume-weighted average price (VWAP) and comparing it to the mid-price, you get a concrete, numerical view of slippage—the gap between an imagined mid-price fill and what a finite-size order actually achieves." 26 | ] 27 | }, 28 | { 29 | "cell_type": "code", 30 | "execution_count": null, 31 | "metadata": { 32 | "trusted": true 33 | }, 34 | "outputs": [], 35 | "source": [ 36 | "book_asks = [ # [price, size]\n", 37 | " [100.0, 1.0],\n", 38 | " [100.5, 2.0],\n", 39 | " [101.0, 3.0],\n", 40 | "]\n", 41 | "\n", 42 | "order_size = 4.0 # units to buy\n", 43 | "remaining = order_size\n", 44 | "cost = 0.0\n", 45 | "\n", 46 | "for price, size in book_asks:\n", 47 | " if remaining <= 0:\n", 48 | " break\n", 49 | " take = min(size, remaining)\n", 50 | " cost += take * price\n", 51 | " remaining -= take\n", 52 | "\n", 53 | "vwap = cost / order_size\n", 54 | "mid = (book_asks[0][0] + (book_asks[0][0] - 0.5)) / 2 # toy mid using best ask and an implied bid\n", 55 | "\n", 56 | "print(\"Order size:\", order_size)\n", 57 | "print(\"VWAP:\", vwap)\n", 58 | "print(\"Approx mid:\", mid)\n", 59 | "print(\"Slippage (% of mid):\", (vwap - mid) / mid * 100)\n" 60 | ] 61 | }, 62 | { 63 | "cell_type": "markdown", 64 | "metadata": {}, 65 | "source": [ 66 | "\"The
\n" 67 | ] 68 | } 69 | ], 70 | "metadata": { 71 | "colab": { 72 | "name": "Cryptomarkets and Systems – Chapter 5 – Execution Costs and Slippage", 73 | "provenance": [] 74 | }, 75 | "kernelspec": { 76 | "display_name": "Python 3", 77 | "language": "python", 78 | "name": "python3" 79 | }, 80 | "language_info": { 81 | "name": "python", 82 | "pygments_lexer": "ipython3" 83 | } 84 | }, 85 | "nbformat": 4, 86 | "nbformat_minor": 5 87 | } 88 | 89 | -------------------------------------------------------------------------------- /notebooks/3_book/appendixA_data_formats_and_apis.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "\"The
\n" 8 | ] 9 | }, 10 | { 11 | "cell_type": "markdown", 12 | "metadata": {}, 13 | "source": [ 14 | "# Cryptomarkets and Systems\n", 15 | "## Appendix A – Data Formats and APIs\n", 16 | "\n", 17 | "© Dr. Yves J. Hilpisch | The Python Quants GmbH
\n", 18 | "The Crypto Engineer | AI-Powered by GPT 5.1.\n" 19 | ] 20 | }, 21 | { 22 | "cell_type": "markdown", 23 | "metadata": {}, 24 | "source": [ 25 | "This appendix is primarily descriptive, but you can still anchor it with a tiny schema check: here we create a minimal price CSV, load it, and assert that required columns (`timestamp`, `close`) exist, reflecting the habits the appendix advocates for all your data files." 26 | ] 27 | }, 28 | { 29 | "cell_type": "code", 30 | "execution_count": null, 31 | "metadata": { 32 | "trusted": true 33 | }, 34 | "outputs": [], 35 | "source": [ 36 | "import csv\n", 37 | "from pathlib import Path\n", 38 | "\n", 39 | "price_path = Path(\"prices_sample.csv\")\n", 40 | "if not price_path.exists():\n", 41 | " rows = [\n", 42 | " {\"timestamp\": \"2025-01-01T00:00:00Z\", \"close\": \"100\"},\n", 43 | " {\"timestamp\": \"2025-01-02T00:00:00Z\", \"close\": \"102\"},\n", 44 | " ]\n", 45 | " fieldnames = [\"timestamp\", \"close\"]\n", 46 | " with price_path.open(\"w\", newline=\"\", encoding=\"utf-8\") as f:\n", 47 | " writer = csv.DictWriter(f, fieldnames=fieldnames)\n", 48 | " writer.writeheader()\n", 49 | " writer.writerows(rows)\n", 50 | "\n", 51 | "with price_path.open(newline=\"\", encoding=\"utf-8\") as f:\n", 52 | " reader = csv.DictReader(f)\n", 53 | " cols = reader.fieldnames\n", 54 | " print(\"Columns:\", cols)\n", 55 | " assert \"timestamp\" in cols and \"close\" in cols\n" 56 | ] 57 | }, 58 | { 59 | "cell_type": "markdown", 60 | "metadata": {}, 61 | "source": [ 62 | "\"The
\n" 63 | ] 64 | } 65 | ], 66 | "metadata": { 67 | "colab": { 68 | "name": "Cryptomarkets and Systems – Appendix A – Data Formats and APIs", 69 | "provenance": [] 70 | }, 71 | "kernelspec": { 72 | "display_name": "Python 3", 73 | "language": "python", 74 | "name": "python3" 75 | }, 76 | "language_info": { 77 | "name": "python", 78 | "pygments_lexer": "ipython3" 79 | } 80 | }, 81 | "nbformat": 4, 82 | "nbformat_minor": 5 83 | } 84 | 85 | -------------------------------------------------------------------------------- /notebooks/3_book/chapter10_defi_primitives_lending_and_amm_state.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "\"The
\n" 8 | ] 9 | }, 10 | { 11 | "cell_type": "markdown", 12 | "metadata": {}, 13 | "source": [ 14 | "# Cryptomarkets and Systems\n", 15 | "## Chapter 10 – DeFi Primitives: Lending and AMM State\n", 16 | "\n", 17 | "© Dr. Yves J. Hilpisch | The Python Quants GmbH
\n", 18 | "The Crypto Engineer | AI-Powered by GPT 5.1.\n" 19 | ] 20 | }, 21 | { 22 | "cell_type": "markdown", 23 | "metadata": {}, 24 | "source": [ 25 | "Here you inspect a tiny JSON snapshot of a lending market—deposits, borrows, and reserves. This mirrors the chapter’s practice of freezing protocol state into compact records you can diff, chart, and reason about without connecting to live infrastructure." 26 | ] 27 | }, 28 | { 29 | "cell_type": "code", 30 | "execution_count": null, 31 | "metadata": { 32 | "trusted": true 33 | }, 34 | "outputs": [], 35 | "source": [ 36 | "import json\n", 37 | "from pathlib import Path\n", 38 | "\n", 39 | "snapshot_path = Path(\"toy_lending_snapshot.json\")\n", 40 | "\n", 41 | "if not snapshot_path.exists():\n", 42 | " sample = {\n", 43 | " \"total_deposits\": 1_000_000.0,\n", 44 | " \"total_borrows\": 600_000.0,\n", 45 | " \"reserves\": 50_000.0,\n", 46 | " \"base_rate\": 0.02,\n", 47 | " \"slope\": 0.1,\n", 48 | " }\n", 49 | " snapshot_path.write_text(json.dumps(sample, indent=2), encoding=\"utf-8\")\n", 50 | "\n", 51 | "snap = json.loads(snapshot_path.read_text(encoding=\"utf-8\"))\n", 52 | "\n", 53 | "utilisation = snap[\"total_borrows\"] / snap[\"total_deposits\"]\n", 54 | "print(\"Total deposits:\", snap[\"total_deposits\"])\n", 55 | "print(\"Total borrows:\", snap[\"total_borrows\"])\n", 56 | "print(\"Utilisation:\", round(utilisation, 3))\n" 57 | ] 58 | }, 59 | { 60 | "cell_type": "markdown", 61 | "metadata": {}, 62 | "source": [ 63 | "\"The
\n" 64 | ] 65 | } 66 | ], 67 | "metadata": { 68 | "colab": { 69 | "name": "Cryptomarkets and Systems – Chapter 10 – DeFi Primitives: Lending and AMM State", 70 | "provenance": [] 71 | }, 72 | "kernelspec": { 73 | "display_name": "Python 3", 74 | "language": "python", 75 | "name": "python3" 76 | }, 77 | "language_info": { 78 | "name": "python", 79 | "pygments_lexer": "ipython3" 80 | } 81 | }, 82 | "nbformat": 4, 83 | "nbformat_minor": 5 84 | } 85 | 86 | -------------------------------------------------------------------------------- /notebooks/3_book/chapter09_smart_contract_platforms_and_gas.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "\"The
\n" 8 | ] 9 | }, 10 | { 11 | "cell_type": "markdown", 12 | "metadata": {}, 13 | "source": [ 14 | "# Cryptomarkets and Systems\n", 15 | "## Chapter 9 – Smart-Contract Platforms and Gas\n", 16 | "\n", 17 | "© Dr. Yves J. Hilpisch | The Python Quants GmbH
\n", 18 | "The Crypto Engineer | AI-Powered by GPT 5.1.\n" 19 | ] 20 | }, 21 | { 22 | "cell_type": "markdown", 23 | "metadata": {}, 24 | "source": [ 25 | "This cell inspects a simplified smart-contract transaction record stored in JSON. By loading fields such as `gas_limit`, `gas_used`, and `gas_price`, you can compute total fee paid and reason about how gas mechanics turn abstract execution into concrete costs." 26 | ] 27 | }, 28 | { 29 | "cell_type": "code", 30 | "execution_count": null, 31 | "metadata": { 32 | "trusted": true 33 | }, 34 | "outputs": [], 35 | "source": [ 36 | "import json\n", 37 | "from pathlib import Path\n", 38 | "\n", 39 | "tx_path = Path(\"toy_smart_tx.json\")\n", 40 | "\n", 41 | "if not tx_path.exists():\n", 42 | " sample_tx = {\n", 43 | " \"from\": \"0xabc...\",\n", 44 | " \"to\": \"0xdef...\",\n", 45 | " \"gas_limit\": 100_000,\n", 46 | " \"gas_used\": 80_000,\n", 47 | " \"gas_price_gwei\": 30,\n", 48 | " }\n", 49 | " tx_path.write_text(json.dumps(sample_tx, indent=2), encoding=\"utf-8\")\n", 50 | "\n", 51 | "tx = json.loads(tx_path.read_text(encoding=\"utf-8\"))\n", 52 | "\n", 53 | "gas_used = tx[\"gas_used\"]\n", 54 | "gas_price_gwei = tx[\"gas_price_gwei\"]\n", 55 | "fee_eth = gas_used * gas_price_gwei / 1e9 # 1 gwei = 1e-9 ETH\n", 56 | "\n", 57 | "print(\"Gas used:\", gas_used)\n", 58 | "print(\"Gas price (gwei):\", gas_price_gwei)\n", 59 | "print(\"Fee (ETH):\", fee_eth)\n" 60 | ] 61 | }, 62 | { 63 | "cell_type": "markdown", 64 | "metadata": {}, 65 | "source": [ 66 | "\"The
\n" 67 | ] 68 | } 69 | ], 70 | "metadata": { 71 | "colab": { 72 | "name": "Cryptomarkets and Systems – Chapter 9 – Smart-Contract Platforms and Gas", 73 | "provenance": [] 74 | }, 75 | "kernelspec": { 76 | "display_name": "Python 3", 77 | "language": "python", 78 | "name": "python3" 79 | }, 80 | "language_info": { 81 | "name": "python", 82 | "pygments_lexer": "ipython3" 83 | } 84 | }, 85 | "nbformat": 4, 86 | "nbformat_minor": 5 87 | } 88 | 89 | -------------------------------------------------------------------------------- /notebooks/2_book/appendixB_difficulty_and_hash_rate_calculations.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "\"The
\n" 8 | ] 9 | }, 10 | { 11 | "cell_type": "markdown", 12 | "metadata": {}, 13 | "source": [ 14 | "# Bitcoin Foundations\n", 15 | "## Appendix B – Difficulty and Hash-Rate Calculations\n", 16 | "\n", 17 | "© Dr. Yves J. Hilpisch | The Python Quants GmbH
\n", 18 | "The Crypto Engineer | AI-Powered by GPT 5.1.\n" 19 | ] 20 | }, 21 | { 22 | "cell_type": "markdown", 23 | "metadata": {}, 24 | "source": [ 25 | "This appendix snippet turns the difficulty and hash-rate formulas into code. You compute an expected block time for a given network hash rate and target success probability, and then estimate an attacker’s share of total hash power for back-of-the-envelope reorg risk checks." 26 | ] 27 | }, 28 | { 29 | "cell_type": "code", 30 | "execution_count": null, 31 | "metadata": { 32 | "trusted": true 33 | }, 34 | "outputs": [], 35 | "source": [ 36 | "import math # access to math functions\n", 37 | "\n", 38 | "\n", 39 | "def expected_block_time(hash_rate: float, target_prob: float) -> float:\n", 40 | " \"\"\"Return expected seconds per block for hash_rate and success probability.\"\"\"\n", 41 | " return 1.0 / (hash_rate * target_prob)\n", 42 | "\n", 43 | "\n", 44 | "def attacker_share(attacker_hash: float, network_hash: float) -> float:\n", 45 | " \"\"\"Return an attacker's fraction of total hash power.\"\"\"\n", 46 | " return attacker_hash / network_hash\n", 47 | "\n", 48 | "\n", 49 | "network_hash = 300e15 # 300 PH/s (toy value)\n", 50 | "target_prob = 2**-32 # toy success probability per hash\n", 51 | "\n", 52 | "et = expected_block_time(network_hash, target_prob) # seconds per block\n", 53 | "print(\"Expected block time (min):\", et / 60)\n", 54 | "\n", 55 | "attacker = 30e15 # 30 PH/s attacker\n", 56 | "print(\"Attacker share:\", attacker_share(attacker, network_hash))\n" 57 | ] 58 | }, 59 | { 60 | "cell_type": "markdown", 61 | "metadata": {}, 62 | "source": [ 63 | "\"The
\n" 64 | ] 65 | } 66 | ], 67 | "metadata": { 68 | "colab": { 69 | "name": "Bitcoin Foundations – Appendix B – Difficulty and Hash-Rate Calculations", 70 | "provenance": [] 71 | }, 72 | "kernelspec": { 73 | "display_name": "Python 3", 74 | "language": "python", 75 | "name": "python3" 76 | }, 77 | "language_info": { 78 | "name": "python", 79 | "pygments_lexer": "ipython3" 80 | } 81 | }, 82 | "nbformat": 4, 83 | "nbformat_minor": 5 84 | } 85 | 86 | -------------------------------------------------------------------------------- /notebooks/2_book/appendixA_bitcoin_data_formats.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "\"The
\n" 8 | ] 9 | }, 10 | { 11 | "cell_type": "markdown", 12 | "metadata": {}, 13 | "source": [ 14 | "# Bitcoin Foundations\n", 15 | "## Appendix A – Bitcoin Data Formats\n", 16 | "\n", 17 | "© Dr. Yves J. Hilpisch | The Python Quants GmbH
\n", 18 | "The Crypto Engineer | AI-Powered by GPT 5.1.\n" 19 | ] 20 | }, 21 | { 22 | "cell_type": "markdown", 23 | "metadata": {}, 24 | "source": [ 25 | "This appendix lab represents a stripped-down block header as a Python dataclass and prints a human-readable summary. It connects the field list from the text (version, prev hash, Merkle root, time, bits, nonce) to a concrete object you can inspect or adapt to real data sources." 26 | ] 27 | }, 28 | { 29 | "cell_type": "code", 30 | "execution_count": null, 31 | "metadata": { 32 | "trusted": true 33 | }, 34 | "outputs": [], 35 | "source": [ 36 | "from dataclasses import dataclass # structured data\n", 37 | "\n", 38 | "\n", 39 | "@dataclass\n", 40 | "class Header: # minimal header model\n", 41 | " version: int\n", 42 | " prev_hash: str\n", 43 | " merkle_root: str\n", 44 | " time: int # Unix-style timestamp\n", 45 | " bits: int\n", 46 | " nonce: int\n", 47 | "\n", 48 | "\n", 49 | "hdr = Header( # toy header values\n", 50 | " version=0x20000000,\n", 51 | " prev_hash=\"000000...prev\",\n", 52 | " merkle_root=\"4d5e6f...root\",\n", 53 | " time=1_700_000_000,\n", 54 | " bits=0x170fffff,\n", 55 | " nonce=42,\n", 56 | ") # end of header\n", 57 | "\n", 58 | "print(\"Block header summary:\")\n", 59 | "print(\" version :\", hex(hdr.version))\n", 60 | "print(\" prev_hash :\", hdr.prev_hash)\n", 61 | "print(\" merkle :\", hdr.merkle_root)\n", 62 | "print(\" time :\", hdr.time)\n", 63 | "print(\" bits :\", hex(hdr.bits))\n", 64 | "print(\" nonce :\", hdr.nonce)\n" 65 | ] 66 | }, 67 | { 68 | "cell_type": "markdown", 69 | "metadata": {}, 70 | "source": [ 71 | "\"The
\n" 72 | ] 73 | } 74 | ], 75 | "metadata": { 76 | "colab": { 77 | "name": "Bitcoin Foundations – Appendix A – Bitcoin Data Formats", 78 | "provenance": [] 79 | }, 80 | "kernelspec": { 81 | "display_name": "Python 3", 82 | "language": "python", 83 | "name": "python3" 84 | }, 85 | "language_info": { 86 | "name": "python", 87 | "pygments_lexer": "ipython3" 88 | } 89 | }, 90 | "nbformat": 4, 91 | "nbformat_minor": 5 92 | } 93 | 94 | -------------------------------------------------------------------------------- /notebooks/4_book/chapter08_incident_response_and_post_mortems.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "\"The
\n" 8 | ] 9 | }, 10 | { 11 | "cell_type": "markdown", 12 | "metadata": {}, 13 | "source": [ 14 | "# Crypto Engineering\n", 15 | "## Chapter 8 – Incident Response and Post-Mortems\n", 16 | "\n", 17 | "© Dr. Yves J. Hilpisch | The Python Quants GmbH
\n", 18 | "The Crypto Engineer | AI-Powered by GPT 5.1.\n" 19 | ] 20 | }, 21 | { 22 | "cell_type": "markdown", 23 | "metadata": {}, 24 | "source": [ 25 | "This helper appends an incident record to a JSON log file under `runbooks/`. It gives you a concrete pattern for capturing severity, timestamps, owners, and control IDs in a structured way, supporting the chapter’s emphasis on evidence-driven incident reviews." 26 | ] 27 | }, 28 | { 29 | "cell_type": "code", 30 | "execution_count": null, 31 | "metadata": { 32 | "trusted": true 33 | }, 34 | "outputs": [], 35 | "source": [ 36 | "import json\n", 37 | "from datetime import datetime\n", 38 | "import pathlib\n", 39 | "\n", 40 | "INCIDENT_LOG = pathlib.Path(\"runbooks/incidents.json\")\n", 41 | "INCIDENT_LOG.parent.mkdir(parents=True, exist_ok=True)\n", 42 | "\n", 43 | "record = {\n", 44 | " \"id\": \"INC-2025-04\",\n", 45 | " \"severity\": \"S1\",\n", 46 | " \"opened_at\": datetime.utcnow().isoformat(),\n", 47 | " \"status\": \"open\",\n", 48 | " \"summary\": \"Signer latency above SLO, withdrawals paused\",\n", 49 | " \"owners\": {\n", 50 | " \"commander\": \"alex\",\n", 51 | " \"comms\": \"morgan\",\n", 52 | " \"scribe\": \"lee\",\n", 53 | " },\n", 54 | " \"controls\": [\"c_sign_latency\", \"c_queue_depth\"],\n", 55 | " \"mttd_seconds\": 420,\n", 56 | " \"mttr_seconds\": None,\n", 57 | "}\n", 58 | "\n", 59 | "log = []\n", 60 | "if INCIDENT_LOG.exists():\n", 61 | " log = json.loads(INCIDENT_LOG.read_text())\n", 62 | "log.append(record)\n", 63 | "INCIDENT_LOG.write_text(json.dumps(log, indent=2))\n", 64 | "print(f\"Recorded incident {record['id']}\")\n" 65 | ] 66 | }, 67 | { 68 | "cell_type": "markdown", 69 | "metadata": {}, 70 | "source": [ 71 | "\"The
\n" 72 | ] 73 | } 74 | ], 75 | "metadata": { 76 | "colab": { 77 | "name": "Crypto Engineering – Chapter 8 – Incident Response and Post-Mortems", 78 | "provenance": [] 79 | }, 80 | "kernelspec": { 81 | "display_name": "Python 3", 82 | "language": "python", 83 | "name": "python3" 84 | }, 85 | "language_info": { 86 | "name": "python", 87 | "pygments_lexer": "ipython3" 88 | } 89 | }, 90 | "nbformat": 4, 91 | "nbformat_minor": 5 92 | } 93 | 94 | -------------------------------------------------------------------------------- /notebooks/1_book/appendixD_tooling_quickstart.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "\"The
\n" 8 | ] 9 | }, 10 | { 11 | "cell_type": "markdown", 12 | "metadata": {}, 13 | "source": [ 14 | "# Crypto Foundations\n", 15 | "## Appendix D – Tooling Quickstart\n", 16 | "\n", 17 | "© Dr. Yves J. Hilpisch | The Python Quants GmbH
\n", 18 | "The Crypto Engineer | AI-Powered by GPT 5.1.\n" 19 | ] 20 | }, 21 | { 22 | "cell_type": "markdown", 23 | "metadata": {}, 24 | "source": [ 25 | "Here you replicate a command-line checksum inside Python. This lets you verify downloads or configuration files programmatically, using the same SHA-256 hashes that tools like `shasum` produce." 26 | ] 27 | }, 28 | { 29 | "cell_type": "code", 30 | "execution_count": null, 31 | "metadata": { 32 | "trusted": true 33 | }, 34 | "outputs": [], 35 | "source": [ 36 | "import hashlib\n", 37 | "from pathlib import Path\n", 38 | "\n", 39 | "path = Path(\"example.bin\")\n", 40 | "if not path.exists():\n", 41 | " path.write_bytes(b\"demo data for checksum\") # create a small demo file\n", 42 | "\n", 43 | "h = hashlib.sha256()\n", 44 | "with path.open(\"rb\") as f:\n", 45 | " for chunk in iter(lambda: f.read(4096), b\"\"):\n", 46 | " h.update(chunk)\n", 47 | "\n", 48 | "print(\"SHA-256:\", h.hexdigest())\n" 49 | ] 50 | }, 51 | { 52 | "cell_type": "markdown", 53 | "metadata": {}, 54 | "source": [ 55 | "The second snippet inspects your current Python environment. It echoes the appendix’s advice to know which interpreter and virtual environment you are using before installing packages or running experiments." 56 | ] 57 | }, 58 | { 59 | "cell_type": "code", 60 | "execution_count": null, 61 | "metadata": { 62 | "trusted": true 63 | }, 64 | "outputs": [], 65 | "source": [ 66 | "import sys\n", 67 | "import os\n", 68 | "\n", 69 | "print(\"Executable:\", sys.executable)\n", 70 | "print(\"sys.prefix:\", sys.prefix)\n", 71 | "print(\"VIRTUAL_ENV:\", os.getenv(\"VIRTUAL_ENV\"))\n" 72 | ] 73 | }, 74 | { 75 | "cell_type": "markdown", 76 | "metadata": {}, 77 | "source": [ 78 | "\"The
\n" 79 | ] 80 | } 81 | ], 82 | "metadata": { 83 | "colab": { 84 | "name": "Crypto Foundations – Appendix D – Tooling Quickstart", 85 | "provenance": [] 86 | }, 87 | "kernelspec": { 88 | "display_name": "Python 3", 89 | "language": "python", 90 | "name": "python3" 91 | }, 92 | "language_info": { 93 | "name": "python", 94 | "pygments_lexer": "ipython3" 95 | } 96 | }, 97 | "nbformat": 4, 98 | "nbformat_minor": 5 99 | } 100 | -------------------------------------------------------------------------------- /notebooks/2_book/chapter07_wallets_keys_and_operational_safety.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "\"The
\n" 8 | ] 9 | }, 10 | { 11 | "cell_type": "markdown", 12 | "metadata": {}, 13 | "source": [ 14 | "# Bitcoin Foundations\n", 15 | "## Chapter 7 – Wallets, Keys, and Operational Safety\n", 16 | "\n", 17 | "© Dr. Yves J. Hilpisch | The Python Quants GmbH
\n", 18 | "The Crypto Engineer | AI-Powered by GPT 5.1.\n" 19 | ] 20 | }, 21 | { 22 | "cell_type": "markdown", 23 | "metadata": {}, 24 | "source": [ 25 | "Here you explore a toy hierarchical key derivation scheme inspired by HD wallets. A single root seed and a human-readable path deterministically produce child \"keys,\" giving you a tangible way to think about account branches and change chains without touching real funds or full standards." 26 | ] 27 | }, 28 | { 29 | "cell_type": "code", 30 | "execution_count": null, 31 | "metadata": { 32 | "trusted": true 33 | }, 34 | "outputs": [], 35 | "source": [ 36 | "import hashlib # hashing primitives\n", 37 | "import hmac # keyed hashing\n", 38 | "from typing import Tuple # type annotations (kept for clarity)\n", 39 | "\n", 40 | "\n", 41 | "def derive_child(root: bytes, path: str) -> bytes:\n", 42 | " \"\"\"Deterministically derive a child key from a root and a path.\n", 43 | "\n", 44 | " This is a toy example inspired by HD wallet ideas and is\n", 45 | " not a drop-in replacement for real standards.\n", 46 | " \"\"\"\n", 47 | " msg = path.encode(\"utf-8\") # encode path as bytes\n", 48 | " return hmac.new(root, msg, hashlib.sha256).digest() # keyed hash\n", 49 | "\n", 50 | "\n", 51 | "def format_key(b: bytes) -> str:\n", 52 | " \"\"\"Return a short hex representation for display.\"\"\"\n", 53 | " return b.hex()[:16] # first 8 bytes as 16 hex chars\n", 54 | "\n", 55 | "\n+ 56 | "seed = b\"demo-seed-entropy\" # toy root secret (bytes)\n", 57 | "\n", 58 | "paths = [ # example derivation paths\n", 59 | " \"m/personal/receive/0\",\n", 60 | " \"m/personal/receive/1\",\n", 61 | " \"m/personal/change/0\",\n", 62 | " \"m/business/receive/0\",\n", 63 | "]\n", 64 | "\n", 65 | "for p in paths: # iterate over example paths\n", 66 | " child_key = derive_child(seed, p) # derive key\n", 67 | " print(p, \"->\", format_key(child_key)) # show truncated hex\n" 68 | ] 69 | }, 70 | { 71 | "cell_type": "markdown", 72 | "metadata": {}, 73 | "source": [ 74 | "\"The
\n" 75 | ] 76 | } 77 | ], 78 | "metadata": { 79 | "colab": { 80 | "name": "Bitcoin Foundations – Chapter 7 – Wallets, Keys, and Operational Safety", 81 | "provenance": [] 82 | }, 83 | "kernelspec": { 84 | "display_name": "Python 3", 85 | "language": "python", 86 | "name": "python3" 87 | }, 88 | "language_info": { 89 | "name": "python", 90 | "pygments_lexer": "ipython3" 91 | } 92 | }, 93 | "nbformat": 4, 94 | "nbformat_minor": 5 95 | } 96 | 97 | -------------------------------------------------------------------------------- /notebooks/3_book/chapter12_stablecoins_bridges_and_l2_plumbing.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "\"The
\n" 8 | ] 9 | }, 10 | { 11 | "cell_type": "markdown", 12 | "metadata": {}, 13 | "source": [ 14 | "# Cryptomarkets and Systems\n", 15 | "## Chapter 12 – Stablecoins, Bridges, and L2 Plumbing\n", 16 | "\n", 17 | "© Dr. Yves J. Hilpisch | The Python Quants GmbH
\n", 18 | "The Crypto Engineer | AI-Powered by GPT 5.1.\n" 19 | ] 20 | }, 21 | { 22 | "cell_type": "markdown", 23 | "metadata": {}, 24 | "source": [ 25 | "This snippet reads a toy stablecoin-reserve CSV and computes coverage ratios (reserves/liabilities) over time. It gives you a hands-on way to visualise and reason about peg robustness using the same columns the chapter describes: dates, reserves, and outstanding tokens." 26 | ] 27 | }, 28 | { 29 | "cell_type": "code", 30 | "execution_count": null, 31 | "metadata": { 32 | "trusted": true 33 | }, 34 | "outputs": [], 35 | "source": [ 36 | "import csv # CSV reader\n", 37 | "from pathlib import Path # path handling\n", 38 | "\n", 39 | "snapshots_path = Path(\"stablecoin_snapshots_sample.csv\")\n", 40 | "\n", 41 | "if not snapshots_path.exists():\n", 42 | " rows = [\n", 43 | " {\"date\": \"2024-01-01\", \"reserves\": \"105\", \"liabilities\": \"100\"},\n", 44 | " {\"date\": \"2024-02-01\", \"reserves\": \"102\", \"liabilities\": \"100\"},\n", 45 | " {\"date\": \"2024-03-01\", \"reserves\": \"98\", \"liabilities\": \"100\"},\n", 46 | " ]\n", 47 | " fieldnames = [\"date\", \"reserves\", \"liabilities\"]\n", 48 | " with snapshots_path.open(\"w\", newline=\"\", encoding=\"utf-8\") as f:\n", 49 | " writer = csv.DictWriter(f, fieldnames=fieldnames)\n", 50 | " writer.writeheader()\n", 51 | " writer.writerows(rows)\n", 52 | "\n", 53 | "dates = []\n", 54 | "coverage = []\n", 55 | "\n", 56 | "with snapshots_path.open(newline=\"\", encoding=\"utf-8\") as f:\n", 57 | " reader = csv.DictReader(f)\n", 58 | " for row in reader:\n", 59 | " R = float(row[\"reserves\"])\n", 60 | " L = float(row[\"liabilities\"])\n", 61 | " kappa = R / L if L > 0 else 0.0\n", 62 | " dates.append(row[\"date\"])\n", 63 | " coverage.append(kappa)\n", 64 | "\n", 65 | "print(\"Dates:\", dates)\n", 66 | "print(\"Coverage ratios:\", coverage)\n" 67 | ] 68 | }, 69 | { 70 | "cell_type": "markdown", 71 | "metadata": {}, 72 | "source": [ 73 | "\"The
\n" 74 | ] 75 | } 76 | ], 77 | "metadata": { 78 | "colab": { 79 | "name": "Cryptomarkets and Systems – Chapter 12 – Stablecoins, Bridges, and L2 Plumbing", 80 | "provenance": [] 81 | }, 82 | "kernelspec": { 83 | "display_name": "Python 3", 84 | "language": "python", 85 | "name": "python3" 86 | }, 87 | "language_info": { 88 | "name": "python", 89 | "pygments_lexer": "ipython3" 90 | } 91 | }, 92 | "nbformat": 4, 93 | "nbformat_minor": 5 94 | } 95 | 96 | -------------------------------------------------------------------------------- /notebooks/3_book/chapter03_on_chain_data_and_block_level_metrics.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "\"The
\n" 8 | ] 9 | }, 10 | { 11 | "cell_type": "markdown", 12 | "metadata": {}, 13 | "source": [ 14 | "# Cryptomarkets and Systems\n", 15 | "## Chapter 3 – On-Chain Data and Block-Level Metrics\n", 16 | "\n", 17 | "© Dr. Yves J. Hilpisch | The Python Quants GmbH
\n", 18 | "The Crypto Engineer | AI-Powered by GPT 5.1.\n" 19 | ] 20 | }, 21 | { 22 | "cell_type": "markdown", 23 | "metadata": {}, 24 | "source": [ 25 | "This notebook cell works with a tiny CSV of block-level metrics—heights, timestamps, transaction counts, and fees. By loading the file and computing a few simple aggregates, you mirror the chapter’s message that many on-chain insights start from small, well-structured tables rather than heavyweight analytics stacks." 26 | ] 27 | }, 28 | { 29 | "cell_type": "code", 30 | "execution_count": null, 31 | "metadata": { 32 | "trusted": true 33 | }, 34 | "outputs": [], 35 | "source": [ 36 | "import csv # CSV parsing\n", 37 | "from pathlib import Path # path handling\n", 38 | "\n", 39 | "blocks_path = Path(\"blocks_sample.csv\")\n", 40 | "\n", 41 | "if not blocks_path.exists(): # create a small sample dataset\n", 42 | " sample_rows = [\n", 43 | " {\"height\": 1, \"tx_count\": 120, \"total_fees\": 0.05, \"avg_fee_per_tx\": 0.0004},\n", 44 | " {\"height\": 2, \"tx_count\": 90, \"total_fees\": 0.03, \"avg_fee_per_tx\": 0.0003},\n", 45 | " {\"height\": 3, \"tx_count\": 150, \"total_fees\": 0.08, \"avg_fee_per_tx\": 0.0005},\n", 46 | " ]\n", 47 | " fieldnames = [\"height\", \"tx_count\", \"total_fees\", \"avg_fee_per_tx\"]\n", 48 | " with blocks_path.open(\"w\", newline=\"\", encoding=\"utf-8\") as f:\n", 49 | " writer = csv.DictWriter(f, fieldnames=fieldnames)\n", 50 | " writer.writeheader()\n", 51 | " writer.writerows(sample_rows)\n", 52 | "\n", 53 | "tx_counts = []\n", 54 | "total_fees = []\n", 55 | "\n+ 56 | "with blocks_path.open(newline=\"\", encoding=\"utf-8\") as f:\n", 57 | " reader = csv.DictReader(f)\n", 58 | " for row in reader:\n", 59 | " tx_counts.append(int(row[\"tx_count\"]))\n", 60 | " total_fees.append(float(row[\"total_fees\"]))\n", 61 | "\n", 62 | "print(\"Blocks loaded:\", len(tx_counts))\n", 63 | "print(\"Average tx_count:\", sum(tx_counts) / len(tx_counts))\n", 64 | "print(\"Total fees across sample:\", sum(total_fees))\n" 65 | ] 66 | }, 67 | { 68 | "cell_type": "markdown", 69 | "metadata": {}, 70 | "source": [ 71 | "\"The
\n" 72 | ] 73 | } 74 | ], 75 | "metadata": { 76 | "colab": { 77 | "name": "Cryptomarkets and Systems – Chapter 3 – On-Chain Data and Block-Level Metrics", 78 | "provenance": [] 79 | }, 80 | "kernelspec": { 81 | "display_name": "Python 3", 82 | "language": "python", 83 | "name": "python3" 84 | }, 85 | "language_info": { 86 | "name": "python", 87 | "pygments_lexer": "ipython3" 88 | } 89 | }, 90 | "nbformat": 4, 91 | "nbformat_minor": 5 92 | } 93 | 94 | -------------------------------------------------------------------------------- /notebooks/4_book/chapter01_crypto_system_design_and_threat_modelling.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "\"The
\n" 8 | ] 9 | }, 10 | { 11 | "cell_type": "markdown", 12 | "metadata": {}, 13 | "source": [ 14 | "# Crypto Engineering\n", 15 | "## Chapter 1 – Crypto System Design and Threat Modelling\n", 16 | "\n", 17 | "© Dr. Yves J. Hilpisch | The Python Quants GmbH
\n", 18 | "The Crypto Engineer | AI-Powered by GPT 5.1.\n" 19 | ] 20 | }, 21 | { 22 | "cell_type": "markdown", 23 | "metadata": {}, 24 | "source": [ 25 | "To keep system design reviews grounded, this lab captures a minimal threat-model record as a plain Python dictionary. Each section (assets, adversaries, assumptions, controls, residual risk, review metadata) reflects the chapter’s emphasis on ownership and traceability: you can check this file into version control, diff it like code, and hook it into CI so that design changes always come with an updated model." 26 | ] 27 | }, 28 | { 29 | "cell_type": "code", 30 | "execution_count": null, 31 | "metadata": { 32 | "trusted": true 33 | }, 34 | "outputs": [], 35 | "source": [ 36 | "threat_model = {\n", 37 | " \"system\": \"wallet_signer_v2\",\n", 38 | " \"assets\": [\n", 39 | " {\"name\": \"hot_signing_key\", \"location\": \"hsm_cluster_a\", \"owner\": \"security-team\"},\n", 40 | " {\"name\": \"withdrawal_queue\", \"location\": \"postgres_wallet\", \"owner\": \"payments\"}\n", 41 | " ],\n", 42 | " \"adversaries\": [\n", 43 | " {\"type\": \"external\", \"goal\": \"steal_keys\", \"capability\": \"api_auth+\"},\n", 44 | " {\"type\": \"insider\", \"goal\": \"abuse_rotation\", \"capability\": \"sudo_on_ci\"}\n", 45 | " ],\n", 46 | " \"assumptions\": [\n", 47 | " \"ledger_nodes patched weekly\",\n", 48 | " \"ci_cd secrets rotated every 30 days\"\n", 49 | " ],\n", 50 | " \"controls\": [\n", 51 | " {\"asset\": \"hot_signing_key\", \"control\": \"hsm_rate_limit\", \"detect\": \"metric.wallet.sign\"},\n", 52 | " {\"asset\": \"withdrawal_queue\", \"control\": \"dual_approval\", \"detect\": \"audit.webhook\"}\n", 53 | " ],\n", 54 | " \"residual_risk\": \"requires validator withdrawal redesign before LST launch\",\n", 55 | " \"review\": {\n", 56 | " \"owner\": \"security-lead\",\n", 57 | " \"date\": \"2025-02-15\",\n", 58 | " \"links\": [\n", 59 | " \"docs/design/wallet_lsts.pdf\",\n", 60 | " \"runbooks/incident_ir17.md\"\n", 61 | " ]\n", 62 | " }\n", 63 | "}\n", 64 | "\n", 65 | "for section, value in threat_model.items():\n", 66 | " print(f\"{section.upper()}:\")\n", 67 | " print(value)\n", 68 | " print(\"-\")\n" 69 | ] 70 | }, 71 | { 72 | "cell_type": "markdown", 73 | "metadata": {}, 74 | "source": [ 75 | "\"The
\n" 76 | ] 77 | } 78 | ], 79 | "metadata": { 80 | "colab": { 81 | "name": "Crypto Engineering – Chapter 1 – Crypto System Design and Threat Modelling", 82 | "provenance": [] 83 | }, 84 | "kernelspec": { 85 | "display_name": "Python 3", 86 | "language": "python", 87 | "name": "python3" 88 | }, 89 | "language_info": { 90 | "name": "python", 91 | "pygments_lexer": "ipython3" 92 | } 93 | }, 94 | "nbformat": 4, 95 | "nbformat_minor": 5 96 | } 97 | -------------------------------------------------------------------------------- /notebooks/1_book/chapter07_public_key_tools.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "\"The
\n" 8 | ] 9 | }, 10 | { 11 | "cell_type": "markdown", 12 | "metadata": {}, 13 | "source": [ 14 | "# Crypto Foundations\n", 15 | "## Chapter 7 – Public-Key Tools and Protocols\n", 16 | "\n", 17 | "© Dr. Yves J. Hilpisch | The Python Quants GmbH
\n", 18 | "The Crypto Engineer | AI-Powered by GPT 5.1.\n" 19 | ] 20 | }, 21 | { 22 | "cell_type": "markdown", 23 | "metadata": {}, 24 | "source": [ 25 | "You begin with a toy Diffie–Hellman-style key exchange in a tiny modular arithmetic setting. Even though the parameters are insecure, the code shows how two parties can arrive at the same shared secret while only sending public values over the wire." 26 | ] 27 | }, 28 | { 29 | "cell_type": "code", 30 | "execution_count": null, 31 | "metadata": { 32 | "trusted": true 33 | }, 34 | "outputs": [], 35 | "source": [ 36 | "p = 23 # small prime modulus (insecure but easy to inspect)\n", 37 | "g = 5 # generator modulo p\n", 38 | "\n", 39 | "a = 6 # Alice's secret exponent\n", 40 | "b = 15 # Bob's secret exponent\n", 41 | "\n", 42 | "A = pow(g, a, p) # Alice's public value\n", 43 | "B = pow(g, b, p) # Bob's public value\n", 44 | "\n", 45 | "shared_alice = pow(B, a, p) # Alice computes shared secret\n", 46 | "shared_bob = pow(A, b, p) # Bob computes shared secret\n", 47 | "\n", 48 | "print(\"A (Alice -> Bob):\", A)\n", 49 | "print(\"B (Bob -> Alice):\", B)\n", 50 | "print(\"Shared (Alice):\", shared_alice)\n", 51 | "print(\"Shared (Bob): \", shared_bob)\n", 52 | "print(\"Match?\", shared_alice == shared_bob)\n" 53 | ] 54 | }, 55 | { 56 | "cell_type": "markdown", 57 | "metadata": {}, 58 | "source": [ 59 | "Next, you sketch the \"hash-before-sign\" pattern: rather than signing raw, structured data directly, you first compute a digest and then sign that fixed-size hash. This is a structural practice exercise only, using placeholders where a real signature primitive would go." 60 | ] 61 | }, 62 | { 63 | "cell_type": "code", 64 | "execution_count": null, 65 | "metadata": { 66 | "trusted": true 67 | }, 68 | "outputs": [], 69 | "source": [ 70 | "import hashlib\n", 71 | "\n", 72 | "message = b\"release manifest v1.2\" # structured data to sign\n", 73 | "digest = hashlib.sha256(message).digest() # fixed-size hash\n", 74 | "\n", 75 | "# In a real system, you would call a signature API here, passing `digest`.\n", 76 | "# For this lab we just display the digest to emphasise the separation.\n", 77 | "print(\"Message:\", message)\n", 78 | "print(\"SHA-256 digest (hex):\", digest.hex())\n" 79 | ] 80 | }, 81 | { 82 | "cell_type": "markdown", 83 | "metadata": {}, 84 | "source": [ 85 | "\"The
\n" 86 | ] 87 | } 88 | ], 89 | "metadata": { 90 | "colab": { 91 | "name": "Crypto Foundations – Chapter 7 – Public-Key Tools and Protocols", 92 | "provenance": [] 93 | }, 94 | "kernelspec": { 95 | "display_name": "Python 3", 96 | "language": "python", 97 | "name": "python3" 98 | }, 99 | "language_info": { 100 | "name": "python", 101 | "pygments_lexer": "ipython3" 102 | } 103 | }, 104 | "nbformat": 4, 105 | "nbformat_minor": 5 106 | } 107 | -------------------------------------------------------------------------------- /notebooks/3_book/chapter02_centralised_exchanges_and_custody.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "\"The
\n" 8 | ] 9 | }, 10 | { 11 | "cell_type": "markdown", 12 | "metadata": {}, 13 | "source": [ 14 | "# Cryptomarkets and Systems\n", 15 | "## Chapter 2 – Centralised Exchanges and Custody\n", 16 | "\n", 17 | "© Dr. Yves J. Hilpisch | The Python Quants GmbH
\n", 18 | "The Crypto Engineer | AI-Powered by GPT 5.1.\n" 19 | ] 20 | }, 21 | { 22 | "cell_type": "markdown", 23 | "metadata": {}, 24 | "source": [ 25 | "This lab inspects a toy order-book snapshot stored as JSON. By loading bids and asks from a local file, computing best quotes, spread, and a mid-price, you connect the chapter’s discussion of spreads and depth to a concrete, reproducible data structure without touching any live exchange APIs." 26 | ] 27 | }, 28 | { 29 | "cell_type": "code", 30 | "execution_count": null, 31 | "metadata": { 32 | "trusted": true 33 | }, 34 | "outputs": [], 35 | "source": [ 36 | "import json # JSON parsing\n", 37 | "from pathlib import Path # filesystem paths\n", 38 | "\n", 39 | "book_path = Path(\"toy_order_book.json\") # local file in the notebook workspace\n", 40 | "\n", 41 | "if not book_path.exists(): # create a small example snapshot if missing\n", 42 | " sample = {\n", 43 | " \"bids\": [\n", 44 | " [99.5, 1.2],\n", 45 | " [99.0, 2.0],\n", 46 | " [98.5, 1.5],\n", 47 | " ],\n", 48 | " \"asks\": [\n", 49 | " [100.5, 1.0],\n", 50 | " [101.0, 1.8],\n", 51 | " [102.0, 3.0],\n", 52 | " ],\n", 53 | " \"pair\": \"BTC-USD\",\n", 54 | " }\n", 55 | " book_path.write_text(json.dumps(sample, indent=2), encoding=\"utf-8\")\n", 56 | "\n", 57 | "with book_path.open(encoding=\"utf-8\") as f: # open JSON file\n", 58 | " book = json.load(f) # parse into Python dict\n", 59 | "\n", 60 | "bids = book[\"bids\"] # list of [price, size]\n", 61 | "asks = book[\"asks\"] # list of [price, size]\n", 62 | "\n", 63 | "# Sort bids descending by price, asks ascending by price\n", 64 | "bids_sorted = sorted(bids, key=lambda x: x[0], reverse=True)\n", 65 | "asks_sorted = sorted(asks, key=lambda x: x[0])\n", 66 | "\n", 67 | "best_bid = bids_sorted[0]\n", 68 | "best_ask = asks_sorted[0]\n", 69 | "\n", 70 | "spread = best_ask[0] - best_bid[0] # absolute spread\n", 71 | "mid = (best_ask[0] + best_bid[0]) / 2 # mid-price\n", 72 | "\n", 73 | "print(\"Best bid :\", best_bid)\n", 74 | "print(\"Best ask :\", best_ask)\n", 75 | "print(\"Spread :\", spread, \"(absolute)\")\n", 76 | "print(\"Spread :\", (spread / mid) * 100, \"% of mid\")\n" 77 | ] 78 | }, 79 | { 80 | "cell_type": "markdown", 81 | "metadata": {}, 82 | "source": [ 83 | "\"The
\n" 84 | ] 85 | } 86 | ], 87 | "metadata": { 88 | "colab": { 89 | "name": "Cryptomarkets and Systems – Chapter 2 – Centralised Exchanges and Custody", 90 | "provenance": [] 91 | }, 92 | "kernelspec": { 93 | "display_name": "Python 3", 94 | "language": "python", 95 | "name": "python3" 96 | }, 97 | "language_info": { 98 | "name": "python", 99 | "pygments_lexer": "ipython3" 100 | } 101 | }, 102 | "nbformat": 4, 103 | "nbformat_minor": 5 104 | } 105 | 106 | -------------------------------------------------------------------------------- /notebooks/2_book/chapter08_fees_mempools_and_confirmation_strategy.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "\"The
\n" 8 | ] 9 | }, 10 | { 11 | "cell_type": "markdown", 12 | "metadata": {}, 13 | "source": [ 14 | "# Bitcoin Foundations\n", 15 | "## Chapter 8 – Fees, Mempools, and Confirmation Strategy\n", 16 | "\n", 17 | "© Dr. Yves J. Hilpisch | The Python Quants GmbH
\n", 18 | "The Crypto Engineer | AI-Powered by GPT 5.1.\n" 19 | ] 20 | }, 21 | { 22 | "cell_type": "markdown", 23 | "metadata": {}, 24 | "source": [ 25 | "This notebook cell implements a tiny mempool and block template builder. By sorting transactions by fee rate and filling a limited block, you see how miners optimise revenue per vByte rather than absolute fees, tying code directly to the chapter’s fee-market discussion." 26 | ] 27 | }, 28 | { 29 | "cell_type": "code", 30 | "execution_count": null, 31 | "metadata": { 32 | "trusted": true 33 | }, 34 | "outputs": [], 35 | "source": [ 36 | "from dataclasses import dataclass # structured records\n", 37 | "from typing import List # type hints\n", 38 | "\n", 39 | "\n", 40 | "@dataclass\n", 41 | "class Tx: # simplified transaction model\n", 42 | " txid: str # identifier\n", 43 | " size_vb: int # size in vBytes\n", 44 | " fee_sat: int # fee in satoshis\n", 45 | "\n", 46 | " @property\n", 47 | " def fee_rate(self) -> float:\n", 48 | " \"\"\"Return fee rate in sat/vByte.\"\"\"\n", 49 | " return self.fee_sat / self.size_vb\n", 50 | "\n", 51 | "\n", 52 | "def select_for_block(mempool: List[Tx], max_vb: int) -> List[Tx]:\n", 53 | " \"\"\"Select transactions by descending fee rate up to a size limit.\"\"\"\n", 54 | " chosen: List[Tx] = []\n", 55 | " used = 0 # total vBytes used so far\n", 56 | " for tx in sorted(mempool, key=lambda t: t.fee_rate, reverse=True):\n", 57 | " if used + tx.size_vb <= max_vb: # fits into block?\n", 58 | " chosen.append(tx)\n", 59 | " used += tx.size_vb\n", 60 | " return chosen\n", 61 | "\n", 62 | "\n", 63 | "mempool = [ # toy mempool snapshot\n", 64 | " Tx(\"tx1\", size_vb=200, fee_sat=4_000), # 20 sat/vB\n", 65 | " Tx(\"tx2\", size_vb=300, fee_sat=12_000), # 40 sat/vB\n", 66 | " Tx(\"tx3\", size_vb=150, fee_sat=1_500), # 10 sat/vB\n", 67 | " Tx(\"tx4\", size_vb=500, fee_sat=25_000), # 50 sat/vB\n", 68 | "]\n", 69 | "\n", 70 | "block_limit_vb = 700 # toy block size limit in vBytes\n", 71 | "\n", 72 | "chosen = select_for_block(mempool, block_limit_vb) # build template\n", 73 | "\n", 74 | "print(\"Selected transactions in order:\")\n", 75 | "for tx in chosen:\n", 76 | " print(tx.txid, \"fee_rate=\", f\"{tx.fee_rate:.1f}\", \"sat/vB\")\n" 77 | ] 78 | }, 79 | { 80 | "cell_type": "markdown", 81 | "metadata": {}, 82 | "source": [ 83 | "\"The
\n" 84 | ] 85 | } 86 | ], 87 | "metadata": { 88 | "colab": { 89 | "name": "Bitcoin Foundations – Chapter 8 – Fees, Mempools, and Confirmation Strategy", 90 | "provenance": [] 91 | }, 92 | "kernelspec": { 93 | "display_name": "Python 3", 94 | "language": "python", 95 | "name": "python3" 96 | }, 97 | "language_info": { 98 | "name": "python", 99 | "pygments_lexer": "ipython3" 100 | } 101 | }, 102 | "nbformat": 4, 103 | "nbformat_minor": 5 104 | } 105 | 106 | -------------------------------------------------------------------------------- /notebooks/1_book/appendixC_implementation_crib_sheets.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "\"The
\n" 8 | ] 9 | }, 10 | { 11 | "cell_type": "markdown", 12 | "metadata": {}, 13 | "source": [ 14 | "# Crypto Foundations\n", 15 | "## Appendix C – Implementation Crib Sheets\n", 16 | "\n", 17 | "© Dr. Yves J. Hilpisch | The Python Quants GmbH
\n", 18 | "The Crypto Engineer | AI-Powered by GPT 5.1.\n" 19 | ] 20 | }, 21 | { 22 | "cell_type": "markdown", 23 | "metadata": {}, 24 | "source": [ 25 | "The first snippet demonstrates explicit encoding and decoding between text and bytes. This reinforces the habit of handling encodings deliberately rather than relying on implicit conversions that can break across systems." 26 | ] 27 | }, 28 | { 29 | "cell_type": "code", 30 | "execution_count": null, 31 | "metadata": { 32 | "trusted": true 33 | }, 34 | "outputs": [], 35 | "source": [ 36 | "text = \"café\" # includes a non-ASCII character\n", 37 | "data = text.encode(\"utf-8\") # encode to bytes explicitly\n", 38 | "roundtrip = data.decode(\"utf-8\") # decode back to text\n", 39 | "\n", 40 | "print(\"Text:\", text)\n", 41 | "print(\"Bytes:\", data)\n", 42 | "print(\"Round-trip:\", roundtrip)\n" 43 | ] 44 | }, 45 | { 46 | "cell_type": "markdown", 47 | "metadata": {}, 48 | "source": [ 49 | "Next you use `hmac.compare_digest` to perform constant-time tag comparisons. This tiny habit reduces the risk of subtle timing side channels when checking MACs or password hashes." 50 | ] 51 | }, 52 | { 53 | "cell_type": "code", 54 | "execution_count": null, 55 | "metadata": { 56 | "trusted": true 57 | }, 58 | "outputs": [], 59 | "source": [ 60 | "import hmac\n", 61 | "import hashlib\n", 62 | "\n", 63 | "key = b\"mac-key\"\n", 64 | "msg = b\"important message\"\n", 65 | "\n", 66 | "tag = hmac.new(key, msg, hashlib.sha256).digest()\n", 67 | "candidate = hmac.new(key, msg, hashlib.sha256).digest()\n", 68 | "\n", 69 | "print(\"Tags equal?\", hmac.compare_digest(tag, candidate))\n" 70 | ] 71 | }, 72 | { 73 | "cell_type": "markdown", 74 | "metadata": {}, 75 | "source": [ 76 | "Finally, you draw nonces and tokens from the `secrets` module. This mirrors the appendix’s guidance to rely on vetted randomness APIs for identifiers, tokens, and one-time values instead of home-grown generators." 77 | ] 78 | }, 79 | { 80 | "cell_type": "code", 81 | "execution_count": null, 82 | "metadata": { 83 | "trusted": true 84 | }, 85 | "outputs": [], 86 | "source": [ 87 | "import secrets\n", 88 | "\n", 89 | "nonce = secrets.token_bytes(12) # 96-bit nonce for AEAD-like schemes\n", 90 | "token = secrets.token_urlsafe(32) # URL-safe random token for identifiers\n", 91 | "\n", 92 | "print(\"Nonce (hex):\", nonce.hex())\n", 93 | "print(\"Token:\", token)\n" 94 | ] 95 | }, 96 | { 97 | "cell_type": "markdown", 98 | "metadata": {}, 99 | "source": [ 100 | "\"The
\n" 101 | ] 102 | } 103 | ], 104 | "metadata": { 105 | "colab": { 106 | "name": "Crypto Foundations – Appendix C – Implementation Crib Sheets", 107 | "provenance": [] 108 | }, 109 | "kernelspec": { 110 | "display_name": "Python 3", 111 | "language": "python", 112 | "name": "python3" 113 | }, 114 | "language_info": { 115 | "name": "python", 116 | "pygments_lexer": "ipython3" 117 | } 118 | }, 119 | "nbformat": 4, 120 | "nbformat_minor": 5 121 | } 122 | -------------------------------------------------------------------------------- /notebooks/3_book/chapter01_cryptomarkets_in_context.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "\"The
\n" 8 | ] 9 | }, 10 | { 11 | "cell_type": "markdown", 12 | "metadata": {}, 13 | "source": [ 14 | "# Cryptomarkets and Systems\n", 15 | "## Chapter 1 – Cryptomarkets in Context\n", 16 | "\n", 17 | "© Dr. Yves J. Hilpisch | The Python Quants GmbH
\n", 18 | "The Crypto Engineer | AI-Powered by GPT 5.1.\n" 19 | ] 20 | }, 21 | { 22 | "cell_type": "markdown", 23 | "metadata": {}, 24 | "source": [ 25 | "This notebook turns the chapter’s \"market atlas\" warm-up into runnable code. Instead of pulling live data, you work with a tiny, local CSV of hand-curated cryptomarket events so that everything runs deterministically in Colab. The goal is to practice loading structured event data and preview how sectors and narratives show up over time without touching keys, APIs, or real money." 26 | ] 27 | }, 28 | { 29 | "cell_type": "code", 30 | "execution_count": null, 31 | "metadata": { 32 | "trusted": true 33 | }, 34 | "outputs": [], 35 | "source": [ 36 | "import csv # built-in CSV reader\n", 37 | "from pathlib import Path # convenient path handling\n", 38 | "\n", 39 | "# In the book, the code assumes an existing CSV in a data directory.\n", 40 | "# For a self-contained Colab notebook, we create a tiny sample file on the fly\n", 41 | "# if it is not present yet.\n", 42 | "\n", 43 | "events_path = Path(\"crypto_events_sample.csv\") # local file in the notebook workspace\n", 44 | "\n", 45 | "if not events_path.exists(): # create a small sample dataset\n", 46 | " sample_events = [\n", 47 | " {\"date\": \"2017-12-17\", \"sector\": \"Bitcoin\", \"label\": \"Bitcoin peaks near 20k USD\"},\n", 48 | " {\"date\": \"2020-03-12\", \"sector\": \"DeFi\", \"label\": \"DeFi stress-test during market crash\"},\n", 49 | " {\"date\": \"2020-12-01\", \"sector\": \"Ethereum\", \"label\": \"Ethereum 2.0 beacon chain launches\"},\n", 50 | " {\"date\": \"2021-05-19\", \"sector\": \"Market\", \"label\": \"Leverage flush and broad crypto sell-off\"},\n", 51 | " {\"date\": \"2022-11-08\", \"sector\": \"CEX\", \"label\": \"Major centralised exchange collapse\"},\n", 52 | " ]\n", 53 | "\n", 54 | " fieldnames = [\"date\", \"sector\", \"label\"]\n", 55 | " with events_path.open(\"w\", newline=\"\", encoding=\"utf-8\") as f:\n", 56 | " writer = csv.DictWriter(f, fieldnames=fieldnames)\n", 57 | " writer.writeheader()\n", 58 | " writer.writerows(sample_events)\n", 59 | "\n", 60 | "events = [] # list of event dicts\n", 61 | "with events_path.open(newline=\"\", encoding=\"utf-8\") as f: # open CSV file\n", 62 | " reader = csv.DictReader(f) # read rows as dicts\n", 63 | " for row in reader: # iterate over rows\n", 64 | " events.append(row) # collect event\n", 65 | "\n", 66 | "print(f\"Loaded {len(events)} events\") # quick sanity check\n", 67 | "for e in events[:3]: # show first few\n", 68 | " print(e[\"date\"], e[\"sector\"], \"->\", e[\"label\"]) # basic preview\n" 69 | ] 70 | }, 71 | { 72 | "cell_type": "markdown", 73 | "metadata": {}, 74 | "source": [ 75 | "\"The
\n" 76 | ] 77 | } 78 | ], 79 | "metadata": { 80 | "colab": { 81 | "name": "Cryptomarkets and Systems – Chapter 1 – Cryptomarkets in Context", 82 | "provenance": [] 83 | }, 84 | "kernelspec": { 85 | "display_name": "Python 3", 86 | "language": "python", 87 | "name": "python3" 88 | }, 89 | "language_info": { 90 | "name": "python", 91 | "pygments_lexer": "ipython3" 92 | } 93 | }, 94 | "nbformat": 4, 95 | "nbformat_minor": 5 96 | } 97 | -------------------------------------------------------------------------------- /notebooks/1_book/chapter04_symmetric_encryption_basics.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "\"The
\n" 8 | ] 9 | }, 10 | { 11 | "cell_type": "markdown", 12 | "metadata": {}, 13 | "source": [ 14 | "# Crypto Foundations\n", 15 | "## Chapter 4 – Symmetric Encryption Basics\n", 16 | "\n", 17 | "© Dr. Yves J. Hilpisch | The Python Quants GmbH
\n", 18 | "The Crypto Engineer | AI-Powered by GPT 5.1.\n" 19 | ] 20 | }, 21 | { 22 | "cell_type": "markdown", 23 | "metadata": {}, 24 | "source": [ 25 | "This toy example implements the simplest possible \"stream cipher\": XORing plaintext with a keystream. It is deliberately insecure, but it gives you a concrete mental model for how real stream ciphers combine a secret key, a nonce, and a pseudorandom stream to turn readable bytes into ciphertext." 26 | ] 27 | }, 28 | { 29 | "cell_type": "code", 30 | "execution_count": null, 31 | "metadata": { 32 | "trusted": true 33 | }, 34 | "outputs": [], 35 | "source": [ 36 | "def xor_stream(data: bytes, key: bytes) -> bytes: # toy XOR stream cipher\n", 37 | " \"\"\"XOR each plaintext byte with the corresponding key byte.\n", 38 | "\n", 39 | " This is not secure, but it makes the keystream idea tangible.\n", 40 | " \"\"\"\n", 41 | " return bytes(d ^ k for d, k in zip(data, key))\n", 42 | "\n", 43 | "plaintext = b\"attack at dawn\" # message to \"encrypt\"\n", 44 | "keystream = b\"supersecretstream\"[: len(plaintext)] # truncate to match length\n", 45 | "\n", 46 | "ciphertext = xor_stream(plaintext, keystream) # encrypt\n", 47 | "print(\"Ciphertext bytes:\", ciphertext)\n", 48 | "\n", 49 | "recovered = xor_stream(ciphertext, keystream) # decrypt by XORing again\n", 50 | "print(\"Recovered plaintext:\", recovered)\n" 51 | ] 52 | }, 53 | { 54 | "cell_type": "markdown", 55 | "metadata": {}, 56 | "source": [ 57 | "Next, you demonstrate the classic keystream-reuse failure. By encrypting two different messages with the same XOR keystream, you reveal structure in the XOR of ciphertexts—exactly why real systems must never reuse a (key, nonce) pair." 58 | ] 59 | }, 60 | { 61 | "cell_type": "code", 62 | "execution_count": null, 63 | "metadata": { 64 | "trusted": true 65 | }, 66 | "outputs": [], 67 | "source": [ 68 | "def xor_bytes(a: bytes, b: bytes) -> bytes:\n", 69 | " \"\"\"XOR two byte strings up to the length of the shorter one.\"\"\"\n", 70 | " return bytes(x ^ y for x, y in zip(a, b))\n", 71 | "\n", 72 | "msg1 = b\"pay alice 10\" # first plaintext\n", 73 | "msg2 = b\"pay bob 10 \" # second plaintext (note padding)\n", 74 | "\n", 75 | "ks = b\"samekeystream!!!\"[: max(len(msg1), len(msg2))] # reused keystream\n", 76 | "\n", 77 | "c1 = xor_stream(msg1, ks) # encrypt first message\n", 78 | "c2 = xor_stream(msg2, ks) # encrypt second message with same keystream\n", 79 | "\n", 80 | "xor_ct = xor_bytes(c1, c2) # XOR of ciphertexts\n", 81 | "print(\"c1 XOR c2:\", xor_ct)\n", 82 | "print(\"(Should equal msg1 XOR msg2):\", xor_bytes(msg1, msg2))\n" 83 | ] 84 | }, 85 | { 86 | "cell_type": "markdown", 87 | "metadata": {}, 88 | "source": [ 89 | "\"The
\n" 90 | ] 91 | } 92 | ], 93 | "metadata": { 94 | "colab": { 95 | "name": "Crypto Foundations – Chapter 4 – Symmetric Encryption Basics", 96 | "provenance": [] 97 | }, 98 | "kernelspec": { 99 | "display_name": "Python 3", 100 | "language": "python", 101 | "name": "python3" 102 | }, 103 | "language_info": { 104 | "name": "python", 105 | "pygments_lexer": "ipython3" 106 | } 107 | }, 108 | "nbformat": 4, 109 | "nbformat_minor": 5 110 | } 111 | -------------------------------------------------------------------------------- /notebooks/2_book/chapter09_privacy_attacks_and_real_world_risks.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "\"The
\n" 8 | ] 9 | }, 10 | { 11 | "cell_type": "markdown", 12 | "metadata": {}, 13 | "source": [ 14 | "# Bitcoin Foundations\n", 15 | "## Chapter 9 – Privacy, Attacks, and Real-World Risks\n", 16 | "\n", 17 | "© Dr. Yves J. Hilpisch | The Python Quants GmbH
\n", 18 | "The Crypto Engineer | AI-Powered by GPT 5.1.\n" 19 | ] 20 | }, 21 | { 22 | "cell_type": "markdown", 23 | "metadata": {}, 24 | "source": [ 25 | "This lab constructs a tiny synthetic transaction graph and applies a naive multi-input clustering heuristic. It shows how simple rules can already link addresses together, grounding the chapter’s discussion of on-chain privacy and analyst tooling in a concrete toy example." 26 | ] 27 | }, 28 | { 29 | "cell_type": "code", 30 | "execution_count": null, 31 | "metadata": { 32 | "trusted": true 33 | }, 34 | "outputs": [], 35 | "source": [ 36 | "from collections import defaultdict # grouping helper\n", 37 | "from typing import List, Dict, Set # type hints\n", 38 | "\n", 39 | "\n", 40 | "Tx = Dict[str, List[str]] # tx model: {\"inputs\": [...], \"outputs\": [...]}\\n\n", 41 | "\n", 42 | "\n", 43 | "def multi_input_clusters(txs: List[Tx]) -> List[Set[str]]:\n", 44 | " \"\"\"Return clusters of addresses based on shared inputs.\"\"\"\n", 45 | " parent: Dict[str, str] = {} # union-find parent map\n", 46 | "\n", 47 | " def find(x: str) -> str:\n", 48 | " \"\"\"Find representative for address x.\"\"\"\n", 49 | " parent.setdefault(x, x)\n", 50 | " if parent[x] != x:\n", 51 | " parent[x] = find(parent[x]) # path compression\n", 52 | " return parent[x]\n", 53 | "\n", 54 | " def union(a: str, b: str) -> None:\n", 55 | " \"\"\"Merge clusters containing a and b.\"\"\"\n", 56 | " ra, rb = find(a), find(b)\n", 57 | " if ra != rb:\n", 58 | " parent[rb] = ra # attach one root to the other\n", 59 | "\n", 60 | " # Link all inputs within the same transaction\n", 61 | " for tx in txs:\n", 62 | " inputs = tx[\"inputs\"]\n", 63 | " for i in range(1, len(inputs)):\n", 64 | " union(inputs[0], inputs[i]) # connect to first input\n", 65 | "\n", 66 | " # Build clusters from parent map\n", 67 | " clusters: Dict[str, Set[str]] = defaultdict(set)\n", 68 | " for addr in parent:\n", 69 | " clusters[find(addr)].add(addr)\n", 70 | " return list(clusters.values())\n", 71 | "\n", 72 | "\n", 73 | "toy_txs: List[Tx] = [\n", 74 | " {\"inputs\": [\"a1\", \"a2\"], \"outputs\": [\"s\", \"ca\"]},\n", 75 | " {\"inputs\": [\"b1\"], \"outputs\": [\"s\", \"cb\"]},\n", 76 | " {\"inputs\": [\"a2\", \"a3\"], \"outputs\": [\"x\"]},\n", 77 | "] # end of toy transaction list\n", 78 | "\n", 79 | "for idx, cluster in enumerate(multi_input_clusters(toy_txs), start=1):\n", 80 | " print(f\"Cluster {idx}: {sorted(cluster)}\") # show grouped addresses\n" 81 | ] 82 | }, 83 | { 84 | "cell_type": "markdown", 85 | "metadata": {}, 86 | "source": [ 87 | "\"The
\n" 88 | ] 89 | } 90 | ], 91 | "metadata": { 92 | "colab": { 93 | "name": "Bitcoin Foundations – Chapter 9 – Privacy, Attacks, and Real-World Risks", 94 | "provenance": [] 95 | }, 96 | "kernelspec": { 97 | "display_name": "Python 3", 98 | "language": "python", 99 | "name": "python3" 100 | }, 101 | "language_info": { 102 | "name": "python", 103 | "pygments_lexer": "ipython3" 104 | } 105 | }, 106 | "nbformat": 4, 107 | "nbformat_minor": 5 108 | } 109 | 110 | -------------------------------------------------------------------------------- /notebooks/1_book/chapter02_math_snack_bar.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "\"The
\n" 8 | ] 9 | }, 10 | { 11 | "cell_type": "markdown", 12 | "metadata": {}, 13 | "source": [ 14 | "# Crypto Foundations\n", 15 | "## Chapter 2 – Math Snack Bar\n", 16 | "\n", 17 | "© Dr. Yves J. Hilpisch | The Python Quants GmbH
\n", 18 | "The Crypto Engineer | AI-Powered by GPT 5.1.\n" 19 | ] 20 | }, 21 | { 22 | "cell_type": "markdown", 23 | "metadata": {}, 24 | "source": [ 25 | "This first mini-lab lets you poke at modular inverses numerically. You can change the modulus and base to see when an inverse exists, and verify that the product really lands back on 1 modulo `n`, connecting the abstract extended Euclidean algorithm to concrete numbers." 26 | ] 27 | }, 28 | { 29 | "cell_type": "code", 30 | "execution_count": null, 31 | "metadata": { 32 | "trusted": true 33 | }, 34 | "outputs": [], 35 | "source": [ 36 | "def mod_inverse(a: int, n: int) -> int | None: # return a^{-1} mod n if it exists\n", 37 | " \"\"\"Compute the modular inverse of a modulo n using a brute-force search.\n", 38 | "\n", 39 | " This is for intuition only; later you will use more efficient algorithms.\n", 40 | " \"\"\"\n", 41 | " a = a % n # normalise a into 0..n-1\n", 42 | " for x in range(1, n): # try all possible candidates\n", 43 | " if (a * x) % n == 1: # check inverse condition\n", 44 | " return x\n", 45 | " return None # no inverse exists if we never hit 1\n", 46 | "\n", 47 | "pairs = [(3, 11), (2, 8), (7, 26)] # (a, n) examples to explore\n", 48 | "for a, n in pairs: # iterate over (a, n) pairs\n", 49 | " inv = mod_inverse(a, n) # attempt to find inverse\n", 50 | " if inv is None:\n", 51 | " print(f\"a={a} has no inverse modulo n={n}\")\n", 52 | " else:\n", 53 | " print(f\"a={a}, n={n} -> inverse={inv}, check={(a * inv) % n}\")\n" 54 | ] 55 | }, 56 | { 57 | "cell_type": "markdown", 58 | "metadata": {}, 59 | "source": [ 60 | "Next, you translate the birthday-bound intuition into a tiny function you can experiment with. By changing the sample count `k` and the space size `N`, you see how quickly collision probability grows and why even modest key or nonce sizes must be chosen with care." 61 | ] 62 | }, 63 | { 64 | "cell_type": "code", 65 | "execution_count": null, 66 | "metadata": { 67 | "trusted": true 68 | }, 69 | "outputs": [], 70 | "source": [ 71 | "import math # exponential function for the birthday bound\n", 72 | "\n", 73 | "def birthday_collision_prob(k: int, N: int) -> float:\n", 74 | " \"\"\"Approximate collision probability for k samples from a space of size N.\"\"\"\n", 75 | " if k <= 1:\n", 76 | " return 0.0\n", 77 | " return 1 - math.exp(-k * (k - 1) / (2 * N)) # standard birthday-bound form\n", 78 | "\n", 79 | "N = 10**6 # toy space size (e.g., 20-bit space)\n", 80 | "for k in (10, 100, 500, 1000): # sample sizes to compare\n", 81 | " p = birthday_collision_prob(k, N) # compute approximate collision probability\n", 82 | " print(f\"k={k:4d}, N={N:>8d} -> collision probability ≈ {p:.6f}\")\n" 83 | ] 84 | }, 85 | { 86 | "cell_type": "markdown", 87 | "metadata": {}, 88 | "source": [ 89 | "\"The
\n" 90 | ] 91 | } 92 | ], 93 | "metadata": { 94 | "colab": { 95 | "name": "Crypto Foundations – Chapter 2 – Math Snack Bar", 96 | "provenance": [] 97 | }, 98 | "kernelspec": { 99 | "display_name": "Python 3", 100 | "language": "python", 101 | "name": "python3" 102 | }, 103 | "language_info": { 104 | "name": "python", 105 | "pygments_lexer": "ipython3" 106 | } 107 | }, 108 | "nbformat": 4, 109 | "nbformat_minor": 5 110 | } 111 | -------------------------------------------------------------------------------- /notebooks/1_book/appendixA_math_foundations_refresher.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "\"The
\n" 8 | ] 9 | }, 10 | { 11 | "cell_type": "markdown", 12 | "metadata": {}, 13 | "source": [ 14 | "# Crypto Foundations\n", 15 | "## Appendix A – Math Foundations Refresher\n", 16 | "\n", 17 | "© Dr. Yves J. Hilpisch | The Python Quants GmbH
\n", 18 | "The Crypto Engineer | AI-Powered by GPT 5.1.\n" 19 | ] 20 | }, 21 | { 22 | "cell_type": "markdown", 23 | "metadata": {}, 24 | "source": [ 25 | "This first snippet checks, by brute force, which residues have multiplicative inverses modulo a fixed `n`. It turns the textbook rule “inverse exists iff `gcd(a, n) == 1`” into a quick experiment you can run whenever you want to sanity-check arithmetic over small moduli." 26 | ] 27 | }, 28 | { 29 | "cell_type": "code", 30 | "execution_count": null, 31 | "metadata": { 32 | "trusted": true 33 | }, 34 | "outputs": [], 35 | "source": [ 36 | "import math # access to gcd\n", 37 | "\n", 38 | "def has_inverse(a: int, n: int) -> bool:\n", 39 | " \"\"\"Return True if a has a multiplicative inverse modulo n.\"\"\"\n", 40 | " return math.gcd(a, n) == 1 # inverse exists iff gcd(a, n) == 1\n", 41 | "\n", 42 | "for a in range(1, 13): # sample values for a\n", 43 | " print(f\"a={a}, has inverse mod 12? {has_inverse(a, 12)}\")\n" 44 | ] 45 | }, 46 | { 47 | "cell_type": "markdown", 48 | "metadata": {}, 49 | "source": [ 50 | "Next you approximate the birthday bound numerically. By plugging different sample counts `k` into the same function, you see how quickly collision probability rises even for modest spaces, reinforcing the intuition behind nonce and hash-length choices." 51 | ] 52 | }, 53 | { 54 | "cell_type": "code", 55 | "execution_count": null, 56 | "metadata": { 57 | "trusted": true 58 | }, 59 | "outputs": [], 60 | "source": [ 61 | "import math # access to exp for approximations\n", 62 | "\n", 63 | "def birthday_prob(k: int, N: int) -> float:\n", 64 | " \"\"\"Approximate collision probability for k draws from N possibilities.\"\"\"\n", 65 | " return 1 - math.exp(-k * (k - 1) / (2 * N))\n", 66 | "\n", 67 | "N = 2**16 # sample space size (like a 16-bit nonce)\n", 68 | "for k in [100, 500, 2000]:\n", 69 | " p = birthday_prob(k, N)\n", 70 | " print(f\"k={k}, N={N}, p≈{p:.4f}\")\n" 71 | ] 72 | }, 73 | { 74 | "cell_type": "markdown", 75 | "metadata": {}, 76 | "source": [ 77 | "Finally, you connect raw counts to \"bits of entropy\" via `log2`. This is the mental bridge the appendix uses whenever it turns large keyspaces or password dictionaries into more intuitive bit-sized security levels." 78 | ] 79 | }, 80 | { 81 | "cell_type": "code", 82 | "execution_count": null, 83 | "metadata": { 84 | "trusted": true 85 | }, 86 | "outputs": [], 87 | "source": [ 88 | "import math # mathematical functions including log2\n", 89 | "\n", 90 | "options = 10_000_000 # number of possibilities\n", 91 | "bits = math.log2(options) # bits of entropy for uniform choices\n", 92 | "\n", 93 | "print(\"Options:\", options)\n", 94 | "print(\"Entropy (bits):\", bits)\n" 95 | ] 96 | }, 97 | { 98 | "cell_type": "markdown", 99 | "metadata": {}, 100 | "source": [ 101 | "\"The
\n" 102 | ] 103 | } 104 | ], 105 | "metadata": { 106 | "colab": { 107 | "name": "Crypto Foundations – Appendix A – Math Foundations Refresher", 108 | "provenance": [] 109 | }, 110 | "kernelspec": { 111 | "display_name": "Python 3", 112 | "language": "python", 113 | "name": "python3" 114 | }, 115 | "language_info": { 116 | "name": "python", 117 | "pygments_lexer": "ipython3" 118 | } 119 | }, 120 | "nbformat": 4, 121 | "nbformat_minor": 5 122 | } 123 | -------------------------------------------------------------------------------- /notebooks/1_book/chapter06_keys_randomness_and_kdfs.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "\"The
\n" 8 | ] 9 | }, 10 | { 11 | "cell_type": "markdown", 12 | "metadata": {}, 13 | "source": [ 14 | "# Crypto Foundations\n", 15 | "## Chapter 6 – Keys, Randomness, and KDFs\n", 16 | "\n", 17 | "© Dr. Yves J. Hilpisch | The Python Quants GmbH
\n", 18 | "The Crypto Engineer | AI-Powered by GPT 5.1.\n" 19 | ] 20 | }, 21 | { 22 | "cell_type": "markdown", 23 | "metadata": {}, 24 | "source": [ 25 | "You begin by deriving a key from a password using PBKDF2. This mirrors real-world setups where humans remember a passphrase but systems need a fixed-length key with tunable work factors to slow down offline guessing attacks." 26 | ] 27 | }, 28 | { 29 | "cell_type": "code", 30 | "execution_count": null, 31 | "metadata": { 32 | "trusted": true 33 | }, 34 | "outputs": [], 35 | "source": [ 36 | "import hashlib\n", 37 | "import os\n", 38 | "\n", 39 | "password = \"correct horse battery staple\".encode(\"utf-8\")\n", 40 | "salt = os.urandom(16) # random salt per user/record\n", 41 | "iterations = 200_000 # work factor; adjust as hardware evolves\n", 42 | "\n", 43 | "dk = hashlib.pbkdf2_hmac(\"sha256\", password, salt, iterations, dklen=32)\n", 44 | "print(\"Derived key (hex):\", dk.hex())\n", 45 | "print(\"Salt (hex):\", salt.hex())\n", 46 | "print(\"Iterations:\", iterations)\n" 47 | ] 48 | }, 49 | { 50 | "cell_type": "markdown", 51 | "metadata": {}, 52 | "source": [ 53 | "Next, you wrap this derivation in a tiny storage format. The idea is to bundle salt, iteration count, and derived key in a single string so that verification later uses the exact same parameters, making password checks reproducible and explicit." 54 | ] 55 | }, 56 | { 57 | "cell_type": "code", 58 | "execution_count": null, 59 | "metadata": { 60 | "trusted": true 61 | }, 62 | "outputs": [], 63 | "source": [ 64 | "import hashlib\n", 65 | "import os\n", 66 | "\n", 67 | "def derive_password_record(password: str) -> str:\n", 68 | " salt = os.urandom(16)\n", 69 | " iterations = 150_000\n", 70 | " dk = hashlib.pbkdf2_hmac(\"sha256\", password.encode(\"utf-8\"), salt, iterations, dklen=32)\n", 71 | " return f\"pbkdf2_sha256${iterations}${salt.hex()}${dk.hex()}\"\n", 72 | "\n", 73 | "record = derive_password_record(\"s3cret\")\n", 74 | "print(\"Stored record:\", record)\n" 75 | ] 76 | }, 77 | { 78 | "cell_type": "markdown", 79 | "metadata": {}, 80 | "source": [ 81 | "Finally, you draw a truly random key and nonce from the operating system. This exercise reinforces that key material and nonces should come from a cryptographically strong source of randomness, not from ad hoc generators or predictable sequences." 82 | ] 83 | }, 84 | { 85 | "cell_type": "code", 86 | "execution_count": null, 87 | "metadata": { 88 | "trusted": true 89 | }, 90 | "outputs": [], 91 | "source": [ 92 | "import os\n", 93 | "\n", 94 | "key = os.urandom(32) # 256-bit symmetric key\n", 95 | "nonce = os.urandom(12) # 96-bit nonce often used with AEAD\n", 96 | "\n", 97 | "print(\"Key (hex):\", key.hex())\n", 98 | "print(\"Nonce (hex):\", nonce.hex())\n" 99 | ] 100 | }, 101 | { 102 | "cell_type": "markdown", 103 | "metadata": {}, 104 | "source": [ 105 | "\"The
\n" 106 | ] 107 | } 108 | ], 109 | "metadata": { 110 | "colab": { 111 | "name": "Crypto Foundations – Chapter 6 – Keys, Randomness, and KDFs", 112 | "provenance": [] 113 | }, 114 | "kernelspec": { 115 | "display_name": "Python 3", 116 | "language": "python", 117 | "name": "python3" 118 | }, 119 | "language_info": { 120 | "name": "python", 121 | "pygments_lexer": "ipython3" 122 | } 123 | }, 124 | "nbformat": 4, 125 | "nbformat_minor": 5 126 | } 127 | -------------------------------------------------------------------------------- /notebooks/4_book/chapter09_governance_risk_and_compliance_memos.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "\"The
\n" 8 | ] 9 | }, 10 | { 11 | "cell_type": "markdown", 12 | "metadata": {}, 13 | "source": [ 14 | "# Crypto Engineering\n", 15 | "## Chapter 9 – Governance, Risk, and Compliance Memos\n", 16 | "\n", 17 | "© Dr. Yves J. Hilpisch | The Python Quants GmbH
\n", 18 | "The Crypto Engineer | AI-Powered by GPT 5.1.\n" 19 | ] 20 | }, 21 | { 22 | "cell_type": "markdown", 23 | "metadata": {}, 24 | "source": [ 25 | "This helper reads a small change description and template, then writes a Markdown risk memo file under `governance/memos/`. It turns the chapter’s memo pattern into a concrete generator you can adapt for your own governance workflows." 26 | ] 27 | }, 28 | { 29 | "cell_type": "code", 30 | "execution_count": null, 31 | "metadata": { 32 | "trusted": true 33 | }, 34 | "outputs": [], 35 | "source": [ 36 | "import json\n", 37 | "import pathlib\n", 38 | "from datetime import date\n", 39 | "\n", 40 | "base = pathlib.Path(\"governance\")\n", 41 | "base.mkdir(exist_ok=True)\n", 42 | "memo_dir = base / \"memos\"\n", 43 | "memo_dir.mkdir(exist_ok=True)\n", 44 | "\n", 45 | "template_path = base / \"risk_template.json\"\n", 46 | "change_path = base / \"pending_change.json\"\n", 47 | "\n", 48 | "if not template_path.exists():\n", 49 | " template_path.write_text(json.dumps({\"regulatory_questions\": \"List key regulatory considerations here.\"}), encoding=\"utf-8\")\n", 50 | "if not change_path.exists():\n", 51 | " change_path.write_text(\n", 52 | " json.dumps(\n", 53 | " {\n", 54 | " \"id\": \"chg-wallet-limits\",\n", 55 | " \"title\": \"Adjust withdrawal limits\",\n", 56 | " \"owner\": \"alice\",\n", 57 | " \"summary\": \"Increase daily withdrawal cap for verified users.\",\n", 58 | " \"user_impact\": \"Higher limits for KYC-complete accounts.\",\n", 59 | " \"controls\": \"Monitor large outflows; alert on anomalies.\",\n", 60 | " \"open_questions\": \"Do we need updated terms of service?\",\n+ " }\n", 61 | " ),\n", 62 | " encoding=\"utf-8\",\n", 63 | " )\n", 64 | "\n", 65 | "template = json.loads(template_path.read_text(encoding=\"utf-8\"))\n", 66 | "change = json.loads(change_path.read_text(encoding=\"utf-8\"))\n", 67 | "\n", 68 | "memo = f\"\"\"# Risk Memo: {change['title']}\n", 69 | "Date: {date.today().isoformat()}\n", 70 | "Owner: {change['owner']}\n", 71 | "\n", 72 | "## Summary\n", 73 | "{change['summary']}\n", 74 | "\n", 75 | "## User Impact\n", 76 | "{change['user_impact']}\n", 77 | "\n", 78 | "## Regulatory Considerations\n", 79 | "{template['regulatory_questions']}\n", 80 | "\n", 81 | "## Controls and Monitoring\n", 82 | "{change['controls']}\n", 83 | "\n", 84 | "## Open Questions\n", 85 | "{change['open_questions']}\n", 86 | "\"\"\"\n", 87 | "\n", 88 | "path = memo_dir / f\"{change['id']}.md\"\n", 89 | "path.write_text(memo, encoding=\"utf-8\")\n", 90 | "print(f\"Wrote memo to {path}\")\n" 91 | ] 92 | }, 93 | { 94 | "cell_type": "markdown", 95 | "metadata": {}, 96 | "source": [ 97 | "\"The
\n" 98 | ] 99 | } 100 | ], 101 | "metadata": { 102 | "colab": { 103 | "name": "Crypto Engineering – Chapter 9 – Governance, Risk, and Compliance Memos", 104 | "provenance": [] 105 | }, 106 | "kernelspec": { 107 | "display_name": "Python 3", 108 | "language": "python", 109 | "name": "python3" 110 | }, 111 | "language_info": { 112 | "name": "python", 113 | "pygments_lexer": "ipython3" 114 | } 115 | }, 116 | "nbformat": 4, 117 | "nbformat_minor": 5 118 | } 119 | 120 | -------------------------------------------------------------------------------- /notebooks/2_book/chapter02_hashes_addresses_and_identities.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "\"The
\n" 8 | ] 9 | }, 10 | { 11 | "cell_type": "markdown", 12 | "metadata": {}, 13 | "source": [ 14 | "# Bitcoin Foundations\n", 15 | "## Chapter 2 – Hashes, Addresses, and Identities\n", 16 | "\n", 17 | "© Dr. Yves J. Hilpisch | The Python Quants GmbH
\n", 18 | "The Crypto Engineer | AI-Powered by GPT 5.1.\n" 19 | ] 20 | }, 21 | { 22 | "cell_type": "markdown", 23 | "metadata": {}, 24 | "source": [ 25 | "You begin by turning a toy compressed public key into a shorter fingerprint using SHA-256 followed by RIPEMD-160. This mirrors the real \"hash160\" pipeline used in classic Bitcoin addresses and gives you a concrete feel for how long public keys become fixed-size identifiers." 26 | ] 27 | }, 28 | { 29 | "cell_type": "code", 30 | "execution_count": null, 31 | "metadata": { 32 | "trusted": true 33 | }, 34 | "outputs": [], 35 | "source": [ 36 | "import hashlib # hashing primitives from the standard library\n", 37 | "\n", 38 | "pubkey_hex = \"02\" + \"11\" * 32 # toy compressed public key (not real)\n", 39 | "pubkey_bytes = bytes.fromhex(pubkey_hex) # parse hex into bytes\n", 40 | "\n", 41 | "sha256_digest = hashlib.sha256(pubkey_bytes).digest() # SHA-256 of public key\n", 42 | "ripemd160 = hashlib.new(\"ripemd160\") # construct RIPEMD-160 hash object\n", 43 | "ripemd160.update(sha256_digest) # feed SHA-256 output into RIPEMD-160\n", 44 | "hash160_bytes = ripemd160.digest() # 20-byte fingerprint\n", 45 | "\n", 46 | "print(\"pubkey:\", pubkey_hex) # show toy public key\n", 47 | "print(\"hash160:\", hash160_bytes.hex()) # show resulting fingerprint\n" 48 | ] 49 | }, 50 | { 51 | "cell_type": "markdown", 52 | "metadata": {}, 53 | "source": [ 54 | "Next you wrap this fingerprint in a Version+Payload+Checksum structure and encode it with a Base58-like alphabet. This toy Base58Check sketch leaves out edge cases, but it makes visible how version bytes, payloads, and checksums combine into the address strings you see in wallets and explorers." 55 | ] 56 | }, 57 | { 58 | "cell_type": "code", 59 | "execution_count": null, 60 | "metadata": { 61 | "trusted": true 62 | }, 63 | "outputs": [], 64 | "source": [ 65 | "import hashlib # reused for checksum\n", 66 | "\n", 67 | "alphabet = \"123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz\" # Base58 set\n", 68 | "\n", 69 | "version_byte = b\"\\x00\" # pretend mainnet P2PKH\n", 70 | "payload = hash160_bytes # use fingerprint from previous example\n", 71 | "data = version_byte + payload # version + payload\n", 72 | "\n", 73 | "checksum_full = hashlib.sha256(hashlib.sha256(data).digest()).digest() # double SHA-256\n", 74 | "checksum = checksum_full[:4] # first four bytes as checksum\n", 75 | "full = data + checksum # bytes to encode\n", 76 | "\n", 77 | "num = int.from_bytes(full, \"big\") # interpret as big-endian integer\n", 78 | "encoded = \"\" # result string\n", 79 | "while num > 0: # repeated division by 58\n", 80 | " num, rem = divmod(num, 58) # quotient and remainder\n", 81 | " encoded = alphabet[rem] + encoded # prepend matching character\n", 82 | "\n", 83 | "print(\"Toy address:\", encoded) # human-readable representation\n" 84 | ] 85 | }, 86 | { 87 | "cell_type": "markdown", 88 | "metadata": {}, 89 | "source": [ 90 | "\"The
\n" 91 | ] 92 | } 93 | ], 94 | "metadata": { 95 | "colab": { 96 | "name": "Bitcoin Foundations – Chapter 2 – Hashes, Addresses, and Identities", 97 | "provenance": [] 98 | }, 99 | "kernelspec": { 100 | "display_name": "Python 3", 101 | "language": "python", 102 | "name": "python3" 103 | }, 104 | "language_info": { 105 | "name": "python", 106 | "pygments_lexer": "ipython3" 107 | } 108 | }, 109 | "nbformat": 4, 110 | "nbformat_minor": 5 111 | } 112 | 113 | -------------------------------------------------------------------------------- /notebooks/1_book/chapter08_secure_transport_and_storage.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "\"The
\n" 8 | ] 9 | }, 10 | { 11 | "cell_type": "markdown", 12 | "metadata": {}, 13 | "source": [ 14 | "# Crypto Foundations\n", 15 | "## Chapter 8 – Secure Transport and Storage\n", 16 | "\n", 17 | "© Dr. Yves J. Hilpisch | The Python Quants GmbH
\n", 18 | "The Crypto Engineer | AI-Powered by GPT 5.1.\n" 19 | ] 20 | }, 21 | { 22 | "cell_type": "markdown", 23 | "metadata": {}, 24 | "source": [ 25 | "The first sketch builds a toy envelope-encryption record in memory. You separate the data-encryption key (DEK) from the key-encryption key (KEK) and store a structured record with nonce, ciphertext, wrapped key, and metadata—mirroring the tuple you would persist in a real system." 26 | ] 27 | }, 28 | { 29 | "cell_type": "code", 30 | "execution_count": null, 31 | "metadata": { 32 | "trusted": true 33 | }, 34 | "outputs": [], 35 | "source": [ 36 | "import os # random bytes for keys and nonces\n", 37 | "\n", 38 | "def xor_stream(data: bytes, key: bytes) -> bytes:\n", 39 | " \"\"\"Toy XOR-based \"encryption\" to illustrate structure, not security.\"\"\"\n", 40 | " return bytes(d ^ k for d, k in zip(data, key))\n", 41 | "\n", 42 | "dek = os.urandom(32) # data-encryption key (DEK)\n", 43 | "kek = os.urandom(32) # key-encryption key (KEK)\n", 44 | "nonce = os.urandom(12) # nonce that would pair with AEAD in real code\n", 45 | "\n", 46 | "plaintext = b\"example document contents\" # data to protect\n", 47 | "stream = os.urandom(len(plaintext)) # toy keystream derived from DEK+nonce in reality\n", 48 | "\n", 49 | "ciphertext = xor_stream(plaintext, stream)\n", 50 | "wrapped_dek = xor_stream(dek, kek) # protect DEK with KEK\n", 51 | "\n", 52 | "record = {\n", 53 | " \"nonce\": nonce.hex(),\n", 54 | " \"ciphertext\": ciphertext.hex(),\n", 55 | " \"wrapped_dek\": wrapped_dek.hex(),\n", 56 | " \"metadata\": {\"name\": \"example.txt\", \"version\": 1},\n", 57 | "}\n", 58 | "\n", 59 | "print(\"Envelope record:\", record)\n" 60 | ] 61 | }, 62 | { 63 | "cell_type": "markdown", 64 | "metadata": {}, 65 | "source": [ 66 | "The final lab sketches a minimal TLS client using the Python standard library. It connects to a hostname, performs a TLS handshake with default certificate authorities, and then inspects the negotiated protocol version and peer certificate—highlighting client-side checks that support end-to-end security." 67 | ] 68 | }, 69 | { 70 | "cell_type": "code", 71 | "execution_count": null, 72 | "metadata": { 73 | "trusted": true 74 | }, 75 | "outputs": [], 76 | "source": [ 77 | "import ssl\n", 78 | "import socket\n", 79 | "\n", 80 | "hostname = \"example.com\" # expected server name\n", 81 | "port = 443\n", 82 | "\n", 83 | "context = ssl.create_default_context() # load system CAs and sane defaults\n", 84 | "\n", 85 | "with socket.create_connection((hostname, port)) as sock:\n", 86 | " with context.wrap_socket(sock, server_hostname=hostname) as tls_sock:\n", 87 | " cert = tls_sock.getpeercert() # server certificate as a dict\n", 88 | " print(\"TLS version:\", tls_sock.version())\n", 89 | " print(\"Certificate subject:\", cert.get(\"subject\"))\n" 90 | ] 91 | }, 92 | { 93 | "cell_type": "markdown", 94 | "metadata": {}, 95 | "source": [ 96 | "\"The
\n" 97 | ] 98 | } 99 | ], 100 | "metadata": { 101 | "colab": { 102 | "name": "Crypto Foundations – Chapter 8 – Secure Transport and Storage", 103 | "provenance": [] 104 | }, 105 | "kernelspec": { 106 | "display_name": "Python 3", 107 | "language": "python", 108 | "name": "python3" 109 | }, 110 | "language_info": { 111 | "name": "python", 112 | "pygments_lexer": "ipython3" 113 | } 114 | }, 115 | "nbformat": 4, 116 | "nbformat_minor": 5 117 | } 118 | -------------------------------------------------------------------------------- /notebooks/2_book/chapter03_utxos_and_the_transaction_model.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "\"The
\n" 8 | ] 9 | }, 10 | { 11 | "cell_type": "markdown", 12 | "metadata": {}, 13 | "source": [ 14 | "# Bitcoin Foundations\n", 15 | "## Chapter 3 – UTXOs and the Transaction Model\n", 16 | "\n", 17 | "© Dr. Yves J. Hilpisch | The Python Quants GmbH
\n", 18 | "The Crypto Engineer | AI-Powered by GPT 5.1.\n" 19 | ] 20 | }, 21 | { 22 | "cell_type": "markdown", 23 | "metadata": {}, 24 | "source": [ 25 | "This lab models a tiny UTXO set using Python classes and dictionaries. By stepping through `apply_tx`, you see how inputs remove existing outputs and how new outputs get added—mirroring how a real node evolves its global UTXO set as it processes transactions." 26 | ] 27 | }, 28 | { 29 | "cell_type": "code", 30 | "execution_count": null, 31 | "metadata": { 32 | "trusted": true 33 | }, 34 | "outputs": [], 35 | "source": [ 36 | "from dataclasses import dataclass # lightweight data containers\n", 37 | "from typing import Dict, List # type hints for clarity\n", 38 | "\n", 39 | "\n", 40 | "@dataclass\n", 41 | "class UTXO: # representation of one unspent output\n", 42 | " txid: str # transaction identifier\n", 43 | " index: int # output index within transaction\n", 44 | " value: int # value in satoshis\n", 45 | " owner: str # simplified owner label for this toy model\n", 46 | "\n", 47 | "\n", 48 | "UTXOSet = Dict[tuple, UTXO] # maps (txid, index) to UTXO\n", 49 | "\n", 50 | "\n", 51 | "def apply_tx(utxos: UTXOSet, inputs, outputs) -> None: # mutate UTXO set in place\n", 52 | " total_in = 0 # track sum of input values\n", 53 | " for txid, idx in inputs: # each input references a prior UTXO\n", 54 | " utxo = utxos.pop((txid, idx)) # remove UTXO, raising if missing\n", 55 | " total_in += utxo.value # add its value to total\n", 56 | " total_out = sum(v for _, v, _ in outputs) # sum of output values\n", 57 | " assert total_in >= total_out # enforce conservation with non-negative fee\n", 58 | " for new_txid, value, owner in outputs: # create new UTXOs\n", 59 | " key = (new_txid, 0) # toy model: one output per tx\n", 60 | " utxos[key] = UTXO(new_txid, 0, value, owner) # insert into UTXO set\n" 61 | ] 62 | }, 63 | { 64 | "cell_type": "markdown", 65 | "metadata": {}, 66 | "source": [ 67 | "With the UTXO set and update function in place, you can now play through a concrete \"Alice pays Bob\" transaction. Watching the before/after state helps make the conservation-of-value and double-spend rules feel much more tangible than in pure prose." 68 | ] 69 | }, 70 | { 71 | "cell_type": "code", 72 | "execution_count": null, 73 | "metadata": { 74 | "trusted": true 75 | }, 76 | "outputs": [], 77 | "source": [ 78 | "utxos: UTXOSet = {} # start with an empty set\n", 79 | "\n", 80 | "# initial coin: tx0 -> Alice with 1_000_000 sat\n", 81 | "utxos[(\"tx0\", 0)] = UTXO(\"tx0\", 0, 1_000_000, \"Alice\") # one UTXO for Alice\n", 82 | "\n", 83 | "inputs = [(\"tx0\", 0)] # Alice will spend this output\n", 84 | "outputs = [(\"tx1\", 300_000, \"Bob\")] # Bob receives 0.003 BTC\n", 85 | "\n", 86 | "apply_tx(utxos, inputs, outputs) # apply transaction to UTXO set\n", 87 | "\n", 88 | "for key, utxo in utxos.items(): # inspect resulting UTXO set\n", 89 | " print(key, utxo)\n" 90 | ] 91 | }, 92 | { 93 | "cell_type": "markdown", 94 | "metadata": {}, 95 | "source": [ 96 | "\"The
\n" 97 | ] 98 | } 99 | ], 100 | "metadata": { 101 | "colab": { 102 | "name": "Bitcoin Foundations – Chapter 3 – UTXOs and the Transaction Model", 103 | "provenance": [] 104 | }, 105 | "kernelspec": { 106 | "display_name": "Python 3", 107 | "language": "python", 108 | "name": "python3" 109 | }, 110 | "language_info": { 111 | "name": "python", 112 | "pygments_lexer": "ipython3" 113 | } 114 | }, 115 | "nbformat": 4, 116 | "nbformat_minor": 5 117 | } 118 | 119 | -------------------------------------------------------------------------------- /notebooks/1_book/appendixB_probability_entropy_and_collision_intuition.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "\"The
\n" 8 | ] 9 | }, 10 | { 11 | "cell_type": "markdown", 12 | "metadata": {}, 13 | "source": [ 14 | "# Crypto Foundations\n", 15 | "## Appendix B – Probability, Entropy, and Collision Intuition\n", 16 | "\n", 17 | "© Dr. Yves J. Hilpisch | The Python Quants GmbH
\n", 18 | "The Crypto Engineer | AI-Powered by GPT 5.1.\n" 19 | ] 20 | }, 21 | { 22 | "cell_type": "markdown", 23 | "metadata": {}, 24 | "source": [ 25 | "You start by estimating passphrase entropy under a simple uniform model. The goal is not to endorse real-world passwords, but to practice turning alphabet size and length into approximate bits so you can reason about brute-force cost." 26 | ] 27 | }, 28 | { 29 | "cell_type": "code", 30 | "execution_count": null, 31 | "metadata": { 32 | "trusted": true 33 | }, 34 | "outputs": [], 35 | "source": [ 36 | "import math\n", 37 | "\n", 38 | "alphabet_size = 26 # e.g. lowercase letters\n", 39 | "length = 10 # characters in the passphrase\n", 40 | "\n", 41 | "entropy_bits = length * math.log2(alphabet_size)\n", 42 | "print(\"Alphabet size:\", alphabet_size)\n", 43 | "print(\"Length:\", length)\n", 44 | "print(\"Idealised entropy (bits):\", entropy_bits)\n" 45 | ] 46 | }, 47 | { 48 | "cell_type": "markdown", 49 | "metadata": {}, 50 | "source": [ 51 | "Next you simulate collisions for randomly drawn nonces. By counting how many draws it takes before you hit a repeat, you build intuition for the birthday-bound curves discussed in the appendix." 52 | ] 53 | }, 54 | { 55 | "cell_type": "code", 56 | "execution_count": null, 57 | "metadata": { 58 | "trusted": true 59 | }, 60 | "outputs": [], 61 | "source": [ 62 | "import secrets # cryptographically strong randomness\n", 63 | "\n", 64 | "def simulate_collisions(trials: int, bits: int) -> int:\n", 65 | " \"\"\"Draw nonces until a collision occurs or we hit the trial limit.\n", 66 | "\n", 67 | " Returns the number of draws actually performed.\n", 68 | " \"\"\"\n", 69 | " seen = set()\n", 70 | " for i in range(1, trials + 1):\n", 71 | " nonce = secrets.token_bytes(bits // 8)\n", 72 | " if nonce in seen:\n", 73 | " return i # collision at draw i\n", 74 | " seen.add(nonce)\n", 75 | " return trials # no collision within the trial budget\n", 76 | "\n", 77 | "for bits in (16, 24): # toy sizes so collisions are observable\n", 78 | " draws = simulate_collisions(10_000, bits)\n", 79 | " print(f\"bits={bits}, draws until collision or cap={draws}\")\n" 80 | ] 81 | }, 82 | { 83 | "cell_type": "markdown", 84 | "metadata": {}, 85 | "source": [ 86 | "Finally, you connect \"security bits\" to rough work estimates. The appendix uses this line of reasoning whenever it translates bit levels into more intuitive statements about how much brute force an attacker would need on average." 87 | ] 88 | }, 89 | { 90 | "cell_type": "code", 91 | "execution_count": null, 92 | "metadata": { 93 | "trusted": true 94 | }, 95 | "outputs": [], 96 | "source": [ 97 | "for bits in (32, 64, 80, 128):\n", 98 | " space = 2**bits\n", 99 | " avg_work = space / 2 # average brute-force trials\n", 100 | " print(f\"{bits:3d} bits -> space≈2^{bits}, average work≈{avg_work:.2e} trials\")\n" 101 | ] 102 | }, 103 | { 104 | "cell_type": "markdown", 105 | "metadata": {}, 106 | "source": [ 107 | "\"The
\n" 108 | ] 109 | } 110 | ], 111 | "metadata": { 112 | "colab": { 113 | "name": "Crypto Foundations – Appendix B – Probability, Entropy, and Collision Intuition", 114 | "provenance": [] 115 | }, 116 | "kernelspec": { 117 | "display_name": "Python 3", 118 | "language": "python", 119 | "name": "python3" 120 | }, 121 | "language_info": { 122 | "name": "python", 123 | "pygments_lexer": "ipython3" 124 | } 125 | }, 126 | "nbformat": 4, 127 | "nbformat_minor": 5 128 | } 129 | -------------------------------------------------------------------------------- /notebooks/2_book/chapter04_scripts_and_spending_conditions.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "\"The
\n" 8 | ] 9 | }, 10 | { 11 | "cell_type": "markdown", 12 | "metadata": {}, 13 | "source": [ 14 | "# Bitcoin Foundations\n", 15 | "## Chapter 4 – Scripts and Spending Conditions\n", 16 | "\n", 17 | "© Dr. Yves J. Hilpisch | The Python Quants GmbH
\n", 18 | "The Crypto Engineer | AI-Powered by GPT 5.1.\n" 19 | ] 20 | }, 21 | { 22 | "cell_type": "markdown", 23 | "metadata": {}, 24 | "source": [ 25 | "To demystify Script, you first implement a tiny stack-based interpreter that understands a handful of opcodes: pushing data, duplicating the top of the stack, hashing, equality+verify, and a toy signature check. This makes the \"locking and unlocking\" narrative concrete in a few dozen lines of Python." 26 | ] 27 | }, 28 | { 29 | "cell_type": "code", 30 | "execution_count": null, 31 | "metadata": { 32 | "trusted": true 33 | }, 34 | "outputs": [], 35 | "source": [ 36 | "from typing import List, Union # type hints for clarity\n", 37 | "\n", 38 | "Opcode = str # represent opcodes as strings like \"OP_DUP\"\n", 39 | "Word = Union[bytes, bool] # stack elements are bytes or booleans\n", 40 | "\n", 41 | "\n", 42 | "def run_script(script: List[Union[Opcode, Word]], stack: List[Word]) -> List[Word]:\n", 43 | " for token in script: # walk through script tokens\n", 44 | " if isinstance(token, bytes): # data push\n", 45 | " stack.append(token) # push data on stack\n", 46 | " elif token == \"OP_DUP\": # duplicate top element\n", 47 | " stack.append(stack[-1])\n", 48 | " elif token == \"OP_HASH160\": # toy hash: just mark value\n", 49 | " data = stack.pop()\n", 50 | " stack.append(b\"H(\" + data + b\")\") # stand-in for real hash160\n", 51 | " elif token == \"OP_EQUALVERIFY\": # equality check with pop\n", 52 | " a = stack.pop()\n", 53 | " b = stack.pop()\n", 54 | " if a != b:\n", 55 | " raise ValueError(\"OP_EQUALVERIFY failed\")\n", 56 | " elif token == \"OP_CHECKSIG\": # toy signature check\n", 57 | " stack.pop() # pretend to consume signature\n", 58 | " stack.pop() # pretend to consume public key\n", 59 | " stack.append(True) # push success flag\n", 60 | " else:\n", 61 | " raise ValueError(f\"Unknown opcode: {token}\")\n", 62 | " return stack # final stack state\n" 63 | ] 64 | }, 65 | { 66 | "cell_type": "markdown", 67 | "metadata": {}, 68 | "source": [ 69 | "With the interpreter in place, you can simulate a simplified P2PKH spend by concatenating a toy `scriptSig` and `scriptPubKey`. Watching how the stack evolves and ends in `[True]` makes the spend condition feel much more mechanical and less like black magic." 70 | ] 71 | }, 72 | { 73 | "cell_type": "code", 74 | "execution_count": null, 75 | "metadata": { 76 | "trusted": true 77 | }, 78 | "outputs": [], 79 | "source": [ 80 | "sig = b\"\" # placeholder for signature bytes\n", 81 | "pub = b\"\" # placeholder for public key bytes\n", 82 | "expected_hash = b\"H(\" + pub + b\")\" # toy hash160(pubkey)\n", 83 | "\n", 84 | "script_sig = [sig, pub] # unlocking script pushes sig and pubkey\n", 85 | "script_pubkey = [ # locking script template\n", 86 | " \"OP_DUP\",\n", 87 | " \"OP_HASH160\",\n", 88 | " expected_hash,\n", 89 | " \"OP_EQUALVERIFY\",\n", 90 | " \"OP_CHECKSIG\",\n", 91 | "]\n", 92 | "\n", 93 | "stack: List[Word] = [] # start with empty stack\n", 94 | "stack = run_script(script_sig + script_pubkey, stack) # run combined script\n", 95 | "\n", 96 | "print(\"Final stack:\", stack) # should end with [True] in this toy model\n" 97 | ] 98 | }, 99 | { 100 | "cell_type": "markdown", 101 | "metadata": {}, 102 | "source": [ 103 | "\"The
\n" 104 | ] 105 | } 106 | ], 107 | "metadata": { 108 | "colab": { 109 | "name": "Bitcoin Foundations – Chapter 4 – Scripts and Spending Conditions", 110 | "provenance": [] 111 | }, 112 | "kernelspec": { 113 | "display_name": "Python 3", 114 | "language": "python", 115 | "name": "python3" 116 | }, 117 | "language_info": { 118 | "name": "python", 119 | "pygments_lexer": "ipython3" 120 | } 121 | }, 122 | "nbformat": 4, 123 | "nbformat_minor": 5 124 | } 125 | 126 | -------------------------------------------------------------------------------- /notebooks/2_book/chapter05_blocks_merkle_trees_and_chain_structure.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "\"The
\n" 8 | ] 9 | }, 10 | { 11 | "cell_type": "markdown", 12 | "metadata": {}, 13 | "source": [ 14 | "# Bitcoin Foundations\n", 15 | "## Chapter 5 – Blocks, Merkle Trees, and Chain Structure\n", 16 | "\n", 17 | "© Dr. Yves J. Hilpisch | The Python Quants GmbH
\n", 18 | "The Crypto Engineer | AI-Powered by GPT 5.1.\n" 19 | ] 20 | }, 21 | { 22 | "cell_type": "markdown", 23 | "metadata": {}, 24 | "source": [ 25 | "This first lab builds a Merkle root from a handful of toy transaction IDs. It mirrors the book’s explanation that blocks summarise many transactions into a single root hash, which is what actually gets committed into the header and chained over time." 26 | ] 27 | }, 28 | { 29 | "cell_type": "code", 30 | "execution_count": null, 31 | "metadata": { 32 | "trusted": true 33 | }, 34 | "outputs": [], 35 | "source": [ 36 | "import hashlib # hashing functions\n", 37 | "from typing import List # type hints\n", 38 | "\n", 39 | "\n", 40 | "def merkle_root(leaves: List[bytes]) -> bytes:\n", 41 | " level = leaves[:] # copy initial leaves\n", 42 | " if not level:\n", 43 | " return b\"\" # empty tree case\n", 44 | " while len(level) > 1: # iterate until one root remains\n", 45 | " if len(level) % 2 == 1: # if odd number of nodes\n", 46 | " level.append(level[-1]) # duplicate last leaf (Bitcoin-style)\n", 47 | " new_level = [] # next level up\n", 48 | " for i in range(0, len(level), 2): # pairwise combine\n", 49 | " h = hashlib.sha256(level[i] + level[i + 1]).digest() # hash pair\n", 50 | " new_level.append(h) # append parent hash\n", 51 | " level = new_level # move up one level\n", 52 | " return level[0] # single root hash\n", 53 | "\n", 54 | "\n", 55 | "txids = [ # toy transaction IDs as bytes\n", 56 | " b\"tx1\",\n", 57 | " b\"tx2\",\n", 58 | " b\"tx3\",\n", 59 | " b\"tx4\",\n", 60 | "]\n", 61 | "\n", 62 | "root = merkle_root(txids) # compute Merkle root\n", 63 | "print(\"Merkle root (hex):\", root.hex()) # show root as hex string\n" 64 | ] 65 | }, 66 | { 67 | "cell_type": "markdown", 68 | "metadata": {}, 69 | "source": [ 70 | "You then represent a tiny chain of block headers and verify that each header’s `prev_hash` field matches the previous block’s `hash`. This captures the core continuity check nodes perform when following the best chain." 71 | ] 72 | }, 73 | { 74 | "cell_type": "code", 75 | "execution_count": null, 76 | "metadata": { 77 | "trusted": true 78 | }, 79 | "outputs": [], 80 | "source": [ 81 | "from dataclasses import dataclass # structured data for headers\n", 82 | "from typing import Optional # optional types\n", 83 | "\n", 84 | "\n", 85 | "@dataclass\n", 86 | "class Header: # minimal header model\n", 87 | " height: int # block height\n", 88 | " hash: str # block hash (placeholder)\n", 89 | " prev_hash: Optional[str] # previous block hash\n", 90 | "\n", 91 | "\n", 92 | "chain = [ # small chain of three headers\n", 93 | " Header(height=0, hash=\"h0\", prev_hash=None),\n", 94 | " Header(height=1, hash=\"h1\", prev_hash=\"h0\"),\n", 95 | " Header(height=2, hash=\"h2\", prev_hash=\"h1\"),\n", 96 | "] # end of chain\n", 97 | "\n", 98 | "\n", 99 | "def is_chain_valid(headers) -> bool: # continuity check\n", 100 | " for i in range(1, len(headers)): # check links from second onward\n", 101 | " if headers[i].prev_hash != headers[i - 1].hash: # mismatch?\n", 102 | " return False # invalid link\n", 103 | " return True # all links consistent\n", 104 | "\n", 105 | "\n", 106 | "print(\"Chain valid?\", is_chain_valid(chain)) # expect True\n" 107 | ] 108 | }, 109 | { 110 | "cell_type": "markdown", 111 | "metadata": {}, 112 | "source": [ 113 | "\"The
\n" 114 | ] 115 | } 116 | ], 117 | "metadata": { 118 | "colab": { 119 | "name": "Bitcoin Foundations – Chapter 5 – Blocks, Merkle Trees, and Chain Structure", 120 | "provenance": [] 121 | }, 122 | "kernelspec": { 123 | "display_name": "Python 3", 124 | "language": "python", 125 | "name": "python3" 126 | }, 127 | "language_info": { 128 | "name": "python", 129 | "pygments_lexer": "ipython3" 130 | } 131 | }, 132 | "nbformat": 4, 133 | "nbformat_minor": 5 134 | } 135 | 136 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

The Crypto Engineer

2 | 3 | # The Crypto Engineer — Companion Code Base 4 | 5 | Welcome! This repository ships the runnable material that goes hand in hand with the four volumes of **The Crypto Engineer (TCE)**: 6 | 7 | 1. **Crypto Foundations** — hashing, symmetric crypto, MACs, key generation, probability intuition. 8 | 2. **Bitcoin Foundations** — UTXOs, scripts, mempools, mining, wallet drills. 9 | 3. **Cryptomarkets & Systems** — market plumbing, execution metrics, DeFi tooling. 10 | 4. **Crypto Engineering** — reviews, monitoring, incidents, governance, production runbooks. 11 | 12 | Each directory mirrors the structure of the books so you can jump from a chapter to the corresponding notebook or script without hunting for files. 13 | 14 | --- 15 | 16 | ## Repository Layout 17 | 18 | The folder structure mirrors the narrative arc of the books. Use it as your map: when a chapter references a notebook or script, you can find it instantly here. 19 | 20 | ``` 21 | code/ 22 | hashing/, aead/, entropy/, ... # Python scripts for the labs and drills 23 | notebooks/ 24 | 1_book/, 2_book/, 3_book/, 4_book/ # Jupyter notebooks aligned with each volume 25 | README.md # Learner-facing overview (this document) 26 | ``` 27 | 28 | ### Notebooks (`notebooks/`) 29 | 30 | These are your guided workbooks. Each one pairs directly with a chapter so you can move from reading to running code without guessing what to open. 31 | 32 | - Named after book and chapter (e.g., `1_book/chapter05_message_authentication_codes.ipynb`). 33 | - Designed to run top-to-bottom in Google Colab or any local Jupyter environment. 34 | - Contain the same callouts, diagrams, and exercises you see in the PDFs, plus runnable cells that demonstrate the algorithms step by step. 35 | 36 | ### Python Scripts (`code/`) 37 | 38 | Short, composable helpers live here. They back the lab notes and let you rehearse the same drills you read about in the books. 39 | 40 | - Small, focused utilities used in the lab notes (hash diagnostics, fee policy helpers, wallet drills, etc.). 41 | - Each subdirectory corresponds to a lab cluster, making it easy to locate the exact script referenced in a book margin or lab checklist. 42 | 43 | --- 44 | 45 | ## Working in Google Colab 46 | 47 | Colab is the fastest way to get started — no local setup, just a browser. Pick whichever approach matches your workflow. 48 | 49 | ### Option 1: Open a Notebook Directly in Colab 50 | 51 | 1. Navigate to the notebook in GitHub (e.g., `notebooks/2_book/chapter04_scripts_and_spending_conditions.ipynb`). 52 | 2. Replace the `github.com` portion of the URL with `colab.research.google.com/github`. 53 | 3. Click “Run anyway” when prompted — all notebooks assume Python 3.10+ and standard scientific packages. 54 | 55 | ### Option 2: Clone the Entire Repo in a Colab Notebook 56 | 57 | ```python 58 | from pathlib import Path 59 | import subprocess, sys 60 | 61 | repo_url = "https://github.com/yhilpisch/tcecode.git" 62 | target = Path("/content/tce") 63 | 64 | if not target.exists(): 65 | subprocess.run(["git", "clone", repo_url, str(target)], check=True) 66 | 67 | sys.path.append(str(target / "code")) 68 | print("Repo ready at", target) 69 | ``` 70 | 71 | Once cloned: 72 | 73 | - Open any notebook under `/content/tce/notebooks/...`. 74 | - To run a script, use `!python /content/tce/code/entropy/combined_entropy_bits.py`. 75 | - Modify notebooks directly in Colab and download/save copies to keep a personal record of experiments. 76 | 77 | ### Recommended Colab Settings 78 | 79 | These tweaks keep runtime surprises to a minimum. 80 | 81 | - **Runtime → Change runtime type → GPU (optional):** only needed for heavier simulations; most notebooks run fine on CPU. 82 | - **Install requirements:** when a notebook needs extra packages, the first cell installs them via `pip`. Let the cell finish before continuing. 83 | 84 | --- 85 | 86 | ## Running Locally 87 | 88 | Prefer your own machine? This path gives you full control, from IDE integration to offline work. 89 | 90 | ```bash 91 | git clone https://github.com/thepythonquants/tce-code-companion.git 92 | cd tce-code-companion 93 | python -m venv .venv 94 | source .venv/bin/activate 95 | pip install -r requirements.txt # provided in the repo root 96 | jupyter lab 97 | ``` 98 | 99 | Launch the matching notebook, follow the chapter instructions, and drop into the `code/` scripts whenever the book references a specific helper. 100 | 101 | --- 102 | 103 | ## Staying in Sync with the Books 104 | 105 | Treat this repo as the living lab companion: when the manuscripts evolve, so do these assets. 106 | 107 | - The notebooks are intentionally short and focused; they echo the diagrams and equations in the PDFs so you can iterate between reading and executing without context switching. 108 | - Each lab note includes a “Major Benefit” callout so you can decide whether to run the drill solo or with a team. 109 | - When the books are updated, this companion repo is refreshed in lockstep. Simply `git pull` (or re-clone in Colab) to pick up the latest code. 110 | 111 | Enjoy the build journey, and remember: if you can rehearse it here, you can ship it in production. 112 | 113 |

The Crypto Engineer

114 | -------------------------------------------------------------------------------- /notebooks/2_book/chapter01_bitcoin_in_context.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "\"The
\n" 8 | ] 9 | }, 10 | { 11 | "cell_type": "markdown", 12 | "metadata": {}, 13 | "source": [ 14 | "# Bitcoin Foundations\n", 15 | "## Chapter 1 – Bitcoin in Context\n", 16 | "\n", 17 | "© Dr. Yves J. Hilpisch | The Python Quants GmbH
\n", 18 | "The Crypto Engineer | AI-Powered by GPT 5.1.\n" 19 | ] 20 | }, 21 | { 22 | "cell_type": "markdown", 23 | "metadata": {}, 24 | "source": [ 25 | "We start by turning the narrative idea of a \"chain of blocks\" into an explicit Python structure. This tiny example models three block headers as dictionaries linked via `prev_hash` fields so you can see how each block points back to its predecessor, mirroring the way real Bitcoin nodes verify hash links when they walk the best chain." 26 | ] 27 | }, 28 | { 29 | "cell_type": "code", 30 | "execution_count": null, 31 | "metadata": { 32 | "trusted": true 33 | }, 34 | "outputs": [], 35 | "source": [ 36 | "blocks = [ # toy chain of three blocks\n", 37 | " {\n", 38 | " \"height\": 0, # genesis block height\n", 39 | " \"hash\": \"0000000...genesis\", # placeholder hash\n", 40 | " \"prev_hash\": None, # no previous block\n", 41 | " },\n", 42 | " {\n", 43 | " \"height\": 1, # next block in the chain\n", 44 | " \"hash\": \"0000001...block1\", # placeholder hash\n", 45 | " \"prev_hash\": \"0000000...genesis\", # points to genesis hash\n", 46 | " },\n", 47 | " {\n", 48 | " \"height\": 2, # third block\n", 49 | " \"hash\": \"0000002...block2\", # placeholder hash\n", 50 | " \"prev_hash\": \"0000001...block1\", # points to prior block\n", 51 | " },\n", 52 | "] # end of toy chain\n", 53 | "\n", 54 | "for b in blocks: # iterate over blocks\n", 55 | " print( # show height and hash link\n", 56 | " f\"height={b['height']}, hash={b['hash']}, prev={b['prev_hash']}\"\n", 57 | " )\n" 58 | ] 59 | }, 60 | { 61 | "cell_type": "markdown", 62 | "metadata": {}, 63 | "source": [ 64 | "Next, you encode the classic \"Alice pays Bob\" story as a simple threat model. Instead of keeping this in prose only, you capture assets, adversaries, assumptions, and controls in a dictionary that can live in version control. This makes it easy to diff how your understanding of Bitcoin risk evolves as you read later chapters and build real tools." 65 | ] 66 | }, 67 | { 68 | "cell_type": "code", 69 | "execution_count": null, 70 | "metadata": { 71 | "trusted": true 72 | }, 73 | "outputs": [], 74 | "source": [ 75 | "threat_model = { # structured view of one payment\n", 76 | " \"assets\": [ # what we want to protect\n", 77 | " \"Alice's spendable coins\",\n", 78 | " \"Bob's receipt of 0.01 BTC\",\n", 79 | " \"ledger integrity for this payment\",\n", 80 | " ],\n", 81 | " \"adversaries\": [ # who might attack\n", 82 | " \"double-spender broadcasting conflicts\",\n", 83 | " \"miner coalition attempting a short reorg\",\n", 84 | " \"network attacker eclipsing a node\",\n", 85 | " \"malicious or buggy wallet software\",\n", 86 | " ],\n", 87 | " \"assumptions\": [ # conditions we rely on\n", 88 | " \"no single miner has majority hash power\",\n", 89 | " \"Alice and Bob see multiple peers\",\n", 90 | " \"wallet implementations are well-audited\",\n", 91 | " ],\n", 92 | " \"controls\": [ # mitigations in and around Bitcoin\n", 93 | " \"wait for sufficient confirmations\",\n", 94 | " \"use full-node verification\",\n", 95 | " \"cross-check chain tips from several sources\",\n", 96 | " ],\n", 97 | "} # end of threat model\n", 98 | "\n", 99 | "for k, v in threat_model.items(): # iterate for display\n", 100 | " print(f\"{k.upper()}:\") # section heading\n", 101 | " for item in v: # each element in section\n", 102 | " print(\" -\", item) # bullet-like line\n" 103 | ] 104 | }, 105 | { 106 | "cell_type": "markdown", 107 | "metadata": {}, 108 | "source": [ 109 | "\"The
\n" 110 | ] 111 | } 112 | ], 113 | "metadata": { 114 | "colab": { 115 | "name": "Bitcoin Foundations – Chapter 1 – Bitcoin in Context", 116 | "provenance": [] 117 | }, 118 | "kernelspec": { 119 | "display_name": "Python 3", 120 | "language": "python", 121 | "name": "python3" 122 | }, 123 | "language_info": { 124 | "name": "python", 125 | "pygments_lexer": "ipython3" 126 | } 127 | }, 128 | "nbformat": 4, 129 | "nbformat_minor": 5 130 | } 131 | -------------------------------------------------------------------------------- /notebooks/2_book/chapter06_proof_of_work_and_mining_economics.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "\"The
\n" 8 | ] 9 | }, 10 | { 11 | "cell_type": "markdown", 12 | "metadata": {}, 13 | "source": [ 14 | "# Bitcoin Foundations\n", 15 | "## Chapter 6 – Proof of Work and Mining Economics\n", 16 | "\n", 17 | "© Dr. Yves J. Hilpisch | The Python Quants GmbH
\n", 18 | "The Crypto Engineer | AI-Powered by GPT 5.1.\n" 19 | ] 20 | }, 21 | { 22 | "cell_type": "markdown", 23 | "metadata": {}, 24 | "source": [ 25 | "You start by turning the PoW success probability into an expected block time. Given a hash rate and per-hash success probability, this function computes how long, on average, you would wait to find a block—linking the abstract target parameter to concrete seconds and minutes." 26 | ] 27 | }, 28 | { 29 | "cell_type": "code", 30 | "execution_count": null, 31 | "metadata": { 32 | "trusted": true 33 | }, 34 | "outputs": [], 35 | "source": [ 36 | "def expected_block_time(hash_rate: float, success_prob: float) -> float:\n", 37 | " \"\"\"Return expected seconds to find a block.\"\"\"\n", 38 | " return 1.0 / (hash_rate * success_prob) # 1 / (trials per second * success probability)\n", 39 | "\n", 40 | "\n", 41 | "hash_rate = 1e12 # 1 terahash per second (TH/s)\n", 42 | "target_ratio = 2**-32 # toy success probability per hash (not real difficulty)\n", 43 | "\n", 44 | "et = expected_block_time(hash_rate, target_ratio) # expected seconds\n", 45 | "print(\"Expected time (s):\", et)\n", 46 | "print(\"Expected time (min):\", et / 60)\n" 47 | ] 48 | }, 49 | { 50 | "cell_type": "markdown", 51 | "metadata": {}, 52 | "source": [ 53 | "Next, you simulate the randomness of block discovery using exponential inter-arrival times. This reinforces the idea that \"10 minutes\" is an average over many blocks, not a deterministic schedule, and shows how variable real block intervals can be." 54 | ] 55 | }, 56 | { 57 | "cell_type": "code", 58 | "execution_count": null, 59 | "metadata": { 60 | "trusted": true 61 | }, 62 | "outputs": [], 63 | "source": [ 64 | "import random # pseudo-random number generator\n", 65 | "import math # logarithms for exponential sampling\n", 66 | "\n", 67 | "\n", 68 | "def sample_inter_block_time(lam: float) -> float:\n", 69 | " \"\"\"Sample one exponential inter-block time with rate lam (blocks per second).\"\"\"\n", 70 | " u = random.random() # uniform in [0, 1)\n", 71 | " return -math.log(1.0 - u) / lam # inverse CDF for exponential\n", 72 | "\n", 73 | "\n", 74 | "network_hash_rate = 1e15 # 1 petahash per second (PH/s)\n", 75 | "success_prob = 2**-32 # same toy probability per hash as above\n", 76 | "lam = network_hash_rate * success_prob # expected blocks per second\n", 77 | "\n", 78 | "samples = [sample_inter_block_time(lam) for _ in range(10)] # draw 10 inter-block times\n", 79 | "\n", 80 | "print(\"Inter-block times (min):\")\n", 81 | "for t in samples:\n", 82 | " print(f\" {t/60:.2f}\")\n" 83 | ] 84 | }, 85 | { 86 | "cell_type": "markdown", 87 | "metadata": {}, 88 | "source": [ 89 | "Finally, you sketch a toy mining revenue calculator. It takes a miner’s hash-rate share, the current block reward, and a BTC/USD price to estimate expected daily revenue, connecting protocol parameters back to basic economic incentives." 90 | ] 91 | }, 92 | { 93 | "cell_type": "code", 94 | "execution_count": null, 95 | "metadata": { 96 | "trusted": true 97 | }, 98 | "outputs": [], 99 | "source": [ 100 | "block_reward_btc = 6.25 # BTC per block (example)\n", 101 | "btc_price_usd = 30_000 # USD per BTC (example)\n", 102 | "\n", 103 | "miner_hash_rate = 100e12 # 100 TH/s\n", 104 | "network_hash_rate = 200e15 # 200 PH/s\n", 105 | "\n", 106 | "blocks_per_day = 144 # approximate (24h * 60 / 10)\n", 107 | "\n", 108 | "share = miner_hash_rate / network_hash_rate # miner's fraction of hash power\n", 109 | "expected_blocks = share * blocks_per_day # expected blocks per day\n", 110 | "\n", 111 | "revenue_usd = expected_blocks * block_reward_btc * btc_price_usd # daily revenue\n", 112 | "\n", 113 | "print(\"Expected blocks/day:\", expected_blocks)\n", 114 | "print(\"Expected revenue/day (USD):\", revenue_usd)\n" 115 | ] 116 | }, 117 | { 118 | "cell_type": "markdown", 119 | "metadata": {}, 120 | "source": [ 121 | "\"The
\n" 122 | ] 123 | } 124 | ], 125 | "metadata": { 126 | "colab": { 127 | "name": "Bitcoin Foundations – Chapter 6 – Proof of Work and Mining Economics", 128 | "provenance": [] 129 | }, 130 | "kernelspec": { 131 | "display_name": "Python 3", 132 | "language": "python", 133 | "name": "python3" 134 | }, 135 | "language_info": { 136 | "name": "python", 137 | "pygments_lexer": "ipython3" 138 | } 139 | }, 140 | "nbformat": 4, 141 | "nbformat_minor": 5 142 | } 143 | 144 | --------------------------------------------------------------------------------