파이썬으로 나만의 블록체인 만들기 #4

정보보안 2017. 11. 16. 16:55

#참고 : 이 글은 @ecomusing 님의 포스트를 제 맘대로 번역한 글입니다. 원문보기

#참고2 : 전체 Jupyter / iPython 소스코드 받기 


모두 합치기 : 최종 블록체인 아키텍쳐(Architecture)


실제 블록체인 네트워크에서 새로운 노드는 블록체인의 사본을 다운로드 받고 이것을 검증한 다음, peer-to-peer 네트워크에 자신의 존재를 알리고 거래내역과 관련된 요청을 받을 준비를 시작합니다. 블록 안으로 묶여진 거래내역들은 proposed block 을 다른 노드들에게 전달합니다.


앞에서 우리는 블록체인의 사본을 어떻게 검증하는지, 거래내역들이 어떻게 블록으로 묶여지는지 확인하였습니다. 만약 우리가 다른 곳에서 블록을 만았다면, 이것을 검증하고 우리의 블록체인에 추가하는 것은 쉽습니다. 


다음의 코드가 노드 A에서 실행된다고 해봅시다.


In [19]:
import copy
nodeBchain = copy.copy(chain)
nodeBtxns  = [makeTransaction() for i in range(5)]
newBlock   = makeBlock(nodeBtxns,nodeBchain)

이제 newBlock 이 우리 노드로 넘어온다고 가정해봅시다. 그리고 우리는 이 newBlock 을 확인하고 해당 블록이 유효하다면 우리의 상태를 업데이트 할 것입니다.


In [20]:
print("Blockchain on Node A is currently %s blocks long"%len(chain))

try:
    print("New Block Received; checking validity...")
    state = checkBlockValidity(newBlock,chain[-1],state) # Update the state- this will throw an error if the block is invalid!
    chain.append(newBlock)
except:
    print("Invalid block; ignoring and waiting for the next block...")

print("Blockchain on Node A is now %s blocks long"%len(chain))
Blockchain on Node A is currently 7 blocks long
New Block Received; checking validity...
Blockchain on Node A is now 8 blocks long

결론 및 확장


지금까지 블록을 생성하기 위한 거래내역 규칙들을 설정하는 것 부터 거래내역, 블록, 그리고 전체 체인에 대해 유효성 검사를 하는 것까지 기본적인 블록체인의 아키텍쳐를 만들어 보았습니다. 우리는 다운로드받은 블록체인의 사본으로 시스템 상태를 확인할 수 있고 다른 노드로부터 받은 새로운 블록을 검증할 수 있으며, 우리의 블록을 생성할 수 있습니다.


우리가 만든 시스템 상태는 배포되는 많은 블록체인의 중심이라 할 수 있는 ledger 또는 데이터베이스 입니다. 우리는 이것을 special transaction types 이나 full smart contracts 로 확장할 수 있습니다.


이 포스팅은 네트워크 아키텍쳐, proof-of-work 또는 proof-of-state validation step, consensus mechanism 등 블록체인이 가지고 있는 보안과 관련된 내용을 다루지 않았습니다. 공개키 암호기술, 프라이버시, verification steps 또한 다루지 않았습니다. 해당 내용을 나중에 올리도록 하겠습니다.


- 끝 -

파이썬으로 나만의 블록체인 만들기 #3

정보보안 2017. 11. 16. 05:21

#참고 : 이 글은 @ecomusing 님의 포스트를 제 맘대로 번역한 글입니다. 원문보기

#참고2 : 전체 Jupyter / iPython 소스코드 받기 


체인 유효성 검사


어떻게 새로운 블록들을 만들고 블록들이 체인 안으로 어떻게 서로 연결이 되는지 확인하였으니 이제 새로운 블록들이 유효한지, 더 나아가 전체 체인이 유효한지 확인하는 함수를 정의해보도록 합시다.


블록체인 네트워크에서 유효성 검사는 다음과 같은 두 가지 의미에서 중요합니다.


◆ 노드를 초기 설정할 떄, 전체 블록체인 히스토리를 다운로드 합니다. 체인의 다운로드 이후에는 시스템 상태 확인을 위해서 블록체인을 훓어보는 것이 필요합니다. 누군가가 유효하지 않은 거래내역을 체인 앞단에 삽입한는 것을 방지하지 위해서 이 초기 다운로드된 체인 전체에 대한 유효성 검사를 하는 것이 필요합니다.


◆ 노드가 네트워크와 동기화 되었을 때(최신의 블록체인 사본을 갖고 시스템이 준비된 상태), 네트워크에 브로드캐스트(broadcast)하여 새로운 블록들에 대한 유효성 검사를 하는 것이 필요합니다.


이 유효성 검사를 위해서 3가지 함수들이 필요합니다.


◆ checkBlockHash : 블록의 내용(contents)가 해쉬값과 동일한지 확인 시켜주는 간단한 helper function 입니다


◆ checkBlockValidity : 블록의 부모 및 현재 시스템 상태를 고려한 블록에 대한 유효성 검사를 합니다. 만약 블록이 유효하면 업데이트된 상태를 리턴하고, 유효하지 않으면 에러를 발생시킵니다.


◆ checkChain : 모든 체인에 대해서 유효성 검사를 하고 genesis block 을 포함한 시스템 상태에 대한 확인을 수행합니다. 만약 체인이 유요하다면 시스템 상태를 리턴해주고 그렇지 않으면 에러를 발생시킵니다.


In [14]:
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
In [15]:
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
In [16]:
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

이제 유효성 검사를 해봅시다.

In [17]:

checkChain(chain)
Out[17]:
{'Alice': 72, 'Bob': 28}

비록 우리는 텍스트 파일(백업을 하거나 초기에 불러오는 작업에 쓰일 수 있는)에서 체인을 불러왔지만, 우리는 체인의 무결성을 확인할 수 있었고 현재 상태(거래 결과)를 확인할 수 있습니다.

In [18]:
chainAsText = json.dumps(chain,sort_keys=True)
checkChain(chainAsText)
Out[18]:
{'Alice': 72, 'Bob': 28}


파이썬으로 나만의 블록체인 만들기 #2

정보보안 2017. 11. 15. 01:06


#참고 : 이 글은 @ecomusing 님의 포스트를 제 맘대로 번역한 글입니다. 원문보기

#참고2 : 전체 Jupyter / iPython 소스코드 받기 


블록체인 만들기 : 거래내역에서 블록으로


이제 우리의 블록체인을 만들기 시작할 준비가 되었습니다. 지금은 블록체인에 아무것도 없지만, 'genesis block'(시스템의 첫번째 블록)을 정의함으로써 시작할 수 있는 몇 가지 포인트를 얻을 수 있습니다. 왜냐하면 'genesis block'은 첫번째 블록이라서 어떠한 블록도 연결이 되어있지 않고, 그래서 조금 다르게 다뤄지고, 우리가 시스템 상태를 임의로 설정할 수 있기 때문입니다. 우리의 경우에는 두 사람의 계좌를 만들고 각각 50 코인씩을 넣어줄 것입니다.

In [7]:
state = {u'Alice':50, u'Bob':50}  # Define the initial state
genesisBlockTxns = [state]
genesisBlockContents = {u'blockNumber':0,u'parentHash':None,u'txnCount':1,u'txns':genesisBlockTxns}
genesisHash = hashMe( genesisBlockContents )
genesisBlock = {u'hash':genesisHash,u'contents':genesisBlockContents}
genesisBlockStr = json.dumps(genesisBlock, sort_keys=True)



그뤠잇! 이 블록이 다른 모든 블록에 연결될 첫번째 요소가 될 것입니다.



In [8]:
chain = [genesisBlock]

각각의 블록에 대해서 우리는 거래내역을 모으고, 해더와 해당 해쉬 값을 만들고 이것을 체인에 추가할 것입니다.


In [9]:
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

이제 이 함수를 사용해서 임의로 생성한 거래내역들을 블록들로 만드는 처리를 진행해봅시다.


In [10]:
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)    
In [11]:
chain[0]
Out[11]:
{'contents': {'blockNumber': 0,
  'parentHash': None,
  'txnCount': 1,
  'txns': [{'Alice': 50, 'Bob': 50}]},
 'hash': '7c88a4312054f89a2b73b04989cd9b9e1ae437e1048f89fbb4e18a08479de507'}
In [12]:
chain[1]
Out[12]:
{'contents': {'blockNumber': 1,
  'parentHash': '7c88a4312054f89a2b73b04989cd9b9e1ae437e1048f89fbb4e18a08479de507',
  'txnCount': 5,
  'txns': [{'Alice': 3, 'Bob': -3},
   {'Alice': -1, 'Bob': 1},
   {'Alice': 3, 'Bob': -3},
   {'Alice': -2, 'Bob': 2},
   {'Alice': 3, 'Bob': -3}]},
 'hash': '7a91fc8206c5351293fd11200b33b7192e87fad6545504068a51aba868bc6f72'}

우리가 예상한대로 'genesis block'에 계좌의 잔금을 초기화하는(?), 정상적이지 않은 거래내역(갑자기 토큰이 생성되는)이 포함되게 됩니다. 부모 블록의 해쉬는 자식 블록에서 참고되어지며 자식 블록은 시스템 상태에 영향을 미치는 새로운 거래내역을 포함하게 됩니다. 이제 우리는 거래내역들이 모두 업데이트된 결과를 확인할 수 있습니다.


In [13]:
state
Out[13]:
{'Alice': 72, 'Bob': 28}


파이썬으로 나만의 블록체인 만들기 #1

정보보안 2017. 11. 14. 19:59

