Packages
signet
1.0.0-beta1
1.6.1
1.6.0
1.5.0
1.4.2
1.4.1
1.4.0
1.3.12
1.3.11
1.3.10
1.3.9
1.3.8
1.3.7
1.3.6
1.3.5
1.3.4
1.3.3
1.3.2
1.3.1
1.3.0
1.2.9
1.2.8
1.2.7
1.2.6
1.2.5
1.2.4
1.2.3
1.2.2
1.2.1
1.2.0
1.1.2
1.1.1
1.1.0
1.0.0-echo8
1.0.0-echo7
1.0.0-echo6
1.0.0-echo5
1.0.0-echo4
1.0.0-echo3
1.0.0-echo2
1.0.0-echo1
1.0.0-delta8
1.0.0-delta7
1.0.0-delta6
1.0.0-delta5
1.0.0-delta4
1.0.0-delta3
1.0.0-delta2
1.0.0-delta1
1.0.0-charlie9
1.0.0-charlie8
1.0.0-charlie7
1.0.0-charlie6
1.0.0-charlie5
1.0.0-charlie4
1.0.0-charlie3
1.0.0-charlie2
1.0.0-charlie1
1.0.0-beta9
1.0.0-beta8
1.0.0-beta7
1.0.0-beta6
1.0.0-beta5
1.0.0-beta4
1.0.0-beta3
1.0.0-beta2
1.0.0-beta1
1.0.0-alpha9
1.0.0-alpha8
1.0.0-alpha7
1.0.0-alpha6
1.0.0-alpha5
1.0.0-alpha4
1.0.0-alpha3
1.0.0-alpha2
1.0.0-alpha10
1.0.0-alpha1
0.2.0-alpha6
0.2.0-alpha5
0.2.0-alpha4
0.2.0-alpha1
0.1.10
0.1.9
0.1.8
0.1.7
0.1.6
0.1.5
0.1.4
0.1.3
0.1.2
0.1.1
0.1.0
0.1.0-rc7
0.1.0-rc6
0.1.0-rc5
0.1.0-rc4
0.1.0-rc3
0.1.0-rc2
0.1.0-rc1
0.1.0-rc0
Lightweight Ethereum and Solana RPC client for Elixir
Current section
Files
Jump to
Current section
Files
lib/signet/recover.ex
defmodule Signet.Recover do
import Signet.Hash, only: [keccak: 1]
import Signet.Util, only: [get_eth_address: 1, decode_hex: 1, encode_hex: 1]
defp decode_signature(s = %Curvy.Signature{}), do: s
defp decode_signature(signature = "0x" <> _signature_hex) do
with {:ok, signature_bytes} <- decode_hex(signature) do
decode_signature(signature_bytes)
end
end
defp decode_signature(<<r::integer-size(256), s::integer-size(256), v::integer-size(8)>>),
do: %Curvy.Signature{
crv: :secp256k1,
r: r,
s: s,
recid:
if v in [0, 1] do
v
else
rem(v + 1, 2)
end
}
@doc """
Prefixes a message with "Etheruem Signed Message" prefix, as per
[EIP-191](https://eips.ethereum.org/EIPS/eip-191).
## Examples
iex> Signet.Recover.prefix_eth("hello")
"\x19Ethereum Signed Message:\\n5hello"
"""
def prefix_eth(msg),
do: "\x19Ethereum Signed Message:\n" <> to_string(String.length(msg)) <> msg
@doc """
Recovers a signer's public key from a signed message. The message will be
digested by keccak first. Note: the rec_id can be embeded in the signature
or passed separately.
## Examples
iex> # Decoded Signature
iex> priv_key = Base.decode16!("800509fa3e80882ad0be77c27505bdc91380f800d51ed80897d22f9fcc75f4bf", case: :mixed)
iex> {:ok, sig} = Signet.Signer.Curvy.sign("test", priv_key)
iex> {:ok, recid} = Signet.Recover.find_recid("test", sig, Base.decode16!("63CC7C25E0CDB121ABB0FE477A6B9901889F99A7"))
iex> Signet.Recover.recover_public_key("test", %{sig|recid: recid}) |> Base.encode16()
"0480076BFB96955526052B2676DFCA87E0B7869CE85E00C5DBCE29E76B8429D6DBF0F33B1A0095B2A9A4D9EA2A9746B122995A5B5874EE3161138C9D19F072B2D9"
iex> # Binary Signature
iex> priv_key = Base.decode16!("800509fa3e80882ad0be77c27505bdc91380f800d51ed80897d22f9fcc75f4bf", case: :mixed)
iex> {:ok, sig} = Signet.Signer.Curvy.sign("test", priv_key)
iex> {:ok, recid} = Signet.Recover.find_recid("test", sig, Base.decode16!("63CC7C25E0CDB121ABB0FE477A6B9901889F99A7"))
iex> signature = <<sig.r::256, sig.s::256, recid>>
iex> Signet.Recover.recover_public_key("test", signature) |> Base.encode16()
"0480076BFB96955526052B2676DFCA87E0B7869CE85E00C5DBCE29E76B8429D6DBF0F33B1A0095B2A9A4D9EA2A9746B122995A5B5874EE3161138C9D19F072B2D9"
iex> # EIP-155 Signature
iex> priv_key = Base.decode16!("800509fa3e80882ad0be77c27505bdc91380f800d51ed80897d22f9fcc75f4bf", case: :mixed)
iex> {:ok, sig} = Signet.Signer.Curvy.sign("test", priv_key)
iex> {:ok, recid} = Signet.Recover.find_recid("test", sig, Base.decode16!("63CC7C25E0CDB121ABB0FE477A6B9901889F99A7"))
iex> recovery_bit = 35 + 5 * 2 + recid
iex> signature = <<sig.r::256, sig.s::256, recovery_bit::8>>
iex> Signet.Recover.recover_public_key("test", signature) |> Base.encode16()
"0480076BFB96955526052B2676DFCA87E0B7869CE85E00C5DBCE29E76B8429D6DBF0F33B1A0095B2A9A4D9EA2A9746B122995A5B5874EE3161138C9D19F072B2D9"
"""
def recover_public_key(message, signature) do
signature
|> decode_signature()
|> Curvy.recover_key(keccak(message), hash: :keccak)
|> Curvy.Key.to_pubkey(compressed: false)
end
@doc """
Recovers a signer's Ethereum address from a signed message. The message will
be digested by keccak first. Note: the rec_id can be embeded in the signature
or passed separately.
## Examples
iex> priv_key = Base.decode16!("800509fa3e80882ad0be77c27505bdc91380f800d51ed80897d22f9fcc75f4bf", case: :mixed)
iex> {:ok, sig} = Signet.Signer.Curvy.sign("test", priv_key)
iex> {:ok, recid} = Signet.Recover.find_recid("test", sig, Base.decode16!("63CC7C25E0CDB121ABB0FE477A6B9901889F99A7"))
iex> Signet.Recover.recover_eth("test", %{sig|recid: recid}) |> Base.encode16()
"63CC7C25E0CDB121ABB0FE477A6B9901889F99A7"
"""
def recover_eth(message, signature) do
message
|> recover_public_key(signature)
|> get_eth_address()
end
@doc """
Finds the given recid which recovers the given signature for the message to the given
Ethereum address. This is a very simple guess-check-revise since there are only four
possible values, and we only accept two of those.
## Examples
iex> priv_key = Base.decode16!("800509fa3e80882ad0be77c27505bdc91380f800d51ed80897d22f9fcc75f4bf", case: :mixed)
iex> {:ok, sig} = Signet.Signer.Curvy.sign("test", priv_key)
iex> {:ok, recid} = Signet.Recover.find_recid("test", sig, Base.decode16!("63CC7C25E0CDB121ABB0FE477A6B9901889F99A7"))
iex> Signet.Recover.recover_eth("test", %{sig|recid: recid}) |> Base.encode16()
"63CC7C25E0CDB121ABB0FE477A6B9901889F99A7"
"""
def find_recid(message, signature, address) do
recid =
Enum.find(0..3, fn recid ->
Signet.Recover.recover_eth(message, %{signature | recid: recid}) == address
end)
case recid do
nil ->
{:error, "unable to recover to address #{encode_hex(address)}"}
x when x > 1 ->
{:error, "too high recovery bit #{recid}"}
_ ->
{:ok, recid}
end
end
end