Pad a short RSA puzzle answer instead of failing the connect

An answer with a leading zero byte comes out of toByteArray() as 63 bytes,
and copying it started one byte too far, failing about one connect in 256.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-14 20:17:49 +00:00
co-authored by Claude Opus 5.5
parent 0549b6bf02
commit 004aa41732
2 changed files with 47 additions and 10 deletions
@@ -159,16 +159,7 @@ public class LocalClientHandlerConnecting extends LocalClientHandler {
BigInteger x = new BigInteger(1, serverReplyStep3.getX());
BigInteger n = new BigInteger(1, serverReplyStep3.getN());
byte[] y = new byte[64];
byte[] solution =
x.modPow(BigInteger.valueOf(2L).pow(serverReplyStep3.getLevel()), n).toByteArray();
System.arraycopy(
solution, Math.abs(solution.length - 64),
y, solution.length < 64 ? 64 - solution.length : 0,
solution.length < 64 ? solution.length : 64
);
byte[] y = unsigned64(x.modPow(BigInteger.valueOf(2L).pow(serverReplyStep3.getLevel()), n));
// Build response
PacketBody8Init1.Step4 step4 = new PacketBody8Init1.Step4();
@@ -360,4 +351,17 @@ public class LocalClientHandlerConnecting extends LocalClientHandler {
getClient().writePacket(new PacketBody2Command(ProtocolRole.CLIENT, clientinit));
}
/**
* The puzzle's answer as 64 unsigned big-endian bytes. {@link BigInteger#toByteArray()}
* adds a sign byte when the top bit is set and drops leading zero bytes, so it can be
* 65 bytes or fewer than 64.
*/
static byte[] unsigned64(BigInteger value) {
byte[] bytes = value.toByteArray();
byte[] y = new byte[64];
int length = Math.min(bytes.length, 64);
System.arraycopy(bytes, bytes.length - length, y, 64 - length, length);
return y;
}
}
@@ -0,0 +1,33 @@
package com.github.manevolent.ts3j.protocol.packet.handler.client;
import junit.framework.TestCase;
import java.math.BigInteger;
import java.util.Arrays;
public class PuzzleAnswerTest extends TestCase {
private static byte[] bytes(int length, int first) {
byte[] b = new byte[length];
Arrays.fill(b, (byte) 0x11);
b[0] = (byte) first;
return b;
}
/** Top bit set: toByteArray() gives 65 bytes, the first a sign byte. */
public void testDropsTheSignByte() {
byte[] answer = bytes(64, 0x80);
assertTrue(Arrays.equals(answer, LocalClientHandlerConnecting.unsigned64(new BigInteger(1, answer))));
}
public void testKeepsSixtyFourBytes() {
byte[] answer = bytes(64, 0x7f);
assertTrue(Arrays.equals(answer, LocalClientHandlerConnecting.unsigned64(new BigInteger(1, answer))));
}
/** A leading zero byte: toByteArray() gives 63 bytes, which used to fail the connect. */
public void testPadsAShortAnswerWithZeros() {
byte[] answer = bytes(64, 0x00);
answer[1] = 0x42;
assertTrue(Arrays.equals(answer, LocalClientHandlerConnecting.unsigned64(new BigInteger(1, answer))));
}
}