比特币协议杂谈(2)

比特币协议

  1. 通用标准:
    • 1 hashes
      • 比特币中的哈希需要被被计算两次。
      • 哈希算法,大多数:SHA-256; ROPEMD-160 在需要较短哈希时被使用(例如: 当创建一个比特币地址),ROPEMD-160: 先计算 sha-256, 再对上一个哈希结果计算 ripemd-160s。
    • 2 merkle 树
      • merkle树是二进制哈希树, 在比特币中使用double sha-256哈希;
      • 如果当形成一个树的子节点,并且有奇数个元素, 最后的哈希元素被复制,确保含有偶数个元素。
      • 首先:对区块的交易字节流做哈希, 形成底层结构。
      • 笔记:

  2. 交易结构
    1. 表格

    大小 字段 描述
    4字节 版本 明确这笔交易参照的规则
    1-9字节 输入数量 被包含的输入的数量
    不定 输入 一个或多个交易输入
    1-9字节 输出数量 被包含的输出的数量
    不定 输出 一个或多个交易输出
    4字节 时钟时间 一个UNIX时间戳或区块号

    1. 序列化的交易长度
      • 版本(4字节) + 时间戳(4字节) + 交易输入Number(var_int) + 交易输出Number(var_int) + 每个交易的输入序列化字节数(var_int) + 每个交易的输出序列化字节数(var_int)
      • 每个交易的输入序列化字节数 = 引用的UTXO哈希(32字节) + 引用的输出索引序列号(4字节) + 未使用的序列号(4字节) + 脚本长度序列化Number(var_int) + 脚本长度字节数
      • 每个交易的输入序列化字节数 = 交易的比特币总量(8字节) + 脚本长度序列化Number(var_int) + 脚本长度字节数
    2. 时间戳:
      • 锁定时间(或时间戳)定义了能被加到区块链里的最早的交易时间。
      • 交易中,被设置为0, 表示立即执行;
      • 大于0 小于 5亿, 表示该交易所位于区块在链中的高度,意思为: 在这个指定的区块高度之前,该交易没有被包含在区块链中。
      • 大于 5 亿, 表示Unix纪元时间戳,表示在这个指定时间点之前, 该交易没有被包含在区块链中。 同时意思为:该交易只有到达指定时间, 才可以被新区快打包,进行交易验证,相当于交易延后执行。


  1. 交易输入的结构
    1. 表格

    尺寸 字段 说明
    32个字节 交易 指向交易包含的被花费的UTXO的哈希指针
    4个字节 输出索引 被花费的UTXO的索引号,第一个是0
    1–9个字节 (可变整数) 解锁脚本尺寸,用字节表示的后面的解锁脚本长度
    变长 解锁脚本 一个达到UTXO锁定脚本中的条件的脚本
    4个字节 序列号 目前未被使用的交易替换功能,设成0xFFFFFFFF


    1. 序列号: 8d3117d26d
      • 用来覆盖在交易锁定时间之前失效的交易,一项目前没有在比特币中用到的功能。
      • 大多数交易把这个值设置成最大的整数(0xFFFFFFFF),并且被比特币网络忽略。
      • 如果一次交易有非零的锁定时间(时间戳),那么它至少需要有一个序列号比0xFFFFFFFF低的输入来激活锁定时间。
  1. 交易输出的结构
    1. 交易输出结构

    尺寸 字段 说明
    8个字节 总量 用聪表示的比特币值(10-8比特币)
    1–9个字节 (可变整数) 锁定脚本尺寸 用字节表示的后面的锁定脚本长度
    变长 锁定脚本 一个定义了支付输出所需条件的脚本
