diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..7af6886 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,73 @@ +name: create-release + +on: + push: + branches: + - main # 监听 main 分支的 push 操作(编译和测试/代码检查) + tags: + - 'v*' # 监听以 'v' 开头的标签的 push 操作(发布 Release) + +jobs: + lint: + name: lint + runs-on: ubuntu-latest + steps: + - uses: actions/setup-go@v5 + with: + go-version: "1.23.x" + - uses: actions/checkout@v4 + - name: golangci-lint + uses: golangci/golangci-lint-action@v6 + with: + version: latest + + test: + runs-on: ubuntu-latest + strategy: + matrix: + go: [ "1.22.x", "1.23.x" ] + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-go@v5 + with: + go-version: ${{ matrix.go }} + + - name: Run test + run: make test COVERAGE_DIR=/tmp/coverage + + - name: Send goveralls coverage + uses: shogo82148/actions-goveralls@v1 + with: + path-to-profile: /tmp/coverage/combined.txt + flag-name: Go-${{ matrix.go }} + parallel: true + + check-coverage: + name: Check coverage + needs: [ test ] + runs-on: ubuntu-latest + steps: + - uses: shogo82148/actions-goveralls@v1 + with: + parallel-finished: true + + # 发布 Release + release: + name: Release a new version + needs: [ lint, test ] + runs-on: ubuntu-latest + # 仅在推送标签时执行 + if: ${{ success() && startsWith(github.ref, 'refs/tags/v') }} + steps: + # 1. 检出代码 + - name: Checkout code + uses: actions/checkout@v4 + + # 2. 创建 Release 和上传源码包 + - name: Create Release + uses: softprops/action-gh-release@v2 + with: + generate_release_notes: true + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..7b2c0ba --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 yangyile-yyle88 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..4563ef5 --- /dev/null +++ b/Makefile @@ -0,0 +1,10 @@ +COVERAGE_DIR ?= .coverage + +# cp from: https://github.com/yyle88/gormcngen/blob/5f75d814c71ec306b276a804c134de6655913951/Makefile#L4 +test: + @-rm -r $(COVERAGE_DIR) + @mkdir $(COVERAGE_DIR) + make test-with-flags TEST_FLAGS='-v -race -covermode atomic -coverprofile $$(COVERAGE_DIR)/combined.txt -bench=. -benchmem -timeout 20m' + +test-with-flags: + @go test $(TEST_FLAGS) ./... diff --git a/README.md b/README.md index bd30fb6..2199693 100644 --- a/README.md +++ b/README.md @@ -1,66 +1,167 @@ +[![GitHub Workflow Status (branch)](https://img.shields.io/github/actions/workflow/status/yyle88/gobtcsign/release.yml?branch=main&label=BUILD)](https://github.com/yyle88/gobtcsign/actions/workflows/release.yml?query=branch%3Amain) +[![GoDoc](https://pkg.go.dev/badge/github.com/yyle88/gobtcsign)](https://pkg.go.dev/github.com/yyle88/gobtcsign) +[![Coverage Status](https://img.shields.io/coveralls/github/yyle88/gobtcsign/master.svg)](https://coveralls.io/github/yyle88/gobtcsign?branch=main) +![Supported Go Versions](https://img.shields.io/badge/Go-1.22%2C%201.23-lightgrey.svg) +[![GitHub Release](https://img.shields.io/github/release/yyle88/gobtcsign.svg)](https://github.com/yyle88/gobtcsign/releases) +[![Go Report Card](https://goreportcard.com/badge/github.com/yyle88/gobtcsign)](https://goreportcard.com/report/github.com/yyle88/gobtcsign) + # gobtcsign -## 第一步创建钱包 +`gobtcsign` is a concise and efficient Bitcoin transaction signing library designed to help developers quickly build, sign, and verify Bitcoin transactions. + +`gobtcsign` is a Golang package that simplifies BTC/DOGECOIN transaction signing and serves as a gateway for developers to explore BTC blockchain knowledge. + +--- + +## CHINESE README + +[中文说明](README.zh.md) + +--- + +## Installation + +```bash +go get github.com/yyle88/gobtcsign +``` + +--- + +## Features + +Here are the core features provided by `gobtcsign`: + +1. **Transaction Construction**: Efficiently construct transactions with support for multiple inputs and outputs, including automatic change calculation. A dynamic fee adjustment feature allows users to control transaction costs. +2. **Transaction Size Estimation**: Estimate the virtual size (vSize) of transactions based on the number and type of inputs/outputs. This helps developers set appropriate fee rates based on real-time conditions. +3. **Transaction Signing**: Compatible with multiple address types, including P2PKH, P2SH, and SegWit. Developers can use private keys to sign transaction inputs. +4. **Signature Verification**: Ensure transaction signatures are valid, reducing the risk of rejection by the network due to signature issues. +5. **Transaction Serialization**: Serialize signed transactions into hexadecimal strings for direct broadcasting to the Bitcoin network. + +--- + +## Dependencies + +`gobtcsign` relies on the following key modules: + +- **github.com/btcsuite/btcd**: Implements Bitcoin's core protocol and serves as the foundation for building and parsing transactions. +- **github.com/btcsuite/btcd/btcec/v2**: Handles elliptic curve cryptography for key management and signature generation/verification. +- **github.com/btcsuite/btcd/btcutil**: Provides utilities for encoding/decoding Bitcoin addresses and other common Bitcoin operations. +- **github.com/btcsuite/btcd/chaincfg/chainhash**: Offers hash calculations and chain-related utilities. +- **github.com/btcsuite/btcwallet/wallet/txauthor**: Constructs transaction inputs/outputs and automatically handles change. +- **github.com/btcsuite/btcwallet/wallet/txrules**: Defines transaction rules, including minimum fee calculations and other constraints. +- **github.com/btcsuite/btcwallet/wallet/txsizes**: Calculates the virtual size (vSize) of transactions, enabling dynamic fee adjustments. + +`gobtcsign` avoids using packages outside the `github.com/btcsuite` suite. Even so, you should never use this library directly for signing transactions without careful review to avoid potential malicious code that could collect your private keys. The best practice is to **fork the project** or copy the relevant code into your project while thoroughly reviewing the code and configuring strict network whitelists for your servers. + +--- + +## Usage Steps + +1. **Initialize Transaction Parameters**: Define transaction inputs (UTXOs), output target addresses, and amounts. Configure Replace-By-Fee (RBF) options as needed. +2. **Estimate Transaction Size and Fees**: Use the library's methods to estimate transaction size and set appropriate fees based on real-time fee rates. +3. **Generate Unsigned Transactions**: Build transactions using the defined parameters. +4. **Sign Transactions**: Sign transaction inputs using the corresponding private keys. +5. **Validate and Serialize**: Verify the signature's validity and serialize the transaction into a hexadecimal string for broadcasting. + +--- + +## Demos + +[BTC-SIGN](internal/demos/signbtc/main/main.go) [DOGECOIN-SIGN](internal/demos/signdoge/main/main.go) + +--- + +## Notes + +1. **Private Key Security**: Never expose private keys in production environments. Only use demo data for development or testing purposes. +2. **Fee Settings**: Set transaction fees reasonably based on size and network congestion to avoid rejection by miners. +3. **Change Address**: Ensure leftover funds are returned to your address as change to avoid loss of funds. +4. **Network Configuration**: Properly configure network parameters (e.g., `chaincfg.TestNet3Params`) for TestNet or MainNet usage. + +--- + +## Getting Started with Bitcoin (BTC) + +Using `gobtcsign`, here is a simple introduction to Bitcoin (`BTC`): + +### Step 1 - Create a Wallet + +Create a test wallet using **offline tools**. For example, see [create_wallet_test.go](create_wallet_test.go). + +Avoid using online tools to create wallets, as they could expose your private key to unauthorized parties. + +Blockchain wallets are created offline, and you can use any offline tool you prefer for this purpose. **Generating a private key online is insecure and should be avoided.** + +### Step 2 - Obtain Test Coins from a Faucet + +Look for a Bitcoin faucet online to receive some test coins. This will provide the UTXOs you need for transaction testing. + +### Step 3 - Sign and Send a Transaction + +Once you have UTXOs from the faucet, you can construct and send transactions. + +In practice, you need extra features like block crawling to automatically get your UTXOs. Without these features, you can't fully automate sending transactions. + +You can use blockchain explorers and program code to send transactions manually, while for automated transactions, block crawling is required. + +### Additional - Use DOGE to Learn BTC -创建个测试钱包即可 +Since Dogecoin (DOGE) is derived from Litecoin (LTC), which itself is derived from Bitcoin (BTC), this library also supports DOGE signing. -注意不要使用在线的网页创建钱包,否则私钥容易被别人收集。 +While Litecoin signing hasn't been tested, you can try it if you want. -## 第二步找水龙头 +DOGE provides an excellent environment for learning due to its faster block times, allowing 6-block confirmation in just a few minutes. This makes testing and iteration more efficient compared to BTC, which requires around an hour for 6-block confirmation. -测试币水头龙,在网上多找找总会有的,让水龙头给自己弄点测试币,这样自己就有了所谓的UTXO啦 +BTC has richer resources and greater adoption, making it more beneficial for learning blockchain concepts. Since DOGE mimics BTC, testing DOGE logic can often reveal BTC-related issues. Supporting both BTC and DOGE is a practical choice for developers. -## 第三步尝试签名和发个交易 +### Important - Don’t Forget Change Outputs -通过水龙头给的UTXO就可以发交易 +Forgetting to include change outputs can lead to significant losses. Here is an example: -当然实际上还是需要你具备其它能力,比如爬块技术,这样才能得到你的UTXO,否则还是不能发交易的 +- Transaction at block height **818087** +- Hash: `b5a2af5845a8d3796308ff9840e567b14cf6bb158ff26c999e6f9a1f5448f9aa` +- The sender transferred **139.42495946 BTC** (worth $5,217,651), but the recipient only received **55.76998378 BTC** (worth $2,087,060). +- The remaining **83.65497568 BTC** (worth $3,130,590) was lost as miner fees. -通过区块链浏览器,以及代码,能够手动发交易,但自动化发交易还是依赖于爬块。 +This is a mistake that would be deeply regrettable and must be avoided. -## 其它 +--- -由于狗狗币是通过LTC衍生来的,而LTC是通过BTC衍生来的,因此这个包也能用于狗狗币的签名 +## DISCLAIMER -至于莱特币签名,没有尝试过,或许也是可以的 +Crypto coin, at its core, is nothing but a scam. It thrives on the concept of "air coins"—valueless digital assets—to exploit the hard-earned wealth of ordinary people, all under the guise of innovation and progress. This ecosystem is inherently devoid of fairness or justice. -该包中有些狗狗币签名的样例,这是因为狗狗币的出块速度快,只几分钟就能达到6个块的确认高度,做实验或者测试相对比较便捷。 -而BTC的确认达到6个块需要1小时甚至更久些,在做开发时就不太方便测试和迭代逻辑。 -但BTC的资料多些,也更主流,有利于学习区块链相关的知识。 -DOGE纯的模仿BTC的,逻辑99%都是互通的,因此在开发时,测试DOGE逻辑也能发现BTC的问题。 -因此同时接BTC+DOGECOIN也是不错的选择。 +For the elderly, cryptocurrencies present significant challenges and risks. The so-called "high-tech" façade often excludes them from understanding or engaging with these tools. Instead, they become easy targets for financial exploitation, stripped of the resources they worked a lifetime to accumulate. -## 注意 +The younger generation faces a different but equally insidious issue. By the time they have the opportunity to engage, the early adopters have already hoarded the lion’s share of resources. The system is inherently tilted, offering little chance for new entrants to gain a fair footing. -该项目几乎没有引用除 `github.com/btcsuite` 以外的其它包,但假如您要签名交易时,依然不应该直接使用该项目,避免添加恶意代码收集您的私钥。正确的做法是fork项目,最正确的做法是拷贝代码到自己的项目里,而不要引用其他项目,而且要严格审查代码。 +The idea that cryptocurrencies like BTC, ETH, or TRX could replace global fiat currencies is nothing more than a pipe dream. This notion serves only as the shameless fantasy of early adopters, particularly those from the 1980s generation, who hoarded significant amounts of crypto coin before the general public even had an opportunity to participate. -注意不要忘记找零否则将会有重大损失,详见下面的案例。 +Ask yourself this: would someone holding thousands, or even tens of thousands, of Bitcoin ever genuinely believe the system is fair? The answer is unequivocally no. These systems were never designed with fairness in mind but rather to entrench the advantages of a select few. -发送方发送了139.42495946 BTC,价值5,217,651美元,而接收方仅收到了55.76998378 BTC,价值2,087,060美元。 -剩余的83.65497568 BTC则是矿工费用,价值3,130,590美元。 -这笔交易发生在区块高度818087里面。 -哈希值:b5a2af5845a8d3796308ff9840e567b14cf6bb158ff26c999e6f9a1f5448f9aa +The rise of cryptocurrencies is not the endgame. It is inevitable that new innovations will emerge, replacing these deeply flawed systems. At this moment, my interest lies purely in understanding the underlying technology—nothing more, nothing less. -这是一笔巨大的损失,需要特别重视,避免重蹈覆辙。 +This project exists solely for the purpose of technical learning and exploration. The author of this project maintains a firm and unequivocal stance of *staunch resistance to cryptocurrencies*. -## 其它的免责声明 +--- -数字货币都是骗局 +## License -都是以空气币掠夺平民财富 +`gobtcsign` is open-source and released under the MIT License. See the [LICENSE](LICENSE) file for more information. -没有公平正义可言 +--- -数字货币对中老年人是极不友好的,因为他们没有机会接触这类披着高科技外衣的割韭菜工具 +## Support -数字货币对青少年也是极不友好的,因为当他们接触的时候,前面的人已经占据了大量的资源 +Welcome to contribute to this project by submitting pull requests or reporting issues. -因此妄图以数字货币,比如稍微主流的 BTC ETH TRX 代替世界货币的操作,都是不可能实现的 +If you find this package helpful, give it a star on GitHub! -都不过是早先持有数字货币的八零后们的无耻幻想 +**Thank you for your support!** -扪心自问,持有几千甚至数万个比特币的人会觉得公平吗,其实不会的 +**Happy Coding with `gobtcsign`!** 🎉 -因此未来还会有新事物来代替它们,而我现在也不过只是了解其中的技术,仅此而已。 +Give me stars. Thank you!!! -该项目作者坚定持有“坚决抵制数字货币”的立场。 +## See stars +[![see stars](https://starchart.cc/yyle88/gobtcsign.svg?variant=adaptive)](https://starchart.cc/yyle88/gobtcsign) diff --git a/README.zh.md b/README.zh.md new file mode 100644 index 0000000..3e1dcf3 --- /dev/null +++ b/README.zh.md @@ -0,0 +1,167 @@ +# gobtcsign + +`gobtcsign` 简洁高效的比特币交易签名工具库,帮助开发者快速构建、签名和验证比特币交易。 + +`gobtcsign` 使用 golang 进行 BTC/DOGECOIN 签名,能帮助开发者探索 BTC 区块链知识。 + +--- + +## 英文文档 + +[ENGLISH README](README.md) + +--- + +## 安装 + +```bash +go get github.com/yyle88/gobtcsign +``` + +--- + +## 功能概述 + +以下是 `gobtcsign` 提供的核心功能: + +1. **交易构建**:提供高效的交易构建工具,支持添加多个输入输出,并自动计算找零金额。通过动态手续费调整功能,用户可以灵活控制交易费用。 +2. **交易大小预估**:依据输入、输出数量及脚本类型,预估交易的虚拟大小(vSize)。这有助于开发者根据实际情况设置合适的手续费率。 +3. **交易签名**:兼容多种地址类型,包括 P2PKH、P2SH 和 SegWit。开发者可以使用私钥快速完成交易输入的签名。 +4. **签名验证**:提供签名校验功能,确保交易签名的正确性,避免因签名问题导致交易被网络拒绝。 +5. **交易序列化**:支持将签名后的交易序列化为十六进制字符串,便于直接广播至比特币网络。 + +--- + +## 依赖模块 + +以下是 `gobtcsign` 依赖的关键模块: + +- **github.com/btcsuite/btcd**:提供比特币核心协议的实现,是构建和解析交易的基础。 +- **github.com/btcsuite/btcd/btcec/v2**:用于椭圆曲线加密操作和密钥管理,支持生成和验证数字签名。 +- **github.com/btcsuite/btcd/btcutil**:处理比特币地址的编码与解码操作,并提供其他常用的比特币实用工具。 +- **github.com/btcsuite/btcd/chaincfg/chainhash**:提供哈希计算和链相关的常用功能。 +- **github.com/btcsuite/btcwallet/wallet/txauthor**:用于构建交易的输入输出,并自动处理找零。 +- **github.com/btcsuite/btcwallet/wallet/txrules**:定义比特币交易规则,包括最小手续费计算和其他限制条件。 +- **github.com/btcsuite/btcwallet/wallet/txsizes**:用于计算交易的虚拟大小(vSize),便于动态调整手续费。 + +该项目几乎没有引用除 `github.com/btcsuite` 以外的其它包,即便如此,当您要签名交易时,依然不应该直接使用该项目,避免添加恶意代码收集您的私钥。正确的做法是fork项目,最正确的做法是拷贝代码到自己的项目里,而不要引用不可信的依赖,而且要严格审查代码,控制服务器的出入网白名单。 + +--- + +## 使用步骤 + +1. **初始化交易参数**:定义交易输入(UTXO)、输出目标地址及金额,同时设置 RBF(Replace-By-Fee)选项。 +2. **预估交易大小与手续费**:调用库中的方法估算交易大小,依据实时费率设置合理的手续费。 +3. **生成待签名交易**:根据输入的交易参数,构建待签名交易。 +4. **签名交易**:使用对应私钥完成交易的数字签名。 +5. **验证与序列化**:验证签名的有效性,并将交易序列化为十六进制字符串以供广播。 + +--- + +## 基本样例 + +[给比特币签名](internal/demos/signbtc/main/main.go) [给狗狗币签名](internal/demos/signdoge/main/main.go) + +--- + +## 注意事项 + +1. **私钥安全性**:请勿在生产环境中暴露私钥,仅在开发或测试环境中使用演示数据。 +2. **手续费设置**:根据交易大小和网络拥堵情况合理设置手续费,避免交易因手续费过低被矿工拒绝。 +3. **找零地址**:在构建交易时,请确保将剩余金额转回自己的地址作为找零,以避免资金损失。 +4. **网络参数**:在使用 TestNet 或 MainNet 时,请正确配置网络参数(如 `chaincfg.TestNet3Params`)。 + +--- + +通过 `gobtcsign`,开发者可以快速高效地实现比特币交易相关功能,助力区块链应用开发。 + +--- + +## 比特币入门教程 + +通过 `gobtcsign` 简单介绍比特币 `BTC` 的入门知识,以下是个简单的入门教程。 + +### 第一步-创建钱包 + +使用任意 **离线的代码** 创建测试钱包。 例如使用代码 [创建钱包](create_wallet_test.go) + +注意不要使用在线的网页创建钱包,否则私钥容易被别人悄悄收集。 + +区块链的钱包创建是离线的,你能使用任意你觉得趁手的离线工具生成你的钱包(任何通过网页在线创建私钥的行为都是耍流氓) + +### 第二步-找水龙头 + +测试币水头龙,在网上多找找总会有的,让水龙头给自己弄点测试币,这样自己就有了所谓的UTXO啦 + +### 第三步-尝试签名和发个交易 + +通过水龙头给的UTXO就可以发交易 + +当然实际上还是需要你具备其它能力,比如爬块技术,这样才能得到你的UTXO,否则还是不能发交易的 + +通过区块链浏览器 和 程序代码,你能够手动发交易,但自动化发交易还是依赖于爬块。 + +### 其它的-使用狗狗币学习BTC + +由于狗狗币是通过LTC衍生来的,而LTC是通过BTC衍生来的,因此这个包也能用于狗狗币的签名 + +至于莱特币签名,没有尝试过,假如需要就试试看吧。 + +该包中有些狗狗币签名的样例,这是因为狗狗币的出块速度快,只几分钟就能达到6个块的确认高度,做实验或者测试相对比较便捷。 +而BTC的确认达到6个块需要1小时甚至更久些,在做开发时就不太方便测试和迭代逻辑。 +但BTC的资料多些,也更主流,有利于学习区块链相关的知识。 +DOGE纯的模仿BTC的,逻辑99%都是互通的,因此在开发时,测试DOGE逻辑也能发现BTC的问题。 +因此同时接BTC+DOGECOIN也是不错的选择。 + +### 特别的-注意不要遗漏找零输出 + +注意不要忘记找零否则将会有重大损失,详见下面的案例。 + +这笔交易发生在区块高度818087里面。 +哈希值:b5a2af5845a8d3796308ff9840e567b14cf6bb158ff26c999e6f9a1f5448f9aa +发送方发送了139.42495946 BTC,价值5,217,651美元,而接收方仅收到了55.76998378 BTC,价值2,087,060美元。 +剩余的83.65497568 BTC则是矿工费用,价值3,130,590美元。 + +这是一笔巨大的损失,需要特别重视,避免重蹈覆辙。 + +## 免责声明: + +数字货币都是骗局 + +都是以空气币掠夺平民财富 + +没有公平正义可言 + +数字货币对中老年人是极不友好的,因为他们没有机会接触这类披着高科技外衣的割韭菜工具 + +数字货币对青少年也是极不友好的,因为当他们接触的时候,前面的人已经占据了大量的资源 + +因此妄图以数字货币,比如稍微主流的 BTC ETH TRX 代替世界货币的操作,都是不可能实现的 + +都不过是早先持有数字货币的八零后们的无耻幻想 + +扪心自问,持有几千甚至数万个比特币的人会觉得公平吗,其实不会的 + +因此未来还会有新事物来代替它们,而我现在也不过只是了解其中的技术,仅此而已。 + +该项目仅以技术学习和探索为目的而存在。 + +该项目作者坚定持有“坚决抵制数字货币”的立场。 + +--- + +## 许可 + +`gobtcsign` 是一个开源项目,发布于 MIT 许可证下。有关更多信息,请参阅 [LICENSE](LICENSE) 文件。 + +## 贡献与支持 + +欢迎通过提交 pull request 或报告问题来贡献此项目。 + +如果你觉得这个包对你有帮助,请在 GitHub 上给个 ⭐,感谢支持!!! + +**感谢你的支持!** + +**祝编程愉快!** 🎉 + +Give me stars. Thank you!!! diff --git a/param_test.go b/param_test.go index 0661672..dee87c1 100644 --- a/param_test.go +++ b/param_test.go @@ -88,8 +88,8 @@ func TestCustomParam_VerifyMsgTxSign(t *testing.T) { netParams := chaincfg.TestNet3Params - preMap := NewOutPointUtxoSenderAmountMap(map[wire.OutPoint]*UtxoSenderAmount{ - *MustNewOutPoint("a06b4450eb63c8a245e799800e7554ef7a25d415874a957720bee79be8fc15e2", 1): NewUtxoSenderAmount(NewAddressTuple("tb1q92kpf4hlj5khmdalshlz6602lvs8vcakxz8hzq"), 49560582), + preMap := NewSenderAmountUtxoCache(map[wire.OutPoint]*SenderAmountUtxo{ + *MustNewOutPoint("a06b4450eb63c8a245e799800e7554ef7a25d415874a957720bee79be8fc15e2", 1): NewSenderAmountUtxo(NewAddressTuple("tb1q92kpf4hlj5khmdalshlz6602lvs8vcakxz8hzq"), 49560582), }) param, err := NewCustomParamFromMsgTx(msgTx, preMap) @@ -112,9 +112,9 @@ func TestCustomParam_CheckMsgTxParam(t *testing.T) { netParams := dogecoin.MainNetParams - preMap := NewOutPointUtxoSenderAmountMap(map[wire.OutPoint]*UtxoSenderAmount{ - *MustNewOutPoint("cb15601f24db291d1ca679e769de5c10891fcd1f2b1257680813986bedda81b8", 0): NewUtxoSenderAmount(NewAddressTuple("D9taZdfvonxSn8USudmhqhwvE7wt3aPW79"), 9230995), - *MustNewOutPoint("90676039a3c6fefb32dfadcaed501d6c20c1d0dcbf4071487e35f22b8097b099", 2): NewUtxoSenderAmount(NewAddressTuple("D9taZdfvonxSn8USudmhqhwvE7wt3aPW79"), 437264864), + preMap := NewSenderAmountUtxoCache(map[wire.OutPoint]*SenderAmountUtxo{ + *MustNewOutPoint("cb15601f24db291d1ca679e769de5c10891fcd1f2b1257680813986bedda81b8", 0): NewSenderAmountUtxo(NewAddressTuple("D9taZdfvonxSn8USudmhqhwvE7wt3aPW79"), 9230995), + *MustNewOutPoint("90676039a3c6fefb32dfadcaed501d6c20c1d0dcbf4071487e35f22b8097b099", 2): NewSenderAmountUtxo(NewAddressTuple("D9taZdfvonxSn8USudmhqhwvE7wt3aPW79"), 437264864), }) param, err := NewCustomParamFromMsgTx(msgTx, preMap) @@ -137,10 +137,10 @@ func TestCustomParam_CheckMsgTxParam_BTC(t *testing.T) { netParams := chaincfg.MainNetParams - preMap := NewOutPointUtxoSenderAmountMap(map[wire.OutPoint]*UtxoSenderAmount{ - *MustNewOutPoint("d19f5a52f98c4bbc25421913ba450c48b3ccd56d042518c92bb6e9656b65e648", 1): NewUtxoSenderAmount(NewAddressTuple("bc1qvuhjmgfr4kxye8eh63qvkv3yst950u8mye9fxh"), 700623171), - *MustNewOutPoint("d8824a43740567d0f1f7e214462ceb0494142194d1b8a55b66b1276edfaa88c0", 0): NewUtxoSenderAmount(NewAddressTuple("bc1q963tmm9tv9884k60puxc3syyld0xzte3duy9uc"), 17232), - *MustNewOutPoint("f9999fb7df8edf4d6aa95189bb6eccabde67d99657ee5e1dc93e3f1f815841cb", 0): NewUtxoSenderAmount(NewAddressTuple("bc1q963tmm9tv9884k60puxc3syyld0xzte3duy9uc"), 17191), + preMap := NewSenderAmountUtxoCache(map[wire.OutPoint]*SenderAmountUtxo{ + *MustNewOutPoint("d19f5a52f98c4bbc25421913ba450c48b3ccd56d042518c92bb6e9656b65e648", 1): NewSenderAmountUtxo(NewAddressTuple("bc1qvuhjmgfr4kxye8eh63qvkv3yst950u8mye9fxh"), 700623171), + *MustNewOutPoint("d8824a43740567d0f1f7e214462ceb0494142194d1b8a55b66b1276edfaa88c0", 0): NewSenderAmountUtxo(NewAddressTuple("bc1q963tmm9tv9884k60puxc3syyld0xzte3duy9uc"), 17232), + *MustNewOutPoint("f9999fb7df8edf4d6aa95189bb6eccabde67d99657ee5e1dc93e3f1f815841cb", 0): NewSenderAmountUtxo(NewAddressTuple("bc1q963tmm9tv9884k60puxc3syyld0xzte3duy9uc"), 17191), }) param, err := NewCustomParamFromMsgTx(msgTx, preMap) @@ -163,10 +163,10 @@ func TestCustomParam_CheckMsgTxParam_BTC_TXN(t *testing.T) { netParams := chaincfg.MainNetParams - preMap := NewOutPointUtxoSenderAmountMap(map[wire.OutPoint]*UtxoSenderAmount{ - *MustNewOutPoint("56ee8ace223a6fb8585458866ba2a5c5d620ccf968eca3060c28e1033f198c89", 6): NewUtxoSenderAmount(NewAddressTuple("bc1qrhut5t4g2wa2lf9h48fcth869h48khe0avxlq60vs5m0y2s8memq6fjt7r"), 31759346), - *MustNewOutPoint("840bdc8d28b5919ea2a8f19e9993109ebd297f5279a6e00e70278684ec187d8d", 3): NewUtxoSenderAmount(NewAddressTuple("bc1qlhqvxmpcqqw64r82h7sm89hn5n8g9p7w6y2m7cxkvtytavgthups57rnws"), 16616320), - *MustNewOutPoint("c57c06e4f2207420663fdf3d8a92a5a5755da469591b9677a03bc8247b36f590", 2): NewUtxoSenderAmount(NewAddressTuple("bc1q673p5npwsz78j4vdzqltsa4fvtvpteynudu446n56xrpy38tgrkqjmzwpk"), 15201401), + preMap := NewSenderAmountUtxoCache(map[wire.OutPoint]*SenderAmountUtxo{ + *MustNewOutPoint("56ee8ace223a6fb8585458866ba2a5c5d620ccf968eca3060c28e1033f198c89", 6): NewSenderAmountUtxo(NewAddressTuple("bc1qrhut5t4g2wa2lf9h48fcth869h48khe0avxlq60vs5m0y2s8memq6fjt7r"), 31759346), + *MustNewOutPoint("840bdc8d28b5919ea2a8f19e9993109ebd297f5279a6e00e70278684ec187d8d", 3): NewSenderAmountUtxo(NewAddressTuple("bc1qlhqvxmpcqqw64r82h7sm89hn5n8g9p7w6y2m7cxkvtytavgthups57rnws"), 16616320), + *MustNewOutPoint("c57c06e4f2207420663fdf3d8a92a5a5755da469591b9677a03bc8247b36f590", 2): NewSenderAmountUtxo(NewAddressTuple("bc1q673p5npwsz78j4vdzqltsa4fvtvpteynudu446n56xrpy38tgrkqjmzwpk"), 15201401), }) param, err := NewCustomParamFromMsgTx(msgTx, preMap) diff --git a/signbtc.go b/signbtc.go index 1991d71..ff4caf7 100644 --- a/signbtc.go +++ b/signbtc.go @@ -52,7 +52,7 @@ func Sign(senderAddress string, privateKeyHex string, param *SignParam) error { return errors.WithMessage(err, "wrong sign") } default: //其它钱包类型暂不支持 - return errors.Errorf("From地址 %s 属于 %s 类型, 类型错误", address, reflect.TypeOf(address).String()) //倒是没必要支持太多的类型 + return errors.Errorf("wrong from address=%s address_type=%s not-support-this-address-type", address, reflect.TypeOf(address).String()) //倒是没必要支持太多的类型 } return nil } diff --git a/target.go b/target.go index 1cb970e..7978591 100644 --- a/target.go +++ b/target.go @@ -8,7 +8,7 @@ import ( ) type AddressTuple struct { - Address string //钱包地址 和 公钥脚本 二选一填写即可 + Address string //钱包地址 和 公钥脚本 二选一填写即可 当 Address 和 PkScript 同时存在时,需要保证匹配 PkScript []byte //公钥脚本 和 钱包地址 二选一填写即可 PkScript(Public Key Script)在拼装交易和签名时使用 } @@ -27,7 +27,7 @@ func (one *AddressTuple) GetPkScript(netParams *chaincfg.Params) ([]byte, error) if err != nil { return nil, errors.WithMessage(err, "wrong-address") } - if bytes.Compare(one.PkScript, pkScript) != 0 { + if !bytes.Equal(one.PkScript, pkScript) { return nil, errors.New("address-pk-script-mismatch") } return pkScript, nil @@ -47,7 +47,7 @@ func (one *AddressTuple) VerifyMatch(netParams *chaincfg.Params) error { if err != nil { return errors.WithMessage(err, "wrong-address") } - if bytes.Compare(one.PkScript, pkScript) != 0 { + if !bytes.Equal(one.PkScript, pkScript) { return errors.New("address-pk-script-mismatch") } } diff --git a/utxo_from.go b/utxo_from.go index 0993b93..7228475 100644 --- a/utxo_from.go +++ b/utxo_from.go @@ -8,60 +8,60 @@ import ( ) type GetUtxoFromInterface interface { - GetUtxoFrom(utxo wire.OutPoint) (*UtxoSenderAmount, error) + GetUtxoFrom(utxo wire.OutPoint) (*SenderAmountUtxo, error) } -type UtxoFromClient struct { +type SenderAmountUtxoClient struct { client *rpcclient.Client } -func NewUtxoFromClient(client *rpcclient.Client) *UtxoFromClient { - return &UtxoFromClient{client: client} +func NewSenderAmountUtxoClient(client *rpcclient.Client) *SenderAmountUtxoClient { + return &SenderAmountUtxoClient{client: client} } -func (uc *UtxoFromClient) GetUtxoFrom(utxo wire.OutPoint) (*UtxoSenderAmount, error) { - preTxn, err := GetRawTransaction(uc.client, utxo.Hash.String()) +func (uc *SenderAmountUtxoClient) GetUtxoFrom(utxo wire.OutPoint) (*SenderAmountUtxo, error) { + previousUtxoTx, err := GetRawTransaction(uc.client, utxo.Hash.String()) if err != nil { - return nil, errors.WithMessage(err, "get-raw-txn") + return nil, errors.WithMessage(err, "get-raw-transaction") } - preOut := preTxn.Vout[utxo.Index] + previousOutput := previousUtxoTx.Vout[utxo.Index] - preAmt, err := btcutil.NewAmount(preOut.Value) + previousAmount, err := btcutil.NewAmount(previousOutput.Value) if err != nil { - return nil, errors.WithMessage(err, "get-pre-amt") + return nil, errors.WithMessage(err, "get-previous-amount") } - utxoFrom := NewUtxoSenderAmount( - NewAddressTuple(preOut.ScriptPubKey.Address), - int64(preAmt), + utxoFrom := NewSenderAmountUtxo( + NewAddressTuple(previousOutput.ScriptPubKey.Address), + int64(previousAmount), ) return utxoFrom, nil } -type UtxoSenderAmount struct { +type SenderAmountUtxo struct { sender *AddressTuple amount int64 } -func NewUtxoSenderAmount(sender *AddressTuple, amount int64) *UtxoSenderAmount { - return &UtxoSenderAmount{ +func NewSenderAmountUtxo(sender *AddressTuple, amount int64) *SenderAmountUtxo { + return &SenderAmountUtxo{ sender: sender, amount: amount, } } -type OutPointUtxoSenderAmountMap struct { - mxp map[wire.OutPoint]*UtxoSenderAmount +type SenderAmountUtxoCache struct { + outputUtxoMap map[wire.OutPoint]*SenderAmountUtxo } -func NewOutPointUtxoSenderAmountMap(mxp map[wire.OutPoint]*UtxoSenderAmount) *OutPointUtxoSenderAmountMap { - return &OutPointUtxoSenderAmountMap{mxp: mxp} +func NewSenderAmountUtxoCache(utxoMap map[wire.OutPoint]*SenderAmountUtxo) *SenderAmountUtxoCache { + return &SenderAmountUtxoCache{outputUtxoMap: utxoMap} } -func (uc OutPointUtxoSenderAmountMap) GetUtxoFrom(utxo wire.OutPoint) (*UtxoSenderAmount, error) { - utxoFrom, ok := uc.mxp[utxo] +func (uc SenderAmountUtxoCache) GetUtxoFrom(utxo wire.OutPoint) (*SenderAmountUtxo, error) { + utxoFrom, ok := uc.outputUtxoMap[utxo] if !ok { - return nil, errors.Errorf("not-exist-utxo[%s:%d]", utxo.Hash.String(), utxo.Index) + return nil, errors.Errorf("wrong utxo[%s:%d] not-exist-in-cache", utxo.Hash.String(), utxo.Index) } return utxoFrom, nil } diff --git a/utxo_from_test.go b/utxo_from_test.go index 0d152b1..11a7b1c 100644 --- a/utxo_from_test.go +++ b/utxo_from_test.go @@ -5,9 +5,9 @@ import ( ) func TestUtxoFromClient_GetUtxoFrom(t *testing.T) { - var _ GetUtxoFromInterface = &UtxoFromClient{} + var _ GetUtxoFromInterface = &SenderAmountUtxoClient{} } -func TestUtxoFromOutMap_GetUtxoFrom(t *testing.T) { - var _ GetUtxoFromInterface = &OutPointUtxoSenderAmountMap{} +func TestSenderAmountUtxoCache_GetUtxoFrom(t *testing.T) { + var _ GetUtxoFromInterface = &SenderAmountUtxoCache{} } diff --git a/verifytx.go b/verifytx.go index a02482c..2f55712 100644 --- a/verifytx.go +++ b/verifytx.go @@ -21,54 +21,11 @@ func NewVerifyTxInputParam(senderAddress string, amount int64) *VerifyTxInputPar } } -func NewVerifyTxInputNotAmountParams(senders []string, netParams *chaincfg.Params) ([]*VerifyTxInputParam, error) { - var results = make([]*VerifyTxInputParam, 0, len(senders)) - - var a2pksMap = make(map[string][]byte, len(senders)) - for _, address := range senders { - pkScript, ok := a2pksMap[address] - if !ok { - pks, err := GetAddressPkScript(address, netParams) - if err != nil { - return nil, errors.WithMessage(err, "cannot get pk-script") - } - a2pksMap[address] = pks - pkScript = pks - } - - results = append(results, &VerifyTxInputParam{ - Sender: AddressTuple{ - Address: address, - PkScript: pkScript, - }, - Amount: 0, //绝大多数的签名,比如,P2PKH 签名,不将 amount 包含在生成的签名哈希中,因此也不验证它,随便填都行 - }) - } - return results, nil -} - type VerifyTxInputsType struct { PkScripts [][]byte InAmounts []btcutil.Amount } -func NewVerifyTxInputsType(inputList []*VerifyTxInputParam, netParams *chaincfg.Params) (*VerifyTxInputsType, error) { - var pkScripts = make([][]byte, 0, len(inputList)) - var inAmounts = make([]btcutil.Amount, 0, len(inputList)) - for idx := range inputList { - pkScript, err := inputList[idx].Sender.GetPkScript(netParams) - if err != nil { - return nil, errors.WithMessage(err, "wrong address->pk-script") - } - pkScripts = append(pkScripts, pkScript) - inAmounts = append(inAmounts, btcutil.Amount(inputList[idx].Amount)) - } - return &VerifyTxInputsType{ - PkScripts: pkScripts, - InAmounts: inAmounts, - }, nil -} - /* VerifyP2PKHSign 验证签名是否有效,只有P2PKH的验证可以不验证数量,因此这里写个简易的函数,以便在需要的时候能够快速派上用场 @@ -83,11 +40,29 @@ VerifyP2PKHSign 验证签名是否有效,只有P2PKH的验证可以不验证 因此这里就是给的utxo的来源地址列表(按正确顺序排列,而且条数要相同)。 */ func VerifyP2PKHSign(msgTx *wire.MsgTx, senders []string, netParams *chaincfg.Params) error { - inputList, err := NewVerifyTxInputNotAmountParams(senders, netParams) - if err != nil { - return errors.WithMessage(err, "wrong new-input-params") + var results = make([]*VerifyTxInputParam, 0, len(senders)) + + var pksCache = make(map[string][]byte, len(senders)) + for _, address := range senders { + pkScript, ok := pksCache[address] + if !ok { + script, err := GetAddressPkScript(address, netParams) + if err != nil { + return errors.WithMessage(err, "cannot get pk-script") + } + pksCache[address] = script + pkScript = script + } + + results = append(results, &VerifyTxInputParam{ + Sender: AddressTuple{ + Address: address, + PkScript: pkScript, + }, + Amount: 0, //绝大多数的签名,比如,P2PKH 签名,不将 amount 包含在生成的签名哈希中,因此也不验证它,随便填都行,因此这里填0 + }) } - return VerifySignV2(msgTx, inputList, netParams) + return VerifySignV2(msgTx, results, netParams) } /* @@ -109,11 +84,20 @@ VerifySignV2 验证签名是否有效,同样的逻辑实现第二遍是为了 NewSigCache 创建的缓存通常不需要显式关闭或清理。它是一个内存中的数据结构,生命周期与其所在的应用程序或模块相同。 */ func VerifySignV2(msgTx *wire.MsgTx, inputList []*VerifyTxInputParam, netParams *chaincfg.Params) error { - inputsItem, err := NewVerifyTxInputsType(inputList, netParams) - if err != nil { - return errors.WithMessage(err, "wrong params-to-inputs") + var pkScripts = make([][]byte, 0, len(inputList)) + var inAmounts = make([]btcutil.Amount, 0, len(inputList)) + for idx := range inputList { + pkScript, err := inputList[idx].Sender.GetPkScript(netParams) + if err != nil { + return errors.WithMessage(err, "wrong address->pk-script") + } + pkScripts = append(pkScripts, pkScript) + inAmounts = append(inAmounts, btcutil.Amount(inputList[idx].Amount)) } - return VerifySignV3(msgTx, inputsItem) + return VerifySignV3(msgTx, &VerifyTxInputsType{ + PkScripts: pkScripts, + InAmounts: inAmounts, + }) } func VerifySignV3(msgTx *wire.MsgTx, inputsItem *VerifyTxInputsType) error {