트랜잭션 내용 보기
•
실제 트랜잭션은 다음과 같이 구성된다.
◦
그림을 보면 0.1BTC를 송금하고 2개의 Unspent 가 생성되었다.
◦
0.15BTC는 다른 주소로 가고 0.0845BTC는 다시 자기 자신으로 보내졌다.
◦
수수료 0.0005BTC도 책정된 것을 볼 수 있다.
•
위 그림을 실제 데이터로 보면 다음과 같다.
{
"version": 1,
"locktime": 0,
"vin": [
{
"txid": "7957a35fe64f80d234d76d83a2a8f1a0d8149a41d81de548f0a65a8a999f6f18",
"vout": 0,
"scriptSig" : "3045022100884d142d86652a3f47ba4746ec719bbfbd040a570b1deccbb6498c75c4ae24cb02204b9f039ff08df09cbe9f6addac960298cad530a863ea8f53982c09db8f6e3813[ALL] 0484ecc0d46f1918b30928fa0e4ed99f16a0fb4fde0735e7ade8416ab9fe423cc5412336376789d172787ec3457eee41c04f4938de5cc17b4a10fa336a8d752adf",
"sequence": 4294967295
}
],
"vout": [
{
"value": 0.01500000,
"scriptPubKey": "OP_DUP OP_HASH160 ab68025513c3dbd2f7b92a94e0581f5d50f654e7 OP_EQUALVERIFY OP_CHECKSIG"
},
{
"value": 0.08450000,
"scriptPubKey": "OP_DUP OP_HASH160 7f9b1a7fb68d60c536c2fd8aeaa53a8f3cc025a8 OP_EQUALVERIFY OP_CHECKSIG",
}
]
}
JSON
복사
◦
vin은 위 그림에서 보는 하나의 송금내역 데이터를 보여주고,
◦
vout은 두 개의 Unspend를 나타낸다.
◦
트랜잭션의 실제 데이터에는 보내고 받는 사람의 주소가 없다.
◦
보내는 사람의 0.1BTC가 어디에도 나와있지 않다.
트랜잭션 출력과 입력 (Outputs and Inputs)
•
트랜잭션의 기본 구성 요소는 출력이다.
•
출력은 비트코인 화폐 덩어리로, 전체 네트워크에서 유효성을 보장한다.
•
여러 출력 중 미사용 출력(Unspent transaction output, UTXO)를 추적하여 잔액을 계산할 수 있다.
•
위 그림은 세 번의 거래를 도식화 한 것이다.
◦
277278 블록: Joe는 Alice에게 가지고 있는 비트코인 전액을 보냈다. Joe의 UTXO는 사용(spent) 되었고, Alice의 UTXO가 생겼다.
◦
277298 블록 : Alice는 Bob에게 가지고 있는 비트코인의 일부를 보냈다. Alice의 과거 UTXO는 사용되었고 새로운 UTXO 두 개가 생겼다. 하나는 Bob에게 보낸 비트코인이고, 하나는 보내고 남은 나머지 잔액이다.
◦
277316 블록 : Bob이 Gopesh에게 비트코인을 일부 보냈다. Bob의 과거 UTXO는 사용되었고, 새로운 UTXO 두 개가 생겼다. 장부상에는 총 3개의 UTXO가 존재한다.
•
트랜잭션에서 바트코인 금액은 사토시 단위 (1억분의 1 비트코인)으로 기록되어 정수로 표기된다.
•
한번 생성된 출력(UTXO)는 재사용될 수 없다.
•
UTXO를 사용하고 새로운 UTXO를 생성하며 비트코인의 이전이 이루어진다.
◦
여러개의 UTXO를 사용하고 한 개의 UTXO로 생성될 수도 있다.
◦
한개의 UTXO가 여러개의 UTXO로 생성될 수도 있다.
•
사용된 입력의 총 합에서 새로 생성된 UTXO 금액의 총 합을 빼면 수수료가 된다.
비트코인과 현금의 유사성
•
앞에서 알아본 바와 같이 비트코인 UTXO는 금액을 가지고 있는 현금과 비슷하다.
◦
실제 지갑에 현금이 얼마가 있는지 알아보려면 직접 세어봐야 하는것 처럼 비트코인도 UTXO의 잔액을 알아야 내 재산이 얼마인지 알 수 있다.
◦
현금에 부여된 일련번호와 유사하게 각 잔액의 돈에 식별자가 있다.
•
지불결제할 때에도 현금과 비슷하다.
◦
예를들어 5000원짜리를 구매할 때 10000원짜리를 지불하면 새로운 5000원이 생겨서 나에게 돌아온다.
•
현금과 UTXO에도 차이가 있다.
◦
현금의 ID는 사라지지 않지만, UTXO의 식별자는 한번 사용하고 나면 다른 식별자로 대체된다.
◦
현금은 천원권, 만원권 등 액수가 정해져있지만 UTXO는 따로 액수가 정해져있지 않다.
•
따라서 비트코인의 거래는차변과 대변으로 이루어지는 복식부기와 유사하다.
이더리움의 거래
•
비트코인이 현금과 유사한점이 많다면, 이더리움의 거래는 은행의 통장과 유사하다.
•
각 주소는 얼마를 가지고 있는지 총액 데이터를 상태저장소에 저장한다.
•
내가 이더리움을 지불하는 경우 총액에서 송금액만큼을 차감하여 재 저장한다.
•
각 방법의 장단점이 있다. 고민해보자.
비트코인의 송금
•
비트코인의 현금(UTXO)은 개인의 지갑에 있지 않고 블록체인에 기록되어 있다.
•
모두의 컴퓨터 안에 있는 이 현금은 아무나 가져가선 안된다.
•
그렇다면 저 널려있는 현금을 가져갈 수 있는 사람은 어떻게 결정될까?
•
그것은 보내는 사람 마음이다.
•
비트코인도 돈뿌리기가 가능하다. “아무나 가져가세요. 먼저 줍는 사람이 임자.”
•
비트코인의 입력과 출력은 이렇게 작성한다.
◦
돈을 가지고 있는 사람은 돈을 보낼 때 문제를 낸다.
◦
돈을 받는 사람은 문제를 맞추고 소유권을 가져온다.
◦
원하면 다음과 같은 트랜잭션도 가능하다.
▪
내가 돈을 보내면서 “5에 몇을 더하면 9가 될까요?" 문제를 낸다.
▪
그러면 누군가 가장 먼저 4를 넣고 가져오면 된다.
•
입력 : 누군가 A 에게 돈을 보냈다.
•
출력 : A가 문제를 냈다.“5에서 몇을 더하면 9가 될까요?"
◦
내 돈을 보내면서 문제를 냈다.
◦
즉 문제를 통해 내 돈을 잠궜다.
◦
그래서 이를 잠금 스크립트라 한다.
•
입력 : B가 정답을 맞췄다.“정답은 4”
◦
누군가가 정답을 맞췄다.
◦
문제를 풀었기 때문에 이를 해제 스크립트라 한다.
•
출력 : B는 자기 주소로 비트코인을 보냈다.
피터 도트의 퀴즈와 상금
•
2013년 피터 도트는 자신의 2.48BTC 비트코인을 잠그고, 다음 문제를 냈다.
•
“해시 충돌을 찾아라.”
•
2017년 구글이 이 문제를 풀어서 가져갔다.
•
비트코인의 서명과 검증
◦
그럼 일반적으로 사람들은 어떤 문제를 낼까?
◦
잠금 스크립트 : “이 공개키의 서명을 만들 수 있으면 이 돈을 가져가.”
▪
잠금 스크립트엔 공개키가 들어있다.
▪
그래서 다른말로 ScriptPubKey라 부른다.
◦
해제 스크립트 : “이것이 나의 서명이야. 그러니까 이 돈은 내꺼.”
▪
해제 스크립트에는 서명값이 들어있다.
▪
그래서 다른말로 ScriptSig라 부른다.
지갑 속에 나의 비트코인 총액은 어떻게 표기할 수 있을까?
•
장부에 있는 모든 UTXO에 있는 잠금 스크립트를 본다.
•
잠금 스크립트 내에 다음 문제를 찾는다.
◦
“내 주소(공개키)의 서명값을 제출하라.”
•
그 UTXO에 달린 비트코인의 합을 계산한다.
•
지갑에서 잔액을 보여준다.
트랜잭션의 출력
•
다음은 출력의 예시이다. 두 개의 출력이 있는것을 알 수 있다.
"vout": [
{
"value": 0.01500000,
"scriptPubKey": "OP_DUP OP_HASH160 ab68025513c3dbd2f7b92a94e0581f5d50f654e7 OP_EQUALVERIFY OP_CHECKSIG"
},
{
"value": 0.08450000,
"scriptPubKey": "OP_DUP OP_HASH160 7f9b1a7fb68d60c536c2fd8aeaa53a8f3cc025a8 OP_EQUALVERIFY OP_CHECKSIG"
}
]
JSON
복사
•
value는 이 출력에 달려있는 비트코인 금액이다.
•
scriptPubKey는 이 돈을 가져갈 수 있는 암호화 퍼즐이다.
•
이 출력을 네트워크로 다른사람에게 보낼때는 이 내용을 다음과 같이 직렬화 해서 보낸다.
크기 | 필드 | 설명 |
8 바이트(리틀 엔디언) | 금액 | 사토시 단위의 비트코인 가치 |
1 바이트(VarInt) | 잠금 스크립트 길이 | 뒤이어 나오는 바이트 단위의 잠금 스크립트 길이 |
가변 길이 | 잠금 스크립트 | 출력을 소비하기 위한 조건을 정의한 스크립트 |
직렬화란?
위의 보이는 그림처럼 json같은 형태로 구조화 해서 전송할 수 있다. 하지만 구조화된 데이터는 필요없는 텍스트가 많아 전송시 낭비가 심하다.
직렬화는 데이터를 일정 규칙대로 붙여서 보내는 것을 말한다. 수신자도 규칙을 알기 때문에 다시 구조화를 할 수 있다.
VarInt (Variable Integer)
•
큰 정수를 표현할 때 가변적 용량을 사용하여 효율성을 높이기 위해 고안되었다.
•
이론적으로는 1바이트에서 표시할 수 있는 수는 255까지이다.
•
더 큰수를 표현하기 위해선 더 많은 용량을 확보해야 하는데, 잘 사용하지 않는 경우 용량 낭비가 심할 수 있어서 고안되었다.
•
접두부 1바이트에 사용할 바이트 수를 결정하고 이후에 숫자를 표현한다
◦
접두부 없음 : 0~252까지 표현 (예약 접두부 3개 제외)
◦
fd : 이후 2바이트가 숫자로 표현된다. (2^16)-1까지
◦
fe : 이후 4바이트가 숫자로 표현된다. (2^32)-1까지
◦
ff : 이후 8바이트가 숫자로 표현된다. (2^64)-1까지
•
위 트랜잭션을 직렬화하면 다음처럼 표현이 가능하다.
◦
위 텍스트는 앨리스 거래의 전체 트랜잭션이다. 매우 복잡한 거래가 직렬화를 전송 용량을 줄일 수 있다.
◦
하지만 저 텍스트는 사람이 해석하기 위해서는 규칙을 따라가야 한다. (색깔로 잘 구분해서 따라오자)
◦
02 : 두 개의 출력이 있다는 의미이다.
◦
60e3160000000000 : 1,500,000사토시를 보낸다는 의미이다.
▪
1500000을 16진수로 나타내면 16 e3 60인데 리틀 엔디언이라 작은 자릿수를 더 앞에 적는다.
▪
따라서 60 e3 16 00 00 00 00 00이 되었다.
◦
19 : scriptPubKey의 길이를 나타낸다. 총 25바이트이나 16진수로 변경되어 19가 되었다.
◦
76 a9 14 ab68025513c3dbd2f7b92a94e0581f5d50f654e7 88 ac : scriptPubKey이다.
▪
스크립트 명령어는 향후 다룰 예정이다. 지금은 직렬화된 데이터를 해석하는데 촛점을 맞추자.
▪
76 : 스크립트 명령어중 OP_DUP를 의미한다.
▪
a9 : 스크립트 명령어 중 OP_HASH160을 의미한다.
▪
14 : 다음 20 바이트(16진수로 14)는 데이터임을 의미한다.
▪
ab68025513c3dbd2f7b92a94e0581f5d50f654e7 : 스크립트에 사용하는 데이터 영역이다.
▪
88 : 스크립트 명령어 중 OP_EQUALVERIFY을 의미한다.
▪
ac : 스크립트 명령어 중 OP_CHECKSIG를 의미한다.
◦
그 뒤 굵은 글씨로 된 하나의 출력이 더 있다. 각자 해석해보자.
트랜잭션의 입력
•
다음은 입력의 예시이다.
"vin": [
{
"txid": "7957a35fe64f80d234d76d83a2a8f1a0d8149a41d81de548f0a65a8a999f6f18",
"vout": 0,
"scriptSig" : "3045022100884d142d86652a3f47ba4746ec719bbfbd040a570b1deccbb6498c75c4ae24cb02204b9f039ff08df09cbe9f6addac960298cad530a863ea8f53982c09db8f6e3813[ALL] 0484ecc0d46f1918b30928fa0e4ed99f16a0fb4fde0735e7ade8416ab9fe423cc5412336376789d172787ec3457eee41c04f4938de5cc17b4a10fa336a8d752adf",
"sequence": 4294967295
}
]
JSON
복사
◦
입력은 어떤 돈을 다른 사람에게 보낼지를 결정해야 하고,
◦
UTXO에 달려있는 scriptPubKey인 암호화 퍼즐을 푼 정답이 필요하다.
비트코인을 송금할 때는 대상자를 따로 정하지 않고, 문제를 내서 그 문제를 푼 사람만이 이 돈을 가져가게 할 수 있는 구조이다.
일반적으로는 받는 사람만이 풀 수 있는 퍼즐을 낸다.
대표적인 문제로는 “특정 공개키에 해당하는 서명을 할 수 있는가?” 이다.
◦
txid 보낼 UTXO가 들어있는 트랜잭션의 식별자를 의미한다.
◦
vout 는 UTXO가 해당 트랜잭션의 몇번째 출력인지를 가르킨다.
◦
scriptSig 는 퍼즐의 정답이다.
◦
secuence는 일련번호이다.
•
입력의 직렬화 규칙은 다음과 같다.
크기 | 필드 | 설명 |
32 바이트 | 트랜잭션 해시 | 소비될 UTXO를 포함하는 트랜잭션에 대한 참조 |
4 바이트 | 출력 인덱스 | 소비된 UTXO의 인덱스 |
1 바이트 (VarInt) | 해제 스크립트 길이 | 이어 나오는 바이트 단위의 해제 스크립트 길이 |
가변 길이 | 해제 스크립트 | UTXO 잠금 스크립트의 조건을 충족하는 스크립트 |
4 바이트 | 시퀀스 번호 | 잠금 시간을 위해 사용하거나 비활성화(0xFFFFFFFF) |
•
다음 직렬화된 데이터를 통해 알아보자
◦
186f9f998a5aa6f048e51dd8419a14d8a0f1a8a2836dd734d2804fe65fa35779: 트랜잭션 해시
◦
00 00 00 00: UTXO 인덱스
◦
8b: scriptSig의 길이. 139 바이트를 16진수로 나타낸 값
◦
48 30 45 … d7 52 ad: scriptSig
◦
ff ff ff ff ff: 시퀀스 번호(비활성화)
UTXO(Unspent transaction output)
•
UTXO는 아직 입력에 사용 되지 않은 상태의 출력을 말한다.
•
즉 UTXO는 사용가능한 잔액을 의미하며
구현 : 입력과 출력
•
입력을 처리하기 위한 코드
class TxIn:
#입력의 구성요소 : 어떤 출력을 지불할건지에 대한 트랜잭션 ID와 인덱스, 스크립트, 시퀀스
def __init__(self, prev_tx, prev_index, script_sig=None, sequence=0xffffffff):
self.prev_tx = prev_tx
self.prev_index = prev_index
if script_sig is None:
self.script_sig = Script()
else:
self.script_sig = script_sig
self.sequence = sequence
def __repr__(self):
return '{}:{}'.format(
self.prev_tx.hex(),
self.prev_index,
)
#입력을 파싱하는 함수
@classmethod
def parse(cls, s):
#32바이트의 이전 트랜잭션 ID
prev_tx = s.read(32)[::-1]
#출력의 인덱스
prev_index = little_endian_to_int(s.read(4))
#스크립트를 각각 파싱한다.
script_sig = Script.parse(s)
#시퀀스
sequence = little_endian_to_int(s.read(4))
#초기화
return cls(prev_tx, prev_index, script_sig, sequence)
#직렬화를 위한 함수
def serialize(self):
#참조할 출력이 있는 트랜잭션
result = self.prev_tx[::-1]
#트랜잭션의 인덱스
result += int_to_little_endian(self.prev_index, 4)
#입력 스크립트
result += self.script_sig.serialize()
#시퀀스
result += int_to_little_endian(self.sequence, 4)
return result
def fetch_tx(self, testnet=False):
return TxFetcher.fetch(self.prev_tx.hex(), testnet=testnet)
#입력에 정의된 비트코인 액수를 리턴하는 함수
def value(self, testnet=False):
#이전 트랜잭션에서 값을 가져오는 사용자 정의 함수를 사용하여
#대상 출력을 fecth함.
tx = self.fetch_tx(testnet=testnet)
#출력에 정의되어 있는 액수를 가져온다.
return tx.tx_outs[self.prev_index].amount
#이전 트랜잭션에서 공개키를 가져오는 함수
def script_pubkey(self, testnet=False):
tx = self.fetch_tx(testnet=testnet)
return tx.tx_outs[self.prev_index].script_pubkey
Python
복사
•
출력을 처리하기 위한 코드
class TxOut:
#출력의 구성요소 : 금액, 스크립트
def __init__(self, amount, script_pubkey):
self.amount = amount
self.script_pubkey = script_pubkey
def __repr__(self):
return '{}:{}'.format(self.amount, self.script_pubkey)
@classmethod
def parse(cls, s):
amount = little_endian_to_int(s.read(8))
script_pubkey = Script.parse(s)
return cls(amount, script_pubkey)
def serialize(self):
result = int_to_little_endian(self.amount, 8)
result += self.script_pubkey.serialize()
return result
Python
복사