btcd:Golang 下的交易构建
锲子
参与 Bitcoin 的相关生态难免需要涉及到链上的操作,相比于 Ethereum 这样简单的交易发送机制,Bitcoin 上构建交易则需要一定的编程能力,这篇博文记录一下 Bitcoin 上构建交易的各种姿势。(博文不间断更新)
这篇博文的前置知识:UTxO、交易结构与脚本语言,可以在 Learn me a bitcoin 学习,另外:
- 测试网交易池可视化:https://mempool.space/testnet
- 测试代币水龙头:https://bitcoinfaucet.uo1.net/
- 测试私钥生成(主网尽量不要使用):https://iancoleman.io/bip39/
golang 下的 Bitcoin 工具库为:github.com/btcsuite/btcd
简单交易的构建
私钥与地址生成
私钥生成
私钥有多种形式,在 Bitcoin 中最为常见的是 WIF (Wallet Import Format)格式的私钥,也有 16 进制的私钥,这些私钥之间是可以相互转换的
事实上目前的大多数钱包插件都不支持 WIF 格式的私钥导入,只能使用一些软件钱包才能导入。
golang 内可以生成私钥并派生 WIF 格式的私钥,也可以使用在线的 BIP-39 助记词生成(不推荐主网使用)
1 | // 博文的代码都默认为测试网 |
地址生成
同样,Bitcoin 的地址有多种类型,在之前的博文 BIP、闪电网络与 Trao 中略有提及,如果需要具体了解也可以阅 Learn me a bitcoin 的 Script 小节中的各种地址。
支付类型和地址相关的内容放在了新坑:Bitcoin 支付方式及地址类型
目前比较常用的是 Taproot 地址,这是为了支持 Taproot 协议而被提出的地址类型,可以支持不同的 Pay-To 方式
在代码的实现层面上,通过 WIF 私钥生成地址的代码如下
1 | taprootAddr, err := btcutil.NewAddressTaproot( |
这里嵌套了四层函数,从内到外依次是
- wif.PrivKey.PubKey():获取 WIF 密钥对的公钥
- txscript.ComputeTaprootKeyNoScript:通过公钥计算得到一个 Taproot 的 Schnorr 签名公钥,类似一个转换的过程
- schnorr.SerializePubKey:对公钥序列化为字节码
- btcutil.NewAddressTaproot:序列化后的公钥生成 Taproot 地址
交易构建
Bitcoin 最为简单的交易即一笔输入和一笔输出的交易,它将一笔输入中的 BTC 转入到另外一个地址
随机生成转账地址:tb1pvwak065fek4y0mup9p4l7t03ey2nu8as7zgcrlgm9mdfl8gs5rzss490qd
虽然说是“简单”交易构建,实际上 Taproot 类型的交易算是比较麻烦的交易构建方式,较为简单的应该是 P2PKH 交易的构建。
构建交易之前需要获取地址的可用 UTxO,需要一个函数 GetUnspent(address string)
来得到地址的 UTxO,这里先手动填入然后返回所需要的 UTxO 的信息
1 | func GetUnspent(address string) (*wire.OutPoint, *txscript.MultiPrevOutFetcher){ |
这里返回的是一个输出点(output point)和前置输出获取器(Fetcher)
- 输出点记录了 UTxO 所在的交易哈希和输出所在交易的位置(index),后续用于填充交易的输入
- 获取器中记录了一个映射关系,即一个输出点对应的输出是什么样的(它输出的数量和锁定脚本)
此外,发送交易之前要将地址解码并且生成 Taproot 下的 PayToAddress 脚本,该过程的实现代码如下
1 | func DecodeTaprootAddress(strAddr string, cfg *chaincfg.Params) ([]byte, |
至此,可以开始一笔简单交易的构建,相比于在 Ethereum 下一个 Json 就可以完成的操作,在 Bitcoin 则是需要实例化一个空的交易体并手动填充
新建交易的输入(wire.TxIn)需要三个参数:前一输出点、签名、见证脚本(witness),构建交易时后面两者均先默认为 nil,在签名完成后才进行填充
签名和见证脚本
在通常情况下,见证脚本和签名脚本是独立的,或者说见证脚本就是一种签名,只是它独立在交易体外,并且这一部分数据也可以在很久之后被节点修剪掉。
1 | // 默认的 version = 1 |
然后需要对交易进行签名,签名是对交易的所有输入进行签名,即需要填入正确的解锁脚本到签名脚本或见证脚本字段中。在 Taproot 交易中,通常需要填入解锁的见证脚本。
1 | // 获取前一笔交易 |
本节不设计代码层面的交易发送,简单来说交易的广播就是将它发布到任意一个区块链节点
所以可以将生成的交易提交到 Broadcast Transaction 即可,演示交易为 https://mempool.space/testnet/tx/f11f3edccb9988729ba4896e1da82b799a7b4e70cca82aa212058076dd49d76f
本节的完整代码:Simple Bitcoin Transaction - Github Gist