چگونه با استفاده از جاوا اسکریپت یک ارز دیجیتال ساده بسازیم.

امروز، ما قصد داریم با استفاده از بلاک چینی که قبلا ایجاد کردیم، یک ارز دیجیتال بسازیم. اگر هنوز آن را نخوانده اید، به سرعت آن را با تغییرات جزئی مرور می کنم، اما ابتدا مقاله قبلی را بخوانید.

ارزهای دیجیتال چیست و چگونه کار می کنند؟

کریپتوکارنسی شکل اصلی ارز برای انجام تراکنش ها در شبکه بلاک چین و سایر برنامه های وب 3.0 است. طبق مقاله سفید ساتوشی در مورد بیت کوین، یک سکه الکترونیکی فقط زنجیره ای از امضاهای دیجیتال است. ویژگی اصلی آن غیرمتمرکز است به طوری که هیچ دولت یا نفوذ خارجی ممکن نیست. و یکی دیگر از مزایای ارزهای دیجیتال ناشناس بودن است، یعنی شما نمی توانید یک فرد را از تراکنش های آنها ردیابی کنید زیرا هر کسی می تواند بدون KYC به شبکه بپیوندد!

یک سکه با استفاده از کلید عمومی ذخیره شده در کیف پول از شخصی به فرد دیگر منتقل می شود. و تراکنش به صورت دیجیتالی توسط کلید خصوصی فرستنده امضا می شود تا هر کسی که از کلید عمومی فرستنده استفاده می کند تراکنش را تأیید کند.

هر زمان که یک تراکنش آغاز می شود، به طور موقت در یک استخر در شبکه بلاک چین ذخیره می شود. هنگامی که مقدار کافی از تراکنش ها در استخر شبکه جمع آوری شده باشد، تمام تراکنش ها به طور دائم در یک بلوک جدید ذخیره می شوند (شروع فرآیند استخراج) و از استخر حذف می شوند.

چگونه و چه کسی ارزهای دیجیتال را تولید می کند؟

به تولید ارزهای دیجیتال، ماینینگ گفته می شود. ارزها توسط شبکه بلاک چین به عنوان پاداش (انگیزه) برای گره های ماینر که با حل معمای رمزنگاری با موفقیت یک بلوک جدید استخراج می کنند، ضرب می شوند.

در صورت نداشتن حساب کاربری، سکه ها در کجا ذخیره می شوند؟

موجودی سکه ای که در کیف پول ها نشان داده می شود فقط سکه های باقی مانده است که در هیچ تراکنشی خرج نمی شوند. ما فقط باید تعداد سکه های خرج نشده را در برابر کلید عمومی کاربر محاسبه کنیم تا مانده سکه آنها را پیدا کنیم.

پیش نیاز ها:

برای دنبال کردن و درک این آموزش باید با موارد زیر آشنا باشید:

  • دانش کار کلاس ها و سایر ویژگی های ES6 در جاوا اسکریپت.
  • Node js  نصب شده بر روی سیستم تان.

بیایید سریع بلاک چین را مرور کنیم:

از مقاله قبلی متوجه شدیم که بلاک چین فقط یک زنجیره از بلوک ها است. بنابراین ابتدا کلاس Block را پیاده سازی می کنیم:

class Block {
  constructor(data, previousHash) {
    this.data = data;
    this.hash = "";
    this.previousHash = previousHash;
    this.rootHash = getMerkleRoot(data);
    this.timestamp = new Date();
    this.pow = 0;
  }

  mine(difficulty) {
    const regex = new RegExp(`^(0){${difficulty}}.*`);
    while (!this.hash.match(regex)) {
      this.pow++;
      this.hash = calculateHash(this);
    }
  }
}

در کد بالا، ما فقط یک ویژگی root hash را در Block اضافه کردیم. این هش ریشه با ایجاد یک درخت Merkle از لیست تراکنش ها ایجاد می شود. ویژگی داده حاوی لیست تراکنش ها خواهد بود.

getMerkleRoot از کلاس Merkle Tree استفاده می کند که در این مقاله پیاده سازی کردیم. پیاده سازی Merkle Tree در این GitHub در دسترس است. بیایید ببینیم که getMerkleRoot وcalculeHash چگونه پیاده سازی می شوند:

