Example #1
0
class MerkleTreeGenerator(object):
    def __init__(self):
        self.tree = MerkleTools(hash_type='sha256')

    def populate(self, node_generator):
        """
        Populate Merkle Tree with data from node_generator. This requires that node_generator yield byte[] elements.
        Hashes, computes hex digest, and adds it to the Merkle Tree
        :param node_generator:
        :return:
        """
        for data in node_generator:
            hashed = hash_byte_array(data)
            self.tree.add_leaf(hashed)

    def get_blockchain_data(self):
        """
        Finalize tree and return byte array to issue on blockchain
        :return:
        """
        self.tree.make_tree()
        merkle_root = self.tree.get_merkle_root()
        return h2b(ensure_string(merkle_root))

    def get_proof_generator(self, tx_id, chain=Chain.bitcoin_mainnet):
        """
        Returns a generator (1-time iterator) of proofs in insertion order.

        :param tx_id: blockchain transaction id
        :return:
        """
        root = ensure_string(self.tree.get_merkle_root())
        node_count = len(self.tree.leaves)
        for index in range(0, node_count):
            proof = self.tree.get_proof(index)
            proof2 = []

            for p in proof:
                dict2 = dict()
                for key, value in p.items():
                    dict2[key] = ensure_string(value)
                proof2.append(dict2)
            target_hash = ensure_string(self.tree.get_leaf(index))
            merkle_proof = {
                "type": ['MerkleProof2017', 'Extension'],
                "merkleRoot":
                root,
                "targetHash":
                target_hash,
                "proof":
                proof2,
                "anchors": [{
                    "sourceId": to_source_id(tx_id, chain),
                    "type": chain.blockchain_type.external_display_value,
                    "chain": chain.external_display_value
                }]
            }
            yield merkle_proof
class MerkleTreeGenerator(object):
    def __init__(self):
        self.tree = MerkleTools(hash_type='sha256')

    def populate(self, node_generator):
        """
        Populate Merkle Tree with data from node_generator. This requires that node_generator yield byte[] elements.
        Hashes, computes hex digest, and adds it to the Merkle Tree
        :param node_generator:
        :return:
        """
        for data in node_generator:
            hashed = hash_byte_array(data)
            self.tree.add_leaf(hashed)

    def get_blockchain_data(self):
        """
        Finalize tree and return byte array to issue on blockchain
        :return:
        """
        self.tree.make_tree()
        merkle_root = self.tree.get_merkle_root()
        return h2b(ensure_string(merkle_root))

    def get_proof_generator(self, tx_id, chain=Chain.bitcoin_mainnet):
        """
        Returns a generator (1-time iterator) of proofs in insertion order.

        :param tx_id: blockchain transaction id
        :return:
        """
        root = ensure_string(self.tree.get_merkle_root())
        node_count = len(self.tree.leaves)
        for index in range(0, node_count):
            proof = self.tree.get_proof(index)
            proof2 = []

            for p in proof:
                dict2 = dict()
                for key, value in p.items():
                    dict2[key] = ensure_string(value)
                proof2.append(dict2)
            target_hash = ensure_string(self.tree.get_leaf(index))
            merkle_proof = {
                "type": ['MerkleProof2017', 'Extension'],
                "merkleRoot": root,
                "targetHash": target_hash,
                "proof": proof2,
                "anchors": [{
                    "sourceId": to_source_id(tx_id, chain),
                    "type": chain.blockchain_type.external_display_value,
                    "chain": chain.external_display_value
                }]}
            yield merkle_proof
