[译]Dumbcoin-基于教育目的的Python实现类比特币的区块链

原文地址 https://github.com/julienr/ipynb_playground/blob/master/bitcoin/dumbcoin/dumbcoin.ipynb

简介

这是一本实验性质的手册,用Python实现了类比特币区块链的大多数概念。它并不安全,也不是真实的区块链,所以你不应该用它做任何其他事情,除了教育目的。

不考虑比特币的金融利弊,它的底层技术很有趣,原始论文也很容易理解。

或许你想要看看这本手册在HackerNews上的讨论。

引用

我使用了比特币的最初论文,还有Michael Nielsen比特币的解释比特币Wiki是非常不错的技术资源。

内容

  1. 哈希函数和挖矿
  2. 难度
  3. 钱包
  4. 交易
  5. 区块
  6. 攻击
  7. 51%攻击
  8. 与比特币的不同

需要用到的库

import hashlib
import random
import string
import json
import binascii
import numpy as np
import pandas as pd
import pylab as pl
import logging
%matplotlib inline

哈希函数和挖矿

我们先实现挖矿。这里我们使用SHA256哈希函数,因为SHA256的Python实现的很稳定。注意比特币使用了2轮SHA256算法,而不是1轮。

我们的哈希函数会把任意长度的字符串转换成固定长度的64进制字符串。例如

def sha256(message):
	return hashlib.sha256(message.encode('ascii')).hexdigest()

挖矿的流程是:任意长度字符串x,找这样一个数字nonce,满足hash(x+nonce)产生的哈希值以特定字符串开始。

这里我们挖一个特定的数字nonce,信息 “hello bitcoin” 连接上nonce产生的哈希值有至少两个前缀数字。

message = 'hello bitcoin'
for nonce in range(1000):
    digest = sha256(message + str(nonce))
    if digest.startswith('11'):
        print('Found nonce = %d' % nonce)
        break
print(sha256(message + str(nonce)))


Found nonce = 32
112c38d2fdb6ddaf32f371a390307ccc779cd92443b42c4b5c58fa548f63ed83

要求的前缀数字越多,越难找到特定nonce。在比特币里,这叫做挖矿难度。注意比特币并不要求前缀数字,而是要求哈希值必须低于某个特定值。但是原理是相同的。

定义两个函数,后面使用:一个函数做哈希运算,一个函数针对字符串挖矿nonce

def dumb_hash(message):
	"""
    返回64进制字符串
	"""
	return sha256(message)
			
			
def mine(message, difficulty=1):
    """
	输入给定字符串,找到nonce,满足hash(string+nonce)有特定难度的前缀
				    
    Returns: (nonce, niters)
		nonce: The found nonce
		niters: The number of iterations required to find the nonce
	"""
	assert difficulty >= 1, "Difficulty of 0 is not possible"
	i = 0
	prefix = '1' * difficulty
	while True:
		nonce = str(i)
		digest = dumb_hash(message + nonce)
		if digest.startswith(prefix):
			return nonce, i
		i += 1
		
		

这样我们就可以在不同难度下挖矿了

nonce, niters = mine('42', difficulty=1)
print('Took %d iterations' % niters)

nonce, niters = mine('42', difficulty=3)
print('Took %d iterations' % niters)


Took 23 iterations
Took 2272 iterations

例子中可以看到难度3的迭代次数要比难度1的迭代次数大很多。注意,运气好的话,第一个nonce(这里是0)就可以找到了。因此难度控制的是尝试的平均次数。基于此,我们可以做一个好看的图表。

难度表

对于每个难度等级,我们会针对多个字符串挖矿nonce。我们记录下每个难度的平均尝试次数。

def random_string(length=10):
	return ''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(length))

strings = [random_string() for i in range(50)]


levels = range(1, 5)
# An array of results with a row for each difficulty and a column for each test string
results = pd.DataFrame(index=strings, columns=levels, dtype=np.int)
results.fillna(value=0)

