diff --git a/apps/i2psnark/java/src/org/klomp/snark/I2PSnarkUtil.java b/apps/i2psnark/java/src/org/klomp/snark/I2PSnarkUtil.java
index b0e26bfe6acb2bb6a361e60b45224d9987031518..bbdc09ab4267bf285d6972df69d2e35c4e1b167d 100644
--- a/apps/i2psnark/java/src/org/klomp/snark/I2PSnarkUtil.java
+++ b/apps/i2psnark/java/src/org/klomp/snark/I2PSnarkUtil.java
@@ -197,11 +197,14 @@ public class I2PSnarkUtil {
     
     /** connect to the given destination */
     I2PSocket connect(PeerID peer) throws IOException {
-        Hash dest = peer.getAddress().calculateHash();
+        Destination addr = peer.getAddress();
+        if (addr == null)
+            throw new IOException("Null address");
+        Hash dest = addr.calculateHash();
         if (_shitlist.contains(dest))
             throw new IOException("Not trying to contact " + dest.toBase64() + ", as they are shitlisted");
         try {
-            I2PSocket rv = _manager.connect(peer.getAddress());
+            I2PSocket rv = _manager.connect(addr);
             if (rv != null)
                 _shitlist.remove(dest);
             return rv;
diff --git a/apps/i2psnark/java/src/org/klomp/snark/Peer.java b/apps/i2psnark/java/src/org/klomp/snark/Peer.java
index e0ae6e1f25449a2b47adf884629ace0d10f5aa66..4e965a072144eabc66befeb178d6c19284ec9c45 100644
--- a/apps/i2psnark/java/src/org/klomp/snark/Peer.java
+++ b/apps/i2psnark/java/src/org/klomp/snark/Peer.java
@@ -129,7 +129,7 @@ public class Peer implements Comparable
     @Override
   public int hashCode()
   {
-    return peerID.hashCode() ^ (2 << _id);
+    return peerID.hashCode() ^ (7777 * (int)_id);
   }
 
   /**
@@ -150,6 +150,7 @@ public class Peer implements Comparable
 
   /**
    * Compares the PeerIDs.
+   * @deprecated unused?
    */
   public int compareTo(Object o)
   {
@@ -207,12 +208,15 @@ public class Peer implements Comparable
             //  = new BufferedOutputStream(sock.getOutputStream());
             byte [] id = handshake(in, out); //handshake(bis, bos);
             byte [] expected_id = peerID.getID();
-            if (!Arrays.equals(expected_id, id))
-              throw new IOException("Unexpected peerID '"
+            if (expected_id == null)
+                peerID.setID(id);
+            else if (Arrays.equals(expected_id, id))
+                _log.debug("Handshake got matching IDs with " + toString());
+            else
+                throw new IOException("Unexpected peerID '"
                                     + PeerID.idencode(id)
                                     + "' expected '"
                                     + PeerID.idencode(expected_id) + "'");
-            _log.debug("Handshake got matching IDs with " + toString());
           } else {
             _log.debug("Already have din [" + sock + "] with " + toString());
           }
diff --git a/apps/i2psnark/java/src/org/klomp/snark/PeerCoordinator.java b/apps/i2psnark/java/src/org/klomp/snark/PeerCoordinator.java
index 2a2e40e7d5f46b14d3b12418dc77c859edb842fb..5e40f662edd9f979daebbac7fafb5f928da7582f 100644
--- a/apps/i2psnark/java/src/org/klomp/snark/PeerCoordinator.java
+++ b/apps/i2psnark/java/src/org/klomp/snark/PeerCoordinator.java
@@ -373,7 +373,8 @@ public class PeerCoordinator implements PeerListener
 
     if (need_more)
       {
-        _log.debug("Adding a peer " + peer.getPeerID().getAddress().calculateHash().toBase64() + " for " + metainfo.getName(), new Exception("add/run"));
+        if (_log.shouldLog(Log.DEBUG))
+            _log.debug("Adding a peer " + peer.getPeerID().toString() + " for " + metainfo.getName(), new Exception("add/run"));
 
         // Run the peer with us as listener and the current bitfield.
         final PeerListener listener = this;
diff --git a/apps/i2psnark/java/src/org/klomp/snark/PeerID.java b/apps/i2psnark/java/src/org/klomp/snark/PeerID.java
index c4a4e9194164dc91b70bdc22c0eada56e9e1f886..37cf1a9b65ccbadad69299d2a783373ce665fc48 100644
--- a/apps/i2psnark/java/src/org/klomp/snark/PeerID.java
+++ b/apps/i2psnark/java/src/org/klomp/snark/PeerID.java
@@ -24,19 +24,33 @@ import java.io.IOException;
 import java.net.UnknownHostException;
 import java.util.Map;
 
+import net.i2p.I2PAppContext;
+import net.i2p.data.Base32;
 import net.i2p.data.Base64;
+import net.i2p.data.DataHelper;
 import net.i2p.data.Destination;
 
 import org.klomp.snark.bencode.BDecoder;
 import org.klomp.snark.bencode.BEValue;
 import org.klomp.snark.bencode.InvalidBEncodingException;
 
+/**
+ *  Store the address information about a peer.
+ *  Prior to 0.8.1, an instantiation required a peer ID, and full Destination address.
+ *  Starting with 0.8.1, to support compact tracker responses,
+ *  a PeerID can be instantiated with a Destination Hash alone.
+ *  The full destination lookup is deferred until getAddress() is called,
+ *  and the PeerID is not required.
+ *  Equality is now determined solely by the dest hash.
+ */
 public class PeerID implements Comparable
 {
-  private final byte[] id;
-  private final Destination address;
+  private byte[] id;
+  private Destination address;
   private final int port;
-
+  private byte[] destHash;
+  /** whether we have tried to get the dest from the hash - only do once */
+  private boolean triedDestLookup;
   private final int hash;
 
   public PeerID(byte[] id, Destination address)
@@ -44,7 +58,7 @@ public class PeerID implements Comparable
     this.id = id;
     this.address = address;
     this.port = 6881;
-
+    this.destHash = address.calculateHash().getData();
     hash = calculateHash();
   }
 
@@ -77,17 +91,49 @@ public class PeerID implements Comparable
         throw new InvalidBEncodingException("Invalid destination [" + bevalue.getString() + "]");
 
     port = 6881;
-
+    this.destHash = address.calculateHash().getData();
     hash = calculateHash();
   }
 
+  /**
+   * Creates a PeerID from a destHash
+   * @since 0.8.1
+   */
+  public PeerID(byte[] dest_hash) throws InvalidBEncodingException
+  {
+    // id and address remain null
+    port = 6881;
+    if (dest_hash.length != 32)
+        throw new InvalidBEncodingException("bad hash length");
+    destHash = dest_hash;
+    hash = DataHelper.hashCode(dest_hash);
+  }
+
   public byte[] getID()
   {
     return id;
   }
 
-  public Destination getAddress()
+  /** for connecting out to peer based on desthash @since 0.8.1 */
+  public void setID(byte[] xid)
   {
+    id = xid;
+  }
+
+  /**
+   *  Get the destination.
+   *  If this PeerId was instantiated with a destHash,
+   *  and we have not yet done so, lookup the full destination, which may take
+   *  up to 10 seconds.
+   *  @return Dest or null if unknown
+   */
+  public synchronized Destination getAddress()
+  {
+    if (address == null && destHash != null && !triedDestLookup) {
+        String b32 = Base32.encode(destHash) + ".b32.i2p";
+        address = I2PAppContext.getGlobalContext().namingService().lookup(b32);
+        triedDestLookup = true;
+    }
     return address;
   }
 
@@ -96,16 +142,19 @@ public class PeerID implements Comparable
     return port;
   }
 
+  /** @since 0.8.1 */
+  public byte[] getDestHash()
+  {
+    return destHash;
+  }
+
   private int calculateHash()
   {
-    int b = 0;
-    for (int i = 0; i < id.length; i++)
-      b ^= id[i];
-    return (b ^ address.hashCode()) ^ port;
+    return DataHelper.hashCode(destHash);
   }
 
   /**
-   * The hash code of a PeerID is the exclusive or of all id bytes.
+   * The hash code of a PeerID is the hashcode of the desthash
    */
     @Override
   public int hashCode()
@@ -115,18 +164,15 @@ public class PeerID implements Comparable
 
   /**
    * Returns true if and only if this peerID and the given peerID have
-   * the same 20 bytes as ID.
+   * the same destination hash
    */
   public boolean sameID(PeerID pid)
   {
-    boolean equal = true;
-    for (int i = 0; equal && i < id.length; i++)
-      equal = id[i] == pid.id[i];
-    return equal;
+    return DataHelper.eq(destHash, pid.getDestHash());
   }
 
   /**
-   * Two PeerIDs are equal when they have the same id, address and port.
+   * Two PeerIDs are equal when they have the same dest hash
    */
     @Override
   public boolean equals(Object o)
@@ -135,9 +181,7 @@ public class PeerID implements Comparable
       {
         PeerID pid = (PeerID)o;
 
-        return port == pid.port
-          && address.equals(pid.address)
-          && sameID(pid);
+        return sameID(pid);
       }
     else
       return false;
@@ -145,6 +189,7 @@ public class PeerID implements Comparable
 
   /**
    * Compares port, address and id.
+   * @deprecated unused? and will NPE now that address can be null?
    */
   public int compareTo(Object o)
   {
@@ -176,6 +221,8 @@ public class PeerID implements Comparable
     @Override
   public String toString()
   {
+    if (id == null || address == null)
+        return "unkn@" + Base64.encode(destHash).substring(0, 6);
     int nonZero = 0;
     for (int i = 0; i < id.length; i++) {
         if (id[i] != 0) {
diff --git a/apps/i2psnark/java/src/org/klomp/snark/TrackerClient.java b/apps/i2psnark/java/src/org/klomp/snark/TrackerClient.java
index 065e7088014a7a1b19c489e75ab31fe7ee230528..05df351c69b7c28e93deff8e783e1aa0a537cf21 100644
--- a/apps/i2psnark/java/src/org/klomp/snark/TrackerClient.java
+++ b/apps/i2psnark/java/src/org/klomp/snark/TrackerClient.java
@@ -357,6 +357,7 @@ public class TrackerClient extends I2PAppThread
       + "&uploaded=" + uploaded
       + "&downloaded=" + downloaded
       + "&left=" + left
+      + "&compact"
       + ((! event.equals(NO_EVENT)) ? ("&event=" + event) : "");
     if (left <= 0 || event.equals(STOPPED_EVENT) || !coordinator.needPeers())
         s += "&numwant=0";
diff --git a/apps/i2psnark/java/src/org/klomp/snark/TrackerInfo.java b/apps/i2psnark/java/src/org/klomp/snark/TrackerInfo.java
index 84198f12f0f0eacca955c53cf14a018c91202f1e..ee2c2da25f46e9c5f95e34fec8a9cd8d7dbc44b2 100644
--- a/apps/i2psnark/java/src/org/klomp/snark/TrackerInfo.java
+++ b/apps/i2psnark/java/src/org/klomp/snark/TrackerInfo.java
@@ -33,11 +33,17 @@ import org.klomp.snark.bencode.BDecoder;
 import org.klomp.snark.bencode.BEValue;
 import org.klomp.snark.bencode.InvalidBEncodingException;
 
+/**
+ *  The data structure for the tracker response.
+ *  Handles both traditional and compact formats.
+ *  Compact format 1 - a list of hashes - implemented
+ *  Compact format 2 - One big string of concatenated hashes - unimplemented
+ */
 public class TrackerInfo
 {
   private final String failure_reason;
   private final int interval;
-  private final Set peers;
+  private final Set<Peer> peers;
   private int complete;
   private int incomplete;
 
@@ -76,6 +82,7 @@ public class TrackerInfo
         if (bePeers == null)
           peers = Collections.EMPTY_SET;
         else
+          // if compact is going to be one big string instead, try/catch here
           peers = getPeers(bePeers.getList(), my_id, metainfo);
 
         BEValue bev = (BEValue)m.get("complete");
@@ -94,33 +101,39 @@ public class TrackerInfo
       }
   }
 
-  public static Set getPeers(InputStream in, byte[] my_id, MetaInfo metainfo)
+/******
+  public static Set<Peer> getPeers(InputStream in, byte[] my_id, MetaInfo metainfo)
     throws IOException
   {
     return getPeers(new BDecoder(in), my_id, metainfo);
   }
 
-  public static Set getPeers(BDecoder be, byte[] my_id, MetaInfo metainfo)
+  public static Set<Peer> getPeers(BDecoder be, byte[] my_id, MetaInfo metainfo)
     throws IOException
   {
     return getPeers(be.bdecodeList().getList(), my_id, metainfo);
   }
+******/
 
-  public static Set getPeers(List l, byte[] my_id, MetaInfo metainfo)
+  private static Set<Peer> getPeers(List<BEValue> l, byte[] my_id, MetaInfo metainfo)
     throws IOException
   {
-    Set peers = new HashSet(l.size());
+    Set<Peer> peers = new HashSet(l.size());
 
-    Iterator it = l.iterator();
-    while (it.hasNext())
-      {
+    for (BEValue bev : l) {
         PeerID peerID;
         try {
-            peerID = new PeerID(((BEValue)it.next()).getMap());
+            // Case 1 - non-compact - A list of dictionaries (maps)
+            peerID = new PeerID(bev.getMap());
         } catch (InvalidBEncodingException ibe) {
-            // don't let one bad entry spoil the whole list
-            //Snark.debug("Discarding peer from list: " + ibe, Snark.ERROR);
-            continue;
+            try {
+                // Case 2 - compact - A list of 32-byte binary strings (hashes)
+                peerID = new PeerID(bev.getBytes());
+            } catch (InvalidBEncodingException ibe2) {
+                // don't let one bad entry spoil the whole list
+                //Snark.debug("Discarding peer from list: " + ibe, Snark.ERROR);
+                continue;
+            }
         }
         peers.add(new Peer(peerID, my_id, metainfo));
       }
@@ -128,7 +141,7 @@ public class TrackerInfo
     return peers;
   }
 
-  public Set getPeers()
+  public Set<Peer> getPeers()
   {
     return peers;
   }
diff --git a/apps/i2psnark/java/src/org/klomp/snark/bencode/BEValue.java b/apps/i2psnark/java/src/org/klomp/snark/bencode/BEValue.java
index cba6b973fd2d70f846e32dd690228cdcb6996e03..c1733cb13b2000ddcc62e0fa29cb82a5fcd6bd60 100644
--- a/apps/i2psnark/java/src/org/klomp/snark/bencode/BEValue.java
+++ b/apps/i2psnark/java/src/org/klomp/snark/bencode/BEValue.java
@@ -140,7 +140,7 @@ public class BEValue
    * succeeds when the BEValue is actually a List, otherwise it will
    * throw a InvalidBEncodingException.
    */
-  public List getList() throws InvalidBEncodingException
+  public List<BEValue> getList() throws InvalidBEncodingException
   {
     try
       {
@@ -157,7 +157,7 @@ public class BEValue
    * values. This operation only succeeds when the BEValue is actually
    * a Map, otherwise it will throw a InvalidBEncodingException.
    */
-  public Map getMap() throws InvalidBEncodingException
+  public Map<BEValue, BEValue> getMap() throws InvalidBEncodingException
   {
     try
       {