자바로 블록체인 만들기 part 2

김성재
CAU_CLink
Published in
10 min readAug 20, 2018

원문 : https://medium.com/programmers-blockchain/creating-your-first-blockchain-with-java-part-2-transactions-2cdac335e0ce

원저자 : Kass, kassCrypto@gmail.com

part2 에서는 앞서 소개했듯이

  1. 지갑을 만들고
  2. 트랜잭션을 발생 시킨다.

part2를 시작하기 전에 필요한 준비물

  1. part 1을 본다.
  2. Bouncycastle 라이브러리를 추가한다. Bouncycastle 라이브러리는 다양한 암호화 알고리즘을 사용할 수 있는 라이브러리이다.

Preparing a Wallet

기본적으로 지갑은 주소(개인키, 공캐키)를 가지고 있으며 개인키와 공개키의 존재 의미는 존재 증명과 식별(identify)을 위해 존재하며 넓은 의미로 디지털 서명이라고 불린다. 블록체인(비트코인)에서 의미하는 지갑은 주소를 저장하고 트랜잭션을 생성할 수 있는 소프트웨어이다.

지갑의 소유주를 구분하기 위해 키는 중요하다. 공개키와 개인키를 가지는 지갑을 만들어 보자.

블록체인에서 공개키, 개인키의 의미는 트랜잭션의 검증수단이다. 트랜잭션이 생성되고 처리되는 과정에서 개인키로 트랜잭션에 서명을하고 공개키로 올바른 서명인지 판단하게 된다.

위의 그림을 보면 쉽게 이해할 수 있을 것이다. 트랜잭션을 일으키는 지갑이 본인의 개인키로 디지털 서명을 만들어내고(암호화 하고), 공개키로 해당 트랜잭션을 복호화 화여 유효한 트랜잭션인지 확인한다.

이제 공개키, 개인키 쌍을 만들어 보자. 보통 이 키 쌍을 가르켜 KeyPair라고 한다. 이 키 쌍을 만드는데에는 타원 곡선 암호(Elliptic-curve cryptography) 알고리즘이 쓰인다. 이 알고리즘을 이용해서 키페어를 만드는 코드를 구현해 보자.

타원 곡선 암호 알고리즘은 이름부터 범상치 않다. 게다가 코드 또한 익숙치 않다. 하지만 우리가 기억해야 하는 것은 단지 타원 곡선 암호 알고리즘을 이용해서 공개키와 개인키를 만들어 낸다는 것이다.

Transaction & Signatures

이제 지갑의 기본적인 뼈대는 완성되었고, 트랜잭션과 서명을 구현할 차례이다. 그 전에 트랜잭션에 어떤 데이터가 들어갈 지 알아보자.

  • 송신자의 공개키(주소)
  • 수신자의 공개키(주소)
  • 전달 할 금액
  • Input (수신자가 보낼 돈을 가지고 있다는 것을 증명 할 이전 트랜잭션 참조값)
  • Output (거래에서 받은 관련 주소 금액 -> 다음에 일어날 새 트랜잭션의 Input으로 사용됨)
  • 암호화된 서명(Cryptographic signature) : 주소의 소유자(송신자)가 데이터가 변경되지 않았음을 증명하는 서명(ex> 제 3자가 임의로 금액을 바꾸지 못하게)

이제 Transaction 클래스를 만들자.

ArrayList에 TransactionInput과 TransactionOutput이 있다. 이건 조금 이따가 자세히 알아보도록 하고 일단 서명(Signatures)의 목적과 이것이 어떻게 작용하는지 알아보는게 우선이다.

서명은 우리가 구현하고 있는 블록체인에서 중요한 두 가지 역할을 하게 된다.

  1. 돈(코인)의 주인만이 돈(코인)을 보낼 수 있다.
  2. 공격자로부터 새로운 블록이 채굴되기 전에 트랜잭션이 위변조 되는 것을 막는다.

개인키는 데이터를 서명하는데 쓰이고, 공개키는 데이터의 무결성을 확인하는데 쓰인다.

이해를 돕기 위해 간단한 예시를 들어서 확인해 보자.

  1. Bob은 Sally에게 2개의 Noob 코인을 보내고 싶어 한다.

2. 지갑은 이 트랜잭션을 생성하고 채굴자에게 트랜잭션을 다음 블록에 붙여달라고 보낸다.