#results = np.zeros((N_LEVELS, len(strings)), dtype=np.int)
for level in levels:
    for s in strings:
		_, niters = mine(s, difficulty=level)
		results[level][s] = niters


pl.figure(figsize=(10, 5))
ax = pl.subplot(111)
ax.set_title('Number of iterations to mine a nonce for various difficulty')
results.plot.box(showfliers=False, ax=ax)
ax.set_xlabel('Difficulty')
ax.set_ylabel('Iterations')

钱包

在比特币里钱包是公钥私钥对。公钥私钥对解释 RSA

公钥用于接受交易,私钥用于花钱。私钥签名交易,所有其他人都可以用公钥验证。

在比特币里,钱包稍微复杂一点。钱包是多个公钥私钥对,钱包地址并不直接是公钥。这更好的保证隐私和安全,但是dumbcoin这里只用一个,公钥作为钱包地址。

import Crypto
import Crypto.Random
from Crypto.Hash import SHA
from Crypto.PublicKey import RSA
from Crypto.Signature import PKCS1_v1_5


class Wallet(object):
    """
    钱包是公钥私钥对
    """
    def __init__(self):
        random_gen = Crypto.Random.new().read
        self._private_key = RSA.generate(1024, random_gen)
        self._public_key = self._private_key.publickey()
        self._signer = PKCS1_v1_5.new(self._private_key)

    @property
    def address(self):
        """我们简单起见,公钥就是地址"""
        return binascii.hexlify(self._public_key.exportKey(format='DER')).decode('ascii')

    def sign(self, message):
        """
        用这个钱包签名message
        """
        h = SHA.new(message.encode('utf8'))
        return binascii.hexlify(self._signer.sign(h)).decode('ascii')


def verify_signature(wallet_address, message, signature):
    """
	检查message的签名signature是否由wallet_address签名
    """
    pubkey = RSA.importKey(binascii.unhexlify(wallet_address))
    verifier = PKCS1_v1_5.new(pubkey)
    h = SHA.new(message.encode('utf8'))
    return verifier.verify(h, binascii.unhexlify(signature))


# 测试一下
w1 = Wallet()
signature = w1.sign('foobar')
assert verify_signature(w1.address, 'foobar', signature)
assert not verify_signature(w1.address, 'rogue message', signature)

做交易

在钱包之间交换钱,需要用到交易。交易是由下面3部分组成:

  • 消费者 Spender,他会给交易签名,花他自己的钱
  • 多个输入 input,是其他交易的输出。所有这些的接受者是消费者 Spender的钱包。
    要不然你就可以花其他人的钱了。
  • 多个输出 output,每个都指定了钱数和接受者。

交易会包含交易费,这样刺激挖矿者在区块中包含该交易。费用就是输入数量和输出数量之间的差异。

所有交易需要一个父节点,在架构里需要一个根节点。我们起名 GenesisTransaction

class TransactionInput(object):
    """
	交易输入,指向另一个交易的输出
    """
    def __init__(self, transaction, output_index):
        self.transaction = transaction
        self.output_index = output_index
        assert 0 <= self.output_index < len(transaction.outputs)

    def to_dict(self):
        d = {
            'transaction': self.transaction.hash(),
            'output_index': self.output_index
        }
        return d

    @property
    def parent_output(self):
        return self.transaction.outputs[self.output_index]


class TransactionOutput(object):
    """
	交易输出,指定数量和接收者钱包
    """
    def __init__(self, recipient_address, amount):
        self.recipient = recipient_address
        self.amount = amount

    def to_dict(self):
        d = {
            'recipient_address': self.recipient,
            'amount': self.amount
        }
        return d


def compute_fee(inputs, outputs):
    """
	通过总输入和总输出,计算交易费
    """
    total_in = sum(i.transaction.outputs[i.output_index].amount for i in inputs)
    total_out = sum(o.amount for o in outputs)
    assert total_out <= total_in, "Invalid transaction with out(%f) > in(%f)" % (total_out, total_in)
    return total_in - total_out


