diff --git a/src/gmc_con.erl b/src/gmc_con.erl index 41e5fb9..3e593ce 100644 --- a/src/gmc_con.erl +++ b/src/gmc_con.erl @@ -15,7 +15,7 @@ open_wallet/2, close_wallet/0, new_wallet/3, import_wallet/3, drop_wallet/2, password/2, refresh/0, - nonce/1, spend/2, chain/1, grids/1, sign_mess/1, + nonce/1, spend/2, chain/1, grids/1, sign_mess/1, sign_tx/1, make_key/6, recover_key/1, mnemonic/1, rename_key/2, drop_key/1, add_node/1, set_sole_node/1]). -export([encrypt/2, decrypt/2]). @@ -154,6 +154,13 @@ sign_mess(Request) -> gen_server:cast(?MODULE, {sign_mess, Request}). +-spec sign_tx(Request) -> ok + when Request :: map(). + +sign_tx(Request) -> + gen_server:cast(?MODULE, {sign_tx, Request}). + + -spec make_key(Type, Size, Name, Seed, Encoding, Transform) -> ok when Type :: {eddsa, ed25519}, Size :: 256, @@ -345,6 +352,9 @@ handle_cast({grids, String}, State) -> handle_cast({sign_mess, Request}, State) -> ok = do_sign_mess(Request, State), {noreply, State}; +handle_cast({sign_tx, Request}, State) -> + ok = do_sign_tx(Request, State), + {noreply, State}; handle_cast({make_key, Name, Seed, Encoding, Transform}, State) -> NewState = do_make_key(Name, Seed, Encoding, Transform, State), {noreply, NewState}; @@ -532,6 +542,7 @@ do_grids(String) -> end. do_grids2({{sign, http}, URL}) -> + ok = tell("Making request to ~p", [URL]), case httpc:request(URL) of {ok, {{_, 200, _}, _, JSON}} -> do_grids_sig(JSON, URL); Error -> gmc_gui:trouble(Error) @@ -540,6 +551,7 @@ do_grids2(Instruction) -> tell("GRIDS: ~tp", [Instruction]). do_grids_sig(JSON, URL) -> + ok = tell("Received: ~p", [JSON]), case zj:decode(JSON) of {ok, GRIDS} -> do_grids_sig2(GRIDS#{"url" => URL}); Error -> gmc_gui:trouble(Error) @@ -547,6 +559,8 @@ do_grids_sig(JSON, URL) -> do_grids_sig2(Request = #{"grids" := 1, "type" := "message"}) -> gmc_gui:grids_mess_sig(Request); +do_grids_sig2(Request = #{"grids" := 1, "type" := "tx"}) -> + gmc_gui:grids_mess_sig(Request); do_grids_sig2(WTF) -> gmc_gui:trouble({trash, WTF}). @@ -608,6 +622,50 @@ eu(N, Size) -> <>. +do_sign_tx(Request = #{"public_id" := ID, "payload" := CallData, "network_id" := NID}, + #s{wallet = #wallet{keys = Keys}}) -> + BinNID = list_to_binary(NID), + case lists:keyfind(ID, #key.id, Keys) of + #key{pair = #{secret := PrivKey}} -> + BinaryTX = list_to_binary(CallData), + SignedTX = sign_tx_hash(BinaryTX, PrivKey, BinNID), + do_sign_tx2(Request#{"signed" => true, "payload" := SignedTX}); + false -> + gmc_gui:trouble({bad_key, ID}) + end. + +do_sign_tx2(Request = #{"url" := URL}) -> + ResponseKeys = + ["grids", + "chain", + "network_id", + "type", + "public_id", + "payload", + "signed"], + Response = zj:encode(maps:with(ResponseKeys, Request)), + case httpc:request(post, {URL, [], "application/json", Response}, [], []) of + {ok, {{_, 200, _}, _, JSON}} -> log(info, "Signature posted: ~p", [JSON]); + Error -> gmc_gui:trouble(Error) + end. + +sign_tx_hash(Unsigned, PrivKey, NetworkID) -> + {ok, TX_Data} = aeser_api_encoder:safe_decode(transaction, Unsigned), + {ok, Hash} = eblake2:blake2b(32, TX_Data), + NetworkHash = <>, + Signature = ecu_eddsa:sign_detached(NetworkHash, PrivKey), + SigTxType = signed_tx, + SigTxVsn = 1, + SigTemplate = + [{signatures, [binary]}, + {transaction, binary}], + TX = + [{signatures, [Signature]}, + {transaction, TX_Data}], + SignedTX = aeser_chain_objects:serialize(SigTxType, SigTxVsn, SigTemplate, TX), + aeser_api_encoder:encode(transaction, SignedTX). + + do_spend(KeyID, TX, State = #s{wallet = #wallet{keys = Keys}}) -> case lists:keyfind(KeyID, #key.id, Keys) of #key{pair = #{secret := PrivKey}} -> diff --git a/src/gmc_gui.erl b/src/gmc_gui.erl index f64fa9b..28b858a 100644 --- a/src/gmc_gui.erl +++ b/src/gmc_gui.erl @@ -804,7 +804,7 @@ is_int(S) -> grids_dialogue(State = #s{frame = Frame, j = J}) -> - Dialog = wxDialog:new(Frame, ?wxID_ANY, J("Password")), + Dialog = wxDialog:new(Frame, ?wxID_ANY, J("GRIDS URL")), Sizer = wxBoxSizer:new(?wxVERTICAL), Label = J("GRIDS URL"), URL_Sz = wxStaticBoxSizer:new(?wxVERTICAL, Dialog, [{label, J(Label)}]), @@ -943,9 +943,22 @@ do_grids_mess_sig(Request = #{"public_id" := false}, Selected -> #poa{id = ID} = lists:nth(Selected + 1, Accounts), do_grids_mess_sig2(Request#{"public_id" := ID}, State) + end; +do_grids_mess_sig(Request = #{"public_id" := ID}, State = #s{accounts = Accounts}) -> + BinID = list_to_binary(ID), + case lists:keymember(BinID, #poa.id, Accounts) of + true -> + do_grids_mess_sig2(Request#{"public_id" := BinID}, State); + false -> + tell("Request sig from ID we don't have: ~p", [BinID]), + tell("Accounts: ~p", [Accounts]) end. -do_grids_mess_sig2(Request = #{"url" := URL, "public_id" := ID, "payload" := Message}, +do_grids_mess_sig2(Request = #{"grids" := 1, + "type" := "message", + "url" := URL, + "public_id" := ID, + "payload" := Message}, #s{frame = Frame, j = J}) -> Dialog = wxDialog:new(Frame, ?wxID_ANY, J("Message Signature Request")), Sizer = wxBoxSizer:new(?wxVERTICAL), @@ -960,7 +973,8 @@ do_grids_mess_sig2(Request = #{"url" := URL, "public_id" := ID, "payload" := Mes URL_Tx = wxStaticText:new(Dialog, ?wxID_ANY, URL), _ = wxStaticBoxSizer:add(URL_Sz, URL_Tx, zxw:flags(wide)), MessSz = wxStaticBoxSizer:new(?wxVERTICAL, Dialog, [{label, J("Message")}]), - MessTx = wxStaticText:new(Dialog, ?wxID_ANY, Message), + MessStyle = ?wxTE_MULTILINE bor ?wxTE_READONLY, + MessTx = wxTextCtrl:new(Dialog, ?wxID_ANY, [{value, Message}, {style, MessStyle}]), _ = wxStaticBoxSizer:add(MessSz, MessTx, zxw:flags(wide)), ButtSz = wxBoxSizer:new(?wxHORIZONTAL), Affirm = wxButton:new(Dialog, ?wxID_OK), @@ -970,10 +984,10 @@ do_grids_mess_sig2(Request = #{"url" := URL, "public_id" := ID, "payload" := Mes _ = wxBoxSizer:add(Sizer, InstTx, zxw:flags(wide)), _ = wxBoxSizer:add(Sizer, AcctSz, zxw:flags(wide)), _ = wxBoxSizer:add(Sizer, URL_Sz, zxw:flags(wide)), - _ = wxBoxSizer:add(Sizer, MessTx, zxw:flags(wide)), + _ = wxBoxSizer:add(Sizer, MessSz, zxw:flags(wide)), _ = wxBoxSizer:add(Sizer, ButtSz, zxw:flags(base)), ok = wxDialog:setSizer(Dialog, Sizer), - ok = wxFrame:setSize(Dialog, {500, 500}), + ok = wxDialog:setSize(Dialog, {500, 500}), ok = wxBoxSizer:layout(Sizer), ok = wxFrame:center(Dialog), ok = @@ -982,9 +996,54 @@ do_grids_mess_sig2(Request = #{"url" := URL, "public_id" := ID, "payload" := Mes ?wxID_CANCEL -> ok end, wxDialog:destroy(Dialog); +do_grids_mess_sig2(Request = #{"grids" := 1, + "type" := "tx", + "url" := URL, + "chain" := Chain, + "network_id" := NetID, + "public_id" := ID, + "payload" := CallData}, + #s{frame = Frame, j = J}) -> + Dialog = wxDialog:new(Frame, ?wxID_ANY, J("Message Signature Request")), + Sizer = wxBoxSizer:new(?wxVERTICAL), + Instruction = + J("The server at the URL below is requesting you sign the following message."), + InstTx = wxStaticText:new(Dialog, ?wxID_ANY, Instruction), + AcctSz = wxStaticBoxSizer:new(?wxVERTICAL, Dialog, [{label, J("Signature Account")}]), + AcctTx = wxStaticText:new(Dialog, ?wxID_ANY, ID), + _ = wxStaticBoxSizer:add(AcctSz, AcctTx, zxw:flags(wide)), + URL_Label = J("Originating URL"), + URL_Sz = wxStaticBoxSizer:new(?wxVERTICAL, Dialog, [{label, URL_Label}]), + URL_Tx = wxStaticText:new(Dialog, ?wxID_ANY, URL), + _ = wxStaticBoxSizer:add(URL_Sz, URL_Tx, zxw:flags(wide)), + MessSz = wxStaticBoxSizer:new(?wxVERTICAL, Dialog, [{label, J("CallData")}]), + MessStyle = ?wxTE_MULTILINE bor ?wxTE_READONLY, + MessTx = wxTextCtrl:new(Dialog, ?wxID_ANY, [{value, CallData}, {style, MessStyle}]), + _ = wxStaticBoxSizer:add(MessSz, MessTx, zxw:flags(wide)), + ButtSz = wxBoxSizer:new(?wxHORIZONTAL), + Affirm = wxButton:new(Dialog, ?wxID_OK), + Cancel = wxButton:new(Dialog, ?wxID_CANCEL), + _ = wxBoxSizer:add(ButtSz, Affirm, zxw:flags(wide)), + _ = wxBoxSizer:add(ButtSz, Cancel, zxw:flags(wide)), + _ = wxBoxSizer:add(Sizer, InstTx, zxw:flags(wide)), + _ = wxBoxSizer:add(Sizer, AcctSz, zxw:flags(wide)), + _ = wxBoxSizer:add(Sizer, URL_Sz, zxw:flags(wide)), + _ = wxBoxSizer:add(Sizer, MessSz, zxw:flags(wide)), + _ = wxBoxSizer:add(Sizer, ButtSz, zxw:flags(base)), + ok = wxDialog:setSizer(Dialog, Sizer), + ok = wxDialog:setSize(Dialog, {500, 500}), + ok = wxBoxSizer:layout(Sizer), + ok = wxFrame:center(Dialog), + ok = + case wxDialog:showModal(Dialog) of + ?wxID_OK -> gmc_con:sign_tx(Request); + ?wxID_CANCEL -> ok + end, + wxDialog:destroy(Dialog); do_grids_mess_sig2(BadRequest, _) -> tell("Bad request: ~tp", [BadRequest]). + %%% Helpers