def op_checksig(stack, message_hash): """ 해제 스크립트가 올바른지 검증하는 부분 - 올바른 경우, 스택에 1추가 - 올바르지 않은 경우, 스택에 0추가 :param stack: 명령어 및 원소 스택 :param message_hash: 메시지 해시 :return: 실행 결과 boolean """ # 필요한 원소는 2개 # : 서명(der 직렬화)과 공개키(sec 직렬화) if len(stack) < 2: return False pubkey_sec = stack.pop() signature_der = stack.pop()[:-1] try: pubkey = S256Point.parse(pubkey_sec) signature = Signature.parse(signature_der) except (ValueError, SyntaxError) as e: return False # 서명 검증 if pubkey.verify(z=message_hash, signature=signature): stack.append(encode_num(1)) else: stack.append(encode_num(0)) return True
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 example2(): print("Example 2 : 서명 검증") from ecc.secp256k1 import S256Point, Signature sec = bytes.fromhex( '0349fc4e631e3624a545de3f89f5d8684c7b8138bd94bdd531d2e213bf016b278a') der = bytes.fromhex( '3045022100ed81ff192e75a3fd2304004dcadb746fa5e24c5031ccfcf21320b0277457c98f02207a986d955c6e0cb35d446a89d3f56100f4d7f67801c31967743a9c8e10615bed' ) z = 0x27e0c5994dec7824e56dec6b2fcb342eb7cdb0d0957c2fce9882f715e85d81a6 point = S256Point.parse(sec) signature = Signature.parse(der) print(point.verify(z, signature))
def example2(): from ecc.secp256k1 import S256Point, Signature from ecc.utils import hash256 modified_tx = bytes.fromhex( '0100000001868278ed6ddfb6c1ed3ad5f8181eb0c7a385aa0836f01d5e4789e6bd304d87221a000000475221022626e955ea6ea6d98850c994f9107b036b1334f18ca8830bfff1295d21cfdb702103b287eaf122eea69030a0e9feed096bed8045c8b98bec453e1ffac7fbdbd4bb7152aeffffffff04d3b11400000000001976a914904a49878c0adfc3aa05de7afad2cc15f483a56a88ac7f400900000000001976a914418327e3f3dda4cf5b9089325a4b95abdfa0334088ac722c0c00000000001976a914ba35042cfe9fc66fd35ac2224eebdafd1028ad2788acdc4ace020000000017a91474d691da1574e6b3c192ecfb52cc8984ee7b6c56870000000001000000' ) h256 = hash256(modified_tx) z = int.from_bytes(h256, 'big') # sec 공개키는 리딤 스크립트에서 구할 수 있음 sec = bytes.fromhex( '022626e955ea6ea6d98850c994f9107b036b1334f18ca8830bfff1295d21cfdb70') point = S256Point.parse(sec) # der 서명은 해제 스크립트에서 구할 수 있음 der = bytes.fromhex( '3045022100dc92655fe37036f47756db8102e0d7d5e28b3beb83a8fef4f5dc0559bddfb94e02205a36d4e4e6c7fcd16658c50783e00c341609977aed3ad00937bf4ee942a89937' ) sig = Signature.parse(der) print(point.verify(z, sig))
def op_checkmultisig(stack, message_hash): """ m-of-n 다중 서명을 검증하는 명령어 - m-of-n 다중 서명 - n개의 공개키가 잠금 스크립트에 들어 있음 - 해당 잠금 스크립트를 해제하기 위해서는 최소 m개의 비밀키를 활용해야 하는 서명 - n개의 공개키 중에서 서로 다른 m개의 비밀키로 m개의 서명을 검증 - 스택의 구성 (top에서부터 시작) - 공개키 정보 - 공개키의 개수 n - n개의 공개키 - 서명 정보 - 비밀키의 개수 m - m개의 서명 => (1+n) + (1+m) = m+n+2 개의 원소 필요 - Off-by-One 버그 - 해당 명령어는 m+n+3개의 원소를 가져옴 - 추가로 가져오는 1개의 원소는 아무런 동작도 하지 않음 - 현재 스택의 크기가 m+n+2개인 경우에 검증에 실패하는 버그 => 원소가 모자르지 않도록 원소 1개를 미리 추가한 stack이 매개변수로 들어옴 :param stack: 명령어 및 원소 스택 :param message_hash: 메시지 해시 :return: 실행 결과 boolean """ if len(stack) < 1: return False # 공개키 개수 n = decode_num(stack.pop()) # 공개키가 모두 저장되어있는지 확인 if len(stack) <= n: return False # 공개키 저장 sec_pubkeys = [] for _ in range(n): sec_pubkeys.append(stack.pop()) # 비밀키 개수 m = decode_num(stack.pop()) # 비밀키가 모두 저장되어있는지 확인 if len(stack) <= m: return False # 비밀키에 해당하는 서명 저장 der_signatures = [] for _ in range(m): # 모든 der 서명은 SIGHASH_ALL로 서명된 것으로 간주 der_signatures.append(stack.pop()[:-1]) # Off-by-One 버그 => 미리 추가된 의미없는 원소를 삭제 stack.pop() try: # 공개키 정보를 통해 공개키 객체(S256Point) 리스트 생성 public_points = [ S256Point.parse(sec_pubkey) for sec_pubkey in sec_pubkeys ] # 비밀키에 대한 서명 정보를 통해 서명 객체(Signature) 리스트 생성 signatures = [ Signature.parse(der_signature) for der_signature in der_signatures ] # 비밀키에 대한 서명 정보를 이용하여 공개키 검증 for sig in signatures: # 검증을 해야하는 공개키가 없는 경우에는 검증 불가 if len(public_points) == 0: return False # 검증을 해야하는 공개키들에 대해서 검증 while public_points: public_point = public_points.pop(0) # 검증이 된다면 참 if public_point.verify(message_hash, sig): break # 검증이 되면 1을 추가한다 stack.append(encode_num(1)) except (ValueError, SyntaxError): return False return True