이제부터는 Smart Contract 로 들어가기로 한다.
Ethereum 이 비트코인과 가장 크게 차별화되는 특징이기도 하며, Public/Consortium/Private Blockchain 에 공통으로 적용되어 기존 C/S 환경 대비 가장 큰 변화를 가져다줄 수 있는 기술이기도 하다.

일단 얼마전에, 블록체인의 응용방법을 주제로 한 Blockchain 초급 개발자를 대상으로 생각하여 만든 자료를 사용하여 설명 해보겠다.

▌Smart Contract 의 개요

쉽고 멋들어지게 일반화된 용어로 Smart Contract 를 표현한 뉴스기사나 글들이 참 많다. 그래서일까, 사람들이 Smart Contract (스마트 계약)이라는 용어를 나름 머릿속으로 해석하여 정말 무슨 계약 문서같은것이 블록체인에 담겨있다는 생각을 하는 이들도 많다. 그도 그럴것이, Blockchain 을 응용한 매우 낮은 수준의 응용 방법 중 대표적인 것이 "Proof of Existence" 였고, "진본증명" 이나 "외부거래증명" 같은 Use-Case 를 다수 접해온 Blockchain 초심자들이 상상해내기 딱 좋은 용어의 조합이기 때문이라고 생각한다.

Nick Szabo (닉 싸보)

Smart Contract 의 최초 발안자는 Nick Szabo 이다. 이양반은 Computer Scientist 이며 암호학과 법학에 조회가 깊고 경제학에도 관심이 많은, 보통 사람이 대하기에는 힘겨운 그런 사람이다.

1994년, Smart Contract 라는 개념에 대해 처음으로 발표하게 되고
1997년, The Idea of Smart Contract 라는 글로 실제로 이러한 Smart Contract 를 어디에 적용하면 좋을지에 대한 아이디어를 낸다

▌Smart Contract 키워드

Nick Szabo 가 말하는 Smart Contract 의 목적은, "신뢰할 수 없는 컴퓨터 네트워크환경" 에서 "(Machine 간에)고도로 발달된 자동 계약 이행 방법" 을 제시하는 것이다. 이러한 개념은 블록체인 기술을 만나면서 빛을 발하게 되었고, Ethereum 이 "Turing Complete Blockchain" 이라는 개념으로 Blockchain 을 진화시켜가며 Nick Szabo 의 아이디어를 실현시켜 준다.

Nick Szabo 는 블록체인 내의 Smart Contract 를 아래와 같이 이야기한다.
 
    ▪  코드 조각 이다
    ▪  공유장부와 상호작용할 수 있는 인터페이스
    ▪  Transaction 을 보내면 코드조각의 함수를 실행
    ▪  실행된 함수는 장부에서 값을 읽거나 씀

그리고, 금융을 의식해서인지, 어느 강연에서는 이렇게 이야기한다.


이제 조금 감이 왔을 것이라고 믿는다. 조금만 더 이해를 돕기 위한 이야기를 해보자.

▌Smart Contract 의 정체

Nick 이 말한대로이다. Smart Contract 는 계약가 아니라 코드 조각이다. 

아래는 실제 Solidity 언어로 구현 한 Smart Contract (Code) 이다.

