diff --git a/ebin/hakuzaru.app b/ebin/hakuzaru.app index 7037387..f457c88 100644 --- a/ebin/hakuzaru.app +++ b/ebin/hakuzaru.app @@ -3,6 +3,6 @@ {included_applications,[]}, {applications,[stdlib,kernel]}, {description,"Gajumaru interoperation library"}, - {vsn,"0.5.2"}, + {vsn,"0.6.0"}, {modules,[hakuzaru,hz,hz_fetcher,hz_man,hz_sup]}, {mod,{hakuzaru,[]}}]}. diff --git a/src/hakuzaru.erl b/src/hakuzaru.erl index 39b454c..e70d5cc 100644 --- a/src/hakuzaru.erl +++ b/src/hakuzaru.erl @@ -6,7 +6,7 @@ %%% @end -module(hakuzaru). --vsn("0.5.2"). +-vsn("0.6.0"). -author("Craig Everett "). -copyright("Craig Everett "). -license("GPL-3.0-or-later"). diff --git a/src/hz.erl b/src/hz.erl index 19e86ed..c3c3d5e 100644 --- a/src/hz.erl +++ b/src/hz.erl @@ -23,7 +23,7 @@ %%% @end -module(hz). --vsn("0.5.2"). +-vsn("0.6.0"). -author("Craig Everett "). -copyright("Craig Everett "). -license("GPL-3.0-or-later"). @@ -73,6 +73,7 @@ decode_bytearray_fate/1, decode_bytearray/2, spend/5, spend/10, sign_tx/2, sign_tx/3, + sign_message/2, verify_signature/3]). @@ -2272,6 +2273,20 @@ spend3(DSenderID, hz:post_tx(Encoded). +-spec sign_message(Message, SecKey) -> Sig + when Message :: binary(), + SecKey :: binary(), + Sig :: binary(). + +sign_message(Message, SecKey) -> + Prefix = <<"Gajumaru Signed Message:\n">>, + {ok, PSize} = vencode(byte_size(Prefix)), + {ok, MSize} = vencode(byte_size(Message)), + Smashed = iolist_to_binary([PSize, Prefix, MSize, Message]), + {ok, Hashed} = eblake2:blake2b(32, Smashed), + ecu_eddsa:sign_detached(Hashed, SecKey). + + -spec verify_signature(Sig, Message, PubKey) -> Result when Sig :: binary(), Message :: iodata(), diff --git a/src/hz_fetcher.erl b/src/hz_fetcher.erl index a7ccac4..0fd4b5b 100644 --- a/src/hz_fetcher.erl +++ b/src/hz_fetcher.erl @@ -1,5 +1,5 @@ -module(hz_fetcher). --vsn("0.5.2"). +-vsn("0.6.0"). -author("Craig Everett "). -copyright("Craig Everett "). -license("MIT"). diff --git a/src/hz_grids.erl b/src/hz_grids.erl new file mode 100644 index 0000000..a1f335f --- /dev/null +++ b/src/hz_grids.erl @@ -0,0 +1,159 @@ +%%% @doc +%%% GRIDS URL parsing +%%% +%%% GRID(S): Gajumaru Remote Instruction Dispatch (Serialization) +%%% GRIDS is a Gajumaru protocol for encoding wallet instructions as URLs. +%%% Version 1 of the protocol consists of two verbs with two contexts each, collapsed to +%%% four symbols for brevity. +%%% +%%% The GRIDS schema begins with "grids://" or "grid://" +%%% Which way this is interpreted can vary depending on the verb. +%%% +%%% The typical "host" component is either an actual hostname or address and an optional +%%% port number (the defaut port being 3013), or a Gajumaru chain network IDi (in which +%%% case the port number is ignored if provided). Which way this field is interpreted +%%% depends on the verb. +%%% +%%% The first element of the path after the host component indicates the protocol version. +%%% Only version 1 exists at the time of this release. +%%% +%%% The next element of the path after the version is a single letter that indicates which +%%% action to take. The following actions are available: +%%% "s": Spend on Chain +%%% Constructs a spend transaction to the address indicated in the path component +%%% indicated in the final path element. Two qargs are valid in the trailing arguments +%%% section: "a" for amount (in Pucks, not Gajus!), and "p" for data payload. +%%% In this context the "host" field in the URL is interpreted as a chain network ID. +%%% "t": Transfer (spend) on Host +%%% The same as "spend" above, but in this context the host field of the URL is +%%% interpreted as host[:port] information and the network chain ID that will be used +%%% will be derived from whatever chain the given host reports. +%%% "d": Dead-drop signature request +%%% This instructs the wallet to retrieve a signature data blob from an HTTP or HTTPS +%%% URL that can be reconstructed by replacing "grids" with "https" or "grid" with +%%% "http", omitting the "/1/d" path component and then recnstructing the URL. +%%% This provides a lightweight method for services to enable contract calls from +%%% wallets that are not capable of compiling contract source. +%%% @end + +-module(hz_grids). +-vsn("0.6.0"). +-export([url/2, parse/1, req/2, req/3]). + + +-spec url(Instruction, HTTP) -> Result + when Instruction :: spend | transfer | sign, + HTTP :: uri_string:uri_string(), + GRIDS :: uri_string:uri_string(), + Result :: {ok, GRIDS} | uri_string:uri_error(). +%% @doc +%% Takes + +url(Instruction, HTTP) -> + case uri_string:parse(HTTP) of + U = #{scheme := "https"} -> url2(Instruction, U#{scheme := "grids"}); + U = #{scheme := "http"} -> url2(Instruction, U#{scheme := "grid"}); + Error -> Error + end. + +url2(Instruction, URL = #{path := Path}) -> + GRIDS = + case Instruction of + spend -> URL#{path := "/1/s" ++ Path}; + transfer -> URL#{path := "/1/t" ++ Path}; + sign -> URL#{path := "/1/d" ++ Path} + end, + {ok, uri_string:recompose(GRIDS)}. + + +-spec parse(GRIDS) -> Result + when GRIDS :: string(), + Result :: {ok, Instruction} | uri_string:error(), + Instruction :: {{spend, chain | node}, {Location, Recipient, Amount, Payload}} + | {{sign, http | https}, URL}, + Location :: Node :: {inet:ip_address() | inet:hostname(), inet:port_number()} + | Chain :: binary(), + Recipient :: gajudesk:id(), + Amount :: non_neg_integer(), + Payload :: binary(), + URL :: string(). + +parse(GRIDS) -> + case uri_string:parse(GRIDS) of + #{path := "/1/s/" ++ R, host := H, query := Q, scheme := "grids"} -> + spend(R, chain, list_to_binary(H), Q); + #{path := "/1/s/" ++ R, host := H, query := Q, scheme := "grid"} -> + spend(R, chain, list_to_binary(H), Q); + #{path := "/1/t/" ++ R, host := H, port := P, query := Q, scheme := "grids"} -> + spend(R, node, {H, P}, Q); + #{path := "/1/t/" ++ R, host := H, port := P, query := Q, scheme := "grid"} -> + spend(R, node, {H, P}, Q); + #{path := "/1/t/" ++ R, host := H, query := Q, scheme := "grids"} -> + spend(R, node, {H, 3013}, Q); + #{path := "/1/t/" ++ R, host := H, query := Q, scheme := "grid"} -> + spend(R, node, {H, 3013}, Q); + U = #{path := "/1/d/" ++ L, scheme := "grids"} -> + HTTP = uri_string:recompose(U#{scheme := "https", path := L}), + {ok ,{{sign, https}, HTTP}}; + U = #{path := "/1/d/" ++ L, scheme := "grid"} -> + HTTP = uri_string:recompose(U#{scheme := "http", path := L}), + {ok, {{sign, http}, HTTP}}; + Error -> + Error + end. + +spend(Recipient, Context, Location, Qwargs) -> + case dissect_query(Qwargs) of + {ok, Amount, Payload} -> + {ok, {{spend, Context}, {Location, Recipient, Amount, Payload}}}; + Error -> + Error + end. + + +dissect_query(Qwargs) -> + case uri_string:dissect_query(Qwargs) of + {error, Reason, Info} -> + {error, Reason, Info}; + ArgList -> + case l_to_i(proplists:get_value("a", ArgList, "0")) of + {ok, Amount} -> + Payload = list_to_binary(proplists:get_value("p", ArgList, "")), + {ok, Amount, Payload}; + Error -> + Error + end + end. + +l_to_i(S) -> + try + {ok, list_to_integer(S)} + catch + error:badarg -> {error, bad_url} + end. + + +req(Type, Message) -> + req(Type, Message, false). + +req(sign, Message, ID) -> + #{"grids" => 1, + "chain" => "gajumaru", + "network_id" => hz:network_id(), + "type" => "message", + "public_id" => ID, + "payload" => Message}; +req(tx, Data, ID) -> + #{"grids" => 1, + "chain" => "gajumaru", + "network_id" => hz:network_id(), + "type" => "tx", + "public_id" => ID, + "payload" => Data}; +req(ack, Message, ID) -> + #{"grids" => 1, + "chain" => "gajumaru", + "network_id" => hz:network_id(), + "type" => "ack", + "public_id" => ID, + "payload" => Message}. diff --git a/src/hz_man.erl b/src/hz_man.erl index 9ffe9ca..ec9cc52 100644 --- a/src/hz_man.erl +++ b/src/hz_man.erl @@ -9,7 +9,7 @@ %%% @end -module(hz_man). --vsn("0.5.2"). +-vsn("0.6.0"). -behavior(gen_server). -author("Craig Everett "). -copyright("Craig Everett "). diff --git a/src/hz_sup.erl b/src/hz_sup.erl index b69cb43..0cfd2a0 100644 --- a/src/hz_sup.erl +++ b/src/hz_sup.erl @@ -9,7 +9,7 @@ %%% @end -module(hz_sup). --vsn("0.5.2"). +-vsn("0.6.0"). -behaviour(supervisor). -author("Craig Everett "). -copyright("Craig Everett "). diff --git a/zomp.meta b/zomp.meta index e567056..63ac1d9 100644 --- a/zomp.meta +++ b/zomp.meta @@ -4,7 +4,7 @@ {prefix,"hz"}. {author,"Craig Everett"}. {desc,"Gajumaru interoperation library"}. -{package_id,{"otpr","hakuzaru",{0,5,2}}}. +{package_id,{"otpr","hakuzaru",{0,6,0}}}. {deps,[{"otpr","gmserialization",{0,1,3}}, {"otpr","sophia",{8,0,1}}, {"otpr","gmbytecode",{3,4,1}},