Current section
Files
Jump to
Current section
Files
src/gleeth@commands@recover.erl
-module(gleeth@commands@recover).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/gleeth/commands/recover.gleam").
-export([execute_recovery/1, validate_options/1, default_options/0, parse_recovery_mode/1, parse_output_format/1, print_usage/0]).
-export_type([recover_options/0, recovery_mode/0, output_format/0, recovery_result/0]).
-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.
-type recover_options() :: {recover_options,
binary(),
binary(),
recovery_mode(),
output_format()}.
-type recovery_mode() :: recover_public_key |
recover_address |
recover_candidates |
{verify_address, binary()}.
-type output_format() :: compact | detailed | json.
-type recovery_result() :: {public_key_result, binary()} |
{address_result, binary()} |
{candidates_result, list(binary()), list(binary()), list(integer())} |
{verification_result, boolean(), binary()}.
-file("src/gleeth/commands/recover.gleam", 127).
?DOC(
" Prepare message hash from input string\n"
" Supports both raw messages and pre-hashed inputs\n"
).
-spec prepare_message_hash(binary()) -> {ok, bitstring()} | {error, binary()}.
prepare_message_hash(Message) ->
case gleam_stdlib:string_starts_with(Message, <<"0x"/utf8>>) andalso (string:length(
Message
)
=:= 66) of
true ->
gleam@result:'try'(
gleeth@utils@hex:decode(Message),
fun(Hash_bytes) -> case erlang:byte_size(Hash_bytes) of
32 ->
{ok, Hash_bytes};
_ ->
{error, <<"Hash must be exactly 32 bytes"/utf8>>}
end end
);
false ->
Message_bytes = gleam_stdlib:identity(Message),
{ok, gleeth@crypto@keccak:keccak256_binary(Message_bytes)}
end.
-file("src/gleeth/commands/recover.gleam", 146).
?DOC(" Parse signature from hex string into Signature type\n").
-spec parse_signature(binary()) -> {ok, gleeth@crypto@secp256k1:signature()} |
{error, binary()}.
parse_signature(Signature_hex) ->
gleam@result:'try'(
gleeth@utils@hex:decode(Signature_hex),
fun(Signature_bytes) -> case erlang:byte_size(Signature_bytes) of
65 ->
gleam@result:'try'(
begin
_pipe = gleam_stdlib:bit_array_slice(
Signature_bytes,
0,
32
),
gleam@result:map_error(
_pipe,
fun(_) ->
<<"Failed to extract r component"/utf8>>
end
)
end,
fun(R) ->
gleam@result:'try'(
begin
_pipe@1 = gleam_stdlib:bit_array_slice(
Signature_bytes,
32,
32
),
gleam@result:map_error(
_pipe@1,
fun(_) ->
<<"Failed to extract s component"/utf8>>
end
)
end,
fun(S) ->
gleam@result:'try'(
begin
_pipe@2 = gleam_stdlib:bit_array_slice(
Signature_bytes,
64,
1
),
gleam@result:map_error(
_pipe@2,
fun(_) ->
<<"Failed to extract v component"/utf8>>
end
)
end,
fun(V_bytes) -> case V_bytes of
<<V>> ->
Recovery_id = case V of
27 ->
0;
28 ->
1;
_ ->
V - 27
end,
{ok,
{signature,
R,
S,
Recovery_id}};
_ ->
{error,
<<"Invalid v component format"/utf8>>}
end end
)
end
)
end
);
64 ->
{error,
<<"64-byte signature requires separate recovery ID. Use 65-byte format (r+s+v) or specify recovery ID."/utf8>>};
_ ->
{error,
<<"Signature must be 65 bytes (r+s+v format)"/utf8>>}
end end
).
-file("src/gleeth/commands/recover.gleam", 209).
?DOC(" Compact output format\n").
-spec format_compact(recovery_result()) -> {ok, binary()} | {error, binary()}.
format_compact(Result) ->
case Result of
{public_key_result, Public_key} ->
{ok, Public_key};
{address_result, Address} ->
{ok, Address};
{candidates_result, Keys, Addrs, _} ->
Combined = gleam@list:zip(Keys, Addrs),
Lines = gleam@list:map(
Combined,
fun(Pair) ->
{Key, Addr} = Pair,
<<<<Key/binary, " -> "/utf8>>/binary, Addr/binary>>
end
),
{ok, gleam@string:join(Lines, <<"\n"/utf8>>)};
{verification_result, Valid, Addr@1} ->
Status = case Valid of
true ->
<<"VALID"/utf8>>;
false ->
<<"INVALID"/utf8>>
end,
{ok, <<<<Status/binary, " -> "/utf8>>/binary, Addr@1/binary>>}
end.
-file("src/gleeth/commands/recover.gleam", 334).
?DOC(" Create a list of tuples from three lists (zip3 implementation)\n").
-spec zip3(list(MGW), list(MGY), list(MHA)) -> list({MGW, MGY, MHA}).
zip3(List1, List2, List3) ->
case {List1, List2, List3} of
{[A | Rest_a], [B | Rest_b], [C | Rest_c]} ->
[{A, B, C} | zip3(Rest_a, Rest_b, Rest_c)];
{_, _, _} ->
[]
end.
-file("src/gleeth/commands/recover.gleam", 233).
?DOC(" Detailed output format\n").
-spec format_detailed(recovery_result()) -> {ok, binary()} | {error, binary()}.
format_detailed(Result) ->
case Result of
{public_key_result, Public_key} ->
{ok, <<"Recovered Public Key:\n"/utf8, Public_key/binary>>};
{address_result, Address} ->
{ok, <<"Recovered Address:\n"/utf8, Address/binary>>};
{candidates_result, Keys, Addrs, Ids} ->
Header = <<<<"Recovery Candidates:\n"/utf8,
(gleam@string:repeat(<<"="/utf8>>, 50))/binary>>/binary,
"\n"/utf8>>,
Candidates = zip3(Ids, Keys, Addrs),
Lines = gleam@list:map(
Candidates,
fun(Candidate) ->
{Id, Key, Addr} = Candidate,
<<<<<<<<<<<<<<<<"Recovery ID "/utf8,
(gleam@string:inspect(Id))/binary>>/binary,
":\n"/utf8>>/binary,
" Public Key: "/utf8>>/binary,
Key/binary>>/binary,
"\n"/utf8>>/binary,
" Address: "/utf8>>/binary,
Addr/binary>>/binary,
"\n"/utf8>>
end
),
{ok,
<<Header/binary,
(gleam@string:join(Lines, <<"\n"/utf8>>))/binary>>};
{verification_result, Valid, Addr@1} ->
Status = case Valid of
true ->
<<"✓ SIGNATURE VALID"/utf8>>;
false ->
<<"✗ SIGNATURE INVALID"/utf8>>
end,
{ok,
<<<<<<<<"Signature Verification Result:\n"/utf8, Status/binary>>/binary,
"\n"/utf8>>/binary,
"Recovered Address: "/utf8>>/binary,
Addr@1/binary>>}
end.
-file("src/gleeth/commands/recover.gleam", 281).
?DOC(" JSON output format\n").
-spec format_json(recovery_result()) -> {ok, binary()} | {error, binary()}.
format_json(Result) ->
case Result of
{public_key_result, Public_key} ->
{ok,
<<<<"{\"type\":\"public_key\",\"result\":\""/utf8,
Public_key/binary>>/binary,
"\"}"/utf8>>};
{address_result, Address} ->
{ok,
<<<<"{\"type\":\"address\",\"result\":\""/utf8, Address/binary>>/binary,
"\"}"/utf8>>};
{candidates_result, Keys, Addrs, Ids} ->
Candidates = zip3(Ids, Keys, Addrs),
Candidate_jsons = gleam@list:map(
Candidates,
fun(Candidate) ->
{Id, Key, Addr} = Candidate,
<<<<<<<<<<<<<<"{\"recovery_id\":"/utf8,
(gleam@string:inspect(Id))/binary>>/binary,
",\"public_key\":\""/utf8>>/binary,
Key/binary>>/binary,
"\""/utf8>>/binary,
",\"address\":\""/utf8>>/binary,
Addr/binary>>/binary,
"\"}"/utf8>>
end
),
{ok,
<<<<"{\"type\":\"candidates\",\"results\":["/utf8,
(gleam@string:join(Candidate_jsons, <<","/utf8>>))/binary>>/binary,
"]}"/utf8>>};
{verification_result, Valid, Addr@1} ->
Valid_str = case Valid of
true ->
<<"true"/utf8>>;
false ->
<<"false"/utf8>>
end,
{ok,
<<<<<<<<"{\"type\":\"verification\",\"is_valid\":"/utf8,
Valid_str/binary>>/binary,
",\"recovered_address\":\""/utf8>>/binary,
Addr@1/binary>>/binary,
"\"}"/utf8>>}
end.
-file("src/gleeth/commands/recover.gleam", 197).
?DOC(" Format recovery results for output\n").
-spec format_output(recovery_result(), output_format()) -> {ok, binary()} |
{error, binary()}.
format_output(Result, Format) ->
case Format of
compact ->
format_compact(Result);
detailed ->
format_detailed(Result);
json ->
format_json(Result)
end.
-file("src/gleeth/commands/recover.gleam", 51).
?DOC(" Execute signature recovery with given options\n").
-spec execute_recovery(recover_options()) -> {ok, binary()} | {error, binary()}.
execute_recovery(Options) ->
gleam@result:'try'(
prepare_message_hash(erlang:element(2, Options)),
fun(Message_hash) ->
gleam@result:'try'(
parse_signature(erlang:element(3, Options)),
fun(Signature) -> case erlang:element(4, Options) of
recover_public_key ->
gleam@result:'try'(
gleeth@crypto@secp256k1:recover_public_key(
Message_hash,
Signature
),
fun(Public_key) ->
Result = {public_key_result,
gleeth@crypto@secp256k1:public_key_to_hex(
Public_key
)},
format_output(
Result,
erlang:element(5, Options)
)
end
);
recover_address ->
gleam@result:'try'(
gleeth@crypto@secp256k1:recover_address(
Message_hash,
Signature
),
fun(Address) ->
Result@1 = {address_result,
gleeth@crypto@secp256k1:address_to_string(
Address
)},
format_output(
Result@1,
erlang:element(5, Options)
)
end
);
recover_candidates ->
{signature, R, S, _} = Signature,
gleam@result:'try'(
gleeth@crypto@secp256k1:recover_public_key_candidates(
Message_hash,
R,
S
),
fun(Public_keys) ->
gleam@result:'try'(
gleeth@crypto@secp256k1:recover_address_candidates(
Message_hash,
R,
S
),
fun(Addresses) ->
Recovery_ids = [0, 1, 2, 3],
Public_key_hexes = gleam@list:map(
Public_keys,
fun gleeth@crypto@secp256k1:public_key_to_hex/1
),
Address_strings = gleam@list:map(
Addresses,
fun gleeth@crypto@secp256k1:address_to_string/1
),
Result@2 = {candidates_result,
Public_key_hexes,
Address_strings,
Recovery_ids},
format_output(
Result@2,
erlang:element(5, Options)
)
end
)
end
);
{verify_address, Expected} ->
gleam@result:'try'(
gleeth@crypto@secp256k1:verify_signature_recovery(
Message_hash,
Signature,
Expected
),
fun(Is_valid) ->
gleam@result:'try'(
gleeth@crypto@secp256k1:recover_address(
Message_hash,
Signature
),
fun(Recovered_address) ->
Result@3 = {verification_result,
Is_valid,
gleeth@crypto@secp256k1:address_to_string(
Recovered_address
)},
format_output(
Result@3,
erlang:element(5, Options)
)
end
)
end
)
end end
)
end
).
-file("src/gleeth/commands/recover.gleam", 349).
?DOC(" Validate recovery options\n").
-spec validate_options(recover_options()) -> {ok, nil} | {error, binary()}.
validate_options(Options) ->
gleam@result:'try'(case gleam@string:is_empty(erlang:element(2, Options)) of
true ->
{error, <<"Message cannot be empty"/utf8>>};
false ->
{ok, nil}
end, fun(_) ->
gleam@result:'try'(
case gleeth@utils@hex:is_valid_hex_chars(
erlang:element(3, Options)
) of
true ->
{ok, nil};
false ->
{error, <<"Signature must be valid hex string"/utf8>>}
end,
fun(_) ->
gleam@result:'try'(
gleeth@utils@hex:decode(erlang:element(3, Options)),
fun(Signature_bytes) ->
case erlang:byte_size(Signature_bytes) of
65 ->
{ok, nil};
_ ->
{error,
<<"Signature must be 65 bytes (130 hex characters)"/utf8>>}
end
end
)
end
)
end).
-file("src/gleeth/commands/recover.gleam", 371).
?DOC(" Create recovery options with defaults\n").
-spec default_options() -> recover_options().
default_options() ->
{recover_options, <<""/utf8>>, <<""/utf8>>, recover_address, detailed}.
-file("src/gleeth/commands/recover.gleam", 385).
?DOC(" Parse recovery mode from string\n").
-spec parse_recovery_mode(binary()) -> {ok, recovery_mode()} | {error, binary()}.
parse_recovery_mode(Mode_str) ->
case string:lowercase(Mode_str) of
<<"pubkey"/utf8>> ->
{ok, recover_public_key};
<<"public-key"/utf8>> ->
{ok, recover_public_key};
<<"public_key"/utf8>> ->
{ok, recover_public_key};
<<"address"/utf8>> ->
{ok, recover_address};
<<"addr"/utf8>> ->
{ok, recover_address};
<<"candidates"/utf8>> ->
{ok, recover_candidates};
<<"all"/utf8>> ->
{ok, recover_candidates};
Other ->
case gleam_stdlib:string_starts_with(Other, <<"verify:"/utf8>>) of
true ->
Address = gleam@string:drop_start(Other, 7),
case gleeth@utils@hex:is_valid_hex_chars(Address) andalso (string:length(
Address
)
>= 40) of
true ->
{ok, {verify_address, Address}};
false ->
{error,
<<"Invalid address for verification: "/utf8,
Address/binary>>}
end;
false ->
{error,
<<<<"Invalid recovery mode: "/utf8, Mode_str/binary>>/binary,
". Valid modes: pubkey, address, candidates, verify:<address>"/utf8>>}
end
end.
-file("src/gleeth/commands/recover.gleam", 411).
?DOC(" Parse output format from string\n").
-spec parse_output_format(binary()) -> {ok, output_format()} | {error, binary()}.
parse_output_format(Format_str) ->
case string:lowercase(Format_str) of
<<"compact"/utf8>> ->
{ok, compact};
<<"c"/utf8>> ->
{ok, compact};
<<"detailed"/utf8>> ->
{ok, detailed};
<<"detail"/utf8>> ->
{ok, detailed};
<<"d"/utf8>> ->
{ok, detailed};
<<"json"/utf8>> ->
{ok, json};
<<"j"/utf8>> ->
{ok, json};
_ ->
{error,
<<<<"Invalid format: "/utf8, Format_str/binary>>/binary,
". Valid formats: compact, detailed, json"/utf8>>}
end.
-file("src/gleeth/commands/recover.gleam", 426).
?DOC(" Print usage information\n").
-spec print_usage() -> nil.
print_usage() ->
gleam_stdlib:println(<<"Signature Recovery Commands:"/utf8>>),
gleam_stdlib:println(<<""/utf8>>),
gleam_stdlib:println(
<<" gleeth recover [OPTIONS] <MESSAGE> <SIGNATURE>"/utf8>>
),
gleam_stdlib:println(<<""/utf8>>),
gleam_stdlib:println(<<"OPTIONS:"/utf8>>),
gleam_stdlib:println(
<<" --mode <MODE> Recovery mode (pubkey|address|candidates|verify:<addr>)"/utf8>>
),
gleam_stdlib:println(
<<" --format <FORMAT> Output format (compact|detailed|json)"/utf8>>
),
gleam_stdlib:println(<<""/utf8>>),
gleam_stdlib:println(<<"EXAMPLES:"/utf8>>),
gleam_stdlib:println(<<" # Recover address from signature"/utf8>>),
gleam_stdlib:println(
<<" gleeth recover --mode address \"Hello\" 0x123...abc"/utf8>>
),
gleam_stdlib:println(<<""/utf8>>),
gleam_stdlib:println(<<" # Show all recovery candidates"/utf8>>),
gleam_stdlib:println(
<<" gleeth recover --mode candidates \"Hello\" 0x123...abc"/utf8>>
),
gleam_stdlib:println(<<""/utf8>>),
gleam_stdlib:println(
<<" # Verify signature against expected address"/utf8>>
),
gleam_stdlib:println(
<<" gleeth recover --mode verify:0xf39fd... \"Hello\" 0x123...abc"/utf8>>
),
gleam_stdlib:println(<<""/utf8>>),
gleam_stdlib:println(<<" # Recover public key in JSON format"/utf8>>),
gleam_stdlib:println(
<<" gleeth recover --mode pubkey --format json \"Hello\" 0x123...abc"/utf8>>
).