Base Fifty Eight v0.1.0 B58 View Source

This module provides Base58 and Base58Check data encoding and decoding functions.

The following alphabets are supported for encoding and decoding, for both Base58 and Base58Check:

  • Bitcoin
  • Flickr
  • Ripple

    Note that IPFS uses the same alphabet as Bitcoin. Since this alphabet appeared in Bitcoin first, it will be referred to hereafter as Bitcoin, though it can be used for encoding and decoding data from IPFS without issue.

Overview

Base58 is a Base58 and Base58Check library that tries to make pragmatic compromises, while maintaining best practices with regard to Elixir and BEAM performance. It has no external dependencies and supports adding additional values with a simple update of an internal map.

Additionally, B58 encodes and decodes data in a consistent manner - plain binary in, plain binary out. No additional steps, clever hacks, or assumptions are made with regard to input and output. Furthermore, the library is relatively consistent with the approach taken in the Elixir core Base library.

In cases where exceptions may be more common, ! suffixed functions are provided, ex: base58_decode vs base58_decode!.

Base58Check data also supports two versions of encoding and decoding respectively. Version binaries are assumed to be input and output via functions prefixed with version, ex: version_encode58_check!/2. A versioned binary can also be created using version_binary/2. Version arguments are also consistently arranged so that they may be easily piped using Elixir. Unversioned binaries may also be encoded and decoded and simply use the same pattern of functions without the version prefix in the name, ex: encode58_check!.

Ensure that when encoding and decoding, you use the appropriate alphabet. For example, if you encode using the Flickr alphabet, decode using the same alphabet. If you do not control encoding, ensure you know up-front what alphabet is used. Base58 will often decode using the incorrect alphabet without an error, but produce wrong results. Base58Check on the other hand will typically fail up-front due to the checksum being incorrect. It is thus possible, but not advisable to try to brute-force detect the alphabet for Base58Check using the alphabets/0 function with a reduce_while/3 function as a last resort.

Ensure the Erlang “crypto” module is installed. There are some OS distributions that do not include this.

Why Base58 Encoding

The most compelling reason to use Base58 is if you are using or interfacing any technology that already uses it. This includes various crypto-currencies such as Bitcoin, Blockchain-based experiments, various distributed systems, IPFS, Flickr URLs, and many more.

Generally, you can also decide to use Base58 if you want an unambiguous encoding that is less prone to errors when read by a human. Moreover, you can use Base58Check when you need to checksum and/or version your data.

Taken from the original Bitcoin source code:

Why base-58 instead of standard base-64 encoding?
  - Don't want 0OIl characters that look the same in some fonts and could be used to create visually identical looking account numbers.
  - A string with non-alphanumeric characters is not as easily accepted as an account number.
  - E-mail usually won't line-break if there's no punctuation to break at.
  - Double-clicking selects the whole number as one word if it's all alphanumeric.

Base58Check Encoding

The following features are outlined per the bitcoin wiki regarding Base58Check:

  * An arbitrarily sized payload.
  * A set of 58 alphanumeric symbols consisting of easily distinguished uppercase and lowercase letters (0OIl are not used)
  * One byte of version/application information. Bitcoin addresses use 0x00 for this byte (future ones may use 0x05).
  * Four bytes (32 bits) of SHA256-based error checking code. This code can be used to automatically detect and possibly correct typographical errors.
  * An extra step for preservation of leading zeroes in the data.

## Base58Check vs Base58 Encoding

You should use Base58Check over Base58 encoding if any of the features it presents are appealing to you. Perhaps the most compelling reason to use Base58Check, however, is the ability to version data and embed a checksum.

Generally, Base58 should perform better as Base58Check is a layer on top of Base58. The checksum in Base58Check, however, will usually prevent accidental decoding using the wrong alphabet.

Why Not Base58 or Base58Check

Base58 and Base58Check are suitable and desirable for many reasons ranging from protection from character ambiguity to checksums to real-world software usage. Nevertheless, there are several considerations you should evaluate before selecting Base58 or Base58Check.

