diff --git a/router/java/src/net/i2p/router/transport/udp/InboundEstablishState2.java b/router/java/src/net/i2p/router/transport/udp/InboundEstablishState2.java
index dca920afe6657042fbf341431df5a8417540d4cb..178ccae628e62b870a82d11daf53994b948f276e 100644
--- a/router/java/src/net/i2p/router/transport/udp/InboundEstablishState2.java
+++ b/router/java/src/net/i2p/router/transport/udp/InboundEstablishState2.java
@@ -9,6 +9,7 @@ import java.net.UnknownHostException;
 import java.security.GeneralSecurityException;
 import java.util.List;
 
+import com.southernstorm.noise.protocol.ChaChaPolyCipherState;
 import com.southernstorm.noise.protocol.CipherState;
 import com.southernstorm.noise.protocol.CipherStatePair;
 import com.southernstorm.noise.protocol.HandshakeState;
@@ -23,6 +24,7 @@ import net.i2p.data.router.RouterAddress;
 import net.i2p.data.router.RouterInfo;
 import net.i2p.router.RouterContext;
 import net.i2p.router.networkdb.kademlia.FloodfillNetworkDatabaseFacade;
+import static net.i2p.router.transport.udp.SSU2Util.*;
 import net.i2p.util.Addresses;
 import net.i2p.util.Log;
 
@@ -80,20 +82,27 @@ class InboundEstablishState2 extends InboundEstablishState implements SSU2Payloa
         int len = pkt.getLength();
         byte data[] = pkt.getData();
         _rcvConnID = DataHelper.fromLong8(data, off);
-        _sendConnID = DataHelper.fromLong8(data, off + 16);
+        _sendConnID = DataHelper.fromLong8(data, off + SRC_CONN_ID_OFFSET);
         if (_rcvConnID == _sendConnID)
             throw new GeneralSecurityException("Identical Conn IDs");
-        int type = data[off + 12] & 0xff;
-        long token = DataHelper.fromLong8(data, off + 24);
-        if (type == 10) {
+        int type = data[off + TYPE_OFFSET] & 0xff;
+        long token = DataHelper.fromLong8(data, off + TOKEN_OFFSET);
+        if (type == TOKEN_REQUEST_FLAG_BYTE) {
             _currentState = InboundState.IB_STATE_TOKEN_REQUEST_RECEIVED;
-            // TODO decrypt chacha?
+            // decrypt in-place
+            ChaChaPolyCipherState chacha = new ChaChaPolyCipherState();
+            chacha.initializeKey(_rcvHeaderEncryptKey1, 0);
+            long n = DataHelper.fromLong(data, off + PKT_NUM_OFFSET, 4);
+            chacha.setNonce(n);
+            chacha.decryptWithAd(data, off, LONG_HEADER_SIZE,
+                                 data, off + LONG_HEADER_SIZE, data, off + LONG_HEADER_SIZE, len - LONG_HEADER_SIZE);
+            processPayload(data, off + LONG_HEADER_SIZE, len - (LONG_HEADER_SIZE + MAC_LEN), true);
             _sendHeaderEncryptKey2 = introKey;
             do {
                 token = ctx.random().nextLong();
             } while (token == 0);
             _token = token;
-        } else if (type == 0 &&
+        } else if (type == SESSION_REQUEST_FLAG_BYTE &&
                    (token == 0 ||
                     (ENFORCE_TOKEN && !_transport.getEstablisher().isInboundTokenValid(_remoteHostId, token)))) {
             _currentState = InboundState.IB_STATE_REQUEST_BAD_TOKEN_RECEIVED;
@@ -104,20 +113,21 @@ class InboundEstablishState2 extends InboundEstablishState implements SSU2Payloa
             _token = token;
         } else {
             // fast MSB check for key < 2^255
-            if ((data[off + 32 + 32 - 1] & 0x80) != 0)
+            if ((data[off + LONG_HEADER_SIZE + KEY_LEN - 1] & 0x80) != 0)
                 throw new GeneralSecurityException("Bad PK msg 1");
             // probably don't need again
             _token = token;
             _handshakeState.start();
             if (_log.shouldDebug())
                 _log.debug("State after start: " + _handshakeState);
-            _handshakeState.mixHash(data, off, 32);
+            _handshakeState.mixHash(data, off, LONG_HEADER_SIZE);
             if (_log.shouldDebug())
                 _log.debug("State after mixHash 1: " + _handshakeState);
 
             byte[] payload = new byte[len - 80]; // 32 hdr, 32 eph. key, 16 MAC
+            // decrypt in-place
             try {
-                _handshakeState.readMessage(data, off + 32, len - 32, payload, 0);
+                _handshakeState.readMessage(data, off + LONG_HEADER_SIZE, len - LONG_HEADER_SIZE, data, off + LONG_HEADER_SIZE);
             } catch (GeneralSecurityException gse) {
                 if (_log.shouldDebug())
                     _log.debug("Session request error, State at failure: " + _handshakeState + '\n' + net.i2p.util.HexDump.dump(data, off, len), gse);
@@ -125,7 +135,7 @@ class InboundEstablishState2 extends InboundEstablishState implements SSU2Payloa
             }
             if (_log.shouldDebug())
                 _log.debug("State after sess req: " + _handshakeState);
-            processPayload(payload, payload.length, true);
+            processPayload(data, off + LONG_HEADER_SIZE, len - (LONG_HEADER_SIZE + KEY_LEN + MAC_LEN), true);
             _sendHeaderEncryptKey2 = SSU2Util.hkdf(_context, _handshakeState.getChainingKey(), "SessCreateHeader");
             _currentState = InboundState.IB_STATE_REQUEST_RECEIVED;
         }
@@ -135,12 +145,12 @@ class InboundEstablishState2 extends InboundEstablishState implements SSU2Payloa
     @Override
     public int getVersion() { return 2; }
     
-    private void processPayload(byte[] payload, int length, boolean isHandshake) throws GeneralSecurityException {
+    private void processPayload(byte[] payload, int offset, int length, boolean isHandshake) throws GeneralSecurityException {
         try {
-            int blocks = SSU2Payload.processPayload(_context, this, payload, 0, length, isHandshake);
+            int blocks = SSU2Payload.processPayload(_context, this, payload, offset, length, isHandshake);
             System.out.println("Processed " + blocks + " blocks");
         } catch (Exception e) {
-            _log.error("IES2 payload error\n" + net.i2p.util.HexDump.dump(payload, 0, length));
+            _log.error("IES2 payload error\n" + net.i2p.util.HexDump.dump(payload, 0, length), e);
             throw new GeneralSecurityException("IES2 payload error", e);
         }
     }
@@ -363,9 +373,9 @@ class InboundEstablishState2 extends InboundEstablishState implements SSU2Payloa
         if (_log.shouldDebug())
             _log.debug("State after mixHash 1: " + _handshakeState);
 
-        byte[] payload = new byte[len - 80]; // 16 hdr, 32 static key, 16 MAC, 16 MAC
+        // decrypt in-place
         try {
-            _handshakeState.readMessage(data, off + 32, len - 32, payload, 0);
+            _handshakeState.readMessage(data, off + LONG_HEADER_SIZE, len - LONG_HEADER_SIZE, data, off + LONG_HEADER_SIZE);
         } catch (GeneralSecurityException gse) {
             if (_log.shouldDebug())
                 _log.debug("Session Request error, State at failure: " + _handshakeState + '\n' + net.i2p.util.HexDump.dump(data, off, len), gse);
@@ -373,7 +383,7 @@ class InboundEstablishState2 extends InboundEstablishState implements SSU2Payloa
         }
         if (_log.shouldDebug())
             _log.debug("State after sess req: " + _handshakeState);
-        processPayload(payload, payload.length, true);
+        processPayload(data, off + LONG_HEADER_SIZE, len - (SHORT_HEADER_SIZE + KEY_LEN + MAC_LEN + MAC_LEN), true);
         _sendHeaderEncryptKey2 = SSU2Util.hkdf(_context, _handshakeState.getChainingKey(), "SessCreateHeader");
         _currentState = InboundState.IB_STATE_REQUEST_RECEIVED;
         
@@ -408,7 +418,7 @@ class InboundEstablishState2 extends InboundEstablishState implements SSU2Payloa
 
         byte[] payload = new byte[len - 80]; // 16 hdr, 32 static key, 16 MAC, 16 MAC
         try {
-            _handshakeState.readMessage(data, off + 16, len - 16, payload, 0);
+            _handshakeState.readMessage(data, off + SHORT_HEADER_SIZE, len - SHORT_HEADER_SIZE, payload, 0);
         } catch (GeneralSecurityException gse) {
             if (_log.shouldDebug())
                 _log.debug("Session Confirmed error, State at failure: " + _handshakeState + '\n' + net.i2p.util.HexDump.dump(data, off, len), gse);
@@ -416,7 +426,7 @@ class InboundEstablishState2 extends InboundEstablishState implements SSU2Payloa
         }
         if (_log.shouldDebug())
             _log.debug("State after sess conf: " + _handshakeState);
-        processPayload(payload, payload.length, false);
+        processPayload(payload, 0, payload.length, false);
         _sessCrForReTX = null;
 
         // TODO split, calculate keys
diff --git a/router/java/src/net/i2p/router/transport/udp/OutboundEstablishState2.java b/router/java/src/net/i2p/router/transport/udp/OutboundEstablishState2.java
index 2b11a019eed1f3cefe2381e8ec10df09b5f4f3cd..ef27d2a03cfd05a9e636473a875cf1ef4909c0f3 100644
--- a/router/java/src/net/i2p/router/transport/udp/OutboundEstablishState2.java
+++ b/router/java/src/net/i2p/router/transport/udp/OutboundEstablishState2.java
@@ -7,6 +7,7 @@ import java.net.SocketAddress;
 import java.net.UnknownHostException;
 import java.security.GeneralSecurityException;
 
+import com.southernstorm.noise.protocol.ChaChaPolyCipherState;
 import com.southernstorm.noise.protocol.CipherState;
 import com.southernstorm.noise.protocol.CipherStatePair;
 import com.southernstorm.noise.protocol.HandshakeState;
@@ -20,6 +21,7 @@ import net.i2p.data.router.RouterAddress;
 import net.i2p.data.router.RouterIdentity;
 import net.i2p.data.router.RouterInfo;
 import net.i2p.router.RouterContext;
+import static net.i2p.router.transport.udp.SSU2Util.*;
 import net.i2p.util.Addresses;
 import net.i2p.util.Log;
 
@@ -156,9 +158,9 @@ class OutboundEstablishState2 extends OutboundEstablishState implements SSU2Payl
         _rcvHeaderEncryptKey2 = null;
     }
 
-    private void processPayload(byte[] payload, int length, boolean isHandshake) throws GeneralSecurityException {
+    private void processPayload(byte[] payload, int offset, int length, boolean isHandshake) throws GeneralSecurityException {
         try {
-            int blocks = SSU2Payload.processPayload(_context, this, payload, 0, length, isHandshake);
+            int blocks = SSU2Payload.processPayload(_context, this, payload, offset, length, isHandshake);
             System.out.println("Processed " + blocks + " blocks");
         } catch (Exception e) {
             throw new GeneralSecurityException("Session Created payload error", e);
@@ -264,6 +266,24 @@ class OutboundEstablishState2 extends OutboundEstablishState implements SSU2Payl
 
     public synchronized void receiveRetry(UDPPacket packet) throws GeneralSecurityException {
         ////// TODO state check
+        DatagramPacket pkt = packet.getPacket();
+        int off = pkt.getOffset();
+        int len = pkt.getLength();
+        byte data[] = pkt.getData();
+        try {
+            // decrypt in-place
+            ChaChaPolyCipherState chacha = new ChaChaPolyCipherState();
+            chacha.initializeKey(_rcvHeaderEncryptKey1, 0);
+            long n = DataHelper.fromLong(data, off + PKT_NUM_OFFSET, 4);
+            chacha.setNonce(n);
+            chacha.decryptWithAd(data, off, LONG_HEADER_SIZE,
+                                 data, off + LONG_HEADER_SIZE, data, off + LONG_HEADER_SIZE, len - LONG_HEADER_SIZE);
+            processPayload(data, off + LONG_HEADER_SIZE, len - (LONG_HEADER_SIZE + MAC_LEN), true);
+        } catch (GeneralSecurityException gse) {
+            if (_log.shouldDebug())
+                _log.debug("Retry error", gse);
+            throw gse;
+        }
         createNewState(_routerAddress);
         ////// TODO state change
     }
@@ -283,13 +303,13 @@ class OutboundEstablishState2 extends OutboundEstablishState implements SSU2Payl
         int off = pkt.getOffset();
         int len = pkt.getLength();
         byte data[] = pkt.getData();
-        _handshakeState.mixHash(data, off, 32);
+        _handshakeState.mixHash(data, off, LONG_HEADER_SIZE);
         if (_log.shouldDebug())
             _log.debug("State after mixHash 2: " + _handshakeState);
 
-        byte[] payload = new byte[len - 80]; // 32 hdr, 32 eph. key, 16 MAC
+        // decrypt in-place
         try {
-            _handshakeState.readMessage(data, off + 32, len - 32, payload, 0);
+            _handshakeState.readMessage(data, off + LONG_HEADER_SIZE, len - LONG_HEADER_SIZE, data, off + LONG_HEADER_SIZE);
         } catch (GeneralSecurityException gse) {
             if (_log.shouldDebug())
                 _log.debug("Session create error, State at failure: " + _handshakeState + '\n' + net.i2p.util.HexDump.dump(data, off, len), gse);
@@ -297,7 +317,7 @@ class OutboundEstablishState2 extends OutboundEstablishState implements SSU2Payl
         }
         if (_log.shouldDebug())
             _log.debug("State after sess cr: " + _handshakeState);
-        processPayload(payload, payload.length, true);
+        processPayload(data, off + LONG_HEADER_SIZE, len - (LONG_HEADER_SIZE + KEY_LEN + MAC_LEN), true);
         _sessReqForReTX = null;
         _sendHeaderEncryptKey2 = SSU2Util.hkdf(_context, _handshakeState.getChainingKey(), "SessionConfirmed");
 
diff --git a/router/java/src/net/i2p/router/transport/udp/PacketBuilder2.java b/router/java/src/net/i2p/router/transport/udp/PacketBuilder2.java
index cf7c157a284aa6e32f36405b20092f49d66c7e53..2c6263116e482a744f898ec366db21bab68bb830 100644
--- a/router/java/src/net/i2p/router/transport/udp/PacketBuilder2.java
+++ b/router/java/src/net/i2p/router/transport/udp/PacketBuilder2.java
@@ -839,7 +839,7 @@ class PacketBuilder2 {
         byte data[] = pkt.getData();
         int off = pkt.getOffset();
         try {
-            List<Block> blocks = new ArrayList<Block>(4);
+            List<Block> blocks = new ArrayList<Block>(3);
             Block block = new SSU2Payload.DateTimeBlock(_context);
             int len = block.getTotalLength();
             blocks.add(block);
@@ -850,8 +850,7 @@ class PacketBuilder2 {
             block = getPadding(len, 1280);
             len += block.getTotalLength();
             blocks.add(block);
-            byte[] payload = new byte[len];
-            SSU2Payload.writePayload(payload, 0, blocks);
+            SSU2Payload.writePayload(data, off + LONG_HEADER_SIZE, blocks);
 
             ChaChaPolyCipherState chacha = new ChaChaPolyCipherState();
             chacha.initializeKey(chachaKey, 0);
@@ -881,7 +880,7 @@ class PacketBuilder2 {
         byte data[] = pkt.getData();
         int off = pkt.getOffset();
         try {
-            List<Block> blocks = new ArrayList<Block>(4);
+            List<Block> blocks = new ArrayList<Block>(2);
             Block block = new SSU2Payload.DateTimeBlock(_context);
             int len = block.getTotalLength();
             blocks.add(block);
@@ -889,14 +888,13 @@ class PacketBuilder2 {
             block = getPadding(len, 1280);
             len += block.getTotalLength();
             blocks.add(block);
-            byte[] payload = new byte[len];
-            SSU2Payload.writePayload(payload, 0, blocks);
+            SSU2Payload.writePayload(data, off + LONG_HEADER_SIZE, blocks);
 
             ChaChaPolyCipherState chacha = new ChaChaPolyCipherState();
             chacha.initializeKey(chachaKey, 0);
             chacha.setNonce(n);
             chacha.encryptWithAd(data, off, LONG_HEADER_SIZE,
-                                 data, off + LONG_HEADER_SIZE, data, off + LONG_HEADER_SIZE, len - LONG_HEADER_SIZE);
+                                 data, off + LONG_HEADER_SIZE, data, off + LONG_HEADER_SIZE, len);
 
             pkt.setLength(pkt.getLength() + len + MAC_LEN);
         } catch (RuntimeException re) {