Skip to content

Commit 8540aa9

Browse files
committed
Add ability to convert to/from PSBT0 and PSBT2
1 parent def2533 commit 8540aa9

File tree

3 files changed

+76
-4
lines changed

3 files changed

+76
-4
lines changed

NBitcoin.Tests/PSBT2Tests.cs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,7 @@ public void InvalidPSBT(string psbt)
156156
[InlineData("70736274ff0102040200000001040101010501020106010701fb0402000000000100520200000001c1aa256e214b96a1822f93de42bff3b5f3ff8d0519306e3515d7515a5e805b120000000000ffffffff0118c69a3b00000000160014b0a3af144208412693ca7d166852b52db0aef06e0000000001011f18c69a3b00000000160014b0a3af144208412693ca7d166852b52db0aef06e010e200b0ad921419c1c8719735d72dc739f9ea9e0638d1fe4c1eef0f9944084815fc8010f040000000000220202d601f84846a6755f776be00e3d9de8fb10acc935fb83c45fb0162d4cad5ab79218f69d873e540000800100008000000080000000002a0000000103080008af2f000000000104160014c430f64c4756da310dbd1a085572ef299926272c00220202e36fbff53dd534070cf8fd396614680f357a9b85db7340bf1cfa745d2ad7b34018f69d873e54000080010000800000008001000000640000000103088bbdeb0b0000000001041600144dd193ac964a56ac1b9e1cca8454fe2f474f851300")]
157157
[InlineData("70736274ff010204020000000104010101050102010601ff01fb0402000000000100520200000001c1aa256e214b96a1822f93de42bff3b5f3ff8d0519306e3515d7515a5e805b120000000000ffffffff0118c69a3b00000000160014b0a3af144208412693ca7d166852b52db0aef06e0000000001011f18c69a3b00000000160014b0a3af144208412693ca7d166852b52db0aef06e010e200b0ad921419c1c8719735d72dc739f9ea9e0638d1fe4c1eef0f9944084815fc8010f040000000000220202d601f84846a6755f776be00e3d9de8fb10acc935fb83c45fb0162d4cad5ab79218f69d873e540000800100008000000080000000002a0000000103080008af2f000000000104160014c430f64c4756da310dbd1a085572ef299926272c00220202e36fbff53dd534070cf8fd396614680f357a9b85db7340bf1cfa745d2ad7b34018f69d873e54000080010000800000008001000000640000000103088bbdeb0b0000000001041600144dd193ac964a56ac1b9e1cca8454fe2f474f851300")]
158158
[InlineData("70736274ff010204020000000103040000000001040101010501020106010701fb0402000000000100520200000001c1aa256e214b96a1822f93de42bff3b5f3ff8d0519306e3515d7515a5e805b120000000000ffffffff0118c69a3b00000000160014b0a3af144208412693ca7d166852b52db0aef06e0000000001011f18c69a3b00000000160014b0a3af144208412693ca7d166852b52db0aef06e010e200b0ad921419c1c8719735d72dc739f9ea9e0638d1fe4c1eef0f9944084815fc8010f0400000000011004feffffff0111048c8dc4620112041027000000220202d601f84846a6755f776be00e3d9de8fb10acc935fb83c45fb0162d4cad5ab79218f69d873e540000800100008000000080000000002a0000000103080008af2f000000000104160014c430f64c4756da310dbd1a085572ef299926272c00220202e36fbff53dd534070cf8fd396614680f357a9b85db7340bf1cfa745d2ad7b34018f69d873e54000080010000800000008001000000640000000103088bbdeb0b0000000001041600144dd193ac964a56ac1b9e1cca8454fe2f474f851300")]
159-
public void ValidPSBTs(string psbt)
159+
public void ValidPSBT2(string psbt)
160160
{
161161
var p = PSBT.Parse(psbt, Network.Main);
162162
Assert.IsType<PSBT2>(p);
@@ -165,6 +165,18 @@ public void ValidPSBTs(string psbt)
165165
Assert.Equal(psbt, Encoders.Hex.EncodeData(bytes));
166166
Assert.Empty(p.Outputs.SelectMany(o => o.Unknown));
167167
Assert.Empty(p.Inputs.SelectMany(o => o.Unknown));
168+
169+
var v0 = p.ToPSBTv0();
170+
var v2 = v0.ToPSBTv2();
171+
var expected = (PSBT2)p.Clone();
172+
// Remove the fields that are not supported in PSBTv0.
173+
// this information has been lost in the roundtrip
174+
expected.FallbackLockTime = null;
175+
expected.ModifiableFlags = null;
176+
foreach (var input in expected.Inputs)
177+
((PSBT2Input)input).UnifiedTimeLock = null;
178+
179+
Assert.Equal(expected.ToString(), v2.ToString());
168180
}
169181

170182
[Fact]

