在先前的课程中,我们运用节点软件中的getnewaddress命令生成新的比特币地址,其对应的私钥及交易签名均由节点钱包模块妥善管理。然而,这种依赖节点的方式在特定场景下可能限制应用功能的发挥。为了获取最大限度的自主性和灵活性,我们需要摆脱节点束缚,采用C#代码离线创建地址。这种自主管理的地址不再归节点钱包管辖,随之而来的是诸多挑战,包括但不限于:
1. 需深入理解比特币内部的密钥、地址、脚本运作机制;
2. 必须自行构建裸交易并进行签名,而非简单调用sendtoaddress;
3. 应自行追踪与这些地址关联的UTXO,而非依赖listunspent;
4. 需手动汇总比特币余额,无现成getbalance可供使用。
这一切繁琐源于我们选择亲自管理地址,实质上是在构建一个微型钱包模块。后续课程中,我们将继续倚仗NBitcoin这一最全面的.NET平台上比特币协议实现工具,它不仅封装了RPC功能,更为离线地址生成与管理提供了强大支持。
二、私钥与公钥的生成回顾知识库,私钥衍生公钥,公钥进一步转化为地址,地址实为公钥的精炼表述。私钥本质上是一个随机数,借助椭圆曲线乘法运算可推导出公钥;而公钥通过哈希算法即可转化为比特币地址。这两个转化过程均具有单向不可逆性,确保无法从地址反推公钥,或从公钥追溯私钥。
使用NBitcoin的Key类,我们能便捷地生成公钥与私钥对。以下代码演示了这一过程,并展示了私钥与公钥的十六进制表示及其WIF格式:
压缩形式的公钥较非压缩版本更为紧凑,但功能上并无差异。Key类默认生成的公钥即为压缩形式,可通过IsCompressed属性验证。公钥对象的Hash属性则提供了构建比特币地址所需的核心数据——公钥哈希。
三、创建P2PKH地址比特币网络中的地址,其核心功能在于接收比特币并以UTXO形态存在于交易中,待将来被消费。因此,地址与密钥密切相关,二者共同标识某个用户或身份。随着比特币的发展,衍生出多种地址形式,但其核心理念始终未变:标识目标用户/身份。
让我们从最基础的P2PKH地址开始探讨。
P2PKH(Pay To Public Key Hash)地址基于公钥哈希生成,是最早被定义的比特币地址类型。其结构包含三部分:8位网络前缀、160位公钥哈希和32位校验码后缀。这三部分数据经base58编码拼接后,即形成P2PKH地址。
地址前缀
比特币P2P协议应用于多个区块链(如比特币主链、测试链、莱特币、dash币等),且存在多种地址格式。为便于区分,各地址类型采用了不同的前缀。例如,比特币主链P2PKH地址前缀为00,测试链P2PKH地址前缀为6F。经过base58编码,不同的前缀产生了易于辨识的前导字符,帮助我们快速识别地址类型。
NBitcoin针对不同网络提供了相应的封装类,其中包含了各自的前缀规则。在生成地址时,需指定网络参数对象,以确保正确应用地址前缀。
以下代码展示了如何通过新生成的密钥及其公钥哈希,在私有链模式下创建P2PKH地址。此外,也可直接通过私钥/公钥路径生成对应P2PKH地址:
四、身份验证逻辑BitcoinAddress类除Network属性外,还有一个值得关注的属性——ScriptPubKey。此属性用于获取与地址关联的公钥脚本,其结果如下所示:
公钥脚本有何作用?让我们先思考一个问题:当UTXO指定了接收地址,该地址持有者应如何向节点证明自己对该UTXO的所有权?
P2PKH地址由公钥衍生,而公钥可用于验证私钥签名。因此,提交交易者只需在引用UTXO的交易中附上自己的公钥和交易签名,节点便能利用公钥验证提交者是否为地址x的合法持有者,验证流程如下:
验证公钥:通过公钥推算地址,与地址x比对,不符则拒收交易;
验证私钥:利用交易和公钥,验证提交的签名是否吻合,不符则拒收交易;
若验证通过,接受并广播交易。
由此,向目标地址发送比特币,即相当于要求发送方向转出的UTXO施加一把由目标地址提供的“锁”,唯有该地址对应的私钥方能解开这把锁。回到ScriptPubKey属性,其所获取的公钥脚本,正是这把供发送方使用的“锁”——在发送比特币时,请先用我提供的“锁”锁定你发来的UTXO。
五、P2PKH脚本执行原理理解了节点如何验证交易提交者对UTXO的所有权后,我们就能轻松明白ScriptPub属性获取的脚本究竟为何物。
简言之,比特币巧妙地将UTXO所有权的验证逻辑从节点内移至交易中实现:在UTXO中定义一段公钥脚本,在引用UTXO时定义另一段签名脚本。节点在验证UTXO所有权时,仅需拼接这两段脚本并确保运行结果为真,即可确认交易提交者确实持有该UTXO。
比特币脚本采用简洁的自定义语法,不含循环,非图灵完备语言,降低了安全风险。脚本使用预定义指令编写,从左至右顺序执行。
以P2PKH地址为例,其对应的公钥脚本与签名脚本采用助记符表示如下:
节点在合并脚本时,始终将scriptPubKey置于末尾,scriptSig置于前端。以下是整个脚本执行过程中,每条指令执行后的栈状态:
接下来,我们逐条跟踪指令的运行情况。
签名与公钥入栈
指令1和2首先将签名和公钥压入栈。执行第1个指令时,将签名<sig>压入栈顶;执行第2个指令时,将公钥<pubkey>压入栈顶。
公钥验证
指令3/4/5/6负责验证公钥是否与scriptPubKey中预留的解锁公钥哈希相符。
1.使用指令OP_DUP将栈顶元素复制一份后再压入栈,此时栈顶有两个公钥<pubkey>。
接着,使用指令OP_HASH160提取栈顶元素并进行两层哈希计算(SHA-256 → RIMPEMD-160),这是公钥哈希的算法。计算结果压入栈,准备与scriptPubKey中的预留公钥哈希比对。
然后,脚本将scriptPubKey中预留的解锁公钥哈希压入栈顶,此时栈顶存在两个公钥哈希:预留的解锁公钥哈希,以及根据解锁脚本提供的公钥重新生成的公钥哈希。
指令OP_EQUALVERIFY提取栈顶的两个公钥哈希进行比较,若不相等则标记交易无效,退出脚本执行。反之,栈顶仅剩两个成员:公钥和交易签名。
签名验证
指令OP_CHECKSIG负责提取栈顶的两个成员进行签名验证。验证成功,则将01压入栈,栈顶的非零值表示验证通过。否则将00压入栈,表示验证失败。
六、创建P2SH地址基于前文学习,我们知晓比特币的UTXO所有权认证完全基于交易中嵌入的两部分脚本。这种独立于节点的脚本化验证机制赋予比特币支付极高的灵活性。
P2SH(Pay To Script Hash)地址正是为了充分利用比特币的脚本能力而设计。如其名所示,此类地址基于脚本的哈希构建——该脚本被称为赎回(redeem)脚本:
P2SH地址的公钥脚本仅需验证UTXO消费者提交的序列化赎回脚本serializedRedeemScript与预留的脚本哈希scriptHash是否匹配:
文章内容来源于网络,不代表本站立场,若侵犯到您的权益,可联系多特删除。(联系邮箱:[email protected])
近期热点