zkRA

114 阅读9分钟

From Interaction to Independence: zkSNARKs for Transparent and Non-Interactive Remote Attestation

2024-3 Network and Distributed System Security (NDSS) CCF-A


论文简记:该论文针对传统RA方案的交互性和过程不透明,引入基于Merkle Tree的zkSNARK和Poseidon hash

方案具有:透明、全局挑战、与平台无关、防DoS攻击、信任分散(去中心化)等优势

🔁 研究内容


💡 该论文的Tag

zkSNARK、Poseidon hash、Merkle Tree、blockchain、smart contract

🧩 研究意义(现实背景)

Remote attestation:用于验证网络设备完整性,应用于安全启动验证、组织内部访问控制、数据权限等领域

e.g.GPT:假设有一个智能门锁

  1. 验证固件完整性:当智能门锁上线时,它将生成一个报告,证明它的固件未被修改。
  2. 报告可信状态:通过安全硬件签名,该报告发送到验证服务器。
  3. 允许访问:验证服务器确认门锁运行的是官方固件后,允许其接入网络。否则,拒绝连入网络

⚙️ 研究的问题

验证者:制造商 证明者:设备

传统RA中,验证者拥有设备内容特权信息,验证者向设备发出随机挑战,设备生成响应,验证者根据特权信息推测预期响应并和设备响应比对,如果一致,则设备可信,否则,存在异常。

显然,上面是交互式,随着设备数量增多,交互式协议存在可拓展性问题

此外,特权信息一般由设备厂商掌握,用户无法验证,即RA不透明

问题总结为:如何在远程设备上进行透明且无交互的远程认证

💧 解决该问题的方案演变、各自优点和局限性,为什么要提出该论文

image-20250102104957579

使用区块链技术、代理验证器来增强传统RA的可扩展性问题,但没有突破交互性质和特权信息的特殊访问权限的限制

有些相关工作探索了非交互式RA协议,依赖于专用代理或者私有Hyperledger全节点,但成本高昂且服务器崩溃\Rightarrow整个RA协议崩溃

总结为:以往RA技术依赖于交互式通信、设备特权信息的预先共享或者由可信方验证RA请求,文章提出zRA方案,消除实时交互,允许不询问可信方(制造商)的情况下对设备认证。

👩🏻‍💻 论文中使用的前人技术、方案

  1. 使用zkSNARKs(零知识简洁非交互式知识论证)生成可证明且可扩展的证明,即不受设备和生成的证明数量影响
  2. 引入Commitment Schemes实现透明验证 attestation claim的正确性
  3. 使用MPC协议在利益相关者之间分配设置,增强 Distributed trust
  4. 使用Poseidon hash作为哈希函数,提高zkSNARK效率
  5. Merkle Tree
  6. 利用区块链的透明、去中心化特性,实现Global challenge和证明透明

🔬 方案设计

image-20250102103100554

  1. 设置阶段:制造商离线执行此阶段,为每个设备计算一组未来的响应,并构建一个包含所有设备响应的Merkle树。

    制造商预先为每个设备生成m个挑战,构成一个Merkle Tree,n个设备的Merkle Tree Root Hash 构成一个新的Merkle Tree。Merkle Tree可以公开访问(但不包括challenge的顺序)这保证,每个设备的每个证明的路径唯一,用于ZKP证明阶段

    img

  2. 更新全局挑战:制造商定期将最新的挑战发布到区块链上,以便设备可以获取最新的挑战。

  3. 认证阶段:设备查询区块链以获取最新的全局挑战,计算认证证据,并使用zkSNARKs生成证明,然后将证明提交到区块链上。

    img

  4. 验证阶段:验证者只需检查提交的证明的有效性,无需特权访问授权数据。

🤔 个人总结


📜 论文中的那些设计/语言值得总结?

  1. 论文中,制造商定期发布挑战,制造商是中心化的,有单点故障问题(制造商崩溃/挑战序列被揭露),论文让所有利益相关者参与阈值挑战生成仪式,将Setup阶段的Distributed Trust,更灵活、实用、适应性

image-20250102095428276

  1. 传统是每个设备与制造商服务器通信,论文是制造商将挑战发布到区块链,设备通过区块链API检索挑战,实现“证明与设备数量无关“

只是把通信成本转移到了区块链,但区块链去中心化,无单点故障、防DoS攻击。可参考思路、写法

论文代码

zkRA repo:github.com/zero-savvy/…

代码主要基于CircomSnarkjs两个库,可以阅读【circom与snarkjs教程】初步入门

这里简要说明:

  1. Circom:将计算问题转化为算术电路,导出

    1. .r1cs:描述电路的逻辑和约束,也就是电路的“数学公式”
    2. .wasa:电路的运行程序,读取数据,按照.r1cs计算,输出结果witness(.wtns)
  2. snarkjs:看作是一个工具链,基于JavaScript语言根据算术电路,来实现zkSNARK的“特性”(简洁性、非交互等)

    • 基于.r1cs文件生成证明密钥和验证密钥,用于证明的生成和验证
    • 基于密钥和电路生成零知识证明
    • 可将验证逻辑导出为xxx.sol,用于与区块链集成
文件结构
benchmarking  circom  doc  js  python  README.md

主文件夹简要说明

  • README.md: 项目的简略文档;
  • circom文件夹:存放Circom电路文件
  • python文件夹:定义一些脚本操作,比如和以太坊的交互操作、打印Merkletree操作等
  • js文件夹:定义密钥和设备管理的脚本、导入工具包、设置项目依赖等
  • benchmarking文件夹:存放性能测试相关文件,如生成的ZKP,公共参数等,每个文件夹是一个“成品”,可以作为最终目标

根据最开始的介绍可知,重点在于js/circom/文件夹

circom/ 文件夹

circom相关知识:Circom 是一种将计算问题编写为“数字电路”的编程语言,用于定义零知识证明电路。一般每个.circom文件定义一种运算操作(+、*、hash、sign)的电路逻辑

Circom 2文档:docs.circom.io/

用于存放Circom电路文件,这些电路是零知识证明系统的核心组件。

  • circuits/tornado-core/circuits/

    • merkleTree.circom: 实现Merkle树验证的电路,用于验证Merkle路径的正确性。
  • circomlib/:

    • circuits/poseidon.circom: 包含Poseidon哈希函数的具体实现细节,供其他电路引用。
  • zRA.circom核心电路文件

    • 定义zkSNARK电路,负责生成证明的电路逻辑
    •   安装circom环境后,生成电路表示文件
        circom zRA.circom --r1cs --wasm --sym -o build
      
    • 提供输入文件(比如benchmarking文件夹中的good_input.json),再通过snarkjs工具,生成证明
  • attest.sol

    • 是论文作者写的一些逻辑判断、权限认证
    • 部署到以太坊用于验证证明、定期发布全局挑战、管理设备的Merkle树
  • verifier.sol

    • 通过snarkjs工具的generateverifier命令生成

    • 证明验证的具体实现,没必要看,看懂zRA.circom即可

    相关命令参考官方文档、参考博客或下文第8节,这里不赘述

js/ 文件夹

包含协议设置阶段的代码,应由制造商执行,负责项目的设置、设备密钥生成、挑战生成以及Merkle树的构建等功能。

  • manufacturer.js: 负责生成设备的私钥、公钥和地址。
  • setup_phase.js: 主要用于设置阶段,包括设备数量的输入、生成挑战、构建Merkle树等。
  • findPath.js: 用于在Merkle树中查找特定叶子的路径,生成Merkle路径和索引。
  • utils.js和tool.js: 提供辅助函数
  • device.js:无用

python/文件夹:

用于与已经部署的合约进行交互

  • device.py:通过web3.py与以太坊网络交互,使用私钥构造并签名一笔“attest”交易,然后将其发送到指定的合约地址。
  • posi.py:根据论文eprint.iacr.org/2019/458.pd…中的参数要求,定义Poseidon哈希函数
  • merkle_tree.py:使用Poseidon哈希函数实现Merkle tree,而非Keccak-256、SHA-256。模数p为256bit

