SkillAgentSearch skills...

B58

Elixir library for encoding and decoding Base58 and Base58Check using the Bitcoin/IPFS, Ripple, and Flickr alphabets.

Install / Use

/learn @nocursor/B58
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

Base Fifty Eight

Base Fifty Eight (B58) is an Elixir library for transparently encoding and decoding Base58 and Base58 checked binaries.

The following alphabets are fully supported for standard and checked encoding and decoding:

  • BTC
  • Flickr
  • Ripple

If by some unholy magic, an additional alphabet is required, please leave an issue and it can be easily added.

Features

  • Base58 and Base58Check encode/decode for all supported alphabets
    • BTC, Flickr, Ripple
  • Support for Base58Check encoding and decoding both versioned and unversioned data
    • Version byte may be specified explicitly, extracted, or inferred
    • Checksum calculation per Base58Check spec using sha-256 double hash
  • Proper handling for encoding and decoding leading zeroes
    • Data is not lost or corrupted
    • No strange behaviors or surprises when adding new alphabets
  • Binary in, Binary out clean interface
  • Transparent encoding and decoding for all alphabets and types of encoding
    • You will always be able to reverse encoding with decoding, including zero bytes
  • Explicit handling for versioned and unversioned binaries when dealing with Base58Check
    • Encoding or Decode a binary with or without the version bytes included
  • Test coverage
  • Consistent functional interface that easily supports piping
  • Consistent design with Elixir core Base module
  • Glorious documentation

Additionally, the following miscellaneous design-related features are present:

  • No external dependencies
  • Compile-time generated matching and optimizations for speed
  • Attention to binary optimization
  • Easy to add new alphabets with a single compile-time update
  • Designed for additional encoding/decoding options should feature requests or needs arise
  • Pragmatic attention to ease-of-use
    • Rather than having a combinatorial explosion for the number of alphabets, all alphabets share the same functional interface
    • Adding new alphabets does not place burden on programmers
    • Performance still maintained without resorting to bad decisions that impact encoding and decoding

Usage

Base58 Encode a Binary (default bitcoin/IPFS alphabet):

B58.encode58("Happy trees happily encode")
"3DYrnueGqPGpJ2tCbbkATC71LBK1kRGm9g9z"

# default for all alphabet options is always bitcoin, but we can specify it too
B58.encode58("Happy trees happily encode", alphabet: :btc)
"3DYrnueGqPGpJ2tCbbkATC71LBK1kRGm9g9z"

Base58 Encode a Binary using the Flickr alphabet:

B58.encode58("Happy trees happily encode", alphabet: :flickr)
"3dxRMUDgQogPi2TcAAKasc71kbj1KqgL9F9Z"

Base58 Encode a Binary using the Ripple alphabet:

B58.encode58("Happy trees happily encode", alphabet: :ripple)
"sDYi8ueGqPGFJptUbbkwTUfrLBKrkRGm9g9z"

Base58 Decode our original Base58 binary encoded with bitcoin:

B58.decode58!("3DYrnueGqPGpJ2tCbbkATC71LBK1kRGm9g9z")
"Happy trees happily encode"

Base58 Decode our original Base58 binary encoded with bitcoin, but with error handling:

B58.decode58("3DYrnueGqPGpJ2tCbbkATC71LBK1kRGm9g9z")
{:ok, "Happy trees happily encode"}

# fails if the encoded string is invalid in some way
# lets add a 0 at the end to break it since it is not part of the bitcoin alphabet
B58.decode58("3DYrnueGqPGpJ2tCbbkATC71LBK1kRGm9g9z0", alphabet: :btc)           
{:error, "non-alphabet digit found: \"0\" (byte 48)"}

Base58Check encoding works in a similar way to Base58 for all functions, but requires a version byte:

B58.encode58_check!("Why is Jack always so Hungry", 0)
"16tCsFCTp3ADeU8WsCt36HFYwg3kiBKtBXUBmU6KAQBsA"

