commit 756d5f8e4d1d04e0b447578269e435483740169e
parent 2a8386e7d54bcdb1a53f28d893b8b0dac1a8fd16
Author: Jared Tobin <jared@jtobin.io>
Date: Sun, 22 Jun 2025 13:26:39 +0400
lib: vertical integration
Diffstat:
6 files changed, 82 insertions(+), 33 deletions(-)
diff --git a/README.md b/README.md
@@ -23,38 +23,34 @@ As we bind to libsecp256k1, the resulting functions are very fast:
```
benchmarking csecp256k1/ecdsa/sign
- time 33.67 μs (33.43 μs .. 34.00 μs)
- 1.000 R² (0.999 R² .. 1.000 R²)
- mean 33.74 μs (33.64 μs .. 33.87 μs)
- std dev 378.5 ns (259.2 ns .. 606.8 ns)
+ time 13.31 μs (13.30 μs .. 13.31 μs)
+ 1.000 R² (1.000 R² .. 1.000 R²)
+ mean 13.33 μs (13.32 μs .. 13.33 μs)
+ std dev 11.15 ns (8.932 ns .. 15.01 ns)
benchmarking csecp256k1/ecdsa/verify
- time 38.01 μs (37.44 μs .. 38.65 μs)
- 0.999 R² (0.998 R² .. 1.000 R²)
- mean 37.82 μs (37.56 μs .. 38.16 μs)
- std dev 912.8 ns (657.5 ns .. 1.263 μs)
- variance introduced by outliers: 22% (moderately inflated)
+ time 12.35 μs (12.34 μs .. 12.38 μs)
+ 1.000 R² (1.000 R² .. 1.000 R²)
+ mean 12.35 μs (12.35 μs .. 12.36 μs)
+ std dev 21.83 ns (9.273 ns .. 47.76 ns)
benchmarking csecp256k1/schnorr/sign
- time 49.97 μs (49.60 μs .. 50.41 μs)
- 0.999 R² (0.999 R² .. 1.000 R²)
- mean 49.95 μs (49.54 μs .. 50.54 μs)
- std dev 1.618 μs (1.200 μs .. 2.399 μs)
- variance introduced by outliers: 34% (moderately inflated)
+ time 18.35 μs (18.35 μs .. 18.36 μs)
+ 1.000 R² (1.000 R² .. 1.000 R²)
+ mean 18.35 μs (18.35 μs .. 18.35 μs)
+ std dev 5.990 ns (4.283 ns .. 9.131 ns)
benchmarking csecp256k1/schnorr/verify
- time 41.84 μs (41.32 μs .. 42.26 μs)
- 0.999 R² (0.998 R² .. 0.999 R²)
- mean 41.50 μs (41.06 μs .. 41.94 μs)
- std dev 1.432 μs (1.167 μs .. 1.715 μs)
- variance introduced by outliers: 37% (moderately inflated)
+ time 14.15 μs (14.14 μs .. 14.15 μs)
+ 1.000 R² (1.000 R² .. 1.000 R²)
+ mean 14.14 μs (14.13 μs .. 14.15 μs)
+ std dev 30.51 ns (14.54 ns .. 57.66 ns)
benchmarking csecp256k1/ecdh/ecdh
- time 47.43 μs (46.78 μs .. 48.19 μs)
- 0.998 R² (0.997 R² .. 0.999 R²)
- mean 46.86 μs (46.33 μs .. 47.58 μs)
- std dev 2.075 μs (1.609 μs .. 2.747 μs)
- variance introduced by outliers: 49% (moderately inflated)
+ time 15.02 μs (15.02 μs .. 15.03 μs)
+ 1.000 R² (1.000 R² .. 1.000 R²)
+ mean 15.02 μs (15.00 μs .. 15.03 μs)
+ std dev 34.78 ns (10.81 ns .. 71.53 ns)
```
## Security
diff --git a/flake.lock b/flake.lock
@@ -34,6 +34,37 @@
"type": "github"
}
},
+ "ppad-base16": {
+ "inputs": {
+ "flake-utils": [
+ "ppad-base16",
+ "ppad-nixpkgs",
+ "flake-utils"
+ ],
+ "nixpkgs": [
+ "ppad-base16",
+ "ppad-nixpkgs",
+ "nixpkgs"
+ ],
+ "ppad-nixpkgs": [
+ "ppad-nixpkgs"
+ ]
+ },
+ "locked": {
+ "lastModified": 1741625558,
+ "narHash": "sha256-ZBDXRD5fsVqA5bGrAlcnhiu67Eo50q0M9614nR3NBwY=",
+ "ref": "master",
+ "rev": "fb63457f2e894eda28250dfe65d0fcd1d195ac2f",
+ "revCount": 24,
+ "type": "git",
+ "url": "git://git.ppad.tech/base16.git"
+ },
+ "original": {
+ "ref": "master",
+ "type": "git",
+ "url": "git://git.ppad.tech/base16.git"
+ }
+ },
"ppad-nixpkgs": {
"inputs": {
"flake-utils": "flake-utils",
@@ -95,6 +126,7 @@
"ppad-nixpkgs",
"nixpkgs"
],
+ "ppad-base16": "ppad-base16",
"ppad-nixpkgs": "ppad-nixpkgs",
"ppad-sha256": "ppad-sha256"
}
diff --git a/flake.nix b/flake.nix
@@ -7,6 +7,12 @@
url = "git://git.ppad.tech/nixpkgs.git";
ref = "master";
};
+ ppad-base16 = {
+ type = "git";
+ url = "git://git.ppad.tech/base16.git";
+ ref = "master";
+ inputs.ppad-nixpkgs.follows = "ppad-nixpkgs";
+ };
ppad-sha256 = {
type = "git";
url = "git://git.ppad.tech/sha256.git";
@@ -18,6 +24,7 @@
};
outputs = { self, nixpkgs, flake-utils, ppad-nixpkgs
+ , ppad-base16
, ppad-sha256
}:
flake-utils.lib.eachDefaultSystem (system:
@@ -27,11 +34,14 @@
pkgs = import nixpkgs { inherit system; };
hlib = pkgs.haskell.lib;
+ base16 = ppad-base16.packages.${system}.default;
sha256 = ppad-sha256.packages.${system}.default;
hpkgs = pkgs.haskell.packages.ghc981.extend (new: old: {
+ ppad-base16 = base16;
ppad-sha256 = sha256;
${lib} = new.callCabal2nix lib ./. {
+ ppad-base16 = new.ppad-base16;
ppad-sha256 = new.ppad-sha256;
};
});
diff --git a/ppad-csecp256k1.cabal b/ppad-csecp256k1.cabal
@@ -50,8 +50,8 @@ test-suite csecp256k1-tests
aeson
, attoparsec
, base
- , base16-bytestring
, bytestring
+ , ppad-base16
, ppad-csecp256k1
, ppad-sha256
, tasty
diff --git a/test/BIP340.hs b/test/BIP340.hs
@@ -16,6 +16,11 @@ import qualified Data.ByteString.Base16 as B16
import Test.Tasty
import Test.Tasty.HUnit
+decodeLenient :: BS.ByteString -> BS.ByteString
+decodeLenient bs = case B16.decode bs of
+ Nothing -> error "bang"
+ Just b -> b
+
data Case = Case {
c_index :: !Int
, c_sk :: !BS.ByteString
@@ -29,7 +34,7 @@ data Case = Case {
execute :: Context -> Case -> TestTree
execute tex Case {..} = testCase ("bip0340 " <> show c_index) $ do
- par <- try (parse_xonly tex (B16.decodeLenient c_pk))
+ par <- try (parse_xonly tex (decodeLenient c_pk))
:: IO (Either Secp256k1Exception XOnlyPub)
case par of
Left _ -> assertBool mempty (not c_res)
@@ -59,15 +64,15 @@ test_case :: AT.Parser Case
test_case = do
c_index <- AT.decimal AT.<?> "index"
_ <- AT.char ','
- c_sk <- fmap B16.decodeLenient (AT.takeWhile (/= ',') AT.<?> "sk")
+ c_sk <- fmap decodeLenient (AT.takeWhile (/= ',') AT.<?> "sk")
_ <- AT.char ','
c_pk <- AT.takeWhile1 (/= ',') AT.<?> "pk"
_ <- AT.char ','
- c_aux <- fmap B16.decodeLenient (AT.takeWhile (/= ',') AT.<?> "aux")
+ c_aux <- fmap decodeLenient (AT.takeWhile (/= ',') AT.<?> "aux")
_ <- AT.char ','
- c_msg <- fmap B16.decodeLenient (AT.takeWhile (/= ',') AT.<?> "msg")
+ c_msg <- fmap decodeLenient (AT.takeWhile (/= ',') AT.<?> "msg")
_ <- AT.char ','
- c_sig <- fmap B16.decodeLenient (AT.takeWhile1 (/= ',') AT.<?> "sig")
+ c_sig <- fmap decodeLenient (AT.takeWhile1 (/= ',') AT.<?> "sig")
_ <- AT.char ','
c_res <- (AT.string "TRUE" *> pure True) <|> (AT.string "FALSE" *> pure False)
AT.<?> "res"
diff --git a/test/Wycheproof.hs b/test/Wycheproof.hs
@@ -13,15 +13,21 @@ import Crypto.Curve.Secp256k1
import qualified Crypto.Hash.SHA256 as SHA256
import Data.Aeson ((.:))
import qualified Data.Aeson as A
+import qualified Data.ByteString as BS
import qualified Data.ByteString.Base16 as B16
import qualified Data.Text as T
import qualified Data.Text.Encoding as TE
import Test.Tasty (TestTree, testGroup)
import Test.Tasty.HUnit (assertBool, testCase)
+decodeLenient :: BS.ByteString -> BS.ByteString
+decodeLenient bs = case B16.decode bs of
+ Nothing -> error "bang"
+ Just b -> b
+
execute_group :: Context -> EcdsaTestGroup -> IO TestTree
execute_group tex EcdsaTestGroup {..} = do
- let raw = B16.decodeLenient (TE.encodeUtf8 pk_uncompressed)
+ let raw = decodeLenient (TE.encodeUtf8 pk_uncompressed)
pub <- parse_pub tex raw
let tests = fmap (execute tex pub) etg_tests
pure (testGroup msg tests)
@@ -31,8 +37,8 @@ execute_group tex EcdsaTestGroup {..} = do
execute :: Context -> Pub -> EcdsaVerifyTest -> TestTree
execute tex pub EcdsaVerifyTest {..} = testCase report $ do
- let msg = SHA256.hash (B16.decodeLenient (TE.encodeUtf8 t_msg))
- sig = B16.decodeLenient (TE.encodeUtf8 t_sig)
+ let msg = SHA256.hash (decodeLenient (TE.encodeUtf8 t_msg))
+ sig = decodeLenient (TE.encodeUtf8 t_sig)
syg <- try (parse_der tex sig) :: IO (Either Secp256k1Exception Sig)
case syg of
Left _ -> assertBool mempty (t_result == "invalid")