Example #3
0
class Issuer:
    def __init__(self,
                 connector,
                 secure_signer,
                 certificate_batch_handler,
                 transaction_handler,
                 max_retry=MAX_TX_RETRIES,
                 prepared_inputs=None):
        self.connector = connector
        self.secure_signer = secure_signer
        self.certificate_batch_handler = certificate_batch_handler
        self.transaction_handler = transaction_handler
        self.max_retry = max_retry
        self.tree = MerkleTools(hash_type='sha256')
        self.prepared_inputs = prepared_inputs

    def estimate_cost_for_certificate_batch(self):
        if self.prepared_inputs:
            return self.transaction_handler.estimate_cost_for_certificate_batch(
                num_inputs=len(self.prepared_inputs))
        else:
            return self.transaction_handler.estimate_cost_for_certificate_batch(
            )

    def sign_batch(self):
        with FinalizableSigner(self.secure_signer) as signer:
            self.certificate_batch_handler.sign_batch(signer)

    def prepare_batch(self):
        node_generator = self.certificate_batch_handler.create_node_generator()
        for node in node_generator:
            self.tree.add_leaf(node, False)

    def issue_on_blockchain(self):
        """
        Issue the certificates on the Bitcoin blockchain
        :param revocation_address:
        :return:
        """

        self.tree.make_tree()
        op_return_value_bytes = unhexlify(self.tree.get_merkle_root())
        op_return_value = hexlify(op_return_value_bytes)

        for attempt_number in range(0, self.max_retry):
            try:
                if self.prepared_inputs:
                    inputs = self.prepared_inputs
                else:
                    spendables = self.connector.get_unspent_outputs(
                        self.secure_signer.issuing_address)
                    if not spendables:
                        error_message = 'No money to spend at address {}'.format(
                            self.secure_signer.issuing_address)
                        logging.error(error_message)
                        raise InsufficientFundsError(error_message)

                    cost = self.transaction_handler.estimate_cost_for_certificate_batch(
                    )
                    current_total = 0
                    inputs = []
                    random.shuffle(spendables)
                    for s in spendables:
                        inputs.append(s)
                        current_total += s.coin_value
                        if current_total > cost:
                            break

                tx = self.transaction_handler.create_transaction(
                    inputs, op_return_value_bytes)
                hex_tx = hexlify(tx.serialize())
                logging.info('Unsigned hextx=%s', hex_tx)

                prepared_tx = tx_utils.prepare_tx_for_signing(hex_tx, inputs)
                with FinalizableSigner(self.secure_signer) as signer:
                    signed_tx = signer.sign_transaction(prepared_tx)

                # log the actual byte count
                tx_byte_count = tx_utils.get_byte_count(signed_tx)
                logging.info('The actual transaction size is %d bytes',
                             tx_byte_count)

                signed_hextx = signed_tx.as_hex()
                logging.info('Signed hextx=%s', signed_hextx)

                # verify transaction before broadcasting
                tx_utils.verify_transaction(signed_hextx, op_return_value)

                # send tx and persist txid
                tx_id = self.connector.broadcast_tx(signed_tx)
                logging.info('Broadcast transaction with txid %s', tx_id)
                return tx_id
            except BroadcastError:
                logging.warning(
                    'Failed broadcast reattempts. Trying to recreate transaction. This is attempt number %d',
                    attempt_number)
        logging.error(
            'All attempts to broadcast failed. Try rerunning issuer.')
        raise BroadcastError(
            'All attempts to broadcast failed. Try rerunning issuer.')

    def finish_batch(self, tx_id):
        def create_proof_generator(tree):
            root = tree.get_merkle_root()
            node_count = len(tree.leaves)
            for index in range(0, node_count):
                proof = tree.get_proof(index)
                target_hash = tree.get_leaf(index)
                # chainpoint context is causing intermittent SSL errors. This isn't part of the JSON-normalized payload,
                # so we can omit it here
                merkle_proof = {
                    # "@context": "https://w3id.org/chainpoint/v2",
                    "type": "ChainpointSHA256v2",
                    "merkleRoot": root,
                    "targetHash": target_hash,
                    "proof": proof,
                    "anchors": [{
                        "sourceId": tx_id,
                        "type": "BTCOpReturn"
                    }]
                }
                yield merkle_proof

        self.certificate_batch_handler.add_proofs(create_proof_generator,
                                                  self.tree)

    def issue_certificates(self):
        logging.info('Preparing certificate batch')
        self.certificate_batch_handler.validate_batch()

        logging.info('Signing certificates')
        self.sign_batch()

        logging.info('Preparing certificate batch')
        self.prepare_batch()

        logging.info('Issuing the certificates on the blockchain')
        tx_id = self.issue_on_blockchain()

        logging.info('Finishing batch with txid=%s', tx_id)
        self.finish_batch(tx_id)
        return tx_id
Example #4
0
class MerkleTree(object):
    """Representation of a Merkle Tree.

    More at https://en.wikipedia.org/wiki/Merkle_tree.
    """
    def __init__(self):
        self.tree = MerkleTools(hash_type='sha256')

    def populate(self, node_generator: Generator) -> None:
        """
        Populate Merkle Tree with data from node_generator. This requires that node_generator yield byte[] elements.
        Hashes, computes hex digest, and adds it to the Merkle Tree
        :param node_generator:
        :return:
        """
        for data in node_generator:
            hashed = hash_byte_array(data)
            self.tree.add_leaf(hashed)

    def get_root(self, binary=False) -> bytearray:
        """
        Finalize tree and return the Root, a byte array to anchor on a blockchain tx.
        :return:
        """
        self.tree.make_tree()
        merkle_root = self.tree.get_merkle_root()
        if binary:
            return h2b(ensure_string(merkle_root))
        return ensure_string(merkle_root)

    def get_proof_generator(self, tx_id: AnyStr, signature_type: AnyStr,
                            chain_name: AnyStr) -> Dict:
        """
        Returns a generator of Merkle Proofs in insertion order.

        :param tx_id: blockchain transaction id
        :return:
        """
        root = ensure_string(self.tree.get_merkle_root())
        node_count = len(self.tree.leaves)
        for index in range(0, node_count):
            proof = self.tree.get_proof(index)
            proof2 = []

            for p in proof:
                dict2 = dict()
                for key, value in p.items():
                    dict2[key] = ensure_string(value)
                proof2.append(dict2)
            target_hash = ensure_string(self.tree.get_leaf(index))
            merkle_proof = {
                "type": ['MerkleProof2017', 'Extension'],
                "merkleRoot":
                root,
                "targetHash":
                target_hash,
                "proof":
                proof2,
                "anchors": [{
                    "sourceId": tx_id,
                    "type": signature_type,
                    "chain": chain_name
                }]
            }
            yield merkle_proof
