Skip to content

Commit a4b41a5

Browse files
Remove BouncyCastle and add MoneroWalletRpc.IsValidAddress
1 parent 31685aa commit a4b41a5

7 files changed

Lines changed: 70 additions & 193 deletions

File tree

Monero.IntegrationTests/MoneroWalletRpcIntegrationTest.cs

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,6 @@ public async Task TestCreateWalletRandom()
149149

150150
await TestWallet(async () =>
151151
{
152-
MoneroUtils.ValidateAddress(await moneroWallet.GetPrimaryAddress());
153152
MoneroUtils.ValidatePrivateViewKey(await moneroWallet.GetPrivateViewKey());
154153
MoneroUtils.ValidatePrivateSpendKey(await moneroWallet.GetPrivateSpendKey());
155154
MoneroUtils.ValidateMnemonic(await moneroWallet.GetSeed());
@@ -247,7 +246,6 @@ await TestWallet(async () =>
247246
{
248247
MoneroUtils.ValidateMnemonic(await moneroWallet.GetSeed());
249248
Assert.True(TestUtils.Seed != await moneroWallet.GetSeed());
250-
MoneroUtils.ValidateAddress(await moneroWallet.GetPrimaryAddress());
251249
Assert.True(TestUtils.Address != await moneroWallet.GetPrimaryAddress());
252250
}, moneroWallet);
253251
}
@@ -376,7 +374,6 @@ public async Task TestGetPublicSpendKey()
376374
public async Task TestGetPrimaryAddress()
377375
{
378376
string primaryAddress = await _wallet.GetPrimaryAddress();
379-
MoneroUtils.ValidateAddress(primaryAddress);
380377
Assert.True(await _wallet.GetAddress(0, 0) == primaryAddress);
381378
}
382379

@@ -742,6 +739,23 @@ public async Task TestSaveAndClose()
742739
await CloseWallet(moneroWallet);
743740
}
744741

742+
// Can validate address
743+
[Fact]
744+
public async Task TestValidateAddress()
745+
{
746+
// mainnet primary and subaddress validation
747+
Assert.True(await _wallet.IsValidAddress("42U9v3qs5CjZEePHBZHwuSckQXebuZu299NSmVEmQ41YJZQhKcPyujyMSzpDH4VMMVSBo3U3b54JaNvQLwAjqDhKS3rvM3L", false, false));
748+
Assert.True(await _wallet.IsValidAddress("891TQPrWshJVpnBR4ZMhHiHpLx1PUnMqa3ccV5TJFBbqcJa3DWhjBh2QByCv3Su7WDPTGMHmCKkiVFN2fyGJKwbM1t6G7Ea", true, false));
749+
750+
// testnet primary and subaddress validation
751+
Assert.True(await _wallet.IsValidAddress("9tUBnNCkC3UKGygHCwYvAB1FscpjUuq5e9MYJd2rXuiiTjjfVeSVjnbSG5VTnJgBgy9Y7GTLfxpZNMUwNZjGfdFr1z79eV1", false, true));
752+
Assert.True(await _wallet.IsValidAddress("BgnKzHPJQDcg7xiP7bMN9MfPv9Z8ciT71iEMYnCdgBRBFETWgu9nKTr8fnzyGfU9h9gyNA8SFzYYzHfTS9KhqytSU943Nu1", true, true));
753+
754+
// stagenet primary and subaddress validation
755+
Assert.True(await _wallet.IsValidAddress("5B8s3obCY2ETeQB3GNAGPK2zRGen5UeW1WzegSizVsmf6z5NvM2GLoN6zzk1vHyzGAAfA8pGhuYAeCFZjHAp59jRVQkunGS", false, true));
756+
Assert.True(await _wallet.IsValidAddress("778B5D2JmMh5TJVWFbygJR15dvio5Z5B24hfSrWDzeroM8j8Lqc9sMoFE6324xg2ReaAZqHJkgfGFRugRmYHugHZ4f17Gxo", true, true));
757+
}
758+
745759
#region Test Utils
746760

