如何写好Simulation证明(一): 语义安全
密码学中很多证明需要用到Simulation, 尤其是ZK, MPC等等. 对于初学者来说, 涉及Simulation的证明往往不容易理解, 更别说自己独立证明, 所以有必要学习一下如何写这样的证明.
文章主要参考Yehuda Lindell的讲义: How to simulate it.
1. Introduction
什么是Simulation? 中文翻译成模拟. 在证明中, 我们需要用Simulation去说明, 某件事在"真实世界"和"理想世界"中是差不多的. 理想世界就是secure by definition, 是安全性定义里的安全.
举个例子, 语义安全性(semantic security)中, 要求接收到密文的敌手和什么都没有接收到的敌手学到的东西一样. 后者就是理想世界. 显然理想世界中的敌手学到的东西为0, 所以实际上需要证明的东西就是现实世界中敌手学到的东西为0. 可能会有人觉得这样定义很冗余. 但是事实上这样的定义能够formal地说明, 现实世界的敌手"learned nothing".
2. The Basic Paradigm: Semantic Security
基于复杂性的密码学就诞生于Semantic Security的定义. 这个定义形式化的表达了"nothing be learned".
2.1 定义
在给出定义前, 我们先做一些解释. 这个定义允许敌手在之前获得一些额外的关于明文的消息 \(h\) (可能是通过各种方式泄露出去的), 增强定义的鲁棒性.
语义安全性的正式定义如下:
现在有对称加密方案 \((G, E, D)\), 若对于任意的 nuPPT \(\mathcal A\), 都存在 nuPPT \(\mathcal A'\), 使得对于任意的prob. ensemble \(\{X_n\}_n\), \(|X_n| < poly(n)\), 对于任意的 poly-bound 函数 \(f, h\), 所有多项式 \(p()\), 当 \(n\) 足够大时, 下面的不等式成立, 则该方案是语义安全的.
定义中涉及符号比较多, 我们简单解释一下. \(\mathcal A\) 是获得了密文的敌手, 它的输入中有 \(E_k(X_n)\). \(\mathcal A'\) 是没有获得密文的人, 输入为明文 \(X_n\) 的长度. 其中函数 \(h\) 表示的是一些附加信息. \(\mathcal A\) 和 \(\mathcal A'\) 都分别尝试去猜函数 \(f(1^n,X_n)\) 的值, 要求他们猜的结果是几乎一样的.
2.2 分析
我们对语义安全性的定义进行分析. 虽然定义中并没有明确提出Simulation, Real world这些字眼, 但是实际上暗含了这个意思. 我们想 \(\mathcal A'\) 其实就是理想世界, 他获得的东西只有 \(h\) 和 \(X_n\) 的长度. 所以他能学到的知识就是从这两个东西中来的. 而 \(\mathcal A\) 就是真实世界, 他们两个学到知识的比较, 其实就是real world 与 ideal world的比较.
下面我们看如何使用定义证明一个加密方案是语义安全的. 主要的问题就是如何构造一个 \(\mathcal A'\), 与 \(\mathcal A\) 以几乎相同的概率输出 \(f(1^n, X_n)\). 方法就是让 \(\mathcal A'\) simulate \(\mathcal A\) 的执行, 然后输出 \(\mathcal A\) 的输出. 但还有一个问题, 就是 \(\mathcal A\) 真的接收到了一个密文, 但是显然 \(\mathcal A'\) 是没有的. 解决方法是 \(\mathcal A\) 喂给 \(\mathcal A\) 一堆垃圾. 下面是具体的模拟.
Simulator \(\mathcal A'\) with input \((1^n,1^{|X_n|},h(1^n,X_n))\):
- Run \(G(1^n)\) to get \(k\)(secret key).
- Compute \(c = E_k(0^{|X_n|})\).
- Run \(\mathcal A(1^n, c,h(1^n,X_n))\) and output what \(\mathcal A\) outputs.
这个simulation可行的前提是: 该加密方案是indistinguishable的. 这样的话 \(\mathcal A\) 无论是收到什么消息的加密, 输出都是一样的. 否则 \(\mathcal A\) 就能区分消息的加密了.
2.3 总结
这个证明实际上是一个典型的simulation proof. 我们通过构造一个模拟器simulator, 他的内部调用敌手运行敌手的程序. 然后证明这个simulator的输出和敌手的输出是不可区分的. 在这个例子中, 我们让simulator给敌手一堆不可区分的垃圾, 以此完成构造.