Биткойн был захватывающим в течение нескольких лет, что также привело к возникновению всей индустрии блокчейнов. Тем не менее, биткойн слишком сложен, чтобы его можно было однозначно признать многообещающей технологией, состоящей из криптографии, распределенных систем и финтеха. Цель этой статьи — упростить некоторые детализированные настройки и представить доказательство концепции Биткойн с помощью JavaScript. Также эту историю можно рассматривать как продолжение статьи @spenserhuang Learn & Build a Javascript Blockchain.

Общие требования

Ожидается, что зрители этой истории сохранят непредвзятость и признают некоторые базовые навыки программирования на JavaScript. Во-первых, криптографическая часть в основном требует цифровой подписи и хеш-функции, некоторые предыдущие истории могут дать некоторое концептуальное понимание. Во-вторых, алгоритм консенсуса, proof of work, будет реализован в приложении как часть распределенной системы. Наконец, перед тем, как начать этот рассказ, необходимо ознакомиться с базовой концепцией объектно-ориентированного программирования.

Концептуальное введение

По сути, мы пишем блокчейн, который представляет собой «цепочку блоков», и каждый блок содержит множество «транзакций»; кроме того, есть много «пользователей», которые владеют закрытыми ключами, чтобы они могли их передавать и майнить. Обзор дает нам представление о написании некоторых «объектов» с точки зрения сверху вниз. Каждый упомянутый выше объект описывается как класс на языке программирования.

Начать строить

NPM и Git

Начнем с инструмента npm. Если npm не установлен, обратитесь к этому сайту. Моя разрабатываемая версия - 6.11.1, возможно, вам может понадобиться близкая версия. Следующая команда может помочь проверить версию npm.

npm -v

Затем git также является инструментом импорта перед сборкой, найдите инструкцию здесь, если git все еще недоступен на вашем компьютере.

Запустите PoC для биткойнов

Я подготовил проект, состоящий из некоторого кода JavaScript, для постепенной реализации PoC для биткойнов. Сначала перейдите по понравившемуся пути к файлу, клонируйте репозиторий BitcoinPoC и войдите в папку после загрузки.

cd your/path
git clone https://github.com/Kylesstory/BitcoinPoC.git
cd ./BitcoinPoC/

Затем некоторые существующие библиотеки могут нам сильно помочь при работе с хэш-функциями и цифровой подписью ECDSA. Эти библиотеки использовались в примерах кода. Итак, давайте сначала установим их.

npm install

В идеале необходимые библиотеки будут установлены автоматически, если вам повезет. Посмотрите, что мы только что установили:

npm list

Аналогичный результат может выглядеть так:

npm list
[email protected] your/path/BitcoinPoC
├── [email protected]
├── [email protected]
├─┬ [email protected]
│ ├── [email protected]
│ ├── [email protected]
│ ├─┬ [email protected]
│ │ ├── [email protected] 
│ │ └── [email protected] 
│ ├─┬ [email protected]
│ │ ├── [email protected] 
│ │ ├── [email protected] 
│ │ └── [email protected] 
│ ├── [email protected]
│ ├── [email protected]
│ └── [email protected]
└── [email protected]

Предположим, вам повезло, теперь мы можем забыть о npm и git; затем с нетерпением ждем возможности сосредоточиться на реализации упрощенной цепочки биткойнов. Давайте откроем файл PoCBitcoin.js в предпочитаемом вами текстовом редакторе, таком как Sublime или VSCode, он может выглядеть так.

Как упоминалось ранее, код почти завершен. Первые 5 строк — это импортированные библиотеки; строки с 12 по 21 — некоторые глобальные переменные; строки с 23 по 111 — это некоторые общие функции, которые могут помочь в некоторых повторяющихся работах. Программа начинается со строки 296. Мы сосредоточимся на классах со строки 115 по строку 294, объектах Пользователь, Транзакция, Блок и Блокчейн, о которых мы упоминали в начале. Все пробелы, которые необходимо заполнить, будут помечены [напишите код здесь].