747761
private static void TestSubaddress(MoneroSubaddress? subaddress)
@@ -770,7 +784,6 @@ private static void TestAccount(MoneroAccount? account)
770784
Assert.NotNull(account);
771785
uint? accountIndex = account.AccountIndex;
772786
Assert.NotNull(accountIndex);
773-
MoneroUtils.ValidateAddress(account.PrimaryAddress, TestUtils.NetworkType);
774787
TestUtils.TestUnsignedBigInteger(account.Balance);
775788
TestUtils.TestUnsignedBigInteger(account.UnlockedBalance);
776789

Monero.UnitTests/MoneroUtilsUnitTest.cs

Lines changed: 0 additions & 121 deletions
Original file line numberDiff line numberDiff line change
@@ -11,113 +11,6 @@ namespace Monero.UnitTests;
1111

1212
public class MoneroUtilsUnitTest
1313
{
14-
// Can validate addresses
15-
[Fact]
16-
public void TestAddressValidation()
17-
{
18-
// test mainnet primary address validation
19-
True(IsValidAddress(
20-
"42U9v3qs5CjZEePHBZHwuSckQXebuZu299NSmVEmQ41YJZQhKcPyujyMSzpDH4VMMVSBo3U3b54JaNvQLwAjqDhKS3rvM3L"));
21-
True(IsValidAddress(
22-
"48ZxX3Y2y5s4nJ8fdz2w65TrTEp9PRsv5J8iHSShkHQcE2V31FhnWptioNst1K9oeDY4KpWZ7v8V2BZNVa4Wdky89iqmPz2"));
23-
True(IsValidAddress(
24-
"48W972Fx1SQMCHVKENnPpM7tRcL5oWMgpMCqQDbhH8UrjDFg2H9i5AQWXuU1qacJgUUCVLTsgDmZKXGz1vPLXY8QB5ypYqG"));
25-
26-
// test mainnet integrated address validation
27-
ValidateAddress(
28-
"4CApvrfMgUFZEePHBZHwuSckQXebuZu299NSmVEmQ41YJZQhKcPyujyMSzpDH4VMMVSBo3U3b54JaNvQLwAjqDhKeGLQ9vfRBRKFKnBtVH");
29-
ValidateAddress(
30-
"4JGdXrMXaMP4nJ8fdz2w65TrTEp9PRsv5J8iHSShkHQcE2V31FhnWptioNst1K9oeDY4KpWZ7v8V2BZNVa4Wdky8DvDyXvDZXvE9jTQwom");
31-
ValidateAddress(
32-
"4JCp7q5SchvMCHVKENnPpM7tRcL5oWMgpMCqQDbhH8UrjDFg2H9i5AQWXuU1qacJgUUCVLTsgDmZKXGz1vPLXY8QFySJXARQWju8AuRN2z");
33-
34-
// test mainnet subaddress validation
35-
ValidateAddress(
36-
"891TQPrWshJVpnBR4ZMhHiHpLx1PUnMqa3ccV5TJFBbqcJa3DWhjBh2QByCv3Su7WDPTGMHmCKkiVFN2fyGJKwbM1t6G7Ea");
37-
ValidateAddress(
38-
"88fyq3t8Gxn1QWMG189EufHtMHXZXkfJtJKFJXqeA4GpSiuyfjVwVyp47PeQJnD7Tc8iK8TDvvhcmEmfh8nx7Va2ToP8wAo");
39-
ValidateAddress(
40-
"88hnoBiX3TPjbFaQE8RxgyBcf3DtMKZWWQMoArBjQfn37JJwtm568mPX6ipcCuGKDnLCzgjmpLSqce4aBDyapJJAFtNxUMb");
41-
42-
// test testnet primary address validation
43-
ValidateAddress(
44-
"9tUBnNCkC3UKGygHCwYvAB1FscpjUuq5e9MYJd2rXuiiTjjfVeSVjnbSG5VTnJgBgy9Y7GTLfxpZNMUwNZjGfdFr1z79eV1");
45-
ValidateAddress(
46-
"9xZmQa1kYakGoHcfXeBgcsLf622NCpChcACwXxfdgY9uAa9hXSPCV9cLvUsAShfDcFKDdPzCNJ1n5cFGKw5GVM722pjuGPd");
47-
ValidateAddress(
48-
"A2TXS6QFQ4wEsp8U7C2Y4B7wBtiML8aDG7mdCbRvDQmRaRNj1YSSgJE46fSzUkwgpMUCXFqscvrQuN7oKpP6eDyQ7XuYsuf");
49-
50-
// test testnet integrated address validation
51-
ValidateAddress(
52-
"A4AroB2EoJzKGygHCwYvAB1FscpjUuq5e9MYJd2rXuiiTjjfVeSVjnbSG5VTnJgBgy9Y7GTLfxpZNMUwNZjGfdFr2QY5Ba2aHhTEdQa2ra");
53-
ValidateAddress(
54-
"A8GSRNqF9rGGoHcfXeBgcsLf622NCpChcACwXxfdgY9uAa9hXSPCV9cLvUsAShfDcFKDdPzCNJ1n5cFGKw5GVM723iPoCEF1Fs9BcPYxTW");
55-
ValidateAddress(
56-
"ACACSuDk1LTEsp8U7C2Y4B7wBtiML8aDG7mdCbRvDQmRaRNj1YSSgJE46fSzUkwgpMUCXFqscvrQuN7oKpP6eDyQAdgDoT3UnMYKQz7SHC");
57-
58-
// test testnet subaddress validation
59-
ValidateAddress(
60-
"BgnKzHPJQDcg7xiP7bMN9MfPv9Z8ciT71iEMYnCdgBRBFETWgu9nKTr8fnzyGfU9h9gyNA8SFzYYzHfTS9KhqytSU943Nu1");
61-
ValidateAddress(
62-
"BZwiuKkoNP59zgPHTxpNw3PM4DW2xiAVQJWqfFRrGyeZ7afVdQqoiJg3E2dDL3Ja8BV4ov2LEoHx9UjzF3W4ihPBSZvWwTx");
63-
ValidateAddress(
64-
"Bhf1DEYrentcehUvNreLK5gxosnC2VStMXNCCs163RTxQq4jxFYvpw7LrQFmrMwWW2KsXLhMRtyho6Lq11ci3Fb246bxYmi");
65-
66-
// test stagenet primary address validation
67-
ValidateAddress(
68-
"5B8s3obCY2ETeQB3GNAGPK2zRGen5UeW1WzegSizVsmf6z5NvM2GLoN6zzk1vHyzGAAfA8pGhuYAeCFZjHAp59jRVQkunGS");
69-
ValidateAddress(
70-
"57VfotUbSZLG82UkKhWXDjS5ZEK9ZCDcmjdk4gpVq2fbKdEgwRCFrGTLZ2MMdSHphRWJDWVBi5qS8T7dz13JTCWtC228zyn");
71-
ValidateAddress(
72-
"52FysgWJYmAG73QUQZRULJj2Dv2C2mceUMB5zHqNzMn8WBtfPWQrSUFSQUKTX9r7bUMmVSGbrau976xYLynR8jTWLdA7rfp");
73-
74-
// test stagenet integrated address validation
75-
ValidateAddress(
76-
"5LqY4cQh9HkTeQB3GNAGPK2zRGen5UeW1WzegSizVsmf6z5NvM2GLoN6zzk1vHyzGAAfA8pGhuYAeCFZjHAp59jRj6LZRFrjuGK8Whthg2");
77-
ValidateAddress(
78-
"5HCLphJ63prG82UkKhWXDjS5ZEK9ZCDcmjdk4gpVq2fbKdEgwRCFrGTLZ2MMdSHphRWJDWVBi5qS8T7dz13JTCWtHETX8zcUhDjVKcynf6");
79-
ValidateAddress(
80-
"5BxetVKoA2gG73QUQZRULJj2Dv2C2mceUMB5zHqNzMn8WBtfPWQrSUFSQUKTX9r7bUMmVSGbrau976xYLynR8jTWVwQwpHNg5fCLgtA2Dv");
81-
82-
// test stagenet subaddress validation
83-
ValidateAddress(
84-
"778B5D2JmMh5TJVWFbygJR15dvio5Z5B24hfSrWDzeroM8j8Lqc9sMoFE6324xg2ReaAZqHJkgfGFRugRmYHugHZ4f17Gxo");
85-
ValidateAddress(
86-
"73U97wGEH9RCVUf6bopo45jSgoqjMzz4mTUsvWs5EusmYAmFcBYFm7wKMVmgtVKCBhMQqXrcMbHvwck2md63jMZSFJxUhQ2");
87-
ValidateAddress(
88-
"747wPpaPKrjDPZrF48jAfz9pRRUHLMCWfYu2UanP4ZfTG8NrmYrSEWNW8gYoadU8hTiwBjV14e6DLaC5xfhyEpX5154aMm6");
89-
90-
// test invalid addresses on mainnet
91-
TestInvalidAddress(null);
92-
TestInvalidAddress("");
93-
TestInvalidAddress(
94-
"42ZxX3Y2y5s4nJ8fdz2w65TrTEp9PRsv5J8iHSShkHQcE2V31FhnWptioNst1K9oeDY4KpWZ7v8V2BZNVa4Wdky89iqmPz2");
95-
TestInvalidAddress(
96-
"41ApvrfMgUFZEePHBZHwuSckQXebuZu299NSmVEmQ41YJZQhKcPyujyMSzpDH4VMMVSBo3U3b54JaNvQLwAjqDhKeGLQ9vfRBRKFKnBtVH");
97-
TestInvalidAddress(
98-
"81fyq3t8Gxn1QWMG189EufHtMHXZXkfJtJKFJXqeA4GpSiuyfjVwVyp47PeQJnD7Tc8iK8TDvvhcmEmfh8nx7Va2ToP8wAo");
99-
100-
// test invalid addresses on testnet
101-
TestInvalidAddress(null);
102-
TestInvalidAddress("");
103-
TestInvalidAddress(
104-
"91UBnNCkC3UKGygHCwYvAB1FscpjUuq5e9MYJd2rXuiiTjjfVeSVjnbSG5VTnJgBgy9Y7GTLfxpZNMUwNZjGfdFr1z79eV1");
105-
TestInvalidAddress(
106-
"A1AroB2EoJzKGygHCwYvAB1FscpjUuq5e9MYJd2rXuiiTjjfVeSVjnbSG5VTnJgBgy9Y7GTLfxpZNMUwNZjGfdFr2QY5Ba2aHhTEdQa2ra");
107-
TestInvalidAddress(
108-
"B1nKzHPJQDcg7xiP7bMN9MfPv9Z8ciT71iEMYnCdgBRBFETWgu9nKTr8fnzyGfU9h9gyNA8SFzYYzHfTS9KhqytSU943Nu1");
109-
110-
// test invalid addresses on stagenet
111-
TestInvalidAddress(null);
112-
TestInvalidAddress("");
113-
TestInvalidAddress(
114-
"518s3obCY2ETeQB3GNAGPK2zRGen5UeW1WzegSizVsmf6z5NvM2GLoN6zzk1vHyzGAAfA8pGhuYAeCFZjHAp59jRVQkunGS");
115-
TestInvalidAddress(
116-
"51qY4cQh9HkTeQB3GNAGPK2zRGen5UeW1WzegSizVsmf6z5NvM2GLoN6zzk1vHyzGAAfA8pGhuYAeCFZjHAp59jRj6LZRFrjuGK8Whthg2");
117-
TestInvalidAddress(
118-
"718B5D2JmMh5TJVWFbygJR15dvio5Z5B24hfSrWDzeroM8j8Lqc9sMoFE6324xg2ReaAZqHJkgfGFRugRmYHugHZ4f17Gxo");
119-
}
120-
12114
// Can validate keys
12215
[Fact]
12316
public void TestKeyValidation()
@@ -185,20 +78,6 @@ public void TestGetPaymentUri()
18578
paymentUri);
18679
}
18780

