Upgrade to NBitcoin 7
MODIFY
WalletWasabi.Fluent.Desktop/packages.lock.json
WalletWasabi.Fluent.Desktop/packages.lock.json
},
"NBitcoin": {
"type": "Transitive",
- "resolved": "6.0.19",
- "contentHash": "l/iN8TX/rNwzJvYLVM0Zspd0NzfGZ77HZXJ0upFS8Oyrd3NaKaW3Tj9sDIX2aYijpIeGUVOUPRXM/MhID5u4ng==",
+ "resolved": "7.0.6",
+ "contentHash": "qRN2ajVUlGo85q1XMCrnTH/48UkV64g6HDGPGekD7obuBxmSIvq7fb79b9oW2xt1o9LMBlpaGST1gDRKWbQ3Ow==",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "1.0.0",
"Newtonsoft.Json": "11.0.2"
},
"NBitcoin.Secp256k1": {
"type": "Transitive",
- "resolved": "1.0.10",
- "contentHash": "+CbOOtba1tv4p0G8uKRmwH4he5LXNtqfxdIrDi0RcVViR7HRTbaoDE7tJ7cAkg7pxuiNHGkpvtn+rFgkPxYgYw=="
+ "resolved": "3.0.1",
+ "contentHash": "n/OaEKIVvgcJ9qwde8g2IZdJMn3oAdEHAKV0gFg3JcrjxfbFCiziZ3WGOJSFPXldhuLy/E9jEIcXdRRG+Q6iUg=="
},
"Newtonsoft.Json": {
"type": "Transitive",
"dependencies": {
"Microsoft.AspNetCore.Mvc.NewtonsoftJson": "6.0.0",
"Microsoft.Win32.SystemEvents": "6.0.0",
- "NBitcoin": "6.0.19",
- "NBitcoin.Secp256k1": "1.0.10"
+ "NBitcoin": "7.0.6",
+ "NBitcoin.Secp256k1": "3.0.1"
}
},
"walletwasabi.fluent": {

MODIFY
WalletWasabi.Fluent/packages.lock.json
WalletWasabi.Fluent/packages.lock.json
},
"NBitcoin": {
"type": "Transitive",
- "resolved": "6.0.19",
- "contentHash": "l/iN8TX/rNwzJvYLVM0Zspd0NzfGZ77HZXJ0upFS8Oyrd3NaKaW3Tj9sDIX2aYijpIeGUVOUPRXM/MhID5u4ng==",
+ "resolved": "7.0.6",
+ "contentHash": "qRN2ajVUlGo85q1XMCrnTH/48UkV64g6HDGPGekD7obuBxmSIvq7fb79b9oW2xt1o9LMBlpaGST1gDRKWbQ3Ow==",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "1.0.0",
"Newtonsoft.Json": "11.0.2"
},
"NBitcoin.Secp256k1": {
"type": "Transitive",
- "resolved": "1.0.10",
- "contentHash": "+CbOOtba1tv4p0G8uKRmwH4he5LXNtqfxdIrDi0RcVViR7HRTbaoDE7tJ7cAkg7pxuiNHGkpvtn+rFgkPxYgYw=="
+ "resolved": "3.0.1",
+ "contentHash": "n/OaEKIVvgcJ9qwde8g2IZdJMn3oAdEHAKV0gFg3JcrjxfbFCiziZ3WGOJSFPXldhuLy/E9jEIcXdRRG+Q6iUg=="
},
"Newtonsoft.Json": {
"type": "Transitive",
"dependencies": {
"Microsoft.AspNetCore.Mvc.NewtonsoftJson": "6.0.0",
"Microsoft.Win32.SystemEvents": "6.0.0",
- "NBitcoin": "6.0.19",
- "NBitcoin.Secp256k1": "1.0.10"
+ "NBitcoin": "7.0.6",
+ "NBitcoin.Secp256k1": "3.0.1"
}
}
},

