Skip to content

Commit 103af30

Browse files
committed
TonUtils.Adnl, Base32
1 parent 614fb29 commit 103af30

File tree

4 files changed

+210
-0
lines changed

4 files changed

+210
-0
lines changed
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
namespace TonLibDotNet.Utils
2+
{
3+
public class AdnlUtilsTests
4+
{
5+
[Theory]
6+
[InlineData("UWYYz2y+kAT2iD50LJouPKU+0C4+NvTO9iqY7h5EkXQ=", "viwmggpns7jabhwra7hile2fy6kkpwqfy7dn5go6yvjr3q6isixjwkf")]
7+
public void Encode(string source, string result)
8+
{
9+
var bytes = Convert.FromBase64String(source);
10+
Assert.Equal(result, AdnlUtils.Instance.Encode(bytes));
11+
}
12+
13+
[Theory]
14+
[InlineData("viwmggpns7jabhwra7hile2fy6kkpwqfy7dn5go6yvjr3q6isixjwkf", "UWYYz2y+kAT2iD50LJouPKU+0C4+NvTO9iqY7h5EkXQ=")]
15+
public void Decode(string source, string result)
16+
{
17+
var bytes = AdnlUtils.Instance.Decode(source);
18+
Assert.Equal(result, Convert.ToBase64String(bytes));
19+
}
20+
}
21+
}

TonLibDotNet/TonUtils.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,5 +19,10 @@ public static class TonUtils
1919
/// Useful function to work with text strings.
2020
/// </summary>
2121
public static TextUtils Text { get; } = TextUtils.Instance;
22+
23+
/// <summary>
24+
/// Useful function to work with ADNL addresses.
25+
/// </summary>
26+
public static AdnlUtils Adnl { get; } = AdnlUtils.Instance;
2227
}
2328
}

TonLibDotNet/Utils/AdnlUtils.cs

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
namespace TonLibDotNet.Utils
2+
{
3+
public class AdnlUtils
4+
{
5+
public static readonly AdnlUtils Instance = new ();
6+
7+
/// <summary>
8+
/// Encodes 256bit ADNL address into 55-characters string.
9+
/// </summary>
10+
/// <param name="adnl">ADNL address bytes.</param>
11+
/// <returns>String ADNL representation (55 characters).</returns>
12+
/// <exception cref="ArgumentOutOfRangeException">Source byte array length is not equal to 32.</exception>
13+
/// <seealso href="https://github.com/ton-blockchain/ton/blob/24dc184a2ea67f9c47042b4104bbb4d82289fac1/crypto/common/util.cpp#L195">Reference implementation</seealso>
14+
public string Encode(ReadOnlySpan<byte> adnl)
15+
{
16+
if (adnl.Length != 32)
17+
{
18+
throw new ArgumentOutOfRangeException(nameof(adnl), "Must be 32 bytes");
19+
}
20+
21+
Span<byte> buf = stackalloc byte[35];
22+
buf[0] = 0x2d;
23+
adnl.CopyTo(buf[1..]);
24+
var crc = Crc16.Ccitt.ComputeChecksum(buf[..33]);
25+
buf[33] = (byte)((crc >> 8) & 0xFF);
26+
buf[34] = (byte)(crc & 0xFF);
27+
28+
return Base32.Encode(buf).ToLowerInvariant()[1..];
29+
}
30+
31+
/// <summary>
32+
/// Decodes ADNL address string into 32 bytes (256 bits).
33+
/// </summary>
34+
/// <param name="adnl">ADNL address as string (55 characters).</param>
35+
/// <returns>ADNL address as 32 bytes.</returns>
36+
/// <exception cref="ArgumentNullException">Input string is null or empty.</exception>
37+
/// <exception cref="ArgumentException">Input string length is not 55.</exception>
38+
public Span<byte> Decode(string adnl)
39+
{
40+
if (string.IsNullOrEmpty(adnl))
41+
{
42+
throw new ArgumentNullException(nameof(adnl));
43+
}
44+
45+
if (adnl.Length != 55)
46+
{
47+
throw new ArgumentException("Must be 55 characters", nameof(adnl));
48+
}
49+
50+
var bytes = Base32.Decode("F" + adnl);
51+
return bytes.AsSpan(1, 32);
52+
}
53+
}
54+
}