188-
private static void TestInvalidAddress(string? address)
189-
{
190-
False(IsValidAddress(address));
191-
try
192-
{
193-
ValidateAddress(address);
194-
throw new Exception("Should have thrown exception");
195-
}
196-
catch (MoneroError e)
197-
{
198-
True(e.Message.Length > 0);
199-
}
200-
}
201-
20281
private static void TestInvalidPrivateViewKey(string? privateViewKey)
20382
{
20483
False(IsValidPrivateViewKey(privateViewKey));

Monero/Common/MoneroUtils.cs

Lines changed: 16 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,6 @@
22
using System.Text;
33
using System.Text.RegularExpressions;
44

5-
using Org.BouncyCastle.Crypto.Digests;
6-
using Org.BouncyCastle.Utilities.Encoders;
7-
85
namespace Monero.Common;
96

107
public static class MoneroUtils
@@ -34,6 +31,8 @@ public static class MoneroUtils
3431

3532
private static readonly Regex StandardAddressPattern = new("^[" + Alphabet + "]{95}$", RegexOptions.Compiled);
3633
private static readonly Regex IntegratedAddressPattern = new("^[" + Alphabet + "]{106}$", RegexOptions.Compiled);
34+
private static readonly int StandardAddressLength = 95;
35+
private static readonly int IntegratedAddressLength = 106;
3736

3837

3938
private static bool IsHex(string? str)
@@ -154,44 +153,6 @@ public static void ValidatePublicSpendKey(string? publicSpendKey)
154153
}
155154
}
156155

