[Web3] 基础第7篇: 说透比特币的基本框架与原理

一、内容提要

  • 比特币的基本介绍
  • Bitcoin 的数据结构
  • Bitcoin 的共识算法
  • Bitcoin 挖矿相关的概念
  • UTXO 模型
  • 比特的地址,版本迭代过程
  • 比特币的脚本编程
  • Taproot 的升级
  • 比特币生态项目

二、Bitcoin 的基本介绍

08 年的金融危机,中心金融,美元崩塌会导致全球经济崩溃,全球的国家为美国的经济买单;比特币全球流通性,资产去中心化发行,强调去中心化性和全球性,不会因为局部经济崩塌而崩溃,比特币崩溃全球经济才会崩溃;数字来取代传统金融,提高资产主权和全球性经济问题。

比特币是第一个区块项目,也是目前为止去中心化程度最高,市值,受欢迎程度最高公链和数字货币,甚至已经成为全球各国央行的储备货币。

Bitcoin 具备银行功能:产币的方式传统银行是中心化的,比特币产币是去中心化的,人人都可以参与的。

  • 发币发行是由国家中央银行发布,Bitcoin 通过 POW 挖矿产生
  • Bitcoin 还可以通过侧链,BRC20/铭文/符文来发行数字资产

Bitcoin 具备法币的属性

  • 法币具备支付功能,比特币也具备支付功能
  • 法币具备投资功能,比特币也同样具备投资功能

Bitcoin 还有自己的特性:

  • 去中心化属性,银行发行代币,去中心,只有银行自己可以发行;比特币是去中心化,任何参与比特挖矿人都可以获得比特币
  • 不可篡改
  • 法币可以无限发行,比特币是不可以无限发行,它是由上限,上限 2100 万枚,它的稀缺性比较强
  • 可追溯性,任何都可以追溯比特代币发行,交易等,但是银行不具备可追溯性

Bitcoin 比起中心化的金融机构,比特币更强调

  • 价值主权/资产主权: 资产控制在掌握私钥的人手里面
  • 数据主权:比特币具备隐私性,没有和 KYC,钱包地址并没有和人进行绑定,资产无法对应实体,更强调隐私属性,UTXO 和 schnnor 使得比特币的隐私性更强
  • 全球流通性:比特币强调全球流通性; 美元,人民币,泰铢更多的是本地流通性
  • 金融效率问题:拿着在美国可以花费,在中国可以花费,拿着泰铢只能泰国消费,来中国肯定需要换成人民币。

三、比特币的发展过程

比特币自 2009 自发布以来,经历了多次重要升级。这些升级旨在提高比特币网络的安全性、效率和功能性。

并且比特币的 Taproot 升级给比特币带来更多的可能性,Taproot 升级带动了 BRC20 和 Bitcoin-Layer2 的发展,这里我们不再做过多的介绍,在未来的 Layer2. 的课程中我们会深入讲解这部分的内容

P2SH(Pay-to-Script-Hash)

💡💡时间:2012年

💡💡BIP(Bitcoin Improvement Proposal):BIP-0016

💡💡内容:允许更复杂的交易脚本,支持多重签名地址。这种升级使得比特币交易更加灵活和安全

比特币改进提案 BIP 66

💡💡时间:2015年

💡💡BIP:BIP-0066

💡💡内容:规范了交易中 DER 格式的签名,解决了交易签名的一致性问题,增强了网络的安全性。

CheckSequenceVerify(CSV)

💡💡时间:2016年

💡💡BIP:BIP-0112

💡💡内容:增加了相对时间锁功能,使得交易可以在指定的时间或块高度之后才生效,这为更复杂的支付通道铺平了道路。

Segregated Witness(SegWit)

💡💡时间:2017年

💡💡BIP:BIP-0141, BIP-0143, BIP-0144

💡💡内容:分离交易签名数据,提高了区块的有效容量,减小了交易体积,降低了交易费用。还解决了交易的可塑性问题,使得闪电网络等侧链解决方案成为可能。

隔离见证2x(SegWit2x)

💡💡时间:2017年

💡💡内容:这次升级是对 SegWit 的后续提案,旨在进一步增加区块大小。然而,由于社区共识未达成,这次升级最终未能实现。

Taproot

💡💡时间:2021年

💡💡BIP:BIP-0340, BIP-0341, BIP-0342

💡💡内容:引入了 Schnorr 签名和默克尔化抽象语法树(MAST),增强了隐私性和执行脚本验证的功能,进一步提高了交易的灵活性和效率。这是自2017年 SegWit 以来最重要的一次升级。

💡💡💡💡MAST(Merkelized Abstract Syntax Trees)

💡💡💡💡💡💡时间:2021年

💡💡💡💡💡💡BIP:作为 Taproot 的一部分

💡💡💡💡💡💡内容:允许将多种条件的智能合约合并为一个,使得只有满足条件的部分才被公开,提高了隐私性和效率。

💡💡💡💡Schnorr Signatures

💡💡💡💡💡💡时间:2021年

💡💡💡💡💡💡BIP:作为 Taproot 的一部分

💡💡💡💡💡💡内容:提供了一种更高效和安全的签名算法,允许多重签名聚合,提高了交易的隐私性和可扩展性

四、Bitcoin 的数据结构

Bitcoin 的基本的区块结构

  • Genesis Block: 创始块,链启动的时候生成的第一个块

区块头是挖矿哈希运算的核心输入,六个字段各自承担不同职责:

字段 含义 作用
Version 区块版本号 标识区块遵循的共识规则
前一区块哈希 上一个区块头的哈希 把区块串成链,保证篡改可被发现
Merkle Root 块内全部交易哈希树的根 把整块交易压缩成一个指纹
Timestamp 区块产生时间 参与难度调整与时间校验
Bits 编码后的难度目标 决定哈希必须小于的阈值
Nonce 随机数 矿工穷举搜索的唯一变量

矿工穷举的正是 nonce:其余五个字段在一轮计算里基本固定,只有不断改变 nonce 重新哈希,才能试探出小于 target 的哈希值。

区块的产生流程

比特币解决区块分叉的问题

  • 比特币遵循最长链原则,短的链会被丢弃,分叉的区块是没有奖励的

确认位

五、通过浏览器和 RPC 看区块头和区块体

https://mempool.space/zh/block/00000000000000000001ed96a59373d7453298c1a6c59c3816faf0bc5df94958

浏览器里面看区块头的信息

  • Mempool
  • Oklink

浏览器里面看区块体的信息

  • mempool
  • Oklink

