「学习笔记」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\) 的上界(临界值), 我们构造格:
在这个格中都是形如 \(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\)":
此外, 对于 \(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, 解出明文.

浙公网安备 33010602011771号