#참고 : 이 글은 @ecomusing 님의 포스트를 제 맘대로 번역한 글입니다. 원문보기

#참고2 : 전체 Jupyter / iPython 소스코드 받기 


나만의 블록체인 만들기 - 기초


이 튜토리얼은 기초부터 차근차근 블록체인을 어떻게 만드는가에 대한 내용을 다룰 것입니다.

구체적인 예제들의 자세한 설명들이 블록체인의 장점과 한계에 대한 깊은 이해를 도울 것입니다.

보다 자세한 개론에 대해서는 BitsOnBlocks 의 글을 읽기를 추천합니다. 


거래내역(Transactions), 검증(Validation), 시스템 상태 업데이트(Updating system state)


블록체인의 중심에는 배포되는 데이터베이스가 있습니다. 이 데이터베이스는 새로 추가되는 데이터베이스에  대한 검증을 할수 있는 규칙들이 있습니다. 이것을 설명하기 위해서 가상의 돈을 서로 거래하는 가상의 인물 앨리스(Alice)와 밥(Bob)의 계좌를 따라가보도록 하겠습니다.


우선 입출금 거래내역들과 거래내역들의 유효성 검사 기능을 만들고 이것들을 블록으로 만드는 것이 필요합니다.


각각의 거래내역들의 '지문'을 만들기 위해 hash function 을 사용할 것입니다. 이 hash function 은 각각의 블록들을 다른 블록들과 연결시켜주는 역할을 합니다. 이 hash function 을 쉽게 사용하기 위해서 우리는 Python hash function 기능을 포함하는 helper function 을 정의할 것입니다.


In [1]:
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!
        
    if sys.version_info.major == 2:
        return unicode(hashlib.sha256(msg).hexdigest(),'utf-8')
    else:
        return hashlib.sha256(str(msg).encode('utf-8')).hexdigest()

다음으로 우리는 앨리스와 밥 사이의 거래내역들을 생성해주는 기능을 만들 것입니다. 출금은 음수로, 입금은 양수로 나타내질 것입니다. 

거래내역들은 항상 우리가 만든 시스템 안의 두 사람 사이에서 발생하는 거래내역들이고, 입금 금액은 출금 금액과 똑같아야 합니다. 즉, 전체 돈은 늘어나거나 줄어들지 않습니다.


In [2]:
import random
random.seed(0)

def makeTransaction(maxValue=3):
    # This will create valid transactions in the range of (1,maxValue)
    sign      = int(random.getrandbits(1))*2 - 1   # This will randomly choose -1 or 1
    amount    = random.randint(1,maxValue)
    alicePays = sign * amount
    bobPays   = -1 * alicePays
    # 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':alicePays,u'Bob':bobPays}

이제 많은 양의 거래내역들을 만들어 봅시다.



In [3]:
txnBuffer = [makeTransaction() for i in range(30)]

다음으로 블록들을 만들 차례입니다. 우리는 임의로 생성한 거래내역들(transaction buffer)에서 k 거래내역들을 가져와서 블록으로 만들 것입니다. 이것을 하기 전에, 우리는 블록에 들어갈 거래내역들을 검증하는 방법을 정의하는 것이 필요합니다.


비트코인의 경우, 유효성 검사는 들어오는 값(input value)이 출력된 거래내역으로 올바르게 쓰였는지(UTXOs), 출력된 거래내역들이 입력 값보다 큰 지, 서명에 사용된 키들이 유요한지에 대해 검사합니다. 이더리움의 경우, 유효성 검사는 smart contracts 가 정확하게 실행되고 gas limits 을 준수했는지 확인합니다.


하지만 우리는 이렇게 복잡한 시스템을 만들 필요는 없습니다. 우리의 블록체인의 기본적인 토큰 시스템을 위한 간단한 규칙들을 정의를 하면 다음과 같습니다.


◆ 입금과 출금의 함은 반드시 0이여야 한다(토큰이 생성되거나 없어지지 않는다)

◆ 사용자의 계좌에 반드시 출금 금액보다 많은 자금이 있어야 한다.


둘 중에 하나의 조건이라도 만족하지 못하면, 그 거래내역은 등록이 거부될 것입니다.


In [4]:
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
In [5]:
def isValidTxn(txn,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 state.keys(): 
            acctBalance = state[key]
        else:
            acctBalance = 0
        if (acctBalance + txn[key]) < 0:
            return False
    
    return True

다음 예제는 가짜 거래내역이 포함되어 있는 몇가지 거래내역 입니다. 우리는 이제 위에서 만든 유효성 검사 함수로 가짜여부를 확인할 수 있습니다.


In [6]:
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!
True
False
False
True
False

각각의 블록은 한 묶음의 거래내역, 이전 블록의 해쉬(블록 번호가 1보다 클 때), 해당 블록의 내용과 헤더에 대한 해쉬값을 가지고 있습니다.