157-
public static bool IsValidAddress(string? address)
158-
{
159-
try
160-
{
161-
ValidateAddress(address);
162-
return true;
163-
}
164-
catch (Exception)
165-
{
166-
return false;
167-
}
168-
}
169-
170-
public static void ValidateAddress(string? address, MoneroNetworkType? networkType)
171-
{
172-
if (networkType == null)
173-
{
174-
throw new MoneroError("Must provide a valid network type");
175-
}
176-
177-
MoneroDecodedAddress decodedAddress = DecodeAddress(address);
178-
179-
if (decodedAddress.GetNetworkType() != networkType)
180-
{
181-
throw new MoneroError("Invalid network type");
182-
}
183-
}
184-
185-
public static void ValidateAddress(string? address)
186-
{
187-
if (address == null)
188-
{
189-
throw new MoneroError("Address is null");
190-
}
191-
192-
DecodeAddress(address);
193-
}
194-
195156
public static void ValidateHex(string? str)
196157
{
197158
if (str == null)
@@ -249,11 +210,19 @@ public static MoneroDecodedAddress DecodeAddress(string? address)
249210
throw new MoneroError("Address is null");
250211
}
251212

213+
foreach (char c in address)
214+
{
215+
if (!Alphabet.Contains(c))
216+
{
217+
throw new MoneroError("Invalid character found in address");
218+
}
219+
}
220+
252221
// determine if an address has an integrated address pattern
253222
bool isIntegrated = false;
254223
if (!StandardAddressPattern.IsMatch(address))
255224
{
256-
if (IntegratedAddressPattern.IsMatch(address))
225+
if (IntegratedAddressPattern.IsMatch(address) && address.Length == IntegratedAddressLength)
257226
{
258227
isIntegrated = true;
259228
}
@@ -262,6 +231,10 @@ public static MoneroDecodedAddress DecodeAddress(string? address)
262231
throw new MoneroError("Address has invalid regex pattern");
263232
}
264233
}
234+
else if (address.Length != StandardAddressLength)
235+
{
236+
throw new MoneroError("Invalid address length");
237+
}
265238

