diff --git a/router/java/src/net/i2p/data/i2np/BuildRequestRecord.java b/router/java/src/net/i2p/data/i2np/BuildRequestRecord.java
index 53db1a3ca7d72352166c4dcc1a7d1690a293cb22..fdea2f388b2f8b73d97f9e8f549ac5aa7d2e5ec5 100644
--- a/router/java/src/net/i2p/data/i2np/BuildRequestRecord.java
+++ b/router/java/src/net/i2p/data/i2np/BuildRequestRecord.java
@@ -18,6 +18,7 @@ import net.i2p.data.Hash;
 import net.i2p.data.PrivateKey;
 import net.i2p.data.PublicKey;
 import net.i2p.data.SessionKey;
+import net.i2p.util.Log;
 import net.i2p.router.RouterContext;
 
 /**
@@ -113,6 +114,8 @@ public class BuildRequestRecord {
     /** we show 16 bytes of the peer hash outside the elGamal block */
     public static final int PEER_SIZE = 16;
     private static final int DEFAULT_EXPIRATION_SECONDS = 10*60;
+    private static final int EC_LEN = EncType.ECIES_X25519.getPubkeyLen();
+    private static final byte[] NULL_KEY = new byte[EC_LEN];
     
     /**
      *  @return 222 (ElG) or 464 (ECIES) bytes, non-null
@@ -379,15 +382,31 @@ public class BuildRequestRecord {
      */
     public BuildRequestRecord(RouterContext ctx, PrivateKey ourKey,
                               EncryptedBuildRecord encryptedRecord) throws DataFormatException {
+        byte[] encrypted = encryptedRecord.getData();
         byte decrypted[];
         EncType type = ourKey.getType();
         if (type == EncType.ELGAMAL_2048) {
             byte preDecrypt[] = new byte[514];
-            System.arraycopy(encryptedRecord.getData(), PEER_SIZE, preDecrypt, 1, 256);
-            System.arraycopy(encryptedRecord.getData(), PEER_SIZE + 256, preDecrypt, 258, 256);
+            System.arraycopy(encrypted, PEER_SIZE, preDecrypt, 1, 256);
+            System.arraycopy(encrypted, PEER_SIZE + 256, preDecrypt, 258, 256);
             decrypted = ctx.elGamalEngine().decrypt(preDecrypt, ourKey);
             _isEC = false;
         } else if (type == EncType.ECIES_X25519) {
+            // There's several reasons to get bogus-encrypted requests:
+            // very old Java and i2pd routers that don't check the type at all and send ElG,
+            // i2pd treating type 4 like type 1,
+            // and very old i2pd routers like 0.9.32 that have all sorts of bugs.
+            // The following 3 checks weed out about 85% before we get to the DH.
+
+            // fast MSB check for key < 2^255
+            if ((encrypted[PEER_SIZE + EC_LEN - 1] & 0x80) != 0)
+                throw new DataFormatException("Bad PK decrypt fail");
+            // i2pd 0.9.46/47 bug, treating us like type 1
+            if (DataHelper.eq(ourKey.toPublic().getData(), 0, encrypted, PEER_SIZE, EC_LEN))
+                throw new DataFormatException("Our PK decrypt fail");
+            // very old i2pd bug?
+            if (DataHelper.eq(NULL_KEY, 0, encrypted, PEER_SIZE, EC_LEN))
+                throw new DataFormatException("Null PK decrypt fail");
             HandshakeState state = null;
             try {
                 KeyFactory kf = TEST ? TESTKF : ctx.commSystem().getXDHFactory();
@@ -396,13 +415,18 @@ public class BuildRequestRecord {
                                                 ourKey.toPublic().getData(), 0);
                 state.start();
                 decrypted = new byte[LENGTH_EC];
-                state.readMessage(encryptedRecord.getData(), PEER_SIZE, EncryptedBuildRecord.LENGTH - PEER_SIZE,
+                state.readMessage(encrypted, PEER_SIZE, EncryptedBuildRecord.LENGTH - PEER_SIZE,
                                   decrypted, 0);
                 _chachaReplyKey = new SessionKey(state.getChainingKey());
                 _chachaReplyAD = new byte[32];
                 System.arraycopy(state.getHandshakeHash(), 0, _chachaReplyAD, 0, 32);
             } catch (GeneralSecurityException gse) {
-                throw new DataFormatException("decrypt fail", gse);
+                if (state != null) {
+                    Log log = ctx.logManager().getLog(BuildRequestRecord.class);
+                    if (log.shouldInfo())
+                        log.info("ECIES BRR decrypt failure, state at failure:\n" + state);
+                }
+                throw new DataFormatException("ChaCha decrypt fail", gse);
             } finally {
                 if (state != null)
                     state.destroy();
diff --git a/router/java/src/net/i2p/router/crypto/ratchet/ECIESAEADEngine.java b/router/java/src/net/i2p/router/crypto/ratchet/ECIESAEADEngine.java
index c0e9114aed010b901343894f408a3259cf856711..de88fee78ded11dc546dfac9cf1a677d0421dee0 100644
--- a/router/java/src/net/i2p/router/crypto/ratchet/ECIESAEADEngine.java
+++ b/router/java/src/net/i2p/router/crypto/ratchet/ECIESAEADEngine.java
@@ -350,18 +350,6 @@ public final class ECIESAEADEngine {
      */
     private CloveSet decryptNewSession(byte data[], PrivateKey targetPrivateKey, RatchetSKM keyManager)
                                      throws DataFormatException {
-        HandshakeState state;
-        try {
-            state = new HandshakeState(HandshakeState.PATTERN_ID_IK, HandshakeState.RESPONDER, _edhThread);
-        } catch (GeneralSecurityException gse) {
-            throw new IllegalStateException("bad proto", gse);
-        }
-        state.getLocalKeyPair().setKeys(targetPrivateKey.getData(), 0,
-                                        targetPrivateKey.toPublic().getData(), 0);
-        state.start();
-        if (_log.shouldDebug())
-            _log.debug("State before decrypt new session: " + state);
-
         // Elg2
         byte[] xx = new byte[KEYLEN];
         System.arraycopy(data, 0, xx, 0, KEYLEN);
@@ -373,12 +361,31 @@ public final class ECIESAEADEngine {
             if (_log.shouldDebug())
                 _log.debug("Elg2 decode fail NS");
             data[KEYLEN - 1] = xx31;
-            state.destroy();
             return null;
         }
+        // fast MSB check for key < 2^255
+        if ((pk.getData()[KEYLEN - 1] & 0x80) != 0) {
+            if (_log.shouldDebug())
+                _log.debug("Bad PK decode fail NS");
+            data[KEYLEN - 1] = xx31;
+            return null;
+        }
+
         // rewrite in place, must restore below on failure
         System.arraycopy(pk.getData(), 0, data, 0, KEYLEN);
 
+        HandshakeState state;
+        try {
+            state = new HandshakeState(HandshakeState.PATTERN_ID_IK, HandshakeState.RESPONDER, _edhThread);
+        } catch (GeneralSecurityException gse) {
+            throw new IllegalStateException("bad proto", gse);
+        }
+        state.getLocalKeyPair().setKeys(targetPrivateKey.getData(), 0,
+                                        targetPrivateKey.toPublic().getData(), 0);
+        state.start();
+        if (_log.shouldDebug())
+            _log.debug("State before decrypt new session: " + state);
+
         int payloadlen = data.length - (KEYLEN + KEYLEN + MACLEN + MACLEN);
         byte[] payload = new byte[payloadlen];
         try {
diff --git a/router/java/src/net/i2p/router/transport/ntcp/InboundEstablishState.java b/router/java/src/net/i2p/router/transport/ntcp/InboundEstablishState.java
index 1bd397cc04b0b34ffcbc31951a539412c0cfc3ba..c391b4708b89b553b6699921a08971cfd1414f0f 100644
--- a/router/java/src/net/i2p/router/transport/ntcp/InboundEstablishState.java
+++ b/router/java/src/net/i2p/router/transport/ntcp/InboundEstablishState.java
@@ -672,7 +672,6 @@ class InboundEstablishState extends EstablishBase implements NTCP2Payload.Payloa
             }
             changeState(State.IB_NTCP2_GOT_X);
             _received = 0;
-
             // replay check using encrypted key
             if (!_transport.isHXHIValid(_X)) {
                 _context.statManager().addRateData("ntcp.replayHXxorBIH", 1);
@@ -680,13 +679,6 @@ class InboundEstablishState extends EstablishBase implements NTCP2Payload.Payloa
                 return;
             }
 
-            try {
-                _handshakeState = new HandshakeState(HandshakeState.PATTERN_ID_XK, HandshakeState.RESPONDER, _transport.getXDHFactory());
-            } catch (GeneralSecurityException gse) {
-                throw new IllegalStateException("bad proto", gse);
-            }
-            _handshakeState.getLocalKeyPair().setKeys(_transport.getNTCP2StaticPrivkey(), 0,
-                                                      _transport.getNTCP2StaticPubkey(), 0);
             Hash h = _context.routerHash();
             SessionKey bobHash = new SessionKey(h.getData());
             // save encrypted data for CBC for msg 2
@@ -696,6 +688,19 @@ class InboundEstablishState extends EstablishBase implements NTCP2Payload.Payloa
                 fail("Bad msg 1, X = 0");
                 return;
             }
+            // fast MSB check for key < 2^255
+            if ((_X[KEY_SIZE - 1] & 0x80) != 0) {
+                fail("Bad PK msg 1");
+                return;
+            }
+
+            try {
+                _handshakeState = new HandshakeState(HandshakeState.PATTERN_ID_XK, HandshakeState.RESPONDER, _transport.getXDHFactory());
+            } catch (GeneralSecurityException gse) {
+                throw new IllegalStateException("bad proto", gse);
+            }
+            _handshakeState.getLocalKeyPair().setKeys(_transport.getNTCP2StaticPrivkey(), 0,
+                                                      _transport.getNTCP2StaticPubkey(), 0);
             byte options[] = new byte[OPTIONS1_SIZE];
             try {
                 _handshakeState.start();