コード例 #1
0
    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
コード例 #2
0
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))
コード例 #3
0
    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)
コード例 #4
0
    def verify_input(self, input_index):
        """
        현재 트랜잭션 입력에 대한 검증
        step 1 : 현재 입력에 대응하는 결합 스크립트 생성
            - 입력에 대응하는 이전 트랜잭션 출력의 잠금 스크립트
            - 입력의 해제 스크립트
        step 2 : 현재 입력에 대응하는 서명 생성
        step 3 : 결합스크립트를 이용하여 서명 계산
        :param input_index: 현재 입력에 대한 인덱스
        :return: 검증 boolean
        """
        # 해당 트랜잭션 해시값에 대응되는 트랜잭션의 잠금 스크립트
        current_intput_tx = self.tx_inputs[input_index]
        current_pubkey = current_intput_tx.script_pubkey(testnet=self.testnet)

        # 스크립트 형식에 따라 리딤 스크립트 생성
        if current_pubkey.is_p2sh_script_pubkey():
            command = current_intput_tx.script_sig.commands[-1]
            redeem_script_raw = encode_varint(len(command)) + command
            redeem_script = Script.parse(BytesIO(redeem_script_raw))
        else:
            redeem_script = None

        # 현재 트랜잭션 입력에 대한 결합 스크립트
        combined = current_intput_tx.script_sig + current_pubkey

        # 현재 트랜잭션 입력에 대한 서명 생성
        message_hash = self.sig_hash(input_index, redeem_script)

        # 결합 스크립트를 통한 서명 검증
        return combined.evaluate(message_hash)
コード例 #5
0
    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
コード例 #6
0
    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
コード例 #7
0
    def serialize(self):
        """
        :return: 길이 정보가 포함된 직렬화
        """
        # 명령어 집합에 대한 직렬화
        result = self.raw_serialize()
        # 길이 정보
        total = len(result)

        return encode_varint(total) + result
コード例 #8
0
    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
コード例 #9
0
    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
コード例 #10
0
    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
コード例 #11
0
    def verify_input(self, input_index):
        """
        현재 트랜잭션 입력에 대한 검증
        step 1 : 현재 입력에 대응하는 결합 스크립트 생성
            - 입력에 대응하는 이전 트랜잭션 출력의 잠금 스크립트
            - 입력의 해제 스크립트
                - p2sh의 경우 리딤 스크립트
                    - 호환성을 위해 p2wpkh/p2wsh 또한 리딤 스크립트에 포함
                - p2wpkh 스크립트의 경우, 증인필드(서명, 공개키)
        step 2 : 현재 입력에 대응하는 서명 생성
        step 3 : 결합 스크립트를 이용하여 서명 계산
        :param input_index: 현재 입력에 대한 인덱스
        :return: 검증 boolean
        """
        # 해당 트랜잭션 해시값에 대응되는 트랜잭션의 잠금 스크립트
        current_intput_tx = self.tx_inputs[input_index]
        current_pubkey = current_intput_tx.script_pubkey(testnet=self.testnet)

        # 스크립트 형식에 따라 리딤 스크립트 생성
        if current_pubkey.is_p2sh_script_pubkey():
            command = current_intput_tx.script_sig.commands[-1]
            redeem_script_raw = encode_varint(len(command)) + command
            redeem_script = Script.parse(BytesIO(redeem_script_raw))

            # 리딤 스크립트 : p2sh-p2wpkh 이거나 p2sh-p2wsh 이다
            # p2wpkh인 경우 :
            if redeem_script.is_p2wpkh_script_pubkey():
                message_hash = self.sig_hash_bip143(input_index, redeem_script)
                witness = current_intput_tx.witness
            # p2wsh인 경우
            elif redeem_script.is_p2wsh_script_pubkey():
                # 증인 스크립트 추출
                cmd = current_intput_tx.witness[-1]

                # 가변 길이의 증인 스크립트 파싱
                raw_witness = encode_varint(len(cmd)) + cmd
                witness_script = Script.parse(BytesIO(raw_witness))
                message_hash = self.sig_hash_bip143(
                    input_index, witness_script=witness_script)
                witness = current_intput_tx.witness
            # p2sh인 경우
            else:
                message_hash = self.sig_hash(input_index, redeem_script)
                witness = None
        else:
            # 리딤 스크립트 : p2sh-p2wpkh 이거나 p2sh-p2wsh 이다
            # p2wpkh인 경우 :
            if current_pubkey.is_p2wpkh_script_pubkey():
                message_hash = self.sig_hash_bip143(input_index)
                witness = current_intput_tx.witness
            # p2wsh인 경우 :
            elif current_pubkey.is_p2wsh_script_pubkey():
                # 증인 스크립트 추출
                cmd = current_intput_tx.witness[-1]

                # 가변 길이의 증인 스크립트 파싱
                raw_witness = encode_varint(len(cmd)) + cmd
                witness_script = Script.parse(BytesIO(raw_witness))
                message_hash = self.sig_hash_bip143(
                    input_index, witness_script=witness_script)
                witness = current_intput_tx.witness
            # p2sh인 경우
            else:
                message_hash = self.sig_hash(input_index)
                witness = None

        # 현재 트랜잭션 입력에 대한 결합 스크립트
        combined = current_intput_tx.script_sig + current_pubkey

        # 결합 스크립트를 통한 서명 검증
        return combined.evaluate(message_hash, witness)
