【问题标题】:Finding seeds for a 5 byte PRNG寻找 5 字节 PRNG 的种子
【发布时间】:2013-07-01 18:59:28
【问题描述】:

一个古老的想法,但从那时起我就无法找到一些相当好的方法来解决它提出的问题。所以我“发明”了(见下文)一个非常紧凑的,并且在我看来,性能相当不错的 PRNG,但我无法找出算法来为它在大位深度上构建合适的种子值。我目前的解决方案只是暴力破解,它的运行时间是 O(n^3)。

生成器

我的想法来自 XOR Taps(本质上是 LFSRs)一些用于声音生成的旧 8 位机器。我在 C64 上摆弄 XOR 作为基础,尝试将操作码放在一起,并体验了结果。最终的工作解决方案如下所示:

asl
adc #num1
eor #num2

这是 6502 上的 5 个字节。使用精心选择的 num1 和 num2,在累加器中它以看似随机的顺序迭代所有 256 个值,也就是说,当用于填充屏幕时它看起来相当随机(我写当时有一个小 256b 演示)。有 40 个合适的 num1 和 num2 对,都给出了不错的序列。

这个概念可以很好的概括,如果用纯C来表达,可能是这样的(BITS是序列的位深度):

r = (((r >> (BITS-1)) & 1U) + (r << 1) + num1) ^ num2;
r = r & ((1U<<BITS)-1U);

此 C 代码较长,因为它是通用的,即使使用无符号整数的完整深度,C 也没有必要的进位逻辑将移位的高位转移到加法操作。

有关一些性能分析和比较,请参阅下面的问题。

问题/疑问

生成器的核心问题是找到合适的 num1 和 num2,这将使其迭代给定位深度的整个可能序列。在本节的最后,我附上了我的代码,它只是暴力破解它。它将在合理的时间内完成最多 12 位,您可能会等待所有 16 位(顺便说一下,有 5736 对可能的配对,前一阵子通过一夜完整搜索获得),您可能会得到几个 20 位如果你有耐心。但是 O(n^3) 真的很讨厌...

(谁会找到第一个完整的 32 位序列?)

出现的其他有趣的问题:

  • 对于 num1 和 num2,只有奇数值能够产生完整的序列。为什么?这可能并不难(我猜逻辑很简单),但我从来没有合理地证明过。

  • 沿 num1(相加值)有一个镜像属性,即如果 'a' 与给定的 'b' num2 给出一个完整的序列,那么 'a' 的 2 补码(在给定的位深度)具有相同的 num2 也是一个完整的序列。我只在我计算的所有完整世代中可靠地观察到这种情况发生。

  • 第三个有趣的特性是,对于所有 num1 和 num2 对,生成的序列似乎形成了正确的圆,也就是说,至少数字 0 似乎总是圆的一部分。如果没有这个属性,我的蛮力搜索将陷入无限循环。

  • 奖励:这个 PRNG 以前是否已知? (我只是重新发明了它)?

这里是蛮力搜索的代码(C):

#define BITS 16

#include "stdio.h"
#include "stdlib.h"

int main(void)
{
 unsigned int r;
 unsigned int c;
 unsigned int num1;
 unsigned int num2;
 unsigned int mc=0U;

 num1=1U;  /* Only odd add values produce useful results */
 do{
  num2=1U; /* Only odd eor values produce useful results */
  do{
   r= 0U;
   c=~0U;
   do{
    r=(((r>>(BITS-1)) & 1U)+r+r+num1)^num2;
    r&=(1U<<(BITS-1)) | ((1U<<(BITS-1))-1U); /* 32bit safe */
    c++;
   }while (r);
   if (c>=mc){
    mc=c;
    printf("Count-1: %08X, Num1(adc): %08X, Num2(eor): %08X\n", c, num1, num2);
   }
   num2+=2U;
   num2&=(1U<<(BITS-1)) | ((1U<<(BITS-1))-1U);
  }while(num2!=1U);
  num1+=2U;
  num1&=((1U<<(BITS-1))-1U); /* Do not check complements */
 }while(num1!=1U);

 return 0;
}

这表明它正在工作,在每次迭代后,如果它的序列长度等于或长于前一个,将输出找到的对。修改其他深度序列的BITS常量。

猎种

我做了一些与种子有关的图表。这是一张很好的图片,显示了所有 9 位序列长度:

白点是全长序列,X轴代表num1(加),Y轴代表num2(异或),点越亮,序列越长。其他位深度在模式上看起来非常相似:它们似乎都被分解为 16 个主要图块,其中两个模式通过镜像重复。瓦片的相似性并不完整,例如从左上角到右下角的对角线上方清晰可见,而对角线不存在,但对于全长序列,此属性似乎是可靠的。

依靠这一点,可以比之前的假设减少更多的工作,但这仍然是 O(n^3)...

性能分析

截至目前,可能生成的最长序列是 24 位:在我的计算机上,为此暴力破解完整的 24 位序列大约需要 5 个小时。对于Diehard 等真正的 PRNG 测试来说,这仍然只是马马虎虎,所以到目前为止,我宁愿采用自己的方法。

首先,了解生成器的作用很重要。这绝不会是一个非常简单的生成器,它的目标是快速生成体面的数字。在这个不需要乘法/除法运算的区域上,Galois LFSR 可以产生类似的性能。因此,如果我的生成器能够胜过这个生成器,它就可以派上用场了。

我执行的测试都是 16 位生成器。我选择了这个深度,因为它提供了一个有用的序列长度,而数字仍然可以分成两个 8 位部分,从而可以呈现各种位精确图以进行可视化分析。

