Skip to content

Commit 8034e68

Browse files
authored
Merge pull request #28 from ipfs-shipyard/update/libp2p-key-as-cid
Add base36 support, libp2p-key codec
2 parents e1a53b4 + f3f266f commit 8034e68

File tree

6 files changed

+285
-84
lines changed

6 files changed

+285
-84
lines changed

src/Base32.cs

Lines changed: 75 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -1,91 +1,85 @@
1-
using System;
2-
using System.Collections.Generic;
3-
using System.Linq;
4-
using System.Text;
1+
namespace Ipfs;
52

6-
namespace Ipfs
3+
/// <summary>
4+
/// A codec for Base-32.
5+
/// </summary>
6+
/// <remarks>
7+
/// <para>
8+
/// A codec for Base-32, <see cref="Encode"/> and <see cref="Decode"/>. Adds the extension method <see cref="ToBase32"/>
9+
/// to encode a byte array and <see cref="FromBase32"/> to decode a Base-32 string.
10+
/// </para>
11+
/// <para>
12+
/// <see cref="Encode"/> and <see cref="ToBase32"/> produce the lower case form of
13+
/// <see href="https://tools.ietf.org/html/rfc4648"/> with no padding.
14+
/// <see cref="Decode"/> and <see cref="FromBase32"/> are case-insensitive and
15+
/// allow optional padding.
16+
/// </para>
17+
/// <para>
18+
/// A thin wrapper around <see href="https://github.com/ssg/SimpleBase"/>.
19+
/// </para>
20+
/// </remarks>
21+
public static class Base32
722
{
823
/// <summary>
9-
/// A codec for Base-32.
24+
/// Converts an array of 8-bit unsigned integers to its equivalent string representation that is
25+
/// encoded with base-32 characters.
26+
/// </summary>s
27+
/// <param name="input">
28+
/// An array of 8-bit unsigned integers.
29+
/// </param>
30+
/// <returns>
31+
/// The string representation, in base 32, of the contents of <paramref name="input"/>.
32+
/// </returns>
33+
public static string Encode(byte[] input)
34+
{
35+
return SimpleBase.Base32.Rfc4648.Encode(input, false).ToLowerInvariant();
36+
}
37+
38+
/// <summary>
39+
/// Converts an array of 8-bit unsigned integers to its equivalent string representation that is
40+
/// encoded with base-32 digits.
1041
/// </summary>
42+
/// <param name="bytes">
43+
/// An array of 8-bit unsigned integers.
44+
/// </param>
45+
/// <returns>
46+
/// The string representation, in base 32, of the contents of <paramref name="bytes"/>.
47+
/// </returns>
48+
public static string ToBase32(this byte[] bytes)
49+
{
50+
return Encode(bytes);
51+
}
52+
53+
/// <summary>
54+
/// Converts the specified <see cref="string"/>, which encodes binary data as base 32 digits,
55+
/// to an equivalent 8-bit unsigned integer array.
56+
/// </summary>
57+
/// <param name="input">
58+
/// The base 32 string to convert.
59+
/// </param>
60+
/// <returns>
61+
/// An array of 8-bit unsigned integers that is equivalent to <paramref name="input"/>.
62+
/// </returns>
1163
/// <remarks>
12-
/// <para>
13-
/// A codec for Base-32, <see cref="Encode"/> and <see cref="Decode"/>. Adds the extension method <see cref="ToBase32"/>
14-
/// to encode a byte array and <see cref="FromBase32"/> to decode a Base-32 string.
15-
/// </para>
16-
/// <para>
17-
/// <see cref="Encode"/> and <see cref="ToBase32"/> produce the lower case form of
18-
/// <see href="https://tools.ietf.org/html/rfc4648"/> with no padding.
19-
/// <see cref="Decode"/> and <see cref="FromBase32"/> are case-insensitive and
20-
/// allow optional padding.
21-
/// </para>
22-
/// <para>
23-
/// A thin wrapper around <see href="https://github.com/ssg/SimpleBase"/>.
24-
/// </para>
64+
/// <paramref name="input"/> is case-insensitive and allows padding.
2565
/// </remarks>
26-
public static class Base32
66+
public static byte[] Decode(string input)
2767
{
28-
/// <summary>
29-
/// Converts an array of 8-bit unsigned integers to its equivalent string representation that is
30-
/// encoded with base-32 characters.
31-
/// </summary>s
32-
/// <param name="input">
33-
/// An array of 8-bit unsigned integers.
34-
/// </param>
35-
/// <returns>
36-
/// The string representation, in base 32, of the contents of <paramref name="input"/>.
37-
/// </returns>
38-
public static string Encode(byte[] input)
39-
{
40-
return SimpleBase.Base32.Rfc4648.Encode(input, false).ToLowerInvariant();
41-
}
42-
43-
/// <summary>
44-
/// Converts an array of 8-bit unsigned integers to its equivalent string representation that is
45-
/// encoded with base-32 digits.
46-
/// </summary>
47-
/// <param name="bytes">
48-
/// An array of 8-bit unsigned integers.
49-
/// </param>
50-
/// <returns>
51-
/// The string representation, in base 32, of the contents of <paramref name="bytes"/>.
52-
/// </returns>
53-
public static string ToBase32(this byte[] bytes)
54-
{
55-
return Encode(bytes);
56-
}
57-
58-
/// <summary>
59-
/// Converts the specified <see cref="string"/>, which encodes binary data as base 32 digits,
60-
/// to an equivalent 8-bit unsigned integer array.
61-
/// </summary>
62-
/// <param name="input">
63-
/// The base 32 string to convert.
64-
/// </param>
65-
/// <returns>
66-
/// An array of 8-bit unsigned integers that is equivalent to <paramref name="input"/>.
67-
/// </returns>
68-
/// <remarks>
69-
/// <paramref name="input"/> is case-insensitive and allows padding.
70-
/// </remarks>
71-
public static byte[] Decode(string input)
72-
{
73-
return SimpleBase.Base32.Rfc4648.Decode(input);
74-
}
68+
return SimpleBase.Base32.Rfc4648.Decode(input);
69+
}
7570

76-
/// <summary>
77-
/// Converts the specified <see cref="string"/>, which encodes binary data as base 32 digits,
78-
/// to an equivalent 8-bit unsigned integer array.
79-
/// </summary>
80-
/// <param name="s">
81-
/// The base 32 string to convert; case-insensitive and allows padding.
82-
/// </param>
83-
/// <returns>
84-
/// An array of 8-bit unsigned integers that is equivalent to <paramref name="s"/>.
85-
/// </returns>
86-
public static byte[] FromBase32(this string s)
87-
{
88-
return Decode(s);
89-
}
71+
/// <summary>
72+
/// Converts the specified <see cref="string"/>, which encodes binary data as base 32 digits,
73+
/// to an equivalent 8-bit unsigned integer array.
74+
/// </summary>
75+
/// <param name="s">
76+
/// The base 32 string to convert; case-insensitive and allows padding.
77+
/// </param>
78+
/// <returns>
79+
/// An array of 8-bit unsigned integers that is equivalent to <paramref name="s"/>.
80+
/// </returns>
81+
public static byte[] FromBase32(this string s)
82+
{
83+
return Decode(s);
9084
}
9185
}

