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))
Esempio n. 2
0
    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)
Esempio n. 4
0
    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