用 Python 实现区块链的实用介绍

区块链可以说是互联网自成立以来最重要和最具颠覆性的技术之一。它是比特币和其他加密货币背后的核心技术,在过去几年引起了很多关注。

作为其核心,区块链是一个分布式数据库,允许双方直接交易,而无需中央机构。这个简单而强大的概念对银行、政府和市场等各种机构具有重大意义。任何依赖中央数据库作为核心竞争优势的企业或组织都可能受到区块链技术的冲击。

抛开所有关于比特币和其他加密货币价格的炒作,本文的目标是为你提供实用的区块链技术的介绍。第1节和第2节介绍了区块链背后的一些核心概念,第3节介绍了如何使用 Python 实现区块链。我们还将实现2个 Web 应用程序,以方便最终用户与我们的区块链进行交互。

请注意,我在此使用比特币作为解释“区块链”通用技术的媒介,本文中描述的大多数概念都适用于其它区块链使用场景和其它加密货币。

本文的代码基于《通过构建一个区块链来学习区块链》一文构建,如果你第一次接触区块链,建议先阅读《通过构建一个区块链来学习区块链》一文。

1. 区块链快速入门

这一切始于 2008 年由一个使用 Satoshi Nakamoto 这个名字的人士发布的白皮书。该白皮书的标题是“比特币:对等电子现金系统”,它奠定了后来被称为区块链blockchain)的基础。在最初的比特币白皮书中,Satoshi 描述了如何建立点对点电子现金系统,使得在线支付可以直接从一方发送到另一方,而无需通过中央机构。该系统解决了数字货币中称为双重支出的重要问题。

1.1 什么是双重支出

假设 Alice 想给 Bob 支付 $1。如果 Alice 和 Bob 使用实物现金,那么在交易执行后 Alice 将不再拥有这 $1。如果 Alice 和 Bob 使用数字货币,那么问题变得更加复杂。数字形式的货币,可以轻易地被复制。如果 Alice 通过电子邮件向 Bob 发送一个价值 $1 的数字文件,Bob 无法确定 Alice 是否删除了她的文件副本。假如 Alice 保留了这个数字文件,那么她可以再将这 $1 发给其他人。这就是所谓的双重支出。

解决双重支出问题的一种方法是在 Alice、Bob 和网络中的所有其他参与者之间建立一个可信的第三方(比如银行)。该第三方负责集中管理总账账本,以便跟踪和验证网络中的所有交易。这个解决方案的缺点是,它需要一个信任的第三方才能使系统正常工作。

1.2 比特币:双重支出问题的分布式解决方案

为了解决双重支出问题,Satoshi 提出了公共总账账本,即利用比特币的区块链来跟踪网络中的所有交易。比特币的区块链具有以下特点:

  • 分布式:总账账本在多台计算机上复制,而不是存储在中央服务器上。任何具有互联网连接的计算机都可以下载区块链的完整副本。

  • 加密:加密技术用于确保发送人拥有其试图发送的比特币,并决定如何将交易添加到区块链中。

  • 不可变:区块链只能以追加方式更改。换句话说,只能往区块链中添加交易,但不能删除或修改交易。

  • 工作量证明(PoW):一种名为“矿工”的特殊类型的参与者参与竞争,寻找加密难题的解决方案,并允许他们在比特币的区块链中添加一个交易块。这个过程称为工作量证明。

发送比特币的流程如下:

  • 步骤1 (只需执行一次):创建一个比特币钱包。无论是要发送还是接收比特币,都需要比特币钱包。比特币钱包存储2条信息:公钥和私钥。私钥是一个加密数字,允许所有者将比特币发送给其他用户,或使用比特币进行付款。公钥是接收比特币所需的加密数字。公钥也被称为比特币地址(并非完全正确,但为了简单起见,我们将假定公钥和比特币地址是相同的)。请注意,钱包本身不存储比特币。比特币的余额信息存储在比特币的区块链中。

  • 步骤2:创建一个比特币交易。如果 Alice 想要给 Bob 发送1 BTC,Alice 需要使用她的私钥连接到她的比特币钱包,并创建一个包含她想要发送的比特币数量和她要发送的地址(此例为 Bob 的公钥)的交易。

  • 步骤3:将交易广播至比特币网络。一旦 Alice 创建好了比特币交易,她需要将此交易广播到整个比特币网络。

  • 步骤4:确认交易。在比特币网络上监听的某个矿工,使用 Alice 的公钥验证交易,确认 Alice 钱包里有足够的比特币(此例为 1 BTC),并向比特币区块链添加一条新的记录,其中包含交易的详细信息。

  • 步骤5:将区块链的更改广播给所有矿工。交易确认后,这位矿工应该向所有矿工广播区块链的更改,以确保区块链的副本全部同步。

