-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 <> -> 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, <<< "/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, <<< "/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(QNV), list(QNX), list(QNZ)) -> list({QNV, QNX, QNZ}). 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, <
>))/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:
"/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] "/utf8>> ), gleam_stdlib:println(<<""/utf8>>), gleam_stdlib:println(<<"OPTIONS:"/utf8>>), gleam_stdlib:println( <<" --mode Recovery mode (pubkey|address|candidates|verify:)"/utf8>> ), gleam_stdlib:println( <<" --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>> ).