Bitcoin Script'e Giriş
Ana sayfa
Makaleler
Bitcoin Script'e Giriş

Bitcoin Script'e Giriş

İleri Seviye
Yayınlanma: Jul 10, 2020Güncellenme: Jan 18, 2022
13m

Giriş

Bitcoin'den kimi zaman programlanabilir para olarak bahsedilir. Bitcoin, dijital doğası sayesinde, fonların nasıl harcanabileceğine yönelik koşulların belirlenmesi konusunda kullanıcılara büyük bir esneklik sunar. 
Bitcoin hakkında konuşurken cüzdanlardan ve coinlerden bahsederiz. Fakat, cüzdanları anahtarlar, coinleri çekler ve blockchaini de kilitli kasalardan oluşan sıralar olarak da düşünebiliriz. Her kasada, herhangi birinin bir çek yatırabileceği ya da bu kasada ne kadar değer saklandığını inceleyebileceği ince bir aralık bulunur. Fakat, kasanın içindekilere yalnızca anahtarın sahibi olan kişi erişebilir.

Anahtar sahibi, başka bir kişiye para ödemek istediğinde kutunun kilidini açar. Eski çeke referans veren yeni bir çek hazırlar (daha sonra eski çek yok edilir) ve bu çeki alıcının açabileceği bir kutuya kilitler. Yeni alıcı, bu çeki harcayabilmek için süreci tekrar eder.

Bu makalede, Bitcoin ağındaki node'lar tarafından kullanılan programlama dili olan Script'i yakından inceleyeceğiz. Kasalar örneğinde bahsettiğimiz kilitleme/kilit açma mekanizmaları Script tarafından yönetilir.


Bitcoin nasıl çalışır?

Yukarıdaki benzetmemiz üzerinden devam edersek her bir işlemin iki bölümden oluştuğunu söyleyebiliriz – bir anahtar (kutunun kilidini açmak için) ve bir kilit. Harcamak istediğiniz çekin bulunduğu kutuyu açarken anahtarınızı kullanırsınız ve daha sonra farklı bir kilide sahip yeni bir kutuya başka bir çek eklersiniz. Yeni kutudan harcama yapmak için başka bir anahtara ihtiyacınız olur.
Oldukça basit değil mi? Sistemdeki kilitler de farklı türlerde olabilir. Bazı kasalar birden fazla anahtarı gerekli kılarken diğerleri gizli bir bilgiyi bildiğinizi ispat etmenizi gerektirebilir. Kasaların nasıl açılacağına yönelik birçok farklı koşul belirlenebilir. 
Anahtarımıza, scriptSig (betik imzası) adı verilir. Kilidimiz ise scriptPubKey'imizdir (betik açık anahtarı). Birazdan bunları daha ayrıntılı incelediğimizde bu öğelerin aslında veri parçalarından ve kod bloklarından oluştuklarını göreceğiz. Birleştiklerinde ise küçük bir program oluştururlar.

Bir işlem yaptığınızda, bu kombinasyonu ağa yayınlamış olursunuz. Bu bilgiyi alan her bir node, işlemin geçerli olup olmadığını söyleyen programı kontrol eder. Geçerli değilse işlem göz ardı edilir ve kilitlenmiş fonları harcamanız mümkün olmaz.

Elinizdeki çeklere (coinler) harcanmamış işlem çıktısı (UTXO) adı verilir. Fonlar, kilide uygun anahtarı sunan herhangi biri tarafından harcanabilir. Burada anahtar scriptSig ve kilit de scriptPubKey'dir.
UTXO'lar cüzdanınızdaysa muhtemelen, fonlar yalnızca bu açık anahtarın sahibi olduğunu ispat edebilen kişi tarafından harcanabilir diyen bir koşula sahip olacaktır. Kilidi açmak için, scriptPubKey'de belirtilen açık anahtarla eşleştirilmiş özel anahtarı kullanarak dijital imza içeren bir scriptSig sunmanız gerekir. Birazdan bu bahsettiklerimiz çok daha açık hale gelecek.


Bitcoin yığınını anlamak

Script, yığın bazlı bir dildir. Yani, bir grup yönerge okuduğumuzda bunları dikey bir kolon şeklinde düşünebileceğimiz bir yapıya yerleştiririz. Örneğin A, B ve C listesi, A'nın en altta C'nin ise en üstte olduğu bir yığın oluşturabilir. Yönergeler bizi bir şey yapmaya yönlendirdiğinde, yığının en üstünden başlayarak bir ya da daha fazla öğeyle işlem yaparız.


