Most tutorials out there just show how to sign it in the legacy way. And I know that NBitcoin already supports SegWit, so is there a snippet out there on how to sign it this way?
2 Answers
signing with Segwit is in no way different from signing with P2SH or P2PKH.
If you want interoperability, you need to use Segwit wrapped into P2SH. Please check https://programmingblockchain.gitbooks.io/programmingblockchain/content/other_types_of_ownership/ for more info.
But in a nutshell, if you want to have a P2WPKH-PS2SH (Segwit P2PKH wrapped into P2SH for interoperability), then query the coins based on this address.
[Fact]
[Trait("UnitTest", "UnitTest")]
public void CanGuessRedeemScriptWithInputKeys()
{
var k = new Key();
//This gives you a Bech32 address (currently not really interoperable in wallets, so you need to convert it into P2SH)
var address = k.PubKey.WitHash.GetAddress(Network.Main);
var p2sh = address.GetScriptAddress();
//p2sh is now an interoperable P2SH segwit address
//For spending, it works the same as a a normal P2SH
//You need to get the ScriptCoin, the RedeemScript of you script coin should be k.PubKey.WitHash.ScriptPubKey.
var coins =
//Get coins from any block explorer.
GetCoins(p2sh)
//Nobody knows your redeem script, so you add here the information
//This line is actually optional since 4.0.0.38, as the TransactionBuilder is smart enough to figure out
//the redeems from the keys added by AddKeys.
//However, explicitely having the redeem will make code more easy to update to other payment like 2-2
.Select(c => c.ToScriptCoin(k.PubKey.WitHash.ScriptPubKey))
.ToArray();
TransactionBuilder builder = new TransactionBuilder();
builder.AddCoins(coins);
builder.AddKeys(k);
builder.Send(new Key().ScriptPubKey, Money.Coins(1));
builder.SendFees(Money.Coins(0.001m));
builder.SetChange(p2sh);
var signedTx = builder.BuildTransaction(true);
Assert.True(builder.Verify(signedTx));
}
Nicolas,
-
You can use
Transaction.Sign(keys, coins)
as well, as it depends on TransactionBuilder under the hood. Sep 10, 2017 at 6:51
I got stuck on this as well and combined with Nicolas answer the code samples below are what helped sort out my understanding.
I used the simplest segwit example from bip-0143 as the basis. The c# code sample below correctly spends the two inputs from the sample transaction linked above. Note that only the second input (hash ef51e...) uses a segwit scriptPubKey so theoretically only coin1 in the sample below is doing a segwit spend.
Key txInKey0 = new Key(hex.DecodeData("bbc27228ddcb9209d7fd6f36b02f7dfa6252af40bb2f1cbc7a557da8027ff866"));
Key txInKey1 = new Key(hex.DecodeData("619c335025c7f4012e556c2a58b2506e30b8511b53ade95ea316fd8c3286feb9"));
Key recipient = new Key();
var coin0 = new Coin(uint256.Parse("fff7f7881a8099afa6940d42d1e7f6362bec38171ea3edf433541db4e4ad969f"), 0, Money.FromUnit(6.25m, MoneyUnit.BTC), new Script(hex.DecodeData("2103c9f4836b9a4f77fc0d81f7bcb01b7f1b35916864b9476c241ce9fc198bd25432ac")));
var coin1 = new Coin(uint256.Parse("ef51e1b804cc89d182d279655c3aa89e815b1b309fe287d9b2b55d57b90ec68a"), 1, Money.FromUnit(6.0m, MoneyUnit.BTC), new Script(hex.DecodeData("00141d0f172a0ecb48aee1be1f2687d2963ae33f71a1")));
var builder = new TransactionBuilder();
var tx = builder
.AddCoins(coin0, coin1)
.AddKeys(txInKey0, txInKey1)
.Send(recipient.ScriptPubKey, Money.Coins(12.0m))
.SetChange(txInKey1.ScriptPubKey)
.SendFees(Money.Coins(0.0001m))
.BuildTransaction(true);
Console.WriteLine(builder.Verify(tx));
logger.Debug(tx.ToHex());
logger.Debug(tx.ToString(RawFormat.BlockExplorer));
To double check the signatures I used the C++ bitcoinconsensus library that gets build as part of bitcoin core.
#include <iostream>
#include "bitcoinconsensus.h"
int char2int(char input)
{
if (input >= '0' && input <= '9')
return input - '0';
if (input >= 'A' && input <= 'F')
return input - 'A' + 10;
if (input >= 'a' && input <= 'f')
return input - 'a' + 10;
throw std::invalid_argument("Invalid input string");
}
// This function assumes src to be a zero terminated sanitized string with
// an even number of [0-9a-f] characters, and target to be sufficiently large
void hex2bin(const char* src, unsigned char* target)
{
while (*src && src[1])
{
*(target++) = char2int(*src) * 16 + char2int(src[1]);
src += 2;
}
}
int main()
{
char script0PubKeyHex[] = "00141d0f172a0ecb48aee1be1f2687d2963ae33f71a1";
int script0PubKeyLen = sizeof script0PubKeyHex / 2;
unsigned char *script0PubKey = new unsigned char[script0PubKeyLen];
hex2bin(script0PubKeyHex, script0PubKey);
char script1PubKeyHex[] = "2103c9f4836b9a4f77fc0d81f7bcb01b7f1b35916864b9476c241ce9fc198bd25432ac";
int script1PubKeyLen = sizeof script1PubKeyHex / 2;
unsigned char *script1PubKey = new unsigned char[script1PubKeyLen];
hex2bin(script1PubKeyHex, script1PubKey);
char txSrcHex[] = "010000000001028ac60eb9575db5b2d987e29f301b5b819ea83a5c6579d282d189cc04b8e151ef0100000000ffffffff9f96ade4b41d5433f4eda31e1738ec2b36f6e7d1420d94a6af99801a88f7f7ff0000000048473044022037e0b64b6512e40f0b530420f8b51374e7778812fe642b43c61343d4672aab3402204f39311b20108c3f99d8b0a1b69c92844ccce1d45aeda9d95dcc7f7aed1717d201ffffffff0230517d01000000001976a9141d0f172a0ecb48aee1be1f2687d2963ae33f71a188ac008c864700000000160014b7cd046b6d522a3d61dbcb5235c0e9cc972654570247304402204ca3d7097e7ec004836574879c00059ccb7505de774832525d49c399c67dd77f022059feff13f7d2e808f47619ea4b3392528521a95d4470a9e2a2466cac5f94fe370121025476c2e83188368da1ff3e292e7acafcdb3566bb0ad253f62fc70f07aeee63570000000000";
int txLen = sizeof txSrcHex / 2;
unsigned char * txDest = new unsigned char[txLen];
hex2bin(txSrcHex, txDest);
auto spend0Result = bitcoinconsensus_verify_script_with_amount(script0PubKey, script0PubKeyLen, 600000000, txDest, txLen, 0, bitcoinconsensus_SCRIPT_FLAGS_VERIFY_ALL, &err);
std::cout << "Spend txin[0] result: " << spend0Result << ", error code " << err << std::endl;
auto spend1Result = bitcoinconsensus_verify_script_with_amount(script1PubKey, script1PubKeyLen, 625000000, txDest, txLen, 1, bitcoinconsensus_SCRIPT_FLAGS_VERIFY_ALL, &err);
std::cout << "Spend txin[1] result: " << spend1Result << ", error code " << err << std::endl;
getchar();
return 0;
}