コード例 #1
0
ファイル: block.py プロジェクト: weixelbaumer/two1-python
    def __bytes__(self):
        """ Serializes the BlockHeader object.

        Returns:
            byte_str (bytes): The serialized byte stream.
        """
        return (pack_u32(self.version) + bytes(self.prev_block_hash) +
                bytes(self.merkle_root_hash) + pack_u32(self.time) +
                pack_u32(self.bits) + pack_u32(self.nonce))
コード例 #2
0
ファイル: txn.py プロジェクト: genavarov/Blockshare-Web-App
    def __bytes__(self):
        """ Serializes the object into a byte stream.

        Returns:
            b (bytes): byte stream containing the serialized input.
        """
        return (
            bytes(self.outpoint) +
            pack_u32(self.outpoint_index) +
            pack_var_str(bytes(self.script)) +
            pack_u32(self.sequence_num)
        )
コード例 #3
0
ファイル: txn.py プロジェクト: shayanb/two1
    def __bytes__(self):
        """ Serializes the object into a byte stream.

        Returns:
            b (bytes): byte stream containing the serialized input.
        """
        return (
            bytes(self.outpoint) +
            pack_u32(self.outpoint_index) +
            pack_var_str(bytes(self.script)) +
            pack_u32(self.sequence_num)
        )
コード例 #4
0
ファイル: txn.py プロジェクト: shayanb/two1
    def __bytes__(self):
        """ Serializes the object into a byte stream.

        Returns:
            b (bytes): The serialized transaction.
        """
        return (
            pack_u32(self.version) +                      # Version
            pack_compact_int(self.num_inputs) +           # Input count
            b''.join([bytes(i) for i in self.inputs]) +   # Inputs
            pack_compact_int(self.num_outputs) +          # Output count
            b''.join([bytes(o) for o in self.outputs]) +  # Outputs
            pack_u32(self.lock_time)                      # Lock time
        )
コード例 #5
0
ファイル: block.py プロジェクト: shayanb/two1
    def __bytes__(self):
        """ Serializes the BlockHeader object.

        Returns:
            byte_str (bytes): The serialized byte stream.
        """
        return (
            pack_u32(self.version) +
            bytes(self.prev_block_hash) +
            bytes(self.merkle_root_hash) +
            pack_u32(self.time) +
            pack_u32(self.bits) +
            pack_u32(self.nonce)
        )
コード例 #6
0
ファイル: txn.py プロジェクト: genavarov/Blockshare-Web-App
    def __bytes__(self):
        """ Serializes the object into a byte stream.

        Returns:
            b (bytes): The serialized transaction.
        """
        return (
            pack_u32(self.version) +                      # Version
            pack_compact_int(self.num_inputs) +           # Input count
            b''.join([bytes(i) for i in self.inputs]) +   # Inputs
            pack_compact_int(self.num_outputs) +          # Output count
            b''.join([bytes(o) for o in self.outputs]) +  # Outputs
            pack_u32(self.lock_time)                      # Lock time
        )
コード例 #7
0
    def _op_checksig(self):
        """ The entire transaction's outputs, inputs, and script (from
            the most recently-executed OP_CODESEPARATOR to the end) are
            hashed. The signature used by OP_CHECKSIG must be a valid
            signature for this hash and public key. If it is, 1 is
            returned, 0 otherwise.
        """
        self._check_stack_len(2)
        self._check_txn()

        pub_key_bytes = self._stack.pop()
        s = self._stack.pop()
        sig_der, hash_type = s[:-1], s[-1]

        pub_key = PublicKey.from_bytes(pub_key_bytes)
        sig = Signature.from_der(sig_der)

        txn_copy = self._txn._copy_for_sig(input_index=self._input_index,
                                           hash_type=hash_type,
                                           sub_script=self._sub_script)
        msg = bytes(txn_copy) + utils.pack_u32(hash_type)
        tx_digest = hashlib.sha256(msg).digest()

        verified = pub_key.verify(tx_digest, sig)

        self._stack.append(verified)