通过 RPC 看区块头和区块体

  • 区块头

    "hash": "000000000000000000011bc12ca8f5fad9fb044274aa5045b85fe0e93770a404",
    "confirmations": 11823,
    "height": 939078,
    "version": 1042489344,
    "versionHex": "3e232000",
    "merkleroot": "66e956fd01b5e97597c63da50624e0512ecb509f89039d729de18816ad1157aa",
    "time": 1772506978,
    "mediantime": 1772504253,
    "nonce": 3330408263,
    "bits": "1701f303",
    "target": "00000000000000000001f3030000000000000000000000000000000000000000",
    "difficulty": 144398401518100.9,
    "chainwork": "0000000000000000000000000000000000000001140061af3c0c85418ed3ffae",
    "nTx": 1426,
    "previousblockhash": "000000000000000000017d851df85772c2f02e7a189126261957e6e3aefcaf27",
    "nextblockhash": "000000000000000000009cc7b20faa6eb0b4c32cadf697da1ddfd4dfb885077b",
    "strippedsize": 548394,
    "size": 1047346,
    "weight": 2692528,

  • 区块体

    Plain Text
    "scriptPubKey": {
    "asm": "OP_RETURN aa21a9ed7b1ced2e398df9783b9286501b40e71ed4b55aca1472b695d7a69658845d2237",
    "desc": "raw(6a24aa21a9ed7b1ced2e398df9783b9286501b40e71ed4b55aca1472b695d7a69658845d2237)#0ln6sxca",
    "hex": "6a24aa21a9ed7b1ced2e398df9783b9286501b40e71ed4b55aca1472b695d7a69658845d2237",
    "type": "nulldata"
    }
    },
    {
    "value": 0.00000000,
    "n": 3,
    "scriptPubKey": {
    "asm": "OP_RETURN 434f524501a21cbd3caa4fe89bccd1d716c92ce4533e4d4733e7ec323813c943336c579e238228a8ebd096a7e5",
    "desc": "raw(6a2d434f524501a21cbd3caa4fe89bccd1d716c92ce4533e4d4733e7ec323813c943336c579e238228a8ebd096a7e5)#6af9gnu2",
    "hex": "6a2d434f524501a21cbd3caa4fe89bccd1d716c92ce4533e4d4733e7ec323813c943336c579e238228a8ebd096a7e5",
    "type": "nulldata"
    }
    },
    {
    "value": 0.00000000,
    "n": 4,
    "scriptPubKey": {
    "asm": "OP_RETURN 455853415401051b0f0e0e0b1f120013",
    "desc": "raw(6a10455853415401051b0f0e0e0b1f120013)#fgh8keqq",
    "hex": "6a10455853415401051b0f0e0e0b1f120013",
    "type": "nulldata"
    }
    },
    {
    "value": 0.00000000,
    "n": 5,
    "scriptPubKey": {
    "asm": "OP_RETURN 486174681efcb35df324954d34d19860eacbc8608e55c6530e1e6e91b56d7c40c4d867be",
    "desc": "raw(6a24486174681efcb35df324954d34d19860eacbc8608e55c6530e1e6e91b56d7c40c4d867be)#q5g8wx6r",
    "hex": "6a24486174681efcb35df324954d34d19860eacbc8608e55c6530e1e6e91b56d7c40c4d867be",
    "type": "nulldata"
    }
    },
    {
    "value": 0.00000000,
    "n": 6,
    "scriptPubKey": {
    "asm": "OP_RETURN 52534b424c4f434b3a83515c7035df25b0c42700defb11407e64dbf7bc8bd56a3bfcc1e1150082fec2",
    "desc": "raw(6a4c2952534b424c4f434b3a83515c7035df25b0c42700defb11407e64dbf7bc8bd56a3bfcc1e1150082fec2)#p9hvjzep",
    "hex": "6a4c2952534b424c4f434b3a83515c7035df25b0c42700defb11407e64dbf7bc8bd56a3bfcc1e1150082fec2",
    "type": "nulldata"
    }
    },
    {
    "value": 0.00000000,
    "n": 7,
    "scriptPubKey": {
    "asm": "OP_RETURN 737973927414d31d73015d81aa6fd8aee44db56de5f715c2ba7baf1ffb6b335cad684c06882100",
    "desc": "raw(6a27737973927414d31d73015d81aa6fd8aee44db56de5f715c2ba7baf1ffb6b335cad684c06882100)#nyqplxsk",
    "hex": "6a27737973927414d31d73015d81aa6fd8aee44db56de5f715c2ba7baf1ffb6b335cad684c06882100",
    "type": "nulldata"
    }
    }
    ],
    "hex": "010000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff640346540e2cfabe6d6df494961afa1e0f2d892c3f8910dd97cb6c9d021ac37880fe792e911f46eb7f1e10000000f09f909f092f4632506f6f6c2f62000000000000000000000000000000000000000000000000000000000000000000000005001a000000000000000822020000000000001976a914c6740a12d0a7d556f89782bf5faf0e12cf25a63988acfbe3bd12000000001976a91469f2a01f4ff9e6ac24df9062e9828753474b348088ac0000000000000000266a24aa21a9ed7b1ced2e398df9783b9286501b40e71ed4b55aca1472b695d7a69658845d223700000000000000002f6a2d434f524501a21cbd3caa4fe89bccd1d716c92ce4533e4d4733e7ec323813c943336c579e238228a8ebd096a7e50000000000000000126a10455853415401051b0f0e0e0b1f1200130000000000000000266a24486174681efcb35df324954d34d19860eacbc8608e55c6530e1e6e91b56d7c40c4d867be00000000000000002c6a4c2952534b424c4f434b3a83515c7035df25b0c42700defb11407e64dbf7bc8bd56a3bfcc1e1150082fec20000000000000000296a27737973927414d31d73015d81aa6fd8aee44db56de5f715c2ba7baf1ffb6b335cad684c06882100012000000000000000000000000000000000000000000000000000000000000000005c7d5240"

六、Bitcoin 的共识算法

所有参与网络共识节点找一个 nonce 值,使得 Hash(nonce + Block_Header) < target,谁率先找到满足条件的 nonce, 谁具备出块的权利。

  • 其中:
  • Block_Header 包含:
    • 前一个区块的哈希
    • Merkle Root(该区块中所有交易的哈希树根)
    • 时间戳(timestamp)
    • 难度目标(bits)
    • 随机数(nonce)
  • target 代表挖矿难度目标(越小代表越难)

交易到落块的生命周期

  • 用户将交易发送到比特币网络,此时交易在节点的 mempool 里面
  • 节点将 mempool 里面交易打包成区块
  • 节点去找到满足条件的 nonce,找到相应 nonce 之后,立即将区块里面 nonce, bits 等打包到区块里面
  • 打包区块的节点会获得奖励
    • 挖矿奖励,每个区块里面的第一笔交易就是挖矿奖励的交易(coinbase 交易)
    • 区块里面交易相关的手续费奖励
  • 将区块广播给其他节点
  • 其他节点收到区块直接进行验证,插入到本地链中
  • 挖矿难度问题
    • 比特币并没有真正的"定时器"来控制出块时间,而是通过难度调整机制实现的动态控制。
    • 每挖出 2016 个区块(大约 2 周时间)后,全网自动调整挖矿难度。
  • 计算公式如下:

n e w _ d i f f i c u l t y = o l d _ d i f f i c u l t y ∗ ( t a r g e t _ t i m e / a c t u a l _ t i m e ) new\_difficulty = old\_difficulty * (target\_time / actual\_time) new_difficulty=old_difficulty∗(target_time/actual_time)

其中:

  • actual_time = 实际挖出 2016 个区块所用的总时间;
  • target_time = 理论时间 = 2016 × 10 分钟 = 20160 分钟(= 2 周)。

把上面的步骤串成一条线:交易先被构造并用私钥签名,广播进入各节点的 mempool;矿工从 mempool 挑选交易打包成候选块,通过 PoW 竞争出块权;出块后广播给全网,其他节点验证通过后插入本地链,交易由此获得第一个确认。"交易生命周期"和"共识算法"其实是同一过程的两个视角:前者看交易走到哪一步,后者看谁有权把它写进账本。

比特币减半问题

Bitcoin 的总量 2100 万枚,每隔 21 万个块会发生一次减半

  • 50
  • 50 / 2
  • 50 / 4
  • 50 / 8
  • 50 / n

减半的公式:50 (1/2 + 1/4 + 1/8 + ... + 1/n) = 2100万

把减半放到时间轴上看更直观(块高按每 21 万块一个纪元计算):

纪元 大约年份 块高区间 单块奖励 (BTC)
纪元 1 2009 0 -- 209999 50
纪元 2 2012 210000 -- 419999 25
纪元 3 2016 420000 -- 629999 12.5
纪元 4 2020 630000 -- 839999 6.25
纪元 5 2024 840000 -- 1049999 3.125

减半本质是一份写死的货币政策:发行节奏不依赖任何人的意志,而是由块高固定下来,这正是"比特币稀缺性由代码保证"这一叙事的来源。

七、Bitcoin 挖矿相关的概念

  • 节点
    • 全节点:拥有全量的数据的节点
    • 轻节点:区块头的数据
  • 矿工:去找出 nonce 值得出 hash(blockHeader) < target
  • 矿池:所有的矿工节点组成的一个集合
  • 矿机:专用于 Bitcoin 的挖矿机器, 专用 CPU 或者 GPU

八、UTXO 模型

当矿工/或者普通持有比特币,他们需要去转帐,把钱转给别人,接收别人转钱给他,在传统银行里面,也有转账的整个操作。

  • 传统银行
    • Source: 出资账户
    • Dest: 接收资金账户
    • Amount: 转账的金额
    • Fee: 转账银行要收取手续费, 也有可能 0 收费转账
  • 在区块世界里面,有两种模式,一种和传统银行是类似(账户模型),另一个是和传统银行模式完全不一致(UTXO)

我们比特币使用模式和传统银行完全不一样,使用 UTXO;

