Bitcoin Transaction -- Part One

Transaction是什麼?

咱們隨意看一個最簡單的Transaction,看看什麼是Transaction。在block exploper中通過簡單的查詢一個經典的txid 7957a35fe64f80d234d76d83a2a8f1a0d8149a41d81de548f0a65a8a999f6f18,能夠看到,他究竟是什麼。git

0100000001524d288f25cada331c298e21995ad070e1d1a0793e818f2f7cfb5f6122ef3e71000000008c493046022100a59e516883459706ac2e6ed6a97ef9788942d3c96a0108f2699fa48d9a5725d1022100f9bb4434943e87901c0c96b5f3af4e7ba7b83e12c69b1edbfe6965f933fcd17d014104e5a0b4de6c09bd9d3f730ce56ff42657da3a7ec4798c0ace2459fb007236bc3249f70170509ed663da0300023a5de700998bfec49d4da4c66288a58374626c8dffffffff0180969800000000001976a9147f9b1a7fb68d60c536c2fd8aeaa53a8f3cc025a888ac00000000
複製代碼

上邊是一個Transaction的hex,可是究竟是什麼意思呢?如何將其decode,能夠看的比較明白。github

{
    hash 7957a35fe64f80d234d76d83a2a8f1a0d8149a41d81de548f0a65a8a999f6f18
    inputs
    {
        input
        {
            address_hash 54a28c6ba2bebdb694fe487a87e3e8ed4eab1502
            previous_output
            {
                hash 713eef22615ffb7c2f8f813e79a0d1e170d05a99218e291c33daca258f284d52
                index 0
            }
            script "[3046022100a59e516883459706ac2e6ed6a97ef9788942d3c96a0108f2699fa48d9a5725d1022100f9bb4434943e87901c0c96b5f3af4e7ba7b83e12c69b1edbfe6965f933fcd17d01] [04e5a0b4de6c09bd9d3f730ce56ff42657da3a7ec4798c0ace2459fb007236bc3249f70170509ed663da0300023a5de700998bfec49d4da4c66288a58374626c8d]"
            sequence 4294967295
        }
    }
    lock_time 0
    outputs
    {
        output
        {
            address_hash 7f9b1a7fb68d60c536c2fd8aeaa53a8f3cc025a8
            script "dup hash160 [7f9b1a7fb68d60c536c2fd8aeaa53a8f3cc025a8] equalverify checksig"
            value 10000000
        }
    }
    version 1
}
複製代碼

一個典型的bitcoin transaction由兩部分組成,input和output,input是這筆transaction的輸入,output是輸出。首先看input,由於bitcoin是utxo model因此一個的輸出是另外一transaction的輸出。仔細看上邊的trasaction,input中有一個script,output中另一個script,那麼script是什麼呢?bash

input中的script是unlocking script, output中的script是locking script。爲何說input中的script是unlocking script呢?由於他是解鎖上一筆transaction的output。也就是說礦工在verify transaction的時候,會找到utxo(上一筆交易的output中的locking script)若是unlocking script + locking script 返回的結果是true那麼這個錢就能花了。具體來看看是怎麼回事。app

下邊是一個例子是典型的一個P2PKH Transaction的unlocking script + locking script,假設Alice在第n個transaction中,有一筆utxo,而後她在第n+1筆transaction中將其花出。那麼她的unlocking script是在第n+1筆 transaction的input中,locking script是在第n的output中。這一點必定要注意一下。ide

unlocking + locking

在圖中的PubkeyHash是Alice的公鑰Hash,Sig是Alice的簽名,Pubkey是Alice的公鑰。因此若是手動運行一下這個script,那麼獲得的結果是什麼呢?ui

執行結果

若是手動運行一下獲得的結果是True。因此那麼Alice就能夠用這筆錢了。因此如今比較明確了爲何output中的script是locking script,由於這個transaction將這個筆錢lock在一個publicKey中,只有你證實你是這個私鑰的owner你才能動這筆錢,反過來這個key的owner由於有私鑰,經過簽名能夠解鎖這筆錢。因此經過這樣的方式把錢lock了起來,Locking script由此得名。Brilliant!this

是否是有另外一問題?

上邊介紹transaction的基本的結構,那麼這裏有一個問題是,那麼簽名到底籤的是什麼,若是能夠隨意簽名,例如簽名一個hello world,miner如何驗證呢?第二若是簽名數據能夠是一個任意的數據那麼若是hacker or other任意修改locking script呢?原本是給A的,hacker or others給了B?這樣是否可能?或者如何應對這個問題?spa

第一個問題,首先簽名籤的是transaction 或者說是transaction的一部分 or hash,這樣miner就能夠知道如何驗證簽名了。.net

第二問題,這就引出了sign的type。bitcoin中的sign hash Type有這幾種。3d

SIGHASH flag Value Description 類比
ALL 0x01 Signature applies to all inputs and outputs 基本類型,from 和 to都簽名
NONE 0x02 Signature applies to all inputs, none of the outputs 空白支票,收款人隨便填
SINGLE 0x03 Signature applies to all inputs, none of the outputs inputs全籤,output只籤一個

