336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.

이전 포스팅에서 트랜잭션의 Input/Output의 자료구조와 UTXO 모델에 대해 설명하였습니다.


Output에는 누가(scriptPubKey) 얼마(nValue)를 받았는지 표시되고, 어떤 Input은 이 Output을 가리켜 해당 nValue만큼의 비트코인을 쓸 수 있다고 하였습니다. 그리고 아직 소비되지 않은 Output을 UTXO(Unspent Transaction Outputs)이라 한다고 하였습니다.


트랜잭션 Input 검증

트랜잭션 검증에서 중요한 일 중 하나는 트랜잭션 Input을 검증하는 일입니다. 예를 들어 A가 채굴에 성공해서 50 비트코인의 Output을 가지고 있었는데, 이를 A가 아닌 다른 사람이 쓰면 안되기 때문에 scriptPubKey라는 A만 풀 수 있는 잠금 장치를 걸어두고, 이를 A가 쓸 때에는 scriptSig라는 일종의 잠금해제 장치를 제공해야 합니다.


이전 포스트에서 트랜잭션 Input은 prevout과 scriptSig로 구성된다고 하였습니다. prevout이 가리키는 트랜잭션 Output에는 scriptPubKey가 있습습니다. scriptSig와 scriptPubKey 두 개 스크립트 쌍이 맞아야 합니다. 개념적으로는 scriptPubKey는 공개키이고 scriptSig는 비밀키 서명입니다. 그러나 실제로는 명령어 코드(OPCODE)와 데이터로 구성된 스크립트이고 이 명령어를 수행한 결과에 의해 Input 검증 성공/실패가 결정됩니다.


비트코인 스크립트 실행 방법

비트코인 스크립트 실행은 스택이 빈 상태에서 input의 scriptSig를 먼저 실행하고, 그 다음 scriptPubKey를 실행시킵니다. 실행 결과 스택의 맨 위 값이 FALSE이면 실패, TRUE(0이 아닌 값)이면 성공한 것입니다. 성공할 경우에만 해당 input 검증을 통과한 것으로 처리합니다.


스크립트는 후위연산식으로 생각할 수 있습니다. 아래는 후위연산식 실행의 간단한 예제입니다.


4 5 +


위의 후위연산식을 실행하면 아래 결과가 나옵니다.


9


조금 더 복잡한 후위연산식


4 5 * 2 +


결과


22


아래와 같이 scriptPubKey가 있다고 가정해 봅시다.


4 + 10 ==


== 연산자는 2개 값을 읽어 같으면 TRUE, 같지 않으면 FALSE를 반환하는 연산자입니다.

+ 연산자는 2개 피연산자를 요구하는데, 그 앞에 4 밖에 없어서 위의 스크립트는 무효합니다. 4 앞에 어떤 하나의 숫자를 제시하면 유효한 스크립트를 만들 수 있습니다.

위의 scriptPubKey를 풀기 위해서는 scriptSig를 제공해야 합니다.


scriptPubKey: 4 + 10 ==

scriptSig: x


여기서 scriptSig와 scriptPubKey를 차례대로 실행시키면 아래 스크립트를 실행시키는 것과 같은 효과입니다.


x 4 + 10 ==

위의 스크립트는 x가 6이면 실행 결과가 TRUE이고, 아닐 경우 FALSE입니다.


즉, "4 + 10 =="는 풀어야 할 문제이고, "6"은 그 해답입니다.



scriptPubKey: 문제

scriptSig: 해답



표준 스크립트

비트코인은 비밀키 서명을 검증할 수 있는 OP_CHECKSIG 명령어를 제공합니다. "x y OP_CHECKSIG" 스크립트는 x를 비밀키 서명, y를 공개키로 취급하여 유효한 서명이면 TRUE, 아닐 경우 FALSE를 반환합니다. OP 코드의 목록은 비트코인 위키에서 확인할 수 있습니다.