UTXO(未花费的输入输出): A 向用户 B 转账

  • A 以前收到资金可能有 n 笔, n 笔是 n input,还没有花费,输入是用户收到的资金,提现是 n input
  • A 向 B 转 1 比特币
    • A 收到 n 笔交易如下,假设 n = 3,假设下面的 input 都消费,那么 A 的地址余额是 1.4 BTC
      • Input-1 0.1 个 BTC
      • Input-2 0.9 个 BTC
      • Input-3 0.5 个 BTC
    • 转账过程, 假设这笔转账的手续费是 0.1 BTC
      • 对于 A 地址
        • Output-1 0.1 个 BTC
        • Output-2 0.9 个 BTC
        • Output-3 0.5 个 BTC
      • 对于 B 地址
        • Input 1 BTC
      • A 其实转完帐加上消耗的手续费之后还剩 0.3 BTC,这个需要找零,找零并找零回到 A 地址
        • Input 0.3 BTC
      • 交易完成
        • 对与 A 地址 input-1 0.3 个BTC
        • 对于 B 地址 input-1 1 个 BTC
        • 打包交易矿工将获取 0.1 BTC, 这个 0.1 BTC 做为交易手续费
  • UTXO 过程图解

转账的过程

  • Input 是收入,没有花费掉的
  • Output 输出,是已经花费掉了
  • 在比特币里面可以多对一,一对多,多对多的转账模式
  • 当一个地址上有多个 Input 的时候,可以选择消费其中的任意一个或者任意几个,或者全部都可以
  • 对于一个地址来说,所有的 input 的金额加起来减去所有的 output 加起来的值就是该地址的余额

UTXO 与账户模型的差别可以沿几个维度对照:

维度 UTXO 模型(比特币) 账户模型(以太坊等)
余额表达 全部未花费输出之和 账户状态里的一个余额字段
交易本质 消耗旧 UTXO、产生新 UTXO 直接修改两个账户的余额
找零处理 需要显式的找零输出 无找零概念,直接扣减
并行验证 各 UTXO 独立,天然可并行 同账户交易需串行处理
隐私性 找零可换新地址 地址长期复用,易被关联

理解"余额不是存出来的、而是算出来的"是理解 UTXO 的钥匙:钱包显示的余额,其实是把该地址能解锁的所有 UTXO 加总后的结果。

实际交易截图,区块截图,链接:https://www.oklink.com/zh-hans/bitcoin/block/916867

实际交易截图,一笔交易的截图,交易链接:https://www.oklink.com/zh-hans/bitcoin/tx/9565dc835d3993c2919263184f083a8e598f9b7c8ca047806af8eef507eab647

九、比特币地址格式

Bitcoin 钱包地址有几种不同的类型,每种类型都有其特定的用途和特点。主要的几种类型包括:

  • P2PKH(Pay-to-PubKeyHash)地址:
    • 格式:以 1 开头,例如,1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa。
    • 特点:这是最传统和最常见的地址类型,广泛用于比特币的早期交易。
    • 优点:兼容性好,几乎所有钱包和交易所都支持。
    • 缺点:随着时间的推移,这种地址类型的使用效率较低,交易费用可能会较高。
  • P2SH(Pay-to-Script-Hash)地址:
    • 格式:以 3 开头,例如,3J98t1WpEZ73CNmQviecrnyiWrnqRhWNLy。
    • 特点:这种地址允许更复杂的交易脚本,例如多重签名地址。
    • 优点:支持更复杂的交易和脚本,安全性更高。
    • 缺点:创建和管理比 P2PKH 地址更复杂。
  • Bech32(SegWit)地址:
    • 格式:以 bc1 开头,例如,bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwfvenl。
    • 特点:这是比特币改进提案BIP-0173中引入的新地址格式,旨在提高交易效率和减少费用。
    • 优点:交易费用更低,处理速度更快,且有助于减少交易体积。
    • 缺点:并非所有的钱包和交易所都支持这种地址类型,尽管支持率在逐步增加。
  • Bech32m
    • 格式:P2TR(Pay-to-Taproot)和使用 Bech32m 编码。P2TR 地址以"bc1p"开头
    • 特点
      • Bech32m 是基于比特币改进提案(BIP-350)设计的,主要用于生成更具鲁棒性的地址。它将地址表示为一串以字母和数字组成的字符串(不区分大小写),确保不会出现混淆字符。
      • 与 Bech32 的区别在于校验和的算法不同,Bech32m 使用了新的校验和常数 0x2bc830a3,这一改动提高了校验的准确性,并且能够避免常见的人为错误。
      • Bech32m 主要用于 Taproot 升级,为比特币未来的扩展性而设计。
    • 优点:
      • Bech32m 格式非常注重防止用户在手动输入地址时的复制和输入错误。通过精确的校验和设计,能够高效地检测和纠正错误输入
      • Bech32m 编码不区分大小写,避免了用户在输入地址时因为大小写混淆而犯错误,提升了使用体验。
      • Bech32m 使用了一种简洁、易于理解的格式,长度相对固定,使用的字符集限制在 32 个字符,避免了难以区分的字符(如 1 和 l、0 和 O),使得地址较为直观。
      • Bech32m 可以检测单个字符的错误和交换错误,进一步减少手动输入地址的风险。
    • 缺点:平台兼容性问题

每种地址类型都有其特定的应用场景和优缺点,用户可以根据自己的需求选择合适的地址类型来存储和交易比特币

把上面四种地址类型压缩成一张速查表:

地址类型 前缀 编码 对应脚本 典型用途
P2PKH 1 Base58Check Pay-to-PubKeyHash 早期普通转账
P2SH 3 Base58Check Pay-to-Script-Hash 多签、复杂脚本
Bech32 (SegWit) bc1q Bech32 P2WPKH / P2WSH 低手续费普通转账
Bech32m (Taproot) bc1p Bech32m P2TR Taproot、聚合签名、MAST

选地址时记住一句话即可:普通转账优先 bc1q 省手续费;多签或 Taproot 高级用法分别看 3 与 bc1p 开头;1 开头地址主要留给兼容老系统。

十、比特币的脚本编程

Script(大多数时候都被直译为 "脚本")是比特币所用的一种微型编程语言,用于为比特币交易的输出提供一种锁定机制。

  • 每个输出都带有一个锁定脚本。
  • 为了解锁一个输出(即将它用作一笔交易的输入时),必须提供一个解锁脚本。

如果完整的脚本(解锁脚本 + 锁定脚本)是有效的,相应的输出就会 "被解锁",可以花费了。

什么是脚本语言

Script 是一种非常基础的编程语言。它由两种类型的对象组成:

  • 数据(Data) ------ 例如:公钥和签名;
  • 操作码(OPCODE) ------ 操作 数据的简单函数。

这里是比特币的一个标准的 P2PKH(支付到公钥哈希值)脚本的简单图示:

操作码清单:https://en.bitcoin.it/wiki/Script#Opcodes

如何运行脚本语言

整个脚本从左到右运行。在运行时,它使用一种叫做 "堆栈" 的数据结构。

  • 数据总会被推入栈中
  • OPCODE 可以把栈中的元素 弹出 并做一些操作;操作完后,有时候会把新的元素 "推入" 栈中。
  • DUP 操作码会复制栈顶的元素

什么样的脚本是有效的?

栈顶的元素是 1 (或者更大的数值)且栈中只余一个元素则表明这是一个有效的脚本。

出现下面四种情况则说明一段脚本是无效的:

•

最终的堆栈中空无一物。

•

栈顶的元素是 0 。

•

在执行结束后,栈中剩余超过 1 个元素 (1)。

•

脚本过早退出(例如纯数据脚本中的 OP_RETURN )。

出现下面四种情况则说明一段脚本是无效的:

  • 最终的堆栈中空无一物。
  • 栈顶的元素是 0 。
  • 在执行结束后,栈中剩余超过 1 个元素 (1)。
  • 脚本过早退出(例如纯数据脚本中的 OP_RETURN )。

在哪里可以找到脚本?

交易的每一个输出中都放置了一个锁定脚本:

而交易的每一个输入都必须提供一个解锁脚本:

每个节点都会把解锁脚本和锁定脚本前后拼接起来并运行,以保证自己验证了脚本。

解锁脚本先行!

虽然 解锁脚本 是在 锁定脚本 之后 提供的,在运行两者时我们会先运行解锁脚本。

