diff --git a/BaseN.md b/BaseN.md index 403def9..2cacbdc 100644 --- a/BaseN.md +++ b/BaseN.md @@ -1,877 +1,877 @@ -# Base64 vs Base58 Explained - -![BaseN diagram](./uploads/baseN-thumb-original-1024x576.png) - -## Quick Reference - -1. Original article: -2. Base64 Erlang: - -3. Base58 Erlang: - -4. Base64 TypeScript: - -5. Base58 TypeScript: - - -## tl;dr - -Base64 and Base58 are two schema for taking binary data and encoding it -to and from plain text. - -1. Conceptually: - 1. These are **NOT** two instances of a general "base N" concept. - 2. Base64 thinks of the binary data as a long stream of bytes. - - Base64 converts bytes to/from text 3 bytes at a time in a - "fire-and-forget" manner. - 3. Base58 thinks of the binary data as a really really long - integer. - - Base58 requires processing the entire bytestring all at once as - a singular unit. - 4. Base58 *is* the general BaseN algorithm. Base64 is simpler - because 64 and 256 are both powers of 2 and computers use - binary. - -2. Terminologically: - 1. The terms "encode" and "decode" are meant *from the perspective - of the computer program*. From the program's perspective, binary - data is what makes sense and plain text is gobbletygook. So - 1. we *decode* plain text (gahbage) into binary data (what - makes sense) - 2. we *encode* binary data (what makes sense) to plain text - (gahbage) - -3. Practically: - 1. Almost every language (including Erlang) has base64 in its - standard library, and you should probably just use that. - 2. You probably have to code Base58 yourself. - 3. If you are implementing Base58 in a language that does not have - bignum arithmetic, you have to implement it yourself. Thankfully - nobody will ever need any language other than Erlang so we can - ignore this problem in the context of this wiki. - 4. Base58 is *super* inefficient both space-wise and time-wise, because it - requires processing the entirety of the binary string as a single - monolithic piece of data. - 5. Base58 exists to preempt `Il10O`-type problems (visual - ambiguity) and doesn't involve `=+/` characters (the idea being - email clients are likely to break long lines at these - characters, increasing the likelihood of input errors). - 6. Consequently, Base58 is only suitable for binary data that is - **both** - 1. short in length - 2. likely to be entered manually (e.g. wallet/contract - addresses) - -## Base64 - -The term "binary" is misleading, because it leads people to think that -the basic unit in computing is a bit (a 1 or a 0). - -It is more accurate to think of the basic unit in computing as a byte, -which is a number between 0 (`2#0000_0000`) and 255 (`2#1111_1111`). - -(If you're totally bewildered by binary notation, you might want to read -[§ Long Division](#long-division) and come back). - -Base64 processes a binary string 3 bytes at a time (which is 24 bits). -It regroups those 3 groups of 8 bits into 4 groups of 6 bits. - - ABCDEFGH 12345678 ABCDEFGH - ABCDEF GH1234 5678AB CDEFGH - -We now have 4 numbers ranging between 0 (`2#00_0000`) and 63 -(`2#11_1111`), hence the name "Base64". - -Each number in the range 0..63 is assigned to a character from the -alphabet (see [§ Base64 Alphabet](#base64-alphabet)) - -```erlang -% abbreviated -% 0..25 map to $A..$Z -int2char(N) when 0 =< N, N =< 25 -> $A + N; -% 26..51 map to $a..$z -int2char(N) when 26 =< N, N =< 51 -> $a + (N - 26); -% 52..61 map to $0..$9 -int2char(N) when 52 =< N, N =< 61 -> $0 + (N - 52); -% special cases -int2char(62) -> $+; -int2char(63) -> $/. - -% $A..$Z map to 0..25 -char2int(C) when $A =< C, C =< $Z -> C - $A; -% $a..$z map to 26..51 -char2int(C) when $a =< C, C =< $z -> C - $a; -% $0..$9 map to 52..61 -char2int(C) when $0 =< C, C =< $9 -> C - $0; -% special cases -char2int($+) -> 62; -char2int($/) -> 63. -``` - -The only stupid cases arise when the number of bytes in the binary data -is not a multiple of 3. In this case there are two padding rules: - -```erlang -% general case: at least 3 bytes (24 bits = 6+6+6+6) remaining -% -% 12345678 abcdefgh 12345678 ... -% 123456 78abcd efgh12 345678 ... -% A B C D Rest -% convert to chars -> -% CA CB CC CD -enc(<>) -> - CA = int2char(A), - CB = int2char(B), - CC = int2char(C), - CD = int2char(D), - [CA, CB, CC, CD | enc(Rest)]; -% terminal case: 2 bytes (16 bits = 6+6+4) remaining -% -% 12345678 abcdefgh -% 123456 78abcd efgh__ -% A B C bsl 2 -% convert to chars -> -% CA CB CC = -enc(<>) -> - CA = int2char(A), - CB = int2char(B), - CC = int2char(C bsl 2), - [CA, CB, CC, $=]; -% terminal case: 1 byte (8 bits = 6+2) remaining -% -% 12345678 -> -% 123456 78____ -% A B bsl 4 -% convert to chars -> -% CA CB = = -enc(<>) -> - CA = int2char(A), - CB = int2char(B bsl 4), - [CA, CB, $=, $=]; -% terminal case: 0 bytes remaining -enc(<<>>) -> - []. -``` - -By the way, that's the *entire* encode procedure right there. - -The decode procedure is similarly simple but slightly trickier: - -```erlang -dec(Base64_String) -> - dec(Base64_String, <<>>). - - -% terminal case: two equal signs at the end = 1 byte (8 bits = 6+2) remaining -% input (characters) -> -% W X = = -% convert to numbers -> -% abcdef gh____ = = -% NW NX -% regroup -> -% abcdefgh ____ abcdef gh____ -% <> = << NW:6, NX:6 >> -dec([W, X, $=, $=], Acc) -> - NW = char2int(W), - NX = char2int(X), - <> = <>, - <>; -% terminal case: one equal sign at the end = 2 bytes remaining -% -% input (characters) -> -% W X Y = -% convert to numbers -> -% abcdef gh1234 5678__ = -% NW NX NY -% regroup -> -% abcdefgh 12345678 __ abcdef gh1234 5678__ -% << B1:8, B2:8, 0:2 >> = << NW:6, NX:6 NY:6 >> -dec([W, X, Y, $=], Acc) -> - NW = char2int(W), - NX = char2int(X), - NY = char2int(Y), - <> = <>, - <>; -% terminal case: 0 bytes remaining -% nothing to do -dec([], Acc) -> - Acc; -% general case: no equal signs = 3 or more bytes remaining -% -% input (characters) -> -% W X Y Z -% convert to numbers -> -% abcdef gh1234 5678ab cdefgh -% NW NX NY NZ -% decompose -> -% abcdefgh 12345678 abcdefgh abcdef gh1234 5678ab cdefgh -% << B1:8, B2:8, B3:2 >> = << NW:6, NX:6 NY:6, NZ:6 >> -dec([W, X, Y, Z | Rest], Acc) -> - NW = char2int(W), - NX = char2int(X), - NY = char2int(Y), - NZ = char2int(Z), - NewAcc = <>, - dec(Rest, NewAcc). -``` - -This is marginally trickier because - -1. it needs an accumulator -2. the order of the cases matters - -## Long Division - -As mentioned above, Base58 is the "general" BaseN algorithm applied to -the case `N=58`. This general algorithm I am calling the -quotient-remainder (QR) algorithm. You might know it as the long division -algorithm you learned in school. - -Consider an integer, say `8763`. To understand the QR algorithm you need -to make a conceptual distinction between the "conceptual integer" 8763 -and its representation as the sequence of symbols `10#8763`. - -The way our notation for numbers works is as follows: - - 10#8763 = 8 * 10^3 (1000) - + 7 * 10^2 (100) - + 6 * 10^1 (10) - + 3 * 10^0 (1) - -So in each "slot" there can exist one of 10 symbols: `0123456789`. Each -slot corresponds to a power of 10. You multiply the number in each -slot by the corresponding power of 10 and then take the sum to -"obtain" the pure integer. - -Binary notation works exactly the same way, except each slot corresponds -to a power of $2$, and consequently each slot contains one of the two -symbols `01`. - -For instance, to convert `2#1010_0111` to its "conceptual integer", we -do exactly the same process: - - 2#1010\_0111 = 1 * 2^7 (128) - + 0 * 2^6 (64) - + 1 * 2^5 (32) - + 0 * 2^4 (16) - + 0 * 2^3 (8) - + 1 * 2^2 (4) - + 1 * 2^1 (2) - + 1 * 2^0 (1) - = 128 + 32 + 4 + 2 + 1 - = 167 - -Exercise for the reader: take this binary counting concept and figure -out how you could use your fingers to count up to 1023. - -All we have done so far is convert things to base 10. This isn't hard -because it's already familiar. - -If you are a native English speaker and kind of know French, it's much -easier to translate French into English than it is to translate English -into French. Expressing things in an unfamiliar context is significantly -more challenging than comprehending things in the unfamiliar context. - -How might we take our integer 8763 and convert it into base 2? - -It's pretty simple: divide by 2 over and over using the long division -you learned in elementary school (you *do* remember how to do long -division, right?) - - quotients remainders - |----| |-| - 8763 = 4381 *2 + 1 - 4381 = 2190 *2 + 1 - 2190 = 1095 *2 + 0 - 1095 = 547 *2 + 1 - 547 = 273 *2 + 1 - 273 = 136 *2 + 1 - 136 = 68 *2 + 0 - 68 = 34 *2 + 0 - 34 = 17 *2 + 0 - 17 = 8 *2 + 1 - 8 = 4 *2 + 0 - 4 = 2 *2 + 0 - 2 = 1 *2 + 0 - 1 = 0 *2 + 1 - 0 = DONE - -The sequence of remainders *in reverse order* is the binary -representation of 8763. Equivalently, the sequence of remainders -"grows to the left". - -```erlang -16> 2#1000_1000_1110_11. -8763 -``` - -Notice that if we delete digits off the right end, we get back the -sequence of quotients: - -```erlang -16> 2#1000_1000_1110_11. -8763 -17> 2#1000_1000_1110_1. -4381 -18> 2#1000_1000_1110. -2190 -19> 2#1000_1000_111. -1095 -20> 2#1000_1000_11. -547 -``` - -Take a minute and figure out for yourself why this makes sense. - -At any rate, the way to code this is - -```erlang --module(ntb). - --export([ - n_to_base/2 -]). - - -n_to_base(N, Base) when N >= 0, Base >= 2 -> - n_to_base(N, Base, []). - -n_to_base(0, _Base, Digits) -> - Digits; -n_to_base(N, Base, Digits) -> - NewN = N div Base, - NewDigit = N rem Base, - NewDigits = [NewDigit | Digits], - n_to_base(NewN, Base, NewDigits). -``` - -```erlang -3> ntb:n_to_base(8763, 2). -[1,0,0,0,1,0,0,0,1,1,1,0,1,1] -``` - -Let us do the calculation manually in Base58 to get our digits: - - 8763 = 151*58 + 5 - 151 = 2*58 + 35 - 2 = 0*58 + 2 - 0 = DONE - -So in "Base58", the sequence of digits is `[2, 35, 5]`. Our program -agrees: - -```erlang -8> ntb:n_to_base(8763, 58). -[2,35,5] -``` - -Our Base58 code is going to contain exactly this idea modulo an -unpleasant volume of edge case stupidity. - -## Base58 algorithm - -Conceptually, we are doing the following: - - binary data <-> integer <-> sequence of numbers 0-57 <-> text characters - -The unpleasantry comes from the fact that positional notation for -numbers makes no distinction between the sequence of symbols `8763` and -`00008763`. - -That is, if we have a bytestring with a bunch of leading 0 bytes, those -bytes are not going to change the integer that bytestring corresponds -to. That is, all of the following byte arrays point to the same integer: - - ABCD_EFGH - 0000_0000 ABCD_EFGH - 0000_0000 0000_0000 ABCD_EFGH - 0000_0000 0000_0000 0000_0000 ABCD_EFGH - -So when we're going from integers back to binary, we have an ambiguity. -Because we could in theory have a billion leading zero bytes that are -just totally unaccounted for. - -The hack to deal with this is that leading 0 bytes are represented as -leading `$1`s in the text representation (`$0` is excluded from the Base58 -alphabet to avoid visual collision with `$O`). - -This ends up meaning we have to do an extra preprocessing step - -1. on decode (text -> binary): pre-strip all the leading `$1` - characters off the text representation and account for them as - leading `0` bytes - -2. on encode (binary -> text): and conversely pre-strip all the leading `0` - bytes and account for them as leading `$1` characters. - -The result is as follows: - -```erlang --module(b58). - --export([ - enc/1, - dec/1, - chars58/2, - unchars58/2, - int2char/1, - char2int/1 -]). - - -%% consume all the leading 0 bytes -enc(<<0:8, Rest/binary>>) -> - [int2char(0) | enc(Rest)]; -enc(Bytes) when is_binary(Bytes) -> - %% Binary -> integer - N = binary:decode_unsigned(Bytes), - %% Integer -> chars - chars58(N, []). - -%% combining steps of converting to numbers 0..57 and then converting those -%% numbers to ascii characters just to avoid traversing the list twice -chars58(N, Acc) when N > 0 -> - Q = N div 58, - R = N rem 58, - NewAcc = [int2char(R) | Acc], - chars58(Q, NewAcc); -chars58(0, Acc) -> - Acc. - - -%% consume all the leading 1 characters -dec([$1 | Rest]) -> - %io:format("dec([$1 | ~p])~n", [Rest]), - <<0:8, (dec(Rest))/binary>>; -%% this case corresponds to the bytestring being entirely 0 bytes -%% in this case, our usual approach (seen below) would compute N=0 and then -%% binary:encode_unsigned/1 encodes 0 to <<0>>, thus incorrectly adding an -%% extra zero byte. this case preempts that -dec([]) -> - %io:format("dec([])~n", []), - <<>>; -dec(Chars) when is_list(Chars) -> - %io:format("dec(~p)~n", [Chars]), - %% chars -> integer - N = unchars58(Chars, 0), - %% integer -> binary - binary:encode_unsigned(N). - -unchars58([Char | Rest], AccN) -> - %io:format("unchars58([~p | ~p], ~p)~n", [Char, Rest, AccN]), - %% Char corresponds to a number in 0..57 - Digit057 = char2int(Char), - % You can imagine the entire original digit as - % AccN ++ Char ++ Rest - % multiplying AccN by 58 corresponds to "shifting" AccN 1 space to the - % left. Then we "++ Char" at the right end, then continue on. - NewAccN = AccN*58 + Digit057, - unchars58(Rest, NewAccN); -unchars58([], AccN) -> - %io:format("unchars58([], ~p)~n", [AccN]), - AccN. - - -int2char( 0) -> $1; -int2char( 1) -> $2; -int2char( 2) -> $3; -int2char( 3) -> $4; -int2char( 4) -> $5; -int2char( 5) -> $6; -int2char( 6) -> $7; -int2char( 7) -> $8; -int2char( 8) -> $9; -int2char( 9) -> $A; -int2char(10) -> $B; -int2char(11) -> $C; -int2char(12) -> $D; -int2char(13) -> $E; -int2char(14) -> $F; -int2char(15) -> $G; -int2char(16) -> $H; -int2char(17) -> $J; -int2char(18) -> $K; -int2char(19) -> $L; -int2char(20) -> $M; -int2char(21) -> $N; -int2char(22) -> $P; -int2char(23) -> $Q; -int2char(24) -> $R; -int2char(25) -> $S; -int2char(26) -> $T; -int2char(27) -> $U; -int2char(28) -> $V; -int2char(29) -> $W; -int2char(30) -> $X; -int2char(31) -> $Y; -int2char(32) -> $Z; -int2char(33) -> $a; -int2char(34) -> $b; -int2char(35) -> $c; -int2char(36) -> $d; -int2char(37) -> $e; -int2char(38) -> $f; -int2char(39) -> $g; -int2char(40) -> $h; -int2char(41) -> $i; -int2char(42) -> $j; -int2char(43) -> $k; -int2char(44) -> $m; -int2char(45) -> $n; -int2char(46) -> $o; -int2char(47) -> $p; -int2char(48) -> $q; -int2char(49) -> $r; -int2char(50) -> $s; -int2char(51) -> $t; -int2char(52) -> $u; -int2char(53) -> $v; -int2char(54) -> $w; -int2char(55) -> $x; -int2char(56) -> $y; -int2char(57) -> $z. - -char2int($1) -> 0; -char2int($2) -> 1; -char2int($3) -> 2; -char2int($4) -> 3; -char2int($5) -> 4; -char2int($6) -> 5; -char2int($7) -> 6; -char2int($8) -> 7; -char2int($9) -> 8; -char2int($A) -> 9; -char2int($B) -> 10; -char2int($C) -> 11; -char2int($D) -> 12; -char2int($E) -> 13; -char2int($F) -> 14; -char2int($G) -> 15; -char2int($H) -> 16; -char2int($J) -> 17; -char2int($K) -> 18; -char2int($L) -> 19; -char2int($M) -> 20; -char2int($N) -> 21; -char2int($P) -> 22; -char2int($Q) -> 23; -char2int($R) -> 24; -char2int($S) -> 25; -char2int($T) -> 26; -char2int($U) -> 27; -char2int($V) -> 28; -char2int($W) -> 29; -char2int($X) -> 30; -char2int($Y) -> 31; -char2int($Z) -> 32; -char2int($a) -> 33; -char2int($b) -> 34; -char2int($c) -> 35; -char2int($d) -> 36; -char2int($e) -> 37; -char2int($f) -> 38; -char2int($g) -> 39; -char2int($h) -> 40; -char2int($i) -> 41; -char2int($j) -> 42; -char2int($k) -> 43; -char2int($m) -> 44; -char2int($n) -> 45; -char2int($o) -> 46; -char2int($p) -> 47; -char2int($q) -> 48; -char2int($r) -> 49; -char2int($s) -> 50; -char2int($t) -> 51; -char2int($u) -> 52; -char2int($v) -> 53; -char2int($w) -> 54; -char2int($x) -> 55; -char2int($y) -> 56; -char2int($z) -> 57. -``` - -```erlang -4> b58:chars58(8763, []). -"3c6" -6> b58:char2int($3). -2 -7> b58:char2int($c). -35 -8> b58:char2int($6). -5 -43> binary:encode_unsigned(8763). -<<"\";">> -44> b58:enc(<<"\";">>). -"3c6" -45> b58:enc(<<0, 0, 0, "\";">>). -"1113c6" -46> b58:dec("3c6"). -<<"\";">> -47> b58:dec("13c6"). -<<0,34,59>> -48> b58:dec("11113c6"). -<<0,0,0,0,34,59>> -``` - - - -## Tables etc - -### Base64 Alphabet - -```erlang -int2char( 0) -> $A; -int2char( 1) -> $B; -int2char( 2) -> $C; -int2char( 3) -> $D; -int2char( 4) -> $E; -int2char( 5) -> $F; -int2char( 6) -> $G; -int2char( 7) -> $H; -int2char( 8) -> $I; -int2char( 9) -> $J; -int2char(10) -> $K; -int2char(11) -> $L; -int2char(12) -> $M; -int2char(13) -> $N; -int2char(14) -> $O; -int2char(15) -> $P; -int2char(16) -> $Q; -int2char(17) -> $R; -int2char(18) -> $S; -int2char(19) -> $T; -int2char(20) -> $U; -int2char(21) -> $V; -int2char(22) -> $W; -int2char(23) -> $X; -int2char(24) -> $Y; -int2char(25) -> $Z; -int2char(26) -> $a; -int2char(27) -> $b; -int2char(28) -> $c; -int2char(29) -> $d; -int2char(30) -> $e; -int2char(31) -> $f; -int2char(32) -> $g; -int2char(33) -> $h; -int2char(34) -> $i; -int2char(35) -> $j; -int2char(36) -> $k; -int2char(37) -> $l; -int2char(38) -> $m; -int2char(39) -> $n; -int2char(40) -> $o; -int2char(41) -> $p; -int2char(42) -> $q; -int2char(43) -> $r; -int2char(44) -> $s; -int2char(45) -> $t; -int2char(46) -> $u; -int2char(47) -> $v; -int2char(48) -> $w; -int2char(49) -> $x; -int2char(50) -> $y; -int2char(51) -> $z; -int2char(52) -> $0; -int2char(53) -> $1; -int2char(54) -> $2; -int2char(55) -> $3; -int2char(56) -> $4; -int2char(57) -> $5; -int2char(58) -> $6; -int2char(59) -> $7; -int2char(60) -> $8; -int2char(61) -> $9; -int2char(62) -> $+; -int2char(63) -> $/. - -char2int($A) -> 0; -char2int($B) -> 1; -char2int($C) -> 2; -char2int($D) -> 3; -char2int($E) -> 4; -char2int($F) -> 5; -char2int($G) -> 6; -char2int($H) -> 7; -char2int($I) -> 8; -char2int($J) -> 9; -char2int($K) -> 10; -char2int($L) -> 11; -char2int($M) -> 12; -char2int($N) -> 13; -char2int($O) -> 14; -char2int($P) -> 15; -char2int($Q) -> 16; -char2int($R) -> 17; -char2int($S) -> 18; -char2int($T) -> 19; -char2int($U) -> 20; -char2int($V) -> 21; -char2int($W) -> 22; -char2int($X) -> 23; -char2int($Y) -> 24; -char2int($Z) -> 25; -char2int($a) -> 26; -char2int($b) -> 27; -char2int($c) -> 28; -char2int($d) -> 29; -char2int($e) -> 30; -char2int($f) -> 31; -char2int($g) -> 32; -char2int($h) -> 33; -char2int($i) -> 34; -char2int($j) -> 35; -char2int($k) -> 36; -char2int($l) -> 37; -char2int($m) -> 38; -char2int($n) -> 39; -char2int($o) -> 40; -char2int($p) -> 41; -char2int($q) -> 42; -char2int($r) -> 43; -char2int($s) -> 44; -char2int($t) -> 45; -char2int($u) -> 46; -char2int($v) -> 47; -char2int($w) -> 48; -char2int($x) -> 49; -char2int($y) -> 50; -char2int($z) -> 51; -char2int($0) -> 52; -char2int($1) -> 53; -char2int($2) -> 54; -char2int($3) -> 55; -char2int($4) -> 56; -char2int($5) -> 57; -char2int($6) -> 58; -char2int($7) -> 59; -char2int($8) -> 60; -char2int($9) -> 61; -char2int($+) -> 62; -char2int($/) -> 63. -``` - -### Base58 Alphabet - -```erlang -int2char( 0) -> $1; -int2char( 1) -> $2; -int2char( 2) -> $3; -int2char( 3) -> $4; -int2char( 4) -> $5; -int2char( 5) -> $6; -int2char( 6) -> $7; -int2char( 7) -> $8; -int2char( 8) -> $9; -int2char( 9) -> $A; -int2char(10) -> $B; -int2char(11) -> $C; -int2char(12) -> $D; -int2char(13) -> $E; -int2char(14) -> $F; -int2char(15) -> $G; -int2char(16) -> $H; -int2char(17) -> $J; -int2char(18) -> $K; -int2char(19) -> $L; -int2char(20) -> $M; -int2char(21) -> $N; -int2char(22) -> $P; -int2char(23) -> $Q; -int2char(24) -> $R; -int2char(25) -> $S; -int2char(26) -> $T; -int2char(27) -> $U; -int2char(28) -> $V; -int2char(29) -> $W; -int2char(30) -> $X; -int2char(31) -> $Y; -int2char(32) -> $Z; -int2char(33) -> $a; -int2char(34) -> $b; -int2char(35) -> $c; -int2char(36) -> $d; -int2char(37) -> $e; -int2char(38) -> $f; -int2char(39) -> $g; -int2char(40) -> $h; -int2char(41) -> $i; -int2char(42) -> $j; -int2char(43) -> $k; -int2char(44) -> $m; -int2char(45) -> $n; -int2char(46) -> $o; -int2char(47) -> $p; -int2char(48) -> $q; -int2char(49) -> $r; -int2char(50) -> $s; -int2char(51) -> $t; -int2char(52) -> $u; -int2char(53) -> $v; -int2char(54) -> $w; -int2char(55) -> $x; -int2char(56) -> $y; -int2char(57) -> $z. - -char2int($1) -> 0; -char2int($2) -> 1; -char2int($3) -> 2; -char2int($4) -> 3; -char2int($5) -> 4; -char2int($6) -> 5; -char2int($7) -> 6; -char2int($8) -> 7; -char2int($9) -> 8; -char2int($A) -> 9; -char2int($B) -> 10; -char2int($C) -> 11; -char2int($D) -> 12; -char2int($E) -> 13; -char2int($F) -> 14; -char2int($G) -> 15; -char2int($H) -> 16; -char2int($J) -> 17; -char2int($K) -> 18; -char2int($L) -> 19; -char2int($M) -> 20; -char2int($N) -> 21; -char2int($P) -> 22; -char2int($Q) -> 23; -char2int($R) -> 24; -char2int($S) -> 25; -char2int($T) -> 26; -char2int($U) -> 27; -char2int($V) -> 28; -char2int($W) -> 29; -char2int($X) -> 30; -char2int($Y) -> 31; -char2int($Z) -> 32; -char2int($a) -> 33; -char2int($b) -> 34; -char2int($c) -> 35; -char2int($d) -> 36; -char2int($e) -> 37; -char2int($f) -> 38; -char2int($g) -> 39; -char2int($h) -> 40; -char2int($i) -> 41; -char2int($j) -> 42; -char2int($k) -> 43; -char2int($m) -> 44; -char2int($n) -> 45; -char2int($o) -> 46; -char2int($p) -> 47; -char2int($q) -> 48; -char2int($r) -> 49; -char2int($s) -> 50; -char2int($t) -> 51; -char2int($u) -> 52; -char2int($v) -> 53; -char2int($w) -> 54; -char2int($x) -> 55; -char2int($y) -> 56; -char2int($z) -> 57. -``` +# Base64 vs Base58 Explained + +![BaseN diagram](./uploads/baseN-thumb-original-1024x576.png) + +## Quick Reference + +1. Original article: +2. Base64 Erlang: + +3. Base58 Erlang: + +4. Base64 TypeScript: + +5. Base58 TypeScript: + + +## tl;dr + +Base64 and Base58 are two schema for taking binary data and encoding it +to and from plain text. + +1. Conceptually: + 1. These are **NOT** two instances of a general "base N" concept. + 2. Base64 thinks of the binary data as a long stream of bytes. + + Base64 converts bytes to/from text 3 bytes at a time in a + "fire-and-forget" manner. + 3. Base58 thinks of the binary data as a really really long + integer. + + Base58 requires processing the entire bytestring all at once as + a singular unit. + 4. Base58 *is* the general BaseN algorithm. Base64 is simpler + because 64 and 256 are both powers of 2 and computers use + binary. + +2. Terminologically: + 1. The terms "encode" and "decode" are meant *from the perspective + of the computer program*. From the program's perspective, binary + data is what makes sense and plain text is gobbletygook. So + 1. we *decode* plain text (gahbage) into binary data (what + makes sense) + 2. we *encode* binary data (what makes sense) to plain text + (gahbage) + +3. Practically: + 1. Almost every language (including Erlang) has base64 in its + standard library, and you should probably just use that. + 2. You probably have to code Base58 yourself. + 3. If you are implementing Base58 in a language that does not have + bignum arithmetic, you have to implement it yourself. Thankfully + nobody will ever need any language other than Erlang so we can + ignore this problem in the context of this wiki. + 4. Base58 is *super* inefficient both space-wise and time-wise, because it + requires processing the entirety of the binary string as a single + monolithic piece of data. + 5. Base58 exists to preempt `Il10O`-type problems (visual + ambiguity) and doesn't involve `=+/` characters (the idea being + email clients are likely to break long lines at these + characters, increasing the likelihood of input errors). + 6. Consequently, Base58 is only suitable for binary data that is + **both** + 1. short in length + 2. likely to be entered manually (e.g. wallet/contract + addresses) + +## Base64 + +The term "binary" is misleading, because it leads people to think that +the basic unit in computing is a bit (a 1 or a 0). + +It is more accurate to think of the basic unit in computing as a byte, +which is a number between 0 (`2#0000_0000`) and 255 (`2#1111_1111`). + +(If you're totally bewildered by binary notation, you might want to read +[§ Long Division](#long-division) and come back). + +Base64 processes a binary string 3 bytes at a time (which is 24 bits). +It regroups those 3 groups of 8 bits into 4 groups of 6 bits. + + ABCDEFGH 12345678 ABCDEFGH + ABCDEF GH1234 5678AB CDEFGH + +We now have 4 numbers ranging between 0 (`2#00_0000`) and 63 +(`2#11_1111`), hence the name "Base64". + +Each number in the range 0..63 is assigned to a character from the +alphabet (see [§ Base64 Alphabet](#base64-alphabet)) + +```erlang +% abbreviated +% 0..25 map to $A..$Z +int2char(N) when 0 =< N, N =< 25 -> $A + N; +% 26..51 map to $a..$z +int2char(N) when 26 =< N, N =< 51 -> $a + (N - 26); +% 52..61 map to $0..$9 +int2char(N) when 52 =< N, N =< 61 -> $0 + (N - 52); +% special cases +int2char(62) -> $+; +int2char(63) -> $/. + +% $A..$Z map to 0..25 +char2int(C) when $A =< C, C =< $Z -> C - $A; +% $a..$z map to 26..51 +char2int(C) when $a =< C, C =< $z -> C - $a; +% $0..$9 map to 52..61 +char2int(C) when $0 =< C, C =< $9 -> C - $0; +% special cases +char2int($+) -> 62; +char2int($/) -> 63. +``` + +The only stupid cases arise when the number of bytes in the binary data +is not a multiple of 3. In this case there are two padding rules: + +```erlang +% general case: at least 3 bytes (24 bits = 6+6+6+6) remaining +% +% 12345678 abcdefgh 12345678 ... +% 123456 78abcd efgh12 345678 ... +% A B C D Rest +% convert to chars -> +% CA CB CC CD +enc(<>) -> + CA = int2char(A), + CB = int2char(B), + CC = int2char(C), + CD = int2char(D), + [CA, CB, CC, CD | enc(Rest)]; +% terminal case: 2 bytes (16 bits = 6+6+4) remaining +% +% 12345678 abcdefgh +% 123456 78abcd efgh__ +% A B C bsl 2 +% convert to chars -> +% CA CB CC = +enc(<>) -> + CA = int2char(A), + CB = int2char(B), + CC = int2char(C bsl 2), + [CA, CB, CC, $=]; +% terminal case: 1 byte (8 bits = 6+2) remaining +% +% 12345678 -> +% 123456 78____ +% A B bsl 4 +% convert to chars -> +% CA CB = = +enc(<>) -> + CA = int2char(A), + CB = int2char(B bsl 4), + [CA, CB, $=, $=]; +% terminal case: 0 bytes remaining +enc(<<>>) -> + []. +``` + +By the way, that's the *entire* encode procedure right there. + +The decode procedure is similarly simple but slightly trickier: + +```erlang +dec(Base64_String) -> + dec(Base64_String, <<>>). + + +% terminal case: two equal signs at the end = 1 byte (8 bits = 6+2) remaining +% input (characters) -> +% W X = = +% convert to numbers -> +% abcdef gh____ = = +% NW NX +% regroup -> +% abcdefgh ____ abcdef gh____ +% <> = << NW:6, NX:6 >> +dec([W, X, $=, $=], Acc) -> + NW = char2int(W), + NX = char2int(X), + <> = <>, + <>; +% terminal case: one equal sign at the end = 2 bytes remaining +% +% input (characters) -> +% W X Y = +% convert to numbers -> +% abcdef gh1234 5678__ = +% NW NX NY +% regroup -> +% abcdefgh 12345678 __ abcdef gh1234 5678__ +% << B1:8, B2:8, 0:2 >> = << NW:6, NX:6 NY:6 >> +dec([W, X, Y, $=], Acc) -> + NW = char2int(W), + NX = char2int(X), + NY = char2int(Y), + <> = <>, + <>; +% terminal case: 0 bytes remaining +% nothing to do +dec([], Acc) -> + Acc; +% general case: no equal signs = 3 or more bytes remaining +% +% input (characters) -> +% W X Y Z +% convert to numbers -> +% abcdef gh1234 5678ab cdefgh +% NW NX NY NZ +% decompose -> +% abcdefgh 12345678 abcdefgh abcdef gh1234 5678ab cdefgh +% << B1:8, B2:8, B3:2 >> = << NW:6, NX:6 NY:6, NZ:6 >> +dec([W, X, Y, Z | Rest], Acc) -> + NW = char2int(W), + NX = char2int(X), + NY = char2int(Y), + NZ = char2int(Z), + NewAcc = <>, + dec(Rest, NewAcc). +``` + +This is marginally trickier because + +1. it needs an accumulator +2. the order of the cases matters + +## Long Division + +As mentioned above, Base58 is the "general" BaseN algorithm applied to +the case `N=58`. This general algorithm I am calling the +quotient-remainder (QR) algorithm. You might know it as the long division +algorithm you learned in school. + +Consider an integer, say `8763`. To understand the QR algorithm you need +to make a conceptual distinction between the "conceptual integer" 8763 +and its representation as the sequence of symbols `10#8763`. + +The way our notation for numbers works is as follows: + + 10#8763 = 8 * 10^3 (1000) + + 7 * 10^2 (100) + + 6 * 10^1 (10) + + 3 * 10^0 (1) + +So in each "slot" there can exist one of 10 symbols: `0123456789`. Each +slot corresponds to a power of 10. You multiply the number in each +slot by the corresponding power of 10 and then take the sum to +"obtain" the pure integer. + +Binary notation works exactly the same way, except each slot corresponds +to a power of $2$, and consequently each slot contains one of the two +symbols `01`. + +For instance, to convert `2#1010_0111` to its "conceptual integer", we +do exactly the same process: + + 2#1010\_0111 = 1 * 2^7 (128) + + 0 * 2^6 (64) + + 1 * 2^5 (32) + + 0 * 2^4 (16) + + 0 * 2^3 (8) + + 1 * 2^2 (4) + + 1 * 2^1 (2) + + 1 * 2^0 (1) + = 128 + 32 + 4 + 2 + 1 + = 167 + +Exercise for the reader: take this binary counting concept and figure +out how you could use your fingers to count up to 1023. + +All we have done so far is convert things to base 10. This isn't hard +because it's already familiar. + +If you are a native English speaker and kind of know French, it's much +easier to translate French into English than it is to translate English +into French. Expressing things in an unfamiliar context is significantly +more challenging than comprehending things in the unfamiliar context. + +How might we take our integer 8763 and convert it into base 2? + +It's pretty simple: divide by 2 over and over using the long division +you learned in elementary school (you *do* remember how to do long +division, right?) + + quotients remainders + |----| |-| + 8763 = 4381 *2 + 1 + 4381 = 2190 *2 + 1 + 2190 = 1095 *2 + 0 + 1095 = 547 *2 + 1 + 547 = 273 *2 + 1 + 273 = 136 *2 + 1 + 136 = 68 *2 + 0 + 68 = 34 *2 + 0 + 34 = 17 *2 + 0 + 17 = 8 *2 + 1 + 8 = 4 *2 + 0 + 4 = 2 *2 + 0 + 2 = 1 *2 + 0 + 1 = 0 *2 + 1 + 0 = DONE + +The sequence of remainders *in reverse order* is the binary +representation of 8763. Equivalently, the sequence of remainders +"grows to the left". + +```erlang +16> 2#1000_1000_1110_11. +8763 +``` + +Notice that if we delete digits off the right end, we get back the +sequence of quotients: + +```erlang +16> 2#1000_1000_1110_11. +8763 +17> 2#1000_1000_1110_1. +4381 +18> 2#1000_1000_1110. +2190 +19> 2#1000_1000_111. +1095 +20> 2#1000_1000_11. +547 +``` + +Take a minute and figure out for yourself why this makes sense. + +At any rate, the way to code this is + +```erlang +-module(ntb). + +-export([ + n_to_base/2 +]). + + +n_to_base(N, Base) when N >= 0, Base >= 2 -> + n_to_base(N, Base, []). + +n_to_base(0, _Base, Digits) -> + Digits; +n_to_base(N, Base, Digits) -> + NewN = N div Base, + NewDigit = N rem Base, + NewDigits = [NewDigit | Digits], + n_to_base(NewN, Base, NewDigits). +``` + +```erlang +3> ntb:n_to_base(8763, 2). +[1,0,0,0,1,0,0,0,1,1,1,0,1,1] +``` + +Let us do the calculation manually in Base58 to get our digits: + + 8763 = 151*58 + 5 + 151 = 2*58 + 35 + 2 = 0*58 + 2 + 0 = DONE + +So in "Base58", the sequence of digits is `[2, 35, 5]`. Our program +agrees: + +```erlang +8> ntb:n_to_base(8763, 58). +[2,35,5] +``` + +Our Base58 code is going to contain exactly this idea modulo an +unpleasant volume of edge case stupidity. + +## Base58 algorithm + +Conceptually, we are doing the following: + + binary data <-> integer <-> sequence of numbers 0-57 <-> text characters + +The unpleasantry comes from the fact that positional notation for +numbers makes no distinction between the sequence of symbols `8763` and +`00008763`. + +That is, if we have a bytestring with a bunch of leading 0 bytes, those +bytes are not going to change the integer that bytestring corresponds +to. That is, all of the following byte arrays point to the same integer: + + ABCD_EFGH + 0000_0000 ABCD_EFGH + 0000_0000 0000_0000 ABCD_EFGH + 0000_0000 0000_0000 0000_0000 ABCD_EFGH + +So when we're going from integers back to binary, we have an ambiguity. +Because we could in theory have a billion leading zero bytes that are +just totally unaccounted for. + +The hack to deal with this is that leading 0 bytes are represented as +leading `$1`s in the text representation (`$0` is excluded from the Base58 +alphabet to avoid visual collision with `$O`). + +This ends up meaning we have to do an extra preprocessing step + +1. on decode (text -> binary): pre-strip all the leading `$1` + characters off the text representation and account for them as + leading `0` bytes + +2. on encode (binary -> text): and conversely pre-strip all the leading `0` + bytes and account for them as leading `$1` characters. + +The result is as follows: + +```erlang +-module(b58). + +-export([ + enc/1, + dec/1, + chars58/2, + unchars58/2, + int2char/1, + char2int/1 +]). + + +%% consume all the leading 0 bytes +enc(<<0:8, Rest/binary>>) -> + [int2char(0) | enc(Rest)]; +enc(Bytes) when is_binary(Bytes) -> + %% Binary -> integer + N = binary:decode_unsigned(Bytes), + %% Integer -> chars + chars58(N, []). + +%% combining steps of converting to numbers 0..57 and then converting those +%% numbers to ascii characters just to avoid traversing the list twice +chars58(N, Acc) when N > 0 -> + Q = N div 58, + R = N rem 58, + NewAcc = [int2char(R) | Acc], + chars58(Q, NewAcc); +chars58(0, Acc) -> + Acc. + + +%% consume all the leading 1 characters +dec([$1 | Rest]) -> + %io:format("dec([$1 | ~p])~n", [Rest]), + <<0:8, (dec(Rest))/binary>>; +%% this case corresponds to the bytestring being entirely 0 bytes +%% in this case, our usual approach (seen below) would compute N=0 and then +%% binary:encode_unsigned/1 encodes 0 to <<0>>, thus incorrectly adding an +%% extra zero byte. this case preempts that +dec([]) -> + %io:format("dec([])~n", []), + <<>>; +dec(Chars) when is_list(Chars) -> + %io:format("dec(~p)~n", [Chars]), + %% chars -> integer + N = unchars58(Chars, 0), + %% integer -> binary + binary:encode_unsigned(N). + +unchars58([Char | Rest], AccN) -> + %io:format("unchars58([~p | ~p], ~p)~n", [Char, Rest, AccN]), + %% Char corresponds to a number in 0..57 + Digit057 = char2int(Char), + % You can imagine the entire original digit as + % AccN ++ Char ++ Rest + % multiplying AccN by 58 corresponds to "shifting" AccN 1 space to the + % left. Then we "++ Char" at the right end, then continue on. + NewAccN = AccN*58 + Digit057, + unchars58(Rest, NewAccN); +unchars58([], AccN) -> + %io:format("unchars58([], ~p)~n", [AccN]), + AccN. + + +int2char( 0) -> $1; +int2char( 1) -> $2; +int2char( 2) -> $3; +int2char( 3) -> $4; +int2char( 4) -> $5; +int2char( 5) -> $6; +int2char( 6) -> $7; +int2char( 7) -> $8; +int2char( 8) -> $9; +int2char( 9) -> $A; +int2char(10) -> $B; +int2char(11) -> $C; +int2char(12) -> $D; +int2char(13) -> $E; +int2char(14) -> $F; +int2char(15) -> $G; +int2char(16) -> $H; +int2char(17) -> $J; +int2char(18) -> $K; +int2char(19) -> $L; +int2char(20) -> $M; +int2char(21) -> $N; +int2char(22) -> $P; +int2char(23) -> $Q; +int2char(24) -> $R; +int2char(25) -> $S; +int2char(26) -> $T; +int2char(27) -> $U; +int2char(28) -> $V; +int2char(29) -> $W; +int2char(30) -> $X; +int2char(31) -> $Y; +int2char(32) -> $Z; +int2char(33) -> $a; +int2char(34) -> $b; +int2char(35) -> $c; +int2char(36) -> $d; +int2char(37) -> $e; +int2char(38) -> $f; +int2char(39) -> $g; +int2char(40) -> $h; +int2char(41) -> $i; +int2char(42) -> $j; +int2char(43) -> $k; +int2char(44) -> $m; +int2char(45) -> $n; +int2char(46) -> $o; +int2char(47) -> $p; +int2char(48) -> $q; +int2char(49) -> $r; +int2char(50) -> $s; +int2char(51) -> $t; +int2char(52) -> $u; +int2char(53) -> $v; +int2char(54) -> $w; +int2char(55) -> $x; +int2char(56) -> $y; +int2char(57) -> $z. + +char2int($1) -> 0; +char2int($2) -> 1; +char2int($3) -> 2; +char2int($4) -> 3; +char2int($5) -> 4; +char2int($6) -> 5; +char2int($7) -> 6; +char2int($8) -> 7; +char2int($9) -> 8; +char2int($A) -> 9; +char2int($B) -> 10; +char2int($C) -> 11; +char2int($D) -> 12; +char2int($E) -> 13; +char2int($F) -> 14; +char2int($G) -> 15; +char2int($H) -> 16; +char2int($J) -> 17; +char2int($K) -> 18; +char2int($L) -> 19; +char2int($M) -> 20; +char2int($N) -> 21; +char2int($P) -> 22; +char2int($Q) -> 23; +char2int($R) -> 24; +char2int($S) -> 25; +char2int($T) -> 26; +char2int($U) -> 27; +char2int($V) -> 28; +char2int($W) -> 29; +char2int($X) -> 30; +char2int($Y) -> 31; +char2int($Z) -> 32; +char2int($a) -> 33; +char2int($b) -> 34; +char2int($c) -> 35; +char2int($d) -> 36; +char2int($e) -> 37; +char2int($f) -> 38; +char2int($g) -> 39; +char2int($h) -> 40; +char2int($i) -> 41; +char2int($j) -> 42; +char2int($k) -> 43; +char2int($m) -> 44; +char2int($n) -> 45; +char2int($o) -> 46; +char2int($p) -> 47; +char2int($q) -> 48; +char2int($r) -> 49; +char2int($s) -> 50; +char2int($t) -> 51; +char2int($u) -> 52; +char2int($v) -> 53; +char2int($w) -> 54; +char2int($x) -> 55; +char2int($y) -> 56; +char2int($z) -> 57. +``` + +```erlang +4> b58:chars58(8763, []). +"3c6" +6> b58:char2int($3). +2 +7> b58:char2int($c). +35 +8> b58:char2int($6). +5 +43> binary:encode_unsigned(8763). +<<"\";">> +44> b58:enc(<<"\";">>). +"3c6" +45> b58:enc(<<0, 0, 0, "\";">>). +"1113c6" +46> b58:dec("3c6"). +<<"\";">> +47> b58:dec("13c6"). +<<0,34,59>> +48> b58:dec("11113c6"). +<<0,0,0,0,34,59>> +``` + + + +## Tables etc + +### Base64 Alphabet + +```erlang +int2char( 0) -> $A; +int2char( 1) -> $B; +int2char( 2) -> $C; +int2char( 3) -> $D; +int2char( 4) -> $E; +int2char( 5) -> $F; +int2char( 6) -> $G; +int2char( 7) -> $H; +int2char( 8) -> $I; +int2char( 9) -> $J; +int2char(10) -> $K; +int2char(11) -> $L; +int2char(12) -> $M; +int2char(13) -> $N; +int2char(14) -> $O; +int2char(15) -> $P; +int2char(16) -> $Q; +int2char(17) -> $R; +int2char(18) -> $S; +int2char(19) -> $T; +int2char(20) -> $U; +int2char(21) -> $V; +int2char(22) -> $W; +int2char(23) -> $X; +int2char(24) -> $Y; +int2char(25) -> $Z; +int2char(26) -> $a; +int2char(27) -> $b; +int2char(28) -> $c; +int2char(29) -> $d; +int2char(30) -> $e; +int2char(31) -> $f; +int2char(32) -> $g; +int2char(33) -> $h; +int2char(34) -> $i; +int2char(35) -> $j; +int2char(36) -> $k; +int2char(37) -> $l; +int2char(38) -> $m; +int2char(39) -> $n; +int2char(40) -> $o; +int2char(41) -> $p; +int2char(42) -> $q; +int2char(43) -> $r; +int2char(44) -> $s; +int2char(45) -> $t; +int2char(46) -> $u; +int2char(47) -> $v; +int2char(48) -> $w; +int2char(49) -> $x; +int2char(50) -> $y; +int2char(51) -> $z; +int2char(52) -> $0; +int2char(53) -> $1; +int2char(54) -> $2; +int2char(55) -> $3; +int2char(56) -> $4; +int2char(57) -> $5; +int2char(58) -> $6; +int2char(59) -> $7; +int2char(60) -> $8; +int2char(61) -> $9; +int2char(62) -> $+; +int2char(63) -> $/. + +char2int($A) -> 0; +char2int($B) -> 1; +char2int($C) -> 2; +char2int($D) -> 3; +char2int($E) -> 4; +char2int($F) -> 5; +char2int($G) -> 6; +char2int($H) -> 7; +char2int($I) -> 8; +char2int($J) -> 9; +char2int($K) -> 10; +char2int($L) -> 11; +char2int($M) -> 12; +char2int($N) -> 13; +char2int($O) -> 14; +char2int($P) -> 15; +char2int($Q) -> 16; +char2int($R) -> 17; +char2int($S) -> 18; +char2int($T) -> 19; +char2int($U) -> 20; +char2int($V) -> 21; +char2int($W) -> 22; +char2int($X) -> 23; +char2int($Y) -> 24; +char2int($Z) -> 25; +char2int($a) -> 26; +char2int($b) -> 27; +char2int($c) -> 28; +char2int($d) -> 29; +char2int($e) -> 30; +char2int($f) -> 31; +char2int($g) -> 32; +char2int($h) -> 33; +char2int($i) -> 34; +char2int($j) -> 35; +char2int($k) -> 36; +char2int($l) -> 37; +char2int($m) -> 38; +char2int($n) -> 39; +char2int($o) -> 40; +char2int($p) -> 41; +char2int($q) -> 42; +char2int($r) -> 43; +char2int($s) -> 44; +char2int($t) -> 45; +char2int($u) -> 46; +char2int($v) -> 47; +char2int($w) -> 48; +char2int($x) -> 49; +char2int($y) -> 50; +char2int($z) -> 51; +char2int($0) -> 52; +char2int($1) -> 53; +char2int($2) -> 54; +char2int($3) -> 55; +char2int($4) -> 56; +char2int($5) -> 57; +char2int($6) -> 58; +char2int($7) -> 59; +char2int($8) -> 60; +char2int($9) -> 61; +char2int($+) -> 62; +char2int($/) -> 63. +``` + +### Base58 Alphabet + +```erlang +int2char( 0) -> $1; +int2char( 1) -> $2; +int2char( 2) -> $3; +int2char( 3) -> $4; +int2char( 4) -> $5; +int2char( 5) -> $6; +int2char( 6) -> $7; +int2char( 7) -> $8; +int2char( 8) -> $9; +int2char( 9) -> $A; +int2char(10) -> $B; +int2char(11) -> $C; +int2char(12) -> $D; +int2char(13) -> $E; +int2char(14) -> $F; +int2char(15) -> $G; +int2char(16) -> $H; +int2char(17) -> $J; +int2char(18) -> $K; +int2char(19) -> $L; +int2char(20) -> $M; +int2char(21) -> $N; +int2char(22) -> $P; +int2char(23) -> $Q; +int2char(24) -> $R; +int2char(25) -> $S; +int2char(26) -> $T; +int2char(27) -> $U; +int2char(28) -> $V; +int2char(29) -> $W; +int2char(30) -> $X; +int2char(31) -> $Y; +int2char(32) -> $Z; +int2char(33) -> $a; +int2char(34) -> $b; +int2char(35) -> $c; +int2char(36) -> $d; +int2char(37) -> $e; +int2char(38) -> $f; +int2char(39) -> $g; +int2char(40) -> $h; +int2char(41) -> $i; +int2char(42) -> $j; +int2char(43) -> $k; +int2char(44) -> $m; +int2char(45) -> $n; +int2char(46) -> $o; +int2char(47) -> $p; +int2char(48) -> $q; +int2char(49) -> $r; +int2char(50) -> $s; +int2char(51) -> $t; +int2char(52) -> $u; +int2char(53) -> $v; +int2char(54) -> $w; +int2char(55) -> $x; +int2char(56) -> $y; +int2char(57) -> $z. + +char2int($1) -> 0; +char2int($2) -> 1; +char2int($3) -> 2; +char2int($4) -> 3; +char2int($5) -> 4; +char2int($6) -> 5; +char2int($7) -> 6; +char2int($8) -> 7; +char2int($9) -> 8; +char2int($A) -> 9; +char2int($B) -> 10; +char2int($C) -> 11; +char2int($D) -> 12; +char2int($E) -> 13; +char2int($F) -> 14; +char2int($G) -> 15; +char2int($H) -> 16; +char2int($J) -> 17; +char2int($K) -> 18; +char2int($L) -> 19; +char2int($M) -> 20; +char2int($N) -> 21; +char2int($P) -> 22; +char2int($Q) -> 23; +char2int($R) -> 24; +char2int($S) -> 25; +char2int($T) -> 26; +char2int($U) -> 27; +char2int($V) -> 28; +char2int($W) -> 29; +char2int($X) -> 30; +char2int($Y) -> 31; +char2int($Z) -> 32; +char2int($a) -> 33; +char2int($b) -> 34; +char2int($c) -> 35; +char2int($d) -> 36; +char2int($e) -> 37; +char2int($f) -> 38; +char2int($g) -> 39; +char2int($h) -> 40; +char2int($i) -> 41; +char2int($j) -> 42; +char2int($k) -> 43; +char2int($m) -> 44; +char2int($n) -> 45; +char2int($o) -> 46; +char2int($p) -> 47; +char2int($q) -> 48; +char2int($r) -> 49; +char2int($s) -> 50; +char2int($t) -> 51; +char2int($u) -> 52; +char2int($v) -> 53; +char2int($w) -> 54; +char2int($x) -> 55; +char2int($y) -> 56; +char2int($z) -> 57. +```