Пример #1
0
class TestSignData(unittest.TestCase):
    def setUp(self):
        self.api = BtcTxStore(dryrun=True, testnet=True)

    def test_sign_a(self):
        wif = fixtures["wallet"]["wif"]
        data = binascii.hexlify(b"testmessage")
        address = self.api.get_address(wif)
        sig = self.api.sign_data(wif, data)
        valid = self.api.verify_signature(address, sig, data)
        self.assertEqual(valid, True)

    def test_sign_b(self):
        wif = "cSuT2J14dYbe1zvB5z5WTXeRcMbj4tnoKssAK1ZQbnX5HtHfW3bi"
        data = binascii.hexlify(b"testmessage")
        address = self.api.get_address(wif)
        sig = self.api.sign_data(wif, data)
        valid = self.api.verify_signature(address, sig, data)
        self.assertEqual(valid, True)
Пример #2
0
class TestSignData(unittest.TestCase):

    def setUp(self):
        self.api = BtcTxStore(dryrun=True, testnet=True)

    def test_sign_a(self):
        wif = fixtures["wallet"]["wif"]
        data = binascii.hexlify(b"testmessage")
        address = self.api.get_address(wif)
        sig = self.api.sign_data(wif, data)
        valid = self.api.verify_signature(address, sig, data)
        self.assertEqual(valid, True)

    def test_sign_b(self):
        wif = "cSuT2J14dYbe1zvB5z5WTXeRcMbj4tnoKssAK1ZQbnX5HtHfW3bi"
        data = binascii.hexlify(b"testmessage")
        address = self.api.get_address(wif)
        sig = self.api.sign_data(wif, data)
        valid = self.api.verify_signature(address, sig, data)
        self.assertEqual(valid, True)
Пример #3
0
def sign(dict_obj, wif):  # FIXME use create instead
    assert(isinstance(dict_obj, OrderedDict))
    if "signature" in dict_obj:
        del dict_obj["signature"]

    if sys.version_info >= (3, 0, 0):
        msg = str(dict_obj).encode("ascii")
    else:
        msg = str(dict_obj)

    # assert("signature" not in msg)  # must be unsigned
    # todo: fix this

    api = BtcTxStore(testnet=False, dryrun=True)
    msg = binascii.hexlify(msg).decode("utf-8")
    sig = api.sign_data(wif, msg)

    if sys.version_info >= (3, 0, 0):
        dict_obj[u"signature"] = sig.decode("utf-8")
    else:
        dict_obj[u"signature"] = unicode(sig)

    return dict_obj