为什么我们要使用 Script 语言?

  • 问:为什么不直接使用公钥和签名的组合,把所有这些 操作码 和堆栈抛在一边呢?
  • 答:因为你可以使用 操作码 的不同组合创建不同类型的锁。

数学谜题

为了花费这个输出,你需要提供和为 8 的两个数字。

  • 可能是世界上最不安全的锁定脚本 -

哈希谜题

在这里,你只需要提供一个可以得出(跟锁定脚本中)相同的哈希值的数据,就可以花费这个输出。

哈希碰撞谜题

这是一种很酷的锁。你需要提供两段可以得出相同哈希值的数据,才能解锁它。换句话说,这是一种激励人们去发现 "哈希碰撞" 的锁。

-- 使用这个脚本锁定的比特币可以在这里找到:35Snmmy3uhaer2gTboc81ayCip4m9DT4ko。但是,脚本本是使用 P2SH 锁定脚本封装过的,所以你看不出原本的锁定脚本(有人来解锁时才会曝光)。--

这些锁定脚本是不标准的。虽然这些脚本是有效的(可以被挖出),但标准的 Bitcoin Core 节点不会转发这些脚本的交易,所以一般而言它们很难被挖出。

标准的脚本

虽然我们可以使用 操作码 的不同组合产生多样的锁定脚本,但大部分节点只会转发少数 "标准脚本" 的交易:

为什么节点不转发非标准的脚本呢?

我知道,这令人遗憾。

但是,不是所有的 操作码 的组合都经过测试。所以如果节点会转发非标准的脚本的交易,可能产生一种攻击:有些人会通过发送需要花费很长时间来验证的脚本来轰炸网络 。这会 "塞爆" 节点、让网络停止运行。

另一方面,标准的脚本经过了充分的测试、可被快速验证。

所以,不转发非标准的交易只是一种安全措施。

非标准的脚本是有效的,只是不会被积极转发。

虽然非标准的交易不会在节点间转发,但依然可以得到区块的确认。不转发非标准的交易是因为节点可能在很短时间内收获大量的交易,但一个区块只能包含有限数量的交易。

为什么节点不转发非标准的脚本呢?

我知道,这令人遗憾。

但是,不是所有的 操作码 的组合都经过测试。所以如果节点会转发非标准的脚本的交易,可能产生一种攻击:有些人会通过发送需要花费很长时间来验证的脚本来轰炸网络 。这会 "塞爆" 节点、让网络停止运行。

另一方面,标准的脚本经过了充分的测试、可被快速验证。

所以,不转发非标准的交易只是一种安全措施。

非标准的脚本是有效的,只是不会被积极转发。

虽然非标准的交易不会在节点间转发,但依然可以得到区块的确认。不转发非标准的交易是因为节点可能在很短时间内收获大量的交易,但一个区块只能包含有限数量的交易。

所以,如果你想发起一笔使用非标准脚本的交易,你要么需要把交易直接发给矿工,要么你就需要自己挖矿了。

各种脚本

P2SH(Pay-to-ScriptHash)

比特币地址是一个包含字母和数字的字符串。用户可以将其分享给其他人,以便从后者那里接收 BTC。比特币交易主要有两大标准:Pay-to-PubKeyHash (P2PKH) 和 Pay-to-ScriptHash (P2SH)。

在讨论 P2SH (Pay To Script Hash) 和 P2PKH (Pay-To-PubKey Hash) 这两个概念之前,我们首先熟悉一下关于比特币的背景知识:

  • 在比特币网络中,比特币的形式是 UTXO。UTXO 是 Unspent Transaction (TX) Output(未花费交易输出)的缩写,即,比特币交易执行后形成的面额不定的单元。例如,你的比特币钱包里有 10 BTC,你想要转 5 BTC 给朋友。比特币区块链的处理方式与众不同。它会把 10 BTC(全部余额)都花掉,将 5 BTC 转入你朋友的钱包,剩下 5 BTC(10 BTC - 5 BTC = 5 BTC)转入你自己的钱包。这下,你和你的朋友各持有未花费的 5 BTC。
  • 比特币使用脚本(几行代码)来规定花费 BTC/UTXO 的条件。脚本被用作一种锁定机制。
  • BTC 锁定在脚本中。当脚本返回成功(即条件得到满足)时,BTC 就会解锁。
  • 任何人都可以向任意比特币地址发送 BTC。只有当脚本中定义的某些条件得到满足时,锁定的 BTC 才可以被花费出去。脚本决定了接收方可以如何花费收到的 BTC。发起交易时,发送方会在交易中放入一个叫作 "PubKey Script(又称锁定脚本)"的脚本。接收方(在日后花费时)需要生成一个 "签名脚本(又称解锁脚本)" ,是满足 PubKey 脚本的数据参数的集合。签名脚本在代码中又被叫作 "scriptSig"。
  • 在上述例子中,你在向朋友发送 5 BTC 时,锁定脚本也会包含在这个交易内。如果你的朋友想要花费这些 BTC,必须生成满足锁定脚本中规定条件的解锁脚本。
Pay-to-PubKeyHash (P2PKH)

Pay-to-PubKeyHash 是一种传统的比特币地址格式。其地址以数字 1 开头。

只有 P2PKH 地址的所有者才能通过提供公钥哈希值和私钥签名来解锁 PubKey 脚本并花费收到的 BTC。私钥是用来证明公钥哈希值的所有权的。

正如我们上文讨论过的那样,脚本定义了特定地址上的 BTC 在什么条件下可以花费。当规定条件得到满足且通过网络验证时,该地址上的 BTC 就会被解锁以供花费。

这一流程是如何运作的?------ 接收方首先生成 PubKey 脚本并将其分享给发送方。发送方在发送 BTC 时将该 PubKey 脚本 添加到交易中。收到 BTC 时,如果接收方想要解锁这些 BTC UTXO,就要提供公钥哈希和私钥签名,并满足 PubKey 脚本中提到的条件。

例如,这些条件可以是:

  • 解锁 BTC 至少需要两个签名。
  • 提供口令(password)才能解锁。
  • BTC 需要等待一段时间才能解锁。

上述这类情况可以作为解锁 BTC 的条件。

发送比特币时,发送方需要在交易中包含 PubKey 脚本。因此,这会增加交易的体积,产生的交易费比普通交易高出 5 倍左右。

这里,发送方必须承担额外的成本。Pay-to-ScriptHash 可以帮助发送方免去这一额外成本。

Pay-to-ScriptHash (P2SH)

Pay To Script Hash (P2SH) 可以帮助发送方免去额外的成本,并将这一责任(额外成本)转移到真正需要使用锁定脚本中规定条件的接收方身上。Pay-to-ScriptHash 比特币地址是以数字 3 开头的。

在这个交易标准下,发送方不需要将很长的 PubKey 脚本放到他们的交易中。这里,锁定脚本被替换成了赎回脚本(redeem script)哈希值。赎回脚本哈希值由赎回脚本计算而来。赎回脚本与 PubKey 脚本类似,包含接收方在花费未花费输出之前必须满足的条件。发送方只需在交易中注明赎回脚本的哈希值。赎回脚本哈希可以翻译成标准比特币地址,发送方无需进行任何特殊操作或支付额外费用即可将 BTC 发送到这些地址。

接收方想要解锁这个 P2SH 地址上的 BTC 时,需要生成具有相同哈希值的赎回脚本并将其包含到交易内。因此,接收方用来解锁 UTXO 的交易大小会增加,执行交易的成本也会增加。

例如,Alice 想要发送 10 BTC 给 Bob。Alice 必须将赎回脚本哈希包含到交易内。首先,Bob 先生成一个赎回脚本,然后将赎回脚本的哈希值发送给 Alice,以便 Alice 将该哈希添加到交易内并发起交易。如果 Bob 想要花费该 UTXO,必须生成相同哈希值的解锁脚本,并满足脚本中提到的条件。

别忘了,Alice 只需将赎回脚本的哈希值而非整个脚本添加到交易内。因此,Alice 无需承担额外的费用。

Pay-to-ScriptHash 的优势
  • 使用哈希值取代冗长的脚本。
  • 发送方可以在不知道脚本中规定的花费条件的情况下,在交易中放入任意数量的赎回脚本哈希值。
  • 减轻了发送方的交易费负担。

总结

