乐趣区

对比特币底层协议的一些理解

媒体对比特币的关注让我开始了解比特币的真正运作方式,直至流经网络的字节数。普通人使用隐藏真实情况的软件,但我想亲自了解比特币协议。我的目标是直接使用比特币系统:手动创建比特币交易,将其作为十六进制数据提供给系统,并查看它是如何处理的。事实证明这比我预期的要困难得多,但我在这个过程中学到了很多东西,希望你会发现它很有趣。
本篇博文首先简要介绍比特币,然后跳转到低级细节:创建比特币地址,进行交易,签署交易,将交易提供给对等网络,并观察结果。
比特币的快速概述
在深入研究细节之前,我将首先快速概述比特币的工作原理。比特币是一种相对较新的数字货币,可以通过互联网传输。你可以用 Coinbase 或 MtGox 等网站上的美元或其他传统资金购买比特币,将比特币发送给其他人,在某些地方用它们买东西,然后将比特币兑换成美元。为了略微简化,比特币由分布式数据库中的条目组成,该数据库跟踪比特币的所有权。与银行不同,比特币与用户或账户无关。相反,比特币由比特币地址拥有,例如 1KKKK6N21XKo48zWKuQKXdvSsCf95ibHFa。
比特币交易
交易是消费比特币的机制。在交易中,某些比特币的所有者将所有权转移到新地址。比特币的一个关键创新是如何通过挖掘在分布式数据库中记录交易。交易被分组为块,大约每 10 分钟发送一个新的交易块,成为交易日志的一部分,称为区块链,表示交易已经(或多或少)正式进行。比特币挖掘是将交易放入块中的过程,以确保每个人都具有一致的交易日志视图。为了挖掘区块,矿工们必须找到一种极其罕见的解决方案来解决(否则无意义的)加密问题。找到此解决方案会生成一个已开采的块,该块将成为官方区块链的一部分。
挖掘也是比特币进入系统的新机制。当块成功挖掘时,块中会生成新的比特币并支付给矿工。这个采矿奖金很大——目前每块 25 比特币(约 19,000 美元)。此外,矿工获得与区块中的交易相关的任何费用。因此,采矿与许多试图开采矿块的人竞争非常激烈。采矿的难度和竞争力是比特币安全的关键部分,因为它确保没有人可以用坏块淹没系统。
点对点网络
没有集中的比特币服务器。相反,比特币在点对点网络上运行。如果你运行比特币客户端,你将成为该网络的一部分。网络上的节点彼此交换其他对等体的交易,块和地址。首次连接到网络时,客户端会从某个随机节点或节点下载区块链。反过来,你的客户端可能会向其他节点提供数据。当你创建比特币交易时,你将其发送给某个对等方,该对等方将其发送给其他对等方,依此类推,直到它到达整个网络。矿工获取你的交易,生成包含你的交易的挖掘区块,并将此挖掘的区块发送给对等方。最终,你的客户端将收到该块,你的客户端将显示该交易已处理完毕。
加密
比特币使用数字签名来确保只有比特币的所有者可以使用它们。比特币地址的所有者具有与该地址相关联的私钥。为了花费比特币,他们用这个私钥签署交易,证明他们是所有者。(这有点像签署物理检查以使其有效。)公钥与每个比特币地址相关联,任何人都可以使用它来验证数字签名。块和交易由其内容的 256 位加密哈希标识。此哈希值用于比特币协议中的多个位置。此外,查找特殊哈希是挖掘块的难题。
深入原始比特币协议
本文的其余部分将逐步讨论我如何使用原始比特币协议。首先,我生成了比特币地址和密钥接下来,我做了一笔交易,将少量的比特币转移到这个地址。签署此交易给我带来了很多时间和困难。最后,我将这笔交易送入比特币点对点网络并等待它开采。本文的其余部分将详细介绍这些步骤。事实证明,实际使用比特币协议比我预期的更难。正如你将看到的,该协议有点混乱:它使用大尾数字,小尾数数字,固定长度数字,可变长度数字,自定义编码,DER 编码和各种加密算法,看似随意。因此,将数据转换为正确的格式会有很多烦人的操作。
直接使用协议的第二个复杂因素是加密,这是非常不可原谅的。如果你得到一个字节错误,则会拒绝该交易,而不知道问题出在何处。
我遇到的最后一个困难是签署交易的过程比必要的困难得多,需要纠正很多细节。特别是,签名的交易版本与实际使用的版本非常不同。
比特币地址和密钥
我的第一步是创建一个比特币地址。通常,你使用比特币客户端软件来创建地址和相关密钥。但是,我写了一些 Python 代码来创建地址,准确显示幕后发生的事情。比特币使用各种键和地址,因此下图可能有助于解释它们。首先创建一个随机的 256 位私钥。需要私钥来签署交易,从而转移(支出)比特币。因此,私钥必须保密,否则你的比特币可能被盗。
Elliptic Curve DSA 算法从私钥生成 512 位公钥。(椭圆曲线加密将在后面讨论。)此公钥用于验证交易上的签名。不方便的是,比特币协议为公钥添加了前缀 04。在签署交易之前不会公开公钥,这与大多数公钥公开的系统不同。