Example #5
0
class Issuer:
    def __init__(self, issuing_address, connector, secure_signer, certificate_batch_handler, transaction_handler):
        self.issuing_address = issuing_address
        self.connector = connector
        self.secure_signer = secure_signer
        self.certificate_batch_handler = certificate_batch_handler
        self.transaction_handler = transaction_handler
        self.tree = MerkleTools(hash_type='sha256')

    def calculate_cost_for_certificate_batch(self):
        return self.transaction_handler.calculate_cost_for_certificate_batch()

    def sign_batch(self):
        with FinalizableSigner(self.secure_signer) as signer:
            self.certificate_batch_handler.sign_batch(signer)

    def prepare_batch(self):
        node_generator = self.certificate_batch_handler.create_node_generator()
        for node in node_generator:
            self.tree.add_leaf(node, False)

    def issue_on_blockchain(self):
        """
        Issue the certificates on the Bitcoin blockchain
        :param revocation_address:
        :return:
        """

        self.tree.make_tree()
        op_return_value_bytes = unhexlify(self.tree.get_merkle_root())
        op_return_value = hexlify(op_return_value_bytes)
        spendables = self.connector.get_unspent_outputs(self.issuing_address)
        if not spendables:
            error_message = 'No money to spend at address {}'.format(self.issuing_address)
            logging.error(error_message)
            raise InsufficientFundsError(error_message)

        last_input = spendables[-1]

        tx = self.transaction_handler.create_transaction(last_input, op_return_value_bytes)

        hex_tx = hexlify(tx.serialize())
        logging.info('Unsigned hextx=%s', hex_tx)

        prepared_tx = tx_utils.prepare_tx_for_signing(hex_tx, [last_input])
        with FinalizableSigner(self.secure_signer) as signer:
            signed_tx = signer.sign_transaction(prepared_tx)

        # log the actual byte count
        tx_byte_count = tx_utils.get_byte_count(signed_tx)
        logging.info('The actual transaction size is %d bytes', tx_byte_count)

        signed_hextx = signed_tx.as_hex()
        logging.info('Signed hextx=%s', signed_hextx)

        # verify transaction before broadcasting
        tx_utils.verify_transaction(signed_hextx, op_return_value)

        # send tx and persist txid
        tx_id = self.connector.broadcast_tx(signed_tx)
        if tx_id:
            logging.info('Broadcast transaction with txid %s', tx_id)
        else:
            logging.warning(
                'could not broadcast transaction but you can manually do it! signed hextx=%s', signed_hextx)

        return tx_id

    def finish_batch(self, tx_id):
        def create_proof_generator(tree):
            root = tree.get_merkle_root()
            node_count = len(tree.leaves)
            for index in range(0, node_count):
                proof = tree.get_proof(index)
                target_hash = tree.get_leaf(index)
                merkle_proof = {
                    "@context": "https://w3id.org/chainpoint/v2",
                    "type": "ChainpointSHA256v2",
                    "merkleRoot": root,
                    "targetHash": target_hash,
                    "proof": proof,
                    "anchors": [{
                        "sourceId": tx_id,
                        "type": "BTCOpReturn"
                    }]}
                yield merkle_proof

        self.certificate_batch_handler.add_proofs(create_proof_generator, self.tree)

    def issue_certificates(self):
        logging.info('Preparing certificate batch')
        self.certificate_batch_handler.validate_batch()

        logging.info('Signing certificates')
        self.sign_batch()

        logging.info('Preparing certificate batch')
        self.prepare_batch()

        logging.info('Issuing the certificates on the blockchain')
        tx_id = self.issue_on_blockchain()

        logging.info('Finishing batch with txid=%s', tx_id)
        self.finish_batch(tx_id)
        return tx_id