コード例 #8
0
    def _op_checksig(self):
        """ The entire transaction's outputs, inputs, and script (from
            the most recently-executed OP_CODESEPARATOR to the end) are
            hashed. The signature used by OP_CHECKSIG must be a valid
            signature for this hash and public key. If it is, 1 is
            returned, 0 otherwise.
        """
        self._check_stack_len(2)
        self._check_txn()

        pub_key_bytes = self._stack.pop()
        s = self._stack.pop()
        sig_der, hash_type = s[:-1], s[-1]

        pub_key = PublicKey.from_bytes(pub_key_bytes)
        sig = Signature.from_der(sig_der)

        txn_copy = self._txn._copy_for_sig(input_index=self._input_index,
                                           hash_type=hash_type,
                                           sub_script=self._sub_script)
        msg = bytes(txn_copy) + utils.pack_u32(hash_type)
        tx_digest = hashlib.sha256(msg).digest()

        verified = pub_key.verify(tx_digest, sig)

        self._stack.append(verified)
コード例 #9
0
ファイル: txn.py プロジェクト: genavarov/Blockshare-Web-App
    def get_signature_for_input(self, input_index, hash_type, private_key,
                                sub_script):
        """ Returns the signature for an input.

        This function only returns the signature for an input, it
        does not insert the signature into the script member of
        the input. It also does not validate that the given private key
        matches any public keys in the sub_script.

        Args:
            input_index (int): The index of the input to sign.
            hash_type (int): What kind of signature hash to do.
            private_key (crypto.PrivateKey): private key with which
                to sign the transaction.
            sub_script (Script): the scriptPubKey of the corresponding
                utxo being spent if the outpoint is P2PKH or the redeem
                script if the outpoint is P2SH.

        Returns:
            tuple:
                A tuple containing the signature object and the message that
                was signed.
        """
        if input_index < 0 or input_index >= len(self.inputs):
            raise ValueError("Invalid input index.")

        tmp_script = sub_script.remove_op("OP_CODESEPARATOR")
        if hash_type & 0x1f == self.SIG_HASH_SINGLE and len(self.inputs) > len(self.outputs):
            # This is to deal with the bug where specifying an index
            # that is out of range (wrt outputs) results in a
            # signature hash of 0x1 (little-endian)
            msg_to_sign = 0x1.to_bytes(32, 'little')
        else:
            txn_copy = self._copy_for_sig(input_index, hash_type, tmp_script)

            msg_to_sign = bytes(Hash.dhash(bytes(txn_copy) +
                                           pack_u32(hash_type)))

        sig = private_key.sign(msg_to_sign, False)

        return sig, msg_to_sign
コード例 #10
0
    def _op_checkmultisig(self, partial=False):
        """ Compares the first signature against each public key until
            it finds an ECDSA match. Starting with the subsequent public
            key, it compares the second signature against each remaining
            public key until it finds an ECDSA match. The process is
            repeated until all signatures have been checked or not enough
            public keys remain to produce a successful result. All
            signatures need to match a public key. Because public keys are
            not checked again if they fail any signature comparison,
            signatures must be placed in the scriptSig using the same
            order as their corresponding public keys were placed in the
            scriptPubKey or redeemScript. If all signatures are valid, 1
            is returned, 0 otherwise. Due to a bug, one extra unused value
            is removed from the stack.
        """
        self._check_stack_len(1)
        self._check_txn()

        num_keys = self._stack.pop()
        self._check_stack_len(num_keys)

        keys_bytes = []
        for i in range(num_keys):
            keys_bytes.insert(0, self._stack.pop())
        public_keys = [PublicKey.from_bytes(p) for p in keys_bytes]

        min_num_sigs = self._stack.pop()

        # Although "m" is the *minimum* number of required signatures, bitcoin
        # core only consumes "m" signatures and then expects an OP_0. This
        # means that if m < min_num_sigs <= n, bitcoin core will return a
        # script failure. See:
        # https://github.com/bitcoin/bitcoin/blob/0.10/src/script/interpreter.cpp#L840
        # We will do the same.
        hash_types = set()
        sigs = []
        for i in range(min_num_sigs):
            s = self._stack.pop()
            try:
                sig = Signature.from_der(s[:-1])
                hash_types.add(s[-1])
                sigs.insert(0, sig)
            except ValueError:
                if partial:
                    # Put it back on stack
                    self._stack.append(s)
                else:
                    # If not a partial evaluation there are not enough
                    # sigs
                    rv = False
                break

        if len(hash_types) != 1:
            raise ScriptInterpreterError(
                "Not all signatures have the same hash type!")

        hash_type = hash_types.pop()
        txn_copy = self._txn._copy_for_sig(input_index=self._input_index,
                                           hash_type=hash_type,
                                           sub_script=self._sub_script)

        msg = bytes(txn_copy) + utils.pack_u32(hash_type)
        txn_digest = hashlib.sha256(msg).digest()

        # Now we verify
        last_match = -1
        rv = True
        match_count = 0

        for sig in sigs:
            matched_any = False
            for i, pub_key in enumerate(public_keys[last_match + 1:]):
                if pub_key.verify(txn_digest, sig):
                    last_match = i
                    match_count += 1
                    matched_any = True
                    break

            if not matched_any:
                # Bail early if the sig couldn't be verified
                # by any public key
                rv = False
                break

        rv &= match_count >= min_num_sigs

        # Now make sure the last thing on the stack is OP_0
        if len(self._stack) == 1:
            rv &= self._stack.pop() == b''
            rv &= len(self._stack) == 0
        else:
            rv = False

        self._stack.append(rv)
        if partial:
            self.match_count = match_count