Among the reasons not to use Base58 or Base58Check include:

  • Speed

    • Usage of div and mod. Both these operations are slow, especially in Elixir. Many other encodings are not performance limited by these operations and thus faster.
    • It may be possible to gain more speed using a technique where div and mod (divmod) together are replaced with a CRC + table approach to avoid the overhead. This is more readily doable in C or C++. It is not clear whether doing this in Elixir has benefits nor if it is worth it in terms of stability and performance doing so via a NIF.
    • Base58Check introduces additional overhead via sha-256, extra binary manipulation, and checksum verification during decoding.
  • Dynamic alphabets

    • Base58 is not as amenable to dynamic alphabets. This library also intentionally ignores this constraint. The main reason is that there are some characters that are counter to the goals of Base58.
    • Losing performance to support a dynamic alphabet is probably not a tradeoff you should be making. Note that dynamic alphabets that follow the guidelines of existing alphabets can be added with a simple compile-time updates of this library.
  • Payload size

    • Some encodings may produce a smaller payload size relative to the source data.
    • You can compress a Base58 payload, but it is not an inherent goal of the algorithm itself.

Selecting an Alphabet

You should select an alphabet that aligns with your inteded use-cases. If in doubt, start with the bitcoin (:btc) alphabet. For usage with URLs such as shorteners or parameters, the Flickr (:flickr) alphabet has seen some real-world usage and is thus worth considering.

If you need to add an alphabet, it is suggested to do so at compile time by updating the alphabet_meta map found in the source. It is strongly advised to use an existing alphabet, however.

Base58 Bitcoin Alphabet

| Value | Encoding | Value | Encoding | Value | Encoding | Value | Encoding |
|------:|---------:|------:|---------:|------:|---------:|------:|---------:|
|      0|         1|     17|         J|     34|         b|     51|         t|
|      1|         2|     18|         K|     35|         c|     52|         u|
|      2|         3|     19|         L|     36|         d|     53|         v|
|      3|         4|     20|         M|     37|         e|     54|         w|
|      4|         5|     21|         N|     38|         f|     55|         x|
|      5|         6|     22|         P|     39|         g|     56|         y|
|      6|         7|     23|         Q|     40|         h|     57|         z|
|      7|         8|     24|         R|     41|         i|       |          |
|      8|         9|     25|         S|     42|         j|       |          |
|      9|         A|     26|         T|     43|         k|       |          |
|     10|         B|     27|         U|     44|         m|       |          |
|     11|         C|     28|         V|     45|         n|       |          |
|     12|         D|     29|         W|     46|         o|       |          |
|     13|         E|     30|         X|     47|         p|       |          |
|     14|         F|     31|         Y|     48|         q|       |          |
|     15|         G|     32|         Z|     49|         r|       |          |
|     16|         H|     33|         a|     50|         s|       |          |

Base58 Flickr Alphabet

| Value | Encoding | Value | Encoding | Value | Encoding | Value | Encoding |
|------:|---------:|------:|---------:|------:|---------:|------:|---------:|
|      0|         1|     17|         i|     34|         A|     51|         T|
|      1|         2|     18|         j|     35|         B|     52|         U|
|      2|         3|     19|         k|     36|         C|     53|         V|
|      3|         4|     20|         m|     37|         D|     54|         W|
|      4|         5|     21|         n|     38|         E|     55|         X|
|      5|         6|     22|         o|     39|         F|     56|         Y|
|      6|         7|     23|         p|     40|         G|     57|         Z|
|      7|         8|     24|         q|     41|         H|       |          |
|      8|         9|     25|         r|     42|         J|       |          |
|      9|         a|     26|         s|     43|         K|       |          |
|     10|         b|     27|         t|     44|         L|       |          |
|     11|         c|     28|         u|     45|         M|       |          |
|     12|         d|     29|         v|     46|         N|       |          |
|     13|         e|     30|         w|     47|         P|       |          |
|     14|         f|     31|         x|     48|         Q|       |          |
|     15|         g|     32|         y|     49|         R|       |          |
|     16|         h|     33|         z|     50|         S|       |          |

