Skip to content

Commit e61b309

Browse files
authored
PublicKey constructor will quietly accept invalid inputs (#403)
* Fatal error for bad value passed to PublicKey constructor * Fix invalid public key arg in test * Version bump * Remove IsMaybeEncoded use IsValidWithoutWhitespace instead
1 parent b65c2f2 commit e61b309

File tree

5 files changed

+58
-16
lines changed

5 files changed

+58
-16
lines changed

SharedBuildProperties.props

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
33
<PropertyGroup>
44
<Product>Solnet</Product>
5-
<Version>6.0.13</Version>
5+
<Version>6.0.14</Version>
66
<Copyright>Copyright 2022 &#169; Solnet</Copyright>
77
<Authors>blockmountain</Authors>
88
<PublisherName>blockmountain</PublisherName>

src/Solnet.Wallet/PublicKey.cs

+17-2
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,9 @@ public PublicKey(byte[] key)
7979
/// <param name="key">The public key as base58 encoded string.</param>
8080
public PublicKey(string key)
8181
{
82-
Key = key ?? throw new ArgumentNullException(nameof(key));
82+
if (key == null) throw new ArgumentNullException(nameof(key));
83+
if (!FastCheck(key)) throw new ArgumentException("publickey contains a non-base58 character");
84+
Key = key;
8385
}
8486

8587
/// <summary>
@@ -201,10 +203,11 @@ public bool IsValid()
201203
/// <returns>Returns true if the input is a valid key, false otherwise.</returns>
202204
public static bool IsValid(string key, bool validateCurve = false)
203205
{
204-
if(!string.IsNullOrEmpty(key))
206+
if (!string.IsNullOrEmpty(key))
205207
{
206208
try
207209
{
210+
if (!FastCheck(key)) return false;
208211
return IsValid(Encoders.Base58.DecodeData(key), validateCurve);
209212
}
210213
catch(Exception)
@@ -249,6 +252,18 @@ public static bool IsValid(ReadOnlySpan<byte> key, bool validateCurve = false)
249252
return key != null && key.Length == PublicKeyLength && (!validateCurve || key.IsOnCurve());
250253
}
251254

255+
/// <summary>
256+
/// Fast validation to determine whether this is a valid public key input pattern.
257+
/// Checks are valid characters for base58 and no whitespace.
258+
/// Avoids performing the conversion to a buffer and checking it is actually 32 bytes as a permformance trade-off.
259+
/// </summary>
260+
/// <param name="value">public key value to check</param>
261+
/// <returns>true means good, false means bad</returns>
262+
private static bool FastCheck(string value)
263+
{
264+
return Base58Encoder.IsValidWithoutWhitespace(value);
265+
}
266+
252267
#region KeyDerivation
253268

254269
/// <summary>

src/Solnet.Wallet/Utilities/Base58Encoder.cs

+21-12
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
using System;
44
using System.Collections;
5+
using System.Collections.Generic;
56
using System.Linq;
67

78
namespace Solnet.Wallet.Utilities
@@ -15,6 +16,7 @@ public sealed class Base58Encoder : DataEncoder
1516
/// The base58 characters.
1617
/// </summary>
1718
private static readonly char[] PszBase58 = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz".ToCharArray();
19+
private static readonly Dictionary<char, bool> Validator = PszBase58.ToDictionary(x => x, x => true);
1820

1921
/// <summary>
2022
///
@@ -37,17 +39,6 @@ public sealed class Base58Encoder : DataEncoder
3739
-1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1,
3840
-1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1,
3941
};
40-
/// <summary>
41-
/// Fast check if the string to know if base58 str
42-
/// </summary>
43-
/// <param name="str"></param>
44-
/// <returns></returns>
45-
public bool IsMaybeEncoded(string str)
46-
{
47-
bool maybeB58 = str.All(t => ((IList)PszBase58).Contains(t));
48-
49-
return maybeB58 && str.Length > 0;
50-
}
5142

5243
/// <summary>
5344
/// Encode the data.
@@ -170,5 +161,23 @@ public override byte[] DecodeData(string encoded)
170161
vch[i2++] = b256[it2++];
171162
return vch;
172163
}
164+
165+
166+
/// <summary>
167+
/// Strict validation with no whitespace allowed
168+
/// </summary>
169+
/// <param name="value">Base58 string data</param>
170+
/// <returns></returns>
171+
/// <exception cref="ArgumentNullException"></exception>
172+
public static bool IsValidWithoutWhitespace(string value)
173+
{
174+
if (value == null) throw new ArgumentNullException(nameof(value));
175+
for (var ix = 0; ix < value.Length; ix++)
176+
if (!Validator.ContainsKey(value[ix]))
177+
return false;
178+
return true;
179+
}
180+
173181
}
174-
}
182+
183+
}

test/Solnet.Extensions.Test/TokenWalletTest.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -284,7 +284,7 @@ public void TestTokenWalletSendAddressCheck()
284284
Assert.IsFalse(testTokenAccount.IsAssociatedTokenAccount);
285285

286286
// trigger send to bogus target wallet
287-
var targetOwner = "FAILzxtbcZ2vy3GSLLsZTEhbAqXPTRvEyoxa8wxSqKp5";
287+
var targetOwner = "BADxzxtbcZ2vy3GSLLsZTEhbAqXPTRvEyoxa8wxSqKp5";
288288
wallet.Send(testTokenAccount, 1M, targetOwner, signer.PublicKey, builder => builder.Build(signer));
289289

290290
}

test/Solnet.Wallet.Test/KeysTest.cs

+18
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,8 @@ public void TestFindProgramAddress()
294294
public void TestIsValid()
295295
{
296296
Assert.IsTrue(PublicKey.IsValid("GUs5qLUfsEHkcMB9T38vjr18ypEhRuNWiePW2LoK4E3K"));
297+
Assert.IsFalse(PublicKey.IsValid("GUs5qLUfsEHkcMB9T38vj*18ypEhRuNWiePW2LoK4E3K"));
298+
Assert.IsFalse(PublicKey.IsValid("GUs5qLUfsEHkcMB9T38vjr18ypEhRuNWiePW2LoK4E3K "));
297299
}
298300

299301
[TestMethod]
@@ -331,12 +333,28 @@ public void TestIsValid_False()
331333
public void TestIsValid_Empty_False()
332334
{
333335
Assert.IsFalse(PublicKey.IsValid(""));
336+
Assert.IsFalse(PublicKey.IsValid(" "));
334337
}
335338

336339
[TestMethod]
337340
public void TestIsValid_InvalidB58_False()
338341
{
339342
Assert.IsFalse(PublicKey.IsValid("lllllll"));
340343
}
344+
345+
[TestMethod]
346+
[ExpectedException(typeof(ArgumentException))]
347+
public void TestCreateBadPublicKeyFatal_1()
348+
{
349+
_ = new PublicKey("GUs5qLUfsEHkcMB9T38vjr18ypEhRuNWiePW2LoK4E3K ");
350+
}
351+
352+
[TestMethod]
353+
[ExpectedException(typeof(ArgumentException))]
354+
public void TestCreateBadPublicKeyFatal_2()
355+
{
356+
_ = new PublicKey("GUs5qLU&sEHkcMB9T38vjr18ypEhRuNWiePW2LoK4E3K");
357+
}
358+
341359
}
342360
}

0 commit comments

Comments
 (0)