比特币真的很酷。当然,有人在想它是否是一种有用的技术,无论我们目前是否处于加密货币泡沫中,或者它目前面临的治理问题是否会得到解决......但在纯粹的技术层面上,神秘的Satoshi Nakamoto创造了令人印象深刻的技术。
不幸的是,虽然有很多资源可以对比特币的工作原理给出高级解释,我强烈推荐的一个这样的视频资源Anders Brownworth的blockchain visual 101,但是底层信息比较少。在我看来,如果你查看10000英尺的视图,那么你可以正确地理解这一点。
作为一个新手来说,我发现自己渴望了解比特币如何运作的机制。幸运的是,由于比特币本质上是去中心化的并且是对等的,所以任何人都能够开发符合协议的客户端。为了更好地了解比特币的运作方式,我决定编写自己的小玩具比特币客户端,该客户端能够向比特币区块链发布交易。
这篇文章介绍了创建一个最低限度可行的比特币客户端的过程,该客户端可以创建一个交易并将其提交给比特币点对点网络,以便它包含在区块链中。如果你只是阅读原始代码,请随时查看我的Github repo。
地址生成
要成为比特币网络的一部分,必须有一个地址,你可以从中发送和接收资金。比特币使用公钥加密,并且地址基本上是从公钥私钥派生的公钥的哈希版本。令人惊讶的是,与大多数公钥加密不同,公钥在保存之前也会加密,直到资金从地址发送——但稍后会有更多的不同和惊讶。
快速了解术语:在比特币中,客户使用术语钱包wallet
来表示地址集合。在协议级别没有钱包的概念,只有地址。
比特币使用椭圆曲线公钥加密技术作为其地址。在超高级别,椭圆曲线加密用于从私钥生成公钥,与RSA相同,但占用空间较小。如果你有兴趣学习一些关于它如何工作的数学知识,那么Cloudflare的入门书是一个很棒的资源。
从256位私钥开始,生成比特币地址的过程如下所示:
在Python中,我使用ecsda库来完成椭圆曲线加密的繁重工作。下面的代码片段获取了一个公钥通过高度令人难忘的(并且非常不安全)私钥0xFEEDB0BDEADBEEF
(前面填充了足够的零,使其成为64个十六进制字符长,或256位)。如果你想在地址中存储任何实际值,你需要一种更安全的生成私钥的方法!
作为一个有趣的测试,我最初使用私钥0xFACEBEEF
创建了一个地址并发送了它0.0005BTC,1个月后,有人**偷了我的0.0005BTC!**我想人们必须偶尔会使用简单的公共私钥去搜索地址。你真的必须使用正确的密钥推导技术!
from ecdsa import SECP256k1, SigningKey
def get_private_key(hex_string):
return bytes.fromhex(hex_string.zfill(64)) # pad the hex string to the required 64 characters
def get_public_key(private_key):
# this returns the concatenated x and y coordinates for the supplied private address
# the prepended 04 is used to signify that it's uncompressed
return (bytes.fromhex("04") SigningKey.from_string(private_key, curve=SECP256k1).verifying_key.to_string())
private_key = get_private_key("FEEDB0BDEADBEEF")
public_key = get_public_key(private_key)
运行此代码获取私钥(十六进制)
代码语言:javascript复制0000000000000000000000000000000000000000000000000feedb0bdeadbeef
和公钥(十六进制)
代码语言:javascript复制04d077e18fd45c031e0d256d75dfa8c3c21c589a861c4c33b99e64cf613113fcff9fc9d90a9d81346bcac64d3c01e6e0ef0828543edad73c0e257b845812cc8d28
预先设置公钥的0x04
表示这是一个未压缩的公钥,这意味着ECDSA的x
和y
坐标只是连接在一起。由于ECSDA的工作方式,如果你知道x
值,y
值只能取两个值,一个偶数,一个奇数。使用该信息,可以使用x
和y
的极性来表达公钥。这将公钥大小从65位减少到33位,并且密钥(和随后的计算地址)被称为压缩。对于压缩公钥,前置值将为0x02
或0x03
,具体取决于y
的极性。未压缩的公钥最常用于比特币,所以这也是我在这里使用的。
从这里开始,要从公钥生成比特币地址,公钥是sha256哈希,然后是cookedmd160哈希。这种双重哈希提供了额外的安全层,而ripemd160哈希提供了sha256的256位哈希的160位哈希,缩短了地址的长度。一个有趣的结果是,两个不同的公钥可以哈希到同一个地址!但是,对于2^160个不同的地址,这不太可能很快发生。
代码语言:javascript复制import hashlib
def get_public_address(public_key):
address = hashlib.sha256(public_key).digest()
h = hashlib.new('ripemd160')
h.update(address)
address = h.digest()
return address
public_address = get_public_address(public_key)
上面的代码生成一个公共地址c8db639c24f6dc026378225e40459ba8a9e54d1a
。这有时被称为哈希160地址。
如前所述,一个有趣的观点是,从私钥到公钥的转换以及从公钥到公共地址的转换都是单向转换。如果你有地址,则向后工作以查找关联公钥的唯一方法是解决SHA256哈希。这与大多数公钥加密略有不同,如在公钥中发布公钥并隐藏你的私钥。在这种情况下,隐藏公钥和私钥,并发布地址(哈希公钥)。
隐藏公钥是有充分理由的。虽然从相应的公钥计算私钥通常是不可行的,但是如果生成私钥的方法已被泄露,那么访问公钥使得推断私钥变得容易得多。在2013年,这个臭名昭着的Android比特币钱包事件。Android有一个产生随机数的关键弱点,它为攻击者打开了一个向量,可以从公钥中找到私钥。这也是为什么不鼓励在比特币中重复使用地址的原因——签署交易时,你需要透露你的公钥。如果在从地址发送交易后不重用地址,则无需担心该地址的公钥被公开。
表达比特币地址的标准方法是使用它的Base58Check编码。该编码仅是地址的表示(因此可以被解码/反转)。Base58Check生成1661HxZpSy5jhcJ2k6av2dxuspa8aafDac
格式的地址。Base58Check编码提供了一个较短的地址来表达,并且还有一个内置的校验和,允许检测错误的地址。在几乎每个比特币客户端中,你的地址的Base58Check编码就是你将看到的地址。Base58Check还包含一个版本号,我在下面的代码中将其设置为0——这表示该地址是一个pubkey哈希。
# 58 character alphabet used
BASE58_ALPHABET = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'
def base58_encode(version, public_address):
"""
Gets a Base58Check string
See https://en.bitcoin.it/wiki/base58Check_encoding
"""
version = bytes.fromhex(version)
checksum = hashlib.sha256(hashlib.sha256(version public_address).digest()).digest()[:4]
payload = version public_address checksum
result = int.from_bytes(payload, byteorder="big")
print(result)
# count the leading 0s
padding = len(payload) - len(payload.lstrip(b'