草庐IT

递归零知识证明

sCrypt 智能合约 2024-01-03 原文

本文我们将介绍递归零知识证明(ZKP),即一个证明证明了另一个证明的有效性,以及它优于标准非递归零知识证明 (ZKP) 的优势,并通过将其应用于证明费拨那契 (Fibonacci) 序列来展示它的强大功能。

什么是递归 ZKP

假设 Peggy 想向 Victor 证明她下周将在公园度过,而她只想使用一张照片来证明这一点。她可以做到以下几点:

图1: 递归证明

第 1 天,她在公园里拍了一张照片,上面有显示日期的日历。

第 2 天,她拿着第 1 天的照片在公园里用日历拍了另一张照片。

第 3 天,她拿着日历在公园里拍了另一张照片,同时拿着第 2 天拍摄的照片。

重复相同的程序直到第 7 天。现在,她用一张照片证明了她为期一周的旅行。

类似于上面的比喻示例,在递归 ZK 证明中,该证明证明了另一个证明的有效性,该证明本身也验证了另一个证明,依此类推。

为什么我们需要它

与标准 ZKP 相比,递归 ZKP 具有几个显着优势。

聚合

多个证明可以聚合成一个证明。单一证明只有在所有组成证明都有效时才有效,并且更容易验证。当在区块链上验证证明时,这尤其有吸引力。数以千计的证明可以压缩成一个证明,从而节省大量的验证成本。

图2: 多个证明合并为一个证明

并行化证明生成

假设我们要证明一批 1000 笔交易在 rollup 中是有效的。使用标准 ZKP,证明者需要为每笔交易生成一个单独的证明来依次验证 1000 笔交易,这是一项非常耗时的工作。所有证明都可以并行生成,因为它们都是独立的,因此证明时间要短得多。这些单独的交易证明可以递归地聚合在一个单一的证明中,如上所示。

增量可验证计算 (IVC)

如果证明是可增量更新的,那么证明某些类型的计算会更有效。

  • 长计算:证明一个过长的计算会在证明者端占用大量内存。有些计算甚至无法放入内存中,因此无法证明。

  • 演进计算:例如,我们想证明区块链的状态,但它在不断增长。我们计算一个新的证明,它不仅可以验证新块,还可以验证现有的证明本身。

我们将计算分解为更小的步骤并迭代地证明它们。每个步骤都包含一个证明,证明计算的当前状态。使用递归 ZKP(更具体地说,IVC),可以通过递归使用当前步骤及其证明来为下一步生成新证明。证明更新不需要像标准 ZKP 那样从第一步开始重新计算,并且与计算的总长度无关。

例如,对于 i0n 的函数 F,我们有以下计算:

zᵢ 是公共输入, wᵢ 是秘密输入(即见证人)。如果我们从 z₀ 开始,我们想要有效地证明最终输出是 zₙ

IVC 通过生成初始证明 𝛑₀F 是证明者算法)并在每一步逐步更新它来实现这一点。例如,𝛑₂ 不仅证明了 F(z₁, w₁) = z₂,而且还证明了 𝛑₁ 也是有效的。通过归纳,最后的 𝛑ₙ 证明所有中间步骤都是正确的。

图5: 增量可验证计算

它是如何工作的

在高层次上,诸如 SNARK 之类的 ZKP 可以验证任意计算。由于验证 SNARK 本身就是一种计算,因此 SNARK 可以验证其他 SNARK 证明。递归 SNARK 证明证明存在先前存在有效的证明。此计算/电路的证明证明了内部证明的有效性,该证明可能包括另一个证明。

图6: 递归 SNARK

图7: 验证者

到目前为止,我们已经解释了递归 SNARK 在理论上是如何工作的。在实践中,验证是一种密集计算,涉及繁重的密码学操作,例如双线性配对。必须采用许多新技术,例如椭圆曲线的循环,才能有效地工作。在这篇简短的博文中,我们不会详述这些实际问题。

斐波那契示例

我们将递归 SNARK 应用于斐波那契数列。斐波那契数列由递归关系定义:

Peggy 想要让 Victor 相信序列中有一个数字,比如 55 (F₁₀)。可以使用 snarkyjs,一种用于递归 SNARK 的 Typescript/Javascript 框架。

import { SelfProof, Field, ZkProgram, verify} from 'snarkyjs';

