مگر اینکه در چند سال اخیر در یک غار سکونت داشتید، باید تاکنون با نام بلاکچِیْن – Blockchain (زنجیرهبلوک) برخورد کرده باشید. اگر حداقل کمی از مفاهیم پایهی آن سر درآورده و با برنامهنویسی نیز تا حدودی آشنایی دارید، در اینجا طریقهی ساخت بلاکچین شخصی خودتان با جاوااسکریپت را خواهید آموخت تا آن مفاهیم را عمیقتر درک کنید.
ابتدا نیاز است کلاسی برای بلوکها تعریف کنیم و این کلاس نیز به متد سازنده نیاز دارد. سه المان اصلی هر بلوک عبارتاند از داده، hash و hash بلوک قبلی. در اینجا فعلاً یک index نیز برای هر بلوک در نظر گرفته و ضمناً از time stamp نیز در hash استفاده خواهیم کرد. لذا داریم:
class Block { constructor(index, timeStamp, data, previousHash = '') { this.index = index; this.timeStamp = timeStamp; this.data = data this.previousHash = previousHash; this.hash = this.calculateHash(); //بعداً تعریف میشود } }
درواقع هر بلوک محتوی دادههایی هست؛ مثلاً اگر سر و کارمان با یک رمزارز – Cryptocurrency همچون بیتکوین باشد، دادهی هر بلوک درواقع اطّلاعاتی از تراکنشهای مالی است. به علاوهی داده، هر بلوک یک «اثر انگشت» داشته که همان محتوای hash شده است. و در نهایت این که هر بلوک به «اثر انگشت» بلوک پیش از خود نیز اشاره میکند؛ لذا به این ترتیب یک زنجیره از بلوکها (یا همان بلاکچین) ساخته میشود. پس در همینجا، دو لایهی امنیتی وجود دارد: hash از دستکاری دادهها جلوگیری کرده و حتّی اگر خود hash نیز تغییر کند، بلوک بعدی دیگر به آن اشاره نکرده و لذا بلاکچین نامعتبر شناخته میشود.
برای محاسبهی hash بلاک فعلی از متدی موسوم به calculateHash استفاده میکنیم. برای این کار نیاز به کتابخانهی crypto-js داریم. برای جاوااسکریپت کافیست با دستور زیر در ترمینال آن را نصب کرد:
> npm install --save crypto-js
پس از نصب و افزودن به پروژه، آن را به این صورت فراخوانی میکنیم:
const SHA256 = require('crypto-js/sha256')
در اینجا از الگوریتم رمزنگاری SHA256 استفاده شده است؛ امّا شما آزادید از هر الگوریتم دلخواه دیگری نیز استفاده کنید. به هر حال متد calculateHash را در کلاس Block به این صورت تعریف مینماییم. در نهایت حاصل چنین میشود:
class Block { constructor(index, timeStamp, data, previousHash = '') { this.index = index; this.timeStamp = timeStamp; this.data = data; this.previousHash = previousHash; this.hash = this.calculateHash(); } calculateHash() { return SHA256(this.index + this.previousHash + this.timeStamp + JSON.stringify(this.data)).toString(); // رمزنگاری و تولید هش } }
حال نوبت ساخت کلاس بلاکچین است. متد سازندهی این کلاس، تنظیمکنندهی آرایهای است از بلوکها. گفته شد که هر بلوک در یک بلاکچین باید به بلوک قبلی خود اشاره داشته باشد. این قاعده در همهی بلوکها صادق است، بجز نخستین بلوک که به بلوک جنسیس شهرت دارد.
بلوک جنسیس استثنائاً باید توسّط خود سازندهی بلاکچین و به صورت دستی ساخته شود و بخش previousHash آن به هیچ آدرسی اشاره ندارد. برای مثال میتوانید بلوک جنسیس بیتکوین که مستقیماً توسّط ساتوشی ناکاموتو ایجاد شده است را در صفحهی زیر مشاهده نمایید:
مشاهده میکنید که hash مرتبط با بلاک قبلی آن به این گونه تنظیم شده است:
0000000000000000000000000000000000000000000000000000000000000000
ما نیز متدی بنام createGenesisBlock ساخته و آن را به عنوان اوّلین index در آرایهی موجود در متد سازنده تنظیم مینماییم. پس کلاس بلاکچین خود را به این گونه تعریف میکنیم:
class Blockchain { constructor() { this.chain = [this.createGenesisBlock()]; // ساخت زنجیره و تنظیم بلوک جنسیس به عنوان اوّلین عنصر } createGenesisBlock() { return new Block(0, "01/01/2019", "Genesis Block", "0"); // ایندکس = ۰ // تاریخ = برای نمونه: اوّل ژانویهی ۲۰۱۹ // داده = مهمنیست // هش بلوک قبلی = ۰ } }
دو متد دیگر نیز لازم است به این کلاس اضافه شود:
۱- متد getLastestBlock که وظیفهی بازگردانی آخرین بلوک از زنجیره را دارد.
۲- متد addBlock که وظیفهی ایجاد یک بلوک جدید در زنجیره را دارد.
getLastestBlock() { return this.chain[this.chain.length - 1]; // بازگردانی آخرین عنصر از آرایه } addBlock(newBlock) { newBlock.previousHash = this.getLastestBlock().hash; // ارجاع بلوک فعلی به هش بلوک قبلی newBlock.hash = newBlock.calculateHash(); // محاسبهی هش بلوک جدید this.chain.push(newBlock); // افزودن بلوک به زنجیره }
یک نکتهی بسیار مهم این است که در بیشتر بلاکچینها و مخصوصاً آنهایی که برای رمزارز استفاده میشوند، اکثراً نمیتوان به همین سادگی و از طریق فراخوانی متد سادهای چون addBlock اقدام به ایجاد یک بلوک جدید کرد؛ بلکه بلوکها لازم است از طریق فرایند mining ایجاد شده و در ازای ایجاد آنها نیز پاداشی اهدا شود. چرا که بلاکچین سیستمی نامتمرکز بوده و در اختیار یک شخص یا یک نهاد قرار ندارد و کسی نباید بتواند به راحتی هر تعداد بلوک دلخواه را ایجاد کند. مسئلهی دیگر نیز امنیت است. اگر هکری قصد تغییر دادهی یکی از بلوکها را داشته باشد باید کلّ زنجیره را دستکاری کرده و بلوکهای جدیدی با hashهای جدید تولید کند. لذا لازم است ایجاد بلوک، فرآیندی دشوار و زمانبر برای رایانهها باشد. به این فرآیند Proof-of-Work گفته میشود. در قسمتهای بعدی به این موضوع خواهیم رسید؛ ولی در اینجا فعلاً کار ایجاد بلوک را با همان متد addBlock پیش میبریم که به سادگی و در کسری از ثانیه، بلوک جدیدی برای ما ایجاد میکند.
اکنون میتوانیم آنچه نوشتیم را تست کنیم. در انتهای فایل و خارج از دو کلاس، یک شی (object) از کلاس Blockchainـمان را ساخته و دو بلوک جدید نیز به آن اضافه کنیم. بنده همین الآن رمزارز شخصی خودم را بانام پویانکوین را معرّفی میکنم! ?
let pouyanCoin = new Blockchain(); pouyanCoin.addBlock(new Block(1, "10/07/2019", {amount: 4})); pouyanCoin.addBlock(new Block(2, "12/07/2019", {amount: 10})); console.log(JSON.stringify(pouyanCoin, null, 4))
در خطّ اوّل، رمزارز شخصی من تعریف شده است. در خطوط دوّم و سوّم، دو بلوک جدید ساخته شده که هریک شامل index و time stamp و همچین یک موجودی فرضی است: در بلوک اوّل مبلغ ۴?? و در دوّمی ۱۰?? منتقل شده است. (?? چیست؟ حروف «پ» و «ک» به خط پهلوی از پارسی میانه که مخفف پویانکوین بوده و دلم میخواهد آن را به عنوان نماد واحد پول شخصی خود برگزینم!... بروید به خودتان بخندید!!) در خطّ چهارم نیز بلاکهای پویانکوین در قالب JSON نمایش داده خواهند شد.
حال کافیست فایلی که تمام کدهای بالا در آن نوشته شده است را با دستور node اجرا نماییم. برای مثال اگر نام آن main.js باشد، دستور از این قرار است:
> node main.js
حاصل اجرا چنین چیزی است:
{ "chain": [ { "index": 0, "timeStamp": "01/01/2019", "data": "Genesis Block", "previousHash": "0", "hash": "55c3348242ed0f9e5bd4804165b2efb2e4f420b7a6f99c43236efab11b19009e" }, { "index": 1, "timeStamp": "10/07/2019", "data": { "amount": 4 }, "previousHash": "55c3348242ed0f9e5bd4804165b2efb2e4f420b7a6f99c43236efab11b19009e", "hash": "1bddcb48e5c832b0863740334887b2bba8a7f35b437c8fe2eaa4842b807ac984" }, { "index": 2, "timeStamp": "12/07/2019", "data": { "amount": 10 }, "previousHash": "1bddcb48e5c832b0863740334887b2bba8a7f35b437c8fe2eaa4842b807ac984", "hash": "ab47226d740f542e708556d1d4ba71af7bb2862ce14384f13e3b3725592c7616" } ] }
مشاهده میشود که ابتدا بلوک جنسیس در بلاکچین قرار گرفته که به هیچ بلوک دیگری نیز اشاره ندارد. پس از آن بلوک شمارهی ۱ است که به بلوک جنسیس اشاره کرده و سپس بلوک شمارهی ۲ که به بلوک شمارهی ۱ اشاره میکند (به مقادیر previousHash و hash هر بلوک خوب دقّت کنید).
زنجیرهی بلوک ما کار میکند؛ امّا هنوز خیلی چیزها کم دارد و یکی از مهمترین آنان این است: بزرگترین مزیت بلاکچینها این است که اجازه نمیدهند دستکاری غیرمجاز در دادهها صورت گیرد. اگر دادهای تغییر کند، hash آن بلوک نیز تغییر میکند و اگر hash بلوکی تغییر کند، اشارهگر بلوک بعدی که به آن اشاره میکند نیز باید تغییر کند (چرا که دیگر به چیزی اشاره نمیکند) و در صورت این کار نیز hash آن تغییر میکند (فراموش نکنید که در متد calculateHash، یکی از عناصری که برای محاسبهی hash استفاده میشود hash بلوک قبلی است. باری دیگر به متد نگاهی بیندازید). به این ترتیب بلوک بعدی، بلوک بعدی، بلوک بعدی و... نیز باید تغییر کند. به بیان دیگر، اگر هکری بخواهد دادهی فقط یک بلوک را تغییر دهد (و مثلاً موجودی رمزارز خود را افزایش دهد) ناچار است نه فقط آن بلوک بلکه تکتک بلوکهای بلاکچین را تغییر دهد و بلوکهای جدیدی را جایگزین تمام آنها کند (در بلاکچین سادهی ما این کار هیچ مانعی ندارد و با استفاده از یک حلقهی for بر روی متد addBlock به راحتی قابل انجام است. امّا زمانی که Proof-of-Work در کار باشد، این کار به غیرممکن نزدیک میشود - مگر آن که هکر گرامی، میلیاردها قرن صبر داشته و یا این که بتواند با در دست داشتن امکان حملهی کوانتومی به بیتکوین و سایر رمزارزها پیروز شود. درواقع یکی از خطرات تهدیدکنندهی امنیت بلاکچینها، رایانش کوانتومی است. امّا جای نگرانی نیست؛ چرا که تا زمان فراگیری رایانش کوانتومی، از خود آن برای حفاظت از بلاکچینها نیز استفاده خواهد شد).
در کلاس Blockchain خود به متدی نیاز داریم که صحت بلاکچینمان را بررسی نماید. این مدت دو وظیفه دارد:
۱- بررسی این که آیا hash هر بلوک با سایر محتوای آن (داده، زمان، hash بلوک قبلی و...) مطابقت دارد یا نه. چرا که بدون چنین سنجی میتوان برای مثال با یک خطّ زیر، دادهی موجود در بلوک ۱ را به راحتی تغییر داد و گفت که نه ۴?? بلکه ۱,۰۰۰?? منتقل شده است!
pouyanCoin.chain[1].data = { amount : 1000 } // چگونه پولدار شویم؟
۲- بررسی آن که آیا هر بلوک به بلوک قبل از خود اشارهای دارد یا خیر. چرا که ممکن است کسی «زرنگبازی» در آورده و علاوهبر اجرای کد بالا، hash آن بلوک را نیز با کد پایین اصلاح نماید:
pouyanCoin.chain[1].hash = pouyanCoin.chain[1].calculateHash() // چگونه پس از پولدار شدن، اجازه ندهیم کسی بفهمد؟
برای جلوگیری از موضوع یادشده، متد دیگری بنام isChainValid را به کلاس Blockchain اضافه میکنیم. در این متد، تکتک بلوکها از دو نظر بررسی میشوند: ۱- آیا hash معتبری دارند؟ ۲- آیا به درستی به بلوک قبلی خود اشاره میکنند؟ (آیا previousHash آنها با hash بلوک قبلیشان یکی است؟)
isChainValid() { for (let i = 1; i < this.chain.length; i++) { // شمارش آرایه باید از ۱ شروع شود // چرا که با بلوک جنسیس کاری نداریم const currentBlock = this.chain[i]; const previousBlock = this.chain[i - 1]; if (currentBlock.hash !== currentBlock.calculateHash()) { // در این شرط، هش هر بلاک مجدداً با دادههای فعلی محاسبه // شده و با هش فعلی مقایسه میگردد return false; // نباید عدم تساویای وجود داشته باشد } if (currentBlock.previousHash !== previousBlock.hash) { // در این شرط، هشی که بلوک فعلی، آن را منتصب به بلوک قبلی // نگهداری میکند، با مقدار واقعی هش بلوک قبلی مقایسه میگردد return false; // نباید عدم تساویای وجود داشته باشد } } return true; // اگر همهچیز درست بود، بلاکچین معتبر است }
حال بگذارید آن را تست کنیم. کدهای تست قبلیای که در انتهای فایل نوشته بودید را پاک کرده و اینها را جایگزین کنید:
let pouyanCoin = new Blockchain(); pouyanCoin.addBlock(new Block(1, "10/07/2019", {amount: 4})); pouyanCoin.addBlock(new Block(2, "12/07/2019", {amount: 10})); pouyanCoin.chain[1].data = { amount : 1000 } // دستکاری داده pouyanCoin.chain[1].hash = pouyanCoin.chain[1].calculateHash() // دستکاری هش console.log("Is the blockchain valid? " + pouyanCoin.isChainValid());
اگر هر دو خطّ مربوط به دستکاری غیرمجاز را کامنت کرده و فایل را اجرا کنید، عبارت زیر در کنسول ظاهر میگردد:
Is the blockchain valid? true
امّا اگر خط مربوط به دستکاری داده به تنهایی یا هر دو خطّ دستکاری داده و دستکاری هش (بروزرسانی هش) اجرا شود، بلاکچین ما معتبر نبوده و این عبارت حاصل میشود:
Is the blockchain valid? false
اکنون یک بلاکچین بسیار ساده داریم. این بلاکچین هنوز از نبود ویژگیهای مهمّ بسیاری رنج میبرد؛ از جملهی آنها میتوان به موار زیر اشاره کرد:
این ویژگیها در بخشهای آینده اضافه خواهد شد.