Script 是一种微型的编程语言,用在比特币中,为比特币交易的输出提供锁定机制。

  • 每个输出都有一个 "锁定脚本"。
  • 为了花费一个输出,你必须在交易中提供 "解锁脚本"。

当一个节点收到了一笔交易时,它会将解锁脚本和锁定脚本拼接起来并运行。如果脚本运算完后栈顶的元素为 1 并且栈中没有别的元素,那么这个脚本就是有效的,这个输出可以被花费。

"脚本" 只是一个 "断言(predicate)"。实际上它只是一个结果要么为真、要么为假的方程。但 predicate 是一个很长而且很罕见的词,所以我就叫它 "脚本" 了。

------ 中本聪

"脚本" 只是一个 "断言(predicate)"。实际上它只是一个结果要么为真、要么为假的方程。但 predicate 是一个很长而且很罕见的词,所以我就叫它 "脚本" 了。

------ 中本聪

十一.Taproot 的升级过程

Taproot 升级的内容

Taproot 的升级包含 schnorr 签名(BIP340)、taproot(BIP341) 和 tapscript (342)

  • Schnorr: 短签名,可以提升签名效率,减小交易的体积,并可以使签名的交易具备一定的隐私性(在 schnorr 的底层,具备一定零知识算法的特性);最早期,比特不适用 schnorr 签名是因为 schnorr 签名处于专利期。
  • Merkel 抽象语法树(MAST):使得 Bitcoin 具备一些智能合约的功能,可以对简单的数据进行验证。

Taproot 概述

比特币改进提案(BIPs)是为比特币引入新功能和信息的设计文档,而 Taproot 升级则是三个 BIPs 的汇编,这三个 BIPs 分别是 Schnorr 签名(BIP 340)、Taproot (BIP 341)和TapScript (BIP 342),这三个升级统称为 BIP Taproot,它将为比特币带来了更高效、更灵活、更私密的传输方式,其核心在于使用了 Schnorr 签名和 Merkel 抽象语法树(MAST)。

Taproot的原理,简单来说,就是定义了一种输出和两种花费路径。如上图所示,有 Alice 和 Bob 两个参与者,Taproot的运作过程如下:

  • 将Alice和Bob的公钥聚合为: C=P_A+P_B
  • 加入MerkleRoot,公钥聚合为:P=C+H(C||MerkleRoot)G,其中 H(C||MerkleRoot) 表示C和MerkleRoot的组合hash
  • 锁定脚本中填入聚合公钥 P,格式类似 Segwit:ver P。ver 表示版本号,Taproot 中 ver=1。
  • 花费路径有两个,二选一:
    1. 签名模式:Alice 和 Bob 全部签名产生聚合签名,填入见证脚本。利用聚合公钥 P, 对签名进行验证后即可花费。
    2. 脚本模式:Alice 和 Bob,有一个拒绝签名,可以走脚本模式。此时 Alice 想要完成花费,那么见证脚本中需要填入:符合Script 1的执行数据D, Script 1, C, Hash 2 。为了验证签名,首先利用Script 1, Hash2,计算MerkleRoot,然后验证 P==C+H(C||MerkleRoot)G 是否成立,最后构建完整脚本D||Script 1并执行脚本,验证结果是否为真。当上述验证通过后,即可完成花费。

Taproot按照签名模式进行花费时,多个参与方和单个参与方在区块链上看起来都长得是一样的,所以许多区块链分析将不可用,从而为所有 Taproot 用户保留隐私。与此同时,Taproot的脚本花费模式让用户可以实现复杂的支出条件。Taproot可以有效压缩交易脚本字节数。签名模式下随着参与者增加,EDSA交易脚本大小线性增长,Taproot交易脚本大小保持不变。脚本模式下随着脚本数量的增加,EDSA交易脚本大小线性增长,Taproot交易脚本大小对数增长。

Huffman Tree

给定N个权值作为N个叶子结点,构造一棵二叉树,若该树的带权路径长度达到最小,称这样的二叉树为最优二叉树,也称为哈夫曼树(Huffman Tree)。哈夫曼树是带权路径长度最短的树,权值较大的结点离根较近。

哈夫曼树的构造过程是利用贪心的思想,每次选择权重最小的两个结点组合成新的节点,加入到原始集合,重复上述过程,直至结束。如上图所示,存在权重集合为 (1, 2, 3, 4, 5) 的5个结点,构造哈夫曼树的过程如下:

  • 从集合中选择权重最小的 1, 2 结点,组合成权重为3的结点,集合变成 (3,3,4,5)
  • 从集合中选择权重最小的 3, 3 结点,组合成权重为6的结点,集合变成 (4, 5, 6)
  • 从集合中选择权重最小的 4, 5 结点,组合成权重为9的结点,集合变成 (6, 9)
  • 从集合中选择权重最小的 6, 9 结点,组合成权重为15的结点,集合变成 (15) , 构造过程结束

可以看出,在哈夫曼树中,权重越大的结点离根越近,权重越小的结点离根越远。

1.2.Huffman Tree在Taproot的应用

Taproot相比于ECDSA多签算法,一大优势是Schnorr聚合签名可以将多个签名数据压缩合并成单个聚合签名。使用聚合签名技术后,交易脚本的字节数大大减少,可以有效减少交易费用的支出。当我们按照脚本模式进行花费时,借助MAST的树形结构,交易脚本大小随着脚本数量对数增长。然而利用Huffman Tree,可以通过改进MAST的构造过程,帮助我们进一步减少脚本花费模式下见证脚本的字节数,最终达到降低交易手续费的目的。

前文提到,当Taproot按照脚本模式进行花费时,需要提供脚本在MAST中的Proof。如上图,当我们想通过script_A进行花费,见证脚本中需要提供:

  • 符合script_A的执行数据 D
  • script_A
  • P
  • TaggedHash(B), TaggedHash© TaggedHash表示带标签的hash,固定长度32字节,使用带标签的hash目的是减少hash碰撞

忽略前两项的字节数,可以看出,最后一项的字节数可以表示为 32*(d-1) ,d=3表示 script_A在MAST中的高度。换句话说,最后一项的字节数与脚本在MAST中高度线性相关。因此,对于 script_A, script_B, script_C 三个脚本,我们可以根据脚本使用频率赋予权重,构造哈夫曼树,作为MAST。最终,根据Huffman的特性,使用频率越高的 script , 在哈夫曼树中的高度越低,见证脚本中字节数越少,交易手续费越低。

总而言之,交易手续费与交易脚本的字节数息息相关。Taproot的聚合签名可以帮助我们有效减少按签名模式花费时的交易脚本字节数,但是利用Huffman构造MAST可以进一步帮助我们减少按脚本模式花费时的交易脚本字节数。

taproot交易过程

Taproot的核心是由Schnorr 签名和MAST抽象语法树组成,Taproot交易过程本文主要从Taproot公钥的创建和Taproot的花费模式两个方面阐述。

创建Taproot公钥

为了创建Taproot公钥,首先需要了解Taproot聚合公钥和聚合签名的产生过程。目前关注度较高的相关研究包括MuSig1和MuSig2。相比于MuSig2两轮通信,MuSig1最大的缺点在于它创建签名需要三轮通信,而每一轮通信都由来回传递的消息组成。由于本文未涉及网络间通信,因此这里主要讨论的是聚合公钥和聚合签名的基本生成过程。

聚合公钥

从上图可以看出聚合公钥的生成过程可以分三步:1)相互传递公钥,并对所有公钥拼接进行一次聚合hash,生成c_all。 2)c_all和i的公钥拼接hash后生成因子c_i。 3) 根据因子c_i进行线性组合得到聚合公钥P_agg。

复制代码
privkey1, pubkey1 = generate_key_pair(sha256(b'key0'))
privkey2, pubkey2 = generate_key_pair(sha256(b'key1'))
privkey3, pubkey3 = generate_key_pair(sha256(b'key2'))
pubkey_list = [pubkey1, pubkey2, pubkey3]
# 1) Aggregate hash of all public keys
pubkey_list_sorted = sorted([int.from_bytes(key.get_bytes()[1:], 'big') for key in pubkey_list])
L = b''
for px in pubkey_list_sorted:
L += px.to_bytes(32, 'big')
Ln = hashlib.sha256(L).digest()
# 2) 3) Calculate the factor and generate the aggregate public key
musig_c = {}
aggregate_key = 0
for key in pubkey_list:
musig_c[key] = hashlib.sha256(Ln + key.get_bytes()[1:]).digest()
aggregate_key += key.mul(musig_c[key])
聚合签名