Base58 Ripple Alphabet

| Value | Encoding | Value | Encoding | Value | Encoding | Value | Encoding |
|------:|---------:|------:|---------:|------:|---------:|------:|---------:|
|      0|         r|     17|         J|     34|         b|     51|         t|
|      1|         p|     18|         K|     35|         c|     52|         u|
|      2|         s|     19|         L|     36|         d|     53|         v|
|      3|         h|     20|         M|     37|         e|     54|         A|
|      4|         n|     21|         4|     38|         C|     55|         x|
|      5|         a|     22|         P|     39|         g|     56|         y|
|      6|         f|     23|         Q|     40|         6|     57|         z|
|      7|         3|     24|         R|     41|         5|       |          |
|      8|         9|     25|         S|     42|         j|       |          |
|      9|         w|     26|         T|     43|         k|       |          |
|     10|         B|     27|         7|     44|         m|       |          |
|     11|         U|     28|         V|     45|         8|       |          |
|     12|         D|     29|         W|     46|         o|       |          |
|     13|         N|     30|         X|     47|         F|       |          |
|     14|         E|     31|         Y|     48|         q|       |          |
|     15|         G|     32|         Z|     49|         i|       |          |
|     16|         H|     33|         2|     50|         1|       |          |

References

Link to this section Summary

Types

An alphabet ID atom that corresponds to the intended alphabet to be used for encoding or decoding

A Base58 encoded binary

A Base58Check encoded binary

1-byte version binary

Unsigned single-byte integer (unint8 commonly)

A binary where the first byte is assumed to be a version

Functions

Lists all the alphabets available as identifiers to use with Base58

Decodes a Base58 binary

Decodes a Base58 binary

Decodes a Base58Check binary

Decodes a Base58Check binary

Encodes a binary in Base58

Encodes a binary in Base58Check using the given version byte

Encodes a binary in Base58Check using the given version byte

Versions a binary according to the Base58Check rules

Decodes a Base58Check binary with the version byte included in the returned binary

Decodes a Base58Check binary with the version byte included in the returned binary

Encodes a binary in Base58Check using the first byte as the version byte. Assumes the binary is already versioned

Encodes a binary in Base58Check using the first byte as the version byte. Assumes the binary is already versioned

Link to this section Types

Link to this type alphabet() View Source
alphabet() :: :btc | :flickr | :ripple

An alphabet ID atom that corresponds to the intended alphabet to be used for encoding or decoding.

Link to this type b58_binary() View Source
b58_binary() :: binary()

A Base58 encoded binary.

Link to this type b58check_binary() View Source
b58check_binary() :: <<_::48, _::_*8>>

A Base58Check encoded binary

Link to this type version() View Source
version() :: <<_::8>>

1-byte version binary

Link to this type version_integer() View Source
version_integer() :: 0..255

Unsigned single-byte integer (unint8 commonly)

Link to this type versioned_binary() View Source
versioned_binary() :: <<_::8, _::_*8>>

A binary where the first byte is assumed to be a version.

Link to this section Functions

Link to this macro alphabets() View Source (macro)
alphabets() :: [alphabet()]

Lists all the alphabets available as identifiers to use with Base58.

Examples

iex> B58.alphabets()
[:btc, :flickr, :ripple]
Link to this function decode58!(string, opts \\ []) View Source
decode58!(b58_binary(), keyword()) :: b58_binary()

Decodes a Base58 binary.

An ArgumentError exception is raised if the binary contains characters that are invalid for the given alphabet.

Examples

iex> B58.decode58!("59QpGHkaSK8tngYpe9h17cseyAEGc")
"Cheese Platter Please"

