使用HSM作为NBitcoin外部签名器时遇签名及手续费错误求助
解决HSM签名比特币交易的两个错误:SigHashType脚本错误与手续费过低
1. 解决SigHashType脚本错误
错误原因
比特币交易签名并非直接对整个交易数据哈希,而是需要针对每个输入生成签名哈希(Signature Hash),并在哈希末尾追加SIGHASH类型字节(默认是SigHash.All对应的0x01)。你的代码直接对整个交易Hex做双重SHA256哈希后签名,未处理SIGHASH类型,导致签名不被交易验证脚本认可;同时构造TransactionSignature时未明确指定SigHashType,也会引发匹配问题。
解决方案
步骤1:修改交易签名流程,生成正确的签名哈希
调用HSM签名前,先用NBitcoin的Transaction.ComputeSignatureHash方法生成对应输入的签名哈希(已包含双重SHA256和SIGHASH字节):
var network = Network.TestNet; var destination = BitcoinAddress.Create(receiverAddress, network); var sender = BitcoinAddress.Create(senderAddress, network); var unspentCoins = await GetUnSpentCoins(senderAddress, network); var builder = network.CreateTransactionBuilder(); var tx = builder .AddCoins(unspentCoins) .Send(destination, Money.Coins(0.00001M)) .SetChange(sender.ScriptPubKey) .BuildTransaction(false); // 生成输入0对应的签名哈希(带SigHash.All标识) var sigHash = tx.ComputeSignatureHash(0, sender.ScriptPubKey, SigHash.All, tx.Inputs[0].ScriptSig); var wallet = HSMWallet.Load(walletName); // 把签名哈希传给HSM,而非整个交易Hex var signedTx = wallet.SignTransaction(Encoders.Hex.EncodeData(sigHash)); // 构造TransactionSignature时明确指定SigHash.All var pubKey = new NBitcoin.PubKey(wallet.PubKey.RawPubKey); var signature = new TransactionSignature(Encoders.Hex.DecodeData(signedTx), SigHash.All); // 添加已知签名后无需再调用SignTransactionInPlace builder.AddKnownSignature(pubKey, signature, unspentCoins[0].Outpoint); var error = builder.Check(tx); var verify = builder.Verify(tx);
步骤2:修改HSM的SignTransaction方法
HSM只需对传入的签名哈希直接签名,无需重复做双重哈希:
public string SignTransaction(string sigHashHex) { var mechanism = Session.Factories.MechanismFactory.Create(CKM.CKM_ECDSA); var sigHashBytes = Convert.FromHexString(sigHashHex); // 直接对签名哈希执行ECDSA签名 var signature = Session.Sign(mechanism, PrivateKeyHandle, sigHashBytes); Session.Verify(mechanism, PublicKeyHandle, sigHashBytes, signature, out bool isValid); if (!isValid) throw new Exception("签名验证失败!"); return ConvertUtils.BytesToHexString(ConstructEcdsaSigValue(signature)); }
2. 解决手续费过低问题
错误原因
代码同时使用SubtractFees()和SendEstimatedFees(new FeeRate(1m))导致逻辑冲突,且设置的1 sat/vbyte费率低于当前测试网的最低手续费要求。
解决方案
步骤1:简化手续费设置逻辑
保留一种手续费计算方式,提高费率至符合网络要求的值(比如2 sat/vbyte):
var tx = builder .AddCoins(unspentCoins) .Send(destination, Money.Coins(0.00001M)) .SendEstimatedFees(new FeeRate(2m)) // 调整为2 sat/vbyte .SetChange(sender.ScriptPubKey) .BuildTransaction(false);
步骤2:手动计算精确手续费(可选)
若需要更精准控制,可手动估算交易大小并计算手续费:
var feeRate = new FeeRate(2m); // 2 sat/vbyte var estimatedSize = builder.EstimateTransactionSize(); var requiredFee = feeRate.GetFee(estimatedSize); var tx = builder .AddCoins(unspentCoins) .Send(destination, Money.Coins(0.00001M)) .SendFees(requiredFee) .SetChange(sender.ScriptPubKey) .BuildTransaction(false);
内容的提问来源于stack exchange,提问作者Rahmat Anjirabi
相关产品推荐
相关产品推荐

