區塊鏈中的雙花攻擊

author: Homaebic
team: Sycloverhtml

mini blockchain

DDCTF2018出了一道區塊鏈題目,對於區塊鏈的web安全網上尚未不少的教程,這道題目給了一個很好的入門機會,我也將學習到的知識分享出來。python

DD本身創造了一個本身的區塊鏈貨幣用於商店,但它並非真正的區塊鏈,由於鏈只保存在服務器的session中,不過這不影響咱們理解和作題。web

來看一張交易模型json

關於UTXO的詳解看http://8btc.com/article-4381-1.htmlflask

區塊鏈就是區塊和鏈。在這道題目中,看上面的圖片,一個方框就是一個區塊(block)。一個區塊包含了一次交易(tx),一個交易包含了交易的輸入(input)和交易的輸出(output),其中輸出又稱爲UTXO。每一次交易的輸入和輸出必須相同,若是輸入10塊錢花了2塊給便利店,那麼2塊輸出給便利店,8塊輸出給本身。也能夠把UTXO理解爲餘額,由於每次能花多少,都是要取決於以前的UTXO向本身輸出了多少。安全

這道題目的背景是銀行在某天發行了100w個DDB(對應上圖第一個方塊),這時黑客出現了,他在第一個block後面append了一個區塊,把銀行的99w9999轉給了本身,把1轉給銀行。這樣銀行就只剩下一塊錢了,黑客還得意的喊:second_block = create_block(genesis_block['hash'], 'HAHA, I AM THE BANK NOW!', [transferred]) 題目的要求是得到兩個鑽石,得到鑽石的方法是商店有100w元。一個鑽石的價格是100w,就是說咱們得有200w才能夠獲得兩個鑽石,可銀行只發行了100w,該怎麼辦?服務器

雙花攻擊

雙花攻擊是同一筆UTXO在不一樣交易中的花費,雙花不會產生新的貨幣,只能把本身花出去的錢從新拿回來。session

這個攻擊方法給了我靈感,實際上這道題就是使用雙花攻擊中的51% attack。51% attack指的是攻擊者若是能夠擁有超過全網50%的算力,就能夠創造一條高度大於原始鏈的鏈,攻擊者能夠發送一個新的塊到這條鏈上。(若是有對比特幣進行51% attack成功的案例,最大的危害在於人們對比特幣的信心受損致使的比特幣大跌而不是51% attack自己帶來的危害)app

如何進行51% attack攻擊?在這道題中,就是創造一條超過原始鏈的長度。爲了在後續講解中方便,先寫出題目給出的幾個塊,主鏈上塊前有*ide

*塊1(創世區塊):銀行發行100w幣
*塊2(1):黑客轉走99w9999,銀行留1
*塊3(1):空塊(什麼都沒操做)

具體操做就是從塊1以後append一個塊,把銀行的100w轉到shop中

*塊1(創世區塊):銀行發行100w幣  
*塊2(1):黑客轉走99w9999,銀行留1 塊2(2)--shop轉走100w
*塊3(1):空塊(什麼都沒操做)

(還能夠隨意轉錢?就是有這種操做23333)

下一步,在本身append的塊後append一個空塊

*塊1(創世區塊):銀行發行100w幣  
*塊2(1):黑客轉走99w9999,銀行留1 塊2(2)--shop轉走100w
*塊3(1):空塊(什麼都不發生) 塊3(2)--空塊(什麼都不發生)

再來一次一樣的操做

*塊1(創世區塊):銀行發行100w幣  
塊2(1):黑客轉走99w9999,銀行留1 *塊2(2)--shop轉走100w
塊3(1):空塊(什麼都沒操做) *塊3(2)--空塊(什麼都不發生)
  *塊4(1)--空塊(什麼都不發生)

此時最長的鏈爲塊1-塊2(2)-塊3(1)-塊4(1)。這樣,咱們就構造了一個比題目給咱們還要長的鏈,區塊鏈這套邏輯會把最長的鏈當作主鏈,主鏈從塊2(2)處分叉,塊2(1)失效了,shop帳戶中多了100w,咱們得到一個鑽石。接下來系統在購買鑽石的塊3(2)塊後添加一個塊,轉走商店中的100w到商店錢包。

*塊1(創世區塊):銀行發行100w幣  
塊2(1):黑客轉走99w9999,銀行留1 *塊2(2)--shop轉走100w
  *塊3(2)--空塊(什麼都不發生)
  *塊4(1)--空塊(什麼都不發生)
  *塊5(1)--把100w轉到shop_wallet_address