测试的核心是寻找与之前和当前生成的数字之间的相关性。为此,我使用了 X:Y 绘图,其中上一代是 Y,当前是 X,两者都分解为低/高部分,如上所述的两个图表。我创建了一个能够实时绘制这些步骤的程序,这样也可以粗略地检查数字是如何相互跟随的,图表是如何填充的。这里显然只显示最终结果,因为生成器运行了完整的 2^16 或 2^16-1 (Galois) 循环。

字段说明:

  • 图像由 8x2 256x256 图形组成,总图像尺寸为 2048x512(以原始尺寸检查)。

  • 左上图只是证实确实绘制了一个完整的序列,它只是一个X = r % 256; Y = r / 256; 图。

  • 左下图显示每隔一个数字仅以与顶部相同的方式绘制,只是确认这些数字合理地随机发生。

  • 第二张图的第一行是高字节相关图。其中第一个使用上一代,下一个跳过一个数字(因此使用上第二代),依此类推,直到上第七代。

  • 从第二个开始,底行是低字节相关图,组织方式与上面相同。

伽罗瓦发生器,0xB400 抽头设置

这是在Wikipedia Galois example 中找到的生成器。它的性能不是最差的,但也绝对不是很好。

伽罗瓦发生器,0xA55A 分接头组

我发现的一个不错的伽罗瓦“种子”。请注意,16 位数字的低位部分似乎比上面的要好很多,但是我找不到任何能模糊高位字节的伽罗瓦“种子”。

我的生成器,0x7F25(adc),0x00DB(eor)种子

这是我最好的生成器,其中 EOR 值的高字节为零。限制高字节在 8 位机器上很有用,因为如果可以承受随机性性能的损失,则可以省略此计算以实现更小的代码和更快的执行。

我的生成器,0x778B(adc),0x4A8B(eor)种子

根据我的测量,这是质量非常好的种子之一。

为了找到具有良好相关性的种子,我构建了一个小程序,可以在一定程度上分析它们,与 Galois 和我的方法相同。 “质量好”的示例由该程序确定,然后我测试了其中几个并从中选择了一个。

一些结论:

  • 伽罗瓦生成器似乎比我的更严格。在所有相关图上,可以观察到明确的几何图案(有些种子会产生“棋盘”图案,此处未显示),即使它不是由线条组成的。我的生成器也显示了模式,但随着代数的增加,它们的定义越来越少。

  • Galois 生成器的一部分结果(包括高字节中的位)似乎本质上是刚性的,我的生成器似乎没有该属性。这是一个薄弱的假设,但可能需要更多的研究(看看在 Galois 生成器中是否总是如此,而不是我的其他位组合)。

  • 伽罗瓦生成器缺少零(最大周期为 2^16-1)。

  • 到目前为止,还不可能为我的生成器生成超过 20 位的良好种子集。

稍后我可能会更深入地研究这个主题,试图用 Diehard 测试生成器,但到目前为止,由于缺乏为它生成足够大种子的能力,这使得它变得不可能。

【问题讨论】:

  • 如前所述,生成器有一些弱点,但它的简单性弥补了这一点。相比之下,我能想到的最简单的经过验证的生成器是 Marsaglia 的 xorshift,而我能得到的最少的 6502 指令(仔细选择移位)是 26(42 字节,使用零页)。在 2**32-1 期间大八倍。可能会找到一个更简单的 24 位生成器。
  • 如果我对并行搜索的蛮力方法进行一些研究,并让它运行一夜,它很可能会找到一两个 24 位序列(毕竟有了这个,它只花了 10 分钟左右获得一些 20 位用于其他目的)。请注意,8 位只有 5 个字节(您需要扩展为 24 位)!不过,我会再深入一点,测试一些 LSFR 以查看差异。对于某些程序生成任务,这个小生成器可能相当不错,其中只需要短序列(甚至可能依赖于完全覆盖)来获得确定性但随机的结果。
  • 运行 5 小时,一对 24 位:0x000001U 用于 adc (num1),0x067241U 用于 eor (num2)。
  • 也许您想在内部循环中搜索加法器。与异或相比,它们会影响更多位,因此越早找到大位越好。
  • 在 2 个核心上运行 5 小时并没有产生任何结果。不过好主意,一个 1 字节的异或会在 8 位上提供更小的代码。什么都没丢失,我只能检查 0x01 和 0x81,还有 126 种可能性(用于异或)!或者有人可以想出一些像样的算法(如最初提出的那样:))?

标签: algorithm random prng


【解决方案1】:

这是某种形式的非线性移位反馈寄存器。我不知道它是否被这样使用,但它有点类似于线性移位反馈寄存器。阅读此Wikipedia 页面作为对 LSFR 的介绍。它们在伪随机数生成中经常使用。

但是,您的伪随机数生成器本质上是不好的,因为先前生成的数字的最高位与下一个生成的数字的最低位之间存在线性相关性。您将最高位 B 移出,然后新数字的最低位将是 XOR 或 B,加法常数 num1 的最低位和 XORed 常数 num2 的最低位,因为二进制加法是等价的排他或在最低位。您的 PRNG 很可能还有其他类似的缺陷。创建好的 PRNG 很难。

但是,我必须承认 C64 代码非常紧凑!

【讨论】:

  • 是的,C64 确实是它的主要目的,以尽可能少的字节/周期数获得合理的 PRNG。今天我做了一些实验,我以图形方式看到了你的意思:16位深度的X:Y图确实证明与我尝试的所有种子有很强的相关性(条带),只有当我将数字相隔大约8代时才会减少(然后X:Y 图恢复为随机)。我记得 LFSR 可能是其中的来源之一,但那是在 2008 年——从那以后互联网发生了很大变化! (绝对值得再次深入研究这个主题,希望有更好的东西)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-06-03
  • 2014-09-16
  • 2021-11-10
相关资源
最近更新 更多