ZKP3.2 Programming ZKPs (Arkworks & Zokrates)

ZKP学习笔记

ZK-Learning MOOC课程笔记

Lecture 3: Programming ZKPs (Guest Lecturers: Pratyush Mishra and Alex Ozdemir)

3.3 Using a library (+ tutorial)

  • R1CS Libraries
    • A library in a host language (Eg: Rust, OCaml, C++, Go, ...)
    • Key type: constraint system
      • Maintains state about R1CS constraints and variables
    • Key operations:
      • create variable
      • create linear combinations of variables
      • add constraint
  • ConstraintSystem Operations
rust 复制代码
//Variable creation
cs.add_var(p, v) → id

//Linear Combination creation
cs.zero()
lc.add(c, id) → lc_
//lc_ := lc + c * id

//Adding constraints
cs.constrain(lcA, lcB, lcC)
//Adds a constraint lcA × lcB = lcC
  • Arkworks Tutorial
rust 复制代码
// main.rs
use ark_ff::PrimeField;
use ark_r1cs_std::{
    prelude::{Boolean, EqGadget, AllocVar},
    uint8::UInt8
};
use ark_relations::r1cs::{SynthesisError, ConstraintSystem};
use cmp::CmpGadget;

mod cmp;
mod alloc;

pub struct Puzzle<const N: usize, ConstraintF: PrimeField>([[UInt8<ConstraintF>; N]; N]);
pub struct Solution<const N: usize, ConstraintF: PrimeField>([[UInt8<ConstraintF>; N]; N]);

fn check_rows<const N: usize, ConstraintF: PrimeField>(
    solution: &Solution<N, ConstraintF>,
) -> Result<(), SynthesisError> {
    for row in &solution.0 {
        for (j, cell) in row.iter().enumerate() {
            for prior_cell in &row[0..j] {
                cell.is_neq(&prior_cell)?
                    .enforce_equal(&Boolean::TRUE)?;
            }
        }
    }
    Ok(())
}

fn check_puzzle_matches_solution<const N: usize, ConstraintF: PrimeField>(
    puzzle: &Puzzle<N, ConstraintF>,
    solution: &Solution<N, ConstraintF>,
) -> Result<(), SynthesisError> {
    for (p_row, s_row) in puzzle.0.iter().zip(&solution.0) {
        for (p, s) in p_row.iter().zip(s_row) {
            // Ensure that the solution `s` is in the range [1, N]
            s.is_leq(&UInt8::constant(N as u8))?
                .and(&s.is_geq(&UInt8::constant(1))?)?
                .enforce_equal(&Boolean::TRUE)?;

            // Ensure that either the puzzle slot is 0, or that
            // the slot matches equivalent slot in the solution
            (p.is_eq(s)?.or(&p.is_eq(&UInt8::constant(0))?)?)
                .enforce_equal(&Boolean::TRUE)?;
        }
    }
    Ok(())
}

fn check_helper<const N: usize, ConstraintF: PrimeField>(
    puzzle: &[[u8; N]; N],
    solution: &[[u8; N]; N],
) {
    let cs = ConstraintSystem::<ConstraintF>::new_ref();
    let puzzle_var = Puzzle::new_input(cs.clone(), || Ok(puzzle)).unwrap();
    let solution_var = Solution::new_witness(cs.clone(), || Ok(solution)).unwrap();
    check_puzzle_matches_solution(&puzzle_var, &solution_var).unwrap();
    check_rows(&solution_var).unwrap();
    assert!(cs.is_satisfied().unwrap());
}

fn main() {
    use ark_bls12_381::Fq as F;
    // Check that it accepts a valid solution.
    let puzzle = [
        [1, 0],
        [0, 2],
    ];
    let solution = [
        [1, 2],
        [1, 2],
    ];
    check_helper::<2, F>(&puzzle, &solution);

    // Check that it rejects a solution with a repeated number in a row.
    let puzzle = [
        [1, 0],
        [0, 2],
    ];
    let solution = [
        [1, 0],
        [1, 2],
    ];
    check_helper::<2, F>(&puzzle, &solution);
}

// cmp.rs
use ark_ff::PrimeField;
use ark_r1cs_std::{prelude::{Boolean, EqGadget}, R1CSVar, uint8::UInt8, ToBitsGadget};
use ark_relations::r1cs::SynthesisError;

pub trait CmpGadget<ConstraintF: PrimeField>: R1CSVar<ConstraintF> + EqGadget<ConstraintF> {
    #[inline]
    fn is_geq(&self, other: &Self) -> Result<Boolean<ConstraintF>, SynthesisError> {
        // self >= other => self == other || self > other
        //               => !(self < other)
        self.is_lt(other).map(|b| b.not())
    }

