From 7a8e840793a0ff055b21f8409857e791e607b8fe Mon Sep 17 00:00:00 2001 From: Ulf Wiger Date: Sun, 5 Jul 2026 08:38:42 +0200 Subject: [PATCH] Add support for extended-auth account types --- src/gmser_api_encoder.erl | 28 ++++++++++++++--- src/gmser_chain_objects.erl | 4 +++ src/gmser_id.erl | 53 ++++++++++++++++++++++++++------ test/gmser_api_encoder_tests.erl | 11 ++++++- 4 files changed, 81 insertions(+), 15 deletions(-) diff --git a/src/gmser_api_encoder.erl b/src/gmser_api_encoder.erl index 9013b66..267b0be 100644 --- a/src/gmser_api_encoder.erl +++ b/src/gmser_api_encoder.erl @@ -36,6 +36,7 @@ | transaction | tx_hash | account_pubkey + | {account_pubkey, 0..6} | account_seckey | associate_chain | entry @@ -107,8 +108,12 @@ safe_decode_keypair(#{<<"pub">> := EncPub, <<"priv">> := EncPriv}) -> -spec encode(known_type(), payload() | gmser_id:id()) -> encoded(). encode(id_hash, Payload) -> - {IdType, Val} = gmser_id:specialize(Payload), - encode(id2type(IdType), Val); + case gmser_id:to_map(Payload) of + #{type := account, subtype := SubT, value := Val} -> + encode({account_pubkey, SubT}, Val); + #{type := IdType, value := Val} -> + encode(id2type(IdType), Val) + end; encode(Type, Payload) -> case type_size_check(Type, Payload) of ok -> @@ -237,17 +242,16 @@ id2type(associate_chain) -> associate_chain; id2type(channel) -> channel; id2type(commitment) -> commitment; id2type(contract) -> contract_pubkey; -id2type(contract_source) -> contract_source; id2type(name) -> name; id2type(native_token) -> native_token; id2type(entry) -> entry. +type2id({account_pubkey, SubT}) -> {account, SubT}; type2id(account_pubkey) -> account; type2id(associate_chain) -> associate_chain; type2id(channel) -> channel; type2id(commitment) -> commitment; type2id(contract_pubkey) -> contract; -type2id(contract_source) -> contract_source; type2id(name) -> name; type2id(native_token) -> native_token; type2id(entry) -> entry. @@ -266,6 +270,7 @@ type2enc(contract_store_value) -> ?BASE64; type2enc(contract_source) -> ?BASE64; type2enc(transaction) -> ?BASE64; type2enc(tx_hash) -> ?BASE58; +type2enc({account_pubkey, _}) -> ?BASE58; type2enc(account_pubkey) -> ?BASE58; type2enc(account_seckey) -> ?BASE58; type2enc(associate_chain) -> ?BASE58; @@ -298,6 +303,13 @@ type2pfx(contract_store_value) -> <<"cv">>; type2pfx(contract_source) -> <<"cx">>; type2pfx(transaction) -> <<"tx">>; 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_seckey) -> <<"sk">>; type2pfx(associate_chain) -> <<"ac">>; @@ -329,6 +341,13 @@ pfx2type(<<"ct">>) -> contract_pubkey; pfx2type(<<"cx">>) -> contract_source; pfx2type(<<"tx">>) -> transaction; 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(<<"sk">>) -> account_seckey; pfx2type(<<"ac">>) -> associate_chain; @@ -363,6 +382,7 @@ byte_size_for_type(contract_source) -> not_applicable; byte_size_for_type(transaction) -> not_applicable; 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_seckey) -> 32; byte_size_for_type(associate_chain) -> 32; byte_size_for_type(signature) -> 64; diff --git a/src/gmser_chain_objects.erl b/src/gmser_chain_objects.erl index 4c89e07..6117cbe 100644 --- a/src/gmser_chain_objects.erl +++ b/src/gmser_chain_objects.erl @@ -115,6 +115,8 @@ tag(entry) -> 140; tag(entry_create_tx) -> 141; tag(entry_transfer_tx) -> 142; tag(entry_destroy_tx) -> 143; +tag(account_key_store) -> 144; +tag(account_create_tx) -> 145; tag(pof) -> 200. rev_tag(10) -> account; @@ -194,4 +196,6 @@ rev_tag(140) -> entry; rev_tag(141) -> entry_create_tx; rev_tag(142) -> entry_transfer_tx; rev_tag(143) -> entry_destroy_tx; +rev_tag(144) -> account_key_store; +rev_tag(145) -> account_create_tx; rev_tag(200) -> pof. diff --git a/src/gmser_id.erl b/src/gmser_id.erl index 80a592a..1056ef1 100644 --- a/src/gmser_id.erl +++ b/src/gmser_id.erl @@ -14,6 +14,7 @@ , specialize/1 , specialize/2 , specialize_type/1 + , to_map/1 , is_id/1 ]). @@ -29,15 +30,20 @@ , val }). --type tag() :: 'account' - | 'associate_chain' - | 'channel' - | 'commitment' - | 'contract' - | 'contract_source' - | 'name' - | 'native_token' - | 'entry'. +-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' + | 'channel' + | 'commitment' + | 'contract' + | 'name' + | 'native_token' + | 'entry'. -type val() :: <<_:256>>. -type id() :: #id{}. @@ -57,7 +63,8 @@ ___TAG___ =:= contract; ___TAG___ =:= channel; ___TAG___ =:= associate_chain; - ___TAG___ =:= entry + ___TAG___ =:= entry; + ___TAG___ =:= native_token ). -define(IS_VAL(___VAL___), byte_size(___VAL___) =:= 32). @@ -69,6 +76,8 @@ create(Tag, Val) when ?IS_TAG(Tag), ?IS_VAL(Val) -> #id{ tag = Tag , 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) -> error({illegal_tag, Tag}); create(Tag, Val) when ?IS_TAG(Tag)-> @@ -78,28 +87,52 @@ create(Tag, Val) -> -spec specialize(id()) -> {tag(), val()}. +specialize(#id{tag = {Tag,_}, val = Val}) -> + {Tag, Val}; specialize(#id{tag = Tag, val = Val}) -> {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) -> Val. -spec specialize_type(id()) -> tag(). +specialize_type(#id{tag = {Tag, _}}) when ?IS_TAG(Tag) -> + Tag; specialize_type(#id{tag = Tag}) when ?IS_TAG(Tag) -> Tag. +-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(). is_id(#id{}) -> true; is_id(_) -> false. -spec encode(id()) -> binary(). +encode(#id{tag = {account, N}, val = Val}) when N =< 2#111_1111 -> + Ext = 2#1000_0000 bor N, + <>; encode(#id{tag = Tag, val = Val}) -> Res = <<(encode_tag(Tag)):?TAG_SIZE/unit:8, Val/binary>>, true = ?SERIALIZED_SIZE =:= byte_size(Res), Res. -spec decode(binary()) -> id(). +decode(<>) when Ext >= 2#1000_0000 -> + %% Extended account id type + Type = Ext band 2#0111_1111, + #id{ tag = {account, Type} + , val = Rest }; decode(<>) -> #id{ tag = decode_tag(Tag) , val = Val}. diff --git a/test/gmser_api_encoder_tests.erl b/test/gmser_api_encoder_tests.erl index a5d7e13..df62fa9 100644 --- a/test/gmser_api_encoder_tests.erl +++ b/test/gmser_api_encoder_tests.erl @@ -194,7 +194,16 @@ known_types() -> Forms = get_forms(), [{type, _, union, Types}] = [Def || {attribute, _, type, {known_type, Def, []}} <- Forms], - [Name || {atom,_, Name} <- Types]. + 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(), -- 2.30.2