const calculateHash = (block) => {
  const blockData =
    block.rootHash +
    block.previousHash +
    block.timestamp.toISOString() +
    block.pow.toString();
  return createHash("sha256").update(blockData).digest("hex");
};

const getMerkleRoot = (transactions) =>
  MerkleTree.create(transactions).root.node;

اکنون کلاس Blockchain را پیاده سازی کنید:

class Blockchain {
  constructor(chain, difficulty) {
    this.chain = chain;
    this.difficulty = difficulty;
    this.blockTime = 10000;
  }
  static create(transaction) {
    const genesisBlock = new Block(transaction);
    return new Blockchain([genesisBlock], 3);
  }

  addBlock(transactions) {
    const lastBlock = this.chain.at(-1);
    const newBlock = new Block(transactions, lastBlock.hash);
    newBlock.mine(this.difficulty);
    this.chain.push(newBlock);
    this.difficulty +=
      Date.now() - newBlock.timestamp.getTime() > this.blockTime ? -1 : 1;
  }

  isValid() {
    for (let index = 0; index < this.chain.length; index++) {
      const currentBlock = this.chain[index];
      const previousBlock = this.chain[index - 1];
      if (
        currentBlock.hash !== calculateHash(currentBlock) ||
        previousBlock.hash !== currentBlock.previousHash
      )
        return false;
    }
    return true;
  }
}

ما کلاس بلاک چین را با حذف بلوک پیدایش با داده های پوچ به روز کرده ایم. این به این دلیل است که ما می خواهیم اولین ارز رمزنگاری شده را ضرب کنیم و اگر داده های پوچ را ارسال کنیم امکان پذیر نیست. ما یک تراکنش 10000 سکه را برای اولین کاربری که در بلوک پیدایش ذخیره می شود، آغاز می کنیم.

شروع کار با ارز دیجیتال:

ما ارز دیجیتال را از طریق تراکنش در شبکه ایجاد خواهیم کرد. سکه ها فقط یک عدد هستند که در یک تراکنش نشان داده می شوند. آنها غیر از این وجود ندارند. کلاس تراکنش به ویژگی های زیر نیاز دارد:

  • کلید عمومی فرستنده و گیرنده
  • مبلغی که باید منتقل شود
  • شناسه تراکنش
  • هش محتوا - کلید عمومی فرستنده و گیرنده، مقدار و شناسه (مهم است که در حین تأیید، نظم را خراب نکنید، در غیر این صورت هش مطابقت نخواهد داشت - برای من اتفاق افتاد؛ p)

تعریف کلاس Transaction:

import { v4 as uuidv4 } from 'uuid';
import { createHash } from "crypto";

class Transaction {
  constructor(senderPubKey, receiverPubKey, amount) {
    const id = uuidv4();
    const data = senderPubKey + receiverPubKey + amount + id;
    const hash = createHash("sha256").update(data).digest("hex")
    this.id = id;
    this.sender = senderPubKey;
    this.receiver = receiverPubKey;
    this.amount = amount;
    this.hash = hash;
  }
}

ما از بسته uuid از npm برای ایجاد شناسه تراکنش منحصر به فرد خود استفاده کرده ایم. برای نصب، فقط npm i uuid را از ترمینال اجرا کنید.

به روز کردن کلاس بلاکچین:

اکنون باید یک ویژگی تراکنش ها را به کلاس Blockchain اضافه کنیم تا تمام تراکنش های معلق در شبکه ذخیره شوند. و همچنین یک ویژگی پاداش که حاوی مقدار پاداشی است که باید به گره ماینر داده شود. در مرحله بعد، یک متد addTransaction را به Blockchain اضافه می کنیم که با اطمینان از اینکه تراکنش داده شده از قبل در استخر وجود ندارد، یک تراکنش و آن را به استخر تراکنش معلق اضافه میکند.

