Circom实现新加坡NRIC校验器遇非二次约束错误求解决方案
新加坡NRIC校验Circom电路错误解决方法
问题描述
我需要用Circom编写新加坡NRIC的校验电路,NRIC的结构与校验规则如下:
NRIC结构
- 前缀:枚举值为S、T、G、F、M
- 后缀:单个校验字母
- 中间7位数字
校验步骤
- 将7位数字依次乘以权重
[2,7,6,5,4,3,2],计算乘积之和 - 若前缀为T或G,总和加4
- 将总和对11取余,得到0-10之间的余数
- 根据前缀分组映射余数到对应字母:
- S/T/M组:余数0→J、1→Z、2→I、3→H、4→G、5→F、6→E、7→D、8→C、9→B、10→A
- F/G组:余数0→X、1→W、2→U、3→T、4→R、5→Q、6→P、7→N、8→M、9→L、10→K
- 映射结果与后缀匹配则校验通过
编写的Circom代码及编译错误如下:
pragma circom 2.1.5; template NRICValidator() { signal input prefix; // 1 or 2 signal input suffix; // 3 or 4 signal input digits[7]; var sumWithOffset; var weights[5] = [2, 7, 6, 5, 4, 3, 2]; var checkDigits[11] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; var offset = 4; var weightedSum = digits[0]*weights[0] + digits[1]*weights[1] + digits[2]*weights[2] + digits[3]*weights[3] + digits[4]*weights[4] + digits[5]*weights[5] + digits[6]*weights[6]; if (prefix == 1 && prefix == 2) { // T or G var sumWithOffset = weightedSum + offset; } else { var sumWithOffset = weightedSum; // S or F } signal output valid; sumWithOffset %= 11; // <=== a number between 0 to 10 valid <== (checkDigits[sumWithOffset] == suffix); // <=== Error: Non-quadratic constraint was detected statically, using unknown index will cause the constraint to be non-quadratic } component main = NRICValidator();
编译错误:
error[T20462]: Typing error found ┌─ "main.circom":21:14 │ 21 │ valid <== (checkDigits[sumWithOffset] == suffix); // <=== Error: Non-quadratic constraint was detected statically, using unknown index will cause the constraint to be non-quadratic │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Non-quadratic constraint was detected statically, using unknown index will cause the constraint to be non-quadratic previous errors were found
错误原因
- 非二次约束问题:Circom仅支持二次及以下的算术约束,用动态计算的
sumWithOffset(编译时未知值)作为数组索引属于非线性操作,违反约束规则。 - 逻辑错误:原代码中
prefix ==1 && prefix ==2永远为假,应该用||判断前缀是否属于T/G组。 - 变量类型错误:
weightedSum和sumWithOffset用var定义会被视为编译期常量,但实际依赖输入信号,需要改为signal类型存储。
解决方案
将动态数组索引的操作转化为遍历所有可能余数的条件约束,确保所有逻辑都符合Circom的二次约束要求,同时修正逻辑错误:
修正后的代码
pragma circom 2.1.5; template NRICValidator() { // 前缀编码规则: // 1=S, 2=T, 3=F, 4=G, 5=M signal input prefix; // 后缀编码:按对应组的映射转为数字(如S/T组J=0、Z=1...A=10;F/G组X=0...K=10) signal input suffix; signal input digits[7]; var weights[7] = [2, 7, 6, 5, 4, 3, 2]; var offset = 4; // 计算加权和(用signal存储,依赖输入信号的计算不能用var) signal weightedSum; weightedSum <== digits[0]*weights[0] + digits[1]*weights[1] + digits[2]*weights[2] + digits[3]*weights[3] + digits[4]*weights[4] + digits[5]*weights[5] + digits[6]*weights[6]; // 计算加偏移后的总和:前缀为T(2)或G(4)时加4 signal sumWithOffset; sumWithOffset <== weightedSum + ((prefix == 2 || prefix == 4) ? offset : 0); // 计算余数并约束范围在0-10之间 signal remainder; remainder <== sumWithOffset - 11 * floor(sumWithOffset / 11); // 额外约束确保余数是0-10中的一个值 signal remRangeCheck; remRangeCheck <== remainder * (remainder-1)*(remainder-2)*(remainder-3)*(remainder-4)* (remainder-5)*(remainder-6)*(remainder-7)*(remainder-8)*(remainder-9)*(remainder-10); remRangeCheck === 0; // 定义两组余数到后缀编码的映射 var mapST[11] = [0,1,2,3,4,5,6,7,8,9,10]; // S/T/M组:J,Z,I,H,G,F,E,D,C,B,A var mapFG[11] = [0,1,2,3,4,5,6,7,8,9,10]; // F/G组:X,W,U,T,R,Q,P,N,M,L,K signal output valid; valid <== 0; // 遍历所有可能的余数,验证后缀是否匹配对应组的映射 for (var i=0; i<11; i++) { // 当余数为i时,检查前缀组和后缀是否匹配 var matchST = (remainder == i) && (prefix ==1 || prefix ==2 || prefix ==5) && (suffix == mapST[i]); var matchFG = (remainder == i) && (prefix ==3 || prefix ==4) && (suffix == mapFG[i]); valid <== valid + matchST + matchFG; } // 确保valid只能是0或1(避免多个条件同时满足的异常情况) valid * (valid - 1) === 0; } component main = NRICValidator();
关键修正点
- 将依赖输入信号的计算从
var改为signal,确保符合Circom的信号约束规则。 - 用遍历+条件判断替代动态数组索引,所有条件均为二次约束,符合Circom要求。
- 增加余数范围约束,确保余数只能是0-10之间的整数。
- 修正前缀判断逻辑,正确区分需要加偏移的分组。
- 最后约束
valid只能为0或1,避免出现无效值。
内容的提问来源于stack exchange,提问作者Jim
相关产品推荐
相关产品推荐