3. 악의적인 채굴자는 Sally가 아닌 John에게 2 개의 Noob 코인을 보내는 걸로 수취인을 바꿔버린다.

4. 하지만, Bob은 자신의 개인키로 데이터를 서명했고 그 데이터는 Bob의 공개키로 데이터의 위, 변조를 식별할 수 있다.

5. 다른사람의 공개키로는 Bob의 개인키로 암호화 된 데이터를 확인할 수 없고, 거래는 무사히 완료된다.

이제 이것을 구현해 볼 차례이다. 이 전의 Transaction 클래스에서 signatures는 byte형의 배열이었다. signature를 생성하기위한 헬퍼 메서드를 StringUtil 클래스 안에 구현하자.

무언가 머리가 아파오는 코드들이다. 위의 메서드들을 완벽하게 이해하지 않아도 된다. 단지 각각 메서드의 역할만 이해하고 있으면 된다. applyECDSASig는 송신자가 자신의 개인키를 이용해 데이터를 암호화하는 메서드, verifyECDSASig는 암호화된 데이터를 송신자의 공개키로 디코딩하여 데이터의 무결성을 식별하는 메서드, getStringFromKey는 타원 곡선 알고리즘으로 생성된 Key(Byte)를 보기 좋게 String으로 반환해 주는 메서드이다.

이제 Transaction 클래스에 위의 메서드를 이용하는 generateSignature, verifySignature라는 메서드를 추가한다.

NoobChain에서 서명에 필요한 데이터는 송신자의 공개키, 수신자의 공개키, 그리고 송금량이다.

실제 비트코인에서의 signature 메커니즘은 이와 조금 다르다.

Signatures는 채굴자들에 의해서 검증된다.

Testing the Wallets and Signatures

이제 NoobChain 클래스에서 이때까지 해왔던 작업을 확인해 보자. 몇 가지의 변수와 main의 내용이 추가되었다.

결과값

Input & Outputs 1 : How crypto currency is owned…

앞부분에서 트랜잭션의 input과 output에 대해 언급했다. 이것의 의미는 무엇일까? 우리가 지갑이라고 하지만 실제로는 지갑에 코인을 직접적으로 더하거나 빼면서 돈을 쌓아 놓지는 않는다. 트랜잭션의 결과로 생성된 특정 output을 추적해서 본인이 쓸 수 있는 코인을 알아낸다.

앞에서 Bob과 Sally로 예시를 들었다. Bob은 Sally에게 2 Noob 코인을 보낼 때, Bob의 지갑에는 2 코인이 있는 것이 아니라 Bob의 이 전 트랜잭션의 output이 쌓여 있었던 것이다. Bob의 지갑은 트랜잭션의 output들을 추적해서 Bob이 2코인 만큼 보낼 코인이 있는지 확인하게 된다.

비트코인에서는 이 특수한 output을 가리켜 사용하지 않은 거래의 출력(Unspent Transaction Outputs)라고 하며 UTXO라고 줄여쓴다. 결국 새로운 트랜잭션을 일으킨다는 것은, UTXO를 추적하여 이전 거래의 output 값을 새로운 트랜잭션의 input으로 사용하겠다는 말이다.

트랜잭션의 로직을 도식화 한 그림이다. 새로운 거래는 이 전 거래의 output을 참조하고 있다.

이제 드디어 TransactionInput 클래스와 TransactionOutput 클래스를 구현해 보자.

트랜잭션의 output은 전송 된 최종 금액이 표시된다. 이것은 새로운 거래를 생성할 때 input이 참조하게 되면서 보낼 수 있는 코인이 있다는 것을 증명하게 된다.

Inputs & Outputs 2 : Processing the transaction…

한 블록에 여러 개의 트랜잭션이 모이게 되고, 시간이 지날수록 블록체인의 길이는 점점 길어지게 된다. 길어지면 길어질수록 UTXO를 찾는데 걸리는 시간 또한 길어진다. 이 문제를 해결하기 위해서 UTXO의 collection을 만들어주자.

비트코인 상에는 UTXO pool이 따로 있다.

HashMap 자료구조를 이용하여 UTXO를 다룰 것이다. <output id, output> 형식의 key-value form이 쓰인다.