コード例 #11
0
    def receive_payment(self, deposit_txid, payment_tx):
        """Receive and process a payment within the channel.

        The customer makes a payment in the channel by sending the merchant a
        half-signed payment transaction. The merchant signs the other half of
        the transaction and saves it in its records (but does not broadcast it
        or send it to the customer). The merchant responds with 200 to verify
        that the payment was handled successfully.

        Args:
            deposit_txid (string): string representation of the deposit
                transaction hash. This is used to look up the payment channel.
            payment_tx (string): half-signed payment transaction from a
                customer.
        Returns:
            (string): payment transaction id
        """
        # Parse payment channel `payment` parameters
        payment_tx = Transaction.from_hex(payment_tx)

        # Get channel and addresses related to the deposit
        channel = self._db.pc.lookup(deposit_txid)

        if not channel:
            raise PaymentChannelNotFoundError('Related channel not found.')

        # Get merchant public key information from payment channel
        redeem_script = PaymentChannelRedeemScript.from_bytes(
            payment_tx.inputs[0].script[-1])
        merch_pubkey = redeem_script.merchant_public_key

        # Verify that the payment has a valid signature from the customer
        txn_copy = payment_tx._copy_for_sig(0, Transaction.SIG_HASH_ALL,
                                            redeem_script)
        msg_to_sign = bytes(
            Hash.dhash(bytes(txn_copy) + pack_u32(Transaction.SIG_HASH_ALL)))
        sig = Signature.from_der(payment_tx.inputs[0].script[0][:-1])
        if not redeem_script.customer_public_key.verify(
                msg_to_sign, sig, False):
            raise BadTransactionError('Invalid payment signature.')

        # Verify the length of the script is what we expect
        if len(payment_tx.inputs[0].script) != 3:
            raise BadTransactionError(
                'Invalid payment channel transaction structure.')

        # Verify the script template is valid for accepting a merchant signature
        if (not Script.validate_template(payment_tx.inputs[0].script,
                                         [bytes, 'OP_1', bytes])
                and not Script.validate_template(payment_tx.inputs[0].script,
                                                 [bytes, 'OP_TRUE', bytes])):
            raise BadTransactionError(
                'Invalid payment channel transaction structure.')

        # Verify that the payment channel is ready
        if channel.state == ChannelSQLite3.CONFIRMING:
            confirmed = self._blockchain.check_confirmed(channel.deposit_txid)
            if confirmed:
                self._db.pc.update_state(channel.deposit_txid,
                                         ChannelSQLite3.READY)
            else:
                raise ChannelClosedError('Payment channel not ready.')
        elif channel.state == ChannelSQLite3.CLOSED:
            raise ChannelClosedError('Payment channel closed.')

        # Verify that payment is made to the merchant's pubkey
        index = payment_tx.output_index_for_address(merch_pubkey.hash160())
        if index is None:
            raise BadTransactionError('Payment must pay to merchant pubkey.')

        # Verify that both payments are not below the dust limit
        for output_index, output in enumerate(payment_tx.outputs):
            if output.value < PaymentServer.DUST_LIMIT:
                # Payment to merchant is less than dust limit
                if output_index == index:
                    raise BadTransactionError(
                        'Initial payment must be greater than {}.'.format(
                            PaymentServer.DUST_LIMIT))
                # Payment to customer is less than dust limit
                else:
                    raise BadTransactionError(
                        'Payment channel balance is not large enough to make payment.'
                    )

        # Validate that the payment is more than the last one
        new_pmt_amt = payment_tx.outputs[index].value
        if new_pmt_amt <= channel.last_payment_amount:
            raise BadTransactionError('Payment must be greater than 0.')

        # Verify that the transaction has adequate fees
        net_pmt_amount = sum([d.value for d in payment_tx.outputs])
        deposit_amount = channel.amount
        if deposit_amount < net_pmt_amount + PaymentServer.MIN_TX_FEE:
            raise BadTransactionError('Payment must have adequate fees.')

        # Update the current payment transaction
        self._db.pc.update_payment(deposit_txid, payment_tx, new_pmt_amt)
        self._db.pmt.create(deposit_txid, payment_tx,
                            new_pmt_amt - channel.last_payment_amount)

        return str(payment_tx.hash)