class Transaction(object):
    def __init__(self, wallet, inputs, outputs):
        """
		创建交易,从指定钱包花钱
        """
        self.inputs = inputs
        self.outputs = outputs
        self.fee = compute_fee(inputs, outputs)
        self.signature = wallet.sign(json.dumps(self.to_dict(include_signature=False)))

    def to_dict(self, include_signature=True):
        d = {
            "inputs": list(map(TransactionInput.to_dict, self.inputs)),
            "outputs": list(map(TransactionOutput.to_dict, self.outputs)),
            "fee": self.fee
        }
        if include_signature:
            d["signature"] = self.signature
        return d

    def hash(self):
        return dumb_hash(json.dumps(self.to_dict()))


class GenesisTransaction(Transaction):
    """
	第一笔交易是特定交易,没有输入,有25个比特币的输出
    """
    def __init__(self, recipient_address, amount=25):
        self.inputs = []
        self.outputs = [
            TransactionOutput(recipient_address, amount)
        ]
        self.fee = 0
        self.signature = 'genesis'

    def to_dict(self, include_signature=False):
        # TODO: Instead, should sign genesis transaction will well-known public key ?
        assert not include_signature, "Cannot include signature of genesis transaction"
        return super().to_dict(include_signature=False)

有了上面的类,我们可以在alice和bob之间做交易了

alice = Wallet()
bob = Wallet()

t1 = GenesisTransaction(alice.address)
t2 = Transaction(
    alice,
    [TransactionInput(t1, 0)],
    [TransactionOutput(bob.address, 2.0), TransactionOutput(alice.address, 22.0)]
)
assert np.abs(t2.fee - 1.0) < 1e-5

在比特币里,并不会存储钱包的钱数。 会遍历交易链来计算你有多少。写一个函数来做这件事。

alice = Wallet()
bob = Wallet()
walter = Wallet()

# This gives 25 coins to Alice
t1 = GenesisTransaction(alice.address)

# Of those 25, Alice will spend
# Alice -- 5 --> Bob
#       -- 15 --> Alice
#       -- 5 --> Walter
t2 = Transaction(
    alice,
    [TransactionInput(t1, 0)],
    [TransactionOutput(bob.address, 5.0), TransactionOutput(alice.address, 15.0), TransactionOutput(walter.address, 5.0)]
)

# Walter -- 5 --> Bob
t3 = Transaction(
    walter,
    [TransactionInput(t2, 2)],
    [TransactionOutput(bob.address, 5.0)])

# Bob -- 8 --> Walter
#     -- 1 --> Bob
#        1 fee
t4 = Transaction(
    bob,
    [TransactionInput(t2, 0), TransactionInput(t3, 0)],
    [TransactionOutput(walter.address, 8.0), TransactionOutput(bob.address, 1.0)]
)

transactions = [t1, t2, t3, t4]


def compute_balance(wallet_address, transactions):
    """
	给定地址和交易链,计算地址的钱包余额
    """
    balance = 0
    for t in transactions:
        # Subtract all the money that the address sent out
        for txin in t.inputs:
            if txin.parent_output.recipient == wallet_address:
                balance -= txin.parent_output.amount
        # Add all the money received by the address
        for txout in t.outputs:
            if txout.recipient == wallet_address:
                balance += txout.amount
    return balance

print("Alice  has %.02f dumbcoins" % compute_balance(alice.address, transactions))
print("Bob    has %.02f dumbcoins" % compute_balance(bob.address, transactions))
print("Walter has %.02f dumbcoins" % compute_balance(walter.address, transactions))


Alice  has 15.00 dumbcoins
Bob    has 1.00 dumbcoins
Walter has 8.00 dumbcoins

还需要确认交易是否有效,这意味着:

  • 你仅仅能花自己的钱。这意味着所有输入由交易的拥有者拥有。
  • 确保你花的钱不会比你有的多。这是有上面的compute_fee检查。

函数