266239
// decode address to hex string
267240
string addressHex = DecodeAddressToHex(address);
@@ -334,28 +307,7 @@ private static bool IsValidAddressHash(string decodedAddrStr)
334307
return false;
335308
}
336309

337-
string checksumCheck = decodedAddrStr.Substring(decodedAddrStr.Length - 8);
338-
string withoutChecksumStr = decodedAddrStr.Substring(0, decodedAddrStr.Length - 8);
339-
340-
byte[] withoutChecksumBytes;
341-
try
342-
{
343-
withoutChecksumBytes = Hex.Decode(withoutChecksumStr);
344-
}
345-
catch
346-
{
347-
return false;
348-
}
349-
350-
KeccakDigest digest256 = new(256);
351-
byte[] hashBytes = new byte[digest256.GetDigestSize()];
352-
digest256.BlockUpdate(withoutChecksumBytes, 0, withoutChecksumBytes.Length);
353-
digest256.DoFinal(hashBytes, 0);
354-
355-
string encodedStr = BitConverter.ToString(hashBytes).Replace("-", "").ToLowerInvariant();
356-
string hashChecksum = encodedStr.Substring(0, 8);
357-
358-
return hashChecksum == checksumCheck;
310+
return Regex.IsMatch(decodedAddrStr, @"\A\b[0-9a-fA-F]+\b\Z");
359311
}
360312

