Onchain.Signer.Secp256k1 (onchain v0.16.0)

Copy Markdown View Source

Signer backend that signs with a local secp256k1 private key.

Implements Onchain.Signer.Backend — its config is the raw private-key binary. The behaviour's Onchain.Signer.Backend.sign_payload/2 signs the 32-byte digest it is handed directly; sign/2 is a convenience wrapper that keccaks a raw message first.

Uses the precompiled RustCrypto k256 backend. Private keys remain in BEAM memory; use Onchain.Signer.CloudKMS when keys must stay in an HSM.

Summary

Functions

Get the Ethereum address associated with the given private key.

Get the uncompressed secp256k1 public key for the given private key.

Signs a raw message, keccak-digesting it first.

Deprecated alias for sign_payload/2; signs an already-digested message.

Signs the 32-byte digest it is handed directly — the pure-payload contract.

Functions

get_address(private_key)

@spec get_address(binary()) :: {:ok, binary()} | {:error, atom()}

Get the Ethereum address associated with the given private key.

Examples

iex> priv_key = "800509fa3e80882ad0be77c27505bdc91380f800d51ed80897d22f9fcc75f4bf" |> Base.decode16!(case: :mixed)
iex> {:ok, address} = Onchain.Signer.Secp256k1.get_address(priv_key)
iex> Onchain.Hex.to_address(address)
"0x63Cc7c25e0cdb121aBb0fE477a6b9901889F99A7"

public_key(private_key)

@spec public_key(binary()) :: {:ok, binary()} | {:error, atom()}

Get the uncompressed secp256k1 public key for the given private key.

Examples

iex> priv_key = "800509fa3e80882ad0be77c27505bdc91380f800d51ed80897d22f9fcc75f4bf" |> Base.decode16!(case: :mixed)
iex> {:ok, pub} = Onchain.Signer.Secp256k1.public_key(priv_key)
iex> Onchain.Hex.to_address(Onchain.Address.from_public_key(pub))
"0x63Cc7c25e0cdb121aBb0fE477a6b9901889F99A7"

sign(message, private_key)

@spec sign(String.t(), binary()) :: {:ok, Onchain.Signature.t()} | {:error, atom()}

Signs a raw message, keccak-digesting it first.

Back-compat convenience over sign_payload/2 for EIP-191-style raw-message signing; for typed data (EIP-712, Hyperliquid) the caller should compute the digest and call sign_payload/2 directly.

Examples

iex> use Onchain.Hex
iex> priv_key = ~h[0x800509fa3e80882ad0be77c27505bdc91380f800d51ed80897d22f9fcc75f4bf]
iex> {:ok, sig} = Onchain.Signer.Secp256k1.sign("test", priv_key)
iex> {:ok, recid} = Onchain.Recover.find_recid("test", sig, ~h[0x63Cc7c25e0cdb121aBb0fE477a6b9901889F99A7])
iex> Onchain.Recover.recover_eth("test", %{sig|recid: recid}) |> Onchain.Hex.to_address()
"0x63Cc7c25e0cdb121aBb0fE477a6b9901889F99A7"

sign_digest(message_hash, private_key)

@spec sign_digest(String.t(), binary()) ::
  {:ok, Onchain.Signature.t()} | {:error, atom()}

Deprecated alias for sign_payload/2; signs an already-digested message.

sign_payload(arg, private_key)

@spec sign_payload(<<_::256>>, binary()) ::
  {:ok, Onchain.Signature.t()} | {:error, atom()}

Signs the 32-byte digest it is handed directly — the pure-payload contract.

This performs no hashing: the caller (Onchain.Signer) owns digest computation, so the same backend serves plain Eth tx, EIP-712, and Hyperliquid.

Examples

iex> use Onchain.Hex
iex> priv_key = ~h[0x800509fa3e80882ad0be77c27505bdc91380f800d51ed80897d22f9fcc75f4bf]
iex> message_hash = ~h[0x9c22ff5f21f0b81b113e63f7db6da94fedef11b2119b4088b89664fb9a3cb658]
iex> {:ok, sig} = Onchain.Signer.Secp256k1.sign_payload(message_hash, priv_key)
iex> {:ok, recid} = Onchain.Recover.find_recid("test", sig, ~h[0x63Cc7c25e0cdb121aBb0fE477a6b9901889F99A7])
iex> Onchain.Recover.recover_eth("test", %{sig|recid: recid}) |> Onchain.Hex.to_address()
"0x63Cc7c25e0cdb121aBb0fE477a6b9901889F99A7"