Falcon 是一种加密签名算法,于2017年11月30日提交给了 NIST 后量子密码学 项目,是目前四个标准后量子密码算法之一。它由 Pierre-Alain Fouque, Jeffrey Hoffstein, Paul Kirchner, Vadim Lyubashevsky, Thomas Pornin, Thomas Prest, Thomas Ricosset, Gregor Seiler, William Whyte, Zhenfei Zhang 设计。
Falcon 基于格基签名方案的 Gentry、Peikert 和 Vaikuntanathan 的理论框架 。其利用称为 "快速傅里叶采样" 的陷门采样器,在 NTRU格 上构建GPV方案。Falcon的基础是 NTRU 格上的短整数解问题(SIS),量子计算机目前还无法高效攻破此问题。
简而言之,如参考文档中演示的:Falcon = GPV 框架 + NTRU格 + 快速傅里叶采样
以下对Falcon的密钥生成与签名验签逻辑进行详细说明,针对2022年发布的进入NIST标准化流程的版本 (Falcon在后续还会有FIPS版,但这个版本截止写作时尚未发布),下面说的参考实现和参考文档都可以在NIST官网 下载到。
本文章不包含任何正确性证明和推理的内容,仅阐述算法设计本身。
背景知识
GPV框架
GPV框架可以用于在格上生成签名算法。
密钥生成 :公钥为一个用于生成q-阶格Λ \Lambda Λ 的矩阵A ∈ Z q n × m , m > n A \in \mathbb{Z}^{n\times m}_q,m>n A ∈ Z q n × m , m > n ;私钥是一个矩阵B ∈ Z q m × m B\in \mathbb{Z}^{m \times m}_q B ∈ Z q m × m ,用于生成一个对偶格Λ q ⊥ \Lambda_q^{\perp} Λ q ⊥ ,满足两个格之中元素的内积为0 mod q。同时也等价于B × A t = 0 B \times A^t = 0 B × A t = 0
签名 :一个短向量s,使得s A t = H ( m ) sA^t=H(m) s A t = H ( m ) ,H是一个哈希函数,s就是签名结果。短向量通过对偶格计算CVP构造:即首先通过式子c 0 A t = H ( m ) c_0A^t =H(m) c 0 A t = H ( m ) 计算c 0 c_0 c 0 ,然后在B B B 上计算对偶格上的相邻向量v v v (由对偶格的性质,有v A t = 0 vA^t=0 v A t = 0 ),令s = c 0 − v s=c_0-v s = c 0 − v ,即有s A t = c 0 A t − v A t = H ( m ) − 0 = H ( m ) sA^t = c_0A^t-vA^t=H(m)-0=H(m) s A t = c 0 A t − v A t = H ( m ) − 0 = H ( m ) 。
验签 :代入上面的式子验证,并要求s确实是一个短向量(Falcon实际实现中会校验L2范数小于等于1.17 q 1.17\sqrt{q} 1.17 q )
NTRU格
令ϕ = x n + 1 \phi=x^n+1 ϕ = x n + 1 , n = 2 k n=2^k n = 2 k , q q q 为正整数,f , g , F , G f,g,F,G f , g , F , G 均为Z [ x ] / ( ϕ ) \mathbb{Z}[x]/(\phi) Z [ x ] / ( ϕ ) 上的多项式,且满足:f G − g F = q m o d ϕ fG-gF =q\mod \phi f G − g F = q mod ϕ 。 其中f f f 模q q q 可逆,定义h = g f − 1 m o d q h=gf^{-1} \mod q h = g f − 1 mod q 。h h h 就是NTRU密码体制中的公钥。f , g , F , G f,g,F,G f , g , F , G 为私钥。f , g f,g f , g 通过随机生成,即使f , g f,g f , g 都较小,也很难找到f ′ , g ′ f^{\prime}, g^{\prime} f ′ , g ′ 满足h = g ′ f ′ − 1 h=g^{\prime}{f^{\prime}}^{-1} h = g ′ f ′ − 1 ,这就是NTRU的安全性假设。
快速傅里叶采样
陷门采样函数的目的是:给定矩阵A A A ,陷门T T T ,目标向量c c c ,计算短向量s s s 满足s t A = c s^{t}A=c s t A = c 。实际上就是在找格上的相近向量,可以规约到CVP。这里采用的是快速傅里叶采样 的算法。
标准细节
在NTRU格上应用GPV框架
首先说明:对于多项式f f f ,当我们把它写成一个n × n n \times n n × n 矩阵时,每一行为x i f m o d ϕ , i = 0.. n − 1 x^if\mod \phi, i=0..n-1 x i f mod ϕ , i = 0.. n − 1 。这样做的原因是:向量转换成的方阵的乘法和加法可以被映射回对应的多项式乘法与加法上,矩阵形式与多项式形式对于加法和乘法运算是同构的。在这个形式下,Falcon算法以矩阵的形式表述公钥与私钥:
公钥A = [ − h I n q I n O n ] A=\left[\begin{array}{c|c}-h & I_n\\ \hline qI_n &O_n\end{array}\right] A = [ − h q I n I n O n ] ,其中I I I 为单位矩阵,O O O 为全零矩阵,h = g / f m o d ϕ m o d q h=g/f\mod \phi \mod q h = g / f mod ϕ mod q
私钥B = [ g − f G − F ] B=\left[\begin{array}{c|c}g&-f\\ \hline G & -F\end{array}\right] B = [ g G − f − F ] ,其中f G − g F = q m o d ϕ fG-gF =q\mod \phi f G − g F = q mod ϕ
签名包含一个nonce值r r r ,以及一对多项式( s 1 , s 2 ) (s_1, s_2) ( s 1 , s 2 ) 构成。满足s 1 + s 2 h = H ( r ∣ ∣ m ) s_1+s_2h=H(r||m) s 1 + s 2 h = H ( r ∣∣ m ) 。实际计算中,因为s 1 s_1 s 1 可以由和s 2 s_2 s 2 计算出,只存储( r , s 2 ) (r,s_2) ( r , s 2 ) 作为签名。
注意这里描述的只是理论状态,按参考文档描述:实际上存储的公私钥应当经过编码和预处理,减小了体积、便于计算的版本。
私钥sk = ( B ^ , T ) \textbf{sk}=({\hat{\textbf{B}}}, \textbf{T}) sk = ( B ^ , T ) :
B ^ = [ F F T ( g ) − F F T ( f ) F F T ( G ) − F F T ( F ) ] \hat{\textbf{B}}=\left[\begin{array}{c|c}FFT(g)&-FFT(f)\\ \hline FFT(G) & -FFT(F)\end{array}\right] B ^ = [ FFT ( g ) FFT ( G ) − FFT ( f ) − FFT ( F ) ]
T \textbf{T} T 是后续描述的Falcon树结构。
但参考实现中似乎并没有这么做。参考实现中的私钥就仅仅只是简单的f , g , F f,g,F f , g , F 挨个塞进了数组里(G G G 被忽略)。
而公钥pk = h \textbf{pk}=h pk = h ,无论是文档中还是参考实现中是都是如此。
密钥对生成
对高斯分布采样生成f , g f,g f , g 两个多项式
从前面的说明可以看到,f , g f,g f , g 都是私钥的一部分,为了降低私钥长度,它们都应当是足够小的向量,同时也应当满足后续的计算要求。Falcon实现中为保证这些性质,进行了以下的具体检查:
采样的分布本身保证它们是足够小的向量(E [ ∥ ( f , g ) ∥ ] = 1.17 q \mathbb{E}[\|(f,g)\|]=1.17\sqrt{q} E [ ∥ ( f , g ) ∥ ] = 1.17 q ),后续也会对这个值进行校验
两个多项式的系数和模2均为1(即都是奇数),以此支持后续解NTRU方程时的运算。
每一项系数需要在特定大小范围内:为了满足后续的编码要求
校验∥ ( f , g ) ∥ < = 1.17 q \|(f,g)\|<=1.17\sqrt{q} ∥ ( f , g ) ∥ <= 1.17 q
校验γ = m a x { ∥ ( g , − f ) ∥ , ∥ ( q f ⋆ f f ⋆ + g g ⋆ , q g ⋆ f f ⋆ + g g ⋆ ) ∥ } ≤ 1.17 q \gamma = max \{\|(g,-f)\|,\|(\frac{qf^\star}{ff^{\star}+gg^\star},\frac{qg^\star}{ff^\star+gg^\star})\|\} \le 1.17\sqrt{q} γ = ma x { ∥ ( g , − f ) ∥ , ∥ ( f f ⋆ + g g ⋆ q f ⋆ , f f ⋆ + g g ⋆ q g ⋆ ) ∥ } ≤ 1.17 q :满足这条式子时,才能计算出符合要求的短签名。
如果不符合上述要求重新生成,期望的生成次数为4左右。
在计算过程中,乘法都通过FFT和预计算的模q q q 常数进行加速。
通过NTRU方程计算F , G F,G F , G
解此方程的基本思路如下:
根据同构结构 Z n ≅ ( Z [ x ] / ( x 2 + 1 ) ) n / 2 ≅ . . . ≅ ( Z [ x ] / ( x n / 2 + 1 ) ) 2 ≅ Z [ x ] / ( x n + 1 ) \mathbb{Z}^n \cong (\mathbb{Z}[x]/(x^2+1))^{n/2} \cong ... \cong (\mathbb{Z}[x]/(x^{n/2}+1))^{2} \cong \mathbb{Z}[x]/(x^n+1) Z n ≅ ( Z [ x ] / ( x 2 + 1 ) ) n /2 ≅ ... ≅ ( Z [ x ] / ( x n /2 + 1 ) ) 2 ≅ Z [ x ] / ( x n + 1 ) ,将f , g f,g f , g 所在的Z [ x ] / ( ϕ ) \mathbb{Z}[x]/(\phi) Z [ x ] / ( ϕ ) 环进行分解,直到获得Z \mathbb{Z} Z 上的元素。
通过扩展GCD算法求解NTRU方程。
逐级上推,将求解结果还原到原始环上
注意在计算的过程中,最终得到的元素往往是超大的整数,因此实际计算中采用CRT的方式进行存储与计算。
F , G F,G F , G 计算完成后,我们就已经拥有了构建私钥所需要的全部信息。
构建Falcon树
计算B ^ = [ F F T ( g ) − F F T ( f ) F F T ( G ) − F F T ( F ) ] \hat{\textbf{B}}=\left[\begin{array}{c|c}FFT(g)&-FFT(f)\\ \hline FFT(G) & -FFT(F)\end{array}\right] B ^ = [ FFT ( g ) FFT ( G ) − FFT ( f ) − FFT ( F ) ] 与Falcon树T \textbf{T} T 。前者可以直接在已有信息基础上计算得到,后者在这一节中阐明。
计算G = ← B ^ × B ^ ⋆ \textbf{G}=\leftarrow \hat{\textbf{B}} \times \hat{\textbf{B}}^{\star} G =← B ^ × B ^ ⋆
对G \textbf{G} G 其进行LDL分解得到L , D \textbf{L},\textbf{D} L , D ,其中L \textbf{L} L 为下三角矩阵,D \textbf{D} D 为对角矩阵(这是LDL分解的性质决定的)
对D \textbf{D} D 的左上左下两个块分别做fft拆分(就是按FFT Cooley-Turkey算法的思路拆成两个多项式),构建两个新矩阵G 0 , G 1 \textbf{G}_0,\textbf{G}_1 G 0 , G 1
当前节点的值为L 10 \textbf{L}_{10} L 10 (即左下角有值的部分),子节点为对G 0 , G 1 \textbf{G}_0,\textbf{G}_1 G 0 , G 1 进行第二步的递归操作得到的子树,边界下的叶节点为D \textbf{D} D 的左上左下两个块
对生成的树的叶节点值做规范化(norm),让其方差缩小到指定的范围。做法是v = σ / v v=\sigma/\sqrt{v} v = σ / v
这一步的目的是在预处理的同时,对矩阵进行递归压缩,尽量减小实际占用的空间大小。
注意虽然文档中将这一步写在了密钥生成环节,但参考代码的密钥对生成实现中并没有这一步,而是放在了签名中。
签名
生成nonce,并将其与信息放在一起生成r ∥ m r\|m r ∥ m ,通过HashToPoint算法(算法不复杂,就是通过Shake256挨个生成每个点)生成一个环上的多项式c c c 。
计算t ← ( − 1 q F F T ( c ) ⋅ F F T ( F ) , − 1 q F F T ( c ) ⋅ F F T ( f ) ) \textbf{t}\leftarrow (-\frac{1}{q}FFT(c)\cdot FFT(F), -\frac{1}{q}FFT(c)\cdot FFT(f)) t ← ( − q 1 FFT ( c ) ⋅ FFT ( F ) , − q 1 FFT ( c ) ⋅ FFT ( f )) 。去掉FFT相关的加速运算环节,可以理解为计算的就是( − c F q , − c f q ) (-\frac{cF}{q},-\frac{cf}q) ( − q c F , − q c f ) ,实际上就是将( c , 0 ) (c,0) ( c , 0 ) 这个向量映射到私钥矩阵生成的格上,再用q做norm。
通过快速傅里叶采样算法,得到t \textbf{t} t 在格上的接近向量z \textbf{z} z ,则将其相减就可以得到最后的我们需要的短向量( s 1 , s 2 ) (s1,s2) ( s 1 , s 2 ) 。具体流程如下:
这个算法的核心思路是:仍然采用之前所述的环同构结构类似的思路,将Q [ x ] / ( x n + 1 ) \mathbb{Q}[x]/(x^n+1) Q [ x ] / ( x n + 1 ) 上的数据通过FFT逐级分解,最终得到Q \mathbb{Q} Q 上的的结果。对其做采样后,再将采样的结果逐级通过FFT的算法合并,最终得到Z [ x ] / ( x n + 1 ) \mathbb{Z}[x]/(x^n+1) Z [ x ] / ( x n + 1 ) 上的采样结果。此算法在计算过程中,运用到了前一节所述的Falcon Tree结构。
具体的采样流程很简单:生成一个μ = t , σ μ=\textbf{t}, \sigma μ = t , σ 为当前节点的值(即为LDL分解后L \textbf{L} L 矩阵的左下角部分)的离散高斯分布,在其中采样,并对其结果做规范化,以概率性产生一个符合要求的格上的接近向量。
对其L2范数做校验,要求小于一定阈值,它们就是签名的结果。如果L2范数不符合要求,则重新采样做生成。
对签名做压缩
验签
验签环节相对来说最简单:
生成nonce,并将其与信息放在一起生成r ∥ m r\|m r ∥ m ,通过HashToPoint算法(见前)生成一个环上的多项式c c c 。
从压缩过的签名解压出s 2 s_2 s 2 ,并计算出对应的s 1 = c − s 2 h m o d q s_1 = c-s_2h \mod q s 1 = c − s 2 h mod q
校验∥ ( s 1 , s 2 ) ∥ \|(s_1,s_2)\| ∥ ( s 1 , s 2 ) ∥ 是否在合法范围内(生成的必定为短签名,对应上述签名流程中的第四步)
上述计算和校验都通过,则认为验签成功,否则验签失败
可以看出,整个签名算法的核心是:不知道f , g f,g f , g ,仅知道h h h 的情况下,想计算出满足条件的( s 1 , s 2 ) (s_1,s_2) ( s 1 , s 2 ) 是困难的,这就是前面说的SIS(短整数解困难问题)。
算法还有很多其他细节,诸如计算上的优化技巧,编码、压缩的格式,但它们并非Falcon算法的核心所在。读者可以自行参考参考文档与实现,以了解其中细节。