let FibonacciSequence = ZkProgram({
  publicInput: Field,

  methods: {
    // those are our base cases that we start with - defined as:
    // fib_0 = 0
    // fib_1 = 1
    // we need a proof associated with the base cases so we can recursively verify their correctness
    fib0: {
      privateInputs: [],

      method(fib: Field) {
        fib.assertEquals(Field.zero);
      },
    },
    fib1: {
      privateInputs: [],

      method(fib: Field) {
        fib.assertEquals(Field.one);
      },
    },

    inductiveFib: {
      privateInputs: [SelfProof, SelfProof],

      method(fib: Field, fib1: SelfProof<Field>, fib2: SelfProof<Field>) {
        // recursion below
        fib1.verify();
        fib2.verify();
        let newFib = fib1.publicInput.add(fib2.publicInput);
        fib.assertEquals(newFib);
      },
    },
  },
});

console.log('compiling ..');
const { verificationKey } = await FibonacciSequence.compile();
console.log('compiling finished');

// proving: generating proof by doing the actual computation
let fib_n_2 = await FibonacciSequence.fib0(Field.zero);
let fib_n_1 = await FibonacciSequence.fib1(Field.one);
// following the formula fibn = fibn-1 + fibn-2
let fib_n;
// proving fib_N
const N = 10;
for (let n = 2; n <= N; n++) {
  console.log(`working on fib_${n}..`);
  let publicInput: Field = fib_n_1.publicInput.add(fib_n_2.publicInput);
  fib_n = await FibonacciSequence.inductiveFib(
    publicInput,
    fib_n_1,
    fib_n_2
  );

  fib_n_2 = fib_n_1;
  fib_n_1 = fib_n;

  console.log(`got fib_${n} = ${fib_n.publicInput.toString()}`);
}


// verifying: verifier only needs the latest proof fib_n
console.log('verify...');
let ok = await verify(fib_n, verificationKey);
console.log(`is ${fib_n.publicInput.toString()} in the sequence? ${ok}`);
使用 snarkyjs 对斐波那契数列进行递归证明

3-38 行定义了递归证明。我们在第 11 行有基本案例 fib0,在第 18 行有 fib1。归纳案例从第 26 行开始。证明在第 31 行和第 32 行递归验证,表示只有当 Fₙ-₁Fₙ-2 的证明都有效时,Fₙ 的证明才有效,并遵循公式。

41 行编译电路并检索验证密钥。

Peggy 从第 44 行到第 64 行迭代生成证明。

Victor 使用验证密钥验证最终证明。值得注意的是,他只需要最终证明并在几秒钟内验证它,而不管第 50 行的序列索引 N 是多少。

在这个简单示例中,Victor 可能只是重新计算整个序列直到 N 为 10 并立即验证它。当 N 很大时,例如 1,000,000,000,000,递归 SNARK 的优势更加明显。Victor 仍然可以在几秒钟内验证证明,比重新计算要快得多。