iex> B58.decode58!("asDvaq68ktFaubY7KsnvBFMMD5Q8cd9PhGma3YcPqoWXX2aJoiaK1kc65gn", alphabet: :ripple)
"Decode using the alphabet it was encoded in"

iex> B58.decode58!("2zPJuvDvDNbavb7zdUVM9niBSHNTjPni85f8hPLJ119awVLKud3kWU5LsRGiqkzav8wD6MFj2QshN3b8SyTEpkG2pYcSAioeUUDe6wqhigji4vkBVQoBJ9mPLmySgjQyy8FrYu3rd", alphabet: :btc) |> Base.decode16!()
"0088C2D2FA846282C870A76CADECBE45C4ACD72BB655DA1216"
Link to this function decode58(string, opts \\ []) View Source
decode58(b58_binary(), keyword()) :: {:ok, b58_binary()} | {:error, term()}

Decodes a Base58 binary.

An {:error, reason} tuple is returned if the binary contains characters that are invalid for the given alphabet.

Examples

iex> B58.decode58("59QpGHkaSK8tngYpe9h17cseyAEGc", alphabet: :btc)
{:ok, "Cheese Platter Please"}

iex> B58.decode58("2nePN7syqoQe2mfeY", alphabet: :flickr)
{:ok, "Hello Sailor"}

iex> B58.decode58("02nePN7syqoQe2mfeY", alphabet: :flickr)
{:error, "non-alphabet digit found: \"0\" (byte 48)"}
Link to this function decode58_check!(string, opts \\ []) View Source
decode58_check!(b58check_binary(), keyword()) :: {binary(), version()}

Decodes a Base58Check binary.

An ArgumentError exception is raised if the binary contains characters that are invalid for the given alphabet or if the checksum bytes do not match the payload.

Examples

iex> B58.decode58_check!("12nwcb8vmxv3waEzzPtaY3XBmWbwvGvqw", alphabet: :flickr)
{"Backstrom for Selke", <<0>>}

iex> B58.decode58_check!("ftyYJFWLdZWzNfyFqPGXGpxM6aFNnTkqmz1QwcoT3eQAQPb9V4bRQScejwWQWTR8", alphabet: :ripple)
{"No one wants to play Sega Genesis with you", <<14>>}

iex> B58.decode58_check!("2mzYnhGdFMacGVTrvyZsnqDGPqWFefouMQA")
{<<245, 74, 88, 81, 233, 55, 43, 135, 129, 10, 142, 96, 205, 210, 231, 207, 216,
11, 110, 49>>, <<255>>}
Link to this function decode58_check(string, opts \\ []) View Source
decode58_check(b58check_binary(), keyword()) ::
  {:ok, {binary(), version()}} | {:error, term()}

Decodes a Base58Check binary.

An {:error, reason} tuple is returned if the binary contains characters that are invalid for the given alphabet or if the checksum bytes do not match the payload.

Examples

iex> B58.decode58_check("QGgq7M6oMmgLQiWY9SUgo5QoaK6FH44nM")
{:ok, {"Backstrom for Selke", <<255>>}}

iex> B58.decode58_check!("17B1aKcMUBrrX9t6Y7ZUSkDoedGLiybgnJ", alphabet: :flickr)
{"Hot Delicious Treats", <<0>>}

iex> B58.decode58_check("QGgq7M6oMmgLQiWY9SUgo5QoaK6FH44n_", alphabet: :btc)
{:error, "non-alphabet digit found: \"_\" (byte 95)"}

iex> B58.decode58_check("QGgq7M6oMmgLQiWY9SUgo5QoaK6FH44nM", alphabet: :ripple)
{:error, "Invalid checksum."}
Link to this function encode58(data, opts \\ []) View Source
encode58(binary(), keyword()) :: b58_binary()

Encodes a binary in Base58.

Examples

iex> B58.encode58("Some chicken is better than no chicken")
"DLrwGKtrzZkxaLwqpwBBbkgFiEGTYhhzEpjXuj5R2DZPGzbXfeVK"

