Drastically speed up rand() pairs -> seed search

Meet in the middle attack trades some RAM (2^16 flag/byte array) to
reduce future searches by a factor of 1:2^8

profiling yielded >100x speed improvements, even a 2x would have been
impressive ;)
knocks the biggest cpu hog (when searching db) out of the race!
This commit is contained in:
Kurt 2017-08-03 00:59:39 -07:00
parent 64919cb8c1
commit 3123f0df2f
2 changed files with 58 additions and 16 deletions

View file

@ -467,26 +467,16 @@ namespace PKHeX.Core
{ {
uint cmp = a << 16; uint cmp = a << 16;
uint x = b << 16; uint x = b << 16;
for (uint i = 0; i <= 0xFFFF; i++) return method.RecoverLower16Bits(x, cmp);
{
var seed = x | i;
if ((method.Next(seed) & 0xFFFF0000) == cmp)
yield return method.Prev(seed);
}
} }
private static IEnumerable<uint> GetSeedsFromIVs(RNG method, uint a, uint b) private static IEnumerable<uint> GetSeedsFromIVs(RNG method, uint a, uint b)
{ {
uint cmp = a << 16 & 0x7FFF0000; uint cmp = a << 16 & 0x7FFF0000;
uint x = b << 16 & 0x7FFF0000; uint x = b << 16 & 0x7FFF0000;
for (uint i = 0; i <= 0xFFFF; i++) return method.RecoverLower16Bits(x ^ 0x00000000, cmp ^ 0x00000000).Concat(
{ method.RecoverLower16Bits(x ^ 0x80000000, cmp ^ 0x00000000).Concat(
var seed = x | i; method.RecoverLower16Bits(x ^ 0x00000000, cmp ^ 0x80000000).Concat(
if ((method.Next(seed) & 0x7FFF0000) != cmp) method.RecoverLower16Bits(x ^ 0x80000000, cmp ^ 0x80000000))));
continue;
var prev = method.Prev(seed);
yield return prev;
yield return prev ^ 0x80000000;
}
} }
/// <summary> /// <summary>

View file

@ -1,4 +1,6 @@
namespace PKHeX.Core using System.Collections.Generic;
namespace PKHeX.Core
{ {
public class RNG public class RNG
{ {
@ -7,12 +9,41 @@
public static readonly RNG ARNG = new RNG(0x6C078965, 0x00000001, 0x9638806D, 0x69C77F93); public static readonly RNG ARNG = new RNG(0x6C078965, 0x00000001, 0x9638806D, 0x69C77F93);
private readonly uint Mult, Add, rMult, rAdd; private readonly uint Mult, Add, rMult, rAdd;
// Bruteforce cache for searching seeds
private const int cacheSize = 1 << 16;
private readonly uint k2;
private readonly byte[] low8 = new byte[cacheSize];
private readonly bool[] flags = new bool[cacheSize];
private RNG(uint f_mult, uint f_add, uint r_mult, uint r_add) private RNG(uint f_mult, uint f_add, uint r_mult, uint r_add)
{ {
Mult = f_mult; Mult = f_mult;
Add = f_add; Add = f_add;
rMult = r_mult; rMult = r_mult;
rAdd = r_add; rAdd = r_add;
// Set up bruteforce utility
k2 = Mult << 8;
PopulateMeetMiddleArrays();
}
private void PopulateMeetMiddleArrays()
{
for (uint i = 0; i <= byte.MaxValue; i++)
{
uint right = Mult * i;
ushort val = (ushort)(right >> 16);
flags[val] = true;
low8[val] = (byte)i;
// when calculating the left side, sometimes the low bits might not carry (not considered in calc)
// since LCGs are linear, there are no collisions if we mark the next adjacent with the prior val
// we do this now so that the search only has to access the array once per loop.
++val;
flags[val] = true;
low8[val] = (byte)i;
}
} }
public uint Next(uint seed) => seed * Mult + Add; public uint Next(uint seed) => seed * Mult + Add;
@ -56,5 +87,26 @@
} }
return ivs; return ivs;
} }
/// <summary>
/// Recovers sets of lower 16 bit seeds, then returns the origin seed.
/// </summary>
/// <param name="first">First rand() call, 16 bits, already shifted left 16 bits.</param>
/// <param name="second">Second rand() call, 16 bits, already shifted left 16 bits.</param>
/// <remarks>
/// Use a meet-in-the-middle attack to reduce the search space to 2^8 instead of 2^16
/// flag/2^8 tables are precomputed and constant (unrelated to rand pairs)
/// </remarks>
/// <returns>Possible origin seeds that generate the 2 random numbers</returns>
internal IEnumerable<uint> RecoverLower16Bits(uint first, uint second)
{
uint k1 = second - (first * Mult + Add);
for (uint i = 0, k3 = k1; i <= 255; ++i, k3 -= k2)
{
ushort val = (ushort)(k3 >> 16);
if (flags[val])
yield return Prev(first | i << 8 | low8[val]);
}
}
} }
} }