-
Notifications
You must be signed in to change notification settings - Fork 0
5_거래생성및전파
비트코인 거래는 Transaction
**(이하 Tx라 표현함)**이라는 "어떤 비트코인 주소로부터 어떤 비트코인주소로 얼만큼의 금액을 보낸다."같은 정보가 담긱 거래 데이터를 생성하는 것 으로부터 모든것이 시작된다. 이러한 거래데이터는 작성될때 같은 주소로부터 두번 지불되는, 즉 이중지불문제를 해결하기위해 비트코인은 input, output, UTXO라는 개념을 만들어 내었고, 또 개인과 개인간의 거래뿐만이 아닌 기업과 기업간의 거래도 지원하기위해 스크립트형식의 인증수단을 도입하였다. 이 파트에서는 이에대한 개념을 가능한 쉽게 이해할 수 있도록 간략적이면서도 자세히 설명한다.
컴퓨터 관련분야에대한 경험이 있으면 당연한 내용이긴하지만 컴퓨터를 접한적없는 사람들을 위해 데이터 구조를 설명하는 표에대한 간단한 설명을 해보자 한다. 일단 컴퓨터는 기본적으로 멍청하고 이를 똑똑하게 하기위해서는 많은 컴퓨터 자원을 소모하게 되기 때문에, 컴퓨터상에 존재하는 거의 대부분의 데이터는 어떤 특정한 규격 에맞춰 작성된다. 이 규격이라는것은 대체로 이런식이다.
처음부터 4Byte는 이 데이터가 어떤 버전의 데이터 표준을 사용했는지 적어둔다.
그 다음 4Byte는 앞으로 몇 개의 문자가 적힐지에 대한 숫자를 적어둔다.
그 다음에는 위에서 밝힌 개수만큼 문자를 작성한다.
그리고 표준문서에서는 위와같은 내용과 그에대한 상세한 해설을 다 적어둔다. 하지만 위와같이 적어두면 보기 힘들고 한눈에 들어오지 않는다. 그래서 데이터에대한 표준을 작성할때 애용하는것이 바로 표 이다. 사실 정해진 규격은 없는데 대체로 다음과 같다.
크기 | 이름 | 타입 | 설명 |
---|---|---|---|
4Byte | 버전 | int | 버전에 대한 정보 |
4Byte | 문자카운터 | int | 앞으로 적힐 문자열의 길이 |
가변적 | 문자열 | string | 문자열 |
위에서부터 시작해 크기
만큼의 공간이 각 필드
를 위해 할당되어 있으며 각 필드를 타입
에 적힌 형식으로 읽어온다고 해석하면 된다.
int는 integer의 약자로 정수를 표현할때 사용되는 타입, 읽기 방식이며 string은 문자열을 표현할때 사용되는 타입이다. 타입은 int, string, char, double 처럼 관습적으로 정해진것도 있고 compactSize uint, TxIn, outpoint 처럼 비트코인 자체에서 새로 정의한 타입을 사용하는 경우도 있기 때문에 참고하고, 궁금하면 구글에 검색해보면 어떻게 구현된 타입인지 자세히 설명되어 있다.
상상해보자 B라는 비트코인 계좌에 현재 A로부터 입금되어 블록체인상에서 인정된 2BTC가 존재하며 B계좌의 소유주는 C계좌로 1.5BTC를 보내고 싶어한다.
graph LR
A-->|2BTC 송금완료|B_2BTC소유
B_2BTC소유-->|1.5BTC 송금예정|C
은행계좌의 경우 위를 구현하는 방법은 무지 간단하다. 일단 원래 A에 있던 2BTC를 차감하고 B에 2BTC를 추가하며, 나중에 B로부터 요청이 들어오면 B에서 1.5BTC를 빼고 C에 1.5BTC를 추가하면 된다. 이러한 방법은 일반적으로 많이 사용되는 방법이며 비트코인 다음으로 (혹은 비트코인보다)규모가 큰 이더리움 또한 이러한 방식을 채용했다. 하지만 비트코인이 선택한 방법은 이러한 방법이 아닌 송금된 돈을 입력으로 다른계좌로 출력하는 방법이다. 즉, B계좌로부터 송금을할 때 A계좌로 부터 온 돈을 입력으로 원래 송금 예정인 1.5BTC를 C로 출력하고 남은 0.5BTC를 거스름돈으로써 다시 B로 출력하는 방법이다.
graph TD
subgraph Transaction0
Tx0_in["Tx0: ...(생략)..."]-->Tx0_out[2BTC를 A로 보냄]
end
Tx0_out-.->Tx1_in
Tx1_out-.->Tx2_in
subgraph Transaction1
Tx1_in["Tx1: Tx0에서 A가 받은 돈 2BTC중"]-->Tx1_out[2BTC를 B로 보냄]
end
subgraph Transaction2
Tx2_in["Tx2: Tx1에서 B가 받은 돈 2TC중"]
Tx2_in-->Tx2_out1["1.5BTC를 C로 보냄"]
Tx2_in-->Tx2_out2["0.5BTC를 B로 보냄"]
end
즉, 위 그림에서 Tx1에는 B에 2BTC를 A로 부터 받았다는 기록이 있고, Tx2에는 Tx1에서 자신(B)에게 출력된 2BTC를 입력으로 다시 C에게 1.5BTC, B에게 0.5BTC를 출력하는 것이다. 이렇게 출력된 값은 추후 다른 Tx에서 입력값으로 사용되며, 출력은 되었으나 입력으로 사용된적이 없는 출력값 (즉 언젠간 사용 가능한 값)을 UTXO라고 한다.
UTXO란 Unspent Transaction Output의 약자로 번역하면 소비되지 않은 출력값정도의 의미를 가진다. 이러한 UTXO의 기억할만한 특징중 하나는 "절대로 쪼개지지 않는다."이다. 이에대해 자세히 설명하기 전에 다음 예시를 한번 보도록 하자. C주소가 B와 A주소로부터 각각 0.5BTC를 받아 1BTC가 된 상황에서 D주소로 0.8BTC를 송금하려고 한다 어떻게 해야할까?
graph LR
subgraph Transaction1
Tx1["Tx1: Tx?에서 A가 받은 돈 중"]-->C0[0.5BTC를 C로 보냄]
end
subgraph Transaction2
Tx2["Tx2: Tx?에서 B가 받은 돈 중"]-->C1[0.5BTC를 C로 보냄]
end
정답은 다음과 같다.
graph LR
subgraph Transaction3
Tx3["Tx1에서 C가 받은 돈"]-->C[0.5+0.5=1BTC 중]
Tx2["Tx2에서 C가 받은 돈"]-->C
subgraph 입력
Tx3
Tx2
end
subgraph 출력
D
E
end
C-->D[0.8BTC를 D로 보냄]
C-->E[0.2BTC를 C로 보냄]
end
Tx3가 발생하기 전 C에게는 Tx1와 Tx2로부터 각각 0.5BTC의 UTXO가 있었고 이 둘의 입력값(=1BTC)를 통해 D에게 0.8BTC를 출력하고 0.2BTC를 다시 자신의 계좌로 반환하는 상황이다. 여기서 (사실 첫 예시로도 설명 가능하지만) 알 수 있는 사실은 UTXO는 본래 그 상태로는 절대로 쪼개지지 않아 0.8BTC만 필요한 상황에서도 0.5BTC의 값을 가지는 UTXO 두개를 입력으로 받아와 1BTC로 만든 뒤에야 이를 쪼개 0.8BTC와 0.2BTC로 출력 가능하다는 것이다. 이는 바이트단위로 수수료를 매기는 비트코인 정책상 사용자 입장에서는 매우 중요한 사항인데, 만약 0.01BTC를 꾸준히 지급받은 어떤 계좌로 1BTC를 송금하는 상황이 생긴다면 100개의 0.01BTC의 값을 가지는 UTXO를 입력으로 써야되며 하나의 입력당 약 180Byte의 공간을 차지하게되어 최소 18KB이상의 크기를 가지게되어 결국 엄청난 양의 수수료를 지불하게 될것이니 이부분은 사용자입장에서 주의해야 할 사항이다.
비트코인에서는 거래가 발생할때마다 수수료를 지불하며, 해당 수수료는 채굴자가 가져가게된다. 이러한 채굴자에게의 수수료 지불은 채굴자들이 비트코인 네트워크를 계속 유지하게 하는 동기로 작용하며, 수수료를 남보다 많이 지불하면 채굴자는 남보다 먼저 블록체인에 포함시켜주려고 노력하게되니 너무 아까워 하지는 말자. 하지만 위에 보여준 과정에서 수수료를 지불하는 과정은 없었다. 그럼 이 수수료는 어떻게 지불되는 것일까?
일단 위 과정에서 입력값에서 쓰고남은 출력값은 자신의 계좌로 출력하는 과정을 확인할 수 있다. 만약 이 과정이 존재하지 않으면 어떻게 될까? 그럼 입력값이 출력값보다 큰 상황이 발생할 것이다. 첫번째의 경우 2BTC가 입력되고 1.5BTC만 출력되며 두번째의 경우 1BTC가 입력되고 0.8BTC만 출력되는 것이다. 그럼 그 차액은? 바로 이것이 수수료이다. 비트코인에서 수수료는 각 거래에서 입력값의 총합과 출력값의 총합의 차이만큼이다. 이러한 구조는 사용자로 하여끔 주의하게끔 하게 되는데 만약 10BTC 의 UTXO를 사용하여 다른 계좌로 1BTC를 지불할때 제대로 자신의 계좌로 출력되게 하지 않으면 나머지 9BTC는 그대로 채굴자에게 수수료로 지불되는 것이다! 반대로 10BTC를 입력으로 쓰고 총 출력값이 10BTC가 되면 수수료는 하나도 지불하지 않은 것이 되며, 대부분의 채굴자들이 최후의 우선순위로 둘것이기에 Tx이 블록체인에 포함되기까지 아주 오랜시간 걸리거나, 어쩌면 영원이 포함 안될수도 있다.
하지만 너무 걱정하지는 말자. 대부분의 비트코인 지갑이 수수료의 적당량까지 계산해서 알아서 지불금액과 수수료를 빼고 남은금액이 자신의 계좌로 들어올 수 있게 해준다.
Tx데이터가 실제 어떻게 구성되어있는지 확인해 보자.
크기 | 이름 | 타입 | 설명 |
---|---|---|---|
4Byte | Version | uint32_t | Tx 표준 버전을 의미하며 2017년 기준 버전 1 을 사용하고 있다. |
1~9Byte | tx_in count | compactSize uint | 입력에 몇개의 UTXO가 오는지 표시한다. 비트코인에서 만든 compactSize uint라는 타입을 사용한다. |
가변적 | tx_in | txIn | 과거의 Tx에서 사용되지 않은 출력값(UTXO)의 정보가 담겨있다. |
1~9Byte | tx_out count | compactSize uint | 출력에 몇개의 UTXO가 오는지 표시한다. |
가변적 | tx_out | txOut | 이 Tx에 오는 입력값들을 어떤 주소로 얼만큼 출력할지에대한 정보가 담겨있다. |
4Byte | lock_time | uint32_t | 잠금시간이라 부르며 여기 정해진 시간 이후에, 혹은 여기 저정해진 몇번쨰 블록이후로 블록체인에 포함되게 할 수 있다. 일반적으로 잘 사용은 안하며 보통 0이다. |
이 중 TxIn은 다음과 같은 양식을 가진다.
크기 | 이름 | 타입 | 설명 |
---|---|---|---|
36Byte | previous_output | outpoint | 입력에 사용될 Tx의 SHA256d의 값과 색인번호에 대한 정보가 담겨있다. |
1~9Byte | script bytes | compactSize uint | signature script의 크기를 Byte단위로 나타낸다. 최대 10,000까지만 입력가능 |
가변적 | signature script | char[] | 입력 스크립트. 추후 설명 |
4Byte | sequence | uint32_t | 이 거래의 일련번호정도로 생각하면 된다. 잠금시간이 0이 아닌 Tx가 블록체인에 포함되기전 해당 거래를 취소시키기 위해 존재한다. 안쓰는경우 0xFFFFFFFF의 값을 가진다. |
비트코인은 각각의 Tx의 고유번호(지문)를 표시하기위해 해당 거래 Tx 전체를 SHA2에 넣고 두번 돌린것, 즉 SHA2(SHA2(Tx))의 값을 쓴다. 이렇게 SHA2를 두번 돌린것을 SHA256d라 한다. 다만 2017년기준 2억개를 돌파한 비트코인 Tx들을 SHA256d 해시값만으로 찾아내기에는 시간이 오래걸리니깐 (물론 해시테이블이라는 단어가 있을정도로 색인하면 충분히 빠르긴 하지만) 32Byte는 해시값을 나타내고 나머지 4Byte는 uint32 형식으로 첫 거래부터 0으로 시작해서 하나하나 번호를 붙혀 탄생한 index값에대한 정보가 담겨있다.
다음으로 TxOut은 다음과 같은 양식을 가진다.
크기 | 이름 | 타입 | 설명 |
---|---|---|---|
8Byte | value | int64_t | 지불될 비트코인 양을 사토시단위로 나타낸 값 |
1~9Byte | pk_script bytes | compactSize uint | pk_script의 크기를 Byte단위로 나타낸다. 최대 10,000까지만 입력가능 |
가변적 | pk_script | char[] | 출력 스크립트, 추후 설명 |
입력-출력 스크립트의 경우 아마 다른경로를 통해 비트코인을 공부한적이 있다면 잠금-해체 스크립트, 서명-공개키 스크립트 등의 용어를 들어본적 있을것이다. 본래 서명-공개키 스크립트가 본래 명칭이긴 하지만, 현재 많은 실시간으로 블록체인의 상태를 보여주는 사이트들이 각각을Transaction의 입력(input)부분에 존재해서 입력(input) 스크립트
, 출력(output)부분에 존재해서 출력(output) 스크립트
라 부르고있기에 이 책에서도 혼란을 줄이기 위해 입력 스크립트
와 출력 스크립트
의 용어를 사용하겠다.
이 입력-출력 스크립트는 간단히 말해서 전자서명과 관련해서 해당 거래가 제대로 되었음을 증명해주는 과정을 보여주는 "스크립트"란 프로그래밍 언어로 작성된 소스코드이다. 이것이 정확히 어떻게 증명을 해주는지 살펴보기전에 먼저 비트코인 내에서 그냥 "스크립트"라 불리는 이 언어가 어떤구조로 작동하는지 알아보자.
스크립트는 아주 초기형태의 프로그래밍 언어와 작동구조가 매우 유사하다. 일단 초기 프로그래밍언어에서나 쓰던 후위 연산자(혹은 역폴란드식 표기법이라고도 함)를 사용하며 Stack기반으로 실행된다는 점에서 1970년대에 개발된 Forth라는 언어와 많이 닮았다고 한다. 다만, 이는 필자가 태어나기도 전의 일이므로 그 분류에 대해서는 자세히 다루지 않겠다. 중요한점은 이 언어가 "스택"을 적극적으로 활용하는 구조라는 것이다.
스택이란?
컴퓨터 자료구조를 배우면 항상 몇몇가는 수십년째 지치지도 않고 항상 조금도 바뀌지 않고 그대로 나오는데 그중 하나가 스택(LIFO라고도 불린다.)이란 것이다. 그 개념은 꽤나 단순하다. 프링글스를 아는가? 길다란 통속에 감자칩이 차곡차곡 쌓여있는 과자인데 그러한 길다란 통을 생각해보자. 이 프링글스 통은 위쪽으로만 집어넣을 수 있으며 꺼낼때는 항상 마지막으로 넣은것부터 꺼낼 수 있다. 이게 바로 스택이라는 자료구조이다. 어떤 공간에 데이터가 들어갈 수 있는데 꺼낼때는 마지막으로 들어간것부터 순서대로 꺼낼 수 밖에 없는 것이다. 물론 컴퓨터는 모든게 가상에서 이뤄지니 넣을떄도 마음대로 꺼낼때도 마음대로를 구현할 수 있지만 이러한 스택구조는 컴퓨터의 자원을 적게 잡아먹으며 이러한 구조를 사용하므로 이득인 상황이 꽤나 있어서 지금도 그 중요성을 인정받고 있다.
일단 다음은 "3+7==10"(3+7은 10이랑 같은가?) 라는 내용을 "스크립트"로 작성했을때 나타나는 형태이다.
3 7 ADD 10 EQUAL
이 언어는 왼쪽부터 오른쪽으로 차례대로 실행되며 아래 표는 각 데이터가 어떻게 처리되는지를 보여준다.
스택 상황 | 입력되는 데이터 | 남은데이터 | 설명 |
---|---|---|---|
3 | 3 | 7 ADD 10 EQUAL | 3을 그대로 넣는다 |
3, 7 | 7 | ADD 10 EQUAL | 7을 그대로 넣는다 |
10 | ADD | 10 EQUAL | Stack으로부터 두개의 데이터를 꺼낸뒤 더하고, 더한값을 Stack에 넣는다. |
10, 10 | 10 | EQUAL | 10을 그대로 넣는다. |
True | EQUAL | Stack으로부터 두개의 데이터를 꺼낸뒤 같은지 비교한다. 같으면 True를 넣는다. |
이러한 "스크립트" 언어에서 ADD나 EQUAL같이 사용가능한 명령어는 "https://en.bitcoin.in/wiki/Script"에서 확인 가능하다. 단, 이러한 사용가능한 명령어 중 반복문같은 일반적인 프로그래밍 언어에서는 필수적으로 있는 요소가 빠져있는 사실을 확인할 수 있는데, 덕분에 이 언어는 "튜링 불완전성"을 띈다. 이는 의도된 사항인데, 가장 간단한 예로 만약 누군가 무한루프가 반복되는 스크립트 언어를 Tx안에 포함시켜서 배포하게 되면 해당 Tx이 정당한건지 검증하는 컴퓨터들은 무한루프속에서 헤어나오지 못하게 될것이고, 이는 비트코인에게 있어 바람직한 현상은 아닌 것이다. 이 때문에 비트코인은 오류를 일으킬 가능성이 있는 명령어는 미리 비활성화 시켜놨다고 한다. 덕분에 비트코인의 "스크립트"를 사용해 논리오류를 일으킬수는 없다고 한다.
한편, 비트코인의 "스크립트"와 유사하게 이더리움에는 "솔리디티"가 존재한다. 솔리디티의 경우 비트코인과는 다르게 튜링 완전한 언어이며 이더리움의 고유한 특징인 "스마트 컨트랙"을 구현하기 위해 존재한다. 이 스마트 컨트랙을통해 이더리움은 블록체인을 메모리처럼 사용해 어떤 프로그램을 실행시키고 현실에서 자동이체나 수익분배같은 다양한 작업을 프로그래머의 생각대로 자동으로 실행될 수 있게 하였다. 또한 프로그램을 실행할때 한번의 명령어당 수수료를 부과하는 방식으로 무한루프의 폐해를 이겨냈지만, 자유도가 높다는것은 그만큼 취약점이 존재할 수 있다는것. 결국 2016년 중순 이러한 취약점을 이용해 이더리움에서 이더리움 자체의 버그를 이용해 부정행위가 일어났고 이 일은 이더리움 하드포크 사태까지 일어나게 하였다.
아직 이더리움과 같은 스마트 컨트랙이 과연 비트코인에 있어 적합한가 아닌가는 모른다. 다만, 필자 개인적으로 생각하기에는 은행에서 자동이체는 해주지만 돈 자체에 자동이체 기능이 있는것은 아닌것처럼, 스마트 컨트랙과 같은 기능은 신뢰할만한 제 3자가 대행해주고 비트코인은 화폐 그 자체로 남아있는게 좋지않나? 싶다.
비트코인에서는 이런식으로 마지막까지 코드가 실행 된 후 True가 남아있으면 해당 "스크립트"는 "유효한"것이라 판단한다.
물론 "스크립트"는 모든것은 아니어도 어느정도 자유로운 프로그래밍이 가능하지만, 중요한건 이걸 검증하는 채굴자가 마음대로 작성된 코드를 받아들일지 말지에 대한 결정권을 가지고 있다는 것이다. (위와같은 3 7 ADD 10 EQUAL
로 거래가 성사되버리면 심히 곤란하다!)
일반적으로 임의로 작성된 코드는 블록체인에 포함되도록 허용되지 않고 "표준거래"로 명시된 거래들만 제대로 인정되며 블록체인에 포함되고 있다. 표준거래에는 일단 가장 많이 사용되는 P2PKH(Pay to Public Key Hash, 비트코인 주소에 지불), 다중서명을 필요로하는 경우를 위한 P2SH(Pay to Script Hash), P2PKH와 P2SH에 의해 대체되어 잘 사용되지 않는 P2PK(Pay to Public Key)와 MultiSig 그리고 앞으로 추가될 가능성이 높은 P2WPKH(Pay to Witness Public Key Hash), P2WSH(Pay to Witness Script Hash) 등이 있다.
필자는 P2PKH만 알면 나머지는 비트코인 공식 문서를 확인하면 이해하는데 크게 어려움이 없을것으로 생각하므로, 이 책에서는 P2PKH가 정확히 어떻게 작동하는지, P2SH는 어떨때 사용하는지, 앞으로 추가될 P2WPKH는 이 책 후반부에서 다루도록 하겠다.
P2PKH란 Pay to Public Key Hash의 약자로 언어 그대로 해석하면 Public Key Hash에 지불하겠다는 의미이다. 일단 Public Key Hash란 우리가 앞에서 비트코인 주소 생성과정에서 나온 **RIPEMD160(SHA2(Public Key))**이와같은 함수를 사용해 나온 존재이다. 이를 Base58Check인코딩으로 표현하면 우리가 사용하는 비트코인 주소가 되는만큼 사실상 P2PKH는 비트코인 주소로 지불하겠다는 뜻으로 받아들이면 된다. 그럼 이 P2PKH는 어떻게 작동하는 것일까?
일단 P2PKH를 포함한 모든 표준거래는 크게 입력 스크립트
부분과 출력 스크립트
부분으로 나눠진다. 앞서 언급한 것처럼 출력 스크립트
는 Transaction의 출력부에 기록되어 있으며 입력 스크립트
는 Transaction의 입력부에 기록되어 있다. 이떄 P2PKH에 기록된 내용은 다음과 같다.
입력 스크립트
: <sig> <Public Key>
출력 스크립트
: DUP HASH160 <Public Key Hash> EQUALVERIFY CEHCKSIG
여기서
<sig>
는 해당 계좌의 소유주가 비밀키로 암호화한 값이며 (Signature의 약자)
<Public Key>
는<sig>
를 복호화할 공개키이다.
비트코인에서는 입력 스크립트
를 먼저 실행한 뒤 출력 스크립트
를 실행시켜 해당 거래의 유효성을 판단하며, 여기서 입력 스크립트
에서 주어진 정보로 TxIn에 명시된 정보가 올바른 정보인지 검증 하며, 그 후 출력 스크립트
는 주어진 공개키가 해당 비트코인 주소의 소유주인지 확인을 하게된다.
자세한 작동 순서는 다음 예시를 살펴보자. 다음 상황은 B가 A로부터 받은 2BTC를 그대로 C로 송금하는 상황이다.
graph LR
subgraph Tx2
subgraph 입력
B
end
subgraph 출력
C
end
B-->|2BTC|C
end
B_in-.-B
subgraph Tx1
subgraph 입력
A
end
subgraph 출력
B_in
end
A-->|2BTC|B_in[B]
end
먼저 Tx1은 주소A의 소유주가 작성한 부분이며 출력 스크립트
부분에는 DUP HASH160 <B의 Public Key Hash> EQUALVERIFY CEHCKSIG
와 같은 데이터가 담겨있어 <B의 Public Key Hash>
, 즉 B의 비트코인 주소로 돈을 송금한다는 정보가 담겨있다. 그 후 주소B의 소유주가 작성한 Tx2의 입력 스크립트
부분에는 <sig> <B의 Public Key>
의 정보가 담겨있다.
이 두 데이터를 합치면 <sig> <B의 Public Key> DUP HASH160 <B의 Public Key Hash> EQUALVERIFY CEHCKSIG
가 탄생하게 되는데 이를 실행시켜보면 다음 표와같이 나타난다.
스택 상황 | 입력되는 데이터 | 남은데이터 | 설명 |
---|---|---|---|
<sig> |
<sig> |
<B의 Public Key> DUP HASH160 <B의 Public Key Hash> EQUALVERIFY CEHCKSIG
|
<sig> 를 그대로 넣는다. |
<sig> <B의 Public Key>
|
<B의 Public Key> |
DUP HASH160 <B의 Public Key Hash> EQUALVERIFY CEHCKSIG
|
<B의 Public Key> 를 그대로 넣는다. |
<sig> <B의 Public Key> <B의 Public Key>
|
DUP |
HASH160 <B의 Public Key Hash> EQUALVERIFY CEHCKSIG
|
맨 위의 데이터를 복제해서 그대로 넣는다. (Duplicate) |
<sig> <B의 Public Key> <B의 Public Key Hash>
|
HASH160 |
<B의 Public Key Hash> EQUALVERIFY CEHCKSIG
|
맨 위의 데이터를 RIPEMD160(SHA2(데이터)) 해준뒤 그 값을 그대로 넣는다. |
<sig> <B의 Public Key> <B의 Public Key Hash> <B의 Public Key Hash>
|
<B의 Public Key Hash> |
EQUALVERIFY CEHCKSIG
|
<B의 Public Key Hash> 를 그대로 넣는다. |
<sig> <B의 Public Key>
|
EQUALVERIFY |
CHECKSIG |
맨 위의 두 데이터를 꺼내 같은지 비교한다. 같으면 계속 진행하고, 다르면 프로그램을 중단한다. |
True |
CHECKSIG |
서명이 유효한지 검사한다. |
다만 여기서 생각해봐야할 점이 <sig>
는 위 Tx중 어떤부분을 보증하느냐? 이다. 일단 <sig>
값을 생성할 때 컴퓨터는 가장먼저 현재 Tx의 복사본 TxCopy
를 만들고 TxCopy
의 모든 입력스크립트 부분을 없는걸로 만든다. 그리고 보통은 TxCopy
전체 데이터의 해시값을 그대로 암호화해서 <sig>
값으로 등록한다. 이는 가장 많이 쓰이는 방법이며 가장 간단명료하지만 비트코인은 여기에 약간의 여지를 남겨두었다. 그것은 바로 의도적으로 몇몇부분의 데이터를 보증하지 않는 방법이다.
좀 더 기술적으로 파고들어가 보면 <sig>
라는 데이터에는 <ecdsaSig>
라는 실제 해시값을 암호화한 데이터와 <sigHash>
라는 4Byte짜리 접미부로 구성되어있는데, 이 <sigHash>
에 어떤 데이터의 해시값을 암호화 했는지에 대한 정보가 담겨있다. 현재 정해지고 비트코인에서 인정되는 방법은 다음 표와 같다.
이름 | 설명 | |
---|---|---|
SIGHASH_ALL | 0x00000001 | 모든 출력값에 대하여 보증함 |
SIGHASH_NONE | 0x00000002 | 그 어떤 출력값도 보증하지 않음. 입력을 통해 주어진 금액이 어디로가던 신경쓰지 않음 |
SIGHASH_SINGLE | 0x00000003 | 입력당 단 하나의 출력값만 보증함. 나머지 출력값은 어디로가던 신경쓰지 않음 |
SIGHASH_ANYONECANPAY | 0x00000080 | 자신의 입력만 보증함. 다른 입력이 추가되어도 신경쓰지 않음 |
ALL
, NONE
, SINGLE
부분은 출력부와 관계된 내용이고 ANYONECANPAY
는 입력부와 관계된 내용이며 둘이 조합하는 방식으로도 사용 가능하다. 다만 왜 이런구조를 채택한 것일까? 아쉽게도 필자도 모른다. 이 부분은 비트코인 개발 초기이후로는 크게 변화가 없던 부분이며 논의에 오른적도 별로 없기에 이러한 구조를 채택한 이유는 필자도 잘 모르겠지만 몇가지 상상은 해볼 수 있다. 예를들면 어떤사람이 고아원에 기부를 하기위해 목표 출력값이랑 제한시간인 잠금시간
만 정해두고 SIGHASH_ANYONECANPAY
형태로 어떤 Tx을 전파시켰다면 기부에 동참하는 사람들이 Tx에 자신의 UTXO를 마음대로 추가시킬수 있도록 해놓으면, 잠금시간이 만료되었을때 모금액을 달성하면 거래가 인정되어 사람들이 등록한 UTXO는 자동으로 소모되고, 만약 실패하면 UTXO는 소모되지않고 Tx은 자동으로 소멸하는 구조가 가능하다. 또 다른 예를 들면 팁문화가 존재하는 레스트랑과 같은 장소에서 SIGHASH_SINGLE
을 설정한 Tx을 통해 손님이 레스토랑으로 가는 출력값만 확실히 한 뒤 나머지 금액은 마음대로 가져갈 수 있게 여지를 두어 해당 잔액을 웨이터가 팁으로 가져가게 할 수도 있다. 다만 이 모든것은 소프트웨어적인 구현이 필요하며, 아직 이게 구현된 비트코인 지갑 혹은 관련 프로그램은 존재하지 않아 아쉬울 뿐이다.
비트코인 개발 초기에는 지금 보여준 예시처럼
입력 스크립트
와출력 스크립트
를 하나로 이어서입력 + 출력 스크립트
의 형태 그대로 프로그램을 실행시켰다고 한다. 하지만 나중에 이는 심각한 보안적 결함을 내포하고 있음을 알게됬는데입력 스크립트
에 이상한 값을 넣어두고 프로그램을 실행시키면잠금 스크립트
부분에서 오류가 발생해, 전혀 유효한값이 아닌데도True
가 나오는 상황이 발생할 수 있다는 것이다. 이를 확인한 비트코인 개발진은 그 후입력 스크립트
를 먼저 실행시켜 값이 유효한지 확인 한 뒤잠금 스크립트
를 따로 실행시키도록 프로그램을 변경했다고 한다.
P2SH는 Pay to Script Hash의 약자로 P2PKH가 Public Key의 해시값에 금액을 지불하도록 하는 수단이라면 P2SH는 Redeem Script라는 존재의 해시값에 금액을 지불하는 수단이라고 보면 된다. Redeem Script는 다음과같이 구성되어있는데 <최소 필요한 서명개수> <PubKey1> <PubKey2> <PubKey3> ... <총 서명개수> CHECKMULTISIG
, 눈치 빠른사람은 이미 파악했다시피 이는 여러명의 비밀키 소유자들중 명시된 최소 서명에 필요한 사람수를 만족해야만 거래가 인정되는 방법이다.
Pay to Script Hash
리딤 스크립트
: <최소 필요한 서명개수> <PubKey1> <PubKey2> <PubKey3> ... <총 서명개수> OP_CHECKMULTISIG
입력 스크립트
: Sig1 Sig2 ... redeem_script
출력 스크립트
: OP_HASH160 <Hash of redeem_script> OP_EQUAL
MultiSig
입력 스크립트
: OP_0 Sig1 Sig2
출력 스크립트
: <최소 필요한 서명개수> <PubKey1> <PubKey2> <PubKey3> ... <총 서명개수> OP_CHECKMULTISIG
본래 비트코인은 P2SH
대신 MultiSig
라는 수단을 사용해 다중서명이 필요한 거래를 처리했었지만, 기존 MultiSig
는 Tx크기에 따른 수수료의 부담을 이 계좌로 입금하려는 사람에게 부담하게 하고 (그러니깐 어떤 기업에 돈을 지불하는 사람이 수수료를 더 많이 부담하게 된다!) , 어떤 사람이 해당 계좌로 송금하기 위해서는 해당 계좌에 사용되는 모든 Public Key에 다한 정보를 알고있어야해 사용하기 불편했으며 이에대한 개선판으로 나온것이 P2SH인 것이다. P2SH는 2012년 BIP16을 통해 제안되었으며 현재 다음과 같이 3으로 시작하는 비트코인 주소는 모두 P2SH를 사용하는 거래이다.
3NukJ6fYZJ5Kk8bPjycAnruZkE5Q7UW7i8
이는 실제로 존재하고 사용되는 주소임으로 궁금한 사람은 해당주소를 BlockChain.info 에 검색해보자.
비트코인은 비트코인 네트워크에 참여하는 수많은 노드(Node)들의 기여로 유지된다. 각 노드들은 크게 다음과 같은 기능들을 포함하거나, 기기 성능이나 목적에 따라 포함하지 않을 수도 있다.
종류 | 기능 |
---|---|
Wallet | 비트코인 주소의 소유주임을 증명하는 비밀키에 대한 정보를 담고있다. |
Block Chain | 2009년이후로 모든 비트코인상의 거래내역을 담고있는 블록체인을 저장하고 있다. 2017년 현재 블록체인의 크기는 100GB정도 된다. |
Routing Node | 생성된 거래혹은 블록을 검증하고 전파하는 역할을 해준다. |
Miner | 생성된 거래를 모아 블록을 생성해 블록체인을 만들어준다. |
비트코인상에서 거래는 대부분 지갑
을 통해 생성된다. 이렇게 생성된 거래는 주변 Routing Node
에 전파되고 해당 Routing Node
는 또 다른 Routing Node
로 전파시키고를 끝없이 반복해서 결국은 비트코인 네트워크에 존재하는 Miner
에게 까지 도달하게된다. 그럼 바로 Miner
에 도달한 Tx은 채굴을 기다리다가 결국 블록체인속에 포함된다.
하지만 이를 구현하는 과정은 쉽지 않다. 일단 시작부터 큰 문제가 발생하는데 TxIn
에 사용할 UTXO의 정보를 어떻게 얻어올 수 있을까? 이다.
스마트폰에서 비트코인을 사용하는 경우를 생각해보자. 스마트폰 용량은 최근 128GB를 넘어가는 것도 있긴 하지만 일부에 불과하며 100GB를 넘어가는 블록체인을 넣기에는 블록체인의 크기가 너무 크다. 하지만 스마트폰을 사용해 비트코인을 거래정보를 생성하려면 자신의 주소로 출력되었지만 사용된적은 없는, 즉 UTXO에 대한 정보가 필요하고 이에대한 정보는 블록체인 속에있다. 이러한 전체 블록체인은 가지고 있지 않지만 거래를 하기위해 제안되어 만들어진 개념이 SPV노드
이다. SPV노드
의 앞서말한 문제에대한 해결방법은 간단하다. 블록체인을 가지고 있는 노드에게 자신의 주소로 출력된 값들좀 알려달라고 요청하면 된다.
다만 여기서 또 문제가 발생한다. 첫째, 우리가 통신하는 전체 블록체인을 소유한 노드가 정직한지 모른다. 이 노드가 우리에게 실제 출력된 값보다 작은값이 출력되있다고 알려주어 우리가 더 많은 수수료를 지불하게 할수도 있다. 둘째, 우리가 통신하는 노드는 해당 비트코인 주소의 소유주가 누구의 디바이스인지 확인할 수 있다. 이는 익명성을 보장하려는 비트코인에게있어 환영할만한일은 아니다. 마지막으로 셋째, 만약 우리가 통신하는 노드가 정보를 주기를 거부한다면 우리는 거래를 생성할 수 없다.
일단 결론적으로 셋다 해결책은 나왔고 대부분의 비트코인 클라이언트에서 구현되어있다. 일단 첫번째 문제의 해결책을 살펴보자. 이부분은 이 파트의 제목인 SPV 노드
와 관련되있는 사항인데 SPV
란 Simplified payment Verification
의 약자로 번역하면 간단한 지불검증 노드 정도 되는 존재이다. 이 SPV 노드
는 여태껏 일어난 모든 거래에대한 정보는 알지 못하지만 어떤 거래정보(블록)이 주어져있을때 이것이 올바른지 검증할 능력이 있다. 자세한 방법은 추후 머클 트리
에 대해 설명할때 언급하겠지만 간단하게 설명해보자면, SPV 노드
는 블록체인에 존재하는 모든 블록의 헤더를 가지고 있다. 이 헤더의 크기는 무척 작아서 첫 블록 생성후 2017년 지금까지 모든 블록의 블록 헤더 크기의 합이 불과 40MB가 좀 안되는 크기로 스마트폰이 가지고있기 부담없는 크기이며 이 블록헤더는 각 블록에 포함되있는 거래내역을 증명할 수 있는 머클루트
에 대한 정보를 가지고 있다. 이런 상황에서 SPV 노드
는 상대에게 특정주소로 출력이 있는 거래데이터가 포함된 블록의 머클트리와 거래 데이터를 요청하면, SPV 노드
는 받은 거래 데이터가 머클트리에 포함되있는 데이터란것을 확인하고, 해당 머클트리의 머클루트와 자신이 가지고있는 블록헤더에 기록된 머클루트를 비교하여 결국 상대방이 준 데이터가 올바른 데이터인지 불과 40MB좀 안되는 데이터로 확인 할 수 있다.
두번째 문제의 경우 블룸필터
라는 방법으로 해결책을 제시하였다. 블룸필터
의 원리는 간단하다. 일단 비트코인 지갑에서 잔액을 확인할 주소를 최소 2개 이상 준비한다. 그리고 각각의 주소는 0과 1로 표현 가능하다. 편의상 주소 1을 1011001100011이라 하고 주소 2를 0011010110001이라 해보자. 이제 이 둘을 OR연산 시킨다. (하나라도 1이면 1을 출력하는 연산이다.) 그럼 필터 101101110001이 탄생한다.
1011001100011
OR 0011010110001
---=====----=-=-=-==
1011011110011
1안
이 필터를 받은 노드는 모든 주소에대해 비교를 행하게 되는데 그 방법은 예상했다싶이 어떤 주소의 x번째 비트가 1일때 필터의 x번째 비트도 1인가? 이다. 만약 아니라면 해당 주소는 필터를 통과하지못하고 맞다면 해당 주소는 필터를 통과하게 될것이다. 이렇게 필터를 통과한 주소들은 당연히 SPV 노드
가 요구로 하는것보다 많은양의 주소가 존재할텐데 이 원리를 활용해 블룸필터는 블록체인을 소유한 노드로 하여끔 어떤 비트코인 주소의 주인이 어떤 디바이스에대한 추청을 어렵게 한다.
2안
이 필터를 받은 노드는 모든 주수에대해 비교를 행하게 되는데 그 방법은 다음과 같다. 1. 필터와 비교해볼 어떤 주소와 OR연산을 해본다. 2. 그 결과값이 필터와 같은지 확인해 본다. 만약 같다면 해당 주소는 필터를 통과하게 될것이다. 이렇게 필터를 통과한 주소들은 당연히 SPV 노드
가 요구로 하는것보다 많은양의 주소가 존재할텐데 이 원리를 활용해 블룸필터는 블록체인을 소유한 노드로 하여끔 어떤 비트코인 주소의 주인이 어떤 디바이스에대한 추청을 어렵게 한다.
실제 비트코인에서 블룸필터는 비트코인 주소를 그대로 사용하지 않고 출력 스크립트
의 해시값을 이용하며, 단순한 작동원리를 가지고 있지만 그 단순함 덕분에 컴퓨터에서 연산속도가 매우 빨라 비트코인에 매우 적합한 방법이라 할 수 있다.
마지막으로 우리가 통신하는 노드가 정보를 주기를 거부하는 문제는 어떻게 해결할까? 사실 이 문제는 DoS, Denial of Service 라는 용어가 탄생할정도로 여기저기서 쓰이고 아직 완벽한 해결방법은 존재하지 않는다. 그래도 대안은 있는데 가능한 많은 블록체인을 소유한 노드와 통신을 시도해 확률적으로 서비스를 제공해줄 노드를 만나는 방법이다. 뭔가 해결책치고는 미온적이긴 하지만 참여를 전제로 설계된 비트코인에서 불참은 어쩔수 없는 사항이기도 하다. 일부에서는 이러한 서비스 제공에도 수수료를 지불하게하여 원활한 사용을 가능하게 하자는 말도 있었으나, 현재 서비스거부로 인한 문제가 발생한적은 없어 (적어도 필자가 듣기로는 없다.) 딱히 새로운 해결책이 나온상태는 아니다.
SPV 노드
는 앞서 말한 방법들을 이용해 성공적으로 거래 생성에 필요한 정보들을 받아내고, 새로운 거래를 생성해낸다. 그 다음은 생성된 거래를 다른 노드로 전파시키기만 하면 된다. 일단 가장먼저 시작하는것은 시드노드
(Seed Node) 에 접속하는 것 부터이다. 시드노드
는 다른 비트코인처럼 꺼졌다 켜졌다 하지않고 네이버나 구글같은 사이트처럼 장기간 켜져있는 노드를 말하는데 2017년 기준 비트코인 클라이언트에 기본적으로 입력되어있는 시드노드
의 종류는 다음과 같다.
- seed.bitcoin.sipa.be
- dnsseed.bluematt.me
- dnsseed.bitcoin.dashjr.org
- seed.bitcoinstats.com
- seed.bitcoin.jonasschnelli.ch
- seed.btc.petertodd.org
위 시드노드
에 접속한 비트코인 클라이언트는 현재 비트코인 네트워크를 유지시키고 있는 다른 노드들의 IP주소를 요청하고 제공받습니다. 그후 제공받은 다른 클라이언트의 IP주소의 8333번 포트로 접속요청을 합니다. 이렇게 첫 P2P네트워크가 성립되었습니다. 이제 앞서 언급한 DoS 문제를 해결하기위에 접속한 첫 노드로부터 다른 노드의 IP주소를 요청을해 사용자 설정에따라 몇몇노드에 추가적으로 P2P네트워크를 구축합니다. 그럼 축하합니다! 여러분도 비트코인에 존재하는 하나의 노드가 되었습니다.
비상 메세지
일반적으로 잘 사용하진 않지만 비트코인 네트워크의 중대한 위험이 발견되면 비트코인 개발자들에 의해 비트코인 네트워크 전역으로 비상 메세지가 전파된다. 이 비상 메세지는 2017년 현재기준 총 12번 발생했는데 대부분의 내용은 "비트코인 규격이 업그레이드 되었으니 각자 비트코인 클라이언트를 새로운 규격에 맞게 업그레이드 해주세요"나 "소프트웨어에 중대한 보한결함이 발견되었으니 업데이트 해주세요"정도의 내용이었지만 딱 한번은 "블록체인이 지금 두갈래로 나가고 있으니 한가지를 포기해 주세요"라는 내용이 있었다. 이에관한 주제는 "블록 전파"부분에서 언급하겠지만 지금 살펴볼것은 비트코인의 긴급메세지는 어떻게 "비트코인 개발자"만 발생시킬 수 있을까? 이다.
일단 비트코인은 완벽한 분산화된 환경이고 중앙에서 통제하는 기관이 전혀 없다. 하지만 비상메세지는 정해진 몇몇사람만 발생 시킬 수 있다. 그 원리는 앞에서 지겹도록 다룬 전자서명이다. 현재 대부분의 비트코인 클라이언트에는 비트코인 개발자가 미리 정해둔 공개키가 포함되어 배포되고 있습니다. 이는 비트코인 클라이언트 종류마다 다르긴 하지만 대체로는 Bitcoin Core에 사용되는 같은 공개키가 포함되어있습니다. 비트코인에서 선택된 일부 개발자는 이 공개키에 맞는 비밀키를 가지고 있는데 자신이 보낼 메세지내용에 해당 비밀키로 서명을 하여 비상 메세지를 네트워크 전역으로 배포하게 된다.
다만 일부 채굴 프로그램은 이러한 비상메세지를 받는부분이 빠져있는경우가 가끔 있다. 이러한 비상메세지가 배포될때는 블록체인에 관련해 중대한 사건이 발생하고 자신의 채굴시스템에 변경이 꼭 필요한 경우일 수 있으니, 채굴장을 운영할경우 스마트폰에 간단한 비트코인 클라이언트를 같이 설치해 메세지를 언제 어디서든 확인할 수 있게 할것을 권장한다.