iex> B58.encode58("Some chicken is better than no chicken", alphabet: :btc)
"DLrwGKtrzZkxaLwqpwBBbkgFiEGTYhhzEpjXuj5R2DZPGzbXfeVK"

iex> B58.encode58("The T in Tacos stands for terrific", alphabet: :flickr)
"2UF2YLgW2MQSutxJWpcW6afxo1YbVm4ZnXociLQ1eExXRwK"

iex> "536D656C6C73206C696B652061206C6F636B657220726F6F6D20696E2068657265" |> Base.decode16!() |> B58.encode58(alphabet: :ripple)
"R8RKuNXBxzb1Ai6HrP3mHmgyBEKmWjp944zS4Y9Q7hsPS"
Link to this function encode58_check!(data, version, opts \\ []) View Source
encode58_check!(binary(), version() | version_integer(), keyword()) ::
  b58check_binary()

Encodes a binary in Base58Check using the given version byte.

Examples

iex> B58.encode58_check!("Lost my sauce, sorry boss.", 0)
"1GKJd5qu8gaFbd4tHVPF6q8ZtzER5ttJSwLTN2A5MT"

iex> B58.encode58_check!("Lost my sauce, sorry boss.", 1, alphabet: :btc)
"29cSfWnUdLoFyrRquaF4fQ2xnKSMg8TWenhe89Mc95"

iex> B58.encode58_check!("Lettuceless Burritos", "m", alphabet: :ripple)
"kyo6DdGZMd7yc1EmNVz2jh4sEc3qUNBY1D"

iex> "466F6F20426172204578616D706C657320617265207265616C6C792064616D6E20737475706964" |> Base.decode16!() |> B58.encode58_check!("2", alphabet: :flickr)
"hm63nMd9884zRLqGD2cYDUgk3JR1EEEbP6UQfEU2uPAoZfQe3VrXHfYvRkok"
Link to this function encode58_check(data, version, opts \\ []) View Source
encode58_check(binary(), version() | version_integer(), keyword()) ::
  {:ok, b58check_binary()} | {:error, term()}

Encodes a binary in Base58Check using the given version byte.

Examples

iex> B58.encode58_check("So Afraid of failure", <<1>>, alphabet: :btc)
{:ok, "XwmCCcWGbEjGsJk4THsxw28EcQuz6TYyu"}

iex> B58.encode58_check("Incapable coworker lacking understanding what a byte is", <<1, 2>>, alphabet: :btc)
{:error,
"version must be a single byte binary or unsigned integer, data must be a binary."}
Link to this function version_binary(data, version) View Source
version_binary(binary(), version() | version_integer()) :: versioned_binary()

Versions a binary according to the Base58Check rules.

Only use with unversioned binaries.

An ArgumentError is raised if the version is not a single byte unsigned integer (uint8).

Examples

iex> B58.version_binary("Chicken Recipe", 42)
"*Chicken Recipe"

iex> B58.version_binary("Chicken Recipe", <<255>>)
<<255, 67, 104, 105, 99, 107, 101, 110, 32, 82, 101, 99, 105, 112, 101>>

iex> B58.version_binary("Chicken Recipe", <<0>>) |> B58.version_encode58_check!()
"13oALC94NVeHBQsneF6tYRmW4R"
Link to this function version_decode58_check!(string, opts \\ []) View Source
version_decode58_check!(b58check_binary(), keyword()) :: versioned_binary()

Decodes a Base58Check binary with the version byte included in the returned binary.

An ArgumentError exception is raised if the binary contains characters that are invalid for the given alphabet or if the checksum bytes do not match the payload.

Examples

iex> B58.version_decode58_check!("2XLgTmqxf6wu4zAwBAb2hvezGWht")
"mSeal of Quality"

iex> B58.version_decode58_check!("5XtVTWqqaYj7VUodxQwwgnioaGCoB1fVFzMSXLfQWBiUmsEfo6oJ73CpJojLDuYa3fFE77uvAax4ctzBT17me", alphabet: :flickr)
"SEvery library says it is fast. Surely, they are all fast."

