最近媒体对比特币的所有关注都激发了我去了解比特币的真正工作原理,一直到流经网络的字节。普通人使用的软件可以隐藏真实发生的事情,但我想亲身体验比特币协议。我的目标是直接使用比特币系统:手动创建比特币交易,将其作为十六进制数据输入系统,然后查看其处理方式。事实证明,这比我预期的要困难得多,但我在这个过程中学到了很多东西,希望会觉得它很有趣。
比特币快速概览
我将快速概述比特币的工作原理,然后再深入研究细节。比特币是一种相对较新的数字货币,可以通过互联网传输。可以用美元或其他传统货币从Coinbase或MtGox[5]等网站购买比特币[4],将比特币发送给其他人,在某些地方用他们购买东西,并将比特币兑换成美元。
为了稍微简化,比特币由分布式数据库中的条目组成,该数据库跟踪比特币的所有权。与银行不同,比特币与用户或账户无关。相反,比特币由比特币地址拥有,例如1KKKK6N21XKo48zWKuQKXdvSsCf95ibHFa。
比特币交易
交易是花费比特币的机制。在交易中,一些比特币的所有者将所有权转移到一个新地址。
比特币的一个关键创新是如何通过挖矿将交易记录在分布式数据库中。交易被分组为区块,大约每10分钟发送一个新的交易区块,成为称为区块链的交易日志的一部分,这表明交易已经(或多或少)正式进行。[6] 比特币挖矿是将交易放入区块的过程,以确保每个人都对交易日志有一致的视图。为了挖矿区块,矿工必须找到一个非常罕见的解决方案来解决一个(否则毫无意义的)加密问题。找到这个解决方案会生成一个挖矿区块,它成为官方区块链的一部分。
挖矿也是新比特币进入系统的机制。当一个区块被成功开采时,新的比特币会在区块中生成并支付给矿工。这个采矿赏金很大 - 目前每个区块25个比特币(约合19,000美元)。此外,矿工还获得与区块中的交易相关的任何费用。正因为如此,采矿竞争非常激烈,许多人试图开采区块。挖矿的难度和竞争力是比特币安全的关键部分,因为它确保没有人能用坏块淹没系统。
点对点网络
没有集中的比特币服务器。相反,比特币在点对点网络上运行。如果运行比特币客户端,将成为该网络的一部分。网络上的节点相互交换其他对等体的交易、区块和地址。当首次连接到网络时,客户端会从一些随机节点下载区块链。反过来,客户端可能会向其他节点提供数据。当创建一个比特币交易时,把它发送给某个对等体,由他们把它发送给其他对等体,依此类推,直到它到达整个网络。矿工拿起交易,生成一个包含交易的挖矿区块,并将这个开采的区块发送给同行。最终,您的客户将收到该区块,客户显示交易已处理。
密码学
比特币使用数字签名来确保只有比特币的所有者才能使用它们。比特币地址的所有者拥有与该地址关联的私钥。为了花费比特币,他们用这个私钥签署交易,这证明他们是所有者。(这有点像签署一张实体支票以使其有效。公钥与每个比特币地址相关联,任何人都可以使用它来验证数字签名。
块和交易由其内容的256位加密哈希标识。此哈希值在比特币协议的多个位置使用。此外,找到一个特殊的哈希值是挖掘区块的困难任务。

深入了解原始比特币协议
本文的其余部分将逐步讨论我如何使用原始比特币协议。首先,我生成了一个比特币地址和密钥。接下来,我做了一笔交易,将少量比特币转移到这个地址。签署这笔交易花费了我很多时间和困难。最后,我将这笔交易输入比特币点对点网络,等待它被开采。本文的其余部分将详细介绍这些步骤。
事实证明,实际使用比特币协议比我预期的要难。正如您将看到的,该协议有点混乱:它使用大端数,小端数,固定长度数,可变长度数,自定义编码,DER编码和各种加密算法,看似任意。因此,要将数据转换为正确的格式,需要进行许多烦人的操作。
直接使用该协议的第二个复杂之处在于,作为加密技术,这是非常无情的。如果一个字节错误,交易将被拒绝,并且不知道问题出在哪里。
我遇到的最后一个困难是,签署交易的过程比必要的要困难得多,有很多细节需要正确。特别是,签名的交易版本与实际使用的版本有很大不同。
比特币地址和密钥
我的第一步是创建一个比特币地址。通常,使用比特币客户端软件创建地址和关联的密钥。但是,我编写了一些Python代码来创建地址,准确地显示了幕后发生的事情。
比特币使用各种密钥和地址,因此下图可能有助于解释它们。首先创建一个随机的 256 位私钥。需要私钥来签署交易,从而转移(花费)比特币。因此,私钥必须保密,否则比特币可能会被盗。
椭圆曲线 DSA 算法从私钥生成 512 位公钥。(椭圆曲线密码学将在后面讨论。此公钥用于验证交易上的签名。不方便的是,比特币协议在公钥上添加了前缀04。公钥在交易签名之前不会泄露,这与公钥公开的大多数系统不同。

下一步是生成与他人共享的比特币地址。由于 512 位公钥大得不方便,因此使用 SHA-256 和 RIPEMD 哈希算法将其哈希化到 160 位。然后使用比特币的自定义Base58Check编码将密钥编码为ASCII。[10]生成的地址,例如,是人们为了接收比特币而发布的地址。请注意,您无法从地址确定公钥或私钥。如果您丢失了私钥(例如扔掉硬盘),您的比特币将永远丢失。1KKKK6N21XKo48zWKuQKXdvSsCf95ibHFa
最后,钱包交换格式密钥(WIF)用于向客户端钱包软件添加私钥。这只是将私钥的Base58Check编码为ASCII,这很容易逆转以获得256位私钥。(我很好奇是否有人会使用上面的私钥来窃取我80美分的比特币,果然有人这样做了。
总而言之,有三种类型的密钥:私钥、公钥和公钥的哈希,它们使用 Base58Check 编码在 ASCII 中以外部表示。私钥是重要的密钥,因为它需要访问比特币,其他密钥可以从中生成。公钥哈希是您看到的已发布的比特币地址。
我使用以下代码片段[11]生成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”。在此交易中,.005 BTC取自交易A中的地址,.003 BTC取自交易B中的地址(请注意,箭头是对先前输出的引用,因此反向指向比特币的流动。对于输出,.003 BTC被定向到第一个地址,.004 BTC被定向到第二个地址。剩余的.001 BTC作为费用将转到区块的矿工。请注意,交易 A 的其他输出中的 .015 BTC 未在此交易中花费。
使用的每个输入都必须在事务中全部花费。如果一个地址在一次交易中收到了100个比特币,而你只想花1个比特币,那么交易必须花费所有100个比特币。解决方案是使用第二个输出进行更改,这会将剩余的99个比特币返回给您。
交易还可以包括费用。如果在将输入相加并减去输出后还剩下任何比特币,则剩余的是支付给矿工的费用。该费用不是严格要求的,但是对于矿工来说,没有费用的交易将是一个低优先级的交易,并且可能在几天内无法处理或可能完全丢弃。[13]一笔交易的典型费用是0.0002比特币(约合20美分),因此费用很低,但并不微不足道。
手动创建交易
对于我的实验,我使用了一个具有一个输入和一个输出的简单交易,如下所示。我从Coinbase购买比特币开始,并将0.00101234比特币放入地址,即交易81b4c832...。我的目标是创建一个交易,将这些比特币转移到我上面创建的地址,减去0.0001比特币的费用。因此,目标地址将收到0.00091234比特币。1MMMMSUb1piy2ufrSguNUdFmAcvqrQF8M51KKKK6N21XKo48zWKuQKXdvSsCf95ibHFa

按照规范,可以相当容易地组装未签名交易,如下所示。有一个输入,它使用交易的输出 0(第一个输出)。请注意,此交易哈希在交易中不方便地反转。输出量为 0.00091234 比特币(91234 是十六进制0x016462),以小端形式存储在值字段中。加密部分 - scriptSig 和 scriptPubKey - 更复杂,稍后将讨论。81b4c832...

下面是我用来生成此未签名交易的代码。这只是将数据打包成二进制文件的问题。签署交易是困难的部分,正如接下来将看到的。
# 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的公钥在交易中使用之前不会公开。
有了这个系统,比特币通过一系列交易从一个地址传递到另一个地址。可以验证链中的每个步骤,以确保比特币得到有效使用。请注意,交易通常可以有多个输入和输出,因此链分支到树中。

比特币脚本语言
可能期望比特币交易只需在交易中包含签名即可签名,但该过程要复杂得多。事实上,每个交易内部都有一个小程序,用于执行该程序以决定交易是否有效。该程序是用脚本编写的,脚本是基于堆栈的比特币脚本语言。复杂的赎回条件可以用这种语言表示。例如,托管系统可能需要三分之二的特定用户必须签署交易才能花费它。或者可以设置各种类型的合同。脚本语言非常复杂,大约有80种不同的操作码。它包括算术、按位运算、字符串运算、条件运算和堆栈运算。该语言还包括必要的加密操作(SHA-256,RIPEMD等)作为原语。为了确保脚本终止,该语言不包含任何循环操作。(因此,它不是图灵完备的。但是,在实践中,仅支持少数类型的交易。
为了使比特币交易有效,赎回脚本的两个部分必须成功运行。旧交易中的脚本称为 scriptPubKey,新交易中的脚本称为scriptSig。为了验证交易,脚本Sig执行,然后是脚本PubKey。如果脚本成功完成,则交易有效,比特币可以花费。否则,交易将无效。这样做的要点是,旧交易中的脚本PubKey定义了花费比特币的条件。脚本在新交易中的Sig必须提供数据以满足条件。
在标准交易中,脚本Sig将签名(从私钥生成)推送到堆栈,然后是公钥。接下来,执行脚本PubKey(来自源交易)以验证公钥,然后验证签名。
如Script中所示,scriptSig为:
PUSHDATA signature data and SIGHASH_ALL PUSHDATA public key data
脚本PubKey是:
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个步骤的过程进行了描述(详细信息)。单击下面的缩略图,查看该过程的详细图表。

最大的复杂之处在于签名出现在交易的中间,这就提出了如何在获得签名之前签署交易的问题。为了避免这个问题,在计算签名之前,脚本PubKey脚本从源交易复制到支出交易(即正在签名的交易)中。然后,将签名转换为脚本语言中的代码,从而创建嵌入到交易中的脚本Sig脚本。在签名期间使用前一个交易的脚本PubKey似乎是出于历史原因,而不是任何逻辑原因。对于具有多个输入的交易,签名甚至更复杂,因为每个输入都需要一个单独的签名,但我不会详细介绍。
让我绊倒的一步是哈希类型。在签名之前,交易会临时附加一个哈希类型常量。对于常规交易,这是SIGHASH_ALL(0x00000001)。签名后,此哈希类型将从交易末尾删除并附加到脚本Sig。
关于比特币协议的另一个令人讨厌的事情是,签名和公钥都是512位椭圆曲线值,但它们以完全不同的方式表示:签名使用DER编码编码,但公钥表示为纯字节。此外,这两个值都有一个额外的字节,但位置不一致:SIGHASH_ALL放在签名之后,类型04放在公钥之前。
由于ECDSA算法使用随机数,因此调试签名变得更加困难。[18]因此,每次计算时签名都是不同的,因此无法将其与已知良好的签名进行比较。
更新(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
最终的脚本Sig包含签名以及源地址()的公钥。这证明我被允许花费这些比特币,使交易有效。1MMMMSUb1piy2ufrSguNUdFmAcvqrQF8M5

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

最终交易
一旦所有必要的方法都到位,就可以组装最终的交易。
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
最终交易如下所示。这将上面的脚本Sig和脚本PubKey与前面描述的未签名交易相结合。

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

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

当普通用户开始和停止比特币客户端时,Peer节点进入和离开网络,因此客户端中有很多营业额。我使用的客户端现在不太可能正常运行,因此,如果想进行实验,则需要找到新的对等节点。可能需要尝试一堆才能找到一个有效的方法。
与同行交谈
一旦我获得了工作对等体的地址,下一步就是将我的交易发送到对等网络。使用点对点协议非常简单。我在端口 8333 上打开了与任意对等体的TCP连接,开始发送消息,然后依次接收消息。比特币点对点协议非常宽容;即使我完全搞砸了请求,同行也会继续沟通。
重要提示:正如一些人指出的那样,如果想进行实验,应该使用比特币测试网,它可以尝试“假”比特币,因为如果在真正的网络上搞砸了,很容易失去宝贵的比特币。(例如,如果在交易中忘记了更改地址,多余的比特币将作为费用流向矿工。但我想我会使用真正的比特币网络,并冒着价值1.00美元的比特币的风险。
该协议由大约24种不同的消息类型组成。每条消息都是一个相当简单的二进制blob,其中包含ASCII命令名称和适合该命令的二进制负载。该协议在比特币维基上有详细的记录。
连接到对等方的第一步是通过交换版本消息来建立连接。首先,我发送一个版本消息,其中包含我的协议版本号,地址和其他一些内容。对等体将其版本消息发送回去。在此之后,节点应该使用verack消息确认版本消息。(正如我所提到的,该协议是宽容的 - 即使我跳过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脚本将交易发送到点对点网络中。脚本发送版本消息,接收(并忽略)对等方的版本和verack消息,然后将交易作为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。(哈希在上面的inv消息中是反向的:ee19...)请注意,哈希值以大量零开头 - 在五分之一的值中找到这样一个字面上的零是使挖掘如此困难的原因。这个特定的块包含462笔交易,其中我的交易只是其中之一。0000000000000001a27b1d6eb8c405410398ece796e742da3b3e35363c2219ee
对于这个区块的挖掘,矿工获得了25个比特币的奖励,以及0.104个比特币的总费用,分别约为19,000美元和80美元。我支付了0.0001比特币的费用,大约是8美分或交易的10%。挖矿过程非常有趣,但我会把它留到以后的文章中。

结论
事实证明,使用原始比特币协议比我预期的要难,但在此过程中我学到了很多关于比特币的知识。我的代码纯粹是为了演示 - 如果真的想通过Python使用比特币,请使用一个真正的库而不是我的代码。
原文链接:http://www.righto.com/2014/02/bitcoins-hard-way-using-raw-bitcoin.html
---煤油灯科技victorlamp.com翻译整理---