Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add PSBTv2 support #1251

Open
wants to merge 16 commits into
base: master
Choose a base branch
from
2 changes: 1 addition & 1 deletion NBitcoin.Tests/Generators/PSBTGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ from locktime in PrimitiveGenerator.UInt32()
from TxsToAdd in Gen.SubListOf(prevTxs)
from CoinsToAdd in Gen.SubListOf(prevTxs.SelectMany(tx => tx.Outputs.AsCoins()))
from scriptsToAdd in Gen.SubListOf<Script>(scripts)
let psbt = tx.CreatePSBT(network)
let psbt = tx.CreatePSBT(network, PSBTVersion.PSBTv0)
.AddTransactions(prevTxs.ToArray())
.AddCoins(CoinsToAdd.ToArray())
.AddScripts(scriptsToAdd.ToArray())
Expand Down
227 changes: 227 additions & 0 deletions NBitcoin.Tests/PSBT2Tests.cs

Large diffs are not rendered by default.

49 changes: 2 additions & 47 deletions NBitcoin.Tests/PSBTTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -176,53 +176,6 @@ public void CalculateFinalizedHashCorrectly()
Assert.False(psbt.TryGetFinalizedHash(out actualHash));
}

[Fact]
[Trait("UnitTest", "UnitTest")]
public void ShouldPreserveOriginalTxPropertyAsPossible()
{
var keys = new Key[] { new Key(), new Key(), new Key() }.Select(k => k.GetWif(Network.RegTest)).ToArray();
var redeem = PayToMultiSigTemplate.Instance.GenerateScriptPubKey(3, keys.Select(k => k.PubKey).ToArray());
var network = Network.Main;
var funds = CreateDummyFunds(network, keys, redeem);

// 1. without signature nor scripts.
var tx = CreateTxToSpendFunds(funds, keys, redeem, false, false);

// 2. with (unsigned) scriptSig and witness.
tx = CreateTxToSpendFunds(funds, keys, redeem, true, false);
var psbt = PSBT.FromTransaction(tx, Network.Main).AddCoins(funds);
Assert.Null(psbt.Inputs[0].FinalScriptSig); // it is not finalized since it is not signed
Assert.Null(psbt.Inputs[1].FinalScriptWitness); // This too
Assert.NotNull(psbt.Inputs[2].RedeemScript); // But it holds redeem script.
Assert.NotNull(psbt.Inputs[3].WitnessScript); // And witness script.
Assert.NotNull(psbt.Inputs[5].WitnessScript); // even in p2sh-nested-p2wsh
Assert.NotNull(psbt.Inputs[5].RedeemScript);

// 3. with finalized scriptSig and witness
tx = CreateTxToSpendFunds(funds, keys, redeem, true, true);
psbt = PSBT.FromTransaction(tx, Network.Main)
.AddTransactions(funds)
.Finalize();

Assert.Equal(tx.ToHex(), psbt.GetOriginalTransaction().ToHex()); // Check that we can still get the original tx

Assert.NotNull(psbt.Inputs[0].FinalScriptSig); // it should be finalized
Assert.NotNull(psbt.Inputs[0].NonWitnessUtxo);
Assert.NotNull(psbt.Inputs[1].FinalScriptWitness); // p2wpkh too
Assert.NotNull(psbt.Inputs[1].WitnessUtxo);
Assert.Null(psbt.Inputs[2].RedeemScript);
Assert.Null(psbt.Inputs[3].WitnessScript);

Assert.NotNull(psbt.Inputs[4].FinalScriptSig); // Same principle holds for p2sh-nested version.
Assert.NotNull(psbt.Inputs[4].FinalScriptWitness);
Assert.Null(psbt.Inputs[5].WitnessScript);
Assert.Null(psbt.Inputs[5].RedeemScript);

Assert.Empty(psbt.Inputs[2].PartialSigs); // It can not hold partial_sigs
Assert.Empty(psbt.Inputs[3].PartialSigs); // Even in p2wsh
Assert.Empty(psbt.Inputs[5].PartialSigs); // And p2sh-p2wsh
}