聚合签名的生成过程上图显示需要进行三轮通信,主要分成两大步:1)生成nonce并线性聚合生成R_agg。 2)各方利用私钥生成Schnorr签名并进行聚合,得到最终的聚合签名(R_agg, s1+s2+s3)。

复制代码
# 1) generate nonce and aggregate
k1, R1 = generate_key_pair()
k2, R2 = generate_key_pair()
k3, R3 = generate_key_pair()
R_agg, negated = aggregate_schnorr_nonces([R1, R2, R3])
if negated:
k1.negate()
k2.negate()
k3.negate()
msg = sha256(b'msg')
# 2) Aggregate signature
s1 = sign_musig(privkey1*musig_c[pubkey1], k1, R_agg, aggregate_key, msg)
s2 = sign_musig(privkey2*musig_c[pubkey2], k2, R_agg, aggregate_key, msg)
s3 = sign_musig(privkey3*musig_c[pubkey3], k3, R_agg, aggregate_key, msg)
sig = aggregate_musig_signatures([s1, s2, s3], R_agg)
# Verify the correctness of the signature
assert aggregate_key.verify_schnorr(sig, msg)
引入MAST抽象语法树

如上图所示,Taproot公钥主要由两部分组成,包括聚合公钥P和MAST结构形成的公钥tG。假设P是Alice,Bob,Charlie的聚合公钥,script_A,script_B,script_C是Alice,Bob,Charlie相关的脚本。那么,Taproot公钥创建过程如下:

  • Alice,Bob和Charlie各自生成公私钥。

    privkey_alice, pubkey_alice = generate_key_pair()
    privkey_bob, pubkey_bob = generate_key_pair()
    privkey_charlie, pubkey_charlie = generate_key_pair()

  • 公钥聚合成pubkey_agg,并调整私钥,用于以后的签名。

    c_map, pubkey_agg = generate_musig_key([pubkey_alice, pubkey_bob, pubkey_charlie])
    privkey_alice_c = privkey_alice * c_map[pubkey_alice]
    privkey_bob_c = privkey_bob * c_map[pubkey_bob]
    privkey_charlie_c = privkey_charlie * c_map[pubkey_charlie]

  • 创建脚本script_A,script_B,script_C

    scriptA = CScript([pubkey_alice.get_bytes(), OP_CHECKSIG])
    scriptB = CScript([pubkey_bob.get_bytes(), OP_CHECKSIG])
    scriptC = CScript([pubkey_charlie.get_bytes(), OP_CHECKSIG])

  • 构建MAST抽象语法树,计算MAST结构对应的私钥taptweak。上图中TaggedHash表示带标签的hash,固定长度32字节,计算方式是TaggedHash(tag, x) = sha256(sha256(tag) + sha256(tag) + x);ver表示Tapscript版本号,当前值为0xc0;size表示scirpt的字节数;A&B表示A,B字典排序后按字节拼接。

    TAPSCRIPT_VER = bytes([0xc0])

    1) Compute TapLeaves A, B and C

    Method: ser_string(data) is a function which adds compactsize to input data.

    hash_inputA = TAPSCRIPT_VER + ser_string(scriptA)
    hash_inputB = TAPSCRIPT_VER + ser_string(scriptB)
    hash_inputC = TAPSCRIPT_VER + ser_string(scriptC)
    taggedhash_leafA = tagged_hash("TapLeaf", hash_inputA)
    taggedhash_leafB = tagged_hash("TapLeaf", hash_inputB)
    taggedhash_leafC = tagged_hash("TapLeaf", hash_inputC)

    Method: Returns tapbranch hash. Child hashes are lexicographically sorted and then concatenated.

    def tapbranch_hash(l, r):
    return tagged_hash("TapBranch", b''.join(sorted([l, r])))

    2) Compute Internal node TapBranch AB

    Method: use tapbranch_hash() function

    internal_nodeAB = tapbranch_hash(taggedhash_leafA, taggedhash_leafB)

    3) Compute TapTweak

    rootABC = tapbranch_hash(internal_nodeAB, taggedhash_leafC)
    taptweak = tagged_hash("TapTweak", pubkey_agg.get_bytes() + rootABC)
    print("TapTweak:", taptweak.hex())

  • 根据公式Q=P+tG合成taproot公钥,并生成segwit_address用于下文交易。

    generate Taproot pubkey

    taptweak_pubkey = pubkey_agg.tweak_add(taptweak)

    generate segwit address

    taptweak_pubkey_b = taptweak_pubkey.get_bytes()
    taptweak_pubkey_b = bytes([taptweak_pubkey_b[0] & 1]) + taptweak_pubkey_b[1:]
    segwit_version = 1
    segwit_address = program_to_witness(segwit_version, taptweak_pubkey_b)
    print('Segwit address:', segwit_address)

  • 向Taproot地址转账50个btc

    Start node

    test = util.TestWrapper()
    test.setup()
    node = test.nodes[0]

    Generate coins and create an output

    tx = node.generate_and_send_coins(segwit_address)
    print("Transaction {}, output 0\nsent to {}\n".format(tx.hash, segwit_address))

Taproot花费

为了完成从上述Taproot地址向Bob个人转账0.5个btc,有两种支付途径:一种是Alice,Bob,Charlie都进行签名后形成聚合签名,完成向Bob的转账;另一种是通过MAST结构中的script向Bob进行转账。

  • 创建交易原文,填充接受方地址,转账数量等数据。

    spending_tx=CTransaction()
    spending_tx.nVersion =2
    spending_tx.nLockTime=0
    outpoint = CoutPoint(int(tx.hash,16),0)
    spending_tx_in=CTxIn(outpoint)
    spending_tx.vin =[spending tx in]
    scriptpubkey =bytes(cscript([op 1, pubkey alice.get bytes()]))
    amount_sat=int(0.5*100000000)
    dest_output =CTxOut(nValue=amount sat, scriptPubKey=scriptpubkey)
    spending_tx.vout =[dest_output]
    print("Spending transaction:\n{}".format(spending tx))

    Spending transaction:
    CTransaction(nVersion=2 vin=[CTxIn(prevout=CoutPoint(hash=102517bf877c74489c83d4b58ala22e067526b79288076afba n=0)scriptSig= nsequence=0)]vout=[CTxOut(nValue=0.50000000 scriptPubKey=7280a8f376ega82ba8e44f1dcf254a8517ae7160f41ea9'73334a)wit=CTxWitness()nLockTime=0)

  • 按照第一种方式进行转账,首先需要Alice,Bob,Charlie各自生成nonce并进行聚合,然后各自利用私钥进行Schnorr签名,最后进行签名的聚合操作。因此,这种方式最终的见证脚本是一个单独的签名,固定长度64字节。

    生成密钥对和随机数

    k_alice, R_alice = generate_key_pair()
    k_bob, R_bob = generate_key_pair()
    k_charlie, R_charlie = generate_key_pair()

    聚合随机数

    R_agg, negated = aggregate_schnorr_nonces([R_alice, R_bob, R_charlie])

    如果聚合后的随机数被拒绝,则反签名

    if negated:
    k_alice.negate()
    k_bob.negate()
    k_charlie.negate()

    创建sighash

    sighash = TaprootSignatureHash(spending_tx, [tx.vout[0]], SIGHASH_ALL_TAPROOT, input_index=0)

    单独签名并聚合部分签名

    e = musig_digest(R_agg, taptweak_pubkey, sighash)
    s_alice = sign_musig(privkey_alice_c, k_alice, R_agg, taptweak_pubkey, sighash)
    s_bob = sign_musig(privkey_bob_c, k_bob, R_agg, taptweak_pubkey, sighash)
    s_charlie = sign_musig(privkey_charlie_c, k_charlie, R_agg, taptweak_pubkey, sighash)

    聚合签名

    sig_agg = aggregate_musig_signatures([s_alice, s_bob, s_charlie, e], R_agg)

    将聚合签名添加到交易见证中

    spending_tx.wit.vtxinwit.append(CTxInWitness([sig_agg]))

  • 测试第一种花费组建的交易原文合法并发送交易

    Test mempool acceptance

    assert node.test_transaction(spending_tx)

    send transaction

    txid = node.sendrawtransaction(hexstring=str(spending_tx.serialize().hex()), maxfeerate=0)
    print("Success!", txid)

  • 按照第二种方式进行转账,假设是Alice通过script_A完成向Bob的转账,那么见证脚本需要包括:1)Stack element(s) satisfying TapScript_A 2)TapScript_A 3)Controlblock c。其中Controlblock c表示的是TapScript_A相关的proof,长度为33+32n。33个字节中的首字节是聚合公钥和Taproot版本号共同计算得出,剩下32个字节表示的是聚合公钥的x坐标。32n表示的是TapScript_A的proof,在本例中n=2,指的是taggedhash_leafB和taggedhash_leafC。

    创建TapLeafA的签名哈希

    sighashA = TaprootSignatureHash(spending_tx,
    [tx.vout[0]],
    SIGHASH_ALL_TAPROOT,
    input_index=0,
    scriptpath=True,
    tapscript=scriptA)

    使用私钥生成签名

    signatureA = privkey_alice.sign_schnorr(sighashA)

    打印签名

    print("Signature for TapLeafA: {}\n".format(signatureA.hex()))

    聚合公钥的字节表示

    pubkey_agg_b = pubkey_agg.get_bytes()

    创建控制块

    Controlblock = bytes([pubkey_agg_b[0] & 0x01 | 0xc0]) + pubkey_agg_b[1:] + taggedhash_leafB + taggedhash_leafC

    向交易添加见证

    提示:见证栈对于脚本路径 - [满足tapscript的元素] [TapLeaf.script] [controlblock]

    提示:tapscript的控制块在control_map[TapLeaf.script]中

    witness_elements = [signatureA, scriptA, Controlblock]
    spending_tx.wit.vtxinwit.append(CTxInWitness(witness_elements))

  • 测试第二种花费组建的交易原文合法并发送交易

    Test mempool acceptance

    assert node.test_transaction(spending_tx)

    send transaction

    txid = node.sendrawtransaction(hexstring=str(spending_tx.serialize().hex()), maxfeerate=0)
    print("Success!", txid)

