Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add base36 support, libp2p-key codec #28

Merged
merged 7 commits into from
Jan 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
156 changes: 75 additions & 81 deletions src/Base32.cs
Original file line number Diff line number Diff line change
@@ -1,91 +1,85 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Ipfs;

namespace Ipfs
/// <summary>
/// A codec for Base-32.
/// </summary>
/// <remarks>
/// <para>
/// A codec for Base-32, <see cref="Encode"/> and <see cref="Decode"/>. Adds the extension method <see cref="ToBase32"/>
/// to encode a byte array and <see cref="FromBase32"/> to decode a Base-32 string.
/// </para>
/// <para>
/// <see cref="Encode"/> and <see cref="ToBase32"/> produce the lower case form of
/// <see href="https://tools.ietf.org/html/rfc4648"/> with no padding.
/// <see cref="Decode"/> and <see cref="FromBase32"/> are case-insensitive and
/// allow optional padding.
/// </para>
/// <para>
/// A thin wrapper around <see href="https://github.com/ssg/SimpleBase"/>.
/// </para>
/// </remarks>
public static class Base32
{
/// <summary>
/// A codec for Base-32.
/// Converts an array of 8-bit unsigned integers to its equivalent string representation that is
/// encoded with base-32 characters.
/// </summary>s
/// <param name="input">
/// An array of 8-bit unsigned integers.
/// </param>
/// <returns>
/// The string representation, in base 32, of the contents of <paramref name="input"/>.
/// </returns>
public static string Encode(byte[] input)
{
return SimpleBase.Base32.Rfc4648.Encode(input, false).ToLowerInvariant();
}

/// <summary>
/// Converts an array of 8-bit unsigned integers to its equivalent string representation that is
/// encoded with base-32 digits.
/// </summary>
/// <param name="bytes">
/// An array of 8-bit unsigned integers.
/// </param>
/// <returns>
/// The string representation, in base 32, of the contents of <paramref name="bytes"/>.
/// </returns>
public static string ToBase32(this byte[] bytes)
{
return Encode(bytes);
}

/// <summary>
/// Converts the specified <see cref="string"/>, which encodes binary data as base 32 digits,
/// to an equivalent 8-bit unsigned integer array.
/// </summary>
/// <param name="input">
/// The base 32 string to convert.
/// </param>
/// <returns>
/// An array of 8-bit unsigned integers that is equivalent to <paramref name="input"/>.
/// </returns>
/// <remarks>
/// <para>
/// A codec for Base-32, <see cref="Encode"/> and <see cref="Decode"/>. Adds the extension method <see cref="ToBase32"/>
/// to encode a byte array and <see cref="FromBase32"/> to decode a Base-32 string.
/// </para>
/// <para>
/// <see cref="Encode"/> and <see cref="ToBase32"/> produce the lower case form of
/// <see href="https://tools.ietf.org/html/rfc4648"/> with no padding.
/// <see cref="Decode"/> and <see cref="FromBase32"/> are case-insensitive and
/// allow optional padding.
/// </para>
/// <para>
/// A thin wrapper around <see href="https://github.com/ssg/SimpleBase"/>.
/// </para>
/// <paramref name="input"/> is case-insensitive and allows padding.
/// </remarks>
public static class Base32
public static byte[] Decode(string input)
{
/// <summary>
/// Converts an array of 8-bit unsigned integers to its equivalent string representation that is
/// encoded with base-32 characters.
/// </summary>s
/// <param name="input">
/// An array of 8-bit unsigned integers.
/// </param>
/// <returns>
/// The string representation, in base 32, of the contents of <paramref name="input"/>.
/// </returns>
public static string Encode(byte[] input)
{
return SimpleBase.Base32.Rfc4648.Encode(input, false).ToLowerInvariant();
}

/// <summary>
/// Converts an array of 8-bit unsigned integers to its equivalent string representation that is
/// encoded with base-32 digits.
/// </summary>
/// <param name="bytes">
/// An array of 8-bit unsigned integers.
/// </param>
/// <returns>
/// The string representation, in base 32, of the contents of <paramref name="bytes"/>.
/// </returns>
public static string ToBase32(this byte[] bytes)
{
return Encode(bytes);
}

/// <summary>
/// Converts the specified <see cref="string"/>, which encodes binary data as base 32 digits,
/// to an equivalent 8-bit unsigned integer array.
/// </summary>
/// <param name="input">
/// The base 32 string to convert.
/// </param>
/// <returns>
/// An array of 8-bit unsigned integers that is equivalent to <paramref name="input"/>.
/// </returns>
/// <remarks>
/// <paramref name="input"/> is case-insensitive and allows padding.
/// </remarks>
public static byte[] Decode(string input)
{
return SimpleBase.Base32.Rfc4648.Decode(input);
}
return SimpleBase.Base32.Rfc4648.Decode(input);
}

/// <summary>
/// Converts the specified <see cref="string"/>, which encodes binary data as base 32 digits,
/// to an equivalent 8-bit unsigned integer array.
/// </summary>
/// <param name="s">
/// The base 32 string to convert; case-insensitive and allows padding.
/// </param>
/// <returns>
/// An array of 8-bit unsigned integers that is equivalent to <paramref name="s"/>.
/// </returns>
public static byte[] FromBase32(this string s)
{
return Decode(s);
}
/// <summary>
/// Converts the specified <see cref="string"/>, which encodes binary data as base 32 digits,
/// to an equivalent 8-bit unsigned integer array.
/// </summary>
/// <param name="s">
/// The base 32 string to convert; case-insensitive and allows padding.
/// </param>
/// <returns>
/// An array of 8-bit unsigned integers that is equivalent to <paramref name="s"/>.
/// </returns>
public static byte[] FromBase32(this string s)
{
return Decode(s);
}
}
202 changes: 202 additions & 0 deletions src/Base36.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
using System;
using System.Linq;
using System.Numerics;
using System.Text;

