Loading

「学习笔记」coppersmith

Coppersmith

相对于前面的学习, 这次对原理更深入理解了一些. 文章感觉更侧重于对原理的理解.

为什么要用 coppersmith

对于求解形如 \(f(x) = a_0 + a_1 x + a_2 x^2 + a_3 x^3 + \cdots + a_{n - 1} x^{n - 1} + x^n\) 这样的多项式等于 \(0\) 的根, 我们可以使用牛顿迭代法来实现, 但如果我们需要求在模 \(M\) 的意义下 \(f(x)\) 的根, 那么牛顿迭代法就不能使用了, 而 coppersmith 就是将困难的模方程求解, 转化成容易的整数方程求解.

过程

我们假设 \(x_0\) 是模 \(M\) 意义下多项式等于 \(0\) 的一个根, 即 \(f(x_0) \equiv 0 \pmod{M}\), 但在整数意义下 \(f(x_0) \neq 0\), 我们可以考虑构造一个 \(g(x)\), 其中 \(g(x)\) 与 \(f(x)\) 在模 \(M\) 意义下有相同的根, 但是 \(|g(x_0)| < M\), 此时要满足 \(g(x_0) \equiv 0 \pmod{M}, |g(x_0)| < M\), 则 \(|g(x_0)| = 0\), 此时我们就又可以使用牛顿迭代法来找根了, 我们目前需要想办法找到这个 \(g(x)\).
我们可以选取有限个部分 \(\{f(x), xf(x), x^2f(x), x^3f(x) \cdots, M, M \cdot x, M \cdot x^2 \cdots \}\) 来构造 \(g(x)\), 这些分成两类, \(f(x), x \cdot f(x), x^2 \cdot f(x), x^3 \cdot f(x) \cdots\) 这一类可以保证 \(f(x)\) 的根是可以传承的, 而 \(M, M \cdot x, M \cdot x^2 \cdots\) 则是保证取模不会发生改变, 同时也可以帮助补充格.
但如果我们拿上面这一大串式子作为我们的 \(g(x)\), 那么会无法满足我们 \(g(x_0) < M\) 的要求, 想要实现 \(g(x_0) < M\), 我们就需要寻找满足条件的新系数, 构造出 \(g(x) = g_0 + g_1 x + g_2 x^2 + \cdots + g_nx^n\), 后面可以证明当 \(g_i\) 满足一定条件时可以保证 \(|g(x_0)| < M\), 而这里, 我们可以用格基规约来找到符合条件的系数.
我们设 \(B\) 是 \(x_0\) 的上界(临界值), 我们构造格:

\[\begin{bmatrix} M & 0 & 0 & \cdots & 0\\ 0 & MB & 0 & \cdots & 0\\ 0 & 0 & MB^2 & \cdots & 0\\ \cdots & \cdots & \cdots & \cdots& \cdots \\ 0 & 0 & \cdots & MB^{n - 1} & 0\\ a_0 & a_1B & \cdots & a_{n-1}B^{n-1} & B^{n}\\ \end{bmatrix} \]

在这个格中都是形如 \(uf(x) + v_1M + v_2MB + \cdot v_{n-1}MB^{n-1}\) 的, 通过格基规约得到的首个基向量 \((g_0, g_1B, g_2B^2, \cdots, g_nB^n)\), 他的范数最小, 当范数小于 \(\frac{M}{\sqrt{n+1}}\) 时, 便可以保证 \(g(x_0) < M\), 此时我们取 \(g_0, g_1, g_2, \cdots\) 来作为 \(g(x)\) 系数.
这里用柯西不等式来证明 "当范数小于 \(\frac{M}{\sqrt{n+1}}\) 时, 便可以保证 \(g(x_0) < M\)":

\[柯西不等式: (\sum_{i = 1}^{n}x_iy_i)^2 \le (\sum_{i = 1}^{n}x_i^2)(\sum_{i = 1}^{n}y_i^2)\\ 设 y_i = 1, 则 (\sum_{i=1}^{n}x_i)^2 \le n\sum_{i = 1}^{n}x_i^2\\ \sum_{i = 1}^{n}x_i \le \sqrt{n}\sqrt{\sum_{i = 1}^{n}x_i^2}\\ 而范数 |b| = \sqrt{g_0^2 + (g_1B)^2 + (g_2B^2)^2 + \cdots + (g_nB^n)^2} < \frac{M}{\sqrt{n+1}}\\ \left|\sum_{i = 0}^{n}g_ix^i\right| = \left|\sum_{i=0}^{n}\left[(g_iB^i)(\frac{x}{B})^i\right]\right|\\ 而由于 x \le B, 因此 \frac{x}{B} \le 1\\ 则 \left|\sum_{i=0}^{n}\left[(g_iB^i)(\frac{x}{B})^i\right]\right| \le \sum_{i=0}^{n}\left|g_iB^i\right| < \sqrt{n+1} |b| < \sqrt{n+1} \cdot \frac{M}{\sqrt{n+1}} = M\\ \]