那麼另外一個鑽石該怎麼得到呢?繼續利用50% attack攻擊,從塊4(1)分叉,添加空塊

*塊1(創世區塊):銀行發行100w幣    
塊2(1):黑客轉走99w9999,銀行留1 *塊2(2)--shop轉走100w  
  *塊3(2)--空塊(什麼都不發生)  
  *塊4(1)--空塊(什麼都不發生)  
  *塊5(1)--把100w轉到shop_wallet_address 塊5(2)--空塊(什麼都不發生)

再append一個空塊

*塊1(創世區塊):銀行發行100w幣    
塊2(1):黑客轉走99w9999,銀行留1 *塊2(2)--shop轉走100w  
  *塊3(2)--空塊(什麼都不發生)  
  *塊4(1)--空塊(什麼都不發生)  
  塊5(1)--把100w轉到shop_wallet_address *塊5(2)--空塊(什麼都不發生)
    *塊6(1)--空塊(什麼都不發生)

主鏈變爲塊1-塊2(2)-塊3(1)-塊4(1)-塊5(2)-塊6(1),塊5(1)失效,shop擁有100w,鑽石+1,獲得flag。

爲什麼能夠直接append?

在這道題目中,給了一個append塊的方法,能夠將post請求當作塊append到某個塊後面,這個是一個正常的功能。在生成sign的時候沒有將使用簽名的交易hash計算進去,致使在驗證的時候沒有驗證sign和交易hash的對應,因此只要有一個sign,就能夠不斷的利用這個sign append區塊。

若是能夠任意append,爲何不直接給shop轉200w?

首先,全部的append都必須在創世block後。其次,系統會驗證append塊中的sign。還會驗證prev值,是否爲某個已存在的block的hash。(block的hash是將block的每一個參數打包後進行hash)沒法知道某個block的hash就沒法在block後append一個block。最後,轉出的錢,必須是以前的UTXO,題目中UTXO總量爲100w,沒法創造200w的UTXO。

51% attack和算力有什麼關係?