def verify_transaction(transaction):
    """
    确保交易是有效的
    - 所有输入都属于相同的钱包
    - 交易是由响应的钱包主人签名的
    """
    tx_message = json.dumps(transaction.to_dict(include_signature=False))
    if isinstance(transaction, GenesisTransaction):
        # TODO: We should probably be more careful about validating genesis transactions
        return True

    # Verify input transactions
    for tx in transaction.inputs:
        if not verify_transaction(tx.transaction):
            logging.error("Invalid parent transaction")
            return False

    # Verify a single wallet owns all the inputs
    first_input_address = transaction.inputs[0].parent_output.recipient
    for txin in transaction.inputs[1:]:
        if txin.parent_output.recipient != first_input_address:
            logging.error(
                "Transaction inputs belong to multiple wallets (%s and %s)" %
                (txin.parent_output.recipient, first_input_address)
            )
            return False

    if not verify_signature(first_input_address, tx_message, transaction.signature):
        logging.error("Invalid transaction signature, trying to spend someone else's money ?")
        return False

    # Call compute_fee here to trigger an assert if output sum is great than input sum. Without this,
    # a miner could put such an invalid transaction.
    compute_fee(transaction.inputs, transaction.outputs)

    return True
	

t1 = GenesisTransaction(alice.address)
# This is an invalid transaction because bob is trying to spend alice's money
# (alice was the recipient of the input - t1)
t2 = Transaction(
    bob,
    [TransactionInput(t1, 0)],
    [TransactionOutput(walter.address, 10.0)]
)
# This is valid, alice is spending her own money
t3 = Transaction(
    alice,
    [TransactionInput(t1, 0)],
    [TransactionOutput(walter.address, 10.0)]
)

assert verify_transaction(t1)
assert not verify_transaction(t2)


ERROR:root:Invalid transaction signature, trying to spend someone else's money ?


assert verify_transaction(t3)

把交易写入区块

我们现在有

  • 定义钱包(公钥私钥对)
  • 在钱包之间创建交易
  • 验证交易(检查交易的签名)

剩下来需要把交易放入区块,让挖矿者挖矿区块。挖区块由两部分组成:

  • 验证区块中的交易
  • 找到一个nonce,区块的哈希以几个0开始

并且挖矿根据规则会生成钱,第一个区块GenesisTransaction会给25个币给任意地址。挖矿者会添加交易,把交易产生的费用传递给任意地址。

BLOCK_INCENTIVE = 25 # 挖矿区块得到的币数
DIFFICULTY = 2


def compute_total_fee(transactions):
    """返回交易的总费用"""
    return sum(t.fee for t in transactions)


class Block(object):
    def __init__(self, transactions, ancestor, miner_address, skip_verif=False):
        """
        Args:
            transactions: 包含在区块中的交易
            ancestor: 上一个区块
            miner_address: 挖矿者的钱包. 区块奖励地址,交易费会存储在这里
        """
        reward = compute_total_fee(transactions) + BLOCK_INCENTIVE
        self.transactions = [GenesisTransaction(miner_address, amount=reward)] + transactions
        self.ancestor = ancestor

        if not skip_verif:
            assert all(map(verify_transaction, transactions))

        json_block = json.dumps(self.to_dict(include_hash=False))
        self.nonce, _ = mine(json_block, DIFFICULTY)
        self.hash = dumb_hash(json_block + self.nonce)

    def fee(self):
        """返回该区块的交易费"""
        return compute_total_fee(self.transactions)

    def to_dict(self, include_hash=True):
        d = {
            "transactions": list(map(Transaction.to_dict, self.transactions)),
            "previous_block": self.ancestor.hash,
        }
        if include_hash:
            d["nonce"] = self.nonce
            d["hash"] = self.hash
        return d


class GenesisBlock(Block):
    """
	区块链中的第一个区块,没有祖先
    """
    def __init__(self, miner_address):
        super(GenesisBlock, self).__init__(transactions=[], ancestor=None, miner_address=miner_address)

    def to_dict(self, include_hash=True):
        d = {
            "transactions": [],
            "genesis_block": True,
        }
        if include_hash:
            d["nonce"] = self.nonce
            d["hash"] = self.hash
        return d

