如何优雅地找到一个简单的 mod 函数的不动点?

Posted

技术标签:

【中文标题】如何优雅地找到一个简单的 mod 函数的不动点?【英文标题】:How to find the fixed points of a simple mod function elegantly? 【发布时间】:2016-05-04 05:59:11 【问题描述】:

这是一个函数,用C表示为:

uint32_t f(uint32_t x) 
    return (x * 0x156) ^ 0xfca802c7;

然后我遇到了一个挑战:如何找到它的所有不动点?

我知道我们可以测试每个uint32_t 值来解决这个问题,但我仍然想知道是否有另一种更优雅的方法 - 特别是当uint32_t 变为uint64_t 并且(0x156, 0xfca802c7) 是任意的一对值。

【问题讨论】:

你需要在求解方程x = x * 0x156 ^ 0xfca802c7时考虑溢出算法。 没有偶数固定点,因为如果 x 是偶数,那么返回值是奇数。这将问题减少了一半。还有很多事情要做。 我预计这个问题会很困难。乘法和异或在代数上是“不相容”的运算,这种逻辑并不容易推理。事实上,有一些密码和哈希函数依赖于混合加法、乘法和 XOR(例如 TEA、MurmurHash),因为它们很难分析。 我怀疑您将不得不将问题分解为位级别。例如,求解x[0] = (x[0] & a[0]) ^ b[0],然后求解x[1] = (x[0] & a[1]) ^ (x[1] & a[0]) ^ b[1],等等。“等”。部分会很困难。 我认为您可以通过归纳找到一种算法。我们已经解决了它模 2。从那里将它提升到模 4 算法:解模 2 是 1,这意味着只有 1 和 3 是可能的解模 4。这是 2 种可能性。试试看,我想只有一个有效。接下来,通过将(假设的)一种解决方案 mod 4 提升到两种可能性 mod 8 来进行 mod 8,依此类推。这应该有效。现在不在火车上的人可以完成这个算法并把它写得很好。我认为运行时间是线性的。 【参考方案1】:

Python 代码:

def f(x, n):
    return ((x*0x156)^0xfca802c7) % n


solns = [1]  # The one solution modulo 2, see text for explanation
n = 1
while n < 2**32:
    prev_n = n
    n = n * 2
    lifted_solns = []
    for soln in solns:
        if f(soln, n) == soln:
            lifted_solns.append(soln)
        if f(soln + prev_n, n) == soln + prev_n:
            lifted_solns.append(soln + prev_n)
    solns = lifted_solns

for soln in solns:
    print soln, "evaluates to ", f(soln, 2**32)

输出:150129329 计算结果为 150129329

算法背后的想法:我们试图找到x XOR 0xfca802c7 = x*0x156 modulo n,在我们的例子中是n=2^32。我这样写是因为右边是一个简单的模乘法,它与左边的表现很好。

我们要使用的主要属性是将x XOR 0xfca802c7 = x*0x156 modulo 2^(i+1) 的解简化为x XOR 0xfca802c7 = x*0x156 modulo 2^i 的解。另一种说法是x XOR 0xfca802c7 = x*0x156 modulo 2^i 的解决方案转换为以2^(i+1) 为模的一个或两个解决方案:这些可能性是x 和/或x+2^i(如果我们想要更精确,我们只是在寻找当我们说“解决方案”时,在 0,...,模数大小 - 1 之间的整数)。

对于i=1,我们可以很容易地解决这个问题:x XOR 0xfca802c7 = x*0x156 modulo 2^1x XOR 1 = x*0 mod 2 相同,这意味着x=1 是唯一的解决方案。从那里我们知道只有 1 和 3 是可能的解模 2^2 = 4。所以我们只有两个可以尝试。事实证明,只有一个有效。这就是我们当前的模 4 解决方案。然后我们可以将该解决方案提升到模 8 的可能性。等等。最终我们得到了所有这些解决方案。

备注1:此代码找到所有解决方案。在这种情况下,只有一个,但对于更通用的参数,可能不止一个。

Remark2:假设我没有出错,运行时间为 O(max[解决方案数,模数大小])。所以它很快,除非有很多很多的固定点。在这种情况下,似乎只有一个。

【讨论】:

【参考方案2】:

让我们使用Z3 solver:

(declare-const x (_ BitVec 32))
(assert (= x (bvxor (bvmul x #x00000156) #xfca802c7)))
(check-sat)
(get-model)

结果是'#x08f2cab1' = 150129329.

【讨论】:

你能解释一下这个神奇的代码是如何工作的吗? @Sayakiss 该代码仅包含问题定义。魔法发生在check-sat,它调用SAT求解器。 SAT 求解器是一个复杂的程序,你无法在一个简单的答案中描述一个人使用的所有策略。很可能发帖者也不了解细节,就像我们大多数人不知道我们选择的语言的编译器是如何实现的细节一样。 这篇博文描述了一个原始的 SAT 求解器:Understanding SAT by Implementing a Simple SAT Solver in Python。此答案中使用的 Z3 求解器可能支持更高级的策略,以便能够有效地解决更多问题。 虽然问题是 SAT 问题,但 Z3 几乎肯定会以比旋转布尔列表快得多的方式工作。事实上,这就是 SAT/SMT 求解器和逻辑编程语言的美妙之处:您可以描述问题并使用编写得非常出色且优化良好的通用解析器来找到问题的解决方案。 严格来说,Z3 不是 SAT 求解器。它可以在更多的情况下使用,官方类别是“SMT”求解器。但在这种特殊情况下,是的,您可以在 SAT 求解器中完成。位向量(例如,整数)是布尔值列表。 xor 和 mul 都可以通过对这些布尔值的逻辑运算来定义。然后等式将其转化为 SAT 问题,即 NPC 所涉及的布尔值(位)数。【参考方案3】:

由于位置 n 的输入位仅影响位置 ≥ n 的输出位,因此您知道可以通过选择第一位,然后选择第二位等来找到解决方案。

以下是如何在 C++ 中解决 64 位整数的问题(当然它也适用于 32 位整数):

#include <cstdint>
#include <cstdio>

uint64_t f(uint64_t x) 
    return (x * 0x7ef93a76ULL) ^ 0x3550e08f8a9c89c7ULL;


static void search(uint64_t x, uint64_t bit)

    if (bit == 0)
    
        printf("Fixed point: 0x%llx\n", (long long unsigned)x);
        return;
    

    if (f(x + bit) & bit) search(x + bit, bit << 1);
    if ((f(x) & bit) == 0) search(x, bit << 1);


int main()

    search(0x0, 1);

有了这个输出:

Fixed point: 0xb9642f1d99863811

【讨论】:

以上是关于如何优雅地找到一个简单的 mod 函数的不动点?的主要内容,如果未能解决你的问题,请参考以下文章

如何优雅地为程序中的变量和函数命名

如何优雅地忽略 MATLAB 函数的某些返回值

并发设计模式 | 两阶段终止模式:如何优雅地终止线程?

如何才能优雅地书写JS代码

如何调试 mod_rewrite 规则?

Python中如何优雅地使用switch语句