BIP-39 standardında 24 kelimelik mnemonic, aslında rastgele seçilmiş kelimeler değil; arka planda bir bit dizisinin (entropy + checksum) kelimelere çevrilmiş halidir.
- BIP-39 wordlist’te 2048 kelime vardır.
- Her kelime, 0–2047 arasında bir indekse karşılık gelir.
- 2048 = 2¹¹ olduğu için her kelime 11 bit bilgi taşır.
- 24 kelime → 24 × 11 = 264 bit eder.
Bu 264 bit şöyle bölünür:
- 256 bit entropy (asıl gizli ana materyali)
- 8 bit checksum (entropy’nin SHA-256 hash’inden türetilen doğrulama biti)
Checksum’un amacı şudur: “Bu kelime dizisi mantıken geçerli mi?” diye hızlıca kontrol edebilmek.
Kod Ne Yapıyor? (Yüksek Seviye Mantık)
Fonksiyonun adı: find_24th_words($twenty_three_words)
1) 23 kelimeyi 11 bit’lik indekslere çeviriyor
$idx = array_search($w, $wordlist);
$prefix_bin .= sprintf('%011b', $idx);
- Wordlist’te her kelimenin indeksini buluyor.
- İndeksi 11 bit binary string’e çevirip
prefix_biniçine ekliyor. - Böylece ilk 23 kelimeden 253 bit elde ediyor (23×11).
2) 24. kelime için tüm olasılıkları tek tek deniyor
foreach ($wordlist as $last_word) { ... }
- 2048 kelimenin her birini “son kelime” varsayıyor.
- Böylece 253 + 11 = 264 bit tamamlanmış oluyor.
3) 264 bit’i “entropy + checksum” olarak ayırıyor
$entropy = substr($full_bin, 0, 256);
$checksum = substr($full_bin, 256, 8);
4) Entropy’yi byte’a çevirip SHA-256 hash alıyor
$hash = hash('sha256', $bytes, true);
$expected = substr(sprintf('%08b', ord($hash[0])), 0, 8);
- Entropy (256 bit) → 32 byte.
- SHA-256 hash’inin ilk byte’ından ilk 8 bit checksum üretiliyor.
- Üretilen checksum ile mnemonic’in son 8 bit’i aynıysa “geçerli” sayılıyor.
5) Checksum’u tutan son kelimeleri “çözüm” listesine ekliyor
if ($checksum === $expected) {
$solutions[] = $last_word;
}
Neden Tek Bir 24. Kelime Çıkmıyor?
Checksum yalnızca 8 bit olduğu için, rastgele seçilmiş bir son kelimenin “tutma” olasılığı 1/256’dır.
- 2048 aday kelime var
- 2048 / 256 = ortalama 8 kelime checksum’ı tutar
Yani pratikte bu fonksiyon çoğu durumda bir değil, birden fazla olası 24. kelime döndürür. Bu da şu anlama gelir:
“Checksum’ı tutan” 24. kelime(ler) bulunur ama hangisinin sizin gerçek seed’iniz olduğu hâlâ kesin değildir.
Gerçek cüzdanı bulmak için genelde bir “doğrulama” gerekir (ör. doğru adreslerin türetilip türetilmediğine bakmak gibi). İşte bu nokta kötüye kullanıma çok açık olduğundan, burada o tarafa girmiyorum.
Güvenlik Perspektifi: 23 Kelime Ne Kadar Tehlikeli?
Bu kodun anlattığı en kritik gerçek şu:
Birinin eline 23 kelimeniz geçerse, kalan 1 kelime “güvenlik bariyeri” gibi davranmaz.
Çünkü checksum yapısı, 24. kelime uzayını efektif olarak çok daraltır. Bu yüzden:
- Seed phrase’inizi asla 23/24 gibi “parçalayıp bir yere yazmak” güvenli bir strateji değildir.
- Seed’i paylaşmak zaten felakettir; 23 kelimeyi paylaşmak da pratikte “neredeyse seed’i paylaşmak” gibidir.
Daha güvenli alternatifler:
- Donanım cüzdan + PIN + passphrase (BIP39 “25. kelime” diye anılan ek parola)
- Shamir’s Secret Sharing (destekleyen cüzdanlarda)
- Multi-sig yapılar (tek noktadan çalınmayı zorlaştırır)
Koda dökecek olursak:
<?php
/**
* Verilen ilk 23 BIP-39 kelimesi için,
* checksum'a göre geçerli olabilecek 24. kelime adaylarını döndürür.
*
* Notlar:
* - BIP-39 wordlist 2048 kelimedir => her kelime 11 bit taşır.
* - 24 kelime = 24 * 11 = 264 bit
* - 264 bit = 256 bit entropy + 8 bit checksum (24 kelime için)
* - Checksum, entropy'nin SHA-256 hash'inin ilk byte'ından türetilir.
*/
function yirmi_dorduncu_kelimeleri_bul(array $ilk_yirmi_uc_kelime): array
{
// BIP-39 wordlist'i yükle (2048 kelimelik dizi)
$kelime_listesi = kelime_listesini_yukle();
$cozumler = [];
// 1) İlk 23 kelimeyi 11-bit indekslere çevirip tek bir binary prefix oluştur.
// 23 kelime * 11 bit = 253 bit prefix elde ederiz.
$prefix_bin = '';
foreach ($ilk_yirmi_uc_kelime as $kelime) {
// Kelimenin wordlist içindeki indeksini bul
$indeks = array_search($kelime, $kelime_listesi, true);
// Kelime listede yoksa, geçersiz input => boş sonuç döndür.
if ($indeks === false) {
return [];
}
// İndeksi 11 bit binary string'e çevir ve prefix'e ekle
$prefix_bin .= sprintf('%011b', $indeks);
}
// 2) Son kelime için tüm 2048 olasılığı dene
foreach ($kelime_listesi as $son_kelime) {
$son_indeks = array_search($son_kelime, $kelime_listesi, true);
// 23 kelimenin 253 bit'i + 24. kelimenin 11 bit'i = 264 bit
$tam_bin = $prefix_bin . sprintf('%011b', $son_indeks);
// 3) 264 bit'i: 256 bit entropy + 8 bit checksum olarak böl
$entropy_bin = substr($tam_bin, 0, 256);
$checksum_bin = substr($tam_bin, 256, 8);
// 4) Entropy'yi (256 bit) byte dizisine çevir (32 byte)
$entropy_bytes = '';
for ($i = 0; $i < 256; $i += 8) {
$bayt_parcasi = substr($entropy_bin, $i, 8);
$entropy_bytes .= chr(bindec($bayt_parcasi));
}
// 5) SHA-256(entropy) hesapla (raw bytes)
$hash = hash('sha256', $entropy_bytes, true);
// 6) 24 kelimelik mnemonic'te checksum uzunluğu 8 bittir.
// SHA-256 çıktısının ilk byte'ının ilk 8 bitini beklenen checksum olarak alıyoruz.
$hash_ilk_byte = ord($hash[0]); // 0..255
$beklenen_checksum_bin = substr(sprintf('%08b', $hash_ilk_byte), 0, 8);
// 7) Checksum uyuşuyorsa bu son kelime "geçerli aday"dır.
if ($checksum_bin === $beklenen_checksum_bin) {
$cozumler[] = $son_kelime;
}
}
// Genelde tek bir kelime değil, ortalama ~8 aday döner (2048 / 256).
return $cozumler;
}
/**
* Örnek "wordlist yükleyici" imzası.
* Gerçekte BIP-39 english.txt gibi 2048 kelimeyi okuyup dizi döndürmelidir.
*/
function kelime_listesini_yukle(): array
{
// TODO: Buraya gerçek wordlist yükleme kodun gelecek.
// Örn: file('bip39_english.txt', FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
return [];
}

Bir yanıt yazın