类似验证交易,还需要验证区块

def verify_block(block, genesis_block, used_outputs=None):
    """
    验证区块的有效性:
    - 验证哈希以指定数量的数字开头
    - 验证相同交易的输出没有使用两次
    - 验证所有交易有效
    - 验证验证第一笔交易是 GenesisTransaction,钱数是 BLOCK_INCENTIVE + total_fee

    Args:
        block: 需要验证的区块
        genesis_block: The genesis block (由所有人共享)
        used_outputs: 在这之上的交易中的所有输出
    """
    if used_outputs is None:
        used_outputs = set()

    # Verify hash
    prefix = '1' * DIFFICULTY
    if not block.hash.startswith(prefix):
        logging.error("Block hash (%s) doesn't start with prefix %s" % (block.hash, prefix))
        return False
    if not all(map(verify_transaction, block.transactions)):
        return False

    # Verify that transactions in this block don't use already spent outputs
    #
    # Note that we could move this in verify_transaction, but this would require some passing the used_outputs
    # around more. So we do it here for simplicity
    for transaction in block.transactions:
        for i in transaction.inputs:
            if i.parent_output in used_outputs:
                logging.error("Transaction uses an already spent output : %s" % json.dumps(i.parent_output.to_dict()))
                return False
            used_outputs.add(i.parent_output)

    # Verify ancestors up to the genesis block
    if not (block.hash == genesis_block.hash):
        if not verify_block(block.ancestor, genesis_block, used_outputs):
            logging.error("Failed to validate ancestor block")
            return False

    # Verify the first transaction is the miner's reward
    tx0 = block.transactions[0]
    if not isinstance(tx0, GenesisTransaction):
        logging.error("Transaction 0 is not a GenesisTransaction")
        return False
    if not len(tx0.outputs) == 1:
        logging.error("Transactions 0 doesn't have exactly 1 output")
        return False
    reward = compute_total_fee(block.transactions[1:]) + BLOCK_INCENTIVE
    if not tx0.outputs[0].amount == reward:
        logging.error("Invalid amount in transaction 0 : %d, expected %d" % (tx0.outputs[0].amount, reward))
        return False

    # Only the first transaction shall be a genesis
    for i, tx in enumerate(block.transactions):
        if i == 0:
            if not isinstance(tx, GenesisTransaction):
                logging.error("Non-genesis transaction at index 0")
                return False  
        elif isinstance(tx, GenesisTransaction):
            logging.error("GenesisTransaction (hash=%s) at index %d != 0", tx.hash(), i)
            return False
    return True
	
	
alice = Wallet()
bob = Wallet()
walter = Wallet()

genesis_block = GenesisBlock(miner_address=alice.address)
print("genesis_block : " + genesis_block.hash + " with fee=" + str(genesis_block.fee()))

t1 = genesis_block.transactions[0]
t2 = Transaction(
    alice,
    [TransactionInput(t1, 0)],
    [TransactionOutput(bob.address, 5.0), TransactionOutput(alice.address, 15.0), TransactionOutput(walter.address, 5.0)]
)
t3 = Transaction(
    walter,
    [TransactionInput(t2, 2)],
    [TransactionOutput(bob.address, 5.0)])

t4 = Transaction(
    bob,
    [TransactionInput(t2, 0), TransactionInput(t3, 0)],
    [TransactionOutput(walter.address, 8.0), TransactionOutput(bob.address, 1.0)]
)

block1 = Block([t2], ancestor=genesis_block, miner_address=walter.address)
print("block1        : " + block1.hash + " with fee=" + str(block1.fee()))

block2 = Block([t3, t4], ancestor=block1, miner_address=walter.address)
print("block2        : " + block2.hash + " with fee=" + str(block2.fee()))		

