diff --git a/core/java/src/net/i2p/client/impl/RequestLeaseSetMessageHandler.java b/core/java/src/net/i2p/client/impl/RequestLeaseSetMessageHandler.java
index bebc2cf2773bbb00ccf07e24b682cd32a821816d..008837a1b2a1a8d6dcec2301f543be7758fac7e6 100644
--- a/core/java/src/net/i2p/client/impl/RequestLeaseSetMessageHandler.java
+++ b/core/java/src/net/i2p/client/impl/RequestLeaseSetMessageHandler.java
@@ -268,8 +268,9 @@ class RequestLeaseSetMessageHandler extends HandlerImpl {
             }
         } else {
             leaseSet.setEncryptionKey(li.getPublicKey());
+            // revocation key
+            leaseSet.setSigningKey(li.getSigningPublicKey());
         }
-        leaseSet.setSigningKey(li.getSigningPublicKey());
         // SubSession options aren't updated via the gui, so use the primary options
         Properties opts;
         if (session instanceof SubSession)
diff --git a/core/java/src/net/i2p/data/EncryptedLeaseSet.java b/core/java/src/net/i2p/data/EncryptedLeaseSet.java
index b96ba2ccc4dc053e0c7906fb07cd482f01e01551..eb88cd86d80dba1a3f941f6715d9a85ff0f2d5b5 100644
--- a/core/java/src/net/i2p/data/EncryptedLeaseSet.java
+++ b/core/java/src/net/i2p/data/EncryptedLeaseSet.java
@@ -5,6 +5,7 @@ import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.util.List;
 
 import net.i2p.I2PAppContext;
 import net.i2p.crypto.Blinding;
@@ -68,7 +69,6 @@ public class EncryptedLeaseSet extends LeaseSet2 {
      */
     @Override
     public int getLeaseCount() {
-        // TODO attempt decryption
         return _decryptedLS2 != null ? _decryptedLS2.getLeaseCount() : 0;
     }
 
@@ -77,10 +77,19 @@ public class EncryptedLeaseSet extends LeaseSet2 {
      */
     @Override
     public Lease getLease(int index) {
-        // TODO attempt decryption
         return _decryptedLS2 != null ? _decryptedLS2.getLease(index) : null;
     }
 
+    /**
+     *  @return null if not decrypted.
+     *  @since 0.9.39
+     */
+    public List<PublicKey> getEncryptionKeys() {
+        if (_decryptedLS2 != null)
+            return _decryptedLS2.getEncryptionKeys();
+        return super.getEncryptionKeys();
+    }
+
     /**
      * Overridden to set the blinded key
      *
@@ -105,7 +114,9 @@ public class EncryptedLeaseSet extends LeaseSet2 {
         if (_signingKey == null)
             _signingKey = bpk;
         else if (!_signingKey.equals(bpk))
-            throw new IllegalArgumentException("blinded pubkey mismatch");
+            throw new IllegalArgumentException("blinded pubkey mismatch:" +
+                                               "\nas received:   " + _signingKey +
+                                               "\nas calculated: " + bpk);
     }
 
     /**
@@ -121,7 +132,13 @@ public class EncryptedLeaseSet extends LeaseSet2 {
             _alpha = Blinding.generateAlpha(ctx, _destination, null);
         else
             _alpha = Blinding.generateAlpha(ctx, _destination, null, _published);
-        return Blinding.blind(spk, _alpha);
+        SigningPublicKey rv = Blinding.blind(spk, _alpha);
+        if (_log.shouldDebug())
+            _log.debug("Blind:" +
+                       "\norig:    " + spk +
+                       "\nalpha:   " + _alpha +
+                       "\nblinded: " + rv);
+        return rv;
     }
 
     /**
@@ -348,6 +365,10 @@ public class EncryptedLeaseSet extends LeaseSet2 {
         plaintext = ciphertext;
         ciphertext = new byte[SALT_LEN + plaintext.length];
         System.arraycopy(salt, 0, ciphertext, 0, SALT_LEN);
+        if (_log.shouldDebug()) {
+            _log.debug("Encrypt: chacha20 key:\n" + net.i2p.util.HexDump.dump(key));
+            _log.debug("Encrypt: chacha20 IV:\n" + net.i2p.util.HexDump.dump(iv));
+        }
         ChaCha20.encrypt(key, iv, plaintext, 0, ciphertext, SALT_LEN, plaintext.length);
         if (_log.shouldDebug())
             _log.debug("Encrypt: outer ciphertext:\n" + net.i2p.util.HexDump.dump(ciphertext));
@@ -377,6 +398,10 @@ public class EncryptedLeaseSet extends LeaseSet2 {
         byte[] plaintext = new byte[ciphertext.length - SALT_LEN];
         // first 32 bytes of ciphertext are the salt
         hkdf.calculate(ciphertext, input, ELS2L1K, key, iv, 0);
+        if (_log.shouldDebug()) {
+            _log.debug("Decrypt: chacha20 key:\n" + net.i2p.util.HexDump.dump(key));
+            _log.debug("Decrypt: chacha20 IV:\n" + net.i2p.util.HexDump.dump(iv));
+        }
         ChaCha20.decrypt(key, iv, ciphertext, SALT_LEN, plaintext, 0, plaintext.length);
         if (_log.shouldDebug()) {
             _log.debug("Decrypt: outer ciphertext:\n" + net.i2p.util.HexDump.dump(ciphertext));
@@ -477,8 +502,8 @@ public class EncryptedLeaseSet extends LeaseSet2 {
         _flags = saveFlags;
         if (_log.shouldDebug()) {
             _log.debug("Sign inner with key: " + key.getType() + ' ' + key.toBase64());
-            _log.debug("Corresponding pubkey: " + key.toPublic().toBase64());
-            _log.debug("Sign inner: " + _signature.getType() + ' ' + _signature.toBase64());
+            _log.debug("Corresponding pubkey: " + key.toPublic());
+            _log.debug("Inner sig: " + _signature.getType() + ' ' + _signature.toBase64());
         }
         encrypt(null);
         SigningPrivateKey bkey = Blinding.blind(key, _alpha);
@@ -498,8 +523,8 @@ public class EncryptedLeaseSet extends LeaseSet2 {
             throw new DataFormatException("Signature failed with " + key.getType() + " key");
         if (_log.shouldDebug()) {
             _log.debug("Sign outer with key: " + bkey.getType() + ' ' + bkey.toBase64());
-            _log.debug("Corresponding pubkey: " + bkey.toPublic().toBase64());
-            _log.debug("Sign outer: " + _signature.getType() + ' ' + _signature.toBase64());
+            _log.debug("Corresponding pubkey: " + bkey.toPublic());
+            _log.debug("Outer sig: " + _signature.getType() + ' ' + _signature.toBase64());
         }
     }
 
@@ -514,7 +539,7 @@ public class EncryptedLeaseSet extends LeaseSet2 {
     public boolean verifySignature() {
         if (_log.shouldDebug()) {
             _log.debug("Sig verify outer with key: " + _signingKey.getType() + ' ' + _signingKey.toBase64());
-            _log.debug("Sig verify outer: " + _signature.getType() + ' ' + _signature.toBase64());
+            _log.debug("Outer sig: " + _signature.getType() + ' ' + _signature.toBase64());
         }
         if (!super.verifySignature()) {
             _log.warn("ELS2 outer sig verify fail");
@@ -537,7 +562,7 @@ public class EncryptedLeaseSet extends LeaseSet2 {
         if (_log.shouldDebug()) {
             _log.debug("Decrypted inner LS2:\n" + _decryptedLS2);
             _log.debug("Sig verify inner with key: " + _decryptedLS2.getDestination().getSigningPublicKey().getType() + ' ' + _decryptedLS2.getDestination().getSigningPublicKey().toBase64());
-            _log.debug("Sig verify inner: " + _decryptedLS2.getSignature().getType() + ' ' + _decryptedLS2.getSignature().toBase64());
+            _log.debug("Inner sig: " + _decryptedLS2.getSignature().getType() + ' ' + _decryptedLS2.getSignature().toBase64());
         }
         boolean rv = _decryptedLS2.verifySignature();
         if (!rv)
diff --git a/core/java/src/net/i2p/data/LeaseSet2.java b/core/java/src/net/i2p/data/LeaseSet2.java
index 208c76496b745f6e1e5fb7dd4abd4959b59e5ed3..a91253b173b4167640096f3b2fe5062920f6b2b2 100644
--- a/core/java/src/net/i2p/data/LeaseSet2.java
+++ b/core/java/src/net/i2p/data/LeaseSet2.java
@@ -16,6 +16,7 @@ import net.i2p.crypto.EncType;
 import net.i2p.crypto.SigAlgo;
 import net.i2p.crypto.SigType;
 import net.i2p.util.Clock;
+import net.i2p.util.Log;
 import net.i2p.util.OrderedProperties;
 
 /**
@@ -256,6 +257,19 @@ public class LeaseSet2 extends LeaseSet {
         return KEY_TYPE_LS2;
     }
 
+    /**
+     *  The revocation key. Overridden to do nothing,
+     *  as we're using the _signingKey field for the blinded key in Enc LS2.
+     *
+     * @since 0.9.39
+     */
+    @Override
+    public void setSigningKey(SigningPublicKey key) {
+        Log log = I2PAppContext.getGlobalContext().logManager().getLog(LeaseSet2.class);
+        if (log.shouldWarn())
+            log.warn("Don't set revocation key in ls2", new Exception("I did it"));
+    }
+
     /** without sig! */
     @Override
     protected byte[] getBytes() {
diff --git a/core/java/src/net/i2p/data/i2cp/CreateLeaseSet2Message.java b/core/java/src/net/i2p/data/i2cp/CreateLeaseSet2Message.java
index df5a8584c6f57007a1f965f7de28495da5650391..194701656b39b89d410201fe6d1d0acad6e675a1 100644
--- a/core/java/src/net/i2p/data/i2cp/CreateLeaseSet2Message.java
+++ b/core/java/src/net/i2p/data/i2cp/CreateLeaseSet2Message.java
@@ -115,18 +115,32 @@ public class CreateLeaseSet2Message extends CreateLeaseSetMessage {
                     type == DatabaseEntry.KEY_TYPE_ENCRYPTED_LS2) {
                     LeaseSet2 ls2 = (LeaseSet2) _leaseSet;
                     // get one PrivateKey for each PublicKey
-                    // TODO decrypt an encrypted LS so we can get the keys
                     List<PublicKey> pks = ls2.getEncryptionKeys();
-                    if (pks == null)
-                        throw new I2CPMessageException("TODO decrypt");
-                    for (PublicKey pk : pks) {
-                        EncType etype = pk.getType();
-                        if (etype == null)
-                            throw new I2CPMessageException("Unsupported encryption type");
+                    int numkeys = in.read();
+                    // pks is null for encrypted LS2
+                    if (pks != null && numkeys != pks.size())
+                        throw new I2CPMessageException("Wrong number of privkeys");
+                    for (int i = 0; i < numkeys; i++) {
                         int encType = (int) DataHelper.readLong(in, 2);
                         int encLen = (int) DataHelper.readLong(in, 2);
-                        if (encType != etype.getCode() || encLen != etype.getPrivkeyLen())
-                            throw new I2CPMessageException("Enc type mismatch");
+                        EncType etype;
+                        if (pks != null) {
+                            // standard LS2
+                            etype = pks.get(i).getType();
+                            if (etype == null)
+                                throw new I2CPMessageException("Unsupported encryption type: " + encType);
+                            if (encType != etype.getCode())
+                                throw new I2CPMessageException("Enc type mismatch");
+                            if (encLen != etype.getPrivkeyLen())
+                                throw new I2CPMessageException("Enc type bad length");
+                        } else {
+                            // encrypted LS2
+                            etype = EncType.getByCode(encType);
+                            if (etype == null)
+                                throw new I2CPMessageException("Unsupported encryption type: " + encType);
+                            if (encLen != etype.getPrivkeyLen())
+                                throw new I2CPMessageException("Enc type bad length");
+                        }
                         PrivateKey priv = new PrivateKey(etype);
                         priv.readBytes(in);
                         addPrivateKey(priv);
@@ -164,7 +178,9 @@ public class CreateLeaseSet2Message extends CreateLeaseSetMessage {
             os.write(_leaseSet.getType());
             _leaseSet.writeBytes(os);
             if (type != DatabaseEntry.KEY_TYPE_META_LS2) {
-                for (PrivateKey pk : getPrivateKeys()) {
+                List<PrivateKey> pks = getPrivateKeys();
+                os.write(pks.size());
+                for (PrivateKey pk : pks) {
                     EncType etype = pk.getType();
                     DataHelper.writeLong(os, 2, etype.getCode());
                     DataHelper.writeLong(os, 2, pk.length());
@@ -187,7 +203,9 @@ public class CreateLeaseSet2Message extends CreateLeaseSetMessage {
         StringBuilder buf = new StringBuilder();
         buf.append("[CreateLeaseSet2Message: ");
         buf.append("\n\tLeaseSet: ").append(_leaseSet);
-        if (_leaseSet.getType() != DatabaseEntry.KEY_TYPE_META_LS2) {
+        int type = _leaseSet.getType();
+        if (type != DatabaseEntry.KEY_TYPE_META_LS2 &&
+            type != DatabaseEntry.KEY_TYPE_ENCRYPTED_LS2) {
             for (PrivateKey pk : getPrivateKeys()) {
                 buf.append("\n\tPrivateKey: ").append(pk);
             }
diff --git a/router/java/src/net/i2p/router/client/ClientMessageEventListener.java b/router/java/src/net/i2p/router/client/ClientMessageEventListener.java
index 57c1e090b58f38eed8d8a96eba7794240b245ba3..48c57bbd70a95edf5cd3e0b0296e041f875d77f8 100644
--- a/router/java/src/net/i2p/router/client/ClientMessageEventListener.java
+++ b/router/java/src/net/i2p/router/client/ClientMessageEventListener.java
@@ -19,7 +19,9 @@ import net.i2p.data.DataHelper;
 import net.i2p.data.Destination;
 import net.i2p.data.Hash;
 import net.i2p.data.LeaseSet;
+import net.i2p.data.LeaseSet2;
 import net.i2p.data.Payload;
+import net.i2p.data.PrivateKey;
 import net.i2p.data.PublicKey;
 import net.i2p.data.i2cp.BandwidthLimitsMessage;
 import net.i2p.data.i2cp.CreateLeaseSetMessage;
@@ -290,7 +292,14 @@ class ClientMessageEventListener implements I2CPMessageReader.I2CPMessageEventLi
             }
         }
         String lsType = props.getProperty("i2cp.leaseSetType");
-        if ("7".equals(lsType)) {
+        if ("5".equals(lsType)) {
+            SigType stype = dest.getSigningPublicKey().getType();
+            if (stype != SigType.EdDSA_SHA512_Ed25519 &&
+                stype != SigType.RedDSA_SHA512_Ed25519) {
+                _runner.disconnectClient("Invalid sig type for encrypted LS");
+                return;
+            }
+        } else if ("7".equals(lsType)) {
             // Prevent tunnel builds for Meta LS
             // more TODO
             props.setProperty("inbound.length", "0");
@@ -512,7 +521,9 @@ class ClientMessageEventListener implements I2CPMessageReader.I2CPMessageEventLi
             return;
         }
         int type = ls.getType();
-        if (type != DatabaseEntry.KEY_TYPE_META_LS2 && message.getPrivateKey() == null) {
+        if (type != DatabaseEntry.KEY_TYPE_META_LS2 &&
+            type != DatabaseEntry.KEY_TYPE_ENCRYPTED_LS2 &&
+            message.getPrivateKey() == null) {
             if (_log.shouldLog(Log.ERROR))
                 _log.error("Null private keys: " + message);
             _runner.disconnectClient("Invalid CreateLeaseSetMessage - null private keys");
@@ -536,12 +547,31 @@ class ClientMessageEventListener implements I2CPMessageReader.I2CPMessageEventLi
             return;
         }
         Destination dest = cfg.getDestination();
-        Destination ndest = ls.getDestination();
-        if (!dest.equals(ndest)) {
-            if (_log.shouldLog(Log.ERROR))
-                _log.error("Different destination in LS");
-            _runner.disconnectClient("Different destination in LS");
-            return;
+        if (type == DatabaseEntry.KEY_TYPE_ENCRYPTED_LS2) {
+            // so we can decrypt it
+            try {
+                ls.setDestination(dest);
+            } catch (RuntimeException re) {
+                if (_log.shouldError())
+                    _log.error("Error decrypting leaseset from client", re);
+                _runner.disconnectClient(re.toString());
+                return;
+            }
+            // we have to do this before checking encryption keys below
+            if (!ls.verifySignature()) {
+                if (_log.shouldError())
+                    _log.error("Error decrypting leaseset from client");
+                _runner.disconnectClient("Error decrypting leaseset from client");
+                return;
+            }
+        } else {
+            Destination ndest = ls.getDestination();
+            if (!dest.equals(ndest)) {
+                if (_log.shouldLog(Log.ERROR))
+                    _log.error("Different destination in LS");
+                _runner.disconnectClient("Different destination in LS");
+                return;
+            }
         }
         if (type != DatabaseEntry.KEY_TYPE_META_LS2) {
             LeaseSetKeys keys = _context.keyManager().getKeys(dest);
@@ -550,20 +580,47 @@ class ClientMessageEventListener implements I2CPMessageReader.I2CPMessageEventLi
                 // Verify and register crypto keys if new or if changed
                 // Private crypto key should never change, and if it does,
                 // one of the checks below will fail
-                PublicKey pk;
-                try {
-                    pk = message.getPrivateKey().toPublic();
-                } catch (IllegalArgumentException iae) {
-                    if (_log.shouldLog(Log.ERROR))
-                        _log.error("Bad private key in LS");
-                    _runner.disconnectClient("Bad private key in LS");
-                    return;
-                }
-                if (!pk.equals(ls.getEncryptionKey())) {
-                    if (_log.shouldLog(Log.ERROR))
-                        _log.error("Private/public crypto key mismatch in LS");
-                    _runner.disconnectClient("Private/public crypto key mismatch in LS");
-                    return;
+                if (type == DatabaseEntry.KEY_TYPE_LEASESET) {
+                    // LS1
+                    PublicKey pk;
+                    try {
+                        pk = message.getPrivateKey().toPublic();
+                    } catch (IllegalArgumentException iae) {
+                        if (_log.shouldLog(Log.ERROR))
+                            _log.error("Bad private key in LS");
+                        _runner.disconnectClient("Bad private key in LS");
+                        return;
+                    }
+                    if (!pk.equals(ls.getEncryptionKey())) {
+                        if (_log.shouldLog(Log.ERROR))
+                            _log.error("Private/public crypto key mismatch in LS");
+                        _runner.disconnectClient("Private/public crypto key mismatch in LS");
+                        return;
+                    }
+                } else {
+                    // LS2
+                    LeaseSet2 ls2 = (LeaseSet2) ls;
+                    CreateLeaseSet2Message msg2 = (CreateLeaseSet2Message) message;
+                    List<PublicKey> eks = ls2.getEncryptionKeys();
+                    List<PrivateKey> pks = msg2.getPrivateKeys();
+                    for (int i = 0; i < eks.size(); i++) {
+                        PublicKey ek = eks.get(i);
+                        PublicKey pk;
+                        try {
+                            pk = pks.get(i).toPublic();
+                        } catch (IllegalArgumentException iae) {
+                            if (_log.shouldLog(Log.ERROR))
+                                _log.error("Bad private key in LS: " + i);
+                            _runner.disconnectClient("Bad private key in LS: " + i);
+                            return;
+                        }
+                        if (!pk.equals(ek)) {
+                            if (_log.shouldLog(Log.ERROR))
+                                _log.error("Private/public crypto key mismatch in LS for key: " + i);
+                            _runner.disconnectClient("Private/public crypto key mismatch in LS for key: " + i);
+                            return;
+                        }
+                    }
                 }
                 // just register new SPK, don't verify, unused
                 _context.keyManager().registerKeys(dest, message.getSigningPrivateKey(), message.getPrivateKey());