diff --git a/src/Base32.cs b/src/Base32.cs index f942e363..8c02996e 100644 --- a/src/Base32.cs +++ b/src/Base32.cs @@ -1,91 +1,85 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; +namespace Ipfs; -namespace Ipfs +/// +/// A codec for Base-32. +/// +/// +/// +/// A codec for Base-32, and . Adds the extension method +/// to encode a byte array and to decode a Base-32 string. +/// +/// +/// and produce the lower case form of +/// with no padding. +/// and are case-insensitive and +/// allow optional padding. +/// +/// +/// A thin wrapper around . +/// +/// +public static class Base32 { /// - /// 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. + /// s + /// + /// An array of 8-bit unsigned integers. + /// + /// + /// The string representation, in base 32, of the contents of . + /// + public static string Encode(byte[] input) + { + return SimpleBase.Base32.Rfc4648.Encode(input, false).ToLowerInvariant(); + } + + /// + /// Converts an array of 8-bit unsigned integers to its equivalent string representation that is + /// encoded with base-32 digits. /// + /// + /// An array of 8-bit unsigned integers. + /// + /// + /// The string representation, in base 32, of the contents of . + /// + public static string ToBase32(this byte[] bytes) + { + return Encode(bytes); + } + + /// + /// Converts the specified , which encodes binary data as base 32 digits, + /// to an equivalent 8-bit unsigned integer array. + /// + /// + /// The base 32 string to convert. + /// + /// + /// An array of 8-bit unsigned integers that is equivalent to . + /// /// - /// - /// A codec for Base-32, and . Adds the extension method - /// to encode a byte array and to decode a Base-32 string. - /// - /// - /// and produce the lower case form of - /// with no padding. - /// and are case-insensitive and - /// allow optional padding. - /// - /// - /// A thin wrapper around . - /// + /// is case-insensitive and allows padding. /// - public static class Base32 + public static byte[] Decode(string input) { - /// - /// Converts an array of 8-bit unsigned integers to its equivalent string representation that is - /// encoded with base-32 characters. - /// s - /// - /// An array of 8-bit unsigned integers. - /// - /// - /// The string representation, in base 32, of the contents of . - /// - public static string Encode(byte[] input) - { - return SimpleBase.Base32.Rfc4648.Encode(input, false).ToLowerInvariant(); - } - - /// - /// Converts an array of 8-bit unsigned integers to its equivalent string representation that is - /// encoded with base-32 digits. - /// - /// - /// An array of 8-bit unsigned integers. - /// - /// - /// The string representation, in base 32, of the contents of . - /// - public static string ToBase32(this byte[] bytes) - { - return Encode(bytes); - } - - /// - /// Converts the specified , which encodes binary data as base 32 digits, - /// to an equivalent 8-bit unsigned integer array. - /// - /// - /// The base 32 string to convert. - /// - /// - /// An array of 8-bit unsigned integers that is equivalent to . - /// - /// - /// is case-insensitive and allows padding. - /// - public static byte[] Decode(string input) - { - return SimpleBase.Base32.Rfc4648.Decode(input); - } + return SimpleBase.Base32.Rfc4648.Decode(input); + } - /// - /// Converts the specified , which encodes binary data as base 32 digits, - /// to an equivalent 8-bit unsigned integer array. - /// - /// - /// The base 32 string to convert; case-insensitive and allows padding. - /// - /// - /// An array of 8-bit unsigned integers that is equivalent to . - /// - public static byte[] FromBase32(this string s) - { - return Decode(s); - } + /// + /// Converts the specified , which encodes binary data as base 32 digits, + /// to an equivalent 8-bit unsigned integer array. + /// + /// + /// The base 32 string to convert; case-insensitive and allows padding. + /// + /// + /// An array of 8-bit unsigned integers that is equivalent to . + /// + public static byte[] FromBase32(this string s) + { + return Decode(s); } } diff --git a/src/Base36.cs b/src/Base36.cs new file mode 100644 index 00000000..6af51498 --- /dev/null +++ b/src/Base36.cs @@ -0,0 +1,202 @@ +using System; +using System.Linq; +using System.Numerics; +using System.Text; + +namespace Ipfs +{ + /// + /// A codec for Base-36. + /// + /// + /// + /// Provides encoding and decoding functionality for Base-36, with methods and for encoding, + /// and for decoding. The encoding methods offer both uppercase and lowercase options. + /// + /// + /// The implementation is case-insensitive for decoding and allows for efficient conversion between byte arrays and Base-36 strings. + /// + /// + /// Ported from https://github.com/multiformats/go-base36/blob/v0.2.0/base36.go + /// + /// + 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; + } + } + } + + /// + /// Encodes a byte array to a Base-36 string using uppercase characters. + /// + /// + /// The byte array to encode. + /// + /// + /// The encoded Base-36 string in uppercase. + /// + public static string EncodeToStringUc(byte[] bytes) => Encode(bytes, UcAlphabet); + + /// + /// Encodes a byte array to a Base-36 string using lowercase characters. + /// + /// + /// The byte array to encode. + /// + /// + /// The encoded Base-36 string in lowercase. + /// + 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); + } + + /// + /// Decodes a Base-36 encoded string to a byte array. + /// + /// + /// The Base-36 encoded string to decode. + /// + /// + /// The decoded byte array. + /// + /// + /// Thrown if the input string is null or empty. + /// + /// + /// Thrown if the input string contains characters not valid in Base-36. + /// + public static byte[] DecodeString(string s) + { + if (string.IsNullOrEmpty(s)) + { + return Array.Empty(); + } + + 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]; + } + } +} diff --git a/src/IKey.cs b/src/IKey.cs index 42b73da1..59502f24 100644 --- a/src/IKey.cs +++ b/src/IKey.cs @@ -9,9 +9,12 @@ public interface IKey /// Unique identifier. /// /// - /// The of the key's public key. + /// A containing the of the public libp2p-key encoded in the requested Multibase. /// - MultiHash Id { get; } + /// + /// The CID of the ipns libp2p-key encoded in the requested multibase. + /// + Cid Id { get; } /// /// The locally assigned name to the key. diff --git a/src/IpfsCore.csproj b/src/IpfsCore.csproj index 1e6a76e6..653d4042 100644 --- a/src/IpfsCore.csproj +++ b/src/IpfsCore.csproj @@ -7,7 +7,7 @@ portable - 0.0.5 + 0.1.0 $(Version) diff --git a/src/Registry/Codec.cs b/src/Registry/Codec.cs index e41a72b6..8401c582 100644 --- a/src/Registry/Codec.cs +++ b/src/Registry/Codec.cs @@ -35,6 +35,7 @@ static Codec() Register("multibase", 0x33); Register("dag-pb", 0x70); Register("dag-cbor", 0x71); + Register("libp2p-key", 0x72); Register("git-raw", 0x78); Register("eth-block", 0x90); Register("eth-block-list", 0x91); diff --git a/src/Registry/MultiBaseAlgorithm .cs b/src/Registry/MultiBaseAlgorithm .cs index d6d9608b..275e0ac3 100644 --- a/src/Registry/MultiBaseAlgorithm .cs +++ b/src/Registry/MultiBaseAlgorithm .cs @@ -57,6 +57,7 @@ static MultiBaseAlgorithm() Register("base32hexpad", 't', bytes => SimpleBase.Base32.ExtendedHex.Encode(bytes, true).ToLowerInvariant(), s => SimpleBase.Base32.ExtendedHex.Decode(s)); + Register("base36", 'k', Base36.EncodeToStringLc, Base36.DecodeString); Register("BASE16", 'F', bytes => SimpleBase.Base16.EncodeUpper(bytes), s => SimpleBase.Base16.Decode(s));