愿望单

初等代数

代数课没有好好听,注定度过失败的一生了。

Montgomery 模乘

在真实计算机中,不同带余除法的代价不同:比如除以 power of 2 非常便宜。Naive 的模乘实现需要用模数作为被除数,代价不好控制。Montgomery 模乘可以将计算过程中的除法替换为另一个被除数。

𝑎,𝑏𝑞 ,需要计算 𝑎𝑏𝑞 ,如果 𝑞 中的元素都使用最小非负代表元表示,这就是要计算 (𝑎𝑏)mod𝑞 。假设有另一个常数 𝑅 满足 𝑅>𝑞 而且两者互素。假设除以 𝑅 的代价较小。

𝑥𝑞 的 Montgomery 形式 𝑥̃=𝑥𝑅𝑞 。因为 𝑅 的带余除法代价小,计算 mod𝑅 的代价很小。需要注意的是 mod𝑞 的代价较大。只要能够避免这两个操作,就能得到更快的模乘实现。

𝑅 𝑅 𝑞 里面的逆元, 𝑞 𝑞 𝑅 里面的逆元。

𝑎𝑏̃=𝑎̃𝑏̃𝑅mod𝑞=(𝑎̃𝑏̃(𝑎̃𝑏̃𝑞mod𝑅)𝑞)𝑅mod𝑞

注意到 𝑥

(𝑥(𝑥𝑞mod𝑅)𝑞)mod𝑅=(𝑥(𝑥𝑞𝑞))mod𝑅=0

所以 (𝑎̃𝑏̃(𝑎̃𝑏̃𝑞mod𝑅)𝑞) 一定是 𝑅 的倍数,在 中计算 ()𝑅mod𝑞 相当于计算 𝑅mod𝑞

𝑎𝑏̃=𝑎̃𝑏̃𝑅mod𝑞=(𝑎̃𝑏̃(𝑎̃𝑏̃𝑞mod𝑅)𝑞)𝑅mod𝑞=𝑎̃𝑏̃(𝑎̃𝑏̃𝑞mod𝑅)𝑞𝑅mod𝑞

最后我们可以通过 Bound (𝑎̃𝑏̃(𝑎̃𝑏̃𝑞mod𝑅)𝑞)/𝑅 来消除最外面的 mod𝑞 :注意到因为 𝑅>𝑞 ,这个值一定在 (𝑞,𝑞) 之间。所以我们只需要比较这个值是否是负数,如果是就加上 𝑞 就好了。

实现中的一些细节:

Gemini 给出的伪代码:

#include <stdint.h>

// 4236238847 is -q^(-1) mod 2^32
#define QINV_NEG 4236238847u 
#define Q 8380417u

uint32_t montgomery_mul(uint32_t a_mont, uint32_t b_mont) {
  uint64_t prod = (uint64_t)a_mont * b_mont;
  
  // Calculate the multiple of Q needed to clear the lower 32 bits
  uint32_t t = (uint32_t)prod * QINV_NEG;
  
  // Add the multiple of Q and shift out the lower 32 bits (which are now guaranteed to be 0)
  uint64_t u = prod + (uint64_t)t * Q;
  uint32_t res = u >> 32;
  
  if (res >= Q) {
      res -= Q;
  }
  
  return res;
}

逻辑

模型大小

一阶逻辑有 Lowenheim-Skolem. 事实上 LS 跟完备性非常相关。Notably,以下两种逻辑因为能够控制模型大小,所以丧失了完备性:

通讯

Rectangle property

在 MPC 设定下,如果暂时不考虑 Active malicious adversary 的话,节点的私有状态 (𝑥𝑖,𝑟𝑖) i.e. 输入和随机性可以确定性决定 Transcript 𝑇 。将这一映射称为 Π .

Π1 具有 “Rectangle property”, 𝑇𝒯︀,Π1(𝑇) 是一个 Rectangle:

Π1(𝑇)=𝑖=1𝑛𝜋𝑖(Π1(𝑇))

可以理解成,如果有任意两个所有 Party 的私有状态集合 𝑆1,𝑆2 满足 Π(𝑆1)=Π(𝑆2)=𝑇 ,那么可以把 𝑆1 𝑆2 任意组合(每个 Party 任意在两者中选一个),那么最终 Transcript 依旧是 𝑇

证明把 T 当成一个消息列,归纳。