Пользователь (часть 1)

Начнем с класса User. В биткойне пользователь владеет парой закрытого и открытого ключа, а также мнемоникой и адресом.

Пара закрытый/открытый ключ делает пользователя уникальным, поскольку он или она может подписывать цифровые подписи, которые не могут быть подделаны другими лицами, не владеющими закрытым ключом. Это делает пользователей различимыми и не заменяемыми (а также неизменным для подписанного контента). Вот почему каждый пользователь должен хранить свой закрытый ключ в тайне.

Мнемоника может быть реализована как копия секретного ключа, закодированная в удобочитаемый формат, такой как 12 или 24 английских слова. Это не реализовано здесь для ясности.

Открытый ключ представляет пользователя. Однако открытый ключ слишком длинный для идентификатора, поэтому вместо открытых ключей предлагаются адреса. Концептуально адрес представляет собой хеш-значение открытого ключа. Благодаря устойчивому к коллизиям свойству хеш-функций разные открытые ключи с огромной вероятностью становятся разными адресами, поэтому адреса могут по-прежнему быть идентификаторами пользователей. Подробную реализацию можно найти здесь, которая состоит из набора хэш-функций (SHA256 и RIPEMD-160), контрольной суммы и кодировки Base58. Для ясности мы просто упростили адреса как shortHash открытого ключа, который shortHash является предопределенной хэш-функцией на основе SHA256 в этой программе.

constructor() означает, что он создает объект User без каких-либо параметров. Во-первых, нам нужна пара закрытый/открытый ключ. Этого можно добиться с помощью библиотеки ec.

constructor(){ // to create a new user with name, secret key, public key, address and balance
  // 1. create public key and secret key
  this.key = ec.genKeyPair();
  // Note. In Bitcoin and Etherum, there are mechanisms to avoid secret key losing.
  // In Bitcoin improvement proposal 39, their is a technique called mnemonic which
  // encodes the secret key into English words, users can write it down as the cold storage of secret key.
  // Still that sentence, we omit it for clear.
 }

Затем нам нужно добавить строку для вычисления адресов пользователей. Хотя адрес концептуально представляет собой хеш-значение открытого ключа, его реализация несколько завершена, так что мы просто «shortHash» открытый ключ в качестве адреса.

constructor(){ // to create a new user with name, secret key, public key, address and balance
  // 1. create public key and secret key
  this.key = ec.genKeyPair();
  // Note. In Bitcoin and Etherum, there are mechanisms to avoid secret key losing.
  // In Bitcoin improvement proposal 39, their is a technique called mnemonic which
  // encodes the secret key into English words, users can write it down as the cold storage of secret key.
  // Still that sentence, we omit it for clear.
  
  // 2. compute the address
  const publicKey = this.key.getPublic();
  // Warn! This is a simplified version of address, not the official one.
  this.address = shortHash(publicKey.getX().toString('hex') + publicKey.getY().toString('hex'));
 }

В-третьих, зарегистрируйте адрес в глобальном списке пользователей, чтобы созданного пользователя можно было найти, введя «users[address]».

constructor(){ // to create a new user with name, secret key, public key, address and balance
  // 1. create public key and secret key
  this.key = ec.genKeyPair();
  // Note. In Bitcoin and Etherum, there are mechanisms to avoid secret key losing.
  // In Bitcoin improvement proposal 39, their is a technique called mnemonic which
  // encodes the secret key into English words, users can write it down as the cold storage of secret key.
  // Still that sentence, we omit it for clear.
  
  // 2. compute the address
  const publicKey = this.key.getPublic();
  // Warn! This is a simplified version of address, not the official one.
  this.address = shortHash(publicKey.getX().toString('hex') + publicKey.getY().toString('hex'));
  // 3. register the address to the blockchain
  users[this.address] = this;
  addresses.push(this.address);
 }

Далее давайте взглянем на класс Transaction.