2. 深入研究区块链技术

本部分的目标是深入介绍构建区块链的区块(block)的技术。我们将介绍公钥加密方法、哈希函数、区块链的挖矿和安全性。

2.1 公钥加密

公钥加密或非对称加密,由可以广泛传播的公钥和只有所有者知道的私钥组成。它完成了两个功能:认证——公钥通过配对的私钥来验证发送的消息;加密——只有配对的私钥持有者才能解密用公钥加密的消息。

RSA 和 ECDSA 是最常用的非对称加密算法。

在比特币中,使用 ECDSA 算法来生成比特币钱包。比特币使用了多种密钥和地址,简单起见,本文假设比特币只有一对私钥/公钥,而比特币钱包的地址就是公钥。

要发送或接收 BTC,用户首先生成一个包含一对私钥和公钥的钱包。如果 Alice 想要给 Bob 发送一些 BTC,她需要创建一个交易,在该交易中她输入她和 Bob 的公钥,以及她想要发送的 BTC 数量。然后使用她的私钥对交易进行签名。区块链中的计算机(即“矿工”——译者注)使用 Alice 的公钥来验证交易是否真实,并将交易添加到区块(block)中,稍后将添加到区块链(blockchain)里。

2.2 哈希函数和挖矿

所有比特币交易都分组在称为区块(block)的文件中。比特币每10分钟添加一个用于交易的新区块。一旦区块被添加到区块链中,它将不能再进行修改和删除。该网络的一个称为矿工(连接到区块链中的电脑)的特殊小组成员,负责创建新的交易块。矿工必须使用发送人的公钥验证每笔交易,确保发送人有足够的余额用于所请求的交易,并将交易添加到区块中。矿工可以完全自由地选择区块中包含哪些交易,因此发送人需要包含一笔交易费以激励矿工将交易添加到区块。

对于被区块链接受的区块,需要被“挖掘”。为了挖掘一个区块,矿工需要找到一个极其罕见的解决加密难题的方法。如果区块链接受了挖掘出来的区块,矿工将获得比特币奖励。挖矿过程也被称为工作量证明,它是保障区块链可靠和安全的主要机制。

哈希和区块链加密难题

要理解区块链的加密难题,我们需要从哈希函数开始。哈希函数是指,那些可以将任意大小的数据映射到固定大小的数据的函数。哈希函数的返回值称为哈希。哈希函数通常用于通过检测重复记录来加速数据库查找,同时它们也广泛用于密码学。

比特币使用 SHA-256 密码哈希函数。SHA-256 应用于区块数据(比特币交易)和一个称为 nonce 的数字的组合。无论是区块数据还是 nonce 被修改,都将得到完全不一样的哈希值。区块数据和 nonce 的哈希值需要满足一定的条件才能被视为有效的挖矿,比如哈希值的前4位必须是 0000。我们可以通过增加条件的复杂度来增加挖矿的困难度,比如可以增加必须为 0 的位数。

矿工需要找到使哈希值满足挖矿条件的随机数,来解决加密难题。

2.3 从区块到区块链

如上所述,交易分组在区块中,而区块添在区块链中。为了创建区块的链,每个新区块都使用前一区块的哈希作为其数据的一部分。为了创建一个新区块,矿工选择一组交易,添加前一区块的哈希并以上述类似的方式进行挖矿。对任何区块中的数据所做的任何更改都会影响到它后面所有区块的哈希值,最终它们将变为无效。

2.4 将区块添加到区块链中

比特币网络中的所有矿工相互竞争,为的是找到一个有效的区块,并将其添加到区块链中,并从网络中获得奖励。找到能通过区块验证的随机数很难,但由于矿工人数众多,让这个难题得到了解决。第一位提交有效区块的矿工将他的区块加入区块链,并获得比特币奖励。但是,如果两名或更多的矿工同时提交他们的区块,会发生什么?

解决冲突

如果两个矿工几乎同时处理一个区块,那么在网络中将有两个不同的区块链,我们需要等待下一个块来解决冲突。有些矿工会在区块链1上进行挖矿,而有些则在区块链2上进行挖矿。第一个找到新区块的矿工解决冲突。如果是在区块链1上先找到新区块,那么区块链2将变成无效。前一区块的奖励将给区块链1上的矿工,而区块链2上没有添加到区块链中的一部分交易将返回到交易池,并添加到下一区块。

