Current section
Files
Jump to
Current section
Files
src/gleeth@crypto@secp256k1.erl
-module(gleeth@crypto@secp256k1).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/gleeth/crypto/secp256k1.gleam").
-export([private_key_from_bytes/1, private_key_from_hex/1, private_key_to_hex/1, private_key_to_bytes/1, create_public_key/1, public_key_to_hex/1, public_key_to_bytes/1, public_key_to_address/1, private_key_to_address/1, address_to_string/1, sign_message_hash/2, sign_message/2, sign_personal_message/2, signature_to_compact/1, signature_to_hex/1, signature_to_vrs/1, signature_from_vrs/3, verify_signature/3, is_valid_private_key/1, generate_private_key/0, recover_public_key/2, recover_public_key_candidates/3, recover_address/2, recover_address_candidates/3, verify_signature_recovery/3, find_recovery_id/4, recover_public_key_compact/3, recover_address_compact/3]).
-export_type([signature/0, private_key/0, public_key/0, ethereum_address/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 signature() :: {signature, bitstring(), bitstring(), integer()}.
-type private_key() :: {private_key, bitstring()}.
-type public_key() :: {public_key, bitstring()}.
-type ethereum_address() :: {ethereum_address, binary()}.
-file("src/gleeth/crypto/secp256k1.gleam", 34).
?DOC(
" Create a private key from a 32-byte BitArray\n"
" The private key must be exactly 32 bytes (256 bits)\n"
).
-spec private_key_from_bytes(bitstring()) -> {ok, private_key()} |
{error, binary()}.
private_key_from_bytes(Bytes) ->
case erlang:byte_size(Bytes) of
32 ->
{ok, {private_key, Bytes}};
Size ->
{error,
<<"Private key must be exactly 32 bytes, got "/utf8,
(gleam@string:inspect(Size))/binary>>}
end.
-file("src/gleeth/crypto/secp256k1.gleam", 46).
?DOC(
" Create a private key from a hex string (with or without 0x prefix)\n"
" The hex string must represent exactly 32 bytes (64 hex characters)\n"
).
-spec private_key_from_hex(binary()) -> {ok, private_key()} | {error, binary()}.
private_key_from_hex(Hex_string) ->
gleam@result:'try'(
gleeth@utils@hex:decode(Hex_string),
fun(Bytes) -> private_key_from_bytes(Bytes) end
).
-file("src/gleeth/crypto/secp256k1.gleam", 52).
?DOC(" Convert a private key to hex string with 0x prefix\n").
-spec private_key_to_hex(private_key()) -> binary().
private_key_to_hex(Private_key) ->
{private_key, Key} = Private_key,
<<"0x"/utf8,
(begin
_pipe = gleam_stdlib:base16_encode(Key),
string:lowercase(_pipe)
end)/binary>>.
-file("src/gleeth/crypto/secp256k1.gleam", 58).
?DOC(" Extract the raw bytes from a private key\n").
-spec private_key_to_bytes(private_key()) -> bitstring().
private_key_to_bytes(Private_key) ->
{private_key, Key} = Private_key,
Key.
-file("src/gleeth/crypto/secp256k1.gleam", 68).
?DOC(" Create a public key from a private key\n").
-spec create_public_key(private_key()) -> {ok, public_key()} | {error, binary()}.
create_public_key(Private_key) ->
{private_key, Key} = Private_key,
case 'Elixir.ExSecp256k1':create_public_key(Key) of
{ok, Pub_key} ->
{ok, {public_key, Pub_key}};
{error, Err} ->
{error,
<<"Failed to create public key: "/utf8,
(gleam@string:inspect(Err))/binary>>}
end.
-file("src/gleeth/crypto/secp256k1.gleam", 77).
?DOC(" Convert a public key to hex string with 0x prefix\n").
-spec public_key_to_hex(public_key()) -> binary().
public_key_to_hex(Public_key) ->
{public_key, Key} = Public_key,
<<"0x"/utf8,
(begin
_pipe = gleam_stdlib:base16_encode(Key),
string:lowercase(_pipe)
end)/binary>>.
-file("src/gleeth/crypto/secp256k1.gleam", 83).
?DOC(" Extract the raw bytes from a public key\n").
-spec public_key_to_bytes(public_key()) -> bitstring().
public_key_to_bytes(Public_key) ->
{public_key, Key} = Public_key,
Key.
-file("src/gleeth/crypto/secp256k1.gleam", 94).
?DOC(
" Generate an Ethereum address from a public key\n"
" Uses the last 20 bytes of keccak256(public_key) as the address\n"
).
-spec public_key_to_address(public_key()) -> {ok, ethereum_address()} |
{error, binary()}.
public_key_to_address(Public_key) ->
{public_key, Key} = Public_key,
Public_key_bytes = case erlang:byte_size(Key) of
65 ->
case gleam_stdlib:bit_array_slice(Key, 1, 64) of
{ok, Sliced} ->
Sliced;
{error, _} ->
Key
end;
64 ->
Key;
_ ->
Key
end,
Hash = gleeth@crypto@keccak:keccak256_binary(Public_key_bytes),
case gleam_stdlib:bit_array_slice(Hash, 12, 20) of
{ok, Address_bytes} ->
Address_hex = begin
_pipe = gleam_stdlib:base16_encode(Address_bytes),
string:lowercase(_pipe)
end,
{ok, {ethereum_address, <<"0x"/utf8, Address_hex/binary>>}};
{error, _} ->
{error, <<"Failed to extract address from hash"/utf8>>}
end.
-file("src/gleeth/crypto/secp256k1.gleam", 129).
?DOC(" Generate an Ethereum address directly from a private key\n").
-spec private_key_to_address(private_key()) -> {ok, ethereum_address()} |
{error, binary()}.
private_key_to_address(Private_key) ->
gleam@result:'try'(
create_public_key(Private_key),
fun(Public_key) -> public_key_to_address(Public_key) end
).
-file("src/gleeth/crypto/secp256k1.gleam", 137).
?DOC(" Extract the address string from an EthereumAddress\n").
-spec address_to_string(ethereum_address()) -> binary().
address_to_string(Address) ->
{ethereum_address, Addr} = Address,
Addr.
-file("src/gleeth/crypto/secp256k1.gleam", 148).
?DOC(
" Sign a message hash with a private key\n"
" The message should already be hashed (e.g., with keccak256)\n"
).
-spec sign_message_hash(bitstring(), private_key()) -> {ok, signature()} |
{error, binary()}.
sign_message_hash(Message_hash, Private_key) ->
{private_key, Key} = Private_key,
case secp256k1_gleam:sign(Message_hash, Key) of
{ok, Sig} ->
{ok,
{signature,
erlang:element(2, Sig),
erlang:element(3, Sig),
erlang:element(4, Sig)}};
{error, Err} ->
{error,
<<"Failed to sign message: "/utf8,
(gleam@string:inspect(Err))/binary>>}
end.
-file("src/gleeth/crypto/secp256k1.gleam", 161).
?DOC(" Sign raw message bytes (will be hashed with keccak256)\n").
-spec sign_message(bitstring(), private_key()) -> {ok, signature()} |
{error, binary()}.
sign_message(Message, Private_key) ->
Message_hash = gleeth@crypto@keccak:keccak256_binary(Message),
sign_message_hash(Message_hash, Private_key).
-file("src/gleeth/crypto/secp256k1.gleam", 170).
?DOC(" Sign an Ethereum personal message (prefixed with \"\\x19Ethereum Signed Message:\\n\")\n").
-spec sign_personal_message(binary(), private_key()) -> {ok, signature()} |
{error, binary()}.
sign_personal_message(Message, Private_key) ->
Message_bytes = gleam_stdlib:identity(Message),
Message_length = begin
_pipe = erlang:byte_size(Message_bytes),
gleam@string:inspect(_pipe)
end,
Prefix = <<"\\x19Ethereum Signed Message:\\n"/utf8, Message_length/binary>>,
Prefix_bytes = gleam_stdlib:identity(Prefix),
Full_message = gleam@bit_array:append(Prefix_bytes, Message_bytes),
sign_message(Full_message, Private_key).
-file("src/gleeth/crypto/secp256k1.gleam", 190).
?DOC(
" Convert signature to compact format (r + s + v)\n"
" Used for Ethereum transaction signatures\n"
).
-spec signature_to_compact(signature()) -> bitstring().
signature_to_compact(Signature) ->
{signature, R, S, Recovery_id} = Signature,
V_byte = case Recovery_id of
0 ->
<<27>>;
1 ->
<<28>>;
_ ->
<<27>>
end,
<<R/bitstring, S/bitstring, V_byte/bitstring>>.
-file("src/gleeth/crypto/secp256k1.gleam", 205).
?DOC(" Convert signature to hex string with 0x prefix\n").
-spec signature_to_hex(signature()) -> binary().
signature_to_hex(Signature) ->
Compact = signature_to_compact(Signature),
<<"0x"/utf8,
(begin
_pipe = gleam_stdlib:base16_encode(Compact),
string:lowercase(_pipe)
end)/binary>>.
-file("src/gleeth/crypto/secp256k1.gleam", 211).
?DOC(" Extract v, r, s components for Ethereum transactions\n").
-spec signature_to_vrs(signature()) -> {integer(), binary(), binary()}.
signature_to_vrs(Signature) ->
{signature, R, S, Recovery_id} = Signature,
V = Recovery_id + 27,
R_hex = <<"0x"/utf8,
(begin
_pipe = gleam_stdlib:base16_encode(R),
string:lowercase(_pipe)
end)/binary>>,
S_hex = <<"0x"/utf8,
(begin
_pipe@1 = gleam_stdlib:base16_encode(S),
string:lowercase(_pipe@1)
end)/binary>>,
{V, R_hex, S_hex}.
-file("src/gleeth/crypto/secp256k1.gleam", 223).
?DOC(" Create signature from v, r, s components\n").
-spec signature_from_vrs(integer(), binary(), binary()) -> {ok, signature()} |
{error, binary()}.
signature_from_vrs(V, R, S) ->
gleam@result:'try'(
gleeth@utils@hex:decode(R),
fun(R_bytes) ->
gleam@result:'try'(
gleeth@utils@hex:decode(S),
fun(S_bytes) ->
Recovery_id = case V of
27 ->
0;
28 ->
1;
_ ->
V - 27
end,
{ok, {signature, R_bytes, S_bytes, Recovery_id}}
end
)
end
).
-file("src/gleeth/crypto/secp256k1.gleam", 246).
?DOC(" Verify a signature against a message hash and public key\n").
-spec verify_signature(bitstring(), signature(), public_key()) -> {ok,
boolean()} |
{error, binary()}.
verify_signature(Message_hash, Signature, Public_key) ->
{signature, R, S, _} = Signature,
{public_key, Key} = Public_key,
Signature_obj = {signature, R, S, erlang:element(4, Signature)},
case secp256k1_gleam:verify(Message_hash, Signature_obj, Key) of
{ok, _} ->
{ok, true};
{error, _} ->
{ok, false}
end.
-file("src/gleeth/crypto/secp256k1.gleam", 344).
?DOC(" Helper to convert public keys to addresses\n").
-spec convert_keys_to_addresses(list(public_key()), list(ethereum_address())) -> {ok,
list(ethereum_address())} |
{error, binary()}.
convert_keys_to_addresses(Public_keys, Acc) ->
case Public_keys of
[] ->
{ok, Acc};
[Public_key | Rest] ->
case public_key_to_address(Public_key) of
{ok, Address} ->
convert_keys_to_addresses(Rest, [Address | Acc]);
{error, Err} ->
{error, Err}
end
end.
-file("src/gleeth/crypto/secp256k1.gleam", 418).
?DOC(" Check if a private key is valid (non-zero and within secp256k1 curve order)\n").
-spec is_valid_private_key(private_key()) -> boolean().
is_valid_private_key(Private_key) ->
{private_key, Key} = Private_key,
case erlang:byte_size(Key) of
32 ->
Key /= <<0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0>>;
_ ->
false
end.
-file("src/gleeth/crypto/secp256k1.gleam", 435).
?DOC(
" Generate a random private key (placeholder - would need proper randomness)\n"
" This is a placeholder implementation - in production, use proper cryptographic randomness\n"
).
-spec generate_private_key() -> {ok, private_key()} | {error, binary()}.
generate_private_key() ->
{error,
<<"Random private key generation not implemented - use external source"/utf8>>}.
-file("src/gleeth/crypto/secp256k1.gleam", 267).
?DOC(" Recover the public key from a signature and message hash\n").
-spec recover_public_key(bitstring(), signature()) -> {ok, public_key()} |
{error, binary()}.
recover_public_key(Message_hash, Signature) ->
{signature, R, S, Recovery_id} = Signature,
case 'Elixir.ExSecp256k1':recover(Message_hash, R, S, Recovery_id) of
{ok, Public_key_bytes} ->
{ok, {public_key, Public_key_bytes}};
{error, Err} ->
{error,
<<"Failed to recover public key: "/utf8,
(gleam@string:inspect(Err))/binary>>}
end.
-file("src/gleeth/crypto/secp256k1.gleam", 292).
?DOC(" Helper function to recover candidates for each recovery ID\n").
-spec recover_candidates_helper(
bitstring(),
bitstring(),
bitstring(),
list(integer()),
list(public_key())
) -> {ok, list(public_key())} | {error, binary()}.
recover_candidates_helper(Message_hash, R, S, Recovery_ids, Acc) ->
case Recovery_ids of
[] ->
{ok, Acc};
[Recovery_id | Rest] ->
case 'Elixir.ExSecp256k1':recover(Message_hash, R, S, Recovery_id) of
{ok, Public_key_bytes} ->
Public_key = {public_key, Public_key_bytes},
recover_candidates_helper(
Message_hash,
R,
S,
Rest,
[Public_key | Acc]
);
{error, _} ->
recover_candidates_helper(Message_hash, R, S, Rest, Acc)
end
end.
-file("src/gleeth/crypto/secp256k1.gleam", 281).
?DOC(
" Recover multiple public key candidates (all 4 possible recovery IDs)\n"
" This is useful when the recovery ID is unknown or needs to be determined\n"
).
-spec recover_public_key_candidates(bitstring(), bitstring(), bitstring()) -> {ok,
list(public_key())} |
{error, binary()}.
recover_public_key_candidates(Message_hash, R, S) ->
Recovery_ids = [0, 1, 2, 3],
recover_candidates_helper(Message_hash, R, S, Recovery_ids, []).
-file("src/gleeth/crypto/secp256k1.gleam", 320).
?DOC(" Recover Ethereum address directly from signature and message hash\n").
-spec recover_address(bitstring(), signature()) -> {ok, ethereum_address()} |
{error, binary()}.
recover_address(Message_hash, Signature) ->
gleam@result:'try'(
recover_public_key(Message_hash, Signature),
fun(Public_key) -> public_key_to_address(Public_key) end
).
-file("src/gleeth/crypto/secp256k1.gleam", 329).
?DOC(" Recover multiple address candidates (all possible recovery IDs)\n").
-spec recover_address_candidates(bitstring(), bitstring(), bitstring()) -> {ok,
list(ethereum_address())} |
{error, binary()}.
recover_address_candidates(Message_hash, R, S) ->
gleam@result:'try'(
recover_public_key_candidates(Message_hash, R, S),
fun(Public_keys) -> convert_keys_to_addresses(Public_keys, []) end
).
-file("src/gleeth/crypto/secp256k1.gleam", 360).
?DOC(" Verify signature recovery by checking if recovered address matches expected\n").
-spec verify_signature_recovery(bitstring(), signature(), binary()) -> {ok,
boolean()} |
{error, binary()}.
verify_signature_recovery(Message_hash, Signature, Expected_address) ->
gleam@result:'try'(
recover_address(Message_hash, Signature),
fun(Recovered_address) ->
Recovered_address_str = address_to_string(Recovered_address),
Expected_lower = string:lowercase(Expected_address),
Recovered_lower = string:lowercase(Recovered_address_str),
{ok, Expected_lower =:= Recovered_lower}
end
).
-file("src/gleeth/crypto/secp256k1.gleam", 389).
?DOC(" Helper function to find the correct recovery ID\n").
-spec find_recovery_id_helper(
bitstring(),
bitstring(),
bitstring(),
binary(),
list(integer())
) -> {ok, integer()} | {error, binary()}.
find_recovery_id_helper(Message_hash, R, S, Expected_address, Recovery_ids) ->
case Recovery_ids of
[] ->
{error,
<<"No valid recovery ID found for the expected address"/utf8>>};
[Recovery_id | Rest] ->
Signature = {signature, R, S, Recovery_id},
case verify_signature_recovery(
Message_hash,
Signature,
Expected_address
) of
{ok, true} ->
{ok, Recovery_id};
{ok, false} ->
find_recovery_id_helper(
Message_hash,
R,
S,
Expected_address,
Rest
);
{error, _} ->
find_recovery_id_helper(
Message_hash,
R,
S,
Expected_address,
Rest
)
end
end.
-file("src/gleeth/crypto/secp256k1.gleam", 377).
?DOC(
" Find the correct recovery ID for a given signature and expected address\n"
" This is useful when you have r,s components but need to determine the recovery ID\n"
).
-spec find_recovery_id(bitstring(), bitstring(), bitstring(), binary()) -> {ok,
integer()} |
{error, binary()}.
find_recovery_id(Message_hash, R, S, Expected_address) ->
Recovery_ids = [0, 1, 2, 3],
find_recovery_id_helper(Message_hash, R, S, Expected_address, Recovery_ids).
-file("src/gleeth/crypto/secp256k1.gleam", 461).
?DOC(" Recover public key from compact signature format\n").
-spec recover_public_key_compact(bitstring(), bitstring(), integer()) -> {ok,
public_key()} |
{error, binary()}.
recover_public_key_compact(Message_hash, Compact_signature, Recovery_id) ->
case 'Elixir.ExSecp256k1':recover_compact(
Message_hash,
Compact_signature,
Recovery_id
) of
{ok, Public_key_bytes} ->
{ok, {public_key, Public_key_bytes}};
{error, Err} ->
{error,
<<"Failed to recover public key from compact signature: "/utf8,
(gleam@string:inspect(Err))/binary>>}
end.
-file("src/gleeth/crypto/secp256k1.gleam", 477).
?DOC(" Recover address from compact signature format\n").
-spec recover_address_compact(bitstring(), bitstring(), integer()) -> {ok,
ethereum_address()} |
{error, binary()}.
recover_address_compact(Message_hash, Compact_signature, Recovery_id) ->
gleam@result:'try'(
recover_public_key_compact(Message_hash, Compact_signature, Recovery_id),
fun(Public_key) -> public_key_to_address(Public_key) end
).