    #[inline]
    fn is_leq(&self, other: &Self) -> Result<Boolean<ConstraintF>, SynthesisError> {
        // self <= other => self == other || self < other
        //               => self == other || other > self
        //               => self >= other
        other.is_geq(self)
    }

    #[inline]
    fn is_gt(&self, other: &Self) -> Result<Boolean<ConstraintF>, SynthesisError> {
        // self > other => !(self == other  || self < other)
        //              => !(self <= other)
        self.is_leq(other).map(|b| b.not())
    }

    fn is_lt(&self, other: &Self) -> Result<Boolean<ConstraintF>, SynthesisError>; 
}

impl<ConstraintF: PrimeField> CmpGadget<ConstraintF> for UInt8<ConstraintF> {
    fn is_lt(&self, other: &Self) -> Result<Boolean<ConstraintF>, SynthesisError> {
        // Determine the variable mode.
        if self.is_constant() && other.is_constant() {
            let self_value = self.value().unwrap();
            let other_value = other.value().unwrap();
            let result = Boolean::constant(self_value < other_value);
            Ok(result)
        } else {
            let diff_bits = self.xor(other)?.to_bits_be()?.into_iter();
            let mut result = Boolean::FALSE;
            let mut a_and_b_equal_so_far = Boolean::TRUE;
            let a_bits = self.to_bits_be()?;
            let b_bits = other.to_bits_be()?;
            for ((a_and_b_are_unequal, a), b) in diff_bits.zip(a_bits).zip(b_bits) {
                let a_is_lt_b = a.not().and(&b)?;
                let a_and_b_are_equal = a_and_b_are_unequal.not();
                result = result.or(&a_is_lt_b.and(&a_and_b_equal_so_far)?)?;
                a_and_b_equal_so_far = a_and_b_equal_so_far.and(&a_and_b_are_equal)?;
            }
            Ok(result)
        }
    }
}

#[cfg(test)]
mod test {
    use ark_r1cs_std::{prelude::{AllocationMode, AllocVar, Boolean, EqGadget}, uint8::UInt8};
    use ark_relations::r1cs::{ConstraintSystem, SynthesisMode};
    use ark_bls12_381::Fr as Fp;
    use itertools::Itertools;

    use crate::cmp::CmpGadget;

    #[test]
    fn test_comparison_for_u8() {
        let modes = [AllocationMode::Constant, AllocationMode::Input, AllocationMode::Witness];
        for (a, a_mode) in (0..=u8::MAX).cartesian_product(modes) {
            for (b, b_mode) in (0..=u8::MAX).cartesian_product(modes) {
                let cs = ConstraintSystem::<Fp>::new_ref();
                cs.set_mode(SynthesisMode::Prove { construct_matrices: true });
                let a_var = UInt8::new_variable(cs.clone(), || Ok(a), a_mode).unwrap();
                let b_var = UInt8::new_variable(cs.clone(), || Ok(b), b_mode).unwrap();
                if a < b {
                    a_var.is_lt(&b_var).unwrap()
                        .enforce_equal(&Boolean::TRUE).unwrap();
                    a_var.is_leq(&b_var).unwrap().enforce_equal(&Boolean::TRUE).unwrap();
                    a_var.is_gt(&b_var).unwrap().enforce_equal(&Boolean::FALSE).unwrap();
                    a_var.is_geq(&b_var).unwrap().enforce_equal(&Boolean::FALSE).unwrap();
                } else if a == b {
                    a_var.is_lt(&b_var).unwrap().enforce_equal(&Boolean::FALSE).unwrap();
                    a_var.is_leq(&b_var).unwrap().enforce_equal(&Boolean::TRUE).unwrap();
                    a_var.is_gt(&b_var).unwrap().enforce_equal(&Boolean::FALSE).unwrap();
                    a_var.is_geq(&b_var).unwrap().enforce_equal(&Boolean::TRUE).unwrap();
                } else {
                    a_var.is_lt(&b_var).unwrap().enforce_equal(&Boolean::FALSE).unwrap();
                    a_var.is_leq(&b_var).unwrap().enforce_equal(&Boolean::FALSE).unwrap();
                    a_var.is_gt(&b_var).unwrap().enforce_equal(&Boolean::TRUE).unwrap();
                    a_var.is_geq(&b_var).unwrap().enforce_equal(&Boolean::TRUE).unwrap();
                }
                assert!(cs.is_satisfied().unwrap(), "a: {a}, b: {b}");
            }
        }
    }
}

//alloc.rs
use std::borrow::Borrow;

use ark_ff::PrimeField;
use ark_r1cs_std::{prelude::{AllocVar, AllocationMode}, uint8::UInt8};
use ark_relations::r1cs::{Namespace, SynthesisError};

