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 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_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 sig_hash(self, input_index, reedem_script=None): """ 해제 스크립트에 서명을 포함시켜 트랜잭션을 변형 step1 : 해제 스크립트 삭제 - 서명 검증시 트랜잭션의 해제 스크립트 삭제 - 서명 생성시 동일 - 입력이 여러 개인 경우, 해당 입력의 해제 스크립트 삭제 step2 : 이전 트랜잭션 출력의 잠금 스크립트를 입력의 스크립트에 삽입 - p2pkh 스크립트 - 입력의 해제 스크립트 삽입 - 트랜잭션 입력에 대해 TxIn.script_pubkey를 사용하여 해제 스크립트 입수 가능 - p2sh 스크립트 - 리딤 스크립트 삽입 - p2sh 스크립트를 사용하는 트랙잭션은 입력마다 리딤 스크립트를 가지고 있음 step3 : 해시 유형 추가 - 서명해시를 구하기 위한 메시지에 포함시킬 필드 결정 - SIGHASH_ALL : 현재 입력과 다른 모든 입출력을 모두 포함 step4 : 해시 계산 - 최종 변경된 트랜잭션의 hash256 해시값 계산 - 32바이트 빅엔디언 정수로 변환 :param input_index: 현재 입력에 대한 인덱스 :param reedem_script : h2sh 스크립트 사용시 해당 입력에 대한 리딤 스크립트 :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: # 해제 스크립트를 대체할 스크립트 설정 if reedem_script: # h2sh 스크립트 사용시 해당 입력에 대한 리딤 스크립트로 스크립트 대체 script_sig = reedem_script else: # 해당 트랜잭션 해시값에 대응되는 트랜잭션의 잠금 스크립트로 스크립트 대체 script_sig = tx_in.script_pubkey(self.testnet) # 다른 입력인 경우 : 해제 스크립트 없음 else: script_sig = None tx_in_copy = TxIn(prev_tx=tx_in.prev_tx, prev_index=tx_in.prev_index, script_sig=script_sig, 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