contract CrowdFunding {
  struct Funder {
    address addr;
    uint amount;
  }
  struct Campaign {
    address beneficiary;
    uint fundingGoal;
    uint numFunders;
    uint amount;
    mapping (uint => Funder) funders;
  }
  uint numCampaigns;
  mapping (uint => Campaign) campaigns;
  function newCampaign(address beneficiary, uint goal) returns (uint campaignID){
    campaignID = numCampaigns++; 
    campaigns[campaignID] = Campaign(beneficiary, goal, 0, 0);
  }
 
 ...

이러한 Smart Contract Code 는 Ethereum 의 경우, Solidity, Serpent, LLL, Mutan 의 언어로 쓰여질 수 있는데, 현재는 Solidity 를 주로 밀고 있으며 문법은 JavaScript-Like 하다. Serpent 는 Frontier 가 Release 되기 전까지만 해도 C 언어와 유사한 문법이고 Solidity 의 완성도가 많이 떨어져서 두루두루 함께 썼지만 지금은 Solidity 의 승이다. Mutan 은 접은지 꽤 됐고, LLL 은 아직도 Assembly-Like 하게 Low Level 로 개발하고 Debug 하는데에 일부 해외 개발자들이 사용한다.

위에 보이는대로, Smart Contract 는 "변수"도 있고 "구조체"도 있고 "함수"도 있는 코드 이다. 물론 좀더 들어가면 'Storage 변수와 Memory 변수'로 변수의 종류가 나뉘고 Contract 간의 Address 기반 호출과 DELEGATECALL 등이 들어가면서 기존 프로그래밍 방식과 다른 점들이 많이 튀어나오긴 하지만 그래도 코드이다.

이러한 Smart Contract Code 는 Compile 과정을 거쳐 Byte Code 로 변환된다.


위 Bytecode 는 Solidity Realtime Compiler 를 통해 컴파일된 결과이다(도구 등에 대해서도 다음번에 다룰 것이다). 모두 16진수로 된 코드이며, 이 Bytecode 를 to: 주소가 없이 Payload (data: ) 로 할당하여 Blockchain 에 Transaction 을 날리면, Miner 에 의해 Block 이 생성되고, 이러한 Transaction은 Contract Creation Transaction 으로 간주되어 Transaction Receipt 의 contractAddress: 필드에 생성(배포)된 Contract 의 주소를 넣어서 리턴해주게 되어있다.

다음은 이러한 Smart Contract 의 응용 흐름에 대해 간략히 이해 해보자

▌Smart Contract 의 응용 흐름

Smart Contract Code 는 크게 [Creation/Deployment] [Invoke by Message] [Call] 의 응용방식으로 나뉜다.
우선 아래 그림을 보면서 해당 Smart Contract의 응용 흐름을 이해 해보자.


▪   Smart Contract 개발환경
 Smart Contract 개발환경은 개발도구와 Compiler 까지를 포함한 범위를 표시한다. Code 를 작성하고 컴파일 하면 모든 컴파일러는 [Byte Code] 와 [Function Signature], [ABI] 를 최소한 벹어낸다. 

  Byte Code 는 이미 위에서 설명한 것 처럼 Smart Contract Code 를 컴파일 한 결과이며, Blockchain 에 Contract Creation Transaction 을 발생시켜 배포하거나 Contract 로의 Message Tx 이나 Call 을 통해 EVM 위에서 실행된다.

  Function Signature 는 Contract 내의 함수 이름의 SHA3 한 Hash 값의 4바이트 값으로, Contract 의 함수를 실행시킬 때 Transaction 의 to: 주소에는 Contract Address 를, data: 부분에는 이 method signature 4바이트와 함께 파라미터 값이 payload 로 들어간다. JSON RPC API 를 통해 직접 실행시킬때에는 신경써야 하지만 web3.js 를 통해 contract 를 실행할때에는 신경 쓸 필요 없다. 아래 ABI 때문에 가능하다.

  ABI(Application Binary Interface) 는 특정 언어나 플랫폼에 종속되지 않은 방식으로 기술된 Application Interface 에 대한 정의이다. 쉽게 말하면, 이 ABI 정의를 컴파일러 혹은 ABI Generator 가 벹어내는데, 이 ABI 에는 Smart Contract 의 함수와 Parameter 에 대한 Metadata 가 정의되어있다. 이 ABI 를 갖고 JavaScript 언어 기반의 어플리케이션을 만들 때 객체를 만들게 할 수 있고, 쉽게 그 객체의 Method를 호출하는것 만으로 Contract 의 함수가 호출되도록 할 수 있는 것이다. 현재 Ethereum 은 web3.js 와 함께 JavaScript 응용에서 쉽게 ABI 로 객체를 만들어 사용하도록 지원하며, 1.4.0 이후의 go-ethereum 에서는 Go Native 언어 기반의 응용에서 Smart Contract 를 쉽게 Binding 가능하도록 ABI 기반으로 Go Code 를 생성 해주는 ABIGen 을 제공하고있다.

▪   Blockchain Engine
 geth 나 parity, eth 와 같은 Ethereum Node 를 의미한다. 결국 모든 Smart Contract 와 관련한 Transaction 처리와 Contract 실행을 위한 EVM 은 Node 가 갖고있다.

▪   Applications
 Smart Contract 는 Logic 만을 갖고있을 뿐이다. 사용자나 외부 시스템과의 상호작용을 위해서는 당연히 Application 이 필요하다. HTML+CSS+JavaScript 가 되었건 Application Server 가 되었건 Wallet 이건 간에, Ethereum 과의 Interface 를 통해 Smart Contract 와 상호작용하는 Application 에 해당하는 부분이다. Contract 파트를 뺀 Dapp 부분 정도로 봐도 된다.


[1] Contract Creation/Deployment

 일단 bytecode 가 생성되면, ABI 기반의 객체를 통하건 RPC API 를 통하건 Ethereum Network 으로 Contract Creation Transaction 을 날릴 수 있다. 이때 Transaction 에서 to: 주소는 빼고 payload 에 bytecode 를 넣고, 충분한 Gas 를 넣어 보내면(Gas Estimation 기능을 잘 활용해야 한다. 아니면 좀 과하게 넣고 남기면 된다) Contract 가 생성되게 된다.

 ABI 를 통해 생성한 객체의 new() 를 통해 Contract 를 Create 했다면 생성이 완료된 시점(Transaction 이 처리되고 블록으로 묶여서 Import 된 시점)에 callback 이 호출되며 그때 Transaction Receipt 를 보면 contractAddress 에 생성된 Contract 의 주소가 들어가 있게 된다. 이후 부터는 이 주소를 통해 Contract 를 사용하면 되는것이다.

[2] Message Transaction

 위에서 생성한 Contract 의 함수를 실행하는 Transaction 이다. JSON RPC API 로 호출할때에는 payload 에 4바이트의 Function Signature 를 제일 먼저 쓰고 그 다음부터 32바이트 단위의 파라미터 값들을 넣어서 보내게 된다. ABI 를 통한 Contract 객체는 단순히 객체의 Method 를 호출하듯 하면 된다. 

 이 Message Transaction 은 단순히 모든 함수 호출에 사용하면 낭패를 볼 수 있다. Message Transaction 또한 Transaction 이며, Transaction 을 발생시키면 당연히 Gas 가 소모된다. 단순히 현재 상태값을 조회하는 함수를 호출하거나 테스트 목적으로 함수를 호출한다면 Message Transaction 을 발생하면 안된다. 이때는 다음 설명하는 Call 방식을 사용해야 하며, Message Transaction 은, Smart Contract 를 통해 Global State 를 변경해야하는 경우 즉, 값이 변경되어야 하는 경우에만 사용하여야 한다. 나중에 상세히 설명하겠지만, Contract 개발자도 이러한 상태변화가 없는 함수는 constant 로 선언하여야 ABI 로 아무생각없이 함수를 호출하는 응용 사용자들이 Gas 를 소모당하지 않게할 수 있다.

[3] Call

 Contract 의 함수를 호출하는 두번째 방법이다. [2] 에서도 잠깐 언급했지만, Ethereum 의 Global State 에 변화를 주지 않는 함수를 Gas 소모 없이 호출하려면 이 call 을 사용해야 한다. Contract 함수를 Call 하게 되면, Transaction 을 발생시키지 않고 자기 Node 내에 이미 저장되어있는 Smart Contract 를 Local 에서 실행시킨다. constant 함수가 아니더라도 Transaction 발생 없이 함수를 실행시킬 수 있으나 Global State 에는 영향을 주지 않고, call 이 끝난 시점에 모든 state 는 원상복귀된다. 


아.. 오늘은 그냥 "이해" 인데 또 글이 길어졌다.. 더이상 쓰다가는 내일 출근에 지장을 줄 터, 이만 줄이고 앞으로도 많은 내용을 올려야 할 듯 하니 다음 글로 미루어 두도록 하겠다~ ^^;;


▌그래서..

Smart Contract 는, 보이는 대로 블록체인에 배포되는 Code 이다. 그래서 IBM 의 경우, OBC-Peer 를 만들 때 부터 Smart Contract 라는 개념을 가져다 쓰지만 용어는 좀더 Clear 하게, "Chain Code" 라고 부른다. Nick Szabo 에게는 좀 미안하긴 하지만, Contract 라는 표현 보다는 좀더 직관적이지 않나 싶다.

그러나 앞으로의 글들을 보다보면 무조건 Chain Code 라고 하기 보다는 어떤 때는 Contract 라는 용어가 더 맞는것 같다는 느낌이 종종 들 것이다. Public Blockchain 과 Private Blockchain 에서 Smart Contract 가 가져야할 특징과 역할이 약간 다르기 때문인데, 이건 앞으로의 글을 이해하면서 느껴보면 되겠다.

아.. 이젠 졸립다.. 그럼 이만..



출처: http://goodjoon.tistory.com/261 [Good Joon]

오늘은 blockchain 이라는 새 유저를 s1에 새로 만들어보겠다.

우선, root 계정으로 가서 adduser을 해주자. 

root@s1:~# adduser blockchain
Adding user `blockchain' ...
Adding new group `blockchain' (1001) ...
Adding new user `blockchain' (1001) with group `blockchain' ...
Creating home directory `/home/blockchain' ...
Copying files from `/etc/skel' ...
Enter new UNIX password:
Retype new UNIX password:
passwd: password updated successfully
Changing the user information for blockchain
Enter the new value, or press ENTER for the default
Full Name []:
Room Number []:
Work Phone []:
Home Phone []:
Other []:
Is the information correct? [Y/n] y
root@s1:~# ls /home
blockchain  han

이제 우리는 s1의 root에 han과 blockchain 유저가 있음을 알 수 있다.

Blockchain@s1 유저에서 본격적으로 genesisblock을 해보자.


blockchain@s1:~$ vi genesis.json
{
    "config": {
        "chainId": 15,
        "homesteadBlock": 0,
        "eip155Block": 0,
        "eip158Block": 0
    },
    "difficulty": "10",
    "gasLimit": "2100000",
    "alloc": {}
}

이제 geth를 초기화해주자.

geth --datadir ~/private_net init genesis.json

명령을 이용한다. Geth를 이용해 지갑을 만들어보자.

blockchain@s1:~$ geth --networkid "15" --datadir "~/private_net" --rpc --rpcaddr 192.168.56.70 --rpcport 8545 --rpcapi "web3, eth, net, personal" --rpccorsdomain "*" console
WARN [08-30|00:22:00.668] Sanitizing cache to Go's GC limits       provided=1024 updated=664
INFO [08-30|00:22:00.670] Maximum peer count                       ETH=25 LES=0 total=25
INFO [08-30|00:22:00.670] Starting peer-to-peer node               instance=Geth/v1.8.15-unstable-1acefafe/linux-amd64/go1.9.7
INFO [08-30|00:22:00.671] Allocated cache and file handles         database=/home/blockchain/private_net/geth/chaindata cache=498 handles=512
INFO [08-30|00:22:00.690] Initialised chain configuration          config="{ChainID: 15 Homestead: 0 DAO: <nil> DAOSupport: false EIP150: <nil> EIP155: 0 EIP158: 0 Byzantium: <nil> Constantinople: <nil> Engine: unknown}"
INFO [08-30|00:22:00.691] Disk storage enabled for ethash caches   dir=/home/blockchain/private_net/geth/ethash count=3
INFO [08-30|00:22:00.691] Disk storage enabled for ethash DAGs     dir=/home/blockchain/.ethash                 count=2
INFO [08-30|00:22:00.691] Initialising Ethereum protocol           versions="[63 62]" network=15
INFO [08-30|00:22:00.692] Loaded most recent local header          number=0 hash=357b5e…1b20b9 td=10
INFO [08-30|00:22:00.692] Loaded most recent local full block      number=0 hash=357b5e…1b20b9 td=10
INFO [08-30|00:22:00.693] Loaded most recent local fast block      number=0 hash=357b5e…1b20b9 td=10
INFO [08-30|00:22:00.693] Regenerated local transaction journal    transactions=0 accounts=0
INFO [08-30|00:22:00.693] Starting P2P networking
INFO [08-30|00:22:02.804] UDP listener up                         self=enode://818dbff9f570dfc1a3af243cc6a6a92093752aed8156ea2a1aa456f2a4177fbfdc1a093b3aa53270c7f995e1cdbf656808cc0432f7816169a177907cb28cb704@[::]:30303
INFO [08-30|00:22:02.807] IPC endpoint opened                      url=/home/blockchain/private_net/geth.ipc
INFO [08-30|00:22:02.809] HTTP endpoint opened                     url=http://192.168.56.70:8545cors=* vhosts=localhost
INFO [08-30|00:22:02.808] RLPx listener up                        self=enode://818dbff9f570dfc1a3af243cc6a6a92093752aed8156ea2a1aa456f2a4177fbfdc1a093b3aa53270c7f995e1cdbf656808cc0432f7816169a177907cb28cb704@[::]:30303
Welcome to the Geth JavaScript console!
instance: Geth/v1.8.15-unstable-1acefafe/linux-amd64/go1.9.7
 modules: admin:1.0 debug:1.0 eth:1.0 ethash:1.0 miner:1.0 net:1.0 personal:1.0 rpc:1.0 txpool:1.0 web3:1.0

//새로운 지갑을 만들어주자.

> personal.newAccount("비밀번호")
"0xcdfab951fca557449f64cf57092ea106e4cd96a1"
> eth.accounts
["0xcdfab951fca557449f64cf57092ea106e4cd96a1"]
> eth.accounts[0]
"0xcdfab951fca557449f64cf57092ea106e4cd96a1"
> eth.accounts[1]
undefined
> eth.coinbase
INFO [08-30|00:23:57.262] Etherbase automatically configured       address=0xcdFab951FCA557449F64cF57092ea106e4CD96a1
"0xcdfab951fca557449f64cf57092ea106e4cd96a1"
> eth.getBlock(0)
{
  difficulty: 10,
  extraData: "0x",
  gasLimit: 2100000,
  gasUsed: 0,
  hash: "0x357b5eb0b98079e70bdd6cc479ac0b2a749320b2f1dc09b8fcd691db331b20b9",
  logsBloom:"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
  miner: "0x0000000000000000000000000000000000000000",
  mixHash: "0x0000000000000000000000000000000000000000000000000000000000000000",
  nonce: "0x0000000000000000",
  number: 0,
  parentHash: "0x0000000000000000000000000000000000000000000000000000000000000000",
  receiptsRoot: "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
  sha3Uncles: "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
  size: 504,
  stateRoot: "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
  timestamp: 0,
  totalDifficulty: 10,
  transactions: [],
  transactionsRoot: "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
  uncles: [] //언클(전에 있었던 블럭에서 다른 방향으로 분화된 놈) 블록들의 해쉬값
}


state와 reciept, body 안의 transaction 정보가 Hash 안에 들어있다. logsBloom은 하나의 필터이다. 

이제 s1에 blockchain 유저를 만들고 지갑을 생성했다.

s2에도 blockchain 유저를 만들고, 혹시 모를 권한 문제를 해결하기 위해 go와 go-ethereum, geth를 다시 설치하고 실행 시켜주자.

root@s2:~# adduser blockchain
Adding user `blockchain' ...
Adding new group `blockchain' (1001) ...
Adding new user `blockchain' (1001) with group `blockchain' ...
Creating home directory `/home/blockchain' ...
Copying files from `/etc/skel' ...
Enter new UNIX password:
Retype new UNIX password:
passwd: password updated successfully
Changing the user information for blockchain
Enter the new value, or press ENTER for the default
Full Name []:
Room Number []:
Work Phone []:
Home Phone []:
Other []:
Is the information correct? [Y/n] y
root@s2:~# rm -rf /usr/local/go
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     1  112M    1 1328k    0     0  1747k      0  0:01:06 --:--:--  0:01:06 17 12  112M   12 13.7M    0     0  3714k      0  0:00:31  0:00:03  0:00:28 37 39  112M   39 44.9M    0     0  9674k      0  0:00:11  0:00:04  0:00:07 96 68  112M   68 77.4M    0     0  13.4M      0  0:00:08  0:00:05  0:00:03 13 88  112M   88 99.2M    0     0  14.2M      0  0:00:07  0:00:06  0:00:01 14100  112M  100  112M    0     0  15.2M      0  0:00:07  0:00:07 --:--:-- 16.8M
root@s2:~# tar -xzf go1.9.7.linux-amd64.tar.gz
root@s2:~# mv go /usr/local
root@s2:~# echo GOPATH="/usr/local/go" >> /home/blockchain/.profile
root@s2:~# echo PATH=\$PATH:\$GOPATH/bin >> /home/blockchain/.profile
Cloning into 'go-ethereum'...
remote: Counting objects: 72871, done.
remote: Total 72871 (delta 0), reused 0 (delta 0), pack-reused 72870
Receiving objects: 100% (72871/72871), 91.89 MiB | 6.59 MiB/s, done.
Resolving deltas: 100% (48522/48522), done.
Checking connectivity... done.
root@s2:~# mv go-ethereum/ /home/blockchain/
root@s2:~# chown -R blockchain:blockchain /home/blockchain/go-ethereum
root@s2:~# su blockchain
blockchain@s2:/root$ cd
blockchain@s2:~$ source .profile
blockchain@s2:~$ go
Go is a tool for managing Go source code.
Usage:
go command [arguments]
The commands are:
build       compile packages and dependencies
clean       remove object files
doc         show documentation for package or symbol
env         print Go environment information
bug         start a bug report
fix         run go tool fix on packages
fmt         run gofmt on package sources
generate    generate Go files by processing source
get         download and install packages and dependencies
install     compile and install packages and dependencies
list        list packages
run         compile and run Go program
test        test packages
tool        run specified go tool
version     print Go version
vet         run go tool vet on packages
Use "go help [command]" for more information about a command.
Additional help topics:
c           calling between Go and C
buildmode   description of build modes
filetype    file types
gopath      GOPATH environment variable
environment environment variables
importpath  import path syntax
packages    description of package lists
testflag    description of testing flags
testfunc    description of testing functions
Use "go help [topic]" for more information about that topic.
blockchain@s2:~$ su
Password:
root@s2:/home/blockchain# chown blockchain:blockchain /usr/bin/geth
root@s2:/home/blockchain# geth version
WARN [08-30|00:56:12.733] Sanitizing cache to Go's GC limits       provided=1024 updated=664
Geth
Version: 1.8.15-unstable
Git Commit: 1acefafe2201ba061a58b51afd7a7edf4aa0f120
Architecture: amd64
Protocol Versions: [63 62]
Network Id: 1
Go Version: go1.9.7
Operating System: linux
GOPATH=
GOROOT=/usr/local/go

잘 실행된다.

## 여기서 깜빡한 부분이 있다. go-ethereum 폴더로 들어가 make geth를 해주고, go-ethereum/build/bin 안으로 들어가 geth 파일을 /usr/bin 밑으로 옮겨주어야

blockchain@s1에서 geth 명령어가 먹힌다.

이제 하나의 터미널은 blockchain@s1 에 접속하여 mining을 켜놓고, 터미널을 하나 더 켜서 blockchain@s2에 접속하자.

원래의 터미널은 투명 바탕, 새로 킨 터미널은 노란색 바탕으로 기록하겠다.

> miner.start(2) <- 2는 스레드 수
INFO [08-30|01:47:12.469] Updated mining threads                   threads=1
INFO [08-30|01:47:12.469] Transaction pool price threshold updated price=18000000000
null
INFO [08-30|01:47:12.470] Commit new mining work                   number=145 sealhash=62fe84…b993d4 uncles=0 txs=0 gas=0 fees=0 elapsed=222.748µs
INFO [08-30|01:47:17.564] Generating DAG in progress               epoch=0 percentage=0 elapsed=4.527s
INFO [08-30|01:47:21.911] Generating DAG in progress               epoch=0 percentage=1 elapsed=8.874s
INFO [08-30|01:47:26.289] Generating DAG in progress               epoch=0 percentage=2 elapsed=13.251s
.
.
.
채굴이 시작되었다.

이제 또 다른 터미널에 blockchain@s1에 연결하고, 채굴이 되고 있는 지갑을 공개지갑으로 바꾸어주자. 그래야 전송, 수금이 가능하다.
(노란 배경은 채굴이 되는 터미널이 아닌 새로 킨 터미널이다.

hanyeoul@ip-70-12-110-167:~$ ssh blockchain@192.168.56.70
blockchain@192.168.56.70's password:
Welcome to Ubuntu 16.04.5 LTS (GNU/Linux 4.15.0-33-generic x86_64)
 * Documentation:  https://help.ubuntu.com
 * Management:     https://landscape.canonical.com
 * Support:        https://ubuntu.com/advantage
3 packages can be updated.
0 updates are security updates.
New release '18.04.1 LTS' available.
Run 'do-release-upgrade' to upgrade to it.
The programs included with the Ubuntu system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.
Ubuntu comes with ABSOLUTELY NO WARRANTY, to the extent permitted by
applicable law.

// 새로운 터미널이므로 원래 터미널의 geth 정보에 attach 시켜주자.

blockchain@s1:~$ geth attach ~/private_net/geth.ipc
WARN [08-30|01:10:56.575] Sanitizing cache to Go's GC limits       provided=1024 updated=664
Welcome to the Geth JavaScript console!
instance: Geth/v1.8.15-unstable-1acefafe/linux-amd64/go1.9.7
coinbase: 0xcdfab951fca557449f64cf57092ea106e4cd96a1
at block: 0 (Thu, 01 Jan 1970 09:00:00 KST)
 datadir: /home/blockchain/private_net
 modules: admin:1.0 debug:1.0 eth:1.0 ethash:1.0 miner:1.0 net:1.0 personal:1.0 rpc:1.0 txpool:1.0 web3:1.0
> personal.unlockAccount(eth.accounts[2])
Unlock account 0x02c06dd554e2c59e96fbb604c0edee58f6a8d143
Passphrase:
true
> eth.coinbase
"0x02c06dd554e2c59e96fbb604c0edee58f6a8d143"
> eth.getBalance("0x02c06dd554e2c59e96fbb604c0edee58f6a8d143")
90000000000000000000
> web3.fromWei(eth.getBalance(eth.accounts[2]),"ether")
90

현재 채굴이 되고 있는 accounts[2] 지갑의 주소는 “0x02c06dd554e2c59e96fbb604c0edee58f6a8d143” 이다.

그냥 밸런스를 조회하면 wei 단위로 나오고, 이를 web3.~~ 명령으로 환산시켜주면 몇 이더가 캐졌는지 나온다.

마이닝 메시지를 보면, mining을 다시 시작하면 기존 블록들과의 동기화를 시작한다.

이 작업이 끝이 나야 새로운 채굴이 시작된다.

동기화가 되고 있는 메시지는 아래와 같다.

INFO [08-30|01:59:06.818] Generating DAG in progress               epoch=0 percentage=83 elapsed=6m20.576s

동기화가 끝나고 나면, 채굴이 시작되고 아래와 같은 메시지를 계속해서 확인할 수 있다.

INFO [08-30|01:41:55.931] Successfully sealed new block            number=140 sealhash=7a36ab…661ddd hash=8ff0f0…cac771 elapsed=2.210s
INFO [08-30|01:41:55.932] 🔗 block reached canonical chain          number=133 hash=811a85…cca896
INFO [08-30|01:41:55.932] 🔨 mined potential block                  number=140 hash=8ff0f0…cac771
INFO [08-30|01:41:55.953] Commit new mining work                   number=141 sealhash=678fa4…b5a90e uncles=0 txs=0 gas=0 fees=0 elapsed=148.233µs

다른 터미널에서 확인해보면, 점점 이더리움이 늘어나는 것을 볼 수 있다.

> web3.fromWei(eth.getBalance(eth.accounts[0]),"ether")
300
> web3.fromWei(eth.getBalance(eth.accounts[0]),"ether")
310
> web3.fromWei(eth.getBalance(eth.accounts[0]),"ether")
450
> web3.fromWei(eth.getBalance(eth.accounts[0]),"ether")
705

다음 포스트에서는 Python을 통해 로그를 확인하는  logsbloom을 만들어볼 것이다.


'프로그래밍 & IT > BlockChain' 카테고리의 다른 글

Ethereum 응용 개발 - Smart Contract 의 이해  (0) 2018.09.07
ubuntu에 geth 설치  (0) 2018.08.28
블록체인에서의 거래  (0) 2018.08.25
블록체인에서 블록이란?  (0) 2018.08.25
비밀키와 공개키  (0) 2018.08.20

1. GIT 세팅


terminal을 열고 git을 apt-get을 통해 다운로드 받는다.

sudo apt-get install -y git

다 받았으면 ethereum 공식 github 홈페이지에서 fork를 뜬 다음 해당 링크로 git 명령어를 통해 clone을 만든다.

git clone https://github.com/ethereum/go-ethereum






2. GO 세팅


GO를 다운받기에 앞서 필요한 유틸리티들을 다운로드 받는다.

sudo apt-get install -y build-essential

sudo apt-get install -y curl

curl을 통해 go의 1.97버전을 다운받는다.

[ apt-get install golang을 통해 다운로드 받을 시 1.97보다 이전버전이 다운로드 돼서 문제가 생길 수 있음!]

curl -O https://dl.google.com/go/go1.9.7.linux-amd64.tar.gz

go의 압축을 푼다.

tar -xzf go1.9.7.linux-amd64.tar.gz

sudo mv go /usr/local

go 명령어를 통해 go를 실행시킬 수 있게 vi 편집기를 통해 .profile에 GOPATH와 GOPATH/bin을 등록한다.

vim .profile

맨 끝에 아래내용 추가

GOPATH="/usr/local/go"

PATH="$PATH:$GOPATH/bin

source .profile

go version 명령어를 통해 버전이 제대로 출력되는지 확인한다.






3. GETH 세팅


1번에서 git세팅했을 때 클론했던 ethereum 디렉토리로 들어간 뒤 geth를 세팅한다.

cd go-ethereum

make geth

make과정이 끝났으면 마찬가지로 geth 명령을 통해 geth를 실행시킬 수 있게 세팅한다.

cd build/bin

sudo mv geth /usr/bin

geth version 명령어를 통해 버전이 제대로 출력되는지 확인한다.

=============================================================================




script 파일로 한 번에 설치 진행하기



script로 한번에 설치하기 전 sudo apt-get명령시 비밀번호 입력하는 부분을 처리한다.

sudo su

비밀번호 입력

echo "사용자이름 ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers


새로운 파일을 만든 뒤 아래 스크립트 내용을 작성한다.

chmod 755 파일이름으로 실행 가능한 파일로 만든 뒤

해당파일을 실행시키면 끝


#!/bin/sh

git=`which git`

if [${#git} -eq 0]

then

sudo apt-get install -y git

fi

git clone https://github.com/ethereum/go-ethereum

build_essential=`which make`

if [${#build_essential} -eq 0]

then

sudo apt-get install -y build-essential

fi

curl=`which curl`

if [${#curl} -eq 0]

then

sudo apt-get install -y curl

fi

cd ~

curl -O https://dl.google.com/go/go1.9.7.linux-amd64.tar.gz

tar -xzf go1.9.7.linux-amd64.tar.gz

sudo mv ~/go /usr/local

echo GOPATH=/usr/local/go >> ~/.profile

echo PATH=$PATH:$GOPATH/bin >> ~/.profile

source ~/.profile

cd ~/go-ethereum

make geth

sudo cp ~/go-ethereum/build/bin/geth /usr/bin

geth version

거래(Transaction)은 입력과 출력으로 구성이 되어 있습니다.

먼저 Bitcoin의 거래를 보도록 하겠습니다.


다음과 같이 각각의 거래는 입력주소에서 출력주소로의 비트코인이 이동하는 형태로 구성되어 있습니다.


거래의 입력값은 이전거래의 출력값의 형태로 구성이 됩니다.

즉, 어떤 거래에서 사용해야할 입력값은 그 이전거래에서 받은 출력값이 된다는 것입니다.

아래 그림과 같이 A로부터 입력으로 받은 10개의 비트코인이 B에게 출력의 형태로 10개가 이동이 되고, 이전거래의 출력값이었던 10개가 다시 입력값이 되어 C에게 10개가 이동하는 형태입니다.


그럼 개념적으로 거래는 어떠한 형태로 구성될수 있는지 보도록 하겠습니다.


1. 하나의 입력값과 2개의 출력값이 있는 경우

이 형태는 가장 일반적인 거래를 표현하는 거래로 볼수 있습니다.

아래와 같이 입력으로 10개의 비트코인을 사용하여 B에게 2개를 전달하고 나머지는 다시 A로 출력하는 형태로 현실에서의 거래에서 거스름돈을 받는 형태라고 볼수 있습니다.

즉, 10개의 비트코인을 2개만 전달하여 8개는 거스름돈으로 다시 받는 형태입니다.

여기서 출력값으로 받은 [A : 8]의 거래는 이후 다른 거래의 입력값으로 사용되게 됩니다.

    

2. 다수의 입력값과 하나의 출력값이 있는 경우

이 거래는 두가지 형태로 나누어볼수 있는 하나의 경우는 자신의 지갑에 여러주소로 나누어져있는 비트코인을 하나로 합쳐서 20개의 거래에 사용하는것입니다.

현실에서 볼때 10만원짜리 제품을 구입하기위해 5만원 1장, 만원 3장, 5천원 3장, 천원 5장을 전달하는것으로 볼수 있습니다.

그리고 또하나의 경우는 자신의 지갑에 분산되어있는 잔돈을 합쳐 하나의 주소에 모으는 것입니다.

즉 다음 거래에서 20개의 비트코인을 거래를 하기위해 작은단위를 합쳐 하나의 큰 단위로 만드는것입니다.

현실에서 저금통의 동전을 지폐로 교환하는것으로 볼수 있습니다.




3. 하나의 입력값과 다수의 출력값이 있는 경우

이 거래는 20개의 비트코인을 4명에게 각각 나누어주는 거래로 볼수 있습니다.


4. 입력값이 없고 출력값만 있는 경우

이 거래는 특별한 거래로 채굴에 성공한경우 채굴자에게 보상으로 비트코인을 제공하는 거래입니다.

이 거래는 이후 채굴에서 다시한번 업급하도록 하겠습니다.





블록체인상에서 거래의 단위는 소비되지 않은 거래출력값(Unspent Transaction Output) 즉 UTXO 입니다.

블록체인에서의 거래를 이해할려면 이 UTXO의 개념을 잘 이해해야합니다.

UTXO는 말 그대로 거래의 출력값으로 아직 사용되지않고, 암호화되어 블록체인상에 저장되어 있는 단위입니다.

암호화된 UTXO는 사용시점에 소유자만이 암호를 해제하여 거래의 입력값으로 사용할수 있습니다.

이전에 지갑을 설명할때에 지갑은 비트코인을 실제 저장하고있는것이 아니고, 개인키와 공개키를 저장하고 관리하고 있다는 것이 이것으로 어느정도 설명이 될수 있습니다.

예를 들어, 자신 소유의 비트코인의 전체 잔액을 확인하고자 한다면, 지갑에 저장되어 있다고  생각되는 비트코인의 갯수를 확인하는것이 아니라, 블록체인상에 자신의 소유로 저장되어 있는 UTXO의 출력값을 모두 합한것이 되는것입니다.


거래를 하기 위해서는 해당 거래가 유효한지 검증이 되어야 합니다.

즉 발생된 거래가 블록체인상에 전송되어 각 사용자가 유효한지 검증을 해주어야 합니다.

이렇게 검증이 된 거래만이 채굴을 통해 메인체인에 포함이 될수 있습니다.

거래의 유효성 검증은 다음과 같이 동작됩니다.


먼저 거래의 송신자는 거래메세지를 작성하고 해당 메세지를 자신의 개인키로 암호화하여 서명을 만듭니다.

이렇게 만든 서명은 자신의 공개키로만 복호화가 가능합니다.

다음에 이렇게 만든 서명을 자신의 거래 메세지와 함께 블록체인상에 전송하고 암호화에 사용한 공개키도 함께 공개를 합니다.

이 거래메세지를 수신한 수신자들은 해당 거래에 포함된 서명을 공개된 공개키로 복호화 하여 포함된 메세지와 비교를 하게 됩니다.

이렇게 비교하여 메세지와 공개키로 복호화된 메세지가 동일하다면 해당 거래는 정상적인 개인키로 암호화된것으로 판단할수 있기 때문에 유효한 거래로 판명되어 연속적으로 블록체인상에 전파가 되게 됩니다.





마지막으로 전송된 거래가 UTXO 형태에서 실제 소비되는 과정을 보도록 하겠습니다.


거래의 출력값은 다음과 같이 거래총액과 잠금스크립트로 구성이 됩니다.

잠금 스크립트는 해당 거래가 소비가능한 조건을 명시해놓는것으로 일반적으로 공개키나 비트코인 주소가 됩니다.


필드 

설명 

 총액

 사토시 단위의 거래금액(1사토시는 1억분의 1 비트코인) 

 잠금 스크립트 크기

 잠금 스크립트 길이 

 잠금 스크립트

 출력값을 소비하는데 필요한 조건 


거래 입력값은 다음과 같이 구성됩니다.

해제 스크립트는 지갑에서 생성되며, UTXO의 잠금 스크립트 조건을 만족할수 있도록 개인키로부터 생성한 서명을 담고 있습니다.


 필드 

설명 

 거래 해시

 소비될 UTXO를 담고있는 거래에 대한 ID (TxID)

 출력값 인덱스

 소비될 UTXO의 인덱스 번호

 해제 스크립트 크기

 해제 스크립트 길이

 해제 스크립트

 UTXO의 소비조건을 충족하는 스크립트

 일련번호

 사용하지않음, 0xFFFFFFFF로 설정


실제 입력과 출력으로 구성된 거래의 포맷은 다음과 같이 구성이 됩니다.

하기 포맷은 일반적으로 사용되는 주소기반의 P2PKH(Pay-to-public-key-hash) 거래입니다.


[참조 : https://klmoney.wordpress.com/bitcoin-dissecting-transactions-part-1/]


블록체인에서 블록이 무엇인지에 대해서 정리하고자 합니다.

블록은 다수의 거래정보와 블록헤더로 구성되어 있습니다.

블록체인이란 말그대로 이러한 블록들이 연속적으로 연결되어 있음을 의미합니다.

그럼 이와같은 블록이 어떤정보로 구성되어있는 보도록 하겠습니다.


비트코인의 블럭정보는 https://blockchain.info/ 에서 확인이 가능합니다.

가장 최신블럭이 다음과 같이 보여지고 있으며, 여기서 관심있게 봐야할 부분인 Age를 보면 평균적으로 블록간의 시간이 10분정도 소요됩니다.

이말은 비트코인의 블록은 평균 10분마다 생성이 되며, 10분간의 거래가 해당블럭에 포함이 된다는 말입니다.



그럼 511435번 블럭을 살펴보도록 하겠습니다.

이 블록은 말그대로 511435번째로 생성된 블럭을 말하며, 0번 블록은 비트코인이 최초에 실행될때 만들어진 제네시스(Genesis) 블록이 됩니다.

각 블록간의 생성시간은 위에 언급한것처럼 평균 10분이 소요됩니다.


블록은 헤더와 트랜잭션(Transaction)으로 구성되며, 블록헤더는 다음과 같이 구성되어 있습니다. (Block.h)


hashPrevBlock과 hashMerkleRoot의 데이타타입이 uint256으로 구성되어있음을 볼수있는데, 이 값들은 모두 SHA256 알고리즘을 통해 생성되는 값이기 때문에 256비트값을 가지게 됨을 짐작할수 있습니다.



Timestamp(블록이 생성된 시간) : 2018-03-01 08:11:49 

Bits(블록 생성의 난이도) : 392009692

Version(프로토콜 버전) : 0x20000000

Merkle Root(트랜잭션 정보를 머클트리 형태로 구성한 해시정보) : 

   b958fda3ce4d3cd0a41f01a6b00f6ae4e70f498a27f526cc5612f72450152d17

Previois Block(이전 블록의 해시정보) : 

   00000000000000000029b633899beed322666cc0041fbdf471300d52e6de1887

Nonce : 2170187189


Hash : 0000000000000000003265329296e10cc51cc37cb0fb8f31636bb15d9fdf8a60


블록헤더과 트랜잭션은 다음과 같이 구성됩니다.



여기서 블록헤더를 구성하는 6개값중 아래의 5개는 그 값이 고정된 상수입니다.

(version, previous hash, merkle root, timestamp, bits)

이 5개의 정보와 Nonce 값을 사용하여 SHA256 알고리즘을 통해 해당 블록의 Hash 값이 계산됩니다.

이 Hash 값이 Bits로 지정된 난이도값보다 작게 생성될때 해당블록이 생성에 성공이 되며, 이 과정이 채굴(Mining) 입니다.

채굴알고리즘은 이후에 다시 정리할려고 합니다.


이와같이 하나의 블록은 계산된 Hash 값과 블록헤더, 10분간 진행된 거래(Transaction) 내역이 포함되며 이 블록이 다음과 같이 연속된 체인형태로 구성되게 됩니다.

이렇게 블록들이 연속적인 체인형태로 구성이 되었다고 해서 블록체인이라고 하는것입니다.




'프로그래밍 & IT > BlockChain' 카테고리의 다른 글

ubuntu에 geth 설치  (0) 2018.08.28
블록체인에서의 거래  (0) 2018.08.25
비밀키와 공개키  (0) 2018.08.20
블록체인의 기본, 해시란 무엇인가?  (0) 2018.08.20
블록체인 한번에 이해하기  (0) 2018.08.20

비밀키와 공개키의 원리


지난 장에서는 상당수 블록체인(Block Chain)의 핵심 알고리즘인 SHA-256 해시(Hash)에 대해서 공부하는 시간을 가졌다. 블록체인은 근본적으로 해시 알고리즘을 이용함으로써 코인의 거래 장부가 정상임을 보장할 수 있다. 즉 거래 장부의 무결성(Integrity)을 보장할 수 있는 것이다. 다만 ‘거래 기록이 변조되지 않았음’을 보장할 수는 있으나 이것만으로는 장부 안에 있는 모든 기록이 애초에 사실이었는지는 보장할 수 없다. 만약 과거의 거래 기록 중에서 ‘A가 B에게 100코인을 보냈다’라는 기록이 존재한다고 할 때 이 기록 자체가 거짓된 것이라면 어떻게 할까?


[그림 1] 거짓 장부를 작성하는 기본 아이디어


간단한 예시를 살펴보자. 가장 대표적인 거짓 장부의 예는 실제로 보내지도 않은 돈을 보냈다고 속이는 것이다. 특정 블록체인에서 위와 같이 ‘동빈’이라는 나쁜 채굴 참여자가 ‘종구’가 자신에게 100 코인을 전송했다는 거짓 거래 내역을 장부에 기록함으로써 자신이 종구의 코인을 가지고 있다고 주장하고 있다. 지난 절에서 자세히 배웠듯이 장부에 기록된 정보는 해시(Hash)에 의해서 무결성을 보장받으므로 블록이 연달아 생성될수록 더욱 변조하기가 어렵다. 따라서 애초에 ‘거짓 거래 내역’이 공공 거래 장부에 기록되는 순간 이를 되돌리기가 어렵다는 것이다.


그러므로 애초에 ‘동빈’이 ‘종구’의 이름으로 거래 내역을 기록할 수 없도록 해야 한다. ‘종구’가 코인을 전송했다는 정보는 반드시 ‘종구’만 기입할 수 있어야 한다. 실제로 블록체인에서는 이것을 공개키 기반 구조(Public Key Infrastructure)를 이용해 해결한다. 공개키 기반 구조란 비밀키와 공개키라는 두 개의 키를 이용하는 암호 구조이다. 이러한 공개키 기반 구조에서 특정한 데이터를 암호화하고 싶을 때는 먼저 비밀키를 이용해 그 데이터를 암호화한다. 이후에 암호화된 데이터는 오직 그 비밀키와 한 쌍을 이루는 공개키를 이용했을 때만 복호화할 수 있다.


이러한 특징 때문에 공개키 기반 구조는 주로 행위를 증명할 때 사용한다. 가령 A가 B에게 100코인을 전송했다는 사실을 증명하고자 하는 경우를 살펴보자. A는 자신이 가지고 있는 비밀키를 이용해 자신이 B에게 100코인을 전송했다는 메시지를 암호화한다. 이후에 그 암호문을 공개키와 함께 주변 네트워크에게 전달한다. 이후에 주변 네트워크에 포함되어 있는 사람들은 해당 암호문이 A의 공개키를 이용해 복호화를 했을 때 성공적으로 복호화가 되는 것을 보고, A가 B에게 코인을 전송했다는 사실을 확인한다. 실제로 코인 거래의 증명은 이러한 방식을 따른다.


[그림 2] 공개키 기반 구조를 활용한 코인 전송의 원리


그렇다면 여기에서 한 번 더 나아가서 특정한 공개키의 주인이 A라는 사실은 어떻게 증명할 수 있을지 알아보자. 이는 공개키의 주인을 식별할 때 사람의 이름이나 아이디를 사용하지 않고 지갑 주소(Address)를 사용함으로써 해결하는데, 여기에서 해시(Hash)의 개념이 한 번 더 등장한다. 실제로 우리가 코인을 거래할 때는 공개키에 해시 알고리즘을 적용한 문자열을 생성하여 이를 지갑 주소로 사용한다. 즉 실제로 블록체인에서 A라는 사람은 그 지갑 주소로서 대변되는 것이다. 이 때 지갑 주소는 오직 해당 공개키로만 만들 수 있으며 중복되지 않는다.


따라서 네트워크에 참여한 사람들은 특정한 암호 메시지를 공개키로 복호화를 한 뒤에 메시지에 적혀 있는 ‘코인을 전송하는 사람’이 해당 공개키에 해시 알고리즘을 적용한 값과 일치하는지 확인한다. 이 때 코인을 전송하는 사람, 즉 송신자 지갑 주소가 해당 공개키에 해시를 적용한 지갑 주소와 일치하는 경우에만 정상적인 거래로 인정해주는 것이다. 이와 같이 블록체인은 다양한 종류의 암호화 알고리즘이 덕지덕지 붙어 있는 암호화 기법의 집합체라고 할 수 있다.


그러므로 이러한 블록체인의 기술에 따라서 우리는 타인의 지갑에 있는 코인을 쉽게 빼낼 수 없다. 개인의 지갑 주소는 네트워크에 공유되므로 쉽게 알아낼 수 있지만 ‘특정 지갑 주소로 다른 지갑으로 코인을 전송했다’는 메시지를 만들기 위해서는 특정 지갑의 비밀키를 알아내야 하기 때문이다. 결과적으로 개인키를 도난당하지 않는 이상 자신의 코인은 안전하다고 볼 수 있다. 구체적으로 개인키부터 공개키와 지갑 주소를 생성할 때 사용하는 매커니즘은 다음과 같다.


[그림 3] 블록체인의 지갑 주소 생성 매커니즘


공개키 기반 구조를 근간으로 하는 암호화 알고리즘에는 다양한 종류가 있지만 블록체인에는 ‘타원 곡선 암호 알고리즘(ECC)’이 주로 사용된다. 타원 곡선 암호 알고리즘은 쉽게 말해 ‘특정한 비밀키를 입력했을 때 타원 곡선을 이용해 그에 상응하는 공개키를 생성해주는 알고리즘’이다. 이 또한 당연히 공개키 기반 구조이므로 공개키를 가지고 원해의 비밀키가 무엇이었는지 알 수 없다는 점에서 강력하다. 공개키를 생성한 이후에는 해시 알고리즘을 적용해 지갑 주소를 생성한다. 이 때 일반적으로 이전 시간에 배웠던 SHA-256 알고리즘을 이용한다.


호기심이 많은 독자라면 자신이 직접 비밀키와 공개키를 생성을 해보고 싶을 것이다. 다행히도 이미 인터넷에는 수없이 많은 자료가 존재하기 때문에 누구나 쉽게 위 매커니즘을 실습해 볼 수 있다. 다음에서 블록체인 암호화를 실습하는 시간을 가져볼 것이다.


공개키를 이용한 블록체인 암호화 실습하기


공개키를 이용한 블록체인 암호화 기법에 대해서 자세히 이해하기 위해서는 실습을 통해 직접 체험해보는 것이 좋다. 앤더스 브라운워스(Anders Brownworth)가 개발한 무료 교육용 웹 사이트인 블록체인 데모(Blockchain Demo)를 이용해보자. 전체 웹 사이트 소스코드는 오픈소스로 공개되어 있다. URL(https://anders.com/blockchain/public-private-keys/keys.html)에 접속하면 다음과 같은 화면을 만날 수 있다.



사이트에 처음 접속하면 위와 같이 무작위의(Random) 비밀키(Private Key)가 생성되어 있다. 또한 해당 비밀키를 이용해 만들어진 공개키(Publick Key)가 출력된다. 사용자가 임의대로 비밀키를 변경할 수 있는데 변경을 할 때마다 공개키도 자동으로 변경된다. 실제 블록체인에서도 비밀키는 절대 잊어버리거나 누설해서는 안 되는 중요한 정보이다.



이제 위와 같이 ‘Signatures’ 탭으로 이동하면 위와 같이 특정한 메시지를 비밀키를 이용해 암호화할 수 있다. 암호문은 메시지 서명(Message Signature)로 표현된다. 이제 이후에 이 암호화된 메시지와 공개키를 주변 네트워크로 전달했다고 가정해보자. 



이후에 ‘Verify’ 탭으로 이동한 뒤에 ‘Verify’ 버튼을 누르면 해당 암호문 메시지를 공개키로 복호화 하여 성공적으로 데이터가 검증되었다고 출력되는 것을 알 수 있다. 이는 블록체인 네트워크에서 주변 컴퓨터들이 특정한 하나의 거래에 대해서 ‘입증’을 해주는 과정과 같다.



이후에 ‘Transaction’ 탭으로 이동한 뒤에 다음과 같이 다시 한 번 서명을 생성해보자.



이번에는 위 그림에서 보여주는 바와 같이 단순한 문자열 메시지가 아니라 실제로 자신의 지갑에서 특정한 지갑으로 코인을 전송했다는 데이터를 토대로 메시지 서명을 만들어낸 것을 알 수 있다. 물론 내부적인 작동 원리는 직전에 했던 단순 문자열 메시지 서명과 거의 동일하다.



이후에 ‘Verify’ 탭으로 이동하면 실제로 주변 네트워크가 해당 메시지 서명을 복호화하는 과정을 테스트할 수 있다. 다만 이 탭에서는 복호화에 필요한 공개키가 존재하지 않는 것을 알 수 있다. 예리한 독자들은 이미 눈치를 챘겠지만 공개키는 지갑의 주소와 1:1로 대응한다. 따라서 송신자(From)에 적혀있는 문자열이 바로 공개키 자체로 사용되는 것이다. 이 블록체인 데모 사이트는 말 그대로 블록체인 데모이므로 블록체인의 핵심 원리를 설명하는 데에 주력하고 있다. 실제로 암호화폐의 블록체인을 그대로 가져온 것은 아니므로 이론적으로만 이해하면 충분하다.



마지막으로 이전 시간에 배운 블록체인의 무결성에 대한 내용과 공개키 기반 구조에 대한 내용을 모두 종합한 예시를 확인해보자. 위와 같이 마지막 탭인 ‘Blockchain’ 탭으로 이동하면 여러 개의 블록이 존재하고, 블록마다 여러 개의 거래 내역이 포함되어 있다. 또한 각 거래 내역은 공개키 알고리즘이 적용되어 있다. 각 메시지 서명(Signature)들은 블록의 ‘현재 해시’ 값에 영향을 미친다는 점에서 특정한 거래 내역의 데이터를 조금만 변경해도 해당 블록은 잘못된 블록으로 인식된다. 이와 같이 블록체인은 공개키 기반 구조와 해시 알고리즘이 적절히 어우러져 있다.

블록체인이 작동하는 원리를 제대로 이해하기 위해서는 무결성에 대한 이해가 필요하다. 무결성(Data Integrity)은 ‘특정한 데이터를 보호하여 그 데이터를 정상인 상태로 유지하는 성질’을 의미한다. 무결성은 정보보안 영역에서 굉장히 중요한 키워드다. 블록체인은 암호화폐, 투표 시스템 등에 적용이 된다는 점에서 무결성을 보장하지 못하면 사람들이 신뢰하고 사용하지 않을 것이다. 즉 누구도 데이터를 변질시킬 수 없는 특성, 무결성이 있어 암호화폐도 존재하는 것이다. 먼저 정보보안에서 가장 중요시하는 세 가지 요소인 CIA에 대해 알아보자.



[그림 1] 정보보안의 세 가지 요소(CIA)


CIA란 기밀성(Confidentiality), 무결성(Integrity), 가용성(Availability)의 앞 글자를 딴 단어다. 각종 IT 관련 자격증 시험에서도 매 번 나오는 개념이므로 컴퓨터에 관심이 많은 독자라면 이를 기억해두면 좋을 것이다. 정보보안에서는 무결성 뿐만 아니라 기밀성, 가용성이라는 요소를 중요하게 여기고 시스템을 설계할 때 이를 기준으로 삼는다. 기밀성이란 ‘허가된 자만 데이터에 접근할 수 있도록 하는 것’이고, 가용성은 ‘서비스가 원활하게 제공되도록 하는 것’이다.


예를 들면 개발자가 웹 서비스를 개발할 때는 사용자가 회원가입 시에 비밀번호를 설정하도록 한다. 일반적으로 타인의 비밀번호를 모르면 접근할 수 없으므로 기밀성을 보장하는 것이다. 또한 비밀번호를 데이터베이스에 저장할 때 해시(Hash)라는 알고리즘을 적용해 해커가 이를 취득해도 원래의 비밀번호를 알아낼 수 없도록 한다. 이는 데이터가 정상임을 보장해주는 해시의 개념을 적용했다는 점에서 무결성을 지킨 것이다. 마지막으로 DDoS 공격 등으로부터 서버를 보호해 웹 서비스가 항상 동작 가능하도록 한다. 이는 가용성을 지키는 것이라고 할 수 있다.


암호화폐의 블록체인은 기본적으로 동일한 장부 데이터를 전 세계의 많은 컴퓨터들이 공유하고 가지고 있다는 점에서 가용성을 철저하게 보장받을 수 있다. 장부가 한 컴퓨터에만 담겨있지 않다는 점에서 해커들은 DDoS 공격을 수행할 대상이 되는 단일 서버를 설정할 수 없기 때문이다. 또한 각 개인의 지갑은 고유한 개인키를 가지고 있어 자신이 아닌 다른 사람이 나의 암호화폐를 인출할 수 없어 기밀성을 보장한다. 마지막으로 블록체인은 누구도 장부 내용을 변조할 수 없도록 해시(Hash) 알고리즘이 시스템 전반에 적용되어 무결성을 보장한다.


사실 모든 사람이 동일한 장부 데이터를 가진다는 블록체인 아이디어를 생각해보면 기밀성과 가용성이 쉽게 지켜질 것이라는 점은 이해하기 어렵지 않다. 하지만 장부의 내용을 누구도 쉽게 변조할 수 없으며 장부를 변조했다고 하더라도 다른 사람들이 어떻게 그 사실을 바로 알아차릴 수 있는 것인지, 그 무결성 매커니즘은 잘 이해가 가지 않을 것이다. 무결성이 성립 가능한 이유를 이해하기 위해서는 약간의 수학적 사고가 필요하다. 바로 컴퓨터공학부의 전공과목인 자료구조(Data Structure)나 암호학 관련 강의에서 자주 배우게 되는 해시(Hash) 알고리즘이다.


해시(Hash)의 정의


일반적으로 우리가 특정한 정품 소프트웨어를 다운로드 받을 때 그 소프트웨어가 해커로 인해 변질되지 않은 정상적인 소프트웨어인 것을 어떻게 검증할 수 있을까? 우리는 이 때 해시(Hash)라는 것을 이용하여 검증한다. 이전 강좌에서도 간단히 언급을 했는데 해시는 ‘특정한 데이터를 이를 상징하는 더 짧은 길이의 데이터로 변환하는 행위’를 의미한다. 여기에서 상징 데이터는 원래의 데이터가 조금만 달라져도 확연하게 달라지는 특성을 가지고 있어 무결성을 지키는 데에 많은 도움을 준다. 예를 들어 ‘A’라는 문자열의 해시와 ‘B’라는 문자열의 해시는 고작 한 알파벳이 다를 뿐인데 그 결과가 천차만별이라는 것이다. 예를 들어 대표적인 해시 알고리즘인 MD5는 어떠한 입력 값을 넣든 항상 각양각색의 32자리의 16진수 값을 반환한다.



[그림 2] 대표적인 해시 알고리즘인 MD5의 연산 결과


16진수는 ‘1부터 9까지의 숫자와 A부터 F까지의 알파벳으로 수를 표현하는 방식’이다. 실제로 ‘A’라는 문자열의 MD5 해시 결과 값은 ‘7FC56270E7A70FA81A5935B72EACBE29’이라는 16진수 문자열을 가진다. 반면에 알파벳 하나만 다른 ‘B’라는 문자열의 MD5 해시 결과 값은 완전히 다른 문자열인 ‘9D5ED678FE57BCCA610140957AFAB571’다. 해시는 그 결과 값을 완전히 바꾸어준다는 점에서 강력한 도구다. 이렇게 ‘암호화된 정보가 큰 폭으로 변화하는 효과’를 눈사태 효과(Avalanche Effect)라고 하는데 좋은 암호 알고리즘들은 모두 눈사태 효과를 그 특성으로 가진다. 흔히 소프트웨어 판매 업체에서는 판매하는 소프트웨어 설치 프로그램의 바이너리 코드에 해시 알고리즘을 적용한 결과 값을 웹 사이트에 함께 게시한다. 고객들은 자신이 어디선가 받은 소프트웨어가 정품인지 확신이 안 설 때 자신이 가지고 있는 소프트웨어의 해시 값이 사이트에 출력된 해시 값과 일치하는 지 확인하여 검증할 수 있다.


MD5 해시 알고리즘의 경우 온라인 MD5 해시 생성기 웹 사이트에 접속하여 실습해볼 수 있다. 해당 URL(http://www.convertstring.com/ko/Hash/MD5)로 접속해 몇 가지 입력 데이터를 넣어 MD5 해시를 거친 결과 데이터가 나오는 것을 확인해보자.


비둘기집의 원리


대부분의 해시 알고리즘들은 항상 고정된 길이의 결과 문자열을 반환한다는 특징을 가지고 있다. 앞서 언급한 MD5 해시 알고리즘은 128 비트로 구성된 결과, 즉 32자리의 16진수 값을 반환한다. 이 문자열의 길이는 변함이 없다. 그렇다면 128 비트라는 결과는 이론적으로 최대 몇 개의 경우의 수를 가질 수 있을까? 한 비트(Bit) 단위는 0 혹은 1이라는 두 가지 경우의 수를 가진다는 점에서 128bit는 총 2의 128 제곱만큼의 경우의 수를 표현할 수 있다. 이는 약 340 간(澗)에 해당하는 매우 큰 숫자이다. 단순히 무차별 대입(Brute-Forcing)으로 해시 결과가 동일한 두 입력 값을 찾기 위해서는 개인용 컴퓨터로는 억겁의 시간이 소요될 것이다.



[그림 3] 128비트로 표현할 수 있는 경우의 수 근사치


하지만 아무리 큰 숫자라고 하더라도 무한(Infinite)은 아니라는 점에서 특정한 두 입력 값의 결과 해시 값이 동일한 경우가 발생할 수 있다. 예를 들어 입력 값의 개수가 2의 128 제곱을 넘어가게 되면 최소한 한 쌍의 입력 값은 그 결과 값이 동일할 것이다. 더 간단한 예로 설명을 하자면 비둘기가 5마리일 때 상자가 4개밖에 존재하지 않는다면 아무리 비둘기를 균등하게 분배해도 최소한 한 상자에는 2마리의 비둘기가 들어가게 된다. 지난 시간에도 언급했지만 이처럼 ‘n + 1개의 물건을 n개의 상자에 넣을 때 최소한 하나의 상자에는 두 개 이상의 물건이 들어 있는 원리’를 비둘기집 원리라고 말한다. 당연히 해시는 수학적 공리인 비둘기집 원리를 따른다.



[그림 4] 비둘기집 원리 예시


비둘기집 원리에 따라 해시에서는 이처럼 ‘서로 다른 입력 값의 해시 결과 값이 동일한 문제’, 즉 한 상자에 두 개의 물건이 들어가는 현상인 충돌(Collision)이 발생할 여지가 있다. 당연히 충돌이 적게 발생할수록 좋은 해시 알고리즘이다. 또한 만약 특정한 해시 알고리즘에 대하여 무차별 대입이 아닌 수학적인 연산 과정으로 원하는 문자열의 해시 결과 값을 만들어낼 수 있다면 그 해시 알고리즘은 무결성을 보장할 수 없다는 점에서 더 이상 사용되지 않게 될 것이다.


더불어 해시는 기본적으로 복호화가 불가능하다는 특징이 있다. 이는 당연히 입력 데이터 집합이 출력 데이터 집합을 포함하고 있으므로 특정한 출력 데이터를 토대로 입력 데이터를 찾을 수 없기 때문이다. 즉 동일한 출력 값을 만들어낼 수 있는 입력 값의 가짓수는 수학적으로 무한(Infinite)개라고 볼 수 있다. 만약에 해시 결과 값에 대해서 복호화를 수행할 수 있다면 이는 압축률이 무한인 압축 알고리즘과도 같다. 해시는 애초에 복호화를 수행할 수 없도록 설계 되었으며, 실제로도 해커가 쉽게 복호화를 할 수 없다는 점에서 매력적인 알고리즘인 것이다.


MD5와 SHA-1


이제 과거에 많이 사용되었던 대표적인 해시 알고리즘인 MD5와 SHA1에 대해서 자세히 알아보도록 하자. MD5(Message-Digest Algorithm 5)는 ‘128비트 고정 길이의 결과를 가지는 암호화 해시 함수’다. 대표적인 인터넷 표준안인 RFC 1321로 지정되어 있고, 10년이 넘는 시간동안 컴퓨터 시스템에 사용되어 해시 알고리즘의 아버지와도 같다. MD5는 최근 개인용 PC 한 대로 수 초 내로 해시 충돌을 찾을 수 있을 정도의 효율적인 공격 알고리즘이 발표되어 무결성을 보장하기 어렵다는 점에서 현재는 사용되지 않고 있다.


MD5의 핵심 알고리즘은 32비트 워드 네 개로 이루어진 128 비트 스테이트(Bit State)를 이용하여 연산하는 것이다. 처음에 각 워드는 A, B, C, D라는 변수명으로 상수 값을 가진다. 이후에 입력 문자열이 블록 단위로 들어오게 되면 각 워드를 이용해 연산 과정을 거치고, 그 결과로 128 비트 스테이트의 값을 새롭게 갱신한다. 입력 문자열 블록을 처리하는 과정은 총 4 라운드(Round)로 구성되는데 이산수학에서 사용되는 기초 연산을 이용하므로 관련 문서를 찾아보면서 구현하면 고급 프로그래머가 아니더라도 프로그래밍 언어로 쉽게 구현할 수 있다는 장점이 있다.


이러한 MD5는 현재 무결성을 효과적으로 보안하지 못한다는 점에서 이보다 더 나은 해시 알고리즘인 SHA-1 혹은 나아가 SHA-256을 사용하도록 권장되고 있다. SHA-1(Secure Hash Algorithm 1)은 ‘미국 국가안보국(NSA)에서 처음으로 개발한 해시 암호화 모델로 MD5를 대신하여 많이 사용되고 있는 해시 알고리즘’이다. 기본적으로 알고리즘은 MD5 해시 함수에서 사용되었던 것과 같이 다양한 기초 수학적 연산 과정을 포함한다. 이러한 SHA-1 알고리즘도 MD5 알고리즘만큼 치명적인 보안 결함이 발견된 것은 아니지만 충돌이 발견된 적이 있다는 점에서 보다 보안이 중요한 시스템에서는 이후에 다루게 될 SHA-256을 사용하도록 권장되고 있다.


기본적으로 충돌(Collision)은 해시 알고리즘의 이용 과정에서 충분히 발생할 수 있다. 단순히 수학적으로 계산해도 컴퓨터가 확률적으로 언젠가는 충돌을 찾아낼 수 있기 때문이다. 하지만 무차별적으로 데이터를 대입하는 방식인 무차별 대입(Brute-Forcing)이 아니라 충돌을 연산 수식을 통해 ‘의도적으로’ 발생시킬 수 있다면 보다 큰 문제가 될 수 있다. 앞서 다루었던 MD5 알고리즘은 이미 다양한 프로그램을 이용해 손쉽게 의도적으로 해시 충돌을 만들어낼 수 있다는 점에서 심각한 보안 결함을 가지고 있다. 이러한 측면에서 저자 또한 실제로 웹 서비스 등의 프로그램을 개발할 때 고객의 비밀번호를 반드시 SHA-256 이상의 해시를 적용해 저장한다.


레인보우 테이블 공격


앞서 해시는 복호화가 불가능하다는 특징이 있다고 했는데 데이터의 유형에 따라서 사실상 복호화가 가능할 수 있다. 예를 들어 특정한 사용자의 비밀번호가 ‘123456’이고 특정한 시스템에서 이 비밀번호를 MD5로 해시하여 데이터베이스에 저장했다고 가정해보자. 이 때 해커는 비밀번호 ‘000000’부터 ‘999999’까지를 반복적으로 MD5 해시를 적용하여 일종의 해시 사전(Hash Dictionary)을 구성할 수 있다. 이후에 특정한 해시 값을 이 사전에 대입하여 원래의 비밀번호를 찾아내는 프로그램을 작성하게 되면 사용자의 비밀번호인 ‘123456’이 해커에게 드러나게 되는 것이다. 이렇게 ‘특정한 해시 알고리즘을 적용해 만들어낼 수 있는 데이터를 모두 저장하여 만든 해시 사전’을 레인보우 테이블(Rainbow Table)이라고 부른다. 특정 서비스에 회원가입을 할 때 어려운 비밀번호를 설정해야 하는 이유는 바로 레인보우 테이블 공격 때문이다.


물론 레인보우 테이블을 막기 위한 다양한 방어 기법도 연구되었다. 그 대표적인 것은 바로 소금(Salt) 값을 넣는 방법이다. 개발자가 사전에 미리 정해 놓은 문자열을 입력 데이터에 추가하여 해시 결과를 저장하는 방식이다. 이처럼 ‘원본 데이터를 찾아내기 어렵도록 하기 위해 추가하는 문자열’을 소금이라고 한다. 예를 들어 소금 값이 ‘Teddy Bear’라고 가정했을 때 MD5에 문자열 ‘A’, ‘B’, ‘C’를 넣었을 때의 결과는 다음과 같다. 이와 같이 소금 값을 적절하게 대입하는 경우 악의적인 공격자는 해시 결과 값을 얻었다고 하더라도 원래 문자열이 ‘A’, ‘B’, ‘C’와 같은 형태였다는 것을 파악하기가 기하급수적으로 어려워진다. 따라서 실제 서버 운영 시에 개발자들은 레인보우 테이블을 막기 위해 소금 값을 반드시 적용한다.



[그림 5] 소금 값을 적용한 MD5 이용 사례 예시


하지만 실제로 해커에게 서버가 해킹을 당하는 경우에는 솔트 값을 적용하는 소스코드 또한 해커의 손에 넘어가는 경우가 많으므로 궁극적으로 가장 중요한 것은 사용자 본인이 암호를 얼마나 어렵게 설정했느냐다. 기본적으로 비밀번호에 특수문자, 알파벳, 숫자가 포함될 수 있도록 설계하면 하나의 문자 당 약 80개의 경우의 수를 가지게 된다. 이 때 사용자의 비밀번호가 12자리라면 80의 12제곱인 68,719,476,736,000,000,000,000 개의 경우의 수를 가지게 되므로 사실상 해커가 하나의 비밀번호 해시 데이터를 탈취하고, 소금 값까지 가지고 있다고 하더라도 원래의 비밀번호를 알아내기는 쉽지 않다. 하지만 특정한 사용자가 이용하는 모든 웹 사이트의 비밀번호가 동일하다면 해커는 그 비밀번호 하나만 복호화에 성공하면 모든 서비스를 해당 사용자의 권한으로 이용할 수 있을 것이다. 따라서 암호를 복잡하게 설정하는 것은 매우 중요하다.


MD5 복호화 실습하기


이제 한 번 MD5 복호화를 실습해보도록 하자. 먼저 저자는 독자들에게 하나의 암호문을 제시할 것이다. 앞서 저자는 레인보우 테이블(Rainbow Table)이라는 개념을 설명하여 독자들에게 힌트를 주었다. 정보보안에 재능이 있는 독자라면 저자가 제시하는 암호문이 의미하는 원래 문장을 찾아내는 방법을 금방 생각해낼 수 있을 것이다.



[그림 6] 특정한 의미를 가지고 있는 MD5 암호문 예제


위와 같은 암호화 문장을 해커의 입장에서 발견했다고 가정해보자. 이 때 이를 어떻게 복호화 할 수 있을까? 해시 암호문을 복호화하는 가장 대표적인 두 가지 방법은 ① 자신이 직접 자신의 컴퓨터에 사전 데이터베이스(Dictionary Database)를 생성한 뒤에, 이를 레인보우 테이블로 삼아 프로그램으로 많은 입력 값을 넣어 해시 결과 값 데이터베이스를 구축하는 방법, 그리고 ② 레인보우 테이블을 제공해주는 무료 사이트를 이용하는 것이다. 두 번째 방법이 자신의 컴퓨터 용량을 소비하지 않아도 된다는 점에서 보다 효율적이다. MD5 복호화를 위한 레인보우 테이블을 무료로 제공해주는 사이트의 URL(http://md5decryption.com/)로 들어가 보자.


위 사이트에서 저자가 제시한 암호문 ‘0CFDE61FEF91A54F5289BE8106FDB85D’을 넣고 실행해보면 결과 값인 ‘나동빈’이 나오는 것을 알 수 있다. 하지만 여기에서 의문점이 들 수 있다. 아무리 이 외국 웹 서비스가 방대한 양의 레인보우 테이블을 가지고 있을 지라도 어떻게 ‘나동빈’이라는 저자의 한국 이름을 가지고 있는 것일까 하는 의문점 말이다. 사실 이는 이 웹 서비스가 현재 방대한 양의 레인보우 테이블을 가지게 된 이유를 고려해보면 이해하기 쉽다. 해당 웹 서비스는 MD5 복호화 기능 말고도 암호화 서비스를 제공하고 있다. 이 때 사용자가 암호화를 위해 입력한 문장을 레인보우 테이블에 새롭게 삽입하는 것이다. 즉 저자는 ‘나동빈’이라는 문장을 먼저 해당 사이트에서 암호화를 시켜놓았기 때문에 이 사이트에서 복호화가 가능한 것이다.


MD5 해킹 실습하기


해시의 개요 챕터의 마지막으로 다룰 내용은 바로 MD5 해킹 실습이다. 구체적으로 ‘서로 다른 역할을 수행하는 두 파일의 MD5 해시 값이 같도록, 즉 충돌이 발생한 것처럼 타인을 속이는 실습’을 해보자. 이를 위해 특정한 파일의 바이너리 코드를 MD5 해시 결과로 바꾸어주는 프로그램을 이용하면 된다. 국내 개발자가 개발한 파일 해시 계산기(File Hash Calculator)를 공식 URL(http://www.owlnetworks.pe.kr/software/calcfilehash)에서 다운로드 받아 실행해보자.



[그림 7] 파일 해시 계산기 실행 결과


이제 ‘찾아보기’ 버튼을 눌러서 아무 파일이나 파일 해시 계산기에 넣은 뒤에 해시를 계산해보자. 저자는 한 번 현재 작성하고 있는 원고 파일(HWP)을 넣어보았다. 그 결과 MD5 해시 값으로 ‘79D4BE7B9C390E30ECC7B5BA8ACEB337’가 출력되었다. 현재 작성하고 있는 원고는 이러한 해시 결과 값으로 상징된다는 것이다. 아마 독자가 특정한 파일을 넣을 때마다 MD5 해시 결과 값은 항상 다를 것이다. 만약에 넣은 두 개의 파일의 해시 결과 값이 우연히 동일하게 나온 독자가 있다면 바로 로또를 구매하라고 추천하고 싶다. 단순히 계산했을 때 확률적으로 로또 1등에 760,620,663,133,661,799,251,010,032 번 당첨된 셈이니까.



[그림 8] 파일 해시 계산기 실습 결과


이제 한 번 완전히 다른 역할을 수행하는 두 개의 파일의 MD5 해시 값이 동일하도록 만들어 보자. 사실 지금 소개할 방법은 MD5 뿐만 아니라 어떠한 해시 알고리즘에 적용하더라도 동일하게 작동하는 방법이다. 일단 저자는 다음과 같이 두 개의 파일을 준비해보았다.



[그림 9] 해시 충돌 눈속임 준비물


이후에 명령 프롬프트(CMD)를 실행해서 두 파일이 위치한 폴더로 이동한다. 이 때 폴더 이동명령어(Change Directory, CD)를 이용한다. 그리고 이어서 copy /b 명령어를 이용해 두 개의 파일을 합친 파일을 만들어보자. 저자는 차례대로 result.jpg와 result.zip이라는 이름으로 결과 파일을 생성해주었다.



[그림 10] 서로 다른 두 파일을 명령 프롬프트를 이용해 합치는 방법


이제 두 결과 파일은 다음과 같다. 둘 다 파일의 용량이 늘어난 것을 제외하고는 기존의 image.jpg 파일과 hided.zip 파일과 동일하게 작동한다. 하지만 실제로 바이너리 코드의 관점에서는 두 파일은 완전히 동일한 파일이라고 할 수 있다.



[그림 11] 해시 충돌 눈속임 결과 파일


한 번 파일 해시 계산기를 이용해 두 결과 파일의 해시 값을 확인해보자. 저자의 경우 생성한 두 파일의 MD5 해시 값이 모두 ‘D7BF42DD206E03A72526F5609E1CBB4C’라는 값을 가지고 있는 것을 확인할 수 있었다. 이와 같이 간단하게 서로 다른 역할을 수행하는 파일이 완전히 동일한 바이너리 코드를 가지게 할 수 있는 것이다.


[그림 12] 해시 충돌 눈속임 결과 파일의 해시 결과 값


이처럼 해시는 무결성이라는 정보보안 요소의 핵심 알고리즘이며 다양한 편법과 기술이 존재한다는 점에서 알고 있으면 도움이 되는 개념이다.


http://www.bitweb.co.kr/news/view.php?idx=360

비트코인과 블록 체인

블록 체인은 비트코인의 바탕이 되는 ‘체계’이며,

비트코인은 블록 체인을 ‘화폐’에 응용한 결과물이다.

블록 체인이 바탕이고, 비트코인은 블록 체인 바탕위에서 구현된 하나의 서비스 또는 상품이라고 할 수 있다. 그래서 블록 체인은 비트코인 뿐아니라 다른 코인의 바탕이 될 수도 있고 실제로도 그렇다. 또한 블록 체인은 코인 뿐아니라 다른 서비스나 상품의 바탕이 될 수도 있다.

재미있는 것은 블록 체인이 비트코인의 바탕이 되는 체계이지만, 블록 체인이 만들어지고 비트코인이 만들어진 것이 아니라 비트코인을 만들기 위해 고민하던 중에 블록 체인이라는 기술이 탄생했다는 점이다.

이제부터 블록 체인이 어떻게 비트코인이라는 화폐의 바탕이 되는 체계가 될 수 있는지 알아보자. 먼저 화폐가 무엇인지 짧게 되새겨 보자.

화폐는 믿음이다

현대 사회에서 화폐란 무엇일까? 너무 모호한 것 같으니 질문을 바꿔보자. 현대 사회에서 화폐의 ‘실체’란 무엇일까?

과거에는 금이나 은 등 실제 실물이 화폐의 역할을 담당했지만, 점차 그 실물에 대한 교환 증서가 화폐의 역할을 담당하게 되었고, 1971년의 닉슨 쇼크 이후에는 사실상 그 실물(금)에 대한 교환도 보장해 주지 않는 불태환 화폐가 되었고, 2008년 금융 위기 이후에는 양적 완화라는 미명 하에 막대한 양의 화폐를 찍어내기도 했다. 그럼에도 불구하고 여전히 우리는 그 화폐를 가지고 큰 문제 없이 살아가고 있다. 어째서 이런 일이 가능할까?

화폐는 ‘믿음’이다.

그렇다. 사실 우리는 화폐를 믿고 있다. 화폐의 실체는 바로 믿음이다. 금으로 교환해준다는 보장은 사라졌지만, 화폐로 여전히 치킨을 먹고 맥주를 마실 수 있다고 모두가 믿고 있다. 화폐는 모두의 ‘신뢰’를 받고 있는 것이다. 비트코인이 가상 화폐, 디지털 화폐 또는 암호화 화폐처럼 화폐라는 용어를 달고 있는 이유도 ‘신뢰’할 만한 대상이 될 수 있기 때문이다. 그 신뢰의 원천이 바로 블록 체인이다.

그럼 블록 체인이 어떻게 신뢰의 원천이 될 수 있는지 차례차례 알아보자.

분산 공개 장부

A가 B에게 만원을 보내는 상황을 생각해보자. A가 은행 사이트에 접속해서 로그인하고, 잔액을 확인하고, B의 계좌 정보를 입력한 후에, 공인인증서나 OTP 등의 비밀번호를 입력하고 확인 버튼을 누르면, A의 잔액에서 만원이 차감되고, B의 잔액에 만원이 더해진다.

개인의 재산이 오가는 이 송금 과정은 매우 중요하다. 그래서 송금 거래에 필요한 모든 확인 과정은 은행에서 수행되며, 그 기록도 은행에 저장되고 외부로는 공개되지 않는다.

그래서 송금에 대해 우리가 믿는 곳은 은행 하나다. 송금 과정에 있어서의 모든 것이 은행 하나에 집중되어 있다. 이는 바꿔 말하면 은행이 바로 단일 실패 지점(Single Point of Failure)이라는 말이다. 즉, 거래 은행의 서버가 정상적으로 동작하지 않거나, 은행의 기록이 사라지게 되면 우리는 재산을 잃게 된다.

컴퓨팅 분야에서 이런 단일 실패 지점 문제를 해결하는 보편적인 방법은 고가용성 처리, 쉽게 말해 다중화다. 2중, 3중으로 복제나 분산처리를 해서 단일 실패 지점을 없애는 전략을 취한다. 은행 시스템도 이런 다중화 처리가 되어 있으므로 앞에서 말한 어처구니 없는 불상사는 쉽게 일어나지 않는다. 소프트웨어 뿐 아니라 보안 담당 직원 배치, 보안 담당 장비 도입 등으로 더 한층 위험을 낮춘다. 하지만 이런 조치를 위해서는 많은 비용이 필요하다.

블록 체인은 이 문제를 완전히 다른 각도에서 바라본다(각주: 설명의 단순화를 위해 이후로 퍼블릭 블록 체인만을 대상으로 한다). 즉, 거래 정보를 감추지 않고 모두에게 공개하고, 누구나 거래 정보를 생성할 수 있으며, 거래 정보를 모두에게 복사해서 사본을 저장하고 그 사본끼리 동기화시킨다. 쉽게 말해 2중화, 3중화 정도가 아니라 수천중화, 수만중화 처리를 해서 기록이 사라지는 일을 원천적으로 막아버린다. 그래서 블록 체인을 거대한 분산 공개 장부라고 부르기도 한다.

블록 체인은 거대한 분산 공개 장부다.

이렇게 수천 수만의 분산 처리를 통해 기록의 멸실은 막을 수 있지만, 이것만으로는 멸실만큼이나 위험한 기록의 위/변조까지 막을 수는 없다. 블록 체인은 이 문제를 암호학의 도움을 받아서 해결한다.

디지털 서명

개발자라면 디지털 서명에 대해 희미하게라도 알고 있겠지만, 블록 체인을 얘기할 때 빼놓을 수 없는 중요한 부분이므로 다시 한 번 짚고 넘어가자.

디지털 서명은 비대칭키와 해쉬 함수를 이용해서 다음과 같은 방식으로 데이터의 진위 여부를 확인할 수 있게 해준다.

Imgur

블록 체인에 담겨 있는 모든 거래 정보에는 이렇게 디지털 서명이 포함되어 있어서 거래 정보가 진본임을 신뢰할 수 있다.

블록이란?

자 이제부터 진짜 블록 체인의 내부로 들어가 볼 차례다. 블록 체인이라는 체인을 이루는 원소인 블록은 무엇일까?

블록은 유효한 거래 정보의 묶음이다.

“A가 B에게 100원을 송금한다.”와 같은 것이 하나의 거래이며, 하나의 블록에는 여러 개의 거래가 포함된다. 블록에 대해 더 구체적으로 이해하려면 실제 구현체를 살펴보는 것이 효율적이다. 그래서 블록 체인의 최초 구현체인 비트코인을 기준으로 블록과 블록 체인에 대해 살펴볼 것이다. 구현체마다 세부적인 방식은 다를 수도 있지만 큰 줄기는 거의 같다.

비트코인의 블록 하나에는 평균 약 1,800개의 거래 정보가 포함될 수 있으며, 블록 하나의 물리적인 크기는 평균 0.98Mbyte이다(출처: https://blockchain.info/charts). 블록은 블록 헤더와 거래 정보, 기타 정보로 구성된다.

  • 블록 헤더는 versionpreviousblockhashmerklehashtimebitsnonce 이렇게 6개의 정보로 구성된다.
  • 거래 정보는 입출금과 관련한 여러가지 정보를 가지고 있다.
  • 기타 정보는 블록 내에 있는 정보 중에서 블록 헤더와 거래 정보에 해당하지 않는 정보를 말하며, 블록 해쉬 계산에 사용되지 않는다.

이 중에서 블록 체인의 메커니즘을 이해하는 데 중요한 열쇠가 되는 정보는 블록 헤더다.

블록 헤더

블록 헤더는 다음의 6가지 정보로 구성된다.

  1. version : 소프트웨어/프로토콜 버전
  2. previousblockhash : 블록 체인에서 바로 앞에 위치하는 블록의 블록 해쉬
  3. merklehash : 개별 거래 정보의 거래 해쉬를 2진 트리 형태로 구성할 때, 트리 루트에 위치하는 해쉬값
  4. time : 블록이 생성된 시간
  5. bits : 난이도 조절용 수치
  6. nonce : 최초 0에서 시작하여 조건을 만족하는 해쉬값을 찾아낼때까지의 1씩 증가하는 계산 회수

블록 헤더가 중요한 이유는 블록의 식별자 역할을 하는 블록 해쉬가 이 블록 헤더의 6가지 정보를 입력값으로 해서 구해지기 때문이다.

블록 해쉬

블록의 식별자 역할을 하는 블록 해쉬는 6가지의 블록 헤더 정보를 입력값으로 하고, 여기에 SHA256 해쉬 함수를 적용해서 계산되는 값으로, 32바이트의 숫자값이다. 이름은 블록 해쉬이지만 그 값은 블록 전체를 해쉬한 값이 아니라, 블록 헤더를 해쉬한 값이다.

지금까지의 내용을 바탕으로 블록 해쉬를 구하는 과정을 그림으로 나타내면 다음과 같다.

Imgur

개별 거래 정보는 결국 머클 트리의 해쉬값인 merklehash 값으로 집약된다. 블록 헤더의 6가지 정보 중에서 versionpreviousblockhashmerklehashtimebits 이렇게 5가지는 블록 해쉬를 만드는 시점에서 이미 확정되어 변하지 않는 값이다. 하지만 마지막 nonce는 확정되어 있지 않고 새로 구해야 하는 값이다. 이 nonce 값을 구해서 최종적으로 블록 해쉬 값을 구하고, 이 블록 해쉬값을 식별자로 가지는 유효한 블록을 만들어내는 것이 바로 작업 증명(Proof of Work), 흔히 말하는 채굴이다.

작업 증명에 대해 알아보기 전에, 이제 블록이 무엇인지 기술적인 수준에서 알게 되었으니 블록이 이어져서 만들어지는 블록 체인도 기술적인 관점에서 정리해보자.

블록 체인이란?

블록 체인은 앞에서는 거대한 분산 공개 장부라고 했는데, 기술적으로는 블록이 이어져서 만들어진 블록의 집합체라고 말할 수 있다.

블록들은 어떻게 이어져 있는걸까? 앞에서 살펴본 블록 헤더의 6가지 정보 중의 하나인 previousblockhash 값은 현재 생성하고 있는 블록 바로 이전에 만들어진 블록의 블록 해쉬값이다. 블록은 바로 앞의 블록 해쉬 값을 포함하는 방식으로 앞의 블록과 이어지게 된다.

Imgur

어디서 많이 본 자료 구조 아닌가? 그렇다. 블록 체인은 사실 링크드 리스트다.

블록 체인은 블록으로 이루어진 링크드 리스트다.

블록 체인을 기술적인 측면에서도 살펴봤으니 이제 그 내부로 조금 더 깊게 들어가보자.

블록 체인의 첫번째 핵심 - 작업 증명

자료 구조 관점에서는 링크드 리스트일 뿐인 블록 체인이 신뢰의 원천이 될 수 있는 첫번째 핵심 요소는 작업 증명(Proof of Work)이다.

앞에서 nonce값을 구해서 최종적으로 블록 해쉬값을 구하고, 이 블록 해쉬값을 식별자로 가지는 유효한 블록을 만들어내는 것이 작업 증명이라고 했다. 결국 nonce 값을 구하는 것이 작업 증명의 핵심이다. nonce값을 구하는 방법을 알아보기 전에 먼저 nonce값이란 어떤 값을 의미하는 지 알아야 한다.

nonce값은, 이 nonce값을 입력값 중의 하나로 해서 계산되는 블록 해쉬값이 특정 숫자보다 작아지게 하는 값을 말한다.

그럼 이 nonce값은 어떻게 구할 수 있을까? 위의 설명에서 맨 마지막 부분을 떼어보면 해쉬값이 특정 숫자보다 작아지게 하는 값이라고 한다. 해쉬 함수의 특성상, 어떤 해쉬값(A라고 하면)을 결과로 나오게 하는 입력값을 찾으려면, A에서 역산을 하는 방식으로는 찾을 수 없고, 결과가 A가 될 때까지 무작위로 입력값을 계속 바꿔가면서 해쉬값을 계산해보면서 찾아낼 수 밖에 없다.

그렇다면 어떤 블록 해쉬값이 어떤 특정 숫자보다 작아지게 하려면, 블록 해쉬의 입력값을 계속 바꿔가면서 구한 해쉬값이 특정 숫자보다 낮은지 비교하는 작업을 계속 반복하는 수 밖에 없다.

블록 해쉬의 입력값은 블록 헤더고, 블록 헤더에는 6가지 정보가 담겨 있으며, 이 중에서 5가지는 값이 고정되어 변경할 수 없고, 오직 nonce만 값을 바꿀 수 있다.

즉, 이 nonce값을 계속 바꿔가면서 계산한 해쉬값이 어떤 특정 숫자보다 작다면, 그 때의 nonce값이 새로 만들어지는 블록의 nonce값으로 확정되고, 특정 숫자 보다 작게 나온 그 해쉬값이 새로 생성되는 블록의 블록 해쉬값으로 최종 확정되며 작업 증명이 끝나게 된다.

이 과정을 그림과 함께 살펴보자. 아래 그림은 블록해쉬가 000000a84...라는 특정값보다 작게 나오게 하는 nonce값을 구하는 과정을 나타내고 있다.

Imgur

앞에서 설명한 대로 nonce 외의 모든 항목의 값은 이미 정해져 있다.

nonce 값이 0 일 때의 해쉬를 구해보니 000000a84...보다 큰 fa8cbaeed... 가 나와서 작업 증명에 실패 했다.

다시 nonce가 1 일 때의 해쉬를 구해보니 932d16e2e... 가 나와서 또 실패.. nonce가 2 일 때의 해쉬, 3 일 때의 해쉬.. 를 계속 반복해서 구하고 실패하다가, nonce가 82,764,351 일 때의 해쉬를 구해보니 드디어 000000a84... 보다 작은 000000a83...이 나와서 작업 증명에 성공했다.

그렇다면 작업 증명 성공 여부의 기준이 되는 000000a84...라는 값은 어떻게 정해지나? 그 값은 작업 난이도에 의해 결정되는데 이 부분은 핵심은 아니므로 그보다 더 중요한 보상에 대해 알아보자.

보상

이렇게 nonce 값을 구하는 채굴 작업에는 엄청나게 많은 횟수의 해쉬 계산이 필요하며 그런 계산을 수행하는 채굴기와 그 채굴기를 돌릴 수 있는 막대한 양의 전기라는 비용이 든다. 그래서 이런 비용에 대한 보상이 없다면 아무도 채굴을 하지 않을 것이다.

비트코인에서의 보상은 새로 발행되는 비트코인과 해당 블록에 포함되는 거래의 거래 수수료의 합이다. 비트코인의 새로운 발행은, 채굴자가 블록을 처음 구성할 때 채굴자의 지갑으로 일정량의 비트코인이 입금되는 거래를 그 블록의 첫 거래(generation transaction)로 추가하는 방식으로 이루어진다.

Imgur

새로 발행되는 비트코인은 최초에 50BTC에서 시작해서 블록 체인에 21만개의 블록이 추가될 때마다 절반으로 줄어들어 2017년 7월 현재는 블록 당 12.5BTC가 새로 발행되며, 보상의 일부로서 채굴자에게 주어진다.

거래 수수료는 각 거래 당사자끼리 자율적으로 정할 수 있고, 거래가 블록에 추가되는 우선 순위를 결정하는데 거래 수수료가 입력값으로 사용되기도 한다.

보상은 nonce 값을 찾아내고, 그 결과 새로운 블록을 블록 체인에 추가해서,

해당 블록에 포함된 모든 거래를 유효한 거래로 확정시켜준 대가라고 할 수 있다.

블록 체인의 두번째 핵심 - 충돌 해소

블록 체인의 첫번째 핵심은 작업 증명이며, 이는 블록 단위에서 처리되는 일이다. 블록 체인의 두번째 핵심은 충돌 해소인데, 이는 블록 단위가 아니라 블록 체인 단위에서 처리되는 일이다. 어떤 충돌을 의미하는 것인지 또 왜 해소시켜야 하는지 알려면 블록 체인을 분산 환경에서 바라볼 필요가 있다.

거래 정보의 전파

앞에서 블록 체인은 거대한 분산 공개 장부라고 했다. 이 분산 공개 장부는 여러 개의 노드에 복사되어 있으며, 여러 개의 노드는 p2p로 연결되어 블록 체인 네트워크를 형성한다. 그리고 하나의 거래 정보가 발생하면 이 거래 정보는 블록 체인 네트워크에 분산되어 있는 수많은 노드에 전파되어야 한다.

다음 그림과 같이 사용자 F가 지갑 앱에서 C에게 수수료 0.001BTC와 함께 1.6BTC를 보내면, 지갑 앱은 블록 체인 네트워크 상의 노드 A에 거래 정보를 전송한다.

거래 정보를 받은 노드 A는 먼저 해당 거래의 유효성을 검증한 후에 그 거래를 아직 블록 생성 작업이 시작되지 않은 후보 블록에 추가하고, 인접한 다른 노드에 그 거래 정보를 전파한다.

거래 정보를 전파받은 노드 B도 마찬가지 방식으로 블록 체인 네트워크 상의 다른 노드에게 거래 정보를 전파하며, 이 거래는 결국 블록 체인 네트워크 전체 노드에 전파된다.

Imgur

블록의 생성 및 전파

블록에 거래 정보가 채워지면 노드는 블록을 생성한다. 이때 캐나다에 있던 노드와 호주에 있던 노드는 상당히 멀리 떨어져 있으므로, 각 블록에 담겨 있는 거래의 내용과 순서는 아래와 같이 서로 다를 수 있다.

Imgur

새로 만들어질 블록은 다음 그림(출처: Mastering Bitcoin - http://chimera.labs.oreilly.com/books/1234000001802/ch08.html#forks) 과 같이 현재 마지막 블록인 파랑블록(P블록)의 다음에 추가될 예정이다.

Imgur

이 때 캐나다의 노드와 호주의 노드가 거의 동시에 nonce 값을 찾아서 블록을 성공적으로 생성했다. 캐나다의 노드가 생성한 블록을 빨강블록(A블록)이라 하고, 호주의 노드가 생성한 블록을 초록블록(B블록)이라하면, 생성 후 전파 과정은 다음 그림(출처: Mastering Bitcoin - http://chimera.labs.oreilly.com/books/1234000001802/ch08.html#forks) 과 같다.

Imgur

캐나다 노드의 인접 노드에서는 전달받은 빨강블록의 블록 해쉬를 다시 계산해서 그 값이 정말로 특정한 숫자보다 작은 올바른 값인지 검증한 후에, 자신이 가지고 있던 파랑블록에 빨강블록을 추가한다.

호주 노드에 인접한 노드에서도 마찬가지 방식으로 기존의 파랑노드에 초록노드가 추가된다. 이 방식이 계속되면서 빨강블록과 초록블록은 전 세계에 분산되어 있는 노드에 전파된다.

블록 체인의 분기

포르투갈에 있는 노드에는 빨강블록이 먼저 전파된 후에 초록블록이 전파파되었다. 포르투갈 노드에서는 늦게 도착한 초록블록은 무시된다.

러시아에 있는 노드도 나름 열심히 nonce 값을 구하고 있었으나, 구하기 전에 아쉽게도 초록블록을 먼저 전달받아서 초록블록을 검증한 후 파랑블록 다음에 초록블록을 추가했다. 초록블록에 이을 새로운 블록의 nonce값을 구하기 시작하고, 잠시 후 빨강블록을 전달받았다. 늦게 도착한 빨강블록은 러시아 노드에서는 무시된다.

이번에는 러시아 노드가 가장 먼저 nonce 값을 구해서 새로 분홍블록(X블록)을 생성하고 초록블록 다음에 분홍블록을 추가한 후에 다음 그림(출처: Mastering Bitcoin - http://chimera.labs.oreilly.com/books/1234000001802/ch08.html#forks) 과 같이 인접 노드에 전파했다.

Imgur

포르투갈에 있던 노드에는 파랑블록 다음에 빨강블록이 추가되어 있는 상태였는데, 파랑블록, 초록블록에 이어진 새로운 분홍블록을 전달받았다. 포르투갈에 있던 노드에는 다음과 같이 블록 체인의 분기가 발생한다.

Imgur

빨강블록과 초록블록의 내용은 서로 다르다. 블록 체인은 신뢰의 원천이 되는 체계라고 했는데, 이렇게 정보가 갈라지는 체계를 믿을 수 있는걸까?

어느 블록 체인을 믿을 것인가?

블록 체인에서는 이렇게 분기가 발생해서 충돌이 일어날 때 어느 블록 체인을 믿을 것인가에 대한 기준이 마련되어 있다. 그렇지 않으면 분산되어 있는 장부 내용의 동기화가 깨지기 때문이다. 그 기준은 다음과 같다.

블록 체인에 분기가 발생하여 충돌하게 될 때는 더 많은 작업 증명이 수행되어 길이가 더 긴 블록을 선택한다.

그래서 포르투갈 노드에 더 긴 블록 체인을 가진 분홍블록이 전파되는 순간, 빨강블록은 파랑블록에서의 연결이 끊어지고 고아가 된다.

Imgur

블록 생성은 평균 10분이 소요될 정도로 연산량이 큰 작업이며, 따라서 앞에서 발생한 것처럼 A블록과 B블록이 거의 동시에 생성되어 분기가 발생할 가능성은 그리 높지 않다. 그리고 길이가 같은 블록 체인이 충돌하더라도 머지 않아 블록 체인의 길이가 달라져서 분기에 의한 충돌이 해소되는 순간이 금방 다가온다.

아래의 그림은 길이가 긴 블록 체인에 의해 고아가 되는 블록의 수를 나타내는데, 최근 1년간 대부분 2개 이하, 최대 3개 이내에서 블록 체인의 분기에 의한 충돌이 해소됨을 알 수 있다.

Imgur

하지만 일시적으로나마 이런 분기 상태가 발생할 수 있기 때문에, 실제 거래 상황에서는 보통 어떤 거래가 포함된 블록 이후로 3-5개의 블록이 더 추가된 후에야 그 거래를 최종적으로 유효한 것으로 확정한다. 일시적으로 분기가 발생하더라도 그 이후로 3-5개의 블록이 추가되는 과정에서 분기 상태가 해소되고 결국 하나의 블록 체인만 남기 때문이다.

고아가 된 A블록에 있던 거래 중 유실되는 거래가 생기지 않을까?

빨강블록에 있던 거래 중에서 초록블록에 포함되지 않았던 거래T가 있을 수 있다. 그럼 빨강블록이 고아가 되면 거래T는 결국 유실되는 것이 아닐까하는 의문이 들 수 있다.

하지만 유실은 발생하지 않는다거래T는 초록블록에는 포함되지 않았더라도 분홍블록 또는 분홍블록 이후의 블록에 결국에는 포함되게 된다. 왜냐하면, 분홍블록은 초록블록을 부모로 해서 생성되는데 거래T가 초록블록에 포함되지 않았으므로, 초록블록에 이어 생성되는 분홍블록 또는 분홍블록 이후의 블록에서는 거래T를 아직 블록 체인에 포함되지 않은 다른 거래들과 마찬가지로 취급하며 블록에 추가하기 떄문이다.

이중 지불 문제

디지털은 복사가 가능하므로 이중 지불이라는 문제가 항상 따라다닌다. 블록 체인도 예외는 아니다. 예외는 아닌 정도가 아니라 수많은 노드에 복제되는 방식이므로 이중 지불 문제가 반드시 생길 것 같다.

일단 동일한 기기에 담긴 지갑에서는 이중 지불을 할 수 없다. 예를 들어 하나의 기기에 설치되어 있는 지갑의 잔액이 5만원일 때, 이 지갑에서 5만원을 지불하는 순간 잔액이 0이 되고, 다시 5만원을 지불할 수 없기 때문이다.

그래서 이중 지불은 보통 물리적으로 떨어진 두 개의 지점에서 생각해 볼 수 있는 문제다. 장부가 복제되어 있으므로 캐나다에서도 내 잔액은 5만원이고, 호주에서도 내 잔액은 5만원이다. 그럼 캐나다에서도 A에게 5만원을 보내고, 이 거래가 아직 도달하지 않은 호주에서도 B에게 5만원을 보내면 어떻게 될까?

시작할 때는 멀리 떨어진 두 곳에서 이중 지불이 실행 되었지만, 두 거래는 블록 체인 네트워크를 타고 전파되다가 어디에선가 반드시 만나게 되어있다. 그 만나는 지점에서는 두 거래 사이에 반드시 선후 관계가 생긴다. 그 지점에 먼저 도달한 거래는 유효한 거래로 인정되지만 늦게 도달한 거래는 이미 잔액이 0인 상태에서 5만원을 보내게 되므로 무효한 거래로 버려지게 된다.

따라서 이중 지불된 거래 중 하나는 결국에는 무효화되는 방식으로 이중 지불 문제가 해결된다.

블록 체인의 세번째 핵심 - 완료된 거래 정보의 변경 불가

거래 정보의 해쉬값은 해당 거래가 포함된 블록의 merklehash 계산에 입력값으로 사용되고, merklehash는 블록 해쉬의 계산에 입력값으로 사용된다. 블록 해쉬는 다음 블록(M이라 하면)의 previousblockhash 값으로 저장되며, 이 previousblockhash은 M블록의 블록 헤더 정보로서, M블록의 블록 해쉬를 계산하는데 입력값으로 사용된다.

Imgur

따라서, 어떤 거래 정보가 변경되면 그 거래 정보가 포함된 머클트리의 merklehash가 변경되고, merklehash가 변경되면 블록 해쉬가 변경된다.

그러면 아래 그림에서 빨간색 밑줄로 표시한 변경된 블록 #1의 블록 해쉬와 그 다음 블록인 블록 #2의 블록 헤더에 previousblockhash로 저장된 값이 달라지게 된다. 따라서 체인을 유지하려면 블록 #2의 previousblockhash 값을 갱신한 후에 블록 #2의 nonce 값을 다시 구해서 블록 해쉬를 새로 구해야 하고, 연이어 블록 #3, #4의 블록 해쉬도 모두 새로 계산해야 한다.

Imgur

그리고 블록 해쉬는 작업 증명의 해답(nonce 값)을 찾아내야 구할 수 있으므로, 거래 정보를 변경한 블록부터 그 이후의 모든 블록을 순서대로 다시 채굴해야 한다.

블록 하나 채굴하는데 평균 10분이 소요되므로 어떤 악의적인 노드가 바로 앞의 블록의 거래 정보를 변경하고 채굴하는 그 10분 동안, 다른 선의의 노드들은 거래 정보가 변경되지 않은 원래의 블록체인에 계속 블록을 이어 나가게 된다. 그래서 그 10분 후에는 악의적인 노드의 블록 체인의 길이는 다른 선의의 노드들이 보유한 블록 체인의 길이보다 1개 더 짧아지게 되고, 두 블록 체인이 만나게 되는 순간 길이가 짧은 블록 체인은 버려지게 된다.

완료된 거래 정보를 변경하려면,

변경하려는 거래 정보가 포함된 블록부터 그 이후의 모든 블록을 순서대로 다시 채굴해서 새로운 블록 체인(A)를 만들어야 하는데,

그동안에도 변경 되지 않은 원래의 블록 체인에는 다른 노드들에 의해 블록이 계속 추가되고 A보다 길이가 길어지게 되므로,

A는 폐기되며 완료된 거래 정보의 변경은 실패하게 된다.

그런데 악의적인 노드가 다른 노드들보다 연산 능력이 훨씬 뛰어나다고 가정해보자.

그렇다면 악의적인 노드에 있는 블록 체인에 블록이 추가되는 속도가 다른 블록 체인에 블록이 추가되는 속도보다 더 빠를 것이고, 언젠가는 악의적인 노드의 블록 체인의 길이가 가장 길어지게 된다. 이렇게 되는 순간 악의적인 노드에 의해 변경된 거래 정보가 유효한 거래 정보로서 전체 블록 체인 네트워크에 퍼지게 되며 과거 거래 정보의 변경이 성공하게 된다. 이를 51% 공격이라고 한다.

하지만 경제적인 관점에서 생각해보면 이런 일이 발생할 가능성은 사실상 없다. 일단, 거래 정보가 변경될 수 있다는 사실이 알려지는 순간 블록 체인의 신뢰는 깨지게 된다.

만약 악의적인 노드가 오랫동안 가장 큰 연산 능력을 가지고 있었다면, 악의적인 노드가 생성한 블록이 많을 것이고 그에 따른 보상액도 많이 보유하고 있을 것이다. 이런 상황에서 블록 체인의 신뢰가 붕괴되면 큰 피해를 보는 쪽은 악의적인 노드 자신이기 떄문에 거래 정보를 변경할 경제적 동기가 없다.

악의적인 노드가 갑자기 많은 연산 능력을 가지게 되었다고 해도, 블록 체인의 신뢰 붕괴로 가치가 사라진 블록을 채굴해봤자 앞으로 가져갈 수 있는 경제적 이익은 없다. 따라서, 경제적으로 이익을 볼 수 없는 거래 정보의 변경은 사실상 발생하지 않게 된다.

마무리

지금까지 블록 체인이 어떻게 비트 코인이라는 암호화 화폐의 신뢰의 원천이 될 수 있는가라는 물음에 대한 답을 찾아봤다. 정리해보면 다음과 같다.

블록 체인은

  • 거대한 분산 공개 장부이며, 그 장부 안에 포함된 개별 거래는 모두 디지털 서명이 붙어 있어서 은행이나 다른 제3자의 개입이 없어도 진본임을 보증할 수 있다.
  • 수천, 수만노드에 분산 되어 있어서 어느 한 지점에 장애나 공격이 발생하더라도 블록 체인이라는 네트워크 전체는 문제 없이 계속 돌아갈 수 있다.
  • 작업 증명이라는 수학적 계산 작업과 경제 관점에서의 논리를 통해 위/변조가 사실상 불가능한 구조를 갖게 되어, 그 안에 기록된 거래들은 은행같은 중앙의 보증 기관이 없이도 신뢰할 수 있는 거래로서 확정될 수 있다.
  • 분산 환경에 전파되는 과정에서 분기가 발생할 수 있으나, 가장 길이가 긴 블록 체인을 유효한 블록 체인으로 선택한다.

블록 체인에도 여러 가지 도전적인 과제들이 있고, 그중에서 가장 중요한 것은 블록 체인의 확장성 문제다. 여기에서 그런 내용을 모두 다룰 수는 없겠지만, 지금까지 펼쳐놓은 이야기가 앞으로 블록 체인에 대한 자료를 접할 때 이해의 폭을 넓혀줄 수 있는 발판이 되어줄 수 있기를 바란다.

블록 체인은 거래 당사자간의 신뢰 확보를 위해 중앙 기관을 필요로 하지 않는 탈중앙화(Decentralization)를 달성한 최초의 소프트웨어 기술이다.

비트코인은 화폐에 한정되어 있지만 이더리움이나 최근 개발되고 있는 EOS나 IOTA, 국내에서 개발되는 BlockchainOS 등은 단순한 화폐를 넘어서 블록 체인 위에서 당사자간의 계약을 프로그램으로 실행시킬 수 있는 탈중앙화 플랫폼을 지향하고 있다.

블록 체인이 세상을 바꿀 수 있는 기술이라고 평가받는 이유도 이처럼 탈중앙화 플랫폼의 바탕이 되기 때문이다.

좀더 나은 세상을 만드는데 기여할 수 있는 기회의 문이 천천히 하지만 분명히 열리고 있다.

FAQ로 정리해보는 블록 체인

블록을 생성하는데 성공한 단 한 명의 채굴자만 보상을 받는건가?

그렇다. 그래서 이론적으로는 평생 채굴기를 돌려도 단 하나의 블록도 생성하지 못할 수도 있다. 그래서 실제로는 채굴 풀(pool)을 형성해서 nonce 값을 찾는 계산 작업을 분담하고, 해당 풀에서 블록이 생성되면 풀에 참가한 채굴자들끼리 각자의 배분 기준에 의해 보상액을 배분 받는 방식으로 채굴 시장이 운영된다.

작업 난이도는 무엇인가?

블록 해쉬가 특정 숫자보다 낮게 나올 때의 nonce 값을 찾아내는 것이 작업 증명이라고 했다. 작업 난이도는 nonce값 계산의 어려운 정도를 나타낸다. 작업 난이도는 블록 헤더 정보에서 bits라는 값으로 조절된다.

앞에서 블록 해쉬는 32바이트의 숫자라고 했는데, 이해를 쉽게 하기 위해 블록 해쉬를 부호 없는 1바이트의 숫자라고 해보자. 그럼 1바이트의 숫자값을 블록 해쉬값으로 산출하는 해쉬 함수는 0 ~ 255 사이의 값을 결과로 산출한다.

블록 해쉬가 128보다 작아야 한다고 하면, 0 ~ 255 사이의 값을 산출하는 해쉬 함수를 적용해서 128보다 작은 블록 해쉬값이 나올 확률은 128보다 작은 수(0~127)의 개수 = 128/해쉬 함수가 산출할 수 있는 모든 값(0~255)의 개수 = 256, 즉, 128/256이므로, 50%의 확률이다.

블록 해쉬가 64보다 작아야 한다면 64/256, 즉 25%의 확률로 nonce 값을 구할 수 있다. 블록 해쉬가 32보다 작아야 한다면 확률은 12.5%로 줄어든다. 여기서 128, 64, 32라는 특정 숫자가 바로 블록 헤더 정보의 bits이다.

실제로 bits의 값이 128, 256 이런 식으로 저장되지는 않고, 지수와 계수를 사용하는 별도의 표현 방식이 있다.

난이도는 2,160개의 블록이 생성되는데 소요되는 시간이 평균 시간인 21,600분(10분/블록 * 2,160블록)보다 오래 걸리면 낮아지고, 적게 걸리면 높아지는 방식으로, 대략 21,600분을 주기로 전체적으로 평균 10분이 소요되는 하나의 난이도가 전체에 적용된다. 따라서 채굴자가 늘어나서 블록을 생성하는데 소요되는 시간이 줄어들게 되면, 정해진 주기에 따라 난이도가 높아져서 결국에는 평균적으로 10분이 소요되게 된다.

블록 헤더의 bits는 nonce 값을 계산하는데 기준이 되는 특정 숫자를 나타내며,

블록체인 전체에 걸쳐 일률적으로 적용되는 숫자다.

비트코인 지갑에도 블록 체인의 모든 거래 정보가 저장되나?

비트코인 지갑은 송수금 거래를 가능하게 해주는 클라이언트 소프트웨어이며, 거래 정보를 블록 체인 네트워크에 전파해야 하므로 블록 체인 네트워크의 노드이기도 하다. 하지만, 지갑은 작업 증명 계산을 하지 않기 때문에 블록 체인의 모든 거래 정보를 저장할 필요가 없고, 저장하지도 않는다. 블록 체인 네트워크에 참여하는 노드는 여러 종류가 있으며, 지갑에는 블록 체인의 전체 거래 정보가 저장되지는 않는다.

채굴 보상이 줄어든다면 채굴에 의해 유지되는 블록 체인이 지속될 수 있나?

비트코인을 예로 들면 채굴 보상은 비트코인으로 지급되며, 지급되는 양은 비트코인 기준으로는 줄어들지만 비트코인 자체의 가치가 늘어난다면 보상 자체가 줄어드는 것은 아니다. 몇 년전에 채굴 보상이 50BTC 이고 현재 보상이 12.5BTC로 1/4로 줄었다고 하더라도, 비트코인 자체의 가치는 4배가 훨씬 넘게 증가했기 때문에 실질 보상액은 오히려 늘어났다고 볼 수 있다.

이처럼 디플레이션 화폐라는 비트코인의 특징은 비트코인의 가격을 높이는 중요한 요인이기도 하다. 하지만 신규 발행 비트코인이 0이 되는 시점(약 2100년 이후)에는 어떤 모습일까? 결국 남는 보상은 수수료뿐이므로 수수료를 극대화 하거나 소요 비용을 낮추는 쪽으로 흘러갈 것이다.

결국 블록 사이즈를 키워서 블록 안에 더 많은 거래를 담아서 수수료 수입을 높이거나, 난이도를 낮춰서 채굴 비용을 낮추는 방식 또는 둘의 조합으로 전개될 가능성이 높다.

블록체인에 대한 개념과 사토시 나카모토에 대한 이야기를 할 때 빠지지 않는 코스로 잠깐이라도 비잔틴 장군 문제 혹은 비잔티움 장군 문제, 비잔틴 오류 허용 에 대해 이야기가 나오곤 한다. 그런데 대부분 '아 무언가 신뢰할 수 없는 환경에서 어려운 문제를 풀게 해서 신뢰를 얻어내게 했구나' 정도로 이해하면 다행이고, 설명하는 사람들 조차도 어떤 과정으로 그러한 신뢰를 갖게 하였는지에 대해서는 상세히 모르고 설명하는 케이스가 대부분이었다. 

아래는 PoW

▌두 장군의 문제

불확실한 통신 상황에서 상호 동기화를 시도할 때의 문제점으로,
이건 중간의 통신 장애가 있는 경우 어떻게 공격 시간을 전달할것인가라는 문제이며 아직까지 이에 대한 해결방안은 나오지 않았다.

위 처럼 장군 A1, A2 는 가운데 B 에 있는 적들을 공격해야 하는데, 동시 공격하지 않으면 이길 수 없는 상황이다.
A1, A2 사이를 흐르는 강이 있으나, 건너는 경우 연락병이 포획될 가능성이 있다.
이러한 상황에서, A1, A2 는 시간을 합의하고 상호 확답을 받아야 한다. 

이 문제는 비잔틴 장군의 문제와는 다른 문제이다. 


▌비잔틴 장애허용, 비잔틴 장군 문제

300명의 병력이 있는 비잔틴 성을 100명씩의 병력을 가진 장군 5명이 치려함
이기려면 적 병력보다 많은 병력이 공격해야 함
장군들은 연락병을 보내 소통 가능함
장군들 중에는 배신자가 있어 서로 신뢰 불가함

문제) 서로 신뢰할 수 없는데, 어떻게 공격 시각을 합의할 수 있는가? 
     . 가정 1 - 공격 시각은 상관 없음. 모두 한번에 공격할 수 있는 합의된 시각이면 됨
     . 가정 2 - 배신자는 병력을 분산시키려 하므로 이전 장군의 메시지와 다른 시각을 제시함
     . 가정 3 - 각 장군은 자신의 바로 다음 장군에게만 연락할 수 있음
     . 가정 4 - 배신자도 지정한 시간에 공격에 가담 함


<<문제 생기는 상황>>
  1. @장군1 이 "9 AM" 공격시각을 적어 서명과 함께 @장군2 에게 보냄 
  2. @장군2 는 @장군1 의 "9 AM" 공격시각을 보고 기억한 후 @장군3 에게 전달함
  3. @장군3 은 배신자로, "9 AM" 메시지를 찢어버리고 "8 AM" 으로 고쳐서 @장군 4에게 전달함
  4. @장군4 는 "8AM" 을 기억한 후 @장군5 에게 전달
  5. @장군5 는 "8AM" 을 기억
  6. 8AM 에 @장군3, @장군4, @장군5 가 쳐들어가지만 300명이므로 지게 됨

<<PoW 블록체인 해결>>
[새로운 규칙]
A. 장군은 메시지를 보내기 위해 반드시 10분의 시간을 들여야 함
B. 메시지는 모든 이전 장군의 메시지와 10분의 시간을 들였다는 증거를 포함하여 보내야 함

  1. @장군1 이 "9 AM" 공격시각을 적어 10분간 작업하여 증거와 함께 @장군2 에게 보냄
  2. @장군2 는 "9 AM" 메시지와 @장군1 의 10분 작업 증거를 보고 확신 후, @장군3 에게 "9 AM" 메시지 보냄
    1. @장군2 도 10분 간 작업 함
    2. @장군1, @장군2 의 메시지와 작업내용 모두를 포함하여 보냄
  3. @장군3 은 배신자로 "8AM" 으로 메시지를 수정하여 보내고 싶으나 그냥 보낼 수 없음. 아래 중 택 1 해야 함.
    1. 속일 수 있는 유일한 방법
      1. @장군3 은 10분보다 빠르게 작업을 하여 "8 AM" 메시지를 만들어 냄
      2. @장군1, @장군2 의 총 20분 작업에 해당하는 메시지 모두를 남은 시간 내에 만들어 포함 시켜 보냄
    2. 안들키고 있으려면 "9 AM" 으로 보내기
  4. @장군4, @장군5 모두 동일

[의미]
  1. 만약 @장군3 이 "8AM" 으로 보냈다면, 모두 장군 3이 거짓임을 알게 됨
  2. 만약 @장군2 가 배신자였다면 @장군2,3,4,5 모두 9AM 공격 가므로 이기게 됨
  3. 만약 @장군1 이 배신자였다면 8AM 에 모두 함께 공격 가므로 이기게 됨


비잔틴 장군의 문제를 PoW 를 통한 블록체인 기술로 해결 한 예시이다. 블록체인은 "이전 메시지를 포함" 시킨 새로운 메시지로 이해할 수 있으며, 포함된 이전 메지시를 변경한 경우 변경(위조)되었다는 사실을 바로 알 수 있는 장부이다. PoW 는 "10분의 시간을 들여 메시지를 만드는 과정" 이며, 정말 그러한 작업을 하였는지를 단번에 검증해낼 수 있는 방법을 제공한다.
그러나 10분의 시간을 들이지 않고는 절대 새로운 메시지에 포함된 이전 메시지를 다시 만들어낼 수는 없는 알고리즘이며, 비잔틴 장군의 문제 해결에 가장 핵심이 되는 메커니즘인 것이다. 



출처: http://goodjoon.tistory.com/256 [Good Joon]

+ Recent posts