<jupyter_start><jupyter_text>Blockchain introduction seminarBased on: * https://github.com/santisiri/proof-of-work/blob/master/pow.py* http://ecomunsing.com/build-your-own-blockchain* https://github.com/JaeDukSeo/Simple-Merkle-Tree-in-Python* https://www.laurentluce.com/posts/python-and-cryptography-with-pycrypto/  1. Proof-of-Work Ниже приведена простая реализация функции Proof-of-Work.Она принимает заголовок блока и сложность вычисления (количество битов, которые должны быть нулевыми в начале хэша) как параметры и возвращает итоговый хэш смайненного блока и nonce для этого блока.<jupyter_code>import hashlib

def proof_of_work(header, difficulty_bits):
    max_nonce = 2 ** 32
    target = 2 ** (256-difficulty_bits)
    for nonce in range(max_nonce):
        hash_result = hashlib.sha256(str(header).encode('utf-8')+str(nonce).encode('utf-8')).hexdigest()

        if int(hash_result, 16) < target:
            return (hash_result, nonce)<jupyter_output><empty_output><jupyter_text>Вычисляем корневой блок<jupyter_code>import time

nonce = 0
block_hash = ""
header = 'This is the root block' + block_hash

difficulty_bits = 14

start_time = time.time()
(new_block, nonce) = proof_of_work(header, difficulty_bits)
end_time = time.time()
elapsed_time = end_time - start_time

print("Success with nonce ", nonce)
print("Hash is ", new_block)
print("Elapsed time: %.4f seconds" % elapsed_time)<jupyter_output><empty_output><jupyter_text>Задание 1.1Рассчитайте N=100 блоков для разного количества нулевых битов в начале хэша. Далее мы выведем логарифм затраченного времени и логарифм количества перебранных значений nonce как функцию от количества нулывых битов вначале. Какие мы ожидаем увидеть зависимости?*Note.* Каждый следующий блок должен включать хэш предыдущего.<jupyter_code># code here

import numpy as np

number_of_blocks = 100

indexes_to_check = 16 #Максимальное количество нулей в начале блока
mean_times = np.zeros(indexes_to_check)

mean_nonce = np.zeros(indexes_to_check)

for current_difficulty in range(1, indexes_to_check + 1):
    print(current_difficulty)
    estimated_times = np.zeros(number_of_blocks)
    estimated_nonces = np.zeros(number_of_blocks)
    for block_number in range(number_of_blocks):
        start_time = time.time()
        # ==== TODO: compute the block hash with given difficulty
        (new_block, nonce) =
        # =====================================
        end_time = time.time()
        estimated_times[block_number] = end_time - start_time
        estimated_nonces[block_number] = nonce
        
    mean_times[current_difficulty-1] = np.mean(estimated_times)
    mean_nonce[current_difficulty-1] = np.mean(estimated_nonces)

%matplotlib inline
import matplotlib.pyplot as plt

plt.figure(figsize=(10,10))

plt.subplot(221)

plt.title('Time')
plt.plot(range(indexes_to_check), mean_times, '-*b')

plt.subplot(222)
plt.title('log-Time')
plt.semilogy(range(indexes_to_check), mean_times, '-*b')

plt.subplot(223)
plt.title('Nonce')
plt.plot(range(indexes_to_check), mean_nonce, '-*b');
plt.subplot(224)
plt.title('log-Nonce')
plt.semilogy(range(indexes_to_check), mean_nonce, '-*b');<jupyter_output><empty_output><jupyter_text>Task 1.2Измените функцию proof_of_work так, чтобы она принимала ограничение сверху на значение хэша(переменная target в нашей реализации) вместо количества нулевых битов вначале.Какие разумные ограничения на это число?Выберете такое значение, чтобы вычислять в среднем 20 160 000 блоков за каждые 14 дней. Докажите это эксперементально. (Вычислите время расчёта как минимум 2016 блоков и покажите, что это близко к целевому значению).<jupyter_code># code here<jupyter_output><empty_output><jupyter_text>Task 1.3 (Для энтузиастов)Повторите эксперименты для других хэш-функций (к примеру, md5(), sha1(), sha224(), sha384(), и sha512()).Какие вы видете изменения?<jupyter_code>from hashlib import md5, sha1, sha224, sha384, sha512
# code here<jupyter_output><empty_output><jupyter_text>2. Простая криптовалюта Начнём с ведения счетов двух воображаемых людей: Алисы и Боба, которые будут торговать виртуальными деньгами друг с другом.Нам необходимо создать пул входящих транзакций, провалидировать их, и собрать их в блок.Будем использовать хэширование чтобы создать 'отпечаток' каждой из транзакций.<jupyter_code>import hashlib, json, sys

