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 +6/−0
- LICENSE +20/−0
- bench/Main.hs +57/−0
- lib/Crypto/HDKey/BIP32.hs +446/−0
- ppad-bip32.cabal +74/−0
- test/Main.hs +202/−0
+ 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")++