src/Base36.cs

Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
using System;
2+
using System.Linq;
3+
using System.Numerics;
4+
using System.Text;
5+
6+
namespace Ipfs
7+
{
8+
/// <summary>
9+
/// A codec for Base-36.
10+
/// </summary>
11+
/// <remarks>
12+
/// <para>
13+
/// Provides encoding and decoding functionality for Base-36, with methods <see cref="EncodeToStringUc"/> and <see cref="EncodeToStringLc"/> for encoding,
14+
/// and <see cref="DecodeString"/> for decoding. The encoding methods offer both uppercase and lowercase options.
15+
/// </para>
16+
/// <para>
17+
/// The implementation is case-insensitive for decoding and allows for efficient conversion between byte arrays and Base-36 strings.
18+
/// </para>
19+
/// <para>
20+
/// Ported from https://github.com/multiformats/go-base36/blob/v0.2.0/base36.go
21+
/// </para>
22+
/// </remarks>
23+
public static class Base36
24+
{
25+
// Constants for the encoding alphabets
26+
private const string UcAlphabet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
27+
private const string LcAlphabet = "0123456789abcdefghijklmnopqrstuvwxyz";
28+
private const int MaxDigitOrdinal = 'z';
29+
private const byte MaxDigitValueB36 = 35;
30+
31+
// Reverse lookup table for decoding
32+
private static readonly byte[] RevAlphabet = new byte[MaxDigitOrdinal + 1];
33+
34+
// Static constructor to initialize the reverse lookup table
35+
static Base36()
36+
{
37+
// Initialize the reverse alphabet array with default values
38+
for (int i = 0; i < RevAlphabet.Length; i++)
39+
{
40+
RevAlphabet[i] = MaxDigitValueB36 + 1;
41+
}
42+
43+
// Populate the reverse alphabet array for decoding
44+
for (int i = 0; i < UcAlphabet.Length; i++)
45+
{
46+
char c = UcAlphabet[i];
47+
RevAlphabet[c] = (byte)i;
48+
if (c > '9')
49+
{
50+
RevAlphabet[char.ToLower(c)] = (byte)i;
51+
}
52+
}
53+
}
54+
55+
/// <summary>
56+
/// Encodes a byte array to a Base-36 string using uppercase characters.
57+
/// </summary>
58+
/// <param name="bytes">
59+
/// The byte array to encode.
60+
/// </param>
61+
/// <returns>
62+
/// The encoded Base-36 string in uppercase.
63+
/// </returns>
64+
public static string EncodeToStringUc(byte[] bytes) => Encode(bytes, UcAlphabet);
65+
66+
/// <summary>
67+
/// Encodes a byte array to a Base-36 string using lowercase characters.
68+
/// </summary>
69+
/// <param name="bytes">
70+
/// The byte array to encode.
71+
/// </param>
72+
/// <returns>
73+
/// The encoded Base-36 string in lowercase.
74+
/// </returns>
75+
public static string EncodeToStringLc(byte[] bytes) => Encode(bytes, LcAlphabet);
76+
77+
// Core encoding logic for Base-36 conversion
78+
private static string Encode(byte[] input, string alphabet)
79+
{
80+
int zeroCount = 0;
81+
while (zeroCount < input.Length && input[zeroCount] == 0)
82+
{
83+
zeroCount++;
84+
}
85+
86+
int size = zeroCount + (input.Length - zeroCount) * 277 / 179 + 1;
87+
byte[] buffer = new byte[size];
88+
int index, stopIndex;
89+
uint carry;
90+
91+
stopIndex = size - 1;
92+
for (int i = zeroCount; i < input.Length; i++)
93+
{
94+
index = size - 1;
95+
carry = input[i];
96+
while (index > stopIndex || carry != 0)
97+
{
98+
carry += (uint)(buffer[index]) * 256;
99+
buffer[index] = (byte)(carry % 36);
100+
carry /= 36;
101+
index--;
102+
}
103+
stopIndex = index;
104+
}
105+
106+
// The purpose of this loop is to skip over the portion of the buffer that contains only zeros (after accounting for any leading zeros in the original input).
107+
// This is important for the encoding process, as these leading zeros are not represented in the base-36 encoded string.
108+
for (stopIndex = zeroCount; stopIndex < size && buffer[stopIndex] == 0; stopIndex++)
109+
{
110+
}
111+
112+
// Once the first non-zero byte is found, the actual encoding of the non-zero part of the buffer can begin.
113+
byte[] valueBuffer = new byte[buffer.Length - (stopIndex - zeroCount)];
114+
for (int i = 0; i < valueBuffer.Length; i++)
115+
{
116+
valueBuffer[i] = (byte)alphabet[buffer[stopIndex - zeroCount + i]];
117+
}
118+
119+
return Encoding.ASCII.GetString(valueBuffer);
120+
}
121+
122+
/// <summary>
123+
/// Decodes a Base-36 encoded string to a byte array.
124+
/// </summary>
125+
/// <param name="s">
126+
/// The Base-36 encoded string to decode.
127+
/// </param>
128+
/// <returns>
129+
/// The decoded byte array.
130+
/// </returns>
131+
/// <exception cref="ArgumentException">
132+
/// Thrown if the input string is null or empty.
133+
/// </exception>
134+
/// <exception cref="FormatException">
135+
/// Thrown if the input string contains characters not valid in Base-36.
136+
/// </exception>
137+
public static byte[] DecodeString(string s)
138+
{
139+
if (string.IsNullOrEmpty(s))
140+
{
141+
return Array.Empty<byte>();
142+
}
143+
144+
int zeroCount = 0;
145+
while (zeroCount < s.Length && s[zeroCount] == '0')
146+
{
147+
zeroCount++;
148+
}
149+
150+
byte[] binu = new byte[2 * ((s.Length) * 179 / 277 + 1)];
151+
uint[] outi = new uint[(s.Length + 3) / 4];
152+
153+
foreach (char r in s)
154+
{
155+
if (r > MaxDigitOrdinal || RevAlphabet[r] > MaxDigitValueB36)
156+
{
157+
throw new FormatException($"Invalid base36 character ({r}).");
158+
}
159+
160+
ulong c = RevAlphabet[r];
161+
162+
for (int j = outi.Length - 1; j >= 0; j--)
163+
{
164+
ulong t = (ulong)outi[j] * 36 + c;
165+
c = (t >> 32);
166+
outi[j] = (uint)(t & 0xFFFFFFFF);
167+
}
168+
}
169+
170+
uint mask = (uint)((s.Length % 4) * 8);
171+
if (mask == 0)
172+
{
173+
mask = 32;
174+
}
175+
mask -= 8;
176+
177+
int outIndex = 0;
178+
for (int j = 0; j < outi.Length; j++)
179+
{
180+
for (; mask < 32; mask -= 8)
181+
{
182+
binu[outIndex] = (byte)(outi[j] >> (int)mask);
183+
outIndex++;
184+
}
185+
mask = 24;
186+
}
187+
188+
for (int msb = zeroCount; msb < outIndex; msb++)
189+
{
190+
if (binu[msb] > 0)
191+
{
192+
int lengthToCopy = outIndex - msb;
193+
byte[] result = new byte[lengthToCopy];
194+
Array.Copy(binu, msb, result, 0, lengthToCopy);
195+
return result;
196+
}
197+
}
198+
199+
return new byte[outIndex - zeroCount];
200+
}
201+
}
202+
}

src/IKey.cs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,12 @@ public interface IKey
99
/// Unique identifier.
1010
/// </summary>
1111
/// <value>
12-
/// The <see cref="MultiHash"/> of the key's public key.
12+
/// A <see cref="Cid"/> containing the <see cref="MultiHash"/> of the public libp2p-key encoded in the requested Multibase.
1313
/// </value>
14-
MultiHash Id { get; }
14+
/// <remarks>
15+
/// The CID of the ipns libp2p-key encoded in the requested multibase.
16+
/// </remarks>
17+
Cid Id { get; }
1518

1619
/// <summary>
1720
/// The locally assigned name to the key.

src/IpfsCore.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
<DebugType>portable</DebugType>
88

99
<!-- https://semver.org/spec/v2.0.0.html -->
10-
<Version>0.0.5</Version>
10+
<Version>0.1.0</Version>
1111
<AssemblyVersion>$(Version)</AssemblyVersion>
1212

1313
<!-- Nuget specs -->

0 commit comments

Comments
 (0)