def hashMe(msg=""):
    # For convenience, this is a helper function that wraps our hashing algorithm
    if type(msg)!=str:
        msg = json.dumps(msg,sort_keys=True)  # If we don't sort keys, we can't guarantee repeatability!
       
    return hashlib.sha256(str(msg).encode('utf-8')).hexdigest()<jupyter_output><empty_output><jupyter_text>Ниже опишем функцию для генерации случайных обменов между Бобом и Алисой. Будем обозначать уход денег отрицательными числами, а приход - положительными. Определим, что транзакции будут всегда между двумя пользователями нашей системы, и убедимся, что сумма изменений счетов в транзакции равна нулю - то есть, что мы не создаём и не уничтожаем деньги.<jupyter_code>import random

def generateTransaction(maxValue=3):
    # This will create valid transactions in the range of (1,maxValue)
    sign      = random.choice([-1,1])   # This will randomly choose -1 or 1
    amount    = random.randint(1,maxValue)
    aliceDiff = sign * amount
    bobDiff = -1 * aliceDiff
    # By construction, this will always return transactions that respect the conservation of tokens.
    # However, note that we have not done anything to check whether these overdraft an account
    return {u'Alice':aliceDiff,u'Bob':bobDiff}<jupyter_output><empty_output><jupyter_text>Теперь сгенерируем список транзакций<jupyter_code>random.seed(0)
txnBuffer = [generateTransaction() for i in range(30)]<jupyter_output><empty_output><jupyter_text>Введём очень простые правила для нашей системы токенов:* Сумма всех изменений счетов равна нулю (Мы не создаём и не уничтожаем токены)* Счёт пользователя не может быть отрицательным ни в какой момент времениЕсли хотябы одно из этих условий не будет выполнено, отменим транзакцию*Note:* В реальных системах проверяется больше условий, в т.ч. электронная подпись участников транзакции<jupyter_code>def updateState(txn, state):
    # Inputs: txn, state: dictionaries keyed with account names, holding numeric values for transfer amount (txn) or account balance (state)
    # Returns: Updated state, with additional users added to state if necessary
    # NOTE: This does not not validate the transaction- just updates the state!
    
    # If the transaction is valid, then update the state
    state = state.copy() # As dictionaries are mutable, let's avoid any confusion by creating a working copy of the data.
    for key in txn:
        if key in state.keys():
            state[key] += txn[key]
        else:
            state[key] = txn[key]
    return state
def isValidTxn(txn,old_state):
    # Assume that the transaction is a dictionary keyed by account names

    # Check that the sum of the deposits and withdrawals is 0
    if sum(txn.values()) is not 0:
        return False
    
    # Check that the transaction does not cause an overdraft
    for key in txn.keys():
        if key in old_state.keys(): 
            acctBalance = old_state[key]
        else:
            acctBalance = 0
        if (acctBalance + txn[key]) < 0:
            return False
    
    return True<jupyter_output><empty_output><jupyter_text>Вот пример транзакций: некоторые из них нарушают правила, но теперь мы можем проверить их валидность!<jupyter_code>state = {u'Alice':5,u'Bob':5}
print(isValidTxn({u'Alice': -3, u'Bob': 3},state))  # Basic transaction - this works great!
print(isValidTxn({u'Alice': -4, u'Bob': 3},state))  # But we can't create or destroy tokens!
print(isValidTxn({u'Alice': -6, u'Bob': 6},state))  # We also can't overdraft our account.
print(isValidTxn({u'Alice': -4, u'Bob': 2,'Lisa':2},state)) # Creating new users is valid
print(isValidTxn({u'Alice': -4, u'Bob': 3,'Lisa':2},state)) # But the same rules still apply!<jupyter_output><empty_output><jupyter_text>Строим блокчейн: От транзакций до блоков<jupyter_code>state = {u'Alice':50, u'Bob':50}  # Define the initial state
genesisBlockTxns = [state]
genesisBlockContents = {
    u'blockNumber':0,
    u'parentHash':None,
    u'txnCount':len(genesisBlockTxns),
    u'txns':genesisBlockTxns
}
genesisHash = hashMe( genesisBlockContents )
genesisBlock = {u'hash':genesisHash,u'contents':genesisBlockContents}
genesisBlockStr = json.dumps(genesisBlock, sort_keys=True)

