{"id":27759621,"url":"https://github.com/ssg/simplebase","last_synced_at":"2025-05-15T06:05:51.430Z","repository":{"id":22405003,"uuid":"25742258","full_name":"ssg/SimpleBase","owner":"ssg","description":".NET library for encoding/decoding Base16, Base32, Base45, Base58, Base62, Base85, and Multibase.","archived":false,"fork":false,"pushed_at":"2025-05-13T00:44:30.000Z","size":726,"stargazers_count":155,"open_issues_count":3,"forks_count":22,"subscribers_count":4,"default_branch":"main","last_synced_at":"2025-05-15T06:05:01.813Z","etag":null,"topics":["c-sharp","encoding","libraries"],"latest_commit_sha":null,"homepage":"","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":"prof-rossetti/nyu-info-2335-70-201706","license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ssg.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null},"funding":{"github":["ssg"]}},"created_at":"2014-10-25T18:42:44.000Z","updated_at":"2025-05-13T00:44:34.000Z","dependencies_parsed_at":"2023-11-13T00:23:01.952Z","dependency_job_id":"c26696a4-a92d-4e0e-b99b-a536b968b339","html_url":"https://github.com/ssg/SimpleBase","commit_stats":{"total_commits":409,"total_committers":11,"mean_commits":37.18181818181818,"dds":"0.39608801955990225","last_synced_commit":"95ee4d58047e4c1bcfd7bcf93e86d21224be93e8"},"previous_names":["ssg/simplebase32"],"tags_count":17,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ssg%2FSimpleBase","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ssg%2FSimpleBase/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ssg%2FSimpleBase/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ssg%2FSimpleBase/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ssg","download_url":"https://codeload.github.com/ssg/SimpleBase/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254283339,"owners_count":22045140,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["c-sharp","encoding","libraries"],"created_at":"2025-04-29T11:59:01.329Z","updated_at":"2025-05-15T06:05:46.400Z","avatar_url":"https://github.com/ssg.png","language":"C#","funding_links":["https://github.com/sponsors/ssg"],"categories":[],"sub_categories":[],"readme":"SimpleBase\n==========\n[![NuGet Version](https://img.shields.io/nuget/v/SimpleBase.svg)](https://www.nuget.org/packages/SimpleBase/)\n[![Build Status](https://github.com/ssg/SimpleBase/actions/workflows/test.yml/badge.svg)](https://github.com/ssg/SimpleBase/actions?query=workflow%3Atest)\n\nThis is my own take for exotic base encodings like Base32, Base58 and Base85. \nI started to write it in 2013 as coding practice and kept it as a small pet \nproject. I suggest anyone who wants to brush up their coding skills to give \nthose encoding problems a shot. They turned out to be more challenging than I \nexpected. To grasp the algorithms I had to get a pen and paper to see how the \nmath worked.\n\nFeatures\n--------\n - **Base32**: RFC 4648, BECH32, Crockford, z-base-32, Geohash, FileCoin and Extended Hex \n   (BASE32-HEX) flavors with Crockford character substitution, or any other \n   custom flavors.\n - **Base58**: Both the standard encoding (Bitcoin (BTC), Ripple (XRP), Flickr, and custom alphabets) and Monero (XMR)\n   Base58 algorithms are supported. Also provides Base58Check and Avalanche CB58 encoding helpers.\n - **Base85**: Ascii85, Z85 and custom flavors. IPv6 encoding/decoding support.\n - **Base16**: UpperCase, LowerCase and ModHex flavors. An experimental hexadecimal \n   encoder/decoder just to see how far I \n   can take the optimizations compared to .NET's  implementations. It's quite \n   fast now. It could also be used as a replacement for `SoapHexBinary.Parse` although\n   .NET has [`Convert.FromHexString()`](https://learn.microsoft.com/en-us/dotnet/api/system.convert.fromhexstring?view=net-5.0) method since .NET 5.\n - One-shot memory buffer based APIs for simple use cases.\n - Stream-based async APIs for more advanced scenarios.\n - Lightweight: No dependencies.\n - Support for big-endian CPUs like IBM s390x (zArchitecture).\n - Thread-safe.\n - Simple to use.\n\nNuGet\n------\nTo install it from [NuGet](https://www.nuget.org/packages/SimpleBase/):\n\n  `Install-Package SimpleBase`\n\nUsage\n------\n\n### Base32\n\nEncode a byte array:\n\n```csharp\nusing SimpleBase;\n\nbyte[] myBuffer;\nstring result = Base32.Crockford.Encode(myBuffer, padding: true);\n// you can also use \"ExtendedHex\" or \"Rfc4648\" as encoder flavors\n```\n\nDecode a Base32-encoded string:\n\n```csharp\nusing SimpleBase;\n\nstring myText = ...\nbyte[] result = Base32.Crockford.Decode(myText);\n```\n\n### Base58\n\nEncode a byte array:\n\n```csharp\nbyte[] myBuffer = ...\nstring result = Base58.Bitcoin.Encode(myBuffer);\n// you can also use \"Ripple\" or \"Flickr\" as encoder flavors\n```\n\nDecode a Base58-encoded string:\n\n```csharp\nstring myText = ...\nbyte[] result = Base58.Bitcoin.Decode(myText);\n```\n\nEncode a Base58Check address:\n\n```csharp\nbyte[] address = ...\nbyte version = 1; // P2PKH address\nstring result = Base58.Bitcoin.EncodeCheck(address, version);\n```\n\nDecode a Base58Check address:\n\n```csharp\nstring address = ...\nSpan\u003cbyte\u003e buffer = new byte[maxAddressLength];\nif (Base58.Bitcoin.TryDecodeCheck(address, buffer, out byte version, out int numBytesWritten));\nbuffer = buffer[..numBytesWritten]; // use only the written portion of the buffer\n```\n\nAvalanche CB58 usage is pretty much the same except it doesn't have a separate\nversion field. Just use `EncodeCb58` and `TryDecodeCb58` methods instead. For \nencoding:\n\n```\nbyte[] address = ...\nbyte version = 1;\nstring result = Base58.Bitcoin.EncodeCb58(address);\n```\n\nFor decoding:\n\n```csharp\nstring address = ...\nSpan\u003cbyte\u003e buffer = new byte[maxAddressLength];\nif (Base58.Bitcoin.TryDecodeCb58(address, buffer, out int numBytesWritten));\nbuffer = buffer[..numBytesWritten]; // use only the written portion of the buffer\n```\n\n### Base85\n\nEncode a byte array to Ascii85 string:\n\n```csharp\nbyte[] myBuffer = ...\nstring result = Base85.Ascii85.Encode(myBuffer);\n// you can also use Z85 as a flavor\n```\n\nDecode an encoded Ascii85 string:\n\n```csharp\nstring encodedString = ...\nbyte[] result = Base85.Ascii85.Decode(encodedString);\n```\n\nBoth \"zero\" and \"space\" shortcuts are supported for Ascii85. Z85 is still \nvanilla.\n\n### Base16\n\nEncode a byte array to hex string:\n\n ```csharp\nbyte[] myBuffer = ...\nstring result = Base16.EncodeUpper(myBuffer); // encode to uppercase\n// or \nstring result = Base16.EncodeLower(myBuffer); // encode to lowercase\n```\n\nTo decode a valid hex string:\n\n```csharp\nstring text = ...\nbyte[] result = Base16.Decode(text); // decodes both upper and lowercase\n```\n\n### Stream Mode\n\nMost encoding classes also support a stream mode that can work on streams, be \nit a network connection, a file or whatever you want. They are ideal for \nhandling arbitrarily large data as they don't consume memory other than a small \nbuffer when encoding or decoding. Their syntaxes are mostly identical. Text \nencoding decoding is done through a `TextReader`/`TextWriter` and the rest is \nread through a `Stream` interface. Here is a simple code that encodes a file to \nanother file using Base85 encoding:\n\n```csharp\nusing (var input = File.Open(\"somefile.bin\"))\nusing (var output = File.Create(\"somefile.ascii85\"))\nusing (var writer = new TextWriter(output)) // you can specify encoding here\n{\n  Base85.Ascii85.Encode(input, writer);\n}\n```\n\nDecode works similarly. Here is a Base32 file decoder:\n\n```csharp\nusing (var input = File.Open(\"somefile.b32\"))\nusing (var output = File.Create(\"somefile.bin\"))\nusing (var reader = new TextReader(input)) // specify encoding here\n{\n\tBase32.Crockford.Decode(reader, output);\n}\n```\n\n### Asynchronous Stream Mode\nYou can also encode/decode streams in asynchronous fashion:\n\n```csharp\nusing (var input = File.Open(\"somefile.bin\"))\nusing (var output = File.Create(\"somefile.ascii85\"))\nusing (var writer = new TextWriter(output)) // you can specify encoding here\n{\n  await Base85.Ascii85.EncodeAsync(input, writer);\n}\n```\n\nAnd the decode:\n\n```csharp\nusing (var input = File.Open(\"somefile.b32\"))\nusing (var output = File.Create(\"somefile.bin\"))\nusing (var reader = new TextReader(input)) // specify encoding here\n{\n\tawait Base32.Crockford.DecodeAsync(reader, output);\n}\n```\n\n### TryEncode/TryDecode\nIf you want to use an existing pre-allocated buffer to encode or decode without \ncausing a GC allocation every time, you can make use of TryEncode/TryDecode \nmethods which receive input, output buffers as parameters. \n\nEncoding is like this:\n\n```csharp\nbyte[] input = new byte[] { 1, 2, 3, 4, 5 };\nint outputBufferSize = Base58.Bitcoin.GetSafeCharCountForEncoding(input);\nvar output = new char[outputBufferSize];\n\nif (Base58.Bitcoin.TryEncode(input, output, out int numCharsWritten))\n{\n   // there you go\n}\n```\n\nand decoding:\n\n```csharp\nstring input = \"... some bitcoin address ...\";\nint outputBufferSize = Base58.Bitcoin.GetSafeByteCountForDecoding(output);\nvar output = new byte[outputBufferSize];\n\nif (Base58.Bitcoin.TryDecode(input, output, out int numBytesWritten))\n{\n    // et voila!\n}\n```\n\n\nBenchmark Results\n-----------------\nSmall buffer sizes are used (64 characters). They are closer to real life \napplications. Base58 performs really bad in decoding of larger buffer sizes, \ndue to polynomial complexity of numeric base conversions.\n\nBenchmarkDotNet v0.14.0, Windows 11 (10.0.26100.3915)\nAMD Ryzen 9 5950X, 1 CPU, 32 logical and 16 physical cores\n.NET SDK 8.0.408\n  [Host]     : .NET 8.0.15 (8.0.1525.16413), X64 RyuJIT AVX2\n  DefaultJob : .NET 8.0.15 (8.0.1525.16413), X64 RyuJIT AVX2\n\nEncoding (64 byte buffer)\n\n| Method                                 | Mean      | Error    | StdDev   | Gen0   | Allocated |\n|--------------------------------------- |----------:|---------:|---------:|-------:|----------:|\n| DotNet_Base64                          |  26.89 ns | 0.070 ns | 0.062 ns | 0.0120 |     200 B |\n| SimpleBase_Base16_UpperCase            |  84.13 ns | 1.640 ns | 1.534 ns | 0.0167 |     280 B |\n| SimpleBase_Base32_CrockfordWithPadding | 149.34 ns | 0.434 ns | 0.385 ns | 0.0138 |     232 B |\n| SimpleBase_Base85_Z85                  | 148.02 ns | 0.227 ns | 0.212 ns | 0.0110 |     184 B |\n| SimpleBase_Base58_Bitcoin              |  46.38 ns | 0.955 ns | 1.100 ns | 0.0091 |     152 B |\n| SimpleBase_Base58_Monero               | 203.60 ns | 1.622 ns | 1.518 ns | 0.0119 |     200 B |\n\nDecoding (80 character string)\n\n| Method                      | Mean        | Error     | StdDev    | Gen0   | Allocated |\n|---------------------------- |------------:|----------:|----------:|-------:|----------:|\n| DotNet_Base64               |   106.07 ns |  2.056 ns |  2.448 ns | 0.0052 |      88 B |\n| SimpleBase_Base16_UpperCase |    49.17 ns |  0.491 ns |  0.459 ns | 0.0038 |      64 B |\n| SimpleBase_Base32_Crockford |   123.07 ns |  0.272 ns |  0.254 ns | 0.0048 |      80 B |\n| SimpleBase_Base85_Z85       |   251.30 ns |  1.106 ns |  0.923 ns | 0.0105 |     176 B |\n| SimpleBase_Base58_Bitcoin   | 5,430.37 ns | 34.332 ns | 32.114 ns | 0.0076 |     176 B |\n| SimpleBase_Base58_Monero    |   128.24 ns |  0.947 ns |  0.885 ns | 0.0105 |     176 B |\n\nNotes\n-----\nI'm sure there are areas for improvement. I didn't want to go further in \noptimizations which would hurt readability and extensibility. I might \nexperiment on them in the future.\n\nTest suite for Base32 isn't complete, I took most of it from RFC4648. Base58 \nreally lacks a good spec or test vectors needed. I had to resort to using \nonline converters to generate preliminary test vectors.\n\nBase85 tests are also makseshift tests based on what output \n[Cryptii](https://cryptii.com/) produces. Contribution to missing test cases \nare greatly appreciated.\n\nIt's interesting that I wasn't able to reach .NET Base64's performance with \nBase16 with a straightforward managed code despite that it's much simpler. I \nwas only able to match it after I converted Base16 to unsafe code with good \nindependent interleaving so CPU pipeline optimizations could take place. \nStill not satisfied though. Is .NET's Base64 implementation native? Perhaps.\n\nThanks\n------\nThanks to all contributors (most up to date is on the GitHub sidebar) who\nprovided patches, and reported bugs.\n\nChatting about this pet project with my friends \n[@detaybey](https://github.com/detaybey), \n[@vhallac](https://github.com/vhallac), \n[@alkimake](https://github.com/alkimake) and \n[@Utopians](https://github.com/Utopians) at one of our friend's birthday \nencouraged me to finish this. Thanks guys.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fssg%2Fsimplebase","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fssg%2Fsimplebase","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fssg%2Fsimplebase/lists"}