神译局是36氪旗下编译团队,关注科技、商业、职场、生活等领域,重点介绍国外的新技术、新观点、新风向。
编者按:比特币相信无人不知。区块链也许也有很多人听说过,但是区块链究竟是怎么回事?它的工作机制又是怎样的?为什么说挖矿消耗的电力居然要比某些国家消耗的电力都要多?asthasr试图用简单的程序来说明清楚,原文发表在其个人博客上,标题是:How Blockchains Work
很可能你已经知道区块链是什么东西了。毕竟,一个比特币的价格一度曾超过47000美元的。不过,这篇文章要讲的不是商业层面的东西,也不是要探讨比特币是不是投机泡沫。我只是想解释一下它的机制。
首先,哈希算法将某给定字符串转换为不可预测的,固定长度字符串(摘要)的一种方法。
哈希算法将文本生成长度固定的摘要
以下是用来演示哈希算法的一小段Python 程序:
#!/usr/bin/env python3
from argparse import ArgumentParser
from hashlib import md5
#将输入的string参数生成十六进制摘要
def hash_string(string):
hash = md5()
hash.update(string.encode("utf-8"))
return hash.hexdigest()
if __name__ == "__main__":
parser = ArgumentParser()
parser.add_argument("STRING", help="The string to be hashed")
args = parser.parse_args()
print(hash_string(args.STRING))
用不同的字符串参数运行这段程序就能得到该参数的摘要(以下“ninjia”和“samurai”的摘要):
$ ./hash ninja
3899dcbab79f92af727c2190bbd8abc5
$ ./hash samurai
99b1983cf3ee09bbaf6f43ac7b4c8748
这种类型的哈希可用来校验密码——你可以检查密码是否匹配,而不需要把密码本身保存起来。(注)
区块链是一种账本:这种账本会逐渐添加条目。而哈希的作用是可以对添加到账本里面的消息的次序和内容进行保护。
区块链捕捉之前的摘要以及当前的消息从而生成一个摘要的图解。
下面是一个简单的实现:
def hash_ledger_entry(string, previous_digest=None):
"""对字符串及账本之前条目的摘要(如果有的话)进行哈希运算"""
hash = md5(string.encode("utf-8"))
if previous_digest:
hash.update(previous_digest.encode("utf-8"))
return hash.hexdigest()def generate_ledger(*strings):
"""生成含一系列字符串的账本的记录"""
digest = None
for string in strings:
digest = hash_ledger_entry(string, digest)
yield digest, string
if __name__ == "__main__":
parser = ArgumentParser()
parser.add_argument("STRINGS", help="The ledger entries", nargs="+")
args = parser.parse_args()
for digest, string in generate_ledger(*args.STRINGS):
print(f"{digest}\t{string}")
给这一脚本提供一组字符串的话,将会生成一个唯一且有序的账本:
$ ./hash ninja samurai
3899dcbab79f92af727c2190bbd8abc5 ninja
6bf8d2cadde40af53d7f0fef95d4ec2c samurai
这些经过哈希的账本是可防篡改的,因为后面条目的摘要取决于前面的条目。修改或添加条目会改变之后的条目的摘要。
$ ./hash ninja pirate samurai
3899dcbab79f92af727c2190bbd8abc5 ninja
7ec21dcf528e12036b04774754ecc4e0 pirate 假设这里被黑进了一条记录636730d86709d03fed9ba64f84fc9be6 samurai 可注意到这条的摘要已经跟前面的不一样了
我们还可以在账本里面添加一条已知的结束记录,从而保护最后那条记录不被篡改:
$ ./hash ninja pirate samurai
3899dcbab79f92af727c2190bbd8abc5 ninja
7ec21dcf528e12036b04774754ecc4e0 pirate
636730d86709d03fed9ba64f84fc9be6 samurai
b233d566fe677d394aafb5eaf149e453 END
要想对账本进行验证,可以对事务进行重放,并确保在每一个步骤都能得到相同的哈希值:
our_digest = None
for line in fileinput.input():
#将摘要和文本拆出来
file_digest, word = line.strip().split("\t")
#计算文本的摘要
our_digest = hash_ledger_entry(word, our_digest)
#将计算结果跟存进账本里面的摘要进行比较
if our_digest != file_digest:
sys.exit(f"The digest for {word} does not match.")
print("All entries match.")
通过利用防篡改的账本,鉴于其中的每个条目都要取决于之前的条目,我们就有效地实现了一个非常简单的区块链。不过,这跟真正的区块链还是有所不同;为了实现真正的区块链,我们需要……
比特币的新颖之处在于它是一个没有所有者的分布式系统。这就是狂热分子所声称的区块链无需信任的意思所在:很多的“矿工”并不是像银行这样的中央权威,他们会互相竞争,看谁能够成功地向区块链写入一条新消息。他们通过工作量证明算法的手段来做到这一点,我们也可以在我们的账本里面去实现。
# 把下面这行加入你的import里面.
from secrets import token_bytes
def hash_ledger_entry_with_salt(salt, string, previous_digest=None):
"""对字符串及账本之前条目的摘要(如果有的话)进行哈希运算."""
hash = md5(string.encode("utf-8"))
hash.update(salt)
if previous_digest:
hash.update(previous_digest.encode("utf-8"))
return hash.hexdigest()
def generate_ledger(difficulty, *strings):
# Difficulty决定了我需要在摘要的前面有多少个0.
prefix = "0" * difficulty
digest = None
previous_digest = None
for string in strings:
# 添加入随机的salt,反复对一个字符串进行哈希运算,直到满足prefix所确定的0的数量.
while digest is None or not digest.startswith(prefix):
salt = token_bytes(16)
digest = hash_ledger_entry_with_salt(salt, string, previous_digest)
# 跟之前一样,要yield回摘要和条目,但也需要salt
# 否则的话,没法重演那些记录并加以验证
yield digest, salt.hex(), string
previous_digest = digest
digest = None
yield hash_ledger_entry_with_salt(salt, "END", previous_digest), salt, "END"
if __name__ == "__main__":
parser = ArgumentParser()
parser.add_argument(
"DIFFICULTY", help="The difficulty of confirming a ledger entry.", type=int
)
parser.add_argument("STRINGS", help="The ledger entries", nargs="+")
args = parser.parse_args()
for digest, salt, string in generate_ledger(args.DIFFICULTY, *args.STRINGS):
print(f"{digest}\t{salt}\t{string}")
这个新的程序会接受一个额外的参数,难度,并尝试生成一个盐值,这个值又会生成一个跟预期的0的数量匹配的哈希值:
$ ./hash 5 ninja pirate samurai
00000ad72553509e6c197e45ab7fa436 af0dce7ac4c87c2b9d9eafb6561c09f4 ninja
000000f556426cfa894ba2ce57383b1d b9d51e0e8ea977ba004e7c30be757144 pirate
000006373b2b336d6dac403a5fa90a73 dd9c6ad89f5014a0901bcb142e04e28b samurai
fa35b5a39bc0318015620684d60a27f0 dd9c6ad89f5014a0901bcb142e04e28b END
“挖矿”的过程可能需要大量计算。这个例子平均需要大约 250 万次尝试。所以比特币挖矿消耗的带能力比很多国家消耗的电力都要多:在“真正的”区块链上,矿工要计算和重新计算每个被开采出来的比特币的千万亿个哈希值。
注:请注意,实际应用不应该拿 md5 用于此目的。本文选择md5是出于它生成摘要的简洁性,但md5并不安全。
译者:boxi。