ZKM Prover 结合 zk-STARK 技术,验证算术与 CPU 操作。算术操作包括加法、乘法、除法等,通过初始化算术表、生成 Trace 数据、执行范围检查与电路验证确保正确性。CPU 操作涵盖指令解码、跳转、内存访问等模块,依次通过本地与电路验证保证操作符合逻辑与约束。
mul: 验证二元乘法操作,例如 $a \times b = c$,适用于简单的两输入一输出乘法。 mult: 验证多输入或复杂乘法操作,处理更高精度或特殊情况的乘法。 addcy: 验证带进位的加法操作,例如 $a + b + carry = c$,用于多字长加法或高精度计算。 slt: 验证小于比较操作 $a < b$,通常用于条件分支或逻辑判断。 lui: 验证加载高位立即数操作,将立即数加载到寄存器的高位,例如 $reg = imm << 16$。 div: 验证二元除法操作 $a \div b = c$,同时可能处理余数,确保除法合法性。 shift: 验证逻辑移位操作,包括左移或右移,例如 $a << b$ 或 $a >> b$。 sra: 验证算术右移操作,保留符号位(Sign Bit),用于有符号数处理。 lo_hi: 验证高低位分解或合并操作,例如高位 $(a >> 16)$ 和低位 $(a \& 0xFFFF)$。
算术操作主要包括以下操作:
Step 1: 初始化算术表(Arithmetic Table)
Step 2: 生成 Trace 数据
Step 3: 验证范围检查(Range Checks)
Step 4: 验证约束条件(Constraints Evaluation)
Step 5: 电路级验证(Ext Circuit Evaluation)
初始化算术表的结构,包括对列的映射(如操作数、操作类型、结果等),并将其与 CPU 表中的字段进行数据链接。
COMBINED_OPS
数组定义 26 种算术操作及其对应的列和操作码,例如:
columns::IS_ADD
表示加法操作,其操作码为 0b100000
。REGISTER_MAP
包含三个寄存器范围:
INPUT_REGISTER_0
和 INPUT_REGISTER_1
分别存储两个输入操作数。OUTPUT_REGISTER
用于存储运算结果。Filter::new_simple
创建过滤器,标记有效的算术操作行。COMBINED_OPS
中定义的算术操作时,过滤器才会激活。cpu_arith_data_link
方法,将算术表的列(如寄存器和操作数)与 CPU 表中对应的字段链接。代码模块:
pub fn ctl_arithmetic_rows<F: Field>() -> TableWithColumns<F> {
const COMBINED_OPS: [(usize, u32); 26] = [
// 映射算术操作和对应的操作码(opcode)。
(columns::IS_ADD, 0b100000 * (1 << 6)),
(columns::IS_ADDU, 0b100001 * (1 << 6)),
(columns::IS_ADDI, 0b001000),
// ... 省略部分 ...
];
const REGISTER_MAP: [Range<usize>; 3] = [
columns::INPUT_REGISTER_0, // 输入寄存器0
columns::INPUT_REGISTER_1, // 输入寄存器1
columns::OUTPUT_REGISTER, // 输出寄存器
];
let filter = Some(Filter::new_simple(Column::sum(
COMBINED_OPS.iter().map(|(c, _v)| *c),
)));
TableWithColumns::new(
Table::Arithmetic,
cpu_arith_data_link(&COMBINED_OPS, ®ISTER_MAP), //将算术表链接到CPU表
filter,
)
}
根据输入的操作(Operation
),逐行生成 Trace 数据,同时补充空行并执行范围检查,主要由以下几步构成。
max_rows
定义 Trace 表的最大行数:
2 * operations.len()
。RANGE_MAX
行,因此最终行数取两者的最大值。Operation
调用 to_rows
方法,将其转换为一行或两行数据。RANGE_MAX
。generate_range_checks
方法,对指定列的数据执行范围检查。代码模块:
pub(crate) fn generate_trace(&self, operations: Vec<Operation>) -> Vec<PolynomialValues<F>> {
let max_rows = std::cmp::max(2 * operations.len(), RANGE_MAX);
let mut trace_rows = Vec::with_capacity(max_rows);
for op in operations {
let (row1, maybe_row2) = op.to_rows(); // 将操作转换为一行或两行数据。
trace_rows.push(row1);
if let Some(row2) = maybe_row2 { // 如果操作需要两行,添加到 Trace。
trace_rows.push(row2);
}
}
// 填充空行
let padded_len = trace_rows.len().next_power_of_two(); // 确保行数为 2 的幂次方,以方便进行插值和FRI的commitment操作。
for _ in trace_rows.len()..std::cmp::max(padded_len, RANGE_MAX) {
trace_rows.push(vec![F::ZERO; columns::NUM_ARITH_COLUMNS]); // 添加全零行。
}
// 转置为列数据
let mut trace_cols = transpose(&trace_rows);
// 范围检查
self.generate_range_checks(&mut trace_cols);
// 转换为多项式表示
trace_cols.into_iter().map(PolynomialValues::new).collect()
}
对 Trace 中的某些列进行范围检查,确保列的值在合法范围内(例如 [0, 2^16))。
代码模块:
fn generate_range_checks(&self, cols: &mut [Vec<F>]) {
// 初始化 RANGE_COUNTER 列,范围为 [0, 2^16)
for i in 0..RANGE_MAX {
cols[RANGE_COUNTER][i] = F::from_canonical_usize(i);
}
for i in RANGE_MAX..cols[0].len() {
cols[RANGE_COUNTER][i] = F::from_canonical_usize(RANGE_MAX - 1);
}
// 对共享列(SHARED_COLS)执行频率检查
for col in SHARED_COLS {
for i in 0..cols[0].len() {
let x = cols[col][i].to_canonical_u64() as usize;
assert!(
x < RANGE_MAX,
"column value {} exceeds the max range value {}",
x,
RANGE_MAX
);
cols[RC_FREQUENCIES][x] += F::ONE; // 统计出现频率
}
}
}
针对不同的算术操作验证约束条件,确保操作的输入、输出及中间状态符合定义,该模块主要在离线验证填充的值是否满足约束。
RANGE_COUNTER
的前 RANGE_MAX
行依次填充 [0, 2^16) 的值。RANGE_MAX
,则填充最大值 2^16-1
。SHARED_COLS
),统计每一行中的值在范围 [0, 2^16) 中的频率。代码模块:
fn eval_packed_generic<FE, P, const D2: usize>(
&self,
vars: &Self::EvaluationFrame<FE, P, D2>,
yield_constr: &mut ConstraintConsumer<P>,
) where
FE: FieldExtension<D2, BaseField = F>,
P: PackedField<Scalar = FE>,
{
let lv: &[P; NUM_ARITH_COLUMNS] = vars.get_local_values().try_into().unwrap();
let nv: &[P; NUM_ARITH_COLUMNS] = vars.get_next_values().try_into().unwrap();
// 范围检查约束
let rc1 = lv[RANGE_COUNTER];
let rc2 = nv[RANGE_COUNTER];
yield_constr.constraint_first_row(rc1); // 第一行必须为 0
let incr = rc2 - rc1;
yield_constr.constraint_transition(incr * incr - incr); // 范围递增约束
let range_max = P::Scalar::from_canonical_u64((RANGE_MAX - 1) as u64);
yield_constr.constraint_last_row(rc1 - range_max); // 最后一行为 RANGE_MAX-1
// 调用各个算术操作的约束验证
mul::eval_packed_generic(lv, yield_constr);
mult::eval_packed_generic(lv, yield_constr);
addcy::eval_packed_generic(lv, yield_constr);
slt::eval_packed_generic(lv, yield_constr);
lui::eval_packed_generic(lv, nv, yield_constr);
div::eval_packed(lv, nv, yield_constr);
shift::eval_packed_generic(lv, nv, yield_constr);
sra::eval_packed_generic(lv, nv, yield_constr);
lo_hi::eval_packed_generic(lv, yield_constr);
}
通过扩展电路验证算术表中的每个操作,确保满足约束条件,该模块主要通过电路约束构建 STARK 系统的证明。
代码模块:
fn eval_ext_circuit(
&self,
builder: &mut CircuitBuilder<F, D>,
vars: &Self::EvaluationFrameTarget,
yield_constr: &mut RecursiveConstraintConsumer<F, D>,
) {
let lv: &[ExtensionTarget<D>; NUM_ARITH_COLUMNS] =
vars.get_local_values().try_into().unwrap();
let nv: &[ExtensionTarget<D>; NUM_ARITH_COLUMNS] =
vars.get_next_values().try_into().unwrap();
// 范围检查约束(电路版)
let rc1 = lv[RANGE_COUNTER];
let rc2 = nv[RANGE_COUNTER];
yield_constr.constraint_first_row(builder, rc1);
let incr = builder.sub_extension(rc2, rc1);
let t = builder.mul_sub_extension(incr, incr, incr);
yield_constr.constraint_transition(builder, t);
let range_max =
builder.constant_extension(F::Extension::from_canonical_usize(RANGE_MAX - 1));
let t = builder.sub_extension(rc1, range_max);
yield_constr.constraint_last_row(builder, t);
// 调用各个算术操作的电路验证
mul::eval_ext_circuit(builder, lv, yield_constr);
mult::eval_ext_circuit(builder, lv, yield_constr);
addcy::eval_ext_circuit(builder, lv, yield_constr);
slt::eval_ext_circuit(builder, lv, yield_constr);
lui::eval_ext_circuit(builder, lv, nv, yield_constr);
div::eval_ext_circuit(builder, lv, nv, yield_constr);
shift::eval_ext_circuit(builder, lv, nv, yield_constr);
sra::eval_ext_circuit(builder, lv, nv, yield_constr);
lo_hi::eval_ext_circuit(builder, lv, yield_constr);
CPU的执行主要分为以下部分:
1. 指令获取与解码(Decode) CPU从内存中获取指令(Fetch),并将其送入解码模块(Decode)。在这里,指令被解析成操作码(Opcode)、功能码(FuncCode)、源寄存器(Source Register)、目标寄存器(Destination Register)等字段。解码模块负责验证指令是否合法,并确定指令的类型(如算术操作、跳转指令或内存操作)。 作用:确保CPU只执行合法的指令,并为后续模块提供清晰的指令结构。 2. 跳转与分支(Jumps) 如果当前指令是跳转或分支指令,跳转模块计算目标地址并更新程序计数器(PC)。无条件跳转会直接更改PC;而条件跳转则根据条件寄存器的状态判断是否执行跳转。分支指令则需要判断具体的条件(如是否等于、大于或小于)。 作用:灵活控制程序的执行流程,实现函数调用、循环等功能。 3. 内存访问与总线操作(Membus 和 Memio) 对于需要访问内存的指令(如加载和存储指令),CPU通过内存总线模块(Membus)与内存进行交互。Membus模块会验证内存通道的状态(如是否被使用)和上下文标志(Kernel/User),确保内存访问的安全性。Memio模块则负责具体的加载(Load)和存储(Store)操作,比如从内存读取一个字(Word)、半字(Halfword)或字节(Byte),或者将数据写入内存。 作用:提供CPU与内存之间的桥梁,确保数据读写操作的正确性和安全性。 4. 算术和逻辑运算 CPU根据指令类型执行算术或逻辑运算,比如加法、减法、位移(Shift)、条件移动(CondMov)等。Shift模块负责位移操作,包括左移、右移以及循环右移;Bits模块处理特定的位操作,比如符号扩展和半字节交换;Count模块用于统计二进制数的前导零或前导一。 作用:执行指令核心逻辑,完成具体的计算任务。 5. 系统调用与特权操作(Syscall 和 Exit Kernel) 当指令为系统调用(Syscall)时,CPU切换到内核模式以处理特权操作,如内存映射、线程管理、文件操作等。Syscall模块负责验证系统调用的正确性,确保操作合法并满足系统需求。当任务完成后,CPU通过Exit Kernel模块退出内核模式,返回用户态继续执行。Exit Kernel模块会同步寄存器状态到内存,更新程序计数器,并检查内存的一致性。 作用:在用户态和内核态之间安全切换,提供系统级服务支持。 6. 结果写回与状态更新 指令执行完成后,计算结果会写入目标寄存器或存储到内存中,同时更新程序计数器(PC)以准备执行下一条指令。 作用:保存运算结果,并推进指令流的执行。
CpuStark
结构,它是一个泛型结构,包含以下参数:
F
:字段类型(Field type),用于支持有限域运算。D
:扩展度(Degree of extension),用于表示扩展字段的深度。PhantomData<F>
,这是一个占位符,表明这个结构与字段类型相关联。#[derive(Copy, Clone, Default)]
pub struct CpuStark<F, const D: usize> {
pub f: PhantomData<F>,
}
CpuStark
是 CPU 模拟的核心结构,负责管理和操作 STARK 的具体实现,包括 trace 验证与约束生成。
fn eval_packed_generic<FE, P, const D2: usize>(
&self,
vars: &Self::EvaluationFrame<FE, P, D2>,
yield_constr: &mut ConstraintConsumer<P>,
) where
FE: FieldExtension<D2, BaseField = F>,
P: PackedField<Scalar = FE>,
Step 2.1: 提取 CPU 状态
vars
中提取当前行(local_values
)和下一行(next_values
)。CpuColumnsView
,便于访问 CPU 的各个字段。let local_values: &[P; NUM_CPU_COLUMNS] = vars.get_local_values().try_into().unwrap();
let local_values: &CpuColumnsView<P> = local_values.borrow();
let next_values: &[P; NUM_CPU_COLUMNS] = vars.get_next_values().try_into().unwrap();
let next_values: &CpuColumnsView<P> = next_values.borrow();
CpuColumnsView
:封装了 CPU 的 trace 数据,如指令、寄存器值等。NUM_CPU_COLUMNS
:CPU 表的总列数。Step 2.2: 调用模块验证
bootstrap_kernel::eval_bootstrap_kernel_packed(local_values, next_values, yield_constr);
decode::eval_packed_generic(local_values, yield_constr);
jumps::eval_packed(local_values, next_values, yield_constr);
membus::eval_packed(local_values, yield_constr);
memio::eval_packed(local_values, next_values, yield_constr);
shift::eval_packed(local_values, yield_constr);
count::eval_packed(local_values, yield_constr);
syscall::eval_packed(local_values, yield_constr);
bits::eval_packed(local_values, yield_constr);
misc::eval_packed(local_values, yield_constr);
exit_kernel::eval_exit_kernel_packed(local_values, next_values, yield_constr);
例子: decode::eval_packed_generic
fn eval_packed_generic<P: PackedField>(
lv: &CpuColumnsView<P>,
yield_constr: &mut ConstraintConsumer<P>,
) {
let filter = lv.op.decode_op; // 过滤条件,指令解码操作
let opcode_check = lv.opcode_bits[0]; // 取操作码第一位
yield_constr.constraint(filter * opcode_check); // 添加约束:验证操作码是否正确
}
filter
)。fn eval_ext_circuit(
&self,
builder: &mut CircuitBuilder<F, D>,
vars: &Self::EvaluationFrameTarget,
yield_constr: &mut RecursiveConstraintConsumer<F, D>,
)
Step 3.1: 提取状态
let local_values: &[ExtensionTarget<D>; NUM_CPU_COLUMNS] =
vars.get_local_values().try_into().unwrap();
let local_values: &CpuColumnsView<ExtensionTarget<D>> = local_values.borrow();
let next_values: &[ExtensionTarget<D>; NUM_CPU_COLUMNS] =
vars.get_next_values().try_into().unwrap();
let next_values: &CpuColumnsView<ExtensionTarget<D>> = next_values.borrow();
ExtensionTarget
数据,用于电路生成。eval_packed_generic
中的状态提取。Step 3.2: 调用模块约束生成
bootstrap_kernel::eval_bootstrap_kernel_ext_circuit(builder, local_values, next_values, yield_constr);
decode::eval_ext_circuit(builder, local_values, yield_constr);
jumps::eval_ext_circuit(builder, local_values, next_values, yield_constr);
membus::eval_ext_circuit(builder, local_values, yield_constr);
// ...
exit_kernel::eval_exit_kernel_ext_circuit(builder, local_values, next_values, yield_constr);
功能:
eval_ext_circuit
方法。例子: decode::eval_ext_circuit
fn eval_ext_circuit<F: RichField + Extendable<D>, const D: usize>(
builder: &mut CircuitBuilder<F, D>,
lv: &CpuColumnsView<ExtensionTarget<D>>,
yield_constr: &mut RecursiveConstraintConsumer<F, D>,
) {
let filter = lv.op.decode_op; // 过滤条件
let opcode_check = lv.opcode_bits[0]; // 操作码第一位
let constr = builder.mul_extension(filter, opcode_check); // 生成约束
yield_constr.constraint(builder, constr); // 添加到约束中
}
CircuitBuilder
构建乘法约束,生成指令解码的电路约束。#[test]
fn test_stark_degree() -> Result<()> {
const D: usize = 2;
type C = PoseidonGoldilocksConfig;
type F = <C as GenericConfig<D>>::F;
type S = CpuStark<F, D>;
let stark = S {
f: Default::default(),
};
test_stark_low_degree(stark)
}
功能:
#[test]
fn test_stark_circuit() -> Result<()> {
const D: usize = 2;
type C = PoseidonGoldilocksConfig;
type F = <C as GenericConfig<D>>::F;
type S = CpuStark<F, D>;
let stark = S {
f: Default::default(),
};
test_stark_circuit_constraints::<F, C, S, D>(stark)
}
功能:
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!