# `Bourse.Signing.Crypto`
[🔗](https://github.com/ZenHive/bourse/blob/main/lib/bourse/signing/crypto.ex#L1)

Shared low-level crypto primitives for the custom DEX signing modules
(`Bourse.Signing.Hyperliquid`, `Bourse.Signing.Derive`) and Lighter L1
ChangePubKey personal-message signatures.

Wraps the Keccak-256 (`ex_keccak`) and secp256k1 (`ex_secp256k1`) NIFs.
Signatures use RFC-6979 deterministic nonces, low-`s` normalization, and
Ethereum recovery value `v = 27 + recovery_id`.

# `signature`

```elixir
@type signature() :: %{r: String.t(), s: String.t(), v: non_neg_integer()}
```

# `decode_hex`

```elixir
@spec decode_hex(String.t()) :: binary()
```

Decodes a (`0x`-prefixed) hex string into a binary.

# `decode_private_key`

```elixir
@spec decode_private_key(String.t()) :: binary()
```

Decodes a (`0x`-prefixed) hex private key into the 32-byte binary
`ExSecp256k1` expects. Accepts the last 64 hex characters.

# `encode_hex`

```elixir
@spec encode_hex(binary()) :: String.t()
```

Lowercase hex-encodes a binary (no `0x` prefix).

# `hash_message`

```elixir
@spec hash_message(String.t()) :: binary()
```

Computes the EIP-191 personal-message hash (`hashMessage`) for `message`.

# `keccak256`

```elixir
@spec keccak256(iodata()) :: binary()
```

Keccak-256 digest of `data` (32 raw bytes).

# `recover_signer_address`

```elixir
@spec recover_signer_address(String.t(), String.t()) ::
  {:ok, String.t()} | {:error, :invalid_signature}
```

Recovers the EIP-191 signer address from `message` and a packed `0x ‖ r ‖ s ‖ v`
signature. `v` may be 27/28 or the raw recovery id 0/1.

# `sign_hash`

```elixir
@spec sign_hash(binary(), binary()) :: signature()
```

Signs a 32-byte `digest` with the secp256k1 `private_key`.

Returns `%{r, s, v}` where `r`/`s` are lowercase
64-char hex strings (no `0x`) and `v = 27 + recovery_id`. Signatures use
RFC-6979 deterministic nonces and canonical low-`s` values.

# `sign_message`

```elixir
@spec sign_message(
  String.t(),
  keyword()
) :: String.t()
```

Signs `message` with EIP-191 personal signing, returning the packed
`0x ‖ r ‖ s ‖ v` signature string. Options: `:private_key` (required).

# `strip_0x`

```elixir
@spec strip_0x(String.t()) :: String.t()
```

Strips a leading `0x`/`0X` prefix if present.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