运行流程

image-20250106201422761

  1. 制造商执行js文件夹下setup_phase.js

    • 输入设备数量k和认证频率t
    • 调用manufacturer.jscreateDeviceKeys()生成k个设备密钥、n个未来待发布的挑战序列->存储在chanllenges.json
    • 每个设备对应n个挑战、n个响应,通过poseidon哈希函数绑定在一起,作为Merkle Tree的叶子节点,一个设备对应一个devMT
    • 根据各个设备的devMT的根节点,构建主Merkle Tree。
    • MerkleTree文件在devMT_files/
  2. 制造商编写circom/zRA.circom通过circom编译、snarkjs导出为attest.sol合约,部署到区块链

  3. 设备本地编译zRA.circom文件,生成generate_witness.js,输入good_input.json文件,生成witness.wtns

  4. 设备通过device.py调用attest.solattest方法,来验证证明

    image-20250108105612348

核心代码分析

js/manufacturer.js:

该文件是整个项目的核心! 
function createResponse(challenge, childSecret) {
    let correct_measurements = '0x01020304050607'; // 正确的测量值(根据应用程序可以是任何值,例如内存占用的哈希值等),也就是secret input!
    return modSNARK('0x' + sha256([challenge, childSecret, correct_measurements]).toString('hex')); // 生成响应
}
// 注:制造商和设备应该是用的相同的createResponse函数,,论文中作者提到response的生成有三类,上面代码只是一个例子,没有实际意义function createChallenges(numAtts) {
    let challenges = [];
    challenges.push('0x' + '1234567890abcdef'); // 初始挑战
    const some_secret = '0x' + '4444555555abcdef'; // 一些秘密值
    for (let i = 0; i < numAtts; i++) {
        challenges.push(modSNARK('0x' + sha256([challenges[0], some_secret, challenges[i]]))); // 生成新的挑战,即cha[i+1] = sha(...)
    }
    return challenges.slice(1); // 返回挑战数组(去掉初始挑战)
}

circom/attest.sol

function attest(
uint[2] calldata _pA,           
uint[2] calldata _pB1,
uint[2] calldata _pB2,
uint[2] calldata _pC,       // 电路的中间结果
uint[3] calldata _pubSignals
) public { 
    ...
}
/*  分析函数调用可知:
    _pubSignals[0]是DevMekrle_root
    _pubSignals[1]是devAddr
    _pubSignals[2]是challenge
    合约并未指定response,可根据需求自己实现,并作为参数传递
*/

运行环境

node + snarkjs

curl -o- https://raw.githubuserconten t.com/nvm-sh/nvm/v0.39.3/install.sh | bash
source  ̃/.bashrc
nvm install v16.20.0
​
npm install -g snarkjs
sudo apt-get install bc

circom

Installation - Circom 2 Documentation

python

本人运行代码时python环境的requierments.txt:

attrs==24.3.0
bcrypt==4.2.1
certifi==2024.12.14
cffi==1.17.1
charset-normalizer==3.4.1
cryptography==44.0.0
galois==0.3.10
idna==3.10
iniconfig==2.0.0
llvmlite==0.42.0
numba==0.59.1
numpy==1.26.4
packaging==24.2
paramiko==3.5.0
pluggy==1.5.0
poseidon==0.3.1
poseidon-hash==0.1.4
py==1.11.0
pycparser==2.22
PyNaCl==1.5.0
pytest==7.1.3
requests==2.32.3
simplejson==3.19.3
tomli==2.2.1
typing_extensions==4.12.2
urllib3==2.3.0

js

cd ~zk-remote-attestation/js
npm install
node 

证明生成流程

电路编译、生成证明、导出合约精简命令如下

命令作用请查看 github.com/iden3/snark… 文档

注:以下使用的good_input从benchmarking/ra40文件夹复制,读者可自行设置

