def filerload(self, flag=1): """ 필터로드 메시지의 페이로드로 직렬화하여 메시지를 생성하는 함수 - 라이트 노드가 블룸 필터를 생성 후, 이 블룸 필터를 풀 노드에 전송해야함 - 풀 노드에게 보내는 VersionMessage의 렐레이 필드 값을 0으로 설정 - 풀 노드는 블룸 필터를 수신 받기 전까지 트랜잭션 메세지를 전송하지 않음 - 풀 노드로 전송하기 위한 블룸 필터 메시지 생성 필요 :param flag: 비트 필드 업데이트 플래그 - 0 : 풀 노드는 동작 중 비트 필드 업데이트 x - 1,2 : 특정 조건하에 새로운 입력을 필터에 더해 비트 필드 업데이트 :return: 직렬화한 페이로드를 담고 있는 GenericMessage 객체 """ # 비트 필드의 길이 정보 가변 정수 형식 payload = encode_varint(self.size) # 비트 필드 정보 payload += self.filter_bytes() # 해시 함수 개수 : 4바이트 리틀엔디언 payload += int_to_little_endian(self.function_count, 4) # tweak 정보 : 4바이트 리틀엔디언 payload += int_to_little_endian(self.tweak, 4) # matched item flag : 1 바이트 리틀엔디언 payload += int_to_little_endian(flag, 1) return GenericMessage(b"filterload", payload)
def sig_hash(self, input_index): """ 해제 스크립트에 서명을 포함시켜 트랜잭션을 변형 step1 : 해제 스크립트 삭제 - 서명 검증시 트랜잭션의 해제 스크립트 삭제 - 서명 생성시 동일 - 입력이 여러 개인 경우, 해당 입력의 해제 스크립트 삭제 step2 : 이전 트랜잭션 출력의 잠금 스크립트를 입력의 해제 스크립트에 삽입 - 트랜잭션 입력에 대해 TxIn.script_pubkey를 사용하여 해제 스크립트 입수 가능 step3 : 해시 유형 추가 - 서명해시를 구하기 위한 메시지에 포함시킬 필드 결정 - SIGHASH_ALL : 현재 입력과 다른 모든 입출력을 모두 포함 step4 : 해시 계산 - 최종 변경된 트랜잭션의 hash256 해시값 계산 - 32바이트 빅엔디언 정수로 변환 :param input_index: 현재 입력에 대한 인덱스 :return: 변형된 트랜잭션의 서명해시 i.e 서명을 포함시킨 직렬화 정보 """ # 버전 : 4바이트 리틀 엔디언 result = int_to_little_endian(self.version, 4) # 이전 트랜잭션 정보 : 이전 트랜잭션 개수 및 이전 트랜잭션들의 정보 # 서명을 포함시키기 위해 serialize()와 다르게 직렬화 result += encode_varint(len(self.tx_inputs)) for idx, tx_in in enumerate(self.tx_inputs): # 현재 입력인 경우 if idx == input_index: # 해당 트랜잭션 해시값에 대응되는 트랜잭션의 잠금 스크립트로 스크립트 대체 tx_in_copy = TxIn(prev_tx=tx_in.prev_tx, prev_index=tx_in.prev_index, script_sig=tx_in.script_pubkey(self.testnet), sequence=tx_in.sequence) result += tx_in_copy.serialize() # 다른 입력 else: # 해제 스크립트는 포함시키지 않는다 tx_in_copy = TxIn(prev_tx=tx_in.prev_tx, prev_index=tx_in.prev_index, sequence=tx_in.sequence) result += tx_in_copy.serialize() # 트랜잭션 출력 : 출력 개수 및 출력들의 정보 result += encode_varint(len(self.tx_outputs)) for tx_output in self.tx_outputs: result += tx_output.serialize() # 록타임 : 4바이트의 리틀엔디언 result += int_to_little_endian(self.locktime, 4) # 해시 유형 덧붙임 : SIGHASH_ALL 사용함 result += int_to_little_endian(SIGHASH_ALL, 4) # 서명 해시 생성 result256 = hash256(result) sig_message_hash = int.from_bytes(result256, "big") return sig_message_hash
def serialize(self): # 이전 트랜잭션 해시값 : 32바이트 리틀엔디언 result = self.prev_tx[::-1] # 이전 트랜잭션 번호 : 4바이트 리틀엔디언 result += int_to_little_endian(self.prev_index, 4) # 해제 스크립트 result += self.script_sig.serialize() # 시퀀스 필드 : 4바이트 리틀엔디언 result += int_to_little_endian(self.sequence, 4) return result
def exercise4(): from io import BytesIO from ecc.secp256k1 import S256Point, Signature from ecc.utils import encode_varint, hash256, int_to_little_endian from ecc.transaction import Tx, SIGHASH_ALL # 문제 : 주어진 p2sh 다중 서명 스크립트와 서명이 주어질 때, 해당 서명 검증 hex_tx = '0100000001868278ed6ddfb6c1ed3ad5f8181eb0c7a385aa0836f01d5e4789e6bd304d87221a000000db00483045022100dc92655fe37036f47756db8102e0d7d5e28b3beb83a8fef4f5dc0559bddfb94e02205a36d4e4e6c7fcd16658c50783e00c341609977aed3ad00937bf4ee942a8993701483045022100da6bee3c93766232079a01639d07fa869598749729ae323eab8eef53577d611b02207bef15429dcadce2121ea07f233115c6f09034c0be68db99980b9a6c5e75402201475221022626e955ea6ea6d98850c994f9107b036b1334f18ca8830bfff1295d21cfdb702103b287eaf122eea69030a0e9feed096bed8045c8b98bec453e1ffac7fbdbd4bb7152aeffffffff04d3b11400000000001976a914904a49878c0adfc3aa05de7afad2cc15f483a56a88ac7f400900000000001976a914418327e3f3dda4cf5b9089325a4b95abdfa0334088ac722c0c00000000001976a914ba35042cfe9fc66fd35ac2224eebdafd1028ad2788acdc4ace020000000017a91474d691da1574e6b3c192ecfb52cc8984ee7b6c568700000000' hex_sec = '03b287eaf122eea69030a0e9feed096bed8045c8b98bec453e1ffac7fbdbd4bb71' hex_der = '3045022100da6bee3c93766232079a01639d07fa869598749729ae323eab8eef53577d611b02207bef15429dcadce2121ea07f233115c6f09034c0be68db99980b9a6c5e754022' hex_redeem_script = '475221022626e955ea6ea6d98850c994f9107b036b1334f18ca8830bfff1295d21cfdb702103b287eaf122eea69030a0e9feed096bed8045c8b98bec453e1ffac7fbdbd4bb7152ae' sec = bytes.fromhex(hex_sec) der = bytes.fromhex(hex_der) # 해답 # sec 공개키와 der 서명을 통해 해당 객체 생성 point = S256Point.parse(sec) signature = Signature.parse(der) redeem_script = Script.parse(BytesIO(bytes.fromhex(hex_redeem_script))) stream = BytesIO(bytes.fromhex(hex_tx)) tx = Tx.parse(stream) # 두번째 서명을 만들기 위한 트랜잭션의 해시 계산 sig = int_to_little_endian(tx.version, 4) # 이전 트랜잭션 정보 : 이전 트랜잭션 개수 및 이전 트랜잭션들의 정보 sig += encode_varint(len(tx.tx_inputs)) # 서명 : 트랜잭션 입력의 해제 스크립트를 리딤 스크립트로 대체 cur_input = tx.tx_inputs[0] tx_in_copy = TxIn(prev_tx=cur_input.prev_tx, prev_index=cur_input.prev_index, script_sig=redeem_script, sequence=cur_input.sequence) sig += tx_in_copy.serialize() # 트랜잭션 출력 : 출력 개수 및 출력들의 정보 sig += encode_varint(len(tx.tx_outputs)) for tx_output in tx.tx_outputs: sig += tx_output.serialize() # 록타임 : 4바이트의 리틀엔디언 sig += int_to_little_endian(tx.locktime, 4) # 해시 유형 덧붙임 : SIGHASH_ALL 사용함 sig += int_to_little_endian(SIGHASH_ALL, 4) # 서명 해시 생성 result256 = hash256(sig) sig_message_hash = int.from_bytes(result256, "big") print(point.verify(sig_message_hash, signature))
def hash_prevouts(self): if self._hash_prevouts is None: all_prevouts = b'' all_sequence = b'' for tx_in in self.tx_inputs: all_prevouts += tx_in.prev_tx[::-1] + int_to_little_endian( tx_in.prev_index, 4) all_sequence += int_to_little_endian(tx_in.sequence, 4) self._hash_prevouts = hash256(all_prevouts) self._hash_sequence = hash256(all_sequence) return self._hash_prevouts
def serialize(self): # 금액 : 8바이트 리틀엔디언 result = int_to_little_endian(self.amount, 8) # 해제 스크립트 result += self.script_pubkey.serialize() return result
def serialize(self): """ :return: 직렬화된 str """ # 버전 : 4바이트 리틀 엔디언 result = int_to_little_endian(self.version, 4) # 이전 트랜잭션 정보 : 이전 트랜잭션 개수 및 이전 트랜잭션들의 정보 result += encode_varint(len(self.tx_inputs)) for tx_input in self.tx_inputs: result += tx_input.serialize() # 트랜잭션 출력 : 출력 개수 및 출력들의 정보 result += encode_varint(len(self.tx_outputs)) for tx_output in self.tx_outputs: result += tx_output.serialize() # 록타임 : 4바이트의 리틀엔디언 result += int_to_little_endian(self.locktime, 4) return result
def serialize(self): # 버전 : 4바이트 리틀엔디언 result = int_to_little_endian(self.version, 4) # 이전 블록 해시값 : 32바이트 리틀엔디언 result += self.prev_block[::-1] # 머클루트 : 32 바이트 리틀엔디언 result += self.merkle_root[::-1] # 타임스탬프 : 4 바이트 리틀엔디언 result += int_to_little_endian(self.timestamp, 4) # 비트값 : 4바이트 result += self.bits # 논스값 : 4바이트 result += self.nounce return result
def serialize(self): # 요청하는 항목의 개수 : 가변 정수 형식 payload = encode_varint(len(self.data)) # 각 항목은 (유형, 항목을 특정하는 해시 식별자)로 구성 for data_type, identifier in self.data: # 유형 : 4바이트 리틀 엔디언 payload += int_to_little_endian(data_type, 4) # 해시 식별자 : 리틀 엔디언 payload += identifier[::-1] return payload
def sig_hash_bip143(self, input_index, redeem_script=None, witness_script=None): '''Returns the integer representation of the hash that needs to get signed for index input_index''' tx_in = self.tx_inputs[input_index] # per BIP143 spec s = int_to_little_endian(self.version, 4) s += self.hash_prevouts() + self.hash_sequence() s += tx_in.prev_tx[::-1] + int_to_little_endian(tx_in.prev_index, 4) if witness_script: script_code = witness_script.serialize() elif redeem_script: script_code = p2pkh_script(redeem_script.cmds[1]).serialize() else: script_code = p2pkh_script( tx_in.script_pubkey(self.testnet).cmds[1]).serialize() s += script_code s += int_to_little_endian(tx_in.value(), 8) s += int_to_little_endian(tx_in.sequence, 4) s += self.hash_outputs() s += int_to_little_endian(self.locktime, 4) s += int_to_little_endian(SIGHASH_ALL, 4) return int.from_bytes(hash256(s), 'big')
def raw_serialize(self): result = b"" for command in self.commands: # 명령어가 정수인 경우는 오피코드 if type(command) == int: result += int_to_little_endian(command, 1) # 명령어가 바이트인 경우, 길이 정보를 추가 else: length = len(command) # 길이가 75 이하이면, 1바이트로 표현 if length <= 75: result += int_to_little_endian(length, 1) # OP_PUSHDATA1 삽입 후, 1바이트로 표현 elif 75 < length < 0x100: result += int_to_little_endian(76, 1) result += int_to_little_endian(length, 1) # OP_PUSHDATA2 삽입 후, 2바이트로 표현 elif 0x100 <= length <= 520: result += int_to_little_endian(77, 1) result += int_to_little_endian(length, 2) else: raise ValueError("too long an command") # 길이 정보 뒤에 실제 명령어 추가 result += command return result
def serialize(self): # 프로토콜 버전 : 4바이트 리틀엔디언 ret = int_to_little_endian(self.version, 4) # 서비스 정보 : 8바이트 리틀엔디언 ret += int_to_little_endian(self.services, 8) # 타임스탬프 : 8바이트 리틀엔디언 ret += int_to_little_endian(self.timestamp, 8) # 수신자 서비스 정보 : 8바이트 리틀엔디언 ret += int_to_little_endian(self.receiver_services, 8) # 수신자 IP : 16바이트 (첫 12바이트는 IPv6 형식) ret += b'\x00' * 10 + b'\xff\xff' + self.receiver_ip # 수신자 포트 정보 : 2바이트 빅엔디언 ret += self.receiver_port.to_bytes(2, "big") # 송신자 서비스 정보 : 8바이트 리틀엔디언 ret += int_to_little_endian(self.sender_services, 8) # 송신자 IP : 16바이트 (첫 12바이트는 IPv6 형식) ret += b'\x00' * 10 + b'\xff\xff' + self.sender_ip # 송신자 포트 정보 : 2바이트 빅엔디언 ret += self.sender_port.to_bytes(2, "big") # 논스 : 8바이트 ret += self.nonce # 사용자 에이전트 : 가변필드 ret += encode_varint(len(self.user_agent)) ret += self.user_agent # 높이 : 4바이트 리틀엔디언 ret += int_to_little_endian(self.latest_block, 4) # 릴레이 : 2비트 if self.relay: ret += b"\x01" else: ret += b"\x00" return ret
def serialize(self): # 프로토컬 버전 : 4바이트 리틀엔디언 ret = int_to_little_endian(self.version, 4) # 블록 헤더 개수 : 가변 정수 ret += encode_varint(self.num_hashes) # 시작 블록 : 리틀엔디언 ret += self.start_block[::-1] # 마지막 블록 : 리틀엔디언 ret += self.end_block[::-1] return ret
def serialize_segwit(self): """ 세그윗 트랜잭션 직렬화 - 이전 트랜잭션의 해제 스크립트는 증인 필드에 들어감 - 증인 필드 관련 정보는 마지막에 직렬화 :return: 직렬화된 str """ # 버전 : 4바이트 리틀 엔디언 result = int_to_little_endian(self.version, 4) # 마커 정보 result += b"\x00\x01" # 이전 트랜잭션 정보 : 이전 트랜잭션 개수 및 이전 트랜잭션들의 정보 result += encode_varint(len(self.tx_inputs)) for tx_input in self.tx_inputs: result += tx_input.serialize() # 트랜잭션 출력 : 출력 개수 및 출력들의 정보 result += encode_varint(len(self.tx_outputs)) for tx_output in self.tx_outputs: result += tx_output.serialize() # 각 입력에 있는 증인 필드 : 가변 정수 형식 for tx_in in self.tx_inputs: result += int_to_little_endian(len(tx_in.witness), 1) for item in tx_in.witness: if type(item) == int: result += int_to_little_endian(item, 1) else: result += encode_varint(len(item)) + item # 록타임 : 4바이트의 리틀엔디언 result += int_to_little_endian(self.locktime, 4) return result
def serialize(self): # 매직 넘버 : 4바이트 ret = self.magic # 커맨드 필드 : 12바이트(빈공간 0x00포함) ret += self.command ret += b"\x00" * (12 - len(self.command)) # 페이로드 길이 : 4바이트 리틀엔디언 ret += int_to_little_endian(len(self.payload), 4) # 체크섬 필드 : 4바이트 ret += hash256(self.payload)[:4] # 페이로드 필드 ret += self.payload return ret
def __init__(self, version=70015, services=0, timestamp=None, receiver_services=0, receiver_ip=b'\x00\x00\x00\x00', receiver_port=8333, sender_services=0, sender_ip=b'\x00\x00\x00\x00', sender_port=8333, nonce=None, user_agent=b'/programmingbitcoin:0.1/', latest_block=0, relay=False): self.version = version self.services = services if timestamp is None: self.timestamp = int(time.time()) else: self.timestamp = timestamp self.receiver_services = receiver_services self.receiver_ip = receiver_ip self.receiver_port = receiver_port self.sender_services = sender_services self.sender_ip = sender_ip self.sender_port = sender_port if nonce is None: self.nonce = int_to_little_endian(randint(0, 2**64), 8) else: self.nonce = nonce self.user_agent = user_agent self.latest_block = latest_block self.relay = relay