2.5 区块链和双重支出

在本节中,我们将介绍对区块链执行双重支出攻击的最常见方式,以及用户应采取哪些措施来防止他们遭受损失。

竞赛攻击

攻击者快速、连续发送同一枚货币到两个不同的地址。为了防止这种攻击,建议在接受付款之前等待至少一个数据块确认。

芬尼攻击

攻击者预先在交易中挖掘一个区块,并在释放区块之前在第二个交易中花费相同的货币。在这种情况下,第二个交易将不会被验证。为了防止这种攻击,建议在接受付款之前至少等待6个数据块确认。

51%攻击

在这次攻击中,攻击者拥有51%的网络计算能力。攻击者首先发起一个广播到整个网络的交易,然后挖掘一个私人区块链,在那里他将前一个交易的货币加倍。由于攻击者拥有大部分计算能力,因此他可以保证他在某个时间点的区块链,比网络中的那些“诚实”区块链要长。然后,他可以发布他的较长的区块链,取代“诚实”区块链并取消原始交易。这种攻击极不可能,因为它在像比特币这样的区块链网络中,成本非常昂贵。

3. 用 Python 实现一个区块链

本节,我们将使用 Python 来实现一个简单的区块链和区块链客户端。我们的区块链有以下功能:

  • 可以将多个节点添加到区块链

  • 工作量证明 (PoW)

  • 解决节点之间冲突的简单方法

  • 使用 RSA 加密交易

我们的区块链客户端有以下功能:

  • 使用公钥/私钥加密来生成钱包(基于 RSA 算法)

  • 使用 RSA 加密生成交易

我们还将实现2个 Web 应用:

  • 面向矿工的“区块链前台”

  • 用于生成钱包和发送货币的“区块链客户端”

这个区块链主要基于该 Github 项目。我对原始代码进行了一些修改,以便为交易添加 RSA 加密。钱包生成和交易加密基于这个 Jupyter 笔记。而2个 Web 应用将使用 HTML/CSS/JS 来从零开始实现。

你可以从 https://github.com/adilmoujahid/blockchain-python-tutorial 中下载本项目的完整源代码。

请注意,此实现仅用于教学目的,不要应用于生产,因为它没有很好的安全性,不能很好地扩展并缺少许多重要功能。

3.1 区块链客户端

通过终端进行 blockchain_client 文件夹,然后运行 python blockchain_client.py 即可启动区块链客户端。在浏览器里输入 http://127.0.0.1:8080,就可以看到客户端的 Web 界面。

这个 Web 界面有3个菜单:

  • Wallet Generator:使用 RSA 加密算法生成钱包

  • Make Transaction:生成交易并将其发送到某个区块链节点

  • View Transactions:查看区块链中的交易

要生成或查看交易,你需要至少一个正在运行的区块链节点(将在下一节介绍)。

以下是 blockchain_client.py 中一些最重要部分的代码说明。

我们定义了一个名为 Transaction 的 Python 类,它有4个属性:sender_addresssender_private_keyrecipient_addressvalue。这是发送者创建交易时所需要的4部分信息。

to_dict() 方法将交易的信息以 Python 的字典形式进行返回(未包含发送者的私钥)。sign_transaction() 方法获取这个交易的信息,并使用发送者的私钥对其进行加密。

class Transaction:

    def __init__(self, sender_address, sender_private_key, recipient_address, value):
        self.sender_address = sender_address
        self.sender_private_key = sender_private_key
        self.recipient_address = recipient_address
        self.value = value

    def __getattr__(self, attr):
        return self.data[attr]

    def to_dict(self):
        return OrderedDict({'sender_address': self.sender_address,
                            'recipient_address': self.recipient_address,
                            'value': self.value})

    def sign_transaction(self):
        """
        使用私钥对交易进行签名
        """
        private_key = RSA.importKey(binascii.unhexlify(self.sender_private_key))
        signer = PKCS1_v1_5.new(private_key)
        h = SHA.new(str(self.to_dict()).encode('utf8'))
        return binascii.hexlify(signer.sign(h)).decode('ascii')

接下来,初始化一个 Flask 应用实例。我们将用它创建不同的 API 来与区块链客户端进行交互。

app = Flask(__name__)

之后,我们定义了3个 Flask 路由,来返回对应菜单项的 HTML 页面。

@app.route('/')
def index():
  return render_template('./index.html')

@app.route('/make/transaction')
def make_transaction():
    return render_template('./make_transaction.html')

@app.route('/view/transactions')
def view_transaction():
    return render_template('./view_transactions.html')