这有两个很直接的后果:

  1. 如果至少有 3 个 Party,那么不存在在纯广播信道上的 𝑡1 的计算元素和的协议。原因是如果存在,假设 Party 1 semi-honest,Fix (𝑥1,𝑟1) ,注意到只要 {𝑖2}𝑥𝑖 不变,那么 Party 1 的 View 也不变。这是纯广播,因此 Transcript 不变。只要有限域大小大于 2, 总能选出来两个不同的元素 𝑒0,𝑒1 ,假设他们是 Party 2 和 Party 3 的输入,那么交换他们,Party 1 View 不变,所以把 Party 3 的输入从 𝑒1 改成 𝑒0 ,Party 1 View 依旧不变,但是结果变了,矛盾。

  2. 不存在 2-Party 计算 AND 的协议。如果存在, 𝑥1=0,𝑥2=1 𝑥1=1,𝑥2=0 都会输出 0 , 所以 Adversary View 相同,只有两个 Party 所以 View 包含整个 Transcript,整个 Transcript 也就相同。所以 𝑥1=𝑥2=1 也应该产生一样的 Transcript,但是这会产生不同的输出,矛盾。

相关历史综述见 Gemini DeepResearch。AI 幻觉有风险,4.6 Opus 就给我幻觉了几个 Ref。不过 DeepResearch 至少给链接了。

Reed-Solomon 纠错

Reed-Solomon 如果有 2𝑡 冗余可以纠 𝑡 个错 (Also, Shamir Secret Sharing).

考虑有限域 GF(𝑞) 上的 Reed-Solomon 码,共 𝑛2𝑡 个数据, 2𝑡 个冗余,总共采样点 𝑛 个。现在要找出来其中最多 𝑡 个错的点。如果我们忽略多项式的其他结构,认为这些采样点是任选的,下面陈述的方法是找到这些错误点的标准方法 Berlekamp-Welch algorithm。

Berlekamp-Welch

假设 Underlying polynomial 是 𝐹(𝑥) ,Claim 存在一个多项式 𝐸(𝑥) ,满足 𝑥𝑖incorrect𝐹(𝑥𝑖)𝑦𝑖𝐸(𝑥𝑖)=0如果 𝑥𝑖 不是错误的点,对 𝐸(𝑥𝑖) 没有要求。因为最多 𝑡 个错误,所以我们可以要求 deg𝐸(𝑥)=𝑡 ,并且最高次项系数为 1 𝐸(𝑥) 剩下 𝑡 个系数未知。

𝐸(𝑥𝑖)𝐹(𝑥𝑖)=𝐸(𝑥𝑖)𝑦𝑖 对所有采样点成立。这给我们提供了 𝑛 个方程, deg𝐹(𝑥)=𝑛2𝑡1 ,令 𝑄(𝑥)=𝐸(𝑥)𝐹(𝑥) deg𝑄(𝑥)=𝑛𝑡1 ,线性系统 𝑄(𝑥𝑖)𝐸(𝑥𝑖)𝑦𝑖 一共 𝑛 个方程, 𝑛 个未知数,可以解出 𝑄(𝑥) 𝐸(𝑥) ,之后 𝐹(𝑥)=𝑄(𝑥)𝐸(𝑥) 或者 Chien search. 复杂度 𝒪︀(𝑛3)

Alternatively, 这是一个 Rational polynomial interpolation 问题, 𝑄(𝑥𝑖)𝐸(𝑥𝑖) 经过 𝑛 个点。wo/ FFT 复杂度 𝒪︀(𝑛2) , w/ FFT 复杂度 𝒪︀(𝑛log2𝑛) 。(但我都不会)

Generator view vs. evaluation view

有两种 (somewhat equivalent) 看待 Reed-Solomon code 的方式。

SSS 里常见的构造对应 RS code 的 Evaluation view,这也是 Reed & Solomon’s original view: 数据在 pre-defined points of evaluation. RS code 目前实现中常见的方法是 Generator view (BCH view): 数据在 polynomial coefficients 上。两者之间的转换就是一次 NTT.

𝑚 为消息长度, 2𝑡 为冗余, 𝑛=𝑚+2𝑡 为 Codeword 长度。

对于 Generator view 的 RS code,预先选定的是一个 Generator polynomial 𝐺(𝑥) deg𝐺(𝑥)=2𝑡 。Underlying field 𝔽 满足 |𝔽|=𝑛+1 ,要求 𝐺(𝑥)𝑥𝑛1

Generator polynomial 的常见构造方式是选定一个 𝔽 的 primitive element 𝛼 和任意 𝑟 , 𝐺(𝑥)=𝑖=12𝑡(𝑥𝛼𝑖+𝑟)

