Skip to content

Commit

Permalink
Merge pull request #28 from ipfs-shipyard/update/libp2p-key-as-cid
Browse files Browse the repository at this point in the history
Add base36 support, libp2p-key codec
  • Loading branch information
Arlodotexe committed Jan 24, 2024
2 parents e1a53b4 + f3f266f commit 8034e68
Show file tree
Hide file tree
Showing 6 changed files with 285 additions and 84 deletions.
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

0 comments on commit 8034e68

Please sign in to comment.