Для краткости транзакция иногда обозначается как tx. Затем, глядя на конструктор транзакции, есть 4 параметра: от, до, сумма и наконечник. Первые два являются адресами, а последние два — числами. Интуитивно понятно, что транзакция отправляется пользователем[от], отправляется пользователю[кому], с суммой и подсказкой.

Представьте себе сценарий, что вы ужинаете в ресторане в США, вы платите цену (сумму) ресторану за еду, а также чаевые официантам за обслуживание. Биткойн использует аналогичную концепцию: когда пользователь переводит определенное количество биткойнов другому пользователю, требуется небольшая часть чаевых в качестве платы за услуги для верификатора транзакций, который участвует в майнинге и поддержании надежности блокчейна. Отправитель транзакции решает, сколько чаевых, а затем подписывает транзакцию, используя свой закрытый ключ для авторизации транзакции.

Вернемся к классу User, касающемуся функции transfer. Для создания транзакции будут переданы три параметра to, amount и tip. У нас есть несколько вещей, которые нужно выполнить в функциональном объекте. Во-первых, сначала проверяется баланс, чтобы обеспечить успешный перевод.

transfer(to, amount, tip){ // to transfer money to someone
  // 1. make sure users' balance if enough to transfer
  if (users[this.address].balance >= (amount + tip)){
   
  } else{
   console.log('Insufficient balance.')
  }
 }

Во-вторых, если проверено достаточно средств, создайте транзакцию с параметрами и собственным адресом.

transfer(to, amount, tip){ // to transfer money to someone
  // 1. make sure users' balance if enough to transfer
  if (users[this.address].balance >= (amount + tip)){
   // 2. create a transaction
   const tx = new Transaction(this.address, to, amount, tip);
   const txString = tx.toString();
  } else{
   console.log('Insufficient balance.')
  }
 }

В-третьих, отправитель подписывает транзакцию, используя свой закрытый ключ.

transfer(to, amount, tip){ // to transfer money to someone
  // 1. make sure users' balance if enough to transfer
  if (users[this.address].balance >= (amount + tip)){
   // 2. create a transaction
   const tx = new Transaction(this.address, to, amount, tip);
   const txString = tx.toString();
   // 3. sign the transaction
   tx.signature = this.key.sign(txString);
   
  } else{
   console.log('Insufficient balance.')
  }
 }

Далее, опубликуйте транзакцию в блокчейне (unpackagedTxs в этой программе).

transfer(to, amount, tip){ // to transfer money to someone
  // 1. make sure users' balance if enough to transfer
  if (users[this.address].balance >= (amount + tip)){
   // 2. create a transaction
   const tx = new Transaction(this.address, to, amount, tip);
   const txString = tx.toString();
   // 3. sign the transaction
   tx.signature = this.key.sign(txString);
   // 4. save the created transaction to 'unpackagedTxs'
   unpackagedTxs.push(tx);
   
  } else{
   console.log('Insufficient balance.')
  }
 }

Итак, мы завершили поэтапное задание. Мы создали объект Пользователь, у которого есть пара ключей подписи и адрес в качестве идентификатора; кроме того, пользователь может перевести кому-то через предложение транзакций. Далее давайте временно оставим функцию mine() и рассмотрим класс транзакции.

Сделка

То, что нам нужно сделать в этом классе, немного проще. Просто сделайте транзакцию поддающейся проверке позже. Необходимо сделать две вещи: убедиться, что транзакция подписана и подписана правильно.

verify(){
  // To make sure the transaction is valid.
        
  // 1. Ensure 'this.signature' has been filled by some verifier.
  if (this.signature === '') {
   return false;
  } else{
   
  }
 }

Приведенные выше команды обеспечивают выполнение подписи этой транзакции; и приведенные ниже команды проверяют цифровую подпись отправителя.

verify(){
  // To make sure the transaction is valid.
        
  // 1. Ensure 'this.signature' has been filled by some verifier.
  if (this.signature === '') {
   return false;
  } else{
   // 2. Verify the validation of the digital signature.
   const user = users[this.from];
   return user.key.verify(this.toString(), this.signature);
  }
 }