下一步是生成与其他人共享的比特币地址。由于 512 位公钥不方便大,因此使用 SHA-256 和 RIPEMD 哈希算法将其分解为 160 位。然后使用比特币的自定义 Base58Check 编码以 ASCII 编码密钥。结果地址,例如 1KKKK6N21XKo48zWKuQKXdvSsCf95ibHFa,是人们为了接收比特币而发布的地址。请注意,你无法从该地址确定公钥或私钥。如果你丢失了私钥(例如丢弃了硬盘),你的比特币将永远丢失。
最后,电子钱包交换格式密钥(WIF)用于向客户端钱包软件添加私钥。这只是将私钥的 Base58Check 编码转换为 ASCII,这很容易被反转以获得 256 位私钥。(我很好奇是否有人会使用上面的私钥来窃取我的 80 美分的比特币,当然有人这样做了。)
总而言之,有三种类型的密钥:私钥,公钥和公钥的 hash,它们使用 Base58Check 编码在 ASCII 外部表示。私钥是重要的密钥,因为它需要访问比特币,而其他密钥可以从中生成。公钥哈希是你看到的比特币地址。
我使用以下代码片段生成 WIF 格式的私钥和地址。私钥只是一个随机的 256 位数字。ECDSA 加密库从私钥生成公钥。比特币地址由 SHA-256 哈希,RIPEMD-160 哈希,然后是带校验和的 Base58 编码生成。最后,私钥在 Base58Check 中编码,以生成用于将私钥输入比特币客户端软件的 WIF 编码。注意:这个 Python 随机函数不是强加密;如果你真的这样做,请使用更好的功能。
def privateKeyToWif(key_hex):
return utils.base58CheckEncode(0x80, key_hex.decode(‘hex’))

def privateKeyToPublicKey(s):
sk = ecdsa.SigningKey.from_string(s.decode(‘hex’), curve=ecdsa.SECP256k1)
vk = sk.verifying_key
return (‘\04’ + sk.verifying_key.to_string()).encode(‘hex’)

def pubKeyToAddr(s):
ripemd160 = hashlib.new(‘ripemd160’)
ripemd160.update(hashlib.sha256(s.decode(‘hex’)).digest())
return utils.base58CheckEncode(0, ripemd160.digest())

def keyToAddr(s):
return pubKeyToAddr(privateKeyToPublicKey(s))

# Warning: this random function is not cryptographically strong and is just for example
private_key = ”.join([‘%x’ % random.randrange(16) for x in range(0, 64)])
print keyUtils.privateKeyToWif(private_key)
print keyUtils.keyToAddr(private_key)
在交易中
交易是比特币系统的基本操作。你可能希望交易只是将一些比特币从一个地址移动到另一个地址,但它比这更复杂。比特币交易在一个或多个输入和输出之间移动比特币。每个输入都是提供比特币的交易和地址。每个输出都是接收比特币的地址,以及到达该地址的比特币数量。