コード例 #12
0
ファイル: payment_server.py プロジェクト: 21dotco/two1-python
    def receive_payment(self, deposit_txid, payment_tx):
        """Receive and process a payment within the channel.

        The customer makes a payment in the channel by sending the merchant a
        half-signed payment transaction. The merchant signs the other half of
        the transaction and saves it in its records (but does not broadcast it
        or send it to the customer). The merchant responds with 200 to verify
        that the payment was handled successfully.

        Args:
            deposit_txid (string): string representation of the deposit
                transaction hash. This is used to look up the payment channel.
            payment_tx (string): half-signed payment transaction from a
                customer.
        Returns:
            (string): payment transaction id
        """
        # Parse payment channel `payment` parameters
        payment_tx = Transaction.from_hex(payment_tx)

        # Get channel and addresses related to the deposit
        channel = self._db.pc.lookup(deposit_txid)

        if not channel:
            raise PaymentChannelNotFoundError('Related channel not found.')

        # Get merchant public key information from payment channel
        merchant_public_key = PublicKey.from_hex(channel.merchant_pubkey)

        # Verify that redeem script contains the merchant public key
        redeem_script = PaymentChannelRedeemScript.from_bytes(payment_tx.inputs[0].script[-1])
        if redeem_script.merchant_public_key.to_hex() != merchant_public_key.to_hex():
            raise BadTransactionError('Invalid merchant pubkey.')

        # Verify that the payment has a valid signature from the customer
        txn_copy = payment_tx._copy_for_sig(0, Transaction.SIG_HASH_ALL, redeem_script)
        msg_to_sign = bytes(Hash.dhash(bytes(txn_copy) + pack_u32(Transaction.SIG_HASH_ALL)))
        sig = Signature.from_der(payment_tx.inputs[0].script[0][:-1])
        if not redeem_script.customer_public_key.verify(msg_to_sign, sig, False):
            raise BadTransactionError('Invalid payment signature.')

        # Verify the length of the script is what we expect
        if len(payment_tx.inputs[0].script) != 3:
            raise BadTransactionError('Invalid payment channel transaction structure.')

        # Verify the script template is valid for accepting a merchant signature
        if (not Script.validate_template(payment_tx.inputs[0].script, [bytes, 'OP_1', bytes]) and
                not Script.validate_template(payment_tx.inputs[0].script, [bytes, 'OP_TRUE', bytes])):
            raise BadTransactionError('Invalid payment channel transaction structure.')

        # Verify that the payment channel is ready
        if channel.state == ChannelSQLite3.CONFIRMING:
            confirmed = self._blockchain.check_confirmed(channel.deposit_txid)
            if confirmed:
                self._db.pc.update_state(channel.deposit_txid, ChannelSQLite3.READY)
            else:
                raise ChannelClosedError('Payment channel not ready.')
        elif channel.state == ChannelSQLite3.CLOSED:
            raise ChannelClosedError('Payment channel closed.')

        # Verify that payment is made to the merchant public key
        index = payment_tx.output_index_for_address(merchant_public_key.hash160())
        if index is None:
            raise BadTransactionError('Payment must pay to merchant pubkey.')

        # Verify that both payments are not below the dust limit
        for output_index, output in enumerate(payment_tx.outputs):
            if output.value < PaymentServer.DUST_LIMIT:
                # Payment to merchant is less than dust limit
                if output_index == index:
                    raise BadTransactionError(
                        'Initial payment must be greater than {}.'.format(PaymentServer.DUST_LIMIT))
                # Payment to customer is less than dust limit
                else:
                    raise BadTransactionError(
                        'Payment channel balance is not large enough to make payment.')

        # Validate that the payment is more than the last one
        new_pmt_amt = payment_tx.outputs[index].value
        if new_pmt_amt <= channel.last_payment_amount:
            raise BadTransactionError('Payment must be greater than 0.')

        # Verify that the transaction has adequate fees
        net_pmt_amount = sum([d.value for d in payment_tx.outputs])
        deposit_amount = channel.amount
        fee = deposit_amount - net_pmt_amount
        if fee < PaymentServer.MIN_TX_FEE:
            raise BadTransactionError('Payment must have adequate fees.')

        # Recreate redeem script from merchant side
        redeem_script_copy = PaymentChannelRedeemScript(
            merchant_public_key,
            redeem_script.customer_public_key,
            channel.expires_at)
        if redeem_script.to_hex() != redeem_script_copy.to_hex():
            raise BadTransactionError('Invalid redeem script.')

        # Recreate customer payment from merchant side
        payment_tx_copy = self._wallet.create_unsigned_payment_tx(
            channel.deposit_tx, redeem_script, new_pmt_amt, fee)

        # Recreate signed input script using signature from customer
        hash_type = pack_compact_int(Transaction.SIG_HASH_ALL)
        signed_input_script = Script(
            [sig.to_der() + hash_type, "OP_1", bytes(redeem_script)])
        payment_tx_copy.inputs[0].script = signed_input_script

        if payment_tx.to_hex() != payment_tx_copy.to_hex():
            raise BadTransactionError('Invalid payment channel transaction structure.')

        # Update the current payment transaction
        self._db.pc.update_payment(deposit_txid, payment_tx_copy, new_pmt_amt)
        self._db.pmt.create(deposit_txid, payment_tx_copy, new_pmt_amt - channel.last_payment_amount)

        return str(payment_tx_copy.hash)