总结

总的来看,Taproot交易主要关注的是一种输出和两种花费模式。一种输出使得无论是个人交易还是多签交易,锁定脚本中公钥是一致的,无法从形式上进行区分。两种花费模式使得我们可以用更少的字节,实现更复杂的交易过程。

十二.比特币的生态项目

比特币 BRC20 协议出现之后(2023 年 3 月 出现,利用 json 数据的序数铭文来部署代币合约,铸造和转移代币),产生了铭文,符文等发行资产的方式。

铭文

可以刻在 statoshi 上的任意文本,包含但不限于文本,图像,视频;在 taproot 中,铭文被刻入到 pay-to-taproot 的输出类型中,铭文内容不执行条件里,由 OP_FALSE OP_IFcontentOP_ENDIF 组成,组成完成之后推送到见证字段,所以其不会影响原始的脚本。

铭文格式:

  • OP_FALSE
  • OP_IF
  • OP_PUSH "命令"
  • OP_1
  • OP_PUSH "文本类型"
  • OP_0
  • OP_PUSH "DappLink"
  • OP_ENDIF

以上的铭文呢,只能进行 mint 和 transfer, 很难在去中心化环境里面进行买卖交易。

铭文的优势

  • 和 IPFS 相比,数据存在了 Bitcoin 链上,更安全
  • 和 Arweave 很相似,只不过 Bitcoin 的网络和共识更能保证数据安全
  • 重复 inscribe:只认第一次 inscribe 的内容,re-inscribe无效

铭文和符文

符文与铭文的概念

符文(Rune)和铭文(Inscription)是比特币生态中的新兴资产类别,主要通过比特币的 UTXO 结构和 Taproot 机制进行存储和解析。它们可以被用于发行代币、NFT、铭文信息等,使比特币生态更具可编程性。

  • 铭文:铭文通常使用 Ordinals(序数理论)为比特币的最小单位(satoshi)附加元数据,例如图片、文本、代码等,从而形成类似 NFT 的资产。
  • 符文:符文是由 Casey Rodarmor 提出的比特币原生代币标准,旨在提供一种高效的比特币链上资产发行方式,类似于以太坊的 ERC-20 代币标准。

符文和铭文的标准协议

Ordinals 序数铭文协议(BRC-20):BRC-20 是基于 Ordinals 实现的比特币原生代币标准,主要依赖文本铭文来定义代币信息,常见的 JSON 格式如下:

复制代码
{
  "p": "brc-20",
  "op": "mint",
  "tick": "ordi",
  "amt": "1000"
}
  • p:协议类型(brc-20)
  • op:操作类型(deploy、mint、transfer)
  • tick:代币名称
  • amt:数量

Runes 符文协议:符文协议是比特币生态的一种新代币协议,相较于 BRC-20,它更高效,并减少了铭文对比特币 UTXO 集的膨胀影响。其特点包括:

  • 无需额外的铭文数据:不依赖 JSON 铭文,而是利用 Taproot 交易中的 OP_RETURN 进行记录
  • 原生 UTXO 模型支持:利用比特币交易本身的输入输出,避免 UTXO 过度碎片化
  • 更低的交易成本:相较于 BRC-20,符文交易数据更少,因此 gas 费更低
铭文与符文的铸造过程
  • 铭文(Inscription)的铸造:铭文的铸造(Inscribing)通常通过 Ordinals 工具进行:
    • 选择铭文内容:可以是文本、图片(Base64 编码)、代码或 JSON 数据
    • 创建 Ordinals 交易:将铭文数据存储到比特币交易的 witness 数据段中
    • 广播交易:交易被矿工打包后,铭文永久记录在比特币链上
  • BRC-20 代币的部署和铸造
    • 部署 BRC-20 代币:
      • 发送一个包含 JSON 铭文的 Ordinals 交易(见上文标准)。
      • 交易确认后,该代币标准被认可。
    • 铸造 BRC-20 代币:
      • 任何人都可以根据 mint 规则,创建铭文交易,并铸造代币。
    • 转移 BRC-20 代币:
      • 通过 transfer 铭文,将特定数量的 BRC-20 代币转移给其他用户。
  • 符文(Runes)的铸造:符文不需要 JSON 铭文,而是通过以下方式铸造:
    • 定义符文名称、供应量和规则
    • 使用 OP_RETURN 或 Taproot 交易存储符文信息
    • 通过比特币 UTXO 进行解析和管理
    • 可在钱包或交易所支持的环境中自由转移

铭文解析器

铭文解析器用于读取比特币交易中的铭文信息,通常需要以下步骤:

  • 监听比特币交易:扫描比特币区块,寻找带有 Ordinals 或符文数据的交易。
  • 解析 Witness 数据:提取 Ordinals 铭文的 JSON 或符文 OP_RETURN 信息。
  • 匹配铭文规则:识别 BRC-20、NFT、符文等不同类型的数据格式。
  • 输出解析结果:
    • 对于 BRC-20,解析代币总供应量、持有人信息、交易历史等。
    • 对于符文,解析符文 ID、铸造数量、转账记录等。

铭文和符文手续费消耗分析

  • 铭文
    • 铭文(Inscription)基于 Ordinals 序数理论,通常将数据存储在比特币 Taproot Witness 数据部分,因此其交易手续费较高。
    • 铭文(如 BRC-20、NFT)通常通过 Taproot 交易存储数据在 Witness 结构中,由于 Witness 数据部分的折扣机制,铭文交易比普通 P2PKH/P2SH 交易稍微便宜,但仍然比普通 BTC 交易昂贵。
  • 符文
    • 符文数据可以通过 OP_RETURN 存储,或直接嵌入 UTXO 交易输入/输出字段,相比铭文,符文避免了 Witness 结构存储大量数据,因此交易更轻量、费用更低。

BRC20

协议概述

BRC -20 代币标准于 2023 年 3 月 8 日创建的比特币实验性可替代代币标准,它利用 JSON 数据的序数铭文来部署代币合约、铸造代币和转移代币。

协议的细节

mint

链上:inscribe一个表示mint operation的sat,并立即 transfer 给 minter 地址

复制代码
{
"p": "brc20",
"op": "mint",
"tick": "ordi",
"amt": 1000
}