上图显示了一个示例交易“C”。在此交易中,0.005BTC 取自交易 A 中的地址,而 0.003BTC 取自交易 B 中的地址。请注意,箭头是对先前输出的引用,因此向后转到比特币流。)对于输出,0.003BTC 指向第一个地址,0.004BTC 指向第二个地址。剩余的 0.001BTC 作为费用给到该区块的矿工。请注意,交易 A 的其他输出中的 0.015 BTC 不会用于此交易。
使用的每个输入必须完全花在交易中。如果一个地址在一个交易中收到了 100 个比特币而你只想花 1 个比特币,那么交易必须花费所有 100 个。解决方案是使用第二个输出进行更改,这会将 99 个剩余比特币返回给你。
交易还可以包括费用。如果在将输入相加并减去输出后仍有任何比特币剩余,则余额是支付给矿工的费用。该费用并非严格要求,但免费交易对矿工来说不是优先考虑的事项,可能几天不会处理,也可能完全丢弃。交易的典型费用是 0.0002 比特币(约 20 美分),因此费用很低但不是微不足道的。
手动创建交易
对于我的实验,我使用了一个带有一个输入和一个输出的简单交易,如下所示。我开始使用 Coinbase 的比特币并将 0.00101234 比特币放入地址 1MMMMSUb1piy2ufrSguNUdFmAcvqrQF8M5,这是交易 81b4c832… 我的目标是创建一个交易,将这些比特币转移到我上面创建的地址,1KKKK6N21XKo48zWKuQKXdvSsCf95ibHFa,减去 0.0001 比特币的费用。因此,目标地址将接收 0.00091234 比特币。

遵循规范,可以非常容易地组装无符号交易,如下所示。有一个输入,它使用来自交易 81b4c832… 输出 0(第一个输出)81b4c832… 请注意,此交易哈希在交易中不方便地反转。输出量为 0.00091234 比特币(91234 为十六进制的 0x016462),以小尾数格式存储在值字段中。加密部分———criptSig 和 scriptPubKey 更复杂,稍后将对其进行讨论。

这是我用来生成这个无符号交易的代码。这只是将数据打包成二进制的问题。签署交易是困难的部分,你将在下面看到。
# Makes a transaction from the inputs
# outputs is a list of [redemptionSatoshis, outputScript]
def makeRawTransaction(outputTransactionHash, sourceIndex, scriptSig, outputs):
def makeOutput(data):
redemptionSatoshis, outputScript = data
return (struct.pack(“<Q”, redemptionSatoshis).encode(‘hex’) +
‘%02x’ % len(outputScript.decode(‘hex’)) + outputScript)
formattedOutputs = ”.join(map(makeOutput, outputs))
return (
“01000000” + # 4 bytes version
“01” + # varint for number of inputs
outputTransactionHash.decode(‘hex’)[::-1].encode(‘hex’) + # reverse outputTransactionHash
struct.pack(‘<L’, sourceIndex).encode(‘hex’) +
‘%02x’ % len(scriptSig.decode(‘hex’)) + scriptSig +
“ffffffff” + # sequence
“%02x” % len(outputs) + # number of outputs
formattedOutputs +
“00000000” # lockTime
)
比特币交易如何签署
下图给出了如何签署和链接交易的简化视图。考虑中间交易,将比特币从地址 B 转移到地址 C. 交易的内容(包括先前交易的哈希)被哈希并用 B 的私钥签名。此外,B 的公钥包含在交易中。
通过执行几个步骤,任何人都可以验证交易是否被 B 授权。首先,B 的公钥必须与前一个交易中的 B 地址相对应,证明公钥是有效的。(如前所述,地址可以很容易地从公钥中导出。)接下来,可以使用交易中 B 的公钥来验证 B 的交易签名。这些步骤确保交易有效并由 B 授权。比特币的一个意外部分是 B 的公钥在交易中使用之前不会公开。
使用这个系统,比特币通过一系列交易从一个地址传递到另一个地址。可以验证链中的每个步骤以确保有效地使用比特币。请注意,交易通常可以有多个输入和输出,因此链分支到树中。