MODIFY
WalletWasabi.Packager/packages.lock.json
WalletWasabi.Packager/packages.lock.json
},
"NBitcoin": {
"type": "Transitive",
- "resolved": "6.0.19",
- "contentHash": "l/iN8TX/rNwzJvYLVM0Zspd0NzfGZ77HZXJ0upFS8Oyrd3NaKaW3Tj9sDIX2aYijpIeGUVOUPRXM/MhID5u4ng==",
+ "resolved": "7.0.6",
+ "contentHash": "qRN2ajVUlGo85q1XMCrnTH/48UkV64g6HDGPGekD7obuBxmSIvq7fb79b9oW2xt1o9LMBlpaGST1gDRKWbQ3Ow==",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "1.0.0",
"Newtonsoft.Json": "11.0.2"
},
"NBitcoin.Secp256k1": {
"type": "Transitive",
- "resolved": "1.0.10",
- "contentHash": "+CbOOtba1tv4p0G8uKRmwH4he5LXNtqfxdIrDi0RcVViR7HRTbaoDE7tJ7cAkg7pxuiNHGkpvtn+rFgkPxYgYw=="
+ "resolved": "3.0.1",
+ "contentHash": "n/OaEKIVvgcJ9qwde8g2IZdJMn3oAdEHAKV0gFg3JcrjxfbFCiziZ3WGOJSFPXldhuLy/E9jEIcXdRRG+Q6iUg=="
},
"Newtonsoft.Json": {
"type": "Transitive",
"dependencies": {
"Microsoft.AspNetCore.Mvc.NewtonsoftJson": "6.0.0",
"Microsoft.Win32.SystemEvents": "6.0.0",
- "NBitcoin": "6.0.19",
- "NBitcoin.Secp256k1": "1.0.10"
+ "NBitcoin": "7.0.6",
+ "NBitcoin.Secp256k1": "3.0.1"
}
}
},

MODIFY
WalletWasabi.Tests/RegressionTests/CoinJoinTests.cs
WalletWasabi.Tests/RegressionTests/CoinJoinTests.cs
@@ -156,7 +156,7 @@ public class CoinJoinTests
HttpRequestException httpRequestException = await Assert.ThrowsAsync<HttpRequestException>(async () => await CreateNewAliceClientAsync(roundId, registeredAddresses, signerPubKeys, requesters, inputsRequest));
Assert.Contains(HttpStatusCode.BadRequest.ToReasonString(), httpRequestException.Message);
- byte[] dummySignature = new byte[65];
+ CompactSignature dummySignature = new(0, new byte[64]);
BlindedOutputWithNonceIndex nullBlindedScript = new(0, uint256.One);
inputsRequest.BlindedOutputScripts = Enumerable.Range(0, round.MixingLevels.Count() + 1).Select(x => nullBlindedScript);
@@ -729,7 +729,7 @@ public class CoinJoinTests
// important data for each of the participants in the coinjoin session.
var participants = new List<(AliceClient4,
List<(Requester requester, BitcoinAddress outputAddress, BlindedOutputWithNonceIndex blindedScript)>,
- List<(OutPoint input, byte[] proof, Coin coin, Key key)>)>();
+ List<(OutPoint input, CompactSignature proof, Coin coin, Key key)>)>();
// INPUTS REGISTRATION PHASE --
for (var anosetIdx = 0; anosetIdx < anonymitySet; anosetIdx++)
@@ -753,7 +753,7 @@ public class CoinJoinTests
var blindedOutputScriptsHash = new uint256(NBitcoin.Crypto.Hashes.SHA256(blindedOutputScriptListBytes));
// Create 4 new coins that we want to mix
- var inputs = new List<(OutPoint input, byte[] proof, Coin coin, Key key)>();
+ var inputs = new List<(OutPoint input, CompactSignature proof, Coin coin, Key key)>();
for (var inputIdx = 0; inputIdx < 4; inputIdx++)
{
var key = new Key();

MODIFY
WalletWasabi.Tests/UnitTests/JsonConverters/ByteArrayJsonConverterTests.cs
WalletWasabi.Tests/UnitTests/JsonConverters/ByteArrayJsonConverterTests.cs
-using Newtonsoft.Json.Converters;
using Newtonsoft.Json;
+using Newtonsoft.Json.Converters;
+using WalletWasabi.JsonConverters;
using Xunit;
namespace WalletWasabi.Tests.UnitTests.JsonConverters;

MODIFY
WalletWasabi.Tests/UnitTests/WabiSabi/JsonConverters/CompactSignatureJsonConverterTests.cs
WalletWasabi.Tests/UnitTests/WabiSabi/JsonConverters/CompactSignatureJsonConverterTests.cs
@@ -23,10 +23,39 @@ public class CompactSignatureJsonConverterTests
Assert.True(privateKey.IsCompressed);
uint256 hashMsg = Hashes.DoubleSHA256(Encoding.ASCII.GetBytes("compact hashing test"));
- byte[] compactSignature = privateKey.SignCompact(hashMsg);
+ CompactSignature compactSignature = privateKey.SignCompact(hashMsg);
Assert.NotNull(compactSignature);
- string hex = ByteHelpers.ToHex(compactSignature);
+ byte[] sigBytes = CompactSignatureJsonConverter.ToBytes(compactSignature);
+ string hex = ByteHelpers.ToHex(sigBytes);
Assert.Equal("1F71932FFF735FA6A57787191A296717F71270B2B7E1D90008B7147117F250DBDE012359EED51D28682B1AAB686A8FD8A411A8D07F1EB4D7CDAC5B7EBE73F260A0", hex);
}
+
+ [Fact]
+ public void ConverterTest()
+ {
+ // Get a compressed private key.
+ string base58 = "Kwr371tjA9u2rFSMZjTNun2PXXP3WPZu2afRHTcta6KxEUdm1vEw";
+ BitcoinSecret bitcoinSecret = Network.Main.CreateBitcoinSecret(base58);
+ Key privateKey = bitcoinSecret.PrivateKey;
+ Assert.True(privateKey.IsCompressed);
+
+ uint256 hashMsg = Hashes.DoubleSHA256(Encoding.ASCII.GetBytes("compact hashing test"));
+ CompactSignature expectedSignature = privateKey.SignCompact(hashMsg);
+ Assert.NotNull(expectedSignature);
+
+ byte[] sigBytes = CompactSignatureJsonConverter.ToBytes(expectedSignature);
+ string hex = ByteHelpers.ToHex(sigBytes);
+ Assert.Equal("1F71932FFF735FA6A57787191A296717F71270B2B7E1D90008B7147117F250DBDE012359EED51D28682B1AAB686A8FD8A411A8D07F1EB4D7CDAC5B7EBE73F260A0", hex);
+
+ CompactSignatureJsonConverter converter = new();
+ string json = JsonConvert.SerializeObject(expectedSignature, converter);
+ Assert.Equal("\"H3GTL/9zX6ald4cZGilnF/cScLK34dkACLcUcRfyUNveASNZ7tUdKGgrGqtoao/YpBGo0H8etNfNrFt+vnPyYKA=\"", json);
+
+ CompactSignature? actual = JsonConvert.DeserializeObject<CompactSignature>(json, converter);
+ Assert.NotNull(actual);
+
+ Assert.Equal(actual!.RecoveryId, expectedSignature.RecoveryId);
+ Assert.Equal(actual.Signature, expectedSignature.Signature);
+ }
}

