X402.Extensions.HTTPMessageSignatures (X402 v0.9.0)

Copy Markdown View Source

The x402 http-message-signatures extension: identity through RFC 9421.

Implements the http-message-signatures extension. A network that authenticates paying agents with HTTP Message Signatures tells clients, in PaymentRequired.extensions, where to register their signature agent, which algorithms it accepts and which signature tag values it validates:

%{
  "http-message-signatures" => %{
    "info" => %{
      "registrationUrl" => "https://network.example.com/signature-agents",
      "signatureSchemes" => ["ed25519"],
      "tags" => ["web-bot-auth"]
    },
    "schema" => %{...}
  }
}

The client hosts its keys at /.well-known/http-message-signatures-directory (X402.HTTPSignature.directory/1, X402.Plug.HTTPSignatureDirectory), registers the directory URL at registrationUrl, and signs its requests with X402.HTTPSignature.sign/3 using one of the advertised algorithms and tags. Servers may sign their responses the same way to give PAYMENT-REQUIRED and PAYMENT-RESPONSE integrity.

schema may be omitted from the advertisement to keep the header small (extension/1 with include_schema: false); decode/1 accepts both shapes.

Summary

Types

The decoded extension info.

Functions

Decodes the extension from a PaymentRequired map (or its extensions map).

Builds the server-side advertisement for PaymentRequired.extensions.

Returns the extension key on the wire.

Returns the JSON schema the server advertises for the extension.

Validates the value advertised under the extension key and extracts its info.

Types

error()

@type error() ::
  :invalid_http_message_signatures_extension | {:missing_field, String.t()}

info()

@type info() :: %{
  registration_url: String.t(),
  signature_schemes: [String.t()],
  tags: [String.t()]
}

The decoded extension info.

Functions

decode(payment_required)

(since 0.9.0)
@spec decode(term()) :: {:ok, info() | nil} | {:error, error()}

Decodes the extension from a PaymentRequired map (or its extensions map).

Returns {:ok, nil} when the extension is absent.

Examples

iex> extension = X402.Extensions.HTTPMessageSignatures.extension(
...>   registration_url: "https://network.example.com/agents", signature_schemes: ["ed25519"], include_schema: false)
iex> payment_required = %{"accepts" => [], "extensions" => %{"http-message-signatures" => extension}}
iex> X402.Extensions.HTTPMessageSignatures.decode(payment_required)
{:ok, %{registration_url: "https://network.example.com/agents", signature_schemes: ["ed25519"], tags: []}}

iex> X402.Extensions.HTTPMessageSignatures.decode(%{"accepts" => []})
{:ok, nil}

iex> X402.Extensions.HTTPMessageSignatures.decode(%{"extensions" => %{"http-message-signatures" => %{"info" => %{}}}})
{:error, {:missing_field, "registrationUrl"}}

extension(opts)

(since 0.9.0)
@spec extension(keyword()) :: map()

Builds the server-side advertisement for PaymentRequired.extensions.

Raises NimbleOptions.ValidationError for invalid options: a malformed declaration is a configuration error.

Options

  • :registration_url - Required. Where signature agents register their key directory (info.registrationUrl).

  • :signature_schemes - Required. Accepted algorithms (info.signatureSchemes), at least one, for example ["ed25519"].

  • :tags - Accepted signature tags (info.tags), for example ["web-bot-auth"]. The default value is [].

  • :include_schema (boolean/0) - Whether to advertise the JSON schema alongside info. The default value is true.

Examples

iex> extension = X402.Extensions.HTTPMessageSignatures.extension(
...>   registration_url: "https://network.example.com/signature-agents",
...>   signature_schemes: ["ed25519", "ecdsa-p256-sha256", "rsa-pss-sha512"],
...>   tags: ["web-bot-auth", "agent-browser-auth"]
...> )
iex> extension["info"]
%{
  "registrationUrl" => "https://network.example.com/signature-agents",
  "signatureSchemes" => ["ed25519", "ecdsa-p256-sha256", "rsa-pss-sha512"],
  "tags" => ["web-bot-auth", "agent-browser-auth"]
}
iex> extension["schema"] == X402.Extensions.HTTPMessageSignatures.schema()
true

iex> X402.Extensions.HTTPMessageSignatures.extension(
...>   registration_url: "https://network.example.com/signature-agents",
...>   signature_schemes: ["ed25519"],
...>   include_schema: false
...> )
%{"info" => %{"registrationUrl" => "https://network.example.com/signature-agents", "signatureSchemes" => ["ed25519"], "tags" => []}}

extension_key()

(since 0.9.0)
@spec extension_key() :: String.t()

Returns the extension key on the wire.

Examples

iex> X402.Extensions.HTTPMessageSignatures.extension_key()
"http-message-signatures"

schema()

(since 0.9.0)
@spec schema() :: map()

Returns the JSON schema the server advertises for the extension.

Examples

iex> schema = X402.Extensions.HTTPMessageSignatures.schema()
iex> schema["required"]
["registrationUrl", "signatureSchemes"]
iex> schema["properties"]["registrationUrl"]["format"]
"uri"

validate(value)

(since 0.9.0)
@spec validate(term()) :: {:ok, info()} | {:error, error()}

Validates the value advertised under the extension key and extracts its info.

Accepts the %{"info" => ...} envelope with or without schema, or a bare info map. tags defaults to [].

Examples

iex> extension = X402.Extensions.HTTPMessageSignatures.extension(
...>   registration_url: "https://network.example.com/agents", signature_schemes: ["ed25519"], tags: ["web-bot-auth"])
iex> X402.Extensions.HTTPMessageSignatures.validate(extension)
{:ok, %{registration_url: "https://network.example.com/agents", signature_schemes: ["ed25519"], tags: ["web-bot-auth"]}}

iex> X402.Extensions.HTTPMessageSignatures.validate(%{"info" => %{"registrationUrl" => "https://n.example/r"}})
{:error, {:missing_field, "signatureSchemes"}}

iex> X402.Extensions.HTTPMessageSignatures.validate(%{"info" => %{"registrationUrl" => "https://n.example/r", "signatureSchemes" => "ed25519"}})
{:error, :invalid_http_message_signatures_extension}

iex> X402.Extensions.HTTPMessageSignatures.validate("nope")
{:error, :invalid_http_message_signatures_extension}