比特币脚本语言
你可能希望仅通过在交易中包含签名来签署比特币交易,但该过程要复杂得多。事实上,每个交易中都有一个小程序可以执行以确定交易是否有效。该程序是用 Script 编写的,这是一种基于堆栈的比特币脚本语言。复杂的赎回条件可以用这种语言表达。例如,托管系统可能需要三个特定用户中的两个必须签署交易才能使用它。或者可以设置各种类型的合约。
Script 语言非常复杂,有大约 80 种不同的操作码。它包括算术运算,按位运算,字符串运算,条件运算和堆栈操作。该语言还包括必要的加密操作(SHA-256,RIPEMD 等)作为基元。为了确保脚本终止,该语言不包含任何循环操作。(因此,它不是 Turing-complete。)但实际上,只支持几种类型的交易。
为了使比特币交易有效,兑换脚本的两个部分必须成功运行。旧交易中的脚本称为 scriptPubKey,新交易中的脚本称为 scriptSig。要验证交易,执行 scriptSig,然后执行 scriptPubKey。如果脚本成功完成,则交易有效并且可以使用比特币。否则,交易无效。关键在于旧交易中的 scriptPubKey 定义了使用比特币的条件。新交易中的 scriptSig 必须提供满足条件的数据。
在标准交易中,scriptSig 将签名(从私钥生成)推送到堆栈,然后是公钥。接下来,执行 scriptPubKey(来自源交易)以验证公钥,然后验证签名。
如脚本中所述,scriptSig 是:
PUSHDATA
signature data and SIGHASH_ALL
PUSHDATA
public key data
scriptPubKey 是:
OP_DUP
OP_HASH160
PUSHDATA
Bitcoin address (public key hash)
OP_EQUALVERIFY
OP_CHECKSIG
执行此代码时,PUSHDATA 首先将签名推送到堆栈。下一个 PUSHDATA 将公钥推送到堆栈。接下来,OP_DUP 复制堆栈上的公钥。OP_HASH160 计算公钥的 160 位哈希值。PUSHDATA 推送所需的比特币地址。然后 OP_EQUALVERIFY 验证前两个堆栈值是否相等。来自新交易的公钥哈希与旧地址中的地址匹配。这证明公钥是有效的。接下来,OP_CHECKSIG 检查交易的签名是否与堆栈上的公钥和签名匹配。这证明签名是有效的。
签署交易
我发现签署交易是手动使用比特币最困难的部分,其过程非常困难且容易出错。基本思想是使用 ECDSA 椭圆曲线算法和私钥生成交易的数字签名,但细节很棘手。签名过程已通过 19 个步骤(更多信息)进行了描述。单击下面的缩略图以获取该过程的详细图表。

最大的复杂因素是签名出现在交易中间,这就提出了在签名之前如何签署交易的问题。为避免此问题,在计算签名之前,将 scriptPubKey 脚本从源交易复制到支出交易(即正在签名的交易)中。然后将签名转换为脚本语言中的代码,创建嵌入在交易中的 scriptSig 脚本。似乎在签名期间使用先前交易的 scriptPubKey 是出于历史原因而不是任何逻辑原因。对于具有多个输入的交易,签名甚至更复杂,因为每个输入都需要单独的签名,但我不会详细介绍。
绊倒我的一步是哈希类型。在签名之前,交易具有临时附加的哈希类型常量。对于常规交易,这是 SIGHASH_ALL(0x00000001)。签名后,此哈希类型将从交易结束时删除并附加到 scriptSig。
关于比特币协议的另一个令人讨厌的事情是签名和公钥都是 512 位椭圆曲线值,但它们以完全不同的方式表示:签名用 DER 编码编码,但公钥表示为普通字节。此外,两个值都有一个额外的字节,但位置不一致:签名后放置 SIGHASH_ALL,类型 04 放在公钥之前。
由于 ECDSA 算法使用随机数,因此调试签名变得更加困难。因此,每次计算时签名都不同,因此无法与已知良好的签名进行比较。
更新(2014 年 2 月):每次签名更改的一个重要副作用是,如果重新签名交易,交易的哈希值将会更改。这称为交易可维护性。还有一些方法可以让第三方以微不足道的方式修改交易,从而改变哈希,而不是交易的意义。尽管多年来人们已经知道,但是可塑性最近在 MtGox(新闻稿)中引起了很大的问题(2014 年 2 月)。
由于这些复杂情况,我花了很长时间才能使签名工作。但最终,我从签名代码中获得了所有错误,并成功签署了一项交易。这是我使用的代码片段。
def makeSignedTransaction(privateKey, outputTransactionHash, sourceIndex, scriptPubKey, outputs):
myTxn_forSig = (makeRawTransaction(outputTransactionHash, sourceIndex, scriptPubKey, outputs)
+ “01000000”) # hash code

s256 = hashlib.sha256(hashlib.sha256(myTxn_forSig.decode(‘hex’)).digest()).digest()
sk = ecdsa.SigningKey.from_string(privateKey.decode(‘hex’), curve=ecdsa.SECP256k1)
sig = sk.sign_digest(s256, sigencode=ecdsa.util.sigencode_der) + ‘\01’ # 01 is hashtype
pubKey = keyUtils.privateKeyToPublicKey(privateKey)
scriptSig = utils.varstr(sig).encode(‘hex’) + utils.varstr(pubKey.decode(‘hex’)).encode(‘hex’)
signed_txn = makeRawTransaction(outputTransactionHash, sourceIndex, scriptSig, outputs)
verifyTxnSignature(signed_txn)
return signed_txn
最终的 scriptSig 包含签名以及源地址的公钥(1MMMMSUb1piy2ufrSguNUdFmAcvqrQF8M5)。这证明我可以使用这些比特币,使交易有效。