对于 𝑚 个消息 𝑟0,,𝑟𝑚1 ,Message polynomial 是 𝑀(𝑥)=𝑖=0𝑚1𝑟𝑖𝑥𝑖 。有两种编码方式得到 Codeword 𝐶(𝑥)

Sidenote: 因为消息总能做 Padding,尤其是 Systematic encoding 里面消息 Padding 直接对应 Codeword padding,所以都可以不传这部分 Codeword。因此 𝑛=|𝔽|1 的限制事实上限制的是最大 Block length。Evaluation view 也有类似的限制,这个限制来自于 Evaluation point 的个数。但是因为可以选任意 𝔽 元素当作 evaluation point,所以限制比 Generator view 要大 1. (Related: Extended RS & Doubly-extended RS, 但是我没看懂这些)

Generator view decoding

在 Generator view 构造中,Berlekamp-Welch 无法直接使用。Berlekamp-Massey 是一个求最小 LFSR 的算法,在 RS code 中可以用于在 generator view 的构造中替代 Berlekamp-Welch 做错误定位。

注意到,两种 encoding 如果理解成 𝑀(𝑥)𝐶(𝑥) 𝔽[𝑥]𝑚+2𝑡1 多项式空间里的函数,都是可逆线性的。因为 𝑀(𝑥) 是一个 degree-m 子空间,因此所有无错误 Codeword 也是一个 degree-m 子空间。错误校验是判定一个多项式是否在这个空间内。

在这个多项式空间上取以下内积结构:系数乘积的和 (i.e. 选取 {𝑥𝑖} 做基映射到 𝔽𝑛 )。 𝐶(𝑥) 。Codeword 空间的正交补空间 degree 是 𝑚+2𝑡𝑚=2𝑡 ,因此可以通过 2𝑡 个多项式来 span 这个正交补空间。

定义 Parity-check polynomial 𝐻(𝑥)=𝑥𝑛1𝐺(𝑥) deg𝐻(𝑥)=𝑛2𝑡=𝑚 ,令 𝐻(𝑥)=𝑥deg𝐻(𝑥)𝐻(𝑥1) 𝐻(𝑥) 的 Reciprocal polynomial。 {𝐻(𝑥)𝑥0,𝐻(𝑥)𝑥,,𝐻(𝑥)𝑥2𝑡1} 是正交补空间的一组基。

他们是正交补空间的基的简短证明:这组基显然线性无关。同样, {𝐺(𝑥)𝑥0,𝐺(𝑥)𝑥,,𝐺(𝑥)𝑥𝑚1} 是 Codeword 空间的一组基。为了证明他们是正交补,只需要证明两组基中任选向量内积为 0 即可。

注意到, 𝐺(𝑥)𝑥𝑖,𝐻(𝑥)𝑥𝑗 正是表达式 𝐺(𝑥)𝐻(𝑥)mod(𝑥𝑛1) deg𝐻(𝑥)+𝑗𝑖 次项系数。而 𝐺(𝑥)𝐻(𝑥)=𝑥𝑛1

对于使用 Powers of primitive root 构造的 𝐺(𝑥) ,有更简单的办法给一组基。注意到任何 𝐺(𝑥) 的根 𝛼𝑖 都是 𝐶(𝑥) 的根,所以令 𝐻𝑖(𝑥)=𝑗=02𝑡𝑎𝑖𝑗𝑥𝑗 ,那么 𝐻𝑖(𝑥),𝐶(𝑥)=𝐶(𝛼𝑖)=0 ,因此 {𝐻𝑖(𝑥)}{𝑖=𝑟+1𝑟+2𝑡} 是一组正交补空间的基。

上述构造出的正交补空间的基不一定正交,但是我们还是可以把 𝐶(𝑥) 投影上去,如果正确的编码是 𝐶0(𝑥) ,投影得到的结果是 𝐸(𝑥)=𝐶(𝑥)𝐶0(𝑥) 也就是错误项在选定基下的坐标。这一坐标通常被称为 Syndrome {𝑆𝑖}𝑖=12𝑡

在使用 Power of primitive root 构造的 𝐺(𝑥) 的情况下,投影直接就是求值,因此不用存储 𝐻 矩阵。

对于一般的 Parity-check matrix 构造,使用 Syndrome 进行错误定位可以利用 Meggitt Decoder: 一个查找表,加上 RS code 在 generator view construction 下是一个 Cyclic code 的特性检查。

Berlekamp-Massey

如果我们使用 Powers of primitive root 构造 𝐺(𝑥) 和 parity-check matrix 𝐻 (as a Vandermond matrix of 𝛼,𝛼2𝛼2𝑡 ),有更高效的做法。