use crate::{Puzzle, Solution};

impl<const N: usize, F: PrimeField> AllocVar<[[u8; N]; N], F> for Puzzle<N, F> {
    fn new_variable<T: Borrow<[[u8; N]; N]>>(
        cs: impl Into<Namespace<F>>,
        f: impl FnOnce() -> Result<T, SynthesisError>,
        mode: AllocationMode,
    ) -> Result<Self, SynthesisError> {
        let cs = cs.into();
        let row = [(); N].map(|_| UInt8::constant(0));
        let mut puzzle = Puzzle([(); N].map(|_| row.clone()));
        let value = f().map_or([[0; N]; N], |f| *f.borrow());
        for (i, row) in value.into_iter().enumerate() {
            for (j, cell) in row.into_iter().enumerate() {
                puzzle.0[i][j] = UInt8::new_variable(cs.clone(), || Ok(cell), mode)?;
            }
        }
        Ok(puzzle)
    }
} 

impl<const N: usize, F: PrimeField> AllocVar<[[u8; N]; N], F> for Solution<N, F> {
    fn new_variable<T: Borrow<[[u8; N]; N]>>(
        cs: impl Into<Namespace<F>>,
        f: impl FnOnce() -> Result<T, SynthesisError>,
        mode: AllocationMode,
    ) -> Result<Self, SynthesisError> {
        let cs = cs.into();
        let row = [(); N].map(|_| UInt8::constant(0));
        let mut solution = Solution([(); N].map(|_| row.clone()));
        let value = f().map_or([[0; N]; N], |f| *f.borrow());
        for (i, row) in value.into_iter().enumerate() {
            for (j, cell) in row.into_iter().enumerate() {
                solution.0[i][j] = UInt8::new_variable(cs.clone(), || Ok(cell), mode)?;
            }
        }
        Ok(solution)
    }
}

3.4 Using a compiler (+ tutorial)

  • HDLs & Circuit Libraries

    • Difference: Host language v. custom language
    • Similarities: explicit wire creation (explicitly wire values); explicit constraint creation
  • ZoKrates Tutorial

    struct Puzzle {
    u8[N][N] elems;
    }
    struct Solution {
    u8[N][N] elems;
    }

    def check_rows(Solution sol) -> bool {
    // for each row
    for u32 i in 0..N {
    // for each column
    for u32 j in 0..N {
    // Check that the (i, j)-th element is not equal to any of the
    // the elements preceding it in the same row.
    for u32 k in 0..j {
    assert(sol.elems[i][j] != sol.elems[i][k]);
    }
    }
    }
    return true;
    }

    def check_puzzle_matches_solution(Solution sol, Puzzle puzzle) -> bool {
    for u32 i in 0..N {
    for u32 j in 0..N {
    assert((sol.elems[i][j] > 0) && (sol.elems[i][j] < 10));
    assert(
    (puzzle.elems[i][j] == 0) ||
    (puzzle.elems[i][j] == sol.elems[i][j])
    );
    }
    }
    return true;
    }

    def main(public Puzzle<2> puzzle, private Solution<2> sol) {
    assert(check_puzzle_matches_solution(sol, puzzle));
    assert(check_rows(sol));
    }

3.5 An overview of prominent ZKP toolchains

  • Toolchain Type
相关推荐
Zhao-Jichao2 小时前
AI Prompting for Everyone 课程笔记 - P21 总结 Conclusion
人工智能·笔记
欢欢李2 小时前
【LTE L3 笔记 05】LTE同步信号
笔记·无线通信·物理层·lte·空口协议
La Pulga4 小时前
【FreeRTOS源码学习笔记】空闲任务与阻塞延时
笔记·mcu·学习·嵌入式·freertos·实时操作系统
蒸蒸yyyyzwd4 小时前
秋招学习笔记 day106
笔记·学习
小李不想当小白5 小时前
WDG看门狗(STM32标准库学习笔记)
笔记·stm32·单片机·嵌入式硬件·学习·嵌入式
欢欢李5 小时前
【LTE L3 笔记 04】LTE帧结构
笔记·无线通信·物理层·lte·空口协议
Zhao-Jichao8 小时前
AI Prompting for Everyone 课程笔记 - P20 实验与期末项目 Lab and Final Project
前端·人工智能·笔记
Z21-X8 小时前
云原生运维实战笔记:企业网站综合上云部署与云运维服务全景解析
运维·笔记·学习·云原生
坤坤子吖8 小时前
Linux网络编程:UDP进阶,从字典服务器到简单聊天室
linux·服务器·网络·c++·笔记·学习
Rabitebla9 小时前
【Linux学习笔记】进程篇(九):进程的创建与退出——fork、写时拷贝与退出码
linux·c++·笔记·学习