script

Primitive (Bitcoin) Script support for Haskell.
git clone git://git.ppad.tech/script.git
Log | Files | Refs | README | LICENSE

commit 8493ab678487eb0691abced2a2863a9a4beee95c
parent 7ae06ff709400e9149169fa9a9a608e248c31d42
Author: Jared Tobin <jared@jtobin.io>
Date:   Sun, 19 Jan 2025 21:16:38 +0400

meta: readme

Diffstat:
AREADME.md | 123+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mbench/Main.hs | 13+++++++++++++
2 files changed, 136 insertions(+), 0 deletions(-)

diff --git a/README.md b/README.md @@ -0,0 +1,123 @@ +# ppad-script + +Representations for [Script](https://en.bitcoin.it/wiki/Script), +including abstract syntax, 'ByteArray', and base16-encoded 'ByteString' +versions, as well as fast conversion utilities for working with them. + +## Usage + +A sample GHCi session: + +``` + > :set -XOverloadedStrings + > + > -- import qualified + > import qualified Bitcoin.Prim.Script as S + > + > -- base16-encoded p2pkh scriptPubKey + > let p2pkh = "76a91489abcdefabbaabbaabbaabbaabbaabbaabbaabba88ac" + > + > -- bytearray-encoded + > let Just script = S.from_base16 p2pkh + > script + Script [ 0x76, 0xa9, 0x14, 0x89, 0xab, 0xcd, 0xef + , 0xab, 0xba, 0xab, 0xba, 0xab, 0xba, 0xab + , 0xba, 0xab, 0xba, 0xab, 0xba, 0xab, 0xba + , 0xab, 0xba, 0x88, 0xac + ] + > + > -- abstract syntax-encoded + > let terms = S.from_script script + > terms + [ OP_DUP, OP_HASH160, OP_PUSHBYTES_20, 0x89, 0xab, 0xcd, 0xef + , 0xab, 0xba, 0xab, 0xba, 0xab, 0xba, 0xab + , 0xba, 0xab, 0xba, 0xab, 0xba, 0xab, 0xba + , 0xab, 0xba, OP_EQUALVERIFY, OP_CHECKSIG + ] + > + > -- round-trip + > S.to_base16 (S.to_script terms) + "76a91489abcdefabbaabbaabbaabbaabbaabbaabbaabba88ac" +``` + +## Documentation + +Haddocks (API documentation, etc.) are hosted at +[docs.ppad.tech/script](https://docs.ppad.tech/script). + +## Performance + +The aim is best-in-class performance for highly-auditable Haskell code. + +Current benchmark figures on my mid-2020 MacBook Air look like (use +`cabal bench` to run the benchmark suite): + +``` + benchmarking to_script + time 484.9 ns (478.3 ns .. 491.4 ns) + 0.998 R² (0.997 R² .. 0.999 R²) + mean 496.2 ns (485.8 ns .. 508.1 ns) + std dev 37.17 ns (30.08 ns .. 49.95 ns) + variance introduced by outliers: 83% (severely inflated) + + benchmarking from_script + time 380.8 ns (374.3 ns .. 387.5 ns) + 0.998 R² (0.996 R² .. 0.999 R²) + mean 383.0 ns (375.3 ns .. 395.4 ns) + std dev 31.88 ns (22.41 ns .. 43.86 ns) + variance introduced by outliers: 86% (severely inflated) + + benchmarking to_base16 + time 291.3 ns (285.6 ns .. 297.9 ns) + 0.996 R² (0.995 R² .. 0.998 R²) + mean 298.3 ns (291.8 ns .. 308.1 ns) + std dev 26.38 ns (21.25 ns .. 34.27 ns) + variance introduced by outliers: 87% (severely inflated) + + benchmarking from_base16 + time 439.1 ns (429.9 ns .. 448.2 ns) + 0.997 R² (0.996 R² .. 0.998 R²) + mean 437.9 ns (429.9 ns .. 450.0 ns) + std dev 32.67 ns (26.12 ns .. 44.04 ns) + variance introduced by outliers: 83% (severely inflated) +``` + +where the inputs to the above functions are variations of the script found +in the 'Usage' section. + +## Security + +This library aims at the maximum security achievable in a +garbage-collected language under an optimizing compiler such as GHC, in +which strict constant-timeness can be challenging to achieve. + +This is an early-development, pre-production-ready release, and is not +**not** yet suitable for mainnet use. + +If you discover any vulnerabilities, please disclose them via +security@ppad.tech. + +## Development + +You'll require [Nix][nixos] with [flake][flake] support enabled. Enter a +development shell with: + +``` +$ nix develop +``` + +Then do e.g.: + +``` +$ cabal repl ppad-script +``` + +to get a REPL for the main library. + +## Attribution + +The list of opcodes was taken verbatim from the 'opcode' crate found in +[rust-bitcoin](https://github.com/rust-bitcoin/rust-bitcoin). + +[nixos]: https://nixos.org/ +[flake]: https://nixos.org/manual/nix/unstable/command-ref/new-cli/nix3-flake.html diff --git a/bench/Main.hs b/bench/Main.hs @@ -60,11 +60,24 @@ from_script = bench "from_script" $ nf S.from_script script where Nothing -> error "invalid script" Just !s -> s +to_base16 :: Benchmark +to_base16 = bench "to_base16" $ nf S.to_base16 script where + b16 = "76a91489abcdefabbaabbaabbaabbaabbaabbaabbaabba88ac" + script = case S.from_base16 b16 of + Nothing -> error "invalid script" + Just !s -> s + +from_base16 :: Benchmark +from_base16 = bench "from_base16" $ nf S.from_base16 b16 where + b16 = "76a91489abcdefabbaabbaabbaabbaabbaabbaabbaabba88ac" + main :: IO () main = defaultMain [ ba_to_bs , bs_to_ba , to_script , from_script + , to_base16 + , from_base16 ]