pps-protocol/laravel-pps

Laravel integration for PulseProof Sentinel Protocol (PPS).

Maintainers

Package info

github.com/pps-protocol/laravel-pps

Documentation

pkg:composer/pps-protocol/laravel-pps

Transparency log

Statistics

Installs: 0

Dependents: 0

Suggesters: 0

Stars: 1

Open Issues: 0

v0.1.0 2026-07-23 18:26 UTC

This package is auto-updated.

Last update: 2026-07-23 19:50:12 UTC


README

Laravel integration for the PulseProof Sentinel Protocol (PPS)

Laravel PPS PHP 8.1+ Laravel 10/11/12/13 Status: Experimental License: Apache-2.0

Introduction

laravel-pps is the official Laravel integration package for the PulseProof Sentinel Protocol, abbreviated PPS.

This package provides:

  • Laravel service provider
  • Publishable configuration
  • API routes
  • HTTP controller
  • Facade
  • Artisan commands
  • Database migrations
  • Storage backend integration
  • Silent duress handling support
  • Transaction signing support

The core protocol implementation lives in the framework-agnostic package:

pps-protocol/pps-php

This package, pps-protocol/laravel-pps, depends on pps-protocol/pps-php and only adds Laravel-specific integration.

What is PPS?

PulseProof Sentinel Protocol, or PPS, is an experimental authentication protocol based on time-bound asymmetric proofs.

Instead of shared-secret TOTP codes:

TOTP = HMAC(shared_secret, time)

PPS uses signed asymmetric proofs:

Pulse = Sign(private_key, time + rp + nonce + counter + context + policy)

The server stores only public keys.

PPS is designed to complement, not replace, WebAuthn/FIDO2.

WebAuthn remains preferred for browser-based passkey authentication.

PPS targets operational gaps such as:

  • offline or semi-offline authentication
  • legacy OTP-style input flows
  • QR-code and deep-link authentication
  • constrained hardware terminals
  • point-of-sale and IoT devices
  • transaction signing with human-visible amount confirmation
  • silent duress signaling
  • offline multi-device threshold approval

Important Links

Resource Link
PPS Specification pps-protocol/pulseproof-sentinel
IETF Internet-Draft draft-hezami-pulseproof-sentinel
Core PHP Package pps-protocol/pps-php
Laravel Package pps-protocol/laravel-pps
Documentation Site pps-protocol.github.io

Requirements

Requirement Version
PHP 8.1+
Laravel 10.x, 11.x, 12.x or 13.x
ext-sodium required
ext-gmp required
ext-hash required
ext-json required

The core package pps-protocol/pps-php will be installed automatically.

Installation

Install via Composer:

composer require pps-protocol/laravel-pps

Laravel package discovery should automatically register:

Pps\Laravel\PulseProofServiceProvider

and the facade:

Pps\Laravel\Facades\PulseProof

If package discovery is disabled, register manually in config/app.php:

'providers' => [
    Pps\Laravel\PulseProofServiceProvider::class,
],

'aliases' => [
    'PulseProof' => Pps\Laravel\Facades\PulseProof::class,
],

Publish Configuration and Migrations

Run the install command:

php artisan pps:install

This publishes:

config/pps.php
database/migrations/*_create_pps_tables.php

Then run migrations if you are using the PDO storage driver:

php artisan migrate

Configuration

The main configuration file is:

config/pps.php

You can also configure PPS using environment variables.

PPS_RP_ID=example.com
PPS_ALGORITHM=PPS-ED25519-CBOR30
PPS_EPOCH_SECONDS=30
PPS_MAX_CLOCK_SKEW=1
PPS_TRUST_DIGITS=8
PPS_NONCE_TTL=60
PPS_ALLOW_OFFLINE=false

PPS_STORAGE_DRIVER=file
# PPS_STORAGE_DRIVER=pdo
# PPS_DB_CONNECTION=mysql
# PPS_STORAGE_DRIVER=redis
# PPS_REDIS_CONNECTION=default

PPS_ROUTES_ENABLED=true
PPS_ROUTE_PREFIX=pps
PPS_ROUTE_MIDDLEWARE=api

Relying Party Identifier

The rp_id identifies your service.

Examples:

example.com
login.example.com
localhost

For local development:

PPS_RP_ID=localhost

Storage Drivers

PPS needs storage for:

  • device public keys
  • key sequence counters
  • threshold groups
  • nonces

Supported drivers:

memory
file
pdo
redis

Memory

For testing only:

PPS_STORAGE_DRIVER=memory

File

Good for development or single-process apps:

PPS_STORAGE_DRIVER=file

Default path:

storage/app/pps

PDO

Recommended for production:

PPS_STORAGE_DRIVER=pdo
PPS_DB_CONNECTION=mysql

Then run:

php artisan migrate

Redis

For high-performance distributed deployments:

PPS_STORAGE_DRIVER=redis
PPS_REDIS_CONNECTION=default

Routes

By default, this package registers the following routes:

Method URI Description
GET /pps/health Health check
GET /pps/challenge Create authentication/registration challenge
POST /pps/register Register a device
POST /pps/verify Verify a Pulse Token
POST /pps/transaction/challenge Create transaction challenge
POST /pps/transaction/verify Verify transaction-bound Pulse

You can customize the prefix:

PPS_ROUTE_PREFIX=pps

Or disable default routes:

PPS_ROUTES_ENABLED=false

Health Check

curl http://localhost:8000/pps/health

Example response:

{
    "status": "ok",
    "protocol": "PulseProof Sentinel Protocol",
    "version": 1,
    "algorithm": "PPS-ED25519-CBOR30",
    "epoch_seconds": 30,
    "rp_id": "localhost",
    "time": 1769174400,
    "epoch": 58972480
}

Basic Authentication Flow

1. Get Challenge

curl http://localhost:8000/pps/challenge

Example response:

{
    "nonce": "base64url-nonce",
    "epoch": 58972480,
    "session_id": "base64url-session",
    "qr_payload": "pulseproof://challenge?..."
}

2. Register Device

Device registration requires a registration_token.

The registration_token is generated by a PPS client using the core package:

use Pps\Client\RegistrationBuilder;

$builder = new RegistrationBuilder();
$registration = $builder
    ->rpId('localhost')
    ->nonce($nonceFromChallenge)
    ->userHandle('user-123')
    ->deviceName('My Phone')
    ->withDuress(true)
    ->build();

$registrationToken = $registration['token'];
$clientState = $registration['client_state'];

Then send it to Laravel:

curl -X POST http://localhost:8000/pps/register \
  -H "Content-Type: application/json" \
  -d '{
    "registration_token": "base64url-registration-token",
    "nonce": "base64url-nonce"
  }'

Example response:

{
    "status": "ok",
    "kid": "base64url-kid"
}

3. Authenticate

The client creates a Pulse Token:

use Pps\Client\AuthenticatorClient;

$client = new AuthenticatorClient($clientState);

$pulse = $client
    ->rpId('localhost')
    ->nonce($nonceFromChallenge)
    ->createPulse();

$pulseToken = $pulse['token'];
$trustCode = $pulse['trust_code'];

Then send it to Laravel:

curl -X POST http://localhost:8000/pps/verify \
  -H "Content-Type: application/json" \
  -d '{
    "pulse_token": "base64url-pulse-token",
    "nonce": "base64url-nonce",
    "trust_code": "49371826"
  }'

Example response:

{
    "status": "ok",
    "mode": "normal"
}

Facade Usage

use Pps\Laravel\Facades\PulseProof;

$challenge = PulseProof::challenge();

$nonce = $challenge['nonce'];

Verify a Pulse:

$result = PulseProof::verify(
    pulseToken: $request->input('pulse_token'),
    nonceB64u: $request->input('nonce'),
    trustCode: $request->input('trust_code')
);

if ($result->honey) {
    // Silent duress detected.
    // Return a normal-looking response.
}

if ($result->ok) {
    // Authentication succeeded.
}

Transaction Signing

Create a transaction challenge:

curl -X POST http://localhost:8000/pps/transaction/challenge \
  -H "Content-Type: application/json" \
  -d '{
    "action": "withdraw",
    "amount_minor": 2500067,
    "currency": "IRR",
    "recipient": "IR820540102680020817909002"
  }'

Example response:

{
    "nonce": "base64url-nonce",
    "epoch": 58972480,
    "session_id": "base64url-session",
    "tx_hash": "base64url-tx-hash",
    "amount_mark": "67"
}

The client should bind the transaction hash and amount to the Pulse:

use Pps\Crypto\Base64Url;
use Pps\Payload\ContextObject;

$context = new ContextObject();
$context->sessionId = Base64Url::decode($sessionId);
$context->txHash = Base64Url::decode($txHash);

$pulse = $client
    ->rpId('localhost')
    ->nonce($nonce)
    ->context($context)
    ->amountMinor(2500067)
    ->createPulse();

Then verify:

curl -X POST http://localhost:8000/pps/transaction/verify \
  -H "Content-Type: application/json" \
  -d '{
    "pulse_token": "base64url-pulse-token",
    "nonce": "base64url-nonce",
    "trust_code": "49371867",
    "action": "withdraw",
    "amount_minor": 2500067,
    "currency": "IRR",
    "recipient": "IR820540102680020817909002",
    "session_id": "base64url-session",
    "tx_hash": "base64url-tx-hash"
  }'

The Trust Code includes the AmountMark:

Trust Code: 49371867
AmountMark: 67

The last two digits match the transaction amount modulo 100.

Silent Duress

PPS supports silent duress signaling.

If a user authenticates under coercion, the client can sign with a hidden duress key.

The server detects this internally:

if ($result->honey) {
    // Outward response must remain normal.
}

Important:

The HTTP response for a duress authentication must be indistinguishable from a normal successful authentication.

Do not return:

{
    "status": "duress"
}

Instead, return:

{
    "status": "ok"
}

Then internally:

  • restrict account capabilities
  • block high-value operations
  • delay settlement
  • trigger a silent alert
  • preserve audit logs

Threshold Mode

PPS supports offline multi-device threshold approval.

The core package implements n-of-m Ed25519 multisignature.

This Laravel package can verify threshold Pulses through the same verification engine provided by pps-protocol/pps-php.

Threshold group management may be implemented in your application or added in future versions of this package.

Artisan Commands

Install PPS Resources

php artisan pps:install

Publishes:

config/pps.php
database/migrations/*_create_pps_tables.php

Generate Key Pair

php artisan pps:key-generate

Example output:

{
    "public_key": "base64url-public-key",
    "secret_key": "base64url-secret-key",
    "seed": "base64url-seed"
}

Warning:

Never commit secret keys to source control.

Testing

Run package tests:

composer install
vendor/bin/phpunit

Package Architecture

pps-protocol/pps-php
    Core protocol implementation
    No framework dependency

pps-protocol/laravel-pps
    Laravel bridge
    Depends on pps-protocol/pps-php

This package does not reimplement the protocol.

It only provides Laravel integration around the core package.

Security

This package is experimental.

It has not been independently audited.

Do not use it in production without:

  • cryptographic review
  • security audit
  • interoperability testing
  • operational risk assessment

See SECURITY.md.

Changelog

See CHANGELOG.md.

License

Apache License 2.0

See LICENSE.