Пример #4
0
class FileTransfer:
    def __init__(self, net, wif=None, store_config=None, handlers=None):
        # Accept direct connections.
        self.net = net

        # Returned by callbacks.
        self.success_value = ("127.0.0.1", 7777)

        # Used for signing messages.
        self.wallet = BtcTxStore(testnet=False, dryrun=True)
        self.wif = wif or self.wallet.create_key()

        # Where will the data be stored?
        self.store_config = store_config
        assert(len(list(store_config)))

        # Handlers for certain events.
        self.handlers = handlers
        if self.handlers is None:
            self.handlers = {}
        if "complete" not in self.handlers:
            self.handlers["complete"] = []
        if "accept" not in self.handlers:
            self.handlers["accept"] = []

        # Start networking.
        if not self.net.is_net_started:
            self.net.start()

        # Dict of data requests: [contract_id] > contract
        self.contracts = {}

        # List of Sock objects returned from UNL.connect.
        self.cons = []

        # Dict of defers for contracts: [contract_id] > defer
        self.defers = {}

        # Three-way handshake status for contracts: [contract_id] > state
        self.handshake = {}

        # All contracts associated with this connection.
        # [con] > [contract_id] > con_info
        self.con_info = {}

        # File transfer currently active on connection.
        # [con] > contract_id
        self.con_transfer = {}

        # List of active downloads.
        # (Never try to download multiple copies of the same thing at once.)
        self.downloading = {}

        # Lock threads.
        self.mutex = Lock()

    def get_their_unl(self, contract):
        if self.net.unl == pyp2p.unl.UNL(value=contract["dest_unl"]):
            their_unl = contract["src_unl"]
        else:
            their_unl = contract["dest_unl"]

        return their_unl

    def get_node_id_from_unl(self, unl):
        unl = pyp2p.unl.UNL(value=unl).deconstruct()

        return unl["node_id"]

    def is_queued(self, con=None):
        if con is not None:
            if con not in self.con_info:
                return 0

        if con is None:
            con_list = list(self.con_info)
        else:
            con_list = [con]

        for con in con_list:
            for contract_id in list(self.con_info[con]):
                con_info = self.con_info[con][contract_id]
                if con_info["remaining"]:
                    return 1

        return 0

    def cleanup_transfers(self, con, contract_id):
        # Cleanup downloading.
        contract = self.contracts[contract_id]
        if contract["data_id"] in self.downloading:
            if contract["direction"] == "receive":
                del self.downloading[contract["data_id"]]

        # Cleanup handshakes.
        if contract_id in self.handshake:
            del self.handshake[contract_id]

        # Cleanup defers.
        if contract_id in self.defers:
            del self.defers[contract_id]

        # Cleanup con transfers.
        if con in self.con_transfer:
            del self.con_transfer[con]

        # Cleanup con_info.
        if con in self.con_info:
            del self.con_info[con]

        # Cleanup contracts.
        if contract_id in self.contracts:
            del self.contracts[contract_id]

    def queue_next_transfer(self, con):
        _log.debug("Queing next transfer")
        for contract_id in list(self.con_info[con]):
            con_info = self.con_info[con][contract_id]
            if con_info["remaining"]:
                self.con_transfer[con] = contract_id
                con.send(contract_id, send_all=1)
                return

        # Mark end of transfers.
        self.con_transfer[con] = u"0" * 64

    def save_contract(self, contract):
        # Record contract details.
        contract_id = self.contract_id(contract)
        self.contracts[contract_id] = contract

        return contract_id

    def send_msg(self, dict_obj, unl):
        node_id = self.net.unl.deconstruct(unl)["node_id"]
        msg = json.dumps(dict_obj, ensure_ascii=True)
        self.net.dht_node.relay_message(
            node_id,
            msg
        )

    def contract_id(self, contract):
        if sys.version_info >= (3, 0, 0):
            contract = str(contract).encode("ascii")
        else:
            contract = str(contract)

        return hashlib.sha256(contract).hexdigest()

    def sign_contract(self, contract):
        if sys.version_info >= (3, 0, 0):
            msg = str(contract).encode("ascii")
        else:
            msg = str(contract)

        msg = binascii.hexlify(msg).decode("utf-8")
        sig = self.wallet.sign_data(self.wif, msg)

        if sys.version_info >= (3, 0, 0):
            contract[u"signature"] = sig.decode("utf-8")
        else:
            contract[u"signature"] = unicode(sig)

        return contract

    def is_valid_contract_sig(self, contract, node_id=None):
        sig = contract[u"signature"][:]
        del contract[u"signature"]

        if sys.version_info >= (3, 0, 0):
            msg = str(contract).encode("ascii")
        else:
            msg = str(contract)

        # Use our address.
        msg = binascii.hexlify(msg).decode("utf-8")
        if node_id is None:
            address = self.wallet.get_address(self.wif)
            ret = self.wallet.verify_signature(address, sig, msg)
        else:
            # Use their node ID: try testnet.
            address = b2a_hashed_base58(b'o' + node_id)
            ret = self.wallet.verify_signature(address, sig, msg)
            if not ret:
                # Use their node ID: try mainnet.
                address = b2a_hashed_base58(b'\0' + node_id)
                ret = self.wallet.verify_signature(address, sig, msg)

        # Move sig back.
        contract[u"signature"] = sig[:]

        return ret

    def simple_data_request(self, data_id, node_unl, direction):
        file_size = 0
        if direction == u"send":
            action = u"upload"
        else:
            action = u"download"

        return self.data_request(action, data_id, file_size, node_unl)

    def data_request(self, action, data_id, file_size, node_unl):
        """
        Action = put (upload), get (download.)
        """
        _log.debug("In data request function")

        # Who is hosting this data?
        if action == "upload":
            # We store this data.
            direction = u"send"
            host_unl = self.net.unl.value
            assert(storage.manager.find(self.store_config, data_id) is not None)
        else:
            # They store the data.
            direction = u"receive"
            host_unl = node_unl
            if data_id in self.downloading:
                raise Exception("Already trying to download this.")

        # Encoding.
        if sys.version_info >= (3, 0, 0):
            if type(data_id) == bytes:
                data_id = data_id.decode("utf-8")

            if type(host_unl) == bytes:
                host_unl = host_unl.decode("utf-8")

            if type(node_unl) == bytes:
                node_unl = node_unl.decode("utf-8")
        else:
            if type(data_id) == str:
                data_id = unicode(data_id)

            if type(host_unl) == str:
                host_unl = unicode(host_unl)

            if type(node_unl) == str:
                node_unl = unicode(node_unl)

        # Create contract.
        contract = OrderedDict({
            u"status": u"SYN",
            u"direction": direction,
            u"data_id": data_id,
            u"file_size": file_size,
            u"host_unl": host_unl,
            u"dest_unl": node_unl,
            u"src_unl": self.net.unl.value,
        })

        # Sign contract.
        contract = self.sign_contract(contract)

        # Route contract.
        contract_id = self.save_contract(contract)
        self.send_msg(contract, node_unl)
        _log.debug("Sending data request")

        # Update handshake.
        self.handshake[contract_id] = {
            u"state": u"SYN",
            u"timestamp": time.time()
        }

        # For async code.
        d = defer.Deferred()
        self.defers[contract_id] = d

        # Return defer for async code.
        return d

    def get_con_by_contract_id(self, needle):
        for con in list(self.con_info):
            for contract_id in list(self.con_info[con]):
                if contract_id == needle:
                    return con

        return None

    def remove_file_from_storage(self, data_id):
        storage.manager.remove(self.store_config, data_id)

    def move_file_to_storage(self, path):
        with open(path, "rb") as shard:
            storage.manager.add(self.store_config, shard)
            return {
                "file_size": storage.shard.get_size(shard),
                "data_id": storage.shard.get_id(shard)
            }

    def get_data_chunk(self, data_id, position, chunk_size=1048576):
        path = storage.manager.find(self.store_config, data_id)
        buf = b""
        with open(path, "rb") as fp:
            fp.seek(position, 0)
            buf = fp.read(chunk_size)

            return buf

    def save_data_chunk(self, data_id, chunk):
        _log.debug("Saving data chunk for " + str(data_id))
        _log.debug("of size + " + str(len(chunk)))
        assert(data_id in self.downloading)

        # Find temp file path.
        path = self.downloading[data_id]

        _log.debug(path)
        with open(path, "ab") as fp:
            fp.write(chunk)