genesis_block : 1162dce8ffec3acf13ce61109f121922eee8cceeea4784aa9d90dc6ec0e0fa92 with fee=0
block1 : 11af277c02c22a7e3c3a73102282ca5a0e01869b1d852527b6a842f0786ee8e3 with fee=0.0
block2 : 119e461d393b793478c7c7cb9fa6feb54fca35865a398d017e541027a78a2e9a with fee=1.0

verify_block(block1, genesis_block)
verify_block(block2, genesis_block)

True

def collect_transactions(block, genesis_block):
    """递归收集该区块和祖先的所有交易"""
    # Important : COPY block.transactions
    transactions = [] + block.transactions
    if block.hash != genesis_block.hash:
        transactions += collect_transactions(block.ancestor, genesis_block)
    return transactions

transactions = collect_transactions(block2, genesis_block)

# Alice mined 25 (from the genesis block) and gave 5 to bob and 5 to walter
print("Alice  has %.02f dumbcoins" % compute_balance(alice.address, transactions))
# Bob received 5 from alice and 5 from walter, but then back 8 to walter with a transaction fee of 1
print("Bob    has %.02f dumbcoins" % compute_balance(bob.address, transactions))
# Walter mined 2 blocks (2 * 25), received 8 from bob and go a transaction fee of 1 on block2
print("Walter has %.02f dumbcoins" % compute_balance(walter.address, transactions))

Alice has 15.00 dumbcoins
Bob has 1.00 dumbcoins
Walter has 59.00 dumbcoins

攻击

如果挖矿者(walter)尝试花bob的钱,bob的区块不会验证通过

tx = Transaction(
    walter,
    [TransactionInput(t4, 1)],
    [TransactionOutput(walter.address, 1.0)],
)

# Note that we have to use skip_verif=True to not trigger the assert in Block's constructor
block = Block([tx], ancestor=block2, miner_address=walter.address, skip_verif=True)
assert not verify_block(block, genesis_block)

ERROR:root:Invalid transaction signature, trying to spend someone else’s money ?

还有一种攻击是挖矿者尝试修改区块中的交易,也不会成功

# This is a transaction signed by bob
tx = Transaction(
    bob,
    [TransactionInput(t2, 0), TransactionInput(t3, 0)],
    [TransactionOutput(walter.address, 8.0), TransactionOutput(bob.address, 1.0)]
)
# And modified by the miner
tx.outputs[0].amount += 4
block = Block([t3, tx], ancestor=block1, miner_address=walter.address, skip_verif=True)
assert not verify_block(block, genesis_block)

ERROR:root:Invalid transaction signature, trying to spend someone else’s money ?

签名的交易可能重复,增加循环中的总钱数

block = Block([t3, t3, t4], ancestor=block1, miner_address=bob.address, skip_verif=True)
assert not verify_block(block, genesis_block)

ERROR:root:Transaction uses an already spent output : {"recipient_address": “30819f300d06092a864886f70d010101050003818d0030818902818100aec88246cc7e63c404c083b34ce153bdcf25749ec8d8764df14c0940ec795ce7522c9923e7aad577f60d346939e6c6aff45c432c6644cef675efb336fc24bdbb966d1448521537390b502c5677251e40c4a0d7b8ec9be732c21b9fe7bac7dc0f8551677645a6b89a8a39ac581fc2d440867f46994d1a52d1ddc1a22f67740f3f0203010001”, “amount”: 5.0}

51%攻击

到目前为止账目是自我完整的,钱是只能由所有者花费。 但是还有个缺陷,没有账目中心,相信哪一个?规则是相信最长的那个区块链。

# Let's start again with the same example transactions and blocks as above
alice = Wallet()
bob = Wallet()
walter = Wallet()

genesis_block = GenesisBlock(miner_address=alice.address)

t1 = genesis_block.transactions[0]
t2 = Transaction(
    alice,
    [TransactionInput(t1, 0)],
    [TransactionOutput(bob.address, 5.0), TransactionOutput(alice.address, 15.0), TransactionOutput(walter.address, 5.0)]
)
t3 = Transaction(
    walter,
    [TransactionInput(t2, 2)],
    [TransactionOutput(bob.address, 5.0)])

