Compare commits

..

15 Commits

Author SHA1 Message Date
Ulf Wiger c80505c810 Fix ac_acct_state tag and restore key_block mapping
Gajumaru Serialization Tests / tests (push) Successful in 11s
The previous commit accidentally removed key_block (tag 100) instead of
adding ac_acct_state (tag 99).
2026-07-07 08:13:37 +02:00
Ulf Wiger 0e157f824a Add ac_acct_state serialization tag
Gajumaru Serialization Tests / tests (push) Successful in 10s
2026-07-07 08:07:58 +02:00
Ulf Wiger faa20bd00b Add ac_receipt and AC side tx tags from ac793dcaf6
Gajumaru Serialization Tests / tests (push) Successful in 12s
Cherry-pick the chain-object tags from ac793dcaf6 while keeping the
extended-auth account types (account_sig_store, auth_tx, etc.).
2026-07-06 22:27:28 +02:00
Ulf Wiger 79b43b0665 Add account_auth_update_tx tag
Gajumaru Serialization Tests / tests (push) Successful in 11s
2026-07-06 15:30:19 +02:00
Ulf Wiger a84dcc880d Add is_account/1 and account_pubkey/1 helpers
Gajumaru Serialization Tests / tests (push) Successful in 11s
Extended-auth account ids use an {account, Subtype} tag. These helpers
let callers test account membership and extract the 32-byte pubkey
without pattern-matching on specialize/1 results.
2026-07-06 12:26:38 +02:00
Ulf Wiger a368c64f7e Add missing tag for proposal_gossip_tx
Gajumaru Serialization Tests / tests (push) Successful in 11s
2026-07-06 10:55:20 +02:00
Ulf Wiger dbfc013c8a Add auth tx and account sig store
Gajumaru Serialization Tests / tests (push) Successful in 11s
2026-07-06 10:19:38 +02:00
Ulf Wiger 7a8e840793 Add support for extended-auth account types
Gajumaru Serialization Tests / tests (push) Successful in 11s
2026-07-05 08:38:42 +02:00
uwiger eecff86500 Merge pull request 'Add zompify support, verup to 0.2.0' (#59) from uw-zomp-vsn-0.2.0 into master
Gajumaru Serialization Tests / tests (push) Successful in -2m7s
Reviewed-on: #59
2026-04-17 14:25:34 +09:00
Ulf Wiger 2ac9363d30 Add zompify support, verup to 0.2.0
Gajumaru Serialization Tests / tests (push) Successful in -2m7s
2026-04-16 23:31:12 +02:00
uwiger 4698b54832 Merge pull request 'Add keypair encoding, fix seckey size checks' (#57) from uw-account_seckey into master
Gajumaru Serialization Tests / tests (push) Successful in -2m43s
Reviewed-on: #57
2026-03-30 05:26:09 +09:00
Ulf Wiger 4cc6adee2e Add unsafe_encode/2 to forego size check
Gajumaru Serialization Tests / tests (push) Successful in -2m49s
2026-03-25 22:10:06 +01:00
Ulf Wiger 4f97dd1bd1 Add keypair encoding, fix seckey size checks
Gajumaru Serialization Tests / tests (push) Successful in -2m51s
2026-03-24 23:06:29 +01:00
uwiger 05bbf058be Map 'hash' as known api type, add some more tests (#56)
Gajumaru Serialization Tests / tests (push) Successful in -2m53s
I noticed that the 'hash' serialization type wasn't in the `known_type()` set, although it was supported otherwise.
This PR adds it to the `known_type()` and also exports the type `gmser_api_encoder:known_type()`.

Also, some more tests are added to try to detect this sort of thing in the future.

Co-authored-by: Ulf Wiger <ulf@wiger.net>
Reviewed-on: #56
2026-03-24 21:23:20 +09:00
uwiger 2b24b17af3 Merge pull request 'Fix OTP 28 dialyzer warnings, rewrite gmser_dyn decoder' (#55) from uw-gmser_dyn-rewrite into master
Gajumaru Serialization Tests / tests (push) Successful in -2m54s
Reviewed-on: #55
2026-03-24 21:20:30 +09:00
16 changed files with 396 additions and 40 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
{application,gmserialization, {application,gmserialization,
[{description,"Serialization of data for the Gajumaru"}, [{description,"Serialization of data for the Gajumaru"},
{vsn,"0.1.2"}, {vsn,"0.2.0"},
{registered,[]}, {registered,[]},
{applications,[kernel,stdlib,crypto,base58]}, {applications,[kernel,stdlib,crypto,base58]},
{env,[]}, {env,[]},
+93 -6
View File
@@ -6,14 +6,20 @@
%%% @end %%% @end
%%%------------------------------------------------------------------- %%%-------------------------------------------------------------------
-module(gmser_api_encoder). -module(gmser_api_encoder).
-vsn("0.1.2"). -vsn("0.2.0").
-export([encode/2, -export([encode/2,
decode/1, decode/1,
safe_decode/2, safe_decode/2,
byte_size_for_type/1]). byte_size_for_type/1]).
-export_type([encoded/0]). -export([encode_keypair/1,
safe_decode_keypair/1]).
-export([unsafe_encode/2]). %% Encode without size checks
-export_type([encoded/0,
known_type/0]).
-type known_type() :: key_block_hash -type known_type() :: key_block_hash
| micro_block_hash | micro_block_hash
@@ -30,6 +36,7 @@
| transaction | transaction
| tx_hash | tx_hash
| account_pubkey | account_pubkey
| {account_pubkey, 0..6}
| account_seckey | account_seckey
| associate_chain | associate_chain
| entry | entry
@@ -38,6 +45,7 @@
| native_token | native_token
| commitment | commitment
| peer_pubkey | peer_pubkey
| hash
| state | state
| poi | poi
| state_trees | state_trees
@@ -51,14 +59,70 @@
-type payload() :: binary(). -type payload() :: binary().
-type encoded() :: binary(). -type encoded() :: binary().
-type keypair() :: #{public := <<_:(32*8)>>, secret := <<_:(64*8)>>}.
-type encoded_keypair() :: #{binary() => binary()}.
-export_type([ keypair/0
, encoded_keypair/0 ]).
-define(BASE58, 1). -define(BASE58, 1).
-define(BASE64, 2). -define(BASE64, 2).
-spec encode_keypair(keypair()) -> encoded_keypair().
encode_keypair(#{public := Pub, secret := Sec}) ->
case Sec of
<<Seed:32/binary, Pub1:32/binary>> when Pub1 =:= Pub ->
#{ <<"pub">> => encode(account_pubkey, Pub)
, <<"priv">> => encode(account_seckey, Seed) };
_ ->
erlang:error(invalid_keypair)
end.
-spec safe_decode_keypair(encoded_keypair()) -> {'ok', keypair()} | {'error', atom()}.
safe_decode_keypair(#{<<"pub">> := EncPub, <<"priv">> := EncPriv}) ->
case safe_decode(account_pubkey, EncPub) of
{ok, Pub} ->
case safe_decode(account_seckey, EncPriv) of
{ok, Seed} when byte_size(Seed) =:= 32 ->
case enacl:sign_seed_keypair(Seed) of
#{public := Pub, secret := _} = KP ->
{ok, KP};
_ ->
{error, illegal_encoding}
end;
{ok, <<Seed:32/binary, Pub:32/binary>>} ->
case enacl:sign_seed_keypair(Seed) of
#{public := Pub} = KP ->
{ok, KP};
_ ->
{error, illegal_encoding}
end;
{ok, _} ->
{error, illegal_encoding};
{error, _} = Error1 ->
Error1
end;
Error ->
Error
end.
-spec encode(known_type(), payload() | gmser_id:id()) -> encoded(). -spec encode(known_type(), payload() | gmser_id:id()) -> encoded().
encode(id_hash, Payload) -> encode(id_hash, Payload) ->
{IdType, Val} = gmser_id:specialize(Payload), case gmser_id:to_map(Payload) of
encode(id2type(IdType), Val); #{type := account, subtype := SubT, value := Val} ->
encode({account_pubkey, SubT}, Val);
#{type := IdType, value := Val} ->
encode(id2type(IdType), Val)
end;
encode(Type, Payload) -> encode(Type, Payload) ->
case type_size_check(Type, Payload) of
ok ->
unsafe_encode(Type, Payload);
{error, Reason} ->
erlang:error(Reason)
end.
unsafe_encode(Type, Payload) ->
Pfx = type2pfx(Type), Pfx = type2pfx(Type),
Enc = case type2enc(Type) of Enc = case type2enc(Type) of
?BASE58 -> base58_check(Payload); ?BASE58 -> base58_check(Payload);
@@ -66,6 +130,7 @@ encode(Type, Payload) ->
end, end,
<<Pfx/binary, "_", Enc/binary>>. <<Pfx/binary, "_", Enc/binary>>.
-spec decode(binary()) -> {known_type(), payload()}. -spec decode(binary()) -> {known_type(), payload()}.
decode(Bin0) -> decode(Bin0) ->
case split(Bin0) of case split(Bin0) of
@@ -81,6 +146,13 @@ decode(Bin0) ->
erlang:error(missing_prefix) erlang:error(missing_prefix)
end. end.
type_size_check(account_seckey, Bin) ->
case byte_size(Bin) of
Sz when Sz =:= 32; Sz =:= 64 ->
ok;
_ ->
{error, incorrect_size}
end;
type_size_check(Type, Bin) -> type_size_check(Type, Bin) ->
case byte_size_for_type(Type) of case byte_size_for_type(Type) of
not_applicable -> ok; not_applicable -> ok;
@@ -170,17 +242,16 @@ id2type(associate_chain) -> associate_chain;
id2type(channel) -> channel; id2type(channel) -> channel;
id2type(commitment) -> commitment; id2type(commitment) -> commitment;
id2type(contract) -> contract_pubkey; id2type(contract) -> contract_pubkey;
id2type(contract_source) -> contract_source;
id2type(name) -> name; id2type(name) -> name;
id2type(native_token) -> native_token; id2type(native_token) -> native_token;
id2type(entry) -> entry. id2type(entry) -> entry.
type2id({account_pubkey, SubT}) -> {account, SubT};
type2id(account_pubkey) -> account; type2id(account_pubkey) -> account;
type2id(associate_chain) -> associate_chain; type2id(associate_chain) -> associate_chain;
type2id(channel) -> channel; type2id(channel) -> channel;
type2id(commitment) -> commitment; type2id(commitment) -> commitment;
type2id(contract_pubkey) -> contract; type2id(contract_pubkey) -> contract;
type2id(contract_source) -> contract_source;
type2id(name) -> name; type2id(name) -> name;
type2id(native_token) -> native_token; type2id(native_token) -> native_token;
type2id(entry) -> entry. type2id(entry) -> entry.
@@ -199,6 +270,7 @@ type2enc(contract_store_value) -> ?BASE64;
type2enc(contract_source) -> ?BASE64; type2enc(contract_source) -> ?BASE64;
type2enc(transaction) -> ?BASE64; type2enc(transaction) -> ?BASE64;
type2enc(tx_hash) -> ?BASE58; type2enc(tx_hash) -> ?BASE58;
type2enc({account_pubkey, _}) -> ?BASE58;
type2enc(account_pubkey) -> ?BASE58; type2enc(account_pubkey) -> ?BASE58;
type2enc(account_seckey) -> ?BASE58; type2enc(account_seckey) -> ?BASE58;
type2enc(associate_chain) -> ?BASE58; type2enc(associate_chain) -> ?BASE58;
@@ -231,6 +303,13 @@ type2pfx(contract_store_value) -> <<"cv">>;
type2pfx(contract_source) -> <<"cx">>; type2pfx(contract_source) -> <<"cx">>;
type2pfx(transaction) -> <<"tx">>; type2pfx(transaction) -> <<"tx">>;
type2pfx(tx_hash) -> <<"th">>; type2pfx(tx_hash) -> <<"th">>;
type2pfx({account_pubkey,0}) -> <<"a0">>;
type2pfx({account_pubkey,1}) -> <<"a1">>;
type2pfx({account_pubkey,2}) -> <<"a2">>;
type2pfx({account_pubkey,3}) -> <<"a3">>;
type2pfx({account_pubkey,4}) -> <<"a4">>;
type2pfx({account_pubkey,5}) -> <<"a5">>;
type2pfx({account_pubkey,6}) -> <<"a6">>;
type2pfx(account_pubkey) -> <<"ak">>; type2pfx(account_pubkey) -> <<"ak">>;
type2pfx(account_seckey) -> <<"sk">>; type2pfx(account_seckey) -> <<"sk">>;
type2pfx(associate_chain) -> <<"ac">>; type2pfx(associate_chain) -> <<"ac">>;
@@ -262,6 +341,13 @@ pfx2type(<<"ct">>) -> contract_pubkey;
pfx2type(<<"cx">>) -> contract_source; pfx2type(<<"cx">>) -> contract_source;
pfx2type(<<"tx">>) -> transaction; pfx2type(<<"tx">>) -> transaction;
pfx2type(<<"th">>) -> tx_hash; pfx2type(<<"th">>) -> tx_hash;
pfx2type(<<"a0">>) -> {account_pubkey, 0};
pfx2type(<<"a1">>) -> {account_pubkey, 1};
pfx2type(<<"a2">>) -> {account_pubkey, 2};
pfx2type(<<"a3">>) -> {account_pubkey, 3};
pfx2type(<<"a4">>) -> {account_pubkey, 4};
pfx2type(<<"a5">>) -> {account_pubkey, 5};
pfx2type(<<"a6">>) -> {account_pubkey, 6};
pfx2type(<<"ak">>) -> account_pubkey; pfx2type(<<"ak">>) -> account_pubkey;
pfx2type(<<"sk">>) -> account_seckey; pfx2type(<<"sk">>) -> account_seckey;
pfx2type(<<"ac">>) -> associate_chain; pfx2type(<<"ac">>) -> associate_chain;
@@ -296,6 +382,7 @@ byte_size_for_type(contract_source) -> not_applicable;
byte_size_for_type(transaction) -> not_applicable; byte_size_for_type(transaction) -> not_applicable;
byte_size_for_type(tx_hash) -> 32; byte_size_for_type(tx_hash) -> 32;
byte_size_for_type(account_pubkey) -> 32; byte_size_for_type(account_pubkey) -> 32;
byte_size_for_type({account_pubkey, _}) -> 32;
byte_size_for_type(account_seckey) -> 32; byte_size_for_type(account_seckey) -> 32;
byte_size_for_type(associate_chain) -> 32; byte_size_for_type(associate_chain) -> 32;
byte_size_for_type(signature) -> 64; byte_size_for_type(signature) -> 64;
+25 -3
View File
@@ -8,7 +8,7 @@
%%%------------------------------------------------------------------- %%%-------------------------------------------------------------------
-module(gmser_chain_objects). -module(gmser_chain_objects).
-vsn("0.1.2"). -vsn("0.2.0").
-export([ serialize/4 -export([ serialize/4
, deserialize/4 , deserialize/4
@@ -100,6 +100,8 @@ tag(ac_deposit_tx) -> 94;
tag(ac_update_cops_tx) -> 95; tag(ac_update_cops_tx) -> 95;
tag(ac_rollup_tx) -> 96; tag(ac_rollup_tx) -> 96;
tag(ac_proposal_tx) -> 97; tag(ac_proposal_tx) -> 97;
tag(ac_receipt) -> 98;
tag(ac_acct_state) -> 99;
tag(key_block) -> 100; tag(key_block) -> 100;
tag(micro_block) -> 101; tag(micro_block) -> 101;
tag(light_micro_block) -> 102; tag(light_micro_block) -> 102;
@@ -115,7 +117,16 @@ tag(entry) -> 140;
tag(entry_create_tx) -> 141; tag(entry_create_tx) -> 141;
tag(entry_transfer_tx) -> 142; tag(entry_transfer_tx) -> 142;
tag(entry_destroy_tx) -> 143; tag(entry_destroy_tx) -> 143;
tag(pof) -> 200. tag(account_key_store) -> 144;
tag(account_create_tx) -> 145;
tag(account_sig_store) -> 146;
tag(auth_tx) -> 147;
tag(proposal_gossip_tx) -> 148;
tag(account_auth_update_tx) -> 149;
tag(pof) -> 200;
%% Gajumaru AC side transactions
tag(ac_side_withdraw_tx) -> 300;
tag(ac_side_rollup_tx) -> 301.
rev_tag(10) -> account; rev_tag(10) -> account;
rev_tag(11) -> signed_tx; rev_tag(11) -> signed_tx;
@@ -179,6 +190,8 @@ rev_tag(94) -> ac_deposit_tx;
rev_tag(95) -> ac_update_cops_tx; rev_tag(95) -> ac_update_cops_tx;
rev_tag(96) -> ac_rollup_tx; rev_tag(96) -> ac_rollup_tx;
rev_tag(97) -> ac_proposal_tx; rev_tag(97) -> ac_proposal_tx;
rev_tag(98) -> ac_receipt;
rev_tag(99) -> ac_acct_state;
rev_tag(100) -> key_block; rev_tag(100) -> key_block;
rev_tag(101) -> micro_block; rev_tag(101) -> micro_block;
rev_tag(102) -> light_micro_block; rev_tag(102) -> light_micro_block;
@@ -194,4 +207,13 @@ rev_tag(140) -> entry;
rev_tag(141) -> entry_create_tx; rev_tag(141) -> entry_create_tx;
rev_tag(142) -> entry_transfer_tx; rev_tag(142) -> entry_transfer_tx;
rev_tag(143) -> entry_destroy_tx; rev_tag(143) -> entry_destroy_tx;
rev_tag(200) -> pof. rev_tag(144) -> account_key_store;
rev_tag(145) -> account_create_tx;
rev_tag(146) -> account_sig_store;
rev_tag(147) -> auth_tx;
rev_tag(148) -> proposal_gossip_tx;
rev_tag(149) -> account_auth_update_tx;
rev_tag(200) -> pof;
%% Gajumaru AC side transactions
rev_tag(300) -> ac_side_withdraw_tx;
rev_tag(301) -> ac_side_rollup_tx.
+1 -1
View File
@@ -6,7 +6,7 @@
%%% @end %%% @end
%%%------------------------------------------------------------------- %%%-------------------------------------------------------------------
-module(gmser_contract_code). -module(gmser_contract_code).
-vsn("0.1.2"). -vsn("0.2.0").
-include("gmser_contract_code.hrl"). -include("gmser_contract_code.hrl").
+1 -1
View File
@@ -6,7 +6,7 @@
%%% @end %%% @end
%%%------------------------------------------------------------------- %%%-------------------------------------------------------------------
-module(gmser_delegation). -module(gmser_delegation).
-vsn("0.1.2"). -vsn("0.2.0").
-export([ aens_preclaim_sig/3 -export([ aens_preclaim_sig/3
, aens_name_sig/4 , aens_name_sig/4
+1
View File
@@ -1,4 +1,5 @@
-module(gmser_dyn). -module(gmser_dyn).
-vsn("0.2.0").
-export([ encode/1 %% (Term) -> rlp() -export([ encode/1 %% (Term) -> rlp()
, encode/2 %% (Term, Types) -> rlp() , encode/2 %% (Term, Types) -> rlp()
+1
View File
@@ -1,4 +1,5 @@
-module(gmser_dyn_types). -module(gmser_dyn_types).
-vsn("0.2.0").
-export([ add_type/3 %% (Tag, Code, Template) -> Types1 -export([ add_type/3 %% (Tag, Code, Template) -> Types1
, add_type/4 %% (Tag, Code, Template, Types) -> Types1 , add_type/4 %% (Tag, Code, Template, Types) -> Types1
+53 -4
View File
@@ -8,12 +8,15 @@
%%%------------------------------------------------------------------- %%%-------------------------------------------------------------------
-module(gmser_id). -module(gmser_id).
-vsn("0.1.2"). -vsn("0.2.0").
-export([ create/2 -export([ create/2
, specialize/1 , specialize/1
, specialize/2 , specialize/2
, specialize_type/1 , specialize_type/1
, is_account/1
, account_pubkey/1
, to_map/1
, is_id/1 , is_id/1
]). ]).
@@ -29,12 +32,17 @@
, val , val
}). }).
-type tag() :: 'account' -type subtype() :: 0..6.
-type id_map() :: #{ type := simple_tag()
, subtype => subtype()
, value := binary() }.
-type tag() :: {'account', subtype()} | simple_tag().
-type simple_tag() :: 'account'
| 'associate_chain' | 'associate_chain'
| 'channel' | 'channel'
| 'commitment' | 'commitment'
| 'contract' | 'contract'
| 'contract_source'
| 'name' | 'name'
| 'native_token' | 'native_token'
| 'entry'. | 'entry'.
@@ -57,7 +65,8 @@
___TAG___ =:= contract; ___TAG___ =:= contract;
___TAG___ =:= channel; ___TAG___ =:= channel;
___TAG___ =:= associate_chain; ___TAG___ =:= associate_chain;
___TAG___ =:= entry ___TAG___ =:= entry;
___TAG___ =:= native_token
). ).
-define(IS_VAL(___VAL___), byte_size(___VAL___) =:= 32). -define(IS_VAL(___VAL___), byte_size(___VAL___) =:= 32).
@@ -69,6 +78,8 @@
create(Tag, Val) when ?IS_TAG(Tag), ?IS_VAL(Val) -> create(Tag, Val) when ?IS_TAG(Tag), ?IS_VAL(Val) ->
#id{ tag = Tag #id{ tag = Tag
, val = Val}; , val = Val};
create({account,I}, Val) when is_binary(Val), I >= 0, I =< 6 ->
#id{ tag = {account, I}, val = Val};
create(Tag, Val) when ?IS_VAL(Val) -> create(Tag, Val) when ?IS_VAL(Val) ->
error({illegal_tag, Tag}); error({illegal_tag, Tag});
create(Tag, Val) when ?IS_TAG(Tag)-> create(Tag, Val) when ?IS_TAG(Tag)->
@@ -78,28 +89,66 @@ create(Tag, Val) ->
-spec specialize(id()) -> {tag(), val()}. -spec specialize(id()) -> {tag(), val()}.
specialize(#id{tag = {Tag,_}, val = Val}) ->
{Tag, Val};
specialize(#id{tag = Tag, val = Val}) -> specialize(#id{tag = Tag, val = Val}) ->
{Tag, Val}. {Tag, Val}.
-spec specialize(id(), tag()) -> val(). -spec specialize(id(), tag()) -> val().
specialize(#id{tag = {Tag, _}, val = Val}, Tag) when is_binary(Val) ->
Val;
specialize(#id{tag = Tag, val = Val}, Tag) when ?IS_TAG(Tag), ?IS_VAL(Val) -> specialize(#id{tag = Tag, val = Val}, Tag) when ?IS_TAG(Tag), ?IS_VAL(Val) ->
Val. Val.
-spec specialize_type(id()) -> tag(). -spec specialize_type(id()) -> tag().
specialize_type(#id{tag = {Tag, _}}) when ?IS_TAG(Tag) ->
Tag;
specialize_type(#id{tag = Tag}) when ?IS_TAG(Tag) -> specialize_type(#id{tag = Tag}) when ?IS_TAG(Tag) ->
Tag. Tag.
-spec is_account(id() | term()) -> boolean().
is_account(#id{tag = account}) ->
true;
is_account(#id{tag = {account, _}}) ->
true;
is_account(_) ->
false.
-spec account_pubkey(id()) -> val().
account_pubkey(#id{tag = account, val = Val}) when ?IS_VAL(Val) ->
Val;
account_pubkey(#id{tag = {account, _}, val = Val}) when ?IS_VAL(Val) ->
Val.
-spec to_map(id()) -> id_map().
to_map(#id{tag = {Tag, SubType}, val = Val}) when ?IS_TAG(Tag) ->
#{ type => Tag
, subtype => SubType
, value => Val };
to_map(#id{tag = Tag, val = Val}) when ?IS_TAG(Tag) ->
#{ type => Tag
, value => Val }.
-spec is_id(term()) -> boolean(). -spec is_id(term()) -> boolean().
is_id(#id{}) -> true; is_id(#id{}) -> true;
is_id(_) -> false. is_id(_) -> false.
-spec encode(id()) -> binary(). -spec encode(id()) -> binary().
encode(#id{tag = {account, N}, val = Val}) when N =< 2#111_1111 ->
Ext = 2#1000_0000 bor N,
<<Ext:8, Val/binary>>;
encode(#id{tag = Tag, val = Val}) -> encode(#id{tag = Tag, val = Val}) ->
Res = <<(encode_tag(Tag)):?TAG_SIZE/unit:8, Val/binary>>, Res = <<(encode_tag(Tag)):?TAG_SIZE/unit:8, Val/binary>>,
true = ?SERIALIZED_SIZE =:= byte_size(Res), true = ?SERIALIZED_SIZE =:= byte_size(Res),
Res. Res.
-spec decode(binary()) -> id(). -spec decode(binary()) -> id().
decode(<<Ext:8, Rest/binary>>) when Ext >= 2#1000_0000 ->
%% Extended account id type
Type = Ext band 2#0111_1111,
#id{ tag = {account, Type}
, val = Rest };
decode(<<Tag:?TAG_SIZE/unit:8, Val:?PUB_SIZE/binary>>) -> decode(<<Tag:?TAG_SIZE/unit:8, Val:?PUB_SIZE/binary>>) ->
#id{ tag = decode_tag(Tag) #id{ tag = decode_tag(Tag)
, val = Val}. , val = Val}.
+1 -1
View File
@@ -11,7 +11,7 @@
%%%------------------------------------------------------------------- %%%-------------------------------------------------------------------
-module(gmser_rlp). -module(gmser_rlp).
-vsn("0.1.2"). -vsn("0.2.0").
-export([ decode/1 -export([ decode/1
, decode_one/1 , decode_one/1
+1 -1
View File
@@ -1,6 +1,6 @@
{application, gmserialization, {application, gmserialization,
[{description, "Serialization of data for the Gajumaru"}, [{description, "Serialization of data for the Gajumaru"},
{vsn, "0.1.0"}, {vsn, "zomp"},
{registered, []}, {registered, []},
{applications, {applications,
[kernel, [kernel,
+14
View File
@@ -0,0 +1,14 @@
%% -*- erlang-mode; erlang-indent-level: 4; indent-tabs-mode: nil -*-
[{application, Name, Opts}] = CONFIG.
case lists:keyfind(vsn, 1, Opts) of
{vsn, "zomp"} ->
ZompMetaF = filename:join(filename:dirname(filename:dirname(SCRIPT)), "zomp.meta"),
{ok, ZMeta} = file:consult(ZompMetaF),
{_, {_, _, {Vmaj,Vmin,Vpatch}}} = lists:keyfind(package_id, 1, ZMeta),
VsnStr = unicode:characters_to_list(io_lib:fwrite("~w.~w.~w", [Vmaj, Vmin, Vpatch])),
Opts1 = lists:keyreplace(vsn, 1, Opts, {vsn, VsnStr}),
[{application, Name, Opts1}];
_ ->
CONFIG
end.
+1 -1
View File
@@ -7,7 +7,7 @@
%%%------------------------------------------------------------------- %%%-------------------------------------------------------------------
-module(gmserialization). -module(gmserialization).
-vsn("0.1.2"). -vsn("0.2.0").
-export([ decode_fields/2 -export([ decode_fields/2
, decode_field/2 , decode_field/2
+101 -12
View File
@@ -22,10 +22,39 @@
, {native_token , 32} , {native_token , 32}
, {commitment , 32} , {commitment , 32}
, {peer_pubkey , 32} , {peer_pubkey , 32}
, {hash , 32}
, {state , 32} , {state , 32}
, {poi , not_applicable}]). , {poi , not_applicable}]).
encode_decode_test_() -> encode_decode_test_() ->
encode_decode_test_(?TYPES).
encode_decode_known_types_test_() ->
KnownTypes = known_types(),
SizedTypes = [{T, ?TEST_MODULE:byte_size_for_type(T)} || T <- KnownTypes],
encode_decode_test_(SizedTypes).
prefixes_are_known_types_test() ->
MappedPfxs = mapped_prefixes(),
KnownTypes = known_types(),
lists:foreach(
fun({Pfx, Type}) ->
case lists:member(Type, KnownTypes) of
true -> ok;
false ->
error({not_a_known_type, Pfx, Type})
end
end, MappedPfxs),
lists:foreach(
fun(Type) ->
case lists:keyfind(Type, 2, MappedPfxs) of
{_, _} -> ok;
false ->
error({has_no_mapped_prefix, Type})
end
end, KnownTypes).
encode_decode_test_(Types) ->
[{"Byte sizes are correct", [{"Byte sizes are correct",
fun() -> fun() ->
lists:foreach( lists:foreach(
@@ -33,7 +62,7 @@ encode_decode_test_() ->
{_Type, _, ByteSize} = {Type, ByteSize, {_Type, _, ByteSize} = {Type, ByteSize,
?TEST_MODULE:byte_size_for_type(Type)} ?TEST_MODULE:byte_size_for_type(Type)}
end, end,
?TYPES) Types)
end end
}, },
{"Serialize/deserialize known types", {"Serialize/deserialize known types",
@@ -50,7 +79,7 @@ encode_decode_test_() ->
{Type, Key} = ?TEST_MODULE:decode(EncodedKey), {Type, Key} = ?TEST_MODULE:decode(EncodedKey),
{ok, Key} = ?TEST_MODULE:safe_decode(Type, EncodedKey) {ok, Key} = ?TEST_MODULE:safe_decode(Type, EncodedKey)
end, end,
?TYPES) Types)
end end
}, },
{"Key size check works", {"Key size check works",
@@ -58,17 +87,18 @@ encode_decode_test_() ->
lists:foreach( lists:foreach(
fun({_Type, not_applicable}) -> ok; fun({_Type, not_applicable}) -> ok;
({Type, ByteSize}) -> ({Type, ByteSize}) ->
CheckIlligalSize = CheckIllegalSize =
fun(S) -> fun(S) ->
Key = <<42:S/unit:8>>, Key = <<42:S/unit:8>>,
EncodedKey = ?TEST_MODULE:encode(Type, Key), ?assertError(incorrect_size, ?TEST_MODULE:encode(Type, Key)),
EncodedKey = ?TEST_MODULE:unsafe_encode(Type, Key), %% no size check
{error, invalid_encoding} = ?TEST_MODULE:safe_decode(Type, EncodedKey) {error, invalid_encoding} = ?TEST_MODULE:safe_decode(Type, EncodedKey)
end, end,
CheckIlligalSize(0), CheckIllegalSize(0),
CheckIlligalSize(ByteSize - 1), CheckIllegalSize(ByteSize - 1),
CheckIlligalSize(ByteSize + 1) CheckIllegalSize(ByteSize + 1)
end, end,
?TYPES) Types)
end end
}, },
{"Missing prefix", {"Missing prefix",
@@ -91,7 +121,7 @@ encode_decode_test_() ->
<<_WholePrefix:3/unit:8, RestOfKey2/binary>> = EncodedKey, <<_WholePrefix:3/unit:8, RestOfKey2/binary>> = EncodedKey,
{error, invalid_encoding} = ?TEST_MODULE:safe_decode(Type, RestOfKey2) {error, invalid_encoding} = ?TEST_MODULE:safe_decode(Type, RestOfKey2)
end, end,
?TYPES) Types)
end end
}, },
{"Piece of encoded key", {"Piece of encoded key",
@@ -110,7 +140,7 @@ encode_decode_test_() ->
{error, invalid_encoding} = ?TEST_MODULE:safe_decode(Type, HalfKey), {error, invalid_encoding} = ?TEST_MODULE:safe_decode(Type, HalfKey),
{error, invalid_encoding} = ?TEST_MODULE:safe_decode(Type, RestOfKey) {error, invalid_encoding} = ?TEST_MODULE:safe_decode(Type, RestOfKey)
end, end,
?TYPES) Types)
end end
}, },
{"Encode/decode binary with only zeros", {"Encode/decode binary with only zeros",
@@ -131,8 +161,67 @@ encode_decode_test_() ->
Encoded1 = base58:binary_to_base58(Bin), Encoded1 = base58:binary_to_base58(Bin),
Decoded1 = base58:base58_to_binary(Encoded1), Decoded1 = base58:base58_to_binary(Encoded1),
?assertEqual(Bin, Decoded1) ?assertEqual(Bin, Decoded1)
end, ?TYPES) end, Types)
end, end,
Bins) Bins)
end} end},
{"Encode/decode keypairs",
fun() ->
KP1 = enacl:sign_keypair(),
Enc1 = ?TEST_MODULE:encode_keypair(KP1),
{ok, KP1} = ?TEST_MODULE:safe_decode_keypair(Enc1),
KP2 = enacl:sign_keypair(),
Enc2 = ?TEST_MODULE:encode_keypair(KP2),
{ok, KP2} = ?TEST_MODULE:safe_decode_keypair(Enc2),
BadEnc = Enc1#{~"priv" => maps:get(~"priv", Enc2)},
{error, illegal_encoding} = ?TEST_MODULE:safe_decode_keypair(BadEnc)
end
},
{"Encode AND decode both 32-byte and 64-byte account_seckey",
fun() ->
%% Originally, we could encode a 64-byte seckey, but decode would fail.
#{public := Pub, secret := Sec} = enacl:sign_keypair(),
<<Seed:32/binary, Pub:32/binary>> = Sec,
EncSeed = ?TEST_MODULE:encode(account_seckey, Seed),
EncSec = ?TEST_MODULE:encode(account_seckey, Sec),
{ok, Seed} = ?TEST_MODULE:safe_decode(account_seckey, EncSeed),
{ok, Sec} = ?TEST_MODULE:safe_decode(account_seckey, EncSec)
end
}
]. ].
known_types() ->
Forms = get_forms(),
[{type, _, union, Types}] =
[Def || {attribute, _, type, {known_type, Def, []}} <- Forms],
lists:flatmap(fun known_type_entry/1, Types).
known_type_entry({atom, _, Name}) ->
[Name];
known_type_entry({type, _, tuple,
[{atom, _, account_pubkey},
{type, _, range, [{integer, _, Lo}, {integer, _, Hi}]}]}) ->
[{account_pubkey, N} || N <- lists:seq(Lo, Hi)];
known_type_entry(Other) ->
error({unsupported_known_type, Other}).
mapped_prefixes() ->
Forms = get_forms(),
[Clauses] = [Cs || {function,_,pfx2type,1,Cs} <- Forms],
Abst = [{B, A} || {clause,_,[B],[],[A]} <- Clauses],
lists:map(
fun({B, A}) ->
{eval_expr(B), eval_expr(A)}
end, Abst).
get_forms() ->
get_forms(code:which(?TEST_MODULE)).
get_forms(Beam) ->
{ok, {_, [{abstract_code, {raw_abstract_v1, Forms}}]}} =
beam_lib:chunks(Beam, [abstract_code]),
Forms.
eval_expr(Expr) ->
{value, Val, []} = erl_eval:expr(Expr, []),
Val.
+44
View File
@@ -0,0 +1,44 @@
-module(gmser_id_tests).
-include_lib("eunit/include/eunit.hrl").
-define(PUBKEY, <<12345:32/unit:8>>).
is_account_test() ->
{"is_account recognizes standard and extended account ids",
fun() ->
?assert(gmser_id:is_account(gmser_id:create(account, ?PUBKEY))),
?assert(gmser_id:is_account(gmser_id:create({account, 0}, ?PUBKEY))),
?assert(gmser_id:is_account(gmser_id:create({account, 5}, ?PUBKEY))),
?assertNot(gmser_id:is_account(gmser_id:create(contract, ?PUBKEY))),
?assertNot(gmser_id:is_account(not_an_id))
end}.
account_pubkey_test() ->
{"account_pubkey returns the 32-byte account hash",
fun() ->
?assertEqual(?PUBKEY,
gmser_id:account_pubkey(gmser_id:create(account, ?PUBKEY))),
?assertEqual(?PUBKEY,
gmser_id:account_pubkey(gmser_id:create({account, 3}, ?PUBKEY))),
?assertEqual(?PUBKEY,
gmser_id:account_pubkey(gmser_id:create({account, 6}, ?PUBKEY)))
end}.
account_pubkey_matches_specialize_test() ->
{"account_pubkey agrees with specialize/2 for account ids",
fun() ->
Id = gmser_id:create({account, 2}, ?PUBKEY),
?assertEqual(gmser_id:specialize(Id, account),
gmser_id:account_pubkey(Id))
end}.
extended_account_roundtrip_test() ->
{"extended account ids round-trip through encode/decode",
fun() ->
Id = gmser_id:create({account, 4}, ?PUBKEY),
?assert(gmser_id:is_account(Id)),
Id1 = gmser_id:decode(gmser_id:encode(Id)),
?assertEqual(?PUBKEY, gmser_id:account_pubkey(Id1)),
?assertEqual(account, gmser_id:specialize_type(Id1))
end}.
+2 -2
View File
@@ -2,9 +2,9 @@
{type,lib}. {type,lib}.
{modules,[]}. {modules,[]}.
{prefix,none}. {prefix,none}.
{author,"Hans Svensson"}.
{desc,"Serialization helpers for the Gajumaru."}. {desc,"Serialization helpers for the Gajumaru."}.
{package_id,{"otpr","gmserialization",{0,1,2}}}. {author,"Hans Svensson"}.
{package_id,{"otpr","gmserialization",{0,2,0}}}.
{deps,[{"otpr","eblake2",{1,0,1}},{"otpr","base58",{0,1,1}}]}. {deps,[{"otpr","eblake2",{1,0,1}},{"otpr","base58",{0,1,1}}]}.
{key_name,none}. {key_name,none}.
{a_email,[]}. {a_email,[]}.
Executable
+49
View File
@@ -0,0 +1,49 @@
#!/bin/sh
set -e
APP=$(basename "$PWD")
SRC="_build/default/lib/$APP"
DST="$PWD/_build/zomp/lib/$APP"
IGNORE_FILE="zomp.ignore"
mkdir -p "$DST"
# Remove broken symlinks
find "$SRC" -type l ! -exec test -e {} \; -delete || true
# Build ignore matcher
IGNORE_TEMP=$(mktemp)
trap "rm -f $IGNORE_TEMP" EXIT
# Expand globs in zomp.ignore to patterns suitable for grep
if [ -e "$IGNORE_FILE" ]; then
grep -v '^\s*#' "$IGNORE_FILE" | sed 's#/#\\/#g' | sed 's/\./\\./g' | sed 's/\*/.*/g' > "$IGNORE_TEMP"
fi
# Copy Git-tracked and Zomp-allowed files
git ls-files -z | while IFS= read -r -d '' file; do
# Skip if ignored
echo "$file" | grep -Eq -f "$IGNORE_TEMP" && continue
# Only copy if file exists in the build dir
if [ -e "$SRC/$file" ]; then
mkdir -p "$DST/$(dirname "$file")"
cp -a "$SRC/$file" "$DST/$file"
fi
done
rm "$IGNORE_TEMP"
# Copy metadata
cp "$PWD/zomp.meta" "$DST/"
cp "$PWD/Emakefile" "$DST/"
# copy generated schema
SCHEMA="$SRC/priv/gmhc_schema.json"
if [ -e "$SCHEMA" ]; then
mkdir -p "$DST/priv"
cp -a "$SCHEMA" "$DST/priv/$(basename "$SCHEMA")"
fi
# Clean up beam files just in case
[ -d "$DST/ebin" ] && find "$DST/ebin" -name '*.beam' -exec rm -f {} + || true