最终的 scriptPubKey 包含必须成功使用比特币的脚本。请注意,在使用比特币时,此脚本将在以后的某个任意时间执行。它包含以十六进制表示的目标地址(1KKKK6N21XKo48zWKuQKXdvSsCf95ibHFa),而不是 Base58Check。结果是只有该地址的私钥的所有者可以使用比特币,因此该地址实际上是所有者。

最后的交易
一旦完成所有必要的方法,就可以组装最终的交易。
privateKey = keyUtils.wifToPrivateKey(“5HusYj2b2x4nroApgfvaSfKYZhRbKFH41bVyPooymbC6KfgSXdD”) #1MMMM

signed_txn = txnUtils.makeSignedTransaction(privateKey,
“81b4c832d70cb56ff957589752eb4125a4cab78a25a8fc52d6a09e5bd4404d48”, # output (prev) transaction hash
0, # sourceIndex
keyUtils.addrHashToScriptPubKey(“1MMMMSUb1piy2ufrSguNUdFmAcvqrQF8M5”),
[[91234, #satoshis
keyUtils.addrHashToScriptPubKey(“1KKKK6N21XKo48zWKuQKXdvSsCf95ibHFa”)]]
)

txnUtils.verifyTxnSignature(signed_txn)
print ‘SIGNED TXN’, signed_txn
最终交易如下所示。这将上面的 scriptSig 和 scriptPubKey 与前面描述的 unsigned 交易相结合。

切线:理解椭圆曲线
比特币使用椭圆曲线作为签名算法的一部分。在解决费马最后定理的背景下,我之前听说过椭圆曲线,所以我很好奇它们是什么。椭圆曲线的数学很有意思,所以我会绕道而行,快速概述一下。名称椭圆曲线令人困惑:椭圆曲线不是椭圆形,看起来不像椭圆,它们与椭圆几乎没有关系。椭圆曲线是满足相当简单的方程 y^2=x^3+ax+ b 的曲线。比特币使用称为 secp256k1 的特定椭圆曲线,其简单方程为 y^2=x^3+7。

椭圆曲线的一个重要特性是你可以使用一个简单的规则在曲线上定义点的添加:如果你在曲线中绘制一条直线并且它击中三个点 A,B 和 C,则添加由 A + 定义 B +C=0。由于椭圆曲线的特殊性质,以这种方式定义的加法“正常”起作用并形成一个组。如果定义了加法,则可以定义整数乘法:例如 4A=A+A+A+A。
使椭圆曲线在加密方面有用的原因是它可以快速进行整数乘法,但除法基本上需要强力。例如,你可以非常快速地计算诸如 12345678A= Q 的乘积(通过计算 2 的幂),但是如果你只知道 A 和 Q 求解 nA= Q 很难。在椭圆曲线加密中,密码 12345678 将是私钥,曲线上的点 Q 将是公钥。
在密码学中,坐标不是在曲线上使用实值点,而是以模数为模的整数。椭圆曲线的一个令人惊讶的特性是,无论使用实数还是模运算,数学运算都非常相似。因此,比特币的椭圆曲线看起来不像上面的图片,而是一个随机的 256 位点混乱(想象一个大的灰色方块点)。
椭圆曲线数字签名算法(ECDSA)采用消息哈希,然后使用消息,私钥和随机数进行一些简单的椭圆曲线算法,以在曲线上生成给出签名的新点。拥有公钥,消息和签名的任何人都可以执行一些简单的椭圆曲线算法来验证签名是否有效。因此,只有具有私钥的人才能签署消息,但是具有公钥的任何人都可以验证该消息。
有关椭圆曲线的更多信息,请参阅参考文献。
将我的交易发送到 p2p 网络
留下椭圆曲线,此时我创建了一个交易并签名。下一步是将其发送到点对点网络,在那里它将被矿工接收并合并到一个块中。
如何找到同行
使用 p2p 网络的第一步是找到一个对等体。每当有人运行客户端时,对等体列表每隔几秒就会更改一次。一旦节点连接到对等节点,它们就会在发现新对等体时通过交换 addr 消息来共享新对等体。因此,新同伴迅速通过该系统传播。
然而,关于如何找到第一个同伴,有一个鸡与蛋的问题。比特币客户通过几种方法解决了这个问题。几个可靠的对等体在 DNS 中以 bitseed.xf2.org 的名称注册。通过执行 nslookup,客户端获取这些对等端的 IP 地址,并希望其中一个可以工作。如果这不起作用,则将对等体的种子列表硬编码到客户端中。

