Основная концепция Lightning Network проста. Для дальнейшего понимания представьте потоки транзакций сети Lightning как код javascript, ссылаясь на белую книгу.

Если я объясню все потоки транзакций в одном сообщении в блоге, его будет трудно читать. Итак, я делю на несколько сообщений в блоге. Цель этого сообщения в блоге - потратить отзывную доставку, что соответствует рисунку 5 официального документа.

Код на Github: lightning-network-tx-flow

Следующее сообщение: Упрощенный код Bitcoin Lightning Network, часть 2 - Средство защиты от утечки

Упрощенные структуры блоков и транзакций

Настоящий биткойн сложен, поэтому я максимально упростил его. Определенные структуры блока и транзакции следующие.

class Block {
  constructor(hashPrev, txs) {
    this.hashPrev = hashPrev;
    this.txs = txs;
  }
}
class Transaction {
  constructor(txIns, txOuts) {
    this.txIns = txIns;
    this.txOuts = txOuts;
  }
}
class TxIn {
  constructor(txPrev, index, scriptSig) {
    this.txPrev = txPrev;
    this.index = index;
    this.scriptSig = scriptSig;
  }
}
class TxOut {
  constructor(value, scriptPubKey) {
    this.value = value;
    this.scriptPubKey = scriptPubKey;
  }
}

Блок содержит только предыдущие хеши и транзакции. Точно так же транзакция содержит только входы и выходы.

6 шагов, чтобы провести отзывную доставку

Я разделяю весь процесс проведения Отзывной доставки на 6 этапов, как показано ниже. Обратите внимание, что на изображении выше написано C1a или C1b или так далее.

  1. Финансирование с несколькими подписями
  2. Сборка C1a и C1b (без знака)
  3. Сборка RD1a и RD1b
  4. Обмен подписью C1a и C1b
  5. Потратьте C1b
  6. Потратьте D1b
  7. Израсходовать RD1b

1. Финансирование с несколькими подписями

Сначала Алиса и Боб создают мультиподпись 2to2. Это не совсем точно, просто упрощенный вариант. Пожалуйста, обратитесь к моему предыдущему сообщению в блоге для дальнейшего понимания.

const redeemScript = { 
  n: 2, 
  m: 2, 
  pubKeyHashs: [ 
    getPubKeyHash(AliceKeys[1]), // Alice pubKeyHash
    getPubKeyHash(BobKeys[1]) // Bob pubKeyHash
  ]
}
const redeemScriptHash = CryptoJS.RIPEMD160(CryptoJS.SHA256(
  JSON.stringify(redeemScript)
)).toString();

Алиса финансирует биткойн на этот «redeemScriptHash» (это как адрес).

Алиса и Боб уже депонировали 0,5 биткойна соответственно в качестве генезисного блока. Итак, Алиса потратит эти 0,5 биткойна на финансирование mulisig.

// Create blockchain
let Blocks = [genesis];
const gTxH = calculateTxHash(Blocks[0].txs[0]);
// Create unsingned transaction
let fundingTxAlice = new Transaction(
  [
    new TxIn(
      gTxH,
      0, 
      { 
        type: 'SINGLE', 
        sig: getPubKeyHash(AliceKeys[0]) // Place pubKeyHash for signing instead.
      }
    )
  ],
  [
    new TxOut(
      50000000, 
      { 
        type: 'NORMAL', 
        pubKeyHash: redeemScriptHash 
      }
    )
  ]
);
// Sign transaction by Alice wallet
fundingTxAlice = signTx(fundingTxAlice, AliceKeys[0]);
// Create new Block as adding transactions
const newBlock = createNewBlock(
  [fundingTxAlice, fundingTxBob], 
  Blocks
)
// Mine block 
Blocks = mineBlock(Blocks, newBlock);

Я не могу запустить код операции Биткойна, поэтому я определяю scriptSig и scriptPubKey как формат json.

scriptSig = { 
  type: 'SINGLE', 
  sig: {
   signature: signature,
   pubKey: pubKey
  }
}
scriptPubKey = { 
  type: 'NORMAL', 
  pubKeyHash: pubKeyHash
}

Таким же образом Боб финансирует мультисиг, так что у мультисиг теперь 1 биткойн.

2. Сборка C1a и C1b (без признаков)

Используя 2 транзакции, которые ранее основали Алиса и Боб, Алиса создала C1a. Обратите внимание, что первый выход типа ведьмы C1a - «RSMS». Точное название - Отзывный договор о сроке погашения последовательности. Для этого требуется 2 подписи. После этого будет создана отзывная доставка. Другой выход - обычный.

В настоящий момент Боб не подписывает. Если Боб подпишет, Алиса может немедленно потратить эту транзакцию. В случае, если Алиса является злоумышленником и Алиса не подписывает RD1a, Боб навсегда теряет 0,5 биткойна, так что Боб не должен подписывать.

const fTxAH = calculateTxHash(fundingTxAlice);
const fTxBH = calculateTxHash(fundingTxBob);
let C1a = new Transaction(
  [
    new TxIn(
      fTxAH, 
      0, 
      { 
        type: 'MULTI',
        sig: [ 
          redeemScript.pubKeyHashs[0], 
          redeemScript.pubKeyHashs[1] 
        ],
        redeemScript
      }
    ),
    new TxIn(
      fTxBH,
      0, 
      { 
        type: 'MULTI',
        sig: [ 
          redeemScript.pubKeyHashs[0], 
          redeemScript.pubKeyHashs[1] 
        ],
        redeemScript
      }
    )
  ],
  [
    new TxOut(
      50000000, 
      { 
        type: 'RSMS',
        pubKeyHash: [ 
          getPubKeyHash(AliceKeys[2]), 
          getPubKeyHash(BobKeys[2]) 
        ]
      }
    ),
    new TxOut(
      50000000, 
      { 
        type: 'NORMAL',
        pubKeyHash: getPubKeyHash(BobKeys[2])
      }
    )
  ]
)