Это действительно кусок пирога, верно? Сделайте перерыв, приготовьте кофе, и мы переходим к классу Block.

Блокировать

Блок немного сложнее, потому что это основная единица блокчейна. Он работает как коробка, скрывающая некоторые данные внутри и прикрепляющая некоторые метаданные снаружи, например, HTTP-пакет.

Сокрытие данных, очевидно, является данными транзакции. В официальных настройках Биткойн размер блока установлен на 1 МБ, что может динамически содержать сотни транзакций в зависимости от их размера. Для облегчения понимания установлено, что каждый блок может содержать три транзакции в этом PoC. Кстати, структура данных дерева Меркла используется для доказательства иерархического хеш-следа для данных транзакций. Звучит сложно, поэтому здесь тоже опущено.

Метаданные включают отметку времени, previousHash, nonce и подпись.

  1. Временная метка проста.
  2. previousHash играет жизненно важную роль в блокчейне. Благодаря свойству "устойчивости к коллизиям" хеш-функций previousHash может обозначать предыдущий блок. Блок n не был изменен, если previousHash n, хранящийся в блоке n+1, верен, и previousHash n-1, хранящийся в блоке n, гарантирует правильность блока n-1. Рекурсивно хеш-значение предыдущего блока сохраняется в текущем блоке. Он неоднократно обеспечивает точность предыдущих блоков, что в конечном итоге создает неизменную систему блокчейна. Это основная идея о неизменности блокчейна.
  3. Переменная nonce является ключевым фактором знаменитого протокола консенсуса, доказательства работы. Благодаря свойству равномерного распределения хеш-функций выходные данные хеш-функций непредсказуемы, если только вы не вычислите их практически. Это создает интересную и, возможно, справедливую ситуацию для каждого участника, чтобы соревноваться в вычислении целевого диапазона хеш-значений. Точнее, данные транзакции, отметка времени и previousHash являются фиксированными полями ввода, и ожидается, что майнеры найдут входной одноразовый номер вместе с тремя вышеупомянутыми входными данными. поля для вычисления отличного хэш-вывода, который принадлежит желаемому диапазону. Я знаю, что описание все еще туман, извините. Например, выходное хеш-значение располагается случайным образом от 0 до 255; а прицельная дальностьменее 32. С фиксированными входными данными данные транзакции, отметка времени и previousHash, майнер продолжает пробовать разные одноразовые номера до тех пор, пока hash(данные транзакции, отметка времени, previousHash, nonce) = x и x ‹ 32. Надеюсь, что пример поможет понять. Процесс одноразового поиска называется майнингом или решением головоломки. На самом деле это непростая работа, и первый, кто решит головоломку, получит большую награду. Награда за майнинг изначально составляет 50 биткойнов за блок, но каждые четыре года она уменьшается до половины. Стоит отметить, что целевой диапазон в Биткойне не фиксирован; он динамически возникает или уменьшается, чтобы среднее время майнинга составляло около 10 минут на блок. Для простоты этот проект PoC поддерживает статистику на уровне 3.
  4. После упаковки транзакций, сбора временной метки и previousHash и получения подходящего одноразового номера верификатор блока подписывает заблокировать, используя ее / его закрытый ключ в качестве доказательства работы (чтобы впоследствии потребовать вознаграждение за майнинг).

Ты еще не спишь? Если да, то давайте перейдем к реализации кода. Головоломка реализуется путем взаимодействия объекта Пользователь и объекта Блок. Концептуально пользователь выполняет майнинг с помощью функции mine(), а блок возвращает достоверность с помощью функции challenge().

challenge(){ // if the hash value begins with three (defined by variable 'blockchain.difficulty') 0s
        const binary = hexToBinary(this.hash);
        // [ Write code here ]
        // To ensure the found 'nonce' satisfy the puzzle challenge 
        // of Bitcoin proof of work with 'difficulty'.
    }

