造格解线性方程
-
当一个多元线性方程有一个比较小的解,那么就有可能可以使用基于格的启发式方法进行求解。这种方法的思路就是,在构造出的某个格中,尝试找到一个特定的最短向量。
-
我们所考虑的方法依赖于一个尚未被证明的假设,该假设涉及到格中小向量的分布或性质,因此这种方法本质上只是启发式,而非严格保证成功的算法。
-
举一个简单的例子来说明一下这个通用方法。假设我们希望求解下面的线性方程:其中x,y,z,w∈Z都是未知数,并且已知x,z,w的值比较小,而y可以是任意整数。
Ax+By+Cz=w
- 注意,此时我们能将这个方程,以及两个平凡方程x=x,y=y,写成一个
向量-矩阵方程:uB=v
(x,z,y)⎣⎢⎡100010ACB⎦⎥⎤=(x,z,w)
- 在上述矩阵B的各行向量是线性无关的,因此可以推出B构成了某个格L的一组基,进一步就有:
- 则就有u=(x,y,z)∈Z3,而目标向量v=(x,y,w)。
- 通过展开
向量-矩阵方程就可以很清晰的看到,目标向量是基矩阵的一个整数线性组合。因此,很容易证明目标向量就在这个格上。
- 当目标向量v和其反向量−v是该格中唯一的最短向量时,我们只需要通过求解该格的最短向量问题,即可解出原线性方程(对于这样一个小规模的格,这个问题可以高效求解),一旦得到v,就可以解出方程。
- 进一步就可以利用uB=v中的u,从而恢复出x,y,z,w的全部数值。
总结:通过上述说明,我们已知知道了造格的要点之一:目标向量必须由基矩阵通过整数线性组合。
造格解模线性方程
-
上面造格解线性方程这个想法,同样也可以应用到多元模线性方程中:
- 使用相同的例子,现在我们希望找到同余式的小解:Ax+By+Cz≡w mod( N),其中N是一些已知的整数。
- 可以先将同余式转换为多元线性方程:Ax+By+Cz=w+nN,其中n是未知的整数。此时只是构造了一个新的线性方程(定义在整数环上),其中引入了一个新的未知量。因此我们可以直接将前面介绍的方法应用到这个新的方程中。
- 具体来说可以构造相应的
向量-矩阵形式的方程:uB=v
(x,y,z,−n)⎣⎢⎢⎢⎡100001000010ABCN⎦⎥⎥⎥⎤=(x,y,z,w)
-
构造之后就可以尝试将(x,y,z,w)作为矩阵行生成的格中最短向量来就解。实际上,这里描述的方法可以用来说明一个广为人知的经验性理论(folklore结果):关于如何寻找模线性方程的小解。
- 考虑一般的多元模线性方程:a1x1+...+anxn≡0 ( mod N),这里所有的ai和N都是已知的,并且设X1,...,Xn>0为整数,使得∣x1∣≤X1,∣xn∣≤Xn
- 这个经验性结论就是:当Πi=1nXi≤N,未知量x1,...,xn是可以被解出的。
-
虽然这个结论多年来一直为人所知并被广泛使用,但对其的理论解释直到2008年由Hermann和May在论文Solvinglinearequations modulo divisors: Onfactoring given any bits.才写出。接下来简述一下他们两个的结果
- 假设gcd(an,N)=1,在论文中通过将原来的模方程两边同时乘−an−1 mod( N),将一般的模方程转换为一个等价的模方程。从而得到b1x1+...+bn−1xn−1≡xn mod( N),其中bi=−an−1ai mod( N),i=1,...,n
- 再将新的线性同余方程转换为线性方程:b1n1+...+bn−1xn−1=−xn−nN,其中n是整数
- 根据新的线性方程,可以造出一个
向量-矩阵方程
(x1,...,xn−1,n)⎣⎢⎢⎢⎢⎢⎢⎡10⋮00010000⋱00…………00⋮10b1b2⋮bn−1N⎦⎥⎥⎥⎥⎥⎥⎤=(x1,...,xn−1,xn)
D=⎣⎢⎢⎢⎢⎢⎢⎢⎡X1N0⋮000X2N00⋱……Xn−1N000⋮0XnN⎦⎥⎥⎥⎥⎥⎥⎥⎤
- 此时我们的目标向量就会变成v=(X1x1N,...,Xn−1xn−1N,XnxnN),此时每一个分量的界都是N。因此就有∣∣v∣∣≤nN
- 右乘一个对角矩阵后得到的新格L′,其体积为:vol(L′)=NΠi=1nXiN=Nn+1Πi=1nXi1
- 因为该新格是n维格,因此使目标向量满足闵科夫斯基界限的一个充分条件是:nN≤n(Nn+1Πi=1nXi1)n1,或者可以更简单的表述为Πi=1nXi≤N,这就是我们想要得到的结果。
- 注意:通过构造可知,目标向量作为最短向量的另一个判据预计也会满足。由于an−1很可能与N大小相当,因此每一个基向量都将大于N。同时,当Xn明显小于N时,每个基向量的大小也会增大。重新标记系数和变量后,总能选择一个Xn,使得所有基向量都大于nN。因此,当Πi=1nXi≤N,且之前说的假设对该格成立时,该解应该是可以被找到的。
Matrix warm up
1 2 3 4 5 6 7 8
| from Crypto.Util.number import * from random import randint from secret import flag
p = getPrime(256)
print("p =", p) print("c =", randint(1,p)*vector(Zmod(p), [i*getPrime(128) for i in flag]))
|
-
首先通过题目代码会发现,代码实现的是这样的一个过程:
- 随机生成一个长
256位的素数
- 构造一个
模p下的向量,向量的每个分量都是一个随机的128位的素数,乘上flag每个字符的ASCII码。再模上p
- 最后再乘一个随机数x,随机数的范围为x∈[1,p)
-
通过查看代码,我们不妨设:256位的素数为p,128位的素数分别为q1,q2,...,qn,flag的每个字符分别为c1,c2,...,cn,那么代码就可以表示为这样,乘积后向量每个分量表示为a1,a2,...,an,随机数为r:
r(q1c1,q2c2,...,qncn)≡(a1,a2,...,an) mod( p)
⎩⎪⎪⎪⎪⎨⎪⎪⎪⎪⎧rq1c1rq2c2......rqncn≡a1 mod( p)≡a2 mod( p)≡...≡an mod( p)⇒⎩⎪⎪⎪⎪⎨⎪⎪⎪⎪⎧q1c1q2c2......qncn≡ra1 mod( p)≡ra2 mod( p)≡ ......≡ran mod( p)
⎩⎪⎪⎪⎪⎪⎨⎪⎪⎪⎪⎪⎧q1c1q2c2......qncn=r−1 mod( p)a1+k1p=r−1 mod( p)a2+k2p= ......=r−1 mod( p)an+knp
- 这样我们就能构造一个向量矩阵方程,其中未知量构成向量,已知量构成矩阵(也被称为基矩阵):
- v=(k1,k2,...,kn,1)
- B=⎣⎢⎢⎢⎢⎢⎢⎢⎢⎡p0⋮00a10p⋮00a2......⋱………00⋮p0an−100⋮0pan⎦⎥⎥⎥⎥⎥⎥⎥⎥⎤
- 这样就有,如果向量含有r的倍数的话,那其实就不是最短向量了,所以我们需要将r提取出来,
vB=(rq1c1,rq2c2,...,rqncn)=r(q1c1,q2c2,...,qncn)
- 接下来我们就要先证明:r−1v这个向量中每个分量都是整数,这样才符合格的定义(也就是我们所构造的向量矩阵方程的结果在基矩阵所构成的这个格上),也就是我们的向量v以及r−1v都需要是整数。首先v中每个分量是整数,这个是很显然的。
- 当r−1是在实数域下的除法,那就会是分数,不是整数。
- 而r−1要是在模意义下的逆元,那就是整数,不是分数。
- 很显然r−1其实是在模意义下的逆元,所以r−1是一个整数。
- 接下来我们就需要判断我们所造的格最短向量的界限如何,我们采用判断最短向量的第二个标准:
- 如果v是最短向量,那么该向量就必须满足闵科夫斯基界限,首先我们可以确定n=40,根据题目结果向量的长度可以确定。
- 我们构造的矩阵是41×40的矩阵,但是最后一行ai并不会被p整除,也就是线性表出不了p。但是会存在一个幺模矩阵U,使得UB的结果最后一行为
0(也就是把最后一行消掉)
- 从而得到结论,这个格的维数其实就是dim(L)=40,但是L的行列式并不是∣∣∣∣∣∣∣∣∣∣∣∣p0⋮000p⋮00......⋱……00⋮p000⋮0p∣∣∣∣∣∣∣∣∣∣∣∣的结果,而是pn−1说是最后一行使得格变得更密集。(主要可能需要注意是在Zn中的格)
- 接下来我们就可以计算出这个格中的闵科夫斯基界限,也就是最短向量必须满足∣∣c∣∣≤40vol(L)401=x,x的比特位数满足
252