Json formate scriptSig и scriptPubKey следующие. Обратите внимание, что я включаю redeemScript среди scriptSig, чтобы потратить фонд с несколькими подписями.

scriptSig =  { 
  type: 'MULTI',
  sig: [ 
     {
      signature: signature,
      pubKey: pubKey1 
     },
     {
      signature: signature,
      pubKey: pubKey2
     }     
  ],
  redeemScript: redeemScript
}
scriptPubKey = { 
  type: 'RSMS',
  pubKeyHash: [ 
    pubKeyHash1,
    pubKeyHash2,
  ]
}

Таким же образом Боб строит C1b.

3. Сборка RD1a и RD1b

RD1a имеет временную блокировку. Если Алиса потратит C1a, Алиса не сможет получить 0,5 биткойна до окончания периода временной блокировки. Я указываю временную блокировку как 3, так что Алисе нужно ждать подтверждения 3 блока. С другой стороны, Боб может получить 0,5 биткойна сразу, потому что у D1a нет временной привязки.

Кстати, в реальном случае временная блокировка может быть 1000.

Обратите внимание, что предыдущая транзакция в TxIn пуста. Потому что C1a еще не потратил, поэтому хеш транзакции посчитать невозможно. Для этого команда разработчиков Lightning предлагает новый код операции SIGHASH_NOINPUT.

И обратите внимание, что RD1a подписан Бобом. Алисе необходимо передать RD1a Бобу и позволить ему подписать, чтобы RD1a потребовала подписи Боба для траты.

let RD1a = new Transaction(
  [
    new TxIn(
      '', // Keep empty because C1a have not yet spent
      0, 
      { 
        type: 'RD',
        sig: [ 
          getPubKeyHash(AliceKeys[2]), 
          getPubKeyHash(BobKeys[2]) 
        ],
        sequence: 3
      }
    ),
  ],
  [
    new TxOut(
      50000000, 
      { 
        type: 'NORMAL',
        pubKeyHash: getPubKeyHash(AliceKeys[3])
      }
    )
  ]
);
// Hand over RD1a to Bob and let him sign
RD1a = signTx(RD1a, BobKeys[2]);

Json formate RD scriptSig выглядит следующим образом. Это содержит временную блокировку.

scriptSig = { 
  type: 'RD',
  sig: [ 
    {
    signature: signature,
    pubKey: pubKey1 
   },
   {
    signature: signature,
    pubKey: pubKey2
   }
  ],
  sequence: 3
}

Таким же образом Боб строит RD1b.

4. Обмен подписью C1a и C1b

Алиса получает подпись Боба RD1a, так что пришло время обменяться подписью C1a. Алиса передает C1a Бобу и позволяет ему подписать. Теперь Алиса может потратить C1a в любое время.

C1a = signTx(C1a, BobKeys[1]);

Таким же образом Боб позволил Алисе подписать C1b.

5. Потратьте C1b

Давайте рассмотрим случай, когда Боб тратит C1b. Боб сам подписывает C1b. После проверки Боб передает C1b и добавляет в блокчейн.

// Sign by himself(Bob)
C1b = signTx(C1b, BobKeys[1]);
// Validate transaction
validateTx(C1b, Blocks);
// Mine block as adding transactions
Blocks = mineBlock(Blocks, createNewBlock([C1b], Blocks));

6. Потратьте D1b.

Алиса может потратить D1b без временной блокировки, потому что биткойны, финансируемые с помощью мультиподписи, были потрачены Бобом как транзакция C1b. Если Боб злонамерен и не сотрудничает с Алисой, Боб может потратить C1b без согласия Алисы. В этом случае Боб несет временную блокировку.

const hashC1b = calculateTxHash(C1b);
let D1b = new Transaction(
  [
    new TxIn(
      hashC1b, 
      0, 
      { 
        type: 'SINGLE',
        sig: [ getPubKeyHash(AliceKeys[2]) ],
      }
    )
  ],
  [
    new TxOut(
      50000000, 
      { 
        type: 'NORMAL',
        pubKeyHash: getPubKeyHash(AliceKeys[3])
      }
    )
  ]
)
D1b = signTx(D1b, AliceKeys[2]);
validateTx(D1b, Blocks);
Blocks = mineBlock(Blocks, createNewBlock([D1b], Blocks));

7. Израсходовать RD1b

Сначала Боб пытается сразу потратить RD1b. Но результат - неудача из-за временной блокировки.

// Set previous transaction hash to txIn
RD1b.txIns[0].txPrev = calculateTxHash(C1b);
RD1b = signTx(RD1b, BobKeys[2]);
validateTx(RD1b, Blocks); // => Fail, because of block time lock

После добавления 2 блоков Боб может успешно потратить RD1b, потому что время блокировки истекло.

// Set previous transaction hash to txIn
RD1b.txIns[0].txPrev = calculateTxHash(C1b);
RD1b = signTx(RD1b, BobKeys[2]);
// Add 2 blocks for time lock
Blocks = mineBlock(Blocks, createNewBlock([], Blocks));
Blocks = mineBlock(Blocks, createNewBlock([], Blocks));
validateTx(RD1b, Blocks); // => Success, because time lock expire
Blocks = mineBlock(Blocks, createNewBlock([RD1b], Blocks));

Получайте лучшие предложения по программному обеспечению прямо в свой почтовый ящик