另外還有其餘的modifier

SIGHASH flag Value Description 類比
ALL_ANYONECANPAY 0x81 Signature applies to one input and all outputs 募資transaction,outputs鎖定
NONE_ANYONECANPAY 0x82 Signature applies to one input, none of the outputs 空白支票,output隨便
SINGLE_ANYONECANPAY 0x83 Signature applies to one input and the output with the same index number inputs全籤,output只籤一個

通常說最近基本的transaction sign hash type 就是ALL,看一下具體的代碼中的實現。

// TODO: remove keyPair.network matching in 4.0.0
  if (keyPair.network && keyPair.network !== network)
    throw new TypeError('Inconsistent network');
  if (!inputs[vin]) throw new Error('No input at index: ' + vin);

  hashType = hashType || Transaction.SIGHASH_ALL;
  if (needsOutputs(hashType)) throw new Error('Transaction needs outputs');

  const input = inputs[vin];

  // if redeemScript was previously provided, enforce consistency
  if (
    input.redeemScript !== undefined &&
    redeemScript &&
    !input.redeemScript.equals(redeemScript)
  ) {
    throw new Error('Inconsistent redeemScript');
  }

複製代碼

tx_builder

const txTmp = this.clone();

    // SIGHASH_NONE: ignore all outputs? (wildcard payee)
    if ((hashType & 0x1f) === Transaction.SIGHASH_NONE) {
      txTmp.outs = [];

      // ignore sequence numbers (except at inIndex)
      txTmp.ins.forEach((input, i) => {
        if (i === inIndex) return;

        input.sequence = 0;
      });

      // SIGHASH_SINGLE: ignore all outputs, except at the same index?
    } else if ((hashType & 0x1f) === Transaction.SIGHASH_SINGLE) {
      // https://github.com/bitcoin/bitcoin/blob/master/src/test/sighash_tests.cpp#L60
      if (inIndex >= this.outs.length) return ONE;

      // truncate outputs after
      txTmp.outs.length = inIndex + 1;

      // "blank" outputs before
      for (let i = 0; i < inIndex; i++) {
        txTmp.outs[i] = BLANK_OUTPUT;
      }

      // ignore sequence numbers (except at inIndex)
      txTmp.ins.forEach((input, y) => {
        if (y === inIndex) return;

        input.sequence = 0;
      });
    }

    // SIGHASH_ANYONECANPAY: ignore inputs entirely?
    if (hashType & Transaction.SIGHASH_ANYONECANPAY) {
      txTmp.ins = [txTmp.ins[inIndex]];
      txTmp.ins[0].script = ourScript;

      // SIGHASH_ALL: only ignore input scripts, leave all the outputs
    } else {
      // "blank" others input scripts 
      txTmp.ins.forEach(input => {
        input.script = EMPTY_SCRIPT;
      });
      txTmp.ins[inIndex].script = ourScript;
    }

    // serialize and hash
    const buffer: Buffer = Buffer.allocUnsafe(txTmp.__byteLength(false) + 4);
    buffer.writeInt32LE(hashType, buffer.length - 4);
    txTmp.__toBuffer(buffer, 0, false);
複製代碼

tx_build_sign

private __byteLength(_ALLOW_WITNESS: boolean): number {
    const hasWitnesses = _ALLOW_WITNESS && this.hasWitnesses();

    return (
      (hasWitnesses ? 10 : 8) +
      varuint.encodingLength(this.ins.length) +
      varuint.encodingLength(this.outs.length) +
      this.ins.reduce((sum, input) => {
        return sum + 40 + varSliceSize(input.script);
      }, 0) +
      this.outs.reduce((sum, output) => {
        return sum + 8 + varSliceSize(output.script);
      }, 0) +
      (hasWitnesses
        ? this.ins.reduce((sum, input) => {
            return sum + vectorSize(input.witness);
          }, 0)
        : 0)
    );
  }
複製代碼

tx, outs

上述的的代碼實現中,能夠看出默認的hashType是ALL,因此上邊提的問題,就能夠經過這樣的方式解決,可是這麼看hash Type中各類形式能夠用於實現不一樣的功能。

最後看看unlocking scirpt的中籤名是什麼。

3045022100884d142d86652a3f47ba4746ec719bbfbd040a570b1deccbb6498c75c4ae24cb02204b9f039ff08df09cbe9f6addac960298cad530a863ea8f53982c09db8f6e381301

複製代碼

簽名是DER格式的。

  • 0x30—indicating the start of a DER sequence

  • 0x45—the length of the sequence (69 bytes)

  • 0x02—an integer value follows

  • 0x21—the length of the integer (33 bytes)

  • R—00884d142d86652a3f47ba4746ec719bbfbd040a570b1deccbb6498c75c4ae24cb

  • 0x02—another integer follows

  • 0x20—the length of the integer (32 bytes)

  • S—4b9f039ff08df09cbe9f6addac960298cad530a863ea8f53982c09db8f6e3813

  • A suffix (0x01) indicating the type of hash used (SIGHASH_ALL)

相關文章
相關標籤/搜索