karpathy/cryptos

GitHub: karpathy/cryptos

一个纯 Python 零依赖的 Bitcoin 教育实现,从底层密码学原语到交易、区块和网络节点全链路从零构建,帮助学习者透彻理解比特币工作原理。

Stars: 1923 | Forks: 328

# 加密货币 这只是我出于教育目的,使用纯 Python 从头开始开发的、零依赖的 Bitcoin 实现,包括所有底层的加密原语,例如 SHA-256 和有限域上的椭圆曲线数学。 ### SHA-256 我的纯 Python SHA-256 实现严格遵循 [NIST FIPS 180-4](https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.180-4.pdf) 规范,位于 `cryptos/sha256.py` 中。由于这是从头开始的纯 Python 实现,因此速度很慢,显然除了教育目的外,不能在任何地方使用。示例用法: ``` $ echo "some test file lol" > testfile.txt $ shasum -a 256 testfile.txt 4a79aed64097a0cd9e87f1e88e9ad771ddb5c5d762b3c3bbf02adf3112d5d375 $ python -m cryptos.sha256 testfile.txt 4a79aed64097a0cd9e87f1e88e9ad771ddb5c5d762b3c3bbf02adf3112d5d375 ``` ### 密钥 `getnewaddress.py` 是一个 cli 入口,用于生成新的 Bitcoin 密钥/公钥对以及相应的(base58check 压缩)地址: ``` $ python getnewaddress.py generated secret key: 0xc322622e6a0033bb93ff666753f77cc8b819d274d9edea007b7e4b2af4caf025 corresponding public key: x: 5B9D87FE091D52EA4CD49EA5CEFDD8C099DF7E6CCF510A9A94C763DE38C575D5 y: 6049637B3683076C5568EC723CF7D38FD603B88447180829BBB508C554EEA413 compressed bitcoin address (b58check format): 1DBGfUXnwTS2PRu8h3JefU9uYwYnyaTd2z ``` ### 数字签名 在 `cryptos/ecdsa.py` 中实现的椭圆曲线数字签名算法 (ECDSA),示例用法: ``` >>> from cryptos.keys import gen_key_pair >>> from cryptos.ecdsa import sign, verify >>> sk1, pk1 = gen_key_pair() >>> sk2, pk2 = gen_key_pair() >>> message = ('pk1 wants to pay pk2 1 BTC').encode('ascii') >>> sig = sign(sk1, message) >>> verify(pk1, message, sig) True >>> verify(pk2, message, sig) False ``` ### 交易 Bitcoin 交易对象(legacy 或 segwit)可以被实例化,并从原始字节中解析出来。以下是解析 legacy 类型交易的示例: ``` >>> from cryptos.transaction import Tx >>> from io import BytesIO >>> # example transaction in Programming Bitcoing Chapter 5 >>> raw = bytes.fromhex('0100000001813f79011acb80925dfe69b3def355fe914bd1d96a3f5f71bf8303c6a989c7d1000000006b483045022100ed81ff192e75a3fd2304004dcadb746fa5e24c5031ccfcf21320b0277457c98f02207a986d955c6e0cb35d446a89d3f56100f4d7f67801c31967743a9c8e10615bed01210349fc4e631e3624a545de3f89f5d8684c7b8138bd94bdd531d2e213bf016b278afeffffff02a135ef01000000001976a914bc3b654dca7e56b04dca18f2566cdaf02e8d9ada88ac99c39800000000001976a9141c4bc762dd5423e332166702cb75f40df79fea1288ac19430600') >>> tx = Tx.parse(BytesIO(raw)) >>> # we get back a Transaction object with parsed fields >>> tx Tx(version=1, tx_ins=[TxIn(prev_tx=b'\xd1\xc7\x89\xa9\xc6\x03\x83\xbfq_?j\xd9\xd1K\x91\xfeU\xf3\xde\xb3i\xfe]\x92\x80\xcb\x1a\x01y?\x81', prev_index=0, script_sig=3045022100ed81ff192e75a3fd2304004dcadb746fa5e24c5031ccfcf21320b0277457c98f02207a986d955c6e0cb35d446a89d3f56100f4d7f67801c31967743a9c8e10615bed01 0349fc4e631e3624a545de3f89f5d8684c7b8138bd94bdd531d2e213bf016b278a, sequence=4294967294, witness=None)], tx_outs=[TxOut(amount=32454049, script_pubkey=OP_DUP OP_HASH160 bc3b654dca7e56b04dca18f2566cdaf02e8d9ada OP_EQUALVERIFY OP_CHECKSIG), TxOut(amount=10011545, script_pubkey=OP_DUP OP_HASH160 1c4bc762dd5423e332166702cb75f40df79fea12 OP_EQUALVERIFY OP_CHECKSIG)], locktime=410393, segwit=False) ``` 并且我们可以验证该交易是否符合 Bitcoin 规则以及具有密码学真实性: ``` >>> tx.validate() True ``` 这并不完全像 Bitcoin 全节点执行的那样是完整的验证,例如,它跳过了对双重支付、脚本大小限制等的验证,而且它仅支持(较简单的)p2pkh 交易。值得注意的是,这不包括自 2017 年 7 月左右 BIP141 软分叉以来,在当今 Bitcoin 网络流量中占主导地位的“现代” segwit 版本。 ### 区块 有关 Block 类、函数和实用工具,请参见 `cryptos/block.py`。 ### 轻量级节点 在 `cryptos/network.py` 中有一个轻量级 Bitcoin 节点,它使用 [Bitcoin 协议](https://en.bitcoin.it/wiki/Protocol_documentation) 的一个子集进行通信。该节点使用 Python 的 `socket` 连接到其他节点,执行版本握手,然后可以请求区块头。例如,我们可以遍历前 40,000 个区块(分批,每批 2,000 个)并对它们进行部分验证。Bitcoin 全节点将获取完整的区块(不仅仅是区块头)及其包含的所有交易,并对它们进行验证等。但是,部分验证的过程如下所示: ``` from io import BytesIO from cryptos.block import Block, GENESIS_BLOCK, calculate_new_bits from cryptos.network import SimpleNode from cryptos.network import ( GetHeadersMessage, HeadersMessage, ) # 连接到一个节点并请求 # 从 genesis block 开始的 20 个 block headers # 从 genesis block 开始 # https://en.bitcoin.it/wiki/Genesis_block # class Block: # version: int # 4 bytes little endian # prev_block: bytes # 32 bytes, little endian # merkle_root: bytes # 32 bytes, little endian # timestamp: int # uint32, seconds since 1970-01-01T00:00 UTC # bits: bytes # 4 bytes, current target in compact format # nonce: bytes # 4 bytes, searched over in pow previous = Block.decode(BytesIO(GENESIS_BLOCK['main'])) # 好了,现在让我们爬取 blockchain 的 block headers node = SimpleNode( host='mainnet.programmingbitcoin.com', net='main', ) node.handshake() blocks = [previous] for _ in range(20): # request next batch of 2,000 headers getheaders = GetHeadersMessage(start_block=bytes.fromhex(previous.id())) node.send(getheaders) headers = node.wait_for(HeadersMessage) # extend our chain of block headers blocks.extend(headers.blocks) previous = headers.blocks[-1] print(f"received another batch of blocks, now have {len(blocks)}") node.close() # 我们现在总共有 40,001 个区块,每个原始大小为 80 bytes,总共约 3.2MB 的数据 # 现在(部分)验证 blockchain 完整性 for i, block in enumerate(blocks): # validate proof of work on this block assert block.validate() # validate pointer to the previous node matches prev = blocks[i - 1] expected_prev_block = b'\x00'*32 if i == 0 else bytes.fromhex(prev.id()) assert block.prev_block == expected_prev_block # validate the proof of work target calculation on the block was correct if i % 2016 == 0: if i == 0: # genesis block had hardcoded value for bits expected_bits = bytes.fromhex('ffff001d') else: # recalculate the target at every epoch (2016 blocks), approx 2 week period # note that Satoshi had an off-by-one bug in this calculation because we are # looking at timestamp difference between first and last block in an epoch, # so these are only 2015 blocks apart instead of 2016 blocks apart ¯\_(ツ)_/¯ prev_epoch = blocks[i - 2016] time_diff = prev.timestamp - prev_epoch.timestamp expected_bits = calculate_new_bits(prev.bits, time_diff) assert block.bits == expected_bits if i % 1000 == 0: print(f"on block {i+1}/{len(blocks)}") ``` 能够独立地至少部分验证区块链的完整性,感觉非常好 :) ### 单元测试 ``` $ pytest ``` ### 许可证 MIT
标签:ECDSA, SHA-256, 加密算法, 密码学, 手动系统调用, 比特币, 逆向工具