def parse(cls, stream, testnet=False): """ 직렬화된 정보를 파싱하는 클래스 메서드 :param stream: 직렬화 정보에 대한 스트림 - 직렬화 정보는 네트워크 통신이나 파일 입출력을 통해 받기 때문에 시간이 걸림 - 받아야 하는 트랜잭션 크기(직렬화 크기)가 커지면 받을 때까지 파싱 불가능 - 직렬화 정보의 스트림을 인수로 받아 파싱하면 효율적 i.e 받은 데이터까지 파싱 진행 가능 :param testnet : 테스트넷 여부. 직렬화 정보에는 없는 정보 :return: 역직렬화된 객체 """ # 버전 : 4바이트 리틀엔디언 version_info = stream.read(4) version = little_endian_to_int(version_info) # 사용할 비트코인 : 이전 트랜잭션 정보 num_inputs = read_varint(stream) inputs = [] for _ in range(num_inputs): inputs.append(TxIn.parse(stream)) # 비트코인을 보내는 종착지 : 트랜잭션 출력 정보 num_outputs = read_varint(stream) outputs = [] for _ in range(num_outputs): outputs.append(TxOut.parse(stream)) # 록타임 : 4바이트의 리틀엔디언 locktime = little_endian_to_int(stream.read(4)) return cls(version, inputs, outputs, locktime, testnet=testnet)
def parse(cls, stream): # 버전 : 4바이트 리틀엔디언 version = little_endian_to_int(stream.read(4)) # 이전 블록 해시값 : 32바이트 리틀엔디언 prev_block = stream.read(32)[::-1] # 머클루트 : 32 바이트 리틀엔디언 merkle_root = stream.read(32)[::-1] # 타임스탬프 : 4 바이트 리틀엔디언 timestamp = little_endian_to_int(stream.read(4)) # 비트값 : 4바이트 bits = stream.read(4) # 논스값 : 4바이트 nounce = stream.read(4) # 전체 트랜잭션 갯수 total = little_endian_to_int(stream.read(4)) # 해시값 : 가변 정수 및 32바이트 리틀엔디언 num_hashes = read_varint(stream) hashes = [] for _ in range(num_hashes): hashes.append(stream.read(32)[::-1]) # 플래그 비트 : 가변 정수 및 1바이트 num_flags = read_varint(stream) flag_bits = stream.read(num_flags) return cls(version, prev_block, merkle_root, timestamp, bits, nounce, total, hashes, flag_bits)
def parse(cls, stream): """ 블록 헤더의 필드는 고정 길이 - 정확히 80바이트 - 버전과 타임스탬프만 정수이고, 나머지는 바이트형 - 매우 작은 헤더의 크기는 단순 지급 검증(SPV, 11장) 기능에서 매우 중요 """ # 버전 : 4바이트 리틀엔디언 version = little_endian_to_int(stream.read(4)) # 이전 블록 해시값 : 32바이트 리틀엔디언 prev_block = stream.read(32)[::-1] # 머클루트 : 32 바이트 리틀엔디언 merkle_root = stream.read(32)[::-1] # 타임스탬프 : 4 바이트 리틀엔디언 timestamp = little_endian_to_int(stream.read(4)) # 비트값 : 4바이트 bits = stream.read(4) # 논스값 : 4바이트 nounce = stream.read(4) return cls(version, prev_block, merkle_root, timestamp, bits, nounce)
def parse(cls, stream): # 이전 트랜잭션 해시값 : 32바이트 리틀엔디언 prev_tx = stream.read(32)[::-1] # 이전 트랜잭션 번호 : 4바이트 리틀엔디언 prev_index = little_endian_to_int(stream.read(4)) # 해제 스크립트 script_sig = Script.parse(stream) # 시퀀스 필드 : 4바이트 리틀엔디언 seqeunce = little_endian_to_int(stream.read(4)) return cls(prev_tx, prev_index, script_sig, seqeunce)
def parse(cls, stream): """ 스크립트 파싱 - 첫 1바이트를 읽어 n이라 한다 - 0x01 ~ 0x4b(1~75) 사이의 값인 경우 : 이어서 n바이트 길이를 읽어 해당 숫자를 한 원소로 간주 - 0x4c 인 경우 : OP_PUSHDATA1 에 해당 => 바로 이후의 1 바이트 값이 그 다음 읽을 원소의 길이 정보를 표현 i.e OP_PUSHDATA1 <1바이트 리틀엔디언으로 표현한 원소 길이> <원소> 형태 - 0x4d 인 경우 : OP_PUSHDATA2 에 해당 => 바로 이후의 2 바이트 값이 그 다음 읽을 원소의 길이 정보를 표현 i.e OP_PUSHDATA1 <2바이트 리틀엔디언으로 표현한 원소 길이> <원소> 형태 - 78 이상인 경우 : 해당 바이트(n)은 오피코드(연산자) :param stream: 직렬화한 스크립트의 스트림 :return: 역질력화된 스크립트 객체 """ # 스크립트는 가변 길이 필드로 시작 length = read_varint(stream) commands = [] count = 0 while count < length: current = stream.read(1) count += 1 current_bytes = current[0] # 다음 current_bytes가 한 데이터 원소 if 1 <= current_bytes <= 75: n = current_bytes commands.append(stream.read(n)) count += n # 다음 한 바이트가 파싱할 데이터 원소의 길이 elif current_bytes == 76: data_length = little_endian_to_int(stream.read(1)) commands.append(stream.read(data_length)) count += (data_length + 1) # 다음 두 바이트가 파싱할 데이터 원소의 길이 elif current_bytes == 77: data_length = little_endian_to_int(stream.read(2)) commands.append(stream.read(data_length)) count += (data_length + 2) # 해당 바이트 자체가 오피코드 else: op_code = current_bytes commands.append(op_code) # 스크립트 파싱이 정확하게 되었는지 확인 if count != length: raise SyntaxError("Parsing srcipt failed") return cls(commands)
def parse_segwit(cls, stream, testnet=False): """ 세그윗 트랜잭션의 직렬화된 정보를 파싱하는 클래스 메서드 - 세그윗 마커 - 입력마다 있는 증인 필드 :param stream: 직렬화 정보에 대한 스트림 :param testnet: 테스트넷 여부. 직렬화 정보에는 없는 정보 :return: 역직렬화된 객체 """ # 버전 : 4바이트 리틀엔디언 version_info = stream.read(4) version = little_endian_to_int(version_info) # 세그윗 마커 정보 : 2바이트 marker = stream.read(2) if marker != b"\x00\x01": raise RuntimeError("Not a segwit transaction : {}".format(marker)) # 사용할 비트코인 : 이전 트랜잭션 정보 num_inputs = read_varint(stream) inputs = [] for _ in range(num_inputs): inputs.append(TxIn.parse(stream)) # 비트코인을 보내는 종착지 : 트랜잭션 출력 정보 num_outputs = read_varint(stream) outputs = [] for _ in range(num_outputs): outputs.append(TxOut.parse(stream)) # 입력마다 주어지는 증인(witness) 필드 for tx_in in inputs: num_items = read_varint(stream) items = [] for _ in range(num_items): item_len = read_varint(stream) if item_len == 0: items.append(0) else: items.append(stream.read(item_len)) tx_in.witness = items # 록타임 : 4바이트의 리틀엔디언 locktime = little_endian_to_int(stream.read(4)) return cls(version, inputs, outputs, locktime, testnet=testnet, segwit=True)
def coinbase_height(self): """ 트랜잭션이 소속한 블록의 높이 - 해제 스크립트의 첫 원소 규정 - 채굴하고 있는 블록의 높이를 코인베이스 해제 스크립트의 첫 원소로 한다는 소프트포크 규정 - 코인베이스 해제 스크립트를 통해 소속된 블록이 몇 번째 블록인지 확인 가능 - 높이는 리틀엔디언 정수로 표현 - 다른 블록에서 동일한 트랜잭션 ID를 갖는 문제 해결 - 서로 다른 코인베이스 트랜잭션은 소속된 블록마다 높이가 다름 - 해당 트랙잭션들의 직렬화 정보가 바이트 단위로 똑같을 수 없음 - 소프트포크 - 포크 - 비트코인 네트워크를 구성하는 채굴 노드의 소프트웨어의 버전을 업데이트하는 것 - 탈중앙화된 비트코인 네트워크는 동시 업데이트가 불가능 - 각 채굴 노드는 점진적으로 버전업이 이루어짐 - 소프트포크 - 예전 버전 노드와 최신 버전 노드가 혼재되어 있어도 네트워크가 멈추지 않고 돌아가도록 갱신하는 것 :return: 코인베이스 트랜잭션의 블록 높이 """ if not self.is_coinbase(): raise ValueError("the transaction is not coinbase") tx_input = self.tx_inputs[0] script_sig = tx_input.script_sig return little_endian_to_int(script_sig.commands[0])
def my_address(): from ecc.secp256k1 import PrivateKey my_message = b'YJ Choi secret' secret_key = little_endian_to_int(hash256(my_message)) private_key = PrivateKey(secret_key) address = private_key.public_key.address(testnet=True) print(address)
def parse(cls, stream): # 금액 : 8바이트 리틀엔디언 amount = little_endian_to_int(stream.read(8)) # 해제 스크립트 script_pubkey = Script.parse(stream) return cls(amount, script_pubkey)
def fetch(cls, tx_id, testnet=False, fresh=False): """ 찾고자 하는 트랜잭션 id롤 가지고 트랜잭션을 찾아 반환 - 전체 트랜잭션을 반환 - 해당 트랜잭션의 해시값(id)를 이용하여 검증을 하기 위해서 :param tx_id: 찾고자 하는 트랜잭션 id :param testnet: 테스트넷 여부 :param fresh: 캐시 이용 여부 :return: 트랜잭션 id에 대응하는 트랜잭션 반환 """ if fresh or (tx_id not in cls.cache): url = "{}/tx/{}/hex".format(cls.get_url(testnet), tx_id) response = requests.get(url) try: raw = bytes.fromhex(response.text.strip()) except ValueError: raise ValueError('unexpected response: {}'.format(response)) if raw[4] == 0: raw = raw[:4] + raw[6:] tx = Tx.parse(BytesIO(raw), testnet=testnet) tx.locktime = little_endian_to_int(raw[-4:]) else: tx = Tx.parse(BytesIO(raw), testnet=testnet) if tx.id() != tx_id: raise ValueError("Not the same id : {} vs {}".format( tx.id(), tx_id)) cls.cache[tx_id] = tx cls.cache[tx_id].testnet = testnet return cls.cache[tx_id]
def parse(cls, stream, testnet=False): # 매직 넘버 : 4바이트 magic = stream.read(4) # 올바른 스트림인지 확인 if magic == b"": raise RuntimeError("Connection Reset") if testnet: expected_magic = TESTNET_NETWORK_MAGIC else: expected_magic = NETWORK_MAGIC if magic != expected_magic: raise RuntimeError("magic is not right {} (expected : {}".format( magic, expected_magic)) # 커맨드 필드 : 12바이트(빈공간 0x00포함) command = stream.read(12) command = command.strip(b"\x00") # 페이로드 길이 : 4바이트 리틀엔디언 payload_length = little_endian_to_int(stream.read(4)) # 체크섬 필드 : 4바이트 checksum = stream.read(4) # 페이로드 필드 payload = stream.read(payload_length) # 체크섬을 통한 에러 확인 payload_checksum = hash256(payload)[:4] if payload_checksum != checksum: raise RuntimeError("checksum error") return cls(command, payload, testnet)
def example2(): print("Example : Height of Transaction in the block") stream = BytesIO( bytes.fromhex( '5e03d71b07254d696e656420627920416e74506f6f6c20626a31312f4542312f4144362f43205914293101fabe6d6d678e2c8c34afc36896e7d9402824ed38e856676ee94bfdb0c6c4bcd8b2e5666a0400000000000000c7270000a5e00e00' )) script_sig = Script.parse(stream) print("Height : {}".format(little_endian_to_int(script_sig.commands[0])))
def exercise6(): def assertEqual(title, my_sol, ans): print("{} : {}".format(title, my_sol == ans)) hex_merkle_block = '00000020df3b053dc46f162a9b00c7f0d5124e2676d47bbe7c5d0793a500000000000000ef445fef2ed495c275892206ca533e7411907971013ab83e3b47bd0d692d14d4dc7c835b67d8001ac157e670bf0d00000aba412a0d1480e370173072c9562becffe87aa661c1e4a6dbc305d38ec5dc088a7cf92e6458aca7b32edae818f9c2c98c37e06bf72ae0ce80649a38655ee1e27d34d9421d940b16732f24b94023e9d572a7f9ab8023434a4feb532d2adfc8c2c2158785d1bd04eb99df2e86c54bc13e139862897217400def5d72c280222c4cbaee7261831e1550dbb8fa82853e9fe506fc5fda3f7b919d8fe74b6282f92763cef8e625f977af7c8619c32a369b832bc2d051ecd9c73c51e76370ceabd4f25097c256597fa898d404ed53425de608ac6bfe426f6e2bb457f1c554866eb69dcb8d6bf6f880e9a59b3cd053e6c7060eeacaacf4dac6697dac20e4bd3f38a2ea2543d1ab7953e3430790a9f81e1c67f5b58c825acf46bd02848384eebe9af917274cdfbb1a28a5d58a23a17977def0de10d644258d9c54f886d47d293a411cb6226103b55635' mb = MerkleBlock.parse(BytesIO(bytes.fromhex(hex_merkle_block))) merkle_root_hex = 'ef445fef2ed495c275892206ca533e7411907971013ab83e3b47bd0d692d14d4' merkle_root = bytes.fromhex(merkle_root_hex)[::-1] assertEqual("merkle root", mb.merkle_root, merkle_root) prev_block_hex = 'df3b053dc46f162a9b00c7f0d5124e2676d47bbe7c5d0793a500000000000000' prev_block = bytes.fromhex(prev_block_hex)[::-1] assertEqual("previous block", mb.prev_block, prev_block) timestamp = little_endian_to_int(bytes.fromhex('dc7c835b')) assertEqual("time stamp", mb.timestamp, timestamp) bits = bytes.fromhex('67d8001a') assertEqual("bits", mb.bits, bits) nonce = bytes.fromhex('c157e670') assertEqual("nonce", mb.nonce, nonce) total = little_endian_to_int(bytes.fromhex('bf0d0000')) assertEqual("total transactions", mb.total, total) hex_hashes = [ 'ba412a0d1480e370173072c9562becffe87aa661c1e4a6dbc305d38ec5dc088a', '7cf92e6458aca7b32edae818f9c2c98c37e06bf72ae0ce80649a38655ee1e27d', '34d9421d940b16732f24b94023e9d572a7f9ab8023434a4feb532d2adfc8c2c2', '158785d1bd04eb99df2e86c54bc13e139862897217400def5d72c280222c4cba', 'ee7261831e1550dbb8fa82853e9fe506fc5fda3f7b919d8fe74b6282f92763ce', 'f8e625f977af7c8619c32a369b832bc2d051ecd9c73c51e76370ceabd4f25097', 'c256597fa898d404ed53425de608ac6bfe426f6e2bb457f1c554866eb69dcb8d', '6bf6f880e9a59b3cd053e6c7060eeacaacf4dac6697dac20e4bd3f38a2ea2543', 'd1ab7953e3430790a9f81e1c67f5b58c825acf46bd02848384eebe9af917274c', 'dfbb1a28a5d58a23a17977def0de10d644258d9c54f886d47d293a411cb62261', ] hashes = [bytes.fromhex(h)[::-1] for h in hex_hashes] assertEqual("hashes", mb.hashes, hashes) flags = bytes.fromhex('b55635') assertEqual("bit flags", mb.flags, flags)
def example4(): print("목표값 설정 : 블록헤더의 해시값이 목표값보다 작을 때 작업 증명 유효") # 목표값 계산 bits = bytes.fromhex("e93c0118") exponent = bits[-1] coef = little_endian_to_int(bits[:-1]) target = coef * 256**(exponent - 3) print("exponent: {}, coef: {}".format(exponent, coef)) print("target: ") print("{:x}".format(target).zfill(64)) # 작업 증명 : 블록 헤더의 해시값을 리틀엔디언으로 표현 block_hash = hash256( bytes.fromhex( '020000208ec39428b17323fa0ddec8e887b4a7c53b8c0a0a220cfd0000000000000000005b0750fce0a889502d40508d39576821155e9c9e3f5c3157f961db38fd8b25be1e77a759e93c0118a4ffd71d' )) block_hash_int = little_endian_to_int(block_hash) print("block_hash:") print("{:x}".format(block_hash_int).zfill(64)) print("proof of work : {}".format(block_hash_int < target))
def bits_to_target(bits): """ 4바이트 비트를 작업 증명에 사용할 목표값으로 변환하는 함수 - 지수 : 마지막 바이트 - 계수 : 앞 세 바이트를 리틀엔디언으로 표현 => target = 계수 * pow(256, 지수-3) :param bits: 4바이트 비트 :return: 목표값 """ exponent = bits[-1] coef = little_endian_to_int(bits[:-1]) target = coef * 256**(exponent - 3) return target
def check_pow(self): """ 작업 증명 - 작업 증명으로 탈중앙화 방식의 비트코인 채굴이 가능 => 전체 네트워크 수준에서 비트코인 보안이 유지 - 특정 조건을 만족하는 작은 값을 찾는 것 - 특정 조건 : 목표값(target) - 작은 값 : 블록 헤더의 해시값 - 블록의 해시값을 리틀엔디언 정수로 표현하여 비교 - 블록 헤더의 해시 변경 방법 - 채굴자가 논스값을 변경 - 코인베이스 트랜잭션 변경 => 머클루트가 변경되어 새로운 해시 - 버전 필드 변경 :return: 자격 증명 결과 """ block_hash = hash256(self.serialize()) block_hash_int = little_endian_to_int(block_hash) return block_hash_int < self.target()