class Blockchain {
  constructor(chain, difficulty) {
    this.chain = chain;
    this.difficulty = difficulty;
    this.blockTime = 10000;
    this.transactions = []; // contains all the unconfirmed transactions
    this.reward = 678; // amount of reward to be given to the miner
  }
  // hidden methods ...
  addTransaction(transaction) {
    const isDuplicate = this.transactions.some(
      ({ hash }) => hash === transaction.hash
    );
    if (!isDuplicate) this.transactions.push(transaction);
  }
}

ما به یک متد دیگر برای بررسی موجودی باقیمانده برای کاربر نیاز داریم. همانطور که قبلاً گفتیم که موجودی فقط مبلغ انتقال نیافته برای یک کلید عمومی معین است (هویت کاربر با کلید عمومی او نشان داده می شود). بنابراین باید در تمام تراکنش‌های ذخیره شده در تمام بلوک‌ها، مبلغ را بررسی کنیم.

متد دریافت موجودی:

class Blockchain {
  // previous methods are hidden ...
  
  getBalance(pubKey) {
    let balance = 0;
    this.chain.forEach((block) => {
      block.data.forEach((transaction) => {
        if (transaction.sender === pubKey) {
          balance -= transaction.amount;
        }

        if (transaction.reciever === pubKey) {
          balance += transaction.amount;
        }
      });
    });
    return balance;
  }
}

در روش getBalance تمام بلوک ها و تمامی تراکنش های موجود در بلاک ها را بررسی کردیم. اگر تراکنش حاوی کلید عمومی داده شده باشد، بررسی می کنیم که آیا کلید عمومی فرستنده یا گیرنده است. اگر کلید با فرستنده مطابقت داشته باشد، مبلغ را از موجودی کسر می کنیم، در غیر این صورت، مبلغ را به موجودی اضافه می کنیم.

حرکت به سمت کیف پول:

کیف پول حاوی اطلاعات لازم برای کاربران خواهد بود. همه کاربران از طریق کیف پول خود تعامل کرده و تراکنش را آغاز می کنند. کلاس Wallet دارای جفت کلید خصوصی و عمومی برای یک کاربر خاص خواهد بود.

تعریف کلاس Wallet:

import { generateKeyPairSync } from "crypto";

class Wallet {
  constructor() {
    const keys = generateKeyPairSync("rsa", {
      modulusLength: 2048,
      publicKeyEncoding: { type: "spki", format: "pem" },
      privateKeyEncoding: { type: "pkcs8", format: "pem" },
    });
    this.privateKey = keys.privateKey;
    this.publicKey = keys.publicKey;
  }
}

در اینجا، ما از روشgeneKeyPairSync کتابخانه رمزنگاری استفاده کرده ایم. ما از کلیدهای RSA برای پیاده سازی خود استفاده می کنیم، اما بیت کوین و اتریوم از ECDSA در منحنی secp256k1 استفاده می کنند. متأسفانه، رمزارز ما به طور کامل از این فرمت پشتیبانی نمی کند، بنابراین ما از RSA استفاده کردیم.

اکنون به یک متد ارسال در کلاس Wallet نیاز داریم که یک تراکنش برای کاربر ایجاد کند. متد ارسال مبلغ و آدرس گیرنده (کلید عمومی) را که قرار است مبلغ به او منتقل شود، می پذیرد.

اما قبل از اجرای  متد ارسال، باید تراکنش را به صورت دیجیتالی امضا کنیم. در غیر این صورت تراکنش ها قابل تایید نخواهد بود. بنابراین ابتدا متد sign را در کلاس Transaction پیاده سازی می کنیم.

امضا تراکنش:

import { createSign } from "crypto";

class Transaction {
  // previous methods are hidden ...

  sign(wallet) {
    if (wallet.publicKey === this.sender) {
      const shaSign = createSign("sha256");
      shaSign.update(this.hash).end();
      this.signature = shaSign.sign(wallet.privateKey).toString("base64");
    }
  }
}

اکنون که متد sign بر روی Transaction پیاده سازی شده است، می توانیم روی متد ارسال تمرکز کنیم.

متد ارسال:

import { generateKeyPairSync } from "crypto";

class Wallet {
  // constructor is hidden ...
  
  send(amount, receiver, blockchain) {
    const transaction  = new Transaction(this.publicKey, receiver, amount);
    transaction.sign(this);
    blockchain.addTransaction(transaction);
  }
}