현재 비트코인 네트워크에서는 표준 스크립트만 허용되고 있습니다. (https://bitcoin.org/en/developer-guide#standard-transactions)


- P2PKH

- P2SH

- P2PK

- Multisig

- Null Data


가장 간단한 표준 스크립트는 P2PK 스크립트입니다.

P2PK (Pay to Public Key)

scriptPubKey

<pubkey> OP_CHECKSIG

scriptSig

<sig>



잘 아시다시피 비트코인 거래 내역은 공개되어 누구나 볼 수 있습니다. 즉 풀어야 할 문제가 미리 공개되어 있고, 누군가 그 문제를 푼다면 해답 역시 공개됩니다. 미리 공개되어 있는 문제는 충분히 어려워야 하고, 제시된 해답은 다른 UTXO를 공격할 힌트가 되지 않도록 조심해야 합니다.


위의 P2PK 스크립트는 내가 비트코인을 받는 시점에서 공개키가 드러나게 됩니다. 만약 ECDSA가 공격당할 경우 P2PK로 만든 UTXO 모두 공격당하게 됩니다. 따라서 더 안전한 표준 스크립트인 P2PKH 스크립트를 쓰는 것이 보안상 권장됩니다.


P2PKH(Pay to Public Key Hash)

scriptPubKey

OP_DUP OP_HASH160 <PubKeyHash> OP_EQUALVERIFY OP_CHECKSIG

scriptSig

<sig> <pubkey>


P2PKH의 scriptPubKey에는 공개키의 해시값만 있습니다. scriptSig를 제시할 때 공개키를 내가 직접 제공해야 합니다. scriptPubKey의 OP_DUP부터 OP_EQUALVERIFY는 공개키 해시를 검증하는 부분이고, 이게 사라지면 "<sig> <pubkey> OP_CHECKSIG"로 P2PK와 같은 형태가 됩니다.


중요한 점은 scriptPubKey에는 공개키의 해시만 있다는 점입니다. 만약 ECDSA 알고리즘이 완전히 뚫리더라도 scriptPubKey에 있는 공개키 해시만 보고 비밀키를 알아내기 위해서는 ECDSA뿐만 아니라 해시 함수까지 공격해야 합니다. 해당 UTXO를 사용하는 순간 scriptSig에 공개키가 드러나면 비밀키를 공격할 수 있습니다. 하지만 이는 매번 새로운 공개키를 사용하면 문제가 되지 않습니다.


P2PKH는 블록 익스플로러나 지갑 앱에서 보여주는 1로 시작하는 주소(예: 1BvBMSEYstWetqTFn5Au4m4GFg7xJaNVN2)에 해당됩니다. 공개키 해시에 네트워크 ID와 체크섬을 붙이고 Base58로 변환한 값입니다.

Multisig

scriptPubKey

<m> <A pubkey> [B pubkey] [C pubkey...] <n> OP_CHECKMULTISIG

scriptSig

OP_0 <A sig> [B sig] [C sig...]


OP_CHECKMULTISIG는 n개 중에 m개의 서명을 받으면 TRUE를 반환하는 OP코드입니다. 이는 다양한 응용에 쓰일 수 있습니다. 예를 들면 두 명이 공동으로 자산을 관리하는데, 돈을 쓰기 위해선 2명의 서명을 모두 필요로 할 수 있고, 한 명의 서명만을 필요로 하게 할 수도 있습니다. 변호사같은 중립적인 역할을 두어 3개 중 2개 서명이 필요하도록 할 수도 있습니다.


여기에선 기본적으로 공개키가 2개 이상이기 때문에 주소를 효율적으로 만들기가 까다롭습니다. 주소라는 것은 결국 저 scriptPubKey를 특정할 수 있어야 합니다. 예를 들면 A가 B에게 돈을 주기 위해서 B에게 주소를 물어보면, B가 n이 10인 Multisig로 받기 위해서는 공개키 10개를 모두 전달해야한다는 의미가 됩니다. 이는 비효율적이고 공개키가 모두 공개되는 문제가 있습니다. Multisig로 돈을 받기 위해서는 P2SH 표준 스크립트를 사용하는 것이 권장됩니다.

P2SH(Pay to Script Hash)

scriptPubKey

OP_HASH160 <Hash160(redeemScript)> OP_EQUAL

scriptSig

<sig> [sig...] <redeemScript>


P2SH에선 scriptSig에 있는 redeemScript가 실제 "문제" 역할을 담당합니다. scriptSig에는 문제와 해답(<sig>)이 동시에 존재하는 셈입니다. 임의의 공격자가 스스로 자기가 풀 수 있는 문제를 제시하고 혼자 푸는 상황을 방지하기 위해, 문제지의 해시를 scriptPubKey에 기록합니다. 


아래는 Multisig를 redeepScript로 사용하는 예입니다.

scriptPubKey

OP_HASH160 <Hash160(redeemScript)> OP_EQUAL

redeemScript

<OP_2> <A pubkey> <B pubkey> <C pubkey> <OP_3> OP_CHECKMULTISIG

scriptSig

OP_0 <A sig> <C sig> <redeemScript>


P2SH가 지갑 앱에서 보여주는 3으로 시작하는 주소(예: 3J98t1WpEZ73CNmQviecrnyiWrnqRhWNLy)에 해당됩니다. 3으로 시작하는 주소는 Multisig일 가능성이 있습니다. 그러나 3으로 시작하는 주소가 실제로 어떤 redeemScript를 쓰는지는 scriptSig가 제시되기 전까진 알 수 없습니다. P2PKH 일 수도 있습니다.


그렇다고 P2SH를 쓰는 것이 항상 좋은 것만은 아닙니다. 스크립트의 크기가 커지면 트랜잭션이 커지고, 이는 수수료가 비싸지는 것을 의미합니다. 최근에 비트코인 수수료가 매우 비싸지면서 P2PK를 쓰는 UTXO도 많이 보이고 있습니다. 통상적인 P2PKH Input 1개 + Output 2개 트랜잭션이 약 226 바이트인데, 주는 사람이 P2PK를 쓸 경우 126 바이트로 공간 비용을 절약할 수 있습니다.

'비트코인 Technical Background' 카테고리의 다른 글

비트코인 주소에 대한 이해 (1) - UTXO 모델  (0) 2018.01.26
비트코인 SPV  (2) 2018.01.17
336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.

https://blockexplorer.com/tx/89c05b4ea989a19e529b93a6fd883c5089aaacfd83350d8fce28d0822259ce2f


비트코인 트랜잭션을 볼 수 있는 BlockExplorer에서 트랜잭션을 보다 보면 "Unparsed Address"를 자주 발견 할 수 있습니다. 이번 포스팅과 다음 주에 이어지는 포스팅을 통해 비트코인 트랜잭션에 표시되는 주소가 어떤 원리에 의해 표현되는지 이해함으로써 비트코인의 내부 동작을 이해해 보도록 하겠습니다.


- UTXO 모델

- Script (2월 3일 작성 예정)


UTXO (Unspent Transaction Outputs)

비트코인 트랜잭션을 생각할 때 흔히 할 수 있는 착각 중 하나는 블록체인 상에 증여자(giver)와 수증자(receiver)의 비트코인 주소가 기록될 것이라는 점입니다. 여러 사람의 거래 내역을 적는 원장(ledger)의 개념으로 비트코인을 처음 접하거나 어카운트(account) 기반인 이더리움을 먼저 공부하셨다면 이런 선입견을 가지기 쉽습니다. 그러나 비트코인은 그런 방식으로 동작하지 않습니다.


비트코인의 레퍼런스 구현인 Bitcoin Core 구현의 트랜잭션 자료구조를 살펴보겠습니다. 트랜잭션은 보내는 사람에 해당하는 CTxIn과 받는 사람에 해당하는 CTxOut 클래스가 있습니다.


https://github.com/bitcoin/bitcoin/blob/9cf6393a4f82b9c81d3b4b468a17a89db10531a2/src/primitives/transaction.h#L61

https://github.com/bitcoin/bitcoin/blob/9cf6393a4f82b9c81d3b4b468a17a89db10531a2/src/primitives/transaction.h#L131





A(증여자)가 B(수증자)에게 10 BTC를 주는 트랜잭션 작성하는 것으로 예로 들어보겠습니다.

A는 B의 주소를 알고 있으며 아래와 같이 트랜잭션을 만들 수 있습니다.


CTxIn prevout - A의 공개키로 받은 CTxOut에 대한 포인터

CTxIn scriptSig - A의 서명 (비밀키)


CTxOut nValue - 비트코인의 양 (10 BTC)

CTxOut scriptPubKey - B의 공개키

 

위의 트랜잭션이 블록에 포함되면 B는 미래에 이 CTxOut을 포인팅해서 10 BTC를 쓸 수 있게 됩니다.


CTxIn은 가리킬 수만 있고 비트코인의 양을 지정할 수 없습니다. 즉, CTxOut은 무조건 한 번에 소비됩니다. 만약 B가 C에게 3 BTC만 주고 싶다면, (10 BTC의 CTxIn) -> (3 BTC의 CTxOut), (7 BTC의 CTxOut)의 트랜잭션을 만들어 3 짜리는 C에게 주고, 7 짜리를 다시 자기 자신에게 돌려주면 됩니다.


B의 입장에서 7 짜리 거스름돈을 받을 때 공개키를 재사용할 이유가 없습니다. B는 새로운 키쌍을 생성해 해당 키에 돌려 받습니다. 이 과정에서 이 거래와 상관없는 D가 블록체인에 기록된 거래내역을 봤을 때 무엇이 거스름돈인지 알 수가 없습니다. 심지어 거스름돈이 없이 수증자가 2명인 가능성도 배제할 수 없습니다. 이 모델은 프라이버시 측면에서 장점이 있습니다.


이 글 처음에 있는 스크린샷을 다시 보겠습니다. 1개 CTxIn과 2개의 CTxOut이 있는 경우입니다. 



위의 경우는 추측컨데 0.099 BTC를 누군가에게 주는 트랜잭션일 것입니다. 남은 돈(5.92.. BTC)은 자신의 공개키로 다시 CTxOut을 만든 것을 볼 수 있습니다.


CTxOut의 오른 쪽에 초록색 폰트로 (U)가 있는데, 이는 해당 CTxOut이 아직 쓰이지 않았다는 것(Unspent)을 의미합니다. 이를 UTXO(Unspent Transaction Outputs)라 합니다. 미래에 이 CTxOut을 포인팅하는 트랜잭션이 일어나면 (S)로 표시됩니다.



비트코인 지갑이나 BlockExplorer에서 보여주는 잔고(balance)는 하나 이상의 UTXO의 합이라 할 수 있습니다. 이로 인해서 비트코인 트랜잭션을 만들 때 여러 옵션이 존재할 수 있습니다.


A가 10 BTC, 5 BTC, 3 BTC의 UTXO를 가지고 있고 B에게 6 BTC를 줘야 하는 상황을 가정해보면 아래와 같습니다.


- 10 을 쓴다. -> 잔고: 4 + 5 + 3

- 5 + 3 을 쓴다. -> 잔고: 10 + 2

- 10 + 5 + 3 을 쓴다 -> 잔고: 12


조합에 따라 트랜잭션 바이트 크기가 달라지고, 바이트 크기가 작을수록 마이너에게 지불하는 수수료를 낮출 수 있기 때문에 위의 조합 문제를 풀어야 하는 요구사항이 있습니다.


결론

비트코인을 보유하는 사람은 공개키를 여러 개 소유할 수 있고, 각 공개키는 하나 이상의 UTXO와 연결될 수 있지만, 프라이버시 측면에서 하나의 공개키 당 하나의 UTXO만 연결되는 것이 바람직합니다. 보유자는 이 UTXO를 조합하여 지불할 수 있습니다.


사용자 입장에서 UTXO 모델은 이해하기 더 어렵고, 위에서 설명한 조합 문제나 UTXO set을 관리하는 문제 등 기술적인 문제들도 있습니다. 하지만 트랜잭션의 병렬 처리나 프라이버시 측면에서 이더리움의 어카운트(Account)처럼 주소가 재사용되는 구조에 비해 장점이 분명히 존재합니다.


앞서 CTxOut의 scriptPubKey를 설명할 때 이를 공개키라고 표현했지만, 실제로는 공개키해시를 포함하는 실행 가능한 스크립트이고, 엄밀히 말하면 공개키 해시를 포함하지 않을 수도 있습니다. 다음 포스팅에서는 비트코인 스크립트의 실행과 표준 스크립트에 대해 설명하겠습니다. 표준 스크립트는 비트코인 주소와 직접적으로 연관되어 있습니다.


336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.

2018년 1월 기준으로 비트코인 블록체인 데이터는 150GB를 넘어섰습니다. 스마트폰에 저장하기엔 너무 크기 때문에 스마트폰에서는 풀노드를 구동하기 어렵습니다. SPV란 Simplified Payment Verification의 약자인데, 비트코인을 받았다는 사실을 전체 블록체인을 다운로드하지 않고도 검증하는 방법이며 이는 사토시 나카모토의  화이트페이퍼에서도 소개되고 있습니다.



비트코인의 트랜잭션 구성은 아래처럼 구성됩니다.


- input (비트코인을 보내는 사람의 서명)

- output (비트코인을 받는 사람)

- 버전

- nLockTime


비트코인을 받는 입장에서는 위의 트랜잭션을 까보고 서명을 검증할 수는 있지만, 실제로 비트코인을 받았는지 확신할 수 없습니다.

보내는 사람이 실제로 비트코인을 그만큼 소유하고 있었고, 이중지불을 하지 않았어야 하는데 이는 트랜잭션만 봐서는 알 수 없습니다.


SPV 클라이언트는 풀노드로부터 머클 증명과 일부 블록의 헤더만 받아서 트랜잭션의 유효성을 검증할 수 있습니다.


일단 비트코인 블록 헤더 구성을 보면


- 버전 (4바이트)

- 이전 블록해시 (32바이트)

- 머클루트 해시 (32바이트)

- 블록 시간 (4바이트)

- nBits (4바이트)

- nonce (4바이트)


로 총 80바이트입니다. 1년 동안 발생하는 52560개 블록 헤더 용량이 4MB 수준이니 매우 가볍다고 할 수 있겠습니다.

참고로 nBits는 블록 난이도를 계산할 수 있는 필드입니다.


받는 사람 입장에서 증명되어야 할 사실은 두 가지입니다.


1. 내가 받은 트랜잭션이 블록에 포함되었는가?

2. 그 블록이 고아블록은 아닌가?




트랜잭션이 블록에 포함되었는지 여부는 머클 증명으로 확인할 수 있습니다.


만약 내가 Tx1이 블록에 포함되었는지 여부를 알고 싶다면 풀노드로부터 Hash0과 Hash23을 받아 해시를 3번만 돌려보면 머클루트 해시를 알 수 있습니다.

블록에 트랜잭션이 2000개쯤 있어도 해시함수를 11번 정도만 돌려보면 됩니다. 해시가 충돌할 수도 있지 않냐구요? 음... 아니요...


머클루트 해시를 알았으면 받은 블록 헤더의 머클루트 해시와 일치하는지 확인하면 됩니다.


블록이 철회 될 확률이 작다는 것을 증명하기 위해서는 해당 블록 뒤에 이어지는 블록 헤더들을 받아서 PoW를 확인하면 됩니다. 통상적으로 6 confirmation이면 안전하다고 판단하니, 6개의 블록 헤더를 받아 검증하면 됩니다.



풀노드가 PoW를 수행해 고아블록을 생성해서 나를 속이거나, 해시함수를 공격해서 나를 속일 가능성이 0인 것은 아닙니다. 다만, 비트코인은 컴퓨팅 파워가 있다면 정직하게 PoW를 해서 블록 생성 보상을 받는게 경제적으로 유리하도록 설계된 프로토콜임을 기억하시길 바랍니다.




위의 SPV 방식은 PoW 알고리즘에 의존하고 있습니다. 따라서 PoS 등 다른 합의 알고리즘에서는 다른 SPV 방식이 필요할 것입니다. 예를 들어 PoS라고 생각해 본다면 "Staking 정보를 전체 블록체인 데이터없이 어떻게 증명할 것인가?" 를 생각해볼 수 있겠습니다. 관련한 비탈릭의 이더리움 블로그 포스트 링크를 남깁니다.


https://blog.ethereum.org/2015/01/10/light-clients-proof-stake/

+ Recent posts