Dagger-Hashimoto
Sayfanın son güncellenmesi: 11 Nisan 2024
Dagger-Hashimoto, Ethereum'un madencilik algoritması için orijinal araştırma uygulaması ve şartnamesiydi. Dagger-Hashimoto'nun yerini Ethash almıştır. 15 Eylül 2022'deki Birleşim ile madencilik tamamen durduruldu. O zamandan beri Ethereum, bunun yerine bir hisse ispatı mekanizması kullanılarak güvence altına alınmıştır. Bu sayfa sadece bilgilendirme içindir - burdaki bilgi Birleşim sonrası Ethereum için geçerli değildir.
Ön Koşullar
Bu sayfayı daha iyi anlamak için önce iş ispatı mutabakatı, madencilik ve madencilik algoritmaları hakkında bilgi edinmenizi öneririz.
Dagger-Hashimoto
Dagger-Hashimoto iki hedefi gerçekleştirmeyi amaçlar:
- ASIC direnci: algoritma için özel donanım oluşturmanın faydası mümkün olduğunca az olmalıdır
- Hafif istemci doğrulanabilirliği: bir blok, hafif bir istemci tarafından verimli bir şekilde doğrulanabilir olmalıdır.
Ek bir değişiklikle, istenirse, ancak ek karmaşıklık pahasına üçüncü bir hedefin nasıl yerine getirileceğini de belirtiyoruz:
Tam zincir depolama: madencilik, tüm blokzincir durumunun depolanmasını gerektirmelidir (Ethereum durum ağacının düzensiz yapısı nedeniyle, özellikle sık kullanılan bazı sözleşmelerde bir miktar budamanın mümkün olacağını tahmin ediyoruz, ancak bunu en aza indirmek istiyoruz).
DAG Üretimi
Algoritmanın kodu aşağıdaki Python'da tanımlanacaktır. İlk olarak, belirtilen hassasiyetteki işaretsiz tam sayıları dizelere sıralamak için encode_int veriyoruz. Tersi de aşağıda verilmiştir:
1NUM_BITS = 51223def encode_int(x):4 "Bir tamsayı x'i büyük endian şeması kullanarak 64 karakterlik bir dize olarak kodlayın"5 o = ''6 for _ in range(NUM_BITS / 8):7 o = chr(x % 256) + o8 x //= 2569 return o1011def decode_int(s):12 "Büyük endian şeması kullanarak bir dizeden bir x tamsayısının kodunu çözün"13 x = 014 for c in s:15 x *= 25616 x += ord(c)17 return xTümünü gösterArdından, sha3'ün bir tam sayı alıp bir tam sayı çıktısı veren bir işlev ve dbl_sha3'ün bir çift sha3 işlevi olduğunu varsayıyoruz; bu referans kodu bir uygulamaya dönüştürüyorsanız, şunu kullanın:
1from pyethereum import utils2def sha3(x):3 if isinstance(x, (int, long)):4 x = encode_int(x)5 return decode_int(utils.sha3(x))67def dbl_sha3(x):8 if isinstance(x, (int, long)):9 x = encode_int(x)10 return decode_int(utils.sha3(utils.sha3(x)))Tümünü gösterParametreler
Algoritma için kullanılan parametreler şunlardır:
1SAFE_PRIME_512 = 2**512 - 38117 # En Büyük Güvenli Prime 2**512'den küçük23parametreler = {4 "n": 4000055296 * 8 // NUM_BITS, # Veri kümesinin boyutu (4 Gigabayt); 65536'NIN KATINDA OLMALIDIR5 "n_inc": 65536, # Dönem başına n değerinde artış; 65536'NIN KATINDA OLMALIDIR6 # epochtime ile=20000, yılda 882 MB büyüme sağlar7 "cache_size": 2500, # Light istemcinin önbelleğinin boyutu (light ile seçilebilir8 # istemci; algo spesifikasyonunun bir parçası değil)9 "diff": 2**14, # Zorluk (blok değerlendirmesi sırasında ayarlanır)10 "epochtime": 100000, # Bir dönemin blok cinsinden uzunluğu (veri kümesinin ne sıklıkta güncellendiği)11 "k": 1, # Bir düğümün ebeveyn sayısı12 "w": w, # Modüler üslü karma için kullanılır13 "erişim": 200, # Hashimoto sırasında veri kümesi erişimlerinin sayısı14 "P": SAFE_PRIME_512 # Karma ve rastgele sayı üretimi için Safe Prime15}Tümünü gösterBu durumda P, log₂(P)'nin 512'den biraz daha küçük olacak şekilde seçilmiş bir asal sayıdır, bu da sayılarımızı temsil etmek için kullandığımız 512 bite karşılık gelir. DAG'nin yalnızca ikinci yarısının gerçekten depolanması gerektiğini unutmayın, bu nedenle RAM gereksinimi 1 GB'den başlar ve yılda 441 MB büyür.
Dagger grafiği oluşturma
Dagger grafiği oluşturma ilkesi şu şekilde tanımlanır:
1def produce_dag(params, seed, length):2 P = params["P"]3 picker = init = pow(sha3(seed), params["w"], P)4 o = [init]5 for i in range(1, length):6 x = picker = (picker * init) % P7 for _ in range(params["k"]):8 x ^= o[x % i]9 o.append(pow(x, params["w"], P))10 return oTümünü gösterEsasen, bir grafiği tek bir düğüm olan sha3(seed) olarak başlatır ve oradan rastgele önceki düğümlere dayalı olarak diğer düğümleri sırayla eklemeye başlar. Yeni bir düğüm oluşturulduğunda, i'den küçük bazı dizinleri rastgele seçmek için tohumun modüler bir gücü hesaplanır (yukarıdaki x % i kullanılarak) ve bu dizinlerdeki düğümlerin değerleri, x için yeni bir değer oluşturmak üzere bir hesaplamada kullanılır, bu da nihai olarak i dizinindeki grafiğin değerini oluşturmak için küçük bir iş ispatı fonksiyonuna (XOR tabanlı) beslenir. Bu özel tasarımın arkasındaki mantık, DAG'nin sıralı erişimini zorlamak; DAG'ın erişilecek bir sonraki değeri, mevcut değer bilinene kadar belirlenemez. Son olarak, modüler üs alma sonucu daha da özetler.
Bu algoritma, sayı teorisinden elde edilen çeşitli sonuçlara dayanır. Bir tartışma için ek bölümü aşağıda görebilirsiniz.
Hafif istemci değerlendirmesi
Yukarıdaki grafik yapısı, grafikteki her bir düğümün, yalnızca az sayıda düğümden oluşan bir alt ağaç hesaplanarak ve yalnızca az miktarda yardımcı bellek gerektirerek yeniden oluşturulmasına izin vermeyi amaçlamaktadır. K=1 ile alt ağacın yalnızca DAG'deki ilk öğeye kadar giden bir değerler zinciri olduğuna dikkat edin.
DAG için hafif istemci hesaplama işlevi aşağıdaki gibi çalışır:
1def quick_calc(params, seed, p):2 w, P = params["w"], params["P"]3 cache = {}45 def quick_calc_cached(p):6 if p in cache:7 pass8 elif p == 0:9 cache[p] = pow(sha3(seed), w, P)10 else:11 x = pow(sha3(seed), (p + 1) * w, P)12 for _ in range(params["k"]):13 x ^= quick_calc_cached(x % p)14 cache[p] = pow(x, w, P)15 return cache[p]1617 return quick_calc_cached(p)Tümünü gösterEsasen, tüm DAG için değerleri hesaplama döngüsünü ortadan kaldıran ve önceki düğüm aramasını özyinelemeli bir çağrı veya bir önbellek aramasıyla değiştiren, yukarıdaki algoritmanın basitçe yeniden yazılmasıdır. k=1 için önbelleğin gereksiz olduğunu, ancak daha ileri bir optimizasyonun aslında DAG'nin ilk birkaç bin değerini önceden hesapladığını ve bunu hesaplamalar için statik bir önbellek olarak tuttuğunu unutmayın; bunun bir kod uygulaması için eke bakın.
DAG'lerin çift arabelleği
Tam bir istemcide, yukarıdaki formülle üretilen 2 DAG'den oluşan bir çift arabellekopens in a new tab kullanılır. Buradaki fikir, DAG'lerin yukarıdaki parametrelere göre her epochtime blok sayısında üretilmesidir. Üretilen en son DAG'ı kullanan istemci yerine, öncekini kullanır. Bunun yararı, madencilerin tüm verileri aniden yeniden hesaplaması gereken bir adımın dahil edilmesine gerek kalmadan, DAG'lerin zaman içinde değiştirilmesine izin vermesidir. Aksi takdirde, düzenli aralıklarla zincir işlemede ani bir geçici yavaşlama ve merkezileşmeyi önemli ölçüde artırma potansiyeli vardır. Bu nedenle, tüm veriler yeniden hesaplanmadan önceki birkaç dakika içinde %51 saldırı riski vardır.
Bir blok için çalışmayı hesaplamak için kullanılan DAG setini oluşturmak için kullanılan algoritma aşağıdaki gibidir:
1def get_prevhash(n):2 from pyethereum.blocks import GENESIS_PREVHASH3 from pyethereum import chain_manager4 if n <= 0:5 return hash_to_int(GENESIS_PREVHASH)6 else:7 prevhash = chain_manager.index.get_block_by_number(n - 1)8 return decode_int(prevhash)910def get_seedset(params, block):11 seedset = {}12 seedset["back_number"] = block.number - (block.number % params["epochtime"])13 seedset["back_hash"] = get_prevhash(seedset["back_number"])14 seedset["front_number"] = max(seedset["back_number"] - params["epochtime"], 0)15 seedset["front_hash"] = get_prevhash(seedset["front_number"])16 return seedset1718def get_dagsize(params, block):19 return params["n"] + (block.number // params["epochtime"]) * params["n_inc"]2021def get_daggerset(params, block):22 dagsz = get_dagsize(params, block)23 seedset = get_seedset(params, block)24 if seedset["front_hash"] <= 0:25 # No back buffer is possible, just make front buffer26 return {"front": {"dag": produce_dag(params, seedset["front_hash"], dagsz),27 "block_number": 0}}28 else:29 return {"front": {"dag": produce_dag(params, seedset["front_hash"], dagsz),30 "block_number": seedset["front_number"]},31 "back": {"dag": produce_dag(params, seedset["back_hash"], dagsz),32 "block_number": seedset["back_number"]}}Tümünü gösterHashimoto
Orijinal Hashimoto'nun arkasındaki fikir, blok zincirini bir veri seti olarak kullanmak, blok zincirinden N indeks seçen, bu indekslerdeki işlemleri toplayan, bu verilerin bir XOR'sini gerçekleştiren ve sonucun karmasını döndüren bir hesaplama yapmaktır. Thaddeus Dryja'nın tutarlılık için Python'a çevrilmiş orijinal algoritması aşağıdaki gibidir:
1def orig_hashimoto(prev_hash, merkle_root, list_of_transactions, nonce):2 hash_output_A = sha256(prev_hash + merkle_root + nonce)3 txid_mix = 04 for i in range(64):5 shifted_A = hash_output_A >> i6 transaction = shifted_A % len(list_of_transactions)7 txid_mix ^= list_of_transactions[transaction] << i8 return txid_mix ^ (nonce << 192)Ne yazık ki, Hashimoto RAM zor olarak kabul edilirken, önemli hesaplama yükü olan 256-bit aritmetik kullanır. Ancak Dagger-Hashimoto, bu sorunu çözmek için veri kümesini indekslerken yalnızca en az önemli 64 biti kullanır.
1def hashimoto(dag, dagsize, params, header, nonce):2 m = dagsize / 23 mix = sha3(encode_int(nonce) + header)4 for _ in range(params["accesses"]):5 mix ^= dag[m + (mix % 2**64) % m]6 return dbl_sha3(mix)Çift SHA3'ün kullanımı, yalnızca doğru bir ara değerin sağlandığını doğrulayan, neredeyse anında ön doğrulama olan bir sıfır veri biçimine olanak tanır. Çalışma kanıtının bu dış katmanı oldukça ASIC dostudur ve oldukça zayıftır, ancak hemen reddedilmeyecek bir blok oluşturmak için bu küçük miktarda işin yapılması gerektiğinden DDoS'u daha da zor hale getirmek için vardır. Hafif-istemci versiyonu aşağıdaki gibidir:
1def quick_hashimoto(seed, dagsize, params, header, nonce):2 m = dagsize // 23 mix = sha3(nonce + header)4 for _ in range(params["accesses"]):5 mix ^= quick_calc(params, seed, m + (mix % 2**64) % m)6 return dbl_sha3(mix)Madencilik ve doğrulama
Şimdi hepsini madencilik algoritmasında bir araya getirelim:
1def mine(daggerset, params, block):2 from random import randint3 nonce = randint(0, 2**64)4 while 1:5 result = hashimoto(daggerset, get_dagsize(params, block),6 params, decode_int(block.prevhash), nonce)7 if result * params["diff"] < 2**256:8 break9 nonce += 110 if nonce >= 2**64:11 nonce = 012 return nonceTümünü gösterDoğrulama algoritması aşağıdadır:
1def verify(daggerset, params, block, nonce):2 result = hashimoto(daggerset, get_dagsize(params, block),3 params, decode_int(block.prevhash), nonce)4 return result * params["diff"] < 2**256Hafif-istemci dostu doğrulama:
1def light_verify(params, header, nonce):2 seedset = get_seedset(params, block)3 result = quick_hashimoto(seedset["front_hash"], get_dagsize(params, block),4 params, decode_int(block.prevhash), nonce)5 return result * params["diff"] < 2**256Ayrıca, Dagger-Hashimoto'nun blok başlığına ek gereksinimler getirdiğini unutmayın:
- İki katmanlı doğrulamanın çalışması için, bir blok başlığı hem nonce hem de orta değer pre-sha3'e sahip olmalıdır
- Bir yerde, bir blok başlığı mevcut tohum setinin sha3'ünü depolamalıdır
Daha fazla kaynak
Size yardımcı olan bir topluluk kaynağı mı biliyorsunuz? Bu sayfayı düzenleyin ve onu ekleyin!
Ek
Yukarıda belirtildiği gibi, DAG üretimi için kullanılan RNG, sayı teorisinden elde edilen bazı sonuçlara dayanır. İlk olarak, picker değişkeninin temeli olan Lehmer RNG'nin geniş bir periyoda sahip olduğuna dair güvence veriyoruz. İkinci olarak, başlangıç için x ∈ [2,P-2] sağlandığında pow(x,3,P)'nin x'i 1'e veya P-1'e eşlemeyeceğini gösteriyoruz. Son olarak, pow(x,3,P)'nin bir karma işlevi olarak ele alındığında düşük bir çarpışma oranına sahip olduğunu gösteriyoruz.
Lehmer rastgele sayı üreteci
produce_dag işlevinin tarafsız rastgele sayılar üretmesi gerekmese de, potansiyel bir tehdit, seed**i % P'nin yalnızca bir avuç değer almasıdır. Bu, modeli tanımayanlara kıyasla madencilere bir avantaj sağlayabilir.
Bundan kaçınmak için sayı teorisinden bir sonuca başvurulur. Güvenli Asalopens in a new tab, (P-1)/2 de asal olacak şekilde bir P asalı olarak tanımlanır. Çarpımsal grupopens in a new tab ℤ/nℤ'nin bir x üyesinin mertebesi,
xᵐ mod P ≡ 1olacak şekildeki minimum
m olarak tanımlanır.
Bu tanımlar göz önüne alındığında, elimizde şunlar vardır:
Gözlem 1.
x, güvenli birPasalı içinℤ/Pℤçarpımsal grubunun bir üyesi olsun. Eğerx mod P ≠ 1 mod Pvex mod P ≠ P-1 mod Pise,x'in mertebesi yaP-1ya da(P-1)/2'dir.
Kanıt. P güvenli bir asal olduğundan, [Lagrange Teoremi][lagrange] gereğince x'in mertebesinin 1, 2, (P-1)/2 veya P-1 olduğunu söyleyebiliriz.
x'in mertebesi 1 olamaz, çünkü Fermat'nın Küçük Teoremi'ne göre elimizde şu vardır:
xP-1 mod P ≡ 1
Dolayısıyla x, benzersiz olan ℤ/nℤ'nin bir çarpımsal birimi olmalıdır. Varsayım gereği x ≠ 1 olduğunu varsaydığımız için bu mümkün değildir.
x'in mertebesi x = P-1 olmadıkça 2 olamaz, çünkü bu P'nin asal olmasını ihlal eder.
Yukarıdaki önermeden, (picker * init) % P yinelemesinin en az (P-1)/2 döngü uzunluğuna sahip olacağını görebiliriz. Bunun nedeni, P'yi ikinin daha yüksek bir kuvvetine yaklaşık olarak eşit olan güvenli bir asal olarak seçmemiz ve init'in [2,2**256+1] aralığında olmasıdır. P'nin büyüklüğü göz önüne alındığında, modüler üs almadan asla bir döngü beklememeliyiz.
DAG'deki ilk hücreyi (etiketi init olan değişken) atarken, pow(sha3(seed) + 2, 3, P) hesaplarız. İlk bakışta bu, sonucun ne 1 ne de P-1 olduğunu garanti etmez. Ancak, P-1 güvenli bir asal olduğundan, Gözlem 1'in bir sonucu olan aşağıdaki ek güvenceye sahibiz:
Gözlem 2.
x, güvenli birPasalı içinℤ/Pℤçarpımsal grubunun bir üyesi vewbir doğal sayı olsun. Eğerx mod P ≠ 1 mod Pvex mod P ≠ P-1 mod Pise, ayrıcaw mod P ≠ P-1 mod Pvew mod P ≠ 0 mod Pise, o zamanxʷ mod P ≠ 1 mod Pvexʷ mod P ≠ P-1 mod Polur.
Karma işlevi olarak modüler üs alma
Belirli P ve w değerleri için, pow(x, w, P) işlevinin birçok çakışması olabilir. Örneğin, pow(x,9,19) yalnızca {1,18} değerlerini alır.
P asal olduğundan, modüler üs alma karma işlevi için uygun bir w, aşağıdaki sonuç kullanılarak seçilebilir:
Gözlem 3.
Pbir asal sayı olsun;wveP-1'in aralarında asal olması için gerek ve yeter koşul,ℤ/Pℤ'deki tümavebiçin şudur:aʷ mod P ≡ bʷ mod Polması için gerek ve yeter koşula mod P ≡ b mod Polmasıdır
Dolayısıyla, P asal ve w, P-1 ile aralarında asal olduğunda, |{pow(x, w, P) : x ∈ ℤ}| = P olur, bu da karma işlevinin mümkün olan en düşük çarpışma oranına sahip olduğu anlamına gelir.
P'nin seçtiğimiz gibi güvenli bir asal olduğu özel durumda, P-1 yalnızca 1, 2, (P-1)/2 ve P-1 çarpanlarına sahiptir. P > 7 olduğundan, 3'ün P-1 ile aralarında asal olduğunu biliyoruz, dolayısıyla w=3 yukarıdaki önermeyi sağlar.
Daha verimli önbellek tabanlı değerlendirme algoritması
1def quick_calc(params, seed, p):2 cache = produce_dag(params, seed, params["cache_size"])3 return quick_calc_cached(cache, params, p)45def quick_calc_cached(cache, params, p):6 P = params["P"]7 if p < len(cache):8 return cache[p]9 else:10 x = pow(cache[0], p + 1, P)11 for _ in range(params["k"]):12 x ^= quick_calc_cached(cache, params, x % p)13 return pow(x, params["w"], P)1415def quick_hashimoto(seed, dagsize, params, header, nonce):16 cache = produce_dag(params, seed, params["cache_size"])17 return quick_hashimoto_cached(cache, dagsize, params, header, nonce)1819def quick_hashimoto_cached(cache, dagsize, params, header, nonce):20 m = dagsize // 221 mask = 2**64 - 122 mix = sha3(encode_int(nonce) + header)23 for _ in range(params["accesses"]):24 mix ^= quick_calc_cached(cache, params, m + (mix & mask) % m)25 return dbl_sha3(mix)Tümünü göster