MODIFY
WalletWasabi/CoinJoin/Common/Models/InputProofModel.cs
WalletWasabi/CoinJoin/Common/Models/InputProofModel.cs
@@ -12,8 +12,6 @@ public class InputProofModel
public OutPoint Input { get; set; }
[Required]
- [MinLength(65, ErrorMessage = "Provided proof is invalid")] // Bitcoin compact signatures are 65 bytes length
- [MaxLength(65, ErrorMessage = "Provided proof is invalid")] // Bitcoin compact signatures are 65 bytes length
- [JsonConverter(typeof(ByteArrayJsonConverter))]
- public byte[] Proof { get; set; }
+ [JsonConverter(typeof(CompactSignatureJsonConverter))]
+ public CompactSignature Proof { get; set; }
}

MODIFY
WalletWasabi/Extensions/NBitcoinExtensions.cs
WalletWasabi/Extensions/NBitcoinExtensions.cs
@@ -172,7 +172,7 @@ public static class NBitcoinExtensions
return me.ToDecimal(MoneyUnit.BTC) * btcExchangeRate;
}
- public static bool VerifyMessage(this BitcoinWitPubKeyAddress address, uint256 messageHash, byte[] signature)
+ public static bool VerifyMessage(this BitcoinWitPubKeyAddress address, uint256 messageHash, CompactSignature signature)
{
PubKey pubKey = PubKey.RecoverCompact(messageHash, signature);
return pubKey.WitHash == address.Hash;

ADD
WalletWasabi/JsonConverters/CompactSignatureJsonConverter.cs
+using NBitcoin;
+using Newtonsoft.Json;
+
+namespace WalletWasabi.JsonConverters;
+
+public class CompactSignatureJsonConverter : JsonConverter<CompactSignature>
+{
+ /// <inheritdoc />
+ public override CompactSignature? ReadJson(JsonReader reader, Type objectType, CompactSignature? existingValue, bool hasExistingValue, JsonSerializer serializer)
+ {
+ var value = reader.Value as string;
+
+ if (string.IsNullOrWhiteSpace(value))
+ {
+ return null;
+ }
+
+ byte[] bytes = Convert.FromBase64String(value);
+ int recoveryId = ComputeRecoveryId(bytes[0]);
+ byte[] signature = bytes[1..];
+
+ return new CompactSignature(recoveryId, signature);
+ }
+
+ private static int ComputeRecoveryId(byte b)
+ {
+ int header = b;
+
+ if (header < 27 || header > 34)
+ {
+ throw new ArgumentException("Header byte out of range: " + header);
+ }
+
+ // This means: Compressed = true.
+ if (header >= 31)
+ {
+ header -= 4;
+ }
+ else
+ {
+ throw new ArgumentException("Only compressed are supported.");
+ }
+
+ int recoveryId = header - 27;
+ return recoveryId;
+ }
+
+ /// <inheritdoc />
+ public override void WriteJson(JsonWriter writer, CompactSignature? value, JsonSerializer serializer)
+ {
+ if (value is null)
+ {
+ throw new ArgumentNullException(nameof(value));
+ }
+ else
+ {
+ byte[] sigBytes = ToBytes(value);
+ writer.WriteValue(Convert.ToBase64String(sigBytes));
+ }
+ }
+
+ internal static byte[] ToBytes(CompactSignature compactSignature)
+ {
+ bool isCompressed = true;
+
+ byte[] result = new byte[65];
+ result[0] = (byte)(27 + compactSignature.RecoveryId + (isCompressed ? 4 : 0));
+
+ for (int i = 0; i < 64; i++)
+ {
+ result[i + 1] = compactSignature.Signature[i];
+ }
+
+ return result;
+ }
+}

MODIFY
WalletWasabi/WalletWasabi.csproj
WalletWasabi/WalletWasabi.csproj
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="6.0.0" />
<PackageReference Include="Microsoft.Win32.SystemEvents" Version="6.0.0" />
- <PackageReference Include="NBitcoin" Version="6.0.19" />
- <PackageReference Include="NBitcoin.Secp256k1" Version="1.0.10" />
+ <PackageReference Include="NBitcoin" Version="7.0.6" />
+ <PackageReference Include="NBitcoin.Secp256k1" Version="3.0.1" />
</ItemGroup>
<ItemGroup>

MODIFY
WalletWasabi/packages.lock.json
WalletWasabi/packages.lock.json
},
"NBitcoin": {
"type": "Direct",
- "requested": "[6.0.19, )",
- "resolved": "6.0.19",
- "contentHash": "l/iN8TX/rNwzJvYLVM0Zspd0NzfGZ77HZXJ0upFS8Oyrd3NaKaW3Tj9sDIX2aYijpIeGUVOUPRXM/MhID5u4ng==",
+ "requested": "[7.0.6, )",
+ "resolved": "7.0.6",
+ "contentHash": "qRN2ajVUlGo85q1XMCrnTH/48UkV64g6HDGPGekD7obuBxmSIvq7fb79b9oW2xt1o9LMBlpaGST1gDRKWbQ3Ow==",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "1.0.0",
"Newtonsoft.Json": "11.0.2"
},
"NBitcoin.Secp256k1": {
"type": "Direct",
- "requested": "[1.0.10, )",
- "resolved": "1.0.10",
- "contentHash": "+CbOOtba1tv4p0G8uKRmwH4he5LXNtqfxdIrDi0RcVViR7HRTbaoDE7tJ7cAkg7pxuiNHGkpvtn+rFgkPxYgYw=="
+ "requested": "[3.0.1, )",
+ "resolved": "3.0.1",
+ "contentHash": "n/OaEKIVvgcJ9qwde8g2IZdJMn3oAdEHAKV0gFg3JcrjxfbFCiziZ3WGOJSFPXldhuLy/E9jEIcXdRRG+Q6iUg=="
},
"Microsoft.AspNetCore.JsonPatch": {
"type": "Transitive",

Upgrade to NBitcoin 7

Co-authored-by: David Molnard <molnardavid84@gmail.com>