一个完整的交易数据: 结构划分(该交易为真实的线上交易)
/*
01000000                        //交易版本号     4字节
01                              //交易输入数量     1字节
33a525c7bb912fea5e4f7633
35ac2afd236bd43c906b5559
f80c0a30c4ccb4b4                //指向的UTXO 哈希 32字节
01000000                        //UTXO 序号       4 字节
6a                              //脚本字节数     1字节
4730440220129bfbd49e9fb990cbc542c95cbfe1
f073c5c8d34aae70d2e80b1bc6c3e3aa2202204b    //包含:签名和公钥
c87a25ce402c7469a2c221cfcb26a95ea95a6142
a733e1ef9e1c242bf5c2d3012103fbf4e8da6848
51fdafdf539deffc424d995b207a8f2bc827c679
366cfcf3489f                    //脚本内容      106字节
ffffffff                        //sequence      1字节
01                              //交易输出数量        1字节
b851950000000000                //输出金额      8字节
17                              //锁定脚本长度        1字节
a91432f08210748b1b0b00e89953b7db2145c8f0
9d0f87                          //锁定脚本      23字节
00000000                        //该交易的时间戳
 */


  1. 区块的结构:
    1. 区块的结构

    大小 字段 描述
    4字节 区块大小 用字节表示的该字段之后的区块大小
    80字节 区块头 组成区块头的几个字段
    1-9 (可变整数) 交易计数器 交易的数量
    可变的 交易 记录在区块里的交易信息
  2. 区块头的结构:
  3. 大小 字段 描述
    4字节 版本 版本号,用于跟踪软件/协议的更新
    32字节 父区块哈希值 引用区块链中父区块的哈希值
    32字节 Merkle根 该区块中交易的merkle树根的哈希值
    4字节 时间戳 该区块产生的近似时间(精确到秒的Unix时间戳)
    4字节 难度目标 该区块工作量证明算法的难度目标
    4字节 Nonce 用于工作量证明算法的计数器

  4. 父区块的哈希值: 一个通过SHA256算法对父区块头进行二次哈希计算而得到的数字指纹。

  5. 请注意,区块哈希值:
    • 实际上并不包含在区块的数据结构里,不管是该区块在网络上传输时,抑或是它作为区块链的一部分被存储在某节点的永久性存储设备上时。
    • 相反,区块哈希值是当该区块从网络被接收时由每个节点计算出来的。
    • 区块的哈希值可能会作为区块元数据的一部分被存储在一个独立的数据库表中,以便于索引和更快地从磁盘检索区块。
  1. Base58Check:
    • 1 详细解释:
      • 增加了错误校验码来检查数据在转录中出现的错误。
      • 校验码长4个字节,添加到需要编码的数据之后。校验码是从需要编码的数据哈希值中得到的,所以可以用来检测并避免转录和输入中产生的错误。
        • 2 为了使用Base58Check编码格式对数据进行编码:
          • 1 首先进行前缀添加
            • 首先我们要对数据添加一个称作“版本字节”的前缀,这个前缀用来明确需要编码的数据的类型。
            • 例如:对比特币地址编码时,比特币地址的前缀是0(十六进制是0x00)
            • 而对私钥编码时前缀是128(十六进制是0x80)
            • 脚本地址的前缀是 0x05
          • 2 计算校验码
            • 采用双哈希,对之前的结果(前缀和数据)运行两次SHA256哈希算法。checksum = SHA256(SHA256(prefix+data))
            • 取上步计算哈希结果的前4个字节作为校验码。
            • 校验码会添加到数据之后。
        • 3 结果的组成:
          • 三部分组成:前缀、数据和校验码。

      • 比特币的地址生成:

    • 生成方式: 由公钥 –》 sha256(公钥) –》 RIPEMD160(上步结果) –》base58check(上步结果) === 比特币地址;
      • 示例: 1J7mdg5rbQyUHENYdx39WVWK7fsLpEoXZy; 长度为34个字节, 该为Base58check编码后的字符串。
  2. 比特币网络

    1. 节点采用TCP协议、使用8333端口.
  3. 校验交易

    1. 打包交易前,校验如何打包交易最合算。
  4. 打包交易Mempool:

    1. 打包交易前的校验算法, 主要数据来源.
  5. 比特币交易的集中方式:

    1. P2PKH: 此类交易都含有一个锁定脚本,该脚本由公钥哈希实现阻止输出功能.支付给公钥哈希的交易
      • 由P2PKH脚本锁定的输出可以通过键入公钥和由相应私钥创设的数字签名得以解锁。

    2. P2PK: 支付给公钥的交易
      • 主要目的一方面为使比特币地址更简短,另一方面也使之更方便使用。
    3. 多重签名交易:
      • 支付给多个
    4. P2SH: 复杂的锁定脚本被电子指纹所取代,电子指纹为密码学哈希。
      • 1 在现金交易中的锁定脚本是这种: 确保在赎回交易中提供的脚本的哈希,是这个创建地址的脚本的哈希。
  6. mempool中的交易:

    • 1 通过检查后的合法交易
    • 2 检查的内容: 交易对象合法, 脚本是否合法, 是否放入区块,
    • 3 交易本身的验证, 交易链的验证—》 全在已经确认的block里面,
    • 4 交易与block的交互:
      • 交易扔给一个交易哈希, block返回一个交易的内容
    • 5 检测交易的两种路径: 检查已确认的区块, 检查mempool。
    • 6 优化方案:
  7. 交易数据长度问题:

    • 1 交易数据长度分为4个等级:
      • 1 交易数据长度 < 253 byte; 使用1个字节表示交易长度。该字节直接表示接下来交易的数据长度,它后面紧跟交易数据;(即:在标识交易长度方面,使用了1个字节,该字节直接表示交易的长度)
      • 2 交易数据长度 < 65535 byte; 使用一个字节 0xfd 作为标识,接下来的两个字节为交易数据的长度,再接下来为交易数据;(即:在标识交易长度方面:使用了3个字节,1个为标识,2个为实际数据)
      • 3 交易数据长度 <= 4294967295 byte; 使用一个字节 0xfe 作为标识,接下来的四个字节为交易数据的长度,再接下来为交易数据;(即:在标识交易长度方面:使用了5个字节,1个为标识,4个为实际数据)
      • 4 交易数据长度 > 4294967295 byte; 使用使用一个字节 0xfe 作为标识,接下来的八个字节为交易数据的长度,再接下来为交易数据;(即:在标识交易长度方面:使用了9个字节,1个为标识,8个为实际数据)
  8. 创币交易(coinbase 交易):

    1. 与常规交易不同,创币交易没有输入,不消耗UTXO。它只包含一个被称作coinbase的输入,仅仅用来创建新的比特币。创币交易有一个输出,支付到这个矿工的比特币地址。
  9. 脚本数据长度:

    1. 脚本数据长度分为 5 个等级:
      • 1 dataLenth = 0, OpCode = OP_0
      • 2 dataLenth = 1, OpCode = OP_1 – OP_16, data= 1–16
        • dataLenth = 1, OpCode = OP_1NEGATE
      • 3 dataLenth <= 75, OpCode = dataLenth
      • 4 dataLenth <= 255, OpCode = OP_PUSHDATA1
      • 5 dataLenth <= 65535, OpCode = OP_PUSHDATA2
  10. 脚本操作码

    1. 表示长度的操作码
      • 1 OP_0(0x00) 一个空字符串被压入栈
      • 2 OP_1,OP_TRUE(0x51) 1 数字1被压入栈
      • 3 OP_2-OP_16(0x52-0x60) 数字(2-16)被压入栈
      • 4 OP_PUSHDATA1(0x4c==76) 接下来一个字节标识:脚本数据长度
      • 5 OP_PUSHDATA2(0x4d==77) 接下来两个字节标识:脚本数据长度
      • 6 OP_PUSHDATA4(0x4e==78) 接下来四个字节标识:脚本数据长度
      • 5 当脚本长度 < OP_PUSHDATA1, 该字节直接表示脚本数据的长度.

    2. P2SH 的脚本概括
      • 1 脚本字节总数 23 byte
      • 2 脚本的第一个字节: OP_HASH160 操作码;
      • 3 脚本的第二个字节: 0x14(20), 脚本数据的长度
      • 4 脚本的数据 随后20个字节,(内容为脚本哈希)
      • 5 最后一个字节 OP_EQUAL 操作码

    3. 脚本数据操作
      • 1 OP_EQUAL : 输入相等返回1,否则返回 0;
      • 2 OP_HASH160(169) : 输入哈希两次,第一次sha-256,第二次ripemd-160;

    4. 数字替代操作符
      • 1 0x80 : 可以用来表示脚本数据值为:负零
      • 2 0x81 : 可以用来表示脚本数据值为:-1
      • 对应的操作码 : OP_1NEGATE, -1入栈
      • 3 false : 用来表示任何 零。
      • 4 true : 用来标识任何 非零。
      • 5 正 0 : 通过0长度向量表示

    5. 保留字(使用保留字将导致交易失败)
      • 1 OP_RESERVED, 导致交易失败,除非在为执行的条件分支中。
    6. 控制流
      • 1 OP_IF (0x63)
      • 2 OP_NOTIF (0x64)
      • 3 OP_VERIF (0x65) 保留字,当出现在未执行的分支中,导致交易失败
      • 4 OP_VERNOTIF (0x66) 保留字,当出现在未执行的分支中,导致交易失败
      • 5 OP_ELSE (0x67)
      • 6 OP_ENDIF (0x68)
      • 7 OP_NOP (0x61) Do Nothing。
    7. 时间戳
      • 1 OP_CHECKLOCKTIMEVERIFY (0xb1)
        • 描述:标识交易无效:如果栈顶项大于交易时间戳字段,否则,脚本继续执行,就像执行OP_NOP一样。交易无效 : 1.栈顶为空;2.栈顶项为负;3.栈顶项大于等于 5亿,然而交易时间戳字段小于5亿,或者相反的情况,都算失败; 4.交易输入序列字段等于0xffffffff.
      • 2 OP_CHECKSEQUENCEVERIFY (0xb2)
        • 描述:标识交易无效:如果交易输入的相对锁定时间不等于或大于栈顶项的值。(交易输入nSequence字段:由BIP68强制执行)
    8. 脚本限制:
      • 1 每个脚本最大操作码个数为 : 201
      • 2 OP_16 : 什么东西?
      • 3 脚本的最大字节为 10000 字节。
  11. 签名的限制:

    • 1 长度范围:9 <= size <= 73
  12. 公钥格式:

    • 压缩公钥
      • lenth = 33, 并且首字节为 0x02 或者 0x03
    • 未压缩公钥
      • lenth = 65, 并且首字节为 0x04
    • 其他都为格式错误的公钥
  13. 闪电网络

    1. 闪电网络的关键技术有三,后后依赖于前前,依次是:RSMC(序列到期可撤销合约),HTLC(哈希时间锁定合约)和闪电网络。
    2. RSMC
      • 1 闪电网络的基础是交易双方之间的双向微支付通道,RSMC定义了该双向微支付通道的最基本工作方式。
      • 2 微支付通道中沉淀了一部分资金,通道也记录有双方对资金的分配方案。通道的设立会记录在比特币区块链上。
      • 3 为了鼓励双方尽可能久地利用通道进行交易,RSMC对主动终止通道方给予了一定的惩罚:主动提出方其资金到账将比对方晚,因此谁发起谁吃亏。
      • 4 通道余额分配方案的本质是结算准备金。在此安排下,因为要完全控制资金交收风险,每笔交易都不能突破当前结算准备金所施限制。
  14. Txmessage 验证

    • 1 自我格式校验
    • 2 链上的校验
      • 1 从mempool取 preOut Tx, 判断该交易此时的状态
        • 0 mempool 为未确认的交易
        • 1 orPhen 交易
        • 2 OK的交易(已经验证过的)
      • 2 从blockChain 取preOut Tx,(这个应该去掉)
      • 3 UTXO中
        • 0 UTXO 中都为已经确认的交易
        • 1
    • 3 Tx需要添加一个状态,是否为孤立交易。
  15. blockMessage 验证
  16. 进入mempool的交易:

    • 1 打包的交易
  17. 脚本包含的内容:

    • 1 签名脚本包含两部分: 签名和公钥
      • 公钥必须匹配上个交易输出锁定脚本中的公钥哈希, 同时这个公钥被用来验证 当前的签名。
      • 签名脚本总字节数为:106byte;
      • 其中:交易签名:72 byte; 公钥:33 byte; 再加入首字节 0x47,
  18. 特殊的交易输出,导致的特殊结果

    • 1 在锁定脚本中,标记交易为不可花费采用的是如下这种形式(即:任何人都不可以花费这笔交易,同时这笔交易也不会被计入UTXO集):
      • scriptPubkey : OP_RETURN
    • 2 任何人都可以花费的交易
  19. 签名类型:

    • 1 签名分为三类: SIGHASH_ALL, SIGHASH_NONE, SIGHASH_SINGLE。
    • 2 SIGHASH_ALL:
      • 默认类型,目前绝大部分交易采用的,即:签整单交易。
      • 组织所有输出、输入,就像上文分解Hex过程一样,每个输入都对应一个签名,暂时留空,其他包括sequence等字段均须填写,这样就形成了一个完整的交易Hex(只缺签名字段)。然后,每一个输入均需使用私钥对该段数据进行签名,签名完成后各自填入相应的位置,N个输入N个签名。简单理解就是:对于该笔单子,认可且只认可的这些输入、输出,并同意花费我的那笔输入。
    • 3 SIGHASH_SINGLE:
      • 该签名类型其次自由松散,仅对自己的输入、输出签名,并留空sequence字段。其输入的次序对应其输出的次序,比如输入是第3个,那么签名的输出也是第三个。简单理解就是:我同意花费我的那笔钱,且只能花费到我认可的输出,至于单子里的其他输入、输出,我不关心。
    • 4 SIGHASH_NONE
      • 该签名类型是最自由松散的,仅对输入签名,不对输出签名,输出可以任意指定。某人对某笔币签名后交给你,你可以在任意时刻填入任意接受地址,广播出去令其生效。简单理解就是:我同意花费我的那笔钱,至于给谁,我不关心。
  20. 交易输入签名的生成:

    • 交易签名使用命令:
      • signrawtransaction \
        [{"txid":txid,"vout":n,"scriptPubKey":hex,"redeemScript":hex},...] [,...] \
        [sighashtype="ALL"]

        • 第一个参数是创建的待签名交易的十六进制字符串;
        • 第二个参数有点类似创建交易时的参数,不过需要多出一个公钥字段scriptPubKey,其他节点验证交易时是通过公钥和签名来完成的,所以要提供公钥;如果是合成地址,则需要提供redeemScript
        • 第三个参数是即将花费的币所在地址的私钥,用来对交易进行签名,如果该地址私钥已经导入至bitcoind中,则无需显式提供;
        • 第四个参数表示签名类型,三种交易签名类型;
    • 该命令输出:
      • 输出为完整的交易16进制字符串,内容为一个交易完整的交易结构,签名内容会自动填充进 签名字段。
    • 解析输出命令:
      • bitcoind decoderawtransaction
      • : 签名命令后输出的内容。
  21. 创建待发送交易:
    • 创建待发送交易,由命令:createrawtransaction [{“txid”:txid,”vout”:n},…] {address:amount,…}来完成。
    • 创建一笔:将 0.1 BTC发送至 1Q8s4qDRbCbFypG5AFNR9tFC57PStkPX1x ,并支付 0.0001 BTC做为矿工费。输入交易的额度为 0.199 ,输出为 0.1 + 0.0001 = 0.1001 ,那么还剩余: 0.199 - 0.1001 = 0.0989 ,将此作为找零发回给自己。
    • 执行命令:
      • bitcoind createrawtransaction \
        '[{"txid":"296ea7bf981b44999d689853d17fe0ceb852a8a34e68fcd19f0a41e589132156","vout":0}]' \
        '{"1Q8s4qDRbCbFypG5AFNR9tFC57PStkPX1x":0.1, "1Lab618UuWjLmVA1Q64tHZXcLoc4397ZX3":0.0989}'
    • 改命令输出:16进制字符串。内容为不包含交易签名的整个完整交易
    • 通过命令:decoderawtransaction ,可以将此段十六进制字符串解码,输出如下结构信息。
{
    "txid" : "54f773a3fdf7cb3292fc76b46c97e536348b3a0715886dbfd2f60e115fb3a8f0",
    "version" : 1,
    "locktime" : 0,
    "vin" : [
        {
            "txid" : "296ea7bf981b44999d689853d17fe0ceb852a8a34e68fcd19f0a41e589132156",
            "vout" : 0,
            "scriptSig" : {
                "asm" : "",
                "hex" : ""
            },
            "sequence" : 4294967295
        }
    ],
    "vout" : [
        {
            "value" : 0.10000000,
            "n" : 0,
            "scriptPubKey" : {
                "asm" : "OP_DUP OP_HASH160 fdc7990956642433ea75cabdcc0a9447c5d2b4ee OP_EQUALVERIFY OP_CHECKSIG",
                "hex" : "76a914fdc7990956642433ea75cabdcc0a9447c5d2b4ee88ac",
                "reqSigs" : 1,
                "type" : "pubkeyhash",
                "addresses" : [
                    "1Q8s4qDRbCbFypG5AFNR9tFC57PStkPX1x"
                ]
            }
        },
        {
            "value" : 0.09890000,
            "n" : 1,
            "scriptPubKey" : {
                "asm" : "OP_DUP OP_HASH160 d6c492056f3f99692b56967a42b8ad44ce76b67a OP_EQUALVERIFY OP_CHECKSIG",
                "hex" : "76a914d6c492056f3f99692b56967a42b8ad44ce76b67a88ac",
                "reqSigs" : 1,
                "type" : "pubkeyhash",
                "addresses" : [
                    "1Lab618UuWjLmVA1Q64tHZXcLoc4397ZX3"
                ]
            }
        }
    ]
}


case : 
hashType & 0x1f:
case : 0x10 == 2 (SIGHASH_NONE)
case : 0x11 == 3 (SIGHASH_SINGLE)
case : 0x01 == 1 (SIGHASH_ALL)


//一个完整的交易
type Tx struct {
    Hash     utils.Hash     //本交易的哈希
    LockTime uint32         //交易时间戳
    Version  int32          //版本号
    Ins      []*TxIn        //交易输入
    Outs     []*TxOut       //交易输出
}

//交易输入
type TxIn struct {
    PreviousOutPoint *OutPoint  //引用的UTXO
    ScriptSig        []byte     //签名脚本
    Sequence         uint32     //todo ?
}
//指向输入中引用的UTXO
type OutPoint struct {
    Hash  *utils.Hash   //该 UTXO 交易的哈希
    Index uint32        //该 UTXO的索引号
}

//交易输出
type TxOut struct {
    Value     int64     //交易币值
    OutScript []byte    //锁定脚本
}




// digest represents the partial evaluation of a checksum.
// 摘要表示对校验和的部分求值
type digest struct {
    h     [8]uint32
    x     [chunk]byte
    nx    int
    len   uint64
    is224 bool // mark if this digest is SHA-224
}

//解析后的脚本结构
type ParsedOpCode struct {
    opValue byte //操作码
    length int    //四种情况, 1:data为nil; -1:数据长度用一个字节标识;-2:数据长度用两个字节标识;-4:数据长度用四个字节标识
    data   []byte //脚本数据
}
3Bzt3JA9eebS2FcgGpSSQDETmewhJUWjFs
//

type ScriptFreeList chan []byte
var scriptPool ScriptFreeList = make(chan []byte, FreeListMaxItems)

你可能感兴趣的:(金融技术)