当普通用户启动和停止比特币客户端时,同行进入和离开网络,因此客户端有大量的营业额。我使用的客户现在不太可能正常运营,所以如果你想做实验,你需要找到新的同行。你可能需要尝试一堆才能找到有效的方法。
和同龄人交谈
一旦我获得了工作对等体的地址,下一步就是将我的交易发送到对等网络。使用点对点协议非常简单。我在端口 8333 上打开了与任意对等方的 TCP 连接,开始发送消息,并依次接收消息。比特币点对点协议非常宽容; 即使我完全搞砸了请求,同行也会保持沟通。
重要提示:正如一些人所指出的,如果你想进行实验,你应该使用比特币测试网,这可以让你试验“虚假”的比特币,因为如果你搞砸了真正的网络,很容易丢失你的宝贵的比特币。(例如,如果你忘记了交易中的更改地址,那么多余的比特币将作为费用交给矿工。)但我想我会使用真正的比特币网络并冒险使用价值 1.00 美元的比特币。
该协议由大约 24 种不同的消息类型组成。每条消息都是一个相当简单的二进制 blob,包含一个 ASCII 命令名和一个适合该命令的二进制有效负载。该协议在比特币维基上有详细记录。
连接到对等方的第一步是通过交换版本消息来建立连接。首先,我发送一个版本消息,其中包含我的协议版本号,地址和其他一些内容。对等体发回其版本消息。在此之后,节点应该用 verack 消息确认版本消息。(正如我所提到的,该协议是宽容的 – 即使我跳过了那个行列,一切正常。)
生成版本消息并不是完全无关紧要的,因为它有一堆字段,但它可以用几行 Python 创建。下面的 makeMessage 根据幻数,命令名和有效负载构建一个任意的对等消息。getVersionMessage 通过将各个字段打包在一起来为版本消息创建有效负载。
magic = 0xd9b4bef9

def makeMessage(magic, command, payload):
checksum = hashlib.sha256(hashlib.sha256(payload).digest()).digest()[0:4]
return struct.pack(‘L12sL4s’, magic, command, len(payload), checksum) + payload

def getVersionMsg():
version = 60002
services = 1
timestamp = int(time.time())
addr_me = utils.netaddr(socket.inet_aton(“127.0.0.1”), 8333)
addr_you = utils.netaddr(socket.inet_aton(“127.0.0.1″), 8333)
nonce = random.getrandbits(64)
sub_version_num = utils.varstr(”)
start_height = 0
payload = struct.pack(‘<LQQ26s26sQsL’, version, services, timestamp, addr_me,
addr_you, nonce, sub_version_num, start_height)
return makeMessage(magic, ‘version’, payload)
发送交易:tx
我使用下面的精简 Python 脚本将交易发送到对等网络。该脚本发送版本消息,接收(并忽略)对等方的版本和维拉消息,然后将该交易作为 tx 消息发送。十六进制字符串是我之前创建的交易。
def getTxMsg(payload):
return makeMessage(magic, ‘tx’, payload)

sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect((“97.88.151.164”, 8333))

sock.send(msgUtils.getVersionMsg())
sock.recv(1000) # receive version
sock.recv(1000) # receive verack
sock.send(msgUtils.getTxMsg(“0100000001484d40d45b9ea0d652fca8258ab7caa42541eb52975857f96fb50cd732c8b481000000008a47304402202cb265bf10707bf49346c3515dd3d16fc454618c58ec0a0ff448a676c54ff71302206c6624d762a1fcef4618284ead8f08678ac05b13c84235f1654e6ad168233e8201410414e301b2328f17442c0b8310d787bf3d8a404cfbd0704f135b6ad4b2d3ee751310f981926e53a6e8c39bd7d3fefd576c543cce493cbac06388f2651d1aacbfcdffffffff0162640100000000001976a914c8e90996c7c6080ee06284600c684ed904d14c5c88ac00000000”.decode(‘hex’)))