B58.encode58_check!("Why is Jack always so Hungry", 0, alphabet: :btc)
"16tCsFCTp3ADeU8WsCt36HFYwg3kiBKtBXUBmU6KAQBsA"

Base58Check takes the same alphabets too:

B58.encode58_check!("Why is Jack always so Hungry", 0, alphabet: :ripple)
"1atU1EUTFswDe73W1UtsaHEYAgsk5BKtBX7Bm7aKwQB1w"

B58.encode58_check!("Why is Jack always so Hungry", 0, alphabet: :flickr)
"16TcSfcsP3adDt8vScT36hfxWF3KHbjTbwtbLt6japbSa"

Base58Check can use version bytes a few ways:

B58.encode58_check!("Chip Clip sounds less cool in other languages", "m", alphabet: :btc)   
"2M5paPVxJK1G77Dm9GLXfyXmHsB8L32U2NDy12GfYxrg181WQzkx52nDnc9BMcuahPEJA"

B58.encode58_check!("Chip Clip sounds less cool in other languages", ?m, alphabet: :btc)  
"2M5paPVxJK1G77Dm9GLXfyXmHsB8L32U2NDy12GfYxrg181WQzkx52nDnc9BMcuahPEJA"

B58.encode58_check!("Chip Clip sounds less cool in other languages", 42, alphabet: :btc)
"XCkzrRJxzqYE9tFeaWPsKeY4m1Fxm34CEe4repib7cLiGFjM7Dzf6ubruT68QZiUE5hC"

# we can also try to pass some bad version info that violates the Base58Check version constraints
B58.encode58_check!("Chip Clip sounds less cool in other languages", 42424242, alphabet: :btc)
# raise ** (ArgumentError) version must be a single byte binary or unsigned integer, data must be a binary.

# And we can use our trust error handling version too
B58.encode58_check("Chip Clip sounds less cool in other languages", 42424242)                 
{:error,
 "version must be a single byte binary or unsigned integer, data must be a binary."}

Base58Check can be encoded with a version byte already present. All calls with an explicit version byte are prefixed with version so there is no confusion and not error-prone:

# notice the "m" byte in front
B58.version_encode58_check!("mChip Clip sounds less cool in other languages")      
"2M5paPVxJK1G77Dm9GLXfyXmHsB8L32U2NDy12GfYxrg181WQzkx52nDnc9BMcuahPEJA"

# same but specifying the alphabet too explicitly
B58.version_encode58_check!("mChip Clip sounds less cool in other languages", alphabet: :btc)      
"2M5paPVxJK1G77Dm9GLXfyXmHsB8L32U2NDy12GfYxrg181WQzkx52nDnc9BMcuahPEJA"

# of course we can also construct a binary too ourselves
B58.version_encode58_check!(<<42, "Chip Clip sounds less cool in other languages">>, alphabet: :btc)
"XCkzrRJxzqYE9tFeaWPsKeY4m1Fxm34CEe4repib7cLiGFjM7Dzf6ubruT68QZiUE5hC"

Likewise, Base58Check can be decoded much like Base58. The big difference is how the version byte is returned.

B58.decode58_check!("2M5paPVxJK1G77Dm9GLXfyXmHsB8L32U2NDy12GfYxrg181WQzkx52nDnc9BMcuahPEJA")                                                                       
{"Chip Clip sounds less cool in other languages", "m"}

B58.decode58_check!("2M5paPVxJK1G77Dm9GLXfyXmHsB8L32U2NDy12GfYxrg181WQzkx52nDnc9BMcuahPEJA", alphabet: :btc)                                                                       
{"Chip Clip sounds less cool in other languages", "m"}

The version prefixed calls can be used to include the version byte:

B58.version_decode58_check!("2M5paPVxJK1G77Dm9GLXfyXmHsB8L32U2NDy12GfYxrg181WQzkx52nDnc9BMcuahPEJA")
"mChip Clip sounds less cool in other languages"