A, B ve C öğeleri ekleniyor ve yığından “dışarı çıkarılıyor”.


Verileri (imzalar, hash'ler ve açık anahtarlar gibi şeyler) ve yönergeleri (ya da işlem kodlarını) birbirinden ayırabiliriz. Yönergeler veriyi çıkarır ve bu veriyle işlem yapar. Bir betiğin (script) nasıl gözükebileceğinin çok basit bir örneği şu şekildedir:
<xyz> <md5 hasher> <d16fb36f0911f878998c136191af705e> <check if equal>
Kırmızılar veri ve maviler de işlem kodlarıdır (opcode). Betik soldan sağa okunur, bu nedenle yığına ilk olarak <xyz> dizisini ekleriz. Sırada <md5 hasher> işlem kodu vardır. Bitcoin'de işlem kodu bulunmaz, fakat işlem kodu yığının en üstünde yer alan öğeyi (<xyz>) çıkarır ve MD5 algoritmasını kullanarak hash eder. Daha sonra çıktı tekrardan yığına eklenir. Burada çıktı d16fb36f0911f878998c136191af705e'dir.
Tesadüfe bakın! Eklenecek bir sonraki öğe <d16fb36f0911f878998c136191af705e>'dir, böylece yığınımızda birbirinin aynısı iki öğe olur. Son olarak, <check if equal> en üstteki iki öğeyi çıkarır ve bunların aynı olup olmadığını kontrol eder. Aynılarsa, yığına <1> ekler. Aynı değillerse, <0> ekler. 
Yönerge listemizin sonuna ulaşmış olduk. Betiğimiz iki şekilde başarısız olabilirdi – kalan öğe sıfır olsaydı ya da bazı koşulların yerine getirilmemesi durumunda operatörlerden biri betiğin başarısız olmasına neden olsaydı. Bu örnekte böyle bir operatörümüz yoktu ve sıfır olmayan bir öğe elde ettik (<1>), dolayısıyla betiğimiz geçerliydi. Bu kurallar gerçek Bitcoin işlemleri için de geçerlidir.

Bu hayali bir programdı. Şimdi gerçek programlardan bazılarına göz atalım.


Pay-to-Pubkey (P2PK - Açık Anahtara Ödeme)

Pay-to-Pubkey (P2PK - Açık Anahtara Ödeme) son derece doğrudandır. Fonları belirli bir açık anahtara kilitlemeyi içerir. Fonları bu şekilde almak isterseniz, göndericiyle bir Bitcoin adresi yerine açık anahtarınızı paylaşmanız gerekir. 

2009'da Satoshi Nakamoto ve Hal Finney arasındaki ilk işlem bir P2PK'dır. Bu yapı Bitcoin'in ilk günlerinde yoğun olarak kullanılmıştır, fakat günümüzde yapının yerini Pay-to-Pubkey-Hash (P2PKH) almıştır. 
P2PK işleminin kilitleme betiği <public key> OP_CHECKSIG formatını kullanır. Oldukça basittir. OP_CHECKSIG sunulan açık anahtarı imzayla karşılaştırarak kontrol eder. Dolayısıyla, scriptSig'imiz de oldukça basit bir <signature> (imza) olacaktır. scriptSig'in kilidin anahtarı olduğu unutulmamalıdır.



Bundan daha basit olamazdı. Bir imza yığına eklenir ve bunu bir açık anahtar takip eder. OP_CHECKSIG, her ikisini de dışarı çıkarır ve açık anahtarla karşılaştırarak imzayı kontrol eder. Eşleşiyorlarsa yığına <1> ekler. Aksi takdirde <0> ekler.

Bir sonraki bölümde bahsedeceğimiz nedenlerden ötürü P2PK artık pek kullanılmamaktadır.


Pay-to-Pubkey-Hash (P2PKH - Açık Anahtar Özetine Ödeme)

Pay-to-Pubkey-Hash (P2PKH - Açık Anahtar Özetine Ödeme) artık en yaygın işlem türüdür. Çok eski bir yazılım yüklemediğiniz takdirde muhtemelen cüzdanınızın varsayılan ayarları bunu içerecektir.

P2PKH'deki scriptPubKey şu şekildedir:

OP_DUP OP_HASH160 <public key hash> OP_EQUALVERIFY OP_CHECKSIG

scriptSig'den bahsetmeden önce yeni işlem kodlarının ne yapacağından bahsedelim:


OP_DUP

OP_DUP, ilk öğeyi çıkarır ve bunun bir kopyasını oluşturur. Daha sonra her ikisini de yığına geri yükler. Genellikle bu işlem, orijinali üzerinde bir etki yaratmadan kopyanın üzerinde işlem yapabilmemiz için gerçekleştirilir.


OP_HASH160

OP_HASH160, ilk öğeyi çıkarır ve bunu iki kere hash eder. İlk turda SHA-256 algoritmasıyla hash edilir. SHA-256 çıktısı daha sonra RIPEMD-160 algoritmasıyla hash edilir. Ortaya çıkan çıktı yığına tekrar eklenir.


OP_EQUALVERIFY

OP_EQUALVERIFY, başka iki operatörü birleştirir – OP_EQUAL ve OP_VERIFY. OP_EQUAL, iki öğe çıkarır ve bunların aynı olup olmadığını kontrol eder. Aynılarsa, yığına 1 ekler. Aynı değillerse, 0 ekler. OP_VERIFY, en üstteki öğeyi çıkarır ve bunun Doğru (yani sıfır-dışı) olup olmadığını kontrol eder. Doğru değilse işlem başarısız olur. Bununla birlikte OP_EQUALVERIFY, en üstteki iki öğenin aynı olmaması durumunda işlemin başarısız olmasına neden olur.

Bu sefer, scriptSig şu şekilde gözükür:

<signature> <public key>

P2PKH çıktılarının kilidini açmak için bir imza ve karşılık gelen açık anahtarı sunmanız gerekir.



Süreci yukarıdaki GIF'ten inceleyebilirsiniz. P2PK komut dizininden çok da farklı değildir. Yalnızca, açık anahtarın betikteki hash'le aynı olup olmadığını kontrol etmek için ekstra bir adım daha eklenir.

Fakat, dikkat edilmesi gereken bir nokta vardır. P2PKH kilitleme betiğinde açık anahtar görünür değildir – yalnızca açık anahtarın hash'ini görebiliriz. Bir blockchain tarayıcısına gider ve harcanmamış bir P2PKH çıktısına bakarsak açık anahtarı belirleyemeyiz. Açık anahtar yalnızca alıcının fonları transfer etmeye karar vermesi durumunda gözükür hale gelir.
Bunun çeşitli avantajları vardır. Birincisi, tam bir açık anahtara kıyasla açık anahtarın hash'inin paylaşılması daha kolaydır. Zaten Satoshi de bunu 2009 yılında bu amaçla kullanıma sunmuştur. Günümüzde Bitcoin adresi olarak bildiğimiz şey bir açık anahtar hash'idir.
İkinci avantaj, açık anahtar hash'lerinin kuantum hesaplama karşısında ek bir güvenlik katmanı sunabilmesidir. Açık anahtarımız, fonlarımızı harcayana kadar görünmez olacağından, özel anahtarın başka kişiler tarafından hesaplanması daha da zor hale gelir. Özel anahtara ulaşmak için hash alma işleminin iki turunun da (RIPEMD-160 ve SHA-256) tersine çevrilmesi gerekir.



Pay-to-Script-Hash (P2SH - Betik Özetine Ödeme)

Pay-to-Script-Hash (P2SH - Betik Özetine Ödeme), Bitcoin için çok ilginç bir geliştirmedir. Göndericinin fonları bir betiğin hash'ine kilitlemesine imkan tanır – betiğin aslında ne yaptığının bilinmesine gerek kalmaz. Aşağıda bir SHA-256 hash'ini görebilirsiniz:

e145fe9ed5c23aa71fdb443de00c7d9b4a69f8a27a2e4fbb1fe1d0dbfb6583f1

Fonları hash'e kilitlemeniz için hash'in girdisini bilmenize gerek yoktur. Fakat, fonları harcayacak kişinin hash alırken kullanılan betiği sunması ve bu betiğin koşullarını yerine getirmesi gerekir.

Yukarıdaki hash şu betikten yaratılmıştır:

<multiply by 2> <4> <check if equal>

Bu scriptPubKey'e bağlanmış coinleri harcamak isterseniz, bu komutları sunmanız yeterli olmaz. Tamamlanmış betiğin Doğru sonucunu vermesini sağlayan bir scriptSig de sunmanız gerekir. Örneğimiz üzerinde bu, <4> sonucuna ulaşmak için <multiply by 2> (2 ile çarptığınız) bir öğedir. Tabi ki bu da scriptSig'imizin <2> olduğu anlamına gelir.

Gerçek hayatta, bir P2SH çıktısı için scriptPubKey şudur:

OP_HASH160 <redeemScript hash> OP_EQUAL

Burada yeni operatör yoktur. Fakat, yeni bir öğe olarak <redeemScript hash> (ödeme betiği hash'i) vardır. İsminden de anlaşılabileceği gibi bu öğe fonları çekebilmemiz için sunmamız gereken betik hash'idir (buna redeemScript adı verilir). redeemScript'te (ödeme betiği) ne olduğuna bağlı olarak scriptSig değişecektir. Fakat genellikle, imzaların ve eklenmiş açık anahtarların bir kombinasyonudur ve onu bir (zorunlu) redeemScript takip eder:

<signature> <public key> <redeemScript>

Değerlendirmemiz, şu ana kadar bahsettiğimiz yığın yürütmesinden biraz farklıdır. İki aşamada gerçekleşir. İlk aşama, doğru hash'i sunup sunmadığımızı kontrol eder. 



redeemScript'ten önce gelen öğelerle bir şey yapmadığımızı görebilirsiniz. Bu aşamada o öğeler kullanılmaz. Bu mini programın sonuna geldik ve en üstteki öğe sıfır-değil. Bu da işlemin geçerli olduğu anlamına gelir.

Fakat henüz işimiz bitmedi. Ağ node'ları bu yapıyı P2SH olarak tanır, dolayısıyla scriptSig'in öğeleri başka bir yığında beklemektedir. İmza ve açık anahtarın kullanıldığı yer burasıdır.

Şimdiye kadar redeemScript'e bir öğe olarak muamele ettik. Ama şimdi onu, herhangi bir şey olabilecek bir yönerge olarak değerlendireceğiz. <redeemScript>'in içindeki bir <public key hash> ile (açık anahtar hash'i) eşleşen <public key> (açık anahtar) ve <signature>'ı (imza) sunmamızı gerektiren bir P2PKH kilitleme betiği örneğini inceleyelim.



redeemScript'inizi genişlettiğinizde standart bir P2PKH işlemiyle birebir aynı görünen bir durumla karşı karşıya olduğunuzu görebilirsiniz. Bu aşamada redeemScript, normal bir betik gibi çalıştırılır.

Burada bir P2SH (P2PKH) betiğinin nasıl çalıştırılacağını gösterdik, fakat bunlardan bir tanesine rastlamanız pek olası değildir. İsterseniz bir P2SH yaratabilirsiniz, ama herhangi bir ekstra avantaj sunmadığı gibi bir blok içinde kapladığı yer de daha fazladır (ve dolayısıyla daha fazla masraf yaratır).

P2SH genellikle, çoklu imzalar ya da SegWit'le uyumlu işlemler gibi konularda kullanışlıdır. Çoklu imza işlemleri, birden fazla anahtar gerektirdiği için çok büyük boyutta olabilir. Pay-to-Script-Hash uygulanmaya konmadan önce bir göndericinin kilitleme betiğindeki olası tüm açık anahtarların listesini çıkarması gerekirdi. 

Fakat P2SH ile harcama koşullarının ne kadar karmaşık olduğu önemli değildir. redeemScript'in hash'i her zaman sabit bir büyüklüktedir. Dolayısıyla masraflar, kilitleme betiğinin kilidini açmak isteyen kullanıcı ya da kullanıcılara geçer.

P2SH'nin kullanışlı olduğu bir diğer konu da SegWit uyumluluğudur (işlem yapısının nasıl farklılık gösterdiğinden bir sonraki bölümde bahsedeceğiz). SegWit, blok/işlem formatlarında değişikliğe yol açan bir soft fork'tur (yumuşak çatallanma). Tercihe bağlı bir yükseltme olduğu için değişiklikler tüm cüzdan yazılımları tarafından tanınmaz.

İstemcilerin SegWit betik hash'ini P2SH ile sarması bir sorun yaratmaz. Bu türdeki tüm işlemlerde olduğu gibi kilidi açacak redeemScript'in ne olduğunun bilinmesine gerek yoktur. 


SegWit İşlemleri (P2WPKH ve P2WSH)

SegWit hakkında daha fazla bilgi için Yeni Başlayanlar İçin Ayrılmış Tanık (Segregated Witness) Rehberi makalemizi okuyabilirsiniz.
SegWit'teki işlem formatını anlamak için artık yalnızca bir scriptSig ve bir scriptPubKey'in olmadığını bilmeniz yeterlidir. Burada tanık (witness) adında yeni bir alan daha vardır. Eskiden scriptSig'de tuttuğumuz veri tanığa geçirilir, böylece scriptSig boş kalır.

‘bc1’ ile başlayan adreslere denk geldiyseniz bunlara yerel (native) SegWit adı verilir (‘3’le başlayanlar ise yalnızca SegWit uyumludur çünkü bunlar aslında P2SH adresleridir).


Pay-to-Witness-Pubkey-Hash (P2WPKH - Tanık Açık Anahtar Özetine Ödeme)

Pay-to-Witness-Pubkey-Hash (P2WPKH - Tanık Açık Anahtar Özetine Ödeme), P2PKH'nin SegWit versiyonudur. Tanığımız şu şekilde gözükür:

<signature> <public key>

Bunun P2PKH'deki scriptSig'le aynı şekilde gözüktüğünü fark edebilirsiniz. Burada, scriptSig boştur. Bununla birlikte, scriptPubKey şu şekildedir:

<OP_0> <public key hash>

Biraz ilginç gözüküyor değil mi? İmza, açık anahtar ve hash'ini karşılaştırmamıza imkan tanıyan işlem kodları nerede?

Burada ek operatörleri göstermiyoruz, çünkü işlemi alan node'lar bu işlemle ne yapacaklarını <public key hash>'in (açık anahtar hash'i) uzunluğuna bağlı olarak bilecektir. Node'lar uzunluğu hesaplayacak ve bildiğimiz eski tip P2PKH işlemiyle aynı şekilde çalıştırılması gerektiğini anlayacaktır.
Yükseltme yapmamış node'lar işlemleri bu şekilde nasıl yorumlayacağını bilemez, ama bunun bir önemi yoktur. Eski kurallara göre tanık yoktur, dolayısıyla boş bir scriptSig ve bazı verileri okumuş olurlar. Bunu değerlendirir ve geçerli olarak işaretlerler – onlara göre herhangi biri çıktıyı harcayabilir. SegWit'in geriye uyumlu bir soft fork (yumuşak çatallanma) olmasının nedeni budur.


Pay-to-Witness-Script-Hash (P2WSH - Tanık Betik Özetine Ödeme)

Pay-to-Witness-Script-Hash (P2WSH - Tanık Betik Özetine Ödeme), yeni P2SH'dir. Buraya kadar okuduysanız nasıl gözükeceğini muhtemelen tahmin edebilirsiniz, fakat yine de burada bir örneğinden bahsedeceğiz. Normalde scriptSig'e koyacağımız şey burada tanığımızdır. Örneğin, bir P2PKH işlemini saran P2WSH şuna benzer şekilde gözükebilir:

<signature 1> <public key>

scriptPubKey'imiz şudur:

<OP_0> <script hash>

Aynı kurallar geçerlidir. SegWit node'ları betik hash'inin uzunluğunu okur ve bunun bir P2WSH çıktısı olduğunu belirler. P2WSH çıktısı da P2SH'ye benzer şekilde değerlendirilir. Bununla birlikte eski node'lar bu hash'i, çıktının herhangi biri tarafından harcanabileceği şeklinde görür.


Son düşünceler

Bu makalede, Bitcoin'in yapı taşları hakkında bazı temel bilgiler edindik. Bu bilgileri kısaca özetleyecek olursak:


Betik türüTanım

Pay-to-Pubkey (P2PK)

Fonları belirli bir açık anahtara kilitler

Pay-to-Pubkey-Hash (P2PKH)

Fonları belirli bir açık anahtar hash'ine (yani bir adrese) kilitler

Pay-to-Script-Hash (P2SH)

Fonları, alıcının sunabileceği bir betik hash'ine kilitler

Pay-to-Witness-Pubkey-Hash (P2WPKH)

P2PK'nin SegWit versiyonu

Pay-to-Witness-Script-Hash (P2WSH)

P2SH'nin SegWit versiyonu


Bitcoin'in daha derinden incelemeye başladığınızda neden bu kadar büyük bir potansiyele sahip olduğunu anlayabilirsiniz. İşlemler, birçok farklı öğeden oluşabilir. Kullanıcılar, bu yapı taşlarını manipüle ederek fonların nasıl ve ne zaman harcanabileceğine yönelik koşullar belirleme konusunda büyük bir esnekliğe sahip olabilir.