Как подписать транзакцию SegWit через NBitcoin?

Большинство руководств просто показывают, как подписать его устаревшим способом. И я знаю, что NBitcoin уже поддерживает SegWit, так есть ли фрагмент о том, как подписать его таким образом?

Ответы (2)

подписание с помощью Segwit ничем не отличается от подписания с помощью P2SH или P2PKH.

Если вам нужна совместимость, вам нужно использовать Segwit, заключенный в P2SH. Пожалуйста, проверьте https://programmingblockchain.gitbooks.io/programmingblockchain/content/other_types_of_ownership/ для получения дополнительной информации.

Но в двух словах, если вы хотите иметь P2WPKH-PS2SH (Segwit P2PKH, завернутый в P2SH для совместимости), то запрашивайте монеты на основе этого адреса.

[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));
}

Николас,

Вы также можете использовать Transaction.Sign(keys, coins), так как это зависит от TransactionBuilder под капотом.

Я тоже застрял на этом и в сочетании с Николасом ответил, что приведенные ниже примеры кода помогли разобраться в моем понимании.

За основу я взял простейший пример segwit из bip-0143 . В приведенном ниже примере кода C# правильно расходуются два входа из приведенной выше примера транзакции. Обратите внимание, что только второй ввод (хэш ef51e...) использует segwit scriptPubKey, поэтому теоретически только coin1 в приведенном ниже примере выполняет трату segwit.

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));

Чтобы перепроверить подписи, я использовал библиотеку биткойн-консенсуса C++, которая создается как часть ядра биткойнов.

#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;
}