diff --git a/core/java/src/net/i2p/client/impl/I2CPMessageProducer.java b/core/java/src/net/i2p/client/impl/I2CPMessageProducer.java
index 545a36eb9afbb060dbd6f9846acb7d1d2d8aea88..4f59355eb16cc381a657c957c8cfbf572370b5be 100644
--- a/core/java/src/net/i2p/client/impl/I2CPMessageProducer.java
+++ b/core/java/src/net/i2p/client/impl/I2CPMessageProducer.java
@@ -17,6 +17,7 @@ import java.util.concurrent.locks.ReentrantLock;
 import net.i2p.I2PAppContext;
 import net.i2p.client.I2PSessionException;
 import net.i2p.client.SendMessageOptions;
+import net.i2p.data.DatabaseEntry;
 import net.i2p.data.DataFormatException;
 import net.i2p.data.Destination;
 import net.i2p.data.LeaseSet;
@@ -28,6 +29,7 @@ import net.i2p.data.SigningPrivateKey;
 import net.i2p.data.i2cp.AbuseReason;
 import net.i2p.data.i2cp.AbuseSeverity;
 import net.i2p.data.i2cp.CreateLeaseSetMessage;
+import net.i2p.data.i2cp.CreateLeaseSet2Message;
 import net.i2p.data.i2cp.CreateSessionMessage;
 import net.i2p.data.i2cp.DestroySessionMessage;
 import net.i2p.data.i2cp.MessageId;
