基于Nova的SHA256证明

1. 引言

前序博客:

基于Nova的SHA256证明,开源代码见:

以SHA-256哈希运算输入为input为例,其中SHA-256算法及示例 中的:

  • 1)“4. 填充”对应add_sha256_padding(input)函数,返回padded_input
  • 2)每个512 bit(64字节)block对应padded_input_to_blocks(padded_input)函数返回block数组blocks_vec,假设该数组长度为N。
  • 3)每个block压缩调用compress256,其中state为前一block压缩结果:
    let mut state = IV; \text{let mut state = IV;} let mut state = IV;
    For  t = 1  to  N \text{For } t=1 \text{ to } N For t=1 to N
        compress256(&mut state, &[blocks_vec[t]]); \ \ \ \ \text{compress256(\&mut state, \&[blocks\_vec[t]]);}     compress256(&mut state, &[blocks_vec[t]]); // 对应为 z i + 1 = F ( z i , w i ) z_{i+1}=F(z_i, w_i) zi+1=F(zi,wi)
    End for  t \text{End for } t End for t
    • 3.1)取每个block调用compress256()时的state输入,构成digest_sequence数组——对应长度为N+1,对应测试用例可参看test_digest_sequence_generation()
  • 4)当处理完最后一个block之后,将 H j ( N ) H_j^{(N)} Hj(N)变量拼接在一起,即为input的哈希值——对应sha256_state_to_bytes()函数返回的hash_bytes,做十六进制编码(hex::encode(hash_bytes))即为input的哈希值。

以上四个步骤,对应一个函数:sha256_state_sequence(),返回:

  • 1)block_sequence:为将填充后input切分的block数组
  • 2)digest_sequence:为处理每个block时的previous 32-byte digest(即compress256()时的state输入)。

当构建约束时,compress256()对应SHA256CompressionCircuit,根据 Nova代码解析 可知,需对其实现StepCircuit trait:

/// A helper trait for a step of the incremental computation (i.e., circuit for F)
pub trait StepCircuit<F: PrimeField>: Send + Sync + Clone {
  /// Return the the number of inputs or outputs of each step
  /// (this method is called only at circuit synthesis time)
  /// `synthesize` and `output` methods are expected to take as
  /// input a vector of size equal to arity and output a vector of size equal to arity  
  fn arity(&self) -> usize;

  /// Sythesize the circuit for a computation step and return variable
  /// that corresponds to the output of the step z_{i+1}
  fn synthesize<CS: ConstraintSystem<F>>(
    &self,
    cs: &mut CS,
    z: &[AllocatedNum<F>],
  ) -> Result<Vec<AllocatedNum<F>>, SynthesisError>;

  /// return the output of the step when provided with the step's input
  fn output(&self, z: &[F]) -> Vec<F>;
}

impl<F> StepCircuit<F> for SHA256CompressionCircuit<F>
where
    F: PrimeField + PrimeFieldBits,
{
    fn arity(&self) -> usize {
        2
    }

    fn synthesize<CS: ConstraintSystem<F>>(
        &self,
        cs: &mut CS,
        z: &[AllocatedNum<F>],
      ) -> Result<Vec<AllocatedNum<F>>, SynthesisError> {
        assert_eq!(z.len(), 2);
        let initial_curr_digest_bits = z[0]
            .to_bits_le(cs.namespace(|| "initial current digest bits"))
            .unwrap();
        let remaining_curr_digest_bits = z[1]
            .to_bits_le(cs.namespace(|| "remaining current digest bits"))
            .unwrap();

        let mut current_digest_bits = vec![];
        for i in 0..F::CAPACITY as usize {
            current_digest_bits.push(initial_curr_digest_bits[i].clone());
        }
        let num_bits_remaining = DIGEST_LENGTH_BYTES*8 - (F::CAPACITY as usize);
        for i in 0..num_bits_remaining {
            current_digest_bits.push(remaining_curr_digest_bits[i].clone());
        }

        let mut current_state: Vec<UInt32> = vec![];
        for c in current_digest_bits.chunks(32) {
            current_state.push(UInt32::from_bits_be(c));
        }
        assert_eq!(current_state.len(), 8); //对应为上面的mut state,也即$(H_1^{(t-1)},H_2^{(t-1)},H_3^{(t-1)},H_4^{(t-1)},H_5^{(t-1)},H_6^{(t-1)},H_7^{(t-1)},H_8^{(t-1)})$
        
        let input_bit_values = bytes_to_bits(&self.input);
        assert_eq!(input_bit_values.len(), BLOCK_LENGTH_BYTES*8); //每个block blocks_vec[t]固定为512bit。
        let input_bits: Vec<Boolean> = input_bit_values
            .iter()
            .enumerate()
            .map(
                |(i, b)| Boolean::from(
                AllocatedBit::alloc(
                    cs.namespace(|| format!("input bit {i}")),
                    Some(*b),

                    )
                    .unwrap()
                )
            )
            .collect();

        // SHA256 compression function application
        let next_state: Vec<UInt32> = sha256_compression_function(&mut *cs, &input_bits, &current_state)?;
        assert_eq!(next_state.len(), 8);

        let next_digest_bits: Vec<Boolean> = next_state
            .into_iter()
            .map(|u| u.into_bits_be())
            .flatten()
            .collect();
        assert_eq!(next_digest_bits.len(), DIGEST_LENGTH_BYTES*8); //当前block处理完毕之后,输出为256bit。

        let mut z_out: Vec<AllocatedNum<F>> = vec![]; //将当前block处理之后的输出转换为scalar值表示。
        let (initial_next_digest_bits, remaining_next_digest_bits)
            = next_digest_bits.split_at(F::CAPACITY as usize);
        z_out.push(pack_bits(
            cs.namespace(|| "Packing initial preimage bits into scalar"),
            initial_next_digest_bits
        )
        .unwrap());
        z_out.push(pack_bits(
            cs.namespace(|| "Packing remaining preimage bits into scalar"),
            remaining_next_digest_bits
        )
        .unwrap());
        
        Ok(z_out)
    }

    fn output(&self, z: &[F]) -> Vec<F> {
        assert_eq!(z.len(), 2);
        assert_eq!(z[0], self.current_digest[0]);
        assert_eq!(z[1], self.current_digest[1]);

        // Compute output using non-deteriministic advice
        self.next_digest.to_vec()
    }

}