cd circom
circom zRA.circom --r1cs --wasm --sym --c
node zRA_js/generate_witness.js zRA_js/zRA.wasm  good_input.json witness.wtns
wget https://hermez.s3-eu-west-1.amazonaws.com/powersOfTau28_hez_final_16.ptau -O pot16_final.ptau
snarkjs groth16 setup zRA.r1cs pot16_final.ptau zRA.zkey
snarkjs groth16 prove zRA.zkey witness.wtns proof.json public.json
snarkjs zkey export solidityverifier zRA.zkey verifier_test.sol
ls
# 之后按需将验证合约部署到区块链,这里不赘述

以下为上面命令的具体输入,可供参考

wenmou@LAPTOP-K7MOCBEL:~/code/py311/zk-remote-attestation/circom$ circom zRA.circom --r1cs --wasm --sym --c
warning[P1004]: File "zRA.circom" does not include pragma version. Assuming pragma version (2, 2, 1)
 = At the beginning of file "zRA.circom", you should add the directive "pragma circom <Version>", to indicate which compiler version you are using.
​
warning[P1004]: File "/home/wenmou/code/py311/zk-remote-attestation/circom/tornado-core/circuits/merkleTree.circom" does not include pragma version. Assuming pragma version (2, 2, 1)
 = At the beginning of file "/home/wenmou/code/py311/zk-remote-attestation/circom/tornado-core/circuits/merkleTree.circom", you should add the directive "pragma circom <Version>", to indicate which compiler version you are using.
​
template instances: 144
non-linear constraints: 10104
linear constraints: 11301
public inputs: 3
private inputs: 81
public outputs: 0
wires: 21449
labels: 32102
Written successfully: ./zRA.r1cs
Written successfully: ./zRA.sym
Written successfully: ./zRA_cpp/zRA.cpp and ./zRA_cpp/zRA.dat
Written successfully: ./zRA_cpp/main.cpp, circom.hpp, calcwit.hpp, calcwit.cpp, fr.hpp, fr.cpp, fr.asm and Makefile
Written successfully: ./zRA_js/zRA.wasm
Everything went okay
wenmou@LAPTOP-K7MOCBEL:~/code/py311/zk-remote-attestation/circom$ node zRA_js/generate_witness.js zRA_js/zRA.wasm  good_input.json witness.wtns
wenmou@LAPTOP-K7MOCBEL:~/code/py311/zk-remote-attestation/circom$ snarkjs groth16 setup zRA.r1cs pot16_final.ptau zRA.zkey
[INFO]  snarkJS: Reading r1cs
[INFO]  snarkJS: Reading tauG1
[INFO]  snarkJS: Reading tauG2
[INFO]  snarkJS: Reading alphatauG1
[INFO]  snarkJS: Reading betatauG1
[INFO]  snarkJS: Circuit hash: 
        1a6c4a04 ad8d7bcc 2148e9fd 3ab295f8
        411468a7 b2fd32e7 988afcf3 0fa8323c
        6fbd74b3 11504e49 81483f5d 9e14a236
        19812d56 7eaa9e31 0e06abe3 ee6b4e16
wenmou@LAPTOP-K7MOCBEL:~/code/py311/zk-remote-attestation/circom$  snarkjs groth16 prove zRA.zkey witness.wtns proof.json public.json
wenmou@LAPTOP-K7MOCBEL:~/code/py311/zk-remote-attestation/circom$ snarkjs zkey export solidityverifier zRA.zkey verifier_test.sol
[INFO]  snarkJS: EXPORT VERIFICATION KEY STARTED
[INFO]  snarkJS: > Detected protocol: groth16
[INFO]  snarkJS: EXPORT VERIFICATION KEY FINISHED
wenmou@LAPTOP-K7MOCBEL:~/code/py311/zk-remote-attestation/circom$ ls
attest.sol        proof.json          tornado-core       zRA.circom  zRA_cpp
circomlib         public.json         verifier.sol       zRA.r1cs    zRA_js
good_input.json   sample_proof.json   verifier_test.sol  zRA.sym
pot16_final.ptau  sample_public.json  witness.wtns       zRA.zkey