以下屏幕截图显示了如何在 Wireshark 网络分析程序中发送我的交易。我编写了 Python 脚本来处理比特币网络流量,但为了简单起见,我将在这里使用 Wireshark。“tx”消息类型在 ASCII 转储中可见,在我的交易开始的下一行(01 00 …)后面。

为了监视我的交易的进度,我有一个套接字打开给另一个随机对等体。发送我的交易五秒后,另一个对等体发送了一条 tx 消息,其中包含我刚刚发送的交易的哈希值。因此,我的交易只需几秒钟即可在对等网络或至少部分网络中传递。
胜利:我的交易被开采了
在将我的交易发送到对等网络后,我需要等待它才能获得胜利。十分钟后,我的脚本收到一条带有新块的 inv 消息(参见下面的 Wireshark 描述)。检查此块显示它包含我的交易,证明我的交易有效。我还可以通过查看我的比特币钱包和在线查询来验证此交易是否成功。因此,经过大量的努力,我成功地手动创建了一个交易并让它被系统接受。(不用说,我的前几次交易尝试都没有成功,我的错误交易消失在网络中,永远不会被再次看到。)

我的交易是由大型 GHash.IO 矿池开采的,块为 #279068,哈希为 0000000000000001a27b1d6eb8c405410398ece796e742da3b3e35363c2219ee。(哈希在上面的 inv 消息中反转:ee19…)请注意,哈希以大量零开始——在 quintillion 值中找到这样的字面意思是使挖掘变得如此困难的原因。这个特殊的块包含 462 个交易,其中我的交易只有一个。
为了开采这个区块,矿工们获得了 25 比特币的奖励,总费用为 0.104 比特币,分别约为 19,000 美元和 80 美元。我支付了 0.0001 比特币的费用,约占我交易的 8 美分或 10%。挖掘过程非常有趣,但我将把它留给以后的文章。
结论
使用原始的比特币协议比我预期的要困难,但我一路上学到了很多关于比特币的知识,我希望你也做到了。我的代码纯粹是为了演示——如果你真的想通过 Python 使用比特币,请使用真正的库而不是我的代码。
======================================================================
分享一些以太坊、EOS、比特币等区块链相关的交互式在线编程实战教程:

EOS 教程,本课程帮助你快速入门 EOS 区块链去中心化应用的开发,内容涵盖 EOS 工具链、账户与钱包、发行代币、智能合约开发与部署、使用代码与智能合约交互等核心知识点,最后综合运用各知识点完成一个便签 DApp 的开发。

java 以太坊开发教程,主要是针对 java 和 android 程序员进行区块链以太坊开发的 web3j 详解。

python 以太坊,主要是针对 python 工程师使用 web3.py 进行区块链以太坊开发的详解。

php 以太坊,主要是介绍使用 php 进行智能合约开发交互,进行账号创建、交易、转账、代币开发以及过滤器和交易等内容。

以太坊入门教程,主要介绍智能合约与 dapp 应用开发,适合入门。

以太坊开发进阶教程,主要是介绍使用 node.js、mongodb、区块链、ipfs 实现去中心化电商 DApp 实战,适合进阶。

C#以太坊,主要讲解如何使用 C# 开发基于.Net 的以太坊应用,包括账户管理、状态与交易、智能合约开发与交互、过滤器和交易等。

java 比特币开发教程,本课程面向初学者,内容即涵盖比特币的核心概念,例如区块链存储、去中心化共识机制、密钥与脚本、交易与 UTXO 等,同时也详细讲解如何在 Java 代码中集成比特币支持功能,例如创建地址、管理钱包、构造裸交易等,是 Java 工程师不可多得的比特币开发学习课程。

php 比特币开发教程,本课程面向初学者,内容即涵盖比特币的核心概念,例如区块链存储、去中心化共识机制、密钥与脚本、交易与 UTXO 等,同时也详细讲解如何在 Php 代码中集成比特币支持功能,例如创建地址、管理钱包、构造裸交易等,是 Php 工程师不可多得的比特币开发学习课程。

tendermint 区块链开发详解,本课程适合希望使用 tendermint 进行区块链开发的工程师,课程内容即包括 tendermint 应用开发模型中的核心概念,例如 ABCI 接口、默克尔树、多版本状态库等,也包括代币发行等丰富的实操代码,是 go 语言工程师快速入门区块链开发的最佳选择。

汇智网原创翻译,转载请标明出处。这里是原文如何使用原始比特币协议

退出移动版