genesisBlockStr
chain = [genesisBlock]
def makeBlock(txns,chain):
    parentBlock = chain[-1]
    parentHash  = parentBlock[u'hash']
    blockNumber = parentBlock[u'contents'][u'blockNumber'] + 1
    txnCount    = len(txns)
    blockContents = {
        u'blockNumber':blockNumber,
        u'parentHash':parentHash,
        u'txnCount':len(txns),
        'txns':txns
    }
    blockHash = hashMe( blockContents )
    block = {u'hash':blockHash,u'contents':blockContents}
    
    return block<jupyter_output><empty_output><jupyter_text>Используем описанную выше функцию, чтобы превратить наш буфер транзакций во множество блоков:<jupyter_code>blockSizeLimit = 5  # Arbitrary number of transactions per block- 
               #  this is chosen by the block miner, and can vary between blocks!

while len(txnBuffer) > 0:
    bufferStartSize = len(txnBuffer)
    
    ## Gather a set of valid transactions for inclusion
    txnList = []
    while (len(txnBuffer) > 0) & (len(txnList) < blockSizeLimit):
        newTxn = txnBuffer.pop()
        validTxn = isValidTxn(newTxn,state) # This will return False if txn is invalid
        
        if validTxn:           # If we got a valid state, not 'False'
            txnList.append(newTxn)
            state = updateState(newTxn,state)
        else:
            print("ignored transaction")
            sys.stdout.flush()
            continue  # This was an invalid transaction; ignore it and move on
        
    ## Make a block
    myBlock = makeBlock(txnList,chain)
    chain.append(myBlock)  
chain[0]
chain[1]
print(len(chain))<jupyter_output><empty_output><jupyter_text>Как видно, корневой блок включает невалидную транзакцию, которая задаёт начальное состояние счетов (создавая токены из ничего). Хэш родительского блока указан в блоке-потомке, который содержит список новых транзакций, изменяющий состояние системы. Теперь посмотрим на обновлённое состояние системы:<jupyter_code>state<jupyter_output><empty_output><jupyter_text>Checking Chain Validity<jupyter_code>def checkBlockHash(block):
    # Raise an exception if the hash does not match the block contents
    expectedHash = hashMe( block['contents'] )
    if block['hash']!=expectedHash:
        raise Exception('Hash does not match contents of block %s'%
                        block['contents']['blockNumber'])
    return
def checkBlockValidity(block,parent,state):    
    # We want to check the following conditions:
    # - Each of the transactions are valid updates to the system state
    # - Block hash is valid for the block contents
    # - Block number increments the parent block number by 1
    # - Accurately references the parent block's hash
    parentNumber = parent['contents']['blockNumber']
    parentHash   = parent['hash']
    blockNumber  = block['contents']['blockNumber']
    
    # Check transaction validity; throw an error if an invalid transaction was found.
    for txn in block['contents']['txns']:
        if isValidTxn(txn,state):
            state = updateState(txn,state)
        else:
            raise Exception('Invalid transaction in block %s: %s'%(blockNumber,txn))

    checkBlockHash(block) # Check hash integrity; raises error if inaccurate

    if blockNumber!=(parentNumber+1):
        raise Exception('Hash does not match contents of block %s'%blockNumber)

    if block['contents']['parentHash'] != parentHash:
        raise Exception('Parent hash not accurate at block %s'%blockNumber)
    
    return state