361313
private static string DecodeAddressToHex(string address)

Monero/Monero.csproj

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,6 @@
3939
<None Include="..\logo.png" Pack="true" PackagePath="\" />
4040
</ItemGroup>
4141

42-
<ItemGroup>
43-
<PackageReference Include="BouncyCastle.Cryptography" Version="2.6.2"/>
44-
</ItemGroup>
45-
4642
<!-- Workaround for https://github.com/dotnet/roslyn/issues/41640 -->
4743
<PropertyGroup>
4844
<GenerateDocumentationFile>true</GenerateDocumentationFile>

Monero/Wallet/MoneroWalletRpc.cs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1391,6 +1391,21 @@ public async Task Close(bool save)
13911391
await _rpc.SendCommandAsync<MoneroJsonRpcParams, MoneroRpcResponse>("close_wallet", parameters);
13921392
}
13931393

1394+
public async Task<bool> IsValidAddress(string address, bool subaddress, bool anyNetType)
1395+
{
1396+
ValidateAddressRequest request = new() { Address = address, AnyNetType = anyNetType };
1397+
1398+
ValidateAddressResponse response =
1399+
await _rpc.SendCommandAsync<ValidateAddressRequest, ValidateAddressResponse>("validate_address", request);
1400+
1401+
if (!response.Valid || (subaddress && !response.Subaddress))
1402+
{
1403+
return false;
1404+
}
1405+
1406+
return true;
1407+
}
1408+
13941409
public async Task<bool> IsClosed()
13951410
{
13961411
try
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
using System.Text.Json.Serialization;
2+
3+
namespace Monero.Wallet.Rpc;
4+
5+
public class ValidateAddressRequest
6+
{
7+
[JsonPropertyName("address")] public string Address { get; set; } = "";
8+
[JsonPropertyName("any_net_type")] public bool AnyNetType { get; set; } = false;
9+
[JsonPropertyName("allow_openalias")] public bool AllowOpenAlias { get; set; } = false;
10+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
using System.Text.Json.Serialization;
2+
3+
namespace Monero.Wallet.Rpc;
4+
5+
public class ValidateAddressResponse
6+
{
7+
[JsonPropertyName("integrated")] public bool Integrated { get; set; } = false;
8+
[JsonPropertyName("nettype")] public string Nettype { get; set; } = "";
9+
[JsonPropertyName("openalias_address")] public string OpenAliasAddress { get; set; } = "";
10+
[JsonPropertyName("subaddress")] public bool Subaddress { get; set; } = false;
11+
[JsonPropertyName("valid")] public bool Valid { get; set; } = false;
12+
}

0 commit comments

Comments
 (0)