コード例 #13
0
ファイル: payment_server.py プロジェクト: shayanb/two1
    def receive_payment(self, deposit_txid, payment_tx):
        """Receive and process a payment within the channel.

        The customer makes a payment in the channel by sending the merchant a
        half-signed payment transaction. The merchant signs the other half of
        the transaction and saves it in its records (but does not broadcast it
        or send it to the customer). The merchant responds with 200 to verify
        that the payment was handled successfully.

        Args:
            deposit_txid (string): string representation of the deposit
                transaction hash. This is used to look up the payment channel.
            payment_tx (string): half-signed payment transaction from a
                customer.
        Returns:
            (string): payment transaction id
        """
        with self.lock:
            # Parse payment channel `payment` parameters
            payment_tx = Transaction.from_hex(payment_tx)

            # Get channel and addresses related to the deposit
            channel = self._db.pc.lookup(deposit_txid)

            if not channel:
                raise PaymentChannelNotFoundError('Related channel not found.')

            # Get merchant public key information from payment channel
            redeem_script = PaymentChannelRedeemScript.from_bytes(payment_tx.inputs[0].script[-1])
            merch_pubkey = redeem_script.merchant_public_key

            # Verify that the payment has a valid signature from the customer
            txn_copy = payment_tx._copy_for_sig(0, Transaction.SIG_HASH_ALL, redeem_script)
            msg_to_sign = bytes(Hash.dhash(bytes(txn_copy) + pack_u32(Transaction.SIG_HASH_ALL)))
            sig = Signature.from_der(payment_tx.inputs[0].script[0][:-1])
            if not redeem_script.customer_public_key.verify(msg_to_sign, sig, False):
                raise BadTransactionError('Invalid payment signature.')

            # Verify the length of the script is what we expect
            if len(payment_tx.inputs[0].script) != 3:
                raise BadTransactionError('Invalid payment channel transaction structure.')

            # Verify the script template is valid for accepting a merchant signature
            if (not Script.validate_template(payment_tx.inputs[0].script, [bytes, 'OP_1', bytes]) and
                    not Script.validate_template(payment_tx.inputs[0].script, [bytes, 'OP_TRUE', bytes])):
                raise BadTransactionError('Invalid payment channel transaction structure.')

            # Verify that the payment channel is ready
            if channel.state == ChannelSQLite3.CONFIRMING:
                raise ChannelClosedError('Payment channel not ready.')
            elif channel.state == ChannelSQLite3.CLOSED:
                raise ChannelClosedError('Payment channel closed.')

            # Verify that payment is made to the merchant's pubkey
            index = payment_tx.output_index_for_address(merch_pubkey.hash160())
            if index is None:
                raise BadTransactionError('Payment must pay to merchant pubkey.')

            # Verify that both payments are not below the dust limit
            if any(p.value < PaymentServer.DUST_LIMIT for p in payment_tx.outputs):
                raise BadTransactionError(
                    'Final payment must have outputs greater than {}.'.format(PaymentServer.DUST_LIMIT))

            # Validate that the payment is more than the last one
            new_pmt_amt = payment_tx.outputs[index].value
            if new_pmt_amt <= channel.last_payment_amount:
                raise BadTransactionError('Payment must be greater than 0.')

            # Verify that the transaction has adequate fees
            net_pmt_amount = sum([d.value for d in payment_tx.outputs])
            deposit_amount = channel.amount
            if deposit_amount < net_pmt_amount + PaymentServer.MIN_TX_FEE:
                raise BadTransactionError('Payment must have adequate fees.')

            # Update the current payment transaction
            self._db.pc.update_payment(deposit_txid, payment_tx, new_pmt_amt)
            self._db.pmt.create(deposit_txid, payment_tx, new_pmt_amt - channel.last_payment_amount)

            return str(payment_tx.hash)
