diff -ur BitTorrent-3.4.2/BitTorrent/Connecter.py BitTorrent/BitTorrent/Connecter.py
--- BitTorrent-3.4.2/BitTorrent/Connecter.py	Mon May 31 17:38:57 2004
+++ BitTorrent/BitTorrent/Connecter.py	Mon Jun 21 17:22:27 2004
@@ -16,71 +16,370 @@
 UNCHOKE = chr(1)
 INTERESTED = chr(2)
 NOT_INTERESTED = chr(3)
-# index
+# index | hash
 HAVE = chr(4)
-# index, bitfield
+# bitfield
 BITFIELD = chr(5)
-# index, begin, length
+# index, begin, length | begin, length, hash
 REQUEST = chr(6)
-# index, begin, piece
+# index, begin, piece | begin, htype(char), hash, piece
 PIECE = chr(7)
-# index, begin, piece
+# index, begin, length | begin, length, hash
 CANCEL = chr(8)
+# msg_type hash (EXPERIMENTAL)
+KEYAMB = chr(9)
 
-class Connection:
-    def __init__(self, connection, connecter):
-        self.connection = connection
+
+class IndexedConnection:
+    def __init__(self, rawconnection, connecter):
+        self.raw = rawconnection
         self.connecter = connecter
         self.got_anything = False
 
     def get_ip(self):
-        return self.connection.get_ip()
+        return self.raw.get_ip()
 
     def get_id(self):
-        return self.connection.get_id()
+        return self.raw.get_id()
 
     def close(self):
-        self.connection.close()
+        self.raw.close()
 
     def is_flushed(self):
         if self.connecter.rate_capped:
             return False
-        return self.connection.is_flushed()
+        return self.raw.is_flushed()
 
     def is_locally_initiated(self):
-        return self.connection.is_locally_initiated()
+        return self.raw.is_locally_initiated()
 
     def send_interested(self):
-        self.connection.send_message(INTERESTED)
+        self.raw.send_message(INTERESTED)
 
     def send_not_interested(self):
-        self.connection.send_message(NOT_INTERESTED)
+        self.raw.send_message(NOT_INTERESTED)
 
     def send_choke(self):
-        self.connection.send_message(CHOKE)
+        self.raw.send_message(CHOKE)
 
     def send_unchoke(self):
-        self.connection.send_message(UNCHOKE)
+        self.raw.send_message(UNCHOKE)
 
     def send_request(self, index, begin, length):