تا کنون همه این متدها را اجرا می کنیم اما یک مشکل مهلک وجود دارد که باید حل شود. شاید متوجه شده باشید که هنوز اعتباری برای تراکنشی که ما اجرا کرده ایم وجود ندارد. اکنون به این موضوع بپردازیم.

اعتبار سنجی تراکنش:

برای اعتبارسنجی تراکنش یک متد isValid به کلاس Transaction اضافه می کنیم. تراکنش باید حاوی یک فرستنده، گیرنده، مبلغ معتبر باشد، موجودی فرستنده باید بیشتر از مبلغی باشد که منتقل می شود، هش تراکنش باید برابر باشد و امضا باید تأیید شود.

اعتبارسنجی یک تراکنش :

import { createVerify, createHash } from "crypto";

class Transaction {
  // previous methods are hidden
  
  isValid(chain) {
    const sig = Buffer.from(this.signature, "base64");
    const verify = crypto.createVerify("SHA256");
    verify.update(this.hash);
    const isVerified = verify.verify(this.sender, sig);
    
    const data = this.sender + this.receiver + this.amount + this.id;
    const hash = createHash("sha256").update(data).digest("hex")
    
    return (
      this.sender &&
      this.receiver &&
      this.amount &&
      chain.getBalance(this.sender) >= this.amount &&
      this.hash === hash &&
      isVerified
    );
  }
}

به یاد داشته باشید که ما تراکنش را در استخر تراکنش های بلاک چین بدون اعتبارسنجی بلاک چین اضافه کرده ایم؟ ما باید هر تراکنش را قبل از اضافه کردن به استخر اعتبار سنجی کنیم. خط زیر را به جای شرط if در متد addTransaction در کلاس Blockchain اضافه می کنیم.

if(!isDuplicate && transaction.isValid(this))

اعتبارسنجی تمام تراکنش ها در یک بلوک:

اکنون باید تمام تراکنش‌های داخل یک بلوک را اعتبارسنجی کنیم. ما می‌توانیم این کار را با حلقه کردن همه تراکنش‌ها و اعتبارسنجی هر یک از آنها انجام دهیم. ما از متدevery  استفاده خواهیم کرد که یک تابع مرتبه بالاتر در JS است که به ما امکان می دهد تمام عناصر یک آرایه را برای یک شرط بررسی کنیم و اگر هر یک از آنها false را برگرداند false را برمی گرداند.

 

class Block {
  // previous methods are hidden

  hasValidTransactions(chain) {
    return this.data.every((transaction) => transaction.isValid(chain));
  }
}

 

استخراج تراکنش:

در نهایت، ما باید تراکنش ها را استخراج کنیم، یعنی یک بلوک با تمام تراکنش های معلق استخراج کنیم. برای استخراج یک بلوک، یک متد mine-transaction را در کلاس Blockchain پیاده سازی خواهیم کرد. پس از افزودن تمامی تراکنش ها به بلوک جدید، لیست تراکنش های معلق را خالی می کنیم.

 

class Blockchain {
  // previous methods are hidden ...
  mineTransaction() {
    this.addBlock([...this.transactions]);
    this.transactions = [];
  }
}

 

یک موضوع دیگر باید حل شود:

گفتیم که به بلوک ماینینگ پاداش ثابتی می دهیم و هنوز آن را اجرا نکرده ایم. در روش mineTransaction در بلاک چین، باید تراکنش دیگری ایجاد کنیم که پاداش را به آدرس ماینر منتقل کند. اما مشکل این است که در این مورد فرستنده کیست؟

پاداش دادن به ماینر:

ما باید یک کیف پول برای شبکه بلاک چین ایجاد کنیم که برای همه روش ها و کلاس ها قابل دسترسی باشد. و همچنین باید برای متد isValid در کلاس Transaction برای کیف پول شبکه یک استثنا قائل شویم، زیرا کیف پول Network هیچ موجودی ندارد اما قادر به ایجاد سکه های جدید است. سپس یک تراکنش پاداش به روش mineTransaction اضافه می کنیم.

 

