Skip to content

L402: alternative DoS prevention using PoW #205

Draft
hieblmi wants to merge 10 commits into
lightninglabs:masterfrom
hieblmi:pow
Draft

L402: alternative DoS prevention using PoW #205
hieblmi wants to merge 10 commits into
lightninglabs:masterfrom
hieblmi:pow

Conversation

@hieblmi
Copy link
Copy Markdown
Collaborator

@hieblmi hieblmi commented Feb 6, 2026

partially addresses #192

Aperture supports an alternative authentication method based on proof-of-work
(PoW) instead of Lightning payments. This allows services to gate access behind
a computational cost rather than a monetary one, which is useful for rate
limiting, spam prevention, or environments where Lightning infrastructure is
unavailable.

PoW authentication is configured per service. A single Aperture instance can
serve both Lightning-authenticated and PoW-authenticated services
simultaneously.

Protocol Flow:

Client                                    Aperture                        Backend
  |                                          |                               |
  |  1. GET /api/resource                    |                               |
  |----------------------------------------->|                               |
  |                                          |                               |
  |  2. 402 Payment Required                 |                               |
  |     WWW-Authenticate: L402               |                               |
  |       macaroon="<base64>",               |                               |
  |       pow="<difficulty>"                 |                               |
  |<-----------------------------------------|                               |
  |                                          |                               |
  |  3. Client solves PoW:                   |                               |
  |     SHA256(tokenID || nonce) has         |                               |
  |     <difficulty> leading zero bits       |                               |
  |                                          |                               |
  |  4. Client adds pow caveat to macaroon   |                               |
  |     and retries:                         |                               |
  |     Authorization: L402 <mac>:POW        |                               |
  |----------------------------------------->|                               |
  |                                          |  5. Forward authenticated     |
  |                                          |     request                   |
  |                                          |------------------------------->|
  |                                          |                               |
  |                                          |  6. Response                   |
  |                                          |<-------------------------------|
  |  7. Response                             |                               |
  |<-----------------------------------------|                               |

Introduce the foundational PoW computation and verification used by both
server and client sides of L402 PoW authentication. The hash function is
SHA256(tokenID || nonce) where the result must have a configurable number
of leading zero bits.

SolvePoW iterates nonces from 0 to find a valid solution. VerifyPoW
performs a single hash check. NewPoWSatisfier returns a Satisfier for
the "pow" caveat condition that can be plugged into the existing caveat
verification framework. The caveat format is pow=<difficulty>:<hex-nonce>.
Add a MacaroonFromHeader function that extracts just the macaroon from
any supported HTTP header format without requiring or validating a
preimage. This is needed by PoW authentication (which has no preimage)
and rate limiting (which only needs the token ID).

A new macOnlyRegex matches both the standard 64-char hex preimage and
the "POW" sentinel in the Authorization header, while the existing
authRegex remains unchanged for backwards compatibility.
Add PowNonce (uint64) and PowDifficulty (uint32) fields to the Token
struct for storing PoW solutions. PoW tokens are never pending since
they are solved instantly, so isPending returns false when isPoW is true.

SolvedMacaroon clones the base macaroon and adds the pow caveat with the
difficulty and nonce, analogous to PaidMacaroon which adds the preimage.

Token serialization appends the two PoW fields at the end of the binary
format. Deserialization gracefully handles EOF for these optional fields,
making the format backwards compatible with existing Lightning tokens.
Implement PoWChallenger which satisfies the mint.Challenger interface
without requiring an LND connection. NewChallenge generates a random
32-byte hash as a placeholder payment hash for the L402 identifier and
returns the difficulty as the challenge string. Stop is a no-op since
there are no resources to clean up.
Add PoWVerificationParams and a VerifyL402PoW method to the Mint. This
method follows the same structure as VerifyL402 — decode identifier,
look up secret, verify HMAC chain, check caveats — but replaces the
preimage check with a PoW satisfier that validates the computational
proof embedded in the macaroon's caveats.

Tests cover valid PoW, wrong nonce, wrong service, tampered macaroon,
and revoked secret scenarios.
Introduce PoWAuthenticator which implements the Authenticator interface
using proof-of-work instead of Lightning invoice verification. Accept
extracts the macaroon via MacaroonFromHeader and delegates to
Mint.VerifyL402PoW. FreshChallengeHeader mints a new L402 and returns
a WWW-Authenticate header with the macaroon and pow difficulty parameter.

The Minter interface gains a VerifyL402PoW method. The authenticator
holds a map of service names to their required difficulties, allowing
per-service difficulty configuration.
Split the single authenticator field on Proxy into lnAuthenticator and
powAuthenticator, selecting the appropriate one per request based on the
target service's AuthMethod. The constructor now takes both authenticators
(powAuth may be nil if no PoW services are configured).

Service gains AuthMethod ("POW" or default "lightning") and PowDifficulty
fields with validation in prepareServices. handlePaymentRequired now
receives the authenticator as a parameter to issue the correct challenge
type.

ExtractRateLimitKey in the rate limiter switches from FromHeader to
MacaroonFromHeader so that PoW-authenticated requests are correctly
rate-limited by token ID rather than falling back to IP.
Introduce PoWClientInterceptor, a gRPC client interceptor that handles
L402 PoW challenges without requiring an LND connection. It follows the
same structure as the existing ClientInterceptor: check store for an
existing token, make the request, handle 402 by solving the challenge,
then retry.

The PoW-specific flow parses the pow="<difficulty>" parameter from the
WWW-Authenticate header, extracts the token ID from the macaroon
identifier, calls SolvePoW to find a valid nonce, and stores the
completed token for reuse on subsequent requests.
In createProxy, detect services with AuthMethod "POW" and create a
dedicated PoW mint and authenticator for them. The PoW mint uses a
PoWChallenger (no LND needed) and shares the same secret store and
service limiter as the Lightning mint. Both authenticators are passed
to proxy.New for per-service dispatch.

The sample configuration gains a PoW service example showing the
authmethod and powdifficulty fields.
Document the L402 proof-of-work authentication feature including the
protocol flow, configuration options, difficulty tuning guide, wire
formats for both HTTP and gRPC, client integration examples, package
architecture, and security considerations.
@hieblmi hieblmi marked this pull request as draft February 6, 2026 15:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant