MetacoSA / NBitcoin

Comprehensive Bitcoin library for the .NET framework.
MIT License
1.86k stars 840 forks source link

Using HSM as an external signer #1122

Closed r-anjirabi closed 1 year ago

r-anjirabi commented 1 year ago

I have implemented signing transactions with HSM as a private key storage, but i encounter some errors!

`
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))
            .SubtractFees()
            .SendEstimatedFees(new FeeRate(1m))
            .SetChange(sender.ScriptPubKey)
            .BuildTransaction(false);

        var wallet = HSMWallet.Load(walletName);
        var signedTx = wallet.SignTransaction(tx.ToHex());

        var pubKey = new NBitcoin.PubKey(wallet.PubKey.RawPubKey);
        var signature = new TransactionSignature(Encoders.Hex.DecodeData(signedTx));
        builder
            .AddKnownSignature(pubKey, signature, unspentCoins[0].Outpoint)
            .SetSigningOptions(SigHash.All);

        builder.SignTransactionInPlace(tx);

        var error = builder.Check(tx);

        var verify = builder.Verify(tx);
        if (verify)
        {
            var result = await BroadcastTransaction(tx.ToHex(), Settings.BlockcypherToken, network);
            return result;

        }`

check method (builder.Check(tx)) i get two errors :

  1. {Script error on input 0 (SigHashType)}
  2. {Fee too low, actual is 0.00000226, policy minimum is 0.00000258}

i use PKCS11Introp.Net and connect to Utimaco HSM for generate key pair and signing operation:

` public string SignTransaction(string hex) { var mechanism = Session.Factories.MechanismFactory.Create(CKM.CKM_ECDSA);

        var dataHash = Digest(Digest(Convert.FromHexString(hex), CKM.CKM_SHA256), CKM.CKM_SHA256);

        var signature = Session.Sign(mechanism, PrivateKeyHandle, dataHash);

        Session.Verify(mechanism, PublicKeyHandle, dataHash, signature, out bool isValid);

        if (isValid == false)
            throw new Exception("error in signing transaction!");

        return ConvertUtils.BytesToHexString(ConstructEcdsaSigValue(signature));

    }`

How can I make this work? Thanks a lot

r-anjirabi commented 1 year ago

finally it solved, thanks to NBitcoin. share my solution:

`public async Task<string> MakePayment(string walletName, string receiverAddress, decimal amount)
        {
            var network = Settings.Network == BitcoinNetwork.TestNet ? Network.TestNet : Network.Main;
            var hsmWallet = HSMWallet.Load(walletName, Session);
            var pubKey = new NBitcoin.PubKey(hsmWallet.PubKey.RawPubKey);

            var destination = BitcoinAddress.Create(receiverAddress, network);
            var sender = pubKey.GetAddress(ScriptPubKeyType.Legacy, network).ToString();

            var unspentCoins = GetUnSpentCoins(sender, network) ?? throw new Exception("no more mony to spend!");

            var builder = network.CreateTransactionBuilder();
            var unsignedTx = builder
                .AddCoins(unspentCoins)
                .Send(destination.ScriptPubKey, Money.Coins(amount))
                .SendEstimatedFees(new FeeRate(2m))
                .SetChange(pubKey)
                .BuildTransaction(sign: false);

            var rebuild = network.CreateTransactionBuilder();
            rebuild.AddCoins(unspentCoins);

            foreach (var coin in unspentCoins)
            {
                var indexedIn = unsignedTx.Inputs.FindIndexedInput(coin.Outpoint);
                if (indexedIn == null) continue;
                var signHash = indexedIn.GetSignatureHash(coin, SigHash.All);
                var sign = hsmWallet.SignTransaction(signHash.ToBytes());

                var signature = new TransactionSignature(sign, SigHash.All).MakeCanonical();

                rebuild.AddKnownSignature(pubKey, signature, coin.Outpoint);
            }

            var signedTx = rebuild.SignTransaction(unsignedTx);

            var error = rebuild.Check(signedTx);

            var verify = rebuild.Verify(signedTx);
            if (verify)
            {
                var result = await BroadcastTransaction(signedTx.ToHex(), Settings.BlockcypherToken, network);
                return signedTx.GetHash().ToString();

            }

            return string.Concat(error.Select(e => e.ToString()), ",");
        }

sign method :

public byte[] SignTransaction(byte[] signHash)
        {
            var mechanism = Session.Factories.MechanismFactory.Create(CKM.CKM_ECDSA);

            var signature = Session.Sign(mechanism, PrivateKeyHandle, signHash);

            Session.Verify(mechanism, PublicKeyHandle, signHash, signature, out bool isValid);

            if (isValid == false)
                throw new Exception("error in signing transaction!");

            return ConstructEcdsaSigValue(signature);

        }