コード例 #14
0
    def _op_checkmultisig(self, partial=False):
        """ Compares the first signature against each public key until
            it finds an ECDSA match. Starting with the subsequent public
            key, it compares the second signature against each remaining
            public key until it finds an ECDSA match. The process is
            repeated until all signatures have been checked or not enough
            public keys remain to produce a successful result. All
            signatures need to match a public key. Because public keys are
            not checked again if they fail any signature comparison,
            signatures must be placed in the scriptSig using the same
            order as their corresponding public keys were placed in the
            scriptPubKey or redeemScript. If all signatures are valid, 1
            is returned, 0 otherwise. Due to a bug, one extra unused value
            is removed from the stack.
        """
        self._check_stack_len(1)
        self._check_txn()

        num_keys = self._stack.pop()
        self._check_stack_len(num_keys)

        keys_bytes = []
        for i in range(num_keys):
            keys_bytes.insert(0, self._stack.pop())
        public_keys = [PublicKey.from_bytes(p) for p in keys_bytes]

        min_num_sigs = self._stack.pop()

        # Although "m" is the *minimum* number of required signatures, bitcoin
        # core only consumes "m" signatures and then expects an OP_0. This
        # means that if m < min_num_sigs <= n, bitcoin core will return a
        # script failure. See:
        # https://github.com/bitcoin/bitcoin/blob/0.10/src/script/interpreter.cpp#L840
        # We will do the same.
        hash_types = set()
        sigs = []
        for i in range(min_num_sigs):
            s = self._stack.pop()
            try:
                sig = Signature.from_der(s[:-1])
                hash_types.add(s[-1])
                sigs.insert(0, sig)
            except ValueError:
                if partial:
                    # Put it back on stack
                    self._stack.append(s)
                else:
                    # If not a partial evaluation there are not enough
                    # sigs
                    rv = False
                break

        if len(hash_types) != 1:
            raise ScriptInterpreterError("Not all signatures have the same hash type!")

        hash_type = hash_types.pop()
        txn_copy = self._txn._copy_for_sig(input_index=self._input_index,
                                           hash_type=hash_type,
                                           sub_script=self._sub_script)

        msg = bytes(txn_copy) + utils.pack_u32(hash_type)
        txn_digest = hashlib.sha256(msg).digest()

        # Now we verify
        last_match = -1
        rv = True
        match_count = 0

        for sig in sigs:
            matched_any = False
            for i, pub_key in enumerate(public_keys[last_match+1:]):
                if pub_key.verify(txn_digest, sig):
                    last_match = i
                    match_count += 1
                    matched_any = True
                    break

            if not matched_any:
                # Bail early if the sig couldn't be verified
                # by any public key
                rv = False
                break

        rv &= match_count >= min_num_sigs

        # Now make sure the last thing on the stack is OP_0
        if len(self._stack) == 1:
            rv &= self._stack.pop() == b''
            rv &= len(self._stack) == 0
        else:
            rv = False

        self._stack.append(rv)
        if partial:
            self.match_count = match_count