@@ -328,13 +330,20 @@ class I2CPMessageProducer {
     }
 
     /**
-     * Create a new signed leaseSet in response to a request to do so and send it
-     * to the router
-     * 
+     * In response to a RequestLeaseSet Message from the router, send a
+     * CreateLeaseset Message back to the router.
+     * This method is misnamed, it does not create the LeaseSet,
+     * the caller does that.
+     *
      */
     public void createLeaseSet(I2PSessionImpl session, LeaseSet leaseSet, SigningPrivateKey signingPriv,
                                PrivateKey priv) throws I2PSessionException {
-        CreateLeaseSetMessage msg = new CreateLeaseSetMessage();
+        CreateLeaseSetMessage msg;
+        int type = leaseSet.getType();
+        if (type == DatabaseEntry.KEY_TYPE_LEASESET)
+            msg = new CreateLeaseSetMessage();
+        else
+            msg = new CreateLeaseSet2Message();
         msg.setLeaseSet(leaseSet);
         msg.setPrivateKey(priv);
         msg.setSigningPrivateKey(signingPriv);
diff --git a/core/java/src/net/i2p/client/impl/I2PSessionImpl.java b/core/java/src/net/i2p/client/impl/I2PSessionImpl.java
index 83931b30ea34c9dfbe899f32b1c113b7dc34a0f7..d3f03f4cd922f9a9424eb1d70d26a3dee078663c 100644
--- a/core/java/src/net/i2p/client/impl/I2PSessionImpl.java
+++ b/core/java/src/net/i2p/client/impl/I2PSessionImpl.java
@@ -171,6 +171,7 @@ public abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2
     private final boolean _fastReceive;
     private volatile boolean _routerSupportsFastReceive;
     private volatile boolean _routerSupportsHostLookup;
+    private volatile boolean _routerSupportsLS2;
 
     protected static final int CACHE_MAX_SIZE = SystemVersion.isAndroid() ? 32 : 128;
     /**
@@ -197,19 +198,25 @@ public abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2
     private static final long MAX_SEND_WAIT = 10*1000;
 
     private static final String MIN_FAST_VERSION = "0.9.4";
+////// TESTING, change to 38 before release
+    private static final String MIN_LS2_VERSION = "0.9.37";
 
     /** @param routerVersion as rcvd in the SetDateMessage, may be null for very old routers */
     void dateUpdated(String routerVersion) {
-        _routerSupportsFastReceive = _context.isRouterContext() ||
+        boolean isrc = _context.isRouterContext();
+        _routerSupportsFastReceive = isrc ||
                                      (routerVersion != null && routerVersion.length() > 0 &&
                                       VersionComparator.comp(routerVersion, MIN_FAST_VERSION) >= 0);
-        _routerSupportsHostLookup = _context.isRouterContext() ||
+        _routerSupportsHostLookup = isrc ||
                                     TEST_LOOKUP ||
                                      (routerVersion != null && routerVersion.length() > 0 &&
                                       VersionComparator.comp(routerVersion, MIN_HOST_LOOKUP_VERSION) >= 0);
-        _routerSupportsSubsessions = _context.isRouterContext() ||
+        _routerSupportsSubsessions = isrc ||
                                      (routerVersion != null && routerVersion.length() > 0 &&
                                       VersionComparator.comp(routerVersion, MIN_SUBSESSION_VERSION) >= 0);
+        _routerSupportsLS2 = isrc ||
+                                     (routerVersion != null && routerVersion.length() > 0 &&
+                                      VersionComparator.comp(routerVersion, MIN_LS2_VERSION) >= 0);
         synchronized (_stateLock) {
             if (_state == State.OPENING) {
                 changeState(State.GOTDATE);
@@ -281,9 +288,11 @@ public abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2
             _privateKey = null;
             _signingPrivateKey = null;
         }
-        _routerSupportsFastReceive = _context.isRouterContext();
-        _routerSupportsHostLookup = _context.isRouterContext();
-        _routerSupportsSubsessions = _context.isRouterContext();
+        boolean isrc = _context.isRouterContext();
+        _routerSupportsFastReceive = isrc;
+        _routerSupportsHostLookup = isrc;
+        _routerSupportsSubsessions = isrc;
+        _routerSupportsLS2 = isrc;
     }
 
     /**
@@ -509,6 +518,13 @@ public abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2
         return _fastReceive && _routerSupportsFastReceive;
     }
 
+    /**
+     *  @since 0.9.38
+     */
+    public boolean supportsLS2() {
+        return _routerSupportsLS2;
+    }
+
     void setLeaseSet(LeaseSet ls) {
         _leaseSet = ls;
         if (ls != null) {
diff --git a/core/java/src/net/i2p/client/impl/RequestLeaseSetMessageHandler.java b/core/java/src/net/i2p/client/impl/RequestLeaseSetMessageHandler.java
index d88b12e759634ed15055e57532449acc0ac2bb87..82a7fb46dfa6483e09af887fd21a8c6d2069f65d 100644
--- a/core/java/src/net/i2p/client/impl/RequestLeaseSetMessageHandler.java
+++ b/core/java/src/net/i2p/client/impl/RequestLeaseSetMessageHandler.java
@@ -17,14 +17,19 @@ import java.util.concurrent.ConcurrentHashMap;
 
 import net.i2p.I2PAppContext;
 import net.i2p.client.I2PSessionException;
+import net.i2p.crypto.EncType;
 import net.i2p.crypto.KeyGenerator;
+import net.i2p.crypto.KeyPair;
 import net.i2p.crypto.SigType;
+import net.i2p.data.DatabaseEntry;
 import net.i2p.data.DataFormatException;
 import net.i2p.data.DataHelper;
 import net.i2p.data.Destination;
 import net.i2p.data.Hash;
 import net.i2p.data.Lease;
+import net.i2p.data.Lease2;
 import net.i2p.data.LeaseSet;
+import net.i2p.data.LeaseSet2;
 import net.i2p.data.PrivateKey;
 import net.i2p.data.PublicKey;
 import net.i2p.data.SessionKey;
@@ -58,13 +63,38 @@ class RequestLeaseSetMessageHandler extends HandlerImpl {
         _existingLeaseSets = new ConcurrentHashMap<Destination, LeaseInfo>(4);
     }
     
+    /**
+     *  Do we send a LeaseSet or a LeaseSet2?
+     *  @since 0.9.38
+     */
+    protected static boolean requiresLS2(I2PSessionImpl session) {
+        if (!session.supportsLS2())
+            return false;
+        String s = session.getOptions().getProperty("crypto.encType");
+        if (s != null) {
+            EncType type = EncType.parseEncType(s);
+            if (type != null && type != EncType.ELGAMAL_2048 && type.isAvailable())
+                return true;
+        }
+        s = session.getOptions().getProperty("i2cp.leaseSetType");
+        if (s != null) {
+            try {
+                int type = Integer.parseInt(s);
+                if (type != DatabaseEntry.KEY_TYPE_LEASESET)
+                    return true;
+            } catch (NumberFormatException nfe) {}
+        }
+        return false;
+    }
+
     public void handleMessage(I2CPMessage message, I2PSessionImpl session) {
         if (_log.shouldLog(Log.DEBUG))
             _log.debug("Handle message " + message);
         RequestLeaseSetMessage msg = (RequestLeaseSetMessage) message;
-        LeaseSet leaseSet = new LeaseSet();
+        boolean isLS2 = requiresLS2(session);
+        LeaseSet leaseSet = isLS2 ? new LeaseSet2() : new LeaseSet();
         for (int i = 0; i < msg.getEndpoints(); i++) {
-            Lease lease = new Lease();
+            Lease lease = isLS2 ? new Lease2() : new Lease();
             lease.setGateway(msg.getRouter(i));
             lease.setTunnelId(msg.getTunnelId(i));
             lease.setEndDate(msg.getEndDate());
@@ -147,7 +177,26 @@ class RequestLeaseSetMessageHandler extends HandlerImpl {
                     li = new LeaseInfo(privKey, dest);
                 }
             } else {
-                li = new LeaseInfo(dest);
+                EncType type = EncType.ELGAMAL_2048;
+                String senc = session.getOptions().getProperty("crypto.encType");
+                if (senc != null) {
+                    EncType newtype = EncType.parseEncType(senc);
+                    if (newtype != null) {
+                        if (newtype.isAvailable()) {
+                            type = newtype;
+                            if (_log.shouldDebug())
+                                _log.debug("Using crypto type: " + type);
+                        } else {
+                            _log.error("Unsupported crypto.encType: " + newtype);
+                        }
+                    } else {
+                        _log.error("Bad crypto.encType: " + senc);
+                    }
+                } else {
+                    if (_log.shouldDebug())
+                        _log.debug("Using default crypto type");
+                }
+                li = new LeaseInfo(dest, type);
                 if (_log.shouldLog(Log.DEBUG))
                     _log.debug("Creating new leaseInfo keys for " + dest + " without configured private keys");
             }
@@ -187,14 +236,18 @@ class RequestLeaseSetMessageHandler extends HandlerImpl {
             // Workaround for unparsable serialized signing private key for revocation
             // Send him a dummy DSA_SHA1 private key since it's unused anyway
             // See CreateLeaseSetMessage.doReadMessage()
+            // For LS1 only
             SigningPrivateKey spk = li.getSigningPrivateKey();
-            if (!_context.isRouterContext() && spk.getType() != SigType.DSA_SHA1) {
+            if (!_context.isRouterContext() && spk.getType() != SigType.DSA_SHA1 &&
+                !(leaseSet instanceof LeaseSet2)) {
                 byte[] dummy = new byte[SigningPrivateKey.KEYSIZE_BYTES];
                 _context.random().nextBytes(dummy);
                 spk = new SigningPrivateKey(dummy);
             }
             session.getProducer().createLeaseSet(session, leaseSet, spk, li.getPrivateKey());
             session.setLeaseSet(leaseSet);
+            if (_log.shouldDebug())
+                _log.debug("Created and signed LeaseSet: " + leaseSet);
         } catch (DataFormatException dfe) {
             session.propogateError("Error signing the leaseSet", dfe);
         } catch (I2PSessionException ise) {
@@ -220,8 +273,8 @@ class RequestLeaseSetMessageHandler extends HandlerImpl {
         /**
          *  New keys
          */
-        public LeaseInfo(Destination dest) {
-            SimpleDataStructure encKeys[] = KeyGenerator.getInstance().generatePKIKeys();
+        public LeaseInfo(Destination dest, EncType type) {
+            KeyPair encKeys = KeyGenerator.getInstance().generatePKIKeys(type);
             // must be same type as the Destination's signing key
             SimpleDataStructure signKeys[];
             try {
@@ -229,8 +282,8 @@ class RequestLeaseSetMessageHandler extends HandlerImpl {
             } catch (GeneralSecurityException gse) {
                 throw new IllegalStateException(gse);
             }
-            _pubKey = (PublicKey) encKeys[0];
-            _privKey = (PrivateKey) encKeys[1];
+            _pubKey = encKeys.getPublic();
+            _privKey = encKeys.getPrivate();
             _signingPubKey = (SigningPublicKey) signKeys[0];
             _signingPrivKey = (SigningPrivateKey) signKeys[1];
         }
diff --git a/core/java/src/net/i2p/client/impl/RequestVariableLeaseSetMessageHandler.java b/core/java/src/net/i2p/client/impl/RequestVariableLeaseSetMessageHandler.java
index 41cc9eb9319714061dc3a469faf5140d170bd48e..4fd7c12a59fbae1c3adb0c4dfcb8455cf21094c8 100644
--- a/core/java/src/net/i2p/client/impl/RequestVariableLeaseSetMessageHandler.java
+++ b/core/java/src/net/i2p/client/impl/RequestVariableLeaseSetMessageHandler.java
@@ -10,7 +10,10 @@ package net.i2p.client.impl;
  */
 
 import net.i2p.I2PAppContext;
+import net.i2p.data.Lease;
+import net.i2p.data.Lease2;
 import net.i2p.data.LeaseSet;
+import net.i2p.data.LeaseSet2;
 import net.i2p.data.i2cp.I2CPMessage;
 import net.i2p.data.i2cp.RequestVariableLeaseSetMessage;
 import net.i2p.util.Log;
@@ -32,9 +35,21 @@ class RequestVariableLeaseSetMessageHandler extends RequestLeaseSetMessageHandle
         if (_log.shouldLog(Log.DEBUG))
             _log.debug("Handle message " + message);
         RequestVariableLeaseSetMessage msg = (RequestVariableLeaseSetMessage) message;
-        LeaseSet leaseSet = new LeaseSet();
+        boolean isLS2 = requiresLS2(session);
+        LeaseSet leaseSet = isLS2 ? new LeaseSet2() : new LeaseSet();
         for (int i = 0; i < msg.getEndpoints(); i++) {
-            leaseSet.addLease(msg.getEndpoint(i));
+            Lease lease;
+            if (isLS2) {
+                // convert Lease to Lease2
+                Lease old = msg.getEndpoint(i);
+                lease = new Lease2();
+                lease.setGateway(old.getGateway());
+                lease.setTunnelId(old.getTunnelId());
+                lease.setEndDate(old.getEndDate());
+            } else {
+                lease = msg.getEndpoint(i);
+            }
+            leaseSet.addLease(lease);
         }
         signLeaseSet(leaseSet, session);
     }
diff --git a/core/java/src/net/i2p/data/i2cp/CreateLeaseSet2Message.java b/core/java/src/net/i2p/data/i2cp/CreateLeaseSet2Message.java
new file mode 100644
index 0000000000000000000000000000000000000000..fe54df66b345aac9670c352880dcd14cd5a39778
--- /dev/null
+++ b/core/java/src/net/i2p/data/i2cp/CreateLeaseSet2Message.java
@@ -0,0 +1,109 @@
+package net.i2p.data.i2cp;
+
+import java.io.ByteArrayOutputStream;
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.InputStream;
+
+import net.i2p.crypto.EncType;
+import net.i2p.crypto.SigType;
+import net.i2p.data.DatabaseEntry;
+import net.i2p.data.DataFormatException;
+import net.i2p.data.LeaseSet;
+import net.i2p.data.LeaseSet2;
+import net.i2p.data.PrivateKey;
+import net.i2p.data.SigningPrivateKey;
+
+/**
+ * Like CreateLeaseSetMessage, but supports both old
+ * and new LeaseSet types, including LS2, Meta, and Encrypted.
+ *
+ * For LS2:
+ * Same as CreateLeaseSetMessage, but has a netdb type before
+ * the LeaseSet. SigningPrivateKey and PrivateKey are
+ * serialized after the LeaseSet, not before, so we can
+ * infer the types from the LeaseSet.
+ *
+ * @since 0.9.38
+ */
+public class CreateLeaseSet2Message extends CreateLeaseSetMessage {
+    public final static int MESSAGE_TYPE = 40;
+
+    public CreateLeaseSet2Message() {
+        super();
+    }
+
+    @Override
+    protected void doReadMessage(InputStream in, int size) throws I2CPMessageException, IOException {
+        try {
+            _sessionId = new SessionId();
+            _sessionId.readBytes(in);
+            int type = in.read();
+            if (type == DatabaseEntry.KEY_TYPE_LEASESET) {
+                _leaseSet = new LeaseSet();
+            } else if (type == DatabaseEntry.KEY_TYPE_LS2) {
+                _leaseSet = new LeaseSet2();
+            } else if (type == -1) {
+                throw new EOFException("EOF reading LS type");
+            } else {
+                throw new I2CPMessageException("Unsupported Leaseset type: " + type);
+            }
+            _leaseSet.readBytes(in);
+            // In CLSM this is the type of the dest, but revocation is unimplemented.
+            // In CLS2M this is the type of the signature (which may be different than the
+            // type of the dest if it's an offline signature)
+            // and is needed by the session tag manager.
+            SigType stype = _leaseSet.getSignature().getType();
+            if (stype == null)
+                throw new I2CPMessageException("Unsupported sig type");
+            _signingPrivateKey = new SigningPrivateKey(stype);
+            _signingPrivateKey.readBytes(in);
+            EncType etype = _leaseSet.getEncryptionKey().getType();
+            if (etype == null)
+                throw new I2CPMessageException("Unsupported encryption type");
+            _privateKey = new PrivateKey(etype);
+            _privateKey.readBytes(in);
+        } catch (DataFormatException dfe) {
+            throw new I2CPMessageException("Error reading the CreateLeaseSetMessage", dfe);
+        }
+    }
+
+    @Override
+    protected byte[] doWriteMessage() throws I2CPMessageException, IOException {
+        if (_sessionId == null || _signingPrivateKey == null || _privateKey == null || _leaseSet == null)
+            throw new I2CPMessageException("Unable to write out the message as there is not enough data");
+        int size = 4 // sessionId
+                 + 1 // type
+                 + _leaseSet.size()
+                 + _signingPrivateKey.length()
+                 + _privateKey.length();
+        ByteArrayOutputStream os = new ByteArrayOutputStream(size);
+        try {
+            _sessionId.writeBytes(os);
+            os.write(_leaseSet.getType());
+            _leaseSet.writeBytes(os);
+            _signingPrivateKey.writeBytes(os);
+            _privateKey.writeBytes(os);
+        } catch (DataFormatException dfe) {
+            throw new I2CPMessageException("Error writing out the message data", dfe);
+        }
+        return os.toByteArray();
+    }
+
+    @Override
+    public int getType() {
+        return MESSAGE_TYPE;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder buf = new StringBuilder();
+        buf.append("[CreateLeaseSet2Message: ");
+        buf.append("\n\tLeaseSet: ").append(getLeaseSet());
+        buf.append("\n\tSigningPrivateKey: ").append(getSigningPrivateKey());
+        buf.append("\n\tPrivateKey: ").append(getPrivateKey());
+        buf.append("\n\tSessionId: ").append(getSessionId());
+        buf.append("]");
+        return buf.toString();
+    }
+}
diff --git a/core/java/src/net/i2p/data/i2cp/CreateLeaseSetMessage.java b/core/java/src/net/i2p/data/i2cp/CreateLeaseSetMessage.java
index d7c55e53bd609aa9919696ea61b457fa6378c655..277e9cd91db911759aee3b1a5ad7f4480f8e273f 100644
--- a/core/java/src/net/i2p/data/i2cp/CreateLeaseSetMessage.java
+++ b/core/java/src/net/i2p/data/i2cp/CreateLeaseSetMessage.java
@@ -26,10 +26,10 @@ import net.i2p.data.SigningPrivateKey;
  */
 public class CreateLeaseSetMessage extends I2CPMessageImpl {
     public final static int MESSAGE_TYPE = 4;
-    private SessionId _sessionId;
-    private LeaseSet _leaseSet;
-    private SigningPrivateKey _signingPrivateKey;
-    private PrivateKey _privateKey;
+    protected SessionId _sessionId;
+    protected LeaseSet _leaseSet;
+    protected SigningPrivateKey _signingPrivateKey;
+    protected PrivateKey _privateKey;
 
     public CreateLeaseSetMessage() {
     }
diff --git a/core/java/src/net/i2p/data/i2cp/I2CPMessageHandler.java b/core/java/src/net/i2p/data/i2cp/I2CPMessageHandler.java
index 915757fc09e8d4860185091139e96898c1ea907d..5c5d76885276735f83fdd6701d8475dc5a294e67 100644
--- a/core/java/src/net/i2p/data/i2cp/I2CPMessageHandler.java
+++ b/core/java/src/net/i2p/data/i2cp/I2CPMessageHandler.java
@@ -112,6 +112,8 @@ public class I2CPMessageHandler {
             return new HostLookupMessage();
         case HostReplyMessage.MESSAGE_TYPE:
             return new HostReplyMessage();
+        case CreateLeaseSet2Message.MESSAGE_TYPE:
+            return new CreateLeaseSet2Message();
         default:
             throw new I2CPMessageException("The type " + type + " is an unknown I2CP message");
         }
diff --git a/router/java/src/net/i2p/router/client/ClientMessageEventListener.java b/router/java/src/net/i2p/router/client/ClientMessageEventListener.java
index b6893b59c0922a9ca95b8dc32e5a701a99031615..76836e315843aaf22d1676ca6da7acff9edf7107 100644
--- a/router/java/src/net/i2p/router/client/ClientMessageEventListener.java
+++ b/router/java/src/net/i2p/router/client/ClientMessageEventListener.java
@@ -20,6 +20,7 @@ import net.i2p.data.Payload;
 import net.i2p.data.PublicKey;
 import net.i2p.data.i2cp.BandwidthLimitsMessage;
 import net.i2p.data.i2cp.CreateLeaseSetMessage;
+import net.i2p.data.i2cp.CreateLeaseSet2Message;
 import net.i2p.data.i2cp.CreateSessionMessage;
 import net.i2p.data.i2cp.DestLookupMessage;
 import net.i2p.data.i2cp.DestroySessionMessage;
@@ -126,6 +127,7 @@ class ClientMessageEventListener implements I2CPMessageReader.I2CPMessageEventLi
                 handleReceiveEnd((ReceiveMessageEndMessage)message);
                 break;
             case CreateLeaseSetMessage.MESSAGE_TYPE:
+            case CreateLeaseSet2Message.MESSAGE_TYPE:
                 handleCreateLeaseSet((CreateLeaseSetMessage)message);
                 break;
             case DestroySessionMessage.MESSAGE_TYPE: