[wip] initial vw commit
This commit is contained in:
@@ -0,0 +1,15 @@
|
|||||||
|
.eunit
|
||||||
|
deps
|
||||||
|
*.o
|
||||||
|
*.beam
|
||||||
|
*.plt
|
||||||
|
*.swp
|
||||||
|
erl_crash.dump
|
||||||
|
ebin/*.beam
|
||||||
|
doc/*.html
|
||||||
|
doc/*.css
|
||||||
|
doc/edoc-info
|
||||||
|
doc/erlang.png
|
||||||
|
rel/example_project
|
||||||
|
.concrete/DEV_MODE
|
||||||
|
.rebar
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"src/*", [debug_info, {i, "include/"}, {outdir, "ebin/"}]}.
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
{application,vw,
|
||||||
|
[{description,"Simple command line ae wallet"},
|
||||||
|
{registered,[]},
|
||||||
|
{included_applications,[]},
|
||||||
|
{applications,[stdlib,kernel]},
|
||||||
|
{vsn,"0.1.0"},
|
||||||
|
{modules,[vw]}]}.
|
||||||
@@ -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),
|
||||||
|
<<RestBigNum:NBitsInRest>> = 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),
|
||||||
|
<<LeadingZeros/binary, RestBytes/binary>>.
|
||||||
|
|
||||||
|
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 = <<R, Acc/binary>>,
|
||||||
|
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.
|
||||||
@@ -0,0 +1,248 @@
|
|||||||
|
%% @doc
|
||||||
|
%% Vanillae data composer/decomposer
|
||||||
|
%%
|
||||||
|
%% This is similar to serialization/deserialization, but not the same thing
|
||||||
|
%%
|
||||||
|
%% This code exists to work out concepts and code structure for Vanillae TS. It
|
||||||
|
%% may eventually become productized. Please do not use this right now.
|
||||||
|
%%
|
||||||
|
%% References:
|
||||||
|
%%
|
||||||
|
%% 1. https://github.com/aeternity/protocol/blob/master/serializations.md
|
||||||
|
%% 2. https://github.com/aeternity/protocol/blob/master/node/api/api_encoding.md
|
||||||
|
-module(vd).
|
||||||
|
|
||||||
|
-compile([export_all, nowarn_export_all]).
|
||||||
|
|
||||||
|
%%% TYPES
|
||||||
|
|
||||||
|
-type obj() :: #{type := atom(),
|
||||||
|
vsn := integer(),
|
||||||
|
fields := map()}.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
-spec decompose(API_String) -> MaybeObject
|
||||||
|
when API_String :: string(),
|
||||||
|
MaybeObject :: {ok, obj()}
|
||||||
|
| {error, Reason :: term()}.
|
||||||
|
%% @doc
|
||||||
|
%% Decompose API-encoded data
|
||||||
|
|
||||||
|
decompose("tx_" ++ Base64) -> decompose_tx_b64(Base64);
|
||||||
|
decompose(X) -> {error, {nyi, {decompose, X}}}.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
%% decode the base64 and check the hash thing
|
||||||
|
decompose_tx_b64(B64_str) ->
|
||||||
|
B64_Bytes = list_to_binary(B64_str),
|
||||||
|
%% This has the double sha at the end
|
||||||
|
Stupid_Bytes = base64:decode(B64_Bytes),
|
||||||
|
Stupid_Size = byte_size(Stupid_Bytes),
|
||||||
|
%% pull apart data
|
||||||
|
<<RLP_encoded_data : (Stupid_Size - 4) /binary,
|
||||||
|
Check : 4 /binary>> = Stupid_Bytes,
|
||||||
|
ActualDoubleSha = shasha(RLP_encoded_data),
|
||||||
|
case Check =:= ActualDoubleSha of
|
||||||
|
false ->
|
||||||
|
{error, {checksum_mismatch, Check, ActualDoubleSha}};
|
||||||
|
true ->
|
||||||
|
decode_and_dispatch(RLP_encoded_data)
|
||||||
|
end.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
%% Double sha
|
||||||
|
shasha(Bytes) ->
|
||||||
|
<<Result:4/binary, _/binary>> = crypto:hash(sha256, crypto:hash(sha256, Bytes)),
|
||||||
|
Result.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
%% decode rlp data
|
||||||
|
decode_and_dispatch(RLP_encoded_bytes) ->
|
||||||
|
{DecodedData, Remainder} = vrlp:decode(RLP_encoded_bytes),
|
||||||
|
case Remainder of
|
||||||
|
<<>> -> decom_dispatch(DecodedData);
|
||||||
|
_ -> {error, trailing_data}
|
||||||
|
end.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
%% at this point we have the rlp data, and based on the first field, we are
|
||||||
|
%% going to decompose the data
|
||||||
|
decom_dispatch([Tag_Bytes, Vsn_Bytes | Fields]) ->
|
||||||
|
Tag = binary:decode_unsigned(Tag_Bytes),
|
||||||
|
Vsn = binary:decode_unsigned(Vsn_Bytes),
|
||||||
|
dd2(Tag, Vsn, Fields);
|
||||||
|
decom_dispatch(X) ->
|
||||||
|
{error, {invalid_data, X}}.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
%% See: https://github.com/aeternity/protocol/blob/master/serializations.md#table-of-object-tags
|
||||||
|
dd2(_Account = 10, Vsn = 1, Fields) -> maybe(fun decompose_fields_account1/1 , 'Account' , Vsn, Fields);
|
||||||
|
dd2(_Account = 10, Vsn = 2, Fields) -> maybe(fun decompose_fields_account2/1 , 'Account' , Vsn, Fields);
|
||||||
|
dd2(_SignedTx = 11, Vsn = 1, Fields) -> maybe(fun decompose_fields_signedtx/1 , 'SignedTx' , Vsn, Fields);
|
||||||
|
dd2(_SpendTx = 12, Vsn = 1, Fields) -> maybe(fun decompose_fields_spendtx/1 , 'SpendTx' , Vsn, Fields);
|
||||||
|
dd2(_ContractCallTx = 43, Vsn = 1, Fields) -> maybe(fun decompose_fields_contractcalltx/1, 'ContractCallTx', Vsn, Fields);
|
||||||
|
dd2(Tag , Vsn , Fields) -> {error, {nyi, {hd2, Tag, Vsn, Fields}}}.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
maybe(MaybeDecompose, Type, Vsn, Fields) ->
|
||||||
|
case MaybeDecompose(Fields) of
|
||||||
|
{ok, DecFields} ->
|
||||||
|
{ok, #{type => Type,
|
||||||
|
vsn => Vsn,
|
||||||
|
fields => DecFields}};
|
||||||
|
Error ->
|
||||||
|
Error
|
||||||
|
end.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
% 10 = account, version 1
|
||||||
|
% https://github.com/aeternity/protocol/blob/master/serializations.md#accounts-version-1-basic-accounts
|
||||||
|
decompose_fields_account1([NonceBytes, BalanceBytes]) ->
|
||||||
|
{ok, #{nonce => binary:decode_unsigned(NonceBytes),
|
||||||
|
balance => binary:decode_unsigned(BalanceBytes)}};
|
||||||
|
decompose_fields_account1(BadFields) ->
|
||||||
|
{error, {invalid_account_v1_fields, BadFields}}.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
% 10 = account, version 2
|
||||||
|
% https://github.com/aeternity/protocol/blob/master/serializations.md#accounts-version-2-generalized-accounts-from-fortuna-release
|
||||||
|
decompose_fields_account2([Flags, %% :: int()
|
||||||
|
Nonce, %% :: int()
|
||||||
|
Balance, %% :: int()
|
||||||
|
GaContract, %% :: id()
|
||||||
|
GaAuthFun]) -> %% :: binary()
|
||||||
|
{ok, #{flags => bdu(Flags),
|
||||||
|
nonce => bdu(Nonce),
|
||||||
|
balance => bdu(Balance),
|
||||||
|
ga_contract => encode_id(GaContract),
|
||||||
|
ga_auth_fun => GaAuthFun}};
|
||||||
|
decompose_fields_account2(BadFields) ->
|
||||||
|
{error, {invalid_account_v2_fields, BadFields}}.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
%% 11 = signedtx
|
||||||
|
decompose_fields_signedtx([Signatures, Transaction]) ->
|
||||||
|
Sigs = lists:map(fun encode_sg/1, Signatures),
|
||||||
|
TxStr = encode_tx(Transaction),
|
||||||
|
{ok, #{signatures => Sigs,
|
||||||
|
transaction => TxStr}};
|
||||||
|
decompose_fields_signedtx(Fields) ->
|
||||||
|
{error, {invalid_signedtx_fields, Fields}}.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
%% 12 = spendtx
|
||||||
|
%% See: https://github.com/aeternity/protocol/blob/master/serializations.md#spend-transaction
|
||||||
|
decompose_fields_spendtx([SenderBytes,
|
||||||
|
RecipBytes,
|
||||||
|
AmountBytes,
|
||||||
|
FeeBytes,
|
||||||
|
TTLBytes,
|
||||||
|
NonceBytes,
|
||||||
|
Payload]) ->
|
||||||
|
% TODO: drop-through to make sure id humanization works
|
||||||
|
SenderStr = encode_id(SenderBytes),
|
||||||
|
RecipStr = encode_id(RecipBytes),
|
||||||
|
Amount = binary:decode_unsigned(AmountBytes),
|
||||||
|
Fee = binary:decode_unsigned(FeeBytes),
|
||||||
|
TTL = binary:decode_unsigned(TTLBytes),
|
||||||
|
Nonce = binary:decode_unsigned(NonceBytes),
|
||||||
|
{ok, #{sender => SenderStr,
|
||||||
|
recipient => RecipStr,
|
||||||
|
amount => Amount,
|
||||||
|
fee => Fee,
|
||||||
|
ttl => TTL,
|
||||||
|
nonce => Nonce,
|
||||||
|
payload => Payload}};
|
||||||
|
decompose_fields_spendtx(Fields) ->
|
||||||
|
{error, {invalid_spendtx_fields, Fields}}.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
%% 43 = contract call tx
|
||||||
|
%% See: https://github.com/aeternity/protocol/blob/master/serializations.md#contract-call-transaction
|
||||||
|
decompose_fields_contractcalltx([Caller, % :: id()
|
||||||
|
Nonce, % :: int()
|
||||||
|
Contract, % :: id()
|
||||||
|
AbiVersion, % :: int()
|
||||||
|
Fee, % :: int()
|
||||||
|
Ttl, % :: int()
|
||||||
|
Amount, % :: int()
|
||||||
|
Gas, % :: int()
|
||||||
|
GasPrice, % :: int()
|
||||||
|
CallData]) -> % :: binary()
|
||||||
|
{ok, #{caller => encode_id(Caller),
|
||||||
|
nonce => binary:decode_unsigned(Nonce),
|
||||||
|
contract => encode_id(Contract),
|
||||||
|
abi_version => binary:decode_unsigned(AbiVersion),
|
||||||
|
fee => binary:decode_unsigned(Fee),
|
||||||
|
ttl => binary:decode_unsigned(Ttl),
|
||||||
|
amount => binary:decode_unsigned(Amount),
|
||||||
|
gas => binary:decode_unsigned(Gas),
|
||||||
|
gas_price => binary:decode_unsigned(GasPrice),
|
||||||
|
call_data => CallData}};
|
||||||
|
decompose_fields_contractcalltx(X) ->
|
||||||
|
{error, {invalid_contractcalltx_fields, X}}.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
%% general byte array
|
||||||
|
encode_ba(Bytes) -> "ba_" ++ sha64enc(Bytes).
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
%% contract byte array (see: https://github.com/aeternity/protocol/blob/master/node/api/api_encoding.md)
|
||||||
|
encode_cb(Bytes) -> "cb_" ++ sha64enc(Bytes).
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
%% See: https://github.com/aeternity/protocol/blob/master/serializations.md#the-id-type
|
||||||
|
%% https://github.com/aeternity/protocol/blob/master/node/api/api_encoding.md
|
||||||
|
%% ak_ account
|
||||||
|
encode_id(<<1, IdBytes:32/binary>>) -> "ak_" ++ sha58enc(IdBytes);
|
||||||
|
%% nm_ name
|
||||||
|
encode_id(<<2, IdBytes:32/binary>>) -> "nm_" ++ sha58enc(IdBytes);
|
||||||
|
%% cm_ commitment
|
||||||
|
encode_id(<<3, IdBytes:32/binary>>) -> "nm_" ++ sha58enc(IdBytes);
|
||||||
|
%% ok_ oracle
|
||||||
|
encode_id(<<4, IdBytes:32/binary>>) -> "ok_" ++ sha58enc(IdBytes);
|
||||||
|
%% ct_ contract
|
||||||
|
encode_id(<<5, IdBytes:32/binary>>) -> "ct_" ++ sha58enc(IdBytes);
|
||||||
|
%% ch_ channel
|
||||||
|
encode_id(<<6, IdBytes:32/binary>>) -> "ch_" ++ sha58enc(IdBytes).
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
encode_sg(Sig) -> "sg_" ++ sha58enc(Sig).
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
encode_tx(TxData) -> "tx_" ++ sha64enc(TxData).
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
sha58enc(Bytes) ->
|
||||||
|
Check = shasha(Bytes),
|
||||||
|
vb58:enc(<<Bytes/binary, Check/binary>>).
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
sha64enc(Bytes) ->
|
||||||
|
Check = shasha(Bytes),
|
||||||
|
binary_to_list(base64:encode(<<Bytes/binary, Check/binary>>)).
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
%% tired of typing this
|
||||||
|
bdu(X) -> binary:decode_unsigned(X).
|
||||||
@@ -0,0 +1,148 @@
|
|||||||
|
%% @doc
|
||||||
|
%% Vanillae RLP encoder/decoder
|
||||||
|
%%
|
||||||
|
%% Reference: https://ethereum.org/en/developers/docs/data-structures-and-encoding/rlp/
|
||||||
|
%%
|
||||||
|
%% Agrees with Ethereum's Python implementation in randomized tests
|
||||||
|
-module(vrlp).
|
||||||
|
|
||||||
|
-export_type([decoded_data/0]).
|
||||||
|
-export([encode/1, decode/1]).
|
||||||
|
|
||||||
|
-type decoded_data() :: binary() | [decoded_data()].
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
-spec encode(Data) -> RLP
|
||||||
|
when Data :: decoded_data(),
|
||||||
|
RLP :: binary().
|
||||||
|
%% @doc
|
||||||
|
%% encode some data
|
||||||
|
|
||||||
|
encode(Binary) when is_binary(Binary) ->
|
||||||
|
encode_binary(Binary);
|
||||||
|
encode(List) when is_list(List) ->
|
||||||
|
encode_list(List).
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
-spec encode_binary(Bytes) -> RLP
|
||||||
|
when Bytes :: binary(),
|
||||||
|
RLP :: binary().
|
||||||
|
%% @private
|
||||||
|
%% encode a binary in rlp
|
||||||
|
%% @end
|
||||||
|
|
||||||
|
% single byte case when the byte is between 0..127
|
||||||
|
% result is the byte itself
|
||||||
|
encode_binary(<<Byte>>) when Byte =< 127 ->
|
||||||
|
<<Byte>>;
|
||||||
|
% if the bytestring is 0..55 items long, the first byte is 128 + Length,
|
||||||
|
% the rest of the string is the string
|
||||||
|
encode_binary(Bytes) when byte_size(Bytes) =< 55 ->
|
||||||
|
Size = byte_size(Bytes),
|
||||||
|
<<(128 + Size), Bytes/binary>>;
|
||||||
|
% more than 55 bytes long, first byte is 183 + ByteLengthOfLength
|
||||||
|
% max byte size is 2^64 - 1
|
||||||
|
encode_binary(Bytes) when 55 < byte_size(Bytes), byte_size(Bytes) < (1 bsl 64) ->
|
||||||
|
SizeInt = byte_size(Bytes),
|
||||||
|
SizeBytes = binary:encode_unsigned(SizeInt, big),
|
||||||
|
SizeOfSizeInt = byte_size(SizeBytes),
|
||||||
|
%% 183 = 128 + 55
|
||||||
|
%% SizeOfSizeInt > 0
|
||||||
|
<<(183 + SizeOfSizeInt),
|
||||||
|
SizeBytes/binary,
|
||||||
|
Bytes/binary>>.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
-spec encode_list(List) -> RLP
|
||||||
|
when List :: [decoded_data()],
|
||||||
|
RLP :: binary().
|
||||||
|
%% @private
|
||||||
|
%% encode a list in rlp
|
||||||
|
%% @end
|
||||||
|
|
||||||
|
% first we encode the total payload of the list
|
||||||
|
% depending on how long it is, we then branch
|
||||||
|
encode_list(List) ->
|
||||||
|
Payload = << (encode(Item)) || Item <- List>>,
|
||||||
|
Payload_Size = byte_size(Payload),
|
||||||
|
if
|
||||||
|
Payload_Size =< 55 ->
|
||||||
|
<<(192 + Payload_Size), Payload/binary>>;
|
||||||
|
55 < Payload_Size ->
|
||||||
|
SizeBytes = binary:encode_unsigned(Payload_Size, big),
|
||||||
|
SizeOfSizeInt = byte_size(SizeBytes),
|
||||||
|
%% 247 = 192 + 55
|
||||||
|
%% SizeOfSizeInt > 0
|
||||||
|
<<(247 + SizeOfSizeInt),
|
||||||
|
SizeBytes/binary,
|
||||||
|
Payload/binary>>
|
||||||
|
end.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
-spec decode(RLP) -> {Data, Rest}
|
||||||
|
when RLP :: binary(),
|
||||||
|
Data :: decoded_data(),
|
||||||
|
Rest :: binary().
|
||||||
|
%% @doc
|
||||||
|
%% decode an RLP-encoded string
|
||||||
|
%% @end
|
||||||
|
|
||||||
|
% if the first byte is between 0 and 127, that is the data
|
||||||
|
decode(<<Byte, Rest/binary>>) when Byte =< 127 ->
|
||||||
|
{<<Byte>>, Rest};
|
||||||
|
% if the first byte is between 128 and 183 = 128 + 55, it is a bytestring and
|
||||||
|
% the length is Byte - 128
|
||||||
|
decode(<<Byte, Rest/binary>>) when Byte =< 183 ->
|
||||||
|
PayloadByteLength = Byte - 128,
|
||||||
|
%PayloadBitLength = 8 * PayloadByteLength,
|
||||||
|
%io:format("Byte : ~p~n"
|
||||||
|
% "Rest : ~w~n"
|
||||||
|
% "PayloadByteLength : ~p~n",
|
||||||
|
% %"PayloadBitLength : ~p~n",
|
||||||
|
% [Byte, Rest, PayloadByteLength]),
|
||||||
|
<<Payload:PayloadByteLength/binary,
|
||||||
|
Rest2/binary>> = Rest,
|
||||||
|
{Payload, Rest2};
|
||||||
|
% If the first byte is between 184 = 183 + 1 and 191 = 183 + 8, it is a
|
||||||
|
% bytestring. The byte length of the byte length of bytestring is FirstByte -
|
||||||
|
% 183. Then pull out the actual data
|
||||||
|
decode(<<Byte, Rest/binary>>) when Byte =< 191 ->
|
||||||
|
ByteLengthOfByteLength = Byte - 183,
|
||||||
|
BitLengthOfByteLength = 8 * ByteLengthOfByteLength,
|
||||||
|
<<ByteLengthInt:BitLengthOfByteLength,
|
||||||
|
Rest2/binary>> = Rest,
|
||||||
|
<<Payload:ByteLengthInt/binary,
|
||||||
|
Rest3/binary>> = Rest2,
|
||||||
|
{Payload, Rest3};
|
||||||
|
% If the first byte is between 192 and 247 = 192 + 55, it is a list. The byte
|
||||||
|
% length of the list-payload is FirstByte - 192. Then the list payload, which
|
||||||
|
% needs to be decoded on its own.
|
||||||
|
decode(<<Byte, Rest/binary>>) when Byte =< 247 ->
|
||||||
|
ByteLengthOfListPayload = Byte - 192,
|
||||||
|
<<ListPayload:ByteLengthOfListPayload/binary,
|
||||||
|
Rest2/binary>> = Rest,
|
||||||
|
List = decode_list(ListPayload),
|
||||||
|
{List, Rest2};
|
||||||
|
% If the first byte is between 248 = 247 + 1 and 255 = 247 + 8, it is a list.
|
||||||
|
% The byte length of the byte length of the list-payload is FirstByte - 247.
|
||||||
|
% Then the byte length of the list. Then the list payload, which needs to be
|
||||||
|
% decoded on its own.
|
||||||
|
decode(<<Byte, Rest/binary>>) ->
|
||||||
|
ByteLengthOfByteLengthOfListPayload_int = Byte - 247,
|
||||||
|
BitLengthOfByteLengthOfListPayload_int = 8 * ByteLengthOfByteLengthOfListPayload_int,
|
||||||
|
<<ByteLengthOfListPayload_int:BitLengthOfByteLengthOfListPayload_int,
|
||||||
|
Rest2/binary>> = Rest,
|
||||||
|
<<ListPayload_bytes:ByteLengthOfListPayload_int/binary,
|
||||||
|
Rest3/binary>> = Rest2,
|
||||||
|
List = decode_list(ListPayload_bytes),
|
||||||
|
{List, Rest3}.
|
||||||
|
|
||||||
|
decode_list(<<>>) ->
|
||||||
|
[];
|
||||||
|
decode_list(Bytes) ->
|
||||||
|
{Item, Rest} = decode(Bytes),
|
||||||
|
[Item | decode_list(Rest)].
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
-module(vw).
|
||||||
|
-vsn("0.1.0").
|
||||||
|
-author("Peter Harpending <ceverett@tsuriai.jp>").
|
||||||
|
-copyright("Peter Harpending <ceverett@tsuriai.jp>").
|
||||||
|
|
||||||
|
-export([start/1]).
|
||||||
|
-compile([export_all, nowarn_export_all]).
|
||||||
|
|
||||||
|
-spec start(ArgV) -> ok
|
||||||
|
when ArgV :: [string()].
|
||||||
|
|
||||||
|
start(ArgV) ->
|
||||||
|
go(ArgV),
|
||||||
|
zx:silent_stop().
|
||||||
|
|
||||||
|
%% Taking break
|
||||||
|
%%
|
||||||
|
%% WHEN back: get decompose to work
|
||||||
|
|
||||||
|
go(["help"]) ->
|
||||||
|
help();
|
||||||
|
go(["--help"]) ->
|
||||||
|
help();
|
||||||
|
go(["generate", "keypair"]) ->
|
||||||
|
error(nyi);
|
||||||
|
go(["decompose", TxStr]) ->
|
||||||
|
decompose(TxStr);
|
||||||
|
go(_) ->
|
||||||
|
error(invalid_subcommand).
|
||||||
|
|
||||||
|
help() ->
|
||||||
|
io:format("you can't help people who won't help themselves~n", []).
|
||||||
|
|
||||||
|
decompose(TxStr) ->
|
||||||
|
case vd:decompose(TxStr) of
|
||||||
|
{ok, X} ->
|
||||||
|
io:format("~tp~n", [X]);
|
||||||
|
{error, Error} ->
|
||||||
|
io:format("ERROR: ~tp~n", [Error])
|
||||||
|
end.
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
{a_email,"ceverett@tsuriai.jp"}.
|
||||||
|
{author,"Peter Harpending"}.
|
||||||
|
{c_email,"ceverett@tsuriai.jp"}.
|
||||||
|
{copyright,"Peter Harpending"}.
|
||||||
|
{deps,[]}.
|
||||||
|
{desc,"Simple command line ae wallet"}.
|
||||||
|
{file_exts,[]}.
|
||||||
|
{key_name,none}.
|
||||||
|
{license,skip}.
|
||||||
|
{mod,"vw"}.
|
||||||
|
{modules,[]}.
|
||||||
|
{name,"Vanillae Wallet"}.
|
||||||
|
{package_id,{"otpr","vw",{0,1,0}}}.
|
||||||
|
{prefix,none}.
|
||||||
|
{repo_url,"https://github.com/aeternity/Vanillae"}.
|
||||||
|
{tags,[]}.
|
||||||
|
{type,cli}.
|
||||||
|
{ws_url,"https://github.com/aeternity/Vanillae"}.
|
||||||
Reference in New Issue
Block a user