# This is quite nice as we can use it for piping too, and as you can see it keeps things fully transparent
# We can also pipe in the alphabet easily wherever
 B58.version_decode58_check!("2M5paPVxJK1G77Dm9GLXfyXmHsB8L32U2NDy12GfYxrg181WQzkx52nDnc9BMcuahPEJA") 
 |> B58.version_encode58_check!() 
 |> B58.version_decode58_check!(alphabet: :btc)
"mChip Clip sounds less cool in other languages"

Base58Check includes a checksum. Let's tamper with our encoded bytes to make it fail:

B58.encode58_check!("Bank Details Poorly Handled by Bitcoin Startup", 42, alphabet: :btc)   
"3JJAcmj3WdhuKTNRAHx1Yjg4XorKHxPoAA8uokfDz9DGFw456jADxtLp6DEP5zTKsM3zq9"

# Let's change that J in second position to a D because we are an evil hacker who hates J
B58.decode58_check!("3DJAcmj3WdhuKTNRAHx1Yjg4XorKHxPoAA8uokfDz9DGFw456jADxtLp6DEP5zTKsM3zq9")
# raises ** (ArgumentError) Invalid checksum.

# Fortunately, we can also use our trusty error/reason tuples too
# Notice the lack of a `!`
B58.decode58_check("3DJAcmj3WdhuKTNRAHx1Yjg4XorKHxPoAA8uokfDz9DGFw456jADxtLp6DEP5zTKsM3zq9") 
{:error, "Invalid checksum."}

# The `version` prefixed functions behave the same
B58.version_decode58_check("3DJAcmj3WdhuKTNRAHx1Yjg4XorKHxPoAA8uokfDz9DGFw456jADxtLp6DEP5zTKsM3zq9")
{:error, "Invalid checksum."}

If we are truly lazy or want some error checking, we can use this library to version our binaries too:

B58.version_binary("Bad Startup Idea", 255)                                  
<<255, 66, 97, 100, 32, 83, 116, 97, 114, 116, 117, 112, 32, 73, 100, 101, 97>>

B58.version_binary("Bad Startup Idea", "X") 
"XBad Startup Idea"

Let's suppose we want to work with something more fancy like Bitcoin (I accept your wealth if you have any to spare). We can work our way through the example in the Bitcoin wiki like so:

# Suppose we have a RIPEMD-160 hashed public key like f54a5851e9372b87810a8e60cdd2e7cfd80b6e31
# You can do the previous steps with the erlang `crypto` module if you wish, but it's out of scope here
# Let's version it with zero for the BTC network, then encode it. 
# As sane humans, we do this if starting from such a string:
Base.decode16!("f54a5851e9372b87810a8e60cdd2e7cfd80b6e31", case: :lower) |>  B58.encode58_check!(0)                 
"1PMycacnJaSqwwJqjawXBErnLsZ7RkXUAs"

# We could also do this if we had it as an integer like so
# Notice the hex syntax for Elixir. Be careful doing this when working with leading zeroes though as `:binary.encode_unsigned/1` will use the smallest representation.
0xf54a5851e9372b87810a8e60cdd2e7cfd80b6e31 |> :binary.encode_unsigned() |>  B58.encode58_check!(0) 
"1PMycacnJaSqwwJqjawXBErnLsZ7RkXUAs"

# DO NOT DO THIS. 
# I am quite positive someone will file an issue after doing this, thus I warn you here for later posterity.
# Suppose we are unintelligent people. 
# This could never happen, but StackOverflow and other examples in the wild whisper to me
# Notice how the result is completely wrong. Why? We're encoding a hex string, which is not a plain binary.
B58.encode58_check!("f54a5851e9372b87810a8e60cdd2e7cfd80b6e31", 0)
"1aES43oGwSz1ve4QR

Related Skills

View on GitHub
GitHub Stars12
CategoryDevelopment
Updated5mo ago
Forks4

Languages

Elixir

Security Score

92/100

Audited on Oct 23, 2025

No findings