Пример #5
0
class FileTransfer:
    def __init__(self, net, wif=None, store_config=None, handlers=None):
        # Accept direct connections.
        self.net = net

        # Returned by callbacks.
        self.success_value = ("127.0.0.1", 7777)

        # Used for signing messages.
        self.wallet = BtcTxStore(testnet=True, dryrun=True)
        self.wif = wif or self.wallet.create_key()

        # Where will the data be stored?
        self.store_config = store_config
        assert (len(list(store_config)))

        # Handlers for certain events.
        self.handlers = handlers

        # Start networking.
        if not self.net.is_net_started:
            self.net.start()

        # Dict of data requests.
        self.contracts = {}

        # Dict of defers for contracts.
        self.defers = {}

        # Three-way handshake status for contracts.
        self.handshake = {}

        # All contracts associated with this connection.
        self.con_info = {}

        # File transfer currently active on connection.
        self.con_transfer = {}

        # List of active downloads.
        # (Never try to download multiple copies of the same thing at once.)
        self.downloading = {}

    def get_their_unl(self, contract):
        if self.net.unl == pyp2p.unl.UNL(value=contract["dest_unl"]):
            their_unl = contract["src_unl"]
        else:
            their_unl = contract["dest_unl"]

        return their_unl

    def is_queued(self, con=None):
        if con is not None:
            if con not in self.con_info:
                return 0

        if con is None:
            con_list = list(self.con_info)
        else:
            con_list = [con]

        for con in con_list:
            for contract_id in list(self.con_info[con]):
                con_info = self.con_info[con][contract_id]
                if con_info["remaining"]:
                    return 1

        return 0

    def cleanup_transfers(self, con):
        # Close con - there's nothing left to download.
        if not self.is_queued(con):
            # Cleanup con transfers.
            if con in self.con_transfer:
                del self.con_transfer[con]

            # Cleanup con_info.
            if con in self.con_info:
                del self.con_info[con]

            # Todo: cleanup contract + handshake state.

    def queue_next_transfer(self, con):
        _log.debug("Queing next transfer")
        for contract_id in list(self.con_info[con]):
            con_info = self.con_info[con][contract_id]
            if con_info["remaining"]:
                self.con_transfer[con] = contract_id
                con.send(contract_id, send_all=1)
                return

        # Mark end of transfers.
        self.con_transfer[con] = u"0" * 64

    def is_valid_syn(self, msg):
        # List of expected fields.
        syn_schema = (u"status", u"direction", u"data_id", u"file_size",
                      u"host_unl", u"dest_unl", u"src_unl", u"signature")

        # Check all fields exist.
        if not all(key in msg for key in syn_schema):
            _log.debug("Missing required key.")
            return 0

        # Check SYN size.
        if len(msg) > 5242880:  # 5 MB.
            _log.debug("SYN is too big")
            return 0

        # Check direction is valid.
        direction_tuple = (u"send", u"receive")
        if msg[u"direction"] not in direction_tuple:
            _log.debug("Missing required direction tuple.")
            return 0

        # Check the UNLs are valid.
        unl_tuple = (u"host_unl", u"dest_unl", u"src_unl")
        for unl_key in unl_tuple:
            if not pyp2p.unl.is_valid_unl(msg[unl_key]):
                _log.debug("Invalid UNL for " + unl_key)
                _log.debug(msg[unl_key])
                return 0

        # Check file size.
        file_size_type = type(msg[u"file_size"])
        if sys.version_info >= (3, 0, 0):
            expr = file_size_type != int
        else:
            expr = file_size_type != int and file_size_type != long
        if expr:
            _log.debug("File size validation failed")
            _log.debug(type(msg[u"file_size"]))
            return 0

        # Are we the host?
        if self.net.unl == pyp2p.unl.UNL(value=msg[u"host_unl"]):
            # Then check we have this file.
            path = storage.manager.find(self.store_config, msg[u"data_id"])
            if path is None:
                _log.debug("Failed to find file we're uploading")
                return 0
        else:
            # Do we already have this file?
            path = storage.manager.find(self.store_config, msg[u"data_id"])
            if path is not None:
                _log.debug("Attempting to download file we already have")
                return 0

            # Are we already trying to download this?
            if msg[u"data_id"] in self.downloading:
                _log.debug("We're already trying to download this")
                return 0

        return 1

    def protocol(self, msg):
        msg = json.loads(msg, object_pairs_hook=OrderedDict)

        # Associate TCP con with contract.
        def success_wrapper(self, contract_id, host_unl):
            def success(con):
                with mutex:
                    _log.debug("IN SUCCESS CALLBACK")
                    _log.debug("Success() contract_id = " + str(contract_id))

                    # Associate TCP con with contract.
                    contract = self.contracts[contract_id]
                    file_size = contract["file_size"]

                    # Store con association.
                    if con not in self.con_info:
                        self.con_info[con] = {}

                    # Associate contract with con.
                    if contract_id not in self.con_info[con]:
                        self.con_info[con][contract_id] = {
                            "contract_id": contract_id,
                            "remaining": 350,  # Tree fiddy.
                            "file_size": file_size,
                            "file_size_buf": b""
                        }

                    # Record download state.
                    data_id = contract["data_id"]
                    if self.net.unl != pyp2p.unl.UNL(value=host_unl):
                        _log.debug("Success: download")
                        fp, self.downloading[data_id] = tempfile.mkstemp()
                    else:
                        # Set initial upload for this con.
                        _log.debug("Success: upload")

                    # Queue first transfer.
                    their_unl = self.get_their_unl(contract)
                    is_master = self.net.unl.is_master(their_unl)
                    _log.debug("Is master = " + str(is_master))
                    if con not in self.con_transfer:
                        if is_master:
                            # A transfer to queue processing.
                            self.queue_next_transfer(con)
                        else:
                            # A transfer to receive (unknown.)
                            self.con_transfer[con] = u""
                    else:
                        if self.con_transfer[con] == u"0" * 64:
                            if is_master:
                                self.queue_next_transfer(con)
                            else:
                                self.con_transfer[con] = u""

            return success

        # Sanity checking.
        if u"status" not in msg:
            return

        # Accept data request.
        if msg[u"status"] == u"SYN":
            # Check syn is valid.
            if not self.is_valid_syn(msg):
                _log.debug("SYN: invalid syn.")
                return

            # Save contract.
            contract_id = self.contract_id(msg)
            self.save_contract(msg)
            self.handshake[contract_id] = {
                "state": u"SYN-ACK",
                "timestamp": time.time()
            }

            # Create reply.
            reply = OrderedDict({
                u"status": u"SYN-ACK",
                u"syn": msg,
            })

            # Sign reply.
            reply = self.sign_contract(reply)

            # Save reply.
            self.send_msg(reply, msg[u"src_unl"])
            _log.debug("SYN")

        # Confirm accept and make connection if needed.
        if msg[u"status"] == u"SYN-ACK":
            # Valid syn-ack?
            if u"syn" not in msg:
                _log.debug("SYN-ACK: syn not in msg.")
                return

            # Is this a reply to our SYN?
            contract_id = self.contract_id(msg[u"syn"])
            if contract_id not in self.contracts:
                _log.debug("--------------")
                _log.debug(msg)
                _log.debug("--------------")
                _log.debug(self.contracts)
                _log.debug("--------------")
                _log.debug("SYN-ACK: contract not found.")
                return

            # Check syn is valid.
            if not self.is_valid_syn(msg[u"syn"]):
                _log.debug("SYN-ACK: invalid syn.")
                return

            # Did I sign this?
            if not self.is_valid_contract_sig(msg[u"syn"]):
                _log.debug("SYN-ACK: sig is invalid.")
                return

            # Update handshake.
            contract = self.contracts[contract_id]
            self.handshake[contract_id] = {
                "state": u"ACK",
                "timestamp": time.time()
            }

            # Create reply contract.
            reply = OrderedDict({u"status": u"ACK", u"syn_ack": msg})

            # Sign reply.
            reply = self.sign_contract(reply)

            # Try make TCP con.
            self.net.unl.connect(contract["dest_unl"], {
                "success":
                success_wrapper(self, contract_id, contract["host_unl"])
            },
                                 force_master=0,
                                 nonce=contract_id)

            # Send reply.
            self.send_msg(reply, msg[u"syn"][u"dest_unl"])
            _log.debug("SYN-ACK")

        if msg[u"status"] == u"ACK":
            # Valid ack.
            if u"syn_ack" not in msg:
                _log.debug("ACK: syn_ack not in msg.")
                return
            if u"syn" not in msg[u"syn_ack"]:
                _log.debug("ACK: syn not in msg.")
                return

            # Is this a reply to our SYN-ACK?
            contract_id = self.contract_id(msg[u"syn_ack"][u"syn"])
            if contract_id not in self.contracts:
                _log.debug("ACK: contract not found.")
                return

            # Did I sign this?
            if not self.is_valid_contract_sig(msg[u"syn_ack"]):
                _log.debug("--------------")
                _log.debug(msg)
                _log.debug("--------------")
                _log.debug(self.contracts)
                _log.debug("--------------")
                _log.debug("ACK: sig is invalid.")
                return

            # Is the syn valid?
            if not self.is_valid_syn(msg[u"syn_ack"][u"syn"]):
                _log.debug("ACK: syn is invalid.")
                return

            # Update handshake.
            contract = self.contracts[contract_id]
            self.handshake[contract_id] = {
                "state": u"ACK",
                "timestamp": time.time()
            }

            # Try make TCP con.
            self.net.unl.connect(contract["src_unl"], {
                "success":
                success_wrapper(self, contract_id, contract["host_unl"])
            },
                                 force_master=0,
                                 nonce=contract_id)

            _log.debug("ACK")

    def save_contract(self, contract):
        # Record contract details.
        contract_id = self.contract_id(contract)
        self.contracts[contract_id] = contract

        return contract_id

    def send_msg(self, dict_obj, unl):
        node_id = self.net.unl.deconstruct(unl)["node_id"]
        msg = json.dumps(dict_obj, ensure_ascii=True)
        self.net.dht_node.direct_message(node_id, msg)

    def contract_id(self, contract):
        if sys.version_info >= (3, 0, 0):
            contract = str(contract).encode("ascii")
        else:
            contract = str(contract)

        return hashlib.sha256(contract).hexdigest()

    def sign_contract(self, contract):
        if sys.version_info >= (3, 0, 0):
            msg = str(contract).encode("ascii")
        else:
            msg = str(contract)

        msg = binascii.hexlify(msg).decode("utf-8")
        sig = self.wallet.sign_data(self.wif, msg)

        if sys.version_info >= (3, 0, 0):
            contract[u"signature"] = sig.decode("utf-8")
        else:
            contract[u"signature"] = unicode(sig)

        return contract

    def is_valid_contract_sig(self, contract):
        sig = contract[u"signature"][:]
        del contract[u"signature"]

        if sys.version_info >= (3, 0, 0):
            msg = str(contract).encode("ascii")
        else:
            msg = str(contract)

        msg = binascii.hexlify(msg).decode("utf-8")
        address = self.wallet.get_address(self.wif)

        ret = self.wallet.verify_signature(address, sig, msg)
        contract[u"signature"] = sig[:]

        return ret

    def simple_data_request(self, data_id, node_unl, direction):
        file_size = 0
        if direction == u"send":
            action = u"upload"
        else:
            action = u"download"

        return self.data_request(action, data_id, file_size, node_unl)

    def data_request(self, action, data_id, file_size, node_unl):
        """
        Action = put (upload), get (download.)
        """
        _log.debug("In data request function")

        # Who is hosting this data?
        if action == "upload":
            # We store this data.
            direction = u"send"
            host_unl = self.net.unl.value
            assert (storage.manager.find(self.store_config, data_id)
                    is not None)
        else:
            # They store the data.
            direction = u"receive"
            host_unl = node_unl
            if data_id in self.downloading:
                raise Exception("Already trying to download this.")

        # Encoding.
        if sys.version_info >= (3, 0, 0):
            if type(data_id) == bytes:
                data_id = data_id.decode("utf-8")

            if type(host_unl) == bytes:
                host_unl = host_unl.decode("utf-8")

            if type(node_unl) == bytes:
                node_unl = node_unl.decode("utf-8")
        else:
            if type(data_id) == str:
                data_id = unicode(data_id)

            if type(host_unl) == str:
                host_unl = unicode(host_unl)

            if type(node_unl) == str:
                node_unl = unicode(node_unl)

        # Create contract.
        contract = OrderedDict({
            u"status": u"SYN",
            u"direction": direction,
            u"data_id": data_id,
            u"file_size": file_size,
            u"host_unl": host_unl,
            u"dest_unl": node_unl,
            u"src_unl": self.net.unl.value
        })

        # Sign contract.
        contract = self.sign_contract(contract)

        # Route contract.
        contract_id = self.save_contract(contract)
        self.send_msg(contract, node_unl)
        _log.debug("Sending data request")

        # Update handshake.
        self.handshake[contract_id] = {
            "state": "SYN",
            "timestamp": time.time()
        }

        # For async code.
        d = defer.Deferred()
        self.defers[contract_id] = d

        # Return defer for async code.
        return d

    def remove_file_from_storage(self, data_id):
        storage.manager.remove(self.store_config, data_id)

    def move_file_to_storage(self, path):
        with open(path, "rb") as shard:
            storage.manager.add(self.store_config, shard)
            return {
                "file_size": storage.shard.get_size(shard),
                "data_id": storage.shard.get_id(shard)
            }

    def get_data_chunk(self, data_id, position, chunk_size=1048576):
        path = storage.manager.find(self.store_config, data_id)
        buf = b""
        with open(path, "rb") as fp:
            fp.seek(position, 0)
            buf = fp.read(chunk_size)

            return buf

    def save_data_chunk(self, data_id, chunk):
        _log.debug("Saving data chunk for " + str(data_id))
        _log.debug("of size + " + str(len(chunk)))
        assert (data_id in self.downloading)

        # Find temp file path.
        path = self.downloading[data_id]

        _log.debug(path)
        with open(path, "ab") as fp:
            fp.write(chunk)