iex> B58.version_decode58_check!("YXUaKUi6gkm3DRXp8hDrbB", alphabet: :ripple)
<<255, 66, 114, 111, 103, 114, 97, 109, 109, 101, 114, 115>>
Link to this function version_decode58_check(string, opts \\ []) View Source
version_decode58_check(b58check_binary(), keyword()) ::
  {:ok, versioned_binary()} | {:error, term()}

Decodes a Base58Check binary with the version byte included in the returned binary.

An {:error, reason} tuple is returned if the binary contains characters that are invalid for the given alphabet or if the checksum bytes do not match the payload.

Examples

iex> B58.version_decode58_check("2XLgTmqxf6wu4zAwBAb2hvezGWht")
{:ok, "mSeal of Quality"}

iex> B58.version_decode58_check("2XLgTmqxf6wu4zAwBAb2hvezGWht", alphabet: :flickr)
{:error, "Invalid checksum."}

iex> B58.version_decode58_check("2XLgTmqxf6wu4zAwBAb2hvezGWht0", alphabet: :btc)
{:error, "non-alphabet digit found: \"0\" (byte 48)"}

iex> B58.version_decode58_check("2QRkeNpTrYPiooUJjcubi1fprQ7aB8XvxSZ2wa4JQeBryrPVCvvkcY5NkXeptH6UzjKBBznXp58MWv4TTBvSHgeyjwPtY", alphabet: :ripple)
{:ok,
<<255, 83, 116, 111, 112, 32, 117, 115, 105, 110, 103, 32, 69, 110, 117, 109,
 46, 97, 116, 32, 116, 111, 32, 108, 111, 111, 107, 117, 112, 32, 116, 104,
 105, 110, 103, 115, 32, 105, 110, 32, 99, 111, 100, 101, 99, 115, 46, 32, 75,
 110, 111, 119, 32, 121, 111, 117, 114, 32, 66, 105, 103, 32, 79, 46>>}
Link to this function version_encode58_check!(versioned_data, opts \\ []) View Source
version_encode58_check!(versioned_binary(), keyword()) :: b58check_binary()

Encodes a binary in Base58Check using the first byte as the version byte. Assumes the binary is already versioned.

Examples

iex> B58.version_encode58_check!(<<5, "256 versions ought to be enough for anyone">>)
"3WGnVcRSYcejzu1tmjqG36i8zGFhPH5v7399paTXYSvCQPRsqSa86CcnRUPVJxXw"

iex> B58.version_encode58_check!("1Mad Lad", alphabet: :ripple)
"vx82hmfYGg4kdDK5"

iex> B58.version_encode58_check!(<<255, "Push the limits until the car starts">>, alphabet: :flickr)
"XZABbyyTLAtpXB4uyan6stxnyFhPH2kbr1v6j26SBAvDqrQi8GGBDKxs"
Link to this function version_encode58_check(versioned_data, opts \\ []) View Source
version_encode58_check(versioned_binary(), keyword()) ::
  {:ok, b58check_binary()} | {:error, term()}

Encodes a binary in Base58Check using the first byte as the version byte. Assumes the binary is already versioned.

Examples

iex> B58.version_encode58_check(<<5, "256 versions ought to be enough for anyone">>)
{:ok, "3WGnVcRSYcejzu1tmjqG36i8zGFhPH5v7399paTXYSvCQPRsqSa86CcnRUPVJxXw"}

iex> B58.version_encode58_check("1Mad Lad", alphabet: :ripple)
{:ok, "vx82hmfYGg4kdDK5"}

iex> B58.version_encode58_check(<<255, "Push the limits until the car starts">>, alphabet: :flickr)
{:ok, "XZABbyyTLAtpXB4uyan6stxnyFhPH2kbr1v6j26SBAvDqrQi8GGBDKxs"}