diff --git a/.gitignore b/.gitignore index ed38920..f531f36 100644 --- a/.gitignore +++ b/.gitignore @@ -10,4 +10,5 @@ rel/example_project .rebar aeb_asm_scan.erl aefa_asm_scan.erl -_build \ No newline at end of file +_build +aefateasm \ No newline at end of file diff --git a/rebar.config b/rebar.config index 557a749..aae208e 100644 --- a/rebar.config +++ b/rebar.config @@ -2,19 +2,34 @@ {erl_opts, [debug_info]}. -{deps, [ - {enacl, {git, "https://github.com/aeternity/enacl.git", - {ref, "26180f4"}}} +{deps, [ {getopt, "1.0.1"} ]}. + +{escript_incl_apps, [aebytecode, getopt]}. +{escript_main_app, aebytecode}. +{escript_name, aefateasm}. +{escript_emu_args, "%%! +sbtu +A0\n"}. +{provider_hooks, [{post, [{compile, escriptize}]}]}. + +{post_hooks, [{"(linux|darwin|solaris|freebsd|netbsd|openbsd)", + escriptize, + "cp \"$REBAR_BUILD_DIR/bin/aefateasm\" ./aefateasm"}, + {"win32", + escriptize, + "robocopy \"%REBAR_BUILD_DIR%/bin/\" ./ aefateasm* " + "/njs /njh /nfl /ndl & exit /b 0"} % silence things + ]}. + {dialyzer, [ {warnings, [unknown]}, {plt_apps, all_deps}, - {base_plt_apps, [erts, kernel, stdlib]} + {base_plt_apps, [erts, kernel, stdlib, crypto]} ]}. + {relx, [{release, {aessembler, "0.0.1"}, - [aebytecode, enacl]}, + [aebytecode, getopt]}, {dev_mode, true}, {include_erts, false}, diff --git a/rebar.lock b/rebar.lock index 6880fc5..3c625aa 100644 --- a/rebar.lock +++ b/rebar.lock @@ -1,4 +1,10 @@ +{"1.1.0", [{<<"enacl">>, {git,"https://github.com/aeternity/enacl.git", {ref,"26180f42c0b3a450905d2efd8bc7fd5fd9cece75"}}, - 0}]. + 0}, + {<<"getopt">>,{pkg,<<"getopt">>,<<"1.0.1">>},0}]}. +[ +{pkg_hash,[ + {<<"getopt">>, <<"C73A9FA687B217F2FF79F68A3B637711BB1936E712B521D8CE466B29CBF7808A">>}]} +]. diff --git a/src/aeblake2.erl b/src/aeblake2.erl new file mode 100644 index 0000000..0519440 --- /dev/null +++ b/src/aeblake2.erl @@ -0,0 +1,148 @@ +%%%============================================================================= +%%% @copyright (C) 2019, Aeternity Anstalt +%%% @doc +%%% BLAKE2b implementation in Erlang - for details see: https://blake2.net +%%% @end +%%%============================================================================= + +-module(aeblake2). + +-export([ blake2b/2 + , blake2b/3 + ]). + +-define(MAX_64BIT, 16#ffffffffffffffff). + +-spec blake2b(HashLen :: integer(), Msg :: binary()) -> {ok, binary()}. +blake2b(HashLen, Msg) -> + blake2b(HashLen, Msg, <<>>). + +-spec blake2b(HashLen :: integer(), Msg :: binary(), Key :: binary()) -> {ok, binary()}. +blake2b(HashLen, Msg0, Key) -> + %% If message should be keyed, prepend message with padded key. + Msg = <<(pad(128, Key))/binary, Msg0/binary>>, + + %% Set up the initial state + Init = (16#01010000 + (byte_size(Key) bsl 8) + HashLen), + <> = blake_iv(), + H = <<(H0 bxor Init):64, H1_7/binary>>, + + %% Perform the compression - message will be chopped into 128-byte chunks. + State = blake2b_compress(H, Msg, 0), + + %% Just return the requested part of the hash + {ok, binary_part(to_little_endian(State), {0, HashLen})}. + +blake2b_compress(H, <>, BCompr) when Rest /= <<>> -> + H1 = blake2b_compress(H, <>, BCompr + 128, false), + blake2b_compress(H1, Rest, BCompr + 128); +blake2b_compress(H, SmallChunk, BCompr) -> + Size = byte_size(SmallChunk), + FillSize = (128 - Size) * 8, + blake2b_compress(H, <>, BCompr + Size, true). + +blake2b_compress(H, Chunk0, BCompr, Last) -> + Chunk = to_big_endian(Chunk0), + <> = <>, + V12_ = V12 bxor (BCompr band ?MAX_64BIT), + V13_ = V13 bxor ((BCompr bsr 64) band ?MAX_64BIT), + V14_ = case Last of + false -> V14; + true -> V14 bxor ?MAX_64BIT + end, + V = <>, + + <> = + lists:foldl(fun(Round, Vx) -> blake2b_mix(Round, Chunk, Vx) end, V, lists:seq(0, 11)), + + <> = H, + <<((HInt bxor VLow) bxor VHigh):(8*64)>>. + +blake2b_mix(Rnd, Chunk, V) -> + <> = V, + <> = Chunk, + Ms = {M0, M1, M2, M3, M4, M5, M6, M7, M8, M9, M10, M11, M12, M13, M14, M15}, + M = fun(Ix) -> element(Ix+1, Ms) end, + + [S0, S1, S2, S3, S4, S5, S6, S7, S8, S9, S10, S11, S12, S13, S14, S15] = sigma(Rnd rem 10), + + {Vx0, Vx4, Vx8, Vx12} = blake2b_mix(V0, V4, V8, V12, M(S0), M(S1)), + {Vx1, Vx5, Vx9, Vx13} = blake2b_mix(V1, V5, V9, V13, M(S2), M(S3)), + {Vx2, Vx6, Vx10, Vx14} = blake2b_mix(V2, V6, V10, V14, M(S4), M(S5)), + {Vx3, Vx7, Vx11, Vx15} = blake2b_mix(V3, V7, V11, V15, M(S6), M(S7)), + + {Vy0, Vy5, Vy10, Vy15} = blake2b_mix(Vx0, Vx5, Vx10, Vx15, M(S8), M(S9)), + {Vy1, Vy6, Vy11, Vy12} = blake2b_mix(Vx1, Vx6, Vx11, Vx12, M(S10), M(S11)), + {Vy2, Vy7, Vy8, Vy13} = blake2b_mix(Vx2, Vx7, Vx8, Vx13, M(S12), M(S13)), + {Vy3, Vy4, Vy9, Vy14} = blake2b_mix(Vx3, Vx4, Vx9, Vx14, M(S14), M(S15)), + + <>. + +blake2b_mix(Va, Vb, Vc, Vd, X, Y) -> + Va1 = (Va + Vb + X) band ?MAX_64BIT, + Vd1 = rotr64(32, Vd bxor Va1), + + Vc1 = (Vc + Vd1) band ?MAX_64BIT, + Vb1 = rotr64(24, Vb bxor Vc1), + + Va2 = (Va1 + Vb1 + Y) band ?MAX_64BIT, + Vd2 = rotr64(16, Va2 bxor Vd1), + + Vc2 = (Vc1 + Vd2) band ?MAX_64BIT, + Vb2 = rotr64(63, Vb1 bxor Vc2), + + {Va2, Vb2, Vc2, Vd2}. + +blake_iv() -> + IV0 = 16#6A09E667F3BCC908, + IV1 = 16#BB67AE8584CAA73B, + IV2 = 16#3C6EF372FE94F82B, + IV3 = 16#A54FF53A5F1D36F1, + IV4 = 16#510E527FADE682D1, + IV5 = 16#9B05688C2B3E6C1F, + IV6 = 16#1F83D9ABFB41BD6B, + IV7 = 16#5BE0CD19137E2179, + <>. + +sigma(N) -> + {_, Row} = lists:keyfind(N, 1, sigma()), Row. + +sigma() -> + [{0, [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]}, + {1, [14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3]}, + {2, [11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4]}, + {3, [ 7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8]}, + {4, [ 9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13]}, + {5, [ 2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9]}, + {6, [12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11]}, + {7, [13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10]}, + {8, [ 6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5]}, + {9, [10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13, 0]}]. + +rotr64(N, I64) -> + <> = rotr641(N, <>), + I64rot. + +rotr641(16, <>) -> <>; +rotr641(24, <>) -> <>; +rotr641(32, <>) -> <>; +rotr641(63, <>) -> <>. + +pad(N, Bin) -> + case (N - (byte_size(Bin) rem N)) rem N of + 0 -> Bin; + Pad -> <> + end. + +to_big_endian(Bin) -> to_big_endian(Bin, <<>>). +to_big_endian(<<>>, Acc) -> Acc; +to_big_endian(<>, Acc) -> + to_big_endian(Rest, <>). + +to_little_endian(Bin) -> to_little_endian(Bin, <<>>). +to_little_endian(<<>>, Acc) -> Acc; +to_little_endian(<>, Acc) -> + to_little_endian(Rest, <>). diff --git a/src/aefa_asm.erl b/src/aefa_asm.erl index 0a44267..0b9d420 100644 --- a/src/aefa_asm.erl +++ b/src/aefa_asm.erl @@ -43,7 +43,8 @@ -module(aefa_asm). --export([ asm_to_bytecode/2 +-export([ assemble_file/3 + , asm_to_bytecode/2 , bytecode_to_fate_code/2 , pp/1 , read_file/1 @@ -53,6 +54,10 @@ -include_lib("aebytecode/include/aefa_opcodes.hrl"). -define(HASH_BYTES, 32). +assemble_file(InFile, OutFile, Options) -> + Asm = read_file(InFile), + {Env, BC} = aefa_asm:asm_to_bytecode(Asm, Options), + ok = file:write_file(OutFile, BC). pp(Asm) -> Listing = format(Asm), @@ -462,7 +467,7 @@ insert_fun({Name, Type, RetType}, Code, #{functions := Functions} = Env) -> insert_symbol(Id, Env) -> %% Use first 4 bytes of blake hash - {ok, <> } = enacl:generichash(?HASH_BYTES, list_to_binary(Id)), + {ok, <> } = aeblake2:blake2b(?HASH_BYTES, list_to_binary(Id)), insert_symbol(Id, <>, Env). insert_symbol(Id, Hash, #{symbols := Symbols} = Env) -> diff --git a/src/aefateasm.erl b/src/aefateasm.erl new file mode 100644 index 0000000..2958808 --- /dev/null +++ b/src/aefateasm.erl @@ -0,0 +1,58 @@ +-module(aefateasm). + +-export([main/1]). + +-define(OPT_SPEC, + [ {src_file, undefined, undefined, string, "Fate assembler code file"} + , {verbose, $v, "verbose", undefined, "Verbose output"} + , {help, $h, "help", undefined, "Show this message"} + , {outfile, $o, "out", string, "Output file (experimental)"} ]). + +usage() -> + getopt:usage(?OPT_SPEC, "aefateasm"). + +main(Args) -> + case getopt:parse(?OPT_SPEC, Args) of + {ok, {Opts, []}} -> + case proplists:get_value(help, Opts, false) of + false -> + assemble(Opts); + true -> + usage() + end; + + {ok, {_, NonOpts}} -> + io:format("Can't understand ~p\n\n", [NonOpts]), + usage(); + + {error, {Reason, Data}} -> + io:format("Error: ~s ~p\n\n", [Reason, Data]), + usage() + end. + +assemble(Opts) -> + case proplists:get_value(src_file, Opts, undefined) of + undefined -> + io:format("Error: no input source file\n\n"), + usage(); + File -> + assemble(File, Opts) + end. + +assemble(File, Opts) -> + Verbose = proplists:get_value(verbose, Opts, false), + case proplists:get_value(outfile, Opts, undefined) of + undefined -> + Asm = aefa_asm:read_file(File), + {Env, BC} = aefa_asm:asm_to_bytecode(Asm, Opts), + case Verbose of + true -> + io:format("Env: ~0p~n", [Env]); + false -> ok + end, + io:format("Code: ~0p~n", [BC]); + OutFile -> + aefa_asm:assemble_file(File, OutFile, Opts) + end. + +