有关递归零知识证明的更多相关文章

  1. ruby - 递归地将所有数字字符串转换为 Ruby 哈希中的整数 - 2

    我有一个随机大小的散列,它可能有类似"100"的值,我想将其转换为整数。我知道我可以使用value.to_iifvalue.to_i.to_s==value来做到这一点,但我不确定我将如何在我的散列中递归地做到这一点,考虑到一个值可以是一个字符串,或一个数组(哈希或字符串),或另一个哈希。 最佳答案 这是一个非常简单的递归实现(尽管必须同时处理数组和散列会增加一些技巧)。deffixnumifyobjifobj.respond_to?:to_i#IfwecancastittoaFixnum,doit.obj.to_ielsifobj

  2. Ruby:标准递归模式 - 2

    我经常迷上ruby​​的一件事是递归模式。例如,假设我有一个数组,它可能包含无限深度的数组作为元素。所以,例如:my_array=[1,[2,3,[4,5,[6,7]]]]我想创建一个方法,可以将数组展平为[1,2,3,4,5,6,7]。我知道.flatten可以完成这项工作,但这个问题是作为我经常遇到的递归问题的一个例子-因此我试图找到一个更可重用的解决方案。简而言之-我猜这种事情有一个标准模式,但我想不出任何特别优雅的东西。任何想法表示赞赏 最佳答案 递归是一种方法,它不依赖于语言。您在编写算法时要考虑两种情况:再次调用函数的情

  3. ruby - 为什么我用递归得到 "stack level too deep"? - 2

    我有这个ruby代码:defget_sumnreturn0ifn似乎正在为999之前的值工作。当我尝试9999时,它给了我这个:stackleveltoodeep(SystemStackError)所以,我添加了这个:RubyVM::InstructionSequence.compile_option={:tailcall_optimization=>true,:trace_instruction=>false}但什么也没发生。我的ruby版本是:ruby1.9.3p392(2013-02-22revision39386)[x86_64-darwin12.2.1]我还增加了机器的堆栈大

  4. ruby - 如何证明 Ruby `for` 循环实际上是使用 `each` 方法实现的? - 2

    在EloquentRuby(第21页,第一版,第六次打印)一书中,作者(RussOlsen)提倡使用each方法而不是for循环,这与我在其他地方读到的所有内容一致。但是作者还继续说,这样做的一个原因是for循环实际上调用了each方法,所以为什么不直接删掉中间人并使用each?所以我想知道这实际上是如何工作的。为了调查,我确实在github上的Ruby存储库上进行了搜索,但发现很难确定我在哪里/如何看到它的实际效果。重述问题:我如何证明Rubyfor循环实际上是使用each方法实现的? 最佳答案 您可以通过编写一个实现每个的类来展

  5. ruby - 构建网络蜘蛛时,应该使用递归吗? - 2

    构建一个深度优先的网络蜘蛛,这意味着它将访问第一页上的所有链接,然后转到每个链接,并访问所有第二页上的链接...你应该使用递归吗?我发现这是CPU密集型的。defrecursion()linkz_on_first_page.eachdo|link|recursion(link)endendrecursion(firstpage) 最佳答案 绝对不是,由于万维网的实际性质,您很快就会遇到问题。当您访问带有主导航部分的网站时,每个页面都链接到其他页面,您就进入了一个无限循环。您可以跟踪您处理了哪些链接,但即便如此,递归循环并不真正适合万

  6. ruby-on-rails - 如何以递归方式将 YAML 文件扁平化为 JSON 对象,其中键是点分隔的字符串? - 2

    例如,如果我有YAML文件en:questions:new:'NewQuestion'other:recent:'Recent'old:'Old'这最终会变成一个json对象,例如{'questions.new':'NewQuestion','questions.other.recent':'Recent','questions.other.old':'Old'} 最佳答案 由于问题是关于在Rails应用程序上使用YAML文件进行i18n,因此值得注意i18ngem提供了一个辅助模块I18n::Backend::Flatten完全像

  7. ruby - 为什么尾递归 gcd 比 rubinius 的 while 循环更快 - 2

    我有这两个gcd函数的实现:defgcd1(a,b)ifa==baelsifa>bif(a%b)==0belsegcd1(a%b,b)endelseif(b%a)==0aelsegcd1(a,b%a)endendenddefgcd2(a,b)if(a==b)returnaelsifb>amin,max=a,belsemin,max=b,aendwhile(max%min)!=0min,max=max%min,minendminend函数gcd1是尾递归的,而gcd2使用while循环。我已经验证rubinius通过对阶乘函数进行基准测试来执行TCO,只有阶乘函数基准测试显示递归版本和迭

  8. ruby-on-rails - 为什么我的 helper 递归方法不返回每个值? - 2

    我想显示一个由gem祖先管理的类别树。我想使用一个助手,它会递归地遍历树并一个一个地返回类别,暂时没有html标签或内容。moduleCategoriesHelperdefdisplay_tree(category)ifcategory.has_children?category.children.eachdo|sub_category|display_tree(sub_category)puts(sub_category.name)#tocheckifitgoeshereendendcategory.nameendendcategory参数是根类别之一。它应该返回什么?在网页中:它仅

  9. ruby - 如何处理树顶左递归 - 2

    我有一个grammarfile对于我正在尝试构建的一种新的通用编程语言。我正在努力使该语言健壮且易于使用(它深受Ruby等启发),为此我引入了一些左递归规则。我看到一些例子似乎表明了以下左递归规则:rulel_recursel_recurse/'somethingelse'end可以通过将其更改为非左递归:ruler_recurse'somethingelse'/r_recurseend对我来说,这看起来会有不同的问题并且仍然会失败。我是对的,还是这会“奏效”?我试图(查找和)消除的特定左递归可以在这个grammarfile中找到.我不确定哪些规则受到影响,但至少somewerepoi

  10. ruby - 如何递归 rake ? -- 或合适的替代品 - 2

    我希望我的项目的顶级Rakefile使用树中更深的rakefile来构建东西;即顶层rakefile说明如何构建项目(大图),而较低层的rakefile说明如何构建特定模块(本map片)。当然有一组共享的配置,用于在任务之间共享时执行的详细信息:所以它主要是关于保持对需要构建的内容的描述,尽可能接近正在构建的源。例如。/Source/Module/code.foo和cie应该使用/Source/Module/Rakefile中的指令构建;并且/Rakefile了解模块之间的依赖关系。我不关心它是否使用多个rake进程(ala递归make),或者只是创建单独的构建环境。无论哪种方式,它都

随机推荐