commit 8493ab678487eb0691abced2a2863a9a4beee95c
parent 7ae06ff709400e9149169fa9a9a608e248c31d42
Author: Jared Tobin <jared@jtobin.io>
Date: Sun, 19 Jan 2025 21:16:38 +0400
meta: readme
Diffstat:
A | README.md | | | 123 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
M | bench/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
]