PHPackages                             nova-carnivore/bolt12-php - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Payment Processing](/categories/payments)
4. /
5. nova-carnivore/bolt12-php

ActiveLibrary[Payment Processing](/categories/payments)

nova-carnivore/bolt12-php
=========================

Modern PHP 8.3+ BOLT 12 Lightning Network offer/invoice encoder/decoder with BIP-340 Schnorr signatures

v0.1.0(3mo ago)00MITPHPPHP ^8.3CI passing

Since Feb 15Pushed 3mo agoCompare

[ Source](https://github.com/nova-carnivore/bolt12-php)[ Packagist](https://packagist.org/packages/nova-carnivore/bolt12-php)[ RSS](/packages/nova-carnivore-bolt12-php/feed)WikiDiscussions master Synced 1mo ago

READMEChangelogDependencies (4)Versions (2)Used By (0)

bolt12-php
==========

[](#bolt12-php)

[![PHP Version](https://camo.githubusercontent.com/7e80c5a44b0f819258f09384c7af693fe7f3f1ebe4ae8c6833b5c34f2dd57d03/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f7068702d253345253344382e332d3838393242462e737667)](https://php.net)[![License](https://camo.githubusercontent.com/7013272bd27ece47364536a221edb554cd69683b68a46fc0ee96881174c4214c/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c6963656e73652d4d49542d626c75652e737667)](LICENSE)[![CI](https://github.com/nova-carnivore/bolt12-php/actions/workflows/ci.yml/badge.svg)](https://github.com/nova-carnivore/bolt12-php/actions/workflows/ci.yml)[![PHPStan Level](https://camo.githubusercontent.com/3eef5bfb737c3eb8f74ff340fd46dc7a1cc6a5af7e425296fcec68a1ad6f5ad9/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f7068707374616e2d6c6576656c253230392d627269676874677265656e2e737667)](https://phpstan.org)[![Latest Version](https://camo.githubusercontent.com/68d47e883b5c6617cefa058ef13390fc53e8f5307dda590dcd4ad835bd93ecac/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f6e6f76612d6361726e69766f72652f626f6c7431322d7068702e737667)](https://packagist.org/packages/nova-carnivore/bolt12-php)

Modern PHP 8.3+ BOLT 12 Lightning Network offer/invoice encoder/decoder with BIP-340 Schnorr signatures. Full spec compliance, production-ready.

Features
--------

[](#features)

- ⚡ **Full BOLT 12 spec compliance** — Offers, Invoice Requests, Invoices, Invoice Errors
- 🔐 **BIP-340 Schnorr signatures** — Pure PHP implementation with Merkle tree construction
- 🏗️ **Modern PHP 8.3+** — Enums, readonly classes, named arguments, match expressions
- 🔍 **PHPStan level 9** — Maximum static analysis strictness
- 🌐 **All message types** — Offer (lno), Invoice Request (lnr), Invoice (lni), Invoice Error
- 🏷️ **All TLV fields** — chains, metadata, currency, amounts, paths, features, BIP-353, and more
- 🔄 **Round-trip safe** — Encode → decode preserves all data
- 📏 **PSR-12 code style** — Enforced with PHP-CS-Fixer
- 🔒 **Constant-time crypto** — Uses GMP for big integer operations
- 🔗 **Blinded paths** — Full support for multi-hop blinded payment paths

Installation
------------

[](#installation)

```
composer require nova-carnivore/bolt12-php
```

### Requirements

[](#requirements)

- PHP 8.3 or higher
- ext-gmp (required for big integers and BIP-340 crypto)

Quick Start
-----------

[](#quick-start)

### Decode (Auto-detect Type)

[](#decode-auto-detect-type)

```
use Nova\Bitcoin\Bolt12\Decoder;

$offer = Decoder::decode('lno1...');      // Returns Offer object
$invReq = Decoder::decode('lnr1...');     // Returns InvoiceRequest object
$invoice = Decoder::decode('lni1...');    // Returns Invoice object
```

### Encode an Offer

[](#encode-an-offer)

```
use Nova\Bitcoin\Bolt12\Encoder;
use Nova\Bitcoin\Bolt12\Signer;

$encoded = Encoder::encodeOffer(
    issuerId: $myPublicKeyHex,
    description: 'Buy a coffee',
    amountMsat: gmp_init(100000),
    issuer: 'CoffeeShop',
);
// Returns: 'lno1...'
```

### Encode an Invoice Request (auto-signed)

[](#encode-an-invoice-request-auto-signed)

```
$invReq = Encoder::encodeInvoiceRequest(
    invreqMetadata: bin2hex(random_bytes(32)),
    payerId: $myPublicKeyHex,
    payerPrivateKey: $myPrivateKeyHex,
    offerDescription: 'Buy a coffee',
    offerIssuerId: $merchantPubkey,
    offerAmountMsat: gmp_init(100000),
);
// Returns: 'lnr1...' (signed with BIP-340 Schnorr)
```

### Encode an Invoice (auto-signed)

[](#encode-an-invoice-auto-signed)

```
use Nova\Bitcoin\Bolt12\BlindedPath;
use Nova\Bitcoin\Bolt12\BlindedPayInfo;
use Nova\Bitcoin\Bolt12\OnionMessageHop;

$invoice = Encoder::encodeInvoice(
    nodeId: $myNodePubkey,
    nodePrivateKey: $myNodePrivkey,
    createdAt: gmp_init(time()),
    paymentHash: hash('sha256', $preimage),
    amountMsat: gmp_init(100000),
    invoicePaths: [
        new BlindedPath($blindingKey, [
            new OnionMessageHop($nodeId, $encryptedData),
        ]),
    ],
    blindedPayInfo: [
        new BlindedPayInfo(
            feeBaseMsat: 1000,
            feeProportionalMillionths: 100,
            cltvExpiryDelta: 144,
            htlcMinimumMsat: gmp_init(1000),
            htlcMaximumMsat: gmp_init(1000000000),
        ),
    ],
);
// Returns: 'lni1...' (signed with BIP-340 Schnorr)
```

### Verify Signatures

[](#verify-signatures)

```
use Nova\Bitcoin\Bolt12\Signer;

$invReq = Decoder::decode('lnr1...');
$valid = Signer::verifyInvoiceRequest($invReq);  // Returns bool

$invoice = Decoder::decode('lni1...');
$valid = Signer::verifyInvoice($invoice);  // Returns bool
```

### Invoice Errors

[](#invoice-errors)

```
// Encode
$errorBytes = Encoder::encodeInvoiceError(
    error: 'Amount too low',
    erroneousField: gmp_init(82),  // invreq_amount
    suggestedValue: '0186a0',      // 100000 as tu64 hex
);

// Decode
$invoiceError = Decoder::decodeInvoiceError($errorBytes);
echo $invoiceError->error;  // 'Amount too low'
```

Full Payment Flow Example
-------------------------

[](#full-payment-flow-example)

```
use Nova\Bitcoin\Bolt12\{Decoder, Encoder, Signer, BlindedPath, BlindedPayInfo, OnionMessageHop};

// 1. Merchant creates an offer
$offer = Encoder::encodeOffer(
    issuerId: $merchantPubkey,
    description: 'Buy a coffee',
    amountMsat: gmp_init(100000),
);
$decoded = Decoder::decode($offer);

// 2. Payer creates invoice request (mirrors offer fields)
$invReq = Encoder::encodeInvoiceRequest(
    invreqMetadata: bin2hex(random_bytes(32)),
    payerId: $payerPubkey,
    payerPrivateKey: $payerPrivkey,
    offerDescription: $decoded->description,
    offerIssuerId: $decoded->issuerId,
    offerAmountMsat: $decoded->amountMsat,
);

// 3. Merchant creates and signs invoice
$invoice = Encoder::encodeInvoice(
    nodeId: $merchantPubkey,
    nodePrivateKey: $merchantPrivkey,
    createdAt: gmp_init(time()),
    paymentHash: hash('sha256', $preimage),
    amountMsat: gmp_init(100000),
    invoicePaths: [$blindedPath],
    blindedPayInfo: [$payInfo],
);

// 4. Payer verifies invoice signature
$inv = Decoder::decode($invoice);
assert(Signer::verifyInvoice($inv));
```

API Reference
-------------

[](#api-reference)

### `Decoder::decode(string $bolt12String): Offer|InvoiceRequest|Invoice`

[](#decoderdecodestring-bolt12string-offerinvoicerequestinvoice)

Decodes any BOLT 12 bech32 string (auto-detects type by prefix).

### `Decoder::decodeInvoiceError(array $bytes): InvoiceError`

[](#decoderdecodeinvoiceerrorarray-bytes-invoiceerror)

Decodes a BOLT 12 Invoice Error from raw TLV bytes.

### `Encoder::encodeOffer(...): string`

[](#encoderencodeoffer-string)

Encodes a BOLT 12 Offer (lno1...). Not signed per spec.

### `Encoder::encodeInvoiceRequest(...): string`

[](#encoderencodeinvoicerequest-string)

Encodes and signs a BOLT 12 Invoice Request (lnr1...).

### `Encoder::encodeInvoice(...): string`

[](#encoderencodeinvoice-string)

Encodes and signs a BOLT 12 Invoice (lni1...).

### `Encoder::encodeInvoiceError(...): array`

[](#encoderencodeinvoiceerror-array)

Encodes a BOLT 12 Invoice Error as raw TLV bytes.

### `Signer::verifyInvoiceRequest(InvoiceRequest $invReq): bool`

[](#signerverifyinvoicerequestinvoicerequest-invreq-bool)

Verifies a BIP-340 Schnorr signature on an invoice request.

### `Signer::verifyInvoice(Invoice $invoice): bool`

[](#signerverifyinvoiceinvoice-invoice-bool)

Verifies a BIP-340 Schnorr signature on an invoice.

### `Signer::getPublicKey(string $privateKeyHex): string`

[](#signergetpublickeystring-privatekeyhex-string)

Derives a compressed public key from a private key.

### Data Classes

[](#data-classes)

ClassDescription`Offer`Decoded offer with all fields as readonly properties`InvoiceRequest`Decoded invoice request with signature`Invoice`Decoded invoice with paths, payinfo, and signature`InvoiceError`Decoded invoice error`BlindedPath`Blinded path with hops`BlindedPayInfo`Payment info for blinded paths`FallbackAddress`On-chain fallback address`OnionMessageHop`Single hop in a blinded path`Bip353Name`BIP-353 name (user@domain)`TlvEntry`Raw TLV type-value pairBOLT 12 Spec Coverage
---------------------

[](#bolt-12-spec-coverage)

### Message Types

[](#message-types)

TypePrefixEncodeDecodeSignVerifyOffer`lno`✅✅✅\*✅\*Invoice Request`lnr`✅✅✅✅Invoice`lni`✅✅✅✅Invoice Error—✅✅N/AN/A\* Offers are optionally signed per spec

### TLV Fields

[](#tlv-fields)

TypeFieldOfferInvReqInvoice0`invreq_metadata`—✅✅2`offer_chains`✅✅✅4`offer_metadata`✅✅✅6`offer_currency`✅✅✅8`offer_amount`✅✅✅10`offer_description`✅✅✅12`offer_features`✅✅✅14`offer_absolute_expiry`✅✅✅16`offer_paths`✅✅✅18`offer_issuer`✅✅✅20`offer_quantity_max`✅✅✅22`offer_issuer_id`✅✅✅80`invreq_chain`—✅✅82`invreq_amount`—✅✅84`invreq_features`—✅✅86`invreq_quantity`—✅✅88`invreq_payer_id`—✅✅89`invreq_payer_note`—✅✅90`invreq_paths`—✅✅91`invreq_bip_353_name`—✅✅160`invoice_paths`——✅162`invoice_blindedpay`——✅164`invoice_created_at`——✅166`invoice_relative_expiry`——✅168`invoice_payment_hash`——✅170`invoice_amount`——✅172`invoice_fallbacks`——✅174`invoice_features`——✅176`invoice_node_id`——✅240`signature`✅\*✅✅### Encoding

[](#encoding)

- **Bech32 without checksum** — per BOLT 12 spec
- **BigSize** TLV type/length encoding
- **tu64** truncated unsigned 64-bit values
- **`+` concatenation** support for long strings

### Cryptography

[](#cryptography)

- **BIP-340 Schnorr** signatures (64-byte raw, not DER)
- **Tagged hashes** — `SHA256(SHA256(tag) || SHA256(tag) || msg)`
- **Merkle tree** for multi-TLV signatures with nonce leaves
- **secp256k1** elliptic curve arithmetic

Security
--------

[](#security)

- BIP-340 Schnorr signatures use **pure PHP** with GMP for big integer arithmetic
- All elliptic curve operations use **constant-time** modular arithmetic via GMP
- The Merkle tree construction includes **nonce leaves** to prevent revealing adjacent fields
- Private keys are only used in signing operations and never stored

Exception Handling
------------------

[](#exception-handling)

```
use Nova\Bitcoin\Bolt12\Exception\{
    Bolt12Exception,       // Base exception
    DecodeException,       // Malformed input
    EncodeException,       // Missing/invalid fields
    SignatureException,    // Signature issues
};

try {
    $offer = Decoder::decode($bolt12String);
} catch (DecodeException $e) {
    // Malformed string or TLV data
} catch (Bolt12Exception $e) {
    // Any BOLT 12 error
}
```

Development
-----------

[](#development)

```
# Install dependencies
composer install

# Run tests
vendor/bin/phpunit

# Static analysis
vendor/bin/phpstan analyse

# Code style check
vendor/bin/php-cs-fixer fix --dry-run --diff

# Fix code style
vendor/bin/php-cs-fixer fix

# Run all CI checks
composer ci
```

Architecture
------------

[](#architecture)

```
src/
├── Decoder.php          # Main decoder (auto-detect type)
├── Encoder.php          # Main encoder (all message types)
├── Signer.php           # BIP-340 Schnorr + Merkle tree
├── Bech32.php           # Bech32 WITHOUT checksum (per BOLT 12)
├── TlvStream.php        # BigSize TLV encoding/decoding
├── TlvEntry.php         # TLV entry value object
├── Offer.php            # Offer value object (readonly)
├── InvoiceRequest.php   # Invoice request value object (readonly)
├── Invoice.php          # Invoice value object (readonly)
├── InvoiceError.php     # Invoice error value object (readonly)
├── BlindedPath.php      # Blinded path with encoding/decoding
├── BlindedPayInfo.php   # Blinded payment info
├── FallbackAddress.php  # On-chain fallback address
├── OnionMessageHop.php  # Blinded path hop
├── Bip353Name.php       # BIP-353 name resolution
├── Helpers.php          # Byte/hex/string utilities
└── Exception/
    ├── Bolt12Exception.php
    ├── DecodeException.php
    ├── EncodeException.php
    └── SignatureException.php

```

Key Differences from BOLT 11
----------------------------

[](#key-differences-from-bolt-11)

FeatureBOLT 11BOLT 12EncodingBech32 with checksumBech32 **without** checksumCryptoECDSA recovery**BIP-340 Schnorr**SignaturesSimple message hash**Merkle tree** constructionTLV encoding5-bit continuation**BigSize** encodingMessage types1 (invoice only)**4** (offer, invreq, invoice, error)PrivacyDirect node ID**Blinded paths**License
-------

[](#license)

MIT — see [LICENSE](LICENSE).

###  Health Score

33

—

LowBetter than 75% of packages

Maintenance82

Actively maintained with recent releases

Popularity0

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity39

Early-stage or recently created project

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Unknown

Total

1

Last Release

92d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/56e851d5bb00007a398de4c57afd1bfd709e2922fd8ad929c2feb65a92315957?d=identicon)[nova-carnivore](/maintainers/nova-carnivore)

---

Top Contributors

[![nova-carnivore](https://avatars.githubusercontent.com/u/259974792?v=4)](https://github.com/nova-carnivore "nova-carnivore (2 commits)")

---

Tags

invoicebitcoinofferLightninglightning-networkschnorrbolt12bip340lnolnrlni

###  Code Quality

TestsPHPUnit

Static AnalysisPHPStan

Code StylePHP CS Fixer

Type Coverage Yes

### Embed Badge

![Health badge](/badges/nova-carnivore-bolt12-php/health.svg)

```
[![Health](https://phpackages.com/badges/nova-carnivore-bolt12-php/health.svg)](https://phpackages.com/packages/nova-carnivore-bolt12-php)
```

###  Alternatives

[laraveldaily/laravel-invoices

Missing invoices for Laravel

1.5k1.3M4](/packages/laraveldaily-laravel-invoices)[horstoeko/zugferd

A library for creating and reading european electronic invoices

4174.3M18](/packages/horstoeko-zugferd)[atgp/factur-x

PHP library to manage your Factur-X / ZUGFeRD 2.0 PDF invoices files

138825.5k3](/packages/atgp-factur-x)[num-num/ubl-invoice

A modern object-oriented PHP library to create and read valid UBL and EN 16931/Peppol BIS 3.0 files

135820.5k](/packages/num-num-ubl-invoice)[konekt/pdf-invoice

Library to generate PDF invoices

212200.7k](/packages/konekt-pdf-invoice)[josemmo/einvoicing

Library for reading and creating European-compliant electronic invoices (EN 16931)

173279.6k2](/packages/josemmo-einvoicing)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