Переменная difficulty определяет количество последовательных нулей в старших значащих битах. Например, пусть difficulty=3 означает, что блок действителен, если верификатор блока находит одноразовый номер, так что хэш-вывод блока начинается как минимум с трех нулей. Попробуйте следующее решение или вы можете написать в своем дизайне.

challenge(){ // if the hash value begins with three (defined by variable 'blockchain.difficulty') 0s
  const binary = hexToBinary(this.hash);
  let x = 0;
  for (let i = 0; i < binary; i++){
   if (binary[i] === '0'){
    x += 1;
   } else{
    return false;
   }
   if (x >= blockchain.difficulty){
    return true;
   }
  }
  return false;
 }

После функции challenge() необходимо проверить правильность блока, поэтому также требуется функция verify(). В этом разделе кода должны быть реализованы четыре проверки.

verify(){ // to verify a single block
     if (this.signature === ''){ 
     // 1. Ensure 'this.signature' has been filled by some verifier.
      return false
     }
     
    }
  1. Сигнатура этого блока сгенерирована.
verify(){ // to verify a single block
     if (this.signature === ''){ 
     // 1. Ensure 'this.signature' has been filled by some verifier.
      return false
     }
     if (this.hash !== shortHash(this.toString())){ 
     // 2. Ensure 'this.hash' has been correctly computed. 
        //    This step makes sure the block data hasn't been modified.
      return false
     }
     
    }

2. Хеш-свойство вычислено правильно.

verify(){ // to verify a single block
     if (this.signature === ''){ 
     // 1. Ensure 'this.signature' has been filled by some verifier.
      return false
     }
     if (this.hash !== shortHash(this.toString())){ 
     // 2. Ensure 'this.hash' has been correctly computed. 
        //    This step makes sure the block data hasn't been modified.
      return false
     }
     if (! this.challenge()){ 
     // 3. Ensure the found 'this.nonce' in this block satisfies the Bitcoin puzzle.
      return false
     }
     
    }

3. Головоломка в этом блоке решена.

verify(){ // to verify a single block
     if (this.signature === ''){ 
     // 1. Ensure 'this.signature' has been filled by some verifier.
      return false
     }
     if (this.hash !== shortHash(this.toString())){ 
     // 2. Ensure 'this.hash' has been correctly computed. 
        //    This step makes sure the block data hasn't been modified.
      return false
     }
     if (! this.challenge()){ 
     // 3. Ensure the found 'this.nonce' in this block satisfies the Bitcoin puzzle.
      return false
     }
     // 4. Verify the validation of the digital signature.
     const user = users[this.verifier];
     return user.key.verify(this.toString(), this.signature);
    }

4. Подпись верификатора блока действительна.

Блок действителен, если все эти четыре проверки пройдены. Затем давайте снова вернемся к объекту User, чтобы настроить майнинг.

Пользователь (часть 2)

Теперь мы построили блок и проверили его головоломку. Давайте создадим функцию mine() шаг за шагом. Всего шагов шесть.

mine(){
        // This is the proof of work, keep trying different nonces until satisfying the block challenge (puzzle)
        
        // [ Write code here ]
        // 1. collect the timestamp
        const timestamp = new Date();
        
    }
  1. настроить отметку времени
mine(){ // to mine the coins
        // This is the proof of work, keep trying different nonces until satisfying the block challenge (puzzle)
  
  // 1. collect the timestamp
  const timestamp = new Date();
  // 2. pick several transactions (defined by variable 'capability')  
  // Warning. Bitcoin deals with transactions using Merkle tree data structure.
  // In this PoC case, merkle tree is omitted for easy understanding.
  const txs = []; 
  while (unpackagedTxs.length > 0 && txs.length < capability) { // if the block has space and there are unpackaged tx
   const tx = unpackagedTxs.pop(0);
   txs.push(tx);
  }
  const data = JSON.stringify(txs);
 }

2. выберите несколько транзакций для упаковки в блок