TonLibDotNet/Utils/Base32.cs

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
namespace TonLibDotNet.Utils
2+
{
3+
/// <summary>
4+
/// Based on <see href="https://stackoverflow.com/questions/641361/base32-decoding"/>.
5+
/// </summary>
6+
public static class Base32
7+
{
8+
public static byte[] Decode(string input)
9+
{
10+
if (string.IsNullOrEmpty(input))
11+
{
12+
throw new ArgumentNullException(nameof(input));
13+
}
14+
15+
input = input.TrimEnd('='); //remove padding characters
16+
int byteCount = input.Length * 5 / 8; //this must be TRUNCATED
17+
byte[] returnArray = new byte[byteCount];
18+
19+
byte curByte = 0, bitsRemaining = 8;
20+
int mask = 0, arrayIndex = 0;
21+
22+
foreach (char c in input)
23+
{
24+
int cValue = CharToValue(c);
25+
26+
if (bitsRemaining > 5)
27+
{
28+
mask = cValue << (bitsRemaining - 5);
29+
curByte = (byte)(curByte | mask);
30+
bitsRemaining -= 5;
31+
}
32+
else
33+
{
34+
mask = cValue >> (5 - bitsRemaining);
35+
curByte = (byte)(curByte | mask);
36+
returnArray[arrayIndex++] = curByte;
37+
curByte = (byte)(cValue << (3 + bitsRemaining));
38+
bitsRemaining += 3;
39+
}
40+
}
41+
42+
//if we didn't end with a full byte
43+
if (arrayIndex != byteCount)
44+
{
45+
returnArray[arrayIndex] = curByte;
46+
}
47+
48+
return returnArray;
49+
}
50+
51+
public static string Encode(ReadOnlySpan<byte> input)
52+
{
53+
if (input.Length == 0)
54+
{
55+
throw new ArgumentNullException(nameof(input));
56+
}
57+
58+
int charCount = (int)Math.Ceiling(input.Length / 5d) * 8;
59+
Span<char> returnArray = stackalloc char[charCount];
60+
61+
byte nextChar = 0, bitsRemaining = 5;
62+
int arrayIndex = 0;
63+
64+
foreach (byte b in input)
65+
{
66+
nextChar = (byte)(nextChar | (b >> (8 - bitsRemaining)));
67+
returnArray[arrayIndex++] = ValueToChar(nextChar);
68+
69+
if (bitsRemaining < 4)
70+
{
71+
nextChar = (byte)((b >> (3 - bitsRemaining)) & 31);
72+
returnArray[arrayIndex++] = ValueToChar(nextChar);
73+
bitsRemaining += 5;
74+
}
75+
76+
bitsRemaining -= 3;
77+
nextChar = (byte)((b << bitsRemaining) & 31);
78+
}
79+
80+
//if we didn't end with a full char
81+
if (arrayIndex != charCount)
82+
{
83+
returnArray[arrayIndex++] = ValueToChar(nextChar);
84+
while (arrayIndex != charCount) returnArray[arrayIndex++] = '='; //padding
85+
}
86+
87+
return new string(returnArray);
88+
}
89+
90+
private static int CharToValue(char c)
91+
{
92+
int value = c;
93+
94+
//65-90 == uppercase letters
95+
if (value < 91 && value > 64)
96+
{
97+
return value - 65;
98+
}
99+
100+
//50-55 == numbers 2-7
101+
if (value < 56 && value > 49)
102+
{
103+
return value - 24;
104+
}
105+
106+
//97-122 == lowercase letters
107+
if (value < 123 && value > 96)
108+
{
109+
return value - 97;
110+
}
111+
112+
throw new ArgumentException("Character is not a Base32 character.", nameof(c));
113+
}
114+
115+
private static char ValueToChar(byte b)
116+
{
117+
if (b < 26)
118+
{
119+
return (char)(b + 65);
120+
}
121+
122+
if (b < 32)
123+
{
124+
return (char)(b + 24);
125+
}
126+
127+
throw new ArgumentException("Byte is not a valid Base32 value.", nameof(b));
128+
}
129+
}
130+
}

0 commit comments

Comments
 (0)