append的塊除了以上要求,還有一個複雜性要求。也就是工做量證實(https://baike.baidu.com/item/%E5%B7%A5%E4%BD%9C%E9%87%8F%E8%AF%81%E6%98%8E/22448498?fr=aladdin)。任意添加一個塊的要求是

DIFFICULTY = int('00000' + 'f' * 59, 16) ...... if block_hash > difficulty: raise Exception('Please provide a valid Proof-of-Work') 

block的hash要小於系統定義的difficulty。爲了使得能夠控制hash的大小,一個block中還有一個能夠隨意定義的nonce,咱們能夠控制nonce來控制block_hash達到目的。爲了知足複雜度要求,必須窮舉nonce。init()中的幾個塊可使用有語義的nonce是由於在那個階段DIFFICULTY要求極低。

若是世界上有100個用戶在使用這個系統,100個用戶都在計算nonce以append本身的block。若是其中一我的計算nonce的速度要超過其他99個的速度,那麼他添加新塊的速度就會超過其餘99我的添加新塊的速度,他就能夠在隨意的一個塊開始添加本身的塊,使得本身構造的鏈長度大於其他99個構造的鏈,成爲主鏈,達成51%攻擊。這道題沒人和咱們比算力,生成一個比最長鏈長度大一的鏈便可。

一個block結構是怎樣的?塊2(1)

{
'nonce': 'HAHA, I AM THE BANK NOW!', 
'prev': 'dd04faf20c550cf63ae07504884e1fb673cfefaaac2979dde1ae3cbf95961569', 
'hash': '5217b7fa9c1e2296e66202997df0a51b20e58fe921011069535a62cd53518e55', 
'transactions': [{
    'input': ['9d65e5db-8671-4323-b279-af56963f2565'], 
    'output': [{
        'amount': 999999, 
        'hash': 
        'da32c8155ebbec8df888653d4d243698e29c4ea43cc0fa1bff14649e8511416b', 
        'id': '9dcb9e47-5816-4451-b99e-eb6d729f64b7', 
        'addr': 'b2a6484625db7305ea7bb1c8a484832ec32686c0f3a3dac5cfe63779ede94494d841f8117fe1dd55c57e23aa61953391'
        }, 
        {
        'amount': 1, 
        'hash': '19fa5198bc172d6525976b7f0fb5f0647b96ab6b55bd4eb9033ab158faebb0ad', 
        'id': '592e27c6-b111-40a7-8b2d-ccefa333e616', 
        'addr': '99a13a3a21051c8f93c5a87f7f92151b4acfaf01f2e596696e8922e3801278470592cdbc8920f289a1829f726c43a1e9'
        }],

        'hash': '5815cc2ccf6327396ce5490c39e7c6381f15250fa0ab043eae8096d1a1c44704', 
        'signature': ['9455298609f042b631f99cb33f3f683f6b3361962df5f1c6f698e03b23d72c7ea42c939999913424e4c424f6b7024514']
        }
]}

 

參數解釋,括號內爲生成函數

nonce:自定義字符串

prev:上一個塊的hash

hash:本個塊的hash(hashhash,hash_reducer,hash_block)

transactions:交易(tx)

​ input:以前utxo的id

​ output:UTXO

​ amount:數量

​ hash:UTXO的hash(hash,hash_reducer,hash_utxo)

​ id:這個UTXO的id

​ addr:目標地址

​ hash:交易的hash(hash,hash_reducer,hash_tx)

​ signature:交易簽名(sign_input_utxo)

題目代碼:

# -*- encoding: utf-8 -*-
# written in python 2.7

import hashlib, json, rsa, uuid, os
from flask import Flask, session, redirect, url_for, escape, request

app = Flask(__name__)
app.secret_key = '*********************'
url_prefix = '/b9af31f66147e'

def FLAG():
    return 'Here is your flag: DDCTF{******************}'

def hash(x):
    return hashlib.sha256(hashlib.md5(x).digest()).hexdigest()

def hash_reducer(x, y):
    return hash(hash(x)+hash(y))

def has_attrs(d, attrs):
    if type(d) != type({}): raise Exception("Input should be a dict/JSON")
    for attr in attrs:
        if attr not in d:
            raise Exception("{} should be presented in the input".format(attr))

EMPTY_HASH = '0'*64

def addr_to_pubkey(address):
    return rsa.PublicKey(int(address, 16), 65537)

def pubkey_to_address(pubkey):
    assert pubkey.e == 65537
    hexed = hex(pubkey.n)
    if hexed.endswith('L'): hexed = hexed[:-1]
    if hexed.startswith('0x'): hexed = hexed[2:]
    return hexed

def gen_addr_key_pair():
    pubkey, privkey = rsa.newkeys(384)
    return pubkey_to_address(pubkey), privkey

bank_address, bank_privkey = gen_addr_key_pair()
hacker_address, hacker_privkey = gen_addr_key_pair()
shop_address, shop_privkey = gen_addr_key_pair()
shop_wallet_address, shop_wallet_privkey = gen_addr_key_pair()

def sign_input_utxo(input_utxo_id, privkey):
    return rsa.sign(input_utxo_id, privkey, 'SHA-1').encode('hex')

def hash_utxo(utxo):
    return reduce(hash_reducer, [utxo['id'], utxo['addr'], str(utxo['amount'])])

def create_output_utxo(addr_to, amount):
    utxo = {'id': str(uuid.uuid4()), 'addr': addr_to, 'amount': amount}
    utxo['hash'] = hash_utxo(utxo)
    return utxo

def hash_tx(tx):
    return reduce(hash_reducer, [
        reduce(hash_reducer, tx['input'], EMPTY_HASH),
        reduce(hash_reducer, [utxo['hash'] for utxo in tx['output']], EMPTY_HASH)
    ])

def create_tx(input_utxo_ids, output_utxo, privkey_from=None):
    tx = {'input': input_utxo_ids, 'signature': [sign_input_utxo(id, privkey_from) for id in input_utxo_ids], 'output': output_utxo}
    tx['hash'] = hash_tx(tx)
    return tx

def hash_block(block):
    return reduce(hash_reducer, [block['prev'], block['nonce'], reduce(hash_reducer, [tx['hash'] for tx in block['transactions']], EMPTY_HASH)])

def create_block(prev_block_hash, nonce_str, transactions):
    if type(prev_block_hash) != type(''): raise Exception('prev_block_hash should be hex-encoded hash value')
    nonce = str(nonce_str)
    if len(nonce) > 128: raise Exception('the nonce is too long')
    block = {'prev': prev_block_hash, 'nonce': nonce, 'transactions': transactions}
    block['hash'] = hash_block(block)
    return block

def find_blockchain_tail():
    return max(session['blocks'].values(), key=lambda block: block['height'])

def calculate_utxo(blockchain_tail):
    curr_block = blockchain_tail
    blockchain = [curr_block]
    while curr_block['hash'] != session['genesis_block_hash']:
        curr_block = session['blocks'][curr_block['prev']]
        blockchain.append(curr_block)
    blockchain = blockchain[::-1]
    utxos = {}
    for block in blockchain:
        for tx in block['transactions']:
            for input_utxo_id in tx['input']:
                del utxos[input_utxo_id]
            for utxo in tx['output']:
                utxos[utxo['id']] = utxo
    return utxos

def calculate_balance(utxos):
    balance = {bank_address: 0, hacker_address: 0, shop_address: 0}
    for utxo in utxos.values():
        if utxo['addr'] not in balance:
            balance[utxo['addr']] = 0
        balance[utxo['addr']] += utxo['amount']
    return balance

def verify_utxo_signature(address, utxo_id, signature):
    try:
        return rsa.verify(utxo_id, signature.decode('hex'), addr_to_pubkey(address))
    except:
        return False

def append_block(block, difficulty=int('f'*64, 16)):
    has_attrs(block, ['prev', 'nonce', 'transactions'])

    if type(block['prev']) == type(u''): block['prev'] = str(block['prev'])
    if type(block['nonce']) == type(u''): block['nonce'] = str(block['nonce'])
    if block['prev'] not in session['blocks']: raise Exception("unknown parent block")
    tail = session['blocks'][block['prev']]
    utxos = calculate_utxo(tail)

    if type(block['transactions']) != type([]): raise Exception('Please put a transaction array in the block')
    new_utxo_ids = set()
    for tx in block['transactions']:
        has_attrs(tx, ['input', 'output', 'signature'])

        for utxo in tx['output']:
            has_attrs(utxo, ['amount', 'addr', 'id'])
            if type(utxo['id']) == type(u''): utxo['id'] = str(utxo['id'])
            if type(utxo['addr']) == type(u''): utxo['addr'] = str(utxo['addr'])
            if type(utxo['id']) != type(''): raise Exception("unknown type of id of output utxo")
            if utxo['id'] in new_utxo_ids: raise Exception("output utxo of same id({}) already exists.".format(utxo['id']))
            new_utxo_ids.add(utxo['id'])
            if type(utxo['amount']) != type(1): raise Exception("unknown type of amount of output utxo")
            if utxo['amount'] <= 0: raise Exception("invalid amount of output utxo")
            if type(utxo['addr']) != type(''): raise Exception("unknown type of address of output utxo")
            try:
                addr_to_pubkey(utxo['addr'])
            except:
                raise Exception("invalid type of address({})".format(utxo['addr']))
            utxo['hash'] = hash_utxo(utxo)
        tot_output = sum([utxo['amount'] for utxo in tx['output']])

        if type(tx['input']) != type([]): raise Exception("type of input utxo ids in tx should be array")
        if type(tx['signature']) != type([]): raise Exception("type of input utxo signatures in tx should be array")
        if len(tx['input']) != len(tx['signature']): raise Exception("lengths of arrays of ids and signatures of input utxos should be the same")
        tot_input = 0
        tx['input'] = [str(i) if type(i) == type(u'') else i for i in tx['input']]
        tx['signature'] = [str(i) if type(i) == type(u'') else i for i in tx['signature']]
        for utxo_id, signature in zip(tx['input'], tx['signature']):
            if type(utxo_id) != type(''): raise Exception("unknown type of id of input utxo")
            if utxo_id not in utxos: raise Exception("invalid id of input utxo. Input utxo({}) does not exist or it has been consumed.".format(utxo_id))
            utxo = utxos[utxo_id]
            if type(signature) != type(''): raise Exception("unknown type of signature of input utxo")
            if not verify_utxo_signature(utxo['addr'], utxo_id, signature):
                raise Exception("Signature of input utxo is not valid. You are not the owner of this input utxo({})!".format(utxo_id))
            tot_input += utxo['amount']
            del utxos[utxo_id]
        if tot_output > tot_input:
            raise Exception("You don't have enough amount of DDCoins in the input utxo! {}/{}".format(tot_input, tot_output))
        tx['hash'] = hash_tx(tx)

    block = create_block(block['prev'], block['nonce'], block['transactions'])
    block_hash = int(block['hash'], 16)
    if block_hash > difficulty: raise Exception('Please provide a valid Proof-of-Work')
    block['height'] = tail['height']+1
    if len(session['blocks']) > 50: raise Exception('The blockchain is too long. Use ./reset to reset the blockchain')
    if block['hash'] in session['blocks']: raise Exception('A same block is already in the blockchain')
    session['blocks'][block['hash']] = block
    session.modified = True

def init():
    if 'blocks' not in session:
        session['blocks'] = {}
        session['your_diamonds'] = 0

        # First, the bank issued some DDCoins ...
        total_currency_issued = create_output_utxo(bank_address, 1000000)
        genesis_transaction = create_tx([], [total_currency_issued]) # create DDCoins from nothing
        genesis_block = create_block(EMPTY_HASH, 'The Times 03/Jan/2009 Chancellor on brink of second bailout for bank', [genesis_transaction])
        session['genesis_block_hash'] = genesis_block['hash']
        genesis_block['height'] = 0
        session['blocks'][genesis_block['hash']] = genesis_block

        # Then, the bank was hacked by the hacker ...
        handout = create_output_utxo(hacker_address, 999999)
        reserved = create_output_utxo(bank_address, 1)
        transferred = create_tx([total_currency_issued['id']], [handout, reserved], bank_privkey)
        second_block = create_block(genesis_block['hash'], 'HAHA, I AM THE BANK NOW!', [transferred])
        append_block(second_block)

        # Can you buy 2 diamonds using all DDCoins?
        third_block = create_block(second_block['hash'], 'a empty block', [])
        append_block(third_block)

def get_balance_of_all():
    init()
    tail = find_blockchain_tail()
    utxos = calculate_utxo(tail)
    return calculate_balance(utxos), utxos, tail

@app.route(url_prefix+'/')
def homepage():
    announcement = 'Announcement: The server has been restarted at 21:45 04/17. All blockchain have been reset. '
    balance, utxos, _ = get_balance_of_all()
    genesis_block_info = 'hash of genesis block: ' + session['genesis_block_hash']
    addr_info = 'the bank\'s addr: ' + bank_address + ', the hacker\'s addr: ' + hacker_address + ', the shop\'s addr: ' + shop_address
    balance_info = 'Balance of all addresses: ' + json.dumps(balance)
    utxo_info = 'All utxos: ' + json.dumps(utxos)
    blockchain_info = 'Blockchain Explorer: ' + json.dumps(session['blocks'])
    view_source_code_link = "<a href='source_code'>View source code</a>"
    return announcement+('<br /><br />\r\n\r\n'.join([view_source_code_link, genesis_block_info, addr_info, balance_info, utxo_info, blockchain_info]))


@app.route(url_prefix+'/flag')
def getFlag():
    init()
    if session['your_diamonds'] >= 2: return FLAG()
    return 'To get the flag, you should buy 2 diamonds from the shop. You have {} diamonds now. To buy a diamond, transfer 1000000 DDCoins to '.format(session['your_diamonds']) + shop_address

def find_enough_utxos(utxos, addr_from, amount):
    collected = []
    for utxo in utxos.values():
        if utxo['addr'] == addr_from:
            amount -= utxo['amount']
            collected.append(utxo['id'])
        if amount <= 0: return collected, -amount
    raise Exception('no enough DDCoins in ' + addr_from)

def transfer(utxos, addr_from, addr_to, amount, privkey):
    input_utxo_ids, the_change = find_enough_utxos(utxos, addr_from, amount)
    outputs = [create_output_utxo(addr_to, amount)]
    if the_change != 0:
        outputs.append(create_output_utxo(addr_from, the_change))
    return create_tx(input_utxo_ids, outputs, privkey)

@app.route(url_prefix+'/5ecr3t_free_D1diCoin_b@ckD00r/<string:address>')
def free_ddcoin(address):
    balance, utxos, tail = get_balance_of_all()
    if balance[bank_address] == 0: return 'The bank has no money now.'
    try:
        address = str(address)
        addr_to_pubkey(address) # to check if it is a valid address
        transferred = transfer(utxos, bank_address, address, balance[bank_address], bank_privkey)
        new_block = create_block(tail['hash'], 'b@cKd00R tr1993ReD', [transferred])
        append_block(new_block)
        return str(balance[bank_address]) + ' DDCoins are successfully sent to ' + address
    except Exception, e:
        return 'ERROR: ' + str(e)

DIFFICULTY = int('00000' + 'f' * 59, 16)
@app.route(url_prefix+'/create_transaction', methods=['POST'])
def create_tx_and_check_shop_balance():
    init()
    try:
        block = json.loads(request.data)
        append_block(block, DIFFICULTY)
        msg = 'transaction finished.'
    except Exception, e:
        return str(e)

    balance, utxos, tail = get_balance_of_all()
    if balance[shop_address] == 1000000:
        # when 1000000 DDCoins are received, the shop will give you a diamond
        session['your_diamonds'] += 1
        # and immediately the shop will store the money somewhere safe.
        transferred = transfer(utxos, shop_address, shop_wallet_address, balance[shop_address], shop_privkey)
        new_block = create_block(tail['hash'], 'save the DDCoins in a cold wallet', [transferred])
        append_block(new_block)
        msg += ' You receive a diamond.'
    return msg


# if you mess up the blockchain, use this to reset the blockchain.
@app.route(url_prefix+'/reset')
def reset_blockchain():
    if 'blocks' in session: del session['blocks']
    if 'genesis_block_hash' in session: del session['genesis_block_hash']
    return 'reset.'

@app.route(url_prefix+'/source_code')
def show_source_code():
    source = open('serve.py', 'r')
    html = ''
    for line in source:
        html += line.replace('&','&amp;').replace('\t', '&nbsp;'*4).replace(' ','&nbsp;').replace('<', '&lt;').replace('>','&gt;').replace('\n', '<br />')
    source.close()
    return html

if __name__ == '__main__':
    app.run(debug=False, host='0.0.0.0')

 

其他函數功能:

addr_to_pubkey:檢查地址有效性

pubkey_to_address:生成錢包地址

gen_addr_key_pair:生成錢包地址

create_output_utxo:建立一個utxo

create_tx:建立一個tx

create_block:建立一個block

find_blockchain_tail:查詢最後一個block

calculate_utxo:獲得全部utxo

calculate_balance:計算錢包的餘額

verify_utxo_signature:驗證utxo簽名

append_block:添加塊

init:初始化函數

get_balance_of_all:獲得全部block,全部地址和utxo

homepage:web主頁

getFlag:flag獲取頁面

EXP: 重命名源代碼爲btc.py

# -*- encoding: utf-8 -*-

import btc, rsa, uuid, json, copy
#創世塊的hash
genies_hash = "92875ca628cd0890020f6a74f3011b611db814f30300f729f20b5a88c49e3e44"
#黑客轉帳999999,所用的input和簽名
input,signature = ("9018b356-cb1d-44c9-ab4e-bf15a8b2f95c","161ae7eac89f71d50d1019d21288dce23cae6cbb587998df9010e3ff3c80ee8e4c06bd70555604be85ca0869136b3966")
#商店地址
shop_address = "b81ff6d961082076f3801190a731958aec88053e8191258b0ad9399eeecd8306924d2d2a047b5ec1ed8332bf7a53e735"
txout_id = str(uuid.uuid4())

#工做量證實
def pow(b, difficulty, msg=""):
    nonce = 0
    while nonce<(2**32):
        b['nonce'] = msg+str(nonce)
        b['hash'] = btc.hash_block(b)
        block_hash = int(b['hash'], 16)
        if block_hash < difficulty:
            return b
        nonce+=1   

def myprint(b):
    print(json.dumps(b))
    print(len(json.dumps(b)))

#構造一個空塊
def empty_block(msg, prevHash):
    b={}
    b["prev"] = prevHash
    b["transactions"] = []
    b = pow(b, btc.DIFFICULTY, msg)
    return b

#從創世塊開始分叉,給商店轉1000000
block1 = {}
block1["prev"] = genies_hash
tx = {"input":[input],"output":[{"amount":1000000, 'id':txout_id,'addr':shop_address}],'signature':[signature]}
tx["output"][0]["hash"] = btc.hash_utxo(tx["output"][0])
tx['hash'] = btc.hash_tx(tx)
block1["transactions"] = [tx]
block1 = pow(block1, btc.DIFFICULTY)
myprint(block1)

#構造空塊增長分叉鏈長度,使分叉鏈最長,由於max的結果不惟一,少則一次多則兩次
block2 = empty_block("myempty1", block1["hash"])
myprint(block2)
block3 = empty_block("myempty2", block2["hash"])
myprint(block3)

#餘額更新成功,系統自動添加塊,轉走商店錢,鑽石+1

#從本身的塊,即系統轉走錢以前的那個塊再次分叉,添加空塊
block4 = empty_block("myempty3", block3["hash"])
myprint(block4)
block5 = empty_block("myempty4", block4["hash"])
myprint(block5)
#新的分叉鏈最長,餘額更新成功,鑽石+1

 

生成出的四個塊按順序提交,再訪問/flag就能夠獲得flag

相關文章
相關標籤/搜索