diff --git a/snippets/vb58.erl b/snippets/vb58.erl new file mode 100644 index 0000000..37a99d8 --- /dev/null +++ b/snippets/vb58.erl @@ -0,0 +1,249 @@ +%% @doc +%% Vanillae Base58 Encoding/Decoding module +%% +%% References +%% +%% 1. https://digitalbazaar.github.io/base58-spec/#encode +%% 2. https://www.youtube.com/watch?v=GedV3S9X89c +%% @end +-module(vb58). + +-export([enc/1, dec/1]). + +%% TODO: move these cases to test or something +%% this originated from the +%%-mode(compile). +%%-spec enc(binary()) -> string(). +%%% https://digitalbazaar.github.io/base58-spec/#encode +% +%main([]) -> +% {ok, Cases} = file:consult("b58_cases_3.eterms"), +% test_cases(Cases). +% +%test_cases([{{encoded, E}, {decoded, D}} | Rest]) -> +% EncodeOk = E =:= enc(D), +% DecodeOk = D =:= dec(E), +% ok = +% case EncodeOk of +% true -> ok; +% false -> io:format("===============================~n" +% "YOU ARE A FAILURE TO ENCODE~n" +% "===============================~n" +% "decoded : ~tw~n" +% "expected : ~ts~n" +% "actual : ~ts~n~n", +% [D, E, enc(D)]) +% end, +% ok = +% case DecodeOk of +% true -> ok; +% false -> io:format("===============================~n" +% "YOU ARE A FAILURE TO DECODE~n" +% "===============================~n" +% "encoded : ~ts~n" +% "expected : ~tw~n" +% "actual : ~tw~n~n", +% [E, D, dec(E)]) +% end, +% test_cases(Rest); +%test_cases([]) -> +% ok. + +% this was much clearer: https://www.youtube.com/watch?v=GedV3S9X89c + +-spec enc(Bytes) -> Base58 + when Bytes :: binary(), + Base58 :: string(). +%% @doc +%% Encode a bytestring into base58 notation + +enc(Bytes) -> + % grab leading 0s + {NumLeadingZeros, Rest} = split_zeros(Bytes, 0), + NBitsInRest = bit_size(Rest), + <> = Rest, + ZerosBase58 = [$1 || _ <- lists:seq(1, NumLeadingZeros)], + RestBase58 = enc(RestBigNum, []), + ZerosBase58 ++ RestBase58. + + + +-spec split_zeros(Bytes, InitZeros) -> {NumLeadingZeros, Rest} + when Bytes :: binary(), + InitZeros :: integer(), + NumLeadingZeros :: binary(), + Rest :: binary(). + +split_zeros(<<0:8, Rest/binary>>, NumZerosAcc) -> + NewNumZerosAcc = NumZerosAcc + 1, + split_zeros(Rest, NewNumZerosAcc); +split_zeros(Rest, NumZerosAcc) -> + {NumZerosAcc, Rest}. + + + +-spec enc(BytesBigNum, Base58Acc) -> Base58 + when BytesBigNum :: integer(), + Base58Acc :: [0..57], + Base58 :: string(). + +enc(0, Acc) -> + lists:map(fun int2char/1, Acc); +enc(BitNum, Acc) -> + Q = BitNum div 58, + R = BitNum rem 58, + enc(Q, [R | Acc]). + + + +-spec dec(Base58) -> DecodedBytes + when Base58 :: string(), + DecodedBytes :: binary(). +%% @doc +%% Decode a Base58-encoded string into a bytestring + +dec(Str) -> + % the number of leading 1s tells us the number of leading zeros + {NumLeadingZeros, RestStr} = split_ones(Str, 0), + LeadingZeros = << <<0>> || _ <- lists:seq(1, NumLeadingZeros) >>, + RestNs = lists:map(fun char2int/1, RestStr), + RestBytes = dec(RestNs, 0), + <>. + +split_ones([$1 | Rest], NOnes) -> + split_ones(Rest, NOnes + 1); +split_ones(B58Str, NOnes) -> + {NOnes, B58Str}. + + +dec([N | Ns], Acc) -> + NewAcc = (Acc*58) + N, + dec(Ns, NewAcc); +dec([], FinalAccN) -> + bignum_to_binary_bige(FinalAccN, <<>>). + +bignum_to_binary_bige(0, Acc) -> + Acc; +bignum_to_binary_bige(N, Acc) -> + Q = N div 256, + R = N rem 256, + NewAcc = <>, + bignum_to_binary_bige(Q, NewAcc). + +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. diff --git a/snippets/vb64.erl b/snippets/vb64.erl new file mode 100644 index 0000000..09fa6e0 --- /dev/null +++ b/snippets/vb64.erl @@ -0,0 +1,298 @@ +-module(vb64). + +-export([enc/1, dec/1]). + +-export([test/0]). + +test() -> + test(100). + +test(N) when N >= 0 -> + RandBytes = rand:bytes(N), + % encode the bytes + CorrectEnc = erlang:binary_to_list(base64:encode(RandBytes)), + MyEnc = enc(RandBytes), + EncodeOk = CorrectEnc =:= MyEnc, + % decode the bytes + MyDec = dec(MyEnc), + DecodeOk = MyDec =:= RandBytes, + % print results if error + ok = + case EncodeOk of + true -> + ok; + false -> + ok = io:format("===~n" + "Encode failure!~n" + "input : ~tw~n" + "expected output : ~tw~n" + "actual output : ~tw~n" + "===~n~n", + [RandBytes, CorrectEnc, MyEnc]), + ok + end, + ok = + case DecodeOk of + true -> + ok; + false -> + ok = io:format("===~n" + "Decode failure!~n" + "input : ~tw~n" + "expected output : ~tw~n" + "actual output : ~tw~n" + "===~n~n", + [MyEnc, RandBytes, MyDec]), + ok + end, + % recurse + test(N - 1); +test(_) -> + ok. + + + +-spec enc(Binary) -> Base64 + when Binary :: binary(), + Base64 :: string(). +%% @doc +%% "Encode" (from the perspective of the program) binary data into base64 +%% @end + + +% 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(<<>>) -> + []. + + + +-spec dec(Base64) -> Binary + when Base64 :: string(), + Binary :: binary(). +%% @doc +%% "Decode" (from the perspective of the program) a Base64 string into binary data + +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). + + + +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.