t4 = Transaction(
    bob,
    [TransactionInput(t2, 0), TransactionInput(t3, 0)],
    [TransactionOutput(walter.address, 8.0), TransactionOutput(bob.address, 1.0)]
)

block1 = Block([t2], ancestor=genesis_block, miner_address=walter.address)
block2 = Block([t3, t4], ancestor=block1, miner_address=walter.address)

上面的t4,bob发了8个币给walter。假设walter是个商人,他卖给bob一个很好的车,交换了8个币。bob有了一辆车,walter有8个币。

bob感觉有点穷,他不想在block2中花费8个币。他修改了一下历史,他给自己交易,然后挖矿了自己的区块。

t4_malicious = Transaction(
    bob,
    [TransactionInput(t2, 0), TransactionInput(t3, 0)],
    [TransactionOutput(bob.address, 8.0), TransactionOutput(bob.address, 1.0)]
)
block2_malicious = Block([t3, t4_malicious], ancestor=block1, miner_address=bob.address)
print(verify_block(block2_malicious, genesis_block))

True

block2_malicious还没有被网络接受,它还没超过最长的账目block2。更多的交易发生了,在后面的区块挖矿。刚好bob挖矿了新的区块使用上一个交易。

t6 = Transaction(
    alice,
    [TransactionInput(t2, 1)],
    [TransactionOutput(walter.address, 5.0), TransactionOutput(alice.address, 10.0)]
)

block3_malicious = Block([t6], ancestor=block2_malicious, miner_address=bob.address)

在比特币中的规则,是相信区块链中最长的。我们找一下哪个是最长的。

def chain_length(block):
    if block.ancestor is None:
        return 1
    else:
        return 1 + chain_length(block.ancestor)

print("Chain length for block2           : %d" % chain_length(block2))
print("Chain length for block3_malicious : %d " % chain_length(block3_malicious))

Chain length for block2 : 3
Chain length for block3_malicious : 4

接受的账目是block3_malicious,因为它是最长的。bob成功的重写了账目。另外它还是有一辆车,用8个币在walter那交易得来的。walter接受的8个币的交易被认为非法,导致walter并没有拥有8个币。

这就是51%攻击,或者大多数攻击。

然而walter通过计算 block3 = Block([t6], ancestor=block2, miner_address=walter.address),block3应该由网络接受的。bob要么很幸运,很快找到了nonce,或者刚好比walter更快创建了更长的账目。

从这个例子中得到三个有趣的结论:

  • 即使有大多数的挖矿能力,攻击者只能改变他的一个交易数据。他没办法从空气中创造钱(这需要无效区块)或花了其他的人钱(他不能签名交易)。
  • 这个工作的昂贵证据是需要花费足够的代码创建有效区块。如果证据是无足轻重的,网络可能就早被并发冲突给打乱了,也不会达成一致。
  • 相信最长区块规则,就是需要在确认交易有效性之前,要等多个交易确认。交易的确认数量就是在上一笔交易后面有多少区块。等待的交易确认数越多,攻击者修改交易的代价就越大。

与比特币的主要不同

就像前面解释的,这个例子并不是要做一个可使用的比特币实现,因此很多特性都没实现。

  • 使用了公钥作为钱包地址,比特币更复杂。
  • 比特币使用了ECDSA,这里使用了RSA。
  • 比特币使用了两轮SHA256,这里使用了一轮。
  • 比特币的交易使用了脚本语言,这里只是简单的一个交易输出有一个数量。
  • 比特币需要传播区块给所有节点。整块传播并没有在这里写。参考Client Node DiscoveryProtocol documentation
  • 比特币使用了Merkle tree来存储。

安全问题

出了前面说的不同,这个实现还有很多安全因素

  • 使用浮点数运算会产生很多问题。(精度损失)
  • 这里的JSON序列化不够跨平台。例如在不同平台的相同交易的两次序列化会产生不同,(行尾,关键字顺序,空格)
  • 这里实现的挖矿方法会因为整数溢出死循环