コード例 #12
0
    def evaluate(self, message_hash, witness):
        """
        결합 스크립트(잠금+해제)의 명령어들을 실행
        - 실제에서는 해제 및 잠금 스크립트를 분리하여 실행
          (why?) 해제 스크립트가 잠금 스크립트 실행에 영향을 주지 않기 위해
        :param message_hash: 서명해시
        :param witness: 세그윗인 경우 증인 필드(해제 스크립트가 포함되어 있음)
        :return: 실행 결과 boolean
            - True 인 경우만 해당 결합 스크립트가 유효하다는 의미
              i.e 해제 스크립트로 잠금 스크립트를 해제 가능

        p2sh 스크립트
        - 리딤 스크립트
            - 다중 서명 잠금 스크립트(공개키)에 해당
            - 다중 서명 스크립트의 경우 길이가 매우 길다
                => 다수의 공개키를 가지고 있어, 트랜잭션 출력의 잠금 스크립트 길이가 길어짐
        - 리딤 스크립트의 해시를 잠금 스크립트에 포함
            - [OP_HASH160, <hash>, OP_EQUAL] 형태
            - 리딤 스크립트 자체는 해제 스크립트에 포함
            - OP_HASH160(해제 스크립트의 리딤 스크립트) 와 <hash>가 같으면 유효하다고 판단
        - 리딤 스크립트에 대해서 유효하다고 판단한 경우
            - 리딤 스크립트의 명령어들(공개키포함)을 다시 명령어 집합(command)에 추가
            - 이후 명령 스택과 함께 명령어들을 실행
            - 명령어 집합이 [<리딤 스크립트>, OP_HASH160, <hash>, OP_EQUAL] 일 때가 예외 상황
                => 해당 집합 실행 후 유효하다고 판단하면, 리딤 스크립트의 명령어들을 명령어집합에 추가
        """
        # 스크립트 명령집합에서 명령어가 실행되면서 명령어가 하나씩 삭제
        # => commands 변수에 복사
        commands = self.commands[:]
        stack = []
        altstack = []

        while len(commands) > 0:
            command = commands.pop(0)

            # 연산자 명령인 경우 : 해당 명령 실행
            if type(command) == int:
                operation = OP_CODE_FUNCTIONS[command]

                # 흐름제어 명령어(99: op_if, 100: op_notif)
                if command in (99, 100):
                    # 명령을 수행하지 못하는 경우 로거에 남기고 거짓 반환
                    if not operation(stack, commands):
                        LOGGER.info("bad operation : {}".format(
                            OP_CODE_NAMES[command]))
                        print("bad operation : {}".format(
                            OP_CODE_NAMES[command]))
                        return False
                # 임시 스택(altstack)을 사용하는 명령어
                # 107: op_toaltstack, 108: op_fromaltstack
                elif command in (107, 108):
                    # 명령을 수행하지 못하는 경우 로거에 남기고 거짓 반환
                    if not operation(stack, altstack):
                        LOGGER.info("bad operation : {}".format(
                            OP_CODE_NAMES[command]))
                        print("bad operation : {}".format(
                            OP_CODE_NAMES[command]))
                        return False
                # 서명해시(message_hash)가 필요한 명령어
                # 172: op_checksig, 173: op_checksigverify
                # 174: op_checkmultisig, 175: op_checkmultisigverify,
                elif command in (172, 173, 174, 175):
                    # 명령을 수행하지 못하는 경우 로거에 남기고 거짓 반환
                    if not operation(stack, message_hash):
                        LOGGER.info("bad operation : {}".format(
                            OP_CODE_NAMES[command]))
                        print("bad operation : {}".format(
                            OP_CODE_NAMES[command]))
                        return False
                # 나머지 연산은 스택에 대한 연산들
                else:
                    if not operation(stack):
                        LOGGER.info("bad operation : {}".format(
                            OP_CODE_NAMES[command]))
                        print("bad operation : {}".format(
                            OP_CODE_NAMES[command]))
                        return False
            # 연산자가 데이터인 경우 : 스택에 저장
            else:
                stack.append(command)

                # p2sh 예외 상황 : [OP_HASH160, <hash>, OP_EQUAL]
                if len(commands) == 3 and \
                        commands[0] == 0xa9 and \
                        type(commands[1]) == bytes and len(commands[1]) == 20 and \
                        commands[2] == 0x87:

                    # OP_HASH160는 필요 없음
                    commands.pop(0)

                    # 리딤스크립트의 해시값
                    redeem_h160 = commands.pop(0)

                    # OP_EQUAL도 필요없음
                    commands.pop(0)

                    # 스택에 있는 리딤스크립트(해제 스크립트에 있던)를 해시
                    if not op_hash160(stack):
                        return False

                    # 스택에 리딤스크립트의 해시값 추가
                    stack.append(redeem_h160)

                    # 잠금 스크립트에 있는 해시와 해시를 한 해제 스크립트 값 비교
                    if not op_equal(stack):
                        return False
                    if not op_verify(stack):
                        LOGGER.info("bad p2sh hash160 ")
                        print("bad p2sh hash160 ")
                        return False

                    # 스택에 넣었던 command가 리딤 스크립트
                    # 스크립트는 가변 길이 필드로 시작 => 스크립트 길이를 추가하여 파싱해야 한다
                    redeem_script = encode_varint(len(command)) + command
                    stream = BytesIO(redeem_script)

                    # 파싱한 명령어를 명령집합에 추가
                    commands.extend(Script.parse(stream).commands)
                # p2wpkh 예외 상황 : [0x00, <hash>]
                if len(stack) == 2 and stack[0] == b'' and len(stack[1]) == 20:
                    # 0x00 은 필요없음
                    stack.pop(0)

                    # 증인 필드 해시값
                    h160 = stack.pop(0)

                    # 증인 필드(해제스크립트) 포함
                    commands.extend(witness)

                    # 증인 필드 해시값을 통해 생성한 p2p2kh 스크립트의 명령어 추가
                    # 증인 필드와 증인필드 해시값을 비교하기 위한 작업
                    commands.extend(p2pkh_script(h160).commands)
                # p2wsh 예외 상황 : [0x00, <sha256>] - p2sh와 유사
                if len(stack) == 2 and stack[0] == b'' and len(stack[1]) == 32:
                    # 0x00 은 필요없음
                    stack.pop(0)

                    # 증인 필드 해시값
                    s256 = stack.pop()
                    commands.extend(witness[:-1])

                    # 증인 스크립트 : 증인 필드의 가장 마지막 항목
                    witness_script = witness[-1]

                    # 증인 스크립트의 해시와 잠금 스크립트의 해시값 검증
                    if s256 != sha256(witness_script):
                        print('bad sha256 {} vs {}'.format(
                            s256.hex(),
                            sha256(witness_script).hex()))
                        return False

                    # 증인 스크립트 팡싱
                    # 스크립트는 가변 길이 필드로 시작 => 스크립트 길이를 추가하여 파싱해야 한다
                    stream = BytesIO(
                        encode_varint(len(witness_script)) + witness_script)
                    witness_script_cmds = Script.parse(stream).commands
                    commands.extend(witness_script_cmds)

        # 스택이 비어있는 경우, 스크립트 유효성 실패
        if len(stack) == 0:
            print("stack is empty")
            return False
        # 스택의 최상위 원소가 비어있는 바이트인 경우에도 유효성 실패
        # op.py 모듈의 연산 함수의 검증 실패시 스택에 비어있는 바이트를 스택에 올림
        if stack.pop() == b"":
            print("stack has empty bytes")
            return False
        return True