接下来,我们定义一个生成钱包(私钥/公钥对)的 API:

@app.route('/wallet/new', methods=['GET'])
def new_wallet():
  random_gen = Crypto.Random.new().read
  private_key = RSA.generate(1024, random_gen)
  public_key = private_key.publickey()
  response = {
    'private_key': binascii.hexlify(private_key.exportKey(format='DER')).decode('ascii'),
    'public_key': binascii.hexlify(public_key.exportKey(format='DER')).decode('ascii')
  }

  return jsonify(response), 200

下面,我们定义一个返回交易和签名的 API。它从用户输入中获取 sender_address, sender_private_key, recipient_address, value

@app.route('/generate/transaction', methods=['POST'])
def generate_transaction():

  sender_address = request.form['sender_address']
  sender_private_key = request.form['sender_private_key']
  recipient_address = request.form['recipient_address']
  value = request.form['amount']

  transaction = Transaction(sender_address, sender_private_key, recipient_address, value)

  response = {'transaction': transaction.to_dict(), 'signature': transaction.sign_transaction()}

  return jsonify(response), 200

3.2 区块链的实现

你可以这样启动一个区块链节点:进入 blockchain 目录,然后输入 python blockchain.py。打开浏览器,输入 http://127.0.0.1:5000,你将看到区块链前台的 Web 界面。这个界面有2个菜单:

  • Mine:用于查看交易和区块链数据以及正在挖矿的区块

  • Configure:用于配置不同区块链节点之间的连接

以下是 blockchain.py 中一些最重要部分的代码说明。

我们从定义一个 Blockchain 类开始,它有以下属性:

  • transactions:所有将会添加到下一区块的交易列表

  • chain:区块数组构成的实际区块链

  • nodes:一个包含节点 url 的集合。区块链使用这些节点从其他节点检索区块链数据,并实现区块链的同步。

  • node_id:用于标识区块链节点的随机字符串

Blockchain 类还实现了以下方法:

  • register_node(node_url):将一个新节点添加到区块链的节点列表中

  • verify_transaction_signature(sender_address, signature, transaction):检查提供的签名是否和由公钥(发送者的地址)进行签名交易相符合

  • submit_transaction(sender_address, recipient_address, value, signature):将通过签名验证的交易添加到交易列表中

  • create_block(nonce, previous_hash):在区块链中添加一个用于交易的新区块

  • hash(block):创建一个区块的 SHA-256 哈希

  • proof_of_work():工作量证明算法。寻找满足采矿条件的 nonce 随机数。

  • valid_proof(transactions, last_hash, nonce, difficulty=MINING_DIFFICULTY):检查哈希值是否满足挖掘条件。该函数在 proof_of_work() 内部调用

  • valid_chain(chain):检查区块链是否有效

  • resolve_conflicts():使用网络中最长的链替换来解决区块链节点间的冲突

class Blockchain:

    def __init__(self):

        self.transactions = []
        self.chain = []
        self.nodes = set()
        # 生成一个随机字符串,作为 node_id
        self.node_id = str(uuid4()).replace('-', '')
        # 创建一个起始区块
        self.create_block(0, '00')

    def register_node(self, node_url):
        """
        将一个新节点添加到区块链的节点列表中
        """
        # ...

    def verify_transaction_signature(self, sender_address, signature, transaction):
        """
        检查提供的签名是否和由公钥(发送者的地址)进行签名交易相符合
        """
        # ...

    def submit_transaction(self, sender_address, recipient_address, value, signature):
        """
        将通过签名验证的交易添加到交易列表中
        """
        # ...

    def create_block(self, nonce, previous_hash):
        """
        在区块链中添加一个用于交易的新区块
        """
        # ...

    def hash(self, block):
        """
        创建一个区块的 SHA-256 哈希
        """
        ...

    def proof_of_work(self):
        """
        工作量证明算法。寻找满足采矿条件的 `nonce` 随机数
        """
        # ...

    def valid_proof(self, transactions, last_hash, nonce, difficulty=MINING_DIFFICULTY):
        """
        检查哈希值是否满足挖掘条件。该函数在 proof_of_work() 内部调用
        """
        # ...

    def valid_chain(self, chain):
        """
        检查区块链是否有效
        """
        # ...

    def resolve_conflicts(self):
        """
        使用网络中最长的链替换来解决区块链节点间的冲突
        """
        # ...

接下来,初始化一个 Flask 应用实例。我们将用它创建不同的 API 来与区块链进行交互。

app = Flask(__name__)
CORS(app)

然后,我们初始化一个区块链实例:

blockchain = Blockchain()

下面我们定义2个路由来返回区块链前台所需要的 HTML 页面:

@app.route('/')
def index():
    return render_template('./index.html')

@app.route('/configure')
def configure():
    return render_template('./configure.html')

下面,我们定义用于管理区块链交易和挖矿的 API:

  • /transactions/new:这个 API 将创建一个新的交易,并将该交易添加到区块链的交易列表中。如果通过签名验证,它将被加入到下一个区块中。这个 API 从用户输入中获取 'sender_address', 'recipient_address', 'amount' 和 'signature'

  • /transactions/get:这个 API 获取所有将加到下一个区块中的交易

  • /chain:返回所有区块链数据

  • /mine:这个 API 运行工作量证明算法,并将新的交易块添加到区块链中

@app.route('/transactions/new', methods=['POST'])
def new_transaction():
    values = request.form

    # 检查必须由 post 提交的字段
    required = ['sender_address', 'recipient_address', 'amount', 'signature']
    if not all(k in values for k in required):
        return 'Missing values', 400
    # 创建一个新交易
    transaction_result = blockchain.submit_transaction(values['sender_address'], values['recipient_address'], values['amount'], values['signature'])

    if transaction_result == False:
        response = {'message': 'Invalid Transaction!'}
        return jsonify(response), 406
    else:
        response = {'message': 'Transaction will be added to Block '+ str(transaction_result)}
        return jsonify(response), 201

@app.route('/transactions/get', methods=['GET'])
def get_transactions():
    # 从交易池中获取交易
    transactions = blockchain.transactions

    response = {'transactions': transactions}
    return jsonify(response), 200

@app.route('/chain', methods=['GET'])
def full_chain():
    response = {
        'chain': blockchain.chain,
        'length': len(blockchain.chain),
    }
    return jsonify(response), 200

@app.route('/mine', methods=['GET'])
def mine():
    # 通过工作量证明算法来获取下一个证明
    last_block = blockchain.chain[-1]
    nonce = blockchain.proof_of_work()

    # 当找到满足条件的证明后,接收奖励
    blockchain.submit_transaction(sender_address=MINING_SENDER, recipient_address=blockchain.node_id, value=MINING_REWARD, signature="")

    # 通过将其添加到链中来伪造新区块
    previous_hash = blockchain.hash(last_block)
    block = blockchain.create_block(nonce, previous_hash)

    response = {
        'message': "New Block Forged",
        'block_number': block['block_number'],
        'transactions': block['transactions'],
        'nonce': block['nonce'],
        'previous_hash': block['previous_hash'],
    }
    return jsonify(response), 200

下面定义管理区块链节点的 API。

  • /nodes/register:这个 API 从用户输入中获取节点的 URL 列表,并将它们添加到区块链的节点列表中

  • /nodes/resolve:该 API 通过用网络中可用的最长链替换本地链来解决区块链节点之间的冲突

  • /nodes/get:返回节点列表

@app.route('/nodes/register', methods=['POST'])
def register_nodes():
    values = request.form
    nodes = values.get('nodes').replace(" ", "").split(',')

    if nodes is None:
        return "Error: Please supply a valid list of nodes", 400

    for node in nodes:
        blockchain.register_node(node)

    response = {
        'message': 'New nodes have been added',
        'total_nodes': [node for node in blockchain.nodes],
    }
    return jsonify(response), 201


@app.route('/nodes/resolve', methods=['GET'])
def consensus():
    replaced = blockchain.resolve_conflicts()

    if replaced:
        response = {
            'message': 'Our chain was replaced',
            'new_chain': blockchain.chain
        }
    else:
        response = {
            'message': 'Our chain is authoritative',
            'chain': blockchain.chain
        }
    return jsonify(response), 200


@app.route('/nodes/get', methods=['GET'])
def get_nodes():
    nodes = list(blockchain.nodes)
    response = {'nodes': nodes}
    return jsonify(response), 200

结语

在本文中,我们介绍了区块链背后的一些核心概念,并学习了如何使用 Python 实现这些核心概念。为了简单起见,我没有介绍一些技术细节,例如:电子钱包地址和 Merkle 树。如果你想更深入的学习本主题,强烈建议阅读比特币的白皮书和 Andreas Antonopoulos 所著的非常出色的书:《Mastering Bitcoin: Programming the Open Blockchain》。

本文译自 Adil Moujahid 的 《A Practical Introduction to Blockchain with Python》。水平有限,如有错误敬请指教:[email protected]

flask 区块链 blockchain 2018-05-26 21:29 3615733