- 计算完闵科夫斯基界限之后,我们需要确定一下目标向量(q1c1,q2c2,...,qncn)的长度会不会在闵科夫斯基界限范围之内。但此时我们是不知道c1,...cn的,所以我们取c1=...=cn=255。然后随机生成
40个128比特的素数,粗略计算其界限。粗略计算是139远小于闵科夫斯基界限。所以我们可以由此判断,我们构造的目标向量其实就是这个格中的最短向量。

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| from Crypto.Util.number import * p = 98599605855990863958662823830259118267375283238963264208942746159972368798977 c = (82517335089994719678720440397443953324384116847602255948002639513101104313901, 23480188129189591225240386556937897340033310681946797651109881140283150083100, 15745298061459065444463644608757257986229810898393004395352776218436881867854, 84270435615636794052799481698023318073557231543509363804769535392387106088012, 96201177281893449383084244768818058633149464193199178668137778957874162588597, 43099159137504756238001406956750489932275012152611165794936270563202575868628, 87473505105909878544169429075138231076098232834027353502500829730748021802487, 40526868536247532569035007288241093602738034216676401111616964676485866001367, 44063716593961353493617098833905785233979061481957760044796936148478757568227, 73878801949618274670593476331042214019727738696724993431466987347899203829551, 49611915001043486900506624481468610085773333425213914365595384853737803495962, 73663397192166635927598415179372372658073408573520164162260288496381183157084, 51733741043276334909533397330697507522291127501835804656529603983330304127768, 64639581064354157349746518765309305166295198913786821156885357341832971588844, 90354658228040593281300119754247779870329701927324901681798697523188730973033, 81401987979903580862392404586229550887556894433599857234113189812553019212068, 65298685556525832301482171825516369217697144412414009394249099549396780614524, 92349134325759014555606171432361003009117804710882007512683431101244819357154, 39394732129711855468318778879498680882419172115836685759980312734935432566454, 89214625742350879812979703430208753053162953323218138936231425180728813794449, 87951032840368764288085902531369588071408620709369928883374860974551615905037, 60041643006614697975386569737424841551182444106423440342092211624111120068440, 52134076763481562368967639851739622158152428322184528366399356626269315562251, 94460273205082809035309971217762374872749243209992347460192832060386367068612, 81830195945772515483993492042731806886511046889048054326081378383609107573139, 59927341466053806949392223156717407467992244223474305030831956923880506151818, 8751919290908691103794561570673927187109339646175952750394326252264933529062, 54591713702851027898905443928525737279007535818923698240211592382012374983446, 2140612416897646831206582069941332263923242007855261278594389244676754980256, 52417324316592882505143461851975632709758110493345908564966799857008914624366, 67465336731188637403089317771383723385475068909628487431163316746247923651300, 27199905960890285751217134356216526365658255922621785367716717678817928449249, 54473441396851065734077065293435462643909804742918929675879047353889181542133, 80510976090103305519296069614208687998853259233221264088240018787064439260513, 8058130903768026149187035175827909499988897221461529508000687843925051502655, 67156889287780792927386938935000448930480556972869381436437517903446774800552, 72201690705594722108901018434840511628961928552271222882717149213381438349100, 84683979048378610860209511563266111942843164474629561376116322648815861493665, 84303105224028516998747055916665218580859682882704053081074579698916784229642, 47309573725991488669494386361197419696625258114057186352396638593272407412257) n = len(c) M = Matrix(ZZ,n+1,n)
for i in range(n): M[i,i]=p M[40,i]=c[i] L = M.LLL()
x = L[1] flag = [] for i in x: c = abs(i) for j in range(1,255): t1 = c % j t2 = c // j if t1 == 0 and isPrime(t2): flag.append(j)
for i in flag: print(chr(i),end="") """ NSSCTF{F1nd_@_s3cR3t_NUMBER_1s_eAsy_4_U} """
|
Matrix1
Matrix 1_v1
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| from Crypto.Util.number import * from os import urandom from secret import flag
def pad(msg, length): return msg + urandom(length - len(msg))
n = 10 p = getPrime(256) M1 = Matrix(Zmod(p), n, n, pad(flag[:len(flag)//2],n^2)) M2 = Matrix(Zmod(p), n, n, pad(flag[len(flag)//2:],n^2))
A = random_matrix(Zmod(p), n, n) B = M1*A*M2^(-1)
print("p =", p) print("A =", A.list()) print("B =", B.list())
|
Matrix 1_v2
Matrix 2