窥探比特币核心机制如何运转
作者:互联网
比特币真的很酷。当然,有人在想它是否是一种有用的技术,无论我们目前是否处于加密货币泡沫中,或者它目前面临的治理问题是否会得到解决......但在纯粹的技术层面上,神秘的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)
运行此代码获取私钥(十六进制)
0000000000000000000000000000000000000000000000000feedb0bdeadbeef
和公钥(十六进制)
04d077e18fd45c031e0d256d75dfa8c3c21c589a861c4c33b99e64cf613113fcff9fc9d90a9d81346bcac64d3c01e6e0ef0828543edad73c0e257b845812cc8d28
预先设置公钥的0x04表示这是一个未压缩的公钥,这意味着ECDSA的x和y坐标只是连接在一起。由于ECSDA的工作方式,如果你知道x值,y值只能取两个值,一个偶数,一个奇数。使用该信息,可以使用x和y的极性来表达公钥。这将公钥大小从65位减少到33位,并且密钥(和随后的计算地址)被称为压缩。对于压缩公钥,前置值将为0x02或0x03,具体取决于y的极性。未压缩的公钥最常用于比特币,所以这也是我在这里使用的。
从这里开始,要从公钥生成比特币地址,公钥是sha256哈希,然后是cookedmd160哈希。这种双重哈希提供了额外的安全层,而ripemd160哈希提供了sha256的256位哈希的160位哈希,缩短了地址的长度。一个有趣的结果是,两个不同的公钥可以哈希到同一个地址!但是,对于2^160个不同的地址,这不太可能很快发生。
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'\0'))
encoded = []
while result != 0:
result, remainder = divmod(result, 58)
encoded.append(BASE58_ALPHABET[remainder])
return padding*"1" + "".join(encoded)[::-1]
bitcoin_address = base58_encode("00", public_address)
毕竟,从我的私有密钥feedb0bdeadbeef开始(前面填充零),我的比特币地址为1KK2xni6gmTtdnSGRiuAf94jciFgRjDj7W!。
有了地址,现在可以获得一些比特币!为了让比特币进入我的地址,我从btcmarkets以澳元购买了0.0045BTC(在撰写本文时,大约11美元)。使用btcmarket的交易门户网站,我将其转移到上述地址,在此过程中损失了0.0005BTC到交易费用。你可以在交易95855ba9f46c6936d7b5ee6733c81e715ac92199938ce30ac3e1214b8c2cd8d7中的区块链上看到此交易。
连接到p2p网络
现在我有一个地址包含一些比特币,事情变得更有趣。如果我想将比特币发送到其他地方,则需要连接到比特币节点网络。
导言
我在第一次了解比特币时遇到的一个难点是,考虑到网络的分散性,网络中的同行如何找到其他同行?如果没有集中的权限,比特币客户端如何知道如何引导并开始与网络的其他部分进行通信?
事实证明,理想主义提出了实用性,并且在最初的同伴发现过程中有最少量的集中化。新客户端找到要连接的节点的主要方式是使用DNS查找到由比特币社区成员维护的任意数量的DNS种子DNS seed服务器。
事实证明,DNS非常适合引导客户端的目的,因为DNS协议运行在UDP上并且很轻,很难用于DDoS。IRC被用作以前的引导方法,但由于其对DDoS攻击的弱点而被终止。
种子被硬编码为比特币核心的源代码,但核心开发人员可能会对其进行更改。
下面的Python代码连接到DNS种子DNS seed并随意选择要连接的第一个节点。使用套接字库,代码基本上执行nslookup,并在针对种子节点seed.bitcoin.sipa.be运行查询时返回第一个结果的ipv4地址。
import socket
# use a dns request to a seed bitcoin DNS server to find a node
nodes = socket.getaddrinfo("seed.bitcoin.sipa.be", None)
# arbitrarily choose the first node
node = nodes[0][4][0]
运行之后,返回的地址是208.67.251.126,这是一个友好的节点,我可以连接到!
和新节点打个招呼
节点之间的比特币连接是通过TCP进行的。在连接到节点时,比特币协议的开始握手消息是版本消息。在节点交换版本消息之前,不会接受其他消息。
比特币协议消息在比特币开发人员参考中有详细记录。使用开发人员参考作为指南,可以在Python中构建版本消息,如下面的代码段所示。大多数数据都是相当无趣的,用于建立节点连接的管理数据。如果你对所附详细信息的详细信息感兴趣,请阅读开发人员参考。
version = 70014
services = 1 # not a full node, cant provide any data
timestamp = int(time.time())
addr_recvservices = 1
addr_recvipaddress = socket.inet_pton(socket.AF_INET6, "::ffff:127.0.0.1") #ip address of receiving node in big endian
addr_recvport = 8333
addr_transservices = 1
addr_transipaddress = socket.inet_pton(socket.AF_INET6, "::ffff:127.0.0.1")
addr_transport = 8333
nonce = 0
user_agentbytes = 0
start_height = 329167
relay = 0
使用Python的结构库struct library,版本有效负载数据被打包成正确的格式,特别注意数据的字节顺序和字节宽度。将数据打包成正确的格式很重要,否则接收节点将无法理解它接收的原始字节。
payload = struct.pack("
标签:公钥,私钥,比特,窥探,地址,address,public,运转 来源: https://blog.csdn.net/weixin_44239023/article/details/89646971