const NETWORK_WALLET = new Wallet();

class Transaction {
  // previous methods hidden ...
  isValid(chain) {
    // hidden ...
    return (
      this.sender &&
      this.receiver &&
      this.amount &&
      (chain.getBalance(this.sender) >= this.amount ||
        this.sender === NETWORK_WALLET.publicKey) &&
      this.hash === hash &&
      isVerified
    );
  }
}

class Blockchain {
  // previous methods hidden ...
  mineTransactions(rewardAddress) {
    const rewardTransaction = new Transaction(
      NETWORK_WALLET.publicKey,
      rewardAddress,
      this.reward
    );
    rewardTransaction.sign(NETWORK_WALLET);
    this.addBlock([rewardTransaction, ...this.transactions]);

    this.transactions = [];
  }
}

 

انتشار چند سکه اول:

ما نمونه بلاک چین را با یک تراکنش اولیه برای برخی از سکه ها به کاربر اول راه اندازی می کنیم. انتقال از کیف پول شبکه انجام می شود در غیر این صورت اعتبارسنجی نمی شود زیرا قبلاً هیچ سکه ای وجود نداشته است. اولین سکه ها را ضرب می کنیم و به کیف پول کاربر اول منتقل می کنیم.

به روز رسانی متد ایجاد بلاک چین:

 

class Blockchain {
  // other methods hidden ...
  static create(firstUserAddress) {
    const firstTransaction = new Transaction(
      NETWORK_WALLET.publicKey,
      firstUserAddress,
      10000
    );
    const genesisBlock = new Block([firstTransaction]);
    genesisBlock.mine(3);
    return new Blockchain([genesisBlock], 3);
  }
}

 

در روش فوق، ابتدا تراکنش را از NETWORK_WALLET به آدرس کاربر اول ایجاد کردیم. سپس با اولین تراکنش یک بلوک جدید استخراج کردیم و آن را به بلاک چین اضافه کردیم. این اولین بلوک در بلاک چین خواهد بود، بنابراین می‌توانیم آن را Genesis Block بنامیم.

لحظه ی حقیقت:

تمام کدهای لازم پیاده سازی شده است، اکنون زمان تست آن است. ابتدا باید برای 2 کاربر کیف پول ایجاد کنیم تا تراکنش بین آنها انجام شود. بیایید به دلایل واضح آنها را آلیس و باب بنامیم. اکنون شبکه بلاک چین را با دادن اولین سکه ها به آلیس که اولین کاربر است، معرفی می کنیم. سپس تعدادی سکه از آلیس به باب منتقل می کنیم و موجودی هر دو کاربر را بررسی می کنیم و بررسی می کنیم که آیا بلاک چین بعد از تمام تراکنش ها معتبر است یا خیر.

 

const Alice = new Wallet();
const Bob = new Wallet();

function init() {
  const blockchain = Blockchain.create(Alice.publicKey);
  Alice.send(1299, Bob.publicKey, blockchain);
  Alice.send(345, Bob.publicKey, blockchain);
  blockchain.mineTransactions(Bob.publicKey);
  console.log("Alice has " + blockchain.getBalance(Alice.publicKey));
  console.log("Bob has " + blockchain.getBalance(Bob.publicKey));
  console.log(`Blockchain is${blockchain.isValid() ? "" : " not"} valid`);
}

init();

 

پس از اجرای فایل، باید موجودی Alice را 8356 و Bob را 2322 بدست آوریم. و خط بعدی باید "Blockchain معتبر است" باشد. و عکس فوری بلاک چین باید چیزی شبیه به تصویر زیر باشد.

نتیجه:

امروز یاد گرفتیم که چگونه ارزهای رمزنگاری شده چگونه کار می کنند و چگونه با استفاده از جاوا اسکریپت یک ارز ساده ایجاد کنیم. ما از زنجیره بلوکی که قبلاً از این پست یاد گرفتیم استفاده کردیم. کد منبع در این مخزن Github موجود است و فایل‌ها با TypeScript نوشته شده‌اند که ابر مجموعه جاوا اسکریپت است. دستورالعمل ها در readme ارائه شده است.

 

 

منبع: javascript.plainenglish.io