namespace Ipfs
{
/// <summary>
/// A codec for Base-36.
/// </summary>
/// <remarks>
/// <para>
/// Provides encoding and decoding functionality for Base-36, with methods <see cref="EncodeToStringUc"/> and <see cref="EncodeToStringLc"/> for encoding,
/// and <see cref="DecodeString"/> for decoding. The encoding methods offer both uppercase and lowercase options.
/// </para>
/// <para>
/// The implementation is case-insensitive for decoding and allows for efficient conversion between byte arrays and Base-36 strings.
/// </para>
/// <para>
/// Ported from https://github.com/multiformats/go-base36/blob/v0.2.0/base36.go
/// </para>
/// </remarks>
public static class Base36
{
// Constants for the encoding alphabets
private const string UcAlphabet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
private const string LcAlphabet = "0123456789abcdefghijklmnopqrstuvwxyz";
private const int MaxDigitOrdinal = 'z';
private const byte MaxDigitValueB36 = 35;

// Reverse lookup table for decoding
private static readonly byte[] RevAlphabet = new byte[MaxDigitOrdinal + 1];

// Static constructor to initialize the reverse lookup table
static Base36()
{
// Initialize the reverse alphabet array with default values
for (int i = 0; i < RevAlphabet.Length; i++)
{
RevAlphabet[i] = MaxDigitValueB36 + 1;
}

// Populate the reverse alphabet array for decoding
for (int i = 0; i < UcAlphabet.Length; i++)
{
char c = UcAlphabet[i];
RevAlphabet[c] = (byte)i;
if (c > '9')
{
RevAlphabet[char.ToLower(c)] = (byte)i;
}
}
}

/// <summary>
/// Encodes a byte array to a Base-36 string using uppercase characters.
/// </summary>
/// <param name="bytes">
/// The byte array to encode.
/// </param>
/// <returns>
/// The encoded Base-36 string in uppercase.
/// </returns>
public static string EncodeToStringUc(byte[] bytes) => Encode(bytes, UcAlphabet);

/// <summary>
/// Encodes a byte array to a Base-36 string using lowercase characters.
/// </summary>
/// <param name="bytes">
/// The byte array to encode.
/// </param>
/// <returns>
/// The encoded Base-36 string in lowercase.
/// </returns>
public static string EncodeToStringLc(byte[] bytes) => Encode(bytes, LcAlphabet);

// Core encoding logic for Base-36 conversion
private static string Encode(byte[] input, string alphabet)
{
int zeroCount = 0;
while (zeroCount < input.Length && input[zeroCount] == 0)
{
zeroCount++;
}

int size = zeroCount + (input.Length - zeroCount) * 277 / 179 + 1;
byte[] buffer = new byte[size];
int index, stopIndex;
uint carry;

stopIndex = size - 1;
for (int i = zeroCount; i < input.Length; i++)
{
index = size - 1;
carry = input[i];
while (index > stopIndex || carry != 0)
{
carry += (uint)(buffer[index]) * 256;
buffer[index] = (byte)(carry % 36);
carry /= 36;
index--;
}
stopIndex = index;
}

// 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).
// This is important for the encoding process, as these leading zeros are not represented in the base-36 encoded string.
for (stopIndex = zeroCount; stopIndex < size && buffer[stopIndex] == 0; stopIndex++)
{
}

// Once the first non-zero byte is found, the actual encoding of the non-zero part of the buffer can begin.
byte[] valueBuffer = new byte[buffer.Length - (stopIndex - zeroCount)];
for (int i = 0; i < valueBuffer.Length; i++)
{
valueBuffer[i] = (byte)alphabet[buffer[stopIndex - zeroCount + i]];
}

return Encoding.ASCII.GetString(valueBuffer);
}

/// <summary>
/// Decodes a Base-36 encoded string to a byte array.
/// </summary>
/// <param name="s">
/// The Base-36 encoded string to decode.
/// </param>
/// <returns>
/// The decoded byte array.
/// </returns>
/// <exception cref="ArgumentException">
/// Thrown if the input string is null or empty.
/// </exception>
/// <exception cref="FormatException">
/// Thrown if the input string contains characters not valid in Base-36.
/// </exception>
public static byte[] DecodeString(string s)
{
if (string.IsNullOrEmpty(s))
{
return Array.Empty<byte>();
}

int zeroCount = 0;
while (zeroCount < s.Length && s[zeroCount] == '0')
{
zeroCount++;
}

byte[] binu = new byte[2 * ((s.Length) * 179 / 277 + 1)];
uint[] outi = new uint[(s.Length + 3) / 4];

foreach (char r in s)
{
if (r > MaxDigitOrdinal || RevAlphabet[r] > MaxDigitValueB36)
{
throw new FormatException($"Invalid base36 character ({r}).");
}

ulong c = RevAlphabet[r];

for (int j = outi.Length - 1; j >= 0; j--)
{
ulong t = (ulong)outi[j] * 36 + c;
c = (t >> 32);
outi[j] = (uint)(t & 0xFFFFFFFF);
}
}

uint mask = (uint)((s.Length % 4) * 8);
if (mask == 0)
{
mask = 32;
}
mask -= 8;

int outIndex = 0;
for (int j = 0; j < outi.Length; j++)
{
for (; mask < 32; mask -= 8)
{
binu[outIndex] = (byte)(outi[j] >> (int)mask);
outIndex++;
}
mask = 24;
}

for (int msb = zeroCount; msb < outIndex; msb++)
{
if (binu[msb] > 0)
{
int lengthToCopy = outIndex - msb;
byte[] result = new byte[lengthToCopy];
Array.Copy(binu, msb, result, 0, lengthToCopy);
return result;
}
}

return new byte[outIndex - zeroCount];
}
}
}
7 changes: 5 additions & 2 deletions src/IKey.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,12 @@ public interface IKey
/// Unique identifier.
/// </summary>
/// <value>
/// The <see cref="MultiHash"/> of the key's public key.
/// A <see cref="Cid"/> containing the <see cref="MultiHash"/> of the public libp2p-key encoded in the requested Multibase.
/// </value>
MultiHash Id { get; }
/// <remarks>
/// The CID of the ipns libp2p-key encoded in the requested multibase.
/// </remarks>
Cid Id { get; }

/// <summary>
/// The locally assigned name to the key.
Expand Down
2 changes: 1 addition & 1 deletion src/IpfsCore.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<DebugType>portable</DebugType>

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

<!-- Nuget specs -->
Expand Down
Loading
Loading