mine(){ // to mine the coins
        // This is the proof of work, keep trying different nonces until satisfying the block challenge (puzzle)
  
  // 1. collect the timestamp
  const timestamp = new Date();
  // 2. pick several transactions (defined by variable 'capability')  
  // Warning. Bitcoin deals with transactions using Merkle tree data structure.
  // In this PoC case, merkle tree is omitted for easy understanding.
  const txs = []; 
  while (unpackagedTxs.length > 0 && txs.length < capability) { // if the block has space and there are unpackaged tx
   const tx = unpackagedTxs.pop(0);
   txs.push(tx);
  }
  const data = JSON.stringify(txs);
  // 3. collect the hash value of previous block
  const previousHash = blockchain.latestBlock().hash;
 }

3. собрать хеш-значение предыдущего блока

mine(){ // to mine the coins
        // This is the proof of work, keep trying different nonces until satisfying the block challenge (puzzle)
  
  // 1. collect the timestamp
  const timestamp = new Date();
  // 2. pick several transactions (defined by variable 'capability')  
  // Warning. Bitcoin deals with transactions using Merkle tree data structure.
  // In this PoC case, merkle tree is omitted for easy understanding.
  const txs = []; 
  while (unpackagedTxs.length > 0 && txs.length < capability) { // if the block has space and there are unpackaged tx
   const tx = unpackagedTxs.pop(0);
   txs.push(tx);
  }
  const data = JSON.stringify(txs);
  // 3. collect the hash value of previous block
  const previousHash = blockchain.latestBlock().hash;
  // 4. try different nonces until solve the puzzle.
  let nonce, block;
  while (true){ // trying different nonces to satisfy the challenge
   nonce = crypto.randomBytes(32).toString('hex');
   block = new Block(timestamp, data, previousHash, nonce);
   if (block.challenge()){
    break;
   }
  }
  
 }

4. майнинг

mine(){ // to mine the coins
        // This is the proof of work, keep trying different nonces until satisfying the block challenge (puzzle)
  
  // 1. collect the timestamp
  const timestamp = new Date();
  // 2. pick several transactions (defined by variable 'capability')  
  // Warning. Bitcoin deals with transactions using Merkle tree data structure.
  // In this PoC case, merkle tree is omitted for easy understanding.
  const txs = []; 
  while (unpackagedTxs.length > 0 && txs.length < capability) { // if the block has space and there are unpackaged tx
   const tx = unpackagedTxs.pop(0);
   txs.push(tx);
  }
  const data = JSON.stringify(txs);
  // 3. collect the hash value of previous block
  const previousHash = blockchain.latestBlock().hash;
  // 4. try different nonces until solve the puzzle.
  let nonce, block;
  while (true){ // trying different nonces to satisfy the challenge
   nonce = crypto.randomBytes(32).toString('hex');
   block = new Block(timestamp, data, previousHash, nonce);
   if (block.challenge()){
    break;
   }
  }
  // 5. sign the solved block
  block.verifier = this.address;
  block.signature = this.key.sign(block.toString());
  // 6. add the block using 'blockchain.addblock()' 
  blockchain.addBlock(block);
 }

5. подписать блок после успешного майнинга

mine(){ // to mine the coins
        // This is the proof of work, keep trying different nonces until satisfying the block challenge (puzzle)
  
  // 1. collect the timestamp
  const timestamp = new Date();
  // 2. pick several transactions (defined by variable 'capability')  
  // Warning. Bitcoin deals with transactions using Merkle tree data structure.
  // In this PoC case, merkle tree is omitted for easy understanding.
  const txs = []; 
  while (unpackagedTxs.length > 0 && txs.length < capability) { // if the block has space and there are unpackaged tx
   const tx = unpackagedTxs.pop(0);
   txs.push(tx);
  }
  const data = JSON.stringify(txs);
  // 3. collect the hash value of previous block
  const previousHash = blockchain.latestBlock().hash;
  // 4. try different nonces until solve the puzzle.
  let nonce, block;
  while (true){ // trying different nonces to satisfy the challenge
   nonce = crypto.randomBytes(32).toString('hex');
   block = new Block(timestamp, data, previousHash, nonce);
   if (block.challenge()){
    break;
   }
  }
  // 5. sign the solved block
  block.verifier = this.address;
  block.signature = this.key.sign(block.toString());
  // 6. add the block using 'blockchain.addblock()' 
  blockchain.addBlock(block);
 }

