commit 7c32a438cf58b1f6a3a90a570e7f051a9bbaf76a
Author: Jared Tobin <jared@jtobin.io>
Date: Sun, 25 Jan 2026 14:16:37 +0400
ppad-bolt4: initial project skeleton
BOLT4 onion routing implementation for Lightning Network.
Includes:
- Library stub (Lightning.Protocol.BOLT4)
- Test/benchmark scaffolding
- Nix flake with deps: ppad-chacha, ppad-hmac-sha256,
ppad-secp256k1, ppad-sha256
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Diffstat:
11 files changed, 382 insertions(+), 0 deletions(-)
diff --git a/.gitignore b/.gitignore
@@ -0,0 +1 @@
+dist-newstyle/
diff --git a/AGENTS.md b/AGENTS.md
@@ -0,0 +1,76 @@
+# ppad-bolt4
+
+A pure Haskell implementation of BOLT4 (onion routing) from the
+Lightning Network specification.
+
+The implementation consists of the ppad-bolt4 library, a test suite,
+and benchmarks (for time and space).
+
+## build
+
+Builds are handled via Nix and Cabal. Enter a development shell via:
+
+ $ nix develop
+
+and then do Cabal stuff:
+
+ $ cabal build
+ $ cabal test
+ $ cabal bench
+
+## deps
+
+The following dependencies are allowed freely:
+
+* Any GHC boot library or library included with GHC's normal
+ distribution (e.g. bytestring, primitive, etc.).
+* Any 'ppad-' library, as found on github.com/ppad-tech or
+ git.ppad.tech.
+
+Any other library requires explicit confirmation before being used.
+
+Test and benchmark dependencies (e.g. tasty, criterion, QuickCheck,
+etc.) are an exception.
+
+Never use Stack or any non-Nix build or dependency management
+tool. Never use 'pip install' or 'npm install' or anything of the
+sort. The Nix development environment includes everything one
+needs.
+
+## style
+
+Please adhere to the following guidelines:
+
+* Keep lines at less than 80 characters in length.
+* The codebase is Haskell2010.
+* LANGUAGE pragmas should be added per-module.
+* Haddock documentation where appropriate; use `{-# OPTIONS_HADDOCK
+ prune #-}` and `-- |`-style comments for public modules.
+* Prefer total functions; avoid partial Prelude functions such
+ as 'head', 'tail', '!!', etc.
+* Use strict annotations (bang patterns, etc.) liberally. Add
+ UNPACK pragmas in data types.
+* Use newtypes for type safety.
+* Use smart constructors for validation.
+* Use Maybe/Either for fallible operations.
+* Use MagicHash/UnboxedTuples for hot paths.
+* Add INLINE pragmas for small functions.
+
+## git
+
+* Never update the git config.
+* Never use destructive git commands (such as push --force, hard reset,
+ etc.) unless explicitly requested.
+* Never skip hooks unless explicitly requested.
+* The main branch is `master`, which mostly consists of merge commits.
+ Feature branches are branched from and merged (with `--no-ff`) back
+ to master.
+
+## misc
+
+* Be very cautious when suggesting changes to the flake.nix file. You
+ should fully understand the effect of any change before making it.
+* Don't create markdown files, e.g. for documentation purposes.
+* When producing plans, highlight any steps that could potentially be
+ executed by concurrent subagents. Place plans in the `plans/`
+ directory, e.g. as `plans/IMPL1.md`.
diff --git a/CHANGELOG b/CHANGELOG
@@ -0,0 +1,4 @@
+# Changelog
+
+- 0.0.1 (UNRELEASED)
+ * Initial release.
diff --git a/LICENSE b/LICENSE
@@ -0,0 +1,20 @@
+Copyright (c) 2025 Jared Tobin
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be included
+in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/README.md b/README.md
@@ -0,0 +1,20 @@
+# ppad-bolt4
+
+A pure Haskell implementation of [BOLT4][bolt4] (onion routing) from
+the Lightning Network protocol specification.
+
+## Documentation
+
+Haddocks (API documentation, etc.) are hosted at
+[docs.ppad.tech/bolt4][hadoc].
+
+## Security
+
+This library is provided for educational purposes. It has not been
+audited by a third party, and should not be used in production.
+
+If you discover any security-related issues, please contact
+jared@ppad.tech.
+
+[bolt4]: https://github.com/lightning/bolts/blob/master/04-onion-routing.md
+[hadoc]: https://docs.ppad.tech/bolt4
diff --git a/bench/Main.hs b/bench/Main.hs
@@ -0,0 +1,10 @@
+{-# LANGUAGE BangPatterns #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Main where
+
+import Criterion.Main
+
+main :: IO ()
+main = defaultMain [
+ ]
diff --git a/bench/Weight.hs b/bench/Weight.hs
@@ -0,0 +1,9 @@
+{-# LANGUAGE BangPatterns #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Main where
+
+import Weigh
+
+main :: IO ()
+main = mainWith (pure ())
diff --git a/flake.nix b/flake.nix
@@ -0,0 +1,134 @@
+{
+ description = "A Haskell implementation of BOLT4 (onion routing).";
+
+ inputs = {
+ ppad-base16 = {
+ type = "git";
+ url = "git://git.ppad.tech/base16.git";
+ ref = "master";
+ inputs.ppad-nixpkgs.follows = "ppad-nixpkgs";
+ };
+ ppad-chacha = {
+ type = "git";
+ url = "git://git.ppad.tech/chacha.git";
+ ref = "master";
+ inputs.ppad-nixpkgs.follows = "ppad-nixpkgs";
+ };
+ ppad-hmac-sha256 = {
+ type = "git";
+ url = "git://git.ppad.tech/hmac-sha256.git";
+ ref = "master";
+ inputs.ppad-nixpkgs.follows = "ppad-nixpkgs";
+ inputs.ppad-sha256.follows = "ppad-sha256";
+ };
+ ppad-secp256k1 = {
+ type = "git";
+ url = "git://git.ppad.tech/secp256k1.git";
+ ref = "master";
+ inputs.ppad-nixpkgs.follows = "ppad-nixpkgs";
+ inputs.ppad-sha256.follows = "ppad-sha256";
+ };
+ ppad-sha256 = {
+ type = "git";
+ url = "git://git.ppad.tech/sha256.git";
+ ref = "master";
+ inputs.ppad-nixpkgs.follows = "ppad-nixpkgs";
+ };
+ ppad-nixpkgs = {
+ type = "git";
+ url = "git://git.ppad.tech/nixpkgs.git";
+ ref = "master";
+ };
+ flake-utils.follows = "ppad-nixpkgs/flake-utils";
+ nixpkgs.follows = "ppad-nixpkgs/nixpkgs";
+ };
+
+ outputs = { self, nixpkgs, flake-utils, ppad-nixpkgs
+ , ppad-base16, ppad-chacha, ppad-hmac-sha256
+ , ppad-secp256k1, ppad-sha256
+ }:
+ flake-utils.lib.eachDefaultSystem (system:
+ let
+ lib = "ppad-bolt4";
+
+ pkgs = import nixpkgs { inherit system; };
+ hlib = pkgs.haskell.lib;
+ llvm = pkgs.llvmPackages_19.llvm;
+ clang = pkgs.llvmPackages_19.clang;
+
+ base16 = ppad-base16.packages.${system}.default;
+ base16-llvm =
+ hlib.addBuildTools
+ (hlib.enableCabalFlag base16 "llvm")
+ [ llvm clang ];
+
+ chacha = ppad-chacha.packages.${system}.default;
+ chacha-llvm =
+ hlib.addBuildTools
+ (hlib.enableCabalFlag chacha "llvm")
+ [ llvm clang ];
+
+ hmac-sha256 = ppad-hmac-sha256.packages.${system}.default;
+ hmac-sha256-llvm =
+ hlib.addBuildTools
+ (hlib.enableCabalFlag hmac-sha256 "llvm")
+ [ llvm clang ];
+
+ secp256k1 = ppad-secp256k1.packages.${system}.default;
+ secp256k1-llvm =
+ hlib.addBuildTools
+ (hlib.enableCabalFlag secp256k1 "llvm")
+ [ llvm clang ];
+
+ sha256 = ppad-sha256.packages.${system}.default;
+ sha256-llvm =
+ hlib.addBuildTools
+ (hlib.enableCabalFlag sha256 "llvm")
+ [ llvm clang ];
+
+ hpkgs = pkgs.haskell.packages.ghc910.extend (new: old: {
+ ppad-base16 = base16-llvm;
+ ppad-chacha = chacha-llvm;
+ ppad-hmac-sha256 = hmac-sha256-llvm;
+ ppad-secp256k1 = secp256k1-llvm;
+ ppad-sha256 = sha256-llvm;
+ ${lib} = new.callCabal2nix lib ./. {
+ ppad-base16 = new.ppad-base16;
+ ppad-chacha = new.ppad-chacha;
+ ppad-hmac-sha256 = new.ppad-hmac-sha256;
+ ppad-secp256k1 = new.ppad-secp256k1;
+ ppad-sha256 = new.ppad-sha256;
+ };
+ });
+
+ cc = pkgs.stdenv.cc;
+ ghc = hpkgs.ghc;
+ cabal = hpkgs.cabal-install;
+ in
+ {
+ packages.default = hpkgs.${lib};
+
+ packages.haddock = hpkgs.${lib}.doc;
+
+ devShells.default = hpkgs.shellFor {
+ packages = p: [
+ (hlib.doBenchmark p.${lib})
+ ];
+
+ buildInputs = [
+ cabal
+ cc
+ llvm
+ ];
+
+ shellHook = ''
+ PS1="[${lib}] \w$ "
+ echo "entering ${system} shell, using"
+ echo "cc: $(${cc}/bin/cc --version)"
+ echo "ghc: $(${ghc}/bin/ghc --version)"
+ echo "cabal: $(${cabal}/bin/cabal --version)"
+ '';
+ };
+ }
+ );
+}
diff --git a/lib/Lightning/Protocol/BOLT4.hs b/lib/Lightning/Protocol/BOLT4.hs
@@ -0,0 +1,23 @@
+{-# OPTIONS_HADDOCK prune #-}
+{-# LANGUAGE BangPatterns #-}
+{-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE RecordWildCards #-}
+
+-- |
+-- Module: Lightning.Protocol.BOLT4
+-- Copyright: (c) 2025 Jared Tobin
+-- License: MIT
+-- Maintainer: Jared Tobin <jared@ppad.tech>
+--
+-- BOLT4 onion routing for the Lightning Network.
+
+module Lightning.Protocol.BOLT4 (
+ -- placeholder exports
+ ) where
+
+import qualified Data.ByteString as BS
+
+-- XX placeholder
+_placeholder :: BS.ByteString -> BS.ByteString
+_placeholder = id
diff --git a/ppad-bolt4.cabal b/ppad-bolt4.cabal
@@ -0,0 +1,76 @@
+cabal-version: 3.0
+name: ppad-bolt4
+version: 0.0.1
+synopsis: BOLT4 (onion routing) for Lightning Network
+license: MIT
+license-file: LICENSE
+author: Jared Tobin
+maintainer: jared@ppad.tech
+category: Cryptography
+build-type: Simple
+tested-with: GHC == 9.10.3
+description:
+ A pure Haskell implementation of BOLT4 (onion routing) from
+ the Lightning Network protocol specification.
+
+source-repository head
+ type: git
+ location: git://git.ppad.tech/bolt4.git
+
+library
+ default-language: Haskell2010
+ hs-source-dirs: lib
+ ghc-options:
+ -Wall
+ exposed-modules:
+ Lightning.Protocol.BOLT4
+ build-depends:
+ base >= 4.9 && < 5
+ , bytestring >= 0.9 && < 0.13
+ , ppad-chacha >= 0.1 && < 0.2
+ , ppad-hmac-sha256 >= 0.1 && < 0.2
+ , ppad-secp256k1 >= 0.3 && < 0.4
+ , ppad-sha256 >= 0.3 && < 0.4
+
+test-suite bolt4-tests
+ type: exitcode-stdio-1.0
+ default-language: Haskell2010
+ hs-source-dirs: test
+ main-is: Main.hs
+ ghc-options:
+ -rtsopts -Wall -O2
+ build-depends:
+ base
+ , bytestring
+ , ppad-base16
+ , ppad-bolt4
+ , tasty
+ , tasty-hunit
+ , tasty-quickcheck
+
+benchmark bolt4-bench
+ type: exitcode-stdio-1.0
+ default-language: Haskell2010
+ hs-source-dirs: bench
+ main-is: Main.hs
+ ghc-options:
+ -rtsopts -O2 -Wall -fno-warn-orphans
+ build-depends:
+ base
+ , bytestring
+ , criterion
+ , deepseq
+ , ppad-bolt4
+
+benchmark bolt4-weigh
+ type: exitcode-stdio-1.0
+ default-language: Haskell2010
+ hs-source-dirs: bench
+ main-is: Weight.hs
+ ghc-options:
+ -rtsopts -O2 -Wall -fno-warn-orphans
+ build-depends:
+ base
+ , bytestring
+ , ppad-bolt4
+ , weigh
diff --git a/test/Main.hs b/test/Main.hs
@@ -0,0 +1,9 @@
+{-# LANGUAGE OverloadedStrings #-}
+
+module Main where
+
+import Test.Tasty
+
+main :: IO ()
+main = defaultMain $ testGroup "ppad-bolt4" [
+ ]