假设 𝐸(𝑥) 里面实际有 𝜈𝑡 个错误 {𝑒𝑗}𝑗=1𝜈 ,位置分别在 𝑖1,,𝑖𝜈 次项,也就是:

𝐸(𝑥)=𝑗=1𝜈𝑒𝑖𝑗𝑥𝑖𝑗

因此 𝑆𝑖=𝐸(𝛼𝑖) 。现在的目标是找到 {𝑖𝑗}𝑗=1𝜈 。定义 Error locator polynomial:

Λ(𝑥)=𝑗=1𝜈(1𝑥𝛼𝑖𝑗)

Λ(𝑥) 的根为 {𝛼𝑖𝑗}{𝑗=1𝜈} 。假设 Λ(𝑥) 展开为 1+Λ1𝑥+Λ2𝑥2++Λ𝜈𝑥𝜈 ,可以证明对于任意给定的正整数 𝑗2𝑡𝜈 (见 Wikipedia):

𝑆𝑗+𝜈+Λ1𝑆𝑗+𝜈1+Λ2𝑆𝑗+𝜈2++Λ𝜈𝑆𝑗=0

注意到这等价于一个需要找到一个最短的 𝜈 -stage LFSR 生成 𝑆1,𝑆2,,𝑆2𝑡 。注意:对于一个 Recurrent sequence,如果我们知道他的 Generating LFSR has degree 𝑡 ,那么可以通过前 2𝑡 项确定 Generating LFSR。但是反过来不成立: 存在 2𝑡 个元素序列无法被一个 degree 𝑡 的 LFSR 生成。

BM 算法的直觉是一个一个试,看看目前猜的 LFSR 有没有出错。保存一个上一次出错的时候的 LFSR,以及当时出的错 𝑏 ,如果这次出错 𝑑 ,那么把上次的 LFSR ×𝑑𝑏 减掉就可以用来修这次的错。Pseudocode in Rust format:

// Input: S: Vec<Field> of length 2t
let mut lambda = poly!(1); // C in most texts about BM
let mut last_lambda = poly!(1); // B in most texts about BM
let mut last_update = -1;
let mut last_dis_inv = 1;

for k in 0..2t {
  // Loop invariant:
  // last_lambda when evaluates by S[..last_update
  // will generate discrepancy last_dis_inv^(-1)
  let discrepancy = S.rev().skip(2 * t - k - 1) // Start from S[k]
    .zip(lambda.coeffs()) // Coeffs from lowest degree to highest
    .map(|(s, l)| s * l)  // Field multiplication
    .sum();               // Field addition
  if discrepancy == 0 { continue; }
  let tmp = lambda.clone();
  let update = (discrepancy * last_dis_inv)
    * last_lambda * poly!(x^(k - last_update));
  // Because of the invariant, the update polynomial
  // will evaluates to exactly discrepancy at S[..k]
  lambda -= update;

  if lambda.degree() > tmp.degree() {
    // Degree updated
    last_lambda = tmp;
    last_update = k;
    last_dis_inv = 1 / discrepancy;
  }
}

lambda / lambda.coeffs().first()
// So that the constant term is always 1

Loop invariant -> 算法正确性。找到的 LFSR 肯定是一个成立的。最小性原因是我们在 Degree 上升的时候更新用来修 Discrepancy 的多项式,证明待补充(我还没看懂,乐)

Other notes

现代解读中会把 BM 当成 Padé approximation 的一个特例,在这个解读下它的对面是扩展欧几里得。现代 RS decoder 也有很多通过 EEA 实现的。可能需要继续阅读以下内容:

Hello, World!

Hello! 这是 [喵喵](@CircuitCoder) 的笔记本。这里记录了我一些未经整理的笔记和想法,反映我的小脑瓜里在想什么,因此绝大多数内容会使用最接近我思考的中英混杂写成,请见谅。

制作这个笔记的动机是想有一个类似[杰哥](@jiegec)知识库,但是考虑到我的知识一点都没有结构性,所以正好就做成了这样一个杂乱无章的 UI,非常合适。

这里的想法成熟之后可能会整理变成我的博客的帖子,内容也可能会随便删改修订,如果需要 Permalink 的话请复制每个 Section 标题下方更新日期的那个链接,那个链接带有 Commit Hash,可以保证不变。

这里没有评论区,可以直接在 Repo 的 Issue section 发 issue。

类型体操

Rocq 和 Agda 的 Inductive type 的 Elimination 都是 pattern matching as intrinsic, induction principle 是生成出来的。因此去有的时候为了写明白 motive 会比较麻烦。