SiwaKeyring

Isolated signer service and Elixir client for Regent SIWA.

Use this package when a Regent process needs a local wallet for SIWA receipts, request signatures, transaction payloads, or authorization payloads.

Configure The Wallet Store

config :siwa_keyring,
  path: "/data/siwa-keyring.json",
  password: System.fetch_env!("KEYSTORE_PASSWORD"),
  secret: System.fetch_env!("KEYRING_PROXY_SECRET")

The wallet file is encrypted with AES-256-GCM. The proxy secret signs requests to the keyring routes.

Local Service Calls

{:ok, wallet} = SiwaKeyring.create_wallet()
{:ok, %{has_wallet: true}} = SiwaKeyring.has_wallet?()
{:ok, address} = SiwaKeyring.get_address()

{:ok, signature} = SiwaKeyring.sign_message("Sign in to Regent")
{:ok, raw_signature} = SiwaKeyring.sign_raw_message("payload-to-bind")

HTTP Routes

Run SiwaKeyring.Router at your internal service root.

Available routes:

Every non-health route requires:

Build those headers with:

body = Jason.encode!(%{"message" => "Sign in to Regent"})

headers =
  SiwaKeyring.Auth.compute_hmac(
    "proxy-secret",
    "POST",
    "/internal/keyring/sign-message",
    body
  )

The request id is included in the signed payload and can only be used once during the timestamp freshness window.

Transaction and authorization signing accepts the shared wallet-action envelope from regent-services-contract.openapiv3.yaml: chain_id, to, value, data, expected_signer, expires_at, risk_copy, and idempotency_key.

Remote Client

client =
  SiwaKeyring.Client.new(
    base_url: "https://siwa.internal",
    secret: "proxy-secret"
  )

{:ok, %{"address" => address}} = SiwaKeyring.Client.get_address(client)

Development

mix test
mix format --check-formatted