packages feed

ppad-bip32 (empty) → 0.1.0

raw patch · 6 files changed

+805/−0 lines, 6 filesdep +arraydep +basedep +bytestring

Dependencies added: array, base, bytestring, criterion, deepseq, ppad-base16, ppad-base58, ppad-bip32, ppad-ripemd160, ppad-secp256k1, ppad-sha256, ppad-sha512, tasty, tasty-hunit

Files

+ CHANGELOG view
@@ -0,0 +1,6 @@+# Changelog++- 0.1.0 (2025-02-22)+  * Initial release, supporting basic HD wallet functionality.++
+ LICENSE view
@@ -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.
+ bench/Main.hs view
@@ -0,0 +1,57 @@+{-# OPTIONS_GHC -fno-warn-orphans #-}+{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE StandaloneDeriving #-}++module Main where++import Criterion.Main+import Crypto.HDKey.BIP32+import Control.DeepSeq+import Crypto.Curve.Secp256k1 as S++instance NFData S.Projective++instance NFData XPub where+  rnf (XPub (X a b)) = a `deepseq` b `deepseq` ()++instance NFData XPrv where+  rnf (XPrv (X a b)) = a `deepseq` b `deepseq` ()++instance NFData HDKey where+  rnf (HDKey a b c d) = a `deepseq` b `deepseq` c `deepseq` d `deepseq` ()++main :: IO ()+main = defaultMain [+    bgroup "ppad-bip32" [+        bench_master+      , bench_derive_pub+      , bench_derive_priv+      , bench_xpub+      , bench_xprv+    ]+  ]++m :: HDKey+m = case master "my super entropic entropy" of+  Just !s -> s+  _ -> error "bang"++bench_master :: Benchmark+bench_master = bench "master" $ nf master "my super entropic entropy"++bench_derive_pub :: Benchmark+bench_derive_pub = bench "derive_child_pub" $ nf (derive_child_pub m) 0++bench_derive_priv :: Benchmark+bench_derive_priv = bench "derive_child_priv" $ nf (derive_child_priv m) 0++bench_xpub :: Benchmark+bench_xpub = bench "xpub" $ nf xpub m++bench_xprv :: Benchmark+bench_xprv = bench "xprv" $ nf xprv m++bench_parse :: Benchmark+bench_parse = bench "parse" $ nf parse (xprv m)+
+ lib/Crypto/HDKey/BIP32.hs view
@@ -0,0 +1,446 @@+{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE BinaryLiterals #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE NumericUnderscores #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE ViewPatterns #-}++-- |+-- Module: Crypto.HDKey.BIP32+-- Copyright: (c) 2025 Jared Tobin+-- License: MIT+-- Maintainer: Jared Tobin <jared@ppad.tech>+--+-- [BIP32](https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki)+-- hierarchical deterministic wallets and extended keys, with support for+-- serialization and parsing.++module Crypto.HDKey.BIP32 (+  -- * Hierarchical deterministic keys+    HDKey(..)+  , master++  -- * Extended keys+  , Extended(..)+  , XPub(..)+  , XPrv(..)+  , X(..)++  -- * Child derivation via path+  , derive+  , derive_partial++  -- * Serialization+  , xpub+  , xprv+  , tpub+  , tprv++  -- * Parsing+  , parse++  -- * Child key derivation functions+  , derive_child_pub+  , derive_child_priv+  ) where++import Control.Monad (guard)+import qualified Crypto.Hash.SHA256 as SHA256+import qualified Crypto.Hash.SHA512 as SHA512+import qualified Crypto.Hash.RIPEMD160 as RIPEMD160+import qualified Crypto.Curve.Secp256k1 as Secp256k1+import Data.Bits ((.<<.), (.>>.), (.|.), (.&.))+import qualified Data.ByteString as BS+import qualified Data.ByteString.Char8 as B8+import qualified Data.ByteString.Base58Check as B58C+import qualified Data.ByteString.Builder as BSB+import qualified Data.ByteString.Internal as BI+import Data.Word (Word8, Word32)++-- utilities ------------------------------------------------------------------++fi :: (Integral a, Num b) => a -> b+fi = fromIntegral+{-# INLINE fi #-}++-- big-endian bytestring encoding+unroll :: Integer -> BS.ByteString+unroll i = case i of+    0 -> BS.singleton 0+    _ -> BS.reverse $ BS.unfoldr coalg i+  where+    coalg 0 = Nothing+    coalg m = Just (fi m, m .>>. 8)++-- parse 32 bytes to a 256-bit integer+parse256 :: BS.ByteString -> Integer+parse256 bs@(BI.PS _ _ l)+    | l == 32   = BS.foldl' alg 0 bs+    | otherwise = error "ppad-bip32 (parse256): invalid input"+  where+    alg !a (fi -> !b) = (a .<<. 8) .|. b++-- serialize a 256-bit integer to 32 bytes, left-padding with zeros if+-- necessary. the size of the integer is not checked.+ser256 :: Integer -> BS.ByteString+ser256 (unroll -> u@(BI.PS _ _ l))+  | l < 32 = BS.replicate (32 - l) 0 <> u+  | otherwise = u++-- serialize a 32-bit word, MSB first+ser32 :: Word32 -> BS.ByteString+ser32 w =+  let !mask = 0b00000000_00000000_00000000_11111111+      !w0 = fi (w .>>. 24) .&. mask+      !w1 = fi (w .>>. 16) .&. mask+      !w2 = fi (w .>>. 08) .&. mask+      !w3 = fi w .&. mask+  in  BS.cons w0 (BS.cons w1 (BS.cons w2 (BS.singleton w3))) -- XX++-- extended keys --------------------------------------------------------------++-- | A public or private key, extended with a chain code.+data X a = X !a !BS.ByteString+  deriving (Eq, Show)++-- | An extended public key.+newtype XPub = XPub (X Secp256k1.Projective)+  deriving (Eq, Show)++-- | An extended private key.+newtype XPrv = XPrv (X Integer)+  deriving (Eq, Show)++-- | Key types supporting identifier/fingerprint calculation.+class Extended k where+  -- | Calculate the identifier for an extended key.+  identifier  :: k -> BS.ByteString++  -- | Calculate the fingerprint of an extended key.+  fingerprint :: k -> BS.ByteString+  fingerprint = BS.take 4 . identifier++instance Extended XPub where+  identifier (XPub (X pub _)) =+    let ser = Secp256k1.serialize_point pub+    in  RIPEMD160.hash (SHA256.hash ser)++instance Extended XPrv where+  identifier (XPrv (X sec _)) =+    let p = Secp256k1.mul Secp256k1._CURVE_G sec+        ser = Secp256k1.serialize_point p+    in  RIPEMD160.hash (SHA256.hash ser)++-- internal key derivation functions-------------------------------------------++hardened :: Word32 -> Bool+hardened = (>= 0x8000_0000)++-- master xprv from seed+_master :: BS.ByteString -> Maybe XPrv+_master seed@(BI.PS _ _ l)+  | l < 16 = Nothing+  | l > 64 = Nothing+  | otherwise = do+      let i = SHA512.hmac "Bitcoin seed" seed+          (il, c) = BS.splitAt 32 i+          s = parse256 il+      pure $! (XPrv (X s c))++-- private parent key -> private child key+ckd_priv :: XPrv -> Word32 -> XPrv+ckd_priv _xprv@(XPrv (X sec cod)) i =+    let l = SHA512.hmac cod dat+        (il, ci) = BS.splitAt 32 l+        pil = parse256 il+        ki  = Secp256k1.modQ (pil + sec)+    in  if   pil >= Secp256k1._CURVE_Q || ki == 0 -- negl+        then ckd_priv _xprv (succ i)+        else XPrv (X ki ci)+  where+    dat | hardened i = BS.singleton 0x00 <> ser256 sec <> ser32 i+        | otherwise  =+            let p = Secp256k1.mul Secp256k1._CURVE_G sec+            in  Secp256k1.serialize_point p <> ser32 i++-- public parent key -> public child key+ckd_pub :: XPub -> Word32 -> Maybe XPub+ckd_pub _xpub@(XPub (X pub cod)) i+  | hardened i = Nothing+  | otherwise = do+      let dat = Secp256k1.serialize_point pub <> ser32 i+          l   = SHA512.hmac cod dat+          (il, ci) = BS.splitAt 32 l+          pil = parse256 il+          ki = Secp256k1.mul_unsafe Secp256k1._CURVE_G pil `Secp256k1.add` pub+      if   pil >= Secp256k1._CURVE_Q || ki == Secp256k1._CURVE_ZERO -- negl+      then ckd_pub _xpub (succ i)+      else pure (XPub (X ki ci))++-- private parent key -> public child key+n :: XPrv -> XPub+n (XPrv (X sec cod)) =+  let p = Secp256k1.mul Secp256k1._CURVE_G sec+  in  XPub (X p cod)++-- hierarchical deterministic keys --------------------------------------------++-- | A BIP32 hierarchical deterministic key.+--+--   This differs from lower-level "extended" keys in that it carries all+--   information required for serialization.+data HDKey = HDKey {+    hd_key    :: !(Either XPub XPrv) -- ^ extended public or private key+  , hd_depth  :: !Word8              -- ^ key depth+  , hd_parent :: !BS.ByteString      -- ^ parent fingerprint+  , hd_child  :: !BS.ByteString      -- ^ index or child number+  }+  deriving (Eq, Show)++instance Extended HDKey where+  identifier (HDKey ekey _ _ _) = case ekey of+    Left l -> identifier l+    Right r -> identifier r++-- | Derive a master 'HDKey' from a master seed.+--+--   Fails with 'Nothing' if the provided seed has an invalid length.+master :: BS.ByteString -> Maybe HDKey+master seed = do+  m <- _master seed+  pure $! HDKey {+      hd_key = Right m+    , hd_depth = 0+    , hd_parent = "\NUL\NUL\NUL\NUL" -- 0x0000_0000+    , hd_child = ser32 0+    }++-- | Derive a private child node at the provided index.+--+--   Fails with 'Nothing' if derivation is impossible.+derive_child_priv :: HDKey -> Word32 -> Maybe HDKey+derive_child_priv HDKey {..} i = case hd_key of+  Left _ -> Nothing+  Right _xprv -> pure $!+    let key   = Right (ckd_priv _xprv i)+        depth = hd_depth + 1+        parent = fingerprint _xprv+        child = ser32 i+    in  HDKey key depth parent child++-- | Derive a public child node at the provided index.+--+--   Fails with 'Nothing' if derivation is impossible.+derive_child_pub :: HDKey -> Word32 -> Maybe HDKey+derive_child_pub HDKey {..} i = do+  (key, parent) <- case hd_key of+    Left _xpub  -> do+      pub <- ckd_pub _xpub i+      pure (pub, fingerprint _xpub)+    Right _xprv ->+      let pub = n (ckd_priv _xprv i)+      in  pure (pub, fingerprint _xprv)+  let depth = hd_depth + 1+      child = ser32 i+  pure $ HDKey (Left key) depth parent child++-- derivation path expression -------------------------------------------------++-- recursive derivation path+data Path =+    M+  | !Path :| !Word32 -- hardened+  | !Path :/ !Word32+  deriving (Eq, Show)++parse_path :: BS.ByteString -> Maybe Path+parse_path bs = case BS.uncons bs of+    Nothing -> Nothing+    Just (h, t)+      | h == 109  -> go M t -- == 'm'+      | otherwise -> Nothing+  where+    child :: Path -> BS.ByteString -> Maybe (Path, BS.ByteString)+    child pat b = case B8.readInt b of+      Nothing -> Nothing+      Just (fi -> i, etc) -> case BS.uncons etc of+        Nothing -> Just $! (pat :/ i, mempty)+        Just (h, t)+          | h == 39 -> Just $! (pat :| i, t) -- '+          | otherwise -> Just $! (pat :/ i, etc)++    go pat b = case BS.uncons b of+      Nothing -> Just pat+      Just (h, t)+        | h == 47 -> do -- /+            (npat, etc) <- child pat t+            go npat etc+        | otherwise ->+            Nothing++-- | Derive a child node via the provided derivation path.+--+--   Fails with 'Nothing' if derivation is impossible, or if the+--   provided path is invalid.+--+--   >>> let hd = master "my very secret master seed"+--   >>> derive hd "m/44'/0'/0'/0/0"+derive+  :: HDKey+  -> BS.ByteString -- ^ derivation path+  -> Maybe HDKey+derive hd pat = case parse_path pat of+    Nothing -> Nothing+    Just p  -> go p+  where+    go = \case+      M -> pure hd+      p :| i -> do+        hdkey <- go p+        derive_child_priv hdkey (0x8000_0000 + i) -- 2 ^ 31+      p :/ i -> do+        hdkey <- go p+        derive_child_priv hdkey i++-- | Derive a child node via the provided derivation path.+--+--   Fails with 'error' if derivation is impossible, or if the provided+--   path is invalid.+--+--   >>> let hd = master "my very secret master seed"+--   >>> derive hd "m/44'/0'/0'/0/0"+derive_partial+  :: HDKey+  -> BS.ByteString+  -> HDKey+derive_partial hd pat = case derive hd pat of+  Nothing -> error "ppad-bip32 (derive_partial): couldn't derive extended key"+  Just hdkey -> hdkey++-- serialization --------------------------------------------------------------++_MAINNET_PUB, _MAINNET_PRV :: Word32+_TESTNET_PUB, _TESTNET_PRV :: Word32++_MAINNET_PUB_BYTES, _MAINNET_PRV_BYTES :: BS.ByteString+_TESTNET_PUB_BYTES, _TESTNET_PRV_BYTES :: BS.ByteString++_MAINNET_PUB = 0x0488B21E+_MAINNET_PUB_BYTES = "\EOT\136\178\RS"++_MAINNET_PRV = 0x0488ADE4+_MAINNET_PRV_BYTES = "\EOT\136\173\228"++_TESTNET_PUB = 0x043587CF+_TESTNET_PUB_BYTES = "\EOT5\135\207"++_TESTNET_PRV = 0x04358394+_TESTNET_PRV_BYTES = "\EOT5\131\148"++-- | Serialize a mainnet extended public key in base58check format.+xpub :: HDKey -> BS.ByteString+xpub x@HDKey {..} = B58C.encode . BS.toStrict . BSB.toLazyByteString $+  case hd_key of+    Left _  -> _serialize _MAINNET_PUB x+    Right e -> _serialize _MAINNET_PUB HDKey {+        hd_key = Left (n e)+      , ..+      }++-- | Serialize a mainnet extended private key in base58check format.+xprv :: HDKey -> BS.ByteString+xprv x@HDKey {..} = B58C.encode . BS.toStrict . BSB.toLazyByteString $+  case hd_key of+    Left _  -> error "ppad-bip32 (xprv): no private key"+    Right _ -> _serialize _MAINNET_PRV x++-- | Serialize a testnet extended public key in base58check format.+tpub :: HDKey -> BS.ByteString+tpub x@HDKey {..} = B58C.encode . BS.toStrict . BSB.toLazyByteString $+  case hd_key of+    Left _  -> _serialize _TESTNET_PUB x+    Right e -> _serialize _TESTNET_PUB HDKey {+      hd_key = Left (n e)+      , ..+      }++-- | Serialize a testnet extended private key in base58check format.+tprv :: HDKey -> BS.ByteString+tprv x@HDKey {..} = B58C.encode . BS.toStrict . BSB.toLazyByteString $+  case hd_key of+    Left _  -> error "ppad-bip32 (tprv): no private key"+    Right _ -> _serialize _TESTNET_PRV x++_serialize :: Word32 -> HDKey -> BSB.Builder+_serialize version HDKey {..} =+     BSB.word32BE version+  <> BSB.word8 hd_depth+  <> BSB.byteString hd_parent+  <> BSB.byteString hd_child+  <> case hd_key of+       Left (XPub (X pub cod)) ->+            BSB.byteString cod+         <> BSB.byteString (Secp256k1.serialize_point pub)+       Right (XPrv (X sec cod)) ->+            BSB.byteString cod+         <> BSB.word8 0x00+         <> BSB.byteString (ser256 sec)++-- parsing --------------------------------------------------------------------++data KeyType =+    Pub+  | Prv++-- | Parse a base58check-encoded 'ByteString' into a 'HDKey'.+--+--   Fails with 'Nothing' if the provided key is invalid.+parse :: BS.ByteString -> Maybe HDKey+parse b58 = do+    bs <- B58C.decode b58+    case BS.splitAt 4 bs of+      (version, etc)+        | version == _MAINNET_PUB_BYTES || version == _TESTNET_PUB_BYTES ->+            parse_pub etc+        | version == _MAINNET_PRV_BYTES || version == _TESTNET_PRV_BYTES ->+            parse_prv etc+        | otherwise ->+            Nothing+  where+    parse_pub = _parse Pub+    parse_prv = _parse Prv++    _parse ktype bs = do+      (hd_depth, etc0) <- BS.uncons bs+      let (hd_parent, etc1) = BS.splitAt 4 etc0+      guard (BS.length hd_parent == 4)+      let (hd_child, etc2) = BS.splitAt 4 etc1+      guard (BS.length hd_child == 4)+      let (cod, etc3) = BS.splitAt 32 etc2+      guard (BS.length cod == 32)+      let (key, etc4) = BS.splitAt 33 etc3+      guard (BS.length key == 33)+      guard (BS.length etc4 == 0)+      hd <- case ktype of+        Pub -> do+          pub <- Secp256k1.parse_point key+          let hd_key = Left (XPub (X pub cod))+          pure HDKey {..}+        Prv -> do+          (b, parse256 -> prv) <- BS.uncons key+          guard (b == 0)+          guard (prv > 0 && prv < Secp256k1._CURVE_Q)+          let hd_key = Right (XPrv (X prv cod))+          pure HDKey {..}+      guard (valid_lineage hd)+      pure hd+    {-# INLINE _parse #-}++valid_lineage :: HDKey -> Bool+valid_lineage HDKey {..}+  | hd_depth == 0 =+         hd_parent == "\NUL\NUL\NUL\NUL"+      && hd_child == "\NUL\NUL\NUL\NUL"+  | otherwise = True+
+ ppad-bip32.cabal view
@@ -0,0 +1,74 @@+cabal-version:      3.0+name:               ppad-bip32+version:            0.1.0+synopsis:           BIP32 hierarchical deterministic wallets.+license:            MIT+license-file:       LICENSE+author:             Jared Tobin+maintainer:         jared@ppad.tech+category:           Cryptography+build-type:         Simple+tested-with:        GHC == 9.8.1+extra-doc-files:    CHANGELOG+description:+  [BIP32](https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki)+  hierarchical deterministic wallets and extended keys, with support for+  serialization and parsing.++source-repository head+  type:     git+  location: git.ppad.tech/bip32.git++library+  default-language: Haskell2010+  hs-source-dirs:   lib+  ghc-options:+      -Wall+  exposed-modules:+      Crypto.HDKey.BIP32+  build-depends:+      base >= 4.9 && < 5+    , bytestring >= 0.9 && < 0.13+    , ppad-base58 >= 0.2 && < 0.3+    , ppad-ripemd160 >= 0.1.3 && < 0.2+    , ppad-secp256k1 >= 0.2.2 && < 0.3+    , ppad-sha256 >= 0.2.3 && < 0.3+    , ppad-sha512 >= 0.1.3 && < 0.2++test-suite bip32-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+    , array+    , bytestring+    , ppad-base16+    , ppad-base58+    , ppad-bip32+    , tasty+    , tasty-hunit++benchmark bip32-bench+  type:                exitcode-stdio-1.0+  default-language:    Haskell2010+  hs-source-dirs:      bench+  main-is:             Main.hs++  ghc-options:+    -rtsopts -O2 -Wall++  build-depends:+      base+    , array+    , bytestring+    , criterion+    , deepseq+    , ppad-bip32+    , ppad-secp256k1+
+ test/Main.hs view
@@ -0,0 +1,202 @@+{-# OPTIONS_GHC -fno-warn-incomplete-uni-patterns #-}+{-# LANGUAGE OverloadedStrings #-}++module Main where++import Crypto.HDKey.BIP32+import qualified Data.ByteString as BS+import qualified Data.ByteString.Base16 as B16+import Test.Tasty+import qualified Test.Tasty.HUnit as H++main :: IO ()+main = defaultMain $ testGroup "BIP32 vectors" [+    vector_1+  , vector_2+  , vector_3+  , vector_4+  , vector_5+  ]++seed_1 :: BS.ByteString+seed_1 = case B16.decode "000102030405060708090a0b0c0d0e0f" of+  Nothing -> error "bang"+  Just b -> b++xpub_1_m :: BS.ByteString+xpub_1_m = "xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8"++xprv_1_m :: BS.ByteString+xprv_1_m = "xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi"++xpub_1_m_0' :: BS.ByteString+xpub_1_m_0' = "xpub68Gmy5EdvgibQVfPdqkBBCHxA5htiqg55crXYuXoQRKfDBFA1WEjWgP6LHhwBZeNK1VTsfTFUHCdrfp1bgwQ9xv5ski8PX9rL2dZXvgGDnw"++xprv_1_m_0' :: BS.ByteString+xprv_1_m_0' = "xprv9uHRZZhk6KAJC1avXpDAp4MDc3sQKNxDiPvvkX8Br5ngLNv1TxvUxt4cV1rGL5hj6KCesnDYUhd7oWgT11eZG7XnxHrnYeSvkzY7d2bhkJ7"++xpub_1_m_0'_1 :: BS.ByteString+xpub_1_m_0'_1 = "xpub6ASuArnXKPbfEwhqN6e3mwBcDTgzisQN1wXN9BJcM47sSikHjJf3UFHKkNAWbWMiGj7Wf5uMash7SyYq527Hqck2AxYysAA7xmALppuCkwQ"++xprv_1_m_0'_1 :: BS.ByteString+xprv_1_m_0'_1 = "xprv9wTYmMFdV23N2TdNG573QoEsfRrWKQgWeibmLntzniatZvR9BmLnvSxqu53Kw1UmYPxLgboyZQaXwTCg8MSY3H2EU4pWcQDnRnrVA1xe8fs"++xpub_1_m_0'_1_2' :: BS.ByteString+xpub_1_m_0'_1_2' = "xpub6D4BDPcP2GT577Vvch3R8wDkScZWzQzMMUm3PWbmWvVJrZwQY4VUNgqFJPMM3No2dFDFGTsxxpG5uJh7n7epu4trkrX7x7DogT5Uv6fcLW5"++xprv_1_m_0'_1_2' :: BS.ByteString+xprv_1_m_0'_1_2' = "xprv9z4pot5VBttmtdRTWfWQmoH1taj2axGVzFqSb8C9xaxKymcFzXBDptWmT7FwuEzG3ryjH4ktypQSAewRiNMjANTtpgP4mLTj34bhnZX7UiM"++xpub_1_m_0'_1_2'_2 :: BS.ByteString+xpub_1_m_0'_1_2'_2 = "xpub6FHa3pjLCk84BayeJxFW2SP4XRrFd1JYnxeLeU8EqN3vDfZmbqBqaGJAyiLjTAwm6ZLRQUMv1ZACTj37sR62cfN7fe5JnJ7dh8zL4fiyLHV"++xprv_1_m_0'_1_2'_2 :: BS.ByteString+xprv_1_m_0'_1_2'_2 = "xprvA2JDeKCSNNZky6uBCviVfJSKyQ1mDYahRjijr5idH2WwLsEd4Hsb2Tyh8RfQMuPh7f7RtyzTtdrbdqqsunu5Mm3wDvUAKRHSC34sJ7in334"++xpub_1_m_0'_1_2'_2_1000000000 :: BS.ByteString+xpub_1_m_0'_1_2'_2_1000000000 = "xpub6H1LXWLaKsWFhvm6RVpEL9P4KfRZSW7abD2ttkWP3SSQvnyA8FSVqNTEcYFgJS2UaFcxupHiYkro49S8yGasTvXEYBVPamhGW6cFJodrTHy"++xprv_1_m_0'_1_2'_2_1000000000 :: BS.ByteString+xprv_1_m_0'_1_2'_2_1000000000 = "xprvA41z7zogVVwxVSgdKUHDy1SKmdb533PjDz7J6N6mV6uS3ze1ai8FHa8kmHScGpWmj4WggLyQjgPie1rFSruoUihUZREPSL39UNdE3BBDu76"++vector_1 :: TestTree+vector_1 = H.testCase "BIP32 vector 1" $ do+  let Just _m = master seed_1+  H.assertEqual "M" xpub_1_m (xpub _m)+  H.assertEqual "m" xprv_1_m (xprv _m)+  let Just _m_0' = derive_child_priv _m 0x80000000+  H.assertEqual "M/0'" xpub_1_m_0' (xpub _m_0')+  H.assertEqual "m/0'" xprv_1_m_0' (xprv _m_0')+  H.assertEqual "M/0', path" xpub_1_m_0' (xpub (derive_partial _m "m/0'"))+  H.assertEqual "m/0', path" xprv_1_m_0' (xprv (derive_partial _m "m/0'"))+  let Just _m_0'_1 = derive_child_priv _m_0' 1+  H.assertEqual "M/0'/1" xpub_1_m_0'_1 (xpub _m_0'_1)+  H.assertEqual "m/0'/1" xprv_1_m_0'_1 (xprv _m_0'_1)+  H.assertEqual "M/0'/1" xpub_1_m_0'_1 (xpub (derive_partial _m "m/0'/1"))+  H.assertEqual "m/0'/1" xprv_1_m_0'_1 (xprv (derive_partial _m "m/0'/1"))+  let Just _m_0'_1_2' = derive_child_priv _m_0'_1 (0x80000000 + 2)+  H.assertEqual "M/0'/1/2'" xpub_1_m_0'_1_2' (xpub _m_0'_1_2')+  H.assertEqual "m/0'/1/2'" xprv_1_m_0'_1_2' (xprv _m_0'_1_2')+  H.assertEqual "M/0'/1/2'" xpub_1_m_0'_1_2' (xpub (derive_partial _m "m/0'/1/2'"))+  H.assertEqual "m/0'/1/2'" xprv_1_m_0'_1_2' (xprv (derive_partial _m "m/0'/1/2'"))+  let Just _m_0'_1_2'_2 = derive_child_priv _m_0'_1_2' 2+  H.assertEqual "M/0'/1/2'/2" xpub_1_m_0'_1_2'_2 (xpub _m_0'_1_2'_2)+  H.assertEqual "m/0'/1/2'/2" xprv_1_m_0'_1_2'_2 (xprv _m_0'_1_2'_2)+  H.assertEqual "M/0'/1/2'/2" xpub_1_m_0'_1_2'_2+    (xpub (derive_partial _m "m/0'/1/2'/2"))+  H.assertEqual "m/0'/1/2'/2" xprv_1_m_0'_1_2'_2+    (xprv (derive_partial _m "m/0'/1/2'/2"))+  let Just _m_0'_1_2'_2_1000000000 = derive_child_priv _m_0'_1_2'_2 1000000000+  H.assertEqual "M/0'/1/2'/2/1000000000" xpub_1_m_0'_1_2'_2_1000000000+    (xpub _m_0'_1_2'_2_1000000000)+  H.assertEqual "m/0'/1/2'/2/1000000000" xprv_1_m_0'_1_2'_2_1000000000+    (xprv _m_0'_1_2'_2_1000000000)+  H.assertEqual "M/0'/1/2'/2/1000000000" xpub_1_m_0'_1_2'_2_1000000000+    (xpub (derive_partial _m "m/0'/1/2'/2/1000000000"))+  H.assertEqual "m/0'/1/2'/2/1000000000" xprv_1_m_0'_1_2'_2_1000000000+    (xprv (derive_partial _m "m/0'/1/2'/2/1000000000"))++seed_2 :: BS.ByteString+seed_2 = case B16.decode "fffcf9f6f3f0edeae7e4e1dedbd8d5d2cfccc9c6c3c0bdbab7b4b1aeaba8a5a29f9c999693908d8a8784817e7b7875726f6c696663605d5a5754514e4b484542" of+  Nothing -> error "bang"+  Just b -> b++vector_2 :: TestTree+vector_2 = H.testCase "BIP32 vector 2" $ do+  let Just mas = master seed_2+      _m = derive_partial mas "m"+  H.assertEqual "M" "xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB"+    (xpub _m)+  H.assertEqual "m" "xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U"+    (xprv _m)+  let _m_0 = derive_partial mas "m/0"+  H.assertEqual "M/0" "xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH"+    (xpub _m_0)+  H.assertEqual "m/0" "xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt"+    (xprv _m_0)+  let _m_0_2147483647' = derive_partial mas "m/0/2147483647'"+  H.assertEqual "M/0/2147483647'" "xpub6ASAVgeehLbnwdqV6UKMHVzgqAG8Gr6riv3Fxxpj8ksbH9ebxaEyBLZ85ySDhKiLDBrQSARLq1uNRts8RuJiHjaDMBU4Zn9h8LZNnBC5y4a"+    (xpub _m_0_2147483647')+  H.assertEqual "m/0/2147483647'" "xprv9wSp6B7kry3Vj9m1zSnLvN3xH8RdsPP1Mh7fAaR7aRLcQMKTR2vidYEeEg2mUCTAwCd6vnxVrcjfy2kRgVsFawNzmjuHc2YmYRmagcEPdU9"+    (xprv _m_0_2147483647')+  let _m_0_2147483647'_1 = derive_partial mas "m/0/2147483647'/1"+  H.assertEqual "M/0/2147483647'/1" "xpub6DF8uhdarytz3FWdA8TvFSvvAh8dP3283MY7p2V4SeE2wyWmG5mg5EwVvmdMVCQcoNJxGoWaU9DCWh89LojfZ537wTfunKau47EL2dhHKon"+    (xpub _m_0_2147483647'_1)+  H.assertEqual "m/0/2147483647'/1" "xprv9zFnWC6h2cLgpmSA46vutJzBcfJ8yaJGg8cX1e5StJh45BBciYTRXSd25UEPVuesF9yog62tGAQtHjXajPPdbRCHuWS6T8XA2ECKADdw4Ef"+    (xprv _m_0_2147483647'_1)+  let _m_0_2147483647'_1_2147483646' =+        derive_partial mas "m/0/2147483647'/1/2147483646'"+  H.assertEqual "M/0/2147483647'/1/2147483646'" "xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL"+    (xpub _m_0_2147483647'_1_2147483646')+  H.assertEqual "m/0/2147483647'/1/2147483646'" "xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc"+    (xprv _m_0_2147483647'_1_2147483646')+  let _m_0_2147483647'_1_2147483646'_2 =+        derive_partial mas "m/0/2147483647'/1/2147483646'/2"+  H.assertEqual "M/0/2147483647'/1/2147483646'/2" "xpub6FnCn6nSzZAw5Tw7cgR9bi15UV96gLZhjDstkXXxvCLsUXBGXPdSnLFbdpq8p9HmGsApME5hQTZ3emM2rnY5agb9rXpVGyy3bdW6EEgAtqt"+    (xpub _m_0_2147483647'_1_2147483646'_2)+  H.assertEqual "m/0/2147483647'/1/2147483646'/2" "xprvA2nrNbFZABcdryreWet9Ea4LvTJcGsqrMzxHx98MMrotbir7yrKCEXw7nadnHM8Dq38EGfSh6dqA9QWTyefMLEcBYJUuekgW4BYPJcr9E7j"+    (xprv _m_0_2147483647'_1_2147483646'_2)++seed_3 :: BS.ByteString+seed_3 = case B16.decode "4b381541583be4423346c643850da4b320e46a87ae3d2a4e6da11eba819cd4acba45d239319ac14f863b8d5ab5a0d0c64d2e8a1e7d1457df2e5a3c51c73235be" of+  Nothing -> error "bang"+  Just b -> b++vector_3 :: TestTree+vector_3 = H.testCase "BIP32 vector 3" $ do+  let Just mas = master seed_3+      _m = derive_partial mas "m"+  H.assertEqual "M" "xpub661MyMwAqRbcEZVB4dScxMAdx6d4nFc9nvyvH3v4gJL378CSRZiYmhRoP7mBy6gSPSCYk6SzXPTf3ND1cZAceL7SfJ1Z3GC8vBgp2epUt13"+    (xpub _m)+  H.assertEqual "m" "xprv9s21ZrQH143K25QhxbucbDDuQ4naNntJRi4KUfWT7xo4EKsHt2QJDu7KXp1A3u7Bi1j8ph3EGsZ9Xvz9dGuVrtHHs7pXeTzjuxBrCmmhgC6"+    (xprv _m)+  let _m_0' = derive_partial mas "m/0'"+  H.assertEqual "M/0'" "xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y"+    (xpub _m_0')+  H.assertEqual "m/0'" "xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L"+    (xprv _m_0')++seed_4 :: BS.ByteString+seed_4 = case B16.decode "3ddd5602285899a946114506157c7997e5444528f3003f6134712147db19b678" of+  Nothing -> error "bang"+  Just b -> b++vector_4 :: TestTree+vector_4 = H.testCase "BIP32 vector 4" $ do+  let Just mas = master seed_4+      _m = derive_partial mas "m"+  H.assertEqual "M" "xpub661MyMwAqRbcGczjuMoRm6dXaLDEhW1u34gKenbeYqAix21mdUKJyuyu5F1rzYGVxyL6tmgBUAEPrEz92mBXjByMRiJdba9wpnN37RLLAXa"+    (xpub _m)+  H.assertEqual "m" "xprv9s21ZrQH143K48vGoLGRPxgo2JNkJ3J3fqkirQC2zVdk5Dgd5w14S7fRDyHH4dWNHUgkvsvNDCkvAwcSHNAQwhwgNMgZhLtQC63zxwhQmRv"+    (xprv _m)+  let _m_0' = derive_partial mas "m/0'"+  H.assertEqual "M/0'" "xpub69AUMk3qDBi3uW1sXgjCmVjJ2G6WQoYSnNHyzkmdCHEhSZ4tBok37xfFEqHd2AddP56Tqp4o56AePAgCjYdvpW2PU2jbUPFKsav5ut6Ch1m"+    (xpub _m_0')+  H.assertEqual "m/0'" "xprv9vB7xEWwNp9kh1wQRfCCQMnZUEG21LpbR9NPCNN1dwhiZkjjeGRnaALmPXCX7SgjFTiCTT6bXes17boXtjq3xLpcDjzEuGLQBM5ohqkao9G"+    (xprv _m_0')+  let _m_0'_1' = derive_partial mas "m/0'/1'"+  H.assertEqual "M/0'/1'" "xpub6BJA1jSqiukeaesWfxe6sNK9CCGaujFFSJLomWHprUL9DePQ4JDkM5d88n49sMGJxrhpjazuXYWdMf17C9T5XnxkopaeS7jGk1GyyVziaMt"+    (xpub _m_0'_1')+  H.assertEqual "m/0'/1'" "xprv9xJocDuwtYCMNAo3Zw76WENQeAS6WGXQ55RCy7tDJ8oALr4FWkuVoHJeHVAcAqiZLE7Je3vZJHxspZdFHfnBEjHqU5hG1Jaj32dVoS6XLT1"+    (xprv _m_0'_1')++vector_5 :: TestTree+vector_5 = H.testCase "BIP32 vector 5" $ do+  H.assertEqual "k0" Nothing (parse "xpub661MyMwAqRbcEYS8w7XLSVeEsBXy79zSzH1J8vCdxAZningWLdN3zgtU6LBpB85b3D2yc8sfvZU521AAwdZafEz7mnzBBsz4wKY5fTtTQBm")+  H.assertEqual "k1" Nothing (parse "xprv9s21ZrQH143K24Mfq5zL5MhWK9hUhhGbd45hLXo2Pq2oqzMMo63oStZzFGTQQD3dC4H2D5GBj7vWvSQaaBv5cxi9gafk7NF3pnBju6dwKvH")+  H.assertEqual "k2" Nothing (parse "xpub661MyMwAqRbcEYS8w7XLSVeEsBXy79zSzH1J8vCdxAZningWLdN3zgtU6Txnt3siSujt9RCVYsx4qHZGc62TG4McvMGcAUjeuwZdduYEvFn")+  H.assertEqual "k3" Nothing (parse "xprv9s21ZrQH143K24Mfq5zL5MhWK9hUhhGbd45hLXo2Pq2oqzMMo63oStZzFGpWnsj83BHtEy5Zt8CcDr1UiRXuWCmTQLxEK9vbz5gPstX92JQ")+  H.assertEqual "k4" Nothing (parse "xpub661MyMwAqRbcEYS8w7XLSVeEsBXy79zSzH1J8vCdxAZningWLdN3zgtU6N8ZMMXctdiCjxTNq964yKkwrkBJJwpzZS4HS2fxvyYUA4q2Xe4")+  H.assertEqual "k5" Nothing (parse "xprv9s21ZrQH143K24Mfq5zL5MhWK9hUhhGbd45hLXo2Pq2oqzMMo63oStZzFAzHGBP2UuGCqWLTAPLcMtD9y5gkZ6Eq3Rjuahrv17fEQ3Qen6J")+  H.assertEqual "k6" Nothing (parse "xprv9s2SPatNQ9Vc6GTbVMFPFo7jsaZySyzk7L8n2uqKXJen3KUmvQNTuLh3fhZMBoG3G4ZW1N2kZuHEPY53qmbZzCHshoQnNf4GvELZfqTUrcv")+  H.assertEqual "k7" Nothing (parse "xpub661no6RGEX3uJkY4bNnPcw4URcQTrSibUZ4NqJEw5eBkv7ovTwgiT91XX27VbEXGENhYRCf7hyEbWrR3FewATdCEebj6znwMfQkhRYHRLpJ")+  H.assertEqual "k8" Nothing (parse "xprv9s21ZrQH4r4TsiLvyLXqM9P7k1K3EYhA1kkD6xuquB5i39AU8KF42acDyL3qsDbU9NmZn6MsGSUYZEsuoePmjzsB3eFKSUEh3Gu1N3cqVUN")+  H.assertEqual "k9" Nothing (parse "xpub661MyMwAuDcm6CRQ5N4qiHKrJ39Xe1R1NyfouMKTTWcguwVcfrZJaNvhpebzGerh7gucBvzEQWRugZDuDXjNDRmXzSZe4c7mnTK97pTvGS8")+  H.assertEqual "k10" Nothing (parse "DMwo58pR1QLEFihHiXPVykYB6fJmsTeHvyTp7hRThAtCX8CvYzgPcn8XnmdfHGMQzT7ayAmfo4z3gY5KfbrZWZ6St24UVf2Qgo6oujFktLHdHY4")+  H.assertEqual "k11" Nothing (parse "DMwo58pR1QLEFihHiXPVykYB6fJmsTeHvyTp7hRThAtCX8CvYzgPcn8XnmdfHPmHJiEDXkTiJTVV9rHEBUem2mwVbbNfvT2MTcAqj3nesx8uBf9")+  H.assertEqual "k12" Nothing (parse "xprv9s21ZrQH143K24Mfq5zL5MhWK9hUhhGbd45hLXo2Pq2oqzMMo63oStZzF93Y5wvzdUayhgkkFoicQZcP3y52uPPxFnfoLZB21Teqt1VvEHx")+  H.assertEqual "k13" Nothing (parse "xprv9s21ZrQH143K24Mfq5zL5MhWK9hUhhGbd45hLXo2Pq2oqzMMo63oStZzFAzHGBP2UuGCqWLTAPLcMtD5SDKr24z3aiUvKr9bJpdrcLg1y3G")+  H.assertEqual "k14" Nothing (parse "xpub661MyMwAqRbcEYS8w7XLSVeEsBXy79zSzH1J8vCdxAZningWLdN3zgtU6Q5JXayek4PRsn35jii4veMimro1xefsM58PgBMrvdYre8QyULY")+  H.assertEqual "k15" Nothing (parse "xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHL")++