from time import time import random import os import sys import hashlib
import random as py_random
defsample(tau,eta,lens,B,s=None): lower = -B + tau * eta upper = B - tau * eta c_rows = [] for _ inrange(lens): row = [0] * 128 for idx in py_random.sample(range(128), tau): row[idx] = py_random.choice((-1, 1)) c_rows.append(row) c = matrix(ZZ, c_rows)
if s isNone: s = vector(ZZ, [py_random.randint(-eta, eta) for _ inrange(128)]) cs = c * s
# If one coordinate can never satisfy the bound for any y in [-B, B], # rejection sampling would not terminate. count = 0 whileTrue: y = vector(ZZ, [py_random.randint(-B, B) for _ inrange(lens)]) z = cs + y ifall(lower <= zi <= upper for zi in z): return (c_rows, list(z)), (list(s), list(y)) count+=1 if count>10000: raise Exception("Rejection sampling failed after 10000 attempts. Please check the parameters.") exit(0)
tau=39 eta=2 B=2*tau*eta s= vector(ZZ, [py_random.randint(-eta, eta) for _ inrange(128)])
s[0]=eta print("the first stage") cs=[] leaks=[] count=0 while count<15000: print("how many samples do you want to get? (0 to exit)") try: n=int(input()) except: print("[-] Invalid input!") continue
if n == 0: break pk,sk=sample(tau,eta,n,B,s) cs+=pk[0] leaks+=[bin(i&0xffffffff).count('1') for i in sk[1]] print(pk[1]) count+=n print("the second stage") count=0 while count<5000: print("which samples do you interest in more? (-1 to exit)") try: indices=input() indices = [int(item.strip()) for item in indices.split(',')] assertall(-1 <= idx < len(cs) for idx in indices) except: print("[-] Invalid input!") continue if -1in indices: break ans=[(cs[i],leaks[i]) for i in indices] print(ans) count+=len(indices)
print("the third stage") print("plz input the guess secret s:") try: guess_s = [int(item.strip()) for item ininput().split(',')] assertlen(guess_s) == 128 except: print("[-] Invalid input!") sys.exit(1) if guess_s == list(s): flag=open("./flag.txt").read().strip() print(f"[+] Congratulation! Here is your flag: {flag}") else: print("[-] Wrong guess! Better luck next time!")
分析
题目分析
这道题本质上是一个带泄露的稀疏秘密恢复问题,灵感来自 FLIP(Fast Lattice-based Instantiation and Primitive)加密方案。目标是通过服务端给的一些有限信息,把一个 128 维的秘密向量 s 给恢复出来。
参数设置
参数
值
含义
N
128
秘密向量的维度
tau
39
每行 c 的非零元素个数
eta
2
秘密 s 每个分量的范围 [-2, 2]
B
2×39×2 = 156
噪声 y 的范围 [-B, B]
秘密向量 s
1 2
s = vector(ZZ, [py_random.randint(-eta, eta) for _ inrange(128)]) s[0] = eta # s[0] = 2,这是已知条件
lower = -B + tau * eta # = -156 + 78 = -78 upper = B - tau * eta # = 156 - 78 = 78
y = vector(ZZ, [py_random.randint(-B, B) for _ inrange(lens)]) # y ∈ [-156, 156] z = cs + y ifall(lower <= zi <= upper for zi in z): # 要求 z ∈ [-78, 78] return (c_rows, list(z)), (list(s), list(y))
所以最终每个样本满足:
zi=ci⋅s+yi
其中:
zi∈[−78,78](拒绝采样保证)
yi∈[−156,156](均匀随机生成)
ci⋅s∈[−78,78](稀疏性保证)
这里多说一句,为什么拒绝采样要限制 z ∈ [-78, 78]?
如果 z∈[−78,78],那么 y=z−c⋅s∈[−78−78,78+78]=[−156,156],自动满足 y 的范围约束。换句话说,这个约束确保了:给定 z 之后,y 的有效范围是 [z−78,z+78],宽度只有 156(而不是完整的 313)。当 |z| 比较小的时候,这个范围跟 [-156, 156] 的交集更小,HW 约束就更容易把 y 唯一确定下来。