链下

  • 判断statetick存在,amt 不能超过其 lim,累计 amt 不能超过其 max
  • statetick"balances"minter+=amt
transfer

链上

  • 先 inscribe一个表示transfer operation 的 sat,发给 sender 地址

    {
    "p": "brc20",
    "op": "transfer",
    "tick": "ordi",
    "amt": 1000
    }

  • sender再构造一笔交易吧这个 sat 发给receiver

  • 需要 2 比交易,不友好

链下

  • 判断statetick"balances"sender>=amt
  • account_statetick"balances"sender-=amt
  • account_statetick"balances"receiver+=amt

一些brc-20例子

基于PSBT的交易

部分签名简介

PSBT 是比特币原始交易(raw transaction)的扩展格式,包含原始交易所需的信息以及签名过程的辅助数据。PSBT 的数据结构分为以下几部分

  • 全局字段:这些字段适用于整个 PSBT:
    • 版本号(Version):当前版本为 0x00。
    • 原始交易(Unsigned Transaction):PSBT 存储了一个未签名的比特币交易,该交易在未完成签名前不可用。
    • Xpub(可选):用于描述公钥信息(如 BIP32 扩展公钥)。
  • 输入字段: 输入字段(Input Fields)
    • 非最终化的输入(Non-Finalized Inputs):交易的输入,未完全签名。
    • UTXO 信息:
      • 非隔离见证(Legacy)输入:需要 Previous TX(完整的前序交易)
      • 隔离见证(SegWit)输入:只需要 Witness UTXO(简化的 UTXO 信息)
    • 签名相关信息:
      • Partial Signatures(部分签名)。
      • Public Keys(相关的公钥)。
      • Sighash Type(签名哈希类型)。
      • Redeem Script(P2SH 解锁脚本)。
      • Witness Script(P2WSH 见证脚本)。
      • BIP32 Derivation(BIP32 派生路径)
  • 输出字段(Output Fields)每个输出包括:
    • 公钥信息(BIP32 Derivation)
    • 脚本信息(如果适用,存储 Redeem Script 或 Witness Script)
  • PSBT 交易流程:
    • 步骤 1:创建 PSBT
      • 由钱包软件、离线设备或交易构建工具创建一笔未签名的交易。
      • 交易的输入包括 UTXO,但尚未附加签名。
    • 步骤 2:添加签名
      • 私钥持有者(如硬件钱包或冷钱包)逐步对交易进行签名。
      • PSBT 允许多个签名者分别签署(如多重签名地址)。
      • 硬件钱包可以通过 PSBT 提供的 UTXO 信息,在离线环境中验证交易的输入。
    • 步骤 3:合并签名
      • 一旦所有需要的签名都被收集,PSBT 可以被合并(Combine)。
      • 这一阶段会合并多个签名到一个完整的交易中。
    • 步骤 4:最终化交易
    • 当交易的所有输入都已满足签名需求后,PSBT 需要被 Finalized(最终化)。
    • 这会生成一笔标准的比特币交易(Raw Transaction),可以用 bitcoin-cli sendrawtransaction 或其他方式广播到比特币网络。

PSBT 格式由 BIP 174(BIP 174: Partially Signed Bitcoin Transaction Format)定义,目的是使交易签名更加灵活和安全,特别适用于:

  • 冷钱包离线签名:冷钱包可以接收一个 PSBT,进行签名后再返回,不需要暴露私钥。
  • 多重签名(MultiSig):每个签名者可以分别对 PSBT 添加签名,最后由某个节点完成合并和广播。
  • 硬件钱包支持:硬件钱包不需要知道整个钱包的 UTXO 集,只需要处理与交易相关的部分。
Bitcoin 部分签名的组织形式

这张图片展示了 Bitcoin 交易的 SIGHASH(签名哈希)类型,它决定了签名对交易的哪些部分负责。通俗点说,这个机制允许签名者选择只对交易的一部分负责,从而支持更灵活的交易模式,比如多重签名、联合支付等。

  • 图片分为两排:

    • 第一排(基础类型)
    • 第二排(带 ANYONECANPAY 选项的扩展类型)
  • 第一排(基础 SIGHASH 类型)

    • SIGHASH_ALL(0x01)
      • 全签名模式:所有输入和输出都被锁定,交易的任何部分都不能被修改。
      • 适用于普通交易,因为可以确保支付的金额和接收者不会被篡改。
    • SIGHASH_NONE(0x02)
      • 只签名输入,不锁定任何输出。
      • 这样别人可以随意修改交易的输出,适用于灵活支付场景(如某人只想签名但不关心资金流向)。
    • SIGHASH_SINGLE(0x03)
      • 每个输入只锁定对应的一个输出。
      • 适用于联合支付,例如多人签署一笔交易,每个人只负责自己的部分。
  • 第二排(ANYONECANPAY 变体)

    这些变体允许 新增输入,但仍然控制输出。

    • SIGHASH_ALL | ANYONECANPAY(0x81)
      • 只锁定当前输入和所有输出,但其他人可以添加额外的输入(比如共付模式)。
      • 应用场景:类似"众筹"模式,A 先支付,其他人可以随意加钱。
    • SIGHASH_NONE | ANYONECANPAY(0x82)
      • 只锁定当前输入,但允许其他人添加输入和输出。
      • 应用场景:比如发起一个交易,允许其他人修改资金流向。
    • SIGHASH_SINGLE | ANYONECANPAY(0x83)
      • 只锁定当前输入和对应的一个输出,其他部分可修改。
      • 应用场景:适用于某些 P2P 支付场景,如"我要给某人转账,但其他部分随意"。
  • 直观理解

    • SIGHASH_ALL → "我签字后,所有内容都不能改!"
    • SIGHASH_NONE → "我只保证自己花费的钱,别人爱怎么花不管!"
    • SIGHASH_SINGLE → "我对某个输入只负责对应的一个输出!"
    • ANYONECANPAY 变体 → "其他人可以随意增加他们的资金来源!"

基于 PSBT 的交易

基于这一特性,可以实现去中心化的 ask & bid。以 ask 为例:

  • 卖方把 NFT 的 sat 作为 input,把出价作为 output,然后进行SINGLE|ANYONECANPAY签名,最后把这个PSBT发布到 marketplace
    • 左上:铭文
    • 右上:定价 100 BTC,指定一个收币的地址,这里就是一个 output
  • 买方补充 input(包含付款和fee),补充output(接收 sat),签名后广播交易到Bitcoin网络
    • 左下:签名 100 BTC,转到右上的地址里面
    • 右下:买方接收符文的信息
  • 只能定量定价地ask/bid,以ask为例
    • 卖方先 inscribe 一个 transfer 的 sat
    • 用这个 sat 构造 PSBT,发布到交易平台
    • 买方通过构造完整交易,买下这个 sat

RGB/RGB++ 协议简介

RGB 协议 是一种基于比特币的"智能合约"与"资产发行"协议。

其核心理念是:

"让比特币链只承载数据承诺,而不处理资产逻辑。"

RGB++ 是 RGB 的增强版本,它主要解决了:

  • 传统 RGB 的 离线数据传递难题;
  • 以及 客户端状态验证的复杂性;
  • 并通过 UTXO 映射和聚合承诺机制 提升了性能和可扩展性。

本文是 Web3 基础知识系列的第 7 篇,

整套笔记从区块链的由来、密码学原理、组织结构、共识与公链实践逐讲展开。

相关推荐
混凝土拌意大利面2 小时前
WEB3核心技术的一种实现:时间长河共识算法(TRC, Time-River Consensus)
web3·区块链·共识算法
OxYGC1 天前
[Web3] 基础第5篇: 共识算法与 P2P 通信协议
web3·区块链·以太坊·比特币
Bczheng16 天前
四十六.创建一笔交易(9)--第三个区块 添加tx到钱包
区块链
2301_776045237 天前
理解Layer 1区块链协议
区块链
幻智星科技7 天前
零知识证明三大核心特性解析
区块链·零知识证明
木西8 天前
AI 驱动的去中心化资产管理:QuantixAI 核心金库与智能风控架构解析
web3·智能合约·solidity
2301_776045238 天前
山寨币与比特币之间的关系
大数据·人工智能·区块链
JF_Ma9 天前
1000个量化策略第29例:经典红利低波策略
区块链
AlanAshley9 天前
实时湖仓与区块链:数据架构的信任革命
架构·区块链