impl<F> Default for SHA256CompressionCircuit<F> 
where F: PrimeField + PrimeFieldBits,
{
    fn default() -> Self {
        Self {
            input: [0u8; BLOCK_LENGTH_BYTES],
            current_digest: [F::zero(); 2],
            next_digest: [F::zero(); 2],
        }
    }
}

其中:

  • 由于SHA-256输出为256bit,而所使用曲线scalar域小于256bit,需以2个scalar域元素(因此arity设置为2)来表示一个SHA-256 digest值,具体可见test_scalar_digest_roundtrip()测试用例。
  • sha256_compression_function()函数为真正compress256()计算逻辑:
    在这里插入图片描述
  • 详细测试用例见:test_sha256_compression_constraints(),对应约束数为27218个:
    Num constraints = 27218
    Num inputs = 1
    test sha256_step::circuit::tests::test_sha256_compression_constraints ... ok
    

当使用Nova来证明时,具体见example/sha256.rs。实际https://github.com/Microsoft/Nova 中做了2种Nova IVC proof压缩SNARK方案实现:

  • 1)spartan::RelaxedR1CSSNAR:使用Spartan + IPA-PC作为多项式承诺方案。
  • 2)spartan::pp::RelaxedR1CSSNARK:使用Spartan + sum-check,并将向量承诺看成是对多项式的承诺。
  type S1<G1> = spartan::RelaxedR1CSSNARK<G1, EE1<G1>>;
  type S2<G2> = spartan::RelaxedR1CSSNARK<G2, EE2<G2>>;
  type S1Prime<G1> = spartan::pp::RelaxedR1CSSNARK<G1, EE1<G1>>;
  type S2Prime<G2> = spartan::pp::RelaxedR1CSSNARK<G2, EE2<G2>>;

以input为 2 6 2^6 26个字节0为例,有:

Nova-based SHA256 compression function iterations
=========================================================
Producing public parameters...
PublicParams::setup, took 57.5854094s 
Number of constraints per step (primary circuit): 37034
Number of constraints per step (secondary circuit): 10347
Number of variables per step (primary circuit): 37000
Number of variables per step (secondary circuit): 10329
Generating a RecursiveSNARK...
RecursiveSNARK::prove_step 0: true, took 891.396614ms 
RecursiveSNARK::prove_step 1: true, took 1.298414351s 
Total time taken by RecursiveSNARK::prove_steps: 2.189852312s
Verifying a RecursiveSNARK...
RecursiveSNARK::verify: true, took 417.041ms
Generating a CompressedSNARK using Spartan with IPA-PC...
CompressedSNARK::prove: true, took 83.01777488s
Total proving time is 148.783484203s
CompressedSNARK::len 10038 bytes
Verifying a CompressedSNARK...
CompressedSNARK::verify: true, took 695.121631ms
=========================================================
Public parameters generation time: 57.5854094s 
Total proving time (excl pp generation): 148.783484203s
Total verification time: 695.121631ms
=========================================================
Expected value of final hash = "f5a5fd42d16a20302798ef6ed309979b43003d2320d9f0e8ea9831a92759fb4b"
Actual value of final hash   = "f5a5fd42d16a20302798ef6ed309979b43003d2320d9f0e8ea9831a92759fb4b"

Nova系列博客

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值