다음으로는 트랜잭션을 처리하는 방식이다. 특정 트랜잭션이 유효한 트랜잭션인지, 무결한 트랜잭션인지 검증하는 과정이 있어야 한다. 앞서 공부했듯이 트랜잭션은 거래 송신자가 본인의 개인키를 가지고 데이터를 암호화 하고, 송신자의 공개키로만 풀 수 있는 구조다. 이 역할은 verifySignature 메서드가 하고 있고, 이제 거래에 필요한 다른 몇 가지 과정을 넣어 줄 차례이다. 이 과정을 Transaction 클래스 안의 processTransaction이라는 메서드를 통해서 이루어진다. 이 메서드의 과정을 유의 깊게 보도록 하자.

processTransaction 메서드에서 트랜잭션을 처리하는 순서는 다음과 같다.

트랜잭션 유효성 검사 -> 거래 할 UTXO가 충분한 지 -> 거래금과 잔금 반환 -> 쓰인 UTXO 삭제, 새로운 output 생성

트랜잭션을 처리했으니 이제 지갑에서 트랜잭션을 만들어 낼 차례이다. 보통 우리가 거래를 할 때에는 수중에 돈이 얼마 있는지 확인하고, 계산할 수 있는 돈이 있을 때 거래를 한다. 이 과정은 나의 UTXO들을 확인하는 절차이며 UTXOs(코드의 HashMap)의 list를 보면서 나의 UTXO가 있는지 확인하는 과정이 필요하다.

코드로 되어 있을 뿐, 우리가 실세계에서 거래를 하는 방식과 동일하다. getBalance 메서드로 쓸 수 있는 돈(코인)이 얼마 있는지 확인 후, sendFunds 메서드로 거래를 일으킨다.

Adding transactions to our blocks

트랜잭션을 구현하는 것은 완성되었고 이제 이것을 우리 블록체인 안에 넣기 위한 구현단계가 남아있다. 앞서 트랜잭션에 필요한 데이터들을 하나의 ArrayList로 넣었다. 만약에 하나의 블록 안에 많은 개수(ex> 1000개)의 트랜잭션이 들어가면 어떻게 될까? 당연히 블록의 크기는 커질 것이고, 특정 트랜잭션을 찾기위한 시간은 길어질 것이다. 이를 위해 머클트리 자료구조가 쓰인다.

머클트리의 주요 특징은

  1. 이진트리(binary tree)와 유사하다.
  2. 거래가 아무리 많아도 root node의 크기는 32비트이다.
  3. leaf node만이 transaction의 정보를 가진다.
  4. parent node는 child node들의 hash값을 합친 후 다시 hashing한 결과 값이다.

자세한 설명은 이것으로 대체하겠다.

출처 : 마스터링 비트코인

머클트리를 이용하기 위해 StringUtils 클래스에 다음과 같은 메서드를 추가하자.

다음으로, Block 클래스를 조금 바꿔보자.

생성자에서 data 변수를 없애고, hash를 계산하는데 merkle root를 추가했다. 그리고 Boolean 타입의 addTransaction 메서드를 추가했다. 이 메서드는 트랜잭션의 유효성을 검사하고 블록에 추가할 지 말지를 결정한다.

The Grand Finale(In the beginning there was noobchain)

이제 지갑에서 트랜잭션을 발생시키고, 블록에 포함되는 과정을 테스트해 볼 시간이다. 실제 비트코인 채굴과정에서 채굴자들이 어떤 트랜잭션을 블록에 포함 시킬지에 대한 기준이 있고 코인이 발행되는 메커니즘도 있다. 하지만 우리가 하고 있는 프로젝트에서는 첫 블록(genesis block)에서만 코인을 발행시키고, 하드코딩을 통해 그 과정을 알아보도록 하겠다.

결과값

이로서 part2가 마무리 되었다. 트랜잭션 발생, 지갑, 디지털 서명 체계, 머클트리 등 여러 개념을 배웠고 구현했다. 아직 miner와 수수료의 존재가 없지만 bitcoin-like한 블록체인의 큰 틀을 이론이 아닌 code로 구현해 볼 수 있다는 것에 큰 의미를 두고싶다.

part3는 아직 작성중이라고 하며 p2p networt와 database에 대해서 다룰 것이다.

--

--