此外, 对于 \(x\) 的临界值 \(B\) 也是很重要的, 我们要求 \(B < M^{\frac{1}{d} - \epsilon}\), 格的 \(\det\) 值对于范数也是有影响的, 具体可以看 大佬的笔记 学习.

一元 coppersmith 代码

sage 中有直接集成 coppersmith 的函数, 可以直接调用.

P.<x> = PolynomialRing(Zmod(N))
f = (x + h)^e - c # 根据实际情况填写, 这里只是一个示例
f = f.monic() # 首项系数为 1
root = f.small_roots(X = ..., beta = ..., epsilon = ...)
if root:
    for i in root:
        ...

small_roots() 函数中, X 是我们未知的 \(x\) 的可能最大值, 不一定非要是临界值, 但必须保证要比临界值 \(B\) 小, beta 表示 \(N\) 有一个因子 \(b\), 而 \(b > N^{beta}\), 使得多项式能够被这个因子整除; 而 epsilon 则是微调参数, epsilon 越大, X 的理论上限就越小, 范围更保守, 但速度更快, epsilon 越小, X 的理论上限就越大, 范围更大, 但复杂度会增加甚至搜索失败.
当然也有手写代码(参数需要自己手算, 感觉不如 small_roots):

def coppersmith(pol, N, beta, epsilon, X, m, t):
    polz = pol.change_ring(ZZ) # 先将多项式数字系数转移到整数环上.
    dd = pol.degree()
    x = polz.parent().gen() # .parent() 找到多项式所在的环, .gen() 找到未知数.
    g = []
    for i in range(m): # 构造 x^j * N^(m - i) * polz^i 使得能够被 N^m 整除, 扩大 m 可以扩大范围
        for j in range(dd):
            g.append(x^j * N^(m - i) * polz^i)
    for i in range(t): # 再加 t 个多项式, 提高效率
        g.append(x^i * polz^m)
    C = []
    for gi in g:
        C.append(gi(x * X).monomials()) # 将 x 替换为 x * X, 提取出变量的幂次部分.
    C = sorted(set(C), key=lambda m:m.degree()) # 先去重, 然后按照位次从低到高依次排序
    L = []
    for gi in g:
        poly = gi(x * X)
        l = [poly.monomials_coefficient(m) for m in C] # 将系数提取出来
        L.append(l)
    L = matrix(ZZ, L)
    L_LLL = L.LLL()
    roots = []
    for row in L_LLL:
        # 将 LLL 输出的短向量还原为多项式 h(x)
        h = sum(row[i] * C[i] / (X^C[i].degree()) for i in range(len(C)))
        h = h.change_ring(ZZ)
        try:
            for r, _ in h.roots(ring=RR): # h.roots() 返回的是元组(根, 重数)
                r = round(r)  # 四舍五入到最近的整数
                # 检查是否在范围内,并且确实是模 N 下的根
                if abs(r) < XX and pol(r) % N == 0:
                    roots.append(r)
        except:
            # 如果求根失败,跳过
            continue
    return list(set(roots))

二元 coppersmith

找到了代码, 原理有点复杂……

def small_roots(f, bounds, m=1, d=None):
    if not d:
        d = f.degree()

    R = f.base_ring()
    N = R.cardinality()

    f /= f.coefficients().pop(0)
    f = f.change_ring(ZZ)

    G = Sequence([], f.parent())
    for i in range(m + 1):
        base = N ^ (m - i) * f ^ i
        for shifts in itertools.product(range(d), repeat=f.nvariables()):
            g = base * prod(map(power, f.variables(), shifts))
            G.append(g)

    B, monomials = G.coefficient_matrix()
    monomials = vector(monomials)

    factors = [monomial(*bounds) for monomial in monomials]
    for i, factor in enumerate(factors):
        B.rescale_col(i, factor)

    B = B.dense_matrix().LLL()

    B = B.change_ring(QQ)
    for i, factor in enumerate(factors):
        B.rescale_col(i, 1 / factor)

    H = Sequence([], f.parent().change_ring(QQ))
    for h in filter(None, B * monomials):
        H.append(h)
        I = H.ideal()
        if I.dimension() == -1:
            H.pop()
        elif I.dimension() == 0:
            roots = []
            for root in I.variety(ring=ZZ):
                root = tuple(R(root[var]) for var in f.variables())
                roots.append(root)
            return roots

    return []

相关攻击

泄露 \(p\) 高低位

这应该算是最常见的攻击了, 一般为 \(p\) 泄露高位或低位, 然后需要构造 \(f = p_0 + x\), 进行 coppersmith 来得到未泄露部分, 从而得到 \(p\), 进而解出 \(d\).

明文泄露攻击

构造 \(f = (m_0 + x)^e - c\), 进行 monic() 后进行 coppersmith, 解出明文.

posted @ 2026-09-04 16:54  yi_fan0305  阅读(30)  评论(0)    收藏  举报