Пример #6
0
class FileTransfer:
    def __init__(self, net, wif=None, store_config=None, handlers=None):
        # Accept direct connections.
        self.net = net

        # Returned by callbacks.
        self.success_value = ("127.0.0.1", 7777)

        # Used for signing messages.
        self.wallet = BtcTxStore(testnet=True, dryrun=True)
        self.wif = wif or self.wallet.create_key()

        # Where will the data be stored?
        self.store_config = store_config
        assert(len(list(store_config)))

        # Handlers for certain events.
        self.handlers = handlers

        # Start networking.
        if not self.net.is_net_started:
            self.net.start()

        # Dict of data requests.
        self.contracts = {}

        # Dict of defers for contracts.
        self.defers = {}

        # Three-way handshake status for contracts.
        self.handshake = {}

        # All contracts associated with this connection.
        self.con_info = {}

        # File transfer currently active on connection.
        self.con_transfer = {}

        # List of active downloads.
        # (Never try to download multiple copies of the same thing at once.)
        self.downloading = {}

    def get_their_unl(self, contract):
        if self.net.unl == pyp2p.unl.UNL(value=contract["dest_unl"]):
            their_unl = contract["src_unl"]
        else:
            their_unl = contract["dest_unl"]

        return their_unl

    def is_queued(self, con=None):
        if con is not None:
            if con not in self.con_info:
                return 0

        if con is None:
            con_list = list(self.con_info)
        else:
            con_list = [con]

        for con in con_list:
            for contract_id in list(self.con_info[con]):
                con_info = self.con_info[con][contract_id]
                if con_info["remaining"]:
                    return 1

        return 0

    def cleanup_transfers(self, con):
        # Close con - there's nothing left to download.
        if not self.is_queued(con):
            # Cleanup con transfers.
            if con in self.con_transfer:
                del self.con_transfer[con]

            # Cleanup con_info.
            if con in self.con_info:
                del self.con_info[con]

            # Todo: cleanup contract + handshake state.

    def queue_next_transfer(self, con):
        _log.debug("Queing next transfer")
        for contract_id in list(self.con_info[con]):
            con_info = self.con_info[con][contract_id]
            if con_info["remaining"]:
                self.con_transfer[con] = contract_id
                con.send(contract_id, send_all=1)
                return

        # Mark end of transfers.
        self.con_transfer[con] = u"0" * 64

    def is_valid_syn(self, msg):
        # List of expected fields.
        syn_schema = (
            u"status",
            u"direction",
            u"data_id",
            u"file_size",
            u"host_unl",
            u"dest_unl",
            u"src_unl",
            u"signature"
        )

        # Check all fields exist.
        if not all(key in msg for key in syn_schema):
            _log.debug("Missing required key.")
            return 0

        # Check SYN size.
        if len(msg) > 5242880: # 5 MB.
            _log.debug("SYN is too big")
            return 0

        # Check direction is valid.
        direction_tuple = (u"send", u"receive")
        if msg[u"direction"] not in direction_tuple:
            _log.debug("Missing required direction tuple.")
            return 0

        # Check the UNLs are valid.
        unl_tuple = (u"host_unl", u"dest_unl", u"src_unl")
        for unl_key in unl_tuple:
            if not pyp2p.unl.is_valid_unl(msg[unl_key]):
                _log.debug("Invalid UNL for " + unl_key)
                _log.debug(msg[unl_key])
                return 0

        # Check file size.
        file_size_type = type(msg[u"file_size"])
        if sys.version_info >= (3, 0, 0):
            expr = file_size_type != int
        else:
            expr = file_size_type != int and file_size_type != long
        if expr:
            _log.debug("File size validation failed")
            _log.debug(type(msg[u"file_size"]))
            return 0

        # Are we the host?
        if self.net.unl == pyp2p.unl.UNL(value=msg[u"host_unl"]):
            # Then check we have this file.
            path = storage.manager.find(self.store_config, msg[u"data_id"])
            if path is None:
                _log.debug("Failed to find file we're uploading")
                return 0
        else:
            # Do we already have this file?
            path = storage.manager.find(self.store_config, msg[u"data_id"])
            if path is not None:
                _log.debug("Attempting to download file we already have")
                return 0

            # Are we already trying to download this?
            if msg[u"data_id"] in self.downloading:
                _log.debug("We're already trying to download this")
                return 0

        return 1

    def protocol(self, msg):
        msg = json.loads(msg, object_pairs_hook=OrderedDict)

        # Associate TCP con with contract.
        def success_wrapper(self, contract_id, host_unl):
            def success(con):
                with mutex:
                    _log.debug("IN SUCCESS CALLBACK")
                    _log.debug("Success() contract_id = " + str(contract_id))

                    # Associate TCP con with contract.
                    contract = self.contracts[contract_id]
                    file_size = contract["file_size"]

                    # Store con association.
                    if con not in self.con_info:
                        self.con_info[con] = {}

                    # Associate contract with con.
                    if contract_id not in self.con_info[con]:
                        self.con_info[con][contract_id] = {
                            "contract_id": contract_id,
                            "remaining": 350, # Tree fiddy.
                            "file_size": file_size,
                            "file_size_buf": b""
                        }

                    # Record download state.
                    data_id = contract["data_id"]
                    if self.net.unl != pyp2p.unl.UNL(value=host_unl):
                        _log.debug("Success: download")
                        fp, self.downloading[data_id] = tempfile.mkstemp()
                    else:
                        # Set initial upload for this con.
                        _log.debug("Success: upload")

                    # Queue first transfer.
                    their_unl = self.get_their_unl(contract)
                    is_master = self.net.unl.is_master(their_unl)
                    _log.debug("Is master = " + str(is_master))
                    if con not in self.con_transfer:
                        if is_master:
                            # A transfer to queue processing.
                            self.queue_next_transfer(con)
                        else:
                            # A transfer to receive (unknown.)
                            self.con_transfer[con] = u""
                    else:
                        if self.con_transfer[con] == u"0" * 64:
                            if is_master:
                                self.queue_next_transfer(con)
                            else:
                                self.con_transfer[con] = u""
            return success

        # Sanity checking.
        if u"status" not in msg:
            return

        # Accept data request.
        if msg[u"status"] == u"SYN":
            # Check syn is valid.
            if not self.is_valid_syn(msg):
                _log.debug("SYN: invalid syn.")
                return

            # Save contract.
            contract_id = self.contract_id(msg)
            self.save_contract(msg)
            self.handshake[contract_id] = {
                "state": u"SYN-ACK",
                "timestamp": time.time()
            }

            # Create reply.
            reply = OrderedDict({
                u"status": u"SYN-ACK",
                u"syn": msg,
            })

            # Sign reply.
            reply = self.sign_contract(reply)

            # Save reply.
            self.send_msg(reply, msg[u"src_unl"])
            _log.debug("SYN")

        # Confirm accept and make connection if needed.
        if msg[u"status"] == u"SYN-ACK":
            # Valid syn-ack?
            if u"syn" not in msg:
                _log.debug("SYN-ACK: syn not in msg.")
                return

            # Is this a reply to our SYN?
            contract_id = self.contract_id(msg[u"syn"])
            if contract_id not in self.contracts:
                _log.debug("--------------")
                _log.debug(msg)
                _log.debug("--------------")
                _log.debug(self.contracts)
                _log.debug("--------------")
                _log.debug("SYN-ACK: contract not found.")
                return

            # Check syn is valid.
            if not self.is_valid_syn(msg[u"syn"]):
                _log.debug("SYN-ACK: invalid syn.")
                return

            # Did I sign this?
            if not self.is_valid_contract_sig(msg[u"syn"]):
                _log.debug("SYN-ACK: sig is invalid.")
                return

            # Update handshake.
            contract = self.contracts[contract_id]
            self.handshake[contract_id] = {
                "state": u"ACK",
                "timestamp": time.time()
            }

            # Create reply contract.
            reply = OrderedDict({
                u"status": u"ACK",
                u"syn_ack": msg
            })

            # Sign reply.
            reply = self.sign_contract(reply)

            # Try make TCP con.
            self.net.unl.connect(
                contract["dest_unl"],
                {
                    "success": success_wrapper(
                        self,
                        contract_id,
                        contract["host_unl"]
                    )
                },
                force_master=0,
                nonce=contract_id
            )

            # Send reply.
            self.send_msg(reply, msg[u"syn"][u"dest_unl"])
            _log.debug("SYN-ACK")

        if msg[u"status"] == u"ACK":
            # Valid ack.
            if u"syn_ack" not in msg:
                _log.debug("ACK: syn_ack not in msg.")
                return
            if u"syn" not in msg[u"syn_ack"]:
                _log.debug("ACK: syn not in msg.")
                return

            # Is this a reply to our SYN-ACK?
            contract_id = self.contract_id(msg[u"syn_ack"][u"syn"])
            if contract_id not in self.contracts:
                _log.debug("ACK: contract not found.")
                return

            # Did I sign this?
            if not self.is_valid_contract_sig(msg[u"syn_ack"]):
                _log.debug("--------------")
                _log.debug(msg)
                _log.debug("--------------")
                _log.debug(self.contracts)
                _log.debug("--------------")
                _log.debug("ACK: sig is invalid.")
                return

            # Is the syn valid?
            if not self.is_valid_syn(msg[u"syn_ack"][u"syn"]):
                _log.debug("ACK: syn is invalid.")
                return

            # Update handshake.
            contract = self.contracts[contract_id]
            self.handshake[contract_id] = {
                "state": u"ACK",
                "timestamp": time.time()
            }

            # Try make TCP con.
            self.net.unl.connect(
                contract["src_unl"],
                {
                    "success": success_wrapper(
                        self,
                        contract_id,
                        contract["host_unl"]
                    )
                },
                force_master=0,
                nonce=contract_id
            )

            _log.debug("ACK")

    def save_contract(self, contract):
        # Record contract details.
        contract_id = self.contract_id(contract)
        self.contracts[contract_id] = contract

        return contract_id

    def send_msg(self, dict_obj, unl):
        node_id = self.net.unl.deconstruct(unl)["node_id"]
        msg = json.dumps(dict_obj, ensure_ascii=True)
        self.net.dht_node.direct_message(
            node_id,
            msg
        )

    def contract_id(self, contract):
        if sys.version_info >= (3, 0, 0):
            contract = str(contract).encode("ascii")
        else:
            contract = str(contract)

        return hashlib.sha256(contract).hexdigest()

    def sign_contract(self, contract):
        if sys.version_info >= (3, 0, 0):
            msg = str(contract).encode("ascii")
        else:
            msg = str(contract)

        msg = binascii.hexlify(msg).decode("utf-8")
        sig = self.wallet.sign_data(self.wif, msg)

        if sys.version_info >= (3, 0, 0):
            contract[u"signature"] = sig.decode("utf-8")
        else:
            contract[u"signature"] = unicode(sig)

        return contract

    def is_valid_contract_sig(self, contract):
        sig = contract[u"signature"][:]
        del contract[u"signature"]

        if sys.version_info >= (3, 0, 0):
            msg = str(contract).encode("ascii")
        else:
            msg = str(contract)

        msg = binascii.hexlify(msg).decode("utf-8")
        address = self.wallet.get_address(self.wif)

        ret = self.wallet.verify_signature(address, sig, msg)
        contract[u"signature"] = sig[:]

        return ret

    def simple_data_request(self, data_id, node_unl, direction):
        file_size = 0
        if direction == u"send":
            action = u"upload"
        else:
            action = u"download"

        return self.data_request(action, data_id, file_size, node_unl)

    def data_request(self, action, data_id, file_size, node_unl):
        """
        Action = put (upload), get (download.)
        """
        _log.debug("In data request function")

        # Who is hosting this data?
        if action == "upload":
            # We store this data.
            direction = u"send"
            host_unl = self.net.unl.value
            assert(storage.manager.find(self.store_config, data_id) is not None)
        else:
            # They store the data.
            direction = u"receive"
            host_unl = node_unl
            if data_id in self.downloading:
                raise Exception("Already trying to download this.")

        # Encoding.
        if sys.version_info >= (3, 0, 0):
            if type(data_id) == bytes:
                data_id = data_id.decode("utf-8")

            if type(host_unl) == bytes:
                host_unl = host_unl.decode("utf-8")

            if type(node_unl) == bytes:
                node_unl = node_unl.decode("utf-8")
        else:
            if type(data_id) == str:
                data_id = unicode(data_id)

            if type(host_unl) == str:
                host_unl = unicode(host_unl)

            if type(node_unl) == str:
                node_unl = unicode(node_unl)

        # Create contract.
        contract = OrderedDict({
            u"status": u"SYN",
            u"direction": direction,
            u"data_id": data_id,
            u"file_size": file_size,
            u"host_unl": host_unl,
            u"dest_unl": node_unl,
            u"src_unl": self.net.unl.value
        })

        # Sign contract.
        contract = self.sign_contract(contract)

        # Route contract.
        contract_id = self.save_contract(contract)
        self.send_msg(contract, node_unl)
        _log.debug("Sending data request")

        # Update handshake.
        self.handshake[contract_id] = {
            "state": "SYN",
            "timestamp": time.time()
        }

        # For async code.
        d = defer.Deferred()
        self.defers[contract_id] = d

        # Return defer for async code.
        return d

    def remove_file_from_storage(self, data_id):
        storage.manager.remove(self.store_config, data_id)

    def move_file_to_storage(self, path):
        with open(path, "rb") as shard:
            storage.manager.add(self.store_config, shard)
            return {
                "file_size": storage.shard.get_size(shard),
                "data_id": storage.shard.get_id(shard)
            }

    def get_data_chunk(self, data_id, position, chunk_size=1048576):
        path = storage.manager.find(self.store_config, data_id)
        buf = b""
        with open(path, "rb") as fp:
            fp.seek(position, 0)
            buf = fp.read(chunk_size)

            return buf

    def save_data_chunk(self, data_id, chunk):
        _log.debug("Saving data chunk for " + str(data_id))
        _log.debug("of size + " + str(len(chunk)))
        assert(data_id in self.downloading)

        # Find temp file path.
        path = self.downloading[data_id]

        _log.debug(path)
        with open(path, "ab") as fp:
            fp.write(chunk)
Пример #7
0
#!/usr/bin/env python
# coding: utf-8
# Copyright (c) 2015 Fabian Barkhau <*****@*****.**>
# License: MIT (see LICENSE file)

from __future__ import print_function
from __future__ import unicode_literals
import binascii
from btctxstore import BtcTxStore

api = BtcTxStore(testnet=True, dryrun=True)  # use testing setup for example
wif = api.create_key()  # create new private key
address = api.get_address(wif)  # get private key address
data = binascii.hexlify(b"messagetext")  # hexlify messagetext

# sign data with private key
signature = api.sign_data(wif, data)
print("signature:", signature)

# verify signature (no public or private key needed)
isvalid = api.verify_signature(address, signature, data)
print("valid signature" if isvalid else "invalid signature")
# store data in blockchain as nulldata output (max 40bytes)
data = binascii.hexlify(b"example_data")
txid = api.store_nulldata(data, wifs)

# Show current transaction id
print("Current Transaction ID: {}".format(txid))


# Now, retrieve data based on transaction id
hexnulldata = api.retrieve_nulldata(txid)


print("Retrieved Data: {}".format(hexnulldata))

# create new private key
wif = api.create_key()  

 # get private key address
address = api.get_address(wif) 

 # hexlify messagetext
data = binascii.hexlify(b"messagetext")

# sign data with private key
signature = api.sign_data(wif, data)
print("signature:", signature)

# verify signature (no public or private key needed)
isvalid = api.verify_signature(address, signature, data)
print("valid signature" if isvalid else "invalid signature")