Raxol.Payments.Secret (Raxol Payments v0.2.0)

Copy Markdown View Source

An opaque wrapper around sensitive bytes (a private key) that refuses to reveal itself through Inspect, logging, or string interpolation.

A raw private key held in plain process state or a config map leaks the moment that state is inspected: an unhandled GenServer crash makes OTP's default error report inspect the process state into the log, and dbg/1 or an exception carrying the value does the same. Wrapping the bytes here means those paths print #Raxol.Payments.Secret<redacted> instead of the key. The bytes are recovered only at the signing call site via reveal/1.

This does not protect the key while it is in BEAM memory (it is not zeroed, and nothing prevents a determined caller from reveal/1-ing it). It closes the accidental-disclosure paths: logs, crash dumps, inspect output.

secret = Raxol.Payments.Secret.new(private_key_bytes)
inspect(secret)            #=> "#Raxol.Payments.Secret<redacted>"
Raxol.Payments.Secret.reveal(secret) == private_key_bytes  #=> true

Summary

Functions

Wrap raw bytes. Returns the value unchanged if already wrapped.

Reveal the wrapped bytes for use at a signing call site.

Reveal the wrapped bytes only for fun, converting any exception raised inside it into {:error, :secret_operation_crashed}.

Types

t()

@opaque t()

Functions

new(secret)

@spec new(binary() | t()) :: t()

Wrap raw bytes. Returns the value unchanged if already wrapped.

reveal(secret)

@spec reveal(t()) :: binary()

Reveal the wrapped bytes for use at a signing call site.

with_revealed(secret, fun)

@spec with_revealed(t(), (binary() -> result)) ::
  result | {:error, :secret_operation_crashed}
when result: term()

Reveal the wrapped bytes only for fun, converting any exception raised inside it into {:error, :secret_operation_crashed}.

The reveal-and-use sites pass the raw key as an argument to a signing NIF. If that NIF raises (e.g. badarg on a malformed key), the raw bytes would appear as a call argument in the crash stacktrace and OTP crash report. Running the call under this guard turns such a raise into a fixed error atom before it can reach a log, so the key never leaves this function. Normal {:ok, _} / {:error, _} returns from fun pass through unchanged.