def checkChain(chain):
    # Work through the chain from the genesis block (which gets special treatment), 
    #  checking that all transactions are internally valid,
    #    that the transactions do not cause an overdraft,
    #    and that the blocks are linked by their hashes.
    # This returns the state as a dictionary of accounts and balances,
    #   or returns False if an error was detected

    
    ## Data input processing: Make sure that our chain is a list of dicts
    if type(chain)==str:
        try:
            chain = json.loads(chain)
            assert( type(chain)==list)
        except:  # This is a catch-all, admittedly crude
            return False
    elif type(chain)!=list:
        return False
    
    state = {}
    ## Prime the pump by checking the genesis block
    # We want to check the following conditions:
    # - Each of the transactions are valid updates to the system state
    # - Block hash is valid for the block contents

    for txn in chain[0]['contents']['txns']:
        state = updateState(txn,state)
    checkBlockHash(chain[0])
    parent = chain[0]
    
    ## Checking subsequent blocks: These additionally need to check
    #    - the reference to the parent block's hash
    #    - the validity of the block number
    for block in chain[1:]:
        state = checkBlockValidity(block,parent,state)
        parent = block
        
    return state<jupyter_output><empty_output><jupyter_text>Теперь мы можем проверить валидность нашего блокчейна (если он валиден, то функция вернёт итоговое состояние счетов, иначе - будет выброшено исключение):<jupyter_code>checkChain(chain)<jupyter_output><empty_output><jupyter_text>И даже если мы сохраним куда-либо наш блокчейн, мы можем проверить целостность цепочки и узнать текущее состояние:<jupyter_code>chainAsText = json.dumps(chain,sort_keys=True)
checkChain(chainAsText)<jupyter_output><empty_output><jupyter_text>Если мы удалим блок из цепи, она станет не валидной:<jupyter_code>import copy

cropped_chain = copy.copy(chain)
cropped_chain.remove(cropped_chain[2])

checkChain(cropped_chain)<jupyter_output><empty_output><jupyter_text>Если мы изменим блок в цепи, то проверка тоже это выявит:<jupyter_code>changedChain = copy.copy(chain)
changedChain[2][u'contents'][u'txns'].append({u'Alice': 1, u'Bob': -1})
checkChain(changedChain)<jupyter_output><empty_output><jupyter_text>Задание 2.1Добавьте Proof-of-Work в блокчейн и nonce в заголовок блока.  Задание 2.2Добавьте в транзакцию поля nonce (количество транзакций аккаунта за всю историю блокчейна) для каждого аккаунта, являющегося её участником, и измените функцию проверки транзакций, чтобы избежать повторения транзакций и чётко понимать, в каком порядке должны происходить транзакции.Правило: Транзакция валидна, если поле nonce для каждого аккаунта, участвующего в транзакции, равно увеличенному на 1 полю nonce этого аккаунта в состоянии системы до проведения транзакции.  Криптография с использованием публичных ключей<jupyter_code>from Crypto import Random
from Crypto.Hash import MD5
from Crypto.PublicKey import RSA
random_generator = Random.new().read
key = RSA.generate(1024, random_generator)
print(key)
print(key.can_encrypt())
print(key.can_sign())
print(key.has_private())<jupyter_output><empty_output><jupyter_text>Шифрование<jupyter_code>public_key = key.publickey()
enc_data = public_key.encrypt(str('abcdefgh').encode('utf-8'), 32)
print(enc_data)<jupyter_output><empty_output><jupyter_text>Дешифрование<jupyter_code>key.decrypt(enc_data)<jupyter_output><empty_output><jupyter_text>Подпись<jupyter_code>from Crypto.Hash import MD5
from Crypto.PublicKey import RSA
RSAkey = RSA.generate(1024, random_generator)   # This will take a while...
text = 'abcdefgh'
plaintext = str(text).encode('utf-8')
hash = MD5.new(plaintext).digest()
signature = RSAkey.sign(hash, "")
print(signature)<jupyter_output><empty_output><jupyter_text>Верификация подписи<jupyter_code>public_key = RSAkey.publickey()

print(public_key.verify(hash, signature))     # This sig will check out
print(public_key.verify(hash[:-1], signature)) # This sig will fail<jupyter_output><empty_output><jupyter_text>Нельзя подписать публичным ключем<jupyter_code>public_key.sign(hash, "")<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.09481007203102874, 'fraction_numerical': 0.01021978698516284, 'mean_word_length': 4.530813755532857, 'pattern_counts': {'":': 0, '<': 100, '<?xml version=': 0, '>': 99, 'https://': 3, 'lorem ipsum': 0, 'www.': 1, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '24', 'dir': 'jupyter-structured-clean-dedup', 'id': '99621', 'license': 'MIT', 'n_tokens_mistral': 5722, 'n_tokens_neox': 5972, 'n_words': 1474, 'path': 'blockchain_seminar.ipynb', 'repo_name': 'TIXFeniks/blockchain_tutorial', 'size': '16243'}