6. опубликовать этот блок в блокчейне

Как вы могли заметить, на 6-м шаге использовалась неопределенная функция addBlock(block) в объекте Blockchain. Давайте перейдем к последнему шагу к созданию блокчейна.

Блокчейн

Бегло взглянув на конструктор, становится очевидным, что объект Блокчейн содержит только три переменные: цепочка (блоков), сложность майнинга и вознаграждение за майнинг. Первый блок называется блоком генезиса, который сильно отличается от других блоков. Здесь могут быть определены некоторые начальные настройки. Здесь осталось выполнить только две работы — завершить функции addBlock(block) и verifyAllBlocks().

addBlock(block){ // to add a new block
     // 1. verify the validity of the block
     if (block.verify()){
      
     } else{
        console.log('Invalid block.')
     }
    }

Прежде всего, необходимо проверить действительность блока, который необходимо добавить. Слава богу, он был отключен в объекте Block.

addBlock(block){ // to add a new block
     // 1. verify the validity of the block
     if (block.verify()){
        // 1-1. verify the validity of all transactions
        const txs = JSON.parse(block.data);
        let validTxs = true, tx, sender;
        for (let i = 0; i < txs.length; i++){ // for all txs in block, verify the signature
        tx = new Transaction(txs[i]['from'], txs[i]['to'], txs[i]['amount'], txs[i]['tip']);
        tx.signature = txs[i]['signature'];
        if (! tx.verify()){
           validTxs = false;
           break;
        }
     } else{
        console.log('Invalid block.')
     }
    }

Во-вторых, проверить, все ли транзакции в этом блоке действительны или нет. Это немного запутано, приведенный выше код концептуально перестраивает объекты транзакций и вызывает его функцию Transaction.verify() для проверки достоверности.

addBlock(block){ // to add a new block
     // 1. verify the validity of the block
     if (block.verify()){
        // 1-1. verify the validity of all transactions
        const txs = JSON.parse(block.data);
        let validTxs = true, tx, sender;
        for (let i = 0; i < txs.length; i++){ // for all txs in block, verify the signature
        tx = new Transaction(txs[i]['from'], txs[i]['to'], txs[i]['amount'], txs[i]['tip']);
        tx.signature = txs[i]['signature'];
        if (! tx.verify()){
           validTxs = false;
           break;
        }
        // 1-2. if all transactions are verified valid, deal with the payment
        if (! validTxs){
            console.log('Invalid transaction found.');
        } else{
            // 1-3. for each payment
            for (let i = 0; i < txs.length; i++){ // for all txs in block
            const tx = txs[i];
            // 1-3-1. pay amount to tx.to
            pay(tx.from, tx.to, tx.amount); 
            // 1-3-2. pay tip to the block verifyer
            pay(tx.from, block.verifier, tx.tip); 
            // 1-3-3. record the transaction on the blockchain
            txs[tx.hash] = tx; // record the transaction
        }
     } else{
        console.log('Invalid block.')
     }
    }

В-третьих, обработайте платеж, если все транзакции подтверждены. Платеж в биткойнах зависит от неизрасходованного объекта транзакции (UTXO). Потому что история слишком длинная, и я так устал, что мы просто забудем ее на время. Предопределенная функция платить(от, до суммы) рассматривается как интуитивно понятная реализация для работы со счетом. Для каждой транзакции:

  1. отправитель платит получателю «сумму»
  2. отправитель платит верификатору блока «чаевые»
  3. добавить транзакцию в историю
addBlock(block){ // to add a new block
     // 1. verify the validity of the block
     if (block.verify()){
        // 1-1. verify the validity of all transactions
        const txs = JSON.parse(block.data);
        let validTxs = true, tx, sender;
        for (let i = 0; i < txs.length; i++){ // for all txs in block, verify the signature
        tx = new Transaction(txs[i]['from'], txs[i]['to'], txs[i]['amount'], txs[i]['tip']);
        tx.signature = txs[i]['signature'];
        if (! tx.verify()){
           validTxs = false;
           break;
        }
        // 1-2. if all transactions are verified valid, deal with the payment
        if (! validTxs){
            console.log('Invalid transaction found.');
        } else{
            // 1-3. for each payment
            for (let i = 0; i < txs.length; i++){ // for all txs in block
            const tx = txs[i];
            // 1-3-1. pay amount to tx.to
            pay(tx.from, tx.to, tx.amount); 
            // 1-3-2. pay tip to the block verifyer
            pay(tx.from, block.verifier, tx.tip); 
            // 1-3-3. record the transaction on the blockchain
            txs[tx.hash] = tx; // record the transaction
            // 1-4. deal with the block
            // 1-4-1. the blockchain pays block award to the block verifier
            pay(admin.address, block.verifier, this.reward); 
            // the admin pays the mining reward to the block verifier
            // 1-4-2. add the block to the blockchain
            this.chain.push(block);
        }
     } else{
        console.log('Invalid block.')
     }
    }

В-четвертых, блокчейн вознаграждает усердно работающего верификатора блоков и добавляет этот блок в блокчейн.

Наконец, у нас есть последняя функция, которую нужно реализовать. Функция verifyAllBlock() относительно проста по сравнению с вышеупомянутыми другими функциями.

verifyAllBlocks() { // to verify all blocks of the chain
        
    // [ Write code here ]
    // 1. for each block
    for(let i = 1; i < this.chain.length; i++) {
        const currentBlock = this.chain[i];
        const previousBlock = this.chain[i - 1];
        // 1-1. make sure the hash value of each block is kept in the next block
        if (previousBlock.hash !== currentBlock.previousHash) {
            return false;
        }
      }
        console.log('Whole blockchain is verified correct.\n');
        return true;
    }
}

Для каждого блока убедитесь, что хэш previousBlock хранится в currentBlock.

verifyAllBlocks() { // to verify all blocks of the chain
        
    // [ Write code here ]
// 1. for each block
    for(let i = 1; i < this.chain.length; i++) {
        const currentBlock = this.chain[i];
        const previousBlock = this.chain[i - 1];
        // 1-1. make sure the hash value of each block is kept in the next block
        if (previousBlock.hash !== currentBlock.previousHash) {
            return false;
        }
        // 1-2. make sure each block (except for the first block, genesis) is valid
        if (! currentBlock.verify()) {
           return false;
        }
      }
        console.log('Whole blockchain is verified correct.\n');
        return true;
    }
}

Во-вторых, убедитесь, что все блоки действительны.

Поздравляю!! Мы закончили наш Биткойн PoC, это действительно тяжелая работа (в том числе и для написания). Надеюсь, это поможет вам на практике понять больше деталей дизайна Биткойна. Если вы очень много работаете, но программа не работает, готовая программа под названием index.js доступна в том же каталоге. Любой вопрос или что-либо, что вы нашли, что я могу неправильно понять, пожалуйста, дайте мне знать. Честно говоря, в этой истории опущены многие разработки в биткойнах, но она все равно выглядит недружелюбно длинной. Надеюсь, что в будущем некоторые специалисты доделают пропущенные детали.

Если вам нравятся истории и вы хотите читать более понятный контент, связанный с криптографией или блокчейном, пожалуйста, похлопайте мне. Я буду польщен, если будет какое-то спонсорство через Биткойн или Эфириум.

Биткойн: 36FmpfiDPVAJdxKtXM79Lj5G5awZgDNKar

Эфириум: 0x37fd7D56a7228344F1bca1132d7D1b1ED398FCaa