NBitcoin/BIP174/PartiallySignedTransaction.cs

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
using NBitcoin.BuilderExtensions;
1010
using System.Diagnostics.CodeAnalysis;
1111
using NBitcoin.BIP370;
12+
using NBitcoin.Protocol;
1213

1314
namespace NBitcoin
1415
{
@@ -1286,6 +1287,65 @@ public PSBT RebaseKeyPaths(IHDKey accountKey, RootedKeyPath newRoot)
12861287
}
12871288
public Transaction GetGlobalTransaction() => GetGlobalTransaction(false);
12881289
internal abstract Transaction GetGlobalTransaction(bool @unsafe);
1290+
1291+
/// <summary>
1292+
/// If this instance is <see cref="PSBT0"/>, returns it.
1293+
/// Else, converts to a <see cref="PSBT0"/> instance.
1294+
/// </summary>
1295+
/// <returns>This instance, or a conversion</returns>
1296+
public PSBT0 ToPSBTv0()
1297+
{
1298+
if (this is PSBT0 p)
1299+
return p;
1300+
var maps = new Maps();
1301+
FillMaps(maps);
1302+
var global = this.GetGlobalTransaction(true);
1303+
int i = 0;
1304+
foreach (var b in PSBT2Constants.PSBT_V0_GLOBAL_EXCLUSIONSET)
1305+
maps[i].Remove([b]);
1306+
maps[i].Add(PSBTConstants.PSBT_GLOBAL_UNSIGNED_TX, global.ToBytes());
1307+
i++;
1308+
for (int o = 0; o < this.Inputs.Count; o++)
1309+
{
1310+
foreach (var b in PSBT2Constants.PSBT_V0_INPUT_EXCLUSIONSET)
1311+
maps[i].Remove([b]);
1312+
i++;
1313+
}
1314+
for (int o = 0; o < this.Outputs.Count; o++)
1315+
{
1316+
foreach (var b in PSBT2Constants.PSBT_V0_OUTPUT_EXCLUSIONSET)
1317+
maps[i].Remove([b]);
1318+
i++;
1319+
}
1320+
return new PSBT0(maps, Network);
1321+
}
1322+
/// <summary>
1323+
/// If this instance is <see cref="PSBT2"/>, returns it.
1324+
/// Else, converts to a <see cref="PSBT2"/> instance.
1325+
/// </summary>
1326+
/// <returns>This instance, or a conversion</returns>
1327+
public PSBT2 ToPSBTv2()
1328+
{
1329+
if (this is PSBT2 p)
1330+
return p;
1331+
var maps = new Maps();
1332+
FillMaps(maps);
1333+
var tx = GetGlobalTransaction(true);
1334+
maps[0].Remove([PSBTConstants.PSBT_GLOBAL_UNSIGNED_TX]);
1335+
maps[0].Add(PSBT2Constants.PSBT_GLOBAL_TX_VERSION, tx.Version);
1336+
maps[0].Add(PSBT2Constants.PSBT_GLOBAL_INPUT_COUNT, new VarInt((uint)tx.Inputs.Count));
1337+
maps[0].Add(PSBT2Constants.PSBT_GLOBAL_OUTPUT_COUNT, new VarInt((uint)tx.Outputs.Count));
1338+
int i = 1;
1339+
foreach (var txin in tx.Inputs)
1340+
{
1341+
PSBT2Input.FillMap(maps[i++], txin);
1342+
}
1343+
foreach (var txout in tx.Outputs)
1344+
{
1345+
PSBT2Output.FillMap(maps[i++], txout);
1346+
}
1347+
return new PSBT2(maps, Network);
1348+
}
12891349
}
12901350
}
12911351
#nullable disable

NBitcoin/BIP370/PSBT2.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ internal PSBT2(Transaction tx, Network network): this(CreateMap(tx, network), ne
1515
{
1616
}
1717

18-
private static Maps CreateMap(Transaction tx, Network network)
18+
internal static Maps CreateMap(Transaction tx, Network network)
1919
{
2020
var m = new Maps();
2121
var global = m.NewMap();
@@ -39,9 +39,9 @@ internal PSBT2(Maps maps, Network network) : base(maps, network, PSBTVersion.PSB
3939
{
4040
var globalMap = maps[0];
4141
if (globalMap.ContainsKey([PSBTConstants.PSBT_GLOBAL_UNSIGNED_TX]))
42-
{
4342
throw new FormatException("PSBT v2 must not contain PSBT_GLOBAL_UNSIGNED_TX");
44-
}
43+
if (globalMap.TryRemove<int>(PSBTConstants.PSBT_GLOBAL_VERSION, out var psbtVersion) && psbtVersion != 2)
44+
throw new FormatException("PSBTv2 should have PSBT_GLOBAL_VERSION set to 2");
4545

4646
if (globalMap.TryRemove<uint>(PSBT2Constants.PSBT_GLOBAL_FALLBACK_LOCKTIME, out var v))
4747
FallbackLockTime = new LockTime(v);

0 commit comments

Comments
 (0)