[Fact]
[Trait("UnitTest", "UnitTest")]
public void CanUpdate()
Expand Down Expand Up @@ -584,6 +537,8 @@ public void CanHandleUnKnown()
data1.Combine(data2);
var expected = PSBT.Parse((string)testdata["psbtUnknown2"], Network.Main);
Assert.Equal(data1, expected, ComparerInstance);


}
[Fact]
[Trait("UnitTest", "UnitTest")]
Expand Down
7 changes: 5 additions & 2 deletions NBitcoin.Tests/PropertyTest/PSBTSerializationTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,11 @@ public void CanCoinJoin(PSBT a, PSBT b)
var result = a.CoinJoin(b);
Assert.Equal(result.Inputs.Count, a.Inputs.Count + b.Inputs.Count);
Assert.Equal(result.Outputs.Count, a.Outputs.Count + b.Outputs.Count);
Assert.Equal(result.tx.Inputs.Count, a.tx.Inputs.Count + b.tx.Inputs.Count);
Assert.Equal(result.tx.Outputs.Count, a.tx.Outputs.Count + b.tx.Outputs.Count);
var tx = result.GetGlobalTransaction();
var atx = a.GetGlobalTransaction();
var btx = b.GetGlobalTransaction();
Assert.Equal(tx.Inputs.Count, atx.Inputs.Count + btx.Inputs.Count);
Assert.Equal(tx.Outputs.Count, atx.Outputs.Count + btx.Outputs.Count);
// These will work in netcoreapp2.1, but not in net472 ... :(
// Assert.Subset<PSBTInput>(result.inputs.ToHashSet(), a.inputs.ToHashSet());
// Assert.Subset<PSBTInput>(result.inputs.ToHashSet(), b.inputs.ToHashSet());
Expand Down
14 changes: 8 additions & 6 deletions NBitcoin.Tests/sample_tests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,11 @@ namespace NBitcoin.Tests
public class sample_tests
{
#if HAS_SPAN
[Fact]
[Theory]
[InlineData(PSBTVersion.PSBTv0)]
[InlineData(PSBTVersion.PSBTv2)]
[Trait("UnitTest", "UnitTest")]
public async Task CanBuildTaprootSingleSigTransactions()
public async Task CanBuildTaprootSingleSigTransactions(PSBTVersion version)
{
using (var nodeBuilder = NodeBuilderEx.Create())
{
Expand Down Expand Up @@ -60,7 +62,7 @@ async Task RefreshCoin()
.SubtractFees()
.SetChange(change)
.SendEstimatedFees(rate)
.BuildPSBT(false);
.BuildPSBT(false, version);

var tk = key.PubKey.GetTaprootFullPubKey();
psbt.Inputs[0].HDTaprootKeyPaths.Add(tk.OutputKey, new TaprootKeyPath(accountRootKeyPath.Derive(KeyPath.Parse("0/0"))));
Expand All @@ -81,7 +83,7 @@ async Task RefreshCoin()
.SubtractFees()
.SetChange(change)
.SendEstimatedFees(rate)
.BuildPSBT(true);
.BuildPSBT(true, version);
psbt.Finalize();
rpc.SendRawTransaction(psbt.ExtractTransaction());

Expand All @@ -97,7 +99,7 @@ async Task RefreshCoin()
.SubtractFees()
.SetChange(change)
.SendEstimatedFees(rate)
.BuildPSBT(true);
.BuildPSBT(true, version);
Assert.NotNull(psbt.Inputs[0].TaprootMerkleRoot);
Assert.NotNull(psbt.Inputs[0].TaprootInternalKey);
Assert.NotNull(psbt.Inputs[0].TaprootKeySignature);
Expand All @@ -113,7 +115,7 @@ async Task RefreshCoin()
.SubtractFees()
.SetChange(change)
.SendEstimatedFees(rate)
.BuildPSBT(false);
.BuildPSBT(false, version);

var taprootKeyPair = key.CreateTaprootKeyPair(merkleRoot);
psbt.Inputs[0].Sign(taprootKeyPair);
Expand Down
26 changes: 16 additions & 10 deletions NBitcoin.Tests/transaction_tests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2213,9 +2213,11 @@ public void CanBuildWithKnownSignatures()
Assert.True(tx.Inputs.AsIndexedInputs().First().VerifyScript(coin));
}

[Fact]
[Theory]
[InlineData(PSBTVersion.PSBTv2)]
[InlineData(PSBTVersion.PSBTv0)]
[Trait("UnitTest", "UnitTest")]
public void AssertCanSendBackSmallSegwitChange()
public void AssertCanSendBackSmallSegwitChange(PSBTVersion version)
{
var k = new Key();
var txBuilder = Bitcoin.Instance.Regtest.CreateTransactionBuilder();
Expand All @@ -2224,7 +2226,7 @@ public void AssertCanSendBackSmallSegwitChange()
txBuilder.SetChange(new Key().PubKey.WitHash);
// The dust should be 294, so should have 2 outputs
txBuilder.SendFees(Money.Satoshis(400 - 294));
var signed = txBuilder.BuildPSBT(false);
var signed = txBuilder.BuildPSBT(false, version);
Assert.Equal(2, signed.Outputs.Count);

txBuilder = Bitcoin.Instance.Regtest.CreateTransactionBuilder();
Expand All @@ -2233,8 +2235,9 @@ public void AssertCanSendBackSmallSegwitChange()
txBuilder.SetChange(new Key().PubKey.WitHash);
// The dust should be 293, so should have 1 outputs
txBuilder.SendFees(Money.Satoshis(400 - 293));
signed = txBuilder.BuildPSBT(false);
signed = txBuilder.BuildPSBT(false, version);
Assert.Single(signed.Outputs);
Assert.Equal(signed.Version, version);
}

[Fact]
Expand Down Expand Up @@ -2274,9 +2277,11 @@ public void DoNotBuildTooBigTransaction()
Assert.Equal(new FeeRate(1.0m).SatoshiPerByte, rate.SatoshiPerByte, 1);
}

[Fact]
[Theory]
[InlineData(PSBTVersion.PSBTv0)]
[InlineData(PSBTVersion.PSBTv2)]
[Trait("UnitTest", "UnitTest")]
public void CanBuildTransaction()
public void CanBuildTransaction(PSBTVersion version)
{
var keys = Enumerable.Range(0, 5).Select(i => new Key()).ToArray();

Expand Down Expand Up @@ -2364,10 +2369,10 @@ public void CanBuildTransaction()
var psbt = txBuilder.BuildPSBT(true);
Assert.All(psbt.Inputs, input => input.PartialSigs.Any()); // All inputs should have partial sigs
Assert.False(psbt.IsAllFinalized());
psbt.TryFinalize(out _);
Assert.False(psbt.TryFinalize(out _));
Assert.False(psbt.IsAllFinalized()); // Non segwit transactions need the previous tx
psbt.AddTransactions(fundingTx);
psbt.TryFinalize(out _);
Assert.True(psbt.TryFinalize(out _));
Assert.True(psbt.IsAllFinalized()); // All signed!
Assert.True(psbt.CanExtractTransaction());
Assert.True(txBuilder.Verify(psbt.ExtractTransaction(), "0.0001"));
Expand Down Expand Up @@ -2416,7 +2421,7 @@ public void CanBuildTransaction()
.SignTransaction(tx);
Assert.True(txBuilder.Verify(partiallySigned));

var partiallySignedPSBT = partiallySigned.CreatePSBT(txBuilder.Network);
var partiallySignedPSBT = partiallySigned.CreatePSBT(txBuilder.Network, version);
txBuilder.ExtractSignatures(partiallySignedPSBT, partiallySigned);
partiallySignedPSBT.AddCoins(allCoins);
partiallySignedPSBT.AddTransactions(fundingTx);
Expand Down Expand Up @@ -3123,8 +3128,9 @@ public void CanParseTransaction()
}

[Fact]
public void Play()
public void DoNotCrashOnRegtest()
{
Assert.NotNull(Network.GetNetwork("regtest"));
}

[Fact]
Expand Down
Loading
Loading