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)

相关文章
相关标签/搜索