Peera.

Post

Share your knowledge.

Discussion

How do I send Bitcoin from 1 Taproot Address to Another?

I made a taproot bitcoin address (bech32m) and sent 1 UTXO from a segwit non-taproot address (bech32) to this taproot address. How would I write a c# script to send this btc to another taproot address? I don't need anything too complicated, just a simple script to send this btc from one taproot address to another. I think NBitcoin would be the most sensible package to use.

I found this in one of the NBitcoin tests:

public async Task CanBuildTaprootSingleSigTransactions()
{
    using (var nodeBuilder = NodeBuilderEx.Create())
    {
        var rpc = nodeBuilder.CreateNode().CreateRPCClient();
        nodeBuilder.StartAll();
        rpc.Generate(102);
        var change = new Key();
        var rootKey = new ExtKey();
        var accountKeyPath = new KeyPath("86'/0'/0'");
        var accountRootKeyPath = new RootedKeyPath(rootKey.GetPublicKey().GetHDFingerPrint(), accountKeyPath);
        var accountKey = rootKey.Derive(accountKeyPath);
        var key = accountKey.Derive(new KeyPath("0/0")).PrivateKey;
        var address = key.PubKey.GetAddress(ScriptPubKeyType.TaprootBIP86, nodeBuilder.Network);
        var destination = new Key();
        var amount = new Money(1, MoneyUnit.BTC);
        uint256 id = null;
        Transaction tx = null;
        ICoin coin = null;
        TransactionBuilder builder = null;
        var rate = new FeeRate(Money.Satoshis(1), 1);

        async Task RefreshCoin()
        {
            id = await rpc.SendToAddressAsync(address, Money.Coins(1));
            tx = await rpc.GetRawTransactionAsync(id);
            coin = tx.Outputs.AsCoins().Where(o => o.ScriptPubKey == address.ScriptPubKey).Single();
            builder = Network.Main.CreateTransactionBuilder(0);
        }
        await RefreshCoin();
        var signedTx = builder
            .AddCoins(coin)
            .AddKeys(key)
            .Send(destination, amount)
            .SubtractFees()
            .SetChange(change)
            .SendEstimatedFees(rate)
            .BuildTransaction(true);
        rpc.SendRawTransaction(signedTx);

        await RefreshCoin();
        // Let's try again, but this time with PSBT
        var psbt = builder
            .AddCoins(coin)
            .Send(destination, amount)
            .SubtractFees()
            .SetChange(change)
            .SendEstimatedFees(rate)
            .BuildPSBT(false);

        var tk = key.PubKey.GetTaprootFullPubKey();
        psbt.Inputs[0].HDTaprootKeyPaths.Add(tk.OutputKey, new TaprootKeyPath(accountRootKeyPath.Derive(KeyPath.Parse("0/0"))));
        psbt.SignAll(ScriptPubKeyType.TaprootBIP86, accountKey, accountRootKeyPath);

        // Check if we can roundtrip
        psbt = CanRoundtripPSBT(psbt);

        psbt.Finalize();
        rpc.SendRawTransaction(psbt.ExtractTransaction());

        // Let's try again, but this time with BuildPSBT(true)
        await RefreshCoin();
        psbt = builder
            .AddCoins(coin)
            .AddKeys(key)
            .Send(destination, amount)
            .SubtractFees()
            .SetChange(change)
            .SendEstimatedFees(rate)
            .BuildPSBT(true);
        psbt.Finalize();
        rpc.SendRawTransaction(psbt.ExtractTransaction());

        // Let's try again, this time with a merkle root
        var merkleRoot = RandomUtils.GetUInt256();
        address = key.PubKey.GetTaprootFullPubKey(merkleRoot).GetAddress(nodeBuilder.Network);

        await RefreshCoin();
        psbt = builder
            .AddCoins(coin)
            .AddKeys(key.CreateTaprootKeyPair(merkleRoot))
            .Send(destination, amount)
            .SubtractFees()
            .SetChange(change)
            .SendEstimatedFees(rate)
            .BuildPSBT(true);
        Assert.NotNull(psbt.Inputs[0].TaprootMerkleRoot);
        Assert.NotNull(psbt.Inputs[0].TaprootInternalKey);
        Assert.NotNull(psbt.Inputs[0].TaprootKeySignature);
        psbt = CanRoundtripPSBT(psbt);
        psbt.Finalize();
        rpc.SendRawTransaction(psbt.ExtractTransaction());

        // Can we sign the PSBT separately?
        await RefreshCoin();
        psbt = builder
            .AddCoins(coin)
            .Send(destination, amount)
            .SubtractFees()
            .SetChange(change)
            .SendEstimatedFees(rate)
            .BuildPSBT(false);

        var taprootKeyPair = key.CreateTaprootKeyPair(merkleRoot);
        psbt.Inputs[0].Sign(taprootKeyPair);
        Assert.NotNull(psbt.Inputs[0].TaprootMerkleRoot);
        Assert.NotNull(psbt.Inputs[0].TaprootInternalKey);
        Assert.NotNull(psbt.Inputs[0].TaprootKeySignature);

        // This line is useless, we just use it to test the PSBT roundtrip
        psbt.Inputs[0].HDTaprootKeyPaths.Add(taprootKeyPair.PubKey,
                                                new TaprootKeyPath(RootedKeyPath.Parse("12345678/86'/0'/0'/0/0"),
                                                new uint256[] { RandomUtils.GetUInt256() }));
        psbt = CanRoundtripPSBT(psbt);
        psbt.Finalize();
        rpc.SendRawTransaction(psbt.ExtractTransaction());

        // Can we sign the transaction separately?
        await RefreshCoin();
        var coin1 = coin;
        await RefreshCoin();
        var coin2 = coin;
        builder = Network.Main.CreateTransactionBuilder(0);
        signedTx = builder
            .AddCoins(coin1, coin2)
            .Send(destination, amount)
            .SubtractFees()
            .SetChange(change)
            .SendEstimatedFees(rate)
            .BuildTransaction(false);
        var unsignedTx = signedTx.Clone();
        builder = Network.Main.CreateTransactionBuilder(0);
        builder.AddKeys(key.CreateTaprootKeyPair(merkleRoot));
        builder.AddCoins(coin1);
        var ex = Assert.Throws<InvalidOperationException>(() => builder.SignTransactionInPlace(signedTx));
        Assert.Contains("taproot", ex.Message);
        builder.AddCoin(coin2);
        builder.SignTransactionInPlace(signedTx);
        Assert.True(!WitScript.IsNullOrEmpty(signedTx.Inputs.FindIndexedInput(coin2.Outpoint).WitScript));
        // Another solution is to set the precomputed transaction data.
        signedTx = unsignedTx;
        builder = Network.Main.CreateTransactionBuilder(0);
        builder.AddKeys(key.CreateTaprootKeyPair(merkleRoot));
        builder.AddCoins(coin2);
        builder.SetSigningOptions(new SigningOptions() { PrecomputedTransactionData = signedTx.PrecomputeTransactionData(new ICoin[] { coin1, coin2 }) });
        builder.SignTransactionInPlace(signedTx);
        Assert.True(!WitScript.IsNullOrEmpty(signedTx.Inputs.FindIndexedInput(coin2.Outpoint).WitScript));


        // Let's check if we estimate precisely the size of a taproot transaction.
        await RefreshCoin();
        signedTx = builder
            .AddCoins(coin)
            .AddKeys(key.CreateTaprootKeyPair(merkleRoot))
            .Send(destination, amount)
            .SubtractFees()
            .SetChange(change)
            .SendEstimatedFees(rate)
            .BuildTransaction(false);
        var actualvsize = builder.EstimateSize(signedTx, true);
        builder.SignTransactionInPlace(signedTx);
        var expectedvsize = signedTx.GetVirtualSize();
        // The estimator can't assume the sighash to be default
        // for all inputs, so we likely overestimate 1 bytes per input
        Assert.Equal(expectedvsize, actualvsize - 1);
    }
}

But I can't figure out how to write a simple C# console app in the format:

class Program
{
    static async Task Main()
    ...

}

I have the private key, as well as the full mnemonic phrase for the private key, of the taproot bitcoin address (bech32m) that I funded with 1 UTXO. How would I write a simple C# console app that spends this UTXO to another taproot address?

And please MIT license your solution to me.

  • blockchain
  • cryptocurrency
0
0
Share
Comments
.

Do you know the answer?

Please log in and share it.

Web3 (also known as Web 3.0) is an idea for a new iteration of the World Wide Web which incorporates concepts such as decentralization, blockchain technologies, and token-based economics.

142Posts198Answers
Sui.X.Peera.

Earn Your Share of 1000 Sui

Gain Reputation Points & Get Rewards for Helping the Sui Community Grow.

Reward CampaignMay
We use cookies to ensure you get the best experience on our website.
More info