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, 加密算法, 密码学, 手动系统调用, 比特币, 逆向工具