-        self.connection.send_message(REQUEST + tobinary(index) + 
+        self.raw.send_message(REQUEST + tobinary(index) + 
             tobinary(begin) + tobinary(length))
 
     def send_cancel(self, index, begin, length):
-        self.connection.send_message(CANCEL + tobinary(index) + 
+        self.raw.send_message(CANCEL + tobinary(index) + 
             tobinary(begin) + tobinary(length))
 
     def send_piece(self, index, begin, piece):
         assert not self.connecter.rate_capped
         self.connecter._update_upload_rate(len(piece))
-        self.connection.send_message(PIECE + tobinary(index) + 
+        self.raw.send_message(PIECE + tobinary(index) + 
             tobinary(begin) + piece)
 
     def send_bitfield(self, bitfield):
-        self.connection.send_message(BITFIELD + bitfield)
+        self.raw.send_message(BITFIELD + bitfield.tostring())
 
     def send_have(self, index):
-        self.connection.send_message(HAVE + tobinary(index))
+        if not self.download.have[index]:
+            self.raw.send_message(HAVE + tobinary(index))
+
+    def got_message(self, message):
+        t = message[0]
+        if t == BITFIELD and self.got_anything:
+            self.raw.close()
+            return
+        self.got_anything = True
+        if t == CHOKE:
+            if len(message) != 1:
+                self.raw.close()
+                return
+            self.download.got_choke()
+        elif t == UNCHOKE:
+            if len(message) != 1:
+                self.raw.close()
+                return
+            self.download.got_unchoke()
+        elif t == INTERESTED:
+            if len(message) != 1:
+                self.raw.close()
+                return
+            self.upload.got_interested()
+        elif t == NOT_INTERESTED:
+            if len(message) != 1:
+                self.raw.close()
+                return
+            self.upload.got_not_interested()
+        elif t == HAVE:
+            if len(message) != 5:
+                self.raw.close()
+                return
+            i = toint(message[1:5])
+            if i >= self.connecter.numpieces:
+                self.raw.close()
+                return
+            self.download.got_have(i)
+        elif t == BITFIELD:
+            if len(message) <= 1:
+                self.raw.close()
+                return
+            try:
+                b = Bitfield(self.connecter.numpieces, message[1:])
+            except ValueError:
+                self.raw.close()
+                return
+            self.download.got_have_bitfield(b)
+        elif t == REQUEST:
+            if len(message) != 13:
+                self.raw.close()
+                return
+            i = toint(message[1:5])
+            if i >= self.connecter.numpieces:
+                self.raw.close()
+                return
+            self.upload.got_request(i, toint(message[5:9]), 
+                toint(message[9:13]))
+        elif t == CANCEL:
+            if len(message) != 13:
+                self.raw.close()
+                return
+            i = toint(message[1:5])
+            if i >= self.connecter.numpieces:
+                self.raw.close()
+                return
+            self.upload.got_cancel(i, toint(message[5:9]), 
+                toint(message[9:13]))
+        elif t == PIECE:
+            if len(message) <= 9:
+                self.raw.close()
+                return
+            i = toint(message[1:5])
+            if i >= self.connecter.numpieces:
+                self.raw.close()
+                return
+            if self.download.got_piece(i, toint(message[5:9]), message[9:]):
+                self.connecter.got_piece(i)
+        else:
+            self.raw.close()
+
+    def get_upload(self):
+        return self.upload
+
+    def get_download(self):
+        return self.download
+
+# HashedConnection written and Copyright (C) 2004, Elliott Mitchell
+# see LICENSE.txt for license information
+class HashedConnection:
+    def __init__(self, rawconnection, connecter):
+        self.raw = rawconnection
+        self.connecter = connecter
+        self.got_anything = False
+
+    def get_ip(self):
+        return self.raw.get_ip()
+
+    def get_id(self):
+        return self.raw.get_id()
+
+    def close(self):
+        self.raw.close()
+
+    def is_flushed(self):
+        if self.connecter.rate_capped:
+            return False
+        return self.raw.is_flushed()
+
+    def is_locally_initiated(self):
+        return self.raw.is_locally_initiated()
+
+    def send_interested(self):
+        self.raw.send_message(INTERESTED)
+
+    def send_not_interested(self):
+        self.raw.send_message(NOT_INTERESTED)
+
+    def send_choke(self):
+        self.raw.send_message(CHOKE)
+
+    def send_unchoke(self):
+        self.raw.send_message(UNCHOKE)
+
+    def send_request(self, index, begin, length):
+        self.raw.send_message(REQUEST + tobinary(begin) + tobinary(length) +
+            self.connecter.downloader.storage.hashes[index])
+
+    def send_cancel(self, index, begin, length):
+        self.raw.send_message(CANCEL + tobinary(begin) + tobinary(length) +
+            self.connecter.downloader.storage.hashes[index])
+
+    def send_piece(self, index, begin, piece):
+        assert not self.connecter.rate_capped
+        self.connecter._update_upload_rate(len(piece))
+        self.raw.send_message(PIECE + tobinary(begin) +
+            chr(0x09) + self.connecter.downloader.storage.hashes[index] + piece)
+
+    def send_bitfield(self, native):
+        send = Bitfield(len(native))
+        mapping = self.connecter.downloader.storage.hash_index_map
+        for i in xrange(len(native)):
+            send[i] = native[mapping[i]]
+        self.raw.send_message(BITFIELD + send.tostring())
+
+    def send_have(self, index, length = 4):
+        if not self.download.have[index]:
+            while not self.connecter.downloader.storage.hashes[index][0:length]:
+                # This computes the next quarter step (it works)
+                length = length + (length >> 2)
+                length = length & ~(length >> 2 | length >> 3)
+            self.raw.send_message(HAVE +
+                self.connecter.downloader.storage.hashes[index][0:length])
+
+    def send_keyamb(self, type, hash, begin = None):
+        # I actually suspect including the original message type is a mistake.
+        # Relying on it being correct opens one to security holes...
+        if begin == None:
+            self.connection.send_message(KEYAMB + type + hash)
+        else:
+            self.connection.send_message(KEYAMB + type + begin + hash)
+
+    def got_message(self, message):
+        t = message[0]
+        if t == BITFIELD and self.got_anything:
+            self.raw.close()
+            return
+        self.got_anything = True
+        if t == CHOKE:
+            if len(message) != 1:
+                self.raw.close()
+                return
+            self.download.got_choke()
+        elif t == UNCHOKE:
+            if len(message) != 1:
+                self.raw.close()
+                return
+            self.download.got_unchoke()
+        elif t == INTERESTED:
+            if len(message) != 1:
+                self.raw.close()
+                return
+            self.upload.got_interested()
+        elif t == NOT_INTERESTED:
+            if len(message) != 1:
+                self.raw.close()
+                return
+            self.upload.got_not_interested()
+        elif t == HAVE:
+            if len(message) < 5 or len(message) > 21:
+                self.raw.close()
+                return
+            try:
+                i = self.connecter.downloader.storage.get_index_by_hash(
+                    message[1:])
+            except KeyError:
+                self.raw.close()
+                return
+            if i == None:
+                self.send_keyamb(t, message[1:])
+                return
+            self.download.got_have(i)
+        elif t == BITFIELD:
+            if len(message) <= 1:
+                self.raw.close()
+                return
+            try:
+                netbf = Bitfield(self.connecter.numpieces, message[1:])
+            except ValueError:
+                self.raw.close()
+                return
+            native = Bitfield(len(netbf))
+            mapping = self.connecter.downloader.storage.hash_index_map
+            for i in xrange(len(netbf)):
+                native[mapping[i]] = netbf[i]
+            self.download.got_have_bitfield(native)
+        elif t == REQUEST:
+            if len(message) < 13 or len(message) > 29:
+                self.raw.close()
+                return
+            try:
+                i = self.connecter.downloader.storage.get_index_by_hash(
+                    message[9:])
+            except KeyError:
+                self.raw.close()
+                return
+            if i == None:
+                self.send_keyamb(t, message[9:], message[5:9])
+                return
+            self.upload.got_request(i, toint(message[1:5]), toint(message[5:9]))
+        elif t == CANCEL:
+            if len(message) < 13 or len(message) > 29:
+                self.raw.close()
+                return
+            try:
+                i = self.connecter.downloader.storage.get_index_by_hash(
+                    message[9:])
+            except KeyError:
+                self.raw.close()
+                return
+            if i == None:
+                self.send_keyamb(t, message[9:], message[1:5])
+                return
+            self.upload.got_cancel(i, toint(message[1:5]), toint(message[5:9]))
+        elif t == PIECE:
+            if len(message) <= 10:
+                self.raw.close()
+                return
+            hlen = ord(message[5])
+            if hlen > 0x1F:
+                # You're using 1792 bytes of hash? Is 10gb ethernet now the
+                # standard Internet connection??? More likely someone using the
+                # upper 3 bits for flags, but we don't understand it either way
+                self.raw.close()
+                return
+            hlen = ((hlen & 0x3) | 0x4) << (hlen >> 2)
+            if len(message) <= (6 + hlen):
+                self.raw.close()
+                return
+            try:
+                i = self.connecter.downloader.storage.get_index_by_hash(
+                    message[6:6+hlen])
+            except KeyError:
+                self.raw.close()
+                return
+            if i == None:
+                self.send_keyamb(t, message[6:6+hlen], message[1:5])
+                return
+            if self.download.got_piece(i, toint(message[1:5]), message[6+hlen:]):
+                self.connecter.got_piece(i)
+        elif t == KEYAMB:
+            if message[0] == HAVE:
+                if len(message) < 5 or len(message) > 21:
+                    self.raw.close()
+                    return
+                try:
+                    i = self.connecter.downloader.storage.get_index_by_hash(
+                        message[1:])
+                except KeyError:
+                    self.raw.close()
+                    return
+                hlen = len(message) - 1
+                if hlen >= 20:
+                    # not much we can do
+                    self.raw.close()
+                    return
+                # This computes the next quarter step (it works)
+                hlen = hlen + (hlen >> 2)
+                hlen = hlen & ~(hlen >> 2 | hlen >> 3)
+                self.send_have(i, hlen)
+            # Variable length hash sending is only used in HAVE messages with
+            # this client, so if we get this for another message, there is a
+            # severe problem
+            self.raw.close()
+            return
+        else:
+            self.raw.close()
 
     def get_upload(self):
         return self.upload
@@ -113,7 +412,7 @@
             up = None
             minrate = None
             for i in self.connections.values():
-                if not i.upload.is_choked() and i.upload.has_queries() and i.connection.is_flushed():
+                if not i.upload.is_choked() and i.upload.has_queries() and i.raw.is_flushed():
                     rate = i.upload.get_rate()
                     if up is None or rate < minrate:
                         up = i.upload
@@ -136,91 +435,35 @@
     def how_many_connections(self):
         return len(self.connections)
 
-    def connection_made(self, connection):
-        c = Connection(connection, self)
-        self.connections[connection] = c
+    def connection_made(self, rawconnection, hashmode):
+        if hashmode:
+            c = HashedConnection(rawconnection, self)
+        else:
+            c = IndexedConnection(rawconnection, self)
+        self.connections[rawconnection] = c
         c.upload = self.make_upload(c)
         c.download = self.downloader.make_download(c)
         self.choker.connection_made(c)
+        return c
 
-    def connection_lost(self, connection):
-        c = self.connections[connection]
+    def connection_lost(self, rawconnection):
+        c = self.connections[rawconnection]
         d = c.download
-        del self.connections[connection]
+        del self.connections[rawconnection]
         d.disconnected()
         self.choker.connection_lost(c)
 
-    def connection_flushed(self, connection):
-        self.connections[connection].upload.flushed()
+    def connection_flushed(self, rawconnection):
+        self.connections[rawconnection].upload.flushed()
+
+    def got_message(self, rawconnection, message):
+        c = self.connections[rawconnection]
+        c.got_message(message)
+
+    def got_piece(self, index):
+        for co in self.connections.values():
+            co.send_have(index)
 
-    def got_message(self, connection, message):
-        c = self.connections[connection]
-        t = message[0]
-        if t == BITFIELD and c.got_anything:
-            connection.close()
-            return
-        c.got_anything = True
-        if (t in [CHOKE, UNCHOKE, INTERESTED, NOT_INTERESTED] and 
-                len(message) != 1):
-            connection.close()
-            return
-        if t == CHOKE:
-            c.download.got_choke()
-        elif t == UNCHOKE:
-            c.download.got_unchoke()
-        elif t == INTERESTED:
-            c.upload.got_interested()
-        elif t == NOT_INTERESTED:
-            c.upload.got_not_interested()
-        elif t == HAVE:
-            if len(message) != 5:
-                connection.close()
-                return
-            i = toint(message[1:])
-            if i >= self.numpieces:
-                connection.close()
-                return
-            c.download.got_have(i)
-        elif t == BITFIELD:
-            try:
-                b = Bitfield(self.numpieces, message[1:])
-            except ValueError:
-                connection.close()
-                return
-            c.download.got_have_bitfield(b)
-        elif t == REQUEST:
-            if len(message) != 13:
-                connection.close()
-                return
-            i = toint(message[1:5])
-            if i >= self.numpieces:
-                connection.close()
-                return
-            c.upload.got_request(i, toint(message[5:9]), 
-                toint(message[9:]))
-        elif t == CANCEL:
-            if len(message) != 13:
-                connection.close()
-                return
-            i = toint(message[1:5])
-            if i >= self.numpieces:
-                connection.close()
-                return
-            c.upload.got_cancel(i, toint(message[5:9]), 
-                toint(message[9:]))
-        elif t == PIECE:
-            if len(message) <= 9:
-                connection.close()
-                return
-            i = toint(message[1:5])
-            if i >= self.numpieces:
-                connection.close()
-                return
-            if c.download.got_piece(i, toint(message[5:9]), message[9:]):
-                for co in self.connections.values():
-                    co.send_have(i)
-        else:
-            connection.close()
 
 class DummyUpload:
     def __init__(self, events):
@@ -275,7 +518,7 @@
     def make_download(self, connection):
         return DummyDownload(self.events)
 
-class DummyConnection:
+class DummyRawConnection:
     def __init__(self, events):
         self.events = events
 
@@ -287,8 +530,12 @@
         self.events = events
         self.cs = cs
 
-    def connection_made(self, c):
+    def connection_made(self, c, hm):
         self.events.append('made')
+        if hm:
+            self.events.append('  hash mode')
+        else:
+            self.events.append('  index mode')
         self.cs.append(c)
 
     def connection_lost(self, c):
@@ -303,8 +550,8 @@
     assert events == []
     assert cs == []
     
-    dc = DummyConnection(events)
-    co.connection_made(dc)
+    dc = DummyRawConnection(events)
+    co.connection_made(dc, False)
     assert len(cs) == 1
     cc = cs[0]
     co.got_message(dc, BITFIELD + chr(0xc0))
diff -ur BitTorrent-3.4.2/BitTorrent/Encrypter.py BitTorrent/BitTorrent/Encrypter.py
--- BitTorrent-3.4.2/BitTorrent/Encrypter.py	Mon May 31 17:39:31 2004
+++ BitTorrent/BitTorrent/Encrypter.py	Mon Jun 21 17:24:19 2004
@@ -16,7 +16,10 @@
 
 # header, reserved, download id, my id, [length, message]
 
-class Connection:
+BY_HASH_BYTE = 7
+BY_HASH_BIT = 0x01
+
+class RawConnection:
     def __init__(self, Encoder, connection, id, is_local):
         self.encoder = Encoder
         self.connection = connection
@@ -27,9 +30,11 @@
         self.buffer = StringIO()
         self.next_len = 1
         self.next_func = self.read_header_len
+        self.cooked = None
+        self.options = None
         if self.locally_initiated:
             connection.write(chr(len(protocol_name)) + protocol_name + 
-                (chr(0) * 8) + self.encoder.download_id)
+                (chr(0) * 7) + chr(BY_HASH_BIT) + self.encoder.download_id)
             if self.id is not None:
                 connection.write(self.encoder.my_id)
 
@@ -56,6 +61,7 @@
         return 8, self.read_reserved
 
     def read_reserved(self, s):
+        self.options = s
         return 20, self.read_download_id
 
     def read_download_id(self, s):
@@ -63,7 +69,8 @@
             return None
         if not self.locally_initiated:
             self.connection.write(chr(len(protocol_name)) + protocol_name + 
-                (chr(0) * 8) + self.encoder.download_id + self.encoder.my_id)
+                (chr(0) * 7) + chr(ord(self.options[BY_HASH_BYTE]) & BY_HASH_BIT) +
+                self.encoder.download_id + self.encoder.my_id)
         return 20, self.read_peer_id
 
     def read_peer_id(self, s):
@@ -82,7 +89,8 @@
             if s != self.id:
                 return None
         self.complete = True
-        self.encoder.connecter.connection_made(self)
+        self.cooked = self.encoder.connecter.connection_made(self,
+            ord(self.options[BY_HASH_BYTE]) & BY_HASH_BIT)
         return 4, self.read_len
 
     def read_len(self, s):
@@ -93,7 +101,7 @@
 
     def read_message(self, s):
         if s != '':
-            self.encoder.connecter.got_message(self, s)
+            self.cooked.got_message(s)
         return 4, self.read_len
 
     def read_dead(self, s):
@@ -105,6 +113,7 @@
             self.sever()
 
     def sever(self):
+        self.cooked = None
         self.closed = True
         del self.encoder.connections[self.connection]
         if self.complete:
@@ -172,7 +181,7 @@
             return
         try:
             c = self.raw_server.start_connection(dns)
-            self.connections[c] = Connection(self, c, id, True)
+            self.connections[c] = RawConnection(self, c, id, True)
         except socketerror:
             pass
     
@@ -193,7 +202,7 @@
         return self.everinc
 
     def external_connection_made(self, connection):
-        self.connections[connection] = Connection(self, 
+        self.connections[connection] = RawConnection(self, 
             connection, None, False)
 
     def connection_flushed(self, connection):
diff -ur BitTorrent-3.4.2/BitTorrent/StorageWrapper.py BitTorrent/BitTorrent/StorageWrapper.py
--- BitTorrent-3.4.2/BitTorrent/StorageWrapper.py	Mon May 31 17:39:15 2004
+++ BitTorrent/BitTorrent/StorageWrapper.py	Mon Jun 21 17:22:36 2004
@@ -19,6 +19,21 @@
         self.storage = storage
         self.request_size = request_size
         self.hashes = hashes
+        # begining of by_hash code
+        self.hash_dict = {}
+        for index in xrange(len(hashes)):
+            # The sizes allowed, powers of 2 with quarter steps starting with 4
+            for size in (16, 14, 12, 10, 8, 7, 6, 5, 4):
+                if self.hash_dict.has_key(hashes[index][0:size]):
+                    self.hash_dict[hashes[index][0:size]]=None
+                else:
+                    self.hash_dict[hashes[index][0:size]]=index
+            assert(not self.hash_dict.has_key(hashes[index]))
+            self.hash_dict[hashes[index]]=index
+        tmp = [x for x in hashes]
+        tmp.sort()
+        self.hash_index_map = [self.hash_dict[el] for el in tmp]
+        # end of by_hash code
         self.piece_size = piece_size
         self.data_flunked = data_flunked
         self.total_length = storage.get_total_length()
@@ -112,7 +127,10 @@
         return self.endgame
 
     def get_have_list(self):
-        return self.have.tostring()
+        return self.have
+
+    def get_index_by_hash(self, hash):
+        return self.hash_dict[hash]
 
     def do_I_have(self, index):
         return self.have[index]
