diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,130 +1,471 @@
 # Changelog
+
 All notable changes to this project will be documented in this file.
 
 The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
 and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
 
-## 0.11.0
+## [1.2.2] - 2025-05-08
+
+### Changed
+
+- Standardize author name and correct README link.
+
+## [1.2.1] - 2025-05-08
+
+### Changed
+
+- New home in github.com/jprupp rather than github.com/haskoin.
+
+## [1.2.0] - 2023-07-23
+
+### Changed
+
+- Move from `cryptonite` to `crypton` for now.
+
+## [1.1.0] - 2023-03-14
+
+### Changed
+
+- Update upstream dependencies & LTS Haskell.
+
+## [1.0.4] - 2023-10-09
+
+### Changed
+
+- Export the BIP39 word list
+- Add new BIP39 test vectors
+
+## [1.0.3] - 2023-09-02
+
+### Changed
+
+- New dependency on data-default
+- Renamed some arbitrary (test) functions for clarity
+- Reworked the identity testing tool to include Marshal and MarshalJSON classes
+
+## [1.0.2] - 2023-08-04
+
+### Fixed
+
+- Text files needed for testing now in tarball.
+
+## [1.0.1] - 2023-08-03
+
+### Changed
+
+- Do not use alternative monad for Either String values.
+
+## [1.0.0] - 2023-07-28
+
+### Changed
+
+- Shorten all field names allow duplicates, using DuplicateRecordFields extension.
+- Use OverloadedRecordDot language extension and syntax.
+- Use explicit context object from secp256k1-haskell library.
+- Unify serialization into custom Marhsal and MarshalJSON classes.
+- Use ormolu for formatting.
+- Simplify module organisation.
+- Strongly break backwards compatibility.
+
+## [0.22.0] - 2023-06-28
+
+### Changed
+
+- Upgrade to lastest secp256k1 and base16 packages.
+
+## [0.21.2] - 2022-04-13
+
+### Changed
+
+- Serialisation test now works for both strict and lazy bytestrings.
+
+## [0.21.1] - 2021-12-13
+
+### Changed
+
+- Make Base58 faster.
+
+## [0.21.0] - 2022-11-23
+
 ### Added
+
+- BCH Testnet4 support.
+
+### Changed
+
+- Fix inconsistent serialization/deserialization issues.
+
+### Fixed
+
+- Makes `finalScriptWitness` field encoding conform to bitcoin core.
+- Fixes bug in `finalizeTransaction`
+
+### Added
+
+- Signing support for PSBTs
+- Helper function for merging PSBTs
+- More PSBT tests
+- Partial support for taproot
+
+## [0.20.5] - 2021-09-13
+
+### Added
+
+- Support Bech32m address format for Taproot.
+
+## [0.20.4] - 2021-06-08
+
+### Fixed
+
+- Add missing case for witness version.
+
+## [0.20.3] - 2021-05-17
+
+### Fixed
+
+- Allow unknown inv types.
+
+## [0.20.2] - 2021-05-17
+
+### Fixed
+
+- Allow unknown messages of zero length.
+
+## [0.20.1] - 2021-05-14
+
+### Fixed
+
+- Correct case where binary search returned the wrong element.
+
+## [0.20.0] - 2021-02-22
+
+### Chaged
+
+- Use bytes instead of binary or cereal.
+
+## [0.19.0] - 2021-01-25
+
+### Added
+
+- Hashable instances for extended keys.
+
+### Changed
+
+- Mnemonic passphrases now `Text` instead of `ByteString`.
+
+### Fixed
+
+- Tests now pass for witness addresses.
+
+## [0.18.0] - 2020-12-10
+
+### Added
+
+- Support SegWit addresses with version other than 0.
+
+## [0.17.6] - 2020-12-07
+
+### Added
+
+- Serialize instances for `XPubKey` and `XPrvKey`.
+
+## [0.17.5] - 2020-12-03
+
+### Fixed
+
+- Handle special case in block header binary search function.
+
+## [0.17.4] - 2020-12-03
+
+### Fixed
+
+- Bounds check too restrictive in block header binary search function.
+
+## [0.17.3] - 2020-11-17
+
+### Changed
+
+- Reduce minimum version of text package dependency.
+
+## [0.17.2] - 2020-11-17
+
+### Changed
+
+- Update lists of seeds for all networks.
+
+## [0.17.1] - 2020-11-02
+
+### Changed
+
+- Use the C-preprocessor to handle versions of `base16-bytestring`
+
+## [0.17.0] - 2020-10-21
+
+### Added
+
+- Support for Bitcoin Cash November 2020 hard fork.
+- Functions to find block headers matching arbitrary sorted attributes.
+
+### Removed
+
+- GenesisNode constructor for BlockNode type.
+
+## [0.15.0] - 2020-07-23
+
+### Added
+
+- Add more test vectors
+
+### Changed
+
+- stringToAddr renamed to textToAddr
+- Move ScriptOutput to Standard.hs
+- Move WIF encoding/decoding to Keys.hs
+- (breaking) rename `OP_NOP2` and `OP_NOP3` to `OP_CHECKLOCKTIMEVERIFY` and `OP_CHECKSEQUENCEVERIFY` resp.
+- Update to latest secp256k1 bindings.
+
+## [0.14.1] - 2020-06-14
+
+### Fixed
+
+- Correct some Bitcoin Cash Testnet3 seeds.
+- Add helpers for writing Data.Serialize and Data.Aeson identity tests
+
+## [0.14.0] - 2020-06-14
+
+### Changed
+
+- Expose all modules for tests.
+- Tests depend on library instead of having access to its source code.
+- Use MIT license.
+- Update seeds.
+- Bump secp256k1-haskell dependency.
+
+## [0.13.6] - 2020-06-05
+
+### Changed
+
+- Expose the Arbitrary test instances under Haskoin.Util.Arbitrary
+
+## [0.13.5] - 2020-05-16
+
+### Changed
+
+- Provide meaningful JSON instances for most types.
+
+## [0.13.4] - 2020-05-14
+
+### Added
+
+- Support for Bitcoin Cash May 2020 hard fork.
+
+## [0.13.3] - 2020-05-08
+
+### Changed
+
+- Improve code and documentation organisation.
+
+## [0.13.2] - 2020-05-08
+
+### Changed
+
+- Move all packages from Network.Haskoin namespace to Haskoin namespace.
+- Expose all top-level modules directly.
+
+## [0.13.1] - 2020-05-06
+
+### Changed
+
+- Faster JSON serialization.
+
+## [0.13.0] - 2020-05-06
+
+### Changed
+
+- Consolidate all modules in Haskoin module.
+
+### Removed
+
+- Deprecate Network.Haskoin namespace.
+- Hide QuickCheck generators in test suite.
+
+## [0.12.0] - 2020-04-10
+
+### Added
+
+- Support for signing segwit transactions.
 - High-level representation of segwit v0 data and auxilliary functions.
 
 ### Changed
+
 - Adds handling of segwit signing parameters to transaction signing code.
 
-## 0.10.1
+## [0.10.1] - 2020-02-08
+
 ### Added
+
 - Lower bound versions for some dependencies.
 
-## 0.10.0
+## [0.10.0] - 2020-01-15
+
 ### Added
+
 - DeepSeq instances for all data types.
 
 ### Changed
+
 - There is no `SockAddr` inside `NetworkAddress` anymore.
 
-## 0.9.8
+## [0.9.8] - 2020-01-01
+
 ### Added
+
 - Ord instance for `DerivPathI`
 
-## 0.9.7
+## [0.9.7] - 2019-12-04
+
 ### Added
+
 - JSON encoding/decoding for blocks.
 
 ### Fixed
+
 - Fix lowercase HRP test for Bech32.
 
-## 0.9.6
+## [0.9.6] - 2019-10-29
+
 ### Added
+
 - `bloomRelevantUpdate` implementation for Bloom filters (thanks to @IlyasRidhuan).
 
 ### Fixed
+
 - Fix for Bech32 encoding (thanks to @pavel-main).
 
-## 0.9.5
+## [0.9.5] - 2019-10-23
+
 ### Added
+
 - Expose functions added in 0.9.4.
 
-## 0.9.4
+## [0.9.4] - 2019-10-23
+
 ### Added
+
 - Support for (P2SH-)P2WPKH addresses derived from extended keys.
 
 ### Changed
+
 - Change names of backwards-compatible P2SH-P2WPKH functions from 0.9.3.
 
-## 0.9.3
+## [0.9.3] - 2019-10-22
+
 ### Added
+
 - Some support for P2WPKH-over-P2SH addresses.
 
-## 0.9.2
+## [0.9.2] - 2019-10-09
+
 ### Removed
+
 - Disable unnecessary `-O2` optimisation added in previous version.
 
 ### Added
+
 - Allow decoding unknown P2P messages.
 
-## 0.9.1
+## [0.9.1] - 2019-10-02
+
 ### Added
+
 - Add a function to produce a structured signature over a transaction.
 - Enable `-O2` optimisations.
 
-## 0.9.0
+## [0.9.0] - 2019-04-12
+
 ### Changed
+
 - Address conversion to string now defined for all inputs.
 
-## 0.8.4
+## [0.8.4] - 2018-12-05
+
 ### Added
+
 - Add reward computation to block functions.
-- Add PSBT [BIP-174](https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki) types and functions
+- Add PSBT BIP-174 types and functions.
 
-## 0.8.3
+## [0.8.3] - 2018-12-04
+
 ### Added
+
 - Add reward halving interval parameter to network constants.
 
-## 0.8.2
+## [0.8.2] - 2018-11-06
+
 ### Added
+
 - Recognize `OP_CHECKDATASIG` and `OP_CHECKDATASIGVERIFY` opcodes.
 
-## 0.8.1
+## [0.8.1] - 2018-10-13
+
 ### Added
+
 - Add instances of `Hashable` and `Generic` where possible.
 
-## 0.8.0
+## [0.8.0] - 2018-10-13
+
 ### Removed
+
 - Remove `deepseq` dependency.
 - Remove network constant reference from address and extended keys.
 
-## 0.7.0
+## [0.7.0] - 2018-10-13
+
 ### Added
+
 - Add `Serialize` instance for network constants.
 - Add `Serialize` instance for addresses that includes network constants.
 
 ### Changed
+
 - Move functions related to addresses from `Script` to `Address` module.
 
-## 0.6.1
+## [0.6.1] - 2018-10-09
+
 ### Added
+
 - Compatibility with latest GHC and base.
 
 ### Changed
+
 - Update minimum base to 4.9.
 
-## 0.6.0
+## [0.6.0] - 2018-10-08
+
 ### Changed
+
 - Force initialization of addresses through smart constructor.
 - Assume addresses are always valid when instantiated in code.
 - Allow to provide unwrapped private keys to transaction signing functions.
 
-## 0.5.2
+## [0.5.2] - 2018-09-10
+
 ### Changed
+
 - Make dependencies more specific.
 
-## 0.5.1
+## [0.5.1] - 2018-09-10
+
 ### Changed
+
 - Remove some unneeded dependencies from `stack.yaml`.
 - Change `secp256k1` dependency to `secp256k1-haskell`.
 
-## 0.5.0
+## [0.5.0] - 2018-09-09
+
 ### Added
+
 - Support for Bitcoin Cash network block sychronization.
 - Support for Bitcoin Cash signatures.
 - Initial work on SegWit support.
@@ -140,6 +481,7 @@
 - Support for CashAddr addresses.
 
 ### Changed
+
 - Use of hpack `package.yaml` file to auto-generate Cabal file.
 - Removal of dependency version limits, relying on `stack.yaml` instead.
 - Tests moved to `hspec`.
@@ -150,6 +492,7 @@
 - Target LTS Haskell 12.9.
 
 ### Removed
+
 - Removed `.stylish-haskell.yaml` files.
 - Removed old `haskoin-node` and `haskoin-wallet` packages from main repository.
 - Removed support for non-strict signatures and related tests.
diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,19 @@
+Copyright 2020 Haskoin Developers
+
+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.
+
diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -14,3 +14,8 @@
 - Parsing and manipulation of all Bitcoin and Bitcoin Cash protocol messages
 - Bloom filters and partial merkle trees (used in SPV wallets)
 - Comprehensive test suite
+
+## Contributing
+
+All code is formatted with [Ormolu](https://github.com/tweag/ormolu).
+Convenience formatting script available at [scripts/format](scripts/format)
diff --git a/UNLICENSE b/UNLICENSE
deleted file mode 100644
--- a/UNLICENSE
+++ /dev/null
@@ -1,24 +0,0 @@
-This is free and unencumbered software released into the public domain.
-
-Anyone is free to copy, modify, publish, use, compile, sell, or
-distribute this software, either in source code form or as a compiled
-binary, for any purpose, commercial or non-commercial, and by any
-means.
-
-In jurisdictions that recognize copyright laws, the author or authors
-of this software dedicate any and all copyright interest in the
-software to the public domain. We make this dedication for the benefit
-of the public at large and to the detriment of our heirs and
-successors. We intend this dedication to be an overt act of
-relinquishment in perpetuity of all present and future rights to this
-software under copyright law.
-
-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 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.
-
-For more information, please refer to <http://unlicense.org/>
diff --git a/data/bip341.json b/data/bip341.json
new file mode 100644
--- /dev/null
+++ b/data/bip341.json
@@ -0,0 +1,452 @@
+{
+    "version": 1,
+    "scriptPubKey": [
+        {
+            "given": {
+                "internalPubkey": "d6889cb081036e0faefa3a35157ad71086b123b2b144b649798b494c300a961d",
+                "scriptTree": null
+            },
+            "intermediary": {
+                "merkleRoot": null,
+                "tweak": "b86e7be8f39bab32a6f2c0443abbc210f0edac0e2c53d501b36b64437d9c6c70",
+                "tweakedPubkey": "53a1f6e454df1aa2776a2814a721372d6258050de330b3c6d10ee8f4e0dda343"
+            },
+            "expected": {
+                "scriptPubKey": "512053a1f6e454df1aa2776a2814a721372d6258050de330b3c6d10ee8f4e0dda343",
+                "bip350Address": "bc1p2wsldez5mud2yam29q22wgfh9439spgduvct83k3pm50fcxa5dps59h4z5"
+            }
+        },
+        {
+            "given": {
+                "internalPubkey": "187791b6f712a8ea41c8ecdd0ee77fab3e85263b37e1ec18a3651926b3a6cf27",
+                "scriptTree": {
+                    "id": 0,
+                    "script": "20d85a959b0290bf19bb89ed43c916be835475d013da4b362117393e25a48229b8ac",
+                    "leafVersion": 192
+                }
+            },
+            "intermediary": {
+                "leafHashes": [
+                    "5b75adecf53548f3ec6ad7d78383bf84cc57b55a3127c72b9a2481752dd88b21"
+                ],
+                "merkleRoot": "5b75adecf53548f3ec6ad7d78383bf84cc57b55a3127c72b9a2481752dd88b21",
+                "tweak": "cbd8679ba636c1110ea247542cfbd964131a6be84f873f7f3b62a777528ed001",
+                "tweakedPubkey": "147c9c57132f6e7ecddba9800bb0c4449251c92a1e60371ee77557b6620f3ea3"
+            },
+            "expected": {
+                "scriptPubKey": "5120147c9c57132f6e7ecddba9800bb0c4449251c92a1e60371ee77557b6620f3ea3",
+                "bip350Address": "bc1pz37fc4cn9ah8anwm4xqqhvxygjf9rjf2resrw8h8w4tmvcs0863sa2e586",
+                "scriptPathControlBlocks": [
+                    "c1187791b6f712a8ea41c8ecdd0ee77fab3e85263b37e1ec18a3651926b3a6cf27"
+                ]
+            }
+        },
+        {
+            "given": {
+                "internalPubkey": "93478e9488f956df2396be2ce6c5cced75f900dfa18e7dabd2428aae78451820",
+                "scriptTree": {
+                    "id": 0,
+                    "script": "20b617298552a72ade070667e86ca63b8f5789a9fe8731ef91202a91c9f3459007ac",
+                    "leafVersion": 192
+                }
+            },
+            "intermediary": {
+                "leafHashes": [
+                    "c525714a7f49c28aedbbba78c005931a81c234b2f6c99a73e4d06082adc8bf2b"
+                ],
+                "merkleRoot": "c525714a7f49c28aedbbba78c005931a81c234b2f6c99a73e4d06082adc8bf2b",
+                "tweak": "6af9e28dbf9d6aaf027696e2598a5b3d056f5fd2355a7fd5a37a0e5008132d30",
+                "tweakedPubkey": "e4d810fd50586274face62b8a807eb9719cef49c04177cc6b76a9a4251d5450e"
+            },
+            "expected": {
+                "scriptPubKey": "5120e4d810fd50586274face62b8a807eb9719cef49c04177cc6b76a9a4251d5450e",
+                "bip350Address": "bc1punvppl2stp38f7kwv2u2spltjuvuaayuqsthe34hd2dyy5w4g58qqfuag5",
+                "scriptPathControlBlocks": [
+                    "c093478e9488f956df2396be2ce6c5cced75f900dfa18e7dabd2428aae78451820"
+                ]
+            }
+        },
+        {
+            "given": {
+                "internalPubkey": "ee4fe085983462a184015d1f782d6a5f8b9c2b60130aff050ce221ecf3786592",
+                "scriptTree": [
+                    {
+                        "id": 0,
+                        "script": "20387671353e273264c495656e27e39ba899ea8fee3bb69fb2a680e22093447d48ac",
+                        "leafVersion": 192
+                    },
+                    {
+                        "id": 1,
+                        "script": "06424950333431",
+                        "leafVersion": 250
+                    }
+                ]
+            },
+            "intermediary": {
+                "leafHashes": [
+                    "8ad69ec7cf41c2a4001fd1f738bf1e505ce2277acdcaa63fe4765192497f47a7",
+                    "f224a923cd0021ab202ab139cc56802ddb92dcfc172b9212261a539df79a112a"
+                ],
+                "merkleRoot": "6c2dc106ab816b73f9d07e3cd1ef2c8c1256f519748e0813e4edd2405d277bef",
+                "tweak": "9e0517edc8259bb3359255400b23ca9507f2a91cd1e4250ba068b4eafceba4a9",
+                "tweakedPubkey": "712447206d7a5238acc7ff53fbe94a3b64539ad291c7cdbc490b7577e4b17df5"
+            },
+            "expected": {
+                "scriptPubKey": "5120712447206d7a5238acc7ff53fbe94a3b64539ad291c7cdbc490b7577e4b17df5",
+                "bip350Address": "bc1pwyjywgrd0ffr3tx8laflh6228dj98xkjj8rum0zfpd6h0e930h6saqxrrm",
+                "scriptPathControlBlocks": [
+                    "c0ee4fe085983462a184015d1f782d6a5f8b9c2b60130aff050ce221ecf3786592f224a923cd0021ab202ab139cc56802ddb92dcfc172b9212261a539df79a112a",
+                    "faee4fe085983462a184015d1f782d6a5f8b9c2b60130aff050ce221ecf37865928ad69ec7cf41c2a4001fd1f738bf1e505ce2277acdcaa63fe4765192497f47a7"
+                ]
+            }
+        },
+        {
+            "given": {
+                "internalPubkey": "f9f400803e683727b14f463836e1e78e1c64417638aa066919291a225f0e8dd8",
+                "scriptTree": [
+                    {
+                        "id": 0,
+                        "script": "2044b178d64c32c4a05cc4f4d1407268f764c940d20ce97abfd44db5c3592b72fdac",
+                        "leafVersion": 192
+                    },
+                    {
+                        "id": 1,
+                        "script": "07546170726f6f74",
+                        "leafVersion": 192
+                    }
+                ]
+            },
+            "intermediary": {
+                "leafHashes": [
+                    "64512fecdb5afa04f98839b50e6f0cb7b1e539bf6f205f67934083cdcc3c8d89",
+                    "2cb2b90daa543b544161530c925f285b06196940d6085ca9474d41dc3822c5cb"
+                ],
+                "merkleRoot": "ab179431c28d3b68fb798957faf5497d69c883c6fb1e1cd9f81483d87bac90cc",
+                "tweak": "639f0281b7ac49e742cd25b7f188657626da1ad169209078e2761cefd91fd65e",
+                "tweakedPubkey": "77e30a5522dd9f894c3f8b8bd4c4b2cf82ca7da8a3ea6a239655c39c050ab220"
+            },
+            "expected": {
+                "scriptPubKey": "512077e30a5522dd9f894c3f8b8bd4c4b2cf82ca7da8a3ea6a239655c39c050ab220",
+                "bip350Address": "bc1pwl3s54fzmk0cjnpl3w9af39je7pv5ldg504x5guk2hpecpg2kgsqaqstjq",
+                "scriptPathControlBlocks": [
+                    "c1f9f400803e683727b14f463836e1e78e1c64417638aa066919291a225f0e8dd82cb2b90daa543b544161530c925f285b06196940d6085ca9474d41dc3822c5cb",
+                    "c1f9f400803e683727b14f463836e1e78e1c64417638aa066919291a225f0e8dd864512fecdb5afa04f98839b50e6f0cb7b1e539bf6f205f67934083cdcc3c8d89"
+                ]
+            }
+        },
+        {
+            "given": {
+                "internalPubkey": "e0dfe2300b0dd746a3f8674dfd4525623639042569d829c7f0eed9602d263e6f",
+                "scriptTree": [
+                    {
+                        "id": 0,
+                        "script": "2072ea6adcf1d371dea8fba1035a09f3d24ed5a059799bae114084130ee5898e69ac",
+                        "leafVersion": 192
+                    },
+                    [
+                        {
+                            "id": 1,
+                            "script": "202352d137f2f3ab38d1eaa976758873377fa5ebb817372c71e2c542313d4abda8ac",
+                            "leafVersion": 192
+                        },
+                        {
+                            "id": 2,
+                            "script": "207337c0dd4253cb86f2c43a2351aadd82cccb12a172cd120452b9bb8324f2186aac",
+                            "leafVersion": 192
+                        }
+                    ]
+                ]
+            },
+            "intermediary": {
+                "leafHashes": [
+                    "2645a02e0aac1fe69d69755733a9b7621b694bb5b5cde2bbfc94066ed62b9817",
+                    "ba982a91d4fc552163cb1c0da03676102d5b7a014304c01f0c77b2b8e888de1c",
+                    "9e31407bffa15fefbf5090b149d53959ecdf3f62b1246780238c24501d5ceaf6"
+                ],
+                "merkleRoot": "ccbd66c6f7e8fdab47b3a486f59d28262be857f30d4773f2d5ea47f7761ce0e2",
+                "tweak": "b57bfa183d28eeb6ad688ddaabb265b4a41fbf68e5fed2c72c74de70d5a786f4",
+                "tweakedPubkey": "91b64d5324723a985170e4dc5a0f84c041804f2cd12660fa5dec09fc21783605"
+            },
+            "expected": {
+                "scriptPubKey": "512091b64d5324723a985170e4dc5a0f84c041804f2cd12660fa5dec09fc21783605",
+                "bip350Address": "bc1pjxmy65eywgafs5tsunw95ruycpqcqnev6ynxp7jaasylcgtcxczs6n332e",
+                "scriptPathControlBlocks": [
+                    "c0e0dfe2300b0dd746a3f8674dfd4525623639042569d829c7f0eed9602d263e6fffe578e9ea769027e4f5a3de40732f75a88a6353a09d767ddeb66accef85e553",
+                    "c0e0dfe2300b0dd746a3f8674dfd4525623639042569d829c7f0eed9602d263e6f9e31407bffa15fefbf5090b149d53959ecdf3f62b1246780238c24501d5ceaf62645a02e0aac1fe69d69755733a9b7621b694bb5b5cde2bbfc94066ed62b9817",
+                    "c0e0dfe2300b0dd746a3f8674dfd4525623639042569d829c7f0eed9602d263e6fba982a91d4fc552163cb1c0da03676102d5b7a014304c01f0c77b2b8e888de1c2645a02e0aac1fe69d69755733a9b7621b694bb5b5cde2bbfc94066ed62b9817"
+                ]
+            }
+        },
+        {
+            "given": {
+                "internalPubkey": "55adf4e8967fbd2e29f20ac896e60c3b0f1d5b0efa9d34941b5958c7b0a0312d",
+                "scriptTree": [
+                    {
+                        "id": 0,
+                        "script": "2071981521ad9fc9036687364118fb6ccd2035b96a423c59c5430e98310a11abe2ac",
+                        "leafVersion": 192
+                    },
+                    [
+                        {
+                            "id": 1,
+                            "script": "20d5094d2dbe9b76e2c245a2b89b6006888952e2faa6a149ae318d69e520617748ac",
+                            "leafVersion": 192
+                        },
+                        {
+                            "id": 2,
+                            "script": "20c440b462ad48c7a77f94cd4532d8f2119dcebbd7c9764557e62726419b08ad4cac",
+                            "leafVersion": 192
+                        }
+                    ]
+                ]
+            },
+            "intermediary": {
+                "leafHashes": [
+                    "f154e8e8e17c31d3462d7132589ed29353c6fafdb884c5a6e04ea938834f0d9d",
+                    "737ed1fe30bc42b8022d717b44f0d93516617af64a64753b7a06bf16b26cd711",
+                    "d7485025fceb78b9ed667db36ed8b8dc7b1f0b307ac167fa516fe4352b9f4ef7"
+                ],
+                "merkleRoot": "2f6b2c5397b6d68ca18e09a3f05161668ffe93a988582d55c6f07bd5b3329def",
+                "tweak": "6579138e7976dc13b6a92f7bfd5a2fc7684f5ea42419d43368301470f3b74ed9",
+                "tweakedPubkey": "75169f4001aa68f15bbed28b218df1d0a62cbbcf1188c6665110c293c907b831"
+            },
+            "expected": {
+                "scriptPubKey": "512075169f4001aa68f15bbed28b218df1d0a62cbbcf1188c6665110c293c907b831",
+                "bip350Address": "bc1pw5tf7sqp4f50zka7629jrr036znzew70zxyvvej3zrpf8jg8hqcssyuewe",
+                "scriptPathControlBlocks": [
+                    "c155adf4e8967fbd2e29f20ac896e60c3b0f1d5b0efa9d34941b5958c7b0a0312d3cd369a528b326bc9d2133cbd2ac21451acb31681a410434672c8e34fe757e91",
+                    "c155adf4e8967fbd2e29f20ac896e60c3b0f1d5b0efa9d34941b5958c7b0a0312dd7485025fceb78b9ed667db36ed8b8dc7b1f0b307ac167fa516fe4352b9f4ef7f154e8e8e17c31d3462d7132589ed29353c6fafdb884c5a6e04ea938834f0d9d",
+                    "c155adf4e8967fbd2e29f20ac896e60c3b0f1d5b0efa9d34941b5958c7b0a0312d737ed1fe30bc42b8022d717b44f0d93516617af64a64753b7a06bf16b26cd711f154e8e8e17c31d3462d7132589ed29353c6fafdb884c5a6e04ea938834f0d9d"
+                ]
+            }
+        }
+    ],
+    "keyPathSpending": [
+        {
+            "given": {
+                "rawUnsignedTx": "02000000097de20cbff686da83a54981d2b9bab3586f4ca7e48f57f5b55963115f3b334e9c010000000000000000d7b7cab57b1393ace2d064f4d4a2cb8af6def61273e127517d44759b6dafdd990000000000fffffffff8e1f583384333689228c5d28eac13366be082dc57441760d957275419a418420000000000fffffffff0689180aa63b30cb162a73c6d2a38b7eeda2a83ece74310fda0843ad604853b0100000000feffffffaa5202bdf6d8ccd2ee0f0202afbbb7461d9264a25e5bfd3c5a52ee1239e0ba6c0000000000feffffff956149bdc66faa968eb2be2d2faa29718acbfe3941215893a2a3446d32acd050000000000000000000e664b9773b88c09c32cb70a2a3e4da0ced63b7ba3b22f848531bbb1d5d5f4c94010000000000000000e9aa6b8e6c9de67619e6a3924ae25696bb7b694bb677a632a74ef7eadfd4eabf0000000000ffffffffa778eb6a263dc090464cd125c466b5a99667720b1c110468831d058aa1b82af10100000000ffffffff0200ca9a3b000000001976a91406afd46bcdfd22ef94ac122aa11f241244a37ecc88ac807840cb0000000020ac9a87f5594be208f8532db38cff670c450ed2fea8fcdefcc9a663f78bab962b0065cd1d",
+                "utxosSpent": [
+                    {
+                        "scriptPubKey": "512053a1f6e454df1aa2776a2814a721372d6258050de330b3c6d10ee8f4e0dda343",
+                        "amountSats": 420000000
+                    },
+                    {
+                        "scriptPubKey": "5120147c9c57132f6e7ecddba9800bb0c4449251c92a1e60371ee77557b6620f3ea3",
+                        "amountSats": 462000000
+                    },
+                    {
+                        "scriptPubKey": "76a914751e76e8199196d454941c45d1b3a323f1433bd688ac",
+                        "amountSats": 294000000
+                    },
+                    {
+                        "scriptPubKey": "5120e4d810fd50586274face62b8a807eb9719cef49c04177cc6b76a9a4251d5450e",
+                        "amountSats": 504000000
+                    },
+                    {
+                        "scriptPubKey": "512091b64d5324723a985170e4dc5a0f84c041804f2cd12660fa5dec09fc21783605",
+                        "amountSats": 630000000
+                    },
+                    {
+                        "scriptPubKey": "00147dd65592d0ab2fe0d0257d571abf032cd9db93dc",
+                        "amountSats": 378000000
+                    },
+                    {
+                        "scriptPubKey": "512075169f4001aa68f15bbed28b218df1d0a62cbbcf1188c6665110c293c907b831",
+                        "amountSats": 672000000
+                    },
+                    {
+                        "scriptPubKey": "5120712447206d7a5238acc7ff53fbe94a3b64539ad291c7cdbc490b7577e4b17df5",
+                        "amountSats": 546000000
+                    },
+                    {
+                        "scriptPubKey": "512077e30a5522dd9f894c3f8b8bd4c4b2cf82ca7da8a3ea6a239655c39c050ab220",
+                        "amountSats": 588000000
+                    }
+                ]
+            },
+            "intermediary": {
+                "hashAmounts": "58a6964a4f5f8f0b642ded0a8a553be7622a719da71d1f5befcefcdee8e0fde6",
+                "hashOutputs": "a2e6dab7c1f0dcd297c8d61647fd17d821541ea69c3cc37dcbad7f90d4eb4bc5",
+                "hashPrevouts": "e3b33bb4ef3a52ad1fffb555c0d82828eb22737036eaeb02a235d82b909c4c3f",
+                "hashScriptPubkeys": "23ad0f61ad2bca5ba6a7693f50fce988e17c3780bf2b1e720cfbb38fbdd52e21",
+                "hashSequences": "18959c7221ab5ce9e26c3cd67b22c24f8baa54bac281d8e6b05e400e6c3a957e"
+            },
+            "inputSpending": [
+                {
+                    "given": {
+                        "txinIndex": 0,
+                        "internalPrivkey": "6b973d88838f27366ed61c9ad6367663045cb456e28335c109e30717ae0c6baa",
+                        "merkleRoot": null,
+                        "hashType": 3
+                    },
+                    "intermediary": {
+                        "internalPubkey": "d6889cb081036e0faefa3a35157ad71086b123b2b144b649798b494c300a961d",
+                        "tweak": "b86e7be8f39bab32a6f2c0443abbc210f0edac0e2c53d501b36b64437d9c6c70",
+                        "tweakedPrivkey": "2405b971772ad26915c8dcdf10f238753a9b837e5f8e6a86fd7c0cce5b7296d9",
+                        "sigMsg": "0003020000000065cd1de3b33bb4ef3a52ad1fffb555c0d82828eb22737036eaeb02a235d82b909c4c3f58a6964a4f5f8f0b642ded0a8a553be7622a719da71d1f5befcefcdee8e0fde623ad0f61ad2bca5ba6a7693f50fce988e17c3780bf2b1e720cfbb38fbdd52e2118959c7221ab5ce9e26c3cd67b22c24f8baa54bac281d8e6b05e400e6c3a957e0000000000d0418f0e9a36245b9a50ec87f8bf5be5bcae434337b87139c3a5b1f56e33cba0",
+                        "precomputedUsed": [
+                            "hashAmounts",
+                            "hashPrevouts",
+                            "hashScriptPubkeys",
+                            "hashSequences"
+                        ],
+                        "sigHash": "2514a6272f85cfa0f45eb907fcb0d121b808ed37c6ea160a5a9046ed5526d555"
+                    },
+                    "expected": {
+                        "witness": [
+                            "ed7c1647cb97379e76892be0cacff57ec4a7102aa24296ca39af7541246d8ff14d38958d4cc1e2e478e4d4a764bbfd835b16d4e314b72937b29833060b87276c03"
+                        ]
+                    }
+                },
+                {
+                    "given": {
+                        "txinIndex": 1,
+                        "internalPrivkey": "1e4da49f6aaf4e5cd175fe08a32bb5cb4863d963921255f33d3bc31e1343907f",
+                        "merkleRoot": "5b75adecf53548f3ec6ad7d78383bf84cc57b55a3127c72b9a2481752dd88b21",
+                        "hashType": 131
+                    },
+                    "intermediary": {
+                        "internalPubkey": "187791b6f712a8ea41c8ecdd0ee77fab3e85263b37e1ec18a3651926b3a6cf27",
+                        "tweak": "cbd8679ba636c1110ea247542cfbd964131a6be84f873f7f3b62a777528ed001",
+                        "tweakedPrivkey": "ea260c3b10e60f6de018455cd0278f2f5b7e454be1999572789e6a9565d26080",
+                        "sigMsg": "0083020000000065cd1d00d7b7cab57b1393ace2d064f4d4a2cb8af6def61273e127517d44759b6dafdd9900000000808f891b00000000225120147c9c57132f6e7ecddba9800bb0c4449251c92a1e60371ee77557b6620f3ea3ffffffffffcef8fb4ca7efc5433f591ecfc57391811ce1e186a3793024def5c884cba51d",
+                        "precomputedUsed": [],
+                        "sigHash": "325a644af47e8a5a2591cda0ab0723978537318f10e6a63d4eed783b96a71a4d"
+                    },
+                    "expected": {
+                        "witness": [
+                            "052aedffc554b41f52b521071793a6b88d6dbca9dba94cf34c83696de0c1ec35ca9c5ed4ab28059bd606a4f3a657eec0bb96661d42921b5f50a95ad33675b54f83"
+                        ]
+                    }
+                },
+                {
+                    "given": {
+                        "txinIndex": 3,
+                        "internalPrivkey": "d3c7af07da2d54f7a7735d3d0fc4f0a73164db638b2f2f7c43f711f6d4aa7e64",
+                        "merkleRoot": "c525714a7f49c28aedbbba78c005931a81c234b2f6c99a73e4d06082adc8bf2b",
+                        "hashType": 1
+                    },
+                    "intermediary": {
+                        "internalPubkey": "93478e9488f956df2396be2ce6c5cced75f900dfa18e7dabd2428aae78451820",
+                        "tweak": "6af9e28dbf9d6aaf027696e2598a5b3d056f5fd2355a7fd5a37a0e5008132d30",
+                        "tweakedPrivkey": "97323385e57015b75b0339a549c56a948eb961555973f0951f555ae6039ef00d",
+                        "sigMsg": "0001020000000065cd1de3b33bb4ef3a52ad1fffb555c0d82828eb22737036eaeb02a235d82b909c4c3f58a6964a4f5f8f0b642ded0a8a553be7622a719da71d1f5befcefcdee8e0fde623ad0f61ad2bca5ba6a7693f50fce988e17c3780bf2b1e720cfbb38fbdd52e2118959c7221ab5ce9e26c3cd67b22c24f8baa54bac281d8e6b05e400e6c3a957ea2e6dab7c1f0dcd297c8d61647fd17d821541ea69c3cc37dcbad7f90d4eb4bc50003000000",
+                        "precomputedUsed": [
+                            "hashAmounts",
+                            "hashOutputs",
+                            "hashPrevouts",
+                            "hashScriptPubkeys",
+                            "hashSequences"
+                        ],
+                        "sigHash": "bf013ea93474aa67815b1b6cc441d23b64fa310911d991e713cd34c7f5d46669"
+                    },
+                    "expected": {
+                        "witness": [
+                            "ff45f742a876139946a149ab4d9185574b98dc919d2eb6754f8abaa59d18b025637a3aa043b91817739554f4ed2026cf8022dbd83e351ce1fabc272841d2510a01"
+                        ]
+                    }
+                },
+                {
+                    "given": {
+                        "txinIndex": 4,
+                        "internalPrivkey": "f36bb07a11e469ce941d16b63b11b9b9120a84d9d87cff2c84a8d4affb438f4e",
+                        "merkleRoot": "ccbd66c6f7e8fdab47b3a486f59d28262be857f30d4773f2d5ea47f7761ce0e2",
+                        "hashType": 0
+                    },
+                    "intermediary": {
+                        "internalPubkey": "e0dfe2300b0dd746a3f8674dfd4525623639042569d829c7f0eed9602d263e6f",
+                        "tweak": "b57bfa183d28eeb6ad688ddaabb265b4a41fbf68e5fed2c72c74de70d5a786f4",
+                        "tweakedPrivkey": "a8e7aa924f0d58854185a490e6c41f6efb7b675c0f3331b7f14b549400b4d501",
+                        "sigMsg": "0000020000000065cd1de3b33bb4ef3a52ad1fffb555c0d82828eb22737036eaeb02a235d82b909c4c3f58a6964a4f5f8f0b642ded0a8a553be7622a719da71d1f5befcefcdee8e0fde623ad0f61ad2bca5ba6a7693f50fce988e17c3780bf2b1e720cfbb38fbdd52e2118959c7221ab5ce9e26c3cd67b22c24f8baa54bac281d8e6b05e400e6c3a957ea2e6dab7c1f0dcd297c8d61647fd17d821541ea69c3cc37dcbad7f90d4eb4bc50004000000",
+                        "precomputedUsed": [
+                            "hashAmounts",
+                            "hashOutputs",
+                            "hashPrevouts",
+                            "hashScriptPubkeys",
+                            "hashSequences"
+                        ],
+                        "sigHash": "4f900a0bae3f1446fd48490c2958b5a023228f01661cda3496a11da502a7f7ef"
+                    },
+                    "expected": {
+                        "witness": [
+                            "b4010dd48a617db09926f729e79c33ae0b4e94b79f04a1ae93ede6315eb3669de185a17d2b0ac9ee09fd4c64b678a0b61a0a86fa888a273c8511be83bfd6810f"
+                        ]
+                    }
+                },
+                {
+                    "given": {
+                        "txinIndex": 6,
+                        "internalPrivkey": "415cfe9c15d9cea27d8104d5517c06e9de48e2f986b695e4f5ffebf230e725d8",
+                        "merkleRoot": "2f6b2c5397b6d68ca18e09a3f05161668ffe93a988582d55c6f07bd5b3329def",
+                        "hashType": 2
+                    },
+                    "intermediary": {
+                        "internalPubkey": "55adf4e8967fbd2e29f20ac896e60c3b0f1d5b0efa9d34941b5958c7b0a0312d",
+                        "tweak": "6579138e7976dc13b6a92f7bfd5a2fc7684f5ea42419d43368301470f3b74ed9",
+                        "tweakedPrivkey": "241c14f2639d0d7139282aa6abde28dd8a067baa9d633e4e7230287ec2d02901",
+                        "sigMsg": "0002020000000065cd1de3b33bb4ef3a52ad1fffb555c0d82828eb22737036eaeb02a235d82b909c4c3f58a6964a4f5f8f0b642ded0a8a553be7622a719da71d1f5befcefcdee8e0fde623ad0f61ad2bca5ba6a7693f50fce988e17c3780bf2b1e720cfbb38fbdd52e2118959c7221ab5ce9e26c3cd67b22c24f8baa54bac281d8e6b05e400e6c3a957e0006000000",
+                        "precomputedUsed": [
+                            "hashAmounts",
+                            "hashPrevouts",
+                            "hashScriptPubkeys",
+                            "hashSequences"
+                        ],
+                        "sigHash": "15f25c298eb5cdc7eb1d638dd2d45c97c4c59dcaec6679cfc16ad84f30876b85"
+                    },
+                    "expected": {
+                        "witness": [
+                            "a3785919a2ce3c4ce26f298c3d51619bc474ae24014bcdd31328cd8cfbab2eff3395fa0a16fe5f486d12f22a9cedded5ae74feb4bbe5351346508c5405bcfee002"
+                        ]
+                    }
+                },
+                {
+                    "given": {
+                        "txinIndex": 7,
+                        "internalPrivkey": "c7b0e81f0a9a0b0499e112279d718cca98e79a12e2f137c72ae5b213aad0d103",
+                        "merkleRoot": "6c2dc106ab816b73f9d07e3cd1ef2c8c1256f519748e0813e4edd2405d277bef",
+                        "hashType": 130
+                    },
+                    "intermediary": {
+                        "internalPubkey": "ee4fe085983462a184015d1f782d6a5f8b9c2b60130aff050ce221ecf3786592",
+                        "tweak": "9e0517edc8259bb3359255400b23ca9507f2a91cd1e4250ba068b4eafceba4a9",
+                        "tweakedPrivkey": "65b6000cd2bfa6b7cf736767a8955760e62b6649058cbc970b7c0871d786346b",
+                        "sigMsg": "0082020000000065cd1d00e9aa6b8e6c9de67619e6a3924ae25696bb7b694bb677a632a74ef7eadfd4eabf00000000804c8b2000000000225120712447206d7a5238acc7ff53fbe94a3b64539ad291c7cdbc490b7577e4b17df5ffffffff",
+                        "precomputedUsed": [],
+                        "sigHash": "cd292de50313804dabe4685e83f923d2969577191a3e1d2882220dca88cbeb10"
+                    },
+                    "expected": {
+                        "witness": [
+                            "ea0c6ba90763c2d3a296ad82ba45881abb4f426b3f87af162dd24d5109edc1cdd11915095ba47c3a9963dc1e6c432939872bc49212fe34c632cd3ab9fed429c482"
+                        ]
+                    }
+                },
+                {
+                    "given": {
+                        "txinIndex": 8,
+                        "internalPrivkey": "77863416be0d0665e517e1c375fd6f75839544eca553675ef7fdf4949518ebaa",
+                        "merkleRoot": "ab179431c28d3b68fb798957faf5497d69c883c6fb1e1cd9f81483d87bac90cc",
+                        "hashType": 129
+                    },
+                    "intermediary": {
+                        "internalPubkey": "f9f400803e683727b14f463836e1e78e1c64417638aa066919291a225f0e8dd8",
+                        "tweak": "639f0281b7ac49e742cd25b7f188657626da1ad169209078e2761cefd91fd65e",
+                        "tweakedPrivkey": "ec18ce6af99f43815db543f47b8af5ff5df3b2cb7315c955aa4a86e8143d2bf5",
+                        "sigMsg": "0081020000000065cd1da2e6dab7c1f0dcd297c8d61647fd17d821541ea69c3cc37dcbad7f90d4eb4bc500a778eb6a263dc090464cd125c466b5a99667720b1c110468831d058aa1b82af101000000002b0c230000000022512077e30a5522dd9f894c3f8b8bd4c4b2cf82ca7da8a3ea6a239655c39c050ab220ffffffff",
+                        "precomputedUsed": [
+                            "hashOutputs"
+                        ],
+                        "sigHash": "cccb739eca6c13a8a89e6e5cd317ffe55669bbda23f2fd37b0f18755e008edd2"
+                    },
+                    "expected": {
+                        "witness": [
+                            "bbc9584a11074e83bc8c6759ec55401f0ae7b03ef290c3139814f545b58a9f8127258000874f44bc46db7646322107d4d86aec8e73b8719a61fff761d75b5dd981"
+                        ]
+                    }
+                }
+            ],
+            "auxiliary": {
+                "fulledSignedTx": "020000000001097de20cbff686da83a54981d2b9bab3586f4ca7e48f57f5b55963115f3b334e9c010000000000000000d7b7cab57b1393ace2d064f4d4a2cb8af6def61273e127517d44759b6dafdd990000000000fffffffff8e1f583384333689228c5d28eac13366be082dc57441760d957275419a41842000000006b4830450221008f3b8f8f0537c420654d2283673a761b7ee2ea3c130753103e08ce79201cf32a022079e7ab904a1980ef1c5890b648c8783f4d10103dd62f740d13daa79e298d50c201210279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798fffffffff0689180aa63b30cb162a73c6d2a38b7eeda2a83ece74310fda0843ad604853b0100000000feffffffaa5202bdf6d8ccd2ee0f0202afbbb7461d9264a25e5bfd3c5a52ee1239e0ba6c0000000000feffffff956149bdc66faa968eb2be2d2faa29718acbfe3941215893a2a3446d32acd050000000000000000000e664b9773b88c09c32cb70a2a3e4da0ced63b7ba3b22f848531bbb1d5d5f4c94010000000000000000e9aa6b8e6c9de67619e6a3924ae25696bb7b694bb677a632a74ef7eadfd4eabf0000000000ffffffffa778eb6a263dc090464cd125c466b5a99667720b1c110468831d058aa1b82af10100000000ffffffff0200ca9a3b000000001976a91406afd46bcdfd22ef94ac122aa11f241244a37ecc88ac807840cb0000000020ac9a87f5594be208f8532db38cff670c450ed2fea8fcdefcc9a663f78bab962b0141ed7c1647cb97379e76892be0cacff57ec4a7102aa24296ca39af7541246d8ff14d38958d4cc1e2e478e4d4a764bbfd835b16d4e314b72937b29833060b87276c030141052aedffc554b41f52b521071793a6b88d6dbca9dba94cf34c83696de0c1ec35ca9c5ed4ab28059bd606a4f3a657eec0bb96661d42921b5f50a95ad33675b54f83000141ff45f742a876139946a149ab4d9185574b98dc919d2eb6754f8abaa59d18b025637a3aa043b91817739554f4ed2026cf8022dbd83e351ce1fabc272841d2510a010140b4010dd48a617db09926f729e79c33ae0b4e94b79f04a1ae93ede6315eb3669de185a17d2b0ac9ee09fd4c64b678a0b61a0a86fa888a273c8511be83bfd6810f0247304402202b795e4de72646d76eab3f0ab27dfa30b810e856ff3a46c9a702df53bb0d8cc302203ccc4d822edab5f35caddb10af1be93583526ccfbade4b4ead350781e2f8adcd012102f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f90141a3785919a2ce3c4ce26f298c3d51619bc474ae24014bcdd31328cd8cfbab2eff3395fa0a16fe5f486d12f22a9cedded5ae74feb4bbe5351346508c5405bcfee0020141ea0c6ba90763c2d3a296ad82ba45881abb4f426b3f87af162dd24d5109edc1cdd11915095ba47c3a9963dc1e6c432939872bc49212fe34c632cd3ab9fed429c4820141bbc9584a11074e83bc8c6759ec55401f0ae7b03ef290c3139814f545b58a9f8127258000874f44bc46db7646322107d4d86aec8e73b8719a61fff761d75b5dd9810065cd1d"
+            }
+        }
+    ]
+}
diff --git a/data/complex_psbt.json b/data/complex_psbt.json
new file mode 100644
--- /dev/null
+++ b/data/complex_psbt.json
@@ -0,0 +1,18 @@
+{
+  "initial_psbt": "cHNidP8BAP2aAQIAAAAJ0winN9IByUg2QqbcNv1NVfiD8Uh8MhEGnB9i/Ff6SFAAAAAAAP/////TCKc30gHJSDZCptw2/U1V+IPxSHwyEQacH2L8V/pIUAEAAAAA/////9MIpzfSAclINkKm3Db9TVX4g/FIfDIRBpwfYvxX+khQAgAAAAD/////0winN9IByUg2QqbcNv1NVfiD8Uh8MhEGnB9i/Ff6SFADAAAAAP/////TCKc30gHJSDZCptw2/U1V+IPxSHwyEQacH2L8V/pIUAQAAAAA/////9MIpzfSAclINkKm3Db9TVX4g/FIfDIRBpwfYvxX+khQBQAAAAD/////0winN9IByUg2QqbcNv1NVfiD8Uh8MhEGnB9i/Ff6SFAGAAAAAP/////TCKc30gHJSDZCptw2/U1V+IPxSHwyEQacH2L8V/pIUAcAAAAA/////9MIpzfSAclINkKm3Db9TVX4g/FIfDIRBpwfYvxX+khQCAAAAAD/////AcBByBcAAAAAFgAU86NkDG3go6XBS3IWTpj5bKedZfoAAAAAAAAAAAAAAAAAAAA=",
+  "miner_psbt": "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",
+  "p2pkh_psbt": "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",
+  "p2sh_pk_psbt": "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",
+  "p2sh_ms_1_psbt": "cHNidP8BAP2aAQIAAAAJ0winN9IByUg2QqbcNv1NVfiD8Uh8MhEGnB9i/Ff6SFAAAAAAAP/////TCKc30gHJSDZCptw2/U1V+IPxSHwyEQacH2L8V/pIUAEAAAAA/////9MIpzfSAclINkKm3Db9TVX4g/FIfDIRBpwfYvxX+khQAgAAAAD/////0winN9IByUg2QqbcNv1NVfiD8Uh8MhEGnB9i/Ff6SFADAAAAAP/////TCKc30gHJSDZCptw2/U1V+IPxSHwyEQacH2L8V/pIUAQAAAAA/////9MIpzfSAclINkKm3Db9TVX4g/FIfDIRBpwfYvxX+khQBQAAAAD/////0winN9IByUg2QqbcNv1NVfiD8Uh8MhEGnB9i/Ff6SFAGAAAAAP/////TCKc30gHJSDZCptw2/U1V+IPxSHwyEQacH2L8V/pIUAcAAAAA/////9MIpzfSAclINkKm3Db9TVX4g/FIfDIRBpwfYvxX+khQCAAAAAD/////AcBByBcAAAAAFgAU86NkDG3go6XBS3IWTpj5bKedZfoAAAAAAAEA/WkBAgAAAAEXUNNyuMJUiRxTf70xOP1jeqP319FEl7pG0OAxv2QmFAAAAAAA/v///wnOXi4SAQAAABYAFJ/NWVnRkyEUv9JimT6osqkmcauxgPD6AgAAAAAXqRSd5nSHjaZHXgBa1LuBJLvcf35j/IeA8PoCAAAAACIAIFTqZ4EMPhJ4ZabcILTBn62oVb4Gu1kegf/amL1qFDRJgPD6AgAAAAAWABR2WjPfBmcSViSAyRnb5DCwReSmmYDw+gIAAAAAIgAgvnOo3OeUTjP10JgLepzp4olyPr52+65XfA+h2tB4LN6A8PoCAAAAABepFAyQrTw0xTxZivkWe2bigAgT1chph4Dw+gIAAAAAF6kULdzdQIJRR4ojhfFdmfLdw6wUqpWHgPD6AgAAAAAXqRRVX4CcBGmL8gYxdD+Qj4OQ95+vAoeA8PoCAAAAABl2qRRHrrheehmBxwe2/LDeK5ALI8ti6oisZQAAAAEBH85eLhIBAAAAFgAUn81ZWdGTIRS/0mKZPqiyqSZxq7EAAQD9aQECAAAAARdQ03K4wlSJHFN/vTE4/WN6o/fX0USXukbQ4DG/ZCYUAAAAAAD+////Cc5eLhIBAAAAFgAUn81ZWdGTIRS/0mKZPqiyqSZxq7GA8PoCAAAAABepFJ3mdIeNpkdeAFrUu4Eku9x/fmP8h4Dw+gIAAAAAIgAgVOpngQw+EnhlptwgtMGfrahVvga7WR6B/9qYvWoUNEmA8PoCAAAAABYAFHZaM98GZxJWJIDJGdvkMLBF5KaZgPD6AgAAAAAiACC+c6jc55ROM/XQmAt6nOniiXI+vnb7rld8D6Ha0Hgs3oDw+gIAAAAAF6kUDJCtPDTFPFmK+RZ7ZuKACBPVyGmHgPD6AgAAAAAXqRQt3N1AglFHiiOF8V2Z8t3DrBSqlYeA8PoCAAAAABepFFVfgJwEaYvyBjF0P5CPg5D3n68Ch4Dw+gIAAAAAGXapFEeuuF56GYHHB7b8sN4rkAsjy2LqiKxlAAAAAAEA/WkBAgAAAAEXUNNyuMJUiRxTf70xOP1jeqP319FEl7pG0OAxv2QmFAAAAAAA/v///wnOXi4SAQAAABYAFJ/NWVnRkyEUv9JimT6osqkmcauxgPD6AgAAAAAXqRSd5nSHjaZHXgBa1LuBJLvcf35j/IeA8PoCAAAAACIAIFTqZ4EMPhJ4ZabcILTBn62oVb4Gu1kegf/amL1qFDRJgPD6AgAAAAAWABR2WjPfBmcSViSAyRnb5DCwReSmmYDw+gIAAAAAIgAgvnOo3OeUTjP10JgLepzp4olyPr52+65XfA+h2tB4LN6A8PoCAAAAABepFAyQrTw0xTxZivkWe2bigAgT1chph4Dw+gIAAAAAF6kULdzdQIJRR4ojhfFdmfLdw6wUqpWHgPD6AgAAAAAXqRRVX4CcBGmL8gYxdD+Qj4OQ95+vAoeA8PoCAAAAABl2qRRHrrheehmBxwe2/LDeK5ALI8ti6oisZQAAAAABAP1pAQIAAAABF1DTcrjCVIkcU3+9MTj9Y3qj99fRRJe6RtDgMb9kJhQAAAAAAP7///8Jzl4uEgEAAAAWABSfzVlZ0ZMhFL/SYpk+qLKpJnGrsYDw+gIAAAAAF6kUneZ0h42mR14AWtS7gSS73H9+Y/yHgPD6AgAAAAAiACBU6meBDD4SeGWm3CC0wZ+tqFW+BrtZHoH/2pi9ahQ0SYDw+gIAAAAAFgAUdloz3wZnElYkgMkZ2+QwsEXkppmA8PoCAAAAACIAIL5zqNznlE4z9dCYC3qc6eKJcj6+dvuuV3wPodrQeCzegPD6AgAAAAAXqRQMkK08NMU8WYr5Fntm4oAIE9XIaYeA8PoCAAAAABepFC3c3UCCUUeKI4XxXZny3cOsFKqVh4Dw+gIAAAAAF6kUVV+AnARpi/IGMXQ/kI+DkPefrwKHgPD6AgAAAAAZdqkUR664XnoZgccHtvyw3iuQCyPLYuqIrGUAAAABAR+A8PoCAAAAABYAFHZaM98GZxJWJIDJGdvkMLBF5KaZAAEA/WkBAgAAAAEXUNNyuMJUiRxTf70xOP1jeqP319FEl7pG0OAxv2QmFAAAAAAA/v///wnOXi4SAQAAABYAFJ/NWVnRkyEUv9JimT6osqkmcauxgPD6AgAAAAAXqRSd5nSHjaZHXgBa1LuBJLvcf35j/IeA8PoCAAAAACIAIFTqZ4EMPhJ4ZabcILTBn62oVb4Gu1kegf/amL1qFDRJgPD6AgAAAAAWABR2WjPfBmcSViSAyRnb5DCwReSmmYDw+gIAAAAAIgAgvnOo3OeUTjP10JgLepzp4olyPr52+65XfA+h2tB4LN6A8PoCAAAAABepFAyQrTw0xTxZivkWe2bigAgT1chph4Dw+gIAAAAAF6kULdzdQIJRR4ojhfFdmfLdw6wUqpWHgPD6AgAAAAAXqRRVX4CcBGmL8gYxdD+Qj4OQ95+vAoeA8PoCAAAAABl2qRRHrrheehmBxwe2/LDeK5ALI8ti6oisZQAAAAABAP1pAQIAAAABF1DTcrjCVIkcU3+9MTj9Y3qj99fRRJe6RtDgMb9kJhQAAAAAAP7///8Jzl4uEgEAAAAWABSfzVlZ0ZMhFL/SYpk+qLKpJnGrsYDw+gIAAAAAF6kUneZ0h42mR14AWtS7gSS73H9+Y/yHgPD6AgAAAAAiACBU6meBDD4SeGWm3CC0wZ+tqFW+BrtZHoH/2pi9ahQ0SYDw+gIAAAAAFgAUdloz3wZnElYkgMkZ2+QwsEXkppmA8PoCAAAAACIAIL5zqNznlE4z9dCYC3qc6eKJcj6+dvuuV3wPodrQeCzegPD6AgAAAAAXqRQMkK08NMU8WYr5Fntm4oAIE9XIaYeA8PoCAAAAABepFC3c3UCCUUeKI4XxXZny3cOsFKqVh4Dw+gIAAAAAF6kUVV+AnARpi/IGMXQ/kI+DkPefrwKHgPD6AgAAAAAZdqkUR664XnoZgccHtvyw3iuQCyPLYuqIrGUAAAAAAQD9aQECAAAAARdQ03K4wlSJHFN/vTE4/WN6o/fX0USXukbQ4DG/ZCYUAAAAAAD+////Cc5eLhIBAAAAFgAUn81ZWdGTIRS/0mKZPqiyqSZxq7GA8PoCAAAAABepFJ3mdIeNpkdeAFrUu4Eku9x/fmP8h4Dw+gIAAAAAIgAgVOpngQw+EnhlptwgtMGfrahVvga7WR6B/9qYvWoUNEmA8PoCAAAAABYAFHZaM98GZxJWJIDJGdvkMLBF5KaZgPD6AgAAAAAiACC+c6jc55ROM/XQmAt6nOniiXI+vnb7rld8D6Ha0Hgs3oDw+gIAAAAAF6kUDJCtPDTFPFmK+RZ7ZuKACBPVyGmHgPD6AgAAAAAXqRQt3N1AglFHiiOF8V2Z8t3DrBSqlYeA8PoCAAAAABepFFVfgJwEaYvyBjF0P5CPg5D3n68Ch4Dw+gIAAAAAGXapFEeuuF56GYHHB7b8sN4rkAsjy2LqiKxlAAAAIgIDiZk4Rf6NS9KEJCxttsdjcLCR688XBDP3ME9uwcypSAxHMEQCIHNtMH2gxzGIqzEuVdEsMCbggEpeKhfdmaPYajq8pzdeAiBJm4a7JOGfEG74g+Wj44suYtx1VNoqRHIEzPODvtS9ZwEBBGlSIQMVRqGbYX3GqDgQ2OB/M0zjUbQYYT6vJjWqQnsVJqXH+CEDOVaBbrUNQEs9XzJ5kVoXogcopGNj3wWn8p1G1VQGJlAhA4mZOEX+jUvShCQsbbbHY3CwkevPFwQz9zBPbsHMqUgMU64iBgMVRqGbYX3GqDgQ2OB/M0zjUbQYYT6vJjWqQnsVJqXH+AQkPXW6IgYDOVaBbrUNQEs9XzJ5kVoXogcopGNj3wWn8p1G1VQGJlAEReMpVCIGA4mZOEX+jUvShCQsbbbHY3CwkevPFwQz9zBPbsHMqUgMBGrkS7MAAQD9aQECAAAAARdQ03K4wlSJHFN/vTE4/WN6o/fX0USXukbQ4DG/ZCYUAAAAAAD+////Cc5eLhIBAAAAFgAUn81ZWdGTIRS/0mKZPqiyqSZxq7GA8PoCAAAAABepFJ3mdIeNpkdeAFrUu4Eku9x/fmP8h4Dw+gIAAAAAIgAgVOpngQw+EnhlptwgtMGfrahVvga7WR6B/9qYvWoUNEmA8PoCAAAAABYAFHZaM98GZxJWJIDJGdvkMLBF5KaZgPD6AgAAAAAiACC+c6jc55ROM/XQmAt6nOniiXI+vnb7rld8D6Ha0Hgs3oDw+gIAAAAAF6kUDJCtPDTFPFmK+RZ7ZuKACBPVyGmHgPD6AgAAAAAXqRQt3N1AglFHiiOF8V2Z8t3DrBSqlYeA8PoCAAAAABepFFVfgJwEaYvyBjF0P5CPg5D3n68Ch4Dw+gIAAAAAGXapFEeuuF56GYHHB7b8sN4rkAsjy2LqiKxlAAAAAAEA/WkBAgAAAAEXUNNyuMJUiRxTf70xOP1jeqP319FEl7pG0OAxv2QmFAAAAAAA/v///wnOXi4SAQAAABYAFJ/NWVnRkyEUv9JimT6osqkmcauxgPD6AgAAAAAXqRSd5nSHjaZHXgBa1LuBJLvcf35j/IeA8PoCAAAAACIAIFTqZ4EMPhJ4ZabcILTBn62oVb4Gu1kegf/amL1qFDRJgPD6AgAAAAAWABR2WjPfBmcSViSAyRnb5DCwReSmmYDw+gIAAAAAIgAgvnOo3OeUTjP10JgLepzp4olyPr52+65XfA+h2tB4LN6A8PoCAAAAABepFAyQrTw0xTxZivkWe2bigAgT1chph4Dw+gIAAAAAF6kULdzdQIJRR4ojhfFdmfLdw6wUqpWHgPD6AgAAAAAXqRRVX4CcBGmL8gYxdD+Qj4OQ95+vAoeA8PoCAAAAABl2qRRHrrheehmBxwe2/LDeK5ALI8ti6oisZQAAAAAA",
+  "p2sh_ms_2_psbt": "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",
+  "p2sh_wsh_pk_psbt": "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",
+  "p2sh_wsh_ms_1_psbt": "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",
+  "p2sh_wsh_ms_2_psbt": "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",
+  "p2wpkh_psbt": "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",
+  "p2wsh_pk_psbt": "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",
+  "p2wsh_ms_1_psbt": "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",
+  "p2wsh_ms_2_psbt": "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",
+  "combined_psbt": "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",
+  "complete_psbt": "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",
+  "final_tx": "02000000000109d308a737d201c9483642a6dc36fd4d55f883f1487c3211069c1f62fc57fa48500000000000ffffffffd308a737d201c9483642a6dc36fd4d55f883f1487c3211069c1f62fc57fa485001000000232200208637ab26e75a3150d2aafa9d5d4fd9ba4db83f952ae98c7301f3a25752f479e5ffffffffd308a737d201c9483642a6dc36fd4d55f883f1487c3211069c1f62fc57fa48500200000000ffffffffd308a737d201c9483642a6dc36fd4d55f883f1487c3211069c1f62fc57fa48500300000000ffffffffd308a737d201c9483642a6dc36fd4d55f883f1487c3211069c1f62fc57fa48500400000000ffffffffd308a737d201c9483642a6dc36fd4d55f883f1487c3211069c1f62fc57fa48500500000023220020b9d25e328ff72a0302abdd98e790099ed3f326775a1a9c7b31458b9f2d955f63ffffffffd308a737d201c9483642a6dc36fd4d55f883f1487c3211069c1f62fc57fa485006000000fc0047304402207b0a8e5b2163005382621e4810d59e0e5c729b574a7abe4f2dec0670be202177022068203b4a9ffe67ac05bca14569887050d6a3727e0bbc254a972b7a1c2f3fa039014730440220736d307da0c73188ab312e55d12c3026e0804a5e2a17dd99a3d86a3abca7375e0220499b86bb24e19f106ef883e5a3e38b2e62dc7554da2a447204ccf383bed4bd67014c695221031546a19b617dc6a83810d8e07f334ce351b418613eaf2635aa427b1526a5c7f821033956816eb50d404b3d5f3279915a17a20728a46363df05a7f29d46d554062650210389993845fe8d4bd284242c6db6c76370b091ebcf170433f7304f6ec1cca9480c53aeffffffffd308a737d201c9483642a6dc36fd4d55f883f1487c3211069c1f62fc57fa4850070000006c47304402205ba47300dd590f445cb56443ce6c13f0bcd07129de44764a26b83705f66262a602205bba7cd49fe34aa1c06e83ead381d64382dcb0109adcd9157250656d781929ff01232102628c8b13d6ad3290b100d40ae1cdecae515efc3f1abd5ef854c0755cb187d1f6acffffffffd308a737d201c9483642a6dc36fd4d55f883f1487c3211069c1f62fc57fa4850080000006a4730440220782f395dd1ee76f83395273eb214bda9ee21004e76be88951b863982287c850e0220200434957f72e0bae2ca10a50c8cf97ff279d51e13880b820d8e923cd99296ae0121035010f2bda2a5996aaf6edac6cd89e987730f8ad1b7620eac29a72795d513a623ffffffff01c041c81700000000160014f3a3640c6de0a3a5c14b72164e98f96ca79d65fa024730440220407fc97f300771f0499b7eb8f2cdd8705ce7a615fc893a1f2c25e90a322ff84202201f7c69db3033aebf7febb0809df36a750d065db56b9fb0f1e4492500ee7c1f440121028ec9808ed77394c3ff6e94c1c0f83fa0baaec915eff20566c7e8b09758f08beb0247304402207434a180083d825e5f034279832541fa3a140894a7950119d04ee4b64310c1e302206218a7134dc2e19d88b1fb931cd1577d988f1aa004690c17193a63bcd4a84dd4012321034f208b00923a0c6c5a91d735206b0a63e746d63a8b232b6aa9bce9832f3f4da5ac02473044022020a694a50573f2261baf3593afcc10ac9e6e74fb4b36a8db8273ed976195c0a302205e3c5c64ed8ab19aa1a1934195c89f4424b34d99f9fa62c781654b55a7c58427012321020ae496cfca86e51c88c45438b70697ade7969429d79ceefde7a824fc585d0f7fac02473044022074645c573a73d6e9eceac7f2b61b0d411df41f6691ef612f5f2d25209b6cc4c10220114fab0a3dfd58a7e7bed5aa828844e8cfd521450b2428330cd2507227b22b91012103693192b81b9cbefdd05321eb4c460264a1081d64396a76332f3db3b121bdd433040047304402203c168f021ee2bc3aeea53b2b289b2add195162fa090d3859e1f4ada6551dbc5302204badf65a27f22c22023eb0bf46e39f190fd3c794c98881ee1f9d44e5a1a5ca140147304402200c112ae3a3758c252af58c48b3ff756b93e43ac3e7a388651c7c1f8054209d7c022063e679bdef2b69b14c570fd6315096e372ea888b4149682cdbb37a8d12ada90f0169522102f8310173f04a117fa3c2ff96593908d1ec72c3e9da848099fd91daf118c48af3210346c02205f439c57cdf441a8cb55f4c339521c8d037ad804a15ed68e3391eb87b21034cf24693086f890b5d20b0c3f3152839ae00461ad1b29771e459a1676c47d57253ae040047304402201fc9a9292a2a92db4e99081fca2bf4c7062715c48b05c487b844a49e677e14b102207153528156c535e1500e1980593ba37ac649f8b2c1f8693a0a47beaf2dd4c67a01473044022026b040772ec6a96f2e29f457c89e9520c22f47e368945e0ad5f1af0435ea8079022028b4d895852c3efb4ad7b76133ccdee0f425431781e8b2f098014ade4c7369fc01695221021cf20f5e699d4b29397b2485f322f21aeb93264ab8532bc61127978418d49ed12102c06f94c7526991f26901f227e3ab7b398749bc0c307b0a4891ddc25ac88af51c210378fc18eb885a72bc6c3e3ebec5eade1bce0ef68a4863b2b7ceba2d96e511972f53ae00000000000000"
+}
diff --git a/data/key_io_invalid.json b/data/key_io_invalid.json
new file mode 100644
--- /dev/null
+++ b/data/key_io_invalid.json
@@ -0,0 +1,182 @@
+[
+    [
+        ""
+    ],
+    [
+        "x"
+    ],
+    [
+        "37qgekLpCCHrQuSjvX3fs496FWTGsHFHizjJAs6NPcR47aefnnCWECAhHV6E3g4YN7u7Yuwod5Y"
+    ],
+    [
+        "dzb7VV1Ui55BARxv7ATxAtCUeJsANKovDGWFVgpTbhq9gvPqP3yv"
+    ],
+    [
+        "MuNu7ZAEDFiHthiunm7dPjwKqrVNCM3mAz6rP9zFveQu14YA8CxExSJTHcVP9DErn6u84E6Ej7S"
+    ],
+    [
+        "rPpQpYknyNQ5AEHuY6H8ijJJrYc2nDKKk9jjmKEXsWzyAQcFGpDLU2Zvsmoi8JLR7hAwoy3RQWf"
+    ],
+    [
+        "4Uc3FmN6NQ6zLBK5QQBXRBUREaaHwCZYsGCueHauuDmJpZKn6jkEskMB2Zi2CNgtb5r6epWEFfUJq"
+    ],
+    [
+        "7aQgR5DFQ25vyXmqZAWmnVCjL3PkBcdVkBUpjrjMTcghHx3E8wb"
+    ],
+    [
+        "17QpPprjeg69fW1DV8DcYYCKvWjYhXvWkov6MJ1iTTvMFj6weAqW7wybZeH57WTNxXVCRH4veVs"
+    ],
+    [
+        "KxuACDviz8Xvpn1xAh9MfopySZNuyajYMZWz16Dv2mHHryznWUp3"
+    ],
+    [
+        "7nK3GSmqdXJQtdohvGfJ7KsSmn3TmGqExug49583bDAL91pVSGq5xS9SHoAYL3Wv3ijKTit65th"
+    ],
+    [
+        "cTivdBmq7bay3RFGEBBuNfMh2P1pDCgRYN2Wbxmgwr4ki3jNUL2va"
+    ],
+    [
+        "gjMV4vjNjyMrna4fsAr8bWxAbwtmMUBXJS3zL4NJt5qjozpbQLmAfK1uA3CquSqsZQMpoD1g2nk"
+    ],
+    [
+        "emXm1naBMoVzPjbk7xpeTVMFy4oDEe25UmoyGgKEB1gGWsK8kRGs"
+    ],
+    [
+        "7VThQnNRj1o3Zyvc7XHPRrjDf8j2oivPTeDXnRPYWeYGE4pXeRJDZgf28ppti5hsHWXS2GSobdqyo"
+    ],
+    [
+        "1G9u6oCVCPh2o8m3t55ACiYvG1y5BHewUkDSdiQarDcYXXhFHYdzMdYfUAhfxn5vNZBwpgUNpso"
+    ],
+    [
+        "31QQ7ZMLkScDiB4VyZjuptr7AEc9j1SjstF7pRoLhHTGkW4Q2y9XELobQmhhWxeRvqcukGd1XCq"
+    ],
+    [
+        "DHqKSnpxa8ZdQyH8keAhvLTrfkyBMQxqngcQA5N8LQ9KVt25kmGN"
+    ],
+    [
+        "2LUHcJPbwLCy9GLH1qXmfmAwvadWw4bp4PCpDfduLqV17s6iDcy1imUwhQJhAoNoN1XNmweiJP4i"
+    ],
+    [
+        "7USRzBXAnmck8fX9HmW7RAb4qt92VFX6soCnts9s74wxm4gguVhtG5of8fZGbNPJA83irHVY6bCos"
+    ],
+    [
+        "1DGezo7BfVebZxAbNT3XGujdeHyNNBF3vnficYoTSp4PfK2QaML9bHzAMxke3wdKdHYWmsMTJVu"
+    ],
+    [
+        "2D12DqDZKwCxxkzs1ZATJWvgJGhQ4cFi3WrizQ5zLAyhN5HxuAJ1yMYaJp8GuYsTLLxTAz6otCfb"
+    ],
+    [
+        "8AFJzuTujXjw1Z6M3fWhQ1ujDW7zsV4ePeVjVo7D1egERqSW9nZ"
+    ],
+    [
+        "163Q17qLbTCue8YY3AvjpUhotuaodLm2uqMhpYirsKjVqnxJRWTEoywMVY3NbBAHuhAJ2cF9GAZ"
+    ],
+    [
+        "2MnmgiRH4eGLyLc9eAqStzk7dFgBjFtUCtu"
+    ],
+    [
+        "461QQ2sYWxU7H2PV4oBwJGNch8XVTYYbZxU"
+    ],
+    [
+        "2UCtv53VttmQYkVU4VMtXB31REvQg4ABzs41AEKZ8UcB7DAfVzdkV9JDErwGwyj5AUHLkmgZeobs"
+    ],
+    [
+        "cSNjAsnhgtiFMi6MtfvgscMB2Cbhn2v1FUYfviJ1CdjfidvmeW6mn"
+    ],
+    [
+        "gmsow2Y6EWAFDFE1CE4Hd3Tpu2BvfmBfG1SXsuRARbnt1WjkZnFh1qGTiptWWbjsq2Q6qvpgJVj"
+    ],
+    [
+        "nksUKSkzS76v8EsSgozXGMoQFiCoCHzCVajFKAXqzK5on9ZJYVHMD5CKwgmX3S3c7M1U3xabUny"
+    ],
+    [
+        "L3favK1UzFGgdzYBF2oBT5tbayCo4vtVBLJhg2iYuMeePxWG8SQc"
+    ],
+    [
+        "7VxLxGGtYT6N99GdEfi6xz56xdQ8nP2dG1CavuXx7Rf2PrvNMTBNevjkfgs9JmkcGm6EXpj8ipyPZ"
+    ],
+    [
+        "2mbZwFXF6cxShaCo2czTRB62WTx9LxhTtpP"
+    ],
+    [
+        "dB7cwYdcPSgiyAwKWL3JwCVwSk6epU2txw"
+    ],
+    [
+        "HPhFUhUAh8ZQQisH8QQWafAxtQYju3SFTX"
+    ],
+    [
+        "4ctAH6AkHzq5ioiM1m9T3E2hiYEev5mTsB"
+    ],
+    [
+        "Hn1uFi4dNexWrqARpjMqgT6cX1UsNPuV3cHdGg9ExyXw8HTKadbktRDtdeVmY3M1BxJStiL4vjJ"
+    ],
+    [
+        "Sq3fDbvutABmnAHHExJDgPLQn44KnNC7UsXuT7KZecpaYDMU9Txs"
+    ],
+    [
+        "6TqWyrqdgUEYDQU1aChMuFMMEimHX44qHFzCUgGfqxGgZNMUVWJ"
+    ],
+    [
+        "giqJo7oWqFxNKWyrgcBxAVHXnjJ1t6cGoEffce5Y1y7u649Noj5wJ4mmiUAKEVVrYAGg2KPB3Y4"
+    ],
+    [
+        "cNzHY5e8vcmM3QVJUcjCyiKMYfeYvyueq5qCMV3kqcySoLyGLYUK"
+    ],
+    [
+        "37uTe568EYc9WLoHEd9jXEvUiWbq5LFLscNyqvAzLU5vBArUJA6eydkLmnMwJDjkL5kXc2VK7ig"
+    ],
+    [
+        "EsYbG4tWWWY45G31nox838qNdzksbPySWc"
+    ],
+    [
+        "nbuzhfwMoNzA3PaFnyLcRxE9bTJPDkjZ6Rf6Y6o2ckXZfzZzXBT"
+    ],
+    [
+        "cQN9PoxZeCWK1x56xnz6QYAsvR11XAce3Ehp3gMUdfSQ53Y2mPzx"
+    ],
+    [
+        "1Gm3N3rkef6iMbx4voBzaxtXcmmiMTqZPhcuAepRzYUJQW4qRpEnHvMojzof42hjFRf8PE2jPde"
+    ],
+    [
+        "2TAq2tuN6x6m233bpT7yqdYQPELdTDJn1eU"
+    ],
+    [
+        "ntEtnnGhqPii4joABvBtSEJG6BxjT2tUZqE8PcVYgk3RHpgxgHDCQxNbLJf7ardf1dDk2oCQ7Cf"
+    ],
+    [
+        "Ky1YjoZNgQ196HJV3HpdkecfhRBmRZdMJk89Hi5KGfpfPwS2bUbfd"
+    ],
+    [
+        "2A1q1YsMZowabbvta7kTy2Fd6qN4r5ZCeG3qLpvZBMzCixMUdkN2Y4dHB1wPsZAeVXUGD83MfRED"
+    ],
+    [
+        "tc1qw508d6qejxtdg4y5r3zarvary0c5xw7kg3g4ty"
+    ],
+    [
+        "bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t5"
+    ],
+    [
+        "BC13W508D6QEJXTDG4Y5R3ZARVARY0C5XW7KN40WF2"
+    ],
+    [
+        "bc1rw5uspcuh"
+    ],
+    [
+        "bc10w508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7kw5rljs90"
+    ],
+    [
+        "BC1QR508D6QEJXTDG4Y5R3ZARVARYV98GJ9P"
+    ],
+    [
+        "tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sL5k7"
+    ],
+    [
+        "bc1zw508d6qejxtdg4y5r3zarvaryvqyzf3du"
+    ],
+    [
+        "tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3pjxtptv"
+    ],
+    [
+        "bc1gmk9yu"
+    ]
+]
diff --git a/data/key_io_valid.json b/data/key_io_valid.json
new file mode 100644
--- /dev/null
+++ b/data/key_io_valid.json
@@ -0,0 +1,536 @@
+[
+    [
+        "1AGNa15ZQXAZUgFiqJ2i7Z2DPU2J6hW62i",
+        "76a91465a16059864a2fdbc7c99a4723a8395bc6f188eb88ac",
+        {
+            "isPrivkey": false,
+            "chain": "main"
+        }
+    ],
+    [
+        "3CMNFxN1oHBc4R1EpboAL5yzHGgE611Xou",
+        "a91474f209f6ea907e2ea48f74fae05782ae8a66525787",
+        {
+            "isPrivkey": false,
+            "chain": "main"
+        }
+    ],
+    [
+        "mo9ncXisMeAoXwqcV5EWuyncbmCcQN4rVs",
+        "76a91453c0307d6851aa0ce7825ba883c6bd9ad242b48688ac",
+        {
+            "isPrivkey": false,
+            "chain": "test"
+        }
+    ],
+    [
+        "mo9ncXisMeAoXwqcV5EWuyncbmCcQN4rVs",
+        "76a91453c0307d6851aa0ce7825ba883c6bd9ad242b48688ac",
+        {
+            "isPrivkey": false,
+            "chain": "regtest"
+        }
+    ],
+    [
+        "2N2JD6wb56AfK4tfmM6PwdVmoYk2dCKf4Br",
+        "a9146349a418fc4578d10a372b54b45c280cc8c4382f87",
+        {
+            "isPrivkey": false,
+            "chain": "test"
+        }
+    ],
+    [
+        "5Kd3NBUAdUnhyzenEwVLy9pBKxSwXvE9FMPyR4UKZvpe6E3AgLr",
+        "eddbdc1168f1daeadbd3e44c1e3f8f5a284c2029f78ad26af98583a499de5b19",
+        {
+            "isCompressed": false,
+            "isPrivkey": true,
+            "chain": "main"
+        }
+    ],
+    [
+        "Kz6UJmQACJmLtaQj5A3JAge4kVTNQ8gbvXuwbmCj7bsaabudb3RD",
+        "55c9bccb9ed68446d1b75273bbce89d7fe013a8acd1625514420fb2aca1a21c4",
+        {
+            "isCompressed": true,
+            "isPrivkey": true,
+            "chain": "main"
+        }
+    ],
+    [
+        "9213qJab2HNEpMpYNBa7wHGFKKbkDn24jpANDs2huN3yi4J11ko",
+        "36cb93b9ab1bdabf7fb9f2c04f1b9cc879933530ae7842398eef5a63a56800c2",
+        {
+            "isCompressed": false,
+            "isPrivkey": true,
+            "chain": "test"
+        }
+    ],
+    [
+        "9213qJab2HNEpMpYNBa7wHGFKKbkDn24jpANDs2huN3yi4J11ko",
+        "36cb93b9ab1bdabf7fb9f2c04f1b9cc879933530ae7842398eef5a63a56800c2",
+        {
+            "isCompressed": false,
+            "isPrivkey": true,
+            "chain": "regtest"
+        }
+    ],
+    [
+        "cTpB4YiyKiBcPxnefsDpbnDxFDffjqJob8wGCEDXxgQ7zQoMXJdH",
+        "b9f4892c9e8282028fea1d2667c4dc5213564d41fc5783896a0d843fc15089f3",
+        {
+            "isCompressed": true,
+            "isPrivkey": true,
+            "chain": "test"
+        }
+    ],
+    [
+        "cTpB4YiyKiBcPxnefsDpbnDxFDffjqJob8wGCEDXxgQ7zQoMXJdH",
+        "b9f4892c9e8282028fea1d2667c4dc5213564d41fc5783896a0d843fc15089f3",
+        {
+            "isCompressed": true,
+            "isPrivkey": true,
+            "chain": "regtest"
+        }
+    ],
+    [
+        "1Ax4gZtb7gAit2TivwejZHYtNNLT18PUXJ",
+        "76a9146d23156cbbdcc82a5a47eee4c2c7c583c18b6bf488ac",
+        {
+            "isPrivkey": false,
+            "chain": "main"
+        }
+    ],
+    [
+        "3QjYXhTkvuj8qPaXHTTWb5wjXhdsLAAWVy",
+        "a914fcc5460dd6e2487c7d75b1963625da0e8f4c597587",
+        {
+            "isPrivkey": false,
+            "chain": "main"
+        }
+    ],
+    [
+        "n3ZddxzLvAY9o7184TB4c6FJasAybsw4HZ",
+        "76a914f1d470f9b02370fdec2e6b708b08ac431bf7a5f788ac",
+        {
+            "isPrivkey": false,
+            "chain": "test"
+        }
+    ],
+    [
+        "2NBFNJTktNa7GZusGbDbGKRZTxdK9VVez3n",
+        "a914c579342c2c4c9220205e2cdc285617040c924a0a87",
+        {
+            "isPrivkey": false,
+            "chain": "test"
+        }
+    ],
+    [
+        "5K494XZwps2bGyeL71pWid4noiSNA2cfCibrvRWqcHSptoFn7rc",
+        "a326b95ebae30164217d7a7f57d72ab2b54e3be64928a19da0210b9568d4015e",
+        {
+            "isCompressed": false,
+            "isPrivkey": true,
+            "chain": "main"
+        }
+    ],
+    [
+        "L1RrrnXkcKut5DEMwtDthjwRcTTwED36thyL1DebVrKuwvohjMNi",
+        "7d998b45c219a1e38e99e7cbd312ef67f77a455a9b50c730c27f02c6f730dfb4",
+        {
+            "isCompressed": true,
+            "isPrivkey": true,
+            "chain": "main"
+        }
+    ],
+    [
+        "93DVKyFYwSN6wEo3E2fCrFPUp17FtrtNi2Lf7n4G3garFb16CRj",
+        "d6bca256b5abc5602ec2e1c121a08b0da2556587430bcf7e1898af2224885203",
+        {
+            "isCompressed": false,
+            "isPrivkey": true,
+            "chain": "test"
+        }
+    ],
+    [
+        "cTDVKtMGVYWTHCb1AFjmVbEbWjvKpKqKgMaR3QJxToMSQAhmCeTN",
+        "a81ca4e8f90181ec4b61b6a7eb998af17b2cb04de8a03b504b9e34c4c61db7d9",
+        {
+            "isCompressed": true,
+            "isPrivkey": true,
+            "chain": "test"
+        }
+    ],
+    [
+        "1C5bSj1iEGUgSTbziymG7Cn18ENQuT36vv",
+        "76a9147987ccaa53d02c8873487ef919677cd3db7a691288ac",
+        {
+            "isPrivkey": false,
+            "chain": "main"
+        }
+    ],
+    [
+        "3AnNxabYGoTxYiTEZwFEnerUoeFXK2Zoks",
+        "a91463bcc565f9e68ee0189dd5cc67f1b0e5f02f45cb87",
+        {
+            "isPrivkey": false,
+            "chain": "main"
+        }
+    ],
+    [
+        "n3LnJXCqbPjghuVs8ph9CYsAe4Sh4j97wk",
+        "76a914ef66444b5b17f14e8fae6e7e19b045a78c54fd7988ac",
+        {
+            "isPrivkey": false,
+            "chain": "test"
+        }
+    ],
+    [
+        "2NB72XtkjpnATMggui83aEtPawyyKvnbX2o",
+        "a914c3e55fceceaa4391ed2a9677f4a4d34eacd021a087",
+        {
+            "isPrivkey": false,
+            "chain": "test"
+        }
+    ],
+    [
+        "5KaBW9vNtWNhc3ZEDyNCiXLPdVPHCikRxSBWwV9NrpLLa4LsXi9",
+        "e75d936d56377f432f404aabb406601f892fd49da90eb6ac558a733c93b47252",
+        {
+            "isCompressed": false,
+            "isPrivkey": true,
+            "chain": "main"
+        }
+    ],
+    [
+        "L1axzbSyynNYA8mCAhzxkipKkfHtAXYF4YQnhSKcLV8YXA874fgT",
+        "8248bd0375f2f75d7e274ae544fb920f51784480866b102384190b1addfbaa5c",
+        {
+            "isCompressed": true,
+            "isPrivkey": true,
+            "chain": "main"
+        }
+    ],
+    [
+        "927CnUkUbasYtDwYwVn2j8GdTuACNnKkjZ1rpZd2yBB1CLcnXpo",
+        "44c4f6a096eac5238291a94cc24c01e3b19b8d8cef72874a079e00a242237a52",
+        {
+            "isCompressed": false,
+            "isPrivkey": true,
+            "chain": "test"
+        }
+    ],
+    [
+        "cUcfCMRjiQf85YMzzQEk9d1s5A4K7xL5SmBCLrezqXFuTVefyhY7",
+        "d1de707020a9059d6d3abaf85e17967c6555151143db13dbb06db78df0f15c69",
+        {
+            "isCompressed": true,
+            "isPrivkey": true,
+            "chain": "test"
+        }
+    ],
+    [
+        "1Gqk4Tv79P91Cc1STQtU3s1W6277M2CVWu",
+        "76a914adc1cc2081a27206fae25792f28bbc55b831549d88ac",
+        {
+            "isPrivkey": false,
+            "chain": "main"
+        }
+    ],
+    [
+        "33vt8ViH5jsr115AGkW6cEmEz9MpvJSwDk",
+        "a914188f91a931947eddd7432d6e614387e32b24470987",
+        {
+            "isPrivkey": false,
+            "chain": "main"
+        }
+    ],
+    [
+        "mhaMcBxNh5cqXm4aTQ6EcVbKtfL6LGyK2H",
+        "76a9141694f5bc1a7295b600f40018a618a6ea48eeb49888ac",
+        {
+            "isPrivkey": false,
+            "chain": "test"
+        }
+    ],
+    [
+        "2MxgPqX1iThW3oZVk9KoFcE5M4JpiETssVN",
+        "a9143b9b3fd7a50d4f08d1a5b0f62f644fa7115ae2f387",
+        {
+            "isPrivkey": false,
+            "chain": "test"
+        }
+    ],
+    [
+        "5HtH6GdcwCJA4ggWEL1B3jzBBUB8HPiBi9SBc5h9i4Wk4PSeApR",
+        "091035445ef105fa1bb125eccfb1882f3fe69592265956ade751fd095033d8d0",
+        {
+            "isCompressed": false,
+            "isPrivkey": true,
+            "chain": "main"
+        }
+    ],
+    [
+        "L2xSYmMeVo3Zek3ZTsv9xUrXVAmrWxJ8Ua4cw8pkfbQhcEFhkXT8",
+        "ab2b4bcdfc91d34dee0ae2a8c6b6668dadaeb3a88b9859743156f462325187af",
+        {
+            "isCompressed": true,
+            "isPrivkey": true,
+            "chain": "main"
+        }
+    ],
+    [
+        "92xFEve1Z9N8Z641KQQS7ByCSb8kGjsDzw6fAmjHN1LZGKQXyMq",
+        "b4204389cef18bbe2b353623cbf93e8678fbc92a475b664ae98ed594e6cf0856",
+        {
+            "isCompressed": false,
+            "isPrivkey": true,
+            "chain": "test"
+        }
+    ],
+    [
+        "92xFEve1Z9N8Z641KQQS7ByCSb8kGjsDzw6fAmjHN1LZGKQXyMq",
+        "b4204389cef18bbe2b353623cbf93e8678fbc92a475b664ae98ed594e6cf0856",
+        {
+            "isCompressed": false,
+            "isPrivkey": true,
+            "chain": "regtest"
+        }
+    ],
+    [
+        "cVM65tdYu1YK37tNoAyGoJTR13VBYFva1vg9FLuPAsJijGvG6NEA",
+        "e7b230133f1b5489843260236b06edca25f66adb1be455fbd38d4010d48faeef",
+        {
+            "isCompressed": true,
+            "isPrivkey": true,
+            "chain": "test"
+        }
+    ],
+    [
+        "1JwMWBVLtiqtscbaRHai4pqHokhFCbtoB4",
+        "76a914c4c1b72491ede1eedaca00618407ee0b772cad0d88ac",
+        {
+            "isPrivkey": false,
+            "chain": "main"
+        }
+    ],
+    [
+        "3QCzvfL4ZRvmJFiWWBVwxfdaNBT8EtxB5y",
+        "a914f6fe69bcb548a829cce4c57bf6fff8af3a5981f987",
+        {
+            "isPrivkey": false,
+            "chain": "main"
+        }
+    ],
+    [
+        "mizXiucXRCsEriQCHUkCqef9ph9qtPbZZ6",
+        "76a914261f83568a098a8638844bd7aeca039d5f2352c088ac",
+        {
+            "isPrivkey": false,
+            "chain": "test"
+        }
+    ],
+    [
+        "2NEWDzHWwY5ZZp8CQWbB7ouNMLqCia6YRda",
+        "a914e930e1834a4d234702773951d627cce82fbb5d2e87",
+        {
+            "isPrivkey": false,
+            "chain": "test"
+        }
+    ],
+    [
+        "5KQmDryMNDcisTzRp3zEq9e4awRmJrEVU1j5vFRTKpRNYPqYrMg",
+        "d1fab7ab7385ad26872237f1eb9789aa25cc986bacc695e07ac571d6cdac8bc0",
+        {
+            "isCompressed": false,
+            "isPrivkey": true,
+            "chain": "main"
+        }
+    ],
+    [
+        "L39Fy7AC2Hhj95gh3Yb2AU5YHh1mQSAHgpNixvm27poizcJyLtUi",
+        "b0bbede33ef254e8376aceb1510253fc3550efd0fcf84dcd0c9998b288f166b3",
+        {
+            "isCompressed": true,
+            "isPrivkey": true,
+            "chain": "main"
+        }
+    ],
+    [
+        "91cTVUcgydqyZLgaANpf1fvL55FH53QMm4BsnCADVNYuWuqdVys",
+        "037f4192c630f399d9271e26c575269b1d15be553ea1a7217f0cb8513cef41cb",
+        {
+            "isCompressed": false,
+            "isPrivkey": true,
+            "chain": "test"
+        }
+    ],
+    [
+        "cQspfSzsgLeiJGB2u8vrAiWpCU4MxUT6JseWo2SjXy4Qbzn2fwDw",
+        "6251e205e8ad508bab5596bee086ef16cd4b239e0cc0c5d7c4e6035441e7d5de",
+        {
+            "isCompressed": true,
+            "isPrivkey": true,
+            "chain": "test"
+        }
+    ],
+    [
+        "19dcawoKcZdQz365WpXWMhX6QCUpR9SY4r",
+        "76a9145eadaf9bb7121f0f192561a5a62f5e5f5421029288ac",
+        {
+            "isPrivkey": false,
+            "chain": "main"
+        }
+    ],
+    [
+        "37Sp6Rv3y4kVd1nQ1JV5pfqXccHNyZm1x3",
+        "a9143f210e7277c899c3a155cc1c90f4106cbddeec6e87",
+        {
+            "isPrivkey": false,
+            "chain": "main"
+        }
+    ],
+    [
+        "myoqcgYiehufrsnnkqdqbp69dddVDMopJu",
+        "76a914c8a3c2a09a298592c3e180f02487cd91ba3400b588ac",
+        {
+            "isPrivkey": false,
+            "chain": "test"
+        }
+    ],
+    [
+        "2N7FuwuUuoTBrDFdrAZ9KxBmtqMLxce9i1C",
+        "a91499b31df7c9068d1481b596578ddbb4d3bd90baeb87",
+        {
+            "isPrivkey": false,
+            "chain": "test"
+        }
+    ],
+    [
+        "5KL6zEaMtPRXZKo1bbMq7JDjjo1bJuQcsgL33je3oY8uSJCR5b4",
+        "c7666842503db6dc6ea061f092cfb9c388448629a6fe868d068c42a488b478ae",
+        {
+            "isCompressed": false,
+            "isPrivkey": true,
+            "chain": "main"
+        }
+    ],
+    [
+        "KwV9KAfwbwt51veZWNscRTeZs9CKpojyu1MsPnaKTF5kz69H1UN2",
+        "07f0803fc5399e773555ab1e8939907e9badacc17ca129e67a2f5f2ff84351dd",
+        {
+            "isCompressed": true,
+            "isPrivkey": true,
+            "chain": "main"
+        }
+    ],
+    [
+        "93N87D6uxSBzwXvpokpzg8FFmfQPmvX4xHoWQe3pLdYpbiwT5YV",
+        "ea577acfb5d1d14d3b7b195c321566f12f87d2b77ea3a53f68df7ebf8604a801",
+        {
+            "isCompressed": false,
+            "isPrivkey": true,
+            "chain": "test"
+        }
+    ],
+    [
+        "cMxXusSihaX58wpJ3tNuuUcZEQGt6DKJ1wEpxys88FFaQCYjku9h",
+        "0b3b34f0958d8a268193a9814da92c3e8b58b4a4378a542863e34ac289cd830c",
+        {
+            "isCompressed": true,
+            "isPrivkey": true,
+            "chain": "test"
+        }
+    ],
+    [
+        "13p1ijLwsnrcuyqcTvJXkq2ASdXqcnEBLE",
+        "76a9141ed467017f043e91ed4c44b4e8dd674db211c4e688ac",
+        {
+            "isPrivkey": false,
+            "chain": "main"
+        }
+    ],
+    [
+        "3ALJH9Y951VCGcVZYAdpA3KchoP9McEj1G",
+        "a9145ece0cadddc415b1980f001785947120acdb36fc87",
+        {
+            "isPrivkey": false,
+            "chain": "main"
+        }
+    ],
+    [
+        "bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4",
+        "0014751e76e8199196d454941c45d1b3a323f1433bd6",
+        {
+            "isPrivkey": false,
+            "chain": "main",
+            "tryCaseFlip": true
+        }
+    ],
+    [
+        "bcrt1qw508d6qejxtdg4y5r3zarvary0c5xw7kygt080",
+        "0014751e76e8199196d454941c45d1b3a323f1433bd6",
+        {
+            "isPrivkey": false,
+            "chain": "regtest",
+            "tryCaseFlip": true
+        }
+    ],
+    [
+        "tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sl5k7",
+        "00201863143c14c5166804bd19203356da136c985678cd4d27a1b8c6329604903262",
+        {
+            "isPrivkey": false,
+            "chain": "test",
+            "tryCaseFlip": true
+        }
+    ],
+    [
+        "bc1pw508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7k7grplx",
+        "5128751e76e8199196d454941c45d1b3a323f1433bd6751e76e8199196d454941c45d1b3a323f1433bd6",
+        {
+            "isPrivkey": false,
+            "chain": "main",
+            "tryCaseFlip": true,
+            "disabled": true
+        }
+    ],
+    [
+        "bc1sw50qa3jx3s",
+        "6002751e",
+        {
+            "isPrivkey": false,
+            "chain": "main",
+            "tryCaseFlip": true,
+            "disabled": true
+        }
+    ],
+    [
+        "bc1zw508d6qejxtdg4y5r3zarvaryvg6kdaj",
+        "5210751e76e8199196d454941c45d1b3a323",
+        {
+            "isPrivkey": false,
+            "chain": "main",
+            "tryCaseFlip": true,
+            "disabled": true
+        }
+    ],
+    [
+        "tb1qqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvsesrxh6hy",
+        "0020000000c4a5cad46221b2a187905e5266362b99d5e91c6ce24d165dab93e86433",
+        {
+            "isPrivkey": false,
+            "chain": "test",
+            "tryCaseFlip": true
+        }
+    ],
+    [
+        "bcrt1qqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvseswlauz7",
+        "0020000000c4a5cad46221b2a187905e5266362b99d5e91c6ce24d165dab93e86433",
+        {
+            "isPrivkey": false,
+            "chain": "regtest",
+            "tryCaseFlip": true
+        }
+    ]
+]
diff --git a/data/rfc6979DERcash.json b/data/rfc6979DERcash.json
new file mode 100644
--- /dev/null
+++ b/data/rfc6979DERcash.json
@@ -0,0 +1,102 @@
+[
+    [
+        "0000000000000000000000000000000000000000000000000000000000000001",
+        "Absence makes the heart grow fonder.",
+        "304402206c0db71422a66840f238b55635dc87d013a77caebb2057713e14928abe0d0ce602207f687dedcd60e4a8feb3ef31450ad8e42730d909dd19b5237a5eaa4bf50c0741"
+    ],
+    [
+        "0000000000000000000000000000000000000000000000000000000000000002",
+        "Actions speak louder than words.",
+        "3045022100912705043b888d052c35a2d9736dde7f29c453cb6b2c355fa1b1a1328a1d23170220021ede2707be213c7d489720af88c43f6eb86c47835e7d6ff61cbe91ebec288a"
+    ],
+    [
+        "0000000000000000000000000000000000000000000000000000000000000003",
+        "All for one and one for all.",
+        "3044022055bffc0d939ae7f8209dea0ae77f5b4905818201a4c2ca7f06a302ea56258186022079cfc2f2b5ce08e0a8f3f1082366f509783bceb2c4a05819a715724d68bbe7c0"
+    ],
+    [
+        "0000000000000000000000000000000000000000000000000000000000000004",
+        "All's fair in love and war.",
+        "3045022100e1ea024069bd02f1332fafdbd12f50103e0438cbce1e32517c9932c1e043c902022078f4408e6851f25e8fdc91f82a920cf4ce400554f236d810c499886f97c9500b"
+    ],
+    [
+        "0000000000000000000000000000000000000000000000000000000000000005",
+        "All work and no play makes Jack a dull boy.",
+        "3045022100824b35db88c1600747a3d9fe3d40d1400c6b8c3527c91d3bdb22b317bdf4dfd60220793025386e2de4289b3f0e135121f20088e1d0e0cb5bbf366561dffc9d17c0d4"
+    ],
+    [
+        "0000000000000000000000000000000000000000000000000000000000000006",
+        "All's well that ends well.",
+        "3045022100f3a3f37b80880a5b1ac2a3fcde9a86aae7036e8576f1ce28ab2849155237de63022027ef8c5e61c42a86fe13953d57aea273f797828f22df686bbdbc5601a7cdf49a"
+    ],
+    [
+        "0000000000000000000000000000000000000000000000000000000000000007",
+        "An apple a day keeps the doctor away.",
+        "3045022100e8f392a1657ec6fdf747ea8ff5a6dbeec7e932975c0c2f9430b9f6b7e3120018022064dd010aba1522d1ae16219e425225e9508c34a00d3e5d7327336391fc3e0cac"
+    ],
+    [
+        "0000000000000000000000000000000000000000000000000000000000000008",
+        "An apple never falls far from the tree.",
+        "3045022100ba82ea8d785a951ce33dcf65379abfd1e3d1affa4557870c1d931bad119e3fde022070d86f0cdb023fd40c365d9caab1351900371166a72f55315e661486ed769753"
+    ],
+    [
+        "0000000000000000000000000000000000000000000000000000000000000009",
+        "An ounce of prevention is worth a pound of cure.",
+        "30450221009d898ae395ed916e3b58f3f9e08019c65be8bcdc1634581891c30f7873de2570022048c7c244ceef3c3a6a6cd34119dc07ad54b8f8d50273629ac20fadd580241a92"
+    ],
+    [
+        "000000000000000000000000000000000000000000000000000000000000000a",
+        "Appearances can be deceiving.",
+        "304402206a75d44757c6ae076d8dce8ea79dfde47765f02659e0ccb6e9e3f675c6d0013602200345ff627949d08d6d02ba1d43d07e89fd74c54d250f212b6d9e1e227887cce2"
+    ],
+    [
+        "4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a",
+        "Absence makes the heart grow fonder.",
+        "3044022001e95d38c5d65344800d9d87de5c2d4319674adf6d248bb59113d42d18bdb97502200d5afe847651e64a358e65ba16cca9d1e02eac371cfa8e1206cd3dceb438f9d1"
+    ],
+    [
+        "dbc1b4c900ffe48d575b5da5c638040125f65db0fe3e24494b76ea986457d986",
+        "Actions speak louder than words.",
+        "30440220771a29e95d6e9e4c1b05b3cb7853ebc1057a9f68133fcc34190c4ba237e40b99022046067aff0918c18a3b3822c937921905ef22bf10c9f59d806ccfb23f7fcaa1ac"
+    ],
+    [
+        "084fed08b978af4d7d196a7446a86b58009e636b611db16211b65a9aadff29c5",
+        "All for one and one for all.",
+        "3045022100822eb70daf001ed5b8a9ccd9e2fa849918148bdd5d2cdf3fb5797be0fa82fec0022060d43b32e6c86804d351dfe863135d8391f10442552dd59b75f2cbc3894f7277"
+    ],
+    [
+        "e52d9c508c502347344d8c07ad91cbd6068afc75ff6292f062a09ca381c89e71",
+        "All's fair in love and war.",
+        "30440220264b1220e15eb1256bdac36512687aa37e059bb3b177bbf42f19a75cb95a9710022040928fcc170203596bb449f4d745b49f75b76d013a5c0d70af3f6eefa9af94d6"
+    ],
+    [
+        "e77b9a9ae9e30b0dbdb6f510a264ef9de781501d7b6b92ae89eb059c5ab743db",
+        "All work and no play makes Jack a dull boy.",
+        "304402203b96a2ea837df2aba4e94be5167ce9bcb1f00c2a6c6698f4ff340b7a9379dd5f0220051222212a9a4d1886acfac0594ae8f98db354448c8aa626d34ee7d4abb83d4c"
+    ],
+    [
+        "67586e98fad27da0b9968bc039a1ef34c939b9b8e523a8bef89d478608c5ecf6",
+        "All's well that ends well.",
+        "30450221009d6297db8a9446e376d45cc816ea9172d6a6db715680928dc0ead4f2f0fbbdfc02207df1a6c9e7a23c9baee9b2833d8dd18ea6ed33f7f7d653a1a29e75ead07ce143"
+    ],
+    [
+        "ca358758f6d27e6cf45272937977a748fd88391db679ceda7dc7bf1f005ee879",
+        "An apple a day keeps the doctor away.",
+        "304402202e58d297a0d452ae6afc3cc8f3bfc4ff1a10c76c3f71ff1062564f196d4038f3022073f77ea431bd2b95a0e226eccb2fd4680b71a2ee6ba46a6b25d54db5a928f4ce"
+    ],
+    [
+        "beead77994cf573341ec17b58bbf7eb34d2711c993c1d976b128b3188dc1829a",
+        "An apple never falls far from the tree.",
+        "304402204997ddbdb33574552d938e84378f29c3bb24a32e7a32e467315dd841fa278a040220198c27facdf3ac10b59cdc97ab269df337268d5a61527f41966f0ff876ac00df"
+    ],
+    [
+        "2b4c342f5433ebe591a1da77e013d1b72475562d48578dca8b84bac6651c3cb9",
+        "An ounce of prevention is worth a pound of cure.",
+        "304402202803b367eacdff2a0ee8727108f43efc864519d0fc972e42a5e4f3f2221b6f880220632003636d7293672003650babc3a589e35be93b98dabf20dead8e70085fa8a8"
+    ],
+    [
+        "01ba4719c80b6fe911b091a7c05124b64eeece964e09c058ef8f9805daca546b",
+        "Appearances can be deceiving.",
+        "30440220033aef6b3a2cea67a19d91130ee2736652588a21fdca9520ed778ca2d30e343d02206f0be9db4ebfc474470c4d164a67479e232f20e57f8659bc5b404520b665765d"
+    ]
+]
diff --git a/data/rfc6979DERcore.json b/data/rfc6979DERcore.json
new file mode 100644
--- /dev/null
+++ b/data/rfc6979DERcore.json
@@ -0,0 +1,102 @@
+[
+    [
+        "0000000000000000000000000000000000000000000000000000000000000001",
+        "Absence makes the heart grow fonder.",
+        "3045022100afff580595971b8c1700e77069d73602aef4c2a760dbd697881423dfff845de80220579adb6a1ac03acde461b5821a049ebd39a8a8ebf2506b841b15c27342d2e342"
+    ],
+    [
+        "0000000000000000000000000000000000000000000000000000000000000002",
+        "Actions speak louder than words.",
+        "304502210085f28bbc90975b1907a51cbfe7bf0dc1ac74ade49318ee97498dbbde3894a31c0220241d24da8d263e7af7ff49bca6a7a850f0e087faf6fef44f85851b0283c3f026"
+    ],
+    [
+        "0000000000000000000000000000000000000000000000000000000000000003",
+        "All for one and one for all.",
+        "30440220502c6ac38e1c68ce68f044f5ab680f2880a6c1cd34e70f2b4f945c6fd30abd03022018ef5c6c3392b9d67ad5109c85476a0e159425d7f6ace2cebeaa65f02f210bbb"
+    ],
+    [
+        "0000000000000000000000000000000000000000000000000000000000000004",
+        "All's fair in love and war.",
+        "30440220452d4ab234891cf6e5432cd5472bdca1cfc6fb28563333885f068da02ee216d8022056c368d16a64d29cff92f17203d926e113064527af0480d3bcc1d3fadfde9364"
+    ],
+    [
+        "0000000000000000000000000000000000000000000000000000000000000005",
+        "All work and no play makes Jack a dull boy.",
+        "3045022100995025b4880eeb1ecedba945fe8c9b2ddf2b07dbc293c2586c079d7b663ef38a022022fb54ab95014616d014277e05c97a7ed9e22596a0420bbd2d749ca9a2f876fe"
+    ],
+    [
+        "0000000000000000000000000000000000000000000000000000000000000006",
+        "All's well that ends well.",
+        "3045022100a9c1593fa6459777b2eba6d7e2a206e3bb119e85b2163973cf28ffaf24ec381c02202f166f13230b3853b928efb649d30375ec6a4b1a64a8d56fbcc0a9d86a0943e9"
+    ],
+    [
+        "0000000000000000000000000000000000000000000000000000000000000007",
+        "An apple a day keeps the doctor away.",
+        "304402202fc9c8b749621241c33fd51b57fc5140c1d7fc1594f91b073953e79da2f5e8f60220345e4ea7693b5069c0251771ea476cbe236586ed24b90aeeea7b7c2814edf477"
+    ],
+    [
+        "0000000000000000000000000000000000000000000000000000000000000008",
+        "An apple never falls far from the tree.",
+        "3044022052b6e2c49a6f6adbe52fb6bbe744caa3f49364085db118eab8670bc766be160302207d96a42866637ca3d4caf36e597a460eb305adac0220b027410c821a7191a1c4"
+    ],
+    [
+        "0000000000000000000000000000000000000000000000000000000000000009",
+        "An ounce of prevention is worth a pound of cure.",
+        "3045022100be53e7c00788e4417083d7511800f18c7c6f5f259de39bc6f8b1bebcd5056bd002201f389e13cfe7d1dbd8d2d1bff18138219f57de166673762009686a28fbc44df6"
+    ],
+    [
+        "000000000000000000000000000000000000000000000000000000000000000a",
+        "Appearances can be deceiving.",
+        "304402202f2413a1673f642c30ea2e23fcae45776bc77a94f96920aea3c14303b1469428022053ac3e8ea0a488e9159d56e429a51f207bf04e462f8d4ba2c69b1b1635f30217"
+    ],
+    [
+        "4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a",
+        "Absence makes the heart grow fonder.",
+        "3045022100996d79fba54b24e9394fc5fab6bf94d173f3752645075de6e32574fe08625f770220345e638b373dcb0ce0c09e5799695ef64ffc5e01dd8367b9a205ce25f28870f6"
+    ],
+    [
+        "dbc1b4c900ffe48d575b5da5c638040125f65db0fe3e24494b76ea986457d986",
+        "Actions speak louder than words.",
+        "304502210088164430985a4437471417c2386faa536e1fe8ec91bd0f1f642bc22a776891530220090dc83d6e3b54a1a54dc2e79c693144179a512d9c9e686a6c25e7641a2101a8"
+    ],
+    [
+        "084fed08b978af4d7d196a7446a86b58009e636b611db16211b65a9aadff29c5",
+        "All for one and one for all.",
+        "30450221009f1073c9c09b664498d4b216983330b01c29a0fb55dd61aa145b4ebd0579905502204592fb6626f672d4f3ad4bb2d0a1ed6c2a161cc35c6bb77e6f0fd3b63feab36f"
+    ],
+    [
+        "e52d9c508c502347344d8c07ad91cbd6068afc75ff6292f062a09ca381c89e71",
+        "All's fair in love and war.",
+        "304502210080eabf24117b492635043886e7229b9705b970cbb6828c4e03a39dae7ac34bda022070e8a32ca1df82add53facbd58b4f2d3984d0a17b6b13c44460238d9ff74e41f"
+    ],
+    [
+        "e77b9a9ae9e30b0dbdb6f510a264ef9de781501d7b6b92ae89eb059c5ab743db",
+        "All work and no play makes Jack a dull boy.",
+        "3045022100a43ff5edea7ea0b9716d4359574e990a6859cdaeb9d7d6b4964afd40be11bd35022067f9d82e22fc447a122997335525f117f37b141c3efa9f8c6d77b586753f962f"
+    ],
+    [
+        "67586e98fad27da0b9968bc039a1ef34c939b9b8e523a8bef89d478608c5ecf6",
+        "All's well that ends well.",
+        "3044022053ce16251f4fae7eb87e2ab040a6f334e08687fb445566256cd217ece389e0440220576506a168cbc9ee0dd485d6c418961e7a0861b0f05d22a93401812978d0b215"
+    ],
+    [
+        "ca358758f6d27e6cf45272937977a748fd88391db679ceda7dc7bf1f005ee879",
+        "An apple a day keeps the doctor away.",
+        "3045022100df8744cc06a304b041e88149acfd84a68d8f4a2a4047056644e1ec8357e11ebe02204ba2d5499a26d072c797a86c7851533f287ceb8b818cae2c5d4483c37c62750c"
+    ],
+    [
+        "beead77994cf573341ec17b58bbf7eb34d2711c993c1d976b128b3188dc1829a",
+        "An apple never falls far from the tree.",
+        "3045022100878372d211ed0dbde1273ae3dd85aec577c08a06a55960f2e274f97cc9f2f38f02203f992caa66f472a64f6ccdd8076c0a12202c674155a6a61b8cd23c1ded08aab7"
+    ],
+    [
+        "2b4c342f5433ebe591a1da77e013d1b72475562d48578dca8b84bac6651c3cb9",
+        "An ounce of prevention is worth a pound of cure.",
+        "3045022100d5cb4e148c0a29ce37f1542be416e8ef575da522666b19b541960d726c99662b022045c951c1ca938c90dad6c3eede7c5df67fcf0d14f90faf201e8d215f215c5c18"
+    ],
+    [
+        "01ba4719c80b6fe911b091a7c05124b64eeece964e09c058ef8f9805daca546b",
+        "Appearances can be deceiving.",
+        "304402203e2f0118062306e2239c873828a7275dd35545a143797e224148c5bbbd59dd08022073a8c9e17be75c66362913b5e05d81fd619b434edda766fae6c352e86987809d"
+    ]
+]
diff --git a/data/rfc6979cash.json b/data/rfc6979cash.json
new file mode 100644
--- /dev/null
+++ b/data/rfc6979cash.json
@@ -0,0 +1,62 @@
+[
+    [
+        "0000000000000000000000000000000000000000000000000000000000000001",
+        "Satoshi Nakamoto",
+        "cd7a225c6661e35a85f309a66ba9fbcd134487b80ae1d8a3c58d85015ac1f7ed046b573d040c8324ecf796d6b86732fdfd8938888fd8a28309e476732475a743"
+    ],
+    [
+        "0000000000000000000000000000000000000000000000000000000000000001",
+        "All those moments will be lost in time, like tears in rain. Time to die...",
+        "fb073d886dda0444913a3f1fa32d5b2bc35d09420672b6e7e8e36c530422c98f114230b9362ba6985ccb0d87b86c752e597fa3ca46f1c0abe226a9496187588c"
+    ],
+    [
+        "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140",
+        "Satoshi Nakamoto",
+        "20d0306cd55957786c6dedae03c2dc85cea8030de04ec6ba027cffe8b1be37d15f46f9282a7117c4179f54d00c944b4ce04b51e118cfddcdc73d72d5b5923933"
+    ],
+    [
+        "f8b8af8ce3c7cca5e300d33939540c10d45ce001b8f252bfbc57ba0342904181",
+        "Alan Turing",
+        "b27049b4834f874e41a4b6c586ed6aa1d86dba5ef411bd2a149161e39287ab4247ad691e06358323d6bbb16589ac186c63393c694bf75914c4a29ee56675c740"
+    ],
+    [
+        "e91671c46231f833a6406ccbea0e3e392c76c167bac1cb013f6f1013980455c2",
+        "There is a computer disease that anybody who works with computers knows about. It's a very serious disease and it interferes completely with the work. The trouble with computers is that you 'play' with them!",
+        "08438a0a59cb5c1f3de6a0c3c4852282472b9286ff6427f65dc8af31b6bdd41b300ad6ef64cf7d2f6ce7109d134d1c542c61430d4d3d1e404f4fd2634a0da0ad"
+    ],
+    [
+        "0000000000000000000000000000000000000000000000000000000000000001",
+        "Everything should be made as simple as possible, but not simpler.",
+        "53452b2fbb94b30051ab0e7075c1e613685079cc5244c36aa7b9148521a04b87268579ff46388ff7ceb7d5a691fcc995cd62d596603fffff6013eaaef3ec1f65"
+    ],
+    [
+        "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140",
+        "Equations are more important to me, because politics is for the present, but an equation is something for eternity.",
+        "fb904bd574f61510463e4e6593be29aa59de55f1dc31d3075920ac9ca7ef55997afa037dc9192877bf13ecf5458966bb92abd3328247b32425ab21d8b8a0d767"
+    ],
+    [
+        "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140",
+        "Not only is the Universe stranger than we think, it is stranger than we can think.",
+        "402f863e862bdfabfa9444e1370f4163a8f61a5f3e16b56ce639d3c373d0ad551aea2bdfb410f1f327737f7fa28646338876f4097b86c1204952e8f1733989d7"
+    ],
+    [
+        "0000000000000000000000000000000000000000000000000000000000000001",
+        "How wonderful that we have met with a paradox. Now we have some hope of making progress.",
+        "efdff52e87f77261f1aa07c7938e346547b712d3892a0ef6380cfff46198588b17efea735778bc04bae52cec6617b85537dc913f1423f21fd97860405ad78aee"
+    ],
+    [
+        "69ec59eaa1f4f2e36b639716b7c30ca86d9a5375c7b38d8918bd9c0ebc80ba64",
+        "Computer science is no more about computers than astronomy is about telescopes.",
+        "d270a8604b11ec88a4e95cc1e4006f88452d5faf71dfd3f6d561d130a52903f93a6e9662a0fdf9f05ee5da5ba9fc437c97cf894a17844ccfb19d708942b72324"
+    ],
+    [
+        "00000000000000000000000000007246174ab1e92e9149c6e446fe194d072637",
+        "...if you aren't, at any given time, scandalized by code you wrote five or even three years ago, you're not learning anywhere near enough",
+        "c0b51457714f4786e0f1d38219bfa124e06cbfff8b0c74c1a0ab11dff80b3a1b207aefdd3ac67158746b0ffad2be589a8f816296012b394302ab8a514aaeb120"
+    ],
+    [
+        "000000000000000000000000000000000000000000056916d0f9b31dc9b637f3",
+        "The question of whether computers can think is like the question of whether submarines can swim.",
+        "fdcc51599f0b25a48f4b1f3701c153d840d49414b77d582b73bc0a93a3d2fe0c5770d55329857e5e1095545f911881447feccdde067faac1338c7f48a0355407"
+    ]
+]
diff --git a/data/rfc6979core.json b/data/rfc6979core.json
new file mode 100644
--- /dev/null
+++ b/data/rfc6979core.json
@@ -0,0 +1,62 @@
+[
+    [
+        "0000000000000000000000000000000000000000000000000000000000000001",
+        "Satoshi Nakamoto",
+        "934b1ea10a4b3c1757e2b0c017d0b6143ce3c9a7e6a4a49860d7a6ab210ee3d82442ce9d2b916064108014783e923ec36b49743e2ffa1c4496f01a512aafd9e5"
+    ],
+    [
+        "0000000000000000000000000000000000000000000000000000000000000001",
+        "All those moments will be lost in time, like tears in rain. Time to die...",
+        "8600dbd41e348fe5c9465ab92d23e3db8b98b873beecd930736488696438cb6b547fe64427496db33bf66019dacbf0039c04199abb0122918601db38a72cfc21"
+    ],
+    [
+        "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140",
+        "Satoshi Nakamoto",
+        "fd567d121db66e382991534ada77a6bd3106f0a1098c231e47993447cd6af2d06b39cd0eb1bc8603e159ef5c20a5c8ad685a45b06ce9bebed3f153d10d93bed5"
+    ],
+    [
+        "f8b8af8ce3c7cca5e300d33939540c10d45ce001b8f252bfbc57ba0342904181",
+        "Alan Turing",
+        "7063ae83e7f62bbb171798131b4a0564b956930092b33b07b395615d9ec7e15c58dfcc1e00a35e1572f366ffe34ba0fc47db1e7189759b9fb233c5b05ab388ea"
+    ],
+    [
+        "e91671c46231f833a6406ccbea0e3e392c76c167bac1cb013f6f1013980455c2",
+        "There is a computer disease that anybody who works with computers knows about. It's a very serious disease and it interferes completely with the work. The trouble with computers is that you 'play' with them!",
+        "b552edd27580141f3b2a5463048cb7cd3e047b97c9f98076c32dbdf85a68718b279fa72dd19bfae05577e06c7c0c1900c371fcd5893f7e1d56a37d30174671f6"
+    ],
+    [
+        "0000000000000000000000000000000000000000000000000000000000000001",
+        "Everything should be made as simple as possible, but not simpler.",
+        "33a69cd2065432a30f3d1ce4eb0d59b8ab58c74f27c41a7fdb5696ad4e6108c96f807982866f785d3f6418d24163ddae117b7db4d5fdf0071de069fa54342262"
+    ],
+    [
+        "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140",
+        "Equations are more important to me, because politics is for the present, but an equation is something for eternity.",
+        "54c4a33c6423d689378f160a7ff8b61330444abb58fb470f96ea16d99d4a2fed07082304410efa6b2943111b6a4e0aaa7b7db55a07e9861d1fb3cb1f421044a5"
+    ],
+    [
+        "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140",
+        "Not only is the Universe stranger than we think, it is stranger than we can think.",
+        "ff466a9f1b7b273e2f4c3ffe032eb2e814121ed18ef84665d0f515360dab3dd06fc95f5132e5ecfdc8e5e6e616cc77151455d46ed48f5589b7db7771a332b283"
+    ],
+    [
+        "0000000000000000000000000000000000000000000000000000000000000001",
+        "How wonderful that we have met with a paradox. Now we have some hope of making progress.",
+        "c0dafec8251f1d5010289d210232220b03202cba34ec11fec58b3e93a85b91d375afdc06b7d6322a590955bf264e7aaa155847f614d80078a90292fe205064d3"
+    ],
+    [
+        "69ec59eaa1f4f2e36b639716b7c30ca86d9a5375c7b38d8918bd9c0ebc80ba64",
+        "Computer science is no more about computers than astronomy is about telescopes.",
+        "7186363571d65e084e7f02b0b77c3ec44fb1b257dee26274c38c928986fea45d0de0b38e06807e46bda1f1e293f4f6323e854c86d58abdd00c46c16441085df6"
+    ],
+    [
+        "00000000000000000000000000007246174ab1e92e9149c6e446fe194d072637",
+        "...if you aren't, at any given time, scandalized by code you wrote five or even three years ago, you're not learning anywhere near enough",
+        "fbfe5076a15860ba8ed00e75e9bd22e05d230f02a936b653eb55b61c99dda4870e68880ebb0050fe4312b1b1eb0899e1b82da89baa5b895f612619edf34cbd37"
+    ],
+    [
+        "000000000000000000000000000000000000000000056916d0f9b31dc9b637f3",
+        "The question of whether computers can think is like the question of whether submarines can swim.",
+        "cde1302d83f8dd835d89aef803c74a119f561fbaef3eb9129e45f30de86abbf906ce643f5049ee1f27890467b77a6a8e11ec4661cc38cd8badf90115fbd03cef"
+    ]
+]
diff --git a/data/test_vectors_aserti3-2d_run01.txt b/data/test_vectors_aserti3-2d_run01.txt
new file mode 100644
--- /dev/null
+++ b/data/test_vectors_aserti3-2d_run01.txt
@@ -0,0 +1,19 @@
+## description: run1 - steady 600s blocks at POW limit target
+##   anchor height: 1
+##   anchor parent time: 0
+##   anchor nBits: 0x1d00ffff
+##   start height: 2
+##   start time: 1200
+##   iterations: 10
+# iteration,height,time,target
+1 2 1200 0x1d00ffff
+2 3 1800 0x1d00ffff
+3 4 2400 0x1d00ffff
+4 5 3000 0x1d00ffff
+5 6 3600 0x1d00ffff
+6 7 4200 0x1d00ffff
+7 8 4800 0x1d00ffff
+8 9 5400 0x1d00ffff
+9 10 6000 0x1d00ffff
+10 11 6600 0x1d00ffff
+
diff --git a/data/test_vectors_aserti3-2d_run02.txt b/data/test_vectors_aserti3-2d_run02.txt
new file mode 100644
--- /dev/null
+++ b/data/test_vectors_aserti3-2d_run02.txt
@@ -0,0 +1,19 @@
+## description: run2 - steady 600s blocks at arbitrary non-limit target 0x1a2b3c4d
+##   anchor height: 1
+##   anchor parent time: 0
+##   anchor nBits: 0x1a2b3c4d
+##   start height: 2
+##   start time: 1200
+##   iterations: 10
+# iteration,height,time,target
+1 2 1200 0x1a2b3c4d
+2 3 1800 0x1a2b3c4d
+3 4 2400 0x1a2b3c4d
+4 5 3000 0x1a2b3c4d
+5 6 3600 0x1a2b3c4d
+6 7 4200 0x1a2b3c4d
+7 8 4800 0x1a2b3c4d
+8 9 5400 0x1a2b3c4d
+9 10 6000 0x1a2b3c4d
+10 11 6600 0x1a2b3c4d
+
diff --git a/data/test_vectors_aserti3-2d_run03.txt b/data/test_vectors_aserti3-2d_run03.txt
new file mode 100644
--- /dev/null
+++ b/data/test_vectors_aserti3-2d_run03.txt
@@ -0,0 +1,19 @@
+## description: run3 - steady 600s blocks at minimum limit target 0x01010000
+##   anchor height: 1
+##   anchor parent time: 0
+##   anchor nBits: 0x01010000
+##   start height: 2
+##   start time: 1200
+##   iterations: 10
+# iteration,height,time,target
+1 2 1200 0x01010000
+2 3 1800 0x01010000
+3 4 2400 0x01010000
+4 5 3000 0x01010000
+5 6 3600 0x01010000
+6 7 4200 0x01010000
+7 8 4800 0x01010000
+8 9 5400 0x01010000
+9 10 6000 0x01010000
+10 11 6600 0x01010000
+
diff --git a/data/test_vectors_aserti3-2d_run04.txt b/data/test_vectors_aserti3-2d_run04.txt
new file mode 100644
--- /dev/null
+++ b/data/test_vectors_aserti3-2d_run04.txt
@@ -0,0 +1,234 @@
+## description: run4 - from minimum target, a series of halflife schedule jumps, doubling target at each block
+##   anchor height: 1
+##   anchor parent time: 0
+##   anchor nBits: 0x01010000
+##   start height: 2
+##   start time: 174000
+##   iterations: 225
+# iteration,height,time,target
+1 2 174000 0x01020000
+2 3 347400 0x01040000
+3 4 520800 0x01080000
+4 5 694200 0x01100000
+5 6 867600 0x01200000
+6 7 1041000 0x01400000
+7 8 1214400 0x02008000
+8 9 1387800 0x02010000
+9 10 1561200 0x02020000
+10 11 1734600 0x02040000
+11 12 1908000 0x02080000
+12 13 2081400 0x02100000
+13 14 2254800 0x02200000
+14 15 2428200 0x02400000
+15 16 2601600 0x03008000
+16 17 2775000 0x03010000
+17 18 2948400 0x03020000
+18 19 3121800 0x03040000
+19 20 3295200 0x03080000
+20 21 3468600 0x03100000
+21 22 3642000 0x03200000
+22 23 3815400 0x03400000
+23 24 3988800 0x04008000
+24 25 4162200 0x04010000
+25 26 4335600 0x04020000
+26 27 4509000 0x04040000
+27 28 4682400 0x04080000
+28 29 4855800 0x04100000
+29 30 5029200 0x04200000
+30 31 5202600 0x04400000
+31 32 5376000 0x05008000
+32 33 5549400 0x05010000
+33 34 5722800 0x05020000
+34 35 5896200 0x05040000
+35 36 6069600 0x05080000
+36 37 6243000 0x05100000
+37 38 6416400 0x05200000
+38 39 6589800 0x05400000
+39 40 6763200 0x06008000
+40 41 6936600 0x06010000
+41 42 7110000 0x06020000
+42 43 7283400 0x06040000
+43 44 7456800 0x06080000
+44 45 7630200 0x06100000
+45 46 7803600 0x06200000
+46 47 7977000 0x06400000
+47 48 8150400 0x07008000
+48 49 8323800 0x07010000
+49 50 8497200 0x07020000
+50 51 8670600 0x07040000
+51 52 8844000 0x07080000
+52 53 9017400 0x07100000
+53 54 9190800 0x07200000
+54 55 9364200 0x07400000
+55 56 9537600 0x08008000
+56 57 9711000 0x08010000
+57 58 9884400 0x08020000
+58 59 10057800 0x08040000
+59 60 10231200 0x08080000
+60 61 10404600 0x08100000
+61 62 10578000 0x08200000
+62 63 10751400 0x08400000
+63 64 10924800 0x09008000
+64 65 11098200 0x09010000
+65 66 11271600 0x09020000
+66 67 11445000 0x09040000
+67 68 11618400 0x09080000
+68 69 11791800 0x09100000
+69 70 11965200 0x09200000
+70 71 12138600 0x09400000
+71 72 12312000 0x0a008000
+72 73 12485400 0x0a010000
+73 74 12658800 0x0a020000
+74 75 12832200 0x0a040000
+75 76 13005600 0x0a080000
+76 77 13179000 0x0a100000
+77 78 13352400 0x0a200000
+78 79 13525800 0x0a400000
+79 80 13699200 0x0b008000
+80 81 13872600 0x0b010000
+81 82 14046000 0x0b020000
+82 83 14219400 0x0b040000
+83 84 14392800 0x0b080000
+84 85 14566200 0x0b100000
+85 86 14739600 0x0b200000
+86 87 14913000 0x0b400000
+87 88 15086400 0x0c008000
+88 89 15259800 0x0c010000
+89 90 15433200 0x0c020000
+90 91 15606600 0x0c040000
+91 92 15780000 0x0c080000
+92 93 15953400 0x0c100000
+93 94 16126800 0x0c200000
+94 95 16300200 0x0c400000
+95 96 16473600 0x0d008000
+96 97 16647000 0x0d010000
+97 98 16820400 0x0d020000
+98 99 16993800 0x0d040000
+99 100 17167200 0x0d080000
+100 101 17340600 0x0d100000
+101 102 17514000 0x0d200000
+102 103 17687400 0x0d400000
+103 104 17860800 0x0e008000
+104 105 18034200 0x0e010000
+105 106 18207600 0x0e020000
+106 107 18381000 0x0e040000
+107 108 18554400 0x0e080000
+108 109 18727800 0x0e100000
+109 110 18901200 0x0e200000
+110 111 19074600 0x0e400000
+111 112 19248000 0x0f008000
+112 113 19421400 0x0f010000
+113 114 19594800 0x0f020000
+114 115 19768200 0x0f040000
+115 116 19941600 0x0f080000
+116 117 20115000 0x0f100000
+117 118 20288400 0x0f200000
+118 119 20461800 0x0f400000
+119 120 20635200 0x10008000
+120 121 20808600 0x10010000
+121 122 20982000 0x10020000
+122 123 21155400 0x10040000
+123 124 21328800 0x10080000
+124 125 21502200 0x10100000
+125 126 21675600 0x10200000
+126 127 21849000 0x10400000
+127 128 22022400 0x11008000
+128 129 22195800 0x11010000
+129 130 22369200 0x11020000
+130 131 22542600 0x11040000
+131 132 22716000 0x11080000
+132 133 22889400 0x11100000
+133 134 23062800 0x11200000
+134 135 23236200 0x11400000
+135 136 23409600 0x12008000
+136 137 23583000 0x12010000
+137 138 23756400 0x12020000
+138 139 23929800 0x12040000
+139 140 24103200 0x12080000
+140 141 24276600 0x12100000
+141 142 24450000 0x12200000
+142 143 24623400 0x12400000
+143 144 24796800 0x13008000
+144 145 24970200 0x13010000
+145 146 25143600 0x13020000
+146 147 25317000 0x13040000
+147 148 25490400 0x13080000
+148 149 25663800 0x13100000
+149 150 25837200 0x13200000
+150 151 26010600 0x13400000
+151 152 26184000 0x14008000
+152 153 26357400 0x14010000
+153 154 26530800 0x14020000
+154 155 26704200 0x14040000
+155 156 26877600 0x14080000
+156 157 27051000 0x14100000
+157 158 27224400 0x14200000
+158 159 27397800 0x14400000
+159 160 27571200 0x15008000
+160 161 27744600 0x15010000
+161 162 27918000 0x15020000
+162 163 28091400 0x15040000
+163 164 28264800 0x15080000
+164 165 28438200 0x15100000
+165 166 28611600 0x15200000
+166 167 28785000 0x15400000
+167 168 28958400 0x16008000
+168 169 29131800 0x16010000
+169 170 29305200 0x16020000
+170 171 29478600 0x16040000
+171 172 29652000 0x16080000
+172 173 29825400 0x16100000
+173 174 29998800 0x16200000
+174 175 30172200 0x16400000
+175 176 30345600 0x17008000
+176 177 30519000 0x17010000
+177 178 30692400 0x17020000
+178 179 30865800 0x17040000
+179 180 31039200 0x17080000
+180 181 31212600 0x17100000
+181 182 31386000 0x17200000
+182 183 31559400 0x17400000
+183 184 31732800 0x18008000
+184 185 31906200 0x18010000
+185 186 32079600 0x18020000
+186 187 32253000 0x18040000
+187 188 32426400 0x18080000
+188 189 32599800 0x18100000
+189 190 32773200 0x18200000
+190 191 32946600 0x18400000
+191 192 33120000 0x19008000
+192 193 33293400 0x19010000
+193 194 33466800 0x19020000
+194 195 33640200 0x19040000
+195 196 33813600 0x19080000
+196 197 33987000 0x19100000
+197 198 34160400 0x19200000
+198 199 34333800 0x19400000
+199 200 34507200 0x1a008000
+200 201 34680600 0x1a010000
+201 202 34854000 0x1a020000
+202 203 35027400 0x1a040000
+203 204 35200800 0x1a080000
+204 205 35374200 0x1a100000
+205 206 35547600 0x1a200000
+206 207 35721000 0x1a400000
+207 208 35894400 0x1b008000
+208 209 36067800 0x1b010000
+209 210 36241200 0x1b020000
+210 211 36414600 0x1b040000
+211 212 36588000 0x1b080000
+212 213 36761400 0x1b100000
+213 214 36934800 0x1b200000
+214 215 37108200 0x1b400000
+215 216 37281600 0x1c008000
+216 217 37455000 0x1c010000
+217 218 37628400 0x1c020000
+218 219 37801800 0x1c040000
+219 220 37975200 0x1c080000
+220 221 38148600 0x1c100000
+221 222 38322000 0x1c200000
+222 223 38495400 0x1c400000
+223 224 38668800 0x1d008000
+224 225 38842200 0x1d00ffff
+225 226 39015600 0x1d00ffff
+
diff --git a/data/test_vectors_aserti3-2d_run05.txt b/data/test_vectors_aserti3-2d_run05.txt
new file mode 100644
--- /dev/null
+++ b/data/test_vectors_aserti3-2d_run05.txt
@@ -0,0 +1,234 @@
+## description: run5 - from POW limit, a series of halflife block height jumps w/o time increment, halving target at each block
+##   anchor height: 1
+##   anchor parent time: 0
+##   anchor nBits: 0x1d00ffff
+##   start height: 2
+##   start time: 0
+##   iterations: 225
+# iteration,height,time,target
+1 2 0 0x1d00fec5
+2 290 0 0x1c7f62c0
+3 578 0 0x1c3fb160
+4 866 0 0x1c1fd8b0
+5 1154 0 0x1c0fec58
+6 1442 0 0x1c07f62c
+7 1730 0 0x1c03fb16
+8 2018 0 0x1c01fd8b
+9 2306 0 0x1c00fec5
+10 2594 0 0x1b7f62c0
+11 2882 0 0x1b3fb160
+12 3170 0 0x1b1fd8b0
+13 3458 0 0x1b0fec58
+14 3746 0 0x1b07f62c
+15 4034 0 0x1b03fb16
+16 4322 0 0x1b01fd8b
+17 4610 0 0x1b00fec5
+18 4898 0 0x1a7f62c0
+19 5186 0 0x1a3fb160
+20 5474 0 0x1a1fd8b0
+21 5762 0 0x1a0fec58
+22 6050 0 0x1a07f62c
+23 6338 0 0x1a03fb16
+24 6626 0 0x1a01fd8b
+25 6914 0 0x1a00fec5
+26 7202 0 0x197f62c0
+27 7490 0 0x193fb160
+28 7778 0 0x191fd8b0
+29 8066 0 0x190fec58
+30 8354 0 0x1907f62c
+31 8642 0 0x1903fb16
+32 8930 0 0x1901fd8b
+33 9218 0 0x1900fec5
+34 9506 0 0x187f62c0
+35 9794 0 0x183fb160
+36 10082 0 0x181fd8b0
+37 10370 0 0x180fec58
+38 10658 0 0x1807f62c
+39 10946 0 0x1803fb16
+40 11234 0 0x1801fd8b
+41 11522 0 0x1800fec5
+42 11810 0 0x177f62c0
+43 12098 0 0x173fb160
+44 12386 0 0x171fd8b0
+45 12674 0 0x170fec58
+46 12962 0 0x1707f62c
+47 13250 0 0x1703fb16
+48 13538 0 0x1701fd8b
+49 13826 0 0x1700fec5
+50 14114 0 0x167f62c0
+51 14402 0 0x163fb160
+52 14690 0 0x161fd8b0
+53 14978 0 0x160fec58
+54 15266 0 0x1607f62c
+55 15554 0 0x1603fb16
+56 15842 0 0x1601fd8b
+57 16130 0 0x1600fec5
+58 16418 0 0x157f62c0
+59 16706 0 0x153fb160
+60 16994 0 0x151fd8b0
+61 17282 0 0x150fec58
+62 17570 0 0x1507f62c
+63 17858 0 0x1503fb16
+64 18146 0 0x1501fd8b
+65 18434 0 0x1500fec5
+66 18722 0 0x147f62c0
+67 19010 0 0x143fb160
+68 19298 0 0x141fd8b0
+69 19586 0 0x140fec58
+70 19874 0 0x1407f62c
+71 20162 0 0x1403fb16
+72 20450 0 0x1401fd8b
+73 20738 0 0x1400fec5
+74 21026 0 0x137f62c0
+75 21314 0 0x133fb160
+76 21602 0 0x131fd8b0
+77 21890 0 0x130fec58
+78 22178 0 0x1307f62c
+79 22466 0 0x1303fb16
+80 22754 0 0x1301fd8b
+81 23042 0 0x1300fec5
+82 23330 0 0x127f62c0
+83 23618 0 0x123fb160
+84 23906 0 0x121fd8b0
+85 24194 0 0x120fec58
+86 24482 0 0x1207f62c
+87 24770 0 0x1203fb16
+88 25058 0 0x1201fd8b
+89 25346 0 0x1200fec5
+90 25634 0 0x117f62c0
+91 25922 0 0x113fb160
+92 26210 0 0x111fd8b0
+93 26498 0 0x110fec58
+94 26786 0 0x1107f62c
+95 27074 0 0x1103fb16
+96 27362 0 0x1101fd8b
+97 27650 0 0x1100fec5
+98 27938 0 0x107f62c0
+99 28226 0 0x103fb160
+100 28514 0 0x101fd8b0
+101 28802 0 0x100fec58
+102 29090 0 0x1007f62c
+103 29378 0 0x1003fb16
+104 29666 0 0x1001fd8b
+105 29954 0 0x1000fec5
+106 30242 0 0x0f7f62c0
+107 30530 0 0x0f3fb160
+108 30818 0 0x0f1fd8b0
+109 31106 0 0x0f0fec58
+110 31394 0 0x0f07f62c
+111 31682 0 0x0f03fb16
+112 31970 0 0x0f01fd8b
+113 32258 0 0x0f00fec5
+114 32546 0 0x0e7f62c0
+115 32834 0 0x0e3fb160
+116 33122 0 0x0e1fd8b0
+117 33410 0 0x0e0fec58
+118 33698 0 0x0e07f62c
+119 33986 0 0x0e03fb16
+120 34274 0 0x0e01fd8b
+121 34562 0 0x0e00fec5
+122 34850 0 0x0d7f62c0
+123 35138 0 0x0d3fb160
+124 35426 0 0x0d1fd8b0
+125 35714 0 0x0d0fec58
+126 36002 0 0x0d07f62c
+127 36290 0 0x0d03fb16
+128 36578 0 0x0d01fd8b
+129 36866 0 0x0d00fec5
+130 37154 0 0x0c7f62c0
+131 37442 0 0x0c3fb160
+132 37730 0 0x0c1fd8b0
+133 38018 0 0x0c0fec58
+134 38306 0 0x0c07f62c
+135 38594 0 0x0c03fb16
+136 38882 0 0x0c01fd8b
+137 39170 0 0x0c00fec5
+138 39458 0 0x0b7f62c0
+139 39746 0 0x0b3fb160
+140 40034 0 0x0b1fd8b0
+141 40322 0 0x0b0fec58
+142 40610 0 0x0b07f62c
+143 40898 0 0x0b03fb16
+144 41186 0 0x0b01fd8b
+145 41474 0 0x0b00fec5
+146 41762 0 0x0a7f62c0
+147 42050 0 0x0a3fb160
+148 42338 0 0x0a1fd8b0
+149 42626 0 0x0a0fec58
+150 42914 0 0x0a07f62c
+151 43202 0 0x0a03fb16
+152 43490 0 0x0a01fd8b
+153 43778 0 0x0a00fec5
+154 44066 0 0x097f62c0
+155 44354 0 0x093fb160
+156 44642 0 0x091fd8b0
+157 44930 0 0x090fec58
+158 45218 0 0x0907f62c
+159 45506 0 0x0903fb16
+160 45794 0 0x0901fd8b
+161 46082 0 0x0900fec5
+162 46370 0 0x087f62c0
+163 46658 0 0x083fb160
+164 46946 0 0x081fd8b0
+165 47234 0 0x080fec58
+166 47522 0 0x0807f62c
+167 47810 0 0x0803fb16
+168 48098 0 0x0801fd8b
+169 48386 0 0x0800fec5
+170 48674 0 0x077f62c0
+171 48962 0 0x073fb160
+172 49250 0 0x071fd8b0
+173 49538 0 0x070fec58
+174 49826 0 0x0707f62c
+175 50114 0 0x0703fb16
+176 50402 0 0x0701fd8b
+177 50690 0 0x0700fec5
+178 50978 0 0x067f62c0
+179 51266 0 0x063fb160
+180 51554 0 0x061fd8b0
+181 51842 0 0x060fec58
+182 52130 0 0x0607f62c
+183 52418 0 0x0603fb16
+184 52706 0 0x0601fd8b
+185 52994 0 0x0600fec5
+186 53282 0 0x057f62c0
+187 53570 0 0x053fb160
+188 53858 0 0x051fd8b0
+189 54146 0 0x050fec58
+190 54434 0 0x0507f62c
+191 54722 0 0x0503fb16
+192 55010 0 0x0501fd8b
+193 55298 0 0x0500fec5
+194 55586 0 0x047f62c0
+195 55874 0 0x043fb160
+196 56162 0 0x041fd8b0
+197 56450 0 0x040fec58
+198 56738 0 0x0407f62c
+199 57026 0 0x0403fb16
+200 57314 0 0x0401fd8b
+201 57602 0 0x0400fec5
+202 57890 0 0x037f62c0
+203 58178 0 0x033fb160
+204 58466 0 0x031fd8b0
+205 58754 0 0x030fec58
+206 59042 0 0x0307f62c
+207 59330 0 0x0303fb16
+208 59618 0 0x0301fd8b
+209 59906 0 0x0300fec5
+210 60194 0 0x027f6200
+211 60482 0 0x023fb100
+212 60770 0 0x021fd800
+213 61058 0 0x020fec00
+214 61346 0 0x0207f600
+215 61634 0 0x0203fb00
+216 61922 0 0x0201fd00
+217 62210 0 0x0200fe00
+218 62498 0 0x017f0000
+219 62786 0 0x013f0000
+220 63074 0 0x011f0000
+221 63362 0 0x010f0000
+222 63650 0 0x01070000
+223 63938 0 0x01030000
+224 64226 0 0x01010000
+225 64514 0 0x01010000
+
diff --git a/data/test_vectors_aserti3-2d_run06.txt b/data/test_vectors_aserti3-2d_run06.txt
new file mode 100644
--- /dev/null
+++ b/data/test_vectors_aserti3-2d_run06.txt
@@ -0,0 +1,1009 @@
+## description: run6 - deterministically random solvetimes for stable hashrate around a recent real life nBits
+##   anchor height: 1
+##   anchor parent time: 0
+##   anchor nBits: 0x1802aee8
+##   start height: 2
+##   start time: 1200
+##   iterations: 1000
+# iteration,height,time,target
+1 2 1200 0x1802aee8
+2 3 1310 0x1802ad91
+3 4 1327 0x1802abf8
+4 5 1739 0x1802ab73
+5 6 2219 0x1802ab20
+6 7 2604 0x1802aa89
+7 8 2746 0x1802a94b
+8 9 7099 0x1802b39c
+9 10 7099 0x1802b1f2
+10 11 7657 0x1802b1d4
+11 12 8626 0x1802b2db
+12 13 8816 0x1802b1b7
+13 14 9632 0x1802b252
+14 15 10804 0x1802b3e7
+15 16 11727 0x1802b4ce
+16 17 11761 0x1802b33c
+17 18 13000 0x1802b501
+18 19 14144 0x1802b686
+19 20 15302 0x1802b816
+20 21 15443 0x1802b6cf
+21 22 18902 0x1802bed6
+22 23 19348 0x1802be68
+23 24 20359 0x1802bf92
+24 25 20760 0x1802bf01
+25 26 21285 0x1802becb
+26 27 21425 0x1802bd7c
+27 28 21985 0x1802bd61
+28 29 22100 0x1802bc02
+29 30 22170 0x1802ba85
+30 31 23287 0x1802bbf9
+31 32 23465 0x1802baca
+32 33 24549 0x1802bc27
+33 34 24981 0x1802bbae
+34 35 25619 0x1802bbc9
+35 36 26539 0x1802bcb0
+36 37 27381 0x1802bd5c
+37 38 28265 0x1802be2a
+38 39 30203 0x1802c1f6
+39 40 30824 0x1802c203
+40 41 31537 0x1802c254
+41 42 32479 0x1802c34d
+42 43 33013 0x1802c31d
+43 44 33615 0x1802c31f
+44 45 33944 0x1802c25c
+45 46 34232 0x1802c177
+46 47 34368 0x1802c028
+47 48 34434 0x1802bea6
+48 49 34805 0x1802bdff
+49 50 34810 0x1802bc52
+50 51 34841 0x1802bab7
+51 52 34914 0x1802b93d
+52 53 35335 0x1802b8bc
+53 54 37423 0x1802bcee
+54 55 38049 0x1802bcfe
+55 56 38435 0x1802bc65
+56 57 39072 0x1802bc7d
+57 58 39434 0x1802bbd4
+58 59 39506 0x1802ba57
+59 60 39760 0x1802b960
+60 61 40232 0x1802b902
+61 62 40553 0x1802b83c
+62 63 41296 0x1802b8a2
+63 64 41590 0x1802b7c6
+64 65 41709 0x1802b66e
+65 66 42647 0x1802b760
+66 67 43493 0x1802b80e
+67 68 43493 0x1802b663
+68 69 43501 0x1802b4bb
+69 70 43969 0x1802b45d
+70 71 44175 0x1802b344
+71 72 44756 0x1802b339
+72 73 44763 0x1802b191
+73 74 45570 0x1802b225
+74 75 46234 0x1802b252
+75 76 46484 0x1802b159
+76 77 46668 0x1802b032
+77 78 46739 0x1802aebd
+78 79 46749 0x1802ad21
+79 80 46815 0x1802aba9
+80 81 48149 0x1802adab
+81 82 48206 0x1802ac2f
+82 83 48491 0x1802ab52
+83 84 48530 0x1802a9c9
+84 85 49095 0x1802a9b2
+85 86 49640 0x1802a98b
+86 87 49766 0x1802a840
+87 88 50290 0x1802a80d
+88 89 52536 0x1802ac8a
+89 90 52614 0x1802ab1c
+90 91 52773 0x1802a9e9
+91 92 53231 0x1802a986
+92 93 55043 0x1802acd4
+93 94 56035 0x1802ade9
+94 95 56085 0x1802ac65
+95 96 56918 0x1802ad0a
+96 97 58635 0x1802b019
+97 98 61599 0x1802b6ae
+98 99 61973 0x1802b60b
+99 100 63111 0x1802b78d
+100 101 63201 0x1802b620
+101 102 64481 0x1802b809
+102 103 64935 0x1802b79d
+103 104 65231 0x1802b6c4
+104 105 65793 0x1802b6a9
+105 106 66209 0x1802b626
+106 107 67377 0x1802b7bb
+107 108 67640 0x1802b6cc
+108 109 67760 0x1802b575
+109 110 68169 0x1802b4ec
+110 111 68459 0x1802b410
+111 112 69548 0x1802b56c
+112 113 69669 0x1802b415
+113 114 69893 0x1802b30b
+114 115 69933 0x1802b17c
+115 116 71307 0x1802b3a2
+116 117 71811 0x1802b35f
+117 118 71866 0x1802b1dc
+118 119 72099 0x1802b0d8
+119 120 72262 0x1802afa1
+120 121 72407 0x1802ae60
+121 122 72452 0x1802acdb
+122 123 72631 0x1802abb4
+123 124 73096 0x1802ab56
+124 125 73181 0x1802a9ee
+125 126 77005 0x1802b2cb
+126 127 77349 0x1802b215
+127 128 78766 0x1802b45b
+128 129 78889 0x1802b306
+129 130 79167 0x1802b222
+130 131 79454 0x1802b143
+131 132 79556 0x1802afe4
+132 133 80610 0x1802b126
+133 134 81949 0x1802b331
+134 135 82413 0x1802b2d0
+135 136 83361 0x1802b3c7
+136 137 83730 0x1802b326
+137 138 84038 0x1802b255
+138 139 85156 0x1802b3c5
+139 140 85409 0x1802b2ce
+140 141 86177 0x1802b346
+141 142 86755 0x1802b336
+142 143 87052 0x1802b25d
+143 144 87370 0x1802b196
+144 145 87389 0x1802aff9
+145 146 87442 0x1802ae7b
+146 147 87678 0x1802ad7b
+147 148 88050 0x1802acda
+148 149 88291 0x1802abdf
+149 150 88673 0x1802ab46
+150 151 88863 0x1802aa27
+151 152 88867 0x1802a887
+152 153 89154 0x1802a7ac
+153 154 89506 0x1802a700
+154 155 91610 0x1802ab18
+155 156 93523 0x1802aeb2
+156 157 93538 0x1802ad17
+157 158 93737 0x1802abfe
+158 159 94252 0x1802abc3
+159 160 94544 0x1802aaeb
+160 161 95195 0x1802ab10
+161 162 95394 0x1802a9f8
+162 163 95663 0x1802a910
+163 164 95922 0x1802a822
+164 165 96051 0x1802a6d9
+165 166 96105 0x1802a55e
+166 167 96501 0x1802a4d1
+167 168 97176 0x1802a505
+168 169 97543 0x1802a464
+169 170 97695 0x1802a32d
+170 171 99835 0x1802a75a
+171 172 100007 0x1802a630
+172 173 100819 0x1802a6c4
+173 174 101427 0x1802a6c9
+174 175 101665 0x1802a5ce
+175 176 103212 0x1802a861
+176 177 104441 0x1802aa17
+177 178 105132 0x1802aa57
+178 179 106722 0x1802ad0b
+179 180 106762 0x1802ab83
+180 181 107856 0x1802acde
+181 182 108452 0x1802acdb
+182 183 109334 0x1802ada1
+183 184 109367 0x1802ac14
+184 185 109654 0x1802ab38
+185 186 112245 0x1802b0b0
+186 187 112521 0x1802afc9
+187 188 115789 0x1802b735
+188 189 115930 0x1802b5ed
+189 190 116109 0x1802b4c1
+190 191 116489 0x1802b425
+191 192 117759 0x1802b603
+192 193 119446 0x1802b90d
+193 194 120049 0x1802b910
+194 195 120141 0x1802b7a3
+195 196 120436 0x1802b6c9
+196 197 122187 0x1802ba04
+197 198 122997 0x1802ba97
+198 199 123490 0x1802ba4c
+199 200 125018 0x1802bce8
+200 201 125604 0x1802bcde
+201 202 126289 0x1802bd1b
+202 203 126617 0x1802bc57
+203 204 127145 0x1802bc22
+204 205 127271 0x1802bacd
+205 206 127839 0x1802bab7
+206 207 127846 0x1802b90d
+207 208 128461 0x1802b918
+208 209 129263 0x1802b9a8
+209 210 129952 0x1802b9e9
+210 211 130129 0x1802b8ba
+211 212 130818 0x1802b8fa
+212 213 131516 0x1802b940
+213 214 133313 0x1802bc9d
+214 215 134069 0x1802bd0e
+215 216 134091 0x1802bb6e
+216 217 135378 0x1802bd5c
+217 218 135572 0x1802bc37
+218 219 135617 0x1802baa7
+219 220 136908 0x1802bc98
+220 221 138330 0x1802beeb
+221 222 138420 0x1802bd79
+222 223 138693 0x1802bc8d
+223 224 139047 0x1802bbdc
+224 225 141426 0x1802c0e4
+225 226 142736 0x1802c2e7
+226 227 143823 0x1802c449
+227 228 144028 0x1802c32a
+228 229 144076 0x1802c198
+229 230 144305 0x1802c08b
+230 231 144409 0x1802bf24
+231 232 144753 0x1802be6b
+232 233 145140 0x1802bdd2
+233 234 145256 0x1802bc72
+234 235 146289 0x1802bdac
+235 236 146670 0x1802bd0e
+236 237 146851 0x1802bbdf
+237 238 147778 0x1802bccb
+238 239 148460 0x1802bd08
+239 240 148765 0x1802bc32
+240 241 150937 0x1802c0a1
+241 242 152635 0x1802c3be
+242 243 152668 0x1802c223
+243 244 154605 0x1802c5f1
+244 245 154626 0x1802c44c
+245 246 156211 0x1802c718
+246 247 157840 0x1802ca0d
+247 248 158420 0x1802c9fd
+248 249 158546 0x1802c8a0
+249 250 158683 0x1802c74e
+250 251 159190 0x1802c70b
+251 252 159346 0x1802c5c6
+252 253 161669 0x1802cab3
+253 254 162729 0x1802cc06
+254 255 162805 0x1802ca86
+255 256 162908 0x1802c919
+256 257 162913 0x1802c764
+257 258 163796 0x1802c832
+258 259 163885 0x1802c6bd
+259 260 164782 0x1802c797
+260 261 165461 0x1802c7d2
+261 262 166072 0x1802c7d7
+262 263 166184 0x1802c672
+263 264 167970 0x1802c9d7
+264 265 168010 0x1802c840
+265 266 168100 0x1802c6c8
+266 267 168290 0x1802c59e
+267 268 168805 0x1802c55e
+268 269 168867 0x1802c3d6
+269 270 168956 0x1802c264
+270 271 169552 0x1802c261
+271 272 169886 0x1802c1a0
+272 273 171499 0x1802c47f
+273 274 171533 0x1802c2e4
+274 275 171626 0x1802c172
+275 276 171784 0x1802c033
+276 277 171789 0x1802be83
+277 278 172130 0x1802bdca
+278 279 172878 0x1802be35
+279 280 173620 0x1802be9b
+280 281 174625 0x1802bfbf
+281 282 175913 0x1802c1b3
+282 283 176123 0x1802c099
+283 284 176659 0x1802c068
+284 285 179230 0x1802c604
+285 286 179652 0x1802c581
+286 287 180535 0x1802c652
+287 288 181130 0x1802c64c
+288 289 181754 0x1802c65f
+289 290 182492 0x1802c6c3
+290 291 182943 0x1802c657
+291 292 184205 0x1802c83a
+292 293 185303 0x1802c9a7
+293 294 185558 0x1802c8ab
+294 295 185565 0x1802c6f8
+295 296 186150 0x1802c6ed
+296 297 187086 0x1802c7e4
+297 298 188965 0x1802cb8f
+298 299 189427 0x1802cb27
+299 300 189476 0x1802c992
+300 301 191306 0x1802cd1d
+301 302 191342 0x1802cb7d
+302 303 193074 0x1802cebf
+303 304 194118 0x1802d009
+304 305 195282 0x1802d1a9
+305 306 195586 0x1802d0cd
+306 307 196066 0x1802d075
+307 308 196253 0x1802cf43
+308 309 196875 0x1802cf55
+309 310 197005 0x1802cdf9
+310 311 198744 0x1802d143
+311 312 199668 0x1802d232
+312 313 200183 0x1802d1f4
+313 314 201905 0x1802d537
+314 315 202203 0x1802d455
+315 316 202217 0x1802d2a3
+316 317 203398 0x1802d453
+317 318 203442 0x1802d2b5
+318 319 203719 0x1802d1c7
+319 320 204031 0x1802d0ed
+320 321 204406 0x1802d04a
+321 322 204630 0x1802cf33
+322 323 205282 0x1802cf58
+323 324 205986 0x1802cfa6
+324 325 206367 0x1802cf05
+325 326 207491 0x1802d087
+326 327 208220 0x1802d0e8
+327 328 208462 0x1802cfde
+328 329 209152 0x1802d021
+329 330 209504 0x1802cf68
+330 331 209810 0x1802ce8f
+331 332 210374 0x1802ce74
+332 333 210480 0x1802cd0a
+333 334 210775 0x1802cc28
+334 335 210886 0x1802cac1
+335 336 211520 0x1802cad9
+336 337 212428 0x1802cbbd
+337 338 212804 0x1802cb17
+338 339 212881 0x1802c997
+339 340 213220 0x1802c8d6
+340 341 213649 0x1802c85a
+341 342 214589 0x1802c954
+342 343 214776 0x1802c825
+343 344 215486 0x1802c875
+344 345 215631 0x1802c728
+345 346 215936 0x1802c652
+346 347 216036 0x1802c4e2
+347 348 216088 0x1802c355
+348 349 216873 0x1802c3db
+349 350 218112 0x1802c5ae
+350 351 218515 0x1802c51d
+351 352 219568 0x1802c667
+352 353 219869 0x1802c58e
+353 354 220479 0x1802c596
+354 355 220868 0x1802c4fd
+355 356 221242 0x1802c457
+356 357 221357 0x1802c2f5
+357 358 221500 0x1802c1aa
+358 359 223428 0x1802c570
+359 360 224084 0x1802c599
+360 361 224126 0x1802c404
+361 362 224965 0x1802c4af
+362 363 225185 0x1802c39b
+363 364 225755 0x1802c385
+364 365 225964 0x1802c269
+365 366 227000 0x1802c3a6
+366 367 227111 0x1802c243
+367 368 227897 0x1802c2ca
+368 369 228316 0x1802c246
+369 370 228380 0x1802c0c1
+370 371 228576 0x1802bf9d
+371 372 228582 0x1802bdef
+372 373 228962 0x1802bd4e
+373 374 229363 0x1802bcc0
+374 375 229905 0x1802bc98
+375 376 230037 0x1802bb46
+376 377 230138 0x1802b9de
+377 378 231396 0x1802bbb9
+378 379 231623 0x1802baaa
+379 380 232554 0x1802bb9c
+380 381 232611 0x1802ba14
+381 382 233200 0x1802ba0c
+382 383 233719 0x1802b9d1
+383 384 234535 0x1802ba6c
+384 385 236433 0x1802be15
+385 386 236471 0x1802bc7d
+386 387 236693 0x1802bb6e
+387 388 239252 0x1802c0f4
+388 389 239765 0x1802c0b6
+389 390 240052 0x1802bfd5
+390 391 240278 0x1802bec6
+391 392 240756 0x1802be6d
+392 393 240885 0x1802bd19
+393 394 241254 0x1802bc72
+394 395 241913 0x1802bc9d
+395 396 242464 0x1802bc78
+396 397 243033 0x1802bc62
+397 398 243081 0x1802bad5
+398 399 243768 0x1802bb13
+399 400 243817 0x1802b988
+400 401 244075 0x1802b891
+401 402 244468 0x1802b7fe
+402 403 244512 0x1802b671
+403 404 245063 0x1802b64b
+404 405 245326 0x1802b55c
+405 406 245549 0x1802b450
+406 407 245780 0x1802b349
+407 408 246848 0x1802b496
+408 409 247721 0x1802b55a
+409 410 247779 0x1802b3d5
+410 411 248989 0x1802b58a
+411 412 249067 0x1802b415
+412 413 249862 0x1802b4a1
+413 414 249934 0x1802b326
+414 415 250197 0x1802b237
+415 416 251266 0x1802b384
+416 417 251496 0x1802b27d
+417 418 251502 0x1802b0d8
+418 419 252819 0x1802b2d6
+419 420 253248 0x1802b25d
+420 421 253637 0x1802b1c7
+421 422 254084 0x1802b159
+422 423 254230 0x1802b017
+423 424 254564 0x1802af5b
+424 425 255322 0x1802afcc
+425 426 257123 0x1802b31e
+426 427 257814 0x1802b361
+427 428 259029 0x1802b517
+428 429 259280 0x1802b420
+429 430 261956 0x1802b9ec
+430 431 262037 0x1802b877
+431 432 262598 0x1802b85c
+432 433 262640 0x1802b6cc
+433 434 262985 0x1802b616
+434 435 263302 0x1802b54c
+435 436 263576 0x1802b463
+436 437 265608 0x1802b861
+437 438 266294 0x1802b8a2
+438 439 266491 0x1802b780
+439 440 266737 0x1802b684
+440 441 267140 0x1802b5f5
+441 442 268115 0x1802b702
+442 443 268169 0x1802b57a
+443 444 268543 0x1802b4d9
+444 445 268693 0x1802b39a
+445 446 269434 0x1802b3fd
+446 447 270331 0x1802b4d1
+447 448 270936 0x1802b4d6
+448 449 271165 0x1802b3cd
+449 450 271296 0x1802b27d
+450 451 271654 0x1802b1d1
+451 452 272212 0x1802b1b4
+452 453 272468 0x1802b0c0
+453 454 272519 0x1802af3b
+454 455 273022 0x1802aef8
+455 456 273131 0x1802ada0
+456 457 273635 0x1802ad5c
+457 458 274006 0x1802acbb
+458 459 275468 0x1802af15
+459 460 275673 0x1802ae02
+460 461 275779 0x1802aca8
+461 462 275995 0x1802ab9a
+462 463 277324 0x1802ad9b
+463 464 277819 0x1802ad50
+464 465 278231 0x1802accc
+465 466 278774 0x1802aca4
+466 467 278979 0x1802ab90
+467 468 279447 0x1802ab33
+468 469 280730 0x1802ad11
+469 470 282233 0x1802af8b
+470 471 282874 0x1802afa9
+471 472 283519 0x1802afc9
+472 473 283875 0x1802af1a
+473 474 284002 0x1802add2
+474 475 284113 0x1802ac79
+475 476 284407 0x1802aba4
+476 477 285150 0x1802ac07
+477 478 285223 0x1802aa96
+478 479 285898 0x1802aacc
+479 480 286658 0x1802ab3c
+480 481 288074 0x1802ad77
+481 482 289756 0x1802b06f
+482 483 289845 0x1802af08
+483 484 289872 0x1802ad75
+484 485 291161 0x1802af58
+485 486 291360 0x1802ae3e
+486 487 291396 0x1802acb5
+487 488 293190 0x1802aff9
+488 489 293469 0x1802af18
+489 490 293870 0x1802ae8e
+490 491 294577 0x1802aed9
+491 492 295696 0x1802b047
+492 493 295883 0x1802af23
+493 494 297061 0x1802b0ba
+494 495 297178 0x1802af66
+495 496 297235 0x1802ade9
+496 497 298229 0x1802aefa
+497 498 299108 0x1802afc1
+498 499 299129 0x1802ae2a
+499 500 299790 0x1802ae55
+500 501 300620 0x1802aef5
+501 502 302957 0x1802b3c7
+502 503 302969 0x1802b225
+503 504 303470 0x1802b1dc
+504 505 304333 0x1802b298
+505 506 304549 0x1802b186
+506 507 304873 0x1802b0c5
+507 508 306711 0x1802b433
+508 509 307111 0x1802b3a4
+509 510 307259 0x1802b265
+510 511 308120 0x1802b31e
+511 512 308324 0x1802b204
+512 513 308744 0x1802b184
+513 514 309667 0x1802b26a
+514 515 310251 0x1802b25d
+515 516 310840 0x1802b258
+516 517 311159 0x1802b18e
+517 518 311209 0x1802b009
+518 519 312869 0x1802b2f9
+519 520 313088 0x1802b1ea
+520 521 313594 0x1802b1a7
+521 522 313847 0x1802b0b2
+522 523 314156 0x1802afe4
+523 524 317015 0x1802b62b
+524 525 318195 0x1802b7c8
+525 526 318450 0x1802b6d1
+526 527 319118 0x1802b702
+527 528 319131 0x1802b562
+528 529 319169 0x1802b3cf
+529 530 319969 0x1802b45d
+530 531 320490 0x1802b425
+531 532 320547 0x1802b2a3
+532 533 320795 0x1802b1a9
+533 534 320964 0x1802b075
+534 535 323147 0x1802b4dc
+535 536 323559 0x1802b455
+536 537 324733 0x1802b5f0
+537 538 324771 0x1802b45d
+538 539 325284 0x1802b420
+539 540 325507 0x1802b313
+540 541 326393 0x1802b3e2
+541 542 326935 0x1802b3b7
+542 543 328262 0x1802b5bd
+543 544 329269 0x1802b6e1
+544 545 329549 0x1802b5fb
+545 546 330098 0x1802b5d8
+546 547 331221 0x1802b74d
+547 548 331352 0x1802b5fd
+548 549 332256 0x1802b6d7
+549 550 332403 0x1802b592
+550 551 333860 0x1802b7f8
+551 552 338121 0x1802c249
+552 553 338373 0x1802c14d
+553 554 338589 0x1802c038
+554 555 340901 0x1802c513
+555 556 341396 0x1802c4c7
+556 557 341506 0x1802c360
+557 558 343767 0x1802c81d
+558 559 345582 0x1802cb9a
+559 560 346627 0x1802cce2
+560 561 346726 0x1802cb72
+561 562 347129 0x1802cade
+562 563 347323 0x1802c9b5
+563 564 347755 0x1802c939
+564 565 349011 0x1802cb1c
+565 566 349125 0x1802c9b7
+566 567 349616 0x1802c967
+567 568 352124 0x1802cee5
+568 569 352790 0x1802cf15
+569 570 353733 0x1802d011
+570 571 353889 0x1802ceca
+571 572 354836 0x1802cfcb
+572 573 355122 0x1802cee2
+573 574 355281 0x1802cd9d
+574 575 358636 0x1802d597
+575 576 358980 0x1802d4d9
+576 577 359817 0x1802d58a
+577 578 360240 0x1802d506
+578 579 360536 0x1802d422
+579 580 361017 0x1802d3ca
+580 581 361085 0x1802d242
+581 582 361301 0x1802d123
+582 583 361342 0x1802cf86
+583 584 362321 0x1802d09f
+584 585 363170 0x1802d156
+585 586 363245 0x1802cfd1
+586 587 363645 0x1802cf3d
+587 588 363941 0x1802ce5f
+588 589 363946 0x1802cca6
+589 590 365270 0x1802cebc
+590 591 365501 0x1802cdab
+591 592 366068 0x1802cd93
+592 593 366544 0x1802cd37
+593 594 366924 0x1802cc96
+594 595 368705 0x1802cffe
+595 596 368748 0x1802ce61
+596 597 368918 0x1802cd25
+597 598 369292 0x1802cc7e
+598 599 369315 0x1802cad6
+599 600 369787 0x1802ca76
+600 601 370035 0x1802c977
+601 602 370217 0x1802c842
+602 603 371353 0x1802c9cd
+603 604 371422 0x1802c845
+604 605 372018 0x1802c845
+605 606 372199 0x1802c710
+606 607 372218 0x1802c568
+607 608 372439 0x1802c454
+608 609 372514 0x1802c2d7
+609 610 373133 0x1802c2e4
+610 611 373323 0x1802c1bb
+611 612 374030 0x1802c208
+612 613 374127 0x1802c09b
+613 614 374378 0x1802bf9f
+614 615 374482 0x1802be38
+615 616 375874 0x1802c073
+616 617 376239 0x1802bfca
+617 618 376843 0x1802bfcd
+618 619 376992 0x1802be86
+619 620 378438 0x1802c0ec
+620 621 379308 0x1802c1b0
+621 622 379375 0x1802c02b
+622 623 379950 0x1802c01b
+623 624 380261 0x1802bf49
+624 625 380465 0x1802be2a
+625 626 380597 0x1802bcd8
+626 627 383464 0x1802c342
+627 628 383491 0x1802c1a2
+628 629 383788 0x1802c0c6
+629 630 384865 0x1802c221
+630 631 384907 0x1802c08b
+631 632 384970 0x1802bf06
+632 633 384987 0x1802bd64
+633 634 385254 0x1802bc72
+634 635 385907 0x1802bc98
+635 636 386117 0x1802bb81
+636 637 386299 0x1802ba52
+637 638 386731 0x1802b9d9
+638 639 386754 0x1802b83c
+639 640 387502 0x1802b8a7
+640 641 387599 0x1802b73d
+641 642 387922 0x1802b676
+642 643 388862 0x1802b76a
+643 644 389096 0x1802b663
+644 645 389179 0x1802b4f4
+645 646 390205 0x1802b623
+646 647 392842 0x1802bbd9
+647 648 393650 0x1802bc70
+648 649 395227 0x1802bf2f
+649 650 395437 0x1802be17
+650 651 396328 0x1802bee9
+651 652 396734 0x1802be5d
+652 653 397021 0x1802bd7c
+653 654 398290 0x1802bf5c
+654 655 398351 0x1802bdda
+655 656 398352 0x1802bc2a
+656 657 398608 0x1802bb30
+657 658 400692 0x1802bf5f
+658 659 401050 0x1802beb0
+659 660 401877 0x1802bf54
+660 661 402081 0x1802be35
+661 662 402895 0x1802bed1
+662 663 403141 0x1802bdd2
+663 664 403889 0x1802be3a
+664 665 405485 0x1802c10c
+665 666 406102 0x1802c11a
+666 667 406424 0x1802c050
+667 668 407129 0x1802c09b
+668 669 408261 0x1802c21e
+669 670 411009 0x1802c83d
+670 671 411058 0x1802c6aa
+671 672 411202 0x1802c55b
+672 673 411222 0x1802c3b6
+673 674 412528 0x1802c5b9
+674 675 413119 0x1802c5b4
+675 676 413150 0x1802c414
+676 677 413673 0x1802c3db
+677 678 414107 0x1802c363
+678 679 414707 0x1802c363
+679 680 416015 0x1802c566
+680 681 416297 0x1802c47f
+681 682 416872 0x1802c46c
+682 683 417179 0x1802c396
+683 684 417346 0x1802c25c
+684 685 417907 0x1802c241
+685 686 417967 0x1802c0b6
+686 687 418431 0x1802c056
+687 688 418720 0x1802bf74
+688 689 419267 0x1802bf4f
+689 690 419422 0x1802be0d
+690 691 419535 0x1802bcab
+691 692 419837 0x1802bbd4
+692 693 420828 0x1802bcee
+693 694 421425 0x1802bcee
+694 695 421770 0x1802bc37
+695 696 422491 0x1802bc8a
+696 697 422687 0x1802bb69
+697 698 423350 0x1802bb96
+698 699 423617 0x1802baa7
+699 700 425437 0x1802be17
+700 701 425498 0x1802bc92
+701 702 426799 0x1802be8b
+702 703 430429 0x1802c726
+703 704 430913 0x1802c6d3
+704 705 432168 0x1802c8b3
+705 706 433472 0x1802cab6
+706 707 433525 0x1802c924
+707 708 434589 0x1802ca78
+708 709 434648 0x1802c8eb
+709 710 434654 0x1802c739
+710 711 436085 0x1802c99a
+711 712 436419 0x1802c8d6
+712 713 436687 0x1802c7e4
+713 714 437526 0x1802c893
+714 715 437933 0x1802c805
+715 716 439897 0x1802cbf0
+716 717 441227 0x1802ce09
+717 718 441509 0x1802cd1f
+718 719 441573 0x1802cb95
+719 720 441768 0x1802ca68
+720 721 443123 0x1802cc94
+721 722 443312 0x1802cb67
+722 723 443356 0x1802c9cf
+723 724 445295 0x1802cda8
+724 725 446484 0x1802cf5b
+725 726 447388 0x1802d03c
+726 727 447650 0x1802cf40
+727 728 447685 0x1802cda0
+728 729 447685 0x1802cbe5
+729 730 447846 0x1802caa3
+730 731 447887 0x1802c909
+731 732 448873 0x1802ca23
+732 733 449601 0x1802ca83
+733 734 450072 0x1802ca23
+734 735 451346 0x1802cc13
+735 736 451515 0x1802cad6
+736 737 451545 0x1802c934
+737 738 451547 0x1802c77e
+738 739 451600 0x1802c5ec
+739 740 451710 0x1802c48a
+740 741 452201 0x1802c439
+741 742 452501 0x1802c35d
+742 743 454119 0x1802c644
+743 744 454121 0x1802c48f
+744 745 455736 0x1802c776
+745 746 455859 0x1802c619
+746 747 456066 0x1802c4fa
+747 748 456519 0x1802c48f
+748 749 456655 0x1802c33d
+749 750 456902 0x1802c23b
+750 751 457978 0x1802c396
+751 752 458088 0x1802c233
+752 753 458522 0x1802c1bb
+753 754 458649 0x1802c060
+754 755 458776 0x1802bf0c
+755 756 459470 0x1802bf4f
+756 757 459726 0x1802be55
+757 758 460982 0x1802c030
+758 759 462187 0x1802c1e8
+759 760 462493 0x1802c114
+760 761 463504 0x1802c23e
+761 762 463910 0x1802c1b0
+762 763 464257 0x1802c0f9
+763 764 464410 0x1802bfb5
+764 765 464528 0x1802be58
+765 766 464756 0x1802bd4c
+766 767 465137 0x1802bcad
+767 768 465417 0x1802bbc9
+768 769 465800 0x1802bb2b
+769 770 466521 0x1802bb81
+770 771 468519 0x1802bf72
+771 772 468840 0x1802beab
+772 773 468909 0x1802bd29
+773 774 469521 0x1802bd33
+774 775 469605 0x1802bbbe
+775 776 469736 0x1802ba6c
+776 777 469906 0x1802b938
+777 778 470457 0x1802b915
+778 779 470592 0x1802b7c8
+779 780 470786 0x1802b6a4
+780 781 470867 0x1802b531
+781 782 471904 0x1802b66b
+782 783 472233 0x1802b5a7
+783 784 472413 0x1802b47e
+784 785 473428 0x1802b5a5
+785 786 474802 0x1802b7ce
+786 787 475964 0x1802b963
+787 788 476987 0x1802ba92
+788 789 477048 0x1802b910
+789 790 477122 0x1802b795
+790 791 477238 0x1802b63b
+791 792 477281 0x1802b4ae
+792 793 477855 0x1802b49b
+793 794 478989 0x1802b618
+794 795 480590 0x1802b8e5
+795 796 480594 0x1802b73a
+796 797 480743 0x1802b5f8
+797 798 481364 0x1802b605
+798 799 481456 0x1802b49b
+799 800 481721 0x1802b3ac
+800 801 482486 0x1802b422
+801 802 482910 0x1802b3a4
+802 803 482941 0x1802b20f
+803 804 482966 0x1802b077
+804 805 483761 0x1802b103
+805 806 484289 0x1802b0d0
+806 807 484520 0x1802afc9
+807 808 484641 0x1802ae78
+808 809 485590 0x1802af6e
+809 810 485727 0x1802ae29
+810 811 486746 0x1802af4d
+811 812 487753 0x1802b06f
+812 813 488318 0x1802b054
+813 814 488451 0x1802af0a
+814 815 488545 0x1802adaa
+815 816 488830 0x1802accb
+816 817 489408 0x1802acbc
+817 818 489635 0x1802abb8
+818 819 489659 0x1802aa24
+819 820 490329 0x1802aa56
+820 821 492017 0x1802ad4e
+821 822 492262 0x1802ac56
+822 823 492747 0x1802ac06
+823 824 492753 0x1802aa66
+824 825 493678 0x1802ab48
+825 826 493893 0x1802aa3c
+826 827 494049 0x1802a906
+827 828 494357 0x1802a83a
+828 829 494704 0x1802a789
+829 830 494910 0x1802a679
+830 831 495801 0x1802a742
+831 832 496075 0x1802a65f
+832 833 497310 0x1802a819
+833 834 498058 0x1802a880
+834 835 500404 0x1802ad45
+835 836 500831 0x1802accc
+836 837 501001 0x1802aba0
+837 838 502507 0x1802ae1a
+838 839 502709 0x1802ad02
+839 840 503289 0x1802acf6
+840 841 503410 0x1802aba5
+841 842 503585 0x1802aa7d
+842 843 503977 0x1802a9ea
+843 844 505389 0x1802ac23
+844 845 505926 0x1802abf7
+845 846 506588 0x1802ac23
+846 847 506933 0x1802ab6f
+847 848 507014 0x1802aa04
+848 849 507175 0x1802a8d3
+849 850 507429 0x1802a7e0
+850 851 507459 0x1802a653
+851 852 507602 0x1802a517
+852 853 507816 0x1802a40c
+853 854 508462 0x1802a42b
+854 855 508502 0x1802a2a8
+855 856 509225 0x1802a2fd
+856 857 509685 0x1802a29c
+857 858 510040 0x1802a1f2
+858 859 510381 0x1802a13f
+859 860 510483 0x18029fe8
+860 861 510760 0x18029f0a
+861 862 510856 0x18029daf
+862 863 513127 0x1802a22e
+863 864 513536 0x1802a1ab
+864 865 513564 0x1802a020
+865 866 513968 0x18029f9a
+866 867 514016 0x18029e1d
+867 868 514416 0x18029d94
+868 869 514964 0x18029d71
+869 870 515313 0x18029cc4
+870 871 516450 0x18029e35
+871 872 516713 0x18029d4d
+872 873 516741 0x18029bc5
+873 874 516934 0x18029ab0
+874 875 517227 0x180299de
+875 876 517336 0x1802988e
+876 877 517910 0x1802987c
+877 878 517947 0x180296fe
+878 879 518259 0x1802963a
+879 880 518613 0x18029592
+880 881 518711 0x1802943f
+881 882 518715 0x180292ac
+882 883 519175 0x1802924d
+883 884 519234 0x180290e0
+884 885 520514 0x180292ac
+885 886 520866 0x18029205
+886 887 522826 0x1802959c
+887 888 523988 0x18029719
+888 889 524155 0x180295f3
+889 890 524618 0x18029596
+890 891 525169 0x18029576
+891 892 527140 0x1802991a
+892 893 527335 0x18029806
+893 894 527959 0x18029816
+894 895 528204 0x18029725
+895 896 528856 0x18029748
+896 897 529145 0x18029674
+897 898 529809 0x180296a1
+898 899 529832 0x18029518
+899 900 529900 0x180293b0
+900 901 530252 0x18029309
+901 902 530363 0x180291bd
+902 903 530792 0x1802914b
+903 904 531419 0x1802915d
+904 905 531553 0x18029023
+905 906 531867 0x18028f63
+906 907 532051 0x18028e4b
+907 908 533275 0x18028fef
+908 909 533769 0x18028fa6
+909 910 534468 0x18028feb
+910 911 534494 0x18028e68
+911 912 535858 0x1802906a
+912 913 536485 0x1802907b
+913 914 537936 0x180292ba
+914 915 538082 0x18029188
+915 916 538508 0x18029113
+916 917 538754 0x18029023
+917 918 539975 0x180291c5
+918 919 540307 0x18029110
+919 920 540541 0x1802901c
+920 921 541484 0x18029103
+921 922 541637 0x18028fd5
+922 923 541982 0x18028f28
+923 924 542150 0x18028e08
+924 925 542551 0x18028d81
+925 926 545123 0x180292b2
+926 927 545167 0x1802913a
+927 928 545556 0x180290ac
+928 929 545588 0x18028f2d
+929 930 545634 0x18028dba
+930 931 545831 0x18028cac
+931 932 546487 0x18028cd2
+932 933 546691 0x18028bc8
+933 934 546703 0x18028a40
+934 935 547152 0x180289dc
+935 936 547189 0x18028864
+936 937 547424 0x18028771
+937 938 547627 0x18028669
+938 939 547649 0x180284ea
+939 940 547720 0x1802838c
+940 941 551263 0x18028b2f
+941 942 551730 0x18028ad7
+942 943 551767 0x1802895f
+943 944 551984 0x18028860
+944 945 552234 0x18028777
+945 946 553696 0x180289b5
+946 947 553905 0x180288b0
+947 948 554812 0x1802897c
+948 949 554989 0x18028864
+949 950 555619 0x18028877
+950 951 556119 0x18028834
+951 952 557125 0x18028943
+952 953 557238 0x180287ff
+953 954 557459 0x18028703
+954 955 557592 0x180285cd
+955 956 558854 0x18028785
+956 957 559950 0x180288ce
+957 958 560504 0x180288b0
+958 959 561621 0x18028a09
+959 960 563620 0x18028daf
+960 961 563879 0x18028ccc
+961 962 563942 0x18028b63
+962 963 564154 0x18028a60
+963 964 565325 0x18028bdf
+964 965 565451 0x18028aa2
+965 966 565566 0x1802895f
+966 967 565635 0x180287fe
+967 968 566460 0x18028893
+968 969 567692 0x18028a38
+969 970 568536 0x18028adb
+970 971 568724 0x180289c8
+971 972 569029 0x18028904
+972 973 569348 0x18028848
+973 974 569672 0x18028791
+974 975 571368 0x18028a6a
+975 976 571742 0x180289d5
+976 977 572100 0x18028933
+977 978 572426 0x1802887c
+978 979 572436 0x180286f3
+979 980 573333 0x180287ba
+980 981 573363 0x1802863f
+981 982 573856 0x180285f8
+982 983 576984 0x18028c8c
+983 984 577936 0x18028d77
+984 985 578576 0x18028d93
+985 986 580110 0x18029007
+986 987 581045 0x180290e8
+987 988 582554 0x1802934d
+988 989 583197 0x1802936b
+989 990 583323 0x1802922a
+990 991 583647 0x1802916e
+991 992 583905 0x1802908a
+992 993 586689 0x1802964e
+993 994 586926 0x18029557
+994 995 587291 0x180294b7
+995 996 587688 0x1802942f
+996 997 587802 0x180292e6
+997 998 588403 0x180292e6
+998 999 588544 0x180291b0
+999 1000 589215 0x180291e0
+1000 1001 589738 0x180291ad
+
diff --git a/data/test_vectors_aserti3-2d_run07.txt b/data/test_vectors_aserti3-2d_run07.txt
new file mode 100644
--- /dev/null
+++ b/data/test_vectors_aserti3-2d_run07.txt
@@ -0,0 +1,1009 @@
+## description: run7 - deterministically random solvetimes for up-ramping hashrate around a recent real life nBits
+##   anchor height: 1
+##   anchor parent time: 0
+##   anchor nBits: 0x1802aee8
+##   start height: 2
+##   start time: 1200
+##   iterations: 1000
+# iteration,height,time,target
+1 2 1200 0x1802aee8
+2 3 1310 0x1802ad91
+3 4 1327 0x1802abf8
+4 5 1739 0x1802ab73
+5 6 2219 0x1802ab20
+6 7 2604 0x1802aa89
+7 8 2746 0x1802a94b
+8 9 7099 0x1802b39c
+9 10 7099 0x1802b1f2
+10 11 7657 0x1802b1d4
+11 12 8141 0x1802b181
+12 13 8236 0x1802b01c
+13 14 8644 0x1802af93
+14 15 9230 0x1802af8b
+15 16 9691 0x1802af28
+16 17 9708 0x1802ad8f
+17 18 10327 0x1802ad9c
+18 19 10899 0x1802ad88
+19 20 11478 0x1802ad7b
+20 21 11548 0x1802ac07
+21 22 12701 0x1802ad89
+22 23 12849 0x1802ac4d
+23 24 13186 0x1802ab95
+24 25 13319 0x1802aa4e
+25 26 13494 0x1802a925
+26 27 13540 0x1802a7a4
+27 28 13726 0x1802a684
+28 29 13764 0x1802a4fd
+29 30 13787 0x1802a36d
+30 31 14159 0x1802a2cf
+31 32 14203 0x1802a14e
+32 33 14474 0x1802a06d
+33 34 14582 0x18029f19
+34 35 14741 0x18029dea
+35 36 14971 0x18029ceb
+36 37 15181 0x18029be0
+37 38 15402 0x18029adf
+38 39 15886 0x18029a8e
+39 40 16041 0x1802995f
+40 41 16219 0x1802983e
+41 42 16407 0x18029727
+42 43 16513 0x180295d7
+43 44 16633 0x18029491
+44 45 16698 0x18029326
+45 46 16755 0x180291b8
+46 47 16782 0x18029036
+47 48 16795 0x18028eab
+48 49 16869 0x18028d4a
+49 50 16870 0x18028bbb
+50 51 16876 0x18028a2d
+51 52 16888 0x180288a6
+52 53 16958 0x18028746
+53 54 17306 0x1802869d
+54 55 17410 0x18028556
+55 56 17474 0x180283f4
+56 57 17580 0x180282ac
+57 58 17640 0x18028147
+58 59 17652 0x18027fc5
+59 60 17694 0x18027e58
+60 61 17772 0x18027d02
+61 62 17817 0x18027b98
+62 63 17923 0x18027a56
+63 64 17965 0x180278ea
+64 65 17982 0x18027770
+65 66 18116 0x18027642
+66 67 18236 0x1802750c
+67 68 18236 0x1802738a
+68 69 18237 0x18027209
+69 70 18303 0x180270b0
+70 71 18332 0x18026f43
+71 72 18404 0x18026df1
+72 73 18404 0x18026c72
+73 74 18504 0x18026b35
+74 75 18587 0x180269ec
+75 76 18618 0x18026883
+76 77 18641 0x18026715
+77 78 18649 0x180265a1
+78 79 18650 0x18026428
+79 80 18658 0x180262b3
+80 81 18824 0x180261a4
+81 82 18830 0x1802602f
+82 83 18861 0x18025ecd
+83 84 18865 0x18025d5b
+84 85 18927 0x18025c0d
+85 86 18987 0x18025abd
+86 87 19001 0x18025952
+87 88 19059 0x18025804
+88 89 19308 0x1802572c
+89 90 19316 0x180255bf
+90 91 19333 0x1802545a
+91 92 19378 0x18025306
+92 93 19559 0x18025206
+93 94 19658 0x180250d6
+94 95 19663 0x18024f6a
+95 96 19746 0x18024e30
+96 97 19917 0x18024d2c
+97 98 20213 0x18024c74
+98 99 20250 0x18024b1f
+99 100 20363 0x180249fb
+100 101 20372 0x18024899
+101 102 20488 0x18024775
+102 103 20529 0x18024626
+103 104 20555 0x180244cf
+104 105 20606 0x18024387
+105 106 20643 0x18024239
+106 107 20749 0x18024113
+107 108 20772 0x18023fbd
+108 109 20782 0x18023e62
+109 110 20819 0x18023d15
+110 111 20845 0x18023bc2
+111 112 20935 0x18023a98
+112 113 20945 0x1802393e
+113 114 20963 0x180237ea
+114 115 20966 0x1802368d
+115 116 21080 0x18023574
+116 117 21122 0x1802342f
+117 118 21126 0x180232d5
+118 119 21145 0x18023187
+119 120 21158 0x18023033
+120 121 21170 0x18022ee1
+121 122 21173 0x18022d8b
+122 123 21186 0x18022c3a
+123 124 21221 0x18022af8
+124 125 21227 0x180229a6
+125 126 21521 0x180228f8
+126 127 21547 0x180227b2
+127 128 21656 0x1802269c
+128 129 21665 0x1802254d
+129 130 21686 0x18022407
+130 131 21708 0x180222c2
+131 132 21715 0x18022174
+132 133 21790 0x1802204e
+133 134 21885 0x18021f33
+134 135 21918 0x18021df8
+135 136 21985 0x18021ccf
+136 137 22011 0x18021b90
+137 138 22033 0x18021a4f
+138 139 22112 0x1802192f
+139 140 22130 0x180217f0
+140 141 22184 0x180216c3
+141 142 22222 0x1802158f
+142 143 22241 0x1802144f
+143 144 22262 0x18021314
+144 145 22263 0x180211cd
+145 146 22266 0x18021088
+146 147 22281 0x18020f4a
+147 148 22305 0x18020e13
+148 149 22321 0x18020cd7
+149 150 22346 0x18020ba2
+150 151 22358 0x18020a65
+151 152 22358 0x18020924
+152 153 22375 0x180207ec
+153 154 22397 0x180206b7
+154 155 22528 0x180205be
+155 156 22647 0x180204bd
+156 157 22647 0x1802037f
+157 158 22659 0x18020248
+158 159 22691 0x1802011d
+159 160 22709 0x1801ffea
+160 161 22749 0x1801fec4
+161 162 22760 0x1801fd8e
+162 163 22775 0x1801fc5d
+163 164 22790 0x1801fb2c
+164 165 22797 0x1801f9f7
+165 166 22800 0x1801f8c0
+166 167 22823 0x1801f797
+167 168 22862 0x1801f674
+168 169 22883 0x1801f549
+169 170 22891 0x1801f41a
+170 171 23016 0x1801f326
+171 172 23025 0x1801f1f6
+172 173 23070 0x1801f0db
+173 174 23103 0x1801efb9
+174 175 23116 0x1801ee8e
+175 176 23201 0x1801ed8a
+176 177 23269 0x1801ec7c
+177 178 23307 0x1801eb60
+178 179 23395 0x1801ea5e
+179 180 23397 0x1801e930
+180 181 23457 0x1801e821
+181 182 23488 0x1801e705
+182 183 23534 0x1801e5ef
+183 184 23535 0x1801e4c5
+184 185 23550 0x1801e3a2
+185 186 23686 0x1801e2bc
+186 187 23700 0x1801e19a
+187 188 23872 0x1801e0c7
+188 189 23879 0x1801dfa3
+189 190 23888 0x1801de80
+190 191 23908 0x1801dd63
+191 192 23971 0x1801dc5c
+192 193 24055 0x1801db5f
+193 194 24085 0x1801da49
+194 195 24089 0x1801d927
+195 196 24103 0x1801d80c
+196 197 24190 0x1801d712
+197 198 24230 0x1801d605
+198 199 24254 0x1801d4ee
+199 200 24330 0x1801d3f3
+200 201 24359 0x1801d2e0
+201 202 24391 0x1801d1d1
+202 203 24406 0x1801d0ba
+203 204 24431 0x1801cfa7
+204 205 24437 0x1801ce8d
+205 206 24464 0x1801cd7d
+206 207 24464 0x1801cc62
+207 208 24493 0x1801cb54
+208 209 24531 0x1801ca4a
+209 210 24563 0x1801c941
+210 211 24571 0x1801c82b
+211 212 24602 0x1801c720
+212 213 24633 0x1801c616
+213 214 24714 0x1801c526
+214 215 24748 0x1801c41e
+215 216 24749 0x1801c309
+216 217 24807 0x1801c20d
+217 218 24815 0x1801c0fd
+218 219 24817 0x1801bfea
+219 220 24875 0x1801bef0
+220 221 24939 0x1801bdfb
+221 222 24942 0x1801bcea
+222 223 24953 0x1801bbdd
+223 224 24968 0x1801bad3
+224 225 25071 0x1801b9f2
+225 226 25127 0x1801b8fc
+226 227 25174 0x1801b801
+227 228 25182 0x1801b6f6
+228 229 25184 0x1801b5ea
+229 230 25193 0x1801b4e0
+230 231 25197 0x1801b3d5
+231 232 25211 0x1801b2d0
+232 233 25227 0x1801b1cc
+233 234 25231 0x1801b0c3
+234 235 25274 0x1801afcb
+235 236 25289 0x1801aec8
+236 237 25296 0x1801adc4
+237 238 25334 0x1801accc
+238 239 25362 0x1801abcf
+239 240 25374 0x1801aace
+240 241 25464 0x1801a9ef
+241 242 25531 0x1801a907
+242 243 25532 0x1801a803
+243 244 25609 0x1801a71f
+244 245 25609 0x1801a61c
+245 246 25672 0x1801a532
+246 247 25737 0x1801a44c
+247 248 25760 0x1801a353
+248 249 25765 0x1801a254
+249 250 25770 0x1801a154
+250 251 25790 0x1801a05c
+251 252 25796 0x18019f60
+252 253 25885 0x18019e86
+253 254 25925 0x18019d99
+254 255 25927 0x18019c9b
+255 256 25930 0x18019b9e
+256 257 25930 0x18019aa2
+257 258 25963 0x180199b3
+258 259 25966 0x180198b8
+259 260 26000 0x180197cc
+260 261 26026 0x180196dc
+261 262 26048 0x180195ea
+262 263 26052 0x180194f3
+263 264 26118 0x18019415
+264 265 26119 0x1801931e
+265 266 26122 0x18019227
+266 267 26129 0x18019133
+267 268 26148 0x18019043
+268 269 26150 0x18018f4e
+269 270 26153 0x18018e5a
+270 271 26175 0x18018d6e
+271 272 26186 0x18018c7f
+272 273 26243 0x18018ba2
+273 274 26244 0x18018aaf
+274 275 26247 0x180189be
+275 276 26252 0x180188cd
+276 277 26252 0x180187dc
+277 278 26264 0x180186f0
+278 279 26290 0x1801860a
+279 280 26316 0x18018525
+280 281 26351 0x18018444
+281 282 26395 0x18018366
+282 283 26402 0x1801827a
+283 284 26420 0x18018195
+284 285 26508 0x180180ca
+285 286 26522 0x18017fe2
+286 287 26552 0x18017f02
+287 288 26572 0x18017e1f
+288 289 26593 0x18017d3d
+289 290 26618 0x18017c5c
+290 291 26633 0x18017b77
+291 292 26675 0x18017a9e
+292 293 26711 0x180179c3
+293 294 26719 0x180178df
+294 295 26719 0x180177f6
+295 296 26738 0x18017717
+296 297 26769 0x1801763c
+297 298 26831 0x1801756e
+298 299 26846 0x1801748d
+299 300 26847 0x180173a8
+300 301 26908 0x180172db
+301 302 26909 0x180171f7
+302 303 26964 0x18017128
+303 304 26997 0x18017050
+304 305 27034 0x18016f7c
+305 306 27043 0x18016e9d
+306 307 27058 0x18016dc1
+307 308 27064 0x18016ce3
+308 309 27084 0x18016c09
+309 310 27088 0x18016b2b
+310 311 27144 0x18016a60
+311 312 27172 0x1801698b
+312 313 27188 0x180168b3
+313 314 27241 0x180167e8
+314 315 27250 0x1801670d
+315 316 27250 0x18016630
+316 317 27286 0x18016562
+317 318 27287 0x18016485
+318 319 27295 0x180163ac
+319 320 27304 0x180162d4
+320 321 27315 0x180161fd
+321 322 27321 0x18016125
+322 323 27340 0x18016053
+323 324 27361 0x18015f80
+324 325 27372 0x18015eac
+325 326 27406 0x18015de0
+326 327 27428 0x18015d10
+327 328 27435 0x18015c3b
+328 329 27455 0x18015b6b
+329 330 27465 0x18015a9a
+330 331 27474 0x180159c6
+331 332 27490 0x180158f7
+332 333 27493 0x18015822
+333 334 27501 0x18015751
+334 335 27504 0x18015680
+335 336 27522 0x180155b4
+336 337 27548 0x180154eb
+337 338 27559 0x1801541e
+338 339 27561 0x1801534e
+339 340 27570 0x18015280
+340 341 27582 0x180151b4
+341 342 27608 0x180150ee
+342 343 27613 0x18015021
+343 344 27633 0x18014f59
+344 345 27637 0x18014e8c
+345 346 27645 0x18014dc2
+346 347 27647 0x18014cf5
+347 348 27648 0x18014c29
+348 349 27670 0x18014b64
+349 350 27705 0x18014aa4
+350 351 27716 0x180149dd
+351 352 27745 0x1801491c
+352 353 27753 0x18014854
+353 354 27769 0x18014790
+354 355 27779 0x180146cb
+355 356 27789 0x18014605
+356 357 27792 0x1801453d
+357 358 27795 0x18014476
+358 359 27848 0x180143c0
+359 360 27866 0x180142ff
+360 361 27867 0x18014239
+361 362 27889 0x1801417a
+362 363 27894 0x180140b6
+363 364 27909 0x18013ff6
+364 365 27914 0x18013f32
+365 366 27942 0x18013e77
+366 367 27945 0x18013db4
+367 368 27966 0x18013cf7
+368 369 27977 0x18013c38
+369 370 27978 0x18013b76
+370 371 27983 0x18013ab5
+371 372 27983 0x180139f4
+372 373 27993 0x18013936
+373 374 28003 0x18013878
+374 375 28017 0x180137bd
+375 376 28020 0x180136fd
+376 377 28022 0x1801363f
+377 378 28055 0x1801358b
+378 379 28060 0x180134cd
+379 380 28084 0x18013418
+380 381 28085 0x18013359
+381 382 28100 0x180132a1
+382 383 28113 0x180131e9
+383 384 28133 0x18013132
+384 385 28181 0x18013086
+385 386 28181 0x18012fca
+386 387 28186 0x18012f10
+387 388 28251 0x18012e6a
+388 389 28264 0x18012db4
+389 390 28271 0x18012cfc
+390 391 28276 0x18012c45
+391 392 28287 0x18012b8f
+392 393 28290 0x18012ad7
+393 394 28299 0x18012a22
+394 395 28315 0x1801296f
+395 396 28328 0x180128bc
+396 397 28342 0x1801280a
+397 398 28343 0x18012754
+398 399 28360 0x180126a3
+399 400 28361 0x180125ed
+400 401 28367 0x1801253b
+401 402 28376 0x18012489
+402 403 28377 0x180123d5
+403 404 28390 0x18012325
+404 405 28396 0x18012274
+405 406 28401 0x180121c2
+406 407 28406 0x18012112
+407 408 28432 0x18012067
+408 409 28453 0x18011fbb
+409 410 28454 0x18011f0a
+410 411 28483 0x18011e63
+411 412 28484 0x18011db2
+412 413 28502 0x18011d07
+413 414 28503 0x18011c58
+414 415 28509 0x18011baa
+415 416 28534 0x18011b03
+416 417 28539 0x18011a56
+417 418 28539 0x180119a9
+418 419 28570 0x18011904
+419 420 28580 0x18011859
+420 421 28589 0x180117af
+421 422 28599 0x18011706
+422 423 28602 0x1801165b
+423 424 28609 0x180115b1
+424 425 28626 0x1801150b
+425 426 28667 0x1801146c
+426 427 28683 0x180113c7
+427 428 28711 0x18011325
+428 429 28716 0x1801127d
+429 430 28778 0x180111e5
+430 431 28779 0x1801113d
+431 432 28791 0x18011098
+432 433 28791 0x18010fef
+433 434 28798 0x18010f49
+434 435 28805 0x18010ea4
+435 436 28811 0x18010dff
+436 437 28857 0x18010d66
+437 438 28872 0x18010cc4
+438 439 28876 0x18010c1f
+439 440 28881 0x18010b7b
+440 441 28890 0x18010ad9
+441 442 28911 0x18010a3b
+442 443 28912 0x18010997
+443 444 28920 0x180108f5
+444 445 28923 0x18010854
+445 446 28939 0x180107b5
+446 447 28958 0x18010718
+447 448 28971 0x18010679
+448 449 28976 0x180105d8
+449 450 28978 0x18010538
+450 451 28985 0x18010499
+451 452 28997 0x180103fc
+452 453 29002 0x1801035d
+453 454 29003 0x180102bd
+454 455 29013 0x18010221
+455 456 29015 0x18010182
+456 457 29025 0x180100e6
+457 458 29033 0x1801004a
+458 459 29064 0x1800ffb4
+459 460 29068 0x1800ff18
+460 461 29070 0x1800fe7b
+461 462 29074 0x1800fddf
+462 463 29102 0x1800fd4a
+463 464 29112 0x1800fcb1
+464 465 29120 0x1800fc17
+465 466 29131 0x1800fb80
+466 467 29135 0x1800fae5
+467 468 29144 0x1800fa4e
+468 469 29171 0x1800f9ba
+469 470 29202 0x1800f929
+470 471 29215 0x1800f892
+471 472 29228 0x1800f7fc
+472 473 29235 0x1800f765
+473 474 29237 0x1800f6ce
+474 475 29239 0x1800f637
+475 476 29245 0x1800f5a0
+476 477 29260 0x1800f50d
+477 478 29261 0x1800f476
+478 479 29275 0x1800f3e3
+479 480 29290 0x1800f351
+480 481 29319 0x1800f2c2
+481 482 29353 0x1800f235
+482 483 29354 0x1800f1a1
+483 484 29354 0x1800f10c
+484 485 29380 0x1800f07e
+485 486 29384 0x1800efeb
+486 487 29384 0x1800ef57
+487 488 29420 0x1800eecc
+488 489 29425 0x1800ee3b
+489 490 29433 0x1800edaa
+490 491 29447 0x1800ed1b
+491 492 29469 0x1800ec8f
+492 493 29472 0x1800ebfe
+493 494 29495 0x1800eb72
+494 495 29497 0x1800eae2
+495 496 29498 0x1800ea51
+496 497 29517 0x1800e9c6
+497 498 29534 0x1800e93a
+498 499 29534 0x1800e8aa
+499 500 29547 0x1800e81f
+500 501 29563 0x1800e793
+501 502 29608 0x1800e710
+502 503 29608 0x1800e682
+503 504 29617 0x1800e5f6
+504 505 29633 0x1800e56d
+505 506 29637 0x1800e4e0
+506 507 29643 0x1800e454
+507 508 29679 0x1800e3d1
+508 509 29686 0x1800e346
+509 510 29688 0x1800e2bb
+510 511 29704 0x1800e233
+511 512 29707 0x1800e1a9
+512 513 29715 0x1800e120
+513 514 29732 0x1800e099
+514 515 29743 0x1800e012
+515 516 29754 0x1800df8a
+516 517 29760 0x1800df02
+517 518 29760 0x1800de79
+518 519 29791 0x1800ddf8
+519 520 29795 0x1800dd70
+520 521 29804 0x1800dce9
+521 522 29808 0x1800dc62
+522 523 29813 0x1800dbdc
+523 524 29866 0x1800db61
+524 525 29888 0x1800dadf
+525 526 29892 0x1800da59
+526 527 29904 0x1800d9d6
+527 528 29904 0x1800d950
+528 529 29904 0x1800d8ca
+529 530 29919 0x1800d849
+530 531 29928 0x1800d7c5
+531 532 29929 0x1800d741
+532 533 29933 0x1800d6bd
+533 534 29936 0x1800d63a
+534 535 29976 0x1800d5bf
+535 536 29983 0x1800d53d
+536 537 30004 0x1800d4be
+537 538 30004 0x1800d43c
+538 539 30013 0x1800d3bb
+539 540 30017 0x1800d33b
+540 541 30033 0x1800d2bc
+541 542 30042 0x1800d23c
+542 543 30066 0x1800d1c0
+543 544 30084 0x1800d143
+544 545 30089 0x1800d0c3
+545 546 30098 0x1800d045
+546 547 30118 0x1800cfc9
+547 548 30120 0x1800cf4a
+548 549 30136 0x1800cece
+549 550 30138 0x1800ce4f
+550 551 30164 0x1800cdd6
+551 552 30240 0x1800cd68
+552 553 30244 0x1800cceb
+553 554 30247 0x1800cc6c
+554 555 30288 0x1800cbf8
+555 556 30296 0x1800cb7c
+556 557 30297 0x1800caff
+557 558 30337 0x1800ca8b
+558 559 30369 0x1800ca15
+559 560 30387 0x1800c99d
+560 561 30388 0x1800c921
+561 562 30395 0x1800c8a7
+562 563 30398 0x1800c82c
+563 564 30405 0x1800c7b2
+564 565 30427 0x1800c73c
+565 566 30429 0x1800c6c2
+566 567 30437 0x1800c649
+567 568 30481 0x1800c5d8
+568 569 30492 0x1800c561
+569 570 30508 0x1800c4eb
+570 571 30510 0x1800c472
+571 572 30526 0x1800c3fd
+572 573 30530 0x1800c385
+573 574 30532 0x1800c30d
+574 575 30589 0x1800c2a0
+575 576 30594 0x1800c22a
+576 577 30608 0x1800c1b6
+577 578 30615 0x1800c140
+578 579 30620 0x1800c0ca
+579 580 30628 0x1800c055
+580 581 30629 0x1800bfdf
+581 582 30632 0x1800bf6a
+582 583 30632 0x1800bef4
+583 584 30648 0x1800be82
+584 585 30662 0x1800be0f
+585 586 30663 0x1800bd9a
+586 587 30669 0x1800bd27
+587 588 30674 0x1800bcb4
+588 589 30674 0x1800bc40
+589 590 30696 0x1800bbd0
+590 591 30699 0x1800bb5d
+591 592 30708 0x1800baec
+592 593 30715 0x1800ba79
+593 594 30721 0x1800ba08
+594 595 30750 0x1800b99b
+595 596 30750 0x1800b929
+596 597 30752 0x1800b8b7
+597 598 30758 0x1800b847
+598 599 30758 0x1800b7d5
+599 600 30765 0x1800b765
+600 601 30769 0x1800b6f5
+601 602 30771 0x1800b685
+602 603 30789 0x1800b619
+603 604 30790 0x1800b5a8
+604 605 30799 0x1800b53a
+605 606 30801 0x1800b4ca
+606 607 30801 0x1800b45c
+607 608 30804 0x1800b3ec
+608 609 30805 0x1800b37e
+609 610 30815 0x1800b311
+610 611 30818 0x1800b2a4
+611 612 30829 0x1800b238
+612 613 30830 0x1800b1ca
+613 614 30834 0x1800b15d
+614 615 30835 0x1800b0f0
+615 616 30857 0x1800b087
+616 617 30862 0x1800b01b
+617 618 30871 0x1800afb0
+618 619 30873 0x1800af44
+619 620 30896 0x1800aedc
+620 621 30910 0x1800ae72
+621 622 30911 0x1800ae07
+622 623 30920 0x1800ad9d
+623 624 30924 0x1800ad33
+624 625 30927 0x1800acc9
+625 626 30929 0x1800ac5f
+626 627 30974 0x1800abfc
+627 628 30974 0x1800ab92
+628 629 30978 0x1800ab2a
+629 630 30995 0x1800aac4
+630 631 30995 0x1800aa5a
+631 632 30995 0x1800a9f2
+632 633 30995 0x1800a98a
+633 634 30999 0x1800a922
+634 635 31009 0x1800a8bc
+635 636 31012 0x1800a855
+636 637 31014 0x1800a7ee
+637 638 31020 0x1800a788
+638 639 31020 0x1800a721
+639 640 31031 0x1800a6bc
+640 641 31032 0x1800a656
+641 642 31036 0x1800a5f0
+642 643 31050 0x1800a58c
+643 644 31053 0x1800a527
+644 645 31054 0x1800a4c2
+645 646 31069 0x1800a45f
+646 647 31109 0x1800a401
+647 648 31121 0x1800a39e
+648 649 31145 0x1800a33d
+649 650 31148 0x1800a2da
+650 651 31161 0x1800a278
+651 652 31167 0x1800a215
+652 653 31171 0x1800a1b2
+653 654 31190 0x1800a151
+654 655 31190 0x1800a0ee
+655 656 31190 0x1800a08b
+656 657 31193 0x1800a029
+657 658 31224 0x18009fcc
+658 659 31229 0x18009f6a
+659 660 31241 0x18009f0a
+660 661 31244 0x18009ea9
+661 662 31256 0x18009e49
+662 663 31259 0x18009de8
+663 664 31270 0x18009d89
+664 665 31293 0x18009d2c
+665 666 31302 0x18009ccc
+666 667 31306 0x18009c6c
+667 668 31316 0x18009c0e
+668 669 31332 0x18009bb0
+669 670 31373 0x18009b57
+670 671 31373 0x18009af7
+671 672 31375 0x18009a98
+672 673 31375 0x18009a39
+673 674 31394 0x180099de
+674 675 31402 0x18009980
+675 676 31402 0x18009921
+676 677 31409 0x180098c5
+677 678 31415 0x18009867
+678 679 31423 0x1800980b
+679 680 31442 0x180097b0
+680 681 31446 0x18009753
+681 682 31454 0x180096f7
+682 683 31458 0x1800969b
+683 684 31460 0x1800963f
+684 685 31468 0x180095e3
+685 686 31468 0x18009587
+686 687 31474 0x1800952c
+687 688 31478 0x180094d1
+688 689 31485 0x18009476
+689 690 31487 0x1800941b
+690 691 31488 0x180093c0
+691 692 31492 0x18009365
+692 693 31506 0x1800930d
+693 694 31514 0x180092b3
+694 695 31518 0x18009259
+695 696 31528 0x18009201
+696 697 31530 0x180091a7
+697 698 31539 0x1800914f
+698 699 31542 0x180090f6
+699 700 31568 0x180090a0
+700 701 31568 0x18009047
+701 702 31586 0x18008ff1
+702 703 31637 0x18008fa0
+703 704 31643 0x18008f48
+704 705 31660 0x18008ef2
+705 706 31678 0x18008e9d
+706 707 31678 0x18008e45
+707 708 31692 0x18008df0
+708 709 31692 0x18008d98
+709 710 31692 0x18008d41
+710 711 31712 0x18008ced
+711 712 31716 0x18008c97
+712 713 31719 0x18008c40
+713 714 31730 0x18008bec
+714 715 31735 0x18008b96
+715 716 31762 0x18008b44
+716 717 31780 0x18008af1
+717 718 31783 0x18008a9c
+718 719 31783 0x18008a46
+719 720 31785 0x180089f1
+720 721 31803 0x1800899f
+721 722 31805 0x1800894a
+722 723 31805 0x180088f6
+723 724 31831 0x180088a5
+724 725 31847 0x18008853
+725 726 31859 0x18008801
+726 727 31862 0x180087ae
+727 728 31862 0x1800875a
+728 729 31862 0x18008707
+729 730 31864 0x180086b4
+730 731 31864 0x18008660
+731 732 31877 0x1800860f
+732 733 31886 0x180085be
+733 734 31892 0x1800856d
+734 735 31909 0x1800851d
+735 736 31911 0x180084cb
+736 737 31911 0x18008479
+737 738 31911 0x18008428
+738 739 31911 0x180083d6
+739 740 31912 0x18008385
+740 741 31918 0x18008335
+741 742 31922 0x180082e5
+742 743 31943 0x18008297
+743 744 31943 0x18008247
+744 745 31964 0x180081f9
+745 746 31965 0x180081a9
+746 747 31967 0x1800815a
+747 748 31973 0x1800810b
+748 749 31974 0x180080bb
+749 750 31977 0x1800806c
+750 751 31991 0x1800801f
+751 752 31992 0x177fd0f4
+752 753 31997 0x177f82cd
+753 754 31998 0x177f3451
+754 755 31999 0x177ee62b
+755 756 32008 0x177e995d
+756 757 32011 0x177e4b8c
+757 758 32027 0x177e0015
+758 759 32042 0x177db448
+759 760 32046 0x177d6779
+760 761 32059 0x177d1bac
+761 762 32064 0x177ccf89
+762 763 32068 0x177c8366
+763 764 32069 0x177c36ed
+764 765 32070 0x177bea1f
+765 766 32072 0x177b9e52
+766 767 32076 0x177b52da
+767 768 32079 0x177b070d
+768 769 32083 0x177abbec
+769 770 32092 0x177a7176
+770 771 32117 0x177a295a
+771 772 32121 0x1779de8e
+772 773 32121 0x1779936d
+773 774 32128 0x1779494d
+774 775 32129 0x1778fed7
+775 776 32130 0x1778b4b8
+776 777 32132 0x17786a98
+777 778 32139 0x17782124
+778 779 32140 0x1777d75a
+779 780 32142 0x17778de6
+780 781 32143 0x17774472
+781 782 32156 0x1776fd01
+782 783 32160 0x1776b3e3
+783 784 32162 0x17766b70
+784 785 32174 0x177623ff
+785 786 32191 0x1775dd3a
+786 787 32205 0x1775961f
+787 788 32217 0x17754f5a
+788 789 32217 0x1775073d
+789 790 32217 0x1774bf21
+790 791 32218 0x1774775a
+791 792 32218 0x17742f93
+792 793 32225 0x1773e97a
+793 794 32239 0x1773a360
+794 795 32259 0x17735e9e
+795 796 32259 0x177317d9
+796 797 32260 0x1772d114
+797 798 32267 0x17728b51
+798 799 32268 0x17724537
+799 800 32271 0x1771ff1e
+800 801 32280 0x1771ba06
+801 802 32285 0x17717498
+802 803 32285 0x17712ed5
+803 804 32285 0x1770e911
+804 805 32294 0x1770a4a5
+805 806 32300 0x17706039
+806 807 32302 0x17701b77
+807 808 32303 0x176fd6b5
+808 809 32314 0x176f92f5
+809 810 32315 0x176f4e88
+810 811 32327 0x176f0b74
+811 812 32339 0x176ec85f
+812 813 32345 0x176e84f5
+813 814 32346 0x176e4134
+814 815 32347 0x176dfd74
+815 816 32350 0x176dba09
+816 817 32357 0x176d774b
+817 818 32359 0x176d348c
+818 819 32359 0x176cf177
+819 820 32367 0x176caf0e
+820 821 32387 0x176c6ea8
+821 822 32389 0x176c2c3f
+822 823 32394 0x176bea2c
+823 824 32394 0x176ba7c3
+824 825 32405 0x176b6708
+825 826 32407 0x176b24f5
+826 827 32408 0x176ae338
+827 828 32411 0x176aa226
+828 829 32415 0x176a6115
+829 830 32417 0x176a1fad
+830 831 32427 0x1769df9d
+831 832 32430 0x17699ee2
+832 833 32444 0x17695f28
+833 834 32452 0x17691f6e
+834 835 32479 0x1768e1b7
+835 836 32484 0x1768a1fd
+836 837 32486 0x17686197
+837 838 32503 0x1768238a
+838 839 32505 0x1767e37b
+839 840 32511 0x1767a46c
+840 841 32512 0x176764b2
+841 842 32514 0x1767254e
+842 843 32518 0x1766e696
+843 844 32534 0x1766a8df
+844 845 32540 0x17666a7c
+845 846 32547 0x17662c1a
+846 847 32551 0x1765edb7
+847 848 32551 0x1765af54
+848 849 32552 0x1765709c
+849 850 32554 0x176532e5
+850 851 32554 0x1764f483
+851 852 32555 0x1764b676
+852 853 32557 0x176478bf
+853 854 32564 0x17643bb4
+854 855 32564 0x1763fe53
+855 856 32572 0x1763c19e
+856 857 32577 0x176384e8
+857 858 32581 0x176347dd
+858 859 32584 0x17630b28
+859 860 32585 0x1762ce73
+860 861 32588 0x176291be
+861 862 32589 0x1762555e
+862 863 32615 0x17621bae
+863 864 32619 0x1761dffa
+864 865 32619 0x1761a39b
+865 866 32623 0x176167e7
+866 867 32623 0x17612bdd
+867 868 32627 0x1760f080
+868 869 32633 0x1760b5ce
+869 870 32637 0x17607ac6
+870 871 32650 0x17604069
+871 872 32652 0x176005b7
+872 873 32652 0x175fcaaf
+873 874 32654 0x175f8fa8
+874 875 32657 0x175f554b
+875 876 32658 0x175f1aef
+876 877 32664 0x175ee0e9
+877 878 32664 0x175ea68d
+878 879 32667 0x175e6c86
+879 880 32671 0x175e32d6
+880 881 32672 0x175df925
+881 882 32672 0x175dbf75
+882 883 32677 0x175d861a
+883 884 32677 0x175d4c6a
+884 885 32691 0x175d1466
+885 886 32694 0x175cdbb8
+886 887 32716 0x175ca4b6
+887 888 32729 0x175c6cb3
+888 889 32730 0x175c3404
+889 890 32735 0x175bfbab
+890 891 32741 0x175bc3a8
+891 892 32762 0x175b8cfc
+892 893 32764 0x175b54f9
+893 894 32770 0x175b1d4c
+894 895 32772 0x175ae548
+895 896 32779 0x175aadf1
+896 897 32782 0x175a7699
+897 898 32789 0x175a3f42
+898 899 32789 0x175a07eb
+899 900 32789 0x1759d03d
+900 901 32792 0x1759993c
+901 902 32793 0x1759623a
+902 903 32797 0x17592b39
+903 904 32803 0x1758f4e3
+904 905 32804 0x1758be37
+905 906 32807 0x175887e1
+906 907 32809 0x1758518b
+907 908 32822 0x17581c37
+908 909 32827 0x1757e637
+909 910 32834 0x1757b0e3
+910 911 32834 0x17577a8d
+911 912 32848 0x1757463a
+912 913 32854 0x17571090
+913 914 32869 0x1756dc93
+914 915 32870 0x1756a693
+915 916 32874 0x17567195
+916 917 32876 0x17563c97
+917 918 32889 0x17560844
+918 919 32892 0x1755d3c6
+919 920 32894 0x17559f49
+920 921 32904 0x17556b77
+921 922 32905 0x17553724
+922 923 32908 0x175502fc
+923 924 32909 0x1754ced5
+924 925 32913 0x17549b2e
+925 926 32940 0x1754698a
+926 927 32940 0x175435b8
+927 928 32944 0x1754023c
+928 929 32944 0x1753cec0
+929 930 32944 0x17539b44
+930 931 32946 0x175367f3
+931 932 32952 0x1753354e
+932 933 32954 0x17530252
+933 934 32954 0x1752cf57
+934 935 32958 0x17529cdd
+935 936 32958 0x17526a0c
+936 937 32960 0x17523767
+937 938 32962 0x17520542
+938 939 32962 0x1751d2c8
+939 940 32962 0x1751a04e
+940 941 32999 0x17517159
+941 942 33003 0x17513fb5
+942 943 33003 0x17510d90
+943 944 33005 0x1750dc18
+944 945 33007 0x1750aa74
+945 946 33022 0x17507a28
+946 947 33024 0x175048af
+947 948 33033 0x1750180d
+948 949 33034 0x174fe6ea
+949 950 33040 0x174fb61c
+950 951 33045 0x174f85a5
+951 952 33055 0x174f5584
+952 953 33056 0x174f24e2
+953 954 33058 0x174ef43f
+954 955 33059 0x174ec3c8
+955 956 33072 0x174e9453
+956 957 33083 0x174e64dd
+957 958 33088 0x174e3511
+958 959 33099 0x174e05c7
+959 960 33119 0x174dd77e
+960 961 33121 0x174da7b2
+961 962 33121 0x174d77e7
+962 963 33123 0x174d4871
+963 964 33135 0x174d19d2
+964 965 33136 0x174cea5c
+965 966 33137 0x174cbb11
+966 967 33137 0x174c8bf2
+967 968 33145 0x174c5d53
+968 969 33157 0x174c2f35
+969 970 33165 0x174c0116
+970 971 33166 0x174bd24c
+971 972 33169 0x174ba3d8
+972 973 33172 0x174b758f
+973 974 33175 0x174b471b
+974 975 33192 0x174b1a29
+975 976 33195 0x174aec36
+976 977 33198 0x174abe43
+977 978 33201 0x174a907b
+978 979 33201 0x174a6287
+979 980 33210 0x174a356b
+980 981 33210 0x174a07cd
+981 982 33214 0x1749da86
+982 983 33245 0x1749af6c
+983 984 33254 0x174982a6
+984 985 33260 0x174955b4
+985 986 33275 0x174929c4
+986 987 33284 0x1748fd53
+987 988 33299 0x1748d18e
+988 989 33305 0x1748a51d
+989 990 33306 0x17487881
+990 991 33309 0x17484c10
+991 992 33311 0x17481f9f
+992 993 33338 0x1747f55c
+993 994 33340 0x1747c917
+994 995 33343 0x17479cfc
+995 996 33346 0x17477136
+996 997 33347 0x17474546
+997 998 33353 0x174719ac
+998 999 33354 0x1746ede7
+999 1000 33360 0x1746c2cd
+1000 1001 33365 0x17469789
+
diff --git a/data/test_vectors_aserti3-2d_run08.txt b/data/test_vectors_aserti3-2d_run08.txt
new file mode 100644
--- /dev/null
+++ b/data/test_vectors_aserti3-2d_run08.txt
@@ -0,0 +1,509 @@
+## description: run8 - deterministically random solvetimes for down-ramping hashrate around a recent real life nBits
+##   anchor height: 1
+##   anchor parent time: 0
+##   anchor nBits: 0x1802aee8
+##   start height: 2
+##   start time: 1200
+##   iterations: 500
+# iteration,height,time,target
+1 2 1200 0x1802aee8
+2 3 1310 0x1802ad91
+3 4 1327 0x1802abf8
+4 5 1739 0x1802ab73
+5 6 2219 0x1802ab20
+6 7 2604 0x1802aa89
+7 8 2746 0x1802a94b
+8 9 7099 0x1802b39c
+9 10 7099 0x1802b1f2
+10 11 7657 0x1802b1d4
+11 12 9595 0x1802b58d
+12 13 9975 0x1802b4ee
+13 14 11607 0x1802b7d3
+14 15 13951 0x1802bcb8
+15 16 15797 0x1802c03e
+16 17 15865 0x1802bebb
+17 18 18343 0x1802c40e
+18 19 20631 0x1802c8de
+19 20 22947 0x1802cdce
+20 21 23229 0x1802cce4
+21 22 33606 0x1802e994
+22 23 34944 0x1802ebca
+23 24 37977 0x1802f31e
+24 25 39180 0x1802f4f1
+25 26 40755 0x1802f7e8
+26 27 41175 0x1802f75d
+27 28 42855 0x1802faa7
+28 29 43200 0x1802f9de
+29 30 43410 0x1802f8af
+30 31 46761 0x1803011f
+31 32 47473 0x18030177
+32 33 51809 0x18030d12
+33 34 53537 0x1803109d
+34 35 56089 0x180316c4
+35 36 59769 0x18032096
+36 37 63137 0x18032984
+37 38 66673 0x18033318
+38 39 74425 0x18034ae9
+39 40 76909 0x1803514b
+40 41 79761 0x180358ff
+41 42 84471 0x1803673b
+42 43 87141 0x18036e7c
+43 44 90151 0x18037704
+44 45 91796 0x18037abc
+45 46 93236 0x18037dbe
+46 47 93916 0x18037e07
+47 48 94246 0x18037d10
+48 49 96101 0x1803818f
+49 50 96126 0x18037f7c
+50 51 96281 0x18037de7
+51 52 96719 0x18037d50
+52 53 99245 0x1803843e
+53 54 111773 0x1803b056
+54 55 115529 0x1803bc5f
+55 56 117845 0x1803c2fc
+56 57 121667 0x1803cf82
+57 58 123839 0x1803d5af
+58 59 124271 0x1803d508
+59 60 125795 0x1803d8ab
+60 61 128627 0x1803e189
+61 62 130874 0x1803e81e
+62 63 136075 0x1803fac1
+63 64 138133 0x180400bd
+64 65 138966 0x180401b4
+65 66 145532 0x18041a91
+66 67 151454 0x18043142
+67 68 151454 0x18042eab
+68 69 151510 0x18042c58
+69 70 154786 0x180437e2
+70 71 156228 0x18043b88
+71 72 160876 0x18044d49
+72 73 160932 0x18044ae2
+73 74 167388 0x18046509
+74 75 172700 0x18047a86
+75 76 174700 0x180480fd
+76 77 176172 0x18048509
+77 78 176740 0x180484e1
+78 79 176820 0x18048278
+79 80 177348 0x18048224
+80 81 188020 0x1804b1c2
+81 82 188533 0x1804b157
+82 83 191098 0x1804bad9
+83 84 191449 0x1804b9a4
+84 85 196534 0x1804cf9c
+85 86 201439 0x1804e50e
+86 87 202573 0x1804e7c0
+87 88 207289 0x1804fca6
+88 89 227503 0x18056506
+89 90 228205 0x18056596
+90 91 229636 0x18056a38
+91 92 234216 0x18058096
+92 93 252336 0x1805e733
+93 94 262256 0x180620b3
+94 95 262756 0x18062012
+95 96 271086 0x1806516b
+96 97 288256 0x1806c070
+97 98 317896 0x180795e1
+98 99 321636 0x1807ae83
+99 100 333016 0x18080571
+100 101 333916 0x180807f0
+101 102 347996 0x18087a4a
+102 103 352990 0x1808a0e7
+103 104 356246 0x1808b897
+104 105 362428 0x1808eb32
+105 106 367004 0x18090ff1
+106 107 379852 0x180984da
+107 108 382745 0x18099b63
+108 109 384065 0x1809a27e
+109 110 388564 0x1809c961
+110 111 391754 0x1809e38a
+111 112 404822 0x180a6542
+112 113 406274 0x180a6e5b
+113 114 408962 0x180a84c3
+114 115 409442 0x180a8377
+115 116 425930 0x180b34b4
+116 117 431978 0x180b7423
+117 118 432638 0x180b74d9
+118 119 435434 0x180b8ecd
+119 120 437390 0x180b9ef1
+120 121 439130 0x180bac92
+121 122 439715 0x180bac67
+122 123 442042 0x180bc127
+123 124 448087 0x180c0390
+124 125 449192 0x180c09c5
+125 126 498904 0x180ea828
+126 127 503376 0x180ee2f0
+127 128 521797 0x180ffd6e
+128 129 523396 0x18100de8
+129 130 527010 0x18103fec
+130 131 530741 0x1810748a
+131 132 532169 0x18108296
+132 133 546925 0x181179c7
+133 134 565671 0x1812cbe8
+134 135 572167 0x18133f13
+135 136 585439 0x18143ff4
+136 137 590605 0x18149fab
+137 138 594917 0x1814eec8
+138 139 610569 0x18163c62
+139 140 614111 0x1816800d
+140 141 624863 0x18176f87
+141 142 633533 0x181834d4
+142 143 637988 0x18189542
+143 144 642758 0x1818ff54
+144 145 643043 0x1818f747
+145 146 643838 0x1818fc3a
+146 147 647378 0x18194807
+147 148 652958 0x1819ca80
+148 149 656573 0x181a1aa9
+149 150 662303 0x181aa584
+150 151 665153 0x181ae33b
+151 152 665217 0x181ad479
+152 153 669809 0x181b433e
+153 154 675441 0x181bd174
+154 155 709105 0x181fc36f
+155 156 739713 0x1823d4b2
+156 157 739953 0x1823c772
+157 158 743137 0x182426f4
+158 159 751377 0x1825473b
+159 160 756049 0x1825e474
+160 161 766465 0x18276a1c
+161 162 769848 0x1827db4e
+162 163 774421 0x18287f27
+163 164 778824 0x18291e62
+164 165 781017 0x182961cd
+165 166 781935 0x18296f37
+166 167 788667 0x182a76ed
+167 168 800142 0x182c5c16
+168 169 806381 0x182d605c
+169 170 808965 0x182dbd45
+170 171 845345 0x1834ca94
+171 172 848441 0x1835526a
+172 173 863057 0x18386749
+173 174 874001 0x183acab9
+174 175 878285 0x183baac6
+175 176 906131 0x18429001
+176 177 928253 0x1848922a
+177 178 940691 0x184c1a3e
+178 179 969311 0x185525de
+179 180 970031 0x1855306f
+180 181 989723 0x185bfa53
+181 182 1001047 0x1860050c
+182 183 1017805 0x18667131
+183 184 1018432 0x186673e0
+184 185 1023885 0x186876b9
+185 186 1073114 0x187ef4ed
+186 187 1078358 0x19008158
+187 188 1140450 0x1900a58c
+188 189 1143129 0x1900a6ef
+189 190 1146530 0x1900a8d1
+190 191 1153750 0x1900ad5b
+191 192 1179150 0x1900bf80
+192 193 1212890 0x1900dab2
+193 194 1224950 0x1900e4fa
+194 195 1226790 0x1900e61e
+195 196 1232690 0x1900eb10
+196 197 1267710 0x19010de3
+197 198 1283910 0x19011f55
+198 199 1293770 0x19012a37
+199 200 1324330 0x19015048
+200 201 1336050 0x19015fa0
+201 202 1350435 0x190173a3
+202 203 1357323 0x19017d1f
+203 204 1368411 0x19018d7d
+204 205 1371057 0x190190c2
+205 206 1382985 0x1901a35d
+206 207 1383132 0x1901a29a
+207 208 1396047 0x1901b7c8
+208 209 1412889 0x1901d55e
+209 210 1427358 0x1901f03a
+210 211 1431075 0x1901f679
+211 212 1446233 0x190214b8
+212 213 1461589 0x1902353d
+213 214 1501123 0x190294cc
+214 215 1517755 0x1902c0ae
+215 216 1518239 0x1902c05b
+216 217 1546553 0x1903132e
+217 218 1550821 0x19031ed6
+218 219 1551811 0x19032015
+219 220 1580213 0x19037e5f
+220 221 1611497 0x1903f383
+221 222 1613567 0x1903f97f
+222 223 1619846 0x190410f7
+223 224 1627988 0x190430fa
+224 225 1682705 0x19053535
+225 226 1712835 0x1905dce8
+226 227 1737836 0x1906771c
+227 228 1742551 0x1906929d
+228 229 1743655 0x19069607
+229 230 1748922 0x1906b5da
+230 231 1751314 0x1906c238
+231 232 1759570 0x1906f822
+232 233 1768858 0x19073761
+233 234 1771642 0x190747a0
+234 235 1796434 0x1908059c
+235 236 1805578 0x19084d48
+236 237 1809922 0x19086d7b
+237 238 1832170 0x19093166
+238 239 1848538 0x1909cb1e
+239 240 1855858 0x190a0f95
+240 241 1907986 0x190c5ed6
+241 242 1950436 0x190ea128
+242 243 1951261 0x190ea482
+243 244 1999686 0x1911bddd
+244 245 2000211 0x1911bc7b
+245 246 2039836 0x1914be10
+246 247 2080561 0x19185d54
+247 248 2095061 0x1919c29e
+248 249 2098211 0x191a0649
+249 250 2101636 0x191a522b
+250 251 2114311 0x191ba03b
+251 252 2118367 0x191c02ec
+252 253 2178765 0x19239bd8
+253 254 2206325 0x1927ad86
+254 255 2208301 0x1927e59f
+255 256 2210979 0x19283b3b
+256 257 2211109 0x192827b2
+257 258 2234067 0x192bebe5
+258 259 2236381 0x192c398b
+259 260 2259703 0x1930720b
+260 261 2277357 0x1933df20
+261 262 2293854 0x19374884
+262 263 2296878 0x1937d2b3
+263 264 2345100 0x194393f1
+264 265 2346180 0x1943b550
+265 266 2348610 0x1944351a
+266 267 2353740 0x19457594
+267 268 2367645 0x19494570
+268 269 2369319 0x1949966f
+269 270 2371722 0x194a1f48
+270 271 2387814 0x194ee076
+271 272 2397166 0x1951b169
+272 273 2442330 0x1961ae56
+273 274 2443282 0x1961d1e4
+274 275 2445886 0x19629bce
+275 276 2450310 0x1964218b
+276 277 2450450 0x1963f1ea
+277 278 2459998 0x19679803
+278 279 2480942 0x197064eb
+279 280 2501718 0x1979dde2
+280 281 2529858 0x1a00881c
+281 282 2567210 0x1a009dbf
+282 283 2573300 0x1a00a142
+283 284 2588844 0x1a00ab35
+284 285 2663403 0x1a00e650
+285 286 2675641 0x1a00f152
+286 287 2701248 0x1a010acd
+287 288 2718503 0x1a011d42
+288 289 2736599 0x1a013202
+289 290 2758001 0x1a014ca0
+290 291 2771080 0x1a015db3
+291 292 2808940 0x1a019610
+292 293 2841880 0x1a01ce41
+293 294 2849530 0x1a01db83
+294 295 2849740 0x1a01dac4
+295 296 2867290 0x1a01fc2e
+296 297 2895370 0x1a023777
+297 298 2951740 0x1a02c5c1
+298 299 2965600 0x1a02ec8e
+299 300 2967070 0x1a02ef2a
+300 301 3021970 0x1a03a5c8
+301 302 3023086 0x1a03a7b9
+302 303 3076778 0x1a0485fd
+303 304 3109142 0x1a05235e
+304 305 3145226 0x1a05ec96
+305 306 3154650 0x1a062332
+306 307 3169530 0x1a067fb4
+307 308 3175327 0x1a06a2b6
+308 309 3194609 0x1a0726cc
+309 310 3198639 0x1a074024
+310 311 3252548 0x1a08fb0b
+311 312 3282116 0x1a0a1665
+312 313 3298596 0x1a0abffc
+313 314 3353700 0x1a0d6062
+314 315 3363236 0x1a0ddd3d
+315 316 3363684 0x1a0ddb0f
+316 317 3401476 0x1a1015d4
+317 318 3402884 0x1a102334
+318 319 3411748 0x1a10ae90
+319 320 3421732 0x1a115289
+320 321 3433732 0x1a122246
+321 322 3441124 0x1a12a29c
+322 323 3462640 0x1a14441a
+323 324 3485872 0x1a163110
+324 325 3498445 0x1a1748ca
+325 326 3535537 0x1a1af36a
+326 327 3559594 0x1a1d9bb2
+327 328 3567580 0x1a1e7fb0
+328 329 3590350 0x1a215657
+329 330 3601966 0x1a22d84e
+330 331 3612064 0x1a24331c
+331 332 3631240 0x1a27005f
+332 333 3634844 0x1a277974
+333 334 3644874 0x1a28fec6
+334 335 3648648 0x1a29851a
+335 336 3670204 0x1a2d2986
+336 337 3701076 0x1a32fd66
+337 338 3713860 0x1a358a6d
+338 339 3716478 0x1a35f99d
+339 340 3728004 0x1a386444
+340 341 3742590 0x1a3ba512
+341 342 3775490 0x1a43e6c9
+342 343 3782035 0x1a458ab6
+343 344 3806885 0x1a4ca71d
+344 345 3811960 0x1a4e0a79
+345 346 3822635 0x1a51420e
+346 347 3826135 0x1a523539
+347 348 3827955 0x1a529c5c
+348 349 3855430 0x1a5c0463
+349 350 3898795 0x1a6d383c
+350 351 3912900 0x1a734b80
+351 352 3950808 0x1b0085ea
+352 353 3961644 0x1b008b89
+353 354 3983604 0x1b009807
+354 355 3997608 0x1b00a06d
+355 356 4011072 0x1b00a8e9
+356 357 4015212 0x1b00ab52
+357 358 4020360 0x1b00ae7b
+358 359 4089768 0x1b00e5e9
+359 360 4113384 0x1b00fc27
+360 361 4114896 0x1b00fd14
+361 362 4145939 0x1b011dfc
+362 363 4154079 0x1b0126c7
+363 364 4175169 0x1b014008
+364 365 4182902 0x1b014950
+365 366 4221234 0x1b017f21
+366 367 4225341 0x1b01848d
+367 368 4254423 0x1b01b386
+368 369 4269926 0x1b01ce56
+369 370 4272294 0x1b01d1a1
+370 371 4279546 0x1b01de38
+371 372 4279774 0x1b01dd82
+372 373 4294214 0x1b01f8c8
+373 374 4309452 0x1b021754
+374 375 4330048 0x1b024417
+375 376 4335064 0x1b024e77
+376 377 4338902 0x1b025631
+377 378 4386706 0x1b02d2e3
+378 379 4395332 0x1b02ea8b
+379 380 4430710 0x1b035a31
+380 381 4432876 0x1b035f97
+381 382 4455847 0x1b03b0a1
+382 383 4476088 0x1b03fe19
+383 384 4507912 0x1b04869e
+384 385 4581934 0x1b06136e
+385 386 4583416 0x1b0618f2
+386 387 4592074 0x1b064c28
+387 388 4691875 0x1b096030
+388 389 4711882 0x1b0a227e
+389 390 4723075 0x1b0a92f5
+390 391 4731889 0x1b0aedaf
+391 392 4751009 0x1b0bc599
+392 393 4756169 0x1b0bfd31
+393 394 4770929 0x1b0cb061
+394 395 4797289 0x1b0e119a
+395 396 4819329 0x1b0f5504
+396 397 4842089 0x1b10c22f
+397 398 4844009 0x1b10d8fe
+398 399 4871489 0x1b12c487
+399 400 4873449 0x1b12ded0
+400 401 4883769 0x1b139eeb
+401 402 4899882 0x1b14e128
+402 403 4901686 0x1b14faf0
+403 404 4924277 0x1b16eaa0
+404 405 4935060 0x1b17df22
+405 406 4944203 0x1b18b408
+406 407 4953674 0x1b1998c6
+407 408 4997462 0x1b1e6f81
+408 409 5033255 0x1b230de3
+409 410 5035633 0x1b234e1d
+410 411 5085243 0x1b2af93b
+411 412 5088519 0x1b2b704d
+412 413 5121909 0x1b318bc8
+413 414 5124933 0x1b3207b7
+414 415 5135979 0x1b342b18
+415 416 5180877 0x1b3e4ec8
+416 417 5190537 0x1b409d98
+417 418 5190789 0x1b408673
+418 419 5246103 0x1b505f7d
+419 420 5264121 0x1b562e80
+420 421 5280459 0x1b5bceb9
+421 422 5299680 0x1b62ec4d
+422 423 5305958 0x1b6532e5
+423 424 5320320 0x1b6aefa1
+424 425 5352914 0x1b79926b
+425 426 5430357 0x1c00a57c
+426 427 5460070 0x1c00b9fd
+427 428 5512315 0x1c00e4c3
+428 429 5523108 0x1c00ee4f
+429 430 5638176 0x1c017941
+430 431 5641659 0x1c017da2
+431 432 5666343 0x1c01a44e
+432 433 5668191 0x1c01a668
+433 434 5683371 0x1c01bfd3
+434 435 5697319 0x1c01d873
+435 436 5709375 0x1c01eeac
+436 437 5798783 0x1c02c276
+437 438 5828967 0x1c031b78
+438 439 5837635 0x1c03359a
+439 440 5848459 0x1c0357f3
+440 441 5866191 0x1c0394cd
+441 442 5910066 0x1c0442ad
+442 443 5912496 0x1c044aba
+443 444 5929326 0x1c0494ba
+444 445 5936076 0x1c04b208
+445 446 5969421 0x1c055aa0
+446 447 6009786 0x1c0647b6
+447 448 6037011 0x1c06fcba
+448 449 6047316 0x1c0743b5
+449 450 6053211 0x1c076b9e
+450 451 6069321 0x1c07e59a
+451 452 6094989 0x1c08bbac
+452 453 6106765 0x1c092249
+453 454 6109111 0x1c0932b8
+454 455 6132249 0x1c0a1193
+455 456 6137263 0x1c0a3f96
+456 457 6160447 0x1c0b385a
+457 458 6177513 0x1c0bfc7a
+458 459 6244765 0x1c0fa85d
+459 460 6254195 0x1c1038f7
+460 461 6259071 0x1c1080e8
+461 462 6269223 0x1c112623
+462 463 6331686 0x1c15fae5
+463 464 6354951 0x1c18129e
+464 465 6374315 0x1c19f3c1
+465 466 6399836 0x1c1cada5
+466 467 6409471 0x1c1dbc7b
+467 468 6431467 0x1c206707
+468 469 6491768 0x1c292ce4
+469 470 6562409 0x1c36867c
+470 471 6592536 0x1c3d6125
+471 472 6623496 0x1c4555e2
+472 473 6640584 0x1c4a14e2
+473 474 6646680 0x1c4bbbd4
+474 475 6652008 0x1c4d2f1e
+475 476 6666120 0x1c517b3e
+476 477 6701784 0x1c5dc9da
+477 478 6705288 0x1c5ee342
+478 479 6737688 0x1c6bc7f6
+479 480 6774168 0x1c7c75fc
+480 481 6842136 0x1d00a31a
+481 482 6924554 0x1d00e26d
+482 483 6928915 0x1d00e5dd
+483 484 6930238 0x1d00e688
+484 485 6993399 0x1d00ffff
+485 486 7003150 0x1d00ffff
+486 487 7004914 0x1d00ffff
+487 488 7092820 0x1d00ffff
+488 489 7106491 0x1d00ffff
+489 490 7126140 0x1d00ffff
+490 491 7160783 0x1d00ffff
+491 492 7216733 0x1d00ffff
+492 493 7226083 0x1d00ffff
+493 494 7284983 0x1d00ffff
+494 495 7290833 0x1d00ffff
+495 496 7293683 0x1d00ffff
+496 497 7343383 0x1d00ffff
+497 498 7387333 0x1d00ffff
+498 499 7388383 0x1d00ffff
+499 500 7421433 0x1d00ffff
+500 501 7462933 0x1d00ffff
+
diff --git a/data/test_vectors_aserti3-2d_run09.txt b/data/test_vectors_aserti3-2d_run09.txt
new file mode 100644
--- /dev/null
+++ b/data/test_vectors_aserti3-2d_run09.txt
@@ -0,0 +1,19 @@
+## description: run9 - a sequence of 300s blocks across signed 32-bit max integer height
+##   anchor height: 2147483642
+##   anchor parent time: 1234567290
+##   anchor nBits: 0x1802aee8
+##   start height: 2147483643
+##   start time: 1234568190
+##   iterations: 10
+# iteration,height,time,target
+1 2147483643 1234568190 0x1802ae16
+2 2147483644 1234568490 0x1802ad44
+3 2147483645 1234568790 0x1802ac71
+4 2147483646 1234569090 0x1802ab9e
+5 2147483647 1234569390 0x1802aacd
+6 2147483648 1234569690 0x1802a9fa
+7 2147483649 1234569990 0x1802a929
+8 2147483650 1234570290 0x1802a858
+9 2147483651 1234570590 0x1802a787
+10 2147483652 1234570890 0x1802a6b7
+
diff --git a/data/test_vectors_aserti3-2d_run10.txt b/data/test_vectors_aserti3-2d_run10.txt
new file mode 100644
--- /dev/null
+++ b/data/test_vectors_aserti3-2d_run10.txt
@@ -0,0 +1,19 @@
+## description: run10 - a sequence of 900s blocks across signed 64-bit max integer height and signed 32-bit max integer time 
+##   anchor height: 9223372036854775802
+##   anchor parent time: 2147483047
+##   anchor nBits: 0x1802aee8
+##   start height: 9223372036854775803
+##   start time: 2147484547
+##   iterations: 10
+# iteration,height,time,target
+1 9223372036854775803 2147484547 0x1802afbb
+2 9223372036854775804 2147485447 0x1802b08f
+3 9223372036854775805 2147486347 0x1802b166
+4 9223372036854775806 2147487247 0x1802b23a
+5 9223372036854775807 2147488147 0x1802b30e
+6 9223372036854775808 2147489047 0x1802b3e5
+7 9223372036854775809 2147489947 0x1802b4bb
+8 9223372036854775810 2147490847 0x1802b592
+9 9223372036854775811 2147491747 0x1802b669
+10 9223372036854775812 2147492647 0x1802b73d
+
diff --git a/data/test_vectors_aserti3-2d_run11.txt b/data/test_vectors_aserti3-2d_run11.txt
new file mode 100644
--- /dev/null
+++ b/data/test_vectors_aserti3-2d_run11.txt
@@ -0,0 +1,1009 @@
+## description: run11 - deterministically uniform random solvetimes with negative time
+##   anchor height: 1
+##   anchor parent time: 0
+##   anchor nBits: 0x1802aee8
+##   start height: 2
+##   start time: 1200
+##   iterations: 1000
+# iteration,height,time,target
+1 2 1200 0x1802aee8
+2 3 1496 0x1802ae12
+3 4 1398 0x1802ac28
+4 5 1996 0x1802ac28
+5 6 1731 0x1802a9cb
+6 7 1816 0x1802a863
+7 8 2113 0x1802a790
+8 9 2318 0x1802a67e
+9 10 2679 0x1802a5d7
+10 11 3289 0x1802a5df
+11 12 3557 0x1802a4f8
+12 13 3438 0x1802a305
+13 14 3391 0x1802a147
+14 15 3417 0x18029fba
+15 16 4317 0x1802a08a
+16 17 4073 0x18029e45
+17 18 3774 0x18029bdc
+18 19 3619 0x180299d8
+19 20 4046 0x18029961
+20 21 4571 0x1802992f
+21 22 5233 0x18029959
+22 23 5432 0x18029848
+23 24 5458 0x180296c1
+24 25 5399 0x18029501
+25 26 5991 0x180294fc
+26 27 6856 0x180295b1
+27 28 7586 0x18029608
+28 29 7706 0x180294c2
+29 30 8349 0x180294e0
+30 31 8407 0x18029371
+31 32 8174 0x1802913f
+32 33 8866 0x1802917d
+33 34 9614 0x180291e0
+34 35 9779 0x180290bc
+35 36 10501 0x1802910e
+36 37 10776 0x18029033
+37 38 11502 0x18029088
+38 39 11548 0x18028f14
+39 40 11500 0x18028d60
+40 41 11239 0x18028b1f
+41 42 12136 0x18028be7
+42 43 12921 0x18028c62
+43 44 13251 0x18028baf
+44 45 13017 0x18028980
+45 46 13695 0x180289b5
+46 47 13758 0x1802884f
+47 48 14043 0x1802877f
+48 49 14520 0x1802872b
+49 50 14920 0x180286a7
+50 51 14779 0x180284bc
+51 52 14728 0x1802830d
+52 53 14753 0x18028193
+53 54 15181 0x18028122
+54 55 15735 0x18028104
+55 56 15645 0x18027f3f
+56 57 16234 0x18027f37
+57 58 16067 0x18027d41
+58 59 16120 0x18027bdb
+59 60 16834 0x18027c26
+60 61 16996 0x18027b08
+61 62 17004 0x18027986
+62 63 17454 0x18027924
+63 64 18157 0x18027967
+64 65 17920 0x18027749
+65 66 18236 0x18027690
+66 67 18091 0x180274ae
+67 68 18577 0x18027464
+68 69 18790 0x1802736b
+69 70 19431 0x18027386
+70 71 19352 0x180271d0
+71 72 19958 0x180271d6
+72 73 20307 0x18027135
+73 74 20933 0x18027143
+74 75 21541 0x18027149
+75 76 22394 0x180271eb
+76 77 23139 0x18027249
+77 78 23613 0x180271f9
+78 79 23884 0x18027125
+79 80 24419 0x180270fb
+80 81 25272 0x1802719d
+81 82 25923 0x180271be
+82 83 26492 0x180271ab
+83 84 26900 0x1802712f
+84 85 27144 0x1802704b
+85 86 27605 0x18026ff1
+86 87 28045 0x18026f8a
+87 88 28252 0x18026e8f
+88 89 28138 0x18026cc8
+89 90 28296 0x18026baf
+90 91 28162 0x180269dd
+91 92 28107 0x1802683c
+92 93 28726 0x18026848
+93 94 28552 0x18026660
+94 95 29393 0x180266f7
+95 96 29647 0x1802661e
+96 97 30505 0x180266c0
+97 98 30215 0x1802648e
+98 99 30194 0x18026309
+99 100 29955 0x180260fc
+100 101 29800 0x18025f23
+101 102 29638 0x18025d48
+102 103 29347 0x18025b1f
+103 104 29653 0x18025a6a
+104 105 29683 0x1802590b
+105 106 30547 0x180259ad
+106 107 30413 0x180257e6
+107 108 30890 0x1802579c
+108 109 30699 0x180255b5
+109 110 30969 0x180254eb
+110 111 31199 0x18025407
+111 112 31684 0x180253c2
+112 113 32275 0x180253bb
+113 114 32519 0x180252e2
+114 115 33083 0x180252cd
+115 116 32920 0x180250fa
+116 117 32905 0x18024f83
+117 118 33019 0x18024e5e
+118 119 33176 0x18024d51
+119 120 33530 0x18024cbb
+120 121 33796 0x18024bf2
+121 122 33993 0x18024aff
+122 123 34841 0x18024b94
+123 124 35394 0x18024b78
+124 125 35811 0x18024b0a
+125 126 35977 0x18024a03
+126 127 36103 0x180248e5
+127 128 36019 0x1802474c
+128 129 35828 0x18024572
+129 130 36477 0x18024590
+130 131 36483 0x1802442c
+131 132 37091 0x18024432
+132 133 37854 0x18024492
+133 134 37554 0x1802427b
+134 135 37268 0x1802406d
+135 136 36984 0x18023e62
+136 137 37498 0x18023e2f
+137 138 37848 0x18023d9b
+138 139 38669 0x18023e1f
+139 140 38719 0x18023cda
+140 141 39597 0x18023d7e
+141 142 40042 0x18023d22
+142 143 39885 0x18023b65
+143 144 39599 0x1802395e
+144 145 40432 0x180239e5
+145 146 41020 0x180239e0
+146 147 41490 0x18023992
+147 148 41994 0x1802395b
+148 149 42268 0x1802389b
+149 150 42377 0x1802377d
+150 151 42236 0x180235cd
+151 152 42253 0x1802347b
+152 153 42130 0x180232d9
+153 154 41964 0x1802311d
+154 155 42299 0x18023085
+155 156 42018 0x18022e8a
+156 157 42136 0x18022d76
+157 158 41961 0x18022bba
+158 159 42603 0x18022bd2
+159 160 43374 0x18022c34
+160 161 44180 0x18022caa
+161 162 43989 0x18022ae6
+162 163 44850 0x18022b7b
+163 164 45005 0x18022a7d
+164 165 45852 0x18022b0a
+165 166 45629 0x18022934
+166 167 46154 0x1802290b
+167 168 46586 0x180228aa
+168 169 47003 0x18022843
+169 170 47420 0x180227db
+170 171 47985 0x180227c6
+171 172 47912 0x1802264a
+172 173 48768 0x180226da
+173 174 49167 0x18022669
+174 175 49100 0x180224f0
+175 176 49972 0x18022589
+176 177 50730 0x180225e3
+177 178 50577 0x1802243a
+178 179 50465 0x180222a9
+179 180 50444 0x1802214d
+180 181 50989 0x1802212e
+181 182 51330 0x1802209d
+182 183 51444 0x18021f8c
+183 184 52286 0x18022013
+184 185 52731 0x18021fbd
+185 186 53402 0x18021fe4
+186 187 53983 0x18021fdb
+187 188 53779 0x18021e19
+188 189 54536 0x18021e72
+189 190 55137 0x18021e72
+190 191 55664 0x18021e48
+191 192 56496 0x18021eca
+192 193 56744 0x18021e07
+193 194 57636 0x18021ea9
+194 195 58132 0x18021e6f
+195 196 58390 0x18021db1
+196 197 58294 0x18021c2e
+197 198 59014 0x18021c70
+198 199 59385 0x18021bf1
+199 200 59252 0x18021a5a
+200 201 59734 0x18021a1a
+201 202 60492 0x18021a70
+202 203 61248 0x18021ac8
+203 204 61585 0x18021a36
+204 205 61408 0x18021889
+205 206 61576 0x1802179a
+206 207 61567 0x1802164a
+207 208 61997 0x180215ee
+208 209 62779 0x18021652
+209 210 63079 0x180215ad
+210 211 63753 0x180215d6
+211 212 64482 0x1802161d
+212 213 65054 0x1802160d
+213 214 65180 0x1802150a
+214 215 65954 0x18021569
+215 216 65871 0x180213f3
+216 217 66122 0x18021334
+217 218 66415 0x1802128c
+218 219 67002 0x18021286
+219 220 67163 0x18021196
+220 221 67140 0x18021044
+221 222 67845 0x1802107d
+222 223 68006 0x18020f8e
+223 224 67926 0x18020e1f
+224 225 68677 0x18020e6f
+225 226 68752 0x18020d54
+226 227 68739 0x18020c09
+227 228 68517 0x18020a4f
+228 229 68844 0x180209bd
+229 230 69623 0x18020a1c
+230 231 70389 0x18020a76
+231 232 70771 0x18020a00
+232 233 71391 0x18020a0c
+233 234 71197 0x18020863
+234 235 70997 0x180206b7
+235 236 71084 0x180205a7
+236 237 71262 0x180204c7
+237 238 71248 0x18020381
+238 239 71418 0x1802029d
+239 240 71376 0x18020149
+240 241 72137 0x1802019e
+241 242 71924 0x1801fff2
+242 243 72723 0x1802005a
+243 244 72733 0x1801ff24
+244 245 73287 0x1801ff0c
+245 246 73635 0x1801fe89
+246 247 73347 0x1801fcb7
+247 248 73207 0x1801fb34
+248 249 73162 0x1801f9e6
+249 250 74060 0x1801fa80
+250 251 74354 0x1801f9e0
+251 252 74578 0x1801f91e
+252 253 74420 0x1801f793
+253 254 75207 0x1801f7f5
+254 255 75117 0x1801f690
+255 256 75039 0x1801f533
+256 257 75543 0x1801f502
+257 258 75688 0x1801f418
+258 259 75698 0x1801f2e9
+259 260 75855 0x1801f206
+260 261 76080 0x1801f147
+261 262 75969 0x1801efdb
+262 263 76421 0x1801ef90
+263 264 77114 0x1801efbf
+264 265 77380 0x1801ef14
+265 266 78152 0x1801ef6d
+266 267 78608 0x1801ef23
+267 268 78954 0x1801eea2
+268 269 79243 0x1801ee04
+269 270 79896 0x1801ee1f
+270 271 80189 0x1801ed83
+271 272 80441 0x1801ecd2
+272 273 80632 0x1801ec04
+273 274 80814 0x1801eb31
+274 275 81489 0x1801eb56
+275 276 82203 0x1801eb90
+276 277 82617 0x1801eb32
+277 278 82730 0x1801ea3d
+278 279 82989 0x1801e992
+279 280 83556 0x1801e981
+280 281 84222 0x1801e9a1
+281 282 84665 0x1801e953
+282 283 84767 0x1801e85a
+283 284 84936 0x1801e780
+284 285 85292 0x1801e708
+285 286 85486 0x1801e63c
+286 287 86046 0x1801e628
+287 288 85785 0x1801e47a
+288 289 86591 0x1801e4e0
+289 290 86393 0x1801e355
+290 291 86577 0x1801e286
+291 292 86668 0x1801e18b
+292 293 87430 0x1801e1da
+293 294 87685 0x1801e130
+294 295 88110 0x1801e0d9
+295 296 88208 0x1801dfe2
+296 297 88868 0x1801dfff
+297 298 89133 0x1801df5a
+298 299 90001 0x1801dfde
+299 300 90027 0x1801dec3
+300 301 89785 0x1801dd27
+301 302 89493 0x1801db73
+302 303 89555 0x1801da6c
+303 304 89711 0x1801d994
+304 305 90224 0x1801d969
+305 306 90457 0x1801d8b6
+306 307 90665 0x1801d7f8
+307 308 91530 0x1801d879
+308 309 91706 0x1801d7ab
+309 310 92557 0x1801d826
+310 311 92946 0x1801d7be
+311 312 92677 0x1801d61c
+312 313 93246 0x1801d60c
+313 314 93286 0x1801d4fe
+314 315 93637 0x1801d486
+315 316 94029 0x1801d422
+316 317 94460 0x1801d3d1
+317 318 94623 0x1801d2ff
+318 319 95252 0x1801d30c
+319 320 95747 0x1801d2dc
+320 321 95499 0x1801d145
+321 322 95538 0x1801d039
+322 323 95651 0x1801cf52
+323 324 95785 0x1801ce75
+324 325 95569 0x1801ccf1
+325 326 95690 0x1801cc10
+326 327 95734 0x1801cb09
+327 328 95667 0x1801c9cf
+328 329 96011 0x1801c956
+329 330 95815 0x1801c7e1
+330 331 96294 0x1801c7a9
+331 332 96574 0x1801c714
+332 333 97006 0x1801c6c5
+333 334 97517 0x1801c69b
+334 335 98194 0x1801c6bf
+335 336 98444 0x1801c61d
+336 337 98484 0x1801c517
+337 338 98453 0x1801c3f3
+338 339 99067 0x1801c3f8
+339 340 99934 0x1801c475
+340 341 100298 0x1801c407
+341 342 100298 0x1801c2f0
+342 343 100644 0x1801c27b
+343 344 101235 0x1801c277
+344 345 101096 0x1801c121
+345 346 101561 0x1801c0e3
+346 347 102143 0x1801c0db
+347 348 102236 0x1801bff2
+348 349 102877 0x1801c006
+349 350 103686 0x1801c065
+350 351 103842 0x1801bf99
+351 352 104588 0x1801bfdc
+352 353 105259 0x1801bffd
+353 354 105780 0x1801bfd8
+354 355 105569 0x1801be65
+355 356 106385 0x1801bec7
+356 357 106138 0x1801bd44
+357 358 105916 0x1801bbcd
+358 359 106049 0x1801baf9
+359 360 106756 0x1801bb29
+360 361 107253 0x1801bafa
+361 362 107709 0x1801bab8
+362 363 108165 0x1801ba77
+363 364 108789 0x1801ba83
+364 365 108836 0x1801b988
+365 366 108602 0x1801b80d
+366 367 108367 0x1801b696
+367 368 108523 0x1801b5ce
+368 369 109272 0x1801b611
+369 370 110157 0x1801b692
+370 371 110833 0x1801b6b3
+371 372 110976 0x1801b5e7
+372 373 110746 0x1801b472
+373 374 111641 0x1801b4f6
+374 375 111873 0x1801b451
+375 376 111825 0x1801b32f
+376 377 112406 0x1801b327
+377 378 112416 0x1801b220
+378 379 113140 0x1801b257
+379 380 113403 0x1801b1c2
+380 381 113418 0x1801b0be
+381 382 114174 0x1801b102
+382 383 114250 0x1801b01a
+383 384 115078 0x1801b07f
+384 385 114959 0x1801af41
+385 386 115420 0x1801af03
+386 387 115754 0x1801ae8d
+387 388 115625 0x1801ad4c
+388 389 116160 0x1801ad2f
+389 390 116327 0x1801ac70
+390 391 117137 0x1801accd
+391 392 117973 0x1801ad34
+392 393 117987 0x1801ac34
+393 394 118576 0x1801ac2f
+394 395 118484 0x1801aafe
+395 396 118857 0x1801aa9b
+396 397 119475 0x1801aaa3
+397 398 120281 0x1801aafe
+398 399 120659 0x1801aa9c
+399 400 121107 0x1801aa59
+400 401 121807 0x1801aa85
+401 402 122324 0x1801aa61
+402 403 123027 0x1801aa8f
+403 404 123233 0x1801a9e2
+404 405 123550 0x1801a966
+405 406 123952 0x1801a910
+406 407 124779 0x1801a974
+407 408 124707 0x1801a84e
+408 409 125199 0x1801a81f
+409 410 125634 0x1801a7d8
+410 411 125938 0x1801a757
+411 412 125653 0x1801a5d7
+412 413 125719 0x1801a4f1
+413 414 126189 0x1801a4b8
+414 415 127008 0x1801a518
+415 416 127593 0x1801a511
+416 417 128434 0x1801a579
+417 418 128954 0x1801a555
+418 419 129593 0x1801a567
+419 420 129600 0x1801a466
+420 421 130389 0x1801a4b8
+421 422 130909 0x1801a497
+422 423 130897 0x1801a38e
+423 424 131423 0x1801a36e
+424 425 131825 0x1801a318
+425 426 132665 0x1801a380
+426 427 132788 0x1801a2b2
+427 428 133348 0x1801a2a2
+428 429 133310 0x1801a191
+429 430 133053 0x1801a022
+430 431 132962 0x18019efb
+431 432 133722 0x18019f40
+432 433 133774 0x18019e56
+433 434 133806 0x18019d66
+434 435 134178 0x18019d05
+435 436 133966 0x18019bae
+436 437 134247 0x18019b26
+437 438 135008 0x18019b6b
+438 439 135749 0x18019ba7
+439 440 136537 0x18019bf6
+440 441 136606 0x18019b16
+441 442 136474 0x180199e2
+442 443 137255 0x18019a2e
+443 444 137394 0x1801996c
+444 445 137515 0x180198a2
+445 446 137751 0x1801980b
+446 447 137788 0x18019720
+447 448 138666 0x18019793
+448 449 138670 0x1801969a
+449 450 139435 0x180196e0
+450 451 140141 0x1801970c
+451 452 140845 0x18019737
+452 453 140651 0x180195ec
+453 454 140699 0x18019506
+454 455 141248 0x180194f1
+455 456 141041 0x180193a3
+456 457 141325 0x1801931f
+457 458 141406 0x1801924a
+458 459 141462 0x18019168
+459 460 141354 0x18019047
+460 461 141577 0x18018fac
+461 462 141801 0x18018f11
+462 463 141511 0x18018da6
+463 464 141781 0x18018d1f
+464 465 142602 0x18018d79
+465 466 142513 0x18018c60
+466 467 142329 0x18018b22
+467 468 143015 0x18018b45
+468 469 143201 0x18018a9e
+469 470 143465 0x18018a16
+470 471 144100 0x18018a24
+471 472 144113 0x18018936
+472 473 144259 0x18018880
+473 474 144903 0x18018891
+474 475 145171 0x1801880c
+475 476 145686 0x180187e9
+476 477 146413 0x1801881c
+477 478 146592 0x18018773
+478 479 146913 0x18018704
+479 480 147781 0x1801876f
+480 481 147497 0x1801860d
+481 482 148327 0x18018668
+482 483 148251 0x1801855b
+483 484 148015 0x1801840e
+484 485 147949 0x18018304
+485 486 148802 0x18018369
+486 487 148701 0x18018253
+487 488 148443 0x18018100
+488 489 148243 0x18017fc3
+489 490 149058 0x18018019
+490 491 149534 0x18017fe7
+491 492 150355 0x1801803f
+492 493 150134 0x18017efb
+493 494 150578 0x18017ebe
+494 495 150987 0x18017e74
+495 496 150915 0x18017d6b
+496 497 151369 0x18017d32
+497 498 151314 0x18017c33
+498 499 151391 0x18017b67
+499 500 151776 0x18017b12
+500 501 151798 0x18017a32
+501 502 151773 0x18017941
+502 503 151649 0x18017829
+503 504 152525 0x18017892
+504 505 152634 0x180177d5
+505 506 153329 0x180177fb
+506 507 153368 0x18017722
+507 508 153211 0x180175fe
+508 509 153807 0x180175fd
+509 510 153698 0x180174ee
+510 511 154349 0x18017501
+511 512 154059 0x180173ac
+512 513 154893 0x18017406
+513 514 155518 0x1801740f
+514 515 155934 0x180173ca
+515 516 155800 0x180172b1
+516 517 156150 0x18017253
+517 518 156781 0x1801725e
+518 519 156891 0x180171a5
+519 520 157404 0x18017183
+520 521 157412 0x180170a2
+521 522 157879 0x18017070
+522 523 158231 0x18017012
+523 524 158136 0x18016f0d
+524 525 158289 0x18016e64
+525 526 159128 0x18016ebe
+526 527 159579 0x18016e87
+527 528 159356 0x18016d4f
+528 529 159729 0x18016cfb
+529 530 159596 0x18016be8
+530 531 160403 0x18016c36
+531 532 160429 0x18016b5f
+532 533 160233 0x18016a36
+533 534 160625 0x180169e9
+534 535 161076 0x180169b2
+535 536 160894 0x18016890
+536 537 161553 0x180168a5
+537 538 161418 0x18016795
+538 539 162309 0x18016800
+539 540 162765 0x180167cc
+540 541 163435 0x180167e5
+541 542 163647 0x18016756
+542 543 163732 0x18016699
+543 544 164551 0x180166e9
+544 545 165037 0x180166c0
+545 546 164803 0x1801658b
+546 547 164865 0x180164c6
+547 548 164737 0x180163bb
+548 549 165458 0x180163e9
+549 550 165436 0x18016304
+550 551 165181 0x180161cd
+551 552 164892 0x1801608a
+552 553 165406 0x1801606b
+553 554 165627 0x18015fe1
+554 555 165615 0x18015f03
+555 556 166171 0x18015ef5
+556 557 166381 0x18015e68
+557 558 166933 0x18015e55
+558 559 167185 0x18015dd8
+559 560 167861 0x18015df4
+560 561 167606 0x18015cc1
+561 562 168366 0x18015cfb
+562 563 168642 0x18015c86
+563 564 168697 0x18015bc2
+564 565 168630 0x18015ad3
+565 566 169039 0x18015a90
+566 567 168832 0x18015970
+567 568 169716 0x180159d5
+568 569 169600 0x180158d7
+569 570 169906 0x1801586e
+570 571 170711 0x180158b7
+571 572 171336 0x180158bf
+572 573 171304 0x180157e0
+573 574 171759 0x180157ac
+574 575 171566 0x18015695
+575 576 172042 0x1801566a
+576 577 172470 0x1801562d
+577 578 173020 0x1801561c
+578 579 173716 0x1801563e
+579 580 174051 0x180155e1
+580 581 174724 0x180155fa
+581 582 175478 0x18015631
+582 583 175724 0x180155b5
+583 584 176432 0x180155da
+584 585 177061 0x180155e5
+585 586 177177 0x1801553b
+586 587 177339 0x180154a2
+587 588 177053 0x1801536d
+588 589 177513 0x1801533d
+589 590 177961 0x18015308
+590 591 177771 0x180151f5
+591 592 178420 0x18015207
+592 593 179226 0x1801524e
+593 594 180074 0x180152a4
+594 595 180571 0x18015280
+595 596 180916 0x18015228
+596 597 180882 0x1801514c
+597 598 180677 0x18015037
+598 599 181194 0x1801501a
+599 600 182038 0x1801506e
+600 601 181782 0x18014f47
+601 602 181553 0x18014e2c
+602 603 181606 0x18014d70
+603 604 182440 0x18014dc0
+604 605 182169 0x18014c97
+605 606 182859 0x18014cb5
+606 607 183389 0x18014c9d
+607 608 184117 0x18014cc9
+608 609 184016 0x18014bda
+609 610 184193 0x18014b4a
+610 611 184979 0x18014b89
+611 612 185340 0x18014b38
+612 613 185048 0x18014a0a
+613 614 185070 0x18014946
+614 615 185653 0x18014940
+615 616 186128 0x18014916
+616 617 186692 0x1801490a
+617 618 186626 0x1801482a
+618 619 187357 0x18014856
+619 620 188191 0x180148a5
+620 621 187977 0x18014793
+621 622 188620 0x180147a1
+622 623 188362 0x18014681
+623 624 188754 0x1801463c
+624 625 188695 0x18014560
+625 626 189527 0x180145ad
+626 627 190303 0x180145e7
+627 628 190473 0x18014558
+628 629 190617 0x180144c0
+629 630 190346 0x1801439e
+630 631 191013 0x180143b4
+631 632 191746 0x180143e1
+632 633 192222 0x180143b8
+633 634 192007 0x180142a9
+634 635 192477 0x1801427e
+635 636 192621 0x180141e7
+636 637 192328 0x180140c1
+637 638 192515 0x18014039
+638 639 193101 0x18014035
+639 640 193359 0x18013fc4
+640 641 193452 0x18013f1e
+641 642 193526 0x18013e71
+642 643 193272 0x18013d5b
+643 644 193768 0x18013d39
+644 645 194503 0x18013d65
+645 646 195032 0x18013d4e
+646 647 195810 0x18013d89
+647 648 196075 0x18013d1b
+648 649 196515 0x18013ce7
+649 650 197231 0x18013d0c
+650 651 198079 0x18013d5e
+651 652 198624 0x18013d4c
+652 653 198571 0x18013c78
+653 654 198670 0x18013bd5
+654 655 198378 0x18013ab3
+655 656 198898 0x18013a9a
+656 657 199342 0x18013a68
+657 658 199575 0x180139f2
+658 659 200350 0x18013a2a
+659 660 200530 0x180139a2
+660 661 201300 0x180139d9
+661 662 201732 0x180139a2
+662 663 201462 0x1801388b
+663 664 201357 0x180137a9
+664 665 201612 0x1801373b
+665 666 201778 0x180136b0
+666 667 202649 0x18013706
+667 668 202552 0x18013628
+668 669 203405 0x18013679
+669 670 203888 0x18013654
+670 671 204592 0x18013675
+671 672 205228 0x18013681
+672 673 205139 0x180135a5
+673 674 205398 0x18013539
+674 675 205809 0x180134fc
+675 676 205654 0x1801340e
+676 677 205990 0x180133ba
+677 678 206209 0x18013342
+678 679 206486 0x180132dc
+679 680 206800 0x18013282
+680 681 207279 0x1801325c
+681 682 207929 0x1801326c
+682 683 208569 0x18013278
+683 684 208591 0x180131c3
+684 685 208930 0x18013171
+685 686 209463 0x1801315b
+686 687 209808 0x1801310b
+687 688 209767 0x18013042
+688 689 209747 0x18012f81
+689 690 209925 0x18012efe
+690 691 209923 0x18012e42
+691 692 209807 0x18012d64
+692 693 209783 0x18012ca4
+693 694 209582 0x18012bac
+694 695 209507 0x18012add
+695 696 210083 0x18012ad6
+696 697 210349 0x18012a6f
+697 698 211097 0x18012a9c
+698 699 211417 0x18012a46
+699 700 211705 0x180129e7
+700 701 212539 0x18012a2e
+701 702 213232 0x18012a4b
+702 703 213766 0x18012a37
+703 704 213940 0x180129b5
+704 705 214395 0x18012988
+705 706 214862 0x1801295f
+706 707 215035 0x180128dd
+707 708 215308 0x18012879
+708 709 215276 0x180127b9
+709 710 215533 0x18012751
+710 711 215848 0x180126fa
+711 712 215758 0x1801262a
+712 713 215977 0x180125b7
+713 714 215933 0x180124f4
+714 715 216232 0x18012499
+715 716 217084 0x180124e6
+716 717 217382 0x1801248a
+717 718 217880 0x1801246c
+718 719 218590 0x1801248c
+719 720 218372 0x18012398
+720 721 218717 0x1801234a
+721 722 219321 0x1801234c
+722 723 219465 0x180122c4
+723 724 219534 0x18012226
+724 725 219397 0x1801214a
+725 726 219833 0x18012119
+726 727 220123 0x180120bd
+727 728 220177 0x1801201b
+728 729 221015 0x18012061
+729 730 221702 0x1801207b
+730 731 222268 0x18012072
+731 732 222171 0x18011fa3
+732 733 222380 0x18011f2f
+733 734 222957 0x18011f29
+734 735 223219 0x18011ec5
+735 736 223523 0x18011e6e
+736 737 223451 0x18011da8
+737 738 223273 0x18011cc4
+738 739 223525 0x18011c5e
+739 740 223560 0x18011bba
+740 741 224001 0x18011b8b
+741 742 223714 0x18011a89
+742 743 223762 0x180119e9
+743 744 224025 0x18011988
+744 745 224162 0x18011902
+745 746 224447 0x180118a7
+746 747 224441 0x180117f8
+747 748 224855 0x180117c2
+748 749 224736 0x180116f4
+749 750 224673 0x18011636
+750 751 224462 0x1801154e
+751 752 224349 0x18011483
+752 753 224820 0x1801145f
+753 754 225573 0x1801148a
+754 755 225560 0x180113dc
+755 756 225639 0x18011348
+756 757 225642 0x180112a0
+757 758 226288 0x180112ad
+758 759 226896 0x180112af
+759 760 226705 0x180111d0
+760 761 226894 0x1801115c
+761 762 227345 0x18011132
+762 763 227421 0x180110a0
+763 764 227816 0x18011066
+764 765 227865 0x18010fcc
+765 766 228457 0x18010fca
+766 767 229104 0x18010fd7
+767 768 229954 0x1801101d
+768 769 230055 0x18010f91
+769 770 229829 0x18010eaa
+770 771 229868 0x18010e10
+771 772 229940 0x18010d7d
+772 773 230326 0x18010d42
+773 774 231210 0x18010d90
+774 775 231050 0x18010cbe
+775 776 231440 0x18010c84
+776 777 231500 0x18010bef
+777 778 231656 0x18010b75
+778 779 232039 0x18010b3a
+779 780 232116 0x18010aaa
+780 781 232071 0x180109f9
+781 782 232431 0x180109b8
+782 783 233040 0x180109ba
+783 784 232972 0x18010904
+784 785 233661 0x1801091c
+785 786 233912 0x180108bd
+786 787 233761 0x180107f1
+787 788 234261 0x180107d6
+788 789 234319 0x18010743
+789 790 234740 0x18010712
+790 791 235326 0x1801070f
+791 792 235762 0x180106e2
+792 793 235480 0x180105f5
+793 794 235272 0x1801051b
+794 795 235368 0x18010494
+795 796 235887 0x1801047f
+796 797 235668 0x180103a4
+797 798 235463 0x180102cd
+798 799 235897 0x180102a1
+799 800 236017 0x18010222
+800 801 236085 0x18010194
+801 802 236362 0x1801013f
+802 803 236126 0x18010062
+803 804 235911 0x1800ff8c
+804 805 236796 0x1800ffd6
+805 806 237218 0x1800ffa8
+806 807 237126 0x1800fef2
+807 808 237253 0x1800fe77
+808 809 238102 0x1800feb8
+809 810 238176 0x1800fe2e
+810 811 238920 0x1800fe54
+811 812 239004 0x1800fdcd
+812 813 239098 0x1800fd4a
+813 814 239796 0x1800fd63
+814 815 239748 0x1800fcba
+815 816 240368 0x1800fcc0
+816 817 241065 0x1800fcd9
+817 818 240875 0x1800fc0c
+818 819 241058 0x1800fba0
+819 820 241799 0x1800fbc4
+820 821 242428 0x1800fbcc
+821 822 242274 0x1800fb09
+822 823 242943 0x1800fb1c
+823 824 243524 0x1800fb16
+824 825 243498 0x1800fa75
+825 826 243335 0x1800f9b1
+826 827 243089 0x1800f8d8
+827 828 243216 0x1800f85f
+828 829 243103 0x1800f7a9
+829 830 243801 0x1800f7c2
+830 831 243557 0x1800f6ec
+831 832 243639 0x1800f669
+832 833 244419 0x1800f696
+833 834 244131 0x1800f5b6
+834 835 244258 0x1800f53f
+835 836 245025 0x1800f569
+836 837 245735 0x1800f585
+837 838 246049 0x1800f53c
+838 839 246190 0x1800f4c9
+839 840 246463 0x1800f476
+840 841 246179 0x1800f399
+841 842 246510 0x1800f356
+842 843 247162 0x1800f363
+843 844 247122 0x1800f2c3
+844 845 247736 0x1800f2c6
+845 846 247949 0x1800f266
+846 847 248502 0x1800f25a
+847 848 249063 0x1800f250
+848 849 249494 0x1800f227
+849 850 250335 0x1800f262
+850 851 250795 0x1800f240
+851 852 251316 0x1800f22c
+852 853 251598 0x1800f1dc
+853 854 252340 0x1800f200
+854 855 252895 0x1800f1f5
+855 856 253006 0x1800f17c
+856 857 253621 0x1800f180
+857 858 254508 0x1800f1c6
+858 859 254766 0x1800f172
+859 860 254619 0x1800f0b9
+860 861 254870 0x1800f063
+861 862 255300 0x1800f038
+862 863 255809 0x1800f022
+863 864 255590 0x1800ef58
+864 865 256461 0x1800ef9b
+865 866 256686 0x1800ef3f
+866 867 257249 0x1800ef36
+867 868 257442 0x1800eed2
+868 869 257726 0x1800ee84
+869 870 257866 0x1800ee14
+870 871 257926 0x1800ed90
+871 872 258786 0x1800edcf
+872 873 259075 0x1800ed83
+873 874 259593 0x1800ed6f
+874 875 259728 0x1800ecff
+875 876 259765 0x1800ec75
+876 877 260127 0x1800ec3c
+877 878 260232 0x1800ebc3
+878 879 261088 0x1800ec02
+879 880 261375 0x1800ebb6
+880 881 262144 0x1800ebdf
+881 882 262808 0x1800ebee
+882 883 263025 0x1800eb92
+883 884 262829 0x1800ead2
+884 885 263530 0x1800eaea
+885 886 263787 0x1800ea97
+886 887 263654 0x1800e9e7
+887 888 263620 0x1800e94e
+888 889 264365 0x1800e971
+889 890 264917 0x1800e966
+890 891 264742 0x1800e8ac
+891 892 265373 0x1800e8b4
+892 893 265080 0x1800e7df
+893 894 265543 0x1800e7be
+894 895 265337 0x1800e6ff
+895 896 265418 0x1800e684
+896 897 266029 0x1800e686
+897 898 265965 0x1800e5e9
+898 899 265892 0x1800e54b
+899 900 266131 0x1800e4f6
+900 901 266653 0x1800e4e4
+901 902 267080 0x1800e4bb
+902 903 266974 0x1800e416
+903 904 267091 0x1800e3a5
+904 905 267526 0x1800e37e
+905 906 267325 0x1800e2c4
+906 907 267711 0x1800e291
+907 908 268093 0x1800e25f
+908 909 268256 0x1800e1f9
+909 910 268156 0x1800e158
+910 911 268205 0x1800e0d8
+911 912 268587 0x1800e0a6
+912 913 268535 0x1800e010
+913 914 268789 0x1800dfc0
+914 915 268632 0x1800df13
+915 916 269428 0x1800df40
+916 917 269331 0x1800dea0
+917 918 269378 0x1800de22
+918 919 269204 0x1800dd72
+919 920 269099 0x1800dcd2
+920 921 269884 0x1800dcfc
+921 922 269947 0x1800dc82
+922 923 270104 0x1800dc1e
+923 924 270874 0x1800dc45
+924 925 270786 0x1800dba9
+925 926 271160 0x1800db76
+926 927 271362 0x1800db1c
+927 928 271659 0x1800dad9
+928 929 272018 0x1800daa3
+929 930 272433 0x1800da79
+930 931 273008 0x1800da73
+931 932 273056 0x1800d9f8
+932 933 273012 0x1800d968
+933 934 273362 0x1800d931
+934 935 273706 0x1800d8f8
+935 936 274465 0x1800d91b
+936 937 274703 0x1800d8ca
+937 938 275506 0x1800d8f8
+938 939 276199 0x1800d90c
+939 940 276687 0x1800d8f3
+940 941 277331 0x1800d8fd
+941 942 277822 0x1800d8e5
+942 943 277862 0x1800d868
+943 944 278099 0x1800d818
+944 945 277897 0x1800d767
+945 946 277826 0x1800d6d2
+946 947 278318 0x1800d6ba
+947 948 278221 0x1800d621
+948 949 278603 0x1800d5f1
+949 950 278768 0x1800d592
+950 951 279309 0x1800d585
+951 952 279862 0x1800d57b
+952 953 280580 0x1800d594
+953 954 280418 0x1800d4ee
+954 955 280759 0x1800d4b5
+955 956 281270 0x1800d4a2
+956 957 281030 0x1800d3ea
+957 958 281592 0x1800d3e3
+958 959 281971 0x1800d3b3
+959 960 282758 0x1800d3db
+960 961 282510 0x1800d323
+961 962 283338 0x1800d355
+962 963 284029 0x1800d368
+963 964 283894 0x1800d2c9
+964 965 284258 0x1800d296
+965 966 284011 0x1800d1e0
+966 967 284474 0x1800d1c2
+967 968 285234 0x1800d1e4
+968 969 285138 0x1800d14e
+969 970 285178 0x1800d0d7
+970 971 285432 0x1800d08d
+971 972 285202 0x1800cfdb
+972 973 285799 0x1800cfdb
+973 974 286639 0x1800d00e
+974 975 286862 0x1800cfbe
+975 976 287009 0x1800cf5d
+976 977 287866 0x1800cf94
+977 978 288151 0x1800cf50
+978 979 288077 0x1800cec2
+979 980 288608 0x1800ceb3
+980 981 289036 0x1800ce8f
+981 982 289751 0x1800cea7
+982 983 289547 0x1800cdfd
+983 984 289568 0x1800cd82
+984 985 289563 0x1800cd03
+985 986 290295 0x1800cd1f
+986 987 290354 0x1800ccae
+987 988 290267 0x1800cc1d
+988 989 290973 0x1800cc33
+989 990 290783 0x1800cb8e
+990 991 291640 0x1800cbc4
+991 992 292311 0x1800cbd3
+992 993 292068 0x1800cb22
+993 994 292691 0x1800cb28
+994 995 293156 0x1800cb0c
+995 996 292897 0x1800ca59
+996 997 293056 0x1800c9fd
+997 998 293558 0x1800c9e9
+998 999 293356 0x1800c943
+999 1000 293956 0x1800c943
+1000 1001 293713 0x1800c896
+
diff --git a/data/test_vectors_aserti3-2d_run12.txt b/data/test_vectors_aserti3-2d_run12.txt
new file mode 100644
--- /dev/null
+++ b/data/test_vectors_aserti3-2d_run12.txt
@@ -0,0 +1,10009 @@
+## description: run12 - each block 1 second before the previous one
+##   anchor height: 1
+##   anchor parent time: 10000
+##   anchor nBits: 0x1802aee8
+##   start height: 2
+##   start time: 11200
+##   iterations: 10000
+# iteration,height,time,target
+1 2 11200 0x1802aee8
+2 3 11199 0x1802ad44
+3 4 11198 0x1802ab9e
+4 5 11197 0x1802a9f9
+5 6 11196 0x1802a855
+6 7 11195 0x1802a6b3
+7 8 11194 0x1802a511
+8 9 11193 0x1802a371
+9 10 11192 0x1802a1d1
+10 11 11191 0x1802a033
+11 12 11190 0x18029e94
+12 13 11189 0x18029cf8
+13 14 11188 0x18029b5d
+14 15 11187 0x180299c1
+15 16 11186 0x18029828
+16 17 11185 0x1802968e
+17 18 11184 0x180294f8
+18 19 11183 0x18029361
+19 20 11182 0x180291cb
+20 21 11181 0x18029036
+21 22 11180 0x18028ea2
+22 23 11179 0x18028d0f
+23 24 11178 0x18028b7d
+24 25 11177 0x180289ec
+25 26 11176 0x1802885b
+26 27 11175 0x180286cb
+27 28 11174 0x1802853c
+28 29 11173 0x180283af
+29 30 11172 0x18028222
+30 31 11171 0x18028096
+31 32 11170 0x18027f0c
+32 33 11169 0x18027d84
+33 34 11168 0x18027bfb
+34 35 11167 0x18027a73
+35 36 11166 0x180278ec
+36 37 11165 0x18027765
+37 38 11164 0x180275e0
+38 39 11163 0x1802745c
+39 40 11162 0x180272d9
+40 41 11161 0x18027156
+41 42 11160 0x18026fd5
+42 43 11159 0x18026e54
+43 44 11158 0x18026cd4
+44 45 11157 0x18026b56
+45 46 11156 0x180269d8
+46 47 11155 0x1802685b
+47 48 11154 0x180266df
+48 49 11153 0x18026565
+49 50 11152 0x180263ea
+50 51 11151 0x18026271
+51 52 11150 0x180260f8
+52 53 11149 0x18025f81
+53 54 11148 0x18025e0a
+54 55 11147 0x18025c94
+55 56 11146 0x18025b1f
+56 57 11145 0x180259ab
+57 58 11144 0x18025838
+58 59 11143 0x180256c6
+59 60 11142 0x18025555
+60 61 11141 0x180253e4
+61 62 11140 0x18025274
+62 63 11139 0x18025104
+63 64 11138 0x18024f99
+64 65 11137 0x18024e2c
+65 66 11136 0x18024cbf
+66 67 11135 0x18024b55
+67 68 11134 0x180249e9
+68 69 11133 0x18024880
+69 70 11132 0x18024717
+70 71 11131 0x180245b0
+71 72 11130 0x18024448
+72 73 11129 0x180242e2
+73 74 11128 0x1802417d
+74 75 11127 0x18024018
+75 76 11126 0x18023eb5
+76 77 11125 0x18023d53
+77 78 11124 0x18023bf1
+78 79 11123 0x18023a91
+79 80 11122 0x18023932
+80 81 11121 0x180237d2
+81 82 11120 0x18023674
+82 83 11119 0x18023516
+83 84 11118 0x180233b9
+84 85 11117 0x1802325d
+85 86 11116 0x18023102
+86 87 11115 0x18022fa8
+87 88 11114 0x18022e4e
+88 89 11113 0x18022cf5
+89 90 11112 0x18022b9d
+90 91 11111 0x18022a47
+91 92 11110 0x180228f1
+92 93 11109 0x1802279b
+93 94 11108 0x18022646
+94 95 11107 0x180224f4
+95 96 11106 0x180223a2
+96 97 11105 0x18022250
+97 98 11104 0x180220fe
+98 99 11103 0x18021faf
+99 100 11102 0x18021e5e
+100 101 11101 0x18021d10
+101 102 11100 0x18021bc2
+102 103 11099 0x18021a75
+103 104 11098 0x18021928
+104 105 11097 0x180217dd
+105 106 11096 0x18021691
+106 107 11095 0x18021547
+107 108 11094 0x180213ff
+108 109 11093 0x180212b6
+109 110 11092 0x18021170
+110 111 11091 0x1802102a
+111 112 11090 0x18020ee4
+112 113 11089 0x18020d9e
+113 114 11088 0x18020c59
+114 115 11087 0x18020b16
+115 116 11086 0x180209d4
+116 117 11085 0x18020892
+117 118 11084 0x18020750
+118 119 11083 0x1802060f
+119 120 11082 0x180204d0
+120 121 11081 0x18020391
+121 122 11080 0x18020253
+122 123 11079 0x18020116
+123 124 11078 0x1801ffda
+124 125 11077 0x1801fe9e
+125 126 11076 0x1801fd63
+126 127 11075 0x1801fc29
+127 128 11074 0x1801faef
+128 129 11073 0x1801f9b7
+129 130 11072 0x1801f87f
+130 131 11071 0x1801f748
+131 132 11070 0x1801f611
+132 133 11069 0x1801f4db
+133 134 11068 0x1801f3a6
+134 135 11067 0x1801f272
+135 136 11066 0x1801f13e
+136 137 11065 0x1801f00b
+137 138 11064 0x1801eed9
+138 139 11063 0x1801eda9
+139 140 11062 0x1801ec78
+140 141 11061 0x1801eb49
+141 142 11060 0x1801ea1a
+142 143 11059 0x1801e8ec
+143 144 11058 0x1801e7be
+144 145 11057 0x1801e692
+145 146 11056 0x1801e565
+146 147 11055 0x1801e43a
+147 148 11054 0x1801e310
+148 149 11053 0x1801e1e6
+149 150 11052 0x1801e0bc
+150 151 11051 0x1801df94
+151 152 11050 0x1801de6d
+152 153 11049 0x1801dd46
+153 154 11048 0x1801dc1e
+154 155 11047 0x1801dafa
+155 156 11046 0x1801d9d6
+156 157 11045 0x1801d8b1
+157 158 11044 0x1801d78e
+158 159 11043 0x1801d66c
+159 160 11042 0x1801d549
+160 161 11041 0x1801d429
+161 162 11040 0x1801d308
+162 163 11039 0x1801d1e8
+163 164 11038 0x1801d0c9
+164 165 11037 0x1801cfa9
+165 166 11036 0x1801ce8c
+166 167 11035 0x1801cd6f
+167 168 11034 0x1801cc53
+168 169 11033 0x1801cb36
+169 170 11032 0x1801ca1b
+170 171 11031 0x1801c903
+171 172 11030 0x1801c7e9
+172 173 11029 0x1801c6cf
+173 174 11028 0x1801c5b7
+174 175 11027 0x1801c4a0
+175 176 11026 0x1801c389
+176 177 11025 0x1801c273
+177 178 11024 0x1801c15e
+178 179 11023 0x1801c048
+179 180 11022 0x1801bf35
+180 181 11021 0x1801be20
+181 182 11020 0x1801bd0d
+182 183 11019 0x1801bbfc
+183 184 11018 0x1801baea
+184 185 11017 0x1801b9da
+185 186 11016 0x1801b8c9
+186 187 11015 0x1801b7ba
+187 188 11014 0x1801b6ab
+188 189 11013 0x1801b59e
+189 190 11012 0x1801b490
+190 191 11011 0x1801b382
+191 192 11010 0x1801b276
+192 193 11009 0x1801b16b
+193 194 11008 0x1801b060
+194 195 11007 0x1801af55
+195 196 11006 0x1801ae4b
+196 197 11005 0x1801ad43
+197 198 11004 0x1801ac3b
+198 199 11003 0x1801ab32
+199 200 11002 0x1801aa2c
+200 201 11001 0x1801a925
+201 202 11000 0x1801a820
+202 203 10999 0x1801a71b
+203 204 10998 0x1801a616
+204 205 10997 0x1801a512
+205 206 10996 0x1801a40f
+206 207 10995 0x1801a30c
+207 208 10994 0x1801a20b
+208 209 10993 0x1801a109
+209 210 10992 0x1801a009
+210 211 10991 0x18019f09
+211 212 10990 0x18019e08
+212 213 10989 0x18019d09
+213 214 10988 0x18019c0b
+214 215 10987 0x18019b0d
+215 216 10986 0x18019a11
+216 217 10985 0x18019915
+217 218 10984 0x18019818
+218 219 10983 0x1801971d
+219 220 10982 0x18019623
+220 221 10981 0x18019529
+221 222 10980 0x1801942f
+222 223 10979 0x18019336
+223 224 10978 0x1801923e
+224 225 10977 0x18019146
+225 226 10976 0x1801904f
+226 227 10975 0x18018f59
+227 228 10974 0x18018e62
+228 229 10973 0x18018d6d
+229 230 10972 0x18018c79
+230 231 10971 0x18018b84
+231 232 10970 0x18018a90
+232 233 10969 0x1801899f
+233 234 10968 0x180188ac
+234 235 10967 0x180187ba
+235 236 10966 0x180186c9
+236 237 10965 0x180185d9
+237 238 10964 0x180184e9
+238 239 10963 0x180183f9
+239 240 10962 0x1801830a
+240 241 10961 0x1801821b
+241 242 10960 0x1801812d
+242 243 10959 0x18018040
+243 244 10958 0x18017f54
+244 245 10957 0x18017e68
+245 246 10956 0x18017d7c
+246 247 10955 0x18017c91
+247 248 10954 0x18017ba7
+248 249 10953 0x18017abd
+249 250 10952 0x180179d3
+250 251 10951 0x180178eb
+251 252 10950 0x18017803
+252 253 10949 0x1801771b
+253 254 10948 0x18017634
+254 255 10947 0x1801754d
+255 256 10946 0x18017467
+256 257 10945 0x18017381
+257 258 10944 0x1801729c
+258 259 10943 0x180171b8
+259 260 10942 0x180170d4
+260 261 10941 0x18016ff0
+261 262 10940 0x18016f0d
+262 263 10939 0x18016e2b
+263 264 10938 0x18016d4a
+264 265 10937 0x18016c69
+265 266 10936 0x18016b87
+266 267 10935 0x18016aa7
+267 268 10934 0x180169c7
+268 269 10933 0x180168e8
+269 270 10932 0x1801680a
+270 271 10931 0x1801672b
+271 272 10930 0x1801664e
+272 273 10929 0x18016570
+273 274 10928 0x18016494
+274 275 10927 0x180163b8
+275 276 10926 0x180162dc
+276 277 10925 0x18016202
+277 278 10924 0x18016127
+278 279 10923 0x1801604d
+279 280 10922 0x18015f73
+280 281 10921 0x18015e9b
+281 282 10920 0x18015dc1
+282 283 10919 0x18015ce9
+283 284 10918 0x18015c11
+284 285 10917 0x18015b3b
+285 286 10916 0x18015a64
+286 287 10915 0x1801598d
+287 288 10914 0x180158b8
+288 289 10913 0x180157e3
+289 290 10912 0x1801570f
+290 291 10911 0x1801563c
+291 292 10910 0x18015569
+292 293 10909 0x18015497
+293 294 10908 0x180153c6
+294 295 10907 0x180152f6
+295 296 10906 0x18015225
+296 297 10905 0x18015154
+297 298 10904 0x18015085
+298 299 10903 0x18014fb6
+299 300 10902 0x18014ee7
+300 301 10901 0x18014e19
+301 302 10900 0x18014d4b
+302 303 10899 0x18014c7e
+303 304 10898 0x18014bb2
+304 305 10897 0x18014ae6
+305 306 10896 0x18014a1a
+306 307 10895 0x1801494e
+307 308 10894 0x18014884
+308 309 10893 0x180147ba
+309 310 10892 0x180146f0
+310 311 10891 0x18014627
+311 312 10890 0x1801455e
+312 313 10889 0x18014496
+313 314 10888 0x180143ce
+314 315 10887 0x18014306
+315 316 10886 0x1801423f
+316 317 10885 0x18014179
+317 318 10884 0x180140b2
+318 319 10883 0x18013fed
+319 320 10882 0x18013f28
+320 321 10881 0x18013e63
+321 322 10880 0x18013d9f
+322 323 10879 0x18013cdb
+323 324 10878 0x18013c18
+324 325 10877 0x18013b56
+325 326 10876 0x18013a93
+326 327 10875 0x180139d1
+327 328 10874 0x1801390f
+328 329 10873 0x1801384f
+329 330 10872 0x1801378e
+330 331 10871 0x180136ce
+331 332 10870 0x1801360e
+332 333 10869 0x1801354f
+333 334 10868 0x18013490
+334 335 10867 0x180133d2
+335 336 10866 0x18013314
+336 337 10865 0x18013257
+337 338 10864 0x1801319a
+338 339 10863 0x180130dd
+339 340 10862 0x18013022
+340 341 10861 0x18012f66
+341 342 10860 0x18012eab
+342 343 10859 0x18012df1
+343 344 10858 0x18012d36
+344 345 10857 0x18012c7c
+345 346 10856 0x18012bc3
+346 347 10855 0x18012b0a
+347 348 10854 0x18012a52
+348 349 10853 0x1801299a
+349 350 10852 0x180128e2
+350 351 10851 0x1801282b
+351 352 10850 0x18012774
+352 353 10849 0x180126be
+353 354 10848 0x18012608
+354 355 10847 0x18012553
+355 356 10846 0x1801249e
+356 357 10845 0x180123e9
+357 358 10844 0x18012336
+358 359 10843 0x18012282
+359 360 10842 0x180121cf
+360 361 10841 0x1801211c
+361 362 10840 0x18012069
+362 363 10839 0x18011fb7
+363 364 10838 0x18011f06
+364 365 10837 0x18011e55
+365 366 10836 0x18011da3
+366 367 10835 0x18011cf3
+367 368 10834 0x18011c43
+368 369 10833 0x18011b94
+369 370 10832 0x18011ae5
+370 371 10831 0x18011a37
+371 372 10830 0x18011989
+372 373 10829 0x180118db
+373 374 10828 0x1801182e
+374 375 10827 0x18011781
+375 376 10826 0x180116d4
+376 377 10825 0x18011628
+377 378 10824 0x1801157d
+378 379 10823 0x180114d2
+379 380 10822 0x18011426
+380 381 10821 0x1801137c
+381 382 10820 0x180112d2
+382 383 10819 0x18011228
+383 384 10818 0x1801117f
+384 385 10817 0x180110d6
+385 386 10816 0x1801102e
+386 387 10815 0x18010f87
+387 388 10814 0x18010edf
+388 389 10813 0x18010e38
+389 390 10812 0x18010d91
+390 391 10811 0x18010cea
+391 392 10810 0x18010c45
+392 393 10809 0x18010b9f
+393 394 10808 0x18010afa
+394 395 10807 0x18010a55
+395 396 10806 0x180109b1
+396 397 10805 0x1801090c
+397 398 10804 0x18010869
+398 399 10803 0x180107c6
+399 400 10802 0x18010723
+400 401 10801 0x18010682
+401 402 10800 0x180105df
+402 403 10799 0x1801053e
+403 404 10798 0x1801049d
+404 405 10797 0x180103fc
+405 406 10796 0x1801035b
+406 407 10795 0x180102bb
+407 408 10794 0x1801021b
+408 409 10793 0x1801017c
+409 410 10792 0x180100dd
+410 411 10791 0x1801003f
+411 412 10790 0x1800ffa1
+412 413 10789 0x1800ff03
+413 414 10788 0x1800fe65
+414 415 10787 0x1800fdc8
+415 416 10786 0x1800fd2c
+416 417 10785 0x1800fc91
+417 418 10784 0x1800fbf5
+418 419 10783 0x1800fb59
+419 420 10782 0x1800fabe
+420 421 10781 0x1800fa23
+421 422 10780 0x1800f989
+422 423 10779 0x1800f8ef
+423 424 10778 0x1800f856
+424 425 10777 0x1800f7bc
+425 426 10776 0x1800f723
+426 427 10775 0x1800f68b
+427 428 10774 0x1800f5f3
+428 429 10773 0x1800f55b
+429 430 10772 0x1800f4c4
+430 431 10771 0x1800f42d
+431 432 10770 0x1800f397
+432 433 10769 0x1800f301
+433 434 10768 0x1800f26b
+434 435 10767 0x1800f1d6
+435 436 10766 0x1800f140
+436 437 10765 0x1800f0ac
+437 438 10764 0x1800f017
+438 439 10763 0x1800ef83
+439 440 10762 0x1800eeef
+440 441 10761 0x1800ee5c
+441 442 10760 0x1800edc9
+442 443 10759 0x1800ed36
+443 444 10758 0x1800eca4
+444 445 10757 0x1800ec13
+445 446 10756 0x1800eb81
+446 447 10755 0x1800eaf0
+447 448 10754 0x1800ea5f
+448 449 10753 0x1800e9cf
+449 450 10752 0x1800e93f
+450 451 10751 0x1800e8af
+451 452 10750 0x1800e820
+452 453 10749 0x1800e790
+453 454 10748 0x1800e702
+454 455 10747 0x1800e673
+455 456 10746 0x1800e5e5
+456 457 10745 0x1800e557
+457 458 10744 0x1800e4ca
+458 459 10743 0x1800e43d
+459 460 10742 0x1800e3b1
+460 461 10741 0x1800e324
+461 462 10740 0x1800e298
+462 463 10739 0x1800e20d
+463 464 10738 0x1800e182
+464 465 10737 0x1800e0f7
+465 466 10736 0x1800e06c
+466 467 10735 0x1800dfe2
+467 468 10734 0x1800df58
+468 469 10733 0x1800dece
+469 470 10732 0x1800de45
+470 471 10731 0x1800ddbc
+471 472 10730 0x1800dd34
+472 473 10729 0x1800dcac
+473 474 10728 0x1800dc23
+474 475 10727 0x1800db9c
+475 476 10726 0x1800db14
+476 477 10725 0x1800da8d
+477 478 10724 0x1800da07
+478 479 10723 0x1800d981
+479 480 10722 0x1800d8fb
+480 481 10721 0x1800d876
+481 482 10720 0x1800d7f0
+482 483 10719 0x1800d76b
+483 484 10718 0x1800d6e6
+484 485 10717 0x1800d662
+485 486 10716 0x1800d5de
+486 487 10715 0x1800d55a
+487 488 10714 0x1800d4d6
+488 489 10713 0x1800d454
+489 490 10712 0x1800d3d1
+490 491 10711 0x1800d34f
+491 492 10710 0x1800d2cd
+492 493 10709 0x1800d24b
+493 494 10708 0x1800d1ca
+494 495 10707 0x1800d148
+495 496 10706 0x1800d0c7
+496 497 10705 0x1800d047
+497 498 10704 0x1800cfc6
+498 499 10703 0x1800cf46
+499 500 10702 0x1800cec7
+500 501 10701 0x1800ce47
+501 502 10700 0x1800cdc9
+502 503 10699 0x1800cd4a
+503 504 10698 0x1800cccb
+504 505 10697 0x1800cc4d
+505 506 10696 0x1800cbd0
+506 507 10695 0x1800cb52
+507 508 10694 0x1800cad5
+508 509 10693 0x1800ca59
+509 510 10692 0x1800c9dc
+510 511 10691 0x1800c960
+511 512 10690 0x1800c8e4
+512 513 10689 0x1800c867
+513 514 10688 0x1800c7ec
+514 515 10687 0x1800c771
+515 516 10686 0x1800c6f7
+516 517 10685 0x1800c67c
+517 518 10684 0x1800c602
+518 519 10683 0x1800c588
+519 520 10682 0x1800c50e
+520 521 10681 0x1800c495
+521 522 10680 0x1800c41b
+522 523 10679 0x1800c3a3
+523 524 10678 0x1800c32b
+524 525 10677 0x1800c2b2
+525 526 10676 0x1800c23a
+526 527 10675 0x1800c1c3
+527 528 10674 0x1800c14c
+528 529 10673 0x1800c0d4
+529 530 10672 0x1800c05d
+530 531 10671 0x1800bfe7
+531 532 10670 0x1800bf71
+532 533 10669 0x1800befb
+533 534 10668 0x1800be85
+534 535 10667 0x1800be10
+535 536 10666 0x1800bd9a
+536 537 10665 0x1800bd26
+537 538 10664 0x1800bcb2
+538 539 10663 0x1800bc3e
+539 540 10662 0x1800bbca
+540 541 10661 0x1800bb55
+541 542 10660 0x1800bae2
+542 543 10659 0x1800ba6f
+543 544 10658 0x1800b9fc
+544 545 10657 0x1800b989
+545 546 10656 0x1800b917
+546 547 10655 0x1800b8a5
+547 548 10654 0x1800b833
+548 549 10653 0x1800b7c1
+549 550 10652 0x1800b750
+550 551 10651 0x1800b6df
+551 552 10650 0x1800b66e
+552 553 10649 0x1800b5fe
+553 554 10648 0x1800b58e
+554 555 10647 0x1800b51e
+555 556 10646 0x1800b4ae
+556 557 10645 0x1800b43f
+557 558 10644 0x1800b3d0
+558 559 10643 0x1800b360
+559 560 10642 0x1800b2f2
+560 561 10641 0x1800b284
+561 562 10640 0x1800b215
+562 563 10639 0x1800b1a7
+563 564 10638 0x1800b13a
+564 565 10637 0x1800b0cc
+565 566 10636 0x1800b05f
+566 567 10635 0x1800aff1
+567 568 10634 0x1800af85
+568 569 10633 0x1800af19
+569 570 10632 0x1800aead
+570 571 10631 0x1800ae41
+571 572 10630 0x1800add5
+572 573 10629 0x1800ad69
+573 574 10628 0x1800acfe
+574 575 10627 0x1800ac94
+575 576 10626 0x1800ac29
+576 577 10625 0x1800abbe
+577 578 10624 0x1800ab55
+578 579 10623 0x1800aaeb
+579 580 10622 0x1800aa82
+580 581 10621 0x1800aa19
+581 582 10620 0x1800a9b1
+582 583 10619 0x1800a948
+583 584 10618 0x1800a8e0
+584 585 10617 0x1800a878
+585 586 10616 0x1800a811
+586 587 10615 0x1800a7a9
+587 588 10614 0x1800a742
+588 589 10613 0x1800a6db
+589 590 10612 0x1800a675
+590 591 10611 0x1800a60e
+591 592 10610 0x1800a5a8
+592 593 10609 0x1800a542
+593 594 10608 0x1800a4dc
+594 595 10607 0x1800a476
+595 596 10606 0x1800a411
+596 597 10605 0x1800a3ac
+597 598 10604 0x1800a347
+598 599 10603 0x1800a2e3
+599 600 10602 0x1800a27e
+600 601 10601 0x1800a21b
+601 602 10600 0x1800a1b7
+602 603 10599 0x1800a153
+603 604 10598 0x1800a0f0
+604 605 10597 0x1800a08d
+605 606 10596 0x1800a02a
+606 607 10595 0x18009fc7
+607 608 10594 0x18009f64
+608 609 10593 0x18009f02
+609 610 10592 0x18009ea0
+610 611 10591 0x18009e3f
+611 612 10590 0x18009ddd
+612 613 10589 0x18009d7c
+613 614 10588 0x18009d1a
+614 615 10587 0x18009cba
+615 616 10586 0x18009c5a
+616 617 10585 0x18009bf9
+617 618 10584 0x18009b99
+618 619 10583 0x18009b39
+619 620 10582 0x18009ad9
+620 621 10581 0x18009a7a
+621 622 10580 0x18009a1a
+622 623 10579 0x180099bb
+623 624 10578 0x1800995c
+624 625 10577 0x180098fe
+625 626 10576 0x180098a0
+626 627 10575 0x18009841
+627 628 10574 0x180097e3
+628 629 10573 0x18009786
+629 630 10572 0x18009728
+630 631 10571 0x180096cb
+631 632 10570 0x1800966f
+632 633 10569 0x18009612
+633 634 10568 0x180095b5
+634 635 10567 0x18009559
+635 636 10566 0x180094fd
+636 637 10565 0x180094a1
+637 638 10564 0x18009445
+638 639 10563 0x180093ea
+639 640 10562 0x1800938e
+640 641 10561 0x18009333
+641 642 10560 0x180092d8
+642 643 10559 0x1800927e
+643 644 10558 0x18009223
+644 645 10557 0x180091c9
+645 646 10556 0x1800916f
+646 647 10555 0x18009116
+647 648 10554 0x180090bc
+648 649 10553 0x18009063
+649 650 10552 0x1800900a
+650 651 10551 0x18008fb1
+651 652 10550 0x18008f58
+652 653 10549 0x18008f00
+653 654 10548 0x18008ea8
+654 655 10547 0x18008e4f
+655 656 10546 0x18008df7
+656 657 10545 0x18008da0
+657 658 10544 0x18008d48
+658 659 10543 0x18008cf1
+659 660 10542 0x18008c9a
+660 661 10541 0x18008c43
+661 662 10540 0x18008bed
+662 663 10539 0x18008b97
+663 664 10538 0x18008b41
+664 665 10537 0x18008aeb
+665 666 10536 0x18008a95
+666 667 10535 0x18008a40
+667 668 10534 0x180089ea
+668 669 10533 0x18008995
+669 670 10532 0x18008940
+670 671 10531 0x180088ec
+671 672 10530 0x18008897
+672 673 10529 0x18008842
+673 674 10528 0x180087ef
+674 675 10527 0x1800879a
+675 676 10526 0x18008747
+676 677 10525 0x180086f3
+677 678 10524 0x180086a1
+678 679 10523 0x1800864d
+679 680 10522 0x180085fb
+680 681 10521 0x180085a8
+681 682 10520 0x18008555
+682 683 10519 0x18008503
+683 684 10518 0x180084b1
+684 685 10517 0x1800845f
+685 686 10516 0x1800840d
+686 687 10515 0x180083bc
+687 688 10514 0x1800836a
+688 689 10513 0x18008319
+689 690 10512 0x180082c8
+690 691 10511 0x18008278
+691 692 10510 0x18008227
+692 693 10509 0x180081d7
+693 694 10508 0x18008187
+694 695 10507 0x18008137
+695 696 10506 0x180080e8
+696 697 10505 0x18008098
+697 698 10504 0x18008049
+698 699 10503 0x177ff9df
+699 700 10502 0x177faab7
+700 701 10501 0x177f5c3b
+701 702 10500 0x177f0d69
+702 703 10499 0x177ebeed
+703 704 10498 0x177e70c7
+704 705 10497 0x177e22f7
+705 706 10496 0x177dd527
+706 707 10495 0x177d8756
+707 708 10494 0x177d3a32
+708 709 10493 0x177ced0d
+709 710 10492 0x177c9fe9
+710 711 10491 0x177c531a
+711 712 10490 0x177c064b
+712 713 10489 0x177bb9d2
+713 714 10488 0x177b6db0
+714 715 10487 0x177b218d
+715 716 10486 0x177ad56a
+716 717 10485 0x177a899c
+717 718 10484 0x177a3e25
+718 719 10483 0x1779f2ae
+719 720 10482 0x1779a78d
+720 721 10481 0x17795c6b
+721 722 10480 0x177911a0
+722 723 10479 0x1778c6d4
+723 724 10478 0x17787cb4
+724 725 10477 0x17783295
+725 726 10476 0x1777e875
+726 727 10475 0x17779e55
+727 728 10474 0x1777548b
+728 729 10473 0x17770b17
+729 730 10472 0x1776c1a3
+730 731 10471 0x17767885
+731 732 10470 0x17762f67
+732 733 10469 0x1775e69e
+733 734 10468 0x17759dd6
+734 735 10467 0x17755564
+735 736 10466 0x17750cf1
+736 737 10465 0x1774c4d4
+737 738 10464 0x17747cb8
+738 739 10463 0x17743547
+739 740 10462 0x1773edd6
+740 741 10461 0x1773a665
+741 742 10460 0x17735ef4
+742 743 10459 0x177317d9
+743 744 10458 0x1772d114
+744 745 10457 0x17728a4f
+745 746 10456 0x1772438a
+746 747 10455 0x1771fd1b
+747 748 10454 0x1771b701
+748 749 10453 0x177170e8
+749 750 10452 0x17712ace
+750 751 10451 0x1770e50b
+751 752 10450 0x17709f9d
+752 753 10449 0x17705a2f
+753 754 10448 0x17701517
+754 755 10447 0x176fd056
+755 756 10446 0x176f8b3e
+756 757 10445 0x176f467c
+757 758 10444 0x176f0210
+758 759 10443 0x176ebda3
+759 760 10442 0x176e7937
+760 761 10441 0x176e3577
+761 762 10440 0x176df161
+762 763 10439 0x176dada0
+763 764 10438 0x176d6a36
+764 765 10437 0x176d26cb
+765 766 10436 0x176ce361
+766 767 10435 0x176ca04c
+767 768 10434 0x176c5d8d
+768 769 10433 0x176c1acf
+769 770 10432 0x176bd866
+770 771 10431 0x176b95fd
+771 772 10430 0x176b5394
+772 773 10429 0x176b1181
+773 774 10428 0x176acfc4
+774 775 10427 0x176a8e06
+775 776 10426 0x176a4c49
+776 777 10425 0x176a0ae2
+777 778 10424 0x1769c97a
+778 779 10423 0x17698869
+779 780 10422 0x17694757
+780 781 10421 0x1769069c
+781 782 10420 0x1768c5e0
+782 783 10419 0x17688525
+783 784 10418 0x17684515
+784 785 10417 0x17680505
+785 786 10416 0x1767c4f5
+786 787 10415 0x176784e5
+787 788 10414 0x1767452b
+788 789 10413 0x176705c7
+789 790 10412 0x1766c663
+790 791 10411 0x176686ff
+791 792 10410 0x176647f0
+792 793 10409 0x176608e2
+793 794 10408 0x1765c9d4
+794 795 10407 0x17658b71
+795 796 10406 0x17654cb9
+796 797 10405 0x17650e56
+797 798 10404 0x1764d049
+798 799 10403 0x176491e7
+799 800 10402 0x17645486
+800 801 10401 0x17641679
+801 802 10400 0x1763d918
+802 803 10399 0x17639b61
+803 804 10398 0x17635e00
+804 805 10397 0x176320f5
+805 806 10396 0x1762e3ea
+806 807 10395 0x1762a6df
+807 808 10394 0x17626a2a
+808 809 10393 0x17622d74
+809 810 10392 0x1761f115
+810 811 10391 0x1761b4b6
+811 812 10390 0x176178ac
+812 813 10389 0x17613ca3
+813 814 10388 0x17610099
+814 815 10387 0x1760c4e5
+815 816 10386 0x17608988
+816 817 10385 0x17604e2a
+817 818 10384 0x176012cc
+818 819 10383 0x175fd76e
+819 820 10382 0x175f9c66
+820 821 10381 0x175f61b4
+821 822 10380 0x175f26ac
+822 823 10379 0x175eec50
+823 824 10378 0x175eb19e
+824 825 10377 0x175e7742
+825 826 10376 0x175e3d3b
+826 827 10375 0x175e02df
+827 828 10374 0x175dc92f
+828 829 10373 0x175d8f28
+829 830 10372 0x175d5578
+830 831 10371 0x175d1c73
+831 832 10370 0x175ce2c3
+832 833 10369 0x175ca9be
+833 834 10368 0x175c7063
+834 835 10367 0x175c375f
+835 836 10366 0x175bfeb0
+836 837 10365 0x175bc601
+837 838 10364 0x175b8d52
+838 839 10363 0x175b54f9
+839 840 10362 0x175b1ca0
+840 841 10361 0x175ae447
+841 842 10360 0x175aac44
+842 843 10359 0x175a7440
+843 844 10358 0x175a3c93
+844 845 10357 0x175a04e6
+845 846 10356 0x1759cd8e
+846 847 10355 0x175995e1
+847 848 10354 0x17595e8a
+848 849 10353 0x17592788
+849 850 10352 0x1758f086
+850 851 10351 0x1758b985
+851 852 10350 0x175882d9
+852 853 10349 0x17584c2d
+853 854 10348 0x17581582
+854 855 10347 0x1757df2c
+855 856 10346 0x1757a8d6
+856 857 10345 0x17577280
+857 858 10344 0x17573c80
+858 859 10343 0x17570680
+859 860 10342 0x1756d0d6
+860 861 10341 0x17569b2c
+861 862 10340 0x175665d8
+862 863 10339 0x17563083
+863 864 10338 0x1755fb2f
+864 865 10337 0x1755c606
+865 866 10336 0x1755915d
+866 867 10335 0x17555cb5
+867 868 10334 0x17552837
+868 869 10333 0x1754f3ba
+869 870 10332 0x1754bf92
+870 871 10331 0x17548b6a
+871 872 10330 0x1754576d
+872 873 10329 0x17542370
+873 874 10328 0x1753ef9f
+874 875 10327 0x1753bbf8
+875 876 10326 0x1753887c
+876 877 10325 0x17535556
+877 878 10324 0x17532204
+878 879 10323 0x1752eede
+879 880 10322 0x1752bbb8
+880 881 10321 0x175288bd
+881 882 10320 0x175255ed
+882 883 10319 0x17522347
+883 884 10318 0x1751f0a2
+884 885 10317 0x1751be27
+885 886 10316 0x17518bd8
+886 887 10315 0x17515988
+887 888 10314 0x1751278f
+888 889 10313 0x1750f56a
+889 890 10312 0x1750c39c
+890 891 10311 0x175091cd
+891 892 10310 0x1750607f
+892 893 10309 0x17502edb
+893 894 10308 0x174ffd8d
+894 895 10307 0x174fcc3f
+895 896 10306 0x174f9af2
+896 897 10305 0x174f69f9
+897 898 10304 0x174f3901
+898 899 10303 0x174f0834
+899 900 10302 0x174ed767
+900 901 10301 0x174ea6c5
+901 902 10300 0x174e764e
+902 903 10299 0x174e4602
+903 904 10298 0x174e15b5
+904 905 10297 0x174de594
+905 906 10296 0x174db572
+906 907 10295 0x174d85a7
+907 908 10294 0x174d5606
+908 909 10293 0x174d263b
+909 910 10292 0x174cf6c5
+910 911 10291 0x174cc750
+911 912 10290 0x174c97da
+912 913 10289 0x174c68ba
+913 914 10288 0x174c399a
+914 915 10287 0x174c0a7a
+915 916 10286 0x174bdb86
+916 917 10285 0x174bacbc
+917 918 10284 0x174b7e1d
+918 919 10283 0x174b4f7e
+919 920 10282 0x174b210a
+920 921 10281 0x174af2c1
+921 922 10280 0x174ac477
+922 923 10279 0x174a9684
+923 924 10278 0x174a6891
+924 925 10277 0x174a3a9e
+925 926 10276 0x174a0cd5
+926 927 10275 0x1749df38
+927 928 10274 0x1749b19a
+928 929 10273 0x17498428
+929 930 10272 0x174956b5
+930 931 10271 0x1749296e
+931 932 10270 0x1748fc51
+932 933 10269 0x1748cf35
+933 934 10268 0x1748a243
+934 935 10267 0x1748757c
+935 936 10266 0x174848b6
+936 937 10265 0x17481c1a
+937 938 10264 0x1747efd4
+938 939 10263 0x1747c363
+939 940 10262 0x1747971d
+940 941 10261 0x17476b02
+941 942 10260 0x17473ee7
+942 943 10259 0x174712f7
+943 944 10258 0x1746e707
+944 945 10257 0x1746bb42
+945 946 10256 0x17468fa7
+946 947 10255 0x1746640d
+947 948 10254 0x1746389e
+948 949 10253 0x17460d59
+949 950 10252 0x1745e215
+950 951 10251 0x1745b6fc
+951 952 10250 0x17458be2
+952 953 10249 0x174560f4
+953 954 10248 0x1745365b
+954 955 10247 0x17450b97
+955 956 10246 0x1744e0ff
+956 957 10245 0x1744b666
+957 958 10244 0x17448bf9
+958 959 10243 0x174461b6
+959 960 10242 0x17443773
+960 961 10241 0x17440d5b
+961 962 10240 0x1743e36e
+962 963 10239 0x1743b981
+963 964 10238 0x17438fbf
+964 965 10237 0x174365fd
+965 966 10236 0x17433c66
+966 967 10235 0x174312cf
+967 968 10234 0x1742e98e
+968 969 10233 0x1742c04d
+969 970 10232 0x17429737
+970 971 10231 0x17426e20
+971 972 10230 0x1742450a
+972 973 10229 0x17421c1f
+973 974 10228 0x1741f35e
+974 975 10227 0x1741ca9e
+975 976 10226 0x1741a209
+976 977 10225 0x1741799e
+977 978 10224 0x17415134
+978 979 10223 0x174128c9
+979 980 10222 0x17410089
+980 981 10221 0x1740d875
+981 982 10220 0x1740b08b
+982 983 10219 0x174088a1
+983 984 10218 0x174060e2
+984 985 10217 0x17403924
+985 986 10216 0x17401190
+986 987 10215 0x173fe9fc
+987 988 10214 0x173fc293
+988 989 10213 0x173f9b2a
+989 990 10212 0x173f73ec
+990 991 10211 0x173f4cd9
+991 992 10210 0x173f25c6
+992 993 10209 0x173efede
+993 994 10208 0x173ed7f6
+994 995 10207 0x173eb138
+995 996 10206 0x173e8a7b
+996 997 10205 0x173e63e9
+997 998 10204 0x173e3d82
+998 999 10203 0x173e171a
+999 1000 10202 0x173df0de
+1000 1001 10201 0x173dcaa1
+1001 1002 10200 0x173da490
+1002 1003 10199 0x173d7e7e
+1003 1004 10198 0x173d5898
+1004 1005 10197 0x173d32b1
+1005 1006 10196 0x173d0cf6
+1006 1007 10195 0x173ce765
+1007 1008 10194 0x173cc1d4
+1008 1009 10193 0x173c9c44
+1009 1010 10192 0x173c7709
+1010 1011 10191 0x173c51a3
+1011 1012 10190 0x173c2c68
+1012 1013 10189 0x173c0758
+1013 1014 10188 0x173be248
+1014 1015 10187 0x173bbd8e
+1015 1016 10186 0x173b98aa
+1016 1017 10185 0x173b73ef
+1017 1018 10184 0x173b4f60
+1018 1019 10183 0x173b2ad1
+1019 1020 10182 0x173b0642
+1020 1021 10181 0x173ae1de
+1021 1022 10180 0x173abda5
+1022 1023 10179 0x173a996c
+1023 1024 10178 0x173a7532
+1024 1025 10177 0x173a5124
+1025 1026 10176 0x173a2d41
+1026 1027 10175 0x173a095d
+1027 1028 10174 0x1739e5a5
+1028 1029 10173 0x1739c1ec
+1029 1030 10172 0x17399e8a
+1030 1031 10171 0x17397afc
+1031 1032 10170 0x1739579a
+1032 1033 10169 0x17393437
+1033 1034 10168 0x17391100
+1034 1035 10167 0x1738edc8
+1035 1036 10166 0x1738cabb
+1036 1037 10165 0x1738a7ae
+1037 1038 10164 0x173884cd
+1038 1039 10163 0x173861eb
+1039 1040 10162 0x17383f34
+1040 1041 10161 0x17381c7d
+1041 1042 10160 0x1737f9f1
+1042 1043 10159 0x1737d765
+1043 1044 10158 0x1737b504
+1044 1045 10157 0x173792ce
+1045 1046 10156 0x17377098
+1046 1047 10155 0x17374e8d
+1047 1048 10154 0x17372c57
+1048 1049 10153 0x17370a77
+1049 1050 10152 0x1736e897
+1050 1051 10151 0x1736c6b6
+1051 1052 10150 0x1736a501
+1052 1053 10149 0x1736834c
+1053 1054 10148 0x173661c2
+1054 1055 10147 0x17364037
+1055 1056 10146 0x17361ed8
+1056 1057 10145 0x1735fd79
+1057 1058 10144 0x1735dc19
+1058 1059 10143 0x1735bb10
+1059 1060 10142 0x173599db
+1060 1061 10141 0x173578fd
+1061 1062 10140 0x1735581e
+1062 1063 10139 0x1735373f
+1063 1064 10138 0x1735168c
+1064 1065 10137 0x1734f5d8
+1065 1066 10136 0x1734d524
+1066 1067 10135 0x1734b49c
+1067 1068 10134 0x17349413
+1068 1069 10133 0x173473b5
+1069 1070 10132 0x17345382
+1070 1071 10131 0x1734334f
+1071 1072 10130 0x1734131c
+1072 1073 10129 0x1733f314
+1073 1074 10128 0x1733d30d
+1074 1075 10127 0x1733b330
+1075 1076 10126 0x1733937d
+1076 1077 10125 0x173373a0
+1077 1078 10124 0x173353ee
+1078 1079 10123 0x17333467
+1079 1080 10122 0x173314e0
+1080 1081 10121 0x1732f559
+1081 1082 10120 0x1732d5fd
+1082 1083 10119 0x1732b6a0
+1083 1084 10118 0x1732976f
+1084 1085 10117 0x1732783e
+1085 1086 10116 0x17325938
+1086 1087 10115 0x17323a31
+1087 1088 10114 0x17321b56
+1088 1089 10113 0x1731fc7a
+1089 1090 10112 0x1731dd9f
+1090 1091 10111 0x1731beee
+1091 1092 10110 0x1731a069
+1092 1093 10109 0x173181e3
+1093 1094 10108 0x1731635e
+1094 1095 10107 0x17314503
+1095 1096 10106 0x173126a8
+1096 1097 10105 0x1731084e
+1097 1098 10104 0x1730ea1e
+1098 1099 10103 0x1730cbee
+1099 1100 10102 0x1730adea
+1100 1101 10101 0x17308fe5
+1101 1102 10100 0x1730720b
+1102 1103 10099 0x17305431
+1103 1104 10098 0x17303657
+1104 1105 10097 0x173018a9
+1105 1106 10096 0x172ffb25
+1106 1107 10095 0x172fdda1
+1107 1108 10094 0x172fc01d
+1108 1109 10093 0x172fa2c4
+1109 1110 10092 0x172f856b
+1110 1111 10091 0x172f6812
+1111 1112 10090 0x172f4ae3
+1112 1113 10089 0x172f2db5
+1113 1114 10088 0x172f10b2
+1114 1115 10087 0x172ef3af
+1115 1116 10086 0x172ed6ac
+1116 1117 10085 0x172eb9d4
+1117 1118 10084 0x172e9cfb
+1118 1119 10083 0x172e804e
+1119 1120 10082 0x172e63a1
+1120 1121 10081 0x172e46f3
+1121 1122 10080 0x172e2a9c
+1122 1123 10079 0x172e0e1a
+1123 1124 10078 0x172df1c2
+1124 1125 10077 0x172dd56b
+1125 1126 10076 0x172db913
+1126 1127 10075 0x172d9ce7
+1127 1128 10074 0x172d80ba
+1128 1129 10073 0x172d64b9
+1129 1130 10072 0x172d48b7
+1130 1131 10071 0x172d2cb5
+1131 1132 10070 0x172d10df
+1132 1133 10069 0x172cf508
+1133 1134 10068 0x172cd95c
+1134 1135 10067 0x172cbdb1
+1135 1136 10066 0x172ca205
+1136 1137 10065 0x172c8684
+1137 1138 10064 0x172c6b03
+1138 1139 10063 0x172c4fae
+1139 1140 10062 0x172c3458
+1140 1141 10061 0x172c1902
+1141 1142 10060 0x172bfdac
+1142 1143 10059 0x172be281
+1143 1144 10058 0x172bc781
+1144 1145 10057 0x172bac56
+1145 1146 10056 0x172b9156
+1146 1147 10055 0x172b7681
+1147 1148 10054 0x172b5bac
+1148 1149 10053 0x172b40d7
+1149 1150 10052 0x172b2602
+1150 1151 10051 0x172b0b58
+1151 1152 10050 0x172af0ae
+1152 1153 10049 0x172ad66f
+1153 1154 10048 0x172abc1b
+1154 1155 10047 0x172aa1c6
+1155 1156 10046 0x172a8787
+1156 1157 10045 0x172a6d5e
+1157 1158 10044 0x172a534a
+1158 1159 10043 0x172a3936
+1159 1160 10042 0x172a1f4d
+1160 1161 10041 0x172a054f
+1161 1162 10040 0x1729eb7c
+1162 1163 10039 0x1729d1a8
+1163 1164 10038 0x1729b7ea
+1164 1165 10037 0x17299e42
+1165 1166 10036 0x17298499
+1166 1167 10035 0x17296b1b
+1167 1168 10034 0x172951b3
+1168 1169 10033 0x17293836
+1169 1170 10032 0x17291ee3
+1170 1171 10031 0x17290590
+1171 1172 10030 0x1728ec3e
+1172 1173 10029 0x1728d316
+1173 1174 10028 0x1728b9ee
+1174 1175 10027 0x1728a0c6
+1175 1176 10026 0x172887ca
+1176 1177 10025 0x17286ecd
+1177 1178 10024 0x172855e5
+1178 1179 10023 0x17283d14
+1179 1180 10022 0x17282442
+1180 1181 10021 0x17280b85
+1181 1182 10020 0x1727f2de
+1182 1183 10019 0x1727da37
+1183 1184 10018 0x1727c1bb
+1184 1185 10017 0x1727a93f
+1185 1186 10016 0x172790d9
+1186 1187 10015 0x17277872
+1187 1188 10014 0x1727600c
+1188 1189 10013 0x172747d0
+1189 1190 10012 0x17272f94
+1190 1191 10011 0x1727176e
+1191 1192 10010 0x1726ff5e
+1192 1193 10009 0x1726e74d
+1193 1194 10008 0x1726cf52
+1194 1195 10007 0x1726b756
+1195 1196 10006 0x17269f86
+1196 1197 10005 0x172687a0
+1197 1198 10004 0x17266fe6
+1198 1199 10003 0x17265856
+1199 1200 10002 0x172640b0
+1200 1201 10001 0x1726290b
+1201 1202 10000 0x1726117b
+1202 1203 9999 0x1725fa01
+1203 1204 9998 0x1725e29c
+1204 1205 9997 0x1725cb37
+1205 1206 9996 0x1725b3e7
+1206 1207 9995 0x17259cad
+1207 1208 9994 0x17258573
+1208 1209 9993 0x17256e4f
+1209 1210 9992 0x1725573f
+1210 1211 9991 0x17254030
+1211 1212 9990 0x17252937
+1212 1213 9989 0x1725123d
+1213 1214 9988 0x1724fb84
+1214 1215 9987 0x1724e4a0
+1215 1216 9986 0x1724cde6
+1216 1217 9985 0x1724b72d
+1217 1218 9984 0x1724a089
+1218 1219 9983 0x172489e6
+1219 1220 9982 0x17247357
+1220 1221 9981 0x17245cde
+1221 1222 9980 0x17244666
+1222 1223 9979 0x17243002
+1223 1224 9978 0x172419b4
+1224 1225 9977 0x17240366
+1225 1226 9976 0x1723ed2e
+1226 1227 9975 0x1723d70b
+1227 1228 9974 0x1723c0e8
+1228 1229 9973 0x1723aadb
+1229 1230 9972 0x172394f8
+1230 1231 9971 0x17237f00
+1231 1232 9970 0x17236908
+1232 1233 9969 0x1723533b
+1233 1234 9968 0x17233d6e
+1234 1235 9967 0x172327a1
+1235 1236 9966 0x172311e9
+1236 1237 9965 0x1722fc47
+1237 1238 9964 0x1722e6ba
+1238 1239 9963 0x1722d12d
+1239 1240 9962 0x1722bba1
+1240 1241 9961 0x1722a63f
+1241 1242 9960 0x172290dd
+1242 1243 9959 0x17227b7b
+1243 1244 9958 0x1722662f
+1244 1245 9957 0x1722510d
+1245 1246 9956 0x17223bec
+1246 1247 9955 0x172226cb
+1247 1248 9954 0x172211a9
+1248 1249 9953 0x1721fc9d
+1249 1250 9952 0x1721e7a7
+1250 1251 9951 0x1721d2c6
+1251 1252 9950 0x1721bde5
+1252 1253 9949 0x1721a904
+1253 1254 9948 0x1721944e
+1254 1255 9947 0x17217f82
+1255 1256 9946 0x17216ae2
+1256 1257 9945 0x17215641
+1257 1258 9944 0x172141a1
+1258 1259 9943 0x17212d2b
+1259 1260 9942 0x172118cb
+1260 1261 9941 0x17210455
+1261 1262 9940 0x1720eff5
+1262 1263 9939 0x1720db95
+1263 1264 9938 0x1720c75f
+1264 1265 9937 0x1720b315
+1265 1266 9936 0x17209ef5
+1266 1267 9935 0x17208ad5
+1267 1268 9934 0x172076b5
+1268 1269 9933 0x172062ab
+1269 1270 9932 0x17204eb6
+1270 1271 9931 0x17203ac1
+1271 1272 9930 0x172026e2
+1272 1273 9929 0x17201302
+1273 1274 9928 0x171fff39
+1274 1275 9927 0x171feb84
+1275 1276 9926 0x171fd7e5
+1276 1277 9925 0x171fc431
+1277 1278 9924 0x171fb0a7
+1278 1279 9923 0x171f9d1e
+1279 1280 9922 0x171f8994
+1280 1281 9921 0x171f7620
+1281 1282 9920 0x171f62ac
+1282 1283 9919 0x171f4f4d
+1283 1284 9918 0x171f3c04
+1284 1285 9917 0x171f28bb
+1285 1286 9916 0x171f1587
+1286 1287 9915 0x171f0254
+1287 1288 9914 0x171eef35
+1288 1289 9913 0x171edc2d
+1289 1290 9912 0x171ec924
+1290 1291 9911 0x171eb631
+1291 1292 9910 0x171ea33d
+1292 1293 9909 0x171e9060
+1293 1294 9908 0x171e7d82
+1294 1295 9907 0x171e6ab9
+1295 1296 9906 0x171e57f1
+1296 1297 9905 0x171e453e
+1297 1298 9904 0x171e328b
+1298 1299 9903 0x171e1fee
+1299 1300 9902 0x171e0d51
+1300 1301 9901 0x171dfac9
+1301 1302 9900 0x171de856
+1302 1303 9899 0x171dd5e4
+1303 1304 9898 0x171dc387
+1304 1305 9897 0x171db12a
+1305 1306 9896 0x171d9ee2
+1306 1307 9895 0x171d8cb0
+1307 1308 9894 0x171d7a68
+1308 1309 9893 0x171d684c
+1309 1310 9892 0x171d561a
+1310 1311 9891 0x171d4413
+1311 1312 9890 0x171d31f6
+1312 1313 9889 0x171d2004
+1313 1314 9888 0x171d0e13
+1314 1315 9887 0x171cfc21
+1315 1316 9886 0x171cea45
+1316 1317 9885 0x171cd868
+1317 1318 9884 0x171cc6a2
+1318 1319 9883 0x171cb4f0
+1319 1320 9882 0x171ca33f
+1320 1321 9881 0x171c918e
+1321 1322 9880 0x171c8007
+1322 1323 9879 0x171c6e81
+1323 1324 9878 0x171c5cfb
+1324 1325 9877 0x171c4b74
+1325 1326 9876 0x171c3a03
+1326 1327 9875 0x171c28a8
+1327 1328 9874 0x171c174d
+1328 1329 9873 0x171c0607
+1329 1330 9872 0x171bf4c1
+1330 1331 9871 0x171be37b
+1331 1332 9870 0x171bd24a
+1332 1333 9869 0x171bc12f
+1333 1334 9868 0x171bb014
+1334 1335 9867 0x171b9f0f
+1335 1336 9866 0x171b8e09
+1336 1337 9865 0x171b7d19
+1337 1338 9864 0x171b6c29
+1338 1339 9863 0x171b5b4e
+1339 1340 9862 0x171b4a74
+1340 1341 9861 0x171b3999
+1341 1342 9860 0x171b28d4
+1342 1343 9859 0x171b1824
+1343 1344 9858 0x171b075f
+1344 1345 9857 0x171af6c5
+1345 1346 9856 0x171ae62b
+1346 1347 9855 0x171ad590
+1347 1348 9854 0x171ac50c
+1348 1349 9853 0x171ab49c
+1349 1350 9852 0x171aa417
+1350 1351 9851 0x171a93be
+1351 1352 9850 0x171a834e
+1352 1353 9849 0x171a731f
+1353 1354 9848 0x171a62c6
+1354 1355 9847 0x171a5281
+1355 1356 9846 0x171a4252
+1356 1357 9845 0x171a3223
+1357 1358 9844 0x171a220a
+1358 1359 9843 0x171a11f1
+1359 1360 9842 0x171a01d7
+1360 1361 9841 0x1719f1d3
+1361 1362 9840 0x1719e1e5
+1362 1363 9839 0x1719d1f6
+1363 1364 9838 0x1719c208
+1364 1365 9837 0x1719b22f
+1365 1366 9836 0x1719a256
+1366 1367 9835 0x17199292
+1367 1368 9834 0x171982e4
+1368 1369 9833 0x17197336
+1369 1370 9832 0x17196388
+1370 1371 9831 0x171953da
+1371 1372 9830 0x17194441
+1372 1373 9829 0x171934be
+1373 1374 9828 0x1719253b
+1374 1375 9827 0x171915b7
+1375 1376 9826 0x1719064a
+1376 1377 9825 0x1718f6dc
+1377 1378 9824 0x1718e76e
+1378 1379 9823 0x1718d82b
+1379 1380 9822 0x1718c8d3
+1380 1381 9821 0x1718b990
+1381 1382 9820 0x1718aa4e
+1382 1383 9819 0x17189b36
+1383 1384 9818 0x17188c08
+1384 1385 9817 0x17187cf1
+1385 1386 9816 0x17186dd9
+1386 1387 9815 0x17185ed6
+1387 1388 9814 0x17184fd4
+1388 1389 9813 0x171840d2
+1389 1390 9812 0x171831e5
+1390 1391 9811 0x171822f8
+1391 1392 9810 0x17181420
+1392 1393 9809 0x17180549
+1393 1394 9808 0x1717f671
+1394 1395 9807 0x1717e7af
+1395 1396 9806 0x1717d903
+1396 1397 9805 0x1717ca41
+1397 1398 9804 0x1717bb94
+1398 1399 9803 0x1717ad13
+1399 1400 9802 0x17179e7c
+1400 1401 9801 0x17178fe5
+1401 1402 9800 0x17178163
+1402 1403 9799 0x171772e2
+1403 1404 9798 0x17176475
+1404 1405 9797 0x17175609
+1405 1406 9796 0x1717479d
+1406 1407 9795 0x17173947
+1407 1408 9794 0x17172af0
+1408 1409 9793 0x17171caf
+1409 1410 9792 0x17170e6e
+1410 1411 9791 0x1717002c
+1411 1412 9790 0x1716f201
+1412 1413 9789 0x1716e3ea
+1413 1414 9788 0x1716d5d4
+1414 1415 9787 0x1716c7be
+1415 1416 9786 0x1716b9a8
+1416 1417 9785 0x1716aba7
+1417 1418 9784 0x17169da6
+1418 1419 9783 0x17168fbb
+1419 1420 9782 0x171681cf
+1420 1421 9781 0x171673e4
+1421 1422 9780 0x1716660e
+1422 1423 9779 0x17165838
+1423 1424 9778 0x17164a62
+1424 1425 9777 0x17163ca2
+1425 1426 9776 0x17162ee2
+1426 1427 9775 0x17162137
+1427 1428 9774 0x1716138c
+1428 1429 9773 0x171605f6
+1429 1430 9772 0x1715f861
+1430 1431 9771 0x1715eacb
+1431 1432 9770 0x1715dd36
+1432 1433 9769 0x1715cfb6
+1433 1434 9768 0x1715c24b
+1434 1435 9767 0x1715b4cb
+1435 1436 9766 0x1715a761
+1436 1437 9765 0x171599f6
+1437 1438 9764 0x17158ca1
+1438 1439 9763 0x17157f4c
+1439 1440 9762 0x1715720d
+1440 1441 9761 0x171564d8
+1441 1442 9760 0x171557ae
+1442 1443 9759 0x17154a8e
+1443 1444 9758 0x17153d7a
+1444 1445 9757 0x17153070
+1445 1446 9756 0x17152366
+1446 1447 9755 0x17151666
+1447 1448 9754 0x17150972
+1448 1449 9753 0x1714fc7e
+1449 1450 9752 0x1714ef94
+1450 1451 9751 0x1714e2aa
+1451 1452 9750 0x1714d5d6
+1452 1453 9749 0x1714c902
+1453 1454 9748 0x1714bc38
+1454 1455 9747 0x1714af6e
+1455 1456 9746 0x1714a2b0
+1456 1457 9745 0x171495fc
+1457 1458 9744 0x17148952
+1458 1459 9743 0x17147ca9
+1459 1460 9742 0x17147015
+1460 1461 9741 0x17146381
+1461 1462 9740 0x171456f8
+1462 1463 9739 0x17144a6f
+1463 1464 9738 0x17143df0
+1464 1465 9737 0x17143172
+1465 1466 9736 0x171424fe
+1466 1467 9735 0x17141895
+1467 1468 9734 0x17140c37
+1468 1469 9733 0x1713ffd9
+1469 1470 9732 0x1713f386
+1470 1471 9731 0x1713e73d
+1471 1472 9730 0x1713daf4
+1472 1473 9729 0x1713ceb6
+1473 1474 9728 0x1713c283
+1474 1475 9727 0x1713b665
+1475 1476 9726 0x1713aa3c
+1476 1477 9725 0x17139e1f
+1477 1478 9724 0x17139201
+1478 1479 9723 0x171385f8
+1479 1480 9722 0x171379e5
+1480 1481 9721 0x17136de8
+1481 1482 9720 0x171361ea
+1482 1483 9719 0x171355f7
+1483 1484 9718 0x17134a0f
+1484 1485 9717 0x17133e27
+1485 1486 9716 0x1713324a
+1486 1487 9715 0x1713266c
+1487 1488 9714 0x17131aa4
+1488 1489 9713 0x17130edc
+1489 1490 9712 0x17130314
+1490 1491 9711 0x1712f76d
+1491 1492 9710 0x1712ebba
+1492 1493 9709 0x1712e008
+1493 1494 9708 0x1712d46b
+1494 1495 9707 0x1712c8c3
+1495 1496 9706 0x1712bd31
+1496 1497 9705 0x1712b19e
+1497 1498 9704 0x1712a617
+1498 1499 9703 0x17129a9a
+1499 1500 9702 0x17128f1d
+1500 1501 9701 0x171283ab
+1501 1502 9700 0x17127839
+1502 1503 9699 0x17126cd2
+1503 1504 9698 0x17126175
+1504 1505 9697 0x17125623
+1505 1506 9696 0x17124adc
+1506 1507 9695 0x17123f95
+1507 1508 9694 0x1712344e
+1508 1509 9693 0x17122911
+1509 1510 9692 0x17121ddf
+1510 1511 9691 0x171212ae
+1511 1512 9690 0x17120787
+1512 1513 9689 0x1711fc6b
+1513 1514 9688 0x1711f14e
+1514 1515 9687 0x1711e63d
+1515 1516 9686 0x1711db2b
+1516 1517 9685 0x1711d025
+1517 1518 9684 0x1711c529
+1518 1519 9683 0x1711ba2d
+1519 1520 9682 0x1711af3b
+1520 1521 9681 0x1711a460
+1521 1522 9680 0x17119984
+1522 1523 9679 0x17118e9d
+1523 1524 9678 0x171183cc
+1524 1525 9677 0x171178fb
+1525 1526 9676 0x17116e35
+1526 1527 9675 0x1711636e
+1527 1528 9674 0x171158b3
+1528 1529 9673 0x17114e02
+1529 1530 9672 0x17114351
+1530 1531 9671 0x171138ab
+1531 1532 9670 0x17112e05
+1532 1533 9669 0x17112369
+1533 1534 9668 0x171118d8
+1534 1535 9667 0x17110e48
+1535 1536 9666 0x171103c2
+1536 1537 9665 0x1710f951
+1537 1538 9664 0x1710eed6
+1538 1539 9663 0x1710e466
+1539 1540 9662 0x1710d9f5
+1540 1541 9661 0x1710cf8f
+1541 1542 9660 0x1710c52a
+1542 1543 9659 0x1710bace
+1543 1544 9658 0x1710b07e
+1544 1545 9657 0x1710a62e
+1545 1546 9656 0x17109be8
+1546 1547 9655 0x171091ad
+1547 1548 9654 0x17108773
+1548 1549 9653 0x17107d43
+1549 1550 9652 0x17107312
+1550 1551 9651 0x171068ed
+1551 1552 9650 0x17105ed2
+1552 1553 9649 0x171054b8
+1553 1554 9648 0x17104aa8
+1554 1555 9647 0x17104098
+1555 1556 9646 0x17103693
+1556 1557 9645 0x17102c8e
+1557 1558 9644 0x17102293
+1558 1559 9643 0x171018a4
+1559 1560 9642 0x17100eb4
+1560 1561 9641 0x171004c4
+1561 1562 9640 0x170ffaea
+1562 1563 9639 0x170ff105
+1563 1564 9638 0x170fe735
+1564 1565 9637 0x170fdd66
+1565 1566 9636 0x170fd396
+1566 1567 9635 0x170fc9dc
+1567 1568 9634 0x170fc022
+1568 1569 9633 0x170fb668
+1569 1570 9632 0x170facb9
+1570 1571 9631 0x170fa30a
+1571 1572 9630 0x170f9965
+1572 1573 9629 0x170f8fcb
+1573 1574 9628 0x170f8632
+1574 1575 9627 0x170f7c98
+1575 1576 9626 0x170f7309
+1576 1577 9625 0x170f6984
+1577 1578 9624 0x170f6000
+1578 1579 9623 0x170f5686
+1579 1580 9622 0x170f4d0d
+1580 1581 9621 0x170f439e
+1581 1582 9620 0x170f3a39
+1582 1583 9619 0x170f30e0
+1583 1584 9618 0x170f277c
+1584 1585 9617 0x170f1e22
+1585 1586 9616 0x170f14d4
+1586 1587 9615 0x170f0b85
+1587 1588 9614 0x170f0241
+1588 1589 9613 0x170ef8fd
+1589 1590 9612 0x170eefc4
+1590 1591 9611 0x170ee68b
+1591 1592 9610 0x170edd5c
+1592 1593 9609 0x170ed42e
+1593 1594 9608 0x170ecb0a
+1594 1595 9607 0x170ec1f1
+1595 1596 9606 0x170eb8d8
+1596 1597 9605 0x170eafbf
+1597 1598 9604 0x170ea6bb
+1598 1599 9603 0x170e9db8
+1599 1600 9602 0x170e94b4
+1600 1601 9601 0x170e8bb0
+1601 1602 9600 0x170e82c2
+1602 1603 9599 0x170e79c9
+1603 1604 9598 0x170e70db
+1604 1605 9597 0x170e67f8
+1605 1606 9596 0x170e5f15
+1606 1607 9595 0x170e563c
+1607 1608 9594 0x170e4d63
+1608 1609 9593 0x170e4495
+1609 1610 9592 0x170e3bc7
+1610 1611 9591 0x170e3304
+1611 1612 9590 0x170e2a41
+1612 1613 9589 0x170e2193
+1613 1614 9588 0x170e18db
+1614 1615 9587 0x170e102d
+1615 1616 9586 0x170e0780
+1616 1617 9585 0x170dfedd
+1617 1618 9584 0x170df63a
+1618 1619 9583 0x170deda1
+1619 1620 9582 0x170de514
+1620 1621 9581 0x170ddc7c
+1621 1622 9580 0x170dd3f9
+1622 1623 9579 0x170dcb6b
+1623 1624 9578 0x170dc2f3
+1624 1625 9577 0x170dba70
+1625 1626 9576 0x170db203
+1626 1627 9575 0x170da996
+1627 1628 9574 0x170da128
+1628 1629 9573 0x170d98c6
+1629 1630 9572 0x170d906e
+1630 1631 9571 0x170d880b
+1631 1632 9570 0x170d7fbe
+1632 1633 9569 0x170d7767
+1633 1634 9568 0x170d6f19
+1634 1635 9567 0x170d66d7
+1635 1636 9566 0x170d5e95
+1636 1637 9565 0x170d565d
+1637 1638 9564 0x170d4e25
+1638 1639 9563 0x170d45ee
+1639 1640 9562 0x170d3dc1
+1640 1641 9561 0x170d359f
+1641 1642 9560 0x170d2d7c
+1642 1643 9559 0x170d255a
+1643 1644 9558 0x170d1d4d
+1644 1645 9557 0x170d1536
+1645 1646 9556 0x170d0d29
+1646 1647 9555 0x170d051d
+1647 1648 9554 0x170cfd1b
+1648 1649 9553 0x170cf519
+1649 1650 9552 0x170ced21
+1650 1651 9551 0x170ce52a
+1651 1652 9550 0x170cdd3e
+1652 1653 9549 0x170cd551
+1653 1654 9548 0x170ccd65
+1654 1655 9547 0x170cc583
+1655 1656 9546 0x170cbdac
+1656 1657 9545 0x170cb5ca
+1657 1658 9544 0x170cadfe
+1658 1659 9543 0x170ca631
+1659 1660 9542 0x170c9e65
+1660 1661 9541 0x170c96a3
+1661 1662 9540 0x170c8ee2
+1662 1663 9539 0x170c872b
+1663 1664 9538 0x170c7f74
+1664 1665 9537 0x170c77bd
+1665 1666 9536 0x170c7011
+1666 1667 9535 0x170c6865
+1667 1668 9534 0x170c60c4
+1668 1669 9533 0x170c5922
+1669 1670 9532 0x170c518c
+1670 1671 9531 0x170c49f5
+1671 1672 9530 0x170c425e
+1672 1673 9529 0x170c3ad2
+1673 1674 9528 0x170c3346
+1674 1675 9527 0x170c2bd0
+1675 1676 9526 0x170c244f
+1676 1677 9525 0x170c1cd8
+1677 1678 9524 0x170c1562
+1678 1679 9523 0x170c0deb
+1679 1680 9522 0x170c0680
+1680 1681 9521 0x170bff14
+1681 1682 9520 0x170bf7b3
+1682 1683 9519 0x170bf052
+1683 1684 9518 0x170be8fc
+1684 1685 9517 0x170be1a5
+1685 1686 9516 0x170bda4f
+1686 1687 9515 0x170bd304
+1687 1688 9514 0x170bcbb8
+1688 1689 9513 0x170bc477
+1689 1690 9512 0x170bbd37
+1690 1691 9511 0x170bb600
+1691 1692 9510 0x170baeca
+1692 1693 9509 0x170ba794
+1693 1694 9508 0x170ba05e
+1694 1695 9507 0x170b9933
+1695 1696 9506 0x170b9212
+1696 1697 9505 0x170b8af2
+1697 1698 9504 0x170b83d1
+1698 1699 9503 0x170b7cbb
+1699 1700 9502 0x170b75a5
+1700 1701 9501 0x170b6e90
+1701 1702 9500 0x170b6784
+1702 1703 9499 0x170b6079
+1703 1704 9498 0x170b5979
+1704 1705 9497 0x170b5279
+1705 1706 9496 0x170b4b83
+1706 1707 9495 0x170b4482
+1707 1708 9494 0x170b3d98
+1708 1709 9493 0x170b36a2
+1709 1710 9492 0x170b2fb7
+1710 1711 9491 0x170b28cc
+1711 1712 9490 0x170b21ec
+1712 1713 9489 0x170b1b0c
+1713 1714 9488 0x170b142b
+1714 1715 9487 0x170b0d56
+1715 1716 9486 0x170b0680
+1716 1717 9485 0x170affb6
+1717 1718 9484 0x170af8eb
+1718 1719 9483 0x170af220
+1719 1720 9482 0x170aeb60
+1720 1721 9481 0x170ae4a0
+1721 1722 9480 0x170addeb
+1722 1723 9479 0x170ad736
+1723 1724 9478 0x170ad080
+1724 1725 9477 0x170ac9cb
+1725 1726 9476 0x170ac321
+1726 1727 9475 0x170abc76
+1727 1728 9474 0x170ab5dc
+1728 1729 9473 0x170aaf47
+1729 1730 9472 0x170aa8b7
+1730 1731 9471 0x170aa227
+1731 1732 9470 0x170a9b9d
+1732 1733 9469 0x170a9513
+1733 1734 9468 0x170a8e93
+1734 1735 9467 0x170a8813
+1735 1736 9466 0x170a819f
+1736 1737 9465 0x170a7b24
+1737 1738 9464 0x170a74b5
+1738 1739 9463 0x170a6e45
+1739 1740 9462 0x170a67d6
+1740 1741 9461 0x170a6171
+1741 1742 9460 0x170a5b0c
+1742 1743 9459 0x170a54ad
+1743 1744 9458 0x170a4e4d
+1744 1745 9457 0x170a47f3
+1745 1746 9456 0x170a419f
+1746 1747 9455 0x170a3b4f
+1747 1748 9454 0x170a3500
+1748 1749 9453 0x170a2ebc
+1749 1750 9452 0x170a2872
+1750 1751 9451 0x170a2238
+1751 1752 9450 0x170a1bf9
+1752 1753 9449 0x170a15bf
+1753 1754 9448 0x170a0f8a
+1754 1755 9447 0x170a0956
+1755 1756 9446 0x170a0327
+1756 1757 9445 0x1709fcfd
+1757 1758 9444 0x1709f6d3
+1758 1759 9443 0x1709f0af
+1759 1760 9442 0x1709ea90
+1760 1761 9441 0x1709e471
+1761 1762 9440 0x1709de57
+1762 1763 9439 0x1709d843
+1763 1764 9438 0x1709d22f
+1764 1765 9437 0x1709cc25
+1765 1766 9436 0x1709c616
+1766 1767 9435 0x1709c017
+1767 1768 9434 0x1709ba13
+1768 1769 9433 0x1709b414
+1769 1770 9432 0x1709ae1b
+1770 1771 9431 0x1709a822
+1771 1772 9430 0x1709a22d
+1772 1773 9429 0x17099c3f
+1773 1774 9428 0x17099650
+1774 1775 9427 0x17099067
+1775 1776 9426 0x17098a7d
+1776 1777 9425 0x1709849f
+1777 1778 9424 0x17097ebb
+1778 1779 9423 0x170978e2
+1779 1780 9422 0x17097308
+1780 1781 9421 0x17096d34
+1781 1782 9420 0x1709676b
+1782 1783 9419 0x1709619d
+1783 1784 9418 0x17095bd4
+1784 1785 9417 0x17095610
+1785 1786 9416 0x1709504c
+1786 1787 9415 0x17094a8e
+1787 1788 9414 0x170944cf
+1788 1789 9413 0x17093f16
+1789 1790 9412 0x17093963
+1790 1791 9411 0x170933b4
+1791 1792 9410 0x17092e06
+1792 1793 9409 0x1709285d
+1793 1794 9408 0x170922b4
+1794 1795 9407 0x17091d10
+1795 1796 9406 0x17091772
+1796 1797 9405 0x170911d9
+1797 1798 9404 0x17090c41
+1798 1799 9403 0x170906ad
+1799 1800 9402 0x1709011a
+1800 1801 9401 0x1708fb8c
+1801 1802 9400 0x1708f5fd
+1802 1803 9399 0x1708f07a
+1803 1804 9398 0x1708eaf1
+1804 1805 9397 0x1708e573
+1805 1806 9396 0x1708dff5
+1806 1807 9395 0x1708da7d
+1807 1808 9394 0x1708d504
+1808 1809 9393 0x1708cf91
+1809 1810 9392 0x1708ca1d
+1810 1811 9391 0x1708c4b5
+1811 1812 9390 0x1708bf47
+1812 1813 9389 0x1708b9e9
+1813 1814 9388 0x1708b486
+1814 1815 9387 0x1708af28
+1815 1816 9386 0x1708a9ca
+1816 1817 9385 0x1708a472
+1817 1818 9384 0x17089f19
+1818 1819 9383 0x170899cc
+1819 1820 9382 0x17089479
+1820 1821 9381 0x17088f30
+1821 1822 9380 0x170889e8
+1822 1823 9379 0x170884a0
+1823 1824 9378 0x17087f5d
+1824 1825 9377 0x17087a1f
+1825 1826 9376 0x170874e7
+1826 1827 9375 0x17086faf
+1827 1828 9374 0x17086a7c
+1828 1829 9373 0x17086549
+1829 1830 9372 0x1708601b
+1830 1831 9371 0x17085af3
+1831 1832 9370 0x170855cb
+1832 1833 9369 0x170850a3
+1833 1834 9368 0x17084b80
+1834 1835 9367 0x17084663
+1835 1836 9366 0x1708414b
+1836 1837 9365 0x17083c2d
+1837 1838 9364 0x1708371a
+1838 1839 9363 0x17083208
+1839 1840 9362 0x17082cfa
+1840 1841 9361 0x170827ed
+1841 1842 9360 0x170822e5
+1842 1843 9359 0x17081de8
+1843 1844 9358 0x170818e5
+1844 1845 9357 0x170813e3
+1845 1846 9356 0x17080ee6
+1846 1847 9355 0x170809ee
+1847 1848 9354 0x170804fb
+1848 1849 9353 0x17080003
+1849 1850 9352 0x1707fb16
+1850 1851 9351 0x1707f629
+1851 1852 9350 0x1707f141
+1852 1853 9349 0x1707ec5a
+1853 1854 9348 0x1707e777
+1854 1855 9347 0x1707e295
+1855 1856 9346 0x1707ddb8
+1856 1857 9345 0x1707d8e0
+1857 1858 9344 0x1707d409
+1858 1859 9343 0x1707cf36
+1859 1860 9342 0x1707ca64
+1860 1861 9341 0x1707c597
+1861 1862 9340 0x1707c0ca
+1862 1863 9339 0x1707bc03
+1863 1864 9338 0x1707b740
+1864 1865 9337 0x1707b27e
+1865 1866 9336 0x1707adbc
+1866 1867 9335 0x1707a8ff
+1867 1868 9334 0x1707a448
+1868 1869 9333 0x17079f90
+1869 1870 9332 0x17079ade
+1870 1871 9331 0x1707962c
+1871 1872 9330 0x1707917f
+1872 1873 9329 0x17078cd3
+1873 1874 9328 0x17078831
+1874 1875 9327 0x17078389
+1875 1876 9326 0x17077ee7
+1876 1877 9325 0x17077a4b
+1877 1878 9324 0x170775ae
+1878 1879 9323 0x17077112
+1879 1880 9322 0x17076c7a
+1880 1881 9321 0x170767e8
+1881 1882 9320 0x17076357
+1882 1883 9319 0x17075eca
+1883 1884 9318 0x17075a3e
+1884 1885 9317 0x170755b6
+1885 1886 9316 0x1707512f
+1886 1887 9315 0x17074cad
+1887 1888 9314 0x1707482c
+1888 1889 9313 0x170743b5
+1889 1890 9312 0x17073f38
+1890 1891 9311 0x17073ac1
+1891 1892 9310 0x1707364f
+1892 1893 9309 0x170731de
+1893 1894 9308 0x17072d6c
+1894 1895 9307 0x17072900
+1895 1896 9306 0x17072499
+1896 1897 9305 0x17072032
+1897 1898 9304 0x17071bcb
+1898 1899 9303 0x17071769
+1899 1900 9302 0x1707130d
+1900 1901 9301 0x17070eb1
+1901 1902 9300 0x17070a55
+1902 1903 9299 0x170705fe
+1903 1904 9298 0x170701ac
+1904 1905 9297 0x1706fd60
+1905 1906 9296 0x1706f90f
+1906 1907 9295 0x1706f4c2
+1907 1908 9294 0x1706f07c
+1908 1909 9293 0x1706ec35
+1909 1910 9292 0x1706e7ee
+1910 1911 9291 0x1706e3ad
+1911 1912 9290 0x1706df71
+1912 1913 9289 0x1706db35
+1913 1914 9288 0x1706d6f9
+1914 1915 9287 0x1706d2c2
+1915 1916 9286 0x1706ce91
+1916 1917 9285 0x1706ca60
+1917 1918 9284 0x1706c62e
+1918 1919 9283 0x1706c202
+1919 1920 9282 0x1706bde1
+1920 1921 9281 0x1706b9bb
+1921 1922 9280 0x1706b594
+1922 1923 9279 0x1706b173
+1923 1924 9278 0x1706ad52
+1924 1925 9277 0x1706a936
+1925 1926 9276 0x1706a51a
+1926 1927 9275 0x1706a104
+1927 1928 9274 0x17069ced
+1928 1929 9273 0x170698dc
+1929 1930 9272 0x170694cb
+1930 1931 9271 0x170690bf
+1931 1932 9270 0x17068cb3
+1932 1933 9269 0x170688a8
+1933 1934 9268 0x170684a1
+1934 1935 9267 0x170680a6
+1935 1936 9266 0x17067ca5
+1936 1937 9265 0x170678a4
+1937 1938 9264 0x170674a8
+1938 1939 9263 0x170670ac
+1939 1940 9262 0x17066cb6
+1940 1941 9261 0x170668c5
+1941 1942 9260 0x170664cf
+1942 1943 9259 0x170660de
+1943 1944 9258 0x17065cf3
+1944 1945 9257 0x17065907
+1945 1946 9256 0x17065521
+1946 1947 9255 0x1706513b
+1947 1948 9254 0x17064d55
+1948 1949 9253 0x17064974
+1949 1950 9252 0x17064593
+1950 1951 9251 0x170641bd
+1951 1952 9250 0x17063de2
+1952 1953 9249 0x17063a06
+1953 1954 9248 0x17063635
+1954 1955 9247 0x1706325f
+1955 1956 9246 0x17062e8f
+1956 1957 9245 0x17062abe
+1957 1958 9244 0x170626f3
+1958 1959 9243 0x17062327
+1959 1960 9242 0x17061f61
+1960 1961 9241 0x17061b9b
+1961 1962 9240 0x170617db
+1962 1963 9239 0x1706141a
+1963 1964 9238 0x1706105a
+1964 1965 9237 0x17060c9e
+1965 1966 9236 0x170608e9
+1966 1967 9235 0x1706052d
+1967 1968 9234 0x1706017d
+1968 1969 9233 0x1705fdc7
+1969 1970 9232 0x1705fa16
+1970 1971 9231 0x1705f666
+1971 1972 9230 0x1705f2bb
+1972 1973 9229 0x1705ef10
+1973 1974 9228 0x1705eb6a
+1974 1975 9227 0x1705e7c4
+1975 1976 9226 0x1705e41e
+1976 1977 9225 0x1705e07e
+1977 1978 9224 0x1705dcde
+1978 1979 9223 0x1705d943
+1979 1980 9222 0x1705d5a8
+1980 1981 9221 0x1705d212
+1981 1982 9220 0x1705ce7c
+1982 1983 9219 0x1705cae7
+1983 1984 9218 0x1705c751
+1984 1985 9217 0x1705c3c6
+1985 1986 9216 0x1705c036
+1986 1987 9215 0x1705bcab
+1987 1988 9214 0x1705b920
+1988 1989 9213 0x1705b59a
+1989 1990 9212 0x1705b215
+1990 1991 9211 0x1705ae8f
+1991 1992 9210 0x1705ab0f
+1992 1993 9209 0x1705a78f
+1993 1994 9208 0x1705a40e
+1994 1995 9207 0x1705a094
+1995 1996 9206 0x17059d1e
+1996 1997 9205 0x170599a9
+1997 1998 9204 0x17059633
+1998 1999 9203 0x170592c3
+1999 2000 9202 0x17058f4e
+2000 2001 9201 0x17058be3
+2001 2002 9200 0x17058873
+2002 2003 9199 0x17058508
+2003 2004 9198 0x170581a3
+2004 2005 9197 0x17057e38
+2005 2006 9196 0x17057ad3
+2006 2007 9195 0x17057773
+2007 2008 9194 0x17057413
+2008 2009 9193 0x170570b3
+2009 2010 9192 0x17056d53
+2010 2011 9191 0x170569f8
+2011 2012 9190 0x170566a3
+2012 2013 9189 0x1705634e
+2013 2014 9188 0x17055ff8
+2014 2015 9187 0x17055ca8
+2015 2016 9186 0x1705595b
+2016 2017 9185 0x17055611
+2017 2018 9184 0x170552c9
+2018 2019 9183 0x17054f81
+2019 2020 9182 0x17054c3e
+2020 2021 9181 0x170548fc
+2021 2022 9180 0x170545b9
+2022 2023 9179 0x1705427c
+2023 2024 9178 0x17053f3f
+2024 2025 9177 0x17053c05
+2025 2026 9176 0x170538cd
+2026 2027 9175 0x17053595
+2027 2028 9174 0x17053263
+2028 2029 9173 0x17052f31
+2029 2030 9172 0x17052bfe
+2030 2031 9171 0x170528d1
+2031 2032 9170 0x170525a1
+2032 2033 9169 0x17052277
+2033 2034 9168 0x17051f4d
+2034 2035 9167 0x17051c25
+2035 2036 9166 0x17051900
+2036 2037 9165 0x170515db
+2037 2038 9164 0x170512b9
+2038 2039 9163 0x17050f99
+2039 2040 9162 0x17050c7c
+2040 2041 9161 0x1705095f
+2041 2042 9160 0x17050645
+2042 2043 9159 0x17050330
+2043 2044 9158 0x17050019
+2044 2045 9157 0x1704fd04
+2045 2046 9156 0x1704f9f2
+2046 2047 9155 0x1704f6e0
+2047 2048 9154 0x1704f3d0
+2048 2049 9153 0x1704f0c3
+2049 2050 9152 0x1704edb6
+2050 2051 9151 0x1704eaac
+2051 2052 9150 0x1704e7a5
+2052 2053 9149 0x1704e4a0
+2053 2054 9148 0x1704e19b
+2054 2055 9147 0x1704de99
+2055 2056 9146 0x1704db97
+2056 2057 9145 0x1704d898
+2057 2058 9144 0x1704d5a0
+2058 2059 9143 0x1704d2a4
+2059 2060 9142 0x1704cfaa
+2060 2061 9141 0x1704ccb2
+2061 2062 9140 0x1704c9be
+2062 2063 9139 0x1704c6c9
+2063 2064 9138 0x1704c3d7
+2064 2065 9137 0x1704c0e8
+2065 2066 9136 0x1704bdf8
+2066 2067 9135 0x1704bb0c
+2067 2068 9134 0x1704b81f
+2068 2069 9133 0x1704b535
+2069 2070 9132 0x1704b24e
+2070 2071 9131 0x1704af69
+2071 2072 9130 0x1704ac85
+2072 2073 9129 0x1704a9a3
+2073 2074 9128 0x1704a6c6
+2074 2075 9127 0x1704a3e7
+2075 2076 9126 0x1704a10b
+2076 2077 9125 0x17049e2e
+2077 2078 9124 0x17049b54
+2078 2079 9123 0x1704987d
+2079 2080 9122 0x170495a9
+2080 2081 9121 0x170492d4
+2081 2082 9120 0x17049000
+2082 2083 9119 0x17048d31
+2083 2084 9118 0x17048a61
+2084 2085 9117 0x17048792
+2085 2086 9116 0x170484c9
+2086 2087 9115 0x170481ff
+2087 2088 9114 0x17047f35
+2088 2089 9113 0x17047c71
+2089 2090 9112 0x170479ac
+2090 2091 9111 0x170476eb
+2091 2092 9110 0x17047429
+2092 2093 9109 0x1704716a
+2093 2094 9108 0x17046eab
+2094 2095 9107 0x17046bef
+2095 2096 9106 0x17046935
+2096 2097 9105 0x1704667b
+2097 2098 9104 0x170463c4
+2098 2099 9103 0x1704610d
+2099 2100 9102 0x17045e59
+2100 2101 9101 0x17045ba8
+2101 2102 9100 0x170458f6
+2102 2103 9099 0x17045647
+2103 2104 9098 0x1704539e
+2104 2105 9097 0x170450f1
+2105 2106 9096 0x17044e48
+2106 2107 9095 0x17044b9e
+2107 2108 9094 0x170448f7
+2108 2109 9093 0x17044653
+2109 2110 9092 0x170443af
+2110 2111 9091 0x1704410e
+2111 2112 9090 0x17043e6f
+2112 2113 9089 0x17043bd0
+2113 2114 9088 0x17043931
+2114 2115 9087 0x17043698
+2115 2116 9086 0x170433fe
+2116 2117 9085 0x17043165
+2117 2118 9084 0x17042ece
+2118 2119 9083 0x17042c3a
+2119 2120 9082 0x170429a9
+2120 2121 9081 0x17042717
+2121 2122 9080 0x17042489
+2122 2123 9079 0x170421fa
+2123 2124 9078 0x17041f6b
+2124 2125 9077 0x17041cdf
+2125 2126 9076 0x17041a56
+2126 2127 9075 0x170417cf
+2127 2128 9074 0x17041548
+2128 2129 9073 0x170412c2
+2129 2130 9072 0x1704103e
+2130 2131 9071 0x17040dbc
+2131 2132 9070 0x17040b3e
+2132 2133 9069 0x170408bd
+2133 2134 9068 0x17040641
+2134 2135 9067 0x170403c7
+2135 2136 9066 0x1704014e
+2136 2137 9065 0x1703fed5
+2137 2138 9064 0x1703fc5e
+2138 2139 9063 0x1703f9e8
+2139 2140 9062 0x1703f774
+2140 2141 9061 0x1703f500
+2141 2142 9060 0x1703f28f
+2142 2143 9059 0x1703f020
+2143 2144 9058 0x1703edb2
+2144 2145 9057 0x1703eb46
+2145 2146 9056 0x1703e8da
+2146 2147 9055 0x1703e671
+2147 2148 9054 0x1703e40b
+2148 2149 9053 0x1703e1a4
+2149 2150 9052 0x1703df40
+2150 2151 9051 0x1703dcdd
+2151 2152 9050 0x1703da7c
+2152 2153 9049 0x1703d81a
+2153 2154 9048 0x1703d5bc
+2154 2155 9047 0x1703d35e
+2155 2156 9046 0x1703d102
+2156 2157 9045 0x1703cea9
+2157 2158 9044 0x1703cc50
+2158 2159 9043 0x1703c9f7
+2159 2160 9042 0x1703c7a0
+2160 2161 9041 0x1703c54d
+2161 2162 9040 0x1703c2f9
+2162 2163 9039 0x1703c0a5
+2163 2164 9038 0x1703be57
+2164 2165 9037 0x1703bc06
+2165 2166 9036 0x1703b9bd
+2166 2167 9035 0x1703b771
+2167 2168 9034 0x1703b526
+2168 2169 9033 0x1703b2dd
+2169 2170 9032 0x1703b094
+2170 2171 9031 0x1703ae4e
+2171 2172 9030 0x1703ac0a
+2172 2173 9029 0x1703a9c7
+2173 2174 9028 0x1703a783
+2174 2175 9027 0x1703a542
+2175 2176 9026 0x1703a304
+2176 2177 9025 0x1703a0c6
+2177 2178 9024 0x17039e8a
+2178 2179 9023 0x17039c4f
+2179 2180 9022 0x17039a13
+2180 2181 9021 0x170397dd
+2181 2182 9020 0x170395a7
+2182 2183 9019 0x17039371
+2183 2184 9018 0x1703913d
+2184 2185 9017 0x17038f0a
+2185 2186 9016 0x17038cd9
+2186 2187 9015 0x17038aa8
+2187 2188 9014 0x1703887a
+2188 2189 9013 0x1703864c
+2189 2190 9012 0x17038420
+2190 2191 9011 0x170381f5
+2191 2192 9010 0x17037fcc
+2192 2193 9009 0x17037da6
+2193 2194 9008 0x17037b7d
+2194 2195 9007 0x1703795a
+2195 2196 9006 0x17037737
+2196 2197 9005 0x17037516
+2197 2198 9004 0x170372f3
+2198 2199 9003 0x170370d5
+2199 2200 9002 0x17036eb4
+2200 2201 9001 0x17036c99
+2201 2202 9000 0x17036a7b
+2202 2203 8999 0x17036862
+2203 2204 8998 0x17036647
+2204 2205 8997 0x1703642e
+2205 2206 8996 0x17036218
+2206 2207 8995 0x17036002
+2207 2208 8994 0x17035def
+2208 2209 8993 0x17035bdb
+2209 2210 8992 0x170359cb
+2210 2211 8991 0x170357ba
+2211 2212 8990 0x170355ac
+2212 2213 8989 0x1703539e
+2213 2214 8988 0x17035193
+2214 2215 8987 0x17034f88
+2215 2216 8986 0x17034d7d
+2216 2217 8985 0x17034b74
+2217 2218 8984 0x1703496e
+2218 2219 8983 0x17034766
+2219 2220 8982 0x17034563
+2220 2221 8981 0x1703435f
+2221 2222 8980 0x1703415c
+2222 2223 8979 0x17033f5c
+2223 2224 8978 0x17033d5b
+2224 2225 8977 0x17033b5b
+2225 2226 8976 0x17033960
+2226 2227 8975 0x17033764
+2227 2228 8974 0x17033569
+2228 2229 8973 0x1703336e
+2229 2230 8972 0x17033176
+2230 2231 8971 0x17032f80
+2231 2232 8970 0x17032d8a
+2232 2233 8969 0x17032b94
+2233 2234 8968 0x170329a1
+2234 2235 8967 0x170327ae
+2235 2236 8966 0x170325bb
+2236 2237 8965 0x170323cb
+2237 2238 8964 0x170321dd
+2238 2239 8963 0x17031fef
+2239 2240 8962 0x17031e02
+2240 2241 8961 0x17031c17
+2241 2242 8960 0x17031a2e
+2242 2243 8959 0x17031846
+2243 2244 8958 0x1703165e
+2244 2245 8957 0x17031478
+2245 2246 8956 0x17031292
+2246 2247 8955 0x170310ad
+2247 2248 8954 0x17030eca
+2248 2249 8953 0x17030ce7
+2249 2250 8952 0x17030b06
+2250 2251 8951 0x17030926
+2251 2252 8950 0x17030748
+2252 2253 8949 0x1703056b
+2253 2254 8948 0x1703038d
+2254 2255 8947 0x170301b2
+2255 2256 8946 0x1702ffd7
+2256 2257 8945 0x1702fdff
+2257 2258 8944 0x1702fc2a
+2258 2259 8943 0x1702fa51
+2259 2260 8942 0x1702f87c
+2260 2261 8941 0x1702f6a9
+2261 2262 8940 0x1702f4d3
+2262 2263 8939 0x1702f303
+2263 2264 8938 0x1702f130
+2264 2265 8937 0x1702ef60
+2265 2266 8936 0x1702ed93
+2266 2267 8935 0x1702ebc2
+2267 2268 8934 0x1702e9f7
+2268 2269 8933 0x1702e82a
+2269 2270 8932 0x1702e65f
+2270 2271 8931 0x1702e497
+2271 2272 8930 0x1702e2cc
+2272 2273 8929 0x1702e107
+2273 2274 8928 0x1702df41
+2274 2275 8927 0x1702dd7c
+2275 2276 8926 0x1702dbb6
+2276 2277 8925 0x1702d9f4
+2277 2278 8924 0x1702d831
+2278 2279 8923 0x1702d671
+2279 2280 8922 0x1702d4b1
+2280 2281 8921 0x1702d2f0
+2281 2282 8920 0x1702d133
+2282 2283 8919 0x1702cf76
+2283 2284 8918 0x1702cdbb
+2284 2285 8917 0x1702cbfd
+2285 2286 8916 0x1702ca45
+2286 2287 8915 0x1702c88b
+2287 2288 8914 0x1702c6d5
+2288 2289 8913 0x1702c520
+2289 2290 8912 0x1702c368
+2290 2291 8911 0x1702c1b3
+2291 2292 8910 0x1702c000
+2292 2293 8909 0x1702be4d
+2293 2294 8908 0x1702bc9a
+2294 2295 8907 0x1702baea
+2295 2296 8906 0x1702b93a
+2296 2297 8905 0x1702b78a
+2297 2298 8904 0x1702b5dd
+2298 2299 8903 0x1702b430
+2299 2300 8902 0x1702b283
+2300 2301 8901 0x1702b0d8
+2301 2302 8900 0x1702af2d
+2302 2303 8899 0x1702ad88
+2303 2304 8898 0x1702abe4
+2304 2305 8897 0x1702aa3f
+2305 2306 8896 0x1702a89b
+2306 2307 8895 0x1702a6f8
+2307 2308 8894 0x1702a557
+2308 2309 8893 0x1702a3b6
+2309 2310 8892 0x1702a216
+2310 2311 8891 0x1702a077
+2311 2312 8890 0x17029eda
+2312 2313 8889 0x17029d3d
+2313 2314 8888 0x17029ba1
+2314 2315 8887 0x17029a05
+2315 2316 8886 0x1702986c
+2316 2317 8885 0x170296d3
+2317 2318 8884 0x1702953b
+2318 2319 8883 0x170293a6
+2319 2320 8882 0x1702920f
+2320 2321 8881 0x1702907a
+2321 2322 8880 0x17028ee5
+2322 2323 8879 0x17028d52
+2323 2324 8878 0x17028bc0
+2324 2325 8877 0x17028a2d
+2325 2326 8876 0x1702889e
+2326 2327 8875 0x1702870e
+2327 2328 8874 0x1702857f
+2328 2329 8873 0x170283f1
+2329 2330 8872 0x17028265
+2330 2331 8871 0x170280d9
+2331 2332 8870 0x17027f4e
+2332 2333 8869 0x17027dc3
+2333 2334 8868 0x17027c3d
+2334 2335 8867 0x17027ab4
+2335 2336 8866 0x1702792d
+2336 2337 8865 0x170277a7
+2337 2338 8864 0x17027622
+2338 2339 8863 0x1702749d
+2339 2340 8862 0x17027319
+2340 2341 8861 0x17027197
+2341 2342 8860 0x17027016
+2342 2343 8859 0x17026e95
+2343 2344 8858 0x17026d15
+2344 2345 8857 0x17026b95
+2345 2346 8856 0x17026a17
+2346 2347 8855 0x1702689a
+2347 2348 8854 0x1702671e
+2348 2349 8853 0x170265a2
+2349 2350 8852 0x17026429
+2350 2351 8851 0x170262b0
+2351 2352 8850 0x17026137
+2352 2353 8849 0x17025fc0
+2353 2354 8848 0x17025e48
+2354 2355 8847 0x17025cd2
+2355 2356 8846 0x17025b5d
+2356 2357 8845 0x170259e9
+2357 2358 8844 0x17025876
+2358 2359 8843 0x17025703
+2359 2360 8842 0x17025592
+2360 2361 8841 0x17025421
+2361 2362 8840 0x170252b2
+2362 2363 8839 0x17025142
+2363 2364 8838 0x17024fd4
+2364 2365 8837 0x17024e68
+2365 2366 8836 0x17024cfd
+2366 2367 8835 0x17024b91
+2367 2368 8834 0x17024a26
+2368 2369 8833 0x170248bd
+2369 2370 8832 0x17024754
+2370 2371 8831 0x170245eb
+2371 2372 8830 0x17024485
+2372 2373 8829 0x1702431f
+2373 2374 8828 0x170241b8
+2374 2375 8827 0x17024055
+2375 2376 8826 0x17023ef0
+2376 2377 8825 0x17023d8e
+2377 2378 8824 0x17023c2c
+2378 2379 8823 0x17023acb
+2379 2380 8822 0x1702396b
+2380 2381 8821 0x1702380c
+2381 2382 8820 0x170236ae
+2382 2383 8819 0x1702354f
+2383 2384 8818 0x170233f3
+2384 2385 8817 0x17023297
+2385 2386 8816 0x1702313c
+2386 2387 8815 0x17022fe1
+2387 2388 8814 0x17022e87
+2388 2389 8813 0x17022d2f
+2389 2390 8812 0x17022bd7
+2390 2391 8811 0x17022a80
+2391 2392 8810 0x1702292a
+2392 2393 8809 0x170227d3
+2393 2394 8808 0x17022680
+2394 2395 8807 0x1702252b
+2395 2396 8806 0x170223da
+2396 2397 8805 0x17022288
+2397 2398 8804 0x17022136
+2398 2399 8803 0x17021fe6
+2399 2400 8802 0x17021e96
+2400 2401 8801 0x17021d47
+2401 2402 8800 0x17021bf9
+2402 2403 8799 0x17021aac
+2403 2404 8798 0x1702195f
+2404 2405 8797 0x17021814
+2405 2406 8796 0x170216c8
+2406 2407 8795 0x1702157e
+2407 2408 8794 0x17021436
+2408 2409 8793 0x170212ed
+2409 2410 8792 0x170211a6
+2410 2411 8791 0x17021060
+2411 2412 8790 0x17020f1a
+2412 2413 8789 0x17020dd5
+2413 2414 8788 0x17020c90
+2414 2415 8787 0x17020b4c
+2415 2416 8786 0x17020a0a
+2416 2417 8785 0x170208c8
+2417 2418 8784 0x17020786
+2418 2419 8783 0x17020645
+2419 2420 8782 0x17020506
+2420 2421 8781 0x170203c6
+2421 2422 8780 0x17020288
+2422 2423 8779 0x1702014b
+2423 2424 8778 0x1702000e
+2424 2425 8777 0x1701fed1
+2425 2426 8776 0x1701fd97
+2426 2427 8775 0x1701fc5d
+2427 2428 8774 0x1701fb23
+2428 2429 8773 0x1701f9eb
+2429 2430 8772 0x1701f8b2
+2430 2431 8771 0x1701f77b
+2431 2432 8770 0x1701f645
+2432 2433 8769 0x1701f50f
+2433 2434 8768 0x1701f3d9
+2434 2435 8767 0x1701f2a5
+2435 2436 8766 0x1701f171
+2436 2437 8765 0x1701f03e
+2437 2438 8764 0x1701ef0c
+2438 2439 8763 0x1701eddc
+2439 2440 8762 0x1701ecaa
+2440 2441 8761 0x1701eb7b
+2441 2442 8760 0x1701ea4d
+2442 2443 8759 0x1701e91f
+2443 2444 8758 0x1701e7f1
+2444 2445 8757 0x1701e6c3
+2445 2446 8756 0x1701e598
+2446 2447 8755 0x1701e46c
+2447 2448 8754 0x1701e342
+2448 2449 8753 0x1701e218
+2449 2450 8752 0x1701e0ee
+2450 2451 8751 0x1701dfc6
+2451 2452 8750 0x1701de9d
+2452 2453 8749 0x1701dd76
+2453 2454 8748 0x1701dc50
+2454 2455 8747 0x1701db2a
+2455 2456 8746 0x1701da06
+2456 2457 8745 0x1701d8e3
+2457 2458 8744 0x1701d7be
+2458 2459 8743 0x1701d69c
+2459 2460 8742 0x1701d579
+2460 2461 8741 0x1701d459
+2461 2462 8740 0x1701d337
+2462 2463 8739 0x1701d218
+2463 2464 8738 0x1701d0f9
+2464 2465 8737 0x1701cfda
+2465 2466 8736 0x1701cebc
+2466 2467 8735 0x1701cd9e
+2467 2468 8734 0x1701cc82
+2468 2469 8733 0x1701cb65
+2469 2470 8732 0x1701ca4a
+2470 2471 8731 0x1701c931
+2471 2472 8730 0x1701c818
+2472 2473 8729 0x1701c6fe
+2473 2474 8728 0x1701c5e6
+2474 2475 8727 0x1701c4cf
+2475 2476 8726 0x1701c3b8
+2476 2477 8725 0x1701c2a1
+2477 2478 8724 0x1701c18b
+2478 2479 8723 0x1701c077
+2479 2480 8722 0x1701bf62
+2480 2481 8721 0x1701be4e
+2481 2482 8720 0x1701bd3b
+2482 2483 8719 0x1701bc29
+2483 2484 8718 0x1701bb18
+2484 2485 8717 0x1701ba06
+2485 2486 8716 0x1701b8f6
+2486 2487 8715 0x1701b7e7
+2487 2488 8714 0x1701b6d9
+2488 2489 8713 0x1701b5ca
+2489 2490 8712 0x1701b4bc
+2490 2491 8711 0x1701b3b0
+2491 2492 8710 0x1701b2a4
+2492 2493 8709 0x1701b197
+2493 2494 8708 0x1701b08c
+2494 2495 8707 0x1701af81
+2495 2496 8706 0x1701ae78
+2496 2497 8705 0x1701ad6f
+2497 2498 8704 0x1701ac67
+2498 2499 8703 0x1701ab5f
+2499 2500 8702 0x1701aa58
+2500 2501 8701 0x1701a951
+2501 2502 8700 0x1701a84b
+2502 2503 8699 0x1701a747
+2503 2504 8698 0x1701a641
+2504 2505 8697 0x1701a53e
+2505 2506 8696 0x1701a43a
+2506 2507 8695 0x1701a337
+2507 2508 8694 0x1701a236
+2508 2509 8693 0x1701a134
+2509 2510 8692 0x1701a032
+2510 2511 8691 0x17019f32
+2511 2512 8690 0x17019e33
+2512 2513 8689 0x17019d34
+2513 2514 8688 0x17019c35
+2514 2515 8687 0x17019b38
+2515 2516 8686 0x17019a3a
+2516 2517 8685 0x1701993e
+2517 2518 8684 0x17019843
+2518 2519 8683 0x17019747
+2519 2520 8682 0x1701964c
+2520 2521 8681 0x17019553
+2521 2522 8680 0x17019459
+2522 2523 8679 0x17019360
+2523 2524 8678 0x17019267
+2524 2525 8677 0x1701916f
+2525 2526 8676 0x17019078
+2526 2527 8675 0x17018f81
+2527 2528 8674 0x17018e8c
+2528 2529 8673 0x17018d96
+2529 2530 8672 0x17018ca1
+2530 2531 8671 0x17018bad
+2531 2532 8670 0x17018aba
+2532 2533 8669 0x170189c6
+2533 2534 8668 0x170188d4
+2534 2535 8667 0x170187e3
+2535 2536 8666 0x170186f1
+2536 2537 8665 0x17018601
+2537 2538 8664 0x17018510
+2538 2539 8663 0x17018421
+2539 2540 8662 0x17018332
+2540 2541 8661 0x17018243
+2541 2542 8660 0x17018154
+2542 2543 8659 0x17018068
+2543 2544 8658 0x17017f7b
+2544 2545 8657 0x17017e8f
+2545 2546 8656 0x17017da2
+2546 2547 8655 0x17017cb8
+2547 2548 8654 0x17017bcd
+2548 2549 8653 0x17017ae3
+2549 2550 8652 0x170179fa
+2550 2551 8651 0x17017912
+2551 2552 8650 0x17017829
+2552 2553 8649 0x17017742
+2553 2554 8648 0x1701765a
+2554 2555 8647 0x17017573
+2555 2556 8646 0x1701748d
+2556 2557 8645 0x170173a7
+2557 2558 8644 0x170172c3
+2558 2559 8643 0x170171dd
+2559 2560 8642 0x170170f9
+2560 2561 8641 0x17017016
+2561 2562 8640 0x17016f32
+2562 2563 8639 0x17016e51
+2563 2564 8638 0x17016d70
+2564 2565 8637 0x17016c8e
+2565 2566 8636 0x17016bad
+2566 2567 8635 0x17016acd
+2567 2568 8634 0x170169ed
+2568 2569 8633 0x1701690e
+2569 2570 8632 0x1701682f
+2570 2571 8631 0x17016751
+2571 2572 8630 0x17016673
+2572 2573 8629 0x17016596
+2573 2574 8628 0x170164b8
+2574 2575 8627 0x170163dc
+2575 2576 8626 0x17016300
+2576 2577 8625 0x17016226
+2577 2578 8624 0x1701614b
+2578 2579 8623 0x17016070
+2579 2580 8622 0x17015f97
+2580 2581 8621 0x17015ebe
+2581 2582 8620 0x17015de6
+2582 2583 8619 0x17015d0e
+2583 2584 8618 0x17015c36
+2584 2585 8617 0x17015b5e
+2585 2586 8616 0x17015a87
+2586 2587 8615 0x170159b2
+2587 2588 8614 0x170158db
+2588 2589 8613 0x17015806
+2589 2590 8612 0x17015732
+2590 2591 8611 0x1701565e
+2591 2592 8610 0x1701558c
+2592 2593 8609 0x170154ba
+2593 2594 8608 0x170153e9
+2594 2595 8607 0x17015318
+2595 2596 8606 0x17015247
+2596 2597 8605 0x17015177
+2597 2598 8604 0x170150a7
+2598 2599 8603 0x17014fd9
+2599 2600 8602 0x17014f0a
+2600 2601 8601 0x17014e3c
+2601 2602 8600 0x17014d6e
+2602 2603 8599 0x17014ca0
+2603 2604 8598 0x17014bd4
+2604 2605 8597 0x17014b07
+2605 2606 8596 0x17014a3c
+2606 2607 8595 0x17014970
+2607 2608 8594 0x170148a5
+2608 2609 8593 0x170147db
+2609 2610 8592 0x17014712
+2610 2611 8591 0x17014648
+2611 2612 8590 0x17014580
+2612 2613 8589 0x170144b7
+2613 2614 8588 0x170143ef
+2614 2615 8587 0x17014327
+2615 2616 8586 0x17014260
+2616 2617 8585 0x1701419a
+2617 2618 8584 0x170140d3
+2618 2619 8583 0x1701400e
+2619 2620 8582 0x17013f49
+2620 2621 8581 0x17013e83
+2621 2622 8580 0x17013dc0
+2622 2623 8579 0x17013cfc
+2623 2624 8578 0x17013c38
+2624 2625 8577 0x17013b75
+2625 2626 8576 0x17013ab3
+2626 2627 8575 0x170139f2
+2627 2628 8574 0x17013930
+2628 2629 8573 0x1701386e
+2629 2630 8572 0x170137ae
+2630 2631 8571 0x170136ee
+2631 2632 8570 0x1701362e
+2632 2633 8569 0x1701356f
+2633 2634 8568 0x170134b0
+2634 2635 8567 0x170133f2
+2635 2636 8566 0x17013334
+2636 2637 8565 0x17013276
+2637 2638 8564 0x170131b9
+2638 2639 8563 0x170130fd
+2639 2640 8562 0x17013041
+2640 2641 8561 0x17012f86
+2641 2642 8560 0x17012ecb
+2642 2643 8559 0x17012e0f
+2643 2644 8558 0x17012d56
+2644 2645 8557 0x17012c9b
+2645 2646 8556 0x17012be2
+2646 2647 8555 0x17012b29
+2647 2648 8554 0x17012a70
+2648 2649 8553 0x170129b9
+2649 2650 8552 0x17012901
+2650 2651 8551 0x17012849
+2651 2652 8550 0x17012793
+2652 2653 8549 0x170126dc
+2653 2654 8548 0x17012626
+2654 2655 8547 0x17012570
+2655 2656 8546 0x170124bc
+2656 2657 8545 0x17012408
+2657 2658 8544 0x17012353
+2658 2659 8543 0x1701229f
+2659 2660 8542 0x170121ec
+2660 2661 8541 0x17012139
+2661 2662 8540 0x17012087
+2662 2663 8539 0x17011fd5
+2663 2664 8538 0x17011f23
+2664 2665 8537 0x17011e72
+2665 2666 8536 0x17011dc1
+2666 2667 8535 0x17011d11
+2667 2668 8534 0x17011c61
+2668 2669 8533 0x17011bb1
+2669 2670 8532 0x17011b02
+2670 2671 8531 0x17011a54
+2671 2672 8530 0x170119a6
+2672 2673 8529 0x170118f8
+2673 2674 8528 0x1701184a
+2674 2675 8527 0x1701179e
+2675 2676 8526 0x170116f1
+2676 2677 8525 0x17011645
+2677 2678 8524 0x17011599
+2678 2679 8523 0x170114ee
+2679 2680 8522 0x17011443
+2680 2681 8521 0x17011398
+2681 2682 8520 0x170112ee
+2682 2683 8519 0x17011244
+2683 2684 8518 0x1701119b
+2684 2685 8517 0x170110f2
+2685 2686 8516 0x1701104a
+2686 2687 8515 0x17010fa3
+2687 2688 8514 0x17010efb
+2688 2689 8513 0x17010e53
+2689 2690 8512 0x17010dad
+2690 2691 8511 0x17010d06
+2691 2692 8510 0x17010c60
+2692 2693 8509 0x17010bbb
+2693 2694 8508 0x17010b16
+2694 2695 8507 0x17010a70
+2695 2696 8506 0x170109cc
+2696 2697 8505 0x17010928
+2697 2698 8504 0x17010884
+2698 2699 8503 0x170107e1
+2699 2700 8502 0x1701073e
+2700 2701 8501 0x1701069c
+2701 2702 8500 0x170105f9
+2702 2703 8499 0x17010558
+2703 2704 8498 0x170104b7
+2704 2705 8497 0x17010416
+2705 2706 8496 0x17010376
+2706 2707 8495 0x170102d6
+2707 2708 8494 0x17010236
+2708 2709 8493 0x17010197
+2709 2710 8492 0x170100f8
+2710 2711 8491 0x17010059
+2711 2712 8490 0x1700ffbb
+2712 2713 8489 0x1700ff1d
+2713 2714 8488 0x1700fe80
+2714 2715 8487 0x1700fde3
+2715 2716 8486 0x1700fd46
+2716 2717 8485 0x1700fcaa
+2717 2718 8484 0x1700fc0e
+2718 2719 8483 0x1700fb73
+2719 2720 8482 0x1700fad8
+2720 2721 8481 0x1700fa3d
+2721 2722 8480 0x1700f9a3
+2722 2723 8479 0x1700f909
+2723 2724 8478 0x1700f86f
+2724 2725 8477 0x1700f7d6
+2725 2726 8476 0x1700f73d
+2726 2727 8475 0x1700f6a5
+2727 2728 8474 0x1700f60c
+2728 2729 8473 0x1700f575
+2729 2730 8472 0x1700f4dd
+2730 2731 8471 0x1700f446
+2731 2732 8470 0x1700f3af
+2732 2733 8469 0x1700f31a
+2733 2734 8468 0x1700f284
+2734 2735 8467 0x1700f1ee
+2735 2736 8466 0x1700f159
+2736 2737 8465 0x1700f0c5
+2737 2738 8464 0x1700f030
+2738 2739 8463 0x1700ef9b
+2739 2740 8462 0x1700ef08
+2740 2741 8461 0x1700ee74
+2741 2742 8460 0x1700ede1
+2742 2743 8459 0x1700ed4f
+2743 2744 8458 0x1700ecbc
+2744 2745 8457 0x1700ec2b
+2745 2746 8456 0x1700eb99
+2746 2747 8455 0x1700eb08
+2747 2748 8454 0x1700ea77
+2748 2749 8453 0x1700e9e7
+2749 2750 8452 0x1700e957
+2750 2751 8451 0x1700e8c7
+2751 2752 8450 0x1700e837
+2752 2753 8449 0x1700e7a8
+2753 2754 8448 0x1700e719
+2754 2755 8447 0x1700e68b
+2755 2756 8446 0x1700e5fd
+2756 2757 8445 0x1700e56f
+2757 2758 8444 0x1700e4e2
+2758 2759 8443 0x1700e454
+2759 2760 8442 0x1700e3c8
+2760 2761 8441 0x1700e33b
+2761 2762 8440 0x1700e2b0
+2762 2763 8439 0x1700e223
+2763 2764 8438 0x1700e199
+2764 2765 8437 0x1700e10e
+2765 2766 8436 0x1700e084
+2766 2767 8435 0x1700dff9
+2767 2768 8434 0x1700df6f
+2768 2769 8433 0x1700dee5
+2769 2770 8432 0x1700de5c
+2770 2771 8431 0x1700ddd3
+2771 2772 8430 0x1700dd4a
+2772 2773 8429 0x1700dcc2
+2773 2774 8428 0x1700dc3a
+2774 2775 8427 0x1700dbb2
+2775 2776 8426 0x1700db2b
+2776 2777 8425 0x1700daa4
+2777 2778 8424 0x1700da1d
+2778 2779 8423 0x1700d997
+2779 2780 8422 0x1700d911
+2780 2781 8421 0x1700d88c
+2781 2782 8420 0x1700d806
+2782 2783 8419 0x1700d781
+2783 2784 8418 0x1700d6fd
+2784 2785 8417 0x1700d678
+2785 2786 8416 0x1700d5f4
+2786 2787 8415 0x1700d570
+2787 2788 8414 0x1700d4ed
+2788 2789 8413 0x1700d46a
+2789 2790 8412 0x1700d3e7
+2790 2791 8411 0x1700d364
+2791 2792 8410 0x1700d2e2
+2792 2793 8409 0x1700d260
+2793 2794 8408 0x1700d1de
+2794 2795 8407 0x1700d15e
+2795 2796 8406 0x1700d0dd
+2796 2797 8405 0x1700d05c
+2797 2798 8404 0x1700cfdc
+2798 2799 8403 0x1700cf5c
+2799 2800 8402 0x1700cedc
+2800 2801 8401 0x1700ce5d
+2801 2802 8400 0x1700cddd
+2802 2803 8399 0x1700cd5f
+2803 2804 8398 0x1700cce1
+2804 2805 8397 0x1700cc62
+2805 2806 8396 0x1700cbe4
+2806 2807 8395 0x1700cb67
+2807 2808 8394 0x1700cae9
+2808 2809 8393 0x1700ca6d
+2809 2810 8392 0x1700c9f1
+2810 2811 8391 0x1700c974
+2811 2812 8390 0x1700c8f8
+2812 2813 8389 0x1700c87c
+2813 2814 8388 0x1700c801
+2814 2815 8387 0x1700c785
+2815 2816 8386 0x1700c70b
+2816 2817 8385 0x1700c690
+2817 2818 8384 0x1700c616
+2818 2819 8383 0x1700c59c
+2819 2820 8382 0x1700c522
+2820 2821 8381 0x1700c4a9
+2821 2822 8380 0x1700c42f
+2822 2823 8379 0x1700c3b7
+2823 2824 8378 0x1700c33e
+2824 2825 8377 0x1700c2c7
+2825 2826 8376 0x1700c24f
+2826 2827 8375 0x1700c1d7
+2827 2828 8374 0x1700c160
+2828 2829 8373 0x1700c0e8
+2829 2830 8372 0x1700c072
+2830 2831 8371 0x1700bffb
+2831 2832 8370 0x1700bf85
+2832 2833 8369 0x1700bf0f
+2833 2834 8368 0x1700be99
+2834 2835 8367 0x1700be23
+2835 2836 8366 0x1700bdaf
+2836 2837 8365 0x1700bd39
+2837 2838 8364 0x1700bcc4
+2838 2839 8363 0x1700bc50
+2839 2840 8362 0x1700bbdc
+2840 2841 8361 0x1700bb69
+2841 2842 8360 0x1700baf6
+2842 2843 8359 0x1700ba82
+2843 2844 8358 0x1700ba0f
+2844 2845 8357 0x1700b99d
+2845 2846 8356 0x1700b92a
+2846 2847 8355 0x1700b8b8
+2847 2848 8354 0x1700b846
+2848 2849 8353 0x1700b7d5
+2849 2850 8352 0x1700b763
+2850 2851 8351 0x1700b6f2
+2851 2852 8350 0x1700b681
+2852 2853 8349 0x1700b611
+2853 2854 8348 0x1700b5a0
+2854 2855 8347 0x1700b530
+2855 2856 8346 0x1700b4c1
+2856 2857 8345 0x1700b451
+2857 2858 8344 0x1700b3e2
+2858 2859 8343 0x1700b373
+2859 2860 8342 0x1700b304
+2860 2861 8341 0x1700b296
+2861 2862 8340 0x1700b228
+2862 2863 8339 0x1700b1ba
+2863 2864 8338 0x1700b14c
+2864 2865 8337 0x1700b0de
+2865 2866 8336 0x1700b071
+2866 2867 8335 0x1700b004
+2867 2868 8334 0x1700af97
+2868 2869 8333 0x1700af2a
+2869 2870 8332 0x1700aebe
+2870 2871 8331 0x1700ae53
+2871 2872 8330 0x1700ade7
+2872 2873 8329 0x1700ad7c
+2873 2874 8328 0x1700ad10
+2874 2875 8327 0x1700aca5
+2875 2876 8326 0x1700ac3a
+2876 2877 8325 0x1700abd0
+2877 2878 8324 0x1700ab66
+2878 2879 8323 0x1700aafd
+2879 2880 8322 0x1700aa94
+2880 2881 8321 0x1700aa2b
+2881 2882 8320 0x1700a9c2
+2882 2883 8319 0x1700a959
+2883 2884 8318 0x1700a8f1
+2884 2885 8317 0x1700a889
+2885 2886 8316 0x1700a821
+2886 2887 8315 0x1700a7bb
+2887 2888 8314 0x1700a753
+2888 2889 8313 0x1700a6ec
+2889 2890 8312 0x1700a686
+2890 2891 8311 0x1700a61f
+2891 2892 8310 0x1700a5b9
+2892 2893 8309 0x1700a553
+2893 2894 8308 0x1700a4ed
+2894 2895 8307 0x1700a487
+2895 2896 8306 0x1700a422
+2896 2897 8305 0x1700a3bd
+2897 2898 8304 0x1700a358
+2898 2899 8303 0x1700a2f3
+2899 2900 8302 0x1700a28f
+2900 2901 8301 0x1700a22b
+2901 2902 8300 0x1700a1c7
+2902 2903 8299 0x1700a164
+2903 2904 8298 0x1700a100
+2904 2905 8297 0x1700a09d
+2905 2906 8296 0x1700a03a
+2906 2907 8295 0x17009fd7
+2907 2908 8294 0x17009f75
+2908 2909 8293 0x17009f13
+2909 2910 8292 0x17009eb1
+2910 2911 8291 0x17009e4f
+2911 2912 8290 0x17009ded
+2912 2913 8289 0x17009d8c
+2913 2914 8288 0x17009d2b
+2914 2915 8287 0x17009cca
+2915 2916 8286 0x17009c69
+2916 2917 8285 0x17009c09
+2917 2918 8284 0x17009ba9
+2918 2919 8283 0x17009b49
+2919 2920 8282 0x17009ae9
+2920 2921 8281 0x17009a8a
+2921 2922 8280 0x17009a2a
+2922 2923 8279 0x170099cb
+2923 2924 8278 0x1700996c
+2924 2925 8277 0x1700990e
+2925 2926 8276 0x170098af
+2926 2927 8275 0x17009851
+2927 2928 8274 0x170097f3
+2928 2929 8273 0x17009795
+2929 2930 8272 0x17009738
+2930 2931 8271 0x170096db
+2931 2932 8270 0x1700967e
+2932 2933 8269 0x17009621
+2933 2934 8268 0x170095c4
+2934 2935 8267 0x17009568
+2935 2936 8266 0x1700950c
+2936 2937 8265 0x170094b0
+2937 2938 8264 0x17009454
+2938 2939 8263 0x170093f9
+2939 2940 8262 0x1700939d
+2940 2941 8261 0x17009342
+2941 2942 8260 0x170092e7
+2942 2943 8259 0x1700928d
+2943 2944 8258 0x17009232
+2944 2945 8257 0x170091d8
+2945 2946 8256 0x1700917e
+2946 2947 8255 0x17009124
+2947 2948 8254 0x170090cb
+2948 2949 8253 0x17009072
+2949 2950 8252 0x17009019
+2950 2951 8251 0x17008fc0
+2951 2952 8250 0x17008f67
+2952 2953 8249 0x17008f0f
+2953 2954 8248 0x17008eb6
+2954 2955 8247 0x17008e5e
+2955 2956 8246 0x17008e06
+2956 2957 8245 0x17008dae
+2957 2958 8244 0x17008d57
+2958 2959 8243 0x17008d00
+2959 2960 8242 0x17008ca9
+2960 2961 8241 0x17008c52
+2961 2962 8240 0x17008bfb
+2962 2963 8239 0x17008ba5
+2963 2964 8238 0x17008b4f
+2964 2965 8237 0x17008af9
+2965 2966 8236 0x17008aa3
+2966 2967 8235 0x17008a4e
+2967 2968 8234 0x170089f9
+2968 2969 8233 0x170089a3
+2969 2970 8232 0x1700894e
+2970 2971 8231 0x170088fa
+2971 2972 8230 0x170088a5
+2972 2973 8229 0x17008851
+2973 2974 8228 0x170087fc
+2974 2975 8227 0x170087a9
+2975 2976 8226 0x17008755
+2976 2977 8225 0x17008701
+2977 2978 8224 0x170086ae
+2978 2979 8223 0x1700865b
+2979 2980 8222 0x17008608
+2980 2981 8221 0x170085b5
+2981 2982 8220 0x17008563
+2982 2983 8219 0x17008511
+2983 2984 8218 0x170084bf
+2984 2985 8217 0x1700846c
+2985 2986 8216 0x1700841b
+2986 2987 8215 0x170083c9
+2987 2988 8214 0x17008378
+2988 2989 8213 0x17008327
+2989 2990 8212 0x170082d6
+2990 2991 8211 0x17008285
+2991 2992 8210 0x17008235
+2992 2993 8209 0x170081e4
+2993 2994 8208 0x17008194
+2994 2995 8207 0x17008144
+2995 2996 8206 0x170080f5
+2996 2997 8205 0x170080a5
+2997 2998 8204 0x17008056
+2998 2999 8203 0x17008006
+2999 3000 8202 0x167fb822
+3000 3001 8201 0x167f6950
+3001 3002 8200 0x167f1a7e
+3002 3003 8199 0x167ecc02
+3003 3004 8198 0x167e7ddc
+3004 3005 8197 0x167e2fb6
+3005 3006 8196 0x167de1e5
+3006 3007 8195 0x167d946b
+3007 3008 8194 0x167d46f1
+3008 3009 8193 0x167cf9cc
+3009 3010 8192 0x167caca7
+3010 3011 8191 0x167c5fd9
+3011 3012 8190 0x167c130a
+3012 3013 8189 0x167bc691
+3013 3014 8188 0x167b7a6e
+3014 3015 8187 0x167b2df5
+3015 3016 8186 0x167ae228
+3016 3017 8185 0x167a965b
+3017 3018 8184 0x167a4a8e
+3018 3019 8183 0x1679ff6d
+3019 3020 8182 0x1679b3f6
+3020 3021 8181 0x167968d4
+3021 3022 8180 0x16791e09
+3022 3023 8179 0x1678d33d
+3023 3024 8178 0x167888c8
+3024 3025 8177 0x16783efe
+3025 3026 8176 0x1677f4de
+3026 3027 8175 0x1677aabe
+3027 3028 8174 0x167760f4
+3028 3029 8173 0x16771780
+3029 3030 8172 0x1676ce0c
+3030 3031 8171 0x16768498
+3031 3032 8170 0x16763b7a
+3032 3033 8169 0x1675f2b1
+3033 3034 8168 0x1675a9e9
+3034 3035 8167 0x16756177
+3035 3036 8166 0x16751904
+3036 3037 8165 0x1674d0e8
+3037 3038 8164 0x167488cb
+3038 3039 8163 0x16744104
+3039 3040 8162 0x1673f993
+3040 3041 8161 0x1673b222
+3041 3042 8160 0x16736ab2
+3042 3043 8159 0x16732397
+3043 3044 8158 0x1672dcd1
+3044 3045 8157 0x1672960c
+3045 3046 8156 0x16724f47
+3046 3047 8155 0x167208d8
+3047 3048 8154 0x1671c2be
+3048 3049 8153 0x16717c4f
+3049 3050 8152 0x1671368b
+3050 3051 8151 0x1670f0c8
+3051 3052 8150 0x1670ab04
+3052 3053 8149 0x16706597
+3053 3054 8148 0x1670207f
+3054 3055 8147 0x166fdbbd
+3055 3056 8146 0x166f96a5
+3056 3057 8145 0x166f51e3
+3057 3058 8144 0x166f0d77
+3058 3059 8143 0x166ec90b
+3059 3060 8142 0x166e849f
+3060 3061 8141 0x166e4088
+3061 3062 8140 0x166dfcc8
+3062 3063 8139 0x166db908
+3063 3064 8138 0x166d7547
+3064 3065 8137 0x166d31dd
+3065 3066 8136 0x166ceec8
+3066 3067 8135 0x166cabb4
+3067 3068 8134 0x166c689f
+3068 3069 8133 0x166c25e0
+3069 3070 8132 0x166be321
+3070 3071 8131 0x166ba10e
+3071 3072 8130 0x166b5ea5
+3072 3073 8129 0x166b1c92
+3073 3074 8128 0x166ada7f
+3074 3075 8127 0x166a98c2
+3075 3076 8126 0x166a575b
+3076 3077 8125 0x166a159d
+3077 3078 8124 0x1669d436
+3078 3079 8123 0x16699325
+3079 3080 8122 0x16695213
+3080 3081 8121 0x16691157
+3081 3082 8120 0x1668d09c
+3082 3083 8119 0x16688fe0
+3083 3084 8118 0x16684f7b
+3084 3085 8117 0x16680f6b
+3085 3086 8116 0x1667cfb1
+3086 3087 8115 0x16678fa1
+3087 3088 8114 0x16674fe7
+3088 3089 8113 0x1667102d
+3089 3090 8112 0x1666d0c8
+3090 3091 8111 0x16669164
+3091 3092 8110 0x16665256
+3092 3093 8109 0x16661348
+3093 3094 8108 0x1665d48f
+3094 3095 8107 0x166595d7
+3095 3096 8106 0x1665571e
+3096 3097 8105 0x166518bc
+3097 3098 8104 0x1664da59
+3098 3099 8103 0x16649c4d
+3099 3100 8102 0x16645e96
+3100 3101 8101 0x166420df
+3101 3102 8100 0x1663e328
+3102 3103 8099 0x1663a5c7
+3103 3104 8098 0x16636866
+3104 3105 8097 0x16632b05
+3105 3106 8096 0x1662edfa
+3106 3107 8095 0x1662b0ef
+3107 3108 8094 0x1662743a
+3108 3109 8093 0x16623784
+3109 3110 8092 0x1661fb25
+3110 3111 8091 0x1661bec6
+3111 3112 8090 0x166182bc
+3112 3113 8089 0x166146b3
+3113 3114 8088 0x16610aa9
+3114 3115 8087 0x1660cef5
+3115 3116 8086 0x16609342
+3116 3117 8085 0x166057e4
+3117 3118 8084 0x16601c86
+3118 3119 8083 0x165fe17e
+3119 3120 8082 0x165fa676
+3120 3121 8081 0x165f6b6e
+3121 3122 8080 0x165f30bc
+3122 3123 8079 0x165ef60a
+3123 3124 8078 0x165ebb58
+3124 3125 8077 0x165e80fc
+3125 3126 8076 0x165e46a0
+3126 3127 8075 0x165e0c99
+3127 3128 8074 0x165dd293
+3128 3129 8073 0x165d98e2
+3129 3130 8072 0x165d5f32
+3130 3131 8071 0x165d2581
+3131 3132 8070 0x165cec7d
+3132 3133 8069 0x165cb322
+3133 3134 8068 0x165c7a1d
+3134 3135 8067 0x165c4119
+3135 3136 8066 0x165c0814
+3136 3137 8065 0x165bcf65
+3137 3138 8064 0x165b96b6
+3138 3139 8063 0x165b5e5d
+3139 3140 8062 0x165b2604
+3140 3141 8061 0x165aedab
+3141 3142 8060 0x165ab5a8
+3142 3143 8059 0x165a7da5
+3143 3144 8058 0x165a45a1
+3144 3145 8057 0x165a0df4
+3145 3146 8056 0x1659d647
+3146 3147 8055 0x16599f45
+3147 3148 8054 0x165967ee
+3148 3149 8053 0x16593096
+3149 3150 8052 0x1658f995
+3150 3151 8051 0x1658c293
+3151 3152 8050 0x16588be8
+3152 3153 8049 0x1658553c
+3153 3154 8048 0x16581e90
+3154 3155 8047 0x1657e83a
+3155 3156 8046 0x1657b1e4
+3156 3157 8045 0x16577b8f
+3157 3158 8044 0x1657458f
+3158 3159 8043 0x16570f8f
+3159 3160 8042 0x1656d9e4
+3160 3161 8041 0x1656a43a
+3161 3162 8040 0x16566e90
+3162 3163 8039 0x1656393c
+3163 3164 8038 0x165603e8
+3164 3165 8037 0x1655cee9
+3165 3166 8036 0x16559a16
+3166 3167 8035 0x1655656d
+3167 3168 8034 0x165530f0
+3168 3169 8033 0x1654fc72
+3169 3170 8032 0x1654c84a
+3170 3171 8031 0x16549423
+3171 3172 8030 0x16545ffb
+3172 3173 8029 0x16542c29
+3173 3174 8028 0x1653f857
+3174 3175 8027 0x1653c4b0
+3175 3176 8026 0x16539109
+3176 3177 8025 0x16535d8d
+3177 3178 8024 0x16532a92
+3178 3179 8023 0x1652f741
+3179 3180 8022 0x1652c446
+3180 3181 8021 0x1652914a
+3181 3182 8020 0x16525e7a
+3182 3183 8019 0x16522baa
+3183 3184 8018 0x1651f904
+3184 3185 8017 0x1651c68a
+3185 3186 8016 0x1651943b
+3186 3187 8015 0x165161eb
+3187 3188 8014 0x16512fc7
+3188 3189 8013 0x1650fdcd
+3189 3190 8012 0x1650cbfe
+3190 3191 8011 0x16509a2f
+3191 3192 8010 0x1650688c
+3192 3193 8009 0x16503713
+3193 3194 8008 0x165005c5
+3194 3195 8007 0x164fd477
+3195 3196 8006 0x164fa329
+3196 3197 8005 0x164f7206
+3197 3198 8004 0x164f410e
+3198 3199 8003 0x164f1041
+3199 3200 8002 0x164edf9f
+3200 3201 8001 0x164eaefd
+3201 3202 8000 0x164e7e5b
+3202 3203 7999 0x164e4e0e
+3203 3204 7998 0x164e1dc2
+3204 3205 7997 0x164deda1
+3205 3206 7996 0x164dbd7f
+3206 3207 7995 0x164d8d89
+3207 3208 7994 0x164d5dbd
+3208 3209 7993 0x164d2e48
+3209 3210 7992 0x164cfea7
+3210 3211 7991 0x164ccf31
+3211 3212 7990 0x164c9fbc
+3212 3213 7989 0x164c709c
+3213 3214 7988 0x164c4151
+3214 3215 7987 0x164c125c
+3215 3216 7986 0x164be367
+3216 3217 7985 0x164bb49d
+3217 3218 7984 0x164b85d4
+3218 3219 7983 0x164b575f
+3219 3220 7982 0x164b28c0
+3220 3221 7981 0x164afa77
+3221 3222 7980 0x164acc2e
+3222 3223 7979 0x164a9e10
+3223 3224 7978 0x164a7048
+3224 3225 7977 0x164a4254
+3225 3226 7976 0x164a1461
+3226 3227 7975 0x1649e6c4
+3227 3228 7974 0x1649b926
+3228 3229 7973 0x16498bb4
+3229 3230 7972 0x16495e41
+3230 3231 7971 0x164930fa
+3231 3232 7970 0x164903dd
+3232 3233 7969 0x1648d6c1
+3233 3234 7968 0x1648a9cf
+3234 3235 7967 0x16487d08
+3235 3236 7966 0x16485041
+3236 3237 7965 0x164823a6
+3237 3238 7964 0x1647f70a
+3238 3239 7963 0x1647cac4
+3239 3240 7962 0x16479e7e
+3240 3241 7961 0x16477263
+3241 3242 7960 0x16474648
+3242 3243 7959 0x16471a2d
+3243 3244 7958 0x1646ee68
+3244 3245 7957 0x1646c2a3
+3245 3246 7956 0x164696dd
+3246 3247 7955 0x16466b43
+3247 3248 7954 0x16463fd4
+3248 3249 7953 0x1646148f
+3249 3250 7952 0x1645e94b
+3250 3251 7951 0x1645be07
+3251 3252 7950 0x16459318
+3252 3253 7949 0x1645682a
+3253 3254 7948 0x16453d3b
+3254 3255 7947 0x164512a3
+3255 3256 7946 0x1644e80a
+3256 3257 7945 0x1644bd9c
+3257 3258 7944 0x1644932f
+3258 3259 7943 0x164468c1
+3259 3260 7942 0x16443e7e
+3260 3261 7941 0x16441466
+3261 3262 7940 0x1643ea79
+3262 3263 7939 0x1643c08c
+3263 3264 7938 0x164396a0
+3264 3265 7937 0x16436cde
+3265 3266 7936 0x16434346
+3266 3267 7935 0x164319da
+3267 3268 7934 0x1642f06e
+3268 3269 7933 0x1642c702
+3269 3270 7932 0x16429e17
+3270 3271 7931 0x164274d6
+3271 3272 7930 0x16424bea
+3272 3273 7929 0x164222ff
+3273 3274 7928 0x1641fa14
+3274 3275 7927 0x1641d17e
+3275 3276 7926 0x1641a8be
+3276 3277 7925 0x16418053
+3277 3278 7924 0x164157e9
+3278 3279 7923 0x16412f7e
+3279 3280 7922 0x1641073f
+3280 3281 7921 0x1640df2a
+3281 3282 7920 0x1640b715
+3282 3283 7919 0x16408f2c
+3283 3284 7918 0x1640676d
+3284 3285 7917 0x16403fd9
+3285 3286 7916 0x1640181a
+3286 3287 7915 0x163ff086
+3287 3288 7914 0x163fc91d
+3288 3289 7913 0x163fa1df
+3289 3290 7912 0x163f7a76
+3290 3291 7911 0x163f5363
+3291 3292 7910 0x163f2c50
+3292 3293 7909 0x163f053d
+3293 3294 7908 0x163ede80
+3294 3295 7907 0x163eb798
+3295 3296 7906 0x163e9106
+3296 3297 7905 0x163e6a48
+3297 3298 7904 0x163e43e1
+3298 3299 7903 0x163e1d7a
+3299 3300 7902 0x163df712
+3300 3301 7901 0x163dd101
+3301 3302 7900 0x163daaef
+3302 3303 7899 0x163d84de
+3303 3304 7898 0x163d5ef7
+3304 3305 7897 0x163d3911
+3305 3306 7896 0x163d1355
+3306 3307 7895 0x163ced9a
+3307 3308 7894 0x163cc809
+3308 3309 7893 0x163ca2a3
+3309 3310 7892 0x163c7d3d
+3310 3311 7891 0x163c57d8
+3311 3312 7890 0x163c329d
+3312 3313 7889 0x163c0d8d
+3313 3314 7888 0x163be87d
+3314 3315 7887 0x163bc398
+3315 3316 7886 0x163b9ede
+3316 3317 7885 0x163b7a24
+3317 3318 7884 0x163b556a
+3318 3319 7883 0x163b30db
+3319 3320 7882 0x163b0c4c
+3320 3321 7881 0x163ae7e8
+3321 3322 7880 0x163ac3ae
+3322 3323 7879 0x163a9f75
+3323 3324 7878 0x163a7b3c
+3324 3325 7877 0x163a572e
+3325 3326 7876 0x163a334a
+3326 3327 7875 0x163a0f67
+3327 3328 7874 0x1639eb83
+3328 3329 7873 0x1639c7cb
+3329 3330 7872 0x1639a43d
+3330 3331 7871 0x163980db
+3331 3332 7870 0x16395d78
+3332 3333 7869 0x16393a16
+3333 3334 7868 0x163916de
+3334 3335 7867 0x1638f3a6
+3335 3336 7866 0x1638d09a
+3336 3337 7865 0x1638ad8d
+3337 3338 7864 0x16388a80
+3338 3339 7863 0x163867c9
+3339 3340 7862 0x163844e8
+3340 3341 7861 0x1638225c
+3341 3342 7860 0x1637ffa5
+3342 3343 7859 0x1637dd44
+3343 3344 7858 0x1637bab8
+3344 3345 7857 0x16379882
+3345 3346 7856 0x16377621
+3346 3347 7855 0x16375441
+3347 3348 7854 0x1637320b
+3348 3349 7853 0x16371000
+3349 3350 7852 0x1636ee1f
+3350 3351 7851 0x1636cc3f
+3351 3352 7850 0x1636aa8a
+3352 3353 7849 0x163688d5
+3353 3354 7848 0x1636674a
+3354 3355 7847 0x163645c0
+3355 3356 7846 0x16362461
+3356 3357 7845 0x16360301
+3357 3358 7844 0x1635e1cd
+3358 3359 7843 0x1635c098
+3359 3360 7842 0x16359f64
+3360 3361 7841 0x16357e5a
+3361 3362 7840 0x16355da7
+3362 3363 7839 0x16353cc8
+3363 3364 7838 0x16351be9
+3364 3365 7837 0x1634fb36
+3365 3366 7836 0x1634da82
+3366 3367 7835 0x1634b9f9
+3367 3368 7834 0x1634999c
+3368 3369 7833 0x1634793e
+3369 3370 7832 0x163458e0
+3370 3371 7831 0x163438ad
+3371 3372 7830 0x1634187a
+3372 3373 7829 0x1633f872
+3373 3374 7828 0x1633d86a
+3374 3375 7827 0x1633b862
+3375 3376 7826 0x16339885
+3376 3377 7825 0x163378d3
+3377 3378 7824 0x1633594c
+3378 3379 7823 0x1633399a
+3379 3380 7822 0x16331a13
+3380 3381 7821 0x1632fa8c
+3381 3382 7820 0x1632db30
+3382 3383 7819 0x1632bbd3
+3383 3384 7818 0x16329ca2
+3384 3385 7817 0x16327d71
+3385 3386 7816 0x16325e6a
+3386 3387 7815 0x16323f64
+3387 3388 7814 0x1632205e
+3388 3389 7813 0x16320182
+3389 3390 7812 0x1631e2d2
+3390 3391 7811 0x1631c3f6
+3391 3392 7810 0x1631a571
+3392 3393 7809 0x163186eb
+3393 3394 7808 0x16316866
+3394 3395 7807 0x16314a0b
+3395 3396 7806 0x16312bb0
+3396 3397 7805 0x16310d56
+3397 3398 7804 0x1630ef26
+3398 3399 7803 0x1630d0f6
+3399 3400 7802 0x1630b2f2
+3400 3401 7801 0x163094ed
+3401 3402 7800 0x16307713
+3402 3403 7799 0x16305939
+3403 3404 7798 0x16303b5f
+3404 3405 7797 0x16301db1
+3405 3406 7796 0x16300002
+3406 3407 7795 0x162fe27e
+3407 3408 7794 0x162fc525
+3408 3409 7793 0x162fa7a1
+3409 3410 7792 0x162f8a48
+3410 3411 7791 0x162f6cef
+3411 3412 7790 0x162f4fc0
+3412 3413 7789 0x162f3292
+3413 3414 7788 0x162f158f
+3414 3415 7787 0x162ef88c
+3415 3416 7786 0x162edb89
+3416 3417 7785 0x162ebeb1
+3417 3418 7784 0x162ea1d8
+3418 3419 7783 0x162e8500
+3419 3420 7782 0x162e6853
+3420 3421 7781 0x162e4bd0
+3421 3422 7780 0x162e2f4e
+3422 3423 7779 0x162e12cc
+3423 3424 7778 0x162df674
+3424 3425 7777 0x162dda1d
+3425 3426 7776 0x162dbdc5
+3426 3427 7775 0x162da199
+3427 3428 7774 0x162d856c
+3428 3429 7773 0x162d696b
+3429 3430 7772 0x162d4d69
+3430 3431 7771 0x162d3168
+3431 3432 7770 0x162d1591
+3432 3433 7769 0x162cf9ba
+3433 3434 7768 0x162cdde4
+3434 3435 7767 0x162cc238
+3435 3436 7766 0x162ca68c
+3436 3437 7765 0x162c8b0b
+3437 3438 7764 0x162c6f8b
+3438 3439 7763 0x162c5435
+3439 3440 7762 0x162c38df
+3440 3441 7761 0x162c1d89
+3441 3442 7760 0x162c0233
+3442 3443 7759 0x162be708
+3443 3444 7758 0x162bcc08
+3444 3445 7757 0x162bb0dd
+3445 3446 7756 0x162b95dd
+3446 3447 7755 0x162b7add
+3447 3448 7754 0x162b6008
+3448 3449 7753 0x162b4533
+3449 3450 7752 0x162b2a89
+3450 3451 7751 0x162b0fb4
+3451 3452 7750 0x162af535
+3452 3453 7749 0x162adab6
+3453 3454 7748 0x162ac077
+3454 3455 7747 0x162aa622
+3455 3456 7746 0x162a8bf9
+3456 3457 7745 0x162a71ba
+3457 3458 7744 0x162a57a6
+3458 3459 7743 0x162a3d93
+3459 3460 7742 0x162a2394
+3460 3461 7741 0x162a09ab
+3461 3462 7740 0x1629efc2
+3462 3463 7739 0x1629d5ef
+3463 3464 7738 0x1629bc31
+3464 3465 7737 0x1629a288
+3465 3466 7736 0x162988e0
+3466 3467 7735 0x16296f4d
+3467 3468 7734 0x162955cf
+3468 3469 7733 0x16293c51
+3469 3470 7732 0x16292314
+3470 3471 7731 0x162909c2
+3471 3472 7730 0x1628f06f
+3472 3473 7729 0x1628d747
+3473 3474 7728 0x1628be1f
+3474 3475 7727 0x1628a4f8
+3475 3476 7726 0x16288bfb
+3476 3477 7725 0x162872fe
+3477 3478 7724 0x16285a17
+3478 3479 7723 0x1628412f
+3479 3480 7722 0x1628285d
+3480 3481 7721 0x16280fa1
+3481 3482 7720 0x1627f6fa
+3482 3483 7719 0x1627de53
+3483 3484 7718 0x1627c5c2
+3484 3485 7717 0x1627ad5b
+3485 3486 7716 0x162794df
+3486 3487 7715 0x16277c79
+3487 3488 7714 0x16276427
+3488 3489 7713 0x16274bd6
+3489 3490 7712 0x1627339b
+3490 3491 7711 0x16271b75
+3491 3492 7710 0x1627034e
+3492 3493 7709 0x1626eb53
+3493 3494 7708 0x1626d343
+3494 3495 7707 0x1626bb5d
+3495 3496 7706 0x1626a377
+3496 3497 7705 0x16268ba7
+3497 3498 7704 0x162673d6
+3498 3499 7703 0x16265c1c
+3499 3500 7702 0x162644a1
+3500 3501 7701 0x16262cfc
+3501 3502 7700 0x1626156c
+3502 3503 7699 0x1625fdf1
+3503 3504 7698 0x1625e68d
+3504 3505 7697 0x1625cf28
+3505 3506 7696 0x1625b7d8
+3506 3507 7695 0x1625a089
+3507 3508 7694 0x1625894f
+3508 3509 7693 0x1625722a
+3509 3510 7692 0x16255b05
+3510 3511 7691 0x1625440c
+3511 3512 7690 0x16252cfd
+3512 3513 7689 0x16251618
+3513 3514 7688 0x1624ff34
+3514 3515 7687 0x1624e866
+3515 3516 7686 0x1624d1ac
+3516 3517 7685 0x1624baf3
+3517 3518 7684 0x1624a44f
+3518 3519 7683 0x16248dac
+3519 3520 7682 0x1624771d
+3520 3521 7681 0x162460a4
+3521 3522 7680 0x16244a2c
+3522 3523 7679 0x162433c8
+3523 3524 7678 0x16241d65
+3524 3525 7677 0x1624072c
+3525 3526 7676 0x1623f0de
+3526 3527 7675 0x1623dabc
+3527 3528 7674 0x1623c499
+3528 3529 7673 0x1623ae8b
+3529 3530 7672 0x1623987e
+3530 3531 7671 0x1623829b
+3531 3532 7670 0x16236cb8
+3532 3533 7669 0x162356d6
+3533 3534 7668 0x16234109
+3534 3535 7667 0x16232b3c
+3535 3536 7666 0x16231599
+3536 3537 7665 0x1622ffe2
+3537 3538 7664 0x1622ea55
+3538 3539 7663 0x1622d4c8
+3539 3540 7662 0x1622bf3c
+3540 3541 7661 0x1622a9c4
+3541 3542 7660 0x16229463
+3542 3543 7659 0x16227f16
+3543 3544 7658 0x162269ca
+3544 3545 7657 0x1622547e
+3545 3546 7656 0x16223f72
+3546 3547 7655 0x16222a50
+3547 3548 7654 0x1622152f
+3548 3549 7653 0x16220023
+3549 3550 7652 0x1621eb2d
+3550 3551 7651 0x1621d636
+3551 3552 7650 0x1621c155
+3552 3553 7649 0x1621ac8a
+3553 3554 7648 0x162197be
+3554 3555 7647 0x16218308
+3555 3556 7646 0x16216e52
+3556 3557 7645 0x162159b1
+3557 3558 7644 0x16214511
+3558 3559 7643 0x16213086
+3559 3560 7642 0x16211c10
+3560 3561 7641 0x1621079a
+3561 3562 7640 0x1620f350
+3562 3563 7639 0x1620df05
+3563 3564 7638 0x1620caba
+3564 3565 7637 0x1620b685
+3565 3566 7636 0x1620a250
+3566 3567 7635 0x16208e30
+3567 3568 7634 0x16207a10
+3568 3569 7633 0x16206606
+3569 3570 7632 0x162051fb
+3570 3571 7631 0x16203e1c
+3571 3572 7630 0x16202a27
+3572 3573 7629 0x1620165d
+3573 3574 7628 0x1620027e
+3574 3575 7627 0x161feec9
+3575 3576 7626 0x161fdb15
+3576 3577 7625 0x161fc78b
+3577 3578 7624 0x161fb3ec
+3578 3579 7623 0x161fa04d
+3579 3580 7622 0x161f8cd9
+3580 3581 7621 0x161f7965
+3581 3582 7620 0x161f65f1
+3582 3583 7619 0x161f5292
+3583 3584 7618 0x161f3f34
+3584 3585 7617 0x161f2c00
+3585 3586 7616 0x161f18b7
+3586 3587 7615 0x161f0583
+3587 3588 7614 0x161ef265
+3588 3589 7613 0x161edf5c
+3589 3590 7612 0x161ecc3e
+3590 3591 7611 0x161eb94b
+3591 3592 7610 0x161ea66d
+3592 3593 7609 0x161e937a
+3593 3594 7608 0x161e809c
+3594 3595 7607 0x161e6dd4
+3595 3596 7606 0x161e5b0b
+3596 3597 7605 0x161e4858
+3597 3598 7604 0x161e35a6
+3598 3599 7603 0x161e2308
+3599 3600 7602 0x161e106b
+3600 3601 7601 0x161dfde3
+3601 3602 7600 0x161deb70
+3602 3603 7599 0x161dd8fe
+3603 3604 7598 0x161dc68b
+3604 3605 7597 0x161db42e
+3605 3606 7596 0x161da1e7
+3606 3607 7595 0x161d8f9f
+3607 3608 7594 0x161d7d6d
+3608 3609 7593 0x161d6b51
+3609 3610 7592 0x161d591e
+3610 3611 7591 0x161d4717
+3611 3612 7590 0x161d34fb
+3612 3613 7589 0x161d2309
+3613 3614 7588 0x161d1102
+3614 3615 7587 0x161cff26
+3615 3616 7586 0x161ced34
+3616 3617 7585 0x161cdb6d
+3617 3618 7584 0x161cc9a6
+3618 3619 7583 0x161cb7e0
+3619 3620 7582 0x161ca62e
+3620 3621 7581 0x161c947d
+3621 3622 7580 0x161c82e1
+3622 3623 7579 0x161c7170
+3623 3624 7578 0x161c5fea
+3624 3625 7577 0x161c4e64
+3625 3626 7576 0x161c3cf3
+3626 3627 7575 0x161c2b97
+3627 3628 7574 0x161c1a3c
+3628 3629 7573 0x161c08e1
+3629 3630 7572 0x161bf79b
+3630 3631 7571 0x161be655
+3631 3632 7570 0x161bd524
+3632 3633 7569 0x161bc409
+3633 3634 7568 0x161bb2ee
+3634 3635 7567 0x161ba1d3
+3635 3636 7566 0x161b90cd
+3636 3637 7565 0x161b7fdd
+3637 3638 7564 0x161b6f03
+3638 3639 7563 0x161b5e13
+3639 3640 7562 0x161b4d38
+3640 3641 7561 0x161b3c5d
+3641 3642 7560 0x161b2b98
+3642 3643 7559 0x161b1ae9
+3643 3644 7558 0x161b0a39
+3644 3645 7557 0x161af989
+3645 3646 7556 0x161ae8ef
+3646 3647 7555 0x161ad855
+3647 3648 7554 0x161ac7d0
+3648 3649 7553 0x161ab74b
+3649 3650 7552 0x161aa6dc
+3650 3651 7551 0x161a966d
+3651 3652 7550 0x161a8613
+3652 3653 7549 0x161a75b9
+3653 3654 7548 0x161a658a
+3654 3655 7547 0x161a5546
+3655 3656 7546 0x161a4501
+3656 3657 7545 0x161a34d2
+3657 3658 7544 0x161a24b9
+3658 3659 7543 0x161a149f
+3659 3660 7542 0x161a0486
+3660 3661 7541 0x1619f482
+3661 3662 7540 0x1619e494
+3662 3663 7539 0x1619d490
+3663 3664 7538 0x1619c4b7
+3664 3665 7537 0x1619b4c8
+3665 3666 7536 0x1619a504
+3666 3667 7535 0x1619952b
+3667 3668 7534 0x16198568
+3668 3669 7533 0x161975cf
+3669 3670 7532 0x16196621
+3670 3671 7531 0x16195673
+3671 3672 7530 0x161946da
+3672 3673 7529 0x16193757
+3673 3674 7528 0x161927bf
+3674 3675 7527 0x16191851
+3675 3676 7526 0x161908ce
+3676 3677 7525 0x1618f960
+3677 3678 7524 0x1618ea08
+3678 3679 7523 0x1618daaf
+3679 3680 7522 0x1618cb57
+3680 3681 7521 0x1618bc14
+3681 3682 7520 0x1618ace7
+3682 3683 7519 0x16189da4
+3683 3684 7518 0x16188e8c
+3684 3685 7517 0x16187f75
+3685 3686 7516 0x1618705d
+3686 3687 7515 0x16186145
+3687 3688 7514 0x16185243
+3688 3689 7513 0x16184356
+3689 3690 7512 0x16183453
+3690 3691 7511 0x1618257c
+3691 3692 7510 0x1618168f
+3692 3693 7509 0x161807b7
+3693 3694 7508 0x1617f8f5
+3694 3695 7507 0x1617ea33
+3695 3696 7506 0x1617db71
+3696 3697 7505 0x1617ccaf
+3697 3698 7504 0x1617be18
+3698 3699 7503 0x1617af6c
+3699 3700 7502 0x1617a0ea
+3700 3701 7501 0x16179253
+3701 3702 7500 0x161783d2
+3702 3703 7499 0x16177550
+3703 3704 7498 0x161766e4
+3704 3705 7497 0x16175878
+3705 3706 7496 0x16174a0c
+3706 3707 7495 0x16173bb5
+3707 3708 7494 0x16172d5e
+3708 3709 7493 0x16171f08
+3709 3710 7492 0x161710c7
+3710 3711 7491 0x1617029b
+3711 3712 7490 0x1616f45a
+3712 3713 7489 0x1616e643
+3713 3714 7488 0x1616d818
+3714 3715 7487 0x1616ca17
+3715 3716 7486 0x1616bc01
+3716 3717 7485 0x1616ae00
+3717 3718 7484 0x16169fff
+3718 3719 7483 0x16169214
+3719 3720 7482 0x16168413
+3720 3721 7481 0x1616763d
+3721 3722 7480 0x16166852
+3722 3723 7479 0x16165a7c
+3723 3724 7478 0x16164cbb
+3724 3725 7477 0x16163efb
+3725 3726 7476 0x1616313b
+3726 3727 7475 0x1616237a
+3727 3728 7474 0x161615cf
+3728 3729 7473 0x1616083a
+3729 3730 7472 0x1615faa4
+3730 3731 7471 0x1615ed0f
+3731 3732 7470 0x1615df7a
+3732 3733 7469 0x1615d1fa
+3733 3734 7468 0x1615c47a
+3734 3735 7467 0x1615b70f
+3735 3736 7466 0x1615a9a4
+3736 3737 7465 0x16159c3a
+3737 3738 7464 0x16158ee5
+3738 3739 7463 0x16158190
+3739 3740 7462 0x1615743b
+3740 3741 7461 0x16156711
+3741 3742 7460 0x161559e6
+3742 3743 7459 0x16154cbc
+3743 3744 7458 0x16153fa8
+3744 3745 7457 0x16153293
+3745 3746 7456 0x16152594
+3746 3747 7455 0x16151895
+3747 3748 7454 0x16150b95
+3748 3749 7453 0x1614fea1
+3749 3750 7452 0x1614f1b7
+3750 3751 7451 0x1614e4d8
+3751 3752 7450 0x1614d7f9
+3752 3753 7449 0x1614cb25
+3753 3754 7448 0x1614be51
+3754 3755 7447 0x1614b192
+3755 3756 7446 0x1614a4d3
+3756 3757 7445 0x1614981f
+3757 3758 7444 0x16148b6b
+3758 3759 7443 0x16147ec2
+3759 3760 7442 0x16147223
+3760 3761 7441 0x1614659a
+3761 3762 7440 0x16145906
+3762 3763 7439 0x16144c7d
+3763 3764 7438 0x16143ffe
+3764 3765 7437 0x1614338b
+3765 3766 7436 0x16142717
+3766 3767 7435 0x16141aae
+3767 3768 7434 0x16140e45
+3768 3769 7433 0x161401e7
+3769 3770 7432 0x1613f593
+3770 3771 7431 0x1613e94b
+3771 3772 7430 0x1613dd02
+3772 3773 7429 0x1613d0c4
+3773 3774 7428 0x1613c491
+3774 3775 7427 0x1613b85d
+3775 3776 7426 0x1613ac40
+3776 3777 7425 0x1613a022
+3777 3778 7424 0x16139404
+3778 3779 7423 0x161387f1
+3779 3780 7422 0x16137be9
+3780 3781 7421 0x16136feb
+3781 3782 7420 0x161363ed
+3782 3783 7419 0x161357fa
+3783 3784 7418 0x16134c08
+3784 3785 7417 0x1613401f
+3785 3786 7416 0x16133442
+3786 3787 7415 0x16132865
+3787 3788 7414 0x16131c9d
+3788 3789 7413 0x161310ca
+3789 3790 7412 0x1613050d
+3790 3791 7411 0x1612f950
+3791 3792 7410 0x1612eda8
+3792 3793 7409 0x1612e200
+3793 3794 7408 0x1612d658
+3794 3795 7407 0x1612cabb
+3795 3796 7406 0x1612bf1e
+3796 3797 7405 0x1612b38c
+3797 3798 7404 0x1612a804
+3798 3799 7403 0x16129c7d
+3799 3800 7402 0x16129100
+3800 3801 7401 0x1612858e
+3801 3802 7400 0x16127a27
+3802 3803 7399 0x16126ebf
+3803 3804 7398 0x16126358
+3804 3805 7397 0x16125806
+3805 3806 7396 0x16124cb4
+3806 3807 7395 0x16124178
+3807 3808 7394 0x16123631
+3808 3809 7393 0x16122af4
+3809 3810 7392 0x16121fb8
+3810 3811 7391 0x16121486
+3811 3812 7390 0x1612095f
+3812 3813 7389 0x1611fe43
+3813 3814 7388 0x1611f327
+3814 3815 7387 0x1611e815
+3815 3816 7386 0x1611dd04
+3816 3817 7385 0x1611d1fd
+3817 3818 7384 0x1611c701
+3818 3819 7383 0x1611bc05
+3819 3820 7382 0x1611b114
+3820 3821 7381 0x1611a622
+3821 3822 7380 0x16119b51
+3822 3823 7379 0x16119075
+3823 3824 7378 0x1611859a
+3824 3825 7377 0x16117ac8
+3825 3826 7376 0x16117002
+3826 3827 7375 0x1611653c
+3827 3828 7374 0x16115a80
+3828 3829 7373 0x16114fc5
+3829 3830 7372 0x16114514
+3830 3831 7371 0x16113a6d
+3831 3832 7370 0x16112fd2
+3832 3833 7369 0x1611252c
+3833 3834 7368 0x16111a9b
+3834 3835 7367 0x1611100a
+3835 3836 7366 0x16110585
+3836 3837 7365 0x1610faff
+3837 3838 7364 0x1610f099
+3838 3839 7363 0x1610e61e
+3839 3840 7362 0x1610dbad
+3840 3841 7361 0x1610d147
+3841 3842 7360 0x1610c6ec
+3842 3843 7359 0x1610bc91
+3843 3844 7358 0x1610b236
+3844 3845 7357 0x1610a7e6
+3845 3846 7356 0x16109da0
+3846 3847 7355 0x16109366
+3847 3848 7354 0x1610892b
+3848 3849 7353 0x16107ef0
+3849 3850 7352 0x161074c0
+3850 3851 7351 0x16106a9a
+3851 3852 7350 0x16106075
+3852 3853 7349 0x16105665
+3853 3854 7348 0x16104c55
+3854 3855 7347 0x16104245
+3855 3856 7346 0x16103840
+3856 3857 7345 0x16102e3b
+3857 3858 7344 0x16102441
+3858 3859 7343 0x16101a46
+3859 3860 7342 0x16101056
+3860 3861 7341 0x16100671
+3861 3862 7340 0x160ffc8d
+3862 3863 7339 0x160ff2a8
+3863 3864 7338 0x160fe8d8
+3864 3865 7337 0x160fdf09
+3865 3866 7336 0x160fd539
+3866 3867 7335 0x160fcb74
+3867 3868 7334 0x160fc1c5
+3868 3869 7333 0x160fb80b
+3869 3870 7332 0x160fae51
+3870 3871 7331 0x160fa4ac
+3871 3872 7330 0x160f9afd
+3872 3873 7329 0x160f9163
+3873 3874 7328 0x160f87c9
+3874 3875 7327 0x160f7e30
+3875 3876 7326 0x160f74a0
+3876 3877 7325 0x160f6b1c
+3877 3878 7324 0x160f6198
+3878 3879 7323 0x160f581e
+3879 3880 7322 0x160f4ea4
+3880 3881 7321 0x160f4536
+3881 3882 7320 0x160f3bc7
+3882 3883 7319 0x160f3262
+3883 3884 7318 0x160f2909
+3884 3885 7317 0x160f1fb0
+3885 3886 7316 0x160f1661
+3886 3887 7315 0x160f0d12
+3887 3888 7314 0x160f03ce
+3888 3889 7313 0x160efa8a
+3889 3890 7312 0x160ef146
+3890 3891 7311 0x160ee818
+3891 3892 7310 0x160edede
+3892 3893 7309 0x160ed5bb
+3893 3894 7308 0x160ecc97
+3894 3895 7307 0x160ec373
+3895 3896 7306 0x160eba5a
+3896 3897 7305 0x160eb141
+3897 3898 7304 0x160ea833
+3898 3899 7303 0x160e9f3a
+3899 3900 7302 0x160e9636
+3900 3901 7301 0x160e8d33
+3901 3902 7300 0x160e843a
+3902 3903 7299 0x160e7b4c
+3903 3904 7298 0x160e725e
+3904 3905 7297 0x160e6970
+3905 3906 7296 0x160e608c
+3906 3907 7295 0x160e57b4
+3907 3908 7294 0x160e4edb
+3908 3909 7293 0x160e460d
+3909 3910 7292 0x160e3d3f
+3910 3911 7291 0x160e347c
+3911 3912 7290 0x160e2bb9
+3912 3913 7289 0x160e2300
+3913 3914 7288 0x160e1a53
+3914 3915 7287 0x160e11a5
+3915 3916 7286 0x160e08f7
+3916 3917 7285 0x160e0049
+3917 3918 7284 0x160df7b1
+3918 3919 7283 0x160def0e
+3919 3920 7282 0x160de681
+3920 3921 7281 0x160ddde8
+3921 3922 7280 0x160dd566
+3922 3923 7279 0x160dccd8
+3923 3924 7278 0x160dc455
+3924 3925 7277 0x160dbbdd
+3925 3926 7276 0x160db365
+3926 3927 7275 0x160daaf8
+3927 3928 7274 0x160da28b
+3928 3929 7273 0x160d9a28
+3929 3930 7272 0x160d91d0
+3930 3931 7271 0x160d8978
+3931 3932 7270 0x160d8121
+3932 3933 7269 0x160d78c9
+3933 3934 7268 0x160d707c
+3934 3935 7267 0x160d6839
+3935 3936 7266 0x160d5ff7
+3936 3937 7265 0x160d57b4
+3937 3938 7264 0x160d4f7d
+3938 3939 7263 0x160d4750
+3939 3940 7262 0x160d3f23
+3940 3941 7261 0x160d36f6
+3941 3942 7260 0x160d2ed4
+3942 3943 7259 0x160d26b2
+3943 3944 7258 0x160d1e9a
+3944 3945 7257 0x160d168d
+3945 3946 7256 0x160d0e81
+3946 3947 7255 0x160d0674
+3947 3948 7254 0x160cfe72
+3948 3949 7253 0x160cf670
+3949 3950 7252 0x160cee79
+3950 3951 7251 0x160ce682
+3951 3952 7250 0x160cde8a
+3952 3953 7249 0x160cd69e
+3953 3954 7248 0x160ccebc
+3954 3955 7247 0x160cc6cf
+3955 3956 7246 0x160cbef8
+3956 3957 7245 0x160cb721
+3957 3958 7244 0x160caf4a
+3958 3959 7243 0x160ca773
+3959 3960 7242 0x160c9fb2
+3960 3961 7241 0x160c97f0
+3961 3962 7240 0x160c902f
+3962 3963 7239 0x160c886d
+3963 3964 7238 0x160c80b6
+3964 3965 7237 0x160c790a
+3965 3966 7236 0x160c7153
+3966 3967 7235 0x160c69b2
+3967 3968 7234 0x160c6206
+3968 3969 7233 0x160c5a64
+3969 3970 7232 0x160c52ce
+3970 3971 7231 0x160c4b37
+3971 3972 7230 0x160c43a0
+3972 3973 7229 0x160c3c14
+3973 3974 7228 0x160c3488
+3974 3975 7227 0x160c2d07
+3975 3976 7226 0x160c2591
+3976 3977 7225 0x160c1e10
+3977 3978 7224 0x160c1699
+3978 3979 7223 0x160c0f2d
+3979 3980 7222 0x160c07c2
+3980 3981 7221 0x160c0056
+3981 3982 7220 0x160bf8ea
+3982 3983 7219 0x160bf189
+3983 3984 7218 0x160bea33
+3984 3985 7217 0x160be2dd
+3985 3986 7216 0x160bdb86
+3986 3987 7215 0x160bd43b
+3987 3988 7214 0x160bccef
+3988 3989 7213 0x160bc5a4
+3989 3990 7212 0x160bbe63
+3990 3991 7211 0x160bb738
+3991 3992 7210 0x160baff7
+3992 3993 7209 0x160ba8c1
+3993 3994 7208 0x160ba196
+3994 3995 7207 0x160b9a6a
+3995 3996 7206 0x160b933f
+3996 3997 7205 0x160b8c1e
+3997 3998 7204 0x160b84fe
+3998 3999 7203 0x160b7de8
+3999 4000 7202 0x160b76d2
+4000 4001 7201 0x160b6fbc
+4001 4002 7200 0x160b68b1
+4002 4003 7199 0x160b61a6
+4003 4004 7198 0x160b5a9b
+4004 4005 7197 0x160b539a
+4005 4006 7196 0x160b4ca5
+4006 4007 7195 0x160b45af
+4007 4008 7194 0x160b3eb9
+4008 4009 7193 0x160b37ce
+4009 4010 7192 0x160b30d9
+4010 4011 7191 0x160b29f9
+4011 4012 7190 0x160b230e
+4012 4013 7189 0x160b1c2d
+4013 4014 7188 0x160b1558
+4014 4015 7187 0x160b0e78
+4015 4016 7186 0x160b07a2
+4016 4017 7185 0x160b00d8
+4017 4018 7184 0x160afa0d
+4018 4019 7183 0x160af342
+4019 4020 7182 0x160aec82
+4020 4021 7181 0x160ae5c2
+4021 4022 7180 0x160adf0d
+4022 4023 7179 0x160ad84d
+4023 4024 7178 0x160ad198
+4024 4025 7177 0x160acaed
+4025 4026 7176 0x160ac438
+4026 4027 7175 0x160abd98
+4027 4028 7174 0x160ab6f8
+4028 4029 7173 0x160ab05e
+4029 4030 7172 0x160aa9ce
+4030 4031 7171 0x160aa33e
+4031 4032 7170 0x160a9cb4
+4032 4033 7169 0x160a962a
+4033 4034 7168 0x160a8fa5
+4034 4035 7167 0x160a8925
+4035 4036 7166 0x160a82ab
+4036 4037 7165 0x160a7c3b
+4037 4038 7164 0x160a75c6
+4038 4039 7163 0x160a6f57
+4039 4040 7162 0x160a68e7
+4040 4041 7161 0x160a6283
+4041 4042 7160 0x160a5c1e
+4042 4043 7159 0x160a55b9
+4043 4044 7158 0x160a4f5f
+4044 4045 7157 0x160a4905
+4045 4046 7156 0x160a42b0
+4046 4047 7155 0x160a3c5c
+4047 4048 7154 0x160a3612
+4048 4049 7153 0x160a2fc3
+4049 4050 7152 0x160a297e
+4050 4051 7151 0x160a2339
+4051 4052 7150 0x160a1d00
+4052 4053 7149 0x160a16cb
+4053 4054 7148 0x160a1091
+4054 4055 7147 0x160a0a5d
+4055 4056 7146 0x160a042e
+4056 4057 7145 0x1609fe04
+4057 4058 7144 0x1609f7da
+4058 4059 7143 0x1609f1b6
+4059 4060 7142 0x1609eb91
+4060 4061 7141 0x1609e578
+4061 4062 7140 0x1609df5e
+4062 4063 7139 0x1609d945
+4063 4064 7138 0x1609d336
+4064 4065 7137 0x1609cd27
+4065 4066 7136 0x1609c718
+4066 4067 7135 0x1609c114
+4067 4068 7134 0x1609bb15
+4068 4069 7133 0x1609b516
+4069 4070 7132 0x1609af17
+4070 4071 7131 0x1609a91e
+4071 4072 7130 0x1609a32a
+4072 4073 7129 0x16099d3b
+4073 4074 7128 0x1609974c
+4074 4075 7127 0x16099163
+4075 4076 7126 0x16098b7a
+4076 4077 7125 0x16098596
+4077 4078 7124 0x16097fb7
+4078 4079 7123 0x160979de
+4079 4080 7122 0x16097405
+4080 4081 7121 0x16096e2b
+4081 4082 7120 0x1609685d
+4082 4083 7119 0x16096294
+4083 4084 7118 0x16095ccb
+4084 4085 7117 0x16095707
+4085 4086 7116 0x16095143
+4086 4087 7115 0x16094b7f
+4087 4088 7114 0x160945c6
+4088 4089 7113 0x1609400d
+4089 4090 7112 0x16093a59
+4090 4091 7111 0x160934a6
+4091 4092 7110 0x16092ef7
+4092 4093 7109 0x1609294e
+4093 4094 7108 0x160923a6
+4094 4095 7107 0x16091e02
+4095 4096 7106 0x1609185e
+4096 4097 7105 0x160912c6
+4097 4098 7104 0x16090d27
+4098 4099 7103 0x16090799
+4099 4100 7102 0x16090206
+4100 4101 7101 0x1608fc78
+4101 4102 7100 0x1608f6ef
+4102 4103 7099 0x1608f166
+4103 4104 7098 0x1608ebdd
+4104 4105 7097 0x1608e65f
+4105 4106 7096 0x1608e0e1
+4106 4107 7095 0x1608db63
+4107 4108 7094 0x1608d5eb
+4108 4109 7093 0x1608d077
+4109 4110 7092 0x1608cb0a
+4110 4111 7091 0x1608c59c
+4111 4112 7090 0x1608c02e
+4112 4113 7089 0x1608bacb
+4113 4114 7088 0x1608b56d
+4114 4115 7087 0x1608b00a
+4115 4116 7086 0x1608aab1
+4116 4117 7085 0x1608a553
+4117 4118 7084 0x1608a000
+4118 4119 7083 0x16089aad
+4119 4120 7082 0x1608955a
+4120 4121 7081 0x16089012
+4121 4122 7080 0x16088ac9
+4122 4123 7079 0x16088581
+4123 4124 7078 0x1608803e
+4124 4125 7077 0x16087b00
+4125 4126 7076 0x160875c3
+4126 4127 7075 0x1608708b
+4127 4128 7074 0x16086b58
+4128 4129 7073 0x1608662a
+4129 4130 7072 0x160860f7
+4130 4131 7071 0x16085bcf
+4131 4132 7070 0x160856a7
+4132 4133 7069 0x1608517f
+4133 4134 7068 0x16084c5c
+4134 4135 7067 0x1608473f
+4135 4136 7066 0x16084221
+4136 4137 7065 0x16083d09
+4137 4138 7064 0x160837f1
+4138 4139 7063 0x160832de
+4139 4140 7062 0x16082dd1
+4140 4141 7061 0x160828c4
+4141 4142 7060 0x160823bc
+4142 4143 7059 0x16081eb4
+4143 4144 7058 0x160819b1
+4144 4145 7057 0x160814b9
+4145 4146 7056 0x16080fbc
+4146 4147 7055 0x16080ac4
+4147 4148 7054 0x160805cd
+4148 4149 7053 0x160800da
+4149 4150 7052 0x1607fbe8
+4150 4151 7051 0x1607f6fa
+4151 4152 7050 0x1607f213
+4152 4153 7049 0x1607ed2b
+4153 4154 7048 0x1607e849
+4154 4155 7047 0x1607e366
+4155 4156 7046 0x1607de89
+4156 4157 7045 0x1607d9ac
+4157 4158 7044 0x1607d4d5
+4158 4159 7043 0x1607d002
+4159 4160 7042 0x1607cb35
+4160 4161 7041 0x1607c663
+4161 4162 7040 0x1607c196
+4162 4163 7039 0x1607bccf
+4163 4164 7038 0x1607b807
+4164 4165 7037 0x1607b345
+4165 4166 7036 0x1607ae88
+4166 4167 7035 0x1607a9cb
+4167 4168 7034 0x1607a50e
+4168 4169 7033 0x1607a057
+4169 4170 7032 0x16079ba5
+4170 4171 7031 0x160796f3
+4171 4172 7030 0x16079246
+4172 4173 7029 0x16078d99
+4173 4174 7028 0x160788f2
+4174 4175 7027 0x16078450
+4175 4176 7026 0x16077fae
+4176 4177 7025 0x16077b0c
+4177 4178 7024 0x1607766f
+4178 4179 7023 0x160771d8
+4179 4180 7022 0x16076d41
+4180 4181 7021 0x160768aa
+4181 4182 7020 0x16076418
+4182 4183 7019 0x16075f8b
+4183 4184 7018 0x16075aff
+4184 4185 7017 0x16075678
+4185 4186 7016 0x160751f0
+4186 4187 7015 0x16074d6f
+4187 4188 7014 0x160748ed
+4188 4189 7013 0x16074470
+4189 4190 7012 0x16073ff4
+4190 4191 7011 0x16073b82
+4191 4192 7010 0x1607370b
+4192 4193 7009 0x1607329a
+4193 4194 7008 0x16072e28
+4194 4195 7007 0x160729bc
+4195 4196 7006 0x16072555
+4196 4197 7005 0x160720ee
+4197 4198 7004 0x16071c87
+4198 4199 7003 0x16071825
+4199 4200 7002 0x160713c3
+4200 4201 7001 0x16070f67
+4201 4202 7000 0x16070b10
+4202 4203 6999 0x160706ba
+4203 4204 6998 0x16070263
+4204 4205 6997 0x1606fe11
+4205 4206 6996 0x1606f9c5
+4206 4207 6995 0x1606f579
+4207 4208 6994 0x1606f132
+4208 4209 6993 0x1606eceb
+4209 4210 6992 0x1606e8a5
+4210 4211 6991 0x1606e463
+4211 4212 6990 0x1606e027
+4212 4213 6989 0x1606dbeb
+4213 4214 6988 0x1606d7af
+4214 4215 6987 0x1606d379
+4215 4216 6986 0x1606cf42
+4216 4217 6985 0x1606cb11
+4217 4218 6984 0x1606c6e5
+4218 4219 6983 0x1606c2b3
+4219 4220 6982 0x1606be8d
+4220 4221 6981 0x1606ba6c
+4221 4222 6980 0x1606b645
+4222 4223 6979 0x1606b224
+4223 4224 6978 0x1606ae03
+4224 4225 6977 0x1606a9e7
+4225 4226 6976 0x1606a5cb
+4226 4227 6975 0x1606a1af
+4227 4228 6974 0x16069d9e
+4228 4229 6973 0x16069988
+4229 4230 6972 0x16069577
+4230 4231 6971 0x1606916b
+4231 4232 6970 0x16068d5f
+4232 4233 6969 0x16068953
+4233 4234 6968 0x1606854d
+4234 4235 6967 0x1606814c
+4235 4236 6966 0x16067d4b
+4236 4237 6965 0x1606794f
+4237 4238 6964 0x16067554
+4238 4239 6963 0x16067158
+4239 4240 6962 0x16066d62
+4240 4241 6961 0x1606696c
+4241 4242 6960 0x1606657b
+4242 4243 6959 0x1606618a
+4243 4244 6958 0x16065d99
+4244 4245 6957 0x160659ad
+4245 4246 6956 0x160655c7
+4246 4247 6955 0x160651e1
+4247 4248 6954 0x16064dfb
+4248 4249 6953 0x16064a1a
+4249 4250 6952 0x16064639
+4250 4251 6951 0x1606425e
+4251 4252 6950 0x16063e83
+4252 4253 6949 0x16063aac
+4253 4254 6948 0x160636d6
+4254 4255 6947 0x16063300
+4255 4256 6946 0x16062f30
+4256 4257 6945 0x16062b64
+4257 4258 6944 0x16062794
+4258 4259 6943 0x160623c8
+4259 4260 6942 0x16062002
+4260 4261 6941 0x16061c3c
+4261 4262 6940 0x1606187c
+4262 4263 6939 0x160614b6
+4263 4264 6938 0x160610fb
+4264 4265 6937 0x16060d3a
+4265 4266 6936 0x16060984
+4266 4267 6935 0x160605ce
+4267 4268 6934 0x16060218
+4268 4269 6933 0x1605fe63
+4269 4270 6932 0x1605fab2
+4270 4271 6931 0x1605f702
+4271 4272 6930 0x1605f356
+4272 4273 6929 0x1605efab
+4273 4274 6928 0x1605ec06
+4274 4275 6927 0x1605e860
+4275 4276 6926 0x1605e4ba
+4276 4277 6925 0x1605e11a
+4277 4278 6924 0x1605dd79
+4278 4279 6923 0x1605d9d9
+4279 4280 6922 0x1605d63e
+4280 4281 6921 0x1605d2a8
+4281 4282 6920 0x1605cf0d
+4282 4283 6919 0x1605cb7d
+4283 4284 6918 0x1605c7ec
+4284 4285 6917 0x1605c45c
+4285 4286 6916 0x1605c0cc
+4286 4287 6915 0x1605bd41
+4287 4288 6914 0x1605b9b6
+4288 4289 6913 0x1605b630
+4289 4290 6912 0x1605b2ab
+4290 4291 6911 0x1605af25
+4291 4292 6910 0x1605aba5
+4292 4293 6909 0x1605a825
+4293 4294 6908 0x1605a4a5
+4294 4295 6907 0x1605a12a
+4295 4296 6906 0x16059daf
+4296 4297 6905 0x16059a3a
+4297 4298 6904 0x160596ca
+4298 4299 6903 0x16059354
+4299 4300 6902 0x16058fe4
+4300 4301 6901 0x16058c74
+4301 4302 6900 0x16058904
+4302 4303 6899 0x16058599
+4303 4304 6898 0x16058234
+4304 4305 6897 0x16057ec9
+4305 4306 6896 0x16057b64
+4306 4307 6895 0x16057804
+4307 4308 6894 0x160574a4
+4308 4309 6893 0x16057144
+4309 4310 6892 0x16056de4
+4310 4311 6891 0x16056a89
+4311 4312 6890 0x1605672e
+4312 4313 6889 0x160563de
+4313 4314 6888 0x16056084
+4314 4315 6887 0x16055d34
+4315 4316 6886 0x160559e7
+4316 4317 6885 0x1605569c
+4317 4318 6884 0x16055354
+4318 4319 6883 0x1605500c
+4319 4320 6882 0x16054cca
+4320 4321 6881 0x16054987
+4321 4322 6880 0x16054645
+4322 4323 6879 0x16054305
+4323 4324 6878 0x16053fc8
+4324 4325 6877 0x16053c8e
+4325 4326 6876 0x16053956
+4326 4327 6875 0x1605361e
+4327 4328 6874 0x160532e9
+4328 4329 6873 0x16052fb9
+4329 4330 6872 0x16052c87
+4330 4331 6871 0x16052957
+4331 4332 6870 0x1605262a
+4332 4333 6869 0x160522fd
+4333 4334 6868 0x16051fd6
+4334 4335 6867 0x16051cab
+4335 4336 6866 0x16051986
+4336 4337 6865 0x16051661
+4337 4338 6864 0x1605133f
+4338 4339 6863 0x1605101f
+4339 4340 6862 0x16050d00
+4340 4341 6861 0x160509e3
+4341 4342 6860 0x160506c9
+4342 4343 6859 0x160503b1
+4343 4344 6858 0x1605009c
+4344 4345 6857 0x1604fd87
+4345 4346 6856 0x1604fa75
+4346 4347 6855 0x1604f763
+4347 4348 6854 0x1604f453
+4348 4349 6853 0x1604f144
+4349 4350 6852 0x1604ee3a
+4350 4351 6851 0x1604eb30
+4351 4352 6850 0x1604e826
+4352 4353 6849 0x1604e521
+4353 4354 6848 0x1604e21c
+4354 4355 6847 0x1604df1a
+4355 4356 6846 0x1604dc18
+4356 4357 6845 0x1604d918
+4357 4358 6844 0x1604d61c
+4358 4359 6843 0x1604d322
+4359 4360 6842 0x1604d02a
+4360 4361 6841 0x1604cd30
+4361 4362 6840 0x1604ca3c
+4362 4363 6839 0x1604c747
+4363 4364 6838 0x1604c455
+4364 4365 6837 0x1604c163
+4365 4366 6836 0x1604be74
+4366 4367 6835 0x1604bb87
+4367 4368 6834 0x1604b89d
+4368 4369 6833 0x1604b5b3
+4369 4370 6832 0x1604b2c9
+4370 4371 6831 0x1604afe5
+4371 4372 6830 0x1604ad00
+4372 4373 6829 0x1604aa1e
+4373 4374 6828 0x1604a73c
+4374 4375 6827 0x1604a460
+4375 4376 6826 0x1604a183
+4376 4377 6825 0x16049ea9
+4377 4378 6824 0x16049bd0
+4378 4379 6823 0x160498f6
+4379 4380 6822 0x16049621
+4380 4381 6821 0x1604934d
+4381 4382 6820 0x16049078
+4382 4383 6819 0x16048da7
+4383 4384 6818 0x16048ad7
+4384 4385 6817 0x1604880b
+4385 4386 6816 0x1604853f
+4386 4387 6815 0x16048275
+4387 4388 6814 0x16047fab
+4388 4389 6813 0x16047ce4
+4389 4390 6812 0x16047a22
+4390 4391 6811 0x16047761
+4391 4392 6810 0x1604749f
+4392 4393 6809 0x160471dd
+4393 4394 6808 0x16046f1e
+4394 4395 6807 0x16046c62
+4395 4396 6806 0x160469a8
+4396 4397 6805 0x160466ef
+4397 4398 6804 0x16046438
+4398 4399 6803 0x16046181
+4399 4400 6802 0x16045ecd
+4400 4401 6801 0x16045c1b
+4401 4402 6800 0x16045969
+4402 4403 6799 0x160456ba
+4403 4404 6798 0x1604540c
+4404 4405 6797 0x16045165
+4405 4406 6796 0x16044eb9
+4406 4407 6795 0x16044c12
+4407 4408 6794 0x1604496b
+4408 4409 6793 0x160446c4
+4409 4410 6792 0x16044420
+4410 4411 6791 0x1604417e
+4411 4412 6790 0x16043edd
+4412 4413 6789 0x16043c3e
+4413 4414 6788 0x160439a2
+4414 4415 6787 0x16043706
+4415 4416 6786 0x1604346c
+4416 4417 6785 0x160431d3
+4417 4418 6784 0x16042f3c
+4418 4419 6783 0x16042ca8
+4419 4420 6782 0x16042a14
+4420 4421 6781 0x16042785
+4421 4422 6780 0x160424f4
+4422 4423 6779 0x16042265
+4423 4424 6778 0x16041fd9
+4424 4425 6777 0x16041d4d
+4425 4426 6776 0x16041ac4
+4426 4427 6775 0x1604183a
+4427 4428 6774 0x160415b4
+4428 4429 6773 0x1604132d
+4429 4430 6772 0x160410a9
+4430 4431 6771 0x16040e28
+4431 4432 6770 0x16040ba7
+4432 4433 6769 0x16040928
+4433 4434 6768 0x160406a9
+4434 4435 6767 0x1604042d
+4435 4436 6766 0x160401b7
+4436 4437 6765 0x1603ff3e
+4437 4438 6764 0x1603fcc7
+4438 4439 6763 0x1603fa50
+4439 4440 6762 0x1603f7dd
+4440 4441 6761 0x1603f569
+4441 4442 6760 0x1603f2f8
+4442 4443 6759 0x1603f089
+4443 4444 6758 0x1603ee1b
+4444 4445 6757 0x1603ebaf
+4445 4446 6756 0x1603e943
+4446 4447 6755 0x1603e6da
+4447 4448 6754 0x1603e471
+4448 4449 6753 0x1603e20a
+4449 4450 6752 0x1603dfa4
+4450 4451 6751 0x1603dd43
+4451 4452 6750 0x1603dae1
+4452 4453 6749 0x1603d880
+4453 4454 6748 0x1603d622
+4454 4455 6747 0x1603d3c4
+4455 4456 6746 0x1603d168
+4456 4457 6745 0x1603cf0c
+4457 4458 6744 0x1603ccb3
+4458 4459 6743 0x1603ca5a
+4459 4460 6742 0x1603c804
+4460 4461 6741 0x1603c5b0
+4461 4462 6740 0x1603c35c
+4462 4463 6739 0x1603c109
+4463 4464 6738 0x1603beb8
+4464 4465 6737 0x1603bc69
+4465 4466 6736 0x1603ba1b
+4466 4467 6735 0x1603b7d2
+4467 4468 6734 0x1603b586
+4468 4469 6733 0x1603b33e
+4469 4470 6732 0x1603b0f5
+4470 4471 6731 0x1603aeae
+4471 4472 6730 0x1603ac6b
+4472 4473 6729 0x1603aa27
+4473 4474 6728 0x1603a7e4
+4474 4475 6727 0x1603a5a3
+4475 4476 6726 0x1603a364
+4476 4477 6725 0x1603a126
+4477 4478 6724 0x16039ee8
+4478 4479 6723 0x16039cad
+4479 4480 6722 0x16039a74
+4480 4481 6721 0x1603983b
+4481 4482 6720 0x16039605
+4482 4483 6719 0x160393d1
+4483 4484 6718 0x1603919b
+4484 4485 6717 0x16038f68
+4485 4486 6716 0x16038d37
+4486 4487 6715 0x16038b06
+4487 4488 6714 0x160388d8
+4488 4489 6713 0x160386aa
+4489 4490 6712 0x1603847e
+4490 4491 6711 0x16038253
+4491 4492 6710 0x1603802a
+4492 4493 6709 0x16037e01
+4493 4494 6708 0x16037bd9
+4494 4495 6707 0x160379b5
+4495 4496 6706 0x1603778f
+4496 4497 6705 0x1603756f
+4497 4498 6704 0x1603734e
+4498 4499 6703 0x1603712d
+4499 4500 6702 0x16036f0f
+4500 4501 6701 0x16036cf1
+4501 4502 6700 0x16036ad6
+4502 4503 6699 0x160368ba
+4503 4504 6698 0x160366a2
+4504 4505 6697 0x16036489
+4505 4506 6696 0x16036271
+4506 4507 6695 0x1603605b
+4507 4508 6694 0x16035e47
+4508 4509 6693 0x16035c34
+4509 4510 6692 0x16035a23
+4510 4511 6691 0x16035813
+4511 4512 6690 0x16035602
+4512 4513 6689 0x160353f7
+4513 4514 6688 0x160351e9
+4514 4515 6687 0x16034fde
+4515 4516 6686 0x16034dd5
+4516 4517 6685 0x16034bcd
+4517 4518 6684 0x160349c4
+4518 4519 6683 0x160347be
+4519 4520 6682 0x160345b8
+4520 4521 6681 0x160343b5
+4521 4522 6680 0x160341b2
+4522 4523 6679 0x16033faf
+4523 4524 6678 0x16033db1
+4524 4525 6677 0x16033bb1
+4525 4526 6676 0x160339b3
+4526 4527 6675 0x160337b8
+4527 4528 6674 0x160335bd
+4528 4529 6673 0x160333c4
+4529 4530 6672 0x160331cc
+4530 4531 6671 0x16032fd3
+4531 4532 6670 0x16032ddd
+4532 4533 6669 0x16032be8
+4533 4534 6668 0x160329f5
+4534 4535 6667 0x16032802
+4535 4536 6666 0x1603260e
+4536 4537 6665 0x1603241e
+4537 4538 6664 0x16032230
+4538 4539 6663 0x16032043
+4539 4540 6662 0x16031e55
+4540 4541 6661 0x16031c6a
+4541 4542 6660 0x16031a7f
+4542 4543 6659 0x16031896
+4543 4544 6658 0x160316ae
+4544 4545 6657 0x160314c8
+4545 4546 6656 0x160312e3
+4546 4547 6655 0x160310fd
+4547 4548 6654 0x16030f1a
+4548 4549 6653 0x16030d37
+4549 4550 6652 0x16030b57
+4550 4551 6651 0x16030977
+4551 4552 6650 0x16030796
+4552 4553 6649 0x160305b9
+4553 4554 6648 0x160303de
+4554 4555 6647 0x16030203
+4555 4556 6646 0x16030028
+4556 4557 6645 0x1602fe4d
+4557 4558 6644 0x1602fc75
+4558 4559 6643 0x1602faa2
+4559 4560 6642 0x1602f8ca
+4560 4561 6641 0x1602f6f7
+4561 4562 6640 0x1602f521
+4562 4563 6639 0x1602f351
+4563 4564 6638 0x1602f17e
+4564 4565 6637 0x1602efae
+4565 4566 6636 0x1602edde
+4566 4567 6635 0x1602ec10
+4567 4568 6634 0x1602ea43
+4568 4569 6633 0x1602e878
+4569 4570 6632 0x1602e6ad
+4570 4571 6631 0x1602e4e2
+4571 4572 6630 0x1602e31a
+4572 4573 6629 0x1602e152
+4573 4574 6628 0x1602df8c
+4574 4575 6627 0x1602ddc7
+4575 4576 6626 0x1602dc01
+4576 4577 6625 0x1602da3f
+4577 4578 6624 0x1602d87c
+4578 4579 6623 0x1602d6bc
+4579 4580 6622 0x1602d4fc
+4580 4581 6621 0x1602d33c
+4581 4582 6620 0x1602d17e
+4582 4583 6619 0x1602cfc1
+4583 4584 6618 0x1602ce03
+4584 4585 6617 0x1602cc49
+4585 4586 6616 0x1602ca8e
+4586 4587 6615 0x1602c8d6
+4587 4588 6614 0x1602c71e
+4588 4589 6613 0x1602c568
+4589 4590 6612 0x1602c3b0
+4590 4591 6611 0x1602c1fe
+4591 4592 6610 0x1602c048
+4592 4593 6609 0x1602be96
+4593 4594 6608 0x1602bce3
+4594 4595 6607 0x1602bb33
+4595 4596 6606 0x1602b983
+4596 4597 6605 0x1602b7d3
+4597 4598 6604 0x1602b626
+4598 4599 6603 0x1602b478
+4599 4600 6602 0x1602b2cb
+4600 4601 6601 0x1602b120
+4601 4602 6600 0x1602af76
+4602 4603 6599 0x1602adce
+4603 4604 6598 0x1602ac28
+4604 4605 6597 0x1602aa86
+4605 4606 6596 0x1602a8e2
+4606 4607 6595 0x1602a73e
+4607 4608 6594 0x1602a59d
+4608 4609 6593 0x1602a3fc
+4609 4610 6592 0x1602a25c
+4610 4611 6591 0x1602a0bd
+4611 4612 6590 0x16029f1f
+4612 4613 6589 0x16029d81
+4613 4614 6588 0x16029be5
+4614 4615 6587 0x16029a4a
+4615 4616 6586 0x160298b0
+4616 4617 6585 0x16029717
+4617 4618 6584 0x1602957e
+4618 4619 6583 0x160293e8
+4619 4620 6582 0x16029252
+4620 4621 6581 0x160290bd
+4621 4622 6580 0x16028f28
+4622 4623 6579 0x16028d96
+4623 4624 6578 0x16028c03
+4624 4625 6577 0x16028a71
+4625 4626 6576 0x160288e1
+4626 4627 6575 0x16028751
+4627 4628 6574 0x160285c1
+4628 4629 6573 0x16028434
+4629 4630 6572 0x160282a7
+4630 4631 6571 0x1602811b
+4631 4632 6570 0x16027f8f
+4632 4633 6569 0x16027e05
+4633 4634 6568 0x16027c7c
+4634 4635 6567 0x16027af5
+4635 4636 6566 0x1602796e
+4636 4637 6565 0x160277e7
+4637 4638 6564 0x16027662
+4638 4639 6563 0x160274dd
+4639 4640 6562 0x16027359
+4640 4641 6561 0x160271d7
+4641 4642 6560 0x16027055
+4642 4643 6559 0x16026ed4
+4643 4644 6558 0x16026d54
+4644 4645 6557 0x16026bd6
+4645 4646 6556 0x16026a57
+4646 4647 6555 0x160268d9
+4647 4648 6554 0x1602675d
+4648 4649 6553 0x160265e1
+4649 4650 6552 0x16026467
+4650 4651 6551 0x160262ee
+4651 4652 6550 0x16026176
+4652 4653 6549 0x16025ffd
+4653 4654 6548 0x16025e87
+4654 4655 6547 0x16025d11
+4655 4656 6546 0x16025b9c
+4656 4657 6545 0x16025a27
+4657 4658 6544 0x160258b3
+4658 4659 6543 0x16025741
+4659 4660 6542 0x160255cf
+4660 4661 6541 0x1602545e
+4661 4662 6540 0x160252ee
+4662 4663 6539 0x16025180
+4663 4664 6538 0x16025012
+4664 4665 6537 0x16024ea3
+4665 4666 6536 0x16024d39
+4666 4667 6535 0x16024bce
+4667 4668 6534 0x16024a62
+4668 4669 6533 0x160248f9
+4669 4670 6532 0x16024790
+4670 4671 6531 0x16024627
+4671 4672 6530 0x160244c0
+4672 4673 6529 0x1602435a
+4673 4674 6528 0x160241f5
+4674 4675 6527 0x16024090
+4675 4676 6526 0x16023f2c
+4676 4677 6525 0x16023dc9
+4677 4678 6524 0x16023c67
+4678 4679 6523 0x16023b06
+4679 4680 6522 0x160239a5
+4680 4681 6521 0x16023847
+4681 4682 6520 0x160236e9
+4682 4683 6519 0x1602358a
+4683 4684 6518 0x1602342e
+4684 4685 6517 0x160232d1
+4685 4686 6516 0x16023175
+4686 4687 6515 0x1602301b
+4687 4688 6514 0x16022ec1
+4688 4689 6513 0x16022d68
+4689 4690 6512 0x16022c11
+4690 4691 6511 0x16022ab9
+4691 4692 6510 0x16022962
+4692 4693 6509 0x1602280d
+4693 4694 6508 0x160226b8
+4694 4695 6507 0x16022564
+4695 4696 6506 0x16022411
+4696 4697 6505 0x160222c1
+4697 4698 6504 0x1602216f
+4698 4699 6503 0x1602201e
+4699 4700 6502 0x16021ece
+4700 4701 6501 0x16021d7f
+4701 4702 6500 0x16021c31
+4702 4703 6499 0x16021ae3
+4703 4704 6498 0x16021996
+4704 4705 6497 0x1602184b
+4705 4706 6496 0x160216ff
+4706 4707 6495 0x160215b5
+4707 4708 6494 0x1602146d
+4708 4709 6493 0x16021324
+4709 4710 6492 0x160211db
+4710 4711 6491 0x16021095
+4711 4712 6490 0x16020f51
+4712 4713 6489 0x16020e0b
+4713 4714 6488 0x16020cc6
+4714 4715 6487 0x16020b83
+4715 4716 6486 0x16020a3f
+4716 4717 6485 0x160208fd
+4717 4718 6484 0x160207bb
+4718 4719 6483 0x1602067b
+4719 4720 6482 0x1602053a
+4720 4721 6481 0x160203fb
+4721 4722 6480 0x160202bd
+4722 4723 6479 0x1602017f
+4723 4724 6478 0x16020042
+4724 4725 6477 0x1601ff07
+4725 4726 6476 0x1601fdca
+4726 4727 6475 0x1601fc90
+4727 4728 6474 0x1601fb58
+4728 4729 6473 0x1601fa1f
+4729 4730 6472 0x1601f8e7
+4730 4731 6471 0x1601f7af
+4731 4732 6470 0x1601f678
+4732 4733 6469 0x1601f542
+4733 4734 6468 0x1601f40e
+4734 4735 6467 0x1601f2d9
+4735 4736 6466 0x1601f1a4
+4736 4737 6465 0x1601f071
+4737 4738 6464 0x1601ef3f
+4738 4739 6463 0x1601ee0d
+4739 4740 6462 0x1601ecdd
+4740 4741 6461 0x1601ebae
+4741 4742 6460 0x1601ea7e
+4742 4743 6459 0x1601e951
+4743 4744 6458 0x1601e823
+4744 4745 6457 0x1601e6f6
+4745 4746 6456 0x1601e5ca
+4746 4747 6455 0x1601e49f
+4747 4748 6454 0x1601e373
+4748 4749 6453 0x1601e24a
+4749 4750 6452 0x1601e120
+4750 4751 6451 0x1601dff7
+4751 4752 6450 0x1601decf
+4752 4753 6449 0x1601dda8
+4753 4754 6448 0x1601dc80
+4754 4755 6447 0x1601db5b
+4755 4756 6446 0x1601da36
+4756 4757 6445 0x1601d912
+4757 4758 6444 0x1601d7f0
+4758 4759 6443 0x1601d6cd
+4759 4760 6442 0x1601d5aa
+4760 4761 6441 0x1601d489
+4761 4762 6440 0x1601d367
+4762 4763 6439 0x1601d247
+4763 4764 6438 0x1601d128
+4764 4765 6437 0x1601d009
+4765 4766 6436 0x1601ceeb
+4766 4767 6435 0x1601cdcf
+4767 4768 6434 0x1601ccb1
+4768 4769 6433 0x1601cb96
+4769 4770 6432 0x1601ca7b
+4770 4771 6431 0x1601c960
+4771 4772 6430 0x1601c846
+4772 4773 6429 0x1601c72c
+4773 4774 6428 0x1601c615
+4774 4775 6427 0x1601c4fd
+4775 4776 6426 0x1601c3e6
+4776 4777 6425 0x1601c2d0
+4777 4778 6424 0x1601c1ba
+4778 4779 6423 0x1601c0a4
+4779 4780 6422 0x1601bf90
+4780 4781 6421 0x1601be7d
+4781 4782 6420 0x1601bd6a
+4782 4783 6419 0x1601bc57
+4783 4784 6418 0x1601bb45
+4784 4785 6417 0x1601ba34
+4785 4786 6416 0x1601b923
+4786 4787 6415 0x1601b814
+4787 4788 6414 0x1601b705
+4788 4789 6413 0x1601b5f8
+4789 4790 6412 0x1601b4ea
+4790 4791 6411 0x1601b3dc
+4791 4792 6410 0x1601b2d0
+4792 4793 6409 0x1601b1c4
+4793 4794 6408 0x1601b0b9
+4794 4795 6407 0x1601afaf
+4795 4796 6406 0x1601aea4
+4796 4797 6405 0x1601ad9c
+4797 4798 6404 0x1601ac92
+4798 4799 6403 0x1601ab8b
+4799 4800 6402 0x1601aa83
+4800 4801 6401 0x1601a97d
+4801 4802 6400 0x1601a876
+4802 4803 6399 0x1601a771
+4803 4804 6398 0x1601a66e
+4804 4805 6397 0x1601a569
+4805 4806 6396 0x1601a465
+4806 4807 6395 0x1601a362
+4807 4808 6394 0x1601a261
+4808 4809 6393 0x1601a15f
+4809 4810 6392 0x1601a05d
+4810 4811 6391 0x16019f5d
+4811 4812 6390 0x16019e5d
+4812 4813 6389 0x16019d5e
+4813 4814 6388 0x16019c60
+4814 4815 6387 0x16019b61
+4815 4816 6386 0x16019a64
+4816 4817 6385 0x16019968
+4817 4818 6384 0x1601986b
+4818 4819 6383 0x16019771
+4819 4820 6382 0x16019676
+4820 4821 6381 0x1601957c
+4821 4822 6380 0x16019483
+4822 4823 6379 0x16019389
+4823 4824 6378 0x16019291
+4824 4825 6377 0x16019199
+4825 4826 6376 0x160190a2
+4826 4827 6375 0x16018fab
+4827 4828 6374 0x16018eb4
+4828 4829 6373 0x16018dbf
+4829 4830 6372 0x16018cca
+4830 4831 6371 0x16018bd5
+4831 4832 6370 0x16018ae2
+4832 4833 6369 0x160189ee
+4833 4834 6368 0x160188fc
+4834 4835 6367 0x1601880b
+4835 4836 6366 0x16018719
+4836 4837 6365 0x16018628
+4837 4838 6364 0x16018538
+4838 4839 6363 0x16018449
+4839 4840 6362 0x16018359
+4840 4841 6361 0x1601826a
+4841 4842 6360 0x1601817d
+4842 4843 6359 0x1601808f
+4843 4844 6358 0x16017fa2
+4844 4845 6357 0x16017eb6
+4845 4846 6356 0x16017dc9
+4846 4847 6355 0x16017cdf
+4847 4848 6354 0x16017bf4
+4848 4849 6353 0x16017b0a
+4849 4850 6352 0x16017a21
+4850 4851 6351 0x16017939
+4851 4852 6350 0x1601784f
+4852 4853 6349 0x16017767
+4853 4854 6348 0x16017681
+4854 4855 6347 0x1601759a
+4855 4856 6346 0x160174b3
+4856 4857 6345 0x160173ce
+4857 4858 6344 0x160172e8
+4858 4859 6343 0x16017204
+4859 4860 6342 0x16017120
+4860 4861 6341 0x1601703c
+4861 4862 6340 0x16016f59
+4862 4863 6339 0x16016e77
+4863 4864 6338 0x16016d94
+4864 4865 6337 0x16016cb2
+4865 4866 6336 0x16016bd2
+4866 4867 6335 0x16016af2
+4867 4868 6334 0x16016a12
+4868 4869 6333 0x16016932
+4869 4870 6332 0x16016853
+4870 4871 6331 0x16016775
+4871 4872 6330 0x16016697
+4872 4873 6329 0x160165ba
+4873 4874 6328 0x160164dd
+4874 4875 6327 0x16016401
+4875 4876 6326 0x16016325
+4876 4877 6325 0x1601624a
+4877 4878 6324 0x1601616f
+4878 4879 6323 0x16016095
+4879 4880 6322 0x16015fbb
+4880 4881 6321 0x16015ee2
+4881 4882 6320 0x16015e0a
+4882 4883 6319 0x16015d32
+4883 4884 6318 0x16015c5a
+4884 4885 6317 0x16015b82
+4885 4886 6316 0x16015aab
+4886 4887 6315 0x160159d5
+4887 4888 6314 0x160158ff
+4888 4889 6313 0x1601582a
+4889 4890 6312 0x16015755
+4890 4891 6311 0x16015682
+4891 4892 6310 0x160155af
+4892 4893 6309 0x160154dd
+4893 4894 6308 0x1601540b
+4894 4895 6307 0x1601533a
+4895 4896 6306 0x1601526a
+4896 4897 6305 0x1601519a
+4897 4898 6304 0x160150ca
+4898 4899 6303 0x16014ffb
+4899 4900 6302 0x16014f2c
+4900 4901 6301 0x16014e5e
+4901 4902 6300 0x16014d90
+4902 4903 6299 0x16014cc3
+4903 4904 6298 0x16014bf6
+4904 4905 6297 0x16014b29
+4905 4906 6296 0x16014a5d
+4906 4907 6295 0x16014992
+4907 4908 6294 0x160148c7
+4908 4909 6293 0x160147fc
+4909 4910 6292 0x16014732
+4910 4911 6291 0x16014669
+4911 4912 6290 0x160145a1
+4912 4913 6289 0x160144d8
+4913 4914 6288 0x16014410
+4914 4915 6287 0x16014348
+4915 4916 6286 0x16014281
+4916 4917 6285 0x160141bb
+4917 4918 6284 0x160140f5
+4918 4919 6283 0x1601402f
+4919 4920 6282 0x16013f6a
+4920 4921 6281 0x16013ea4
+4921 4922 6280 0x16013de0
+4922 4923 6279 0x16013d1c
+4923 4924 6278 0x16013c59
+4924 4925 6277 0x16013b95
+4925 4926 6276 0x16013ad3
+4926 4927 6275 0x16013a12
+4927 4928 6274 0x16013950
+4928 4929 6273 0x1601388f
+4929 4930 6272 0x160137ce
+4930 4931 6271 0x1601370e
+4931 4932 6270 0x1601364e
+4932 4933 6269 0x1601358f
+4933 4934 6268 0x160134d0
+4934 4935 6267 0x16013412
+4935 4936 6266 0x16013354
+4936 4937 6265 0x16013296
+4937 4938 6264 0x160131d9
+4938 4939 6263 0x1601311c
+4939 4940 6262 0x16013060
+4940 4941 6261 0x16012fa4
+4941 4942 6260 0x16012ee9
+4942 4943 6259 0x16012e2f
+4943 4944 6258 0x16012d74
+4944 4945 6257 0x16012cbb
+4945 4946 6256 0x16012c01
+4946 4947 6255 0x16012b48
+4947 4948 6254 0x16012a8f
+4948 4949 6253 0x160129d7
+4949 4950 6252 0x1601291f
+4950 4951 6251 0x16012868
+4951 4952 6250 0x160127b1
+4952 4953 6249 0x160126fa
+4953 4954 6248 0x16012644
+4954 4955 6247 0x1601258f
+4955 4956 6246 0x160124da
+4956 4957 6245 0x16012425
+4957 4958 6244 0x16012371
+4958 4959 6243 0x160122be
+4959 4960 6242 0x1601220a
+4960 4961 6241 0x16012157
+4961 4962 6240 0x160120a4
+4962 4963 6239 0x16011ff2
+4963 4964 6238 0x16011f41
+4964 4965 6237 0x16011e90
+4965 4966 6236 0x16011ddf
+4966 4967 6235 0x16011d2e
+4967 4968 6234 0x16011c7e
+4968 4969 6233 0x16011bcf
+4969 4970 6232 0x16011b20
+4970 4971 6231 0x16011a70
+4971 4972 6230 0x160119c2
+4972 4973 6229 0x16011915
+4973 4974 6228 0x16011867
+4974 4975 6227 0x160117ba
+4975 4976 6226 0x1601170e
+4976 4977 6225 0x16011661
+4977 4978 6224 0x160115b6
+4978 4979 6223 0x1601150b
+4979 4980 6222 0x16011460
+4980 4981 6221 0x160113b4
+4981 4982 6220 0x1601130b
+4982 4983 6219 0x16011261
+4983 4984 6218 0x160111b7
+4984 4985 6217 0x1601110e
+4985 4986 6216 0x16011066
+4986 4987 6215 0x16010fbe
+4987 4988 6214 0x16010f16
+4988 4989 6213 0x16010e6f
+4989 4990 6212 0x16010dc8
+4990 4991 6211 0x16010d22
+4991 4992 6210 0x16010c7c
+4992 4993 6209 0x16010bd6
+4993 4994 6208 0x16010b31
+4994 4995 6207 0x16010a8c
+4995 4996 6206 0x160109e8
+4996 4997 6205 0x16010943
+4997 4998 6204 0x160108a0
+4998 4999 6203 0x160107fc
+4999 5000 6202 0x16010759
+5000 5001 6201 0x160106b7
+5001 5002 6200 0x16010614
+5002 5003 6199 0x16010573
+5003 5004 6198 0x160104d2
+5004 5005 6197 0x16010431
+5005 5006 6196 0x16010391
+5006 5007 6195 0x160102f1
+5007 5008 6194 0x16010250
+5008 5009 6193 0x160101b1
+5009 5010 6192 0x16010112
+5010 5011 6191 0x16010073
+5011 5012 6190 0x1600ffd5
+5012 5013 6189 0x1600ff37
+5013 5014 6188 0x1600fe9a
+5014 5015 6187 0x1600fdfd
+5015 5016 6186 0x1600fd60
+5016 5017 6185 0x1600fcc4
+5017 5018 6184 0x1600fc28
+5018 5019 6183 0x1600fb8d
+5019 5020 6182 0x1600faf2
+5020 5021 6181 0x1600fa57
+5021 5022 6180 0x1600f9bd
+5022 5023 6179 0x1600f922
+5023 5024 6178 0x1600f889
+5024 5025 6177 0x1600f7ef
+5025 5026 6176 0x1600f756
+5026 5027 6175 0x1600f6be
+5027 5028 6174 0x1600f626
+5028 5029 6173 0x1600f58e
+5029 5030 6172 0x1600f4f7
+5030 5031 6171 0x1600f460
+5031 5032 6170 0x1600f3c9
+5032 5033 6169 0x1600f332
+5033 5034 6168 0x1600f29d
+5034 5035 6167 0x1600f207
+5035 5036 6166 0x1600f172
+5036 5037 6165 0x1600f0dd
+5037 5038 6164 0x1600f048
+5038 5039 6163 0x1600efb4
+5039 5040 6162 0x1600ef21
+5040 5041 6161 0x1600ee8d
+5041 5042 6160 0x1600edfa
+5042 5043 6159 0x1600ed67
+5043 5044 6158 0x1600ecd5
+5044 5045 6157 0x1600ec43
+5045 5046 6156 0x1600ebb1
+5046 5047 6155 0x1600eb20
+5047 5048 6154 0x1600ea8f
+5048 5049 6153 0x1600e9ff
+5049 5050 6152 0x1600e96e
+5050 5051 6151 0x1600e8df
+5051 5052 6150 0x1600e84f
+5052 5053 6149 0x1600e7c0
+5053 5054 6148 0x1600e731
+5054 5055 6147 0x1600e6a3
+5055 5056 6146 0x1600e614
+5056 5057 6145 0x1600e587
+5057 5058 6144 0x1600e4f9
+5058 5059 6143 0x1600e46c
+5059 5060 6142 0x1600e3e0
+5060 5061 6141 0x1600e353
+5061 5062 6140 0x1600e2c6
+5062 5063 6139 0x1600e23b
+5063 5064 6138 0x1600e1af
+5064 5065 6137 0x1600e125
+5065 5066 6136 0x1600e09a
+5066 5067 6135 0x1600e010
+5067 5068 6134 0x1600df86
+5068 5069 6133 0x1600defc
+5069 5070 6132 0x1600de73
+5070 5071 6131 0x1600dde9
+5071 5072 6130 0x1600dd61
+5072 5073 6129 0x1600dcd8
+5073 5074 6128 0x1600dc50
+5074 5075 6127 0x1600dbc9
+5075 5076 6126 0x1600db41
+5076 5077 6125 0x1600daba
+5077 5078 6124 0x1600da34
+5078 5079 6123 0x1600d9ad
+5079 5080 6122 0x1600d928
+5080 5081 6121 0x1600d8a2
+5081 5082 6120 0x1600d81c
+5082 5083 6119 0x1600d797
+5083 5084 6118 0x1600d713
+5084 5085 6117 0x1600d68e
+5085 5086 6116 0x1600d60a
+5086 5087 6115 0x1600d586
+5087 5088 6114 0x1600d503
+5088 5089 6113 0x1600d47f
+5089 5090 6112 0x1600d3fc
+5090 5091 6111 0x1600d37a
+5091 5092 6110 0x1600d2f8
+5092 5093 6109 0x1600d275
+5093 5094 6108 0x1600d1f4
+5094 5095 6107 0x1600d172
+5095 5096 6106 0x1600d0f2
+5096 5097 6105 0x1600d072
+5097 5098 6104 0x1600cff1
+5098 5099 6103 0x1600cf71
+5099 5100 6102 0x1600cef1
+5100 5101 6101 0x1600ce72
+5101 5102 6100 0x1600cdf3
+5102 5103 6099 0x1600cd74
+5103 5104 6098 0x1600ccf5
+5104 5105 6097 0x1600cc77
+5105 5106 6096 0x1600cbf9
+5106 5107 6095 0x1600cb7c
+5107 5108 6094 0x1600cafe
+5108 5109 6093 0x1600ca81
+5109 5110 6092 0x1600ca05
+5110 5111 6091 0x1600c989
+5111 5112 6090 0x1600c90c
+5112 5113 6089 0x1600c891
+5113 5114 6088 0x1600c816
+5114 5115 6087 0x1600c79a
+5115 5116 6086 0x1600c71f
+5116 5117 6085 0x1600c6a5
+5117 5118 6084 0x1600c62a
+5118 5119 6083 0x1600c5b1
+5119 5120 6082 0x1600c536
+5120 5121 6081 0x1600c4bd
+5121 5122 6080 0x1600c444
+5122 5123 6079 0x1600c3cb
+5123 5124 6078 0x1600c352
+5124 5125 6077 0x1600c2da
+5125 5126 6076 0x1600c263
+5126 5127 6075 0x1600c1eb
+5127 5128 6074 0x1600c173
+5128 5129 6073 0x1600c0fc
+5129 5130 6072 0x1600c085
+5130 5131 6071 0x1600c00e
+5131 5132 6070 0x1600bf98
+5132 5133 6069 0x1600bf22
+5133 5134 6068 0x1600bead
+5134 5135 6067 0x1600be37
+5135 5136 6066 0x1600bdc2
+5136 5137 6065 0x1600bd4d
+5137 5138 6064 0x1600bcd8
+5138 5139 6063 0x1600bc64
+5139 5140 6062 0x1600bbf0
+5140 5141 6061 0x1600bb7c
+5141 5142 6060 0x1600bb09
+5142 5143 6059 0x1600ba96
+5143 5144 6058 0x1600ba22
+5144 5145 6057 0x1600b9b0
+5145 5146 6056 0x1600b93d
+5146 5147 6055 0x1600b8cb
+5147 5148 6054 0x1600b859
+5148 5149 6053 0x1600b7e7
+5149 5150 6052 0x1600b776
+5150 5151 6051 0x1600b705
+5151 5152 6050 0x1600b694
+5152 5153 6049 0x1600b623
+5153 5154 6048 0x1600b5b3
+5154 5155 6047 0x1600b543
+5155 5156 6046 0x1600b4d3
+5156 5157 6045 0x1600b464
+5157 5158 6044 0x1600b3f5
+5158 5159 6043 0x1600b385
+5159 5160 6042 0x1600b316
+5160 5161 6041 0x1600b2a8
+5161 5162 6040 0x1600b23a
+5162 5163 6039 0x1600b1cc
+5163 5164 6038 0x1600b15e
+5164 5165 6037 0x1600b0f0
+5165 5166 6036 0x1600b083
+5166 5167 6035 0x1600b016
+5167 5168 6034 0x1600afa9
+5168 5169 6033 0x1600af3c
+5169 5170 6032 0x1600aed0
+5170 5171 6031 0x1600ae64
+5171 5172 6030 0x1600adf9
+5172 5173 6029 0x1600ad8e
+5173 5174 6028 0x1600ad22
+5174 5175 6027 0x1600acb7
+5175 5176 6026 0x1600ac4c
+5176 5177 6025 0x1600abe2
+5177 5178 6024 0x1600ab78
+5178 5179 6023 0x1600ab0e
+5179 5180 6022 0x1600aaa5
+5180 5181 6021 0x1600aa3c
+5181 5182 6020 0x1600a9d4
+5182 5183 6019 0x1600a96b
+5183 5184 6018 0x1600a903
+5184 5185 6017 0x1600a89b
+5185 5186 6016 0x1600a833
+5186 5187 6015 0x1600a7cb
+5187 5188 6014 0x1600a765
+5188 5189 6013 0x1600a6fd
+5189 5190 6012 0x1600a697
+5190 5191 6011 0x1600a630
+5191 5192 6010 0x1600a5ca
+5192 5193 6009 0x1600a564
+5193 5194 6008 0x1600a4fe
+5194 5195 6007 0x1600a498
+5195 5196 6006 0x1600a433
+5196 5197 6005 0x1600a3ce
+5197 5198 6004 0x1600a369
+5198 5199 6003 0x1600a304
+5199 5200 6002 0x1600a2a0
+5200 5201 6001 0x1600a23c
+5201 5202 6000 0x1600a1d8
+5202 5203 5999 0x1600a174
+5203 5204 5998 0x1600a111
+5204 5205 5997 0x1600a0ae
+5205 5206 5996 0x1600a04b
+5206 5207 5995 0x16009fe8
+5207 5208 5994 0x16009f85
+5208 5209 5993 0x16009f23
+5209 5210 5992 0x16009ec1
+5210 5211 5991 0x16009e5f
+5211 5212 5990 0x16009dfd
+5212 5213 5989 0x16009d9c
+5213 5214 5988 0x16009d3b
+5214 5215 5987 0x16009cda
+5215 5216 5986 0x16009c79
+5216 5217 5985 0x16009c19
+5217 5218 5984 0x16009bb9
+5218 5219 5983 0x16009b59
+5219 5220 5982 0x16009af9
+5220 5221 5981 0x16009a99
+5221 5222 5980 0x16009a3a
+5222 5223 5979 0x160099db
+5223 5224 5978 0x1600997c
+5224 5225 5977 0x1600991d
+5225 5226 5976 0x160098bf
+5226 5227 5975 0x16009861
+5227 5228 5974 0x16009803
+5228 5229 5973 0x160097a5
+5229 5230 5972 0x16009748
+5230 5231 5971 0x160096ea
+5231 5232 5970 0x1600968d
+5232 5233 5969 0x16009630
+5233 5234 5968 0x160095d4
+5234 5235 5967 0x16009577
+5235 5236 5966 0x1600951b
+5236 5237 5965 0x160094bf
+5237 5238 5964 0x16009464
+5238 5239 5963 0x16009408
+5239 5240 5962 0x160093ac
+5240 5241 5961 0x16009351
+5241 5242 5960 0x160092f6
+5242 5243 5959 0x1600929c
+5243 5244 5958 0x16009241
+5244 5245 5957 0x160091e7
+5245 5246 5956 0x1600918d
+5246 5247 5955 0x16009133
+5247 5248 5954 0x160090da
+5248 5249 5953 0x16009080
+5249 5250 5952 0x16009027
+5250 5251 5951 0x16008fce
+5251 5252 5950 0x16008f76
+5252 5253 5949 0x16008f1d
+5253 5254 5948 0x16008ec5
+5254 5255 5947 0x16008e6d
+5255 5256 5946 0x16008e15
+5256 5257 5945 0x16008dbd
+5257 5258 5944 0x16008d66
+5258 5259 5943 0x16008d0e
+5259 5260 5942 0x16008cb7
+5260 5261 5941 0x16008c60
+5261 5262 5940 0x16008c0a
+5262 5263 5939 0x16008bb3
+5263 5264 5938 0x16008b5d
+5264 5265 5937 0x16008b08
+5265 5266 5936 0x16008ab2
+5266 5267 5935 0x16008a5c
+5267 5268 5934 0x16008a07
+5268 5269 5933 0x160089b1
+5269 5270 5932 0x1600895d
+5270 5271 5931 0x16008908
+5271 5272 5930 0x160088b3
+5272 5273 5929 0x1600885f
+5273 5274 5928 0x1600880a
+5274 5275 5927 0x160087b7
+5275 5276 5926 0x16008763
+5276 5277 5925 0x1600870f
+5277 5278 5924 0x160086bc
+5278 5279 5923 0x16008669
+5279 5280 5922 0x16008616
+5280 5281 5921 0x160085c3
+5281 5282 5920 0x16008571
+5282 5283 5919 0x1600851e
+5283 5284 5918 0x160084cc
+5284 5285 5917 0x1600847a
+5285 5286 5916 0x16008428
+5286 5287 5915 0x160083d7
+5287 5288 5914 0x16008386
+5288 5289 5913 0x16008334
+5289 5290 5912 0x160082e3
+5290 5291 5911 0x16008292
+5291 5292 5910 0x16008242
+5292 5293 5909 0x160081f1
+5293 5294 5908 0x160081a1
+5294 5295 5907 0x16008152
+5295 5296 5906 0x16008102
+5296 5297 5905 0x160080b2
+5297 5298 5904 0x16008063
+5298 5299 5903 0x16008014
+5299 5300 5902 0x157fc536
+5300 5301 5901 0x157f7665
+5301 5302 5900 0x157f2793
+5302 5303 5899 0x157ed917
+5303 5304 5898 0x157e8af1
+5304 5305 5897 0x157e3cca
+5305 5306 5896 0x157deefa
+5306 5307 5895 0x157da12a
+5307 5308 5894 0x157d53af
+5308 5309 5893 0x157d068b
+5309 5310 5892 0x157cb9bc
+5310 5311 5891 0x157c6c98
+5311 5312 5890 0x157c1fc9
+5312 5313 5889 0x157bd350
+5313 5314 5888 0x157b86d7
+5314 5315 5887 0x157b3ab4
+5315 5316 5886 0x157aeee7
+5316 5317 5885 0x157aa31a
+5317 5318 5884 0x157a574d
+5318 5319 5883 0x157a0bd6
+5319 5320 5882 0x1579c0b4
+5320 5321 5881 0x15797593
+5321 5322 5880 0x15792a72
+5322 5323 5879 0x1578dffc
+5323 5324 5878 0x15789530
+5324 5325 5877 0x15784b11
+5325 5326 5876 0x157800f1
+5326 5327 5875 0x1577b727
+5327 5328 5874 0x15776d5d
+5328 5329 5873 0x15772393
+5329 5330 5872 0x1576da1f
+5330 5331 5871 0x15769101
+5331 5332 5870 0x157647e3
+5332 5333 5869 0x1575fec4
+5333 5334 5868 0x1575b5fc
+5334 5335 5867 0x15756d8a
+5335 5336 5866 0x15752517
+5336 5337 5865 0x1574dcfb
+5337 5338 5864 0x157494de
+5338 5339 5863 0x15744d17
+5339 5340 5862 0x15740551
+5340 5341 5861 0x1573be35
+5341 5342 5860 0x157376c5
+5342 5343 5859 0x15732faa
+5343 5344 5858 0x1572e88f
+5344 5345 5857 0x1572a1c9
+5345 5346 5856 0x15725b04
+5346 5347 5855 0x15721495
+5347 5348 5854 0x1571ce26
+5348 5349 5853 0x1571880c
+5349 5350 5852 0x15714249
+5350 5351 5851 0x1570fc85
+5351 5352 5850 0x1570b6c2
+5352 5353 5849 0x15707154
+5353 5354 5848 0x15702be6
+5354 5355 5847 0x156fe6ce
+5355 5356 5846 0x156fa262
+5356 5357 5845 0x156f5d4a
+5357 5358 5844 0x156f18de
+5358 5359 5843 0x156ed472
+5359 5360 5842 0x156e9006
+5360 5361 5841 0x156e4bf0
+5361 5362 5840 0x156e082f
+5362 5363 5839 0x156dc419
+5363 5364 5838 0x156d80af
+5364 5365 5837 0x156d3d44
+5365 5366 5836 0x156cf9da
+5366 5367 5835 0x156cb6c5
+5367 5368 5834 0x156c73b0
+5368 5369 5833 0x156c30f2
+5369 5370 5832 0x156bee33
+5370 5371 5831 0x156babca
+5371 5372 5830 0x156b69b7
+5372 5373 5829 0x156b27a4
+5373 5374 5828 0x156ae591
+5374 5375 5827 0x156aa3d3
+5375 5376 5826 0x156a6216
+5376 5377 5825 0x156a20af
+5377 5378 5824 0x1569df48
+5378 5379 5823 0x15699de0
+5379 5380 5822 0x15695d25
+5380 5381 5821 0x15691c13
+5381 5382 5820 0x1568db57
+5382 5383 5819 0x15689af2
+5383 5384 5818 0x15685a36
+5384 5385 5817 0x15681a26
+5385 5386 5816 0x1567da16
+5386 5387 5815 0x15679a5c
+5387 5388 5814 0x15675aa2
+5388 5389 5813 0x15671ae8
+5389 5390 5812 0x1566db84
+5390 5391 5811 0x15669c20
+5391 5392 5810 0x15665cbc
+5392 5393 5809 0x15661dae
+5393 5394 5808 0x1565def5
+5394 5395 5807 0x1565a03d
+5395 5396 5806 0x15656184
+5396 5397 5805 0x15652322
+5397 5398 5804 0x1564e4bf
+5398 5399 5803 0x1564a6b2
+5399 5400 5802 0x156468a6
+5400 5401 5801 0x15642aef
+5401 5402 5800 0x1563ed38
+5402 5403 5799 0x1563afd7
+5403 5404 5798 0x15637276
+5404 5405 5797 0x1563356b
+5405 5406 5796 0x1562f80a
+5406 5407 5795 0x1562bb55
+5407 5408 5794 0x15627e4a
+5408 5409 5793 0x156241ea
+5409 5410 5792 0x15620535
+5410 5411 5791 0x1561c8d5
+5411 5412 5790 0x15618ccc
+5412 5413 5789 0x1561506d
+5413 5414 5788 0x156114b9
+5414 5415 5787 0x1560d8af
+5415 5416 5786 0x15609cfc
+5416 5417 5785 0x1560619e
+5417 5418 5784 0x15602696
+5418 5419 5783 0x155feb38
+5419 5420 5782 0x155fb030
+5420 5421 5781 0x155f7528
+5421 5422 5780 0x155f3a76
+5422 5423 5779 0x155effc4
+5423 5424 5778 0x155ec512
+5424 5425 5777 0x155e8ab6
+5425 5426 5776 0x155e505a
+5426 5427 5775 0x155e1653
+5427 5428 5774 0x155ddc4d
+5428 5429 5773 0x155da29c
+5429 5430 5772 0x155d68ec
+5430 5431 5771 0x155d2f3c
+5431 5432 5770 0x155cf5e1
+5432 5433 5769 0x155cbcdc
+5433 5434 5768 0x155c8382
+5434 5435 5767 0x155c4a7d
+5435 5436 5766 0x155c1178
+5436 5437 5765 0x155bd8c9
+5437 5438 5764 0x155ba01a
+5438 5439 5763 0x155b67c1
+5439 5440 5762 0x155b2f12
+5440 5441 5761 0x155af70f
+5441 5442 5760 0x155abeb6
+5442 5443 5759 0x155a8709
+5443 5444 5758 0x155a4f06
+5444 5445 5757 0x155a1758
+5445 5446 5756 0x1559dfab
+5446 5447 5755 0x1559a854
+5447 5448 5754 0x155970fc
+5448 5449 5753 0x155939fb
+5449 5450 5752 0x155902a3
+5450 5451 5751 0x1558cbf7
+5451 5452 5750 0x155894f6
+5452 5453 5749 0x15585e4a
+5453 5454 5748 0x1558279e
+5454 5455 5747 0x1557f149
+5455 5456 5746 0x1557baf3
+5456 5457 5745 0x1557849d
+5457 5458 5744 0x15574e9d
+5458 5459 5743 0x1557189d
+5459 5460 5742 0x1556e2f3
+5460 5461 5741 0x1556acf3
+5461 5462 5740 0x1556779e
+5462 5463 5739 0x155641f4
+5463 5464 5738 0x15560cf6
+5464 5465 5737 0x1555d7a2
+5465 5466 5736 0x1555a2f9
+5466 5467 5735 0x15556e51
+5467 5468 5734 0x155539a8
+5468 5469 5733 0x15550555
+5469 5470 5732 0x1554d103
+5470 5471 5731 0x15549cb0
+5471 5472 5730 0x155468b3
+5472 5473 5729 0x155434b6
+5473 5474 5728 0x155400e5
+5474 5475 5727 0x1553cd3e
+5475 5476 5726 0x15539997
+5476 5477 5725 0x1553661b
+5477 5478 5724 0x155332ca
+5478 5479 5723 0x1552ffce
+5479 5480 5722 0x1552cca8
+5480 5481 5721 0x155299ad
+5481 5482 5720 0x155266dd
+5482 5483 5719 0x15523437
+5483 5484 5718 0x15520192
+5484 5485 5717 0x1551ceed
+5485 5486 5716 0x15519c9d
+5486 5487 5715 0x15516a4e
+5487 5488 5714 0x15513829
+5488 5489 5713 0x1551062f
+5489 5490 5712 0x1550d436
+5490 5491 5711 0x1550a267
+5491 5492 5710 0x155070c3
+5492 5493 5709 0x15503f20
+5493 5494 5708 0x15500da7
+5494 5495 5707 0x154fdcaf
+5495 5496 5706 0x154fab61
+5496 5497 5705 0x154f7a3e
+5497 5498 5704 0x154f4946
+5498 5499 5703 0x154f1879
+5499 5500 5702 0x154ee7ac
+5500 5501 5701 0x154eb709
+5501 5502 5700 0x154e8667
+5502 5503 5699 0x154e561b
+5503 5504 5698 0x154e25cf
+5504 5505 5697 0x154df582
+5505 5506 5696 0x154dc58c
+5506 5507 5695 0x154d9595
+5507 5508 5694 0x154d65ca
+5508 5509 5693 0x154d35fe
+5509 5510 5692 0x154d0689
+5510 5511 5691 0x154cd713
+5511 5512 5690 0x154ca79d
+5512 5513 5689 0x154c7853
+5513 5514 5688 0x154c4933
+5514 5515 5687 0x154c1a3e
+5515 5516 5686 0x154beb49
+5516 5517 5685 0x154bbc54
+5517 5518 5684 0x154b8db5
+5518 5519 5683 0x154b5f16
+5519 5520 5682 0x154b30a2
+5520 5521 5681 0x154b022e
+5521 5522 5680 0x154ad3e5
+5522 5523 5679 0x154aa5c7
+5523 5524 5678 0x154a77a9
+5524 5525 5677 0x154a49e0
+5525 5526 5676 0x154a1c18
+5526 5527 5675 0x1549ee50
+5527 5528 5674 0x1549c0b2
+5528 5529 5673 0x15499340
+5529 5530 5672 0x154965cd
+5530 5531 5671 0x15493886
+5531 5532 5670 0x15490b69
+5532 5533 5669 0x1548de4d
+5533 5534 5668 0x1548b15b
+5534 5535 5667 0x15488469
+5535 5536 5666 0x154857a2
+5536 5537 5665 0x15482b07
+5537 5538 5664 0x1547fe6b
+5538 5539 5663 0x1547d1fa
+5539 5540 5662 0x1547a5b4
+5540 5541 5661 0x154779c4
+5541 5542 5660 0x15474da9
+5542 5543 5659 0x1547218e
+5543 5544 5658 0x1546f59e
+5544 5545 5657 0x1546c9d9
+5545 5546 5656 0x15469e3e
+5546 5547 5655 0x154672a4
+5547 5548 5654 0x1546470a
+5548 5549 5653 0x15461bc6
+5549 5550 5652 0x1545f081
+5550 5551 5651 0x1545c53d
+5551 5552 5650 0x15459a23
+5552 5553 5649 0x15456f35
+5553 5554 5648 0x15454471
+5554 5555 5647 0x154519ae
+5555 5556 5646 0x1544ef15
+5556 5557 5645 0x1544c4a7
+5557 5558 5644 0x15449a3a
+5558 5559 5643 0x15446fcc
+5559 5560 5642 0x15444589
+5560 5561 5641 0x15441b71
+5561 5562 5640 0x1543f15a
+5562 5563 5639 0x1543c76d
+5563 5564 5638 0x15439dab
+5564 5565 5637 0x154373e9
+5565 5566 5636 0x15434a52
+5566 5567 5635 0x154320bb
+5567 5568 5634 0x1542f74e
+5568 5569 5633 0x1542cde2
+5569 5570 5632 0x1542a4cc
+5570 5571 5631 0x15427bb6
+5571 5572 5630 0x154252cb
+5572 5573 5629 0x154229df
+5573 5574 5628 0x154200f4
+5574 5575 5627 0x1541d833
+5575 5576 5626 0x1541af9e
+5576 5577 5625 0x15418709
+5577 5578 5624 0x15415e9e
+5578 5579 5623 0x15413634
+5579 5580 5622 0x15410df4
+5580 5581 5621 0x1540e5df
+5581 5582 5620 0x1540bdcb
+5582 5583 5619 0x154095e1
+5583 5584 5618 0x15406df7
+5584 5585 5617 0x15404638
+5585 5586 5616 0x15401ea4
+5586 5587 5615 0x153ff73c
+5587 5588 5614 0x153fcfa8
+5588 5589 5613 0x153fa86a
+5589 5590 5612 0x153f8101
+5590 5591 5611 0x153f59ee
+5591 5592 5610 0x153f32db
+5592 5593 5609 0x153f0bc8
+5593 5594 5608 0x153ee4df
+5594 5595 5607 0x153ebe22
+5595 5596 5606 0x153e9765
+5596 5597 5605 0x153e70d3
+5597 5598 5604 0x153e4a40
+5598 5599 5603 0x153e23d9
+5599 5600 5602 0x153dfd72
+5600 5601 5601 0x153dd735
+5601 5602 5600 0x153db14f
+5602 5603 5599 0x153d8b3d
+5603 5604 5598 0x153d652c
+5604 5605 5597 0x153d3f70
+5605 5606 5596 0x153d198a
+5606 5607 5595 0x153cf3f9
+5607 5608 5594 0x153cce3d
+5608 5609 5593 0x153ca8d8
+5609 5610 5592 0x153c8372
+5610 5611 5591 0x153c5e0c
+5611 5612 5590 0x153c38d1
+5612 5613 5589 0x153c13c1
+5613 5614 5588 0x153beeb1
+5614 5615 5587 0x153bc9a2
+5615 5616 5586 0x153ba4e8
+5616 5617 5585 0x153b802d
+5617 5618 5584 0x153b5b9e
+5618 5619 5583 0x153b36e4
+5619 5620 5582 0x153b1280
+5620 5621 5581 0x153aedf1
+5621 5622 5580 0x153ac9b8
+5622 5623 5579 0x153aa57f
+5623 5624 5578 0x153a8145
+5624 5625 5577 0x153a5d37
+5625 5626 5576 0x153a3929
+5626 5627 5575 0x153a1545
+5627 5628 5574 0x1539f18d
+5628 5629 5573 0x1539cdd5
+5629 5630 5572 0x1539aa1c
+5630 5631 5571 0x1539868f
+5631 5632 5570 0x1539632c
+5632 5633 5569 0x15393ff4
+5633 5634 5568 0x15391cbd
+5634 5635 5567 0x1538f985
+5635 5636 5566 0x1538d64d
+5636 5637 5565 0x1538b36c
+5637 5638 5564 0x1538905f
+5638 5639 5563 0x15386d7d
+5639 5640 5562 0x15384ac6
+5640 5641 5561 0x1538280f
+5641 5642 5560 0x15380584
+5642 5643 5559 0x1537e2f8
+5643 5644 5558 0x1537c097
+5644 5645 5557 0x15379e36
+5645 5646 5556 0x15377bd5
+5646 5647 5555 0x153759ca
+5647 5648 5554 0x153737be
+5648 5649 5553 0x153715b3
+5649 5650 5552 0x1536f3d3
+5650 5651 5551 0x1536d1f3
+5651 5652 5550 0x1536b03e
+5652 5653 5549 0x15368e88
+5653 5654 5548 0x15366cd3
+5654 5655 5547 0x15364b49
+5655 5656 5546 0x153629e9
+5656 5657 5545 0x1536088a
+5657 5658 5544 0x1535e756
+5658 5659 5543 0x1535c621
+5659 5660 5542 0x1535a4ed
+5660 5661 5541 0x153583e3
+5661 5662 5540 0x153562da
+5662 5663 5539 0x15354226
+5663 5664 5538 0x15352172
+5664 5665 5537 0x153500bf
+5665 5666 5536 0x1534e00b
+5666 5667 5535 0x1534bf82
+5667 5668 5534 0x15349ef9
+5668 5669 5533 0x15347e9c
+5669 5670 5532 0x15345e3e
+5670 5671 5531 0x15343e0b
+5671 5672 5530 0x15341dd8
+5672 5673 5529 0x1533fda5
+5673 5674 5528 0x1533dd9d
+5674 5675 5527 0x1533bdc0
+5675 5676 5526 0x15339de3
+5676 5677 5525 0x15337e06
+5677 5678 5524 0x15335e54
+5678 5679 5523 0x15333ef8
+5679 5680 5522 0x15331f46
+5680 5681 5521 0x1532ffea
+5681 5682 5520 0x1532e062
+5682 5683 5519 0x1532c131
+5683 5684 5518 0x1532a1d5
+5684 5685 5517 0x153282a4
+5685 5686 5516 0x1532639d
+5686 5687 5515 0x15324497
+5687 5688 5514 0x15322591
+5688 5689 5513 0x153206b5
+5689 5690 5512 0x1531e7da
+5690 5691 5511 0x1531c929
+5691 5692 5510 0x1531aa79
+5692 5693 5509 0x15318bf3
+5693 5694 5508 0x15316d99
+5694 5695 5507 0x15314f13
+5695 5696 5506 0x153130b8
+5696 5697 5505 0x1531125e
+5697 5698 5504 0x1530f42e
+5698 5699 5503 0x1530d5fe
+5699 5700 5502 0x1530b7fa
+5700 5701 5501 0x153099f5
+5701 5702 5500 0x15307bf0
+5702 5703 5499 0x15305e16
+5703 5704 5498 0x15304067
+5704 5705 5497 0x1530228e
+5705 5706 5496 0x153004df
+5706 5707 5495 0x152fe75b
+5707 5708 5494 0x152fc9d7
+5708 5709 5493 0x152fac7e
+5709 5710 5492 0x152f8f25
+5710 5711 5491 0x152f71cc
+5711 5712 5490 0x152f549d
+5712 5713 5489 0x152f376f
+5713 5714 5488 0x152f1a6c
+5714 5715 5487 0x152efd3e
+5715 5716 5486 0x152ee066
+5716 5717 5485 0x152ec363
+5717 5718 5484 0x152ea68a
+5718 5719 5483 0x152e89dd
+5719 5720 5482 0x152e6d30
+5720 5721 5481 0x152e5082
+5721 5722 5480 0x152e3400
+5722 5723 5479 0x152e177e
+5723 5724 5478 0x152dfb26
+5724 5725 5477 0x152ddecf
+5725 5726 5476 0x152dc277
+5726 5727 5475 0x152da64b
+5727 5728 5474 0x152d8a1e
+5728 5729 5473 0x152d6e1d
+5729 5730 5472 0x152d521b
+5730 5731 5471 0x152d361a
+5731 5732 5470 0x152d1a18
+5732 5733 5469 0x152cfe6c
+5733 5734 5468 0x152ce296
+5734 5735 5467 0x152cc6ea
+5735 5736 5466 0x152cab3e
+5736 5737 5465 0x152c8f92
+5737 5738 5464 0x152c7412
+5738 5739 5463 0x152c58bc
+5739 5740 5462 0x152c3d66
+5740 5741 5461 0x152c2210
+5741 5742 5460 0x152c06e5
+5742 5743 5459 0x152beb8f
+5743 5744 5458 0x152bd08f
+5744 5745 5457 0x152bb564
+5745 5746 5456 0x152b9a64
+5746 5747 5455 0x152b7f64
+5747 5748 5454 0x152b648f
+5748 5749 5453 0x152b49ba
+5749 5750 5452 0x152b2ee5
+5750 5751 5451 0x152b143b
+5751 5752 5450 0x152af991
+5752 5753 5449 0x152adf12
+5753 5754 5448 0x152ac4be
+5754 5755 5447 0x152aaa94
+5755 5756 5446 0x152a9055
+5756 5757 5445 0x152a7617
+5757 5758 5444 0x152a5c03
+5758 5759 5443 0x152a41ef
+5759 5760 5442 0x152a27f0
+5760 5761 5441 0x152a0df2
+5761 5762 5440 0x1529f41f
+5762 5763 5439 0x1529da4b
+5763 5764 5438 0x1529c08d
+5764 5765 5437 0x1529a6cf
+5765 5766 5436 0x15298d27
+5766 5767 5435 0x15297393
+5767 5768 5434 0x15295a16
+5768 5769 5433 0x15294098
+5769 5770 5432 0x15292730
+5770 5771 5431 0x15290df3
+5771 5772 5430 0x1528f4b6
+5772 5773 5429 0x1528db78
+5773 5774 5428 0x1528c251
+5774 5775 5427 0x1528a929
+5775 5776 5426 0x15289017
+5776 5777 5425 0x1528771a
+5777 5778 5424 0x15285e33
+5778 5779 5423 0x1528454b
+5779 5780 5422 0x15282c79
+5780 5781 5421 0x152813bd
+5781 5782 5420 0x1527fb16
+5782 5783 5419 0x1527e26f
+5783 5784 5418 0x1527c9dd
+5784 5785 5417 0x1527b14c
+5785 5786 5416 0x152798fb
+5786 5787 5415 0x15278094
+5787 5788 5414 0x1527682e
+5788 5789 5413 0x15274fdd
+5789 5790 5412 0x152737a1
+5790 5791 5411 0x15271f7b
+5791 5792 5410 0x15270755
+5792 5793 5409 0x1526ef44
+5793 5794 5408 0x1526d749
+5794 5795 5407 0x1526bf4e
+5795 5796 5406 0x1526a768
+5796 5797 5405 0x15268f98
+5797 5798 5404 0x152677c7
+5798 5799 5403 0x15266022
+5799 5800 5402 0x15264867
+5800 5801 5401 0x152630ed
+5801 5802 5400 0x1526195d
+5802 5803 5399 0x152601e2
+5803 5804 5398 0x1525ea68
+5804 5805 5397 0x1525d303
+5805 5806 5396 0x1525bbb3
+5806 5807 5395 0x1525a464
+5807 5808 5394 0x15258d2a
+5808 5809 5393 0x15257605
+5809 5810 5392 0x15255ee1
+5810 5811 5391 0x152547d2
+5811 5812 5390 0x152530d8
+5812 5813 5389 0x152519de
+5813 5814 5388 0x152502fa
+5814 5815 5387 0x1524ec2c
+5815 5816 5386 0x1524d55d
+5816 5817 5385 0x1524beb9
+5817 5818 5384 0x1524a815
+5818 5819 5383 0x15249172
+5819 5820 5382 0x15247ae3
+5820 5821 5381 0x15246455
+5821 5822 5380 0x15244df2
+5822 5823 5379 0x15243779
+5823 5824 5378 0x1524212b
+5824 5825 5377 0x15240add
+5825 5826 5376 0x1523f4a4
+5826 5827 5375 0x1523de6c
+5827 5828 5374 0x1523c849
+5828 5829 5373 0x1523b23c
+5829 5830 5372 0x15239c2e
+5830 5831 5371 0x15238636
+5831 5832 5370 0x15237053
+5832 5833 5369 0x15235a71
+5833 5834 5368 0x152344a4
+5834 5835 5367 0x15232eec
+5835 5836 5366 0x15231934
+5836 5837 5365 0x1523037d
+5837 5838 5364 0x1522eddb
+5838 5839 5363 0x1522d84e
+5839 5840 5362 0x1522c2d7
+5840 5841 5361 0x1522ad5f
+5841 5842 5360 0x152297fe
+5842 5843 5359 0x1522829c
+5843 5844 5358 0x15226d4f
+5844 5845 5357 0x15225819
+5845 5846 5356 0x152242e2
+5846 5847 5355 0x15222dd6
+5847 5848 5354 0x152218b4
+5848 5849 5353 0x152203a9
+5849 5850 5352 0x1521eeb2
+5850 5851 5351 0x1521d9bc
+5851 5852 5350 0x1521c4db
+5852 5853 5349 0x1521affa
+5853 5854 5348 0x15219b2e
+5854 5855 5347 0x15218678
+5855 5856 5346 0x152171c2
+5856 5857 5345 0x15215d21
+5857 5858 5344 0x15214881
+5858 5859 5343 0x152133f6
+5859 5860 5342 0x15211f80
+5860 5861 5341 0x15210b0a
+5861 5862 5340 0x1520f6aa
+5862 5863 5339 0x1520e25f
+5863 5864 5338 0x1520ce15
+5864 5865 5337 0x1520b9df
+5865 5866 5336 0x1520a5aa
+5866 5867 5335 0x1520918a
+5867 5868 5334 0x15207d6b
+5868 5869 5333 0x15206960
+5869 5870 5332 0x15205556
+5870 5871 5331 0x15204161
+5871 5872 5330 0x15202d82
+5872 5873 5329 0x152019a2
+5873 5874 5328 0x152005d8
+5874 5875 5327 0x151ff20e
+5875 5876 5326 0x151fde5a
+5876 5877 5325 0x151fcabb
+5877 5878 5324 0x151fb731
+5878 5879 5323 0x151fa392
+5879 5880 5322 0x151f9009
+5880 5881 5321 0x151f7c95
+5881 5882 5320 0x151f6921
+5882 5883 5319 0x151f55c2
+5883 5884 5318 0x151f4279
+5884 5885 5317 0x151f2f30
+5885 5886 5316 0x151f1be7
+5886 5887 5315 0x151f08b3
+5887 5888 5314 0x151ef595
+5888 5889 5313 0x151ee277
+5889 5890 5312 0x151ecf6e
+5890 5891 5311 0x151ebc7b
+5891 5892 5310 0x151ea972
+5892 5893 5309 0x151e96aa
+5893 5894 5308 0x151e83cc
+5894 5895 5307 0x151e70ee
+5895 5896 5306 0x151e5e26
+5896 5897 5305 0x151e4b73
+5897 5898 5304 0x151e38c0
+5898 5899 5303 0x151e2622
+5899 5900 5302 0x151e1385
+5900 5901 5301 0x151e00fd
+5901 5902 5300 0x151dee75
+5902 5903 5299 0x151ddc03
+5903 5904 5298 0x151dc9a6
+5904 5905 5297 0x151db749
+5905 5906 5296 0x151da4ec
+5906 5907 5295 0x151d92a4
+5907 5908 5294 0x151d8072
+5908 5909 5293 0x151d6e55
+5909 5910 5292 0x151d5c23
+5910 5911 5291 0x151d4a07
+5911 5912 5290 0x151d37ff
+5912 5913 5289 0x151d25f8
+5913 5914 5288 0x151d1407
+5914 5915 5287 0x151d0215
+5915 5916 5286 0x151cf039
+5916 5917 5285 0x151cde5d
+5917 5918 5284 0x151ccc96
+5918 5919 5283 0x151cbacf
+5919 5920 5282 0x151ca91e
+5920 5921 5281 0x151c976c
+5921 5922 5280 0x151c85d1
+5922 5923 5279 0x151c744a
+5923 5924 5278 0x151c62c4
+5924 5925 5277 0x151c5153
+5925 5926 5276 0x151c3fe2
+5926 5927 5275 0x151c2e71
+5927 5928 5274 0x151c1d16
+5928 5929 5273 0x151c0bba
+5929 5930 5272 0x151bfa74
+5930 5931 5271 0x151be944
+5931 5932 5270 0x151bd813
+5932 5933 5269 0x151bc6e3
+5933 5934 5268 0x151bb5c8
+5934 5935 5267 0x151ba4ad
+5935 5936 5266 0x151b93a7
+5936 5937 5265 0x151b82b7
+5937 5938 5264 0x151b71b2
+5938 5939 5263 0x151b60ec
+5939 5940 5262 0x151b5012
+5940 5941 5261 0x151b3f37
+5941 5942 5260 0x151b2e72
+5942 5943 5259 0x151b1dad
+5943 5944 5258 0x151b0cfd
+5944 5945 5257 0x151afc4e
+5945 5946 5256 0x151aebb3
+5946 5947 5255 0x151adb19
+5947 5948 5254 0x151aca94
+5948 5949 5253 0x151aba10
+5949 5950 5252 0x151aa9a0
+5950 5951 5251 0x151a9931
+5951 5952 5250 0x151a88c2
+5952 5953 5249 0x151a787d
+5953 5954 5248 0x151a6823
+5954 5955 5247 0x151a57f4
+5955 5956 5246 0x151a47c6
+5956 5957 5245 0x151a3781
+5957 5958 5244 0x151a2768
+5958 5959 5243 0x151a174e
+5959 5960 5242 0x151a0735
+5960 5961 5241 0x1519f731
+5961 5962 5240 0x1519e72d
+5962 5963 5239 0x1519d73e
+5963 5964 5238 0x1519c750
+5964 5965 5237 0x1519b777
+5965 5966 5236 0x1519a79e
+5966 5967 5235 0x151997da
+5967 5968 5234 0x15198817
+5968 5969 5233 0x15197853
+5969 5970 5232 0x151968bb
+5970 5971 5231 0x1519590c
+5971 5972 5230 0x15194974
+5972 5973 5229 0x151939db
+5973 5974 5228 0x15192a58
+5974 5975 5227 0x15191ad5
+5975 5976 5226 0x15190b67
+5976 5977 5225 0x1518fbf9
+5977 5978 5224 0x1518eca1
+5978 5979 5223 0x1518dd33
+5979 5980 5222 0x1518cdf1
+5980 5981 5221 0x1518beae
+5981 5982 5220 0x1518af6b
+5982 5983 5219 0x1518a03e
+5983 5984 5218 0x15189110
+5984 5985 5217 0x151881f9
+5985 5986 5216 0x151872e1
+5986 5987 5215 0x151863c9
+5987 5988 5214 0x151854c6
+5988 5989 5213 0x151845c4
+5989 5990 5212 0x151836d7
+5990 5991 5211 0x151827ea
+5991 5992 5210 0x15181913
+5992 5993 5209 0x15180a3b
+5993 5994 5208 0x1517fb64
+5994 5995 5207 0x1517eca2
+5995 5996 5206 0x1517dde0
+5996 5997 5205 0x1517cf33
+5997 5998 5204 0x1517c087
+5998 5999 5203 0x1517b1da
+5999 6000 5202 0x1517a343
+6000 6001 5201 0x151794c2
+6001 6002 5200 0x15178640
+6002 6003 5199 0x151777bf
+6003 6004 5198 0x1517693d
+6004 6005 5197 0x15175ad1
+6005 6006 5196 0x15174c65
+6006 6007 5195 0x15173e0e
+6007 6008 5194 0x15172fb7
+6008 6009 5193 0x15172176
+6009 6010 5192 0x15171335
+6010 6011 5191 0x151704f4
+6011 6012 5190 0x1516f6c8
+6012 6013 5189 0x1516e89c
+6013 6014 5188 0x1516da71
+6014 6015 5187 0x1516cc5a
+6015 6016 5186 0x1516be5a
+6016 6017 5185 0x1516b059
+6017 6018 5184 0x1516a258
+6018 6019 5183 0x15169457
+6019 6020 5182 0x1516866c
+6020 6021 5181 0x15167881
+6021 6022 5180 0x15166aab
+6022 6023 5179 0x15165cd5
+6023 6024 5178 0x15164eff
+6024 6025 5177 0x1516413f
+6025 6026 5176 0x1516337e
+6026 6027 5175 0x151625d3
+6027 6028 5174 0x15161813
+6028 6029 5173 0x15160a7d
+6029 6030 5172 0x1515fcd3
+6030 6031 5171 0x1515ef53
+6031 6032 5170 0x1515e1bd
+6032 6033 5169 0x1515d43d
+6033 6034 5168 0x1515c6bd
+6034 6035 5167 0x1515b953
+6035 6036 5166 0x1515abd3
+6036 6037 5165 0x15159e7e
+6037 6038 5164 0x15159113
+6038 6039 5163 0x151583be
+6039 6040 5162 0x15157674
+6040 6041 5161 0x1515693f
+6041 6042 5160 0x15155c15
+6042 6043 5159 0x15154ef5
+6043 6044 5158 0x151541d6
+6044 6045 5157 0x151534c1
+6045 6046 5156 0x151527b7
+6046 6047 5155 0x15151ac3
+6047 6048 5154 0x15150dc3
+6048 6049 5153 0x151500cf
+6049 6050 5152 0x1514f3db
+6050 6051 5151 0x1514e6fc
+6051 6052 5150 0x1514da1d
+6052 6053 5149 0x1514cd48
+6053 6054 5148 0x1514c074
+6054 6055 5147 0x1514b3aa
+6055 6056 5146 0x1514a6ec
+6056 6057 5145 0x15149a38
+6057 6058 5144 0x15148d84
+6058 6059 5143 0x151480e5
+6059 6060 5142 0x1514743c
+6060 6061 5141 0x151467a8
+6061 6062 5140 0x15145b1f
+6062 6063 5139 0x15144e95
+6063 6064 5138 0x15144217
+6064 6065 5137 0x15143599
+6065 6066 5136 0x15142925
+6066 6067 5135 0x15141cbc
+6067 6068 5134 0x15141053
+6068 6069 5133 0x151403f5
+6069 6070 5132 0x1513f7a1
+6070 6071 5131 0x1513eb59
+6071 6072 5130 0x1513df10
+6072 6073 5129 0x1513d2d2
+6073 6074 5128 0x1513c694
+6074 6075 5127 0x1513ba61
+6075 6076 5126 0x1513ae38
+6076 6077 5125 0x1513a21a
+6077 6078 5124 0x15139607
+6078 6079 5123 0x151389f4
+6079 6080 5122 0x15137dec
+6080 6081 5121 0x151371e3
+6081 6082 5120 0x151365e6
+6082 6083 5119 0x151359f3
+6083 6084 5118 0x15134e00
+6084 6085 5117 0x15134218
+6085 6086 5116 0x1513363a
+6086 6087 5115 0x15132a68
+6087 6088 5114 0x15131e95
+6088 6089 5113 0x151312c2
+6089 6090 5112 0x15130705
+6090 6091 5111 0x1512fb48
+6091 6092 5110 0x1512ef8b
+6092 6093 5109 0x1512e3ee
+6093 6094 5108 0x1512d846
+6094 6095 5107 0x1512cca9
+6095 6096 5106 0x1512c10c
+6096 6097 5105 0x1512b57a
+6097 6098 5104 0x1512a9f2
+6098 6099 5103 0x15129e6b
+6099 6100 5102 0x151292ee
+6100 6101 5101 0x1512877c
+6101 6102 5100 0x15127c0a
+6102 6103 5099 0x151270a2
+6103 6104 5098 0x15126546
+6104 6105 5097 0x151259e9
+6105 6106 5096 0x15124e97
+6106 6107 5095 0x15124345
+6107 6108 5094 0x15123814
+6108 6109 5093 0x15122ccc
+6109 6110 5092 0x1512219b
+6110 6111 5091 0x15121669
+6111 6112 5090 0x15120b42
+6112 6113 5089 0x1512001b
+6113 6114 5088 0x1511f4ff
+6114 6115 5087 0x1511e9ed
+6115 6116 5086 0x1511dedc
+6116 6117 5085 0x1511d3d5
+6117 6118 5084 0x1511c8ce
+6118 6119 5083 0x1511bddd
+6119 6120 5082 0x1511b2e1
+6120 6121 5081 0x1511a7fb
+6121 6122 5080 0x15119d14
+6122 6123 5079 0x15119238
+6123 6124 5078 0x15118767
+6124 6125 5077 0x15117c96
+6125 6126 5076 0x151171c5
+6126 6127 5075 0x15116709
+6127 6128 5074 0x15115c43
+6128 6129 5073 0x15115192
+6129 6130 5072 0x151146e1
+6130 6131 5071 0x15113c3b
+6131 6132 5070 0x15113195
+6132 6133 5069 0x151126f9
+6133 6134 5068 0x15111c5e
+6134 6135 5067 0x151111cd
+6135 6136 5066 0x15110747
+6136 6137 5065 0x1510fcc1
+6137 6138 5064 0x1510f246
+6138 6139 5063 0x1510e7e0
+6139 6140 5062 0x1510dd70
+6140 6141 5061 0x1510d2ff
+6141 6142 5060 0x1510c8a4
+6142 6143 5059 0x1510be49
+6143 6144 5058 0x1510b3ee
+6144 6145 5057 0x1510a99e
+6145 6146 5056 0x15109f58
+6146 6147 5055 0x15109513
+6147 6148 5054 0x15108ad8
+6148 6149 5053 0x151080a8
+6149 6150 5052 0x15107678
+6150 6151 5051 0x15106c48
+6151 6152 5050 0x1510622d
+6152 6153 5049 0x15105808
+6153 6154 5048 0x15104e03
+6154 6155 5047 0x151043f3
+6155 6156 5046 0x151039e3
+6156 6157 5045 0x15102fe8
+6157 6158 5044 0x151025e3
+6158 6159 5043 0x15101bf3
+6159 6160 5042 0x15101204
+6160 6161 5041 0x15100814
+6161 6162 5040 0x150ffe2f
+6162 6163 5039 0x150ff455
+6163 6164 5038 0x150fea7b
+6164 6165 5037 0x150fe0ab
+6165 6166 5036 0x150fd6dc
+6166 6167 5035 0x150fcd17
+6167 6168 5034 0x150fc352
+6168 6169 5033 0x150fb998
+6169 6170 5032 0x150faff3
+6170 6171 5031 0x150fa644
+6171 6172 5030 0x150f9ca0
+6172 6173 5029 0x150f92fb
+6173 6174 5028 0x150f8961
+6174 6175 5027 0x150f7fc7
+6175 6176 5026 0x150f7638
+6176 6177 5025 0x150f6cb4
+6177 6178 5024 0x150f6330
+6178 6179 5023 0x150f59ab
+6179 6180 5022 0x150f503c
+6180 6181 5021 0x150f46c3
+6181 6182 5020 0x150f3d5e
+6182 6183 5019 0x150f33f0
+6183 6184 5018 0x150f2a96
+6184 6185 5017 0x150f2147
+6185 6186 5016 0x150f17ee
+6186 6187 5015 0x150f0e9f
+6187 6188 5014 0x150f0551
+6188 6189 5013 0x150efc17
+6189 6190 5012 0x150ef2d3
+6190 6191 5011 0x150ee99a
+6191 6192 5010 0x150ee06c
+6192 6193 5009 0x150ed73d
+6193 6194 5008 0x150ece19
+6194 6195 5007 0x150ec4f6
+6195 6196 5006 0x150ebbdc
+6196 6197 5005 0x150eb2c3
+6197 6198 5004 0x150ea9b5
+6198 6199 5003 0x150ea0b2
+6199 6200 5002 0x150e97ae
+6200 6201 5001 0x150e8eb5
+6201 6202 5000 0x150e85bc
+6202 6203 4999 0x150e7cc3
+6203 6204 4998 0x150e73d5
+6204 6205 4997 0x150e6af2
+6205 6206 4996 0x150e620f
+6206 6207 4995 0x150e592b
+6207 6208 4994 0x150e5053
+6208 6209 4993 0x150e4785
+6209 6210 4992 0x150e3eb7
+6210 6211 4991 0x150e35f4
+6211 6212 4990 0x150e2d30
+6212 6213 4989 0x150e246d
+6213 6214 4988 0x150e1bb5
+6214 6215 4987 0x150e1307
+6215 6216 4986 0x150e0a64
+6216 6217 4985 0x150e01c1
+6217 6218 4984 0x150df91e
+6218 6219 4983 0x150df086
+6219 6220 4982 0x150de7ee
+6220 6221 4981 0x150ddf55
+6221 6222 4980 0x150dd6c8
+6222 6223 4979 0x150dce45
+6223 6224 4978 0x150dc5c2
+6224 6225 4977 0x150dbd4a
+6225 6226 4976 0x150db4d2
+6226 6227 4975 0x150dac65
+6227 6228 4974 0x150da3f8
+6228 6229 4973 0x150d9b8a
+6229 6230 4972 0x150d9328
+6230 6231 4971 0x150d8adb
+6231 6232 4970 0x150d8283
+6232 6233 4969 0x150d7a2b
+6233 6234 4968 0x150d71de
+6234 6235 4967 0x150d699b
+6235 6236 4966 0x150d6159
+6236 6237 4965 0x150d5917
+6237 6238 4964 0x150d50df
+6238 6239 4963 0x150d48a7
+6239 6240 4962 0x150d407a
+6240 6241 4961 0x150d3858
+6241 6242 4960 0x150d302b
+6242 6243 4959 0x150d2814
+6243 6244 4958 0x150d1ff2
+6244 6245 4957 0x150d17e5
+6245 6246 4956 0x150d0fd8
+6246 6247 4955 0x150d07cb
+6247 6248 4954 0x150cffca
+6248 6249 4953 0x150cf7c8
+6249 6250 4952 0x150cefc6
+6250 6251 4951 0x150ce7ce
+6251 6252 4950 0x150cdfe2
+6252 6253 4949 0x150cd7f5
+6253 6254 4948 0x150cd009
+6254 6255 4947 0x150cc827
+6255 6256 4946 0x150cc045
+6256 6257 4945 0x150cb86e
+6257 6258 4944 0x150cb097
+6258 6259 4943 0x150ca8c0
+6259 6260 4942 0x150ca0fe
+6260 6261 4941 0x150c9932
+6261 6262 4940 0x150c917b
+6262 6263 4939 0x150c89ba
+6263 6264 4938 0x150c8203
+6264 6265 4937 0x150c7a4c
+6265 6266 4936 0x150c72a0
+6266 6267 4935 0x150c6af4
+6267 6268 4934 0x150c6352
+6268 6269 4933 0x150c5bb1
+6269 6270 4932 0x150c5410
+6270 6271 4931 0x150c4c79
+6271 6272 4930 0x150c44e2
+6272 6273 4929 0x150c3d56
+6273 6274 4928 0x150c35ca
+6274 6275 4927 0x150c2e49
+6275 6276 4926 0x150c26c8
+6276 6277 4925 0x150c1f52
+6277 6278 4924 0x150c17db
+6278 6279 4923 0x150c1065
+6279 6280 4922 0x150c08f9
+6280 6281 4921 0x150c018d
+6281 6282 4920 0x150bfa2c
+6282 6283 4919 0x150bf2cb
+6283 6284 4918 0x150beb6a
+6284 6285 4917 0x150be414
+6285 6286 4916 0x150bdcbe
+6286 6287 4915 0x150bd572
+6287 6288 4914 0x150bce27
+6288 6289 4913 0x150bc6db
+6289 6290 4912 0x150bbf9a
+6290 6291 4911 0x150bb859
+6291 6292 4910 0x150bb12e
+6292 6293 4909 0x150ba9f8
+6293 6294 4908 0x150ba2cd
+6294 6295 4907 0x150b9b97
+6295 6296 4906 0x150b9476
+6296 6297 4905 0x150b8d4b
+6297 6298 4904 0x150b862a
+6298 6299 4903 0x150b7f14
+6299 6300 4902 0x150b77fe
+6300 6301 4901 0x150b70e9
+6301 6302 4900 0x150b69dd
+6302 6303 4899 0x150b62d2
+6303 6304 4898 0x150b5bc7
+6304 6305 4897 0x150b54c7
+6305 6306 4896 0x150b4dc6
+6306 6307 4895 0x150b46d1
+6307 6308 4894 0x150b3fe6
+6308 6309 4893 0x150b38f0
+6309 6310 4892 0x150b3205
+6310 6311 4891 0x150b2b1a
+6311 6312 4890 0x150b243a
+6312 6313 4889 0x150b1d5a
+6313 6314 4888 0x150b167a
+6314 6315 4887 0x150b0f9a
+6315 6316 4886 0x150b08cf
+6316 6317 4885 0x150b01f9
+6317 6318 4884 0x150afb2f
+6318 6319 4883 0x150af464
+6319 6320 4882 0x150aed99
+6320 6321 4881 0x150ae6d9
+6321 6322 4880 0x150ae024
+6322 6323 4879 0x150ad96f
+6323 6324 4878 0x150ad2b9
+6324 6325 4877 0x150acc04
+6325 6326 4876 0x150ac55a
+6326 6327 4875 0x150abeaf
+6327 6328 4874 0x150ab80f
+6328 6329 4873 0x150ab17a
+6329 6330 4872 0x150aaae5
+6330 6331 4871 0x150aa455
+6331 6332 4870 0x150a9dcb
+6332 6333 4869 0x150a9741
+6333 6334 4868 0x150a90bc
+6334 6335 4867 0x150a8a3c
+6335 6336 4866 0x150a83c2
+6336 6337 4865 0x150a7d48
+6337 6338 4864 0x150a76d8
+6338 6339 4863 0x150a7069
+6339 6340 4862 0x150a69f9
+6340 6341 4861 0x150a6394
+6341 6342 4860 0x150a5d2a
+6342 6343 4859 0x150a56cb
+6343 6344 4858 0x150a506b
+6344 6345 4857 0x150a4a11
+6345 6346 4856 0x150a43bd
+6346 6347 4855 0x150a3d68
+6347 6348 4854 0x150a371e
+6348 6349 4853 0x150a30cf
+6349 6350 4852 0x150a2a8a
+6350 6351 4851 0x150a2446
+6351 6352 4850 0x150a1e06
+6352 6353 4849 0x150a17cd
+6353 6354 4848 0x150a1198
+6354 6355 4847 0x150a0b64
+6355 6356 4846 0x150a0535
+6356 6357 4845 0x1509ff0b
+6357 6358 4844 0x1509f8e1
+6358 6359 4843 0x1509f2b7
+6359 6360 4842 0x1509ec98
+6360 6361 4841 0x1509e679
+6361 6362 4840 0x1509e060
+6362 6363 4839 0x1509da4b
+6363 6364 4838 0x1509d437
+6364 6365 4837 0x1509ce28
+6365 6366 4836 0x1509c819
+6366 6367 4835 0x1509c215
+6367 6368 4834 0x1509bc11
+6368 6369 4833 0x1509b618
+6369 6370 4832 0x1509b019
+6370 6371 4831 0x1509aa1f
+6371 6372 4830 0x1509a42b
+6372 6373 4829 0x15099e37
+6373 6374 4828 0x15099849
+6374 6375 4827 0x1509925f
+6375 6376 4826 0x15098c76
+6376 6377 4825 0x15098692
+6377 6378 4824 0x150980b3
+6378 6379 4823 0x15097ad5
+6379 6380 4822 0x150974fb
+6380 6381 4821 0x15096f28
+6381 6382 4820 0x15096954
+6382 6383 4819 0x15096385
+6383 6384 4818 0x15095dc1
+6384 6385 4817 0x150957f8
+6385 6386 4816 0x15095234
+6386 6387 4815 0x15094c76
+6387 6388 4814 0x150946b8
+6388 6389 4813 0x150940ff
+6389 6390 4812 0x15093b4b
+6390 6391 4811 0x15093597
+6391 6392 4810 0x15092fe9
+6392 6393 4809 0x15092a40
+6393 6394 4808 0x15092497
+6394 6395 4807 0x15091ef3
+6395 6396 4806 0x15091950
+6396 6397 4805 0x150913b2
+6397 6398 4804 0x15090e19
+6398 6399 4803 0x15090880
+6399 6400 4802 0x150902f7
+6400 6401 4801 0x1508fd64
+6401 6402 4800 0x1508f7db
+6402 6403 4799 0x1508f252
+6403 6404 4798 0x1508ecca
+6404 6405 4797 0x1508e746
+6405 6406 4796 0x1508e1c8
+6406 6407 4795 0x1508dc50
+6407 6408 4794 0x1508d6d7
+6408 6409 4793 0x1508d15e
+6409 6410 4792 0x1508cbf0
+6410 6411 4791 0x1508c682
+6411 6412 4790 0x1508c114
+6412 6413 4789 0x1508bbb1
+6413 6414 4788 0x1508b64e
+6414 6415 4787 0x1508b0f0
+6415 6416 4786 0x1508ab92
+6416 6417 4785 0x1508a63a
+6417 6418 4784 0x1508a0e2
+6418 6419 4783 0x15089b8f
+6419 6420 4782 0x15089641
+6420 6421 4781 0x150890f3
+6421 6422 4780 0x15088bab
+6422 6423 4779 0x15088662
+6423 6424 4778 0x1508811f
+6424 6425 4777 0x15087be2
+6425 6426 4776 0x150876a4
+6426 6427 4775 0x15087167
+6427 6428 4774 0x15086c34
+6428 6429 4773 0x15086701
+6429 6430 4772 0x150861d9
+6430 6431 4771 0x15085cab
+6431 6432 4770 0x15085783
+6432 6433 4769 0x1508525b
+6433 6434 4768 0x15084d38
+6434 6435 4767 0x15084815
+6435 6436 4766 0x150842fd
+6436 6437 4765 0x15083de0
+6437 6438 4764 0x150838cd
+6438 6439 4763 0x150833ba
+6439 6440 4762 0x15082ea8
+6440 6441 4761 0x1508299a
+6441 6442 4760 0x15082493
+6442 6443 4759 0x15081f8b
+6443 6444 4758 0x15081a88
+6444 6445 4757 0x1508158b
+6445 6446 4756 0x15081093
+6446 6447 4755 0x15080b96
+6447 6448 4754 0x1508069e
+6448 6449 4753 0x150801ab
+6449 6450 4752 0x1507fcb9
+6450 6451 4751 0x1507f7cc
+6451 6452 4750 0x1507f2e4
+6452 6453 4749 0x1507edfc
+6453 6454 4748 0x1507e915
+6454 6455 4747 0x1507e438
+6455 6456 4746 0x1507df55
+6456 6457 4745 0x1507da7d
+6457 6458 4744 0x1507d5a0
+6458 6459 4743 0x1507d0ce
+6459 6460 4742 0x1507cbfc
+6460 6461 4741 0x1507c72f
+6461 6462 4740 0x1507c267
+6462 6463 4739 0x1507bd9a
+6463 6464 4738 0x1507b8d3
+6464 6465 4737 0x1507b411
+6465 6466 4736 0x1507af4f
+6466 6467 4735 0x1507aa92
+6467 6468 4734 0x1507a5da
+6468 6469 4733 0x1507a123
+6469 6470 4732 0x15079c6b
+6470 6471 4731 0x150797b9
+6471 6472 4730 0x1507930d
+6472 6473 4729 0x15078e60
+6473 6474 4728 0x150789b9
+6474 6475 4727 0x15078511
+6475 6476 4726 0x15078075
+6476 6477 4725 0x15077bd3
+6477 6478 4724 0x15077736
+6478 6479 4723 0x15077299
+6479 6480 4722 0x15076e02
+6480 6481 4721 0x15076970
+6481 6482 4720 0x150764de
+6482 6483 4719 0x1507604c
+6483 6484 4718 0x15075bc0
+6484 6485 4717 0x15075739
+6485 6486 4716 0x150752b2
+6486 6487 4715 0x15074e30
+6487 6488 4714 0x150749ae
+6488 6489 4713 0x1507452c
+6489 6490 4712 0x150740b5
+6490 6491 4711 0x15073c39
+6491 6492 4710 0x150737cc
+6492 6493 4709 0x15073355
+6493 6494 4708 0x15072ee9
+6494 6495 4707 0x15072a77
+6495 6496 4706 0x15072610
+6496 6497 4705 0x150721a9
+6497 6498 4704 0x15071d42
+6498 6499 4703 0x150718e1
+6499 6500 4702 0x1507147f
+6500 6501 4701 0x15071023
+6501 6502 4700 0x15070bc7
+6502 6503 4699 0x15070770
+6503 6504 4698 0x1507031f
+6504 6505 4697 0x1506fec8
+6505 6506 4696 0x1506fa7c
+6506 6507 4695 0x1506f635
+6507 6508 4694 0x1506f1e9
+6508 6509 4693 0x1506eda2
+6509 6510 4692 0x1506e95b
+6510 6511 4691 0x1506e51a
+6511 6512 4690 0x1506e0d8
+6512 6513 4689 0x1506dc9c
+6513 6514 4688 0x1506d866
+6514 6515 4687 0x1506d42a
+6515 6516 4686 0x1506cff8
+6516 6517 4685 0x1506cbc7
+6517 6518 4684 0x1506c796
+6518 6519 4683 0x1506c36a
+6519 6520 4682 0x1506bf3e
+6520 6521 4681 0x1506bb17
+6521 6522 4680 0x1506b6f6
+6522 6523 4679 0x1506b2d0
+6523 6524 4678 0x1506aeb4
+6524 6525 4677 0x1506aa93
+6525 6526 4676 0x1506a677
+6526 6527 4675 0x1506a260
+6527 6528 4674 0x15069e4a
+6528 6529 4673 0x15069a39
+6529 6530 4672 0x15069628
+6530 6531 4671 0x15069217
+6531 6532 4670 0x15068e0b
+6532 6533 4669 0x15068a05
+6533 6534 4668 0x150685f9
+6534 6535 4667 0x150681f8
+6535 6536 4666 0x15067df7
+6536 6537 4665 0x150679f6
+6537 6538 4664 0x150675fa
+6538 6539 4663 0x15067204
+6539 6540 4662 0x15066e08
+6540 6541 4661 0x15066a12
+6541 6542 4660 0x15066621
+6542 6543 4659 0x15066230
+6543 6544 4658 0x15065e3f
+6544 6545 4657 0x15065a54
+6545 6546 4656 0x1506566e
+6546 6547 4655 0x15065287
+6547 6548 4654 0x15064ea1
+6548 6549 4653 0x15064abb
+6549 6550 4652 0x150646e0
+6550 6551 4651 0x150642ff
+6551 6552 4650 0x15063f24
+6552 6553 4649 0x15063b53
+6553 6554 4648 0x15063777
+6554 6555 4647 0x150633a7
+6555 6556 4646 0x15062fd6
+6556 6557 4645 0x15062c05
+6557 6558 4644 0x1506283a
+6558 6559 4643 0x1506246f
+6559 6560 4642 0x150620a3
+6560 6561 4641 0x15061cdd
+6561 6562 4640 0x15061917
+6562 6563 4639 0x15061557
+6563 6564 4638 0x15061196
+6564 6565 4637 0x15060ddb
+6565 6566 4636 0x15060a20
+6566 6567 4635 0x1506066a
+6567 6568 4634 0x150602b4
+6568 6569 4633 0x1505ff04
+6569 6570 4632 0x1505fb53
+6570 6571 4631 0x1505f7a3
+6571 6572 4630 0x1505f3f2
+6572 6573 4629 0x1505f047
+6573 6574 4628 0x1505eca1
+6574 6575 4627 0x1505e8fb
+6575 6576 4626 0x1505e556
+6576 6577 4625 0x1505e1b5
+6577 6578 4624 0x1505de15
+6578 6579 4623 0x1505da74
+6579 6580 4622 0x1505d6d9
+6580 6581 4621 0x1505d33e
+6581 6582 4620 0x1505cfa9
+6582 6583 4619 0x1505cc13
+6583 6584 4618 0x1505c883
+6584 6585 4617 0x1505c4f2
+6585 6586 4616 0x1505c162
+6586 6587 4615 0x1505bdd7
+6587 6588 4614 0x1505ba4c
+6588 6589 4613 0x1505b6c7
+6589 6590 4612 0x1505b33c
+6590 6591 4611 0x1505afbc
+6591 6592 4610 0x1505ac36
+6592 6593 4609 0x1505a8b6
+6593 6594 4608 0x1505a53b
+6594 6595 4607 0x1505a1c0
+6595 6596 4606 0x15059e45
+6596 6597 4605 0x15059acb
+6597 6598 4604 0x15059755
+6598 6599 4603 0x150593e5
+6599 6600 4602 0x15059075
+6600 6601 4601 0x15058d05
+6601 6602 4600 0x1505899a
+6602 6603 4599 0x1505862f
+6603 6604 4598 0x150582c5
+6604 6605 4597 0x15057f5a
+6605 6606 4596 0x15057bf4
+6606 6607 4595 0x15057894
+6607 6608 4594 0x1505752f
+6608 6609 4593 0x150571cf
+6609 6610 4592 0x15056e74
+6610 6611 4591 0x15056b1a
+6611 6612 4590 0x150567bf
+6612 6613 4589 0x15056465
+6613 6614 4588 0x15056115
+6614 6615 4587 0x15055dc2
+6615 6616 4586 0x15055a75
+6616 6617 4585 0x1505572a
+6617 6618 4584 0x150553e0
+6618 6619 4583 0x1505509b
+6619 6620 4582 0x15054d55
+6620 6621 4581 0x15054a10
+6621 6622 4580 0x150546d0
+6622 6623 4579 0x15054391
+6623 6624 4578 0x15054054
+6624 6625 4577 0x15053d19
+6625 6626 4576 0x150539df
+6626 6627 4575 0x150536a7
+6627 6628 4574 0x15053372
+6628 6629 4573 0x1505303d
+6629 6630 4572 0x15052d10
+6630 6631 4571 0x150529e0
+6631 6632 4570 0x150526b0
+6632 6633 4569 0x15052386
+6633 6634 4568 0x1505205c
+6634 6635 4567 0x15051d34
+6635 6636 4566 0x15051a0c
+6636 6637 4565 0x150516e7
+6637 6638 4564 0x150513c5
+6638 6639 4563 0x150510a6
+6639 6640 4562 0x15050d86
+6640 6641 4561 0x15050a69
+6641 6642 4560 0x1505074f
+6642 6643 4559 0x15050435
+6643 6644 4558 0x1505011d
+6644 6645 4557 0x1504fe0b
+6645 6646 4556 0x1504faf6
+6646 6647 4555 0x1504f7e6
+6647 6648 4554 0x1504f4d4
+6648 6649 4553 0x1504f1c7
+6649 6650 4552 0x1504eebb
+6650 6651 4551 0x1504ebb1
+6651 6652 4550 0x1504e8a9
+6652 6653 4549 0x1504e5a2
+6653 6654 4548 0x1504e29d
+6654 6655 4547 0x1504df98
+6655 6656 4546 0x1504dc99
+6656 6657 4545 0x1504d999
+6657 6658 4544 0x1504d69a
+6658 6659 4543 0x1504d3a0
+6659 6660 4542 0x1504d0a8
+6660 6661 4541 0x1504cdb1
+6661 6662 4540 0x1504caba
+6662 6663 4539 0x1504c7c5
+6663 6664 4538 0x1504c4d3
+6664 6665 4537 0x1504c1e1
+6665 6666 4536 0x1504bef2
+6666 6667 4535 0x1504bc05
+6667 6668 4534 0x1504b919
+6668 6669 4533 0x1504b62f
+6669 6670 4532 0x1504b347
+6670 6671 4531 0x1504b060
+6671 6672 4530 0x1504ad7c
+6672 6673 4529 0x1504aa9a
+6673 6674 4528 0x1504a7b8
+6674 6675 4527 0x1504a4d9
+6675 6676 4526 0x1504a1ff
+6676 6677 4525 0x15049f22
+6677 6678 4524 0x15049c48
+6678 6679 4523 0x1504996f
+6679 6680 4522 0x1504969a
+6680 6681 4521 0x150493c3
+6681 6682 4520 0x150490f1
+6682 6683 4519 0x15048e1f
+6683 6684 4518 0x15048b50
+6684 6685 4517 0x15048881
+6685 6686 4516 0x150485b5
+6686 6687 4515 0x150482eb
+6687 6688 4514 0x15048021
+6688 6689 4513 0x15047d5a
+6689 6690 4512 0x15047a96
+6690 6691 4511 0x150477d7
+6691 6692 4510 0x15047512
+6692 6693 4509 0x15047253
+6693 6694 4508 0x15046f94
+6694 6695 4507 0x15046cd8
+6695 6696 4506 0x15046a1c
+6696 6697 4505 0x15046762
+6697 6698 4504 0x150464ab
+6698 6699 4503 0x150461f4
+6699 6700 4502 0x15045f40
+6700 6701 4501 0x15045c8e
+6701 6702 4500 0x150459dd
+6702 6703 4499 0x1504572e
+6703 6704 4498 0x1504547f
+6704 6705 4497 0x150451d3
+6705 6706 4496 0x15044f2c
+6706 6707 4495 0x15044c82
+6707 6708 4494 0x150449db
+6708 6709 4493 0x15044735
+6709 6710 4492 0x15044490
+6710 6711 4491 0x150441ef
+6711 6712 4490 0x15043f4d
+6712 6713 4489 0x15043caf
+6713 6714 4488 0x15043a10
+6714 6715 4487 0x15043776
+6715 6716 4486 0x150434da
+6716 6717 4485 0x15043244
+6717 6718 4484 0x15042faa
+6718 6719 4483 0x15042d16
+6719 6720 4482 0x15042a82
+6720 6721 4481 0x150427f1
+6721 6722 4480 0x15042562
+6722 6723 4479 0x150422d3
+6723 6724 4478 0x15042044
+6724 6725 4477 0x15041db8
+6725 6726 4476 0x15041b2f
+6726 6727 4475 0x150418a6
+6727 6728 4474 0x1504161f
+6728 6729 4473 0x15041398
+6729 6730 4472 0x15041114
+6730 6731 4471 0x15040e93
+6731 6732 4470 0x15040c12
+6732 6733 4469 0x15040993
+6733 6734 4468 0x15040715
+6734 6735 4467 0x15040499
+6735 6736 4466 0x1504021d
+6736 6737 4465 0x1503ffa6
+6737 6738 4464 0x1503fd30
+6738 6739 4463 0x1503fab9
+6739 6740 4462 0x1503f845
+6740 6741 4461 0x1503f5d1
+6741 6742 4460 0x1503f360
+6742 6743 4459 0x1503f0ef
+6743 6744 4458 0x1503ee80
+6744 6745 4457 0x1503ec15
+6745 6746 4456 0x1503e9a9
+6746 6747 4455 0x1503e740
+6747 6748 4454 0x1503e4d7
+6748 6749 4453 0x1503e270
+6749 6750 4452 0x1503e00a
+6750 6751 4451 0x1503dda6
+6751 6752 4450 0x1503db45
+6752 6753 4449 0x1503d8e6
+6753 6754 4448 0x1503d685
+6754 6755 4447 0x1503d427
+6755 6756 4446 0x1503d1cb
+6756 6757 4445 0x1503cf6f
+6757 6758 4444 0x1503cd16
+6758 6759 4443 0x1503cabd
+6759 6760 4442 0x1503c867
+6760 6761 4441 0x1503c613
+6761 6762 4440 0x1503c3c0
+6762 6763 4439 0x1503c16c
+6763 6764 4438 0x1503bf1b
+6764 6765 4437 0x1503bccd
+6765 6766 4436 0x1503ba7e
+6766 6767 4435 0x1503b833
+6767 6768 4434 0x1503b5ea
+6768 6769 4433 0x1503b39e
+6769 6770 4432 0x1503b158
+6770 6771 4431 0x1503af0f
+6771 6772 4430 0x1503accb
+6772 6773 4429 0x1503aa88
+6773 6774 4428 0x1503a844
+6774 6775 4427 0x1503a603
+6775 6776 4426 0x1503a3c2
+6776 6777 4425 0x1503a184
+6777 6778 4424 0x15039f49
+6778 6779 4423 0x15039d0d
+6779 6780 4422 0x15039ad2
+6780 6781 4421 0x15039899
+6781 6782 4420 0x15039663
+6782 6783 4419 0x1503942f
+6783 6784 4418 0x150391f9
+6784 6785 4417 0x15038fc5
+6785 6786 4416 0x15038d95
+6786 6787 4415 0x15038b64
+6787 6788 4414 0x15038933
+6788 6789 4413 0x15038708
+6789 6790 4412 0x150384da
+6790 6791 4411 0x150382ae
+6791 6792 4410 0x15038085
+6792 6793 4409 0x15037e5d
+6793 6794 4408 0x15037c37
+6794 6795 4407 0x15037a10
+6795 6796 4406 0x150377ea
+6796 6797 4405 0x150375ca
+6797 6798 4404 0x150373a6
+6798 6799 4403 0x15037188
+6799 6800 4402 0x15036f6a
+6800 6801 4401 0x15036d4c
+6801 6802 4400 0x15036b2e
+6802 6803 4399 0x15036913
+6803 6804 4398 0x150366fa
+6804 6805 4397 0x150364e2
+6805 6806 4396 0x150362cc
+6806 6807 4395 0x150360b6
+6807 6808 4394 0x15035ea0
+6808 6809 4393 0x15035c8d
+6809 6810 4392 0x15035a79
+6810 6811 4391 0x15035869
+6811 6812 4390 0x1503565b
+6812 6813 4389 0x1503544d
+6813 6814 4388 0x15035242
+6814 6815 4387 0x15035036
+6815 6816 4386 0x15034e2b
+6816 6817 4385 0x15034c23
+6817 6818 4384 0x15034a1a
+6818 6819 4383 0x15034814
+6819 6820 4382 0x1503460e
+6820 6821 4381 0x1503440b
+6821 6822 4380 0x15034208
+6822 6823 4379 0x15034005
+6823 6824 4378 0x15033e04
+6824 6825 4377 0x15033c06
+6825 6826 4376 0x15033a09
+6826 6827 4375 0x1503380b
+6827 6828 4374 0x15033610
+6828 6829 4373 0x15033417
+6829 6830 4372 0x1503321f
+6830 6831 4371 0x15033026
+6831 6832 4370 0x15032e31
+6832 6833 4369 0x15032c3b
+6833 6834 4368 0x15032a48
+6834 6835 4367 0x15032855
+6835 6836 4366 0x15032662
+6836 6837 4365 0x15032471
+6837 6838 4364 0x15032281
+6838 6839 4363 0x15032093
+6839 6840 4362 0x15031ea5
+6840 6841 4361 0x15031cba
+6841 6842 4360 0x15031acf
+6842 6843 4359 0x150318e7
+6843 6844 4358 0x150316ff
+6844 6845 4357 0x15031519
+6845 6846 4356 0x15031333
+6846 6847 4355 0x1503114e
+6847 6848 4354 0x15030f6b
+6848 6849 4353 0x15030d88
+6849 6850 4352 0x15030ba7
+6850 6851 4351 0x150309c7
+6851 6852 4350 0x150307e7
+6852 6853 4349 0x15030609
+6853 6854 4348 0x1503042c
+6854 6855 4347 0x15030251
+6855 6856 4346 0x15030076
+6856 6857 4345 0x1502fe9d
+6857 6858 4344 0x1502fcc5
+6858 6859 4343 0x1502faed
+6859 6860 4342 0x1502f91a
+6860 6861 4341 0x1502f745
+6861 6862 4340 0x1502f56f
+6862 6863 4339 0x1502f39f
+6863 6864 4338 0x1502f1cc
+6864 6865 4337 0x1502effc
+6865 6866 4336 0x1502ee2b
+6866 6867 4335 0x1502ec5e
+6867 6868 4334 0x1502ea90
+6868 6869 4333 0x1502e8c3
+6869 6870 4332 0x1502e6f8
+6870 6871 4331 0x1502e52d
+6871 6872 4330 0x1502e365
+6872 6873 4329 0x1502e19d
+6873 6874 4328 0x1502dfd7
+6874 6875 4327 0x1502de12
+6875 6876 4326 0x1502dc4f
+6876 6877 4325 0x1502da8a
+6877 6878 4324 0x1502d8c7
+6878 6879 4323 0x1502d707
+6879 6880 4322 0x1502d547
+6880 6881 4321 0x1502d387
+6881 6882 4320 0x1502d1c7
+6882 6883 4319 0x1502d009
+6883 6884 4318 0x1502ce4e
+6884 6885 4317 0x1502cc91
+6885 6886 4316 0x1502cad9
+6886 6887 4315 0x1502c91e
+6887 6888 4314 0x1502c766
+6888 6889 4313 0x1502c5ae
+6889 6890 4312 0x1502c3f9
+6890 6891 4311 0x1502c246
+6891 6892 4310 0x1502c091
+6892 6893 4309 0x1502bede
+6893 6894 4308 0x1502bd2b
+6894 6895 4307 0x1502bb79
+6895 6896 4306 0x1502b9c9
+6896 6897 4305 0x1502b81b
+6897 6898 4304 0x1502b66b
+6898 6899 4303 0x1502b4be
+6899 6900 4302 0x1502b313
+6900 6901 4301 0x1502b166
+6901 6902 4300 0x1502afbb
+6902 6903 4299 0x1502ae15
+6903 6904 4298 0x1502ac6e
+6904 6905 4297 0x1502aac9
+6905 6906 4296 0x1502a928
+6906 6907 4295 0x1502a784
+6907 6908 4294 0x1502a5e3
+6908 6909 4293 0x1502a441
+6909 6910 4292 0x1502a2a1
+6910 6911 4291 0x1502a102
+6911 6912 4290 0x15029f63
+6912 6913 4289 0x15029dc6
+6913 6914 4288 0x15029c2a
+6914 6915 4287 0x15029a8e
+6915 6916 4286 0x150298f5
+6916 6917 4285 0x1502975a
+6917 6918 4284 0x150295c2
+6918 6919 4283 0x1502942b
+6919 6920 4282 0x15029294
+6920 6921 4281 0x15029100
+6921 6922 4280 0x15028f6c
+6922 6923 4279 0x15028dd9
+6923 6924 4278 0x15028c46
+6924 6925 4277 0x15028ab4
+6925 6926 4276 0x15028922
+6926 6927 4275 0x15028793
+6927 6928 4274 0x15028604
+6928 6929 4273 0x15028476
+6929 6930 4272 0x150282e9
+6930 6931 4271 0x1502815d
+6931 6932 4270 0x15027fd1
+6932 6933 4269 0x15027e47
+6933 6934 4268 0x15027cbe
+6934 6935 4267 0x15027b36
+6935 6936 4266 0x150279ae
+6936 6937 4265 0x15027829
+6937 6938 4264 0x150276a3
+6938 6939 4263 0x1502751f
+6939 6940 4262 0x1502739a
+6940 6941 4261 0x15027217
+6941 6942 4260 0x15027095
+6942 6943 4259 0x15026f14
+6943 6944 4258 0x15026d94
+6944 6945 4257 0x15026c15
+6945 6946 4256 0x15026a96
+6946 6947 4255 0x15026919
+6947 6948 4254 0x1502679c
+6948 6949 4253 0x15026621
+6949 6950 4252 0x150264a6
+6950 6951 4251 0x1502632c
+6951 6952 4250 0x150261b4
+6952 6953 4249 0x1502603d
+6953 6954 4248 0x15025ec5
+6954 6955 4247 0x15025d4f
+6955 6956 4246 0x15025bda
+6956 6957 4245 0x15025a65
+6957 6958 4244 0x150258f1
+6958 6959 4243 0x1502577f
+6959 6960 4242 0x1502560c
+6960 6961 4241 0x1502549b
+6961 6962 4240 0x1502532c
+6962 6963 4239 0x150251bc
+6963 6964 4238 0x1502504e
+6964 6965 4237 0x15024ee1
+6965 6966 4236 0x15024d74
+6966 6967 4235 0x15024c0a
+6967 6968 4234 0x15024a9e
+6968 6969 4233 0x15024935
+6969 6970 4232 0x150247cb
+6970 6971 4231 0x15024664
+6971 6972 4230 0x150244fc
+6972 6973 4229 0x15024396
+6973 6974 4228 0x15024230
+6974 6975 4227 0x150240cb
+6975 6976 4226 0x15023f67
+6976 6977 4225 0x15023e04
+6977 6978 4224 0x15023ca2
+6978 6979 4223 0x15023b3f
+6979 6980 4222 0x150239e0
+6980 6981 4221 0x15023880
+6981 6982 4220 0x15023721
+6982 6983 4219 0x150235c4
+6983 6984 4218 0x15023467
+6984 6985 4217 0x1502330a
+6985 6986 4216 0x150231af
+6986 6987 4215 0x15023055
+6987 6988 4214 0x15022efb
+6988 6989 4213 0x15022da2
+6989 6990 4212 0x15022c49
+6990 6991 4211 0x15022af2
+6991 6992 4210 0x1502299c
+6992 6993 4209 0x15022845
+6993 6994 4208 0x150226f1
+6994 6995 4207 0x1502259d
+6995 6996 4206 0x1502244a
+6996 6997 4205 0x150222f8
+6997 6998 4204 0x150221a7
+6998 6999 4203 0x15022056
+6999 7000 4202 0x15021f05
+7000 7001 4201 0x15021db7
+7001 7002 4200 0x15021c68
+7002 7003 4199 0x15021b1b
+7003 7004 4198 0x150219cf
+7004 7005 4197 0x15021882
+7005 7006 4196 0x15021736
+7006 7007 4195 0x150215ec
+7007 7008 4194 0x150214a4
+7008 7009 4193 0x1502135b
+7009 7010 4192 0x15021212
+7010 7011 4191 0x150210cb
+7011 7012 4190 0x15020f85
+7012 7013 4189 0x15020e40
+7013 7014 4188 0x15020cfc
+7014 7015 4187 0x15020bb8
+7015 7016 4186 0x15020a75
+7016 7017 4185 0x15020933
+7017 7018 4184 0x150207f1
+7018 7019 4183 0x150206b0
+7019 7020 4182 0x15020570
+7020 7021 4181 0x15020430
+7021 7022 4180 0x150202f2
+7022 7023 4179 0x150201b5
+7023 7024 4178 0x15020077
+7024 7025 4177 0x1501ff3b
+7025 7026 4176 0x1501fe00
+7026 7027 4175 0x1501fcc5
+7027 7028 4174 0x1501fb8b
+7028 7029 4173 0x1501fa54
+7029 7030 4172 0x1501f91b
+7030 7031 4171 0x1501f7e2
+7031 7032 4170 0x1501f6ac
+7032 7033 4169 0x1501f576
+7033 7034 4168 0x1501f441
+7034 7035 4167 0x1501f30c
+7035 7036 4166 0x1501f1d7
+7036 7037 4165 0x1501f0a6
+7037 7038 4164 0x1501ef72
+7038 7039 4163 0x1501ee40
+7039 7040 4162 0x1501ed10
+7040 7041 4161 0x1501ebdf
+7041 7042 4160 0x1501eab0
+7042 7043 4159 0x1501e982
+7043 7044 4158 0x1501e856
+7044 7045 4157 0x1501e728
+7045 7046 4156 0x1501e5fb
+7046 7047 4155 0x1501e4d0
+7047 7048 4154 0x1501e3a5
+7048 7049 4153 0x1501e27b
+7049 7050 4152 0x1501e151
+7050 7051 4151 0x1501e029
+7051 7052 4150 0x1501df00
+7052 7053 4149 0x1501ddd9
+7053 7054 4148 0x1501dcb2
+7054 7055 4147 0x1501db8c
+7055 7056 4146 0x1501da66
+7056 7057 4145 0x1501d942
+7057 7058 4144 0x1501d81f
+7058 7059 4143 0x1501d6fd
+7059 7060 4142 0x1501d5da
+7060 7061 4141 0x1501d4b8
+7061 7062 4140 0x1501d398
+7062 7063 4139 0x1501d277
+7063 7064 4138 0x1501d158
+7064 7065 4137 0x1501d039
+7065 7066 4136 0x1501cf1b
+7066 7067 4135 0x1501cdfd
+7067 7068 4134 0x1501cce1
+7068 7069 4133 0x1501cbc5
+7069 7070 4132 0x1501caaa
+7070 7071 4131 0x1501c98e
+7071 7072 4130 0x1501c875
+7072 7073 4129 0x1501c75b
+7073 7074 4128 0x1501c643
+7074 7075 4127 0x1501c52c
+7075 7076 4126 0x1501c414
+7076 7077 4125 0x1501c2fd
+7077 7078 4124 0x1501c1e8
+7078 7079 4123 0x1501c0d3
+7079 7080 4122 0x1501bfbe
+7080 7081 4121 0x1501beab
+7081 7082 4120 0x1501bd98
+7082 7083 4119 0x1501bc85
+7083 7084 4118 0x1501bb73
+7084 7085 4117 0x1501ba61
+7085 7086 4116 0x1501b951
+7086 7087 4115 0x1501b840
+7087 7088 4114 0x1501b731
+7088 7089 4113 0x1501b624
+7089 7090 4112 0x1501b516
+7090 7091 4111 0x1501b40a
+7091 7092 4110 0x1501b2fc
+7092 7093 4109 0x1501b1f1
+7093 7094 4108 0x1501b0e5
+7094 7095 4107 0x1501afdb
+7095 7096 4106 0x1501aed0
+7096 7097 4105 0x1501adc8
+7097 7098 4104 0x1501acbe
+7098 7099 4103 0x1501abb6
+7099 7100 4102 0x1501aaaf
+7100 7101 4101 0x1501a9a8
+7101 7102 4100 0x1501a8a2
+7102 7103 4099 0x1501a79d
+7103 7104 4098 0x1501a697
+7104 7105 4097 0x1501a594
+7105 7106 4096 0x1501a491
+7106 7107 4095 0x1501a38e
+7107 7108 4094 0x1501a28b
+7108 7109 4093 0x1501a18a
+7109 7110 4092 0x1501a088
+7110 7111 4091 0x15019f88
+7111 7112 4090 0x15019e88
+7112 7113 4089 0x15019d89
+7113 7114 4088 0x15019c8a
+7114 7115 4087 0x15019b8c
+7115 7116 4086 0x15019a8f
+7116 7117 4085 0x15019991
+7117 7118 4084 0x15019895
+7118 7119 4083 0x1501979a
+7119 7120 4082 0x1501969f
+7120 7121 4081 0x150195a6
+7121 7122 4080 0x150194ac
+7122 7123 4079 0x150193b3
+7123 7124 4078 0x150192ba
+7124 7125 4077 0x150191c2
+7125 7126 4076 0x150190ca
+7126 7127 4075 0x15018fd3
+7127 7128 4074 0x15018ede
+7128 7129 4073 0x15018de8
+7129 7130 4072 0x15018cf3
+7130 7131 4071 0x15018bff
+7131 7132 4070 0x15018b0a
+7132 7133 4069 0x15018a18
+7133 7134 4068 0x15018925
+7134 7135 4067 0x15018832
+7135 7136 4066 0x15018742
+7136 7137 4065 0x15018650
+7137 7138 4064 0x15018560
+7138 7139 4063 0x15018470
+7139 7140 4062 0x15018381
+7140 7141 4061 0x15018292
+7141 7142 4060 0x150181a5
+7142 7143 4059 0x150180b6
+7143 7144 4058 0x15017fca
+7144 7145 4057 0x15017ede
+7145 7146 4056 0x15017df2
+7146 7147 4055 0x15017d06
+7147 7148 4054 0x15017c1b
+7148 7149 4053 0x15017b31
+7149 7150 4052 0x15017a47
+7150 7151 4051 0x1501795e
+7151 7152 4050 0x15017876
+7152 7153 4049 0x1501778e
+7153 7154 4048 0x150176a7
+7154 7155 4047 0x150175bf
+7155 7156 4046 0x150174da
+7156 7157 4045 0x150173f3
+7157 7158 4044 0x1501730e
+7158 7159 4043 0x1501722a
+7159 7160 4042 0x15017146
+7160 7161 4041 0x15017062
+7161 7162 4040 0x15016f7f
+7162 7163 4039 0x15016e9c
+7163 7164 4038 0x15016db9
+7164 7165 4037 0x15016cd8
+7165 7166 4036 0x15016bf7
+7166 7167 4035 0x15016b18
+7167 7168 4034 0x15016a38
+7168 7169 4033 0x15016958
+7169 7170 4032 0x15016879
+7170 7171 4031 0x1501679a
+7171 7172 4030 0x150166bd
+7172 7173 4029 0x150165e0
+7173 7174 4028 0x15016502
+7174 7175 4027 0x15016426
+7175 7176 4026 0x1501634a
+7176 7177 4025 0x1501626e
+7177 7178 4024 0x15016193
+7178 7179 4023 0x150160b9
+7179 7180 4022 0x15015fdf
+7180 7181 4021 0x15015f06
+7181 7182 4020 0x15015e2e
+7182 7183 4019 0x15015d55
+7183 7184 4018 0x15015c7d
+7184 7185 4017 0x15015ba6
+7185 7186 4016 0x15015acf
+7186 7187 4015 0x150159f9
+7187 7188 4014 0x15015922
+7188 7189 4013 0x1501584d
+7189 7190 4012 0x15015778
+7190 7191 4011 0x150156a5
+7191 7192 4010 0x150155d2
+7192 7193 4009 0x15015500
+7193 7194 4008 0x1501542e
+7194 7195 4007 0x1501535d
+7195 7196 4006 0x1501528c
+7196 7197 4005 0x150151bc
+7197 7198 4004 0x150150ed
+7198 7199 4003 0x1501501d
+7199 7200 4002 0x15014f4f
+7200 7201 4001 0x15014e80
+7201 7202 4000 0x15014db2
+7202 7203 3999 0x15014ce5
+7203 7204 3998 0x15014c18
+7204 7205 3997 0x15014b4c
+7205 7206 3996 0x15014a80
+7206 7207 3995 0x150149b4
+7207 7208 3994 0x150148e9
+7208 7209 3993 0x1501481f
+7209 7210 3992 0x15014754
+7210 7211 3991 0x1501468b
+7211 7212 3990 0x150145c2
+7212 7213 3989 0x150144fa
+7213 7214 3988 0x15014432
+7214 7215 3987 0x1501436a
+7215 7216 3986 0x150142a3
+7216 7217 3985 0x150141dc
+7217 7218 3984 0x15014115
+7218 7219 3983 0x15014050
+7219 7220 3982 0x15013f8a
+7220 7221 3981 0x15013ec5
+7221 7222 3980 0x15013e01
+7222 7223 3979 0x15013d3d
+7223 7224 3978 0x15013c79
+7224 7225 3977 0x15013bb6
+7225 7226 3976 0x15013af3
+7226 7227 3975 0x15013a31
+7227 7228 3974 0x15013970
+7228 7229 3973 0x150138af
+7229 7230 3972 0x150137ee
+7230 7231 3971 0x1501372e
+7231 7232 3970 0x1501366e
+7232 7233 3969 0x150135af
+7233 7234 3968 0x150134f0
+7234 7235 3967 0x15013431
+7235 7236 3966 0x15013373
+7236 7237 3965 0x150132b5
+7237 7238 3964 0x150131f8
+7238 7239 3963 0x1501313c
+7239 7240 3962 0x1501307f
+7240 7241 3961 0x15012fc3
+7241 7242 3960 0x15012f08
+7242 7243 3959 0x15012e4e
+7243 7244 3958 0x15012d93
+7244 7245 3957 0x15012cda
+7245 7246 3956 0x15012c20
+7246 7247 3955 0x15012b67
+7247 7248 3954 0x15012aae
+7248 7249 3953 0x150129f6
+7249 7250 3952 0x1501293e
+7250 7251 3951 0x15012886
+7251 7252 3950 0x150127d0
+7252 7253 3949 0x15012719
+7253 7254 3948 0x15012663
+7254 7255 3947 0x150125ad
+7255 7256 3946 0x150124f8
+7256 7257 3945 0x15012443
+7257 7258 3944 0x1501238f
+7258 7259 3943 0x150122dc
+7259 7260 3942 0x15012228
+7260 7261 3941 0x15012175
+7261 7262 3940 0x150120c2
+7262 7263 3939 0x15012010
+7263 7264 3938 0x15011f5e
+7264 7265 3937 0x15011ead
+7265 7266 3936 0x15011dfc
+7266 7267 3935 0x15011d4c
+7267 7268 3934 0x15011c9b
+7268 7269 3933 0x15011beb
+7269 7270 3932 0x15011b3c
+7270 7271 3931 0x15011a8e
+7271 7272 3930 0x150119e0
+7272 7273 3929 0x15011931
+7273 7274 3928 0x15011885
+7274 7275 3927 0x150117d7
+7275 7276 3926 0x1501172b
+7276 7277 3925 0x1501167e
+7277 7278 3924 0x150115d2
+7278 7279 3923 0x15011527
+7279 7280 3922 0x1501147c
+7280 7281 3921 0x150113d1
+7281 7282 3920 0x15011327
+7282 7283 3919 0x1501127d
+7283 7284 3918 0x150111d3
+7284 7285 3917 0x1501112a
+7285 7286 3916 0x15011082
+7286 7287 3915 0x15010fda
+7287 7288 3914 0x15010f32
+7288 7289 3913 0x15010e8b
+7289 7290 3912 0x15010de4
+7290 7291 3911 0x15010d3e
+7291 7292 3910 0x15010c97
+7292 7293 3909 0x15010bf2
+7293 7294 3908 0x15010b4d
+7294 7295 3907 0x15010aa7
+7295 7296 3906 0x15010a02
+7296 7297 3905 0x1501095f
+7297 7298 3904 0x150108ba
+7298 7299 3903 0x15010817
+7299 7300 3902 0x15010774
+7300 7301 3901 0x150106d2
+7301 7302 3900 0x15010630
+7302 7303 3899 0x1501058d
+7303 7304 3898 0x150104ec
+7304 7305 3897 0x1501044c
+7305 7306 3896 0x150103ab
+7306 7307 3895 0x1501030b
+7307 7308 3894 0x1501026b
+7308 7309 3893 0x150101cb
+7309 7310 3892 0x1501012d
+7310 7311 3891 0x1501008e
+7311 7312 3890 0x1500fff0
+7312 7313 3889 0x1500ff52
+7313 7314 3888 0x1500feb4
+7314 7315 3887 0x1500fe17
+7315 7316 3886 0x1500fd7a
+7316 7317 3885 0x1500fcde
+7317 7318 3884 0x1500fc42
+7318 7319 3883 0x1500fba6
+7319 7320 3882 0x1500fb0b
+7320 7321 3881 0x1500fa71
+7321 7322 3880 0x1500f9d6
+7322 7323 3879 0x1500f93c
+7323 7324 3878 0x1500f8a2
+7324 7325 3877 0x1500f809
+7325 7326 3876 0x1500f770
+7326 7327 3875 0x1500f6d8
+7327 7328 3874 0x1500f63f
+7328 7329 3873 0x1500f5a7
+7329 7330 3872 0x1500f50f
+7330 7331 3871 0x1500f478
+7331 7332 3870 0x1500f3e2
+7332 7333 3869 0x1500f34b
+7333 7334 3868 0x1500f2b5
+7334 7335 3867 0x1500f220
+7335 7336 3866 0x1500f18b
+7336 7337 3865 0x1500f0f6
+7337 7338 3864 0x1500f061
+7338 7339 3863 0x1500efcd
+7339 7340 3862 0x1500ef39
+7340 7341 3861 0x1500eea6
+7341 7342 3860 0x1500ee12
+7342 7343 3859 0x1500ed7f
+7343 7344 3858 0x1500eced
+7344 7345 3857 0x1500ec5b
+7345 7346 3856 0x1500ebc9
+7346 7347 3855 0x1500eb38
+7347 7348 3854 0x1500eaa7
+7348 7349 3853 0x1500ea16
+7349 7350 3852 0x1500e986
+7350 7351 3851 0x1500e8f7
+7351 7352 3850 0x1500e867
+7352 7353 3849 0x1500e7d8
+7353 7354 3848 0x1500e749
+7354 7355 3847 0x1500e6ba
+7355 7356 3846 0x1500e62c
+7356 7357 3845 0x1500e59e
+7357 7358 3844 0x1500e511
+7358 7359 3843 0x1500e483
+7359 7360 3842 0x1500e3f7
+7360 7361 3841 0x1500e36a
+7361 7362 3840 0x1500e2de
+7362 7363 3839 0x1500e252
+7363 7364 3838 0x1500e1c7
+7364 7365 3837 0x1500e13b
+7365 7366 3836 0x1500e0b1
+7366 7367 3835 0x1500e027
+7367 7368 3834 0x1500df9d
+7368 7369 3833 0x1500df13
+7369 7370 3832 0x1500de8a
+7370 7371 3831 0x1500de00
+7371 7372 3830 0x1500dd77
+7372 7373 3829 0x1500dcef
+7373 7374 3828 0x1500dc67
+7374 7375 3827 0x1500dbe0
+7375 7376 3826 0x1500db58
+7376 7377 3825 0x1500dad1
+7377 7378 3824 0x1500da4a
+7378 7379 3823 0x1500d9c4
+7379 7380 3822 0x1500d93d
+7380 7381 3821 0x1500d8b8
+7381 7382 3820 0x1500d832
+7382 7383 3819 0x1500d7ae
+7383 7384 3818 0x1500d729
+7384 7385 3817 0x1500d6a4
+7385 7386 3816 0x1500d620
+7386 7387 3815 0x1500d59c
+7387 7388 3814 0x1500d518
+7388 7389 3813 0x1500d495
+7389 7390 3812 0x1500d412
+7390 7391 3811 0x1500d390
+7391 7392 3810 0x1500d30e
+7392 7393 3809 0x1500d28c
+7393 7394 3808 0x1500d209
+7394 7395 3807 0x1500d189
+7395 7396 3806 0x1500d107
+7396 7397 3805 0x1500d087
+7397 7398 3804 0x1500d007
+7398 7399 3803 0x1500cf87
+7399 7400 3802 0x1500cf07
+7400 7401 3801 0x1500ce87
+7401 7402 3800 0x1500ce08
+7402 7403 3799 0x1500cd89
+7403 7404 3798 0x1500cd0b
+7404 7405 3797 0x1500cc8c
+7405 7406 3796 0x1500cc0f
+7406 7407 3795 0x1500cb90
+7407 7408 3794 0x1500cb13
+7408 7409 3793 0x1500ca96
+7409 7410 3792 0x1500ca19
+7410 7411 3791 0x1500c99d
+7411 7412 3790 0x1500c921
+7412 7413 3789 0x1500c8a6
+7413 7414 3788 0x1500c82a
+7414 7415 3787 0x1500c7af
+7415 7416 3786 0x1500c734
+7416 7417 3785 0x1500c6b9
+7417 7418 3784 0x1500c63f
+7418 7419 3783 0x1500c5c5
+7419 7420 3782 0x1500c54b
+7420 7421 3781 0x1500c4d1
+7421 7422 3780 0x1500c458
+7422 7423 3779 0x1500c3df
+7423 7424 3778 0x1500c366
+7424 7425 3777 0x1500c2ee
+7425 7426 3776 0x1500c276
+7426 7427 3775 0x1500c1fe
+7427 7428 3774 0x1500c187
+7428 7429 3773 0x1500c110
+7429 7430 3772 0x1500c099
+7430 7431 3771 0x1500c022
+7431 7432 3770 0x1500bfac
+7432 7433 3769 0x1500bf36
+7433 7434 3768 0x1500bec0
+7434 7435 3767 0x1500be4b
+7435 7436 3766 0x1500bdd5
+7436 7437 3765 0x1500bd60
+7437 7438 3764 0x1500bceb
+7438 7439 3763 0x1500bc77
+7439 7440 3762 0x1500bc03
+7440 7441 3761 0x1500bb8f
+7441 7442 3760 0x1500bb1c
+7442 7443 3759 0x1500baa8
+7443 7444 3758 0x1500ba36
+7444 7445 3757 0x1500b9c3
+7445 7446 3756 0x1500b950
+7446 7447 3755 0x1500b8de
+7447 7448 3754 0x1500b86c
+7448 7449 3753 0x1500b7fa
+7449 7450 3752 0x1500b789
+7450 7451 3751 0x1500b717
+7451 7452 3750 0x1500b6a7
+7452 7453 3749 0x1500b636
+7453 7454 3748 0x1500b5c5
+7454 7455 3747 0x1500b555
+7455 7456 3746 0x1500b4e5
+7456 7457 3745 0x1500b476
+7457 7458 3744 0x1500b407
+7458 7459 3743 0x1500b398
+7459 7460 3742 0x1500b329
+7460 7461 3741 0x1500b2bb
+7461 7462 3740 0x1500b24c
+7462 7463 3739 0x1500b1de
+7463 7464 3738 0x1500b170
+7464 7465 3737 0x1500b103
+7465 7466 3736 0x1500b095
+7466 7467 3735 0x1500b028
+7467 7468 3734 0x1500afbb
+7468 7469 3733 0x1500af4e
+7469 7470 3732 0x1500aee2
+7470 7471 3731 0x1500ae76
+7471 7472 3730 0x1500ae0a
+7472 7473 3729 0x1500ad9f
+7473 7474 3728 0x1500ad34
+7474 7475 3727 0x1500acc9
+7475 7476 3726 0x1500ac5e
+7476 7477 3725 0x1500abf3
+7477 7478 3724 0x1500ab8a
+7478 7479 3723 0x1500ab20
+7479 7480 3722 0x1500aab7
+7480 7481 3721 0x1500aa4e
+7481 7482 3720 0x1500a9e5
+7482 7483 3719 0x1500a97c
+7483 7484 3718 0x1500a914
+7484 7485 3717 0x1500a8ac
+7485 7486 3716 0x1500a844
+7486 7487 3715 0x1500a7dd
+7487 7488 3714 0x1500a775
+7488 7489 3713 0x1500a70f
+7489 7490 3712 0x1500a6a8
+7490 7491 3711 0x1500a641
+7491 7492 3710 0x1500a5db
+7492 7493 3709 0x1500a575
+7493 7494 3708 0x1500a50f
+7494 7495 3707 0x1500a4a9
+7495 7496 3706 0x1500a444
+7496 7497 3705 0x1500a3df
+7497 7498 3704 0x1500a37a
+7498 7499 3703 0x1500a315
+7499 7500 3702 0x1500a2b1
+7500 7501 3701 0x1500a24c
+7501 7502 3700 0x1500a1e8
+7502 7503 3699 0x1500a185
+7503 7504 3698 0x1500a121
+7504 7505 3697 0x1500a0be
+7505 7506 3696 0x1500a05b
+7506 7507 3695 0x15009ff8
+7507 7508 3694 0x15009f96
+7508 7509 3693 0x15009f33
+7509 7510 3692 0x15009ed1
+7510 7511 3691 0x15009e6f
+7511 7512 3690 0x15009e0e
+7512 7513 3689 0x15009dac
+7513 7514 3688 0x15009d4b
+7514 7515 3687 0x15009cea
+7515 7516 3686 0x15009c89
+7516 7517 3685 0x15009c29
+7517 7518 3684 0x15009bc9
+7518 7519 3683 0x15009b68
+7519 7520 3682 0x15009b09
+7520 7521 3681 0x15009aaa
+7521 7522 3680 0x15009a4a
+7522 7523 3679 0x150099eb
+7523 7524 3678 0x1500998c
+7524 7525 3677 0x1500992d
+7525 7526 3676 0x150098cf
+7526 7527 3675 0x15009870
+7527 7528 3674 0x15009812
+7528 7529 3673 0x150097b5
+7529 7530 3672 0x15009757
+7530 7531 3671 0x150096fa
+7531 7532 3670 0x1500969c
+7532 7533 3669 0x15009640
+7533 7534 3668 0x150095e3
+7534 7535 3667 0x15009587
+7535 7536 3666 0x1500952b
+7536 7537 3665 0x150094cf
+7537 7538 3664 0x15009473
+7538 7539 3663 0x15009417
+7539 7540 3662 0x150093bc
+7540 7541 3661 0x15009361
+7541 7542 3660 0x15009306
+7542 7543 3659 0x150092ab
+7543 7544 3658 0x15009250
+7544 7545 3657 0x150091f6
+7545 7546 3656 0x1500919c
+7546 7547 3655 0x15009142
+7547 7548 3654 0x150090e8
+7548 7549 3653 0x1500908f
+7549 7550 3652 0x15009036
+7550 7551 3651 0x15008fdd
+7551 7552 3650 0x15008f85
+7552 7553 3649 0x15008f2c
+7553 7554 3648 0x15008ed4
+7554 7555 3647 0x15008e7b
+7555 7556 3646 0x15008e23
+7556 7557 3645 0x15008dcc
+7557 7558 3644 0x15008d74
+7558 7559 3643 0x15008d1d
+7559 7560 3642 0x15008cc6
+7560 7561 3641 0x15008c6f
+7561 7562 3640 0x15008c18
+7562 7563 3639 0x15008bc2
+7563 7564 3638 0x15008b6b
+7564 7565 3637 0x15008b15
+7565 7566 3636 0x15008ac0
+7566 7567 3635 0x15008a6a
+7567 7568 3634 0x15008a15
+7568 7569 3633 0x150089bf
+7569 7570 3632 0x1500896b
+7570 7571 3631 0x15008916
+7571 7572 3630 0x150088c1
+7572 7573 3629 0x1500886d
+7573 7574 3628 0x15008819
+7574 7575 3627 0x150087c4
+7575 7576 3626 0x15008771
+7576 7577 3625 0x1500871d
+7577 7578 3624 0x150086ca
+7578 7579 3623 0x15008677
+7579 7580 3622 0x15008623
+7580 7581 3621 0x150085d1
+7581 7582 3620 0x1500857e
+7582 7583 3619 0x1500852c
+7583 7584 3618 0x150084da
+7584 7585 3617 0x15008488
+7585 7586 3616 0x15008436
+7586 7587 3615 0x150083e5
+7587 7588 3614 0x15008393
+7588 7589 3613 0x15008342
+7589 7590 3612 0x150082f1
+7590 7591 3611 0x150082a0
+7591 7592 3610 0x1500824f
+7592 7593 3609 0x150081ff
+7593 7594 3608 0x150081af
+7594 7595 3607 0x1500815f
+7595 7596 3606 0x1500810f
+7596 7597 3605 0x150080c0
+7597 7598 3604 0x15008070
+7598 7599 3603 0x15008021
+7599 7600 3602 0x147fd24b
+7600 7601 3601 0x147f8379
+7601 7602 3600 0x147f34a7
+7602 7603 3599 0x147ee62b
+7603 7604 3598 0x147e9805
+7604 7605 3597 0x147e49df
+7605 7606 3596 0x147dfc0f
+7606 7607 3595 0x147dae3e
+7607 7608 3594 0x147d60c4
+7608 7609 3593 0x147d134a
+7609 7610 3592 0x147cc625
+7610 7611 3591 0x147c7956
+7611 7612 3590 0x147c2cde
+7612 7613 3589 0x147be00f
+7613 7614 3588 0x147b9396
+7614 7615 3587 0x147b4773
+7615 7616 3586 0x147afb50
+7616 7617 3585 0x147aaf83
+7617 7618 3584 0x147a640c
+7618 7619 3583 0x147a183f
+7619 7620 3582 0x1479cd1d
+7620 7621 3581 0x147981fc
+7621 7622 3580 0x14793730
+7622 7623 3579 0x1478ec65
+7623 7624 3578 0x1478a199
+7624 7625 3577 0x14785724
+7625 7626 3576 0x14780d04
+7626 7627 3575 0x1477c33a
+7627 7628 3574 0x14777970
+7628 7629 3573 0x14772ffc
+7629 7630 3572 0x1476e688
+7630 7631 3571 0x14769d14
+7631 7632 3570 0x147653f6
+7632 7633 3569 0x14760ad8
+7633 7634 3568 0x1475c20f
+7634 7635 3567 0x1475799d
+7635 7636 3566 0x1475312a
+7636 7637 3565 0x1474e90e
+7637 7638 3564 0x1474a0f1
+7638 7639 3563 0x147458d4
+7639 7640 3562 0x1474110e
+7640 7641 3561 0x1473c99d
+7641 7642 3560 0x14738282
+7642 7643 3559 0x14733b67
+7643 7644 3558 0x1472f44c
+7644 7645 3557 0x1472ad87
+7645 7646 3556 0x147266c1
+7646 7647 3555 0x14722052
+7647 7648 3554 0x1471d9e3
+7648 7649 3553 0x147193c9
+7649 7650 3552 0x14714db0
+7650 7651 3551 0x147107ed
+7651 7652 3550 0x1470c27f
+7652 7653 3549 0x14707cbb
+7653 7654 3548 0x147037a3
+7654 7655 3547 0x146ff28c
+7655 7656 3546 0x146fad74
+7656 7657 3545 0x146f68b2
+7657 7658 3544 0x146f2446
+7658 7659 3543 0x146edfda
+7659 7660 3542 0x146e9b6d
+7660 7661 3541 0x146e5757
+7661 7662 3540 0x146e1341
+7662 7663 3539 0x146dcf81
+7663 7664 3538 0x146d8bc0
+7664 7665 3537 0x146d4856
+7665 7666 3536 0x146d04eb
+7666 7667 3535 0x146cc1d6
+7667 7668 3534 0x146c7ec2
+7668 7669 3533 0x146c3c03
+7669 7670 3532 0x146bf944
+7670 7671 3531 0x146bb6db
+7671 7672 3530 0x146b7472
+7672 7673 3529 0x146b32b5
+7673 7674 3528 0x146af0a2
+7674 7675 3527 0x146aaee5
+7675 7676 3526 0x146a6d28
+7676 7677 3525 0x146a2b6a
+7677 7678 3524 0x1469ea03
+7678 7679 3523 0x1469a8f2
+7679 7680 3522 0x146967e0
+7680 7681 3521 0x146926cf
+7681 7682 3520 0x1468e613
+7682 7683 3519 0x1468a5ad
+7683 7684 3518 0x146864f2
+7684 7685 3517 0x146824e2
+7685 7686 3516 0x1467e47c
+7686 7687 3515 0x1467a4c2
+7687 7688 3514 0x14676508
+7688 7689 3513 0x1467254e
+7689 7690 3512 0x1466e5ea
+7690 7691 3511 0x1466a686
+7691 7692 3510 0x14666777
+7692 7693 3509 0x14662869
+7693 7694 3508 0x1465e95b
+7694 7695 3507 0x1465aaa2
+7695 7696 3506 0x14656bea
+7696 7697 3505 0x14652d87
+7697 7698 3504 0x1464ef25
+7698 7699 3503 0x1464b118
+7699 7700 3502 0x1464730b
+7700 7701 3501 0x14643555
+7701 7702 3500 0x1463f79e
+7702 7703 3499 0x1463b9e7
+7703 7704 3498 0x14637cdc
+7704 7705 3497 0x14633f7b
+7705 7706 3496 0x14630270
+7706 7707 3495 0x1462c565
+7707 7708 3494 0x146288af
+7708 7709 3493 0x14624bfa
+7709 7710 3492 0x14620f45
+7710 7711 3491 0x1461d2e5
+7711 7712 3490 0x14619686
+7712 7713 3489 0x14615a7c
+7713 7714 3488 0x14611e73
+7714 7715 3487 0x1460e2bf
+7715 7716 3486 0x1460a70c
+7716 7717 3485 0x14606bae
+7717 7718 3484 0x14602ffa
+7718 7719 3483 0x145ff548
+7719 7720 3482 0x145fb9ea
+7720 7721 3481 0x145f7ee2
+7721 7722 3480 0x145f4430
+7722 7723 3479 0x145f097e
+7723 7724 3478 0x145ececc
+7724 7725 3477 0x145e9470
+7725 7726 3476 0x145e5a14
+7726 7727 3475 0x145e200d
+7727 7728 3474 0x145de607
+7728 7729 3473 0x145dac01
+7729 7730 3472 0x145d7250
+7730 7731 3471 0x145d38f6
+7731 7732 3470 0x145cff45
+7732 7733 3469 0x145cc5ea
+7733 7734 3468 0x145c8ce6
+7734 7735 3467 0x145c53e1
+7735 7736 3466 0x145c1b32
+7736 7737 3465 0x145be22d
+7737 7738 3464 0x145ba97e
+7738 7739 3463 0x145b7125
+7739 7740 3462 0x145b3876
+7740 7741 3461 0x145b0073
+7741 7742 3460 0x145ac81a
+7742 7743 3459 0x145a9017
+7743 7744 3458 0x145a586a
+7744 7745 3457 0x145a2067
+7745 7746 3456 0x1459e90f
+7746 7747 3455 0x1459b162
+7747 7748 3454 0x14597a0a
+7748 7749 3453 0x145942b3
+7749 7750 3452 0x14590c07
+7750 7751 3451 0x1458d506
+7751 7752 3450 0x14589e04
+7752 7753 3449 0x14586758
+7753 7754 3448 0x145830ad
+7754 7755 3447 0x1457fa57
+7755 7756 3446 0x1457c401
+7756 7757 3445 0x14578dab
+7757 7758 3444 0x145757ab
+7758 7759 3443 0x145721ab
+7759 7760 3442 0x1456ebab
+7760 7761 3441 0x1456b601
+7761 7762 3440 0x14568057
+7762 7763 3439 0x14564b03
+7763 7764 3438 0x145615ae
+7764 7765 3437 0x1455e05a
+7765 7766 3436 0x1455abb2
+7766 7767 3435 0x14557709
+7767 7768 3434 0x14554261
+7768 7769 3433 0x14550e0e
+7769 7770 3432 0x1454d9bb
+7770 7771 3431 0x1454a569
+7771 7772 3430 0x1454716c
+7772 7773 3429 0x14543d6f
+7773 7774 3428 0x1454099d
+7774 7775 3427 0x1453d5cb
+7775 7776 3426 0x1453a24f
+7776 7777 3425 0x14536ed3
+7777 7778 3424 0x14533b57
+7778 7779 3423 0x14530831
+7779 7780 3422 0x1452d536
+7780 7781 3421 0x1452a23b
+7781 7782 3420 0x14526f6a
+7782 7783 3419 0x14523c9a
+7783 7784 3418 0x145209f5
+7784 7785 3417 0x1451d77a
+7785 7786 3416 0x1451a500
+7786 7787 3415 0x145172b0
+7787 7788 3414 0x1451408c
+7788 7789 3413 0x14510e67
+7789 7790 3412 0x1450dc98
+7790 7791 3411 0x1450aaca
+7791 7792 3410 0x145078fb
+7792 7793 3409 0x14504757
+7793 7794 3408 0x145015de
+7794 7795 3407 0x144fe491
+7795 7796 3406 0x144fb398
+7796 7797 3405 0x144f8276
+7797 7798 3404 0x144f517d
+7798 7799 3403 0x144f2085
+7799 7800 3402 0x144eefb8
+7800 7801 3401 0x144ebf16
+7801 7802 3400 0x144e8e9f
+7802 7803 3399 0x144e5e28
+7803 7804 3398 0x144e2ddb
+7804 7805 3397 0x144dfd8f
+7805 7806 3396 0x144dcd99
+7806 7807 3395 0x144d9d77
+7807 7808 3394 0x144d6dac
+7808 7809 3393 0x144d3de0
+7809 7810 3392 0x144d0e40
+7810 7811 3391 0x144cdef5
+7811 7812 3390 0x144caf7f
+7812 7813 3389 0x144c8034
+7813 7814 3388 0x144c5115
+7814 7815 3387 0x144c21f5
+7815 7816 3386 0x144bf300
+7816 7817 3385 0x144bc436
+7817 7818 3384 0x144b956c
+7818 7819 3383 0x144b66cd
+7819 7820 3382 0x144b3859
+7820 7821 3381 0x144b09e5
+7821 7822 3380 0x144adb9c
+7822 7823 3379 0x144aad7e
+7823 7824 3378 0x144a7f60
+7824 7825 3377 0x144a516c
+7825 7826 3376 0x144a23a4
+7826 7827 3375 0x1449f607
+7827 7828 3374 0x1449c869
+7828 7829 3373 0x14499acc
+7829 7830 3372 0x14496d59
+7830 7831 3371 0x14494012
+7831 7832 3370 0x144912ca
+7832 7833 3369 0x1448e5d8
+7833 7834 3368 0x1448b8bc
+7834 7835 3367 0x14488bf5
+7835 7836 3366 0x14485f2e
+7836 7837 3365 0x14483268
+7837 7838 3364 0x144805f7
+7838 7839 3363 0x1447d986
+7839 7840 3362 0x1447ad15
+7840 7841 3361 0x144780cf
+7841 7842 3360 0x144754df
+7842 7843 3359 0x144728ef
+7843 7844 3358 0x1446fcff
+7844 7845 3357 0x1446d13a
+7845 7846 3356 0x1446a574
+7846 7847 3355 0x144679da
+7847 7848 3354 0x14464e6b
+7848 7849 3353 0x144622fc
+7849 7850 3352 0x1445f7b7
+7850 7851 3351 0x1445cc73
+7851 7852 3350 0x1445a159
+7852 7853 3349 0x1445766b
+7853 7854 3348 0x14454b7c
+7854 7855 3347 0x144520b9
+7855 7856 3346 0x1444f620
+7856 7857 3345 0x1444cbb3
+7857 7858 3344 0x1444a145
+7858 7859 3343 0x144476d7
+7859 7860 3342 0x14444c94
+7860 7861 3341 0x1444227d
+7861 7862 3340 0x1443f865
+7862 7863 3339 0x1443ce78
+7863 7864 3338 0x1443a48b
+7864 7865 3337 0x14437ac9
+7865 7866 3336 0x14435132
+7866 7867 3335 0x1443279b
+7867 7868 3334 0x1442fe2f
+7868 7869 3333 0x1442d4c3
+7869 7870 3332 0x1442ab81
+7870 7871 3331 0x1442826b
+7871 7872 3330 0x14425980
+7872 7873 3329 0x14423094
+7873 7874 3328 0x144207d4
+7874 7875 3327 0x1441df14
+7875 7876 3326 0x1441b653
+7876 7877 3325 0x14418dbe
+7877 7878 3324 0x14416553
+7878 7879 3323 0x14413ce9
+7879 7880 3322 0x144114a9
+7880 7881 3321 0x1440ec95
+7881 7882 3320 0x1440c480
+7882 7883 3319 0x14409c96
+7883 7884 3318 0x144074ac
+7884 7885 3317 0x14404cee
+7885 7886 3316 0x1440252f
+7886 7887 3315 0x143ffd9b
+7887 7888 3314 0x143fd65d
+7888 7889 3313 0x143faef4
+7889 7890 3312 0x143f878b
+7890 7891 3311 0x143f6078
+7891 7892 3310 0x143f393a
+7892 7893 3309 0x143f1252
+7893 7894 3308 0x143eeb6a
+7894 7895 3307 0x143ec482
+7895 7896 3306 0x143e9dc4
+7896 7897 3305 0x143e7732
+7897 7898 3304 0x143e50a0
+7898 7899 3303 0x143e2a38
+7899 7900 3302 0x143e03d1
+7900 7901 3301 0x143ddd95
+7901 7902 3300 0x143db783
+7902 7903 3299 0x143d919d
+7903 7904 3298 0x143d6b8b
+7904 7905 3297 0x143d45a5
+7905 7906 3296 0x143d1fe9
+7906 7907 3295 0x143cfa2d
+7907 7908 3294 0x143cd49d
+7908 7909 3293 0x143caf0c
+7909 7910 3292 0x143c89a6
+7910 7911 3291 0x143c6441
+7911 7912 3290 0x143c3f06
+7912 7913 3289 0x143c19f6
+7913 7914 3288 0x143bf4e6
+7914 7915 3287 0x143bcfd6
+7915 7916 3286 0x143baaf1
+7916 7917 3285 0x143b8637
+7917 7918 3284 0x143b61a8
+7918 7919 3283 0x143b3d19
+7919 7920 3282 0x143b188a
+7920 7921 3281 0x143af426
+7921 7922 3280 0x143acfc1
+7922 7923 3279 0x143aab88
+7923 7924 3278 0x143a874f
+7924 7925 3277 0x143a6341
+7925 7926 3276 0x143a3f32
+7926 7927 3275 0x143a1b4f
+7927 7928 3274 0x1439f76c
+7928 7929 3273 0x1439d3b3
+7929 7930 3272 0x1439b026
+7930 7931 3271 0x14398c98
+7931 7932 3270 0x1439690b
+7932 7933 3269 0x143945a8
+7933 7934 3268 0x1439229b
+7934 7935 3267 0x1438ff64
+7935 7936 3266 0x1438dc2c
+7936 7937 3265 0x1438b91f
+7937 7938 3264 0x1438963e
+7938 7939 3263 0x1438735c
+7939 7940 3262 0x1438507a
+7940 7941 3261 0x14382dee
+7941 7942 3260 0x14380b37
+7942 7943 3259 0x1437e8ab
+7943 7944 3258 0x1437c64a
+7944 7945 3257 0x1437a3e9
+7945 7946 3256 0x14378188
+7946 7947 3255 0x14375f52
+7947 7948 3254 0x14373d47
+7948 7949 3253 0x14371b67
+7949 7950 3252 0x1436f987
+7950 7951 3251 0x1436d7a7
+7951 7952 3250 0x1436b5c6
+7952 7953 3249 0x14369411
+7953 7954 3248 0x14367287
+7954 7955 3247 0x143650fc
+7955 7956 3246 0x14362f72
+7956 7957 3245 0x14360e13
+7957 7958 3244 0x1435ecde
+7958 7959 3243 0x1435cbaa
+7959 7960 3242 0x1435aa75
+7960 7961 3241 0x1435896c
+7961 7962 3240 0x14356862
+7962 7963 3239 0x14354784
+7963 7964 3238 0x143526d0
+7964 7965 3237 0x1435061c
+7965 7966 3236 0x1434e569
+7966 7967 3235 0x1434c4e0
+7967 7968 3234 0x1434a457
+7968 7969 3233 0x143483f9
+7969 7970 3232 0x1434639c
+7970 7971 3231 0x14344369
+7971 7972 3230 0x14342336
+7972 7973 3229 0x14340303
+7973 7974 3228 0x1433e2fb
+7974 7975 3227 0x1433c31e
+7975 7976 3226 0x1433a341
+7976 7977 3225 0x14338364
+7977 7978 3224 0x143363b2
+7978 7979 3223 0x14334400
+7979 7980 3222 0x143324a4
+7980 7981 3221 0x1433051c
+7981 7982 3220 0x1432e5c0
+7982 7983 3219 0x1432c664
+7983 7984 3218 0x1432a708
+7984 7985 3217 0x143287d7
+7985 7986 3216 0x143268d0
+7986 7987 3215 0x1432499f
+7987 7988 3214 0x14322ac3
+7988 7989 3213 0x14320be8
+7989 7990 3212 0x1431ed0d
+7990 7991 3211 0x1431ce31
+7991 7992 3210 0x1431afac
+7992 7993 3209 0x143190fb
+7993 7994 3208 0x14317276
+7994 7995 3207 0x1431541b
+7995 7996 3206 0x143135c0
+7996 7997 3205 0x14311766
+7997 7998 3204 0x1430f936
+7998 7999 3203 0x1430db06
+7999 8000 3202 0x1430bd02
+8000 8001 3201 0x14309efd
+8001 8002 3200 0x143080f8
+8002 8003 3199 0x1430631e
+8003 8004 3198 0x14304544
+8004 8005 3197 0x14302795
+8005 8006 3196 0x143009e7
+8006 8007 3195 0x142fec38
+8007 8008 3194 0x142fceb4
+8008 8009 3193 0x142fb15b
+8009 8010 3192 0x142f9402
+8010 8011 3191 0x142f76a9
+8011 8012 3190 0x142f597a
+8012 8013 3189 0x142f3c4c
+8013 8014 3188 0x142f1f1e
+8014 8015 3187 0x142f021b
+8015 8016 3186 0x142ee518
+8016 8017 3185 0x142ec840
+8017 8018 3184 0x142eab67
+8018 8019 3183 0x142e8eba
+8019 8020 3182 0x142e71e2
+8020 8021 3181 0x142e5560
+8021 8022 3180 0x142e38b2
+8022 8023 3179 0x142e1c30
+8023 8024 3178 0x142dffd8
+8024 8025 3177 0x142de381
+8025 8026 3176 0x142dc754
+8026 8027 3175 0x142daafd
+8027 8028 3174 0x142d8ed0
+8028 8029 3173 0x142d72cf
+8029 8030 3172 0x142d56a2
+8030 8031 3171 0x142d3acc
+8031 8032 3170 0x142d1eca
+8032 8033 3169 0x142d02f3
+8033 8034 3168 0x142ce71d
+8034 8035 3167 0x142ccb71
+8035 8036 3166 0x142cafc5
+8036 8037 3165 0x142c9445
+8037 8038 3164 0x142c78c4
+8038 8039 3163 0x142c5d43
+8039 8040 3162 0x142c41ed
+8040 8041 3161 0x142c2697
+8041 8042 3160 0x142c0b6c
+8042 8043 3159 0x142bf017
+8043 8044 3158 0x142bd4ec
+8044 8045 3157 0x142bb9ec
+8045 8046 3156 0x142b9eec
+8046 8047 3155 0x142b83ec
+8047 8048 3154 0x142b6917
+8048 8049 3153 0x142b4e41
+8049 8050 3152 0x142b336c
+8050 8051 3151 0x142b18c2
+8051 8052 3150 0x142afe18
+8052 8053 3149 0x142ae383
+8053 8054 3148 0x142ac91a
+8054 8055 3147 0x142aaec5
+8055 8056 3146 0x142a94b2
+8056 8057 3145 0x142a7a73
+8057 8058 3144 0x142a605f
+8058 8059 3143 0x142a464b
+8059 8060 3142 0x142a2c37
+8060 8061 3141 0x142a124e
+8061 8062 3140 0x1429f865
+8062 8063 3139 0x1429de92
+8063 8064 3138 0x1429c4d4
+8064 8065 3137 0x1429ab16
+8065 8066 3136 0x1429916d
+8066 8067 3135 0x142977da
+8067 8068 3134 0x14295e47
+8068 8069 3133 0x142944df
+8069 8070 3132 0x14292b61
+8070 8071 3131 0x1429120f
+8071 8072 3130 0x1428f8e7
+8072 8073 3129 0x1428dfaa
+8073 8074 3128 0x1428c682
+8074 8075 3127 0x1428ad5a
+8075 8076 3126 0x14289448
+8076 8077 3125 0x14287b4b
+8077 8078 3124 0x14286264
+8078 8079 3123 0x1428497c
+8079 8080 3122 0x142830ab
+8080 8081 3121 0x142817d9
+8081 8082 3120 0x1427ff32
+8082 8083 3119 0x1427e68b
+8083 8084 3118 0x1427cde4
+8084 8085 3117 0x1427b568
+8085 8086 3116 0x14279cec
+8086 8087 3115 0x1427849b
+8087 8088 3114 0x14276c4a
+8088 8089 3113 0x142753f9
+8089 8090 3112 0x14273bbd
+8090 8091 3111 0x14272381
+8091 8092 3110 0x14270b5b
+8092 8093 3109 0x1426f34a
+8093 8094 3108 0x1426db4f
+8094 8095 3107 0x1426c354
+8095 8096 3106 0x1426ab6e
+8096 8097 3105 0x14269388
+8097 8098 3104 0x14267bce
+8098 8099 3103 0x14266413
+8099 8100 3102 0x14264c58
+8100 8101 3101 0x142634c8
+8101 8102 3100 0x14261d23
+8102 8103 3099 0x142605be
+8103 8104 3098 0x1425ee59
+8104 8105 3097 0x1425d6f4
+8105 8106 3096 0x1425bf8f
+8106 8107 3095 0x1425a83f
+8107 8108 3094 0x14259105
+8108 8109 3093 0x142579e1
+8109 8110 3092 0x142562bc
+8110 8111 3091 0x14254bad
+8111 8112 3090 0x142534b3
+8112 8113 3089 0x14251dba
+8113 8114 3088 0x142506d6
+8114 8115 3087 0x1424eff2
+8115 8116 3086 0x1424d923
+8116 8117 3085 0x1424c26a
+8117 8118 3084 0x1424abdb
+8118 8119 3083 0x14249538
+8119 8120 3082 0x14247ea9
+8120 8121 3081 0x1424681b
+8121 8122 3080 0x142451a2
+8122 8123 3079 0x14243b3f
+8123 8124 3078 0x142424db
+8124 8125 3077 0x14240e8d
+8125 8126 3076 0x1423f855
+8126 8127 3075 0x1423e21d
+8127 8128 3074 0x1423cbfa
+8128 8129 3073 0x1423b5d7
+8129 8130 3072 0x14239fdf
+8130 8131 3071 0x142389d1
+8131 8132 3070 0x142373ee
+8132 8133 3069 0x14235e21
+8133 8134 3068 0x14234854
+8134 8135 3067 0x14233287
+8135 8136 3066 0x14231ccf
+8136 8137 3065 0x14230718
+8137 8138 3064 0x1422f176
+8138 8139 3063 0x1422dbe9
+8139 8140 3062 0x1422c672
+8140 8141 3061 0x1422b0fa
+8141 8142 3060 0x14229b83
+8142 8143 3059 0x14228621
+8143 8144 3058 0x142270d5
+8144 8145 3057 0x14225b9e
+8145 8146 3056 0x14224667
+8146 8147 3055 0x14223146
+8147 8148 3054 0x14221c25
+8148 8149 3053 0x1422072e
+8149 8150 3052 0x1421f222
+8150 8151 3051 0x1421dd2c
+8151 8152 3050 0x1421c84b
+8152 8153 3049 0x1421b36a
+8153 8154 3048 0x14219e9e
+8154 8155 3047 0x142189e8
+8155 8156 3046 0x14217532
+8156 8157 3045 0x14216092
+8157 8158 3044 0x14214bf1
+8158 8159 3043 0x14213766
+8159 8160 3042 0x142122db
+8160 8161 3041 0x14210e7a
+8161 8162 3040 0x1420fa05
+8162 8163 3039 0x1420e5ba
+8163 8164 3038 0x1420d16f
+8164 8165 3037 0x1420bd3a
+8165 8166 3036 0x1420a905
+8166 8167 3035 0x142094e5
+8167 8168 3034 0x142080c5
+8168 8169 3033 0x14206cbb
+8169 8170 3032 0x142058b1
+8170 8171 3031 0x142044bc
+8171 8172 3030 0x142030c7
+8172 8173 3029 0x14201ce7
+8173 8174 3028 0x1420091e
+8174 8175 3027 0x141ff554
+8175 8176 3026 0x141fe19f
+8176 8177 3025 0x141fce00
+8177 8178 3024 0x141fba61
+8178 8179 3023 0x141fa6d8
+8179 8180 3022 0x141f934e
+8180 8181 3021 0x141f7fda
+8181 8182 3020 0x141f6c66
+8182 8183 3019 0x141f5907
+8183 8184 3018 0x141f45a9
+8184 8185 3017 0x141f3260
+8185 8186 3016 0x141f1f2c
+8186 8187 3015 0x141f0bf8
+8187 8188 3014 0x141ef8c5
+8188 8189 3013 0x141ee5a6
+8189 8190 3012 0x141ed29e
+8190 8191 3011 0x141ebf95
+8191 8192 3010 0x141eaca2
+8192 8193 3009 0x141e99ae
+8193 8194 3008 0x141e86d0
+8194 8195 3007 0x141e741e
+8195 8196 3006 0x141e6155
+8196 8197 3005 0x141e4e8d
+8197 8198 3004 0x141e3bda
+8198 8199 3003 0x141e293d
+8199 8200 3002 0x141e169f
+8200 8201 3001 0x141e0417
+8201 8202 3000 0x141df18f
+8202 8203 2999 0x141ddf1d
+8203 8204 2998 0x141dccaa
+8204 8205 2997 0x141dba4d
+8205 8206 2996 0x141da806
+8206 8207 2995 0x141d95a9
+8207 8208 2994 0x141d8377
+8208 8209 2993 0x141d7145
+8209 8210 2992 0x141d5f28
+8210 8211 2991 0x141d4d0b
+8211 8212 2990 0x141d3b04
+8212 8213 2989 0x141d28fd
+8213 8214 2988 0x141d170b
+8214 8215 2987 0x141d051a
+8215 8216 2986 0x141cf328
+8216 8217 2985 0x141ce161
+8217 8218 2984 0x141ccf85
+8218 8219 2983 0x141cbdd4
+8219 8220 2982 0x141cac0d
+8220 8221 2981 0x141c9a71
+8221 8222 2980 0x141c88c0
+8222 8223 2979 0x141c773a
+8223 8224 2978 0x141c659e
+8224 8225 2977 0x141c5442
+8225 8226 2976 0x141c42bc
+8226 8227 2975 0x141c3161
+8227 8228 2974 0x141c1ff0
+8228 8229 2973 0x141c0eaa
+8229 8230 2972 0x141bfd64
+8230 8231 2971 0x141bec1e
+8231 8232 2970 0x141bdaed
+8232 8233 2969 0x141bc9bd
+8233 8234 2968 0x141bb8a2
+8234 8235 2967 0x141ba787
+8235 8236 2966 0x141b9681
+8236 8237 2965 0x141b857c
+8237 8238 2964 0x141b748b
+8238 8239 2963 0x141b639b
+8239 8240 2962 0x141b52c1
+8240 8241 2961 0x141b41fc
+8241 8242 2960 0x141b3136
+8242 8243 2959 0x141b2071
+8243 8244 2958 0x141b0fc2
+8244 8245 2957 0x141aff12
+8245 8246 2956 0x141aee78
+8246 8247 2955 0x141adddd
+8247 8248 2954 0x141acd59
+8248 8249 2953 0x141abcd4
+8249 8250 2952 0x141aac4f
+8250 8251 2951 0x141a9be0
+8251 8252 2950 0x141a8b86
+8252 8253 2949 0x141a7b2c
+8253 8254 2948 0x141a6ad2
+8254 8255 2947 0x141a5a8e
+8255 8256 2946 0x141a4a74
+8256 8257 2945 0x141a3a46
+8257 8258 2944 0x141a2a17
+8258 8259 2943 0x141a19fd
+8259 8260 2942 0x141a09e4
+8260 8261 2941 0x1419f9e0
+8261 8262 2940 0x1419e9dc
+8262 8263 2939 0x1419d9ed
+8263 8264 2938 0x1419c9ff
+8264 8265 2937 0x1419ba26
+8265 8266 2936 0x1419aa4d
+8266 8267 2935 0x14199a74
+8267 8268 2934 0x14198ab0
+8268 8269 2933 0x14197aed
+8269 8270 2932 0x14196b3e
+8270 8271 2931 0x14195ba6
+8271 8272 2930 0x14194c0d
+8272 8273 2929 0x14193c75
+8273 8274 2928 0x14192cf1
+8274 8275 2927 0x14191d6e
+8275 8276 2926 0x14190e00
+8276 8277 2925 0x1418fe93
+8277 8278 2924 0x1418ef25
+8278 8279 2923 0x1418dfcd
+8279 8280 2922 0x1418d075
+8280 8281 2921 0x1418c132
+8281 8282 2920 0x1418b1ef
+8282 8283 2919 0x1418a2c2
+8283 8284 2918 0x14189394
+8284 8285 2917 0x14188467
+8285 8286 2916 0x1418754f
+8286 8287 2915 0x1418664d
+8287 8288 2914 0x1418574a
+8288 8289 2913 0x14184848
+8289 8290 2912 0x1418395b
+8290 8291 2911 0x14182a6e
+8291 8292 2910 0x14181b81
+8292 8293 2909 0x14180caa
+8293 8294 2908 0x1417fdd2
+8294 8295 2907 0x1417ef10
+8295 8296 2906 0x1417e04e
+8296 8297 2905 0x1417d1a2
+8297 8298 2904 0x1417c2f5
+8298 8299 2903 0x1417b449
+8299 8300 2902 0x1417a5b2
+8300 8301 2901 0x1417971b
+8301 8302 2900 0x141788af
+8302 8303 2899 0x14177a2d
+8303 8304 2898 0x14176bac
+8304 8305 2897 0x14175d3f
+8305 8306 2896 0x14174ed3
+8306 8307 2895 0x1417407d
+8307 8308 2894 0x14173226
+8308 8309 2893 0x141723cf
+8309 8310 2892 0x1417158e
+8310 8311 2891 0x1417074d
+8311 8312 2890 0x1416f921
+8312 8313 2889 0x1416eaf5
+8313 8314 2888 0x1416dcca
+8314 8315 2887 0x1416ceb4
+8315 8316 2886 0x1416c09d
+8316 8317 2885 0x1416b2b2
+8317 8318 2884 0x1416a4b1
+8318 8319 2883 0x141696b0
+8319 8320 2882 0x141688c5
+8320 8321 2881 0x14167ada
+8321 8322 2880 0x14166cee
+8322 8323 2879 0x14165f18
+8323 8324 2878 0x14165158
+8324 8325 2877 0x14164382
+8325 8326 2876 0x141635c2
+8326 8327 2875 0x14162817
+8327 8328 2874 0x14161a6c
+8328 8329 2873 0x14160cc1
+8329 8330 2872 0x1415ff16
+8330 8331 2871 0x1415f181
+8331 8332 2870 0x1415e3eb
+8332 8333 2869 0x1415d681
+8333 8334 2868 0x1415c901
+8334 8335 2867 0x1415bb81
+8335 8336 2866 0x1415ae16
+8336 8337 2865 0x1415a0ac
+8337 8338 2864 0x14159357
+8338 8339 2863 0x141585ec
+8339 8340 2862 0x141578ac
+8340 8341 2861 0x14156b6d
+8341 8342 2860 0x14155e43
+8342 8343 2859 0x14155123
+8343 8344 2858 0x14154404
+8344 8345 2857 0x141536ef
+8345 8346 2856 0x141529e5
+8346 8347 2855 0x14151cdb
+8347 8348 2854 0x14150fe7
+8348 8349 2853 0x141502f2
+8349 8350 2852 0x1414f609
+8350 8351 2851 0x1414e91f
+8351 8352 2850 0x1414dc40
+8352 8353 2849 0x1414cf6c
+8353 8354 2848 0x1414c297
+8354 8355 2847 0x1414b5ce
+8355 8356 2846 0x1414a90f
+8356 8357 2845 0x14149c5b
+8357 8358 2844 0x14148fa7
+8358 8359 2843 0x141482fe
+8359 8360 2842 0x14147654
+8360 8361 2841 0x141469c0
+8361 8362 2840 0x14145d2c
+8362 8363 2839 0x141450ae
+8363 8364 2838 0x14144430
+8364 8365 2837 0x141437b1
+8365 8366 2836 0x14142b3e
+8366 8367 2835 0x14141eca
+8367 8368 2834 0x14141261
+8368 8369 2833 0x14140603
+8369 8370 2832 0x1413f9af
+8370 8371 2831 0x1413ed5c
+8371 8372 2830 0x1413e113
+8372 8373 2829 0x1413d4d5
+8373 8374 2828 0x1413c8a2
+8374 8375 2827 0x1413bc6f
+8375 8376 2826 0x1413b046
+8376 8377 2825 0x1413a41d
+8377 8378 2824 0x14139800
+8378 8379 2823 0x14138bf7
+8379 8380 2822 0x14137fef
+8380 8381 2821 0x141373e7
+8381 8382 2820 0x141367e9
+8382 8383 2819 0x14135bf6
+8383 8384 2818 0x14135003
+8384 8385 2817 0x1413441b
+8385 8386 2816 0x14133833
+8386 8387 2815 0x14132c60
+8387 8388 2814 0x14132083
+8388 8389 2813 0x141314bb
+8389 8390 2812 0x141308f3
+8390 8391 2811 0x1412fd36
+8391 8392 2810 0x1412f183
+8392 8393 2809 0x1412e5d1
+8393 8394 2808 0x1412da34
+8394 8395 2807 0x1412ce97
+8395 8396 2806 0x1412c2fa
+8396 8397 2805 0x1412b767
+8397 8398 2804 0x1412abe0
+8398 8399 2803 0x1412a058
+8399 8400 2802 0x141294db
+8400 8401 2801 0x1412895f
+8401 8402 2800 0x14127ded
+8402 8403 2799 0x14127285
+8403 8404 2798 0x14126729
+8404 8405 2797 0x14125bcc
+8405 8406 2796 0x1412507a
+8406 8407 2795 0x14124528
+8407 8408 2794 0x141239e1
+8408 8409 2793 0x14122eaf
+8409 8410 2792 0x14122373
+8410 8411 2791 0x14121841
+8411 8412 2790 0x14120d1a
+8412 8413 2789 0x141201f3
+8413 8414 2788 0x1411f6d7
+8414 8415 2787 0x1411ebc6
+8415 8416 2786 0x1411e0b4
+8416 8417 2785 0x1411d5ad
+8417 8418 2784 0x1411caa7
+8418 8419 2783 0x1411bfab
+8419 8420 2782 0x1411b4b9
+8420 8421 2781 0x1411a9c8
+8421 8422 2780 0x14119ee2
+8422 8423 2779 0x14119406
+8423 8424 2778 0x1411892a
+8424 8425 2777 0x14117e63
+8425 8426 2776 0x14117392
+8426 8427 2775 0x141168cc
+8427 8428 2774 0x14115e10
+8428 8429 2773 0x14115355
+8429 8430 2772 0x141148a4
+8430 8431 2771 0x14113dfe
+8431 8432 2770 0x14113358
+8432 8433 2769 0x141128bc
+8433 8434 2768 0x14111e21
+8434 8435 2767 0x14111390
+8435 8436 2766 0x1411090a
+8436 8437 2765 0x1410fe84
+8437 8438 2764 0x1410f409
+8438 8439 2763 0x1410e98e
+8439 8440 2762 0x1410df28
+8440 8441 2761 0x1410d4c2
+8441 8442 2760 0x1410ca5c
+8442 8443 2759 0x1410c001
+8443 8444 2758 0x1410b5a6
+8444 8445 2757 0x1410ab56
+8445 8446 2756 0x1410a110
+8446 8447 2755 0x141096cb
+8447 8448 2754 0x14108c90
+8448 8449 2753 0x14108255
+8449 8450 2752 0x14107825
+8450 8451 2751 0x14106e00
+8451 8452 2750 0x141063da
+8452 8453 2749 0x141059c0
+8453 8454 2748 0x14104fa5
+8454 8455 2747 0x141045a0
+8455 8456 2746 0x14103b90
+8456 8457 2745 0x1410318b
+8457 8458 2744 0x14102790
+8458 8459 2743 0x14101d96
+8459 8460 2742 0x141013a6
+8460 8461 2741 0x141009b7
+8461 8462 2740 0x140fffd2
+8462 8463 2739 0x140ff5f7
+8463 8464 2738 0x140fec1d
+8464 8465 2737 0x140fe24e
+8465 8466 2736 0x140fd87e
+8466 8467 2735 0x140fceb9
+8467 8468 2734 0x140fc4f5
+8468 8469 2733 0x140fbb3b
+8469 8470 2732 0x140fb181
+8470 8471 2731 0x140fa7e7
+8471 8472 2730 0x140f9e37
+8472 8473 2729 0x140f9493
+8473 8474 2728 0x140f8af9
+8474 8475 2727 0x140f815f
+8475 8476 2726 0x140f77d0
+8476 8477 2725 0x140f6e4c
+8477 8478 2724 0x140f64c7
+8478 8479 2723 0x140f5b43
+8479 8480 2722 0x140f51c9
+8480 8481 2721 0x140f485a
+8481 8482 2720 0x140f3eec
+8482 8483 2719 0x140f3587
+8483 8484 2718 0x140f2c23
+8484 8485 2717 0x140f22ca
+8485 8486 2716 0x140f197b
+8486 8487 2715 0x140f102c
+8487 8488 2714 0x140f06de
+8488 8489 2713 0x140efd9a
+8489 8490 2712 0x140ef460
+8490 8491 2711 0x140eeb27
+8491 8492 2710 0x140ee1ee
+8492 8493 2709 0x140ed8ca
+8493 8494 2708 0x140ecf9c
+8494 8495 2707 0x140ec678
+8495 8496 2706 0x140ebd5f
+8496 8497 2705 0x140eb446
+8497 8498 2704 0x140eab38
+8498 8499 2703 0x140ea229
+8499 8500 2702 0x140e9926
+8500 8501 2701 0x140e9038
+8501 8502 2700 0x140e8734
+8502 8503 2699 0x140e7e46
+8503 8504 2698 0x140e7558
+8504 8505 2697 0x140e6c6a
+8505 8506 2696 0x140e6386
+8506 8507 2695 0x140e5aae
+8507 8508 2694 0x140e51ca
+8508 8509 2693 0x140e48fc
+8509 8510 2692 0x140e402e
+8510 8511 2691 0x140e3760
+8511 8512 2690 0x140e2ea8
+8512 8513 2689 0x140e25e5
+8513 8514 2688 0x140e1d2c
+8514 8515 2687 0x140e147f
+8515 8516 2686 0x140e0bd1
+8516 8517 2685 0x140e032e
+8517 8518 2684 0x140dfa8b
+8518 8519 2683 0x140df1f3
+8519 8520 2682 0x140de95b
+8520 8521 2681 0x140de0c2
+8521 8522 2680 0x140dd835
+8522 8523 2679 0x140dcfb2
+8523 8524 2678 0x140dc72f
+8524 8525 2677 0x140dbeb7
+8525 8526 2676 0x140db63f
+8526 8527 2675 0x140dadc7
+8527 8528 2674 0x140da55a
+8528 8529 2673 0x140d9cf7
+8529 8530 2672 0x140d9495
+8530 8531 2671 0x140d8c32
+8531 8532 2670 0x140d83e5
+8532 8533 2669 0x140d7b8d
+8533 8534 2668 0x140d7340
+8534 8535 2667 0x140d6afe
+8535 8536 2666 0x140d62b0
+8536 8537 2665 0x140d5a79
+8537 8538 2664 0x140d5241
+8538 8539 2663 0x140d4a09
+8539 8540 2662 0x140d41dd
+8540 8541 2661 0x140d39b0
+8541 8542 2660 0x140d318d
+8542 8543 2659 0x140d296b
+8543 8544 2658 0x140d2154
+8544 8545 2657 0x140d193c
+8545 8546 2656 0x140d1125
+8546 8547 2655 0x140d0923
+8547 8548 2654 0x140d0121
+8548 8549 2653 0x140cf91f
+8549 8550 2652 0x140cf11d
+8550 8551 2651 0x140ce926
+8551 8552 2650 0x140ce12e
+8552 8553 2649 0x140cd942
+8553 8554 2648 0x140cd155
+8554 8555 2647 0x140cc974
+8555 8556 2646 0x140cc192
+8556 8557 2645 0x140cb9bb
+8557 8558 2644 0x140cb1e4
+8558 8559 2643 0x140caa17
+8559 8560 2642 0x140ca240
+8560 8561 2641 0x140c9a7f
+8561 8562 2640 0x140c92bd
+8562 8563 2639 0x140c8b06
+8563 8564 2638 0x140c8350
+8564 8565 2637 0x140c7b99
+8565 8566 2636 0x140c73e2
+8566 8567 2635 0x140c6c40
+8567 8568 2634 0x140c6494
+8568 8569 2633 0x140c5cf3
+8569 8570 2632 0x140c5551
+8570 8571 2631 0x140c4dbb
+8571 8572 2630 0x140c4624
+8572 8573 2629 0x140c3e98
+8573 8574 2628 0x140c370c
+8574 8575 2627 0x140c2f8b
+8575 8576 2626 0x140c280a
+8576 8577 2625 0x140c2089
+8577 8578 2624 0x140c191d
+8578 8579 2623 0x140c11a7
+8579 8580 2622 0x140c0a3b
+8580 8581 2621 0x140c02cf
+8581 8582 2620 0x140bfb63
+8582 8583 2619 0x140bf402
+8583 8584 2618 0x140beca1
+8584 8585 2617 0x140be54b
+8585 8586 2616 0x140bddf5
+8586 8587 2615 0x140bd6a9
+8587 8588 2614 0x140bcf5e
+8588 8589 2613 0x140bc812
+8589 8590 2612 0x140bc0d2
+8590 8591 2611 0x140bb991
+8591 8592 2610 0x140bb25b
+8592 8593 2609 0x140bab2f
+8593 8594 2608 0x140ba3f9
+8594 8595 2607 0x140b9cce
+8595 8596 2606 0x140b95a3
+8596 8597 2605 0x140b8e82
+8597 8598 2604 0x140b8761
+8598 8599 2603 0x140b8041
+8599 8600 2602 0x140b792b
+8600 8601 2601 0x140b7215
+8601 8602 2600 0x140b6b0a
+8602 8603 2599 0x140b63ff
+8603 8604 2598 0x140b5cf4
+8604 8605 2597 0x140b55f3
+8605 8606 2596 0x140b4ef3
+8606 8607 2595 0x140b47fd
+8607 8608 2594 0x140b4108
+8608 8609 2593 0x140b3a1d
+8609 8610 2592 0x140b3327
+8610 8611 2591 0x140b2c47
+8611 8612 2590 0x140b255c
+8612 8613 2589 0x140b1e7c
+8613 8614 2588 0x140b179c
+8614 8615 2587 0x140b10c6
+8615 8616 2586 0x140b09f1
+8616 8617 2585 0x140b031b
+8617 8618 2584 0x140afc50
+8618 8619 2583 0x140af586
+8619 8620 2582 0x140aeebb
+8620 8621 2581 0x140ae7fb
+8621 8622 2580 0x140ae13b
+8622 8623 2579 0x140ada86
+8623 8624 2578 0x140ad3db
+8624 8625 2577 0x140acd26
+8625 8626 2576 0x140ac67b
+8626 8627 2575 0x140abfd1
+8627 8628 2574 0x140ab92c
+8628 8629 2573 0x140ab291
+8629 8630 2572 0x140aabfc
+8630 8631 2571 0x140aa56c
+8631 8632 2570 0x140a9ee2
+8632 8633 2569 0x140a9858
+8633 8634 2568 0x140a91d3
+8634 8635 2567 0x140a8b53
+8635 8636 2566 0x140a84d4
+8636 8637 2565 0x140a7e59
+8637 8638 2564 0x140a77e4
+8638 8639 2563 0x140a717a
+8639 8640 2562 0x140a6b0b
+8640 8641 2561 0x140a64a1
+8641 8642 2560 0x140a5e3c
+8642 8643 2559 0x140a57dd
+8643 8644 2558 0x140a517d
+8644 8645 2557 0x140a4b23
+8645 8646 2556 0x140a44c9
+8646 8647 2555 0x140a3e7a
+8647 8648 2554 0x140a382a
+8648 8649 2553 0x140a31db
+8649 8650 2552 0x140a2b97
+8650 8651 2551 0x140a2552
+8651 8652 2550 0x140a1f13
+8652 8653 2549 0x140a18d4
+8653 8654 2548 0x140a129a
+8654 8655 2547 0x140a0c70
+8655 8656 2546 0x140a063c
+8656 8657 2545 0x140a0012
+8657 8658 2544 0x1409f9e8
+8658 8659 2543 0x1409f3be
+8659 8660 2542 0x1409ed9f
+8660 8661 2541 0x1409e780
+8661 8662 2540 0x1409e167
+8662 8663 2539 0x1409db4d
+8663 8664 2538 0x1409d539
+8664 8665 2537 0x1409cf2a
+8665 8666 2536 0x1409c91b
+8666 8667 2535 0x1409c317
+8667 8668 2534 0x1409bd0d
+8668 8669 2533 0x1409b70e
+8669 8670 2532 0x1409b115
+8670 8671 2531 0x1409ab1c
+8671 8672 2530 0x1409a528
+8672 8673 2529 0x14099f33
+8673 8674 2528 0x14099945
+8674 8675 2527 0x1409935b
+8675 8676 2526 0x14098d72
+8676 8677 2525 0x1409878e
+8677 8678 2524 0x140981aa
+8678 8679 2523 0x14097bd1
+8679 8680 2522 0x140975f8
+8680 8681 2521 0x1409701e
+8681 8682 2520 0x14096a4b
+8682 8683 2519 0x1409647c
+8683 8684 2518 0x14095eb3
+8684 8685 2517 0x140958ef
+8685 8686 2516 0x1409532b
+8686 8687 2515 0x14094d6d
+8687 8688 2514 0x140947ae
+8688 8689 2513 0x140941f5
+8689 8690 2512 0x14093c3c
+8690 8691 2511 0x14093689
+8691 8692 2510 0x140930da
+8692 8693 2509 0x14092b31
+8693 8694 2508 0x14092589
+8694 8695 2507 0x14091fe0
+8695 8696 2506 0x14091a41
+8696 8697 2505 0x140914a3
+8697 8698 2504 0x14090f05
+8698 8699 2503 0x14090971
+8699 8700 2502 0x140903de
+8700 8701 2501 0x1408fe50
+8701 8702 2500 0x1408f8c7
+8702 8703 2499 0x1408f339
+8703 8704 2498 0x1408edb6
+8704 8705 2497 0x1408e832
+8705 8706 2496 0x1408e2b4
+8706 8707 2495 0x1408dd36
+8707 8708 2494 0x1408d7be
+8708 8709 2493 0x1408d24a
+8709 8710 2492 0x1408ccd7
+8710 8711 2491 0x1408c769
+8711 8712 2490 0x1408c1fb
+8712 8713 2489 0x1408bc98
+8713 8714 2488 0x1408b730
+8714 8715 2487 0x1408b1d2
+8715 8716 2486 0x1408ac79
+8716 8717 2485 0x1408a71b
+8717 8718 2484 0x1408a1c8
+8718 8719 2483 0x14089c70
+8719 8720 2482 0x14089722
+8720 8721 2481 0x140891d4
+8721 8722 2480 0x14088c8c
+8722 8723 2479 0x14088744
+8723 8724 2478 0x14088201
+8724 8725 2477 0x14087cbe
+8725 8726 2476 0x14087780
+8726 8727 2475 0x14087248
+8727 8728 2474 0x14086d10
+8728 8729 2473 0x140867dd
+8729 8730 2472 0x140862af
+8730 8731 2471 0x14085d87
+8731 8732 2470 0x1408585f
+8732 8733 2469 0x14085337
+8733 8734 2468 0x14084e14
+8734 8735 2467 0x140848f1
+8735 8736 2466 0x140843d4
+8736 8737 2465 0x14083ebc
+8737 8738 2464 0x140839a4
+8738 8739 2463 0x14083491
+8739 8740 2462 0x14082f7e
+8740 8741 2461 0x14082a71
+8741 8742 2460 0x14082569
+8742 8743 2459 0x14082061
+8743 8744 2458 0x14081b5f
+8744 8745 2457 0x1408165c
+8745 8746 2456 0x1408115f
+8746 8747 2455 0x14080c6c
+8747 8748 2454 0x14080775
+8748 8749 2453 0x1408027d
+8749 8750 2452 0x1407fd90
+8750 8751 2451 0x1407f89d
+8751 8752 2450 0x1407f3b5
+8752 8753 2449 0x1407eece
+8753 8754 2448 0x1407e9e6
+8754 8755 2447 0x1407e503
+8755 8756 2446 0x1407e026
+8756 8757 2445 0x1407db49
+8757 8758 2444 0x1407d672
+8758 8759 2443 0x1407d19a
+8759 8760 2442 0x1407ccc8
+8760 8761 2441 0x1407c7fb
+8761 8762 2440 0x1407c333
+8762 8763 2439 0x1407be66
+8763 8764 2438 0x1407b99f
+8764 8765 2437 0x1407b4dd
+8765 8766 2436 0x1407b01b
+8766 8767 2435 0x1407ab5e
+8767 8768 2434 0x1407a6a1
+8768 8769 2433 0x1407a1e9
+8769 8770 2432 0x14079d37
+8770 8771 2431 0x14079885
+8771 8772 2430 0x140793d3
+8772 8773 2429 0x14078f26
+8773 8774 2428 0x14078a7f
+8774 8775 2427 0x140785d8
+8775 8776 2426 0x14078136
+8776 8777 2425 0x14077c94
+8777 8778 2424 0x140777fd
+8778 8779 2423 0x14077360
+8779 8780 2422 0x14076ec9
+8780 8781 2421 0x14076a31
+8781 8782 2420 0x1407659f
+8782 8783 2419 0x1407610e
+8783 8784 2418 0x14075c81
+8784 8785 2417 0x140757fa
+8785 8786 2416 0x14075373
+8786 8787 2415 0x14074eec
+8787 8788 2414 0x14074a6f
+8788 8789 2413 0x140745ed
+8789 8790 2412 0x14074171
+8790 8791 2411 0x14073cfa
+8791 8792 2410 0x14073883
+8792 8793 2409 0x14073417
+8793 8794 2408 0x14072fa5
+8794 8795 2407 0x14072b39
+8795 8796 2406 0x140726cc
+8796 8797 2405 0x14072265
+8797 8798 2404 0x14071dfe
+8798 8799 2403 0x1407199d
+8799 8800 2402 0x1407153b
+8800 8801 2401 0x140710df
+8801 8802 2400 0x14070c83
+8802 8803 2399 0x1407082c
+8803 8804 2398 0x140703d5
+8804 8805 2397 0x1406ff83
+8805 8806 2396 0x1406fb32
+8806 8807 2395 0x1406f6e6
+8807 8808 2394 0x1406f29f
+8808 8809 2393 0x1406ee58
+8809 8810 2392 0x1406ea12
+8810 8811 2391 0x1406e5d0
+8811 8812 2390 0x1406e18f
+8812 8813 2389 0x1406dd53
+8813 8814 2388 0x1406d917
+8814 8815 2387 0x1406d4e0
+8815 8816 2386 0x1406d0a9
+8816 8817 2385 0x1406cc78
+8817 8818 2384 0x1406c847
+8818 8819 2383 0x1406c41b
+8819 8820 2382 0x1406bfef
+8820 8821 2381 0x1406bbc8
+8821 8822 2380 0x1406b7a2
+8822 8823 2379 0x1406b37b
+8823 8824 2378 0x1406af60
+8824 8825 2377 0x1406ab44
+8825 8826 2376 0x1406a728
+8826 8827 2375 0x1406a311
+8827 8828 2374 0x14069efb
+8828 8829 2373 0x14069ae4
+8829 8830 2372 0x140696d3
+8830 8831 2371 0x140692c2
+8831 8832 2370 0x14068eb7
+8832 8833 2369 0x14068ab0
+8833 8834 2368 0x140686a5
+8834 8835 2367 0x140682a4
+8835 8836 2366 0x14067e9d
+8836 8837 2365 0x14067aa2
+8837 8838 2364 0x140676a1
+8838 8839 2363 0x140672aa
+8839 8840 2362 0x14066eb4
+8840 8841 2361 0x14066abe
+8841 8842 2360 0x140666c7
+8842 8843 2359 0x140662d7
+8843 8844 2358 0x14065eeb
+8844 8845 2357 0x14065afa
+8845 8846 2356 0x14065714
+8846 8847 2355 0x1406532e
+8847 8848 2354 0x14064f48
+8848 8849 2353 0x14064b62
+8849 8850 2352 0x14064781
+8850 8851 2351 0x140643a5
+8851 8852 2350 0x14063fca
+8852 8853 2349 0x14063bee
+8853 8854 2348 0x1406381e
+8854 8855 2347 0x14063448
+8855 8856 2346 0x14063077
+8856 8857 2345 0x14062ca6
+8857 8858 2344 0x140628db
+8858 8859 2343 0x14062510
+8859 8860 2342 0x14062144
+8860 8861 2341 0x14061d7e
+8861 8862 2340 0x140619b8
+8862 8863 2339 0x140615f8
+8863 8864 2338 0x14061237
+8864 8865 2337 0x14060e7c
+8865 8866 2336 0x14060ac1
+8866 8867 2335 0x14060706
+8867 8868 2334 0x14060350
+8868 8869 2333 0x1405ff9a
+8869 8870 2332 0x1405fbef
+8870 8871 2331 0x1405f83e
+8871 8872 2330 0x1405f493
+8872 8873 2329 0x1405f0e3
+8873 8874 2328 0x1405ed3d
+8874 8875 2327 0x1405e997
+8875 8876 2326 0x1405e5f1
+8876 8877 2325 0x1405e24b
+8877 8878 2324 0x1405deab
+8878 8879 2323 0x1405db10
+8879 8880 2322 0x1405d775
+8880 8881 2321 0x1405d3da
+8881 8882 2320 0x1405d03f
+8882 8883 2319 0x1405cca9
+8883 8884 2318 0x1405c919
+8884 8885 2317 0x1405c589
+8885 8886 2316 0x1405c1fe
+8886 8887 2315 0x1405be6d
+8887 8888 2314 0x1405bae3
+8888 8889 2313 0x1405b75d
+8889 8890 2312 0x1405b3d2
+8890 8891 2311 0x1405b052
+8891 8892 2310 0x1405accc
+8892 8893 2309 0x1405a94c
+8893 8894 2308 0x1405a5d1
+8894 8895 2307 0x1405a251
+8895 8896 2306 0x14059ed6
+8896 8897 2305 0x14059b61
+8897 8898 2304 0x140597eb
+8898 8899 2303 0x14059476
+8899 8900 2302 0x14059106
+8900 8901 2301 0x14058d9b
+8901 8902 2300 0x14058a2b
+8902 8903 2299 0x140586c0
+8903 8904 2298 0x14058355
+8904 8905 2297 0x14057feb
+8905 8906 2296 0x14057c85
+8906 8907 2295 0x14057925
+8907 8908 2294 0x140575c0
+8908 8909 2293 0x14057260
+8909 8910 2292 0x14056f05
+8910 8911 2291 0x14056ba5
+8911 8912 2290 0x1405684b
+8912 8913 2289 0x140564f5
+8913 8914 2288 0x140561a0
+8914 8915 2287 0x14055e4b
+8915 8916 2286 0x14055b00
+8916 8917 2285 0x140557b6
+8917 8918 2284 0x1405546b
+8918 8919 2283 0x14055126
+8919 8920 2282 0x14054de1
+8920 8921 2281 0x14054a9c
+8921 8922 2280 0x14054759
+8922 8923 2279 0x1405441c
+8923 8924 2278 0x140540dc
+8924 8925 2277 0x14053da2
+8925 8926 2276 0x14053a68
+8926 8927 2275 0x14053730
+8927 8928 2274 0x140533fb
+8928 8929 2273 0x140530c6
+8929 8930 2272 0x14052d93
+8930 8931 2271 0x14052a66
+8931 8932 2270 0x14052739
+8932 8933 2269 0x1405240c
+8933 8934 2268 0x140520e2
+8934 8935 2267 0x14051dba
+8935 8936 2266 0x14051a93
+8936 8937 2265 0x1405176e
+8937 8938 2264 0x1405144b
+8938 8939 2263 0x14051129
+8939 8940 2262 0x14050e09
+8940 8941 2261 0x14050aed
+8941 8942 2260 0x140507d2
+8942 8943 2259 0x140504b8
+8943 8944 2258 0x140501a1
+8944 8945 2257 0x1404fe8c
+8945 8946 2256 0x1404fb79
+8946 8947 2255 0x1404f867
+8947 8948 2254 0x1404f558
+8948 8949 2253 0x1404f248
+8949 8950 2252 0x1404ef3e
+8950 8951 2251 0x1404ec31
+8951 8952 2250 0x1404e92a
+8952 8953 2249 0x1404e622
+8953 8954 2248 0x1404e31e
+8954 8955 2247 0x1404e019
+8955 8956 2246 0x1404dd17
+8956 8957 2245 0x1404da17
+8957 8958 2244 0x1404d71b
+8958 8959 2243 0x1404d41e
+8959 8960 2242 0x1404d124
+8960 8961 2241 0x1404ce2a
+8961 8962 2240 0x1404cb38
+8962 8963 2239 0x1404c843
+8963 8964 2238 0x1404c54f
+8964 8965 2237 0x1404c25f
+8965 8966 2236 0x1404bf70
+8966 8967 2235 0x1404bc81
+8967 8968 2234 0x1404b994
+8968 8969 2233 0x1404b6aa
+8969 8970 2232 0x1404b3c3
+8970 8971 2231 0x1404b0dc
+8971 8972 2230 0x1404adf7
+8972 8973 2229 0x1404ab12
+8973 8974 2228 0x1404a833
+8974 8975 2227 0x1404a554
+8975 8976 2226 0x1404a275
+8976 8977 2225 0x14049f9b
+8977 8978 2224 0x14049cc1
+8978 8979 2223 0x140499ea
+8979 8980 2222 0x14049713
+8980 8981 2221 0x1404943c
+8981 8982 2220 0x1404916a
+8982 8983 2219 0x14048e98
+8983 8984 2218 0x14048bc9
+8984 8985 2217 0x140488fa
+8985 8986 2216 0x1404862d
+8986 8987 2215 0x14048361
+8987 8988 2214 0x1404809a
+8988 8989 2213 0x14047dd3
+8989 8990 2212 0x14047b0c
+8990 8991 2211 0x14047848
+8991 8992 2210 0x14047588
+8992 8993 2209 0x140472c9
+8993 8994 2208 0x1404700a
+8994 8995 2207 0x14046d4b
+8995 8996 2206 0x14046a92
+8996 8997 2205 0x140467d8
+8997 8998 2204 0x1404651f
+8998 8999 2203 0x14046268
+8999 9000 2202 0x14045fb3
+9000 9001 2201 0x14045d02
+9001 9002 2200 0x14045a50
+9002 9003 2199 0x1404579f
+9003 9004 2198 0x140454f0
+9004 9005 2197 0x14045243
+9005 9006 2196 0x14044f9a
+9006 9007 2195 0x14044cf0
+9007 9008 2194 0x14044a4c
+9008 9009 2193 0x140447a5
+9009 9010 2192 0x14044501
+9010 9011 2191 0x14044260
+9011 9012 2190 0x14043fbe
+9012 9013 2189 0x14043d1f
+9013 9014 2188 0x14043a81
+9014 9015 2187 0x140437e4
+9015 9016 2186 0x1404354b
+9016 9017 2185 0x140432b2
+9017 9018 2184 0x1404301b
+9018 9019 2183 0x14042d84
+9019 9020 2182 0x14042af0
+9020 9021 2181 0x1404285c
+9021 9022 2180 0x140425cd
+9022 9023 2179 0x1404233e
+9023 9024 2178 0x140420b2
+9024 9025 2177 0x14041e26
+9025 9026 2176 0x14041b9a
+9026 9027 2175 0x14041911
+9027 9028 2174 0x1404168a
+9028 9029 2173 0x14041404
+9029 9030 2172 0x14041180
+9030 9031 2171 0x14040efe
+9031 9032 2170 0x14040c7d
+9032 9033 2169 0x140409fc
+9033 9034 2168 0x14040780
+9034 9035 2167 0x14040501
+9035 9036 2166 0x14040288
+9036 9037 2165 0x1404000f
+9037 9038 2164 0x1403fd98
+9038 9039 2163 0x1403fb22
+9039 9040 2162 0x1403f8ae
+9040 9041 2161 0x1403f63a
+9041 9042 2160 0x1403f3c9
+9042 9043 2159 0x1403f158
+9043 9044 2158 0x1403eee9
+9044 9045 2157 0x1403ec7d
+9045 9046 2156 0x1403ea11
+9046 9047 2155 0x1403e7a6
+9047 9048 2154 0x1403e53d
+9048 9049 2153 0x1403e2d6
+9049 9050 2152 0x1403e070
+9050 9051 2151 0x1403de0c
+9051 9052 2150 0x1403dbab
+9052 9053 2149 0x1403d94a
+9053 9054 2148 0x1403d6eb
+9054 9055 2147 0x1403d48d
+9055 9056 2146 0x1403d231
+9056 9057 2145 0x1403cfd5
+9057 9058 2144 0x1403cd7a
+9058 9059 2143 0x1403cb23
+9059 9060 2142 0x1403c8ca
+9060 9061 2141 0x1403c677
+9061 9062 2140 0x1403c423
+9062 9063 2139 0x1403c1cf
+9063 9064 2138 0x1403bf7e
+9064 9065 2137 0x1403bd30
+9065 9066 2136 0x1403bae2
+9066 9067 2135 0x1403b893
+9067 9068 2134 0x1403b648
+9068 9069 2133 0x1403b401
+9069 9070 2132 0x1403b1b8
+9070 9071 2131 0x1403af72
+9071 9072 2130 0x1403ad2c
+9072 9073 2129 0x1403aae8
+9073 9074 2128 0x1403a8a5
+9074 9075 2127 0x1403a664
+9075 9076 2126 0x1403a423
+9076 9077 2125 0x1403a1e5
+9077 9078 2124 0x14039fa7
+9078 9079 2123 0x14039d6b
+9079 9080 2122 0x14039b2f
+9080 9081 2121 0x140398f7
+9081 9082 2120 0x140396c0
+9082 9083 2119 0x1403948a
+9083 9084 2118 0x14039257
+9084 9085 2117 0x14039023
+9085 9086 2116 0x14038df3
+9086 9087 2115 0x14038bc2
+9087 9088 2114 0x14038991
+9088 9089 2113 0x14038763
+9089 9090 2112 0x14038537
+9090 9091 2111 0x1403830c
+9091 9092 2110 0x140380e1
+9092 9093 2109 0x14037eb8
+9093 9094 2108 0x14037c92
+9094 9095 2107 0x14037a6c
+9095 9096 2106 0x14037846
+9096 9097 2105 0x14037622
+9097 9098 2104 0x14037402
+9098 9099 2103 0x140371e1
+9099 9100 2102 0x14036fc3
+9100 9101 2101 0x14036da5
+9101 9102 2100 0x14036b8a
+9102 9103 2099 0x1403696e
+9103 9104 2098 0x14036753
+9104 9105 2097 0x1403653a
+9105 9106 2096 0x14036324
+9106 9107 2095 0x1403610e
+9107 9108 2094 0x14035ef8
+9108 9109 2093 0x14035ce5
+9109 9110 2092 0x14035ad2
+9110 9111 2091 0x140358c1
+9111 9112 2090 0x140356b3
+9112 9113 2089 0x140354a3
+9113 9114 2088 0x14035297
+9114 9115 2087 0x1403508c
+9115 9116 2086 0x14034e81
+9116 9117 2085 0x14034c78
+9117 9118 2084 0x14034a70
+9118 9119 2083 0x1403486a
+9119 9120 2082 0x14034664
+9120 9121 2081 0x14034461
+9121 9122 2080 0x1403425e
+9122 9123 2079 0x1403405b
+9123 9124 2078 0x14033e5a
+9124 9125 2077 0x14033c5c
+9125 9126 2076 0x14033a5c
+9126 9127 2075 0x14033861
+9127 9128 2074 0x14033666
+9128 9129 2073 0x1403346a
+9129 9130 2072 0x14033272
+9130 9131 2071 0x1403307c
+9131 9132 2070 0x14032e84
+9132 9133 2069 0x14032c8e
+9133 9134 2068 0x14032a9b
+9134 9135 2067 0x140328a8
+9135 9136 2066 0x140326b5
+9136 9137 2065 0x140324c4
+9137 9138 2064 0x140322d4
+9138 9139 2063 0x140320e6
+9139 9140 2062 0x14031ef9
+9140 9141 2061 0x14031d0e
+9141 9142 2060 0x14031b23
+9142 9143 2059 0x14031937
+9143 9144 2058 0x1403174f
+9144 9145 2057 0x14031569
+9145 9146 2056 0x14031384
+9146 9147 2055 0x1403119e
+9147 9148 2054 0x14030fbb
+9148 9149 2053 0x14030dd8
+9149 9150 2052 0x14030bf8
+9150 9151 2051 0x14030a18
+9151 9152 2050 0x14030837
+9152 9153 2049 0x1403065a
+9153 9154 2048 0x1403047c
+9154 9155 2047 0x140302a1
+9155 9156 2046 0x140300c6
+9156 9157 2045 0x1402feeb
+9157 9158 2044 0x1402fd13
+9158 9159 2043 0x1402fb3b
+9159 9160 2042 0x1402f965
+9160 9161 2041 0x1402f792
+9161 9162 2040 0x1402f5bf
+9162 9163 2039 0x1402f3ea
+9163 9164 2038 0x1402f21a
+9164 9165 2037 0x1402f049
+9165 9166 2036 0x1402ee79
+9166 9167 2035 0x1402eca9
+9167 9168 2034 0x1402eadc
+9168 9169 2033 0x1402e911
+9169 9170 2032 0x1402e746
+9170 9171 2031 0x1402e57b
+9171 9172 2030 0x1402e3b0
+9172 9173 2029 0x1402e1e8
+9173 9174 2028 0x1402e023
+9174 9175 2027 0x1402de5d
+9175 9176 2026 0x1402dc9a
+9176 9177 2025 0x1402dad5
+9177 9178 2024 0x1402d912
+9178 9179 2023 0x1402d752
+9179 9180 2022 0x1402d58f
+9180 9181 2021 0x1402d3d2
+9181 9182 2020 0x1402d212
+9182 9183 2019 0x1402d054
+9183 9184 2018 0x1402ce97
+9184 9185 2017 0x1402ccdc
+9185 9186 2016 0x1402cb21
+9186 9187 2015 0x1402c967
+9187 9188 2014 0x1402c7af
+9188 9189 2013 0x1402c5f9
+9189 9190 2012 0x1402c441
+9190 9191 2011 0x1402c28c
+9191 9192 2010 0x1402c0d9
+9192 9193 2009 0x1402bf27
+9193 9194 2008 0x1402bd74
+9194 9195 2007 0x1402bbc1
+9195 9196 2006 0x1402ba11
+9196 9197 2005 0x1402b861
+9197 9198 2004 0x1402b6b4
+9198 9199 2003 0x1402b507
+9199 9200 2002 0x1402b359
+9200 9201 2001 0x1402b1af
+9201 9202 2000 0x1402b004
+9202 9203 1999 0x1402ae5b
+9203 9204 1998 0x1402acb5
+9204 9205 1997 0x1402ab10
+9205 9206 1996 0x1402a96b
+9206 9207 1995 0x1402a7ca
+9207 9208 1994 0x1402a628
+9208 9209 1993 0x1402a487
+9209 9210 1992 0x1402a2e6
+9210 9211 1991 0x1402a147
+9211 9212 1990 0x14029fa9
+9212 9213 1989 0x14029e0c
+9213 9214 1988 0x14029c6e
+9214 9215 1987 0x14029ad4
+9215 9216 1986 0x14029939
+9216 9217 1985 0x1402979f
+9217 9218 1984 0x14029607
+9218 9219 1983 0x1402946f
+9219 9220 1982 0x140292d8
+9220 9221 1981 0x14029142
+9221 9222 1980 0x14028fb0
+9222 9223 1979 0x14028e1c
+9223 9224 1978 0x14028c89
+9224 9225 1977 0x14028af7
+9225 9226 1976 0x14028966
+9226 9227 1975 0x140287d6
+9227 9228 1974 0x14028646
+9228 9229 1973 0x140284b8
+9229 9230 1972 0x1402832a
+9230 9231 1971 0x1402819f
+9231 9232 1970 0x14028013
+9232 9233 1969 0x14027e88
+9233 9234 1968 0x14027cff
+9234 9235 1967 0x14027b76
+9235 9236 1966 0x140279ee
+9236 9237 1965 0x14027868
+9237 9238 1964 0x140276e4
+9238 9239 1963 0x1402755f
+9239 9240 1962 0x140273dc
+9240 9241 1961 0x14027258
+9241 9242 1960 0x140270d5
+9242 9243 1959 0x14026f54
+9243 9244 1958 0x14026dd5
+9244 9245 1957 0x14026c55
+9245 9246 1956 0x14026ad7
+9246 9247 1955 0x14026958
+9247 9248 1954 0x140267db
+9248 9249 1953 0x14026660
+9249 9250 1952 0x140264e5
+9250 9251 1951 0x1402636b
+9251 9252 1950 0x140261f2
+9252 9253 1949 0x1402607c
+9253 9254 1948 0x14025f04
+9254 9255 1947 0x14025d8e
+9255 9256 1946 0x14025c17
+9256 9257 1945 0x14025aa4
+9257 9258 1944 0x1402592f
+9258 9259 1943 0x140257bc
+9259 9260 1942 0x1402564a
+9260 9261 1941 0x140254d9
+9261 9262 1940 0x1402536a
+9262 9263 1939 0x140251fa
+9263 9264 1938 0x1402508a
+9264 9265 1937 0x14024f1d
+9265 9266 1936 0x14024db1
+9266 9267 1935 0x14024c45
+9267 9268 1934 0x14024adb
+9268 9269 1933 0x14024971
+9269 9270 1932 0x14024808
+9270 9271 1931 0x140246a0
+9271 9272 1930 0x14024539
+9272 9273 1929 0x140243d1
+9273 9274 1928 0x1402426b
+9274 9275 1927 0x14024106
+9275 9276 1926 0x14023fa2
+9276 9277 1925 0x14023e3f
+9277 9278 1924 0x14023cdd
+9278 9279 1923 0x14023b7a
+9279 9280 1922 0x14023a1a
+9280 9281 1921 0x140238ba
+9281 9282 1920 0x1402375c
+9282 9283 1919 0x140235fc
+9283 9284 1918 0x140234a1
+9284 9285 1917 0x14023344
+9285 9286 1916 0x140231e9
+9286 9287 1915 0x1402308f
+9287 9288 1914 0x14022f34
+9288 9289 1913 0x14022ddc
+9289 9290 1912 0x14022c83
+9290 9291 1911 0x14022b2b
+9291 9292 1910 0x140229d4
+9292 9293 1909 0x1402287f
+9293 9294 1908 0x14022729
+9294 9295 1907 0x140225d6
+9295 9296 1906 0x14022482
+9296 9297 1905 0x14022330
+9297 9298 1904 0x140221de
+9298 9299 1903 0x1402208f
+9299 9300 1902 0x14021f3e
+9300 9301 1901 0x14021dee
+9301 9302 1900 0x14021ca0
+9302 9303 1899 0x14021b52
+9303 9304 1898 0x14021a06
+9304 9305 1897 0x140218ba
+9305 9306 1896 0x1402176f
+9306 9307 1895 0x14021623
+9307 9308 1894 0x140214db
+9308 9309 1893 0x14021391
+9309 9310 1892 0x14021249
+9310 9311 1891 0x14021102
+9311 9312 1890 0x14020fbc
+9312 9313 1889 0x14020e76
+9313 9314 1888 0x14020d33
+9314 9315 1887 0x14020bee
+9315 9316 1886 0x14020aab
+9316 9317 1885 0x14020969
+9317 9318 1884 0x14020827
+9318 9319 1883 0x140206e5
+9319 9320 1882 0x140205a5
+9320 9321 1881 0x14020466
+9321 9322 1880 0x14020327
+9322 9323 1879 0x140201e9
+9323 9324 1878 0x140200ac
+9324 9325 1877 0x1401ff70
+9325 9326 1876 0x1401fe34
+9326 9327 1875 0x1401fcf9
+9327 9328 1874 0x1401fbbf
+9328 9329 1873 0x1401fa85
+9329 9330 1872 0x1401f94f
+9330 9331 1871 0x1401f817
+9331 9332 1870 0x1401f6df
+9332 9333 1869 0x1401f5a9
+9333 9334 1868 0x1401f474
+9334 9335 1867 0x1401f33f
+9335 9336 1866 0x1401f20c
+9336 9337 1865 0x1401f0d9
+9337 9338 1864 0x1401efa5
+9338 9339 1863 0x1401ee73
+9339 9340 1862 0x1401ed43
+9340 9341 1861 0x1401ec12
+9341 9342 1860 0x1401eae3
+9342 9343 1859 0x1401e9b4
+9343 9344 1858 0x1401e886
+9344 9345 1857 0x1401e75a
+9345 9346 1856 0x1401e62d
+9346 9347 1855 0x1401e502
+9347 9348 1854 0x1401e3d7
+9348 9349 1853 0x1401e2ad
+9349 9350 1852 0x1401e183
+9350 9351 1851 0x1401e059
+9351 9352 1850 0x1401df32
+9352 9353 1849 0x1401de09
+9353 9354 1848 0x1401dce4
+9354 9355 1847 0x1401dbbd
+9355 9356 1846 0x1401da98
+9356 9357 1845 0x1401d974
+9357 9358 1844 0x1401d84f
+9358 9359 1843 0x1401d72c
+9359 9360 1842 0x1401d60a
+9360 9361 1841 0x1401d4e8
+9361 9362 1840 0x1401d3c8
+9362 9363 1839 0x1401d2a7
+9363 9364 1838 0x1401d188
+9364 9365 1837 0x1401d069
+9365 9366 1836 0x1401cf4a
+9366 9367 1835 0x1401ce2c
+9367 9368 1834 0x1401cd10
+9368 9369 1833 0x1401cbf4
+9369 9370 1832 0x1401cad8
+9370 9371 1831 0x1401c9bd
+9371 9372 1830 0x1401c8a4
+9372 9373 1829 0x1401c78a
+9373 9374 1828 0x1401c672
+9374 9375 1827 0x1401c559
+9375 9376 1826 0x1401c443
+9376 9377 1825 0x1401c32c
+9377 9378 1824 0x1401c217
+9378 9379 1823 0x1401c101
+9379 9380 1822 0x1401bfed
+9380 9381 1821 0x1401bed8
+9381 9382 1820 0x1401bdc5
+9382 9383 1819 0x1401bcb2
+9383 9384 1818 0x1401bba0
+9384 9385 1817 0x1401ba8f
+9385 9386 1816 0x1401b97e
+9386 9387 1815 0x1401b86e
+9387 9388 1814 0x1401b75f
+9388 9389 1813 0x1401b650
+9389 9390 1812 0x1401b542
+9390 9391 1811 0x1401b436
+9391 9392 1810 0x1401b32a
+9392 9393 1809 0x1401b21d
+9393 9394 1808 0x1401b112
+9394 9395 1807 0x1401b007
+9395 9396 1806 0x1401aefc
+9396 9397 1805 0x1401adf3
+9397 9398 1804 0x1401aceb
+9398 9399 1803 0x1401abe2
+9399 9400 1802 0x1401aadb
+9400 9401 1801 0x1401a9d4
+9401 9402 1800 0x1401a8cd
+9402 9403 1799 0x1401a7c8
+9403 9404 1798 0x1401a6c3
+9404 9405 1797 0x1401a5bf
+9405 9406 1796 0x1401a4bc
+9406 9407 1795 0x1401a3b9
+9407 9408 1794 0x1401a2b6
+9408 9409 1793 0x1401a1b5
+9409 9410 1792 0x1401a0b3
+9410 9411 1791 0x14019fb3
+9411 9412 1790 0x14019eb3
+9412 9413 1789 0x14019db4
+9413 9414 1788 0x14019cb5
+9414 9415 1787 0x14019bb6
+9415 9416 1786 0x14019ab8
+9416 9417 1785 0x140199bc
+9417 9418 1784 0x140198c0
+9418 9419 1783 0x140197c4
+9419 9420 1782 0x140196c9
+9420 9421 1781 0x140195ce
+9421 9422 1780 0x140194d6
+9422 9423 1779 0x140193dc
+9423 9424 1778 0x140192e3
+9424 9425 1777 0x140191ec
+9425 9426 1776 0x140190f4
+9426 9427 1775 0x14018ffd
+9427 9428 1774 0x14018f06
+9428 9429 1773 0x14018e10
+9429 9430 1772 0x14018d1b
+9430 9431 1771 0x14018c27
+9431 9432 1770 0x14018b33
+9432 9433 1769 0x14018a40
+9433 9434 1768 0x1401894d
+9434 9435 1767 0x1401885a
+9435 9436 1766 0x14018769
+9436 9437 1765 0x14018678
+9437 9438 1764 0x14018588
+9438 9439 1763 0x14018498
+9439 9440 1762 0x140183a9
+9440 9441 1761 0x140182bb
+9441 9442 1760 0x140181cc
+9442 9443 1759 0x140180de
+9443 9444 1758 0x14017ff1
+9444 9445 1757 0x14017f05
+9445 9446 1756 0x14017e19
+9446 9447 1755 0x14017d2c
+9447 9448 1754 0x14017c42
+9448 9449 1753 0x14017b58
+9449 9450 1752 0x14017a6d
+9450 9451 1751 0x14017985
+9451 9452 1750 0x1401789c
+9452 9453 1749 0x140177b5
+9453 9454 1748 0x140176cd
+9454 9455 1747 0x140175e6
+9455 9456 1746 0x14017500
+9456 9457 1745 0x1401741a
+9457 9458 1744 0x14017335
+9458 9459 1743 0x1401724f
+9459 9460 1742 0x1401716b
+9460 9461 1741 0x14017087
+9461 9462 1740 0x14016fa4
+9462 9463 1739 0x14016ec2
+9463 9464 1738 0x14016ddf
+9464 9465 1737 0x14016cfe
+9465 9466 1736 0x14016c1c
+9466 9467 1735 0x14016b3c
+9467 9468 1734 0x14016a5d
+9468 9469 1733 0x1401697d
+9469 9470 1732 0x1401689f
+9470 9471 1731 0x140167c0
+9471 9472 1730 0x140166e1
+9472 9473 1729 0x14016604
+9473 9474 1728 0x14016526
+9474 9475 1727 0x1401644a
+9475 9476 1726 0x1401636e
+9476 9477 1725 0x14016292
+9477 9478 1724 0x140161b8
+9478 9479 1723 0x140160dd
+9479 9480 1722 0x14016004
+9480 9481 1721 0x14015f2a
+9481 9482 1720 0x14015e51
+9482 9483 1719 0x14015d79
+9483 9484 1718 0x14015ca1
+9484 9485 1717 0x14015bca
+9485 9486 1716 0x14015af2
+9486 9487 1715 0x14015a1c
+9487 9488 1714 0x14015946
+9488 9489 1713 0x14015871
+9489 9490 1712 0x1401579c
+9490 9491 1711 0x140156c8
+9491 9492 1710 0x140155f5
+9492 9493 1709 0x14015523
+9493 9494 1708 0x14015451
+9494 9495 1707 0x14015380
+9495 9496 1706 0x140152af
+9496 9497 1705 0x140151df
+9497 9498 1704 0x1401510f
+9498 9499 1703 0x14015040
+9499 9500 1702 0x14014f71
+9500 9501 1701 0x14014ea3
+9501 9502 1700 0x14014dd5
+9502 9503 1699 0x14014d07
+9503 9504 1698 0x14014c3a
+9504 9505 1697 0x14014b6e
+9505 9506 1696 0x14014aa1
+9506 9507 1695 0x140149d6
+9507 9508 1694 0x1401490b
+9508 9509 1693 0x14014840
+9509 9510 1692 0x14014776
+9510 9511 1691 0x140146ac
+9511 9512 1690 0x140145e3
+9512 9513 1689 0x1401451b
+9513 9514 1688 0x14014453
+9514 9515 1687 0x1401438b
+9515 9516 1686 0x140142c3
+9516 9517 1685 0x140141fd
+9517 9518 1684 0x14014136
+9518 9519 1683 0x14014070
+9519 9520 1682 0x14013fab
+9520 9521 1681 0x14013ee6
+9521 9522 1680 0x14013e21
+9522 9523 1679 0x14013d5e
+9523 9524 1678 0x14013c9a
+9524 9525 1677 0x14013bd7
+9525 9526 1676 0x14013b14
+9526 9527 1675 0x14013a51
+9527 9528 1674 0x14013990
+9528 9529 1673 0x140138cf
+9529 9530 1672 0x1401380f
+9530 9531 1671 0x1401374e
+9531 9532 1670 0x1401368e
+9532 9533 1669 0x140135ce
+9533 9534 1668 0x1401350f
+9534 9535 1667 0x14013451
+9535 9536 1666 0x14013393
+9536 9537 1665 0x140132d5
+9537 9538 1664 0x14013218
+9538 9539 1663 0x1401315b
+9539 9540 1662 0x1401309f
+9540 9541 1661 0x14012fe3
+9541 9542 1660 0x14012f27
+9542 9543 1659 0x14012e6c
+9543 9544 1658 0x14012db2
+9544 9545 1657 0x14012cf8
+9545 9546 1656 0x14012c3f
+9546 9547 1655 0x14012b85
+9547 9548 1654 0x14012acd
+9548 9549 1653 0x14012a14
+9549 9550 1652 0x1401295d
+9550 9551 1651 0x140128a5
+9551 9552 1650 0x140127ee
+9552 9553 1649 0x14012737
+9553 9554 1648 0x14012681
+9554 9555 1647 0x140125cb
+9555 9556 1646 0x14012516
+9556 9557 1645 0x14012461
+9557 9558 1644 0x140123ad
+9558 9559 1643 0x140122f9
+9559 9560 1642 0x14012246
+9560 9561 1641 0x14012193
+9561 9562 1640 0x140120e0
+9562 9563 1639 0x1401202d
+9563 9564 1638 0x14011f7c
+9564 9565 1637 0x14011ecb
+9565 9566 1636 0x14011e1a
+9566 9567 1635 0x14011d69
+9567 9568 1634 0x14011cb9
+9568 9569 1633 0x14011c09
+9569 9570 1632 0x14011b5a
+9570 9571 1631 0x14011aab
+9571 9572 1630 0x140119fc
+9572 9573 1629 0x1401194e
+9573 9574 1628 0x140118a0
+9574 9575 1627 0x140117f4
+9575 9576 1626 0x14011747
+9576 9577 1625 0x1401169b
+9577 9578 1624 0x140115ef
+9578 9579 1623 0x14011544
+9579 9580 1622 0x14011499
+9580 9581 1621 0x140113ed
+9581 9582 1620 0x14011343
+9582 9583 1619 0x14011299
+9583 9584 1618 0x140111f0
+9584 9585 1617 0x14011147
+9585 9586 1616 0x1401109e
+9586 9587 1615 0x14010ff6
+9587 9588 1614 0x14010f4e
+9588 9589 1613 0x14010ea6
+9589 9590 1612 0x14010dff
+9590 9591 1611 0x14010d59
+9591 9592 1610 0x14010cb3
+9592 9593 1609 0x14010c0d
+9593 9594 1608 0x14010b68
+9594 9595 1607 0x14010ac3
+9595 9596 1606 0x14010a1e
+9596 9597 1605 0x1401097a
+9597 9598 1604 0x140108d6
+9598 9599 1603 0x14010832
+9599 9600 1602 0x1401078f
+9600 9601 1601 0x140106ed
+9601 9602 1600 0x1401064b
+9602 9603 1599 0x140105a9
+9603 9604 1598 0x14010507
+9604 9605 1597 0x14010466
+9605 9606 1596 0x140103c6
+9606 9607 1595 0x14010326
+9607 9608 1594 0x14010286
+9608 9609 1593 0x140101e6
+9609 9610 1592 0x14010147
+9610 9611 1591 0x140100a8
+9611 9612 1590 0x1401000a
+9612 9613 1589 0x1400ff6c
+9613 9614 1588 0x1400fece
+9614 9615 1587 0x1400fe31
+9615 9616 1586 0x1400fd94
+9616 9617 1585 0x1400fcf8
+9617 9618 1584 0x1400fc5c
+9618 9619 1583 0x1400fbc0
+9619 9620 1582 0x1400fb25
+9620 9621 1581 0x1400fa8b
+9621 9622 1580 0x1400f9f0
+9622 9623 1579 0x1400f955
+9623 9624 1578 0x1400f8bc
+9624 9625 1577 0x1400f822
+9625 9626 1576 0x1400f789
+9626 9627 1575 0x1400f6f1
+9627 9628 1574 0x1400f658
+9628 9629 1573 0x1400f5c0
+9629 9630 1572 0x1400f529
+9630 9631 1571 0x1400f492
+9631 9632 1570 0x1400f3fb
+9632 9633 1569 0x1400f364
+9633 9634 1568 0x1400f2ce
+9634 9635 1567 0x1400f238
+9635 9636 1566 0x1400f1a3
+9636 9637 1565 0x1400f10e
+9637 9638 1564 0x1400f07a
+9638 9639 1563 0x1400efe6
+9639 9640 1562 0x1400ef52
+9640 9641 1561 0x1400eebe
+9641 9642 1560 0x1400ee2b
+9642 9643 1559 0x1400ed98
+9643 9644 1558 0x1400ed05
+9644 9645 1557 0x1400ec74
+9645 9646 1556 0x1400ebe2
+9646 9647 1555 0x1400eb50
+9647 9648 1554 0x1400eabf
+9648 9649 1553 0x1400ea2e
+9649 9650 1552 0x1400e99e
+9650 9651 1551 0x1400e90e
+9651 9652 1550 0x1400e87f
+9652 9653 1549 0x1400e7f0
+9653 9654 1548 0x1400e760
+9654 9655 1547 0x1400e6d2
+9655 9656 1546 0x1400e644
+9656 9657 1545 0x1400e5b6
+9657 9658 1544 0x1400e528
+9658 9659 1543 0x1400e49b
+9659 9660 1542 0x1400e40e
+9660 9661 1541 0x1400e382
+9661 9662 1540 0x1400e2f5
+9662 9663 1539 0x1400e269
+9663 9664 1538 0x1400e1de
+9664 9665 1537 0x1400e153
+9665 9666 1536 0x1400e0c8
+9666 9667 1535 0x1400e03e
+9667 9668 1534 0x1400dfb4
+9668 9669 1533 0x1400df2a
+9669 9670 1532 0x1400dea1
+9670 9671 1531 0x1400de17
+9671 9672 1530 0x1400dd8e
+9672 9673 1529 0x1400dd06
+9673 9674 1528 0x1400dc7e
+9674 9675 1527 0x1400dbf6
+9675 9676 1526 0x1400db6e
+9676 9677 1525 0x1400dae7
+9677 9678 1524 0x1400da61
+9678 9679 1523 0x1400d9da
+9679 9680 1522 0x1400d954
+9680 9681 1521 0x1400d8ce
+9681 9682 1520 0x1400d849
+9682 9683 1519 0x1400d7c4
+9683 9684 1518 0x1400d73f
+9684 9685 1517 0x1400d6ba
+9685 9686 1516 0x1400d636
+9686 9687 1515 0x1400d5b2
+9687 9688 1514 0x1400d52e
+9688 9689 1513 0x1400d4ab
+9689 9690 1512 0x1400d428
+9690 9691 1511 0x1400d3a5
+9691 9692 1510 0x1400d323
+9692 9693 1509 0x1400d2a1
+9693 9694 1508 0x1400d220
+9694 9695 1507 0x1400d19e
+9695 9696 1506 0x1400d11d
+9696 9697 1505 0x1400d09c
+9697 9698 1504 0x1400d01c
+9698 9699 1503 0x1400cf9c
+9699 9700 1502 0x1400cf1c
+9700 9701 1501 0x1400ce9d
+9701 9702 1500 0x1400ce1d
+9702 9703 1499 0x1400cd9e
+9703 9704 1498 0x1400cd20
+9704 9705 1497 0x1400cca1
+9705 9706 1496 0x1400cc23
+9706 9707 1495 0x1400cba6
+9707 9708 1494 0x1400cb28
+9708 9709 1493 0x1400caab
+9709 9710 1492 0x1400ca2e
+9710 9711 1491 0x1400c9b2
+9711 9712 1490 0x1400c935
+9712 9713 1489 0x1400c8ba
+9713 9714 1488 0x1400c83f
+9714 9715 1487 0x1400c7c3
+9715 9716 1486 0x1400c748
+9716 9717 1485 0x1400c6ce
+9717 9718 1484 0x1400c653
+9718 9719 1483 0x1400c5d9
+9719 9720 1482 0x1400c55f
+9720 9721 1481 0x1400c4e5
+9721 9722 1480 0x1400c46c
+9722 9723 1479 0x1400c3f3
+9723 9724 1478 0x1400c37a
+9724 9725 1477 0x1400c302
+9725 9726 1476 0x1400c28a
+9726 9727 1475 0x1400c212
+9727 9728 1474 0x1400c19b
+9728 9729 1473 0x1400c124
+9729 9730 1472 0x1400c0ad
+9730 9731 1471 0x1400c036
+9731 9732 1470 0x1400bfc0
+9732 9733 1469 0x1400bf4a
+9733 9734 1468 0x1400bed4
+9734 9735 1467 0x1400be5e
+9735 9736 1466 0x1400bde9
+9736 9737 1465 0x1400bd74
+9737 9738 1464 0x1400bcff
+9738 9739 1463 0x1400bc8b
+9739 9740 1462 0x1400bc16
+9740 9741 1461 0x1400bba3
+9741 9742 1460 0x1400bb2f
+9742 9743 1459 0x1400babb
+9743 9744 1458 0x1400ba49
+9744 9745 1457 0x1400b9d6
+9745 9746 1456 0x1400b963
+9746 9747 1455 0x1400b8f1
+9747 9748 1454 0x1400b87f
+9748 9749 1453 0x1400b80d
+9749 9750 1452 0x1400b79c
+9750 9751 1451 0x1400b72a
+9751 9752 1450 0x1400b6ba
+9752 9753 1449 0x1400b649
+9753 9754 1448 0x1400b5d8
+9754 9755 1447 0x1400b568
+9755 9756 1446 0x1400b4f8
+9756 9757 1445 0x1400b489
+9757 9758 1444 0x1400b419
+9758 9759 1443 0x1400b3ab
+9759 9760 1442 0x1400b33b
+9760 9761 1441 0x1400b2cd
+9761 9762 1440 0x1400b25f
+9762 9763 1439 0x1400b1f1
+9763 9764 1438 0x1400b183
+9764 9765 1437 0x1400b115
+9765 9766 1436 0x1400b0a7
+9766 9767 1435 0x1400b03b
+9767 9768 1434 0x1400afcd
+9768 9769 1433 0x1400af61
+9769 9770 1432 0x1400aef5
+9770 9771 1431 0x1400ae89
+9771 9772 1430 0x1400ae1d
+9772 9773 1429 0x1400adb1
+9773 9774 1428 0x1400ad45
+9774 9775 1427 0x1400acdb
+9775 9776 1426 0x1400ac70
+9776 9777 1425 0x1400ac05
+9777 9778 1424 0x1400ab9b
+9778 9779 1423 0x1400ab31
+9779 9780 1422 0x1400aac8
+9780 9781 1421 0x1400aa5f
+9781 9782 1420 0x1400a9f6
+9782 9783 1419 0x1400a98e
+9783 9784 1418 0x1400a925
+9784 9785 1417 0x1400a8bd
+9785 9786 1416 0x1400a855
+9786 9787 1415 0x1400a7ee
+9787 9788 1414 0x1400a787
+9788 9789 1413 0x1400a71f
+9789 9790 1412 0x1400a6b9
+9790 9791 1411 0x1400a652
+9791 9792 1410 0x1400a5ec
+9792 9793 1409 0x1400a586
+9793 9794 1408 0x1400a520
+9794 9795 1407 0x1400a4ba
+9795 9796 1406 0x1400a455
+9796 9797 1405 0x1400a3f0
+9797 9798 1404 0x1400a38b
+9798 9799 1403 0x1400a326
+9799 9800 1402 0x1400a2c1
+9800 9801 1401 0x1400a25d
+9801 9802 1400 0x1400a1f9
+9802 9803 1399 0x1400a195
+9803 9804 1398 0x1400a132
+9804 9805 1397 0x1400a0cf
+9805 9806 1396 0x1400a06c
+9806 9807 1395 0x1400a009
+9807 9808 1394 0x14009fa6
+9808 9809 1393 0x14009f44
+9809 9810 1392 0x14009ee2
+9810 9811 1391 0x14009e80
+9811 9812 1390 0x14009e1e
+9812 9813 1389 0x14009dbc
+9813 9814 1388 0x14009d5b
+9814 9815 1387 0x14009cfa
+9815 9816 1386 0x14009c99
+9816 9817 1385 0x14009c39
+9817 9818 1384 0x14009bd8
+9818 9819 1383 0x14009b78
+9819 9820 1382 0x14009b19
+9820 9821 1381 0x14009ab9
+9821 9822 1380 0x14009a5a
+9822 9823 1379 0x140099fb
+9823 9824 1378 0x1400999c
+9824 9825 1377 0x1400993d
+9825 9826 1376 0x140098de
+9826 9827 1375 0x14009880
+9827 9828 1374 0x14009822
+9828 9829 1373 0x140097c4
+9829 9830 1372 0x14009767
+9830 9831 1371 0x14009709
+9831 9832 1370 0x140096ac
+9832 9833 1369 0x1400964f
+9833 9834 1368 0x140095f2
+9834 9835 1367 0x14009596
+9835 9836 1366 0x1400953a
+9836 9837 1365 0x140094de
+9837 9838 1364 0x14009482
+9838 9839 1363 0x14009426
+9839 9840 1362 0x140093cb
+9840 9841 1361 0x14009370
+9841 9842 1360 0x14009315
+9842 9843 1359 0x140092ba
+9843 9844 1358 0x1400925f
+9844 9845 1357 0x14009205
+9845 9846 1356 0x140091ab
+9846 9847 1355 0x14009151
+9847 9848 1354 0x140090f8
+9848 9849 1353 0x1400909e
+9849 9850 1352 0x14009045
+9850 9851 1351 0x14008fec
+9851 9852 1350 0x14008f93
+9852 9853 1349 0x14008f3a
+9853 9854 1348 0x14008ee2
+9854 9855 1347 0x14008e8a
+9855 9856 1346 0x14008e32
+9856 9857 1345 0x14008dda
+9857 9858 1344 0x14008d83
+9858 9859 1343 0x14008d2b
+9859 9860 1342 0x14008cd4
+9860 9861 1341 0x14008c7d
+9861 9862 1340 0x14008c27
+9862 9863 1339 0x14008bd0
+9863 9864 1338 0x14008b7a
+9864 9865 1337 0x14008b24
+9865 9866 1336 0x14008ace
+9866 9867 1335 0x14008a79
+9867 9868 1334 0x14008a23
+9868 9869 1333 0x140089ce
+9869 9870 1332 0x14008979
+9870 9871 1331 0x14008924
+9871 9872 1330 0x140088cf
+9872 9873 1329 0x1400887b
+9873 9874 1328 0x14008827
+9874 9875 1327 0x140087d2
+9875 9876 1326 0x1400877f
+9876 9877 1325 0x1400872b
+9877 9878 1324 0x140086d8
+9878 9879 1323 0x14008684
+9879 9880 1322 0x14008631
+9880 9881 1321 0x140085de
+9881 9882 1320 0x1400858c
+9882 9883 1319 0x1400853a
+9883 9884 1318 0x140084e7
+9884 9885 1317 0x14008496
+9885 9886 1316 0x14008444
+9886 9887 1315 0x140083f2
+9887 9888 1314 0x140083a0
+9888 9889 1313 0x1400834f
+9889 9890 1312 0x140082fe
+9890 9891 1311 0x140082ad
+9891 9892 1310 0x1400825d
+9892 9893 1309 0x1400820c
+9893 9894 1308 0x140081bc
+9894 9895 1307 0x1400816c
+9895 9896 1306 0x1400811c
+9896 9897 1305 0x140080cd
+9897 9898 1304 0x1400807e
+9898 9899 1303 0x1400802e
+9899 9900 1302 0x137fdf60
+9900 9901 1301 0x137f908e
+9901 9902 1300 0x137f41bc
+9902 9903 1299 0x137ef340
+9903 9904 1298 0x137ea51a
+9904 9905 1297 0x137e56f4
+9905 9906 1296 0x137e08ce
+9906 9907 1295 0x137dbafd
+9907 9908 1294 0x137d6d83
+9908 9909 1293 0x137d2008
+9909 9910 1292 0x137cd2e4
+9910 9911 1291 0x137c8615
+9911 9912 1290 0x137c3947
+9912 9913 1289 0x137becce
+9913 9914 1288 0x137ba055
+9914 9915 1287 0x137b5432
+9915 9916 1286 0x137b080f
+9916 9917 1285 0x137abc42
+9917 9918 1284 0x137a7075
+9918 9919 1283 0x137a24fd
+9919 9920 1282 0x1379d986
+9920 9921 1281 0x13798e65
+9921 9922 1280 0x13794399
+9922 9923 1279 0x1378f8ce
+9923 9924 1278 0x1378ae02
+9924 9925 1277 0x1378638d
+9925 9926 1276 0x1378196d
+9926 9927 1275 0x1377cf4d
+9927 9928 1274 0x137785d9
+9928 9929 1273 0x13773c0f
+9929 9930 1272 0x1376f29b
+9930 9931 1271 0x1376a927
+9931 9932 1270 0x13766009
+9932 9933 1269 0x13761740
+9933 9934 1268 0x1375ce22
+9934 9935 1267 0x137585b0
+9935 9936 1266 0x13753d3d
+9936 9937 1265 0x1374f4cb
+9937 9938 1264 0x1374acae
+9938 9939 1263 0x137464e8
+9939 9940 1262 0x13741d21
+9940 9941 1261 0x1373d5b0
+9941 9942 1260 0x13738e3f
+9942 9943 1259 0x13734724
+9943 9944 1258 0x13730009
+9944 9945 1257 0x1372b944
+9945 9946 1256 0x1372727f
+9946 9947 1255 0x13722c0f
+9947 9948 1254 0x1371e5a0
+9948 9949 1253 0x13719f87
+9949 9950 1252 0x1371596d
+9950 9951 1251 0x137113aa
+9951 9952 1250 0x1370cde6
+9952 9953 1249 0x13708878
+9953 9954 1248 0x1370430b
+9954 9955 1247 0x136ffdf3
+9955 9956 1246 0x136fb8db
+9956 9957 1245 0x136f7419
+9957 9958 1244 0x136f2f57
+9958 9959 1243 0x136eeb41
+9959 9960 1242 0x136ea6d5
+9960 9961 1241 0x136e62bf
+9961 9962 1240 0x136e1ea8
+9962 9963 1239 0x136ddae8
+9963 9964 1238 0x136d9728
+9964 9965 1237 0x136d53bd
+9965 9966 1236 0x136d1052
+9966 9967 1235 0x136ccd3e
+9967 9968 1234 0x136c8a29
+9968 9969 1233 0x136c4715
+9969 9970 1232 0x136c04ac
+9970 9971 1231 0x136bc1ed
+9971 9972 1230 0x136b7f84
+9972 9973 1229 0x136b3d71
+9973 9974 1228 0x136afbb4
+9974 9975 1227 0x136ab9a1
+9975 9976 1226 0x136a77e3
+9976 9977 1225 0x136a367c
+9977 9978 1224 0x1369f515
+9978 9979 1223 0x1369b3ad
+9979 9980 1222 0x1369729c
+9980 9981 1221 0x1369318a
+9981 9982 1220 0x1368f0cf
+9982 9983 1219 0x1368b069
+9983 9984 1218 0x13686fad
+9984 9985 1217 0x13682f9d
+9985 9986 1216 0x1367ef38
+9986 9987 1215 0x1367af28
+9987 9988 1214 0x13676f6e
+9988 9989 1213 0x1367300a
+9989 9990 1212 0x1366f0a6
+9990 9991 1211 0x1366b141
+9991 9992 1210 0x136671dd
+9992 9993 1209 0x136632cf
+9993 9994 1208 0x1365f3c1
+9994 9995 1207 0x1365b508
+9995 9996 1206 0x13657650
+9996 9997 1205 0x136537ed
+9997 9998 1204 0x1364f98b
+9998 9999 1203 0x1364bb7e
+9999 10000 1202 0x13647d71
+10000 10001 1201 0x13643f64
+
diff --git a/haskoin-core.cabal b/haskoin-core.cabal
--- a/haskoin-core.cabal
+++ b/haskoin-core.cabal
@@ -1,158 +1,200 @@
 cabal-version: 1.12
 
--- This file has been generated from package.yaml by hpack version 0.31.2.
+-- This file has been generated from package.yaml by hpack version 0.38.0.
 --
 -- see: https://github.com/sol/hpack
---
--- hash: 1ce94eac889a0e1f03131ba2275354d9e3fcffbb001fa4c0cd8e70cff3e8940a
 
 name:           haskoin-core
-version:        0.12.0
+version:        1.2.2
 synopsis:       Bitcoin & Bitcoin Cash library for Haskell
-description:    Haskoin Core is a complete Bitcoin and Bitcoin Cash library of functions and data types for Haskell developers.
+description:    Please see the README on GitHub at <https://github.com/jprupp/haskoin-core#readme>
 category:       Bitcoin, Finance, Network
-homepage:       http://github.com/haskoin/haskoin#readme
-bug-reports:    http://github.com/haskoin/haskoin/issues
-author:         Philippe Laprade,
-                Jean-Pierre Rupp,
-                Matthew Wraith
+homepage:       http://github.com/jprupp/haskoin-core#readme
+bug-reports:    http://github.com/jprupp/haskoin-core/issues
+author:         JP Rupp
 maintainer:     jprupp@protonmail.ch
-license:        PublicDomain
-license-file:   UNLICENSE
+license:        MIT
+license-file:   LICENSE
 build-type:     Simple
 extra-source-files:
+    data/bip341.json
+    data/complex_psbt.json
     data/forkid_script_tests.json
     data/forkid_sighash.json
+    data/key_io_invalid.json
+    data/key_io_valid.json
+    data/rfc6979cash.json
+    data/rfc6979core.json
+    data/rfc6979DERcash.json
+    data/rfc6979DERcore.json
     data/script_tests.json
     data/sig_nonstrict.json
     data/sig_strict.json
     data/sighash.json
+    data/test_vectors_aserti3-2d_run01.txt
+    data/test_vectors_aserti3-2d_run02.txt
+    data/test_vectors_aserti3-2d_run03.txt
+    data/test_vectors_aserti3-2d_run04.txt
+    data/test_vectors_aserti3-2d_run05.txt
+    data/test_vectors_aserti3-2d_run06.txt
+    data/test_vectors_aserti3-2d_run07.txt
+    data/test_vectors_aserti3-2d_run08.txt
+    data/test_vectors_aserti3-2d_run09.txt
+    data/test_vectors_aserti3-2d_run10.txt
+    data/test_vectors_aserti3-2d_run11.txt
+    data/test_vectors_aserti3-2d_run12.txt
     README.md
     CHANGELOG.md
 
 source-repository head
   type: git
-  location: git://github.com/haskoin/haskoin.git
+  location: git://github.com/jprupp/haskoin-core.git
 
 library
   exposed-modules:
       Haskoin
-      Network.Haskoin.Address
-      Network.Haskoin.Address.Base58
-      Network.Haskoin.Address.CashAddr
-      Network.Haskoin.Address.Bech32
-      Network.Haskoin.Block
-      Network.Haskoin.Block.Headers
-      Network.Haskoin.Block.Merkle
-      Network.Haskoin.Constants
-      Network.Haskoin.Crypto
-      Network.Haskoin.Keys
-      Network.Haskoin.Network
-      Network.Haskoin.Script
-      Network.Haskoin.Test
-      Network.Haskoin.Transaction
-      Network.Haskoin.Transaction.Partial
-      Network.Haskoin.Transaction.Segwit
-      Network.Haskoin.Util
+      Haskoin.Address
+      Haskoin.Address.Base58
+      Haskoin.Address.Bech32
+      Haskoin.Address.CashAddr
+      Haskoin.Block
+      Haskoin.Block.Common
+      Haskoin.Block.Headers
+      Haskoin.Block.Merkle
+      Haskoin.Crypto
+      Haskoin.Crypto.Hash
+      Haskoin.Crypto.Keys
+      Haskoin.Crypto.Keys.Common
+      Haskoin.Crypto.Keys.Extended
+      Haskoin.Crypto.Keys.Mnemonic
+      Haskoin.Crypto.Signature
+      Haskoin.Network
+      Haskoin.Network.Bloom
+      Haskoin.Network.Common
+      Haskoin.Network.Constants
+      Haskoin.Network.Data
+      Haskoin.Network.Message
+      Haskoin.Script
+      Haskoin.Script.Common
+      Haskoin.Script.SigHash
+      Haskoin.Script.Standard
+      Haskoin.Transaction
+      Haskoin.Transaction.Builder
+      Haskoin.Transaction.Builder.Sign
+      Haskoin.Transaction.Common
+      Haskoin.Transaction.Genesis
+      Haskoin.Transaction.Partial
+      Haskoin.Transaction.Segwit
+      Haskoin.Transaction.Taproot
+      Haskoin.Util
+      Haskoin.Util.Arbitrary
+      Haskoin.Util.Arbitrary.Address
+      Haskoin.Util.Arbitrary.Block
+      Haskoin.Util.Arbitrary.Crypto
+      Haskoin.Util.Arbitrary.Keys
+      Haskoin.Util.Arbitrary.Message
+      Haskoin.Util.Arbitrary.Network
+      Haskoin.Util.Arbitrary.Script
+      Haskoin.Util.Arbitrary.Transaction
+      Haskoin.Util.Arbitrary.Util
+      Haskoin.Util.Helpers
+      Haskoin.Util.Marshal
   other-modules:
-      Network.Haskoin.Block.Common
-      Network.Haskoin.Crypto.Hash
-      Network.Haskoin.Crypto.Signature
-      Network.Haskoin.Keys.Common
-      Network.Haskoin.Keys.Extended
-      Network.Haskoin.Keys.Mnemonic
-      Network.Haskoin.Network.Bloom
-      Network.Haskoin.Network.Common
-      Network.Haskoin.Network.Message
-      Network.Haskoin.Script.Common
-      Network.Haskoin.Script.SigHash
-      Network.Haskoin.Script.Standard
-      Network.Haskoin.Test.Address
-      Network.Haskoin.Test.Block
-      Network.Haskoin.Test.Crypto
-      Network.Haskoin.Test.Keys
-      Network.Haskoin.Test.Message
-      Network.Haskoin.Test.Network
-      Network.Haskoin.Test.Script
-      Network.Haskoin.Test.Transaction
-      Network.Haskoin.Test.Util
-      Network.Haskoin.Transaction.Builder
-      Network.Haskoin.Transaction.Builder.Sign
-      Network.Haskoin.Transaction.Common
-      Paths_haskoin_core
+      Haskoin.Crypto.Keys.Extended.Internal
   hs-source-dirs:
       src
   build-depends:
-      HUnit
-    , QuickCheck
-    , aeson
-    , array
+      QuickCheck >=2.13.2
+    , aeson >=1.4.6.0
+    , array >=0.5.4.0
     , base >=4.9 && <5
-    , base16-bytestring
-    , bytestring
-    , cereal
-    , conduit
-    , containers
-    , cryptonite
-    , deepseq
-    , entropy
-    , hashable
-    , hspec
-    , memory
-    , mtl
+    , base16 >=1.0
+    , binary >=0.8.8
+    , bytes >=0.17
+    , bytestring >=0.10.10.0
+    , cereal >=0.5.8
+    , conduit >=1.3.1.2
+    , containers >=0.6.2.1
+    , crypton >=1.0.0
+    , data-default >=0.7.1.1
+    , deepseq >=1.4.4.0
+    , entropy >=0.4.1.5
+    , hashable >=1.3.0.0
+    , hspec >=2.7.1
+    , memory >=0.15.0
+    , mtl >=2.2.2
     , murmur3 >=1.0.3
-    , network
-    , safe
-    , scientific
-    , secp256k1-haskell >=0.2.1
-    , split
-    , string-conversions
-    , text
-    , time
-    , transformers
-    , unordered-containers
-    , vector
+    , network >=3.1.1.1
+    , safe >=0.3.18
+    , scientific >=0.3.6.2
+    , secp256k1-haskell >=1.2.0
+    , split >=0.2.3.3
+    , string-conversions >=0.4.0.1
+    , text >=1.2.3.0
+    , time >=1.9.3
+    , transformers >=0.5.6.2
+    , unordered-containers >=0.2.10.0
+    , vector >=0.12.1.2
   default-language: Haskell2010
 
 test-suite spec
   type: exitcode-stdio-1.0
   main-is: Spec.hs
   other-modules:
-      Network.Haskoin.Address.Bech32Spec
-      Network.Haskoin.Address.CashAddrSpec
-      Network.Haskoin.AddressSpec
-      Network.Haskoin.BlockSpec
-      Network.Haskoin.Crypto.HashSpec
-      Network.Haskoin.Crypto.SignatureSpec
-      Network.Haskoin.CryptoSpec
-      Network.Haskoin.Keys.ExtendedSpec
-      Network.Haskoin.Keys.MnemonicSpec
-      Network.Haskoin.KeysSpec
-      Network.Haskoin.NetworkSpec
-      Network.Haskoin.ScriptSpec
-      Network.Haskoin.Transaction.PartialSpec
-      Network.Haskoin.TransactionSpec
-      Network.Haskoin.UtilSpec
+      Haskoin.Address.Bech32Spec
+      Haskoin.Address.CashAddrSpec
+      Haskoin.AddressSpec
+      Haskoin.BlockSpec
+      Haskoin.Crypto.HashSpec
+      Haskoin.Crypto.Keys.ExtendedSpec
+      Haskoin.Crypto.Keys.MnemonicSpec
+      Haskoin.Crypto.KeysSpec
+      Haskoin.Crypto.SignatureSpec
+      Haskoin.NetworkSpec
+      Haskoin.ScriptSpec
+      Haskoin.Transaction.PartialSpec
+      Haskoin.Transaction.TaprootSpec
+      Haskoin.TransactionSpec
+      Haskoin.UtilSpec
       Paths_haskoin_core
   hs-source-dirs:
       test
   build-depends:
-      HUnit
-    , QuickCheck
-    , aeson
+      HUnit >=1.6.0.0
+    , QuickCheck >=2.13.2
+    , aeson >=1.4.6.0
+    , array >=0.5.4.0
     , base >=4.9 && <5
-    , bytestring
-    , cereal
-    , containers
-    , deepseq
+    , base16 >=1.0
+    , base64
+    , binary >=0.8.8
+    , bytes >=0.17
+    , bytestring >=0.10.10.0
+    , cereal >=0.5.8
+    , conduit >=1.3.1.2
+    , containers >=0.6.2.1
+    , crypton >=1.0.0
+    , data-default >=0.7.1.1
+    , deepseq >=1.4.4.0
+    , entropy >=0.4.1.5
+    , hashable >=1.3.0.0
     , haskoin-core
-    , hspec
-    , mtl
-    , safe
-    , split
-    , string-conversions
-    , text
-    , unordered-containers
-    , vector
+    , hspec >=2.7.1
+    , lens >=4.18.1
+    , lens-aeson >=1.1
+    , memory >=0.15.0
+    , mtl >=2.2.2
+    , murmur3 >=1.0.3
+    , network >=3.1.1.1
+    , safe >=0.3.18
+    , scientific >=0.3.6.2
+    , secp256k1-haskell >=1.2.0
+    , split >=0.2.3.3
+    , string-conversions >=0.4.0.1
+    , text >=1.2.3.0
+    , time >=1.9.3
+    , transformers >=0.5.6.2
+    , unordered-containers >=0.2.10.0
+    , vector >=0.12.1.2
   default-language: Haskell2010
   build-tool-depends: hspec-discover:hspec-discover
diff --git a/src/Haskoin.hs b/src/Haskoin.hs
--- a/src/Haskoin.hs
+++ b/src/Haskoin.hs
@@ -1,45 +1,31 @@
-{-|
-Module      : Haskoin
-Description : Bitcoin (BTC/BCH) Libraries for Haskell
-Copyright   : No rights reserved
-License     : UNLICENSE
-Maintainer  : jprupp@protonmail.ch
-Stability   : experimental
-Portability : POSIX
+{-# LANGUAGE DuplicateRecordFields #-}
 
-This module exports almost all of Haskoin Core, excluding only a few highly
-specialized address and block-related functions.
--}
+-- |
+-- Module      : Haskoin
+-- Description : Bitcoin (BTC/BCH) Libraries for Haskell
+-- Copyright   : No rights reserved
+-- License     : MIT
+-- Maintainer  : jprupp@protonmail.ch
+-- Stability   : experimental
+-- Portability : POSIX
+--
+-- This module exports almost all of Haskoin Core, excluding only a few highly
+-- specialized address and block-related functions.
 module Haskoin
-    ( -- * Address (Base58, Bech32, CashAddr)
-      module Address
-      -- * Network Messages
-    , module Network
-      -- * Network Constants
-    , module Constants
-      -- * Blocks
-    , module Block
-      -- * Transactions
-    , module Transaction
-      -- * Partially Signed Bitcoin Transactions
-    , module Partial
-      -- * Scripts
-    , module Script
-      -- * Cryptographic Keys
-    , module Keys
-      -- * Cryptographic Primitives
-    , module Crypto
-      -- * Various Utilities
-    , module Util
-    ) where
+  ( module Address,
+    module Block,
+    module Transaction,
+    module Script,
+    module Crypto,
+    module Network,
+    module Util,
+  )
+where
 
-import           Network.Haskoin.Address             as Address
-import           Network.Haskoin.Block               as Block
-import           Network.Haskoin.Constants           as Constants
-import           Network.Haskoin.Crypto              as Crypto
-import           Network.Haskoin.Keys                as Keys
-import           Network.Haskoin.Network             as Network
-import           Network.Haskoin.Script              as Script
-import           Network.Haskoin.Transaction         as Transaction
-import           Network.Haskoin.Transaction.Partial as Partial
-import           Network.Haskoin.Util                as Util
+import Haskoin.Address as Address
+import Haskoin.Block as Block
+import Haskoin.Crypto as Crypto
+import Haskoin.Network as Network
+import Haskoin.Script as Script
+import Haskoin.Transaction as Transaction
+import Haskoin.Util as Util
diff --git a/src/Haskoin/Address.hs b/src/Haskoin/Address.hs
new file mode 100644
--- /dev/null
+++ b/src/Haskoin/Address.hs
@@ -0,0 +1,366 @@
+{-# LANGUAGE DeriveAnyClass #-}
+{-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE DuplicateRecordFields #-}
+{-# LANGUAGE FlexibleContexts #-}
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE ImportQualifiedPost #-}
+{-# LANGUAGE LambdaCase #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE OverloadedRecordDot #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE NoFieldSelectors #-}
+
+-- |
+-- Module      : Haskoin.Address
+-- Copyright   : No rights reserved
+-- License     : MIT
+-- Maintainer  : jprupp@protonmail.ch
+-- Stability   : experimental
+-- Portability : POSIX
+--
+-- Base58, CashAddr, Bech32 address and WIF private key serialization support.
+module Haskoin.Address
+  ( -- * Addresses
+    Address (..),
+    isPubKeyAddress,
+    isScriptAddress,
+    isWitnessAddress,
+    isWitnessPubKeyAddress,
+    isWitnessScriptAddress,
+    addrToText,
+    textToAddr,
+    bech32ToAddr,
+    cashToAddr,
+    base58ToAddr,
+    pubKeyAddr,
+    pubKeyWitnessAddr,
+    pubKeyCompatWitnessAddr,
+    p2pkhAddr,
+    p2wpkhAddr,
+    p2shAddr,
+    p2wshAddr,
+    inputAddress,
+    outputAddress,
+    addressToScript,
+    addressToScriptBS,
+    addressToOutput,
+    payToScriptAddress,
+    payToWitnessScriptAddress,
+    payToNestedScriptAddress,
+    scriptToAddress,
+    scriptToAddressBS,
+    module Haskoin.Address.Base58,
+    module Haskoin.Address.Bech32,
+    module Haskoin.Address.CashAddr,
+  )
+where
+
+import Control.Applicative (Alternative ((<|>)))
+import Control.Arrow (second)
+import Control.DeepSeq (NFData)
+import Control.Monad ((<=<))
+import Crypto.Secp256k1
+import Data.Aeson (ToJSON (toJSON), Value, withText)
+import Data.Aeson.Encoding (Encoding, null_, text)
+import Data.Aeson.Types (Encoding, Parser, ToJSON (toJSON), Value, withText)
+import Data.Binary (Binary (..))
+import Data.ByteString (ByteString)
+import Data.ByteString qualified as B
+import Data.Bytes.Get (MonadGet (getByteString, getWord64be, getWord8), runGetS)
+import Data.Bytes.Put (MonadPut (putByteString, putWord64be, putWord8), runPutS)
+import Data.Bytes.Serial (Serial (..))
+import Data.Hashable (Hashable)
+import Data.Maybe (isNothing)
+import Data.Serialize (Serialize (..))
+import Data.Text (Text)
+import Data.Text qualified as T
+import Data.Word (Word8)
+import GHC.Generics (Generic)
+import Haskoin.Address.Base58
+import Haskoin.Address.Bech32
+import Haskoin.Address.CashAddr
+import Haskoin.Crypto.Hash
+import Haskoin.Crypto.Keys.Common
+import Haskoin.Network.Data
+import Haskoin.Script.Common
+import Haskoin.Script.Standard
+import Haskoin.Util
+
+-- | Address format for Bitcoin and Bitcoin Cash.
+data Address
+  = -- | pay to public key hash (regular)
+    PubKeyAddress
+      { -- | RIPEMD160 hash of public key's SHA256 hash
+        hash160 :: !Hash160
+      }
+  | -- | pay to script hash
+    ScriptAddress
+      { -- | RIPEMD160 hash of script's SHA256 hash
+        hash160 :: !Hash160
+      }
+  | -- | pay to witness public key hash
+    WitnessPubKeyAddress
+      { -- | RIPEMD160 hash of public key's SHA256 hash
+        hash160 :: !Hash160
+      }
+  | -- | pay to witness script hash
+    WitnessScriptAddress
+      { -- | HASH256 hash of script
+        hash256 :: !Hash256
+      }
+  | -- | other witness address
+    WitnessAddress
+      { version :: !Word8,
+        bytes :: !ByteString
+      }
+  deriving
+    (Eq, Ord, Generic, Show, Read, Hashable, NFData)
+
+instance Serial Address where
+  serialize (PubKeyAddress k) = do
+    putWord8 0x00
+    serialize k
+  serialize (ScriptAddress s) = do
+    putWord8 0x01
+    serialize s
+  serialize (WitnessPubKeyAddress h) = do
+    putWord8 0x02
+    serialize h
+  serialize (WitnessScriptAddress s) = do
+    putWord8 0x03
+    serialize s
+  serialize (WitnessAddress v d) = do
+    putWord8 0x04
+    putWord8 v
+    putWord64be (fromIntegral (B.length d))
+    putByteString d
+
+  deserialize =
+    getWord8 >>= \case
+      0x00 -> PubKeyAddress <$> deserialize
+      0x01 -> ScriptAddress <$> deserialize
+      0x02 -> WitnessPubKeyAddress <$> deserialize
+      0x03 -> WitnessScriptAddress <$> deserialize
+      0x04 ->
+        WitnessAddress
+          <$> getWord8
+          <*> (getByteString . fromIntegral =<< getWord64be)
+      b ->
+        fail . T.unpack $
+          "Could not decode address type byte: "
+            <> encodeHex (B.singleton b)
+
+instance Serialize Address where
+  put = serialize
+  get = deserialize
+
+instance Binary Address where
+  put = serialize
+  get = deserialize
+
+-- | 'Address' pays to a public key hash.
+isPubKeyAddress :: Address -> Bool
+isPubKeyAddress PubKeyAddress {} = True
+isPubKeyAddress _ = False
+
+-- | 'Address' pays to a script hash.
+isScriptAddress :: Address -> Bool
+isScriptAddress ScriptAddress {} = True
+isScriptAddress _ = False
+
+-- | 'Address' pays to a witness public key hash. Only valid for SegWit
+-- networks.
+isWitnessPubKeyAddress :: Address -> Bool
+isWitnessPubKeyAddress WitnessPubKeyAddress {} = True
+isWitnessPubKeyAddress _ = False
+
+isWitnessScriptAddress :: Address -> Bool
+isWitnessScriptAddress WitnessScriptAddress {} = True
+isWitnessScriptAddress _ = False
+
+isWitnessAddress :: Address -> Bool
+isWitnessAddress WitnessAddress {} = True
+isWitnessAddress _ = False
+
+instance MarshalJSON Network Address where
+  marshalValue net a = toJSON (addrToText net a)
+  marshalEncoding net = maybe null_ text . addrToText net
+  unmarshalValue net =
+    withText "address" $ \t ->
+      case textToAddr net t of
+        Nothing -> fail "could not decode address"
+        Just x -> return x
+
+-- | Convert address to human-readable string. Uses 'Base58', 'Bech32', or
+-- 'CashAddr' depending on network.
+addrToText :: Network -> Address -> Maybe Text
+addrToText net a@PubKeyAddress {hash160 = h}
+  | isNothing net.cashAddrPrefix =
+      Just . encodeBase58Check . runPutS $ base58put net a
+  | otherwise = cashAddrEncode net 0 (runPutS $ serialize h)
+addrToText net a@ScriptAddress {hash160 = h}
+  | isNothing net.cashAddrPrefix =
+      Just . encodeBase58Check . runPutS $ base58put net a
+  | otherwise =
+      cashAddrEncode net 1 (runPutS $ serialize h)
+addrToText net WitnessPubKeyAddress {hash160 = h} = do
+  hrp <- net.bech32Prefix
+  segwitEncode hrp 0 (B.unpack (runPutS $ serialize h))
+addrToText net WitnessScriptAddress {hash256 = h} = do
+  hrp <- net.bech32Prefix
+  segwitEncode hrp 0 (B.unpack (runPutS $ serialize h))
+addrToText net WitnessAddress {version = v, bytes = d} = do
+  hrp <- net.bech32Prefix
+  segwitEncode hrp v (B.unpack d)
+
+-- | Parse 'Base58', 'Bech32' or 'CashAddr' address, depending on network.
+textToAddr :: Network -> Text -> Maybe Address
+textToAddr net txt =
+  cashToAddr net txt <|> bech32ToAddr net txt <|> base58ToAddr net txt
+
+cashToAddr :: Network -> Text -> Maybe Address
+cashToAddr net txt = do
+  (ver, bs) <- cashAddrDecode net txt
+  case ver of
+    0 -> PubKeyAddress <$> eitherToMaybe (runGetS deserialize bs)
+    1 -> ScriptAddress <$> eitherToMaybe (runGetS deserialize bs)
+    _ -> Nothing
+
+bech32ToAddr :: Network -> Text -> Maybe Address
+bech32ToAddr net txt = do
+  hrp <- net.bech32Prefix
+  (ver, bs) <- second B.pack <$> segwitDecode hrp txt
+  case ver of
+    0 -> case B.length bs of
+      20 -> WitnessPubKeyAddress <$> eitherToMaybe (runGetS deserialize bs)
+      32 -> WitnessScriptAddress <$> eitherToMaybe (runGetS deserialize bs)
+      _ -> Nothing
+    _ -> Just $ WitnessAddress ver bs
+
+base58ToAddr :: Network -> Text -> Maybe Address
+base58ToAddr net txt =
+  eitherToMaybe . runGetS (base58get net) =<< decodeBase58Check txt
+
+base58get :: (MonadGet m) => Network -> m Address
+base58get net = do
+  pfx <- getWord8
+  addr <- deserialize
+  f pfx addr
+  where
+    f x a
+      | x == net.addrPrefix = return $ PubKeyAddress a
+      | x == net.scriptPrefix = return $ ScriptAddress a
+      | otherwise = fail "Does not recognize address prefix"
+
+base58put :: (MonadPut m) => Network -> Address -> m ()
+base58put net (PubKeyAddress h) = do
+  putWord8 net.addrPrefix
+  serialize h
+base58put net (ScriptAddress h) = do
+  putWord8 net.scriptPrefix
+  serialize h
+base58put _ _ = error "Cannot serialize this address as Base58"
+
+-- | Obtain a standard pay-to-public-key-hash address from a public key.
+pubKeyAddr :: Ctx -> PublicKey -> Address
+pubKeyAddr ctx = PubKeyAddress . addressHash . marshal ctx
+
+-- | Obtain a standard pay-to-public-key-hash (P2PKH) address from a 'Hash160'.
+p2pkhAddr :: Hash160 -> Address
+p2pkhAddr = PubKeyAddress
+
+-- | Obtain a SegWit pay-to-witness-public-key-hash (P2WPKH) address from a
+-- public key.
+pubKeyWitnessAddr :: Ctx -> PublicKey -> Address
+pubKeyWitnessAddr ctx =
+  WitnessPubKeyAddress . addressHash . marshal ctx
+
+-- | Obtain a backwards-compatible SegWit P2SH-P2WPKH address from a public key.
+pubKeyCompatWitnessAddr :: Ctx -> PublicKey -> Address
+pubKeyCompatWitnessAddr ctx =
+  p2shAddr
+    . addressHash
+    . marshal ctx
+    . PayWitnessPKHash
+    . addressHash
+    . marshal ctx
+
+-- | Obtain a SegWit pay-to-witness-public-key-hash (P2WPKH) address from a
+-- 'Hash160'.
+p2wpkhAddr :: Hash160 -> Address
+p2wpkhAddr = WitnessPubKeyAddress
+
+-- | Obtain a standard pay-to-script-hash (P2SH) address from a 'Hash160'.
+p2shAddr :: Hash160 -> Address
+p2shAddr = ScriptAddress
+
+-- | Obtain a SegWit pay-to-witness-script-hash (P2WSH) address from a 'Hash256'
+p2wshAddr :: Hash256 -> Address
+p2wshAddr = WitnessScriptAddress
+
+-- | Compute a standard pay-to-script-hash (P2SH) address for an output script.
+payToScriptAddress :: Ctx -> ScriptOutput -> Address
+payToScriptAddress ctx = p2shAddr . addressHash . marshal ctx
+
+-- | Compute a SegWit pay-to-witness-script-hash (P2WSH) address for an output
+-- script.
+payToWitnessScriptAddress :: Ctx -> ScriptOutput -> Address
+payToWitnessScriptAddress ctx = p2wshAddr . sha256 . marshal ctx
+
+-- | Compute a backwards-compatible SegWit P2SH-P2WSH address.
+payToNestedScriptAddress :: Ctx -> ScriptOutput -> Address
+payToNestedScriptAddress ctx =
+  p2shAddr . addressHash . marshal ctx . toP2WSH . encodeOutput ctx
+
+-- | Encode an output script from an address. Will fail if using a
+-- pay-to-witness address on a non-SegWit network.
+addressToOutput :: Address -> ScriptOutput
+addressToOutput =
+  \case
+    PubKeyAddress h -> PayPKHash h
+    ScriptAddress h -> PayScriptHash h
+    WitnessPubKeyAddress h -> PayWitnessPKHash h
+    WitnessScriptAddress h -> PayWitnessScriptHash h
+    WitnessAddress v d -> PayWitness v d
+
+-- | Get output script AST for an 'Address'.
+addressToScript :: Ctx -> Address -> Script
+addressToScript ctx = encodeOutput ctx . addressToOutput
+
+-- | Encode address as output script in 'ByteString' form.
+addressToScriptBS :: Ctx -> Address -> ByteString
+addressToScriptBS ctx = runPutS . serialize . addressToScript ctx
+
+-- | Decode an output script into an 'Address' if it has such representation.
+scriptToAddress :: Ctx -> Script -> Either String Address
+scriptToAddress ctx =
+  maybeToEither e . outputAddress ctx <=< decodeOutput ctx
+  where
+    e = "Could not decode address"
+
+-- | Decode a serialized script into an 'Address'.
+scriptToAddressBS :: Ctx -> ByteString -> Either String Address
+scriptToAddressBS ctx =
+  maybeToEither e . outputAddress ctx <=< unmarshal ctx
+  where
+    e = "Could not decode address"
+
+-- | Get the 'Address' of a 'ScriptOutput'.
+outputAddress :: Ctx -> ScriptOutput -> Maybe Address
+outputAddress ctx =
+  \case
+    PayPKHash h -> Just $ PubKeyAddress h
+    PayScriptHash h -> Just $ ScriptAddress h
+    PayPK k -> Just $ pubKeyAddr ctx k
+    PayWitnessPKHash h -> Just $ WitnessPubKeyAddress h
+    PayWitnessScriptHash h -> Just $ WitnessScriptAddress h
+    PayWitness v d -> Just $ WitnessAddress v d
+    _ -> Nothing
+
+-- | Infer the 'Address' of a 'ScriptInput'.
+inputAddress :: Ctx -> ScriptInput -> Maybe Address
+inputAddress ctx =
+  \case
+    (RegularInput (SpendPKHash _ key)) -> Just $ pubKeyAddr ctx key
+    (ScriptHashInput _ rdm) -> Just $ payToScriptAddress ctx rdm
+    _ -> Nothing
diff --git a/src/Haskoin/Address/Base58.hs b/src/Haskoin/Address/Base58.hs
new file mode 100644
--- /dev/null
+++ b/src/Haskoin/Address/Base58.hs
@@ -0,0 +1,125 @@
+{-# LANGUAGE ImportQualifiedPost #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE NoFieldSelectors #-}
+
+-- |
+-- Module      : Haskoin.Address.Base58
+-- Copyright   : No rights reserved
+-- License     : MIT
+-- Maintainer  : jprupp@protonmail.ch
+-- Stability   : experimental
+-- Portability : POSIX
+--
+-- Support for legacy 'Base58' addresses. Superseded by Bech32 for Bitcoin SegWit
+-- (BTC) and CashAddr for Bitcoin Cash (BCH).
+module Haskoin.Address.Base58
+  ( -- * Base58
+    Base58,
+    encodeBase58,
+    decodeBase58,
+    encodeBase58Check,
+    decodeBase58Check,
+  )
+where
+
+import Control.Monad (guard)
+import Data.Array (Array, assocs, listArray, (!), (//))
+import Data.ByteString (ByteString)
+import Data.ByteString qualified as B
+import Data.ByteString.Char8 qualified as C
+import Data.Bytes.Get ()
+import Data.Bytes.Put (runPutS)
+import Data.Bytes.Serial (Serial (serialize))
+import Data.Char (chr, ord)
+import Data.Maybe (fromMaybe, isJust, listToMaybe)
+import Data.String.Conversions (cs)
+import Data.Text (Text)
+import Data.Text qualified as T
+import Data.Word (Word8)
+import Haskoin.Crypto.Hash (checkSum32)
+import Haskoin.Util.Helpers (bsToInteger, integerToBS)
+import Numeric (readInt, showIntAtBase)
+
+-- | 'Base58' classic Bitcoin address format.
+type Base58 = Text
+
+-- | Symbols for Base58 encoding.
+b58Data :: ByteString
+b58Data = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
+
+b58Array :: Array Int Word8
+b58Array = listArray (0, 57) (B.unpack b58Data)
+
+b58InvArray :: Array Word8 (Maybe Int)
+b58InvArray =
+  listArray
+    (minBound, maxBound)
+    (repeat Nothing)
+    // map swap (assocs b58Array)
+  where
+    swap (i, c) = (c, Just i)
+
+-- | Convert a number less than or equal to provided integer into a 'Base58'
+-- character.
+b58 :: Int -> Word8
+b58 = (b58Array !)
+
+-- | Convert a 'Base58' character into the number it represents.
+b58' :: Word8 -> Maybe Int
+b58' = (b58InvArray !)
+
+-- | Encode an arbitrary-length 'Integer' into a 'Base58' string. Leading zeroes
+-- will not be part of the resulting string.
+encodeBase58I :: Integer -> Base58
+encodeBase58I i = cs $ showIntAtBase 58 (chr . fromIntegral . b58) i ""
+
+-- | Decode a 'Base58' string into an arbitrary-length 'Integer'.
+decodeBase58I :: Base58 -> Maybe Integer
+decodeBase58I s =
+  case go of
+    Just (r, []) -> Just r
+    _ -> Nothing
+  where
+    p = isJust . b58' . fromIntegral . ord
+    f = fromMaybe e . b58' . fromIntegral . ord
+    go = listToMaybe $ readInt 58 p f (cs s)
+    e = error "Could not decode base58"
+
+-- | Encode an arbitrary 'ByteString' into a its 'Base58' representation,
+-- preserving leading zeroes.
+encodeBase58 :: ByteString -> Base58
+encodeBase58 bs =
+  l <> r
+  where
+    (z, b) = B.span (== 0) bs
+    l = cs $ B.replicate (B.length z) (b58 0) -- preserve leading 0's
+    r
+      | B.null b = T.empty
+      | otherwise = encodeBase58I $ bsToInteger b
+
+-- | Decode a 'Base58'-encoded 'Text' to a 'ByteString'.
+decodeBase58 :: Base58 -> Maybe ByteString
+decodeBase58 t =
+  B.append prefix <$> r
+  where
+    (z, b) = B.span (== b58 0) (cs t)
+    prefix = B.replicate (B.length z) 0 -- preserve leading 1's
+    r
+      | B.null b = Just B.empty
+      | otherwise = integerToBS <$> decodeBase58I (cs b)
+
+-- | Computes a checksum for the input 'ByteString' and encodes the input and
+-- the checksum as 'Base58'.
+encodeBase58Check :: ByteString -> Base58
+encodeBase58Check bs =
+  (encodeBase58 . B.append bs . runPutS . serialize . checkSum32) bs
+
+-- | Decode a 'Base58'-encoded string that contains a checksum. This function
+-- returns 'Nothing' if the input string contains invalid 'Base58' characters or
+-- if the checksum fails.
+decodeBase58Check :: Base58 -> Maybe ByteString
+decodeBase58Check bs = do
+  rs <- decodeBase58 bs
+  let (res, chk) = B.splitAt (B.length rs - 4) rs
+  guard $ chk == (runPutS . serialize . checkSum32) res
+  return res
diff --git a/src/Haskoin/Address/Bech32.hs b/src/Haskoin/Address/Bech32.hs
new file mode 100644
--- /dev/null
+++ b/src/Haskoin/Address/Bech32.hs
@@ -0,0 +1,265 @@
+{-# LANGUAGE ImportQualifiedPost #-}
+{-# LANGUAGE MultiWayIf #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE NoFieldSelectors #-}
+
+-- |
+-- Module      : Haskoin.Address.Base58
+-- Copyright   : No rights reserved
+-- License     : MIT
+-- Maintainer  : jprupp@protonmail.ch
+-- Stability   : experimental
+-- Portability : POSIX
+--
+-- Support for Bitcoin SegWit (BTC) Bech32 addresses. This module is a modified
+-- version of Marko Bencun's reference implementation.
+module Haskoin.Address.Bech32
+  ( -- * Bech32
+    HRP,
+    Bech32,
+    Bech32Encoding (..),
+    bech32Const,
+    Data,
+    bech32Encode,
+    bech32Decode,
+    toBase32,
+    toBase256,
+    segwitEncode,
+    segwitDecode,
+    Word5 (..),
+    word5,
+    fromWord5,
+  )
+where
+
+import Control.Monad (guard)
+import Data.Array
+  ( Array,
+    assocs,
+    bounds,
+    listArray,
+    (!),
+    (//),
+  )
+import Data.Bits
+  ( Bits,
+    testBit,
+    unsafeShiftL,
+    unsafeShiftR,
+    xor,
+    (.&.),
+    (.|.),
+  )
+import Data.ByteString qualified as B
+import Data.Char (toUpper)
+import Data.Foldable (foldl')
+import Data.Functor.Identity (Identity, runIdentity)
+import Data.Ix (Ix (..))
+import Data.Text (Text)
+import Data.Text qualified as T
+import Data.Text.Encoding qualified as E
+import Data.Word (Word8)
+
+data Bech32Encoding = Bech32 | Bech32m
+  deriving (Eq, Show, Ord, Enum)
+
+-- | Bech32 human-readable string.
+type Bech32 = Text
+
+-- | Human-readable part of 'Bech32' address.
+type HRP = Text
+
+-- | Data part of 'Bech32' address.
+type Data = [Word8]
+
+(.>>.), (.<<.) :: (Bits a) => a -> Int -> a
+(.>>.) = unsafeShiftR
+(.<<.) = unsafeShiftL
+
+-- | Five-bit word for Bech32.
+newtype Word5
+  = UnsafeWord5 Word8
+  deriving (Eq, Ord)
+
+instance Ix Word5 where
+  range (UnsafeWord5 m, UnsafeWord5 n) = map UnsafeWord5 $ range (m, n)
+  index (UnsafeWord5 m, UnsafeWord5 n) (UnsafeWord5 i) = index (m, n) i
+  inRange (m, n) i = m <= i && i <= n
+
+-- | Convert an integer number into a five-bit word.
+word5 :: (Integral a) => a -> Word5
+word5 x = UnsafeWord5 (fromIntegral x .&. 31)
+{-# INLINE word5 #-}
+{-# SPECIALIZE INLINE word5 :: Word8 -> Word5 #-}
+
+-- | Convert a five-bit word into a number.
+fromWord5 :: (Num a) => Word5 -> a
+fromWord5 (UnsafeWord5 x) = fromIntegral x
+{-# INLINE fromWord5 #-}
+{-# SPECIALIZE INLINE fromWord5 :: Word5 -> Word8 #-}
+
+-- | 'Bech32' character map as array of five-bit integers to character.
+charset :: Array Word5 Char
+charset =
+  listArray (UnsafeWord5 0, UnsafeWord5 31) "qpzry9x8gf2tvdw0s3jn54khce6mua7l"
+
+-- | Convert a character to its five-bit value from 'Bech32' 'charset'.
+charsetMap :: Char -> Maybe Word5
+charsetMap c
+  | inRange (bounds inv) upperC = inv ! upperC
+  | otherwise = Nothing
+  where
+    upperC = toUpper c
+    inv = listArray ('0', 'Z') (repeat Nothing) // map swap (assocs charset)
+    swap (a, b) = (toUpper b, Just a)
+
+-- | Calculate or validate 'Bech32' checksum.
+bech32Polymod :: [Word5] -> Word
+bech32Polymod values = foldl' go 1 values .&. 0x3fffffff
+  where
+    go chk value =
+      foldl' xor chk' [g | (g, i) <- zip generator [25 ..], testBit chk i]
+      where
+        generator = [0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3]
+        chk' = chk .<<. 5 `xor` fromWord5 value
+
+-- | Convert human-readable part of 'Bech32' string into a list of five-bit
+-- words.
+bech32HRPExpand :: HRP -> [Word5]
+bech32HRPExpand hrp =
+  map (UnsafeWord5 . (.>>. 5)) hrpBytes
+    ++ [UnsafeWord5 0]
+    ++ map word5 hrpBytes
+  where
+    hrpBytes = B.unpack $ E.encodeUtf8 hrp
+
+bech32Const :: Bech32Encoding -> Word
+bech32Const Bech32 = 0x00000001
+bech32Const Bech32m = 0x2bc830a3
+
+-- | Calculate Bech32 checksum for a string of five-bit words.
+bech32CreateChecksum :: Bech32Encoding -> HRP -> [Word5] -> [Word5]
+bech32CreateChecksum enc hrp dat = [word5 (polymod .>>. i) | i <- [25, 20 .. 0]]
+  where
+    values = bech32HRPExpand hrp ++ dat
+    w5 = values ++ map UnsafeWord5 [0, 0, 0, 0, 0, 0]
+    polymod = bech32Polymod w5 `xor` bech32Const enc
+
+-- | Verify Bech32 checksum for a human-readable part and string of five-bit words.
+bech32VerifyChecksum :: HRP -> [Word5] -> Maybe Bech32Encoding
+bech32VerifyChecksum hrp dat =
+  let poly = bech32Polymod (bech32HRPExpand hrp ++ dat)
+   in if
+        | poly == bech32Const Bech32 -> Just Bech32
+        | poly == bech32Const Bech32m -> Just Bech32m
+        | otherwise -> Nothing
+
+-- | Maximum length of a Bech32 result.
+maxBech32Length :: Int
+maxBech32Length = 90
+
+-- | Encode string of five-bit words into 'Bech32' using a provided
+-- human-readable part. Can fail if 'HRP' is invalid or result would be longer
+-- than 90 characters.
+bech32Encode :: Bech32Encoding -> HRP -> [Word5] -> Maybe Bech32
+bech32Encode enc hrp dat = do
+  guard $ checkHRP hrp
+  let dat' = dat ++ bech32CreateChecksum enc (T.toLower hrp) dat
+      rest = map (charset !) dat'
+      result = T.concat [T.toLower hrp, T.pack "1", T.pack rest]
+  guard $ T.length result <= maxBech32Length
+  return result
+
+-- | Check that human-readable part is valid for a 'Bech32' string.
+checkHRP :: HRP -> Bool
+checkHRP hrp =
+  not (T.null hrp)
+    && T.all (\char -> char >= '\x21' && char <= '\x7e') hrp
+
+-- | Decode human-readable 'Bech32' string into a human-readable part and a
+-- string of five-bit words.
+bech32Decode :: Bech32 -> Maybe (Bech32Encoding, HRP, [Word5])
+bech32Decode bech32 = do
+  guard $ T.length bech32 <= maxBech32Length
+  guard $ T.toUpper bech32 == bech32 || lowerBech32 == bech32
+  let (hrp, dat) = T.breakOnEnd "1" lowerBech32
+  guard $ T.length dat >= 6
+  hrp' <- T.stripSuffix "1" hrp
+  guard $ checkHRP hrp'
+  dat' <- mapM charsetMap $ T.unpack dat
+  enc <- bech32VerifyChecksum hrp' dat'
+  return (enc, hrp', take (T.length dat - 6) dat')
+  where
+    lowerBech32 = T.toLower bech32
+
+type Pad f = Int -> Int -> Word -> [[Word]] -> f [[Word]]
+
+yesPadding :: Pad Identity
+yesPadding _ 0 _ result = return result
+yesPadding _ _ padValue result = return $ [padValue] : result
+{-# INLINE yesPadding #-}
+
+noPadding :: Pad Maybe
+noPadding frombits bits padValue result = do
+  guard $ bits < frombits && padValue == 0
+  return result
+{-# INLINE noPadding #-}
+
+-- | Big endian conversion of a bytestring from base \(2^{frombits}\) to base
+-- \(2^{tobits}\). {frombits} and {twobits} must be positive and
+-- \(2^{frombits}\) and \(2^{tobits}\) must be smaller than the size of Word.
+-- Every value in 'dat' must be strictly smaller than \(2^{frombits}\).
+convertBits :: (Functor f) => [Word] -> Int -> Int -> Pad f -> f [Word]
+convertBits dat frombits tobits pad = concat . reverse <$> go dat 0 0 []
+  where
+    go [] acc bits result =
+      let padValue = (acc .<<. (tobits - bits)) .&. maxv
+       in pad frombits bits padValue result
+    go (value : dat') acc bits result =
+      go dat' acc' (bits' `rem` tobits) (result' : result)
+      where
+        acc' = (acc .<<. frombits) .|. fromIntegral value
+        bits' = bits + frombits
+        result' =
+          [ (acc' .>>. b) .&. maxv
+            | b <- [bits' - tobits, bits' - 2 * tobits .. 0]
+          ]
+    maxv = (1 .<<. tobits) - 1
+{-# INLINE convertBits #-}
+
+-- | Convert from eight-bit to five-bit word string, adding padding as required.
+toBase32 :: [Word8] -> [Word5]
+toBase32 dat =
+  map word5 $ runIdentity $ convertBits (map fromIntegral dat) 8 5 yesPadding
+
+-- | Convert from five-bit word string to eight-bit word string, ignoring padding.
+toBase256 :: [Word5] -> Maybe [Word8]
+toBase256 dat =
+  map fromIntegral <$> convertBits (map fromWord5 dat) 5 8 noPadding
+
+-- | Check if witness version and program are valid.
+segwitCheck :: Bech32Encoding -> Word8 -> Data -> Bool
+segwitCheck enc witver witprog =
+  witver <= 16
+    && if witver == 0
+      then enc == Bech32 && (length witprog == 20 || length witprog == 32)
+      else enc == Bech32m && (length witprog >= 2 && length witprog <= 40)
+
+-- | Decode SegWit 'Bech32' address from a string and expected human-readable part.
+segwitDecode :: HRP -> Bech32 -> Maybe (Word8, Data)
+segwitDecode hrp addr = do
+  (enc, hrp', dat) <- bech32Decode addr
+  guard $ (hrp == hrp') && not (null dat)
+  let (UnsafeWord5 witver : datBase32) = dat
+  decoded <- toBase256 datBase32
+  guard $ segwitCheck enc witver decoded
+  return (witver, decoded)
+
+-- | Encode 'Data' as a SegWit 'Bech32' address. Needs human-readable part and
+-- witness program version.
+segwitEncode :: HRP -> Word8 -> Data -> Maybe Text
+segwitEncode hrp witver witprog = do
+  guard $ segwitCheck enc witver witprog
+  bech32Encode enc hrp $ UnsafeWord5 witver : toBase32 witprog
+  where
+    enc = if witver == 0 then Bech32 else Bech32m
diff --git a/src/Haskoin/Address/CashAddr.hs b/src/Haskoin/Address/CashAddr.hs
new file mode 100644
--- /dev/null
+++ b/src/Haskoin/Address/CashAddr.hs
@@ -0,0 +1,221 @@
+{-# LANGUAGE DuplicateRecordFields #-}
+{-# LANGUAGE ImportQualifiedPost #-}
+{-# LANGUAGE OverloadedRecordDot #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE NoFieldSelectors #-}
+
+-- |
+-- Module      : Haskoin.Address.CashAddr
+-- Copyright   : No rights reserved
+-- License     : MIT
+-- Maintainer  : jprupp@protonmail.ch
+-- Stability   : experimental
+-- Portability : POSIX
+--
+-- Support for Bitcoin Cash (BCH) CashAddr format.
+module Haskoin.Address.CashAddr
+  ( -- * CashAddr
+    CashPrefix,
+    CashVersion,
+    CashAddr,
+    Cash32,
+    cashAddrDecode,
+    cashAddrEncode,
+    cash32decodeType,
+    cash32encodeType,
+    cash32decode,
+    cash32encode,
+  )
+where
+
+import Control.Monad (guard)
+import Data.Bits
+  ( Bits
+      ( shiftL,
+        shiftR,
+        testBit,
+        xor,
+        (.&.),
+        (.|.)
+      ),
+  )
+import Data.ByteString (ByteString)
+import Data.ByteString qualified as B
+import Data.ByteString.Char8 qualified as C
+import Data.Char (ord, toLower, toUpper)
+import Data.List (elemIndex, foldl')
+import Data.Text (Text)
+import Data.Text qualified as T
+import Data.Text.Encoding (decodeUtf8, encodeUtf8)
+import Data.Word (Word64, Word8)
+import Haskoin.Network.Data (Network (cashAddrPrefix))
+import Haskoin.Util.Helpers (convertBits)
+
+-- | 'CashAddr' prefix, usually shown before the colon in addresses, but sometimes
+-- omitted. It is used in the checksum calculation to avoid parsing an address
+-- from the wrong network.
+type CashPrefix = Text
+
+-- | 'CashAddr' version, until new address schemes appear it will be zero.
+type CashVersion = Word8
+
+-- | High level 'CashAddr' human-reabale string, with explicit or implicit prefix.
+type CashAddr = Text
+
+-- | Low level 'Cash32' is the human-readable low-level encoding used by 'CashAddr'.
+-- It need not encode a valid address but any binary data.
+type Cash32 = Text
+
+-- | Symbols for encoding 'Cash32' data in human-readable strings.
+charset :: String
+charset = "qpzry9x8gf2tvdw0s3jn54khce6mua7l"
+
+-- | Get the 32-bit number associated with this 'Cash32' character.
+base32char :: Char -> Maybe Word8
+base32char = fmap fromIntegral . (`elemIndex` charset)
+
+-- | High-Level: decode 'CashAddr' string if it is valid for the
+-- provided 'Network'. Prefix may be omitted from the string.
+cashAddrDecode :: Network -> CashAddr -> Maybe (CashVersion, ByteString)
+cashAddrDecode net ca = do
+  epfx <- net.cashAddrPrefix
+  let (cpfx, cdat) = T.breakOnEnd ":" (T.toLower ca)
+  guard (T.null cpfx || T.init cpfx == epfx)
+  (dpfx, ver, bs) <- cash32decodeType (epfx <> ":" <> cdat)
+  guard (dpfx == epfx)
+  return (ver, bs)
+
+-- | High-Level: encode 'CashAddr' string for the provided network and hash.
+-- Fails if the 'CashVersion' or length of hash 'ByteString' is invalid.
+cashAddrEncode :: Network -> CashVersion -> ByteString -> Maybe CashAddr
+cashAddrEncode net cv bs = do
+  pfx <- net.cashAddrPrefix
+  cash32encodeType pfx cv bs
+
+-- | Mid-Level: decode 'CashAddr' string containing arbitrary prefix, plus a
+-- version byte before the 'ByteString' that encodes type and length.
+cash32decodeType :: Cash32 -> Maybe (CashPrefix, CashVersion, ByteString)
+cash32decodeType ca' = do
+  guard (T.toUpper ca' == ca' || ca == ca')
+  (dpfx, bs) <- cash32decode ca
+  guard (not (B.null bs))
+  let vb = B.head bs
+      pay = B.tail bs
+  (ver, len) <- decodeVersionByte vb
+  guard (B.length pay == len)
+  return (dpfx, ver, pay)
+  where
+    ca = T.toLower ca'
+
+-- | Mid-Level: encode 'CashAddr' string containing arbitrary prefix and
+-- 'CashVersion'. Length must be among those allowed by the standard.
+cash32encodeType :: CashPrefix -> CashVersion -> ByteString -> Maybe Cash32
+cash32encodeType pfx cv bs = do
+  let len = B.length bs
+  vb <- encodeVersionByte cv len
+  let pl = vb `B.cons` bs
+  return (cash32encode pfx pl)
+
+-- | Low-Level: decode 'Cash32' string. 'CashPrefix' must be part of the string.
+-- No version or hash length validation is performed.
+cash32decode :: Cash32 -> Maybe (CashPrefix, ByteString)
+cash32decode text = do
+  let bs = C.map toLower bs'
+  guard (C.map toUpper bs' == bs' || bs == bs')
+  let (pfx', dat) = C.breakEnd (== ':') bs
+  pfx <-
+    if B.null pfx' || pfx' == C.singleton ':'
+      then Nothing
+      else Just (B.init pfx')
+  b32 <- B.pack <$> mapM base32char (C.unpack dat)
+  let px = B.map (.&. 0x1f) pfx
+      pd = px <> B.singleton 0 <> b32
+      cs = cash32Polymod pd
+      bb = B.take (B.length b32 - 8) b32
+  guard (verifyCash32Polymod cs)
+  let out = toBase256 bb
+  return (decodeUtf8 pfx, out)
+  where
+    bs' = encodeUtf8 text
+
+-- | Low-Level: encode 'Cash32' string for 'CashPrefix' provided. Can encode
+-- arbitrary data. No prefix or length validation is performed.
+cash32encode :: CashPrefix -> ByteString -> Cash32
+cash32encode pfx bs =
+  let b32 = toBase32 bs
+      px = B.map (.&. 0x1f) (encodeUtf8 pfx)
+      pd = px <> B.singleton 0 <> b32 <> B.replicate 8 0
+      cs = cash32Polymod pd
+      c32 = B.map f (b32 <> cs)
+      f = fromIntegral . ord . (charset !!) . fromIntegral
+   in pfx <> ":" <> decodeUtf8 c32
+
+-- | Convert base of 'ByteString' from eight bits per byte to five bits per
+-- byte, adding padding as necessary.
+toBase32 :: ByteString -> ByteString
+toBase32 =
+  B.pack
+    . map fromIntegral
+    . fst
+    . convertBits True 8 5
+    . map fromIntegral
+    . B.unpack
+
+-- | Convert base of 'ByteString' from five to eight bits per byte. Ignore
+-- padding to be symmetric with respect to 'toBase32' function.
+toBase256 :: ByteString -> ByteString
+toBase256 =
+  B.pack
+    . map fromIntegral
+    . fst
+    . convertBits False 5 8
+    . map fromIntegral
+    . B.unpack
+
+-- | Obtain 'CashVersion' and payload length from 'CashAddr' version byte.
+decodeVersionByte :: Word8 -> Maybe (CashVersion, Int)
+decodeVersionByte vb = do
+  guard (vb .&. 0x80 == 0)
+  return (ver, len)
+  where
+    ver = vb `shiftR` 3
+    len = ls !! fromIntegral (vb .&. 0x07)
+    ls = [20, 24, 28, 32, 40, 48, 56, 64]
+
+-- | Encode 'CashVersion' and length into version byte. Fail if version is
+-- larger than five bits, or length incorrect, since that is invalid.
+encodeVersionByte :: CashVersion -> Int -> Maybe Word8
+encodeVersionByte ver len = do
+  guard (ver == ver .&. 0x0f)
+  l <- case len of
+    20 -> Just 0
+    24 -> Just 1
+    28 -> Just 2
+    32 -> Just 3
+    40 -> Just 4
+    48 -> Just 5
+    56 -> Just 6
+    64 -> Just 7
+    _ -> Nothing
+  return ((ver `shiftL` 3) .|. l)
+
+-- | Calculate or validate checksum from base32 'ByteString' (excluding prefix).
+cash32Polymod :: ByteString -> ByteString
+cash32Polymod v =
+  B.pack
+    [fromIntegral (polymod `shiftR` (5 * (7 - i))) .&. 0x1f | i <- [0 .. 7]]
+  where
+    polymod = B.foldl' outer (1 :: Word64) v `xor` 1
+    outer c d =
+      let c0 = (fromIntegral (c `shiftR` 35) :: Word8)
+          c' = ((c .&. 0x07ffffffff) `shiftL` 5) `xor` fromIntegral d
+       in foldl' (inner c0) c' (zip [0 ..] generator)
+    generator =
+      [0x98f2bc8e61, 0x79b76d99e2, 0xf33e5fb3c4, 0xae2eabe2a8, 0x1e4f43e470]
+    inner c0 c (b, g)
+      | c0 `testBit` b = c `xor` g
+      | otherwise = c
+
+-- | Validate that polymod 'ByteString' (eight bytes) is equal to zero.
+verifyCash32Polymod :: ByteString -> Bool
+verifyCash32Polymod = (== B.replicate 8 0)
diff --git a/src/Haskoin/Block.hs b/src/Haskoin/Block.hs
new file mode 100644
--- /dev/null
+++ b/src/Haskoin/Block.hs
@@ -0,0 +1,21 @@
+{-# LANGUAGE DuplicateRecordFields #-}
+
+-- |
+-- Module      : Haskoin.Block
+-- Copyright   : No rights reserved
+-- License     : MIT
+-- Maintainer  : jprupp@protonmail.ch
+-- Stability   : experimental
+-- Portability : POSIX
+--
+-- Most functions relating to blocks are exported by this module.
+module Haskoin.Block
+  ( module Haskoin.Block.Common,
+    module Haskoin.Block.Headers,
+    module Haskoin.Block.Merkle,
+  )
+where
+
+import Haskoin.Block.Common
+import Haskoin.Block.Headers
+import Haskoin.Block.Merkle
diff --git a/src/Haskoin/Block/Common.hs b/src/Haskoin/Block/Common.hs
new file mode 100644
--- /dev/null
+++ b/src/Haskoin/Block/Common.hs
@@ -0,0 +1,427 @@
+{-# LANGUAGE DeriveAnyClass #-}
+{-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE DuplicateRecordFields #-}
+{-# LANGUAGE ImportQualifiedPost #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE NoFieldSelectors #-}
+
+-- |
+-- Module      : Haskoin.Block.Common
+-- Copyright   : No rights reserved
+-- License     : MIT
+-- Maintainer  : jprupp@protonmail.ch
+-- Stability   : experimental
+-- Portability : POSIX
+--
+-- Common data types and functions to handle blocks from the block chain.
+module Haskoin.Block.Common
+  ( -- * Blocks
+    Block (..),
+    BlockHeight,
+    Timestamp,
+    BlockHeader (..),
+    headerHash,
+    BlockLocator,
+    GetBlocks (..),
+    GetHeaders (..),
+    BlockHeaderCount,
+    BlockHash (..),
+    blockHashToHex,
+    hexToBlockHash,
+    Headers (..),
+    decodeCompact,
+    encodeCompact,
+  )
+where
+
+import Control.DeepSeq
+import Control.Monad
+import Data.Aeson
+import Data.Aeson.Encoding
+import Data.Binary (Binary (..))
+import Data.Bits
+import Data.ByteString qualified as B
+import Data.ByteString.Builder (char7)
+import Data.ByteString.Lazy qualified as L
+import Data.Bytes.Get
+import Data.Bytes.Put
+import Data.Bytes.Serial
+import Data.Hashable
+import Data.Maybe
+import Data.Serialize (Serialize (..))
+import Data.String
+import Data.String.Conversions
+import Data.Text (Text)
+import Data.Word
+import GHC.Generics
+import Haskoin.Crypto.Hash
+import Haskoin.Network.Common
+import Haskoin.Transaction.Common
+import Haskoin.Util.Helpers
+import Text.Read qualified as R
+
+-- | Height of a block in the block chain, starting at 0 for Genesis.
+type BlockHeight = Word32
+
+-- | Block timestamp as Unix time (seconds since 1970-01-01 00:00 UTC).
+type Timestamp = Word32
+
+-- | Block header and transactions.
+data Block = Block
+  { header :: !BlockHeader,
+    txs :: ![Tx]
+  }
+  deriving (Eq, Show, Read, Generic, Hashable, NFData)
+
+instance Serial Block where
+  deserialize = do
+    header <- deserialize
+    (VarInt c) <- deserialize
+    txs <- replicateM (fromIntegral c) deserialize
+    return $ Block header txs
+  serialize (Block h txs) = do
+    serialize h
+    putVarInt $ length txs
+    forM_ txs serialize
+
+instance Serialize Block where
+  get = deserialize
+  put = serialize
+
+instance Binary Block where
+  get = deserialize
+  put = serialize
+
+instance ToJSON Block where
+  toJSON (Block h t) =
+    object ["header" .= h, "transactions" .= t]
+  toEncoding (Block h t) =
+    pairs $
+      mconcat
+        [ "header" `pair` toEncoding h,
+          "transactions" `pair` list toEncoding t
+        ]
+
+instance FromJSON Block where
+  parseJSON =
+    withObject "Block" $ \o ->
+      Block <$> o .: "header" <*> o .: "transactions"
+
+-- | Block header hash. To be serialized reversed for display purposes.
+newtype BlockHash = BlockHash {get :: Hash256}
+  deriving (Eq, Ord, Generic, Hashable, Serial, NFData)
+
+instance Serialize BlockHash where
+  put = serialize
+  get = deserialize
+
+instance Binary BlockHash where
+  put = serialize
+  get = deserialize
+
+instance Show BlockHash where
+  showsPrec _ = shows . blockHashToHex
+
+instance Read BlockHash where
+  readPrec = do
+    R.String str <- R.lexP
+    maybe R.pfail return $ hexToBlockHash $ cs str
+
+instance IsString BlockHash where
+  fromString s =
+    fromMaybe (error "Could not read block hash from hex string") $
+      hexToBlockHash $
+        cs s
+
+instance FromJSON BlockHash where
+  parseJSON =
+    withText "BlockHash" $
+      maybe mzero return . hexToBlockHash
+
+instance ToJSON BlockHash where
+  toJSON = String . blockHashToHex
+  toEncoding = hexEncoding . L.reverse . runPutL . serialize
+
+-- | Block hashes are reversed with respect to the in-memory byte order in a
+-- block hash when displayed.
+blockHashToHex :: BlockHash -> Text
+blockHashToHex (BlockHash h) = encodeHex (B.reverse (runPutS (serialize h)))
+
+-- | Convert a human-readable hex block hash into a 'BlockHash'. Bytes are
+-- reversed as normal.
+hexToBlockHash :: Text -> Maybe BlockHash
+hexToBlockHash hex = do
+  bs <- B.reverse <$> decodeHex hex
+  h <- eitherToMaybe (runGetS deserialize bs)
+  return $ BlockHash h
+
+-- | Data type recording information of a 'Block'. The hash of a block is
+-- defined as the hash of this data structure, serialized. The block mining
+-- process involves finding a partial hash collision by varying the nonce in the
+-- 'BlockHeader' and/or additional entropy in the coinbase 'Transaction' of this
+-- 'Block'. Variations in the coinbase will result in different merkle roots in
+-- the 'BlockHeader'.
+data BlockHeader = BlockHeader
+  { version :: !Word32, --  4 bytes
+
+    -- | hash of the previous block (parent)
+    prev :: !BlockHash, -- 32 bytes
+
+    -- | root of the merkle tree of transactions
+    merkle :: !Hash256, -- 32 bytes
+
+    -- | unix timestamp
+    timestamp :: !Timestamp, --  4 bytes
+
+    -- | difficulty target
+    bits :: !Word32, --  4 bytes
+
+    -- | random nonce
+    nonce :: !Word32 --  4 bytes
+  }
+  deriving (Eq, Ord, Show, Read, Generic, Hashable, NFData)
+
+-- 80 bytes
+
+instance ToJSON BlockHeader where
+  toJSON (BlockHeader v p m t b n) =
+    object
+      [ "version" .= v,
+        "prevblock" .= p,
+        "merkleroot" .= encodeHex (runPutS $ serialize m),
+        "timestamp" .= t,
+        "bits" .= b,
+        "nonce" .= n
+      ]
+  toEncoding (BlockHeader v p m t b n) =
+    pairs $
+      mconcat
+        [ "version" `pair` word32 v,
+          "prevblock" `pair` toEncoding p,
+          "merkleroot" `pair` hexEncoding (runPutL $ serialize m),
+          "timestamp" `pair` toEncoding t,
+          "bits" `pair` toEncoding b,
+          "nonce" `pair` toEncoding n
+        ]
+
+instance FromJSON BlockHeader where
+  parseJSON =
+    withObject "BlockHeader" $ \o ->
+      BlockHeader
+        <$> o .: "version"
+        <*> o .: "prevblock"
+        <*> (f =<< o .: "merkleroot")
+        <*> o .: "timestamp"
+        <*> o .: "bits"
+        <*> o .: "nonce"
+    where
+      f = maybe mzero return . (eitherToMaybe . runGetS deserialize <=< decodeHex)
+
+instance Serial BlockHeader where
+  deserialize = do
+    v <- getWord32le
+    p <- deserialize
+    m <- deserialize
+    t <- getWord32le
+    b <- getWord32le
+    n <- getWord32le
+    return
+      BlockHeader
+        { version = v,
+          prev = p,
+          merkle = m,
+          timestamp = t,
+          bits = b,
+          nonce = n
+        }
+  serialize (BlockHeader v p m bt bb n) = do
+    putWord32le v
+    serialize p
+    serialize m
+    putWord32le bt
+    putWord32le bb
+    putWord32le n
+
+instance Binary BlockHeader where
+  put = serialize
+  get = deserialize
+
+instance Serialize BlockHeader where
+  put = serialize
+  get = deserialize
+
+-- | Compute hash of 'BlockHeader'.
+headerHash :: BlockHeader -> BlockHash
+headerHash = BlockHash . doubleSHA256 . runPutS . serialize
+
+-- | A block locator is a set of block headers, denser towards the best block
+-- and sparser towards the genesis block. It starts at the highest block known.
+-- It is used by a node to synchronize against the network. When the locator is
+-- provided to a peer, it will send back block hashes starting from the first
+-- block in the locator that it recognizes.
+type BlockLocator = [BlockHash]
+
+-- | Data type representing a getblocks message request. It is used in the
+-- bitcoin protocol to retrieve blocks from a peer by providing it a
+-- 'BlockLocator' object. The response to a 'GetBlocks' message is an 'Inv'
+-- message containing a list of block hashes that the peer believes this node is
+-- missing. The number of block hashes in that inv message will end at the stop
+-- block hash, at at the tip of the chain, or after 500 entries, whichever comes
+-- earlier.
+data GetBlocks = GetBlocks
+  { version :: !Word32,
+    -- | block locator object
+    locator :: !BlockLocator,
+    -- | hash of the last desired block
+    stop :: !BlockHash
+  }
+  deriving (Eq, Show, Read, Generic, NFData)
+
+instance Serial GetBlocks where
+  deserialize =
+    GetBlocks
+      <$> getWord32le
+      <*> (repList =<< deserialize)
+      <*> deserialize
+    where
+      repList (VarInt c) = replicateM (fromIntegral c) deserialize
+  serialize (GetBlocks v xs h) = do
+    putWord32le v
+    putVarInt $ length xs
+    forM_ xs serialize
+    serialize h
+
+instance Serialize GetBlocks where
+  put = serialize
+  get = deserialize
+
+-- | Similar to the 'GetBlocks' message type but for retrieving block headers
+-- only. The response to a 'GetHeaders' request is a 'Headers' message
+-- containing a list of block headers. A maximum of 2000 block headers can be
+-- returned. 'GetHeaders' is used by simplified payment verification (SPV)
+-- clients to exclude block contents when synchronizing the block chain.
+data GetHeaders = GetHeaders
+  { version :: !Word32,
+    -- | block locator object
+    locator :: !BlockLocator,
+    -- | hash of the last desired block header
+    stop :: !BlockHash
+  }
+  deriving (Eq, Show, Read, Generic, NFData)
+
+instance Serial GetHeaders where
+  deserialize =
+    GetHeaders
+      <$> getWord32le
+      <*> (repList =<< deserialize)
+      <*> deserialize
+    where
+      repList (VarInt c) = replicateM (fromIntegral c) deserialize
+  serialize (GetHeaders v xs h) = do
+    putWord32le v
+    putVarInt $ length xs
+    forM_ xs serialize
+    serialize h
+
+instance Serialize GetHeaders where
+  put = serialize
+  get = deserialize
+
+instance Binary GetHeaders where
+  put = serialize
+  get = deserialize
+
+-- | 'BlockHeader' type with a transaction count as 'VarInt'
+type BlockHeaderCount = (BlockHeader, VarInt)
+
+-- | The 'Headers' type is used to return a list of block headers in
+-- response to a 'GetHeaders' message.
+newtype Headers = Headers
+  { -- | list of block headers with transaction count
+    list :: [BlockHeaderCount]
+  }
+  deriving (Eq, Show, Read, Generic, NFData)
+
+instance Serial Headers where
+  deserialize = Headers <$> (repList =<< deserialize)
+    where
+      repList (VarInt c) = replicateM (fromIntegral c) action
+      action = liftM2 (,) deserialize deserialize
+  serialize (Headers xs) = do
+    putVarInt $ length xs
+    forM_ xs $ \(a, b) -> serialize a >> serialize b
+
+instance Serialize Headers where
+  put = serialize
+  get = deserialize
+
+instance Binary Headers where
+  put = serialize
+  get = deserialize
+
+-- | Decode the compact number used in the difficulty target of a block.
+--
+-- The compact format is a representation of a whole number \(N\) using an
+-- unsigned 32-bit number similar to a floating point format. The most
+-- significant 8 bits are the unsigned exponent of base 256. This exponent can
+-- be thought of as the number of bytes of \(N\). The lower 23 bits are the
+-- mantissa. Bit number 24 represents the sign of \(N\).
+--
+-- \[
+-- N = -1^{sign} \times mantissa \times 256^{exponent-3}
+-- \]
+decodeCompact ::
+  Word32 ->
+  -- | true means overflow
+  (Integer, Bool)
+decodeCompact nCompact = (if neg then res * (-1) else res, over)
+  where
+    nSize :: Int
+    nSize = fromIntegral nCompact `shiftR` 24
+    nWord' :: Word32
+    nWord' = nCompact .&. 0x007fffff
+    nWord :: Word32
+    nWord
+      | nSize <= 3 = nWord' `shiftR` (8 * (3 - nSize))
+      | otherwise = nWord'
+    res :: Integer
+    res
+      | nSize <= 3 = fromIntegral nWord
+      | otherwise = fromIntegral nWord `shiftL` (8 * (nSize - 3))
+    neg = nWord /= 0 && (nCompact .&. 0x00800000) /= 0
+    over =
+      nWord /= 0
+        && ( nSize > 34
+               || nWord > 0xff && nSize > 33
+               || nWord > 0xffff && nSize > 32
+           )
+
+-- | Encode an 'Integer' to the compact number format used in the difficulty
+-- target of a block.
+encodeCompact :: Integer -> Word32
+encodeCompact i = nCompact
+  where
+    i' = abs i
+    neg = i < 0
+    nSize' :: Int
+    nSize' =
+      let f 0 = 0
+          f n = 1 + f (n `shiftR` 8)
+       in f i'
+    nCompact''' :: Word32
+    nCompact'''
+      | nSize' <= 3 = fromIntegral $ (low64 .&. i') `shiftL` (8 * (3 - nSize'))
+      | otherwise = fromIntegral $ low64 .&. (i' `shiftR` (8 * (nSize' - 3)))
+    nCompact'' :: Word32
+    nSize :: Int
+    (nCompact'', nSize)
+      | nCompact''' .&. 0x00800000 /= 0 = (nCompact''' `shiftR` 8, nSize' + 1)
+      | otherwise = (nCompact''', nSize')
+    nCompact' :: Word32
+    nCompact' = nCompact'' .|. (fromIntegral nSize `shiftL` 24)
+    nCompact :: Word32
+    nCompact
+      | neg && (nCompact' .&. 0x007fffff /= 0) = nCompact' .|. 0x00800000
+      | otherwise = nCompact'
+    low64 :: Integer
+    low64 = 0xffffffffffffffff
diff --git a/src/Haskoin/Block/Headers.hs b/src/Haskoin/Block/Headers.hs
new file mode 100644
--- /dev/null
+++ b/src/Haskoin/Block/Headers.hs
@@ -0,0 +1,974 @@
+{-# LANGUAGE DeriveAnyClass #-}
+{-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE DuplicateRecordFields #-}
+{-# LANGUAGE FlexibleContexts #-}
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE ImportQualifiedPost #-}
+{-# LANGUAGE LambdaCase #-}
+{-# LANGUAGE OverloadedRecordDot #-}
+{-# LANGUAGE RecordWildCards #-}
+{-# LANGUAGE NoFieldSelectors #-}
+
+-- |
+-- Module      : Haskoin.Block.Headers
+-- Copyright   : No rights reserved
+-- License     : MIT
+-- Maintainer  : jprupp@protonmail.ch
+-- Stability   : experimental
+-- Portability : POSIX
+--
+-- Block chain header synchronization and proof-of-work consensus functions.
+module Haskoin.Block.Headers
+  ( -- * Block Headers
+    BlockNode (..),
+    BlockHeaders (..),
+    BlockWork,
+    genesisNode,
+    genesisBlock,
+    isGenesis,
+    chooseBest,
+
+    -- ** Header Store
+    parentBlock,
+    getParents,
+    getAncestor,
+    splitPoint,
+    connectBlocks,
+    connectBlock,
+    blockLocator,
+
+    -- ** Header Memory Store
+    HeaderMemory (..),
+    ShortBlockHash,
+    BlockMap,
+    shortBlockHash,
+    initialChain,
+    genesisMap,
+
+    -- ** Helper Functions
+    appendBlocks,
+    validBlock,
+    validCP,
+    afterLastCP,
+    bip34,
+    validVersion,
+    lastNoMinDiff,
+    nextWorkRequired,
+    nextEdaWorkRequired,
+    nextDaaWorkRequired,
+    nextAsertWorkRequired,
+    computeAsertBits,
+    computeTarget,
+    getSuitableBlock,
+    nextPowWorkRequired,
+    calcNextWork,
+    isValidPOW,
+    blockPOW,
+    headerWork,
+    diffInterval,
+    blockLocatorNodes,
+    mineBlock,
+    computeSubsidy,
+    mtp,
+    firstGreaterOrEqual,
+    lastSmallerOrEqual,
+  )
+where
+
+import Control.Applicative ((<|>))
+import Control.DeepSeq
+import Control.Monad (guard, mzero, unless, when)
+import Control.Monad.Except (ExceptT (..), runExceptT, throwError)
+import Control.Monad.State.Strict as State (StateT, get, gets, lift, modify)
+import Control.Monad.Trans.Maybe
+import Data.Binary (Binary (..))
+import Data.Bits (shiftL, shiftR, (.&.))
+import Data.ByteString qualified as B
+import Data.ByteString.Short (ShortByteString, fromShort, toShort)
+import Data.Bytes.Get
+import Data.Bytes.Put
+import Data.Bytes.Serial
+import Data.Function (on)
+import Data.HashMap.Strict (HashMap)
+import Data.HashMap.Strict qualified as HashMap
+import Data.Hashable
+import Data.List (sort, sortBy)
+import Data.Maybe (fromMaybe, listToMaybe)
+import Data.Serialize (Serialize (..))
+import Data.Typeable (Typeable)
+import Data.Word (Word32, Word64)
+import GHC.Generics (Generic)
+import Haskoin.Block.Common
+import Haskoin.Crypto
+import Haskoin.Network.Data
+import Haskoin.Transaction.Genesis
+import Haskoin.Util
+
+-- | Short version of the block hash. Uses the good end of the hash (the part
+-- that doesn't have a long string of zeroes).
+type ShortBlockHash = Word64
+
+-- | Memory-based map to a serialized 'BlockNode' data structure.
+-- 'ShortByteString' is used to avoid memory fragmentation and make the data
+-- structure compact.
+type BlockMap = HashMap ShortBlockHash ShortByteString
+
+-- | Represents accumulated work in the block chain so far.
+type BlockWork = Integer
+
+-- | Data structure representing a block header and its position in the
+-- block chain.
+data BlockNode = BlockNode
+  { header :: !BlockHeader,
+    height :: !BlockHeight,
+    -- | accumulated work so far
+    work :: !BlockWork,
+    -- | skip magic block hash
+    skip :: !BlockHash
+  }
+  deriving (Show, Read, Generic, Hashable, NFData)
+
+instance Serial BlockNode where
+  deserialize = do
+    header <- deserialize
+    height <- getWord32le
+    work <- getInteger
+    if height == 0
+      then do
+        let skip = headerHash header
+        return BlockNode {..}
+      else do
+        skip <- deserialize
+        return BlockNode {..}
+  serialize bn = do
+    serialize $ bn.header
+    putWord32le $ bn.height
+    putInteger $ bn.work
+    case bn.height of
+      0 -> return ()
+      _ -> serialize $ bn.skip
+
+instance Serialize BlockNode where
+  put = serialize
+  get = deserialize
+
+instance Binary BlockNode where
+  put = serialize
+  get = deserialize
+
+instance Eq BlockNode where
+  (==) = (==) `on` (.header)
+
+instance Ord BlockNode where
+  compare = compare `on` (.height)
+
+-- | Memory-based header tree.
+data HeaderMemory = HeaderMemory
+  { blocks :: !BlockMap,
+    best :: !BlockNode
+  }
+  deriving (Eq, Typeable, Show, Read, Generic, Hashable, NFData)
+
+-- | Typeclass for block header chain storage monad.
+class (Monad m) => BlockHeaders m where
+  -- | Add a new 'BlockNode' to the chain. Does not validate.
+  addBlockHeader :: BlockNode -> m ()
+
+  -- | Get a 'BlockNode' associated with a 'BlockHash'.
+  getBlockHeader :: BlockHash -> m (Maybe BlockNode)
+
+  -- | Locate the 'BlockNode' for the highest block in the chain
+  getBestBlockHeader :: m BlockNode
+
+  -- | Set the highest block in the chain.
+  setBestBlockHeader :: BlockNode -> m ()
+
+  -- | Add a continuous bunch of block headers the chain. Does not validate.
+  addBlockHeaders :: [BlockNode] -> m ()
+  addBlockHeaders = mapM_ addBlockHeader
+
+instance (Monad m) => BlockHeaders (StateT HeaderMemory m) where
+  addBlockHeader = modify . addBlockHeaderMemory
+  getBlockHeader bh = getBlockHeaderMemory bh <$> State.get
+  getBestBlockHeader = gets (.best)
+  setBestBlockHeader bn = modify $ \s -> s {best = bn}
+
+-- | Initialize memory-based chain.
+initialChain :: Network -> HeaderMemory
+initialChain net =
+  HeaderMemory
+    { blocks = genesisMap net,
+      best = genesisNode net
+    }
+
+-- | Initialize map for memory-based chain.
+genesisMap :: Network -> BlockMap
+genesisMap net =
+  HashMap.singleton
+    (shortBlockHash (headerHash net.genesisHeader))
+    (toShort (runPutS (serialize (genesisNode net))))
+
+-- | Add block header to memory block map.
+addBlockHeaderMemory :: BlockNode -> HeaderMemory -> HeaderMemory
+addBlockHeaderMemory bn s = s {blocks = addBlockToMap bn s.blocks}
+
+-- | Get block header from memory block map.
+getBlockHeaderMemory :: BlockHash -> HeaderMemory -> Maybe BlockNode
+getBlockHeaderMemory bh s = do
+  bs <- shortBlockHash bh `HashMap.lookup` s.blocks
+  eitherToMaybe . runGetS deserialize $ fromShort bs
+
+-- | Calculate short block hash taking eight non-zero bytes from the 16-byte
+-- hash. This function will take the bytes that are not on the zero-side of the
+-- hash, making colissions between short block hashes difficult.
+shortBlockHash :: BlockHash -> ShortBlockHash
+shortBlockHash =
+  either error id . runGetS deserialize . B.take 8 . runPutS . serialize
+
+-- | Add a block to memory-based block map.
+addBlockToMap :: BlockNode -> BlockMap -> BlockMap
+addBlockToMap node =
+  HashMap.insert
+    (shortBlockHash $ headerHash $ node.header)
+    (toShort $ runPutS $ serialize node)
+
+-- | Get the ancestor of the provided 'BlockNode' at the specified
+-- 'BlockHeight'.
+getAncestor ::
+  (BlockHeaders m) =>
+  BlockHeight ->
+  BlockNode ->
+  m (Maybe BlockNode)
+getAncestor height node
+  | height > node.height = return Nothing
+  | otherwise = go node
+  where
+    e1 = error "Could not get current walk skip"
+    e2 = error "Could not get previous walk skip"
+    go walk
+      | walk.height > height =
+          let height_b = skipHeight (walk.height)
+              height_a = skipHeight (walk.height - 1)
+              not_genesis = not (isGenesis walk)
+              is_b = height_b == height
+              below_b = height_b > height
+              at_or_below_a = height <= height_a
+              far_enough = height_b - 2 > height_a && at_or_below_a
+              recurse_b = below_b && not far_enough
+              cond = not_genesis && (is_b || recurse_b)
+           in if cond
+                then do
+                  walk' <- fromMaybe e1 <$> getBlockHeader walk.skip
+                  go walk'
+                else do
+                  walk' <- fromMaybe e2 <$> getBlockHeader walk.header.prev
+                  go walk'
+      | otherwise = return $ Just walk
+
+-- | Is the provided 'BlockNode' the Genesis block?
+isGenesis :: BlockNode -> Bool
+isGenesis BlockNode {height = 0} = True
+isGenesis _ = False
+
+-- | Build the genesis 'BlockNode' for the supplied 'Network'.
+genesisNode :: Network -> BlockNode
+genesisNode net =
+  BlockNode
+    { header = net.genesisHeader,
+      height = 0,
+      work = headerWork net.genesisHeader,
+      skip = headerHash net.genesisHeader
+    }
+
+-- | Validate a list of continuous block headers and import them to the
+-- block chain. Return 'Left' on failure with error information.
+connectBlocks ::
+  (BlockHeaders m) =>
+  Network ->
+  -- | current time
+  Timestamp ->
+  [BlockHeader] ->
+  m (Either String [BlockNode])
+connectBlocks _ _ [] = return $ Right []
+connectBlocks net t bhs@(bh : _) =
+  runExceptT $ do
+    unless (chained bhs) $
+      throwError "Blocks to connect do not form a chain"
+    par <-
+      maybeToExceptT
+        "Could not get parent block"
+        (MaybeT (parentBlock bh))
+    pars <- lift $ getParents 10 par
+    bb <- lift getBestBlockHeader
+    go par [] bb par pars bhs >>= \case
+      bns@(bn : _) -> do
+        lift $ addBlockHeaders bns
+        let bb' = chooseBest bn bb
+        when (bb' /= bb) $ lift $ setBestBlockHeader bb'
+        return bns
+      _ -> undefined
+  where
+    chained (h1 : h2 : hs) = headerHash h1 == h2.prev && chained (h2 : hs)
+    chained _ = True
+    skipit lbh ls par
+      | sh == lbh.height = return lbh
+      | sh < lbh.height = do
+          skM <- lift $ getAncestor sh lbh
+          case skM of
+            Just sk -> return sk
+            Nothing ->
+              throwError $
+                "BUG: Could not get skip for block "
+                  ++ show (headerHash $ par.header)
+      | otherwise = do
+          let sn = ls !! fromIntegral (par.height - sh)
+          when (sn.height /= sh) $
+            throwError "BUG: Node height not right in skip"
+          return sn
+      where
+        sh = skipHeight (par.height + 1)
+    go _ acc _ _ _ [] = return acc
+    go lbh acc bb par pars (h : hs) = do
+      sk <- skipit lbh acc par
+      bn <- ExceptT . return $ validBlock net t bb par pars h sk
+      go lbh (bn : acc) (chooseBest bn bb) bn (take 10 $ par : pars) hs
+
+-- | Block's parent. If the block header is in the store, its parent must also
+-- be there. No block header get deleted or pruned from the store.
+parentBlock ::
+  (BlockHeaders m) =>
+  BlockHeader ->
+  m (Maybe BlockNode)
+parentBlock = getBlockHeader . (.prev)
+
+-- | Validate and connect single block header to the block chain. Return 'Left'
+-- if fails to be validated.
+connectBlock ::
+  (BlockHeaders m) =>
+  Network ->
+  -- | current time
+  Timestamp ->
+  BlockHeader ->
+  m (Either String BlockNode)
+connectBlock net t bh =
+  runExceptT $ do
+    par <-
+      maybeToExceptT
+        "Could not get parent block"
+        (MaybeT (parentBlock bh))
+    pars <- lift $ getParents 10 par
+    skM <- lift $ getAncestor (skipHeight (par.height + 1)) par
+    sk <-
+      case skM of
+        Just sk -> return sk
+        Nothing ->
+          throwError $
+            "BUG: Could not get skip for block "
+              ++ show (headerHash $ par.header)
+    bb <- lift getBestBlockHeader
+    bn <- ExceptT . return $ validBlock net t bb par pars bh sk
+    let bb' = chooseBest bb bn
+    lift $ addBlockHeader bn
+    when (bb /= bb') . lift $ setBestBlockHeader bb'
+    return bn
+
+-- | Validate this block header. Build a 'BlockNode' if successful.
+validBlock ::
+  Network ->
+  -- | current time
+  Timestamp ->
+  -- | best block
+  BlockNode ->
+  -- | immediate parent
+  BlockNode ->
+  -- | 10 parents above
+  [BlockNode] ->
+  -- | header to validate
+  BlockHeader ->
+  -- | skip node (black magic)
+  BlockNode ->
+  Either String BlockNode
+validBlock net t bb par pars bh sk = do
+  let mt = medianTime . map (.header.timestamp) $ par : pars
+      nt = bh.timestamp
+      hh = headerHash bh
+      nv = bh.version
+      ng = par.height + 1
+      aw = par.work + headerWork bh
+  unless (isValidPOW net bh) $
+    Left $
+      "Proof of work failed: " ++ show (headerHash bh)
+  unless (nt <= t + 2 * 60 * 60) $
+    Left $
+      "Invalid header timestamp: " ++ show nt
+  unless (nt >= mt) $
+    Left $
+      "Block timestamp too early: " ++ show nt
+  unless (afterLastCP net (bb.height) ng) $
+    Left $
+      "Rewriting pre-checkpoint chain: " ++ show ng
+  unless (validCP net ng hh) $
+    Left $
+      "Rejected checkpoint: " ++ show ng
+  unless (bip34 net ng hh) $
+    Left $
+      "Rejected BIP-34 block: " ++ show hh
+  unless (validVersion net ng nv) $
+    Left $
+      "Invalid block version: " ++ show nv
+  return
+    BlockNode
+      { header = bh,
+        height = ng,
+        work = aw,
+        skip = headerHash sk.header
+      }
+
+-- | Return the median of all provided timestamps. Can be unsorted. Error on
+-- empty list.
+medianTime :: [Timestamp] -> Timestamp
+medianTime ts
+  | null ts = error "Cannot compute median time of empty header list"
+  | otherwise = sort ts !! (length ts `div` 2)
+
+-- | Calculate the height of the skip (magic) block that corresponds to the
+-- given height. The block hash of the ancestor at that height will be placed on
+-- the 'BlockNode' structure to help locate ancestors at any height quickly.
+skipHeight :: BlockHeight -> BlockHeight
+skipHeight height
+  | height < 2 = 0
+  | height .&. 1 /= 0 = invertLowestOne (invertLowestOne $ height - 1) + 1
+  | otherwise = invertLowestOne height
+
+-- | Part of the skip black magic calculation.
+invertLowestOne :: BlockHeight -> BlockHeight
+invertLowestOne height = height .&. (height - 1)
+
+-- | Get a number of parents for the provided block.
+getParents ::
+  (BlockHeaders m) =>
+  Int ->
+  BlockNode ->
+  -- | starts from immediate parent
+  m [BlockNode]
+getParents = getpars []
+  where
+    getpars acc 0 _ = return $ reverse acc
+    getpars acc n BlockNode {..}
+      | height == 0 = return $ reverse acc
+      | otherwise = do
+          parM <- getBlockHeader header.prev
+          case parM of
+            Just bn -> getpars (bn : acc) (n - 1) bn
+            Nothing -> error "BUG: All non-genesis blocks should have a parent"
+
+-- | Verify that checkpoint location is valid.
+validCP ::
+  Network ->
+  -- | new child height
+  BlockHeight ->
+  -- | new child hash
+  BlockHash ->
+  Bool
+validCP net height newChildHash =
+  case lookup height net.checkpoints of
+    Just cpHash -> cpHash == newChildHash
+    Nothing -> True
+
+-- | New block height above the last checkpoint imported. Used to prevent a
+-- reorg below the highest checkpoint that was already imported.
+afterLastCP ::
+  Network ->
+  -- | best height
+  BlockHeight ->
+  -- | new imported block height
+  BlockHeight ->
+  Bool
+afterLastCP net bestHeight newChildHeight =
+  case lM of
+    Just l -> l < newChildHeight
+    Nothing -> True
+  where
+    lM =
+      listToMaybe . reverse $
+        [c | (c, _) <- net.checkpoints, c <= bestHeight]
+
+-- | This block should be at least version 2 (BIP34). Block height must be
+-- included in the coinbase transaction to prevent non-unique transaction
+-- hashes.
+bip34 ::
+  Network ->
+  -- | new child height
+  BlockHeight ->
+  -- | new child hash
+  BlockHash ->
+  Bool
+bip34 net height hsh
+  | fst net.bip34Block == 0 = True
+  | fst net.bip34Block == height = snd net.bip34Block == hsh
+  | otherwise = True
+
+-- | Check if the provided block height and version are valid.
+validVersion ::
+  Network ->
+  -- | new child height
+  BlockHeight ->
+  -- | new child version
+  Word32 ->
+  Bool
+validVersion net height version
+  | version < 2 = height < fst net.bip34Block
+  | version < 3 = height < net.bip66Height
+  | version < 4 = height < net.bip65Height
+  | otherwise = True
+
+-- | Find last block with normal, as opposed to minimum difficulty (for test
+-- networks).
+lastNoMinDiff :: (BlockHeaders m) => Network -> BlockNode -> m BlockNode
+lastNoMinDiff _ bn@BlockNode {height = 0} = return bn
+lastNoMinDiff net bn = do
+  let i = bn.height `mod` diffInterval net /= 0
+      c = encodeCompact net.powLimit
+      l = bn.header.bits == c
+      e1 =
+        error $
+          "Could not get block header for parent of "
+            ++ show (headerHash bn.header)
+  if i && l
+    then do
+      bn' <- fromMaybe e1 <$> getBlockHeader (bn.header.prev)
+      lastNoMinDiff net bn'
+    else return bn
+
+-- | Returns the work required on a block header given the previous block. This
+-- coresponds to @bitcoind@ function @GetNextWorkRequired@ in @main.cpp@.
+nextWorkRequired ::
+  (BlockHeaders m) =>
+  Network ->
+  BlockNode ->
+  BlockHeader ->
+  m Word32
+nextWorkRequired net par bh = do
+  ma <- getAsertAnchor net
+  case asert ma <|> daa <|> eda <|> pow of
+    Just f -> f par bh
+    Nothing -> error "Could not determine difficulty algorithm"
+  where
+    asert ma = do
+      anchor <- ma
+      guard (par.height > anchor.height)
+      return $ nextAsertWorkRequired net anchor
+    daa = do
+      daa_height <- net.daaHeight
+      guard (par.height + 1 >= daa_height)
+      return $ nextDaaWorkRequired net
+    eda = do
+      eda_height <- net.edaHeight
+      guard (par.height + 1 >= eda_height)
+      return $ nextEdaWorkRequired net
+    pow = return $ nextPowWorkRequired net
+
+-- | Find out the next amount of work required according to the Emergency
+-- Difficulty Adjustment (EDA) algorithm from Bitcoin Cash.
+nextEdaWorkRequired ::
+  (BlockHeaders m) => Network -> BlockNode -> BlockHeader -> m Word32
+nextEdaWorkRequired net par bh
+  | par.height + 1 `mod` diffInterval net == 0 =
+      nextWorkRequired net par bh
+  | mindiff = return (encodeCompact net.powLimit)
+  | par.header.bits == encodeCompact net.powLimit =
+      return (encodeCompact net.powLimit)
+  | otherwise = do
+      par6 <- fromMaybe e1 <$> getAncestor (par.height - 6) par
+      pars <- getParents 10 par
+      pars6 <- getParents 10 par6
+      let par6med =
+            medianTime $ map (.header.timestamp) (par6 : pars6)
+          parmed = medianTime $ map (.header.timestamp) (par : pars)
+          mtp6 = parmed - par6med
+      if mtp6 < 12 * 3600
+        then return $ par.header.bits
+        else
+          return $
+            let (diff, _) = decodeCompact par.header.bits
+                ndiff = diff + (diff `shiftR` 2)
+             in if net.powLimit > ndiff
+                  then encodeCompact net.powLimit
+                  else encodeCompact ndiff
+  where
+    mindiff = bh.timestamp > par.header.timestamp + net.targetSpacing * 2
+    e1 = error "Could not get seventh ancestor of block"
+
+-- | Find the next amount of work required according to the Difficulty
+-- Adjustment Algorithm (DAA) from Bitcoin Cash.
+nextDaaWorkRequired ::
+  (BlockHeaders m) => Network -> BlockNode -> BlockHeader -> m Word32
+nextDaaWorkRequired net par bh
+  | mindiff = return (encodeCompact net.powLimit)
+  | otherwise = do
+      unless (par.height >= diffInterval net) $
+        error "Block height below difficulty interval"
+      l <- getSuitableBlock par
+      par144 <- fromMaybe e1 <$> getAncestor (par.height - 144) par
+      f <- getSuitableBlock par144
+      let nextTarget = computeTarget net f l
+      if nextTarget > net.powLimit
+        then return $ encodeCompact net.powLimit
+        else return $ encodeCompact nextTarget
+  where
+    e1 = error "Cannot get ancestor at parent - 144 height"
+    mindiff = bh.timestamp > par.header.timestamp + net.targetSpacing * 2
+
+mtp :: (BlockHeaders m) => BlockNode -> m Timestamp
+mtp bn
+  | bn.height == 0 = return 0
+  | otherwise = do
+      pars <- getParents 11 bn
+      return $ medianTime (map (.header.timestamp) pars)
+
+firstGreaterOrEqual ::
+  (BlockHeaders m) =>
+  Network ->
+  (BlockNode -> m Ordering) ->
+  m (Maybe BlockNode)
+firstGreaterOrEqual = binSearch False
+
+lastSmallerOrEqual ::
+  (BlockHeaders m) =>
+  Network ->
+  (BlockNode -> m Ordering) ->
+  m (Maybe BlockNode)
+lastSmallerOrEqual = binSearch True
+
+binSearch ::
+  (BlockHeaders m) =>
+  Bool ->
+  Network ->
+  (BlockNode -> m Ordering) ->
+  m (Maybe BlockNode)
+binSearch top net f = runMaybeT $ do
+  (a, b) <- lift $ extremes net
+  go a b
+  where
+    go a b = do
+      m <- lift $ middleBlock a b
+      a' <- lift $ f a
+      b' <- lift $ f b
+      m' <- lift $ f m
+      r (a, a') (b, b') (m, m')
+    r (a, a') (b, b') (m, m')
+      | out_of_bounds a' b' = mzero
+      | select_first a' = return a
+      | select_last b' = return b
+      | no_middle a b = choose_one a b
+      | is_between a' m' = go a m
+      | is_between m' b' = go m b
+      | otherwise = mzero
+    select_first a'
+      | not top = a' /= LT
+      | otherwise = False
+    select_last b'
+      | top = b' /= GT
+      | otherwise = False
+    out_of_bounds a' b'
+      | top = a' == GT
+      | otherwise = b' == LT
+    no_middle a b = b.height - a.height <= 1
+    is_between a' b' = a' /= GT && b' /= LT
+    choose_one a b
+      | top = return a
+      | otherwise = return b
+
+extremes :: (BlockHeaders m) => Network -> m (BlockNode, BlockNode)
+extremes net = do
+  b <- getBestBlockHeader
+  return (genesisNode net, b)
+
+middleBlock :: (BlockHeaders m) => BlockNode -> BlockNode -> m BlockNode
+middleBlock a b =
+  getAncestor h b >>= \case
+    Nothing -> error "You fell into a pit full of mud and snakes"
+    Just x -> return x
+  where
+    h = middleOf a.height b.height
+
+middleOf :: (Integral a) => a -> a -> a
+middleOf a b = a + ((b - a) `div` 2)
+
+-- TODO: Use known anchor after fork
+getAsertAnchor :: (BlockHeaders m) => Network -> m (Maybe BlockNode)
+getAsertAnchor net =
+  case net.asertActivationTime of
+    Nothing -> return Nothing
+    Just act -> firstGreaterOrEqual net (f act)
+  where
+    f act bn = do
+      m <- mtp bn
+      return $ compare m act
+
+-- | Find the next amount of work required according to the aserti3-2d algorithm.
+nextAsertWorkRequired ::
+  (BlockHeaders m) =>
+  Network ->
+  BlockNode ->
+  BlockNode ->
+  BlockHeader ->
+  m Word32
+nextAsertWorkRequired net anchor par bh = do
+  anchor_parent <-
+    fromMaybe e_fork <$> getBlockHeader anchor.header.prev
+  let anchor_parent_time = toInteger anchor_parent.header.timestamp
+      time_diff = current_time - anchor_parent_time
+  return $ computeAsertBits halflife anchor_bits time_diff height_diff
+  where
+    halflife = net.asertHalfLife
+    anchor_height = toInteger anchor.height
+    anchor_bits = anchor.header.bits
+    current_height = toInteger par.height + 1
+    height_diff = current_height - anchor_height
+    current_time = toInteger bh.timestamp
+    e_fork = error "Could not get fork block header"
+
+idealBlockTime :: Integer
+idealBlockTime = 10 * 60
+
+rBits :: Int
+rBits = 16
+
+radix :: Integer
+radix = 1 `shiftL` rBits
+
+maxBits :: Word32
+maxBits = 0x1d00ffff
+
+maxTarget :: Integer
+maxTarget = fst $ decodeCompact maxBits
+
+computeAsertBits ::
+  Integer ->
+  Word32 ->
+  Integer ->
+  Integer ->
+  Word32
+computeAsertBits halflife anchor_bits time_diff height_diff =
+  if e2 >= 0 && e2 < 65536
+    then
+      if g4 == 0
+        then encodeCompact 1
+        else
+          if g4 > maxTarget
+            then maxBits
+            else encodeCompact g4
+    else error $ "Exponent not in range: " ++ show e2
+  where
+    g1 = fst (decodeCompact anchor_bits)
+    e1 =
+      ((time_diff - idealBlockTime * (height_diff + 1)) * radix)
+        `quot` halflife
+    s = e1 `shiftR` rBits
+    e2 = e1 - s * radix
+    g2 =
+      g1
+        * ( radix
+              + ( (195766423245049 * e2 + 971821376 * e2 ^ 2 + 5127 * e2 ^ 3 + 2 ^ 47)
+                    `shiftR` (rBits * 3)
+                )
+          )
+    g3 =
+      if s < 0
+        then g2 `shiftR` negate (fromIntegral s)
+        else g2 `shiftL` fromIntegral s
+    g4 = g3 `shiftR` rBits
+
+-- | Compute Bitcoin Cash DAA target for a new block.
+computeTarget :: Network -> BlockNode -> BlockNode -> Integer
+computeTarget net f l =
+  let work = (l.work - f.work) * fromIntegral net.targetSpacing
+      tspan = l.header.timestamp - f.header.timestamp
+      tspan'
+        | tspan > 288 * net.targetSpacing =
+            288 * net.targetSpacing
+        | tspan < 72 * net.targetSpacing =
+            72 * net.targetSpacing
+        | otherwise = tspan
+      work' = work `div` fromIntegral tspan'
+   in 2 ^ (256 :: Integer) `div` work'
+
+-- | Get suitable block for Bitcoin Cash DAA computation.
+getSuitableBlock :: (BlockHeaders m) => BlockNode -> m BlockNode
+getSuitableBlock par = do
+  unless (par.height >= 3) $ error "Block height is less than three"
+  blocks <- (par :) <$> getParents 2 par
+  return $ sortBy (compare `on` (.header.timestamp)) blocks !! 1
+
+-- | Returns the work required on a block header given the previous block. This
+-- coresponds to bitcoind function GetNextWorkRequired in main.cpp.
+nextPowWorkRequired ::
+  (BlockHeaders m) => Network -> BlockNode -> BlockHeader -> m Word32
+nextPowWorkRequired net par bh
+  | par.height + 1 `mod` diffInterval net /= 0 =
+      if net.minDiffBlocks
+        then
+          if ht > pt + delta
+            then return $ encodeCompact net.powLimit
+            else do
+              d <- lastNoMinDiff net par
+              return d.header.bits
+        else return par.header.bits
+  | otherwise = do
+      let rh = par.height - diffInterval net - 1
+      a <- fromMaybe e1 <$> getAncestor rh par
+      let t = a.header.timestamp
+      return $ calcNextWork net par.header t
+  where
+    e1 = error "Could not get ancestor for block header"
+    pt = par.header.timestamp
+    ht = bh.timestamp
+    delta = net.targetSpacing * 2
+
+-- | Computes the work required for the first block in a new retarget period.
+calcNextWork ::
+  Network ->
+  -- | last block in previous retarget (parent)
+  BlockHeader ->
+  -- | timestamp of first block in previous retarget
+  Timestamp ->
+  Word32
+calcNextWork net header time
+  | net.powNoRetarget = header.bits
+  | new > net.powLimit = encodeCompact net.powLimit
+  | otherwise = encodeCompact new
+  where
+    s = header.timestamp - time
+    n
+      | s < net.targetTimespan `div` 4 = net.targetTimespan `div` 4
+      | s > net.targetTimespan * 4 = net.targetTimespan * 4
+      | otherwise = s
+    l = fst $ decodeCompact header.bits
+    new = l * fromIntegral n `div` fromIntegral net.targetTimespan
+
+-- | Returns True if the difficulty target (bits) of the header is valid and the
+-- proof of work of the header matches the advertised difficulty target. This
+-- function corresponds to the function @CheckProofOfWork@ from @bitcoind@ in
+-- @main.cpp@.
+isValidPOW :: Network -> BlockHeader -> Bool
+isValidPOW net h
+  | target <= 0 || over || target > net.powLimit = False
+  | otherwise = blockPOW (headerHash h) <= fromIntegral target
+  where
+    (target, over) = decodeCompact h.bits
+
+-- | Returns the proof of work of a block header hash as an 'Integer' number.
+blockPOW :: BlockHash -> Integer
+blockPOW = bsToInteger . B.reverse . runPutS . serialize
+
+-- | Returns the work represented by this block. Work is defined as the number
+-- of tries needed to solve a block in the average case with respect to the
+-- target.
+headerWork :: BlockHeader -> Integer
+headerWork bh = largestHash `div` (target + 1)
+  where
+    target = fst $ decodeCompact bh.bits
+    largestHash = 1 `shiftL` 256
+
+-- | Number of blocks on average between difficulty cycles (2016 blocks).
+diffInterval :: Network -> Word32
+diffInterval net = net.targetTimespan `div` net.targetSpacing
+
+-- | Compare two blocks to get the best.
+chooseBest :: BlockNode -> BlockNode -> BlockNode
+chooseBest b1 b2
+  | b1.work == b2.work =
+      if b1.height >= b2.height
+        then b1
+        else b2
+  | b1.work > b2.work = b1
+  | otherwise = b2
+
+-- | Get list of blocks for a block locator.
+blockLocatorNodes :: (BlockHeaders m) => BlockNode -> m [BlockNode]
+blockLocatorNodes best =
+  reverse <$> go [] best 1
+  where
+    e1 = error "Could not get ancestor"
+    go loc bn n =
+      let loc' = bn : loc
+          n' =
+            if length loc' > 10
+              then n * 2
+              else 1
+       in if bn.height < n'
+            then do
+              a <- fromMaybe e1 <$> getAncestor 0 bn
+              return $ a : loc'
+            else do
+              let h = bn.height - n'
+              bn' <- fromMaybe e1 <$> getAncestor h bn
+              go loc' bn' n'
+
+-- | Get block locator.
+blockLocator :: (BlockHeaders m) => BlockNode -> m BlockLocator
+blockLocator bn = map (headerHash . (.header)) <$> blockLocatorNodes bn
+
+-- | Become rich beyond your wildest dreams.
+mineBlock :: Network -> Word32 -> BlockHeader -> BlockHeader
+mineBlock net seed h =
+  head
+    [ j
+      | i <- (+ seed) <$> [0 .. maxBound],
+        let j = h {nonce = i},
+        isValidPOW net j
+    ]
+
+-- | Generate and append new blocks (mining). Only practical in regtest network.
+appendBlocks ::
+  Network ->
+  -- | random seed
+  Word32 ->
+  BlockHeader ->
+  Int ->
+  [BlockHeader]
+appendBlocks _ _ _ 0 = []
+appendBlocks net seed bh i =
+  bh' : appendBlocks net seed bh' (i - 1)
+  where
+    bh' =
+      mineBlock
+        net
+        seed
+        bh
+          { prev = headerHash bh,
+            -- Just to make it different in every header
+            merkle = sha256 $ runPutS $ serialize seed
+          }
+
+-- | Find the last common block ancestor between provided block headers.
+splitPoint :: (BlockHeaders m) => BlockNode -> BlockNode -> m BlockNode
+splitPoint l r = do
+  let h = min l.height r.height
+  ll <- fromMaybe e <$> getAncestor h l
+  lr <- fromMaybe e <$> getAncestor h r
+  f ll lr
+  where
+    e = error "BUG: Could not get ancestor at lowest height"
+    f ll lr =
+      if ll == lr
+        then return lr
+        else do
+          let h = ll.height - 1
+          pl <- fromMaybe e <$> getAncestor h ll
+          pr <- fromMaybe e <$> getAncestor h lr
+          f pl pr
+
+-- | Generate the entire Genesis block for 'Network'.
+genesisBlock :: Network -> Ctx -> Block
+genesisBlock net ctx = Block net.genesisHeader [genesisTx ctx]
+
+-- | Compute block subsidy at particular height.
+computeSubsidy :: Network -> BlockHeight -> Word64
+computeSubsidy net height =
+  let halvings = height `div` net.halvingInterval
+      ini = 50 * 100 * 1000 * 1000
+   in if halvings >= 64
+        then 0
+        else ini `shiftR` fromIntegral halvings
diff --git a/src/Haskoin/Block/Merkle.hs b/src/Haskoin/Block/Merkle.hs
new file mode 100644
--- /dev/null
+++ b/src/Haskoin/Block/Merkle.hs
@@ -0,0 +1,302 @@
+{-# LANGUAGE DeriveAnyClass #-}
+{-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE DuplicateRecordFields #-}
+{-# LANGUAGE OverloadedRecordDot #-}
+{-# LANGUAGE NoFieldSelectors #-}
+
+-- |
+-- Module      : Haskoin.Block.Merkle
+-- Copyright   : No rights reserved
+-- License     : MIT
+-- Maintainer  : jprupp@protonmail.ch
+-- Stability   : experimental
+-- Portability : POSIX
+--
+-- Function to deal with Merkle trees inside blocks.
+module Haskoin.Block.Merkle
+  ( -- * Merkle Blocks
+    MerkleBlock (..),
+    MerkleRoot,
+    FlagBits,
+    PartialMerkleTree,
+    buildMerkleRoot,
+    merkleBlockTxs,
+    testMerkleRoot,
+    buildPartialMerkle,
+    decodeMerkleFlags,
+    encodeMerkleFlags,
+    calcTreeHeight,
+    calcTreeWidth,
+    hash2,
+    calcHash,
+    traverseAndBuild,
+    traverseAndExtract,
+    extractMatches,
+    splitIn,
+    boolsToWord8,
+  )
+where
+
+import Control.DeepSeq
+import Control.Monad (forM_, replicateM, when)
+import Data.Binary (Binary (..))
+import Data.Bits
+import qualified Data.ByteString as BS
+import Data.Bytes.Get
+import Data.Bytes.Put
+import Data.Bytes.Serial
+import Data.Either (isRight)
+import Data.Hashable
+import Data.Maybe
+import Data.Serialize (Serialize (..))
+import Data.Word (Word32, Word8)
+import GHC.Generics
+import Haskoin.Block.Common
+import Haskoin.Crypto.Hash
+import Haskoin.Network.Common
+import Haskoin.Network.Data
+import Haskoin.Transaction.Common
+
+-- | Hash of the block's Merkle root.
+type MerkleRoot = Hash256
+
+-- | Bits that are used to rebuild partial merkle tree transaction hash list.
+type FlagBits = [Bool]
+
+-- | Partial Merkle tree for a filtered block.
+type PartialMerkleTree = [Hash256]
+
+-- | Filtered block: a block with a partial Merkle tree that only includes the
+-- transactions that pass a bloom filter that was negotiated.
+data MerkleBlock = MerkleBlock
+  { -- | block header
+    header :: !BlockHeader,
+    -- | total number of transactions in block
+    txn :: !Word32,
+    -- | hashes in depth-first order
+    hashes :: !PartialMerkleTree,
+    -- | bits to rebuild partial merkle tree
+    flags :: !FlagBits
+  }
+  deriving (Eq, Show, Read, Generic, Hashable, NFData)
+
+instance Serial MerkleBlock where
+  deserialize = do
+    header <- deserialize
+    ntx <- getWord32le
+    (VarInt matchLen) <- deserialize
+    hashes <- replicateM (fromIntegral matchLen) deserialize
+    (VarInt flagLen) <- deserialize
+    ws <- replicateM (fromIntegral flagLen) getWord8
+    return $ MerkleBlock header ntx hashes (decodeMerkleFlags ws)
+
+  serialize (MerkleBlock h ntx hashes flags) = do
+    serialize h
+    putWord32le ntx
+    putVarInt $ length hashes
+    forM_ hashes serialize
+    let ws = encodeMerkleFlags flags
+    putVarInt $ length ws
+    forM_ ws putWord8
+
+instance Binary MerkleBlock where
+  put = serialize
+  get = deserialize
+
+instance Serialize MerkleBlock where
+  put = serialize
+  get = deserialize
+
+-- | Unpack Merkle flags into 'FlagBits' structure.
+decodeMerkleFlags :: [Word8] -> FlagBits
+decodeMerkleFlags ws =
+  [ b | p <- [0 .. length ws * 8 - 1], b <- [testBit (ws !! (p `div` 8)) (p `mod` 8)]
+  ]
+
+-- | Pack Merkle flags from 'FlagBits'.
+encodeMerkleFlags :: FlagBits -> [Word8]
+encodeMerkleFlags bs = map boolsToWord8 $ splitIn 8 bs
+
+-- | Computes the height of a Merkle tree.
+calcTreeHeight ::
+  -- | number of transactions (leaf nodes)
+  Int ->
+  -- | height of the merkle tree
+  Int
+calcTreeHeight ntx
+  | ntx < 2 = 0
+  | even ntx = 1 + calcTreeHeight (ntx `div` 2)
+  | otherwise = calcTreeHeight $ ntx + 1
+
+-- | Computes the width of a Merkle tree at a specific height. The transactions
+-- are at height 0.
+calcTreeWidth ::
+  -- | number of transactions (leaf nodes)
+  Int ->
+  -- | height at which we want to compute the width
+  Int ->
+  -- | width of the Merkle tree
+  Int
+calcTreeWidth ntx h = (ntx + (1 `shiftL` h) - 1) `shiftR` h
+
+-- | Computes the root of a Merkle tree from a list of leaf node hashes.
+buildMerkleRoot ::
+  -- | transaction hashes (leaf nodes)
+  [TxHash] ->
+  -- | root of the Merkle tree
+  MerkleRoot
+buildMerkleRoot txs = calcHash (calcTreeHeight $ length txs) 0 txs
+
+-- | Concatenate and compute double SHA256.
+hash2 :: Hash256 -> Hash256 -> Hash256
+hash2 a b = doubleSHA256 $ runPutS (serialize a) <> runPutS (serialize b)
+
+-- | Computes the hash of a specific node in a Merkle tree.
+calcHash ::
+  -- | height of the node
+  Int ->
+  -- | position of the node (0 for the leftmost node)
+  Int ->
+  -- | transaction hashes (leaf nodes)
+  [TxHash] ->
+  -- | hash of the node at the specified position
+  Hash256
+calcHash height pos txs
+  | height < 0 || pos < 0 = error "calcHash: Invalid parameters"
+  | height == 0 = (txs !! pos).get
+  | otherwise = hash2 left right
+  where
+    left = calcHash (height - 1) (pos * 2) txs
+    right
+      | pos * 2 + 1 < calcTreeWidth (length txs) (height - 1) =
+          calcHash (height - 1) (pos * 2 + 1) txs
+      | otherwise = left
+
+-- | Build a partial Merkle tree. Provide a list of tuples with all transaction
+-- hashes in the block, and whether the transaction is to be included in the
+-- partial tree. Returns a flag bits structure and the computed partial Merkle
+-- tree.
+buildPartialMerkle ::
+  -- | transaction hash and whether to include
+  [(TxHash, Bool)] ->
+  -- | flag bits and partial Merkle tree
+  (FlagBits, PartialMerkleTree)
+buildPartialMerkle hs = traverseAndBuild (calcTreeHeight $ length hs) 0 hs
+
+-- | Helper function to build partial Merkle tree. Used by 'buildPartialMerkle'
+-- above.
+traverseAndBuild ::
+  Int -> Int -> [(TxHash, Bool)] -> (FlagBits, PartialMerkleTree)
+traverseAndBuild height pos txs
+  | height < 0 || pos < 0 = error "traverseAndBuild: Invalid parameters"
+  | height == 0 || not match = ([match], [calcHash height pos t])
+  | otherwise = (match : lb ++ rb, lh ++ rh)
+  where
+    t = map fst txs
+    s = pos `shiftL` height
+    e = min (length txs) $ (pos + 1) `shiftL` height
+    match = any snd $ take (e - s) $ drop s txs
+    (lb, lh) = traverseAndBuild (height - 1) (pos * 2) txs
+    (rb, rh)
+      | (pos * 2 + 1) < calcTreeWidth (length txs) (height - 1) =
+          traverseAndBuild (height - 1) (pos * 2 + 1) txs
+      | otherwise = ([], [])
+
+-- | Helper function to extract transaction hashes from partial Merkle tree.
+traverseAndExtract ::
+  Int ->
+  Int ->
+  Int ->
+  FlagBits ->
+  PartialMerkleTree ->
+  Maybe (MerkleRoot, [TxHash], Int, Int)
+traverseAndExtract height pos ntx flags hashes
+  | null flags = Nothing
+  | height == 0 || not match = leafResult
+  | isNothing leftM = Nothing
+  | (pos * 2 + 1) >= calcTreeWidth ntx (height - 1) =
+      Just (hash2 lh lh, lm, lcf + 1, lch)
+  | isNothing rightM = Nothing
+  | otherwise =
+      Just (hash2 lh rh, lm ++ rm, lcf + rcf + 1, lch + rch)
+  where
+    leafResult
+      | null hashes = Nothing
+      | otherwise = Just (h, [TxHash h | height == 0 && match], 1, 1)
+    (match : fs) = flags
+    (h : _) = hashes
+    leftM = traverseAndExtract (height - 1) (pos * 2) ntx fs hashes
+    (lh, lm, lcf, lch) = fromMaybe e leftM
+    rightM =
+      traverseAndExtract
+        (height - 1)
+        (pos * 2 + 1)
+        ntx
+        (drop lcf fs)
+        (drop lch hashes)
+    (rh, rm, rcf, rch) = fromMaybe e rightM
+    e = error "traverseAndExtract: unexpected error extracting a Maybe value"
+
+-- | Extracts the matching hashes from a partial merkle tree. This will return
+-- the list of transaction hashes that have been included (set to true) in
+-- a call to 'buildPartialMerkle'.
+extractMatches ::
+  Network ->
+  FlagBits ->
+  PartialMerkleTree ->
+  -- | number of transaction at height 0 (leaf nodes)
+  Int ->
+  -- | Merkle root and list of matching transaction hashes
+  Either String (MerkleRoot, [TxHash])
+extractMatches net flags hashes ntx
+  | ntx == 0 =
+      Left
+        "extractMatches: number of transactions can not be 0"
+  | ntx > net.maxBlockSize `div` 60 =
+      Left
+        "extractMatches: number of transactions excessively high"
+  | length hashes > ntx =
+      Left
+        "extractMatches: More hashes provided than the number of transactions"
+  | length flags < length hashes =
+      Left
+        "extractMatches: At least one bit per node and one bit per hash"
+  | isNothing resM =
+      Left
+        "extractMatches: traverseAndExtract failed"
+  | (nBitsUsed + 7) `div` 8 /= (length flags + 7) `div` 8 =
+      Left
+        "extractMatches: All bits were not consumed"
+  | nHashUsed /= length hashes =
+      Left $
+        "extractMatches: All hashes were not consumed: " ++ show nHashUsed
+  | otherwise = return (merkRoot, matches)
+  where
+    resM = traverseAndExtract (calcTreeHeight ntx) 0 ntx flags hashes
+    (merkRoot, matches, nBitsUsed, nHashUsed) = fromMaybe e resM
+    e = error "extractMatches: unexpected error extracting a Maybe value"
+
+-- | Helper function to split a list in chunks 'Int' length. Last chunk may be
+-- smaller.
+splitIn :: Int -> [a] -> [[a]]
+splitIn _ [] = []
+splitIn c xs = xs1 : splitIn c xs2
+  where
+    (xs1, xs2) = splitAt c xs
+
+-- | Pack up to eight bools in a byte.
+boolsToWord8 :: [Bool] -> Word8
+boolsToWord8 [] = 0
+boolsToWord8 xs = foldl setBit 0 (map snd $ filter fst $ zip xs [0 .. 7])
+
+-- | Get matching transactions from Merkle block.
+merkleBlockTxs :: Network -> MerkleBlock -> Either String [TxHash]
+merkleBlockTxs net b = do
+  (root, ths) <- extractMatches net b.flags b.hashes (fromIntegral b.txn)
+  when (root /= b.header.merkle) $
+    Left "merkleBlockTxs: Merkle root incorrect"
+  return ths
+
+-- | Check if Merkle block root is valid against the block header.
+testMerkleRoot :: Network -> MerkleBlock -> Bool
+testMerkleRoot net = isRight . merkleBlockTxs net
diff --git a/src/Haskoin/Crypto.hs b/src/Haskoin/Crypto.hs
new file mode 100644
--- /dev/null
+++ b/src/Haskoin/Crypto.hs
@@ -0,0 +1,23 @@
+{-# LANGUAGE DuplicateRecordFields #-}
+
+-- |
+-- Module      : Haskoin.Crypto
+-- Copyright   : No rights reserved
+-- License     : MIT
+-- Maintainer  : jprupp@protonmail.ch
+-- Stability   : experimental
+-- Portability : POSIX
+--
+-- Hashing functions and ECDSA signatures.
+module Haskoin.Crypto
+  ( module Secp256k1,
+    module Hash,
+    module Keys,
+    module Signature,
+  )
+where
+
+import Crypto.Secp256k1 as Secp256k1
+import Haskoin.Crypto.Hash as Hash
+import Haskoin.Crypto.Keys as Keys
+import Haskoin.Crypto.Signature as Signature
diff --git a/src/Haskoin/Crypto/Hash.hs b/src/Haskoin/Crypto/Hash.hs
new file mode 100644
--- /dev/null
+++ b/src/Haskoin/Crypto/Hash.hs
@@ -0,0 +1,267 @@
+{-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE DerivingStrategies #-}
+{-# LANGUAGE DuplicateRecordFields #-}
+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
+{-# LANGUAGE ImportQualifiedPost #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE OverloadedRecordDot #-}
+{-# LANGUAGE TypeApplications #-}
+{-# LANGUAGE NoFieldSelectors #-}
+
+-- |
+-- Module      : Haskoin.Crypto.Hash
+-- Copyright   : No rights reserved
+-- License     : MIT
+-- Maintainer  : jprupp@protonmail.ch
+-- Stability   : experimental
+-- Portability : POSIX
+--
+-- Hashing functions and corresponding data types. Uses functions from the
+-- cryptonite library.
+module Haskoin.Crypto.Hash
+  ( -- * Hashes
+    Hash512 (get),
+    Hash256 (get),
+    Hash160 (get),
+    CheckSum32 (get),
+    sha512,
+    sha256,
+    ripemd160,
+    sha1,
+    doubleSHA256,
+    addressHash,
+    checkSum32,
+    hmac512,
+    hmac256,
+    split512,
+    join512,
+    initTaggedHash,
+  )
+where
+
+import Control.DeepSeq
+import Crypto.Hash
+import Crypto.MAC.HMAC (HMAC, hmac)
+import Data.Binary (Binary (..))
+import Data.ByteArray (ByteArrayAccess, convert)
+import Data.ByteString (ByteString)
+import Data.ByteString qualified as B
+import Data.ByteString.Short (ShortByteString, fromShort, toShort)
+import Data.Bytes.Get
+import Data.Bytes.Put
+import Data.Bytes.Serial (Serial (..))
+import Data.Either (fromRight)
+import Data.Function (on)
+import Data.Hashable (Hashable)
+import Data.Serialize (Serialize (..))
+import Data.String (IsString, fromString)
+import Data.String.Conversions (cs)
+import Data.Void (Void)
+import Data.Word (Word32)
+import GHC.Generics (Generic)
+import Haskoin.Util.Helpers
+import Haskoin.Util.Marshal
+import Text.Read as R
+
+-- | 'Word32' wrapped for type-safe 32-bit checksums.
+newtype CheckSum32 = CheckSum32
+  { get :: Word32
+  }
+  deriving (Eq, Ord, Show, Read, Generic)
+  deriving newtype (Hashable, NFData)
+
+instance Serial CheckSum32 where
+  serialize (CheckSum32 c) = putWord32be c
+  deserialize = CheckSum32 <$> getWord32be
+
+instance Serialize CheckSum32 where
+  put = serialize
+  get = deserialize
+
+instance Binary CheckSum32 where
+  put = serialize
+  get = deserialize
+
+-- | Type for 512-bit hashes.
+newtype Hash512 = Hash512 {get :: ShortByteString}
+  deriving (Eq, Ord, Generic)
+  deriving newtype (Hashable, NFData)
+
+-- | Type for 256-bit hashes.
+newtype Hash256 = Hash256 {get :: ShortByteString}
+  deriving (Eq, Ord, Generic)
+  deriving newtype (Hashable, NFData)
+
+-- | Type for 160-bit hashes.
+newtype Hash160 = Hash160 {get :: ShortByteString}
+  deriving (Eq, Ord, Generic)
+  deriving newtype (Hashable, NFData)
+
+instance Show Hash512 where
+  showsPrec _ = shows . encodeHex . fromShort . (.get)
+
+instance Read Hash512 where
+  readPrec = do
+    R.String str <- lexP
+    maybe pfail (return . Hash512 . toShort) (decodeHex (cs str))
+
+instance Show Hash256 where
+  showsPrec _ = shows . encodeHex . fromShort . (.get)
+
+instance Read Hash256 where
+  readPrec = do
+    R.String str <- lexP
+    maybe pfail (return . Hash256 . toShort) (decodeHex (cs str))
+
+instance Show Hash160 where
+  showsPrec _ = shows . encodeHex . fromShort . (.get)
+
+instance Read Hash160 where
+  readPrec = do
+    R.String str <- lexP
+    maybe pfail (return . Hash160 . toShort) (decodeHex (cs str))
+
+instance IsString Hash512 where
+  fromString str =
+    case decodeHex $ cs str of
+      Nothing -> e
+      Just bs ->
+        case B.length bs of
+          64 -> Hash512 (toShort bs)
+          _ -> e
+    where
+      e = error "Could not decode hash from hex string"
+
+instance Serial Hash512 where
+  deserialize = Hash512 . toShort <$> getByteString 64
+  serialize = putByteString . fromShort . (.get)
+
+instance Serialize Hash512 where
+  put = serialize
+  get = deserialize
+
+instance Binary Hash512 where
+  put = serialize
+  get = deserialize
+
+instance IsString Hash256 where
+  fromString str =
+    case decodeHex $ cs str of
+      Nothing -> e
+      Just bs ->
+        case B.length bs of
+          32 -> Hash256 (toShort bs)
+          _ -> e
+    where
+      e = error "Could not decode hash from hex string"
+
+instance Serial Hash256 where
+  deserialize = Hash256 . toShort <$> getByteString 32
+  serialize = putByteString . fromShort . (.get)
+
+instance Serialize Hash256 where
+  put = serialize
+  get = deserialize
+
+instance Binary Hash256 where
+  put = serialize
+  get = deserialize
+
+instance IsString Hash160 where
+  fromString str =
+    case decodeHex $ cs str of
+      Nothing -> e
+      Just bs ->
+        case B.length bs of
+          20 -> Hash160 (toShort bs)
+          _ -> e
+    where
+      e = error "Could not decode hash from hex string"
+
+instance Serial Hash160 where
+  deserialize = Hash160 . toShort <$> getByteString 20
+  serialize = putByteString . fromShort . (.get)
+
+instance Serialize Hash160 where
+  put = serialize
+  get = deserialize
+
+instance Binary Hash160 where
+  put = serialize
+  get = deserialize
+
+-- | Calculate SHA512 hash.
+sha512 :: (ByteArrayAccess b) => b -> Hash512
+sha512 = Hash512 . toShort . convert . hashWith SHA512
+
+-- | Calculate SHA256 hash.
+sha256 :: (ByteArrayAccess b) => b -> Hash256
+sha256 = Hash256 . toShort . convert . hashWith SHA256
+
+-- | Calculate RIPEMD160 hash.
+ripemd160 :: (ByteArrayAccess b) => b -> Hash160
+ripemd160 = Hash160 . toShort . convert . hashWith RIPEMD160
+
+-- | Claculate SHA1 hash.
+sha1 :: (ByteArrayAccess b) => b -> Hash160
+sha1 = Hash160 . toShort . convert . hashWith SHA1
+
+-- | Compute two rounds of SHA-256.
+doubleSHA256 :: (ByteArrayAccess b) => b -> Hash256
+doubleSHA256 =
+  Hash256 . toShort . convert . hashWith SHA256 . hashWith SHA256
+
+-- | Compute SHA-256 followed by RIPMED-160.
+addressHash :: (ByteArrayAccess b) => b -> Hash160
+addressHash =
+  Hash160 . toShort . convert . hashWith RIPEMD160 . hashWith SHA256
+
+{- CheckSum -}
+
+-- | Computes a 32 bit checksum.
+checkSum32 :: (ByteArrayAccess b) => b -> CheckSum32
+checkSum32 =
+  fromRight (error "Could not decode bytes as CheckSum32")
+    . runGetS deserialize
+    . B.take 4
+    . convert
+    . hashWith SHA256
+    . hashWith SHA256
+
+{- HMAC -}
+
+-- | Computes HMAC over SHA-512.
+hmac512 :: ByteString -> ByteString -> Hash512
+hmac512 key msg =
+  Hash512 $ toShort $ convert (hmac key msg :: HMAC SHA512)
+
+-- | Computes HMAC over SHA-256.
+hmac256 :: (ByteArrayAccess k, ByteArrayAccess m) => k -> m -> Hash256
+hmac256 key msg =
+  Hash256 $ toShort $ convert (hmac key msg :: HMAC SHA256)
+
+-- | Split a 'Hash512' into a pair of 'Hash256'.
+split512 :: Hash512 -> (Hash256, Hash256)
+split512 h =
+  (Hash256 (toShort a), Hash256 (toShort b))
+  where
+    (a, b) = B.splitAt 32 $ fromShort h.get
+
+-- | Join a pair of 'Hash256' into a 'Hash512'.
+join512 :: (Hash256, Hash256) -> Hash512
+join512 (a, b) = Hash512 (toShort (a.get `app` b.get))
+  where
+    app = B.append `on` fromShort
+
+-- | Initialize tagged hash specified in BIP340
+--
+-- @since 0.21.0
+initTaggedHash ::
+  -- | Hash tag
+  ByteString ->
+  Context SHA256
+initTaggedHash tag =
+  (`hashUpdates` [hashedTag, hashedTag]) $
+    hashInit @SHA256
+  where
+    hashedTag = hashWith SHA256 tag
diff --git a/src/Haskoin/Crypto/Keys.hs b/src/Haskoin/Crypto/Keys.hs
new file mode 100644
--- /dev/null
+++ b/src/Haskoin/Crypto/Keys.hs
@@ -0,0 +1,22 @@
+{-# LANGUAGE DuplicateRecordFields #-}
+
+-- |
+-- Module      : Haskoin.Keys
+-- Copyright   : No rights reserved
+-- License     : MIT
+-- Maintainer  : jprupp@protonmail.ch
+-- Stability   : experimental
+-- Portability : POSIX
+--
+-- ECDSA private and public keys, extended keys (BIP-32) and mnemonic sentences
+-- (BIP-39).
+module Haskoin.Crypto.Keys
+  ( module Haskoin.Crypto.Keys.Common,
+    module Haskoin.Crypto.Keys.Extended,
+    module Haskoin.Crypto.Keys.Mnemonic,
+  )
+where
+
+import Haskoin.Crypto.Keys.Common
+import Haskoin.Crypto.Keys.Extended
+import Haskoin.Crypto.Keys.Mnemonic
diff --git a/src/Haskoin/Crypto/Keys/Common.hs b/src/Haskoin/Crypto/Keys/Common.hs
new file mode 100644
--- /dev/null
+++ b/src/Haskoin/Crypto/Keys/Common.hs
@@ -0,0 +1,185 @@
+{-# LANGUAGE DeriveAnyClass #-}
+{-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE DuplicateRecordFields #-}
+{-# LANGUAGE FlexibleContexts #-}
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE ImportQualifiedPost #-}
+{-# LANGUAGE LambdaCase #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE OverloadedRecordDot #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE NoFieldSelectors #-}
+{-# OPTIONS_GHC -fno-warn-orphans #-}
+
+-- |
+-- Module      : Haskoin.Keys.Common
+-- Copyright   : No rights reserved
+-- License     : MIT
+-- Maintainer  : jprupp@protonmail.ch
+-- Stability   : experimental
+-- Portability : POSIX
+--
+-- ECDSA private and public key functions.
+module Haskoin.Crypto.Keys.Common
+  ( -- * Bitcoin Public & Private Keys
+    PublicKey (..),
+    PrivateKey (..),
+    wrapPubKey,
+    derivePublicKey,
+    wrapSecKey,
+    fromMiniKey,
+    tweakPubKey,
+    tweakSecKey,
+
+    -- ** Private Key Wallet Import Format (WIF)
+    fromWif,
+    toWif,
+  )
+where
+
+import Control.DeepSeq
+import Control.Monad (guard, mzero, (<=<))
+import Crypto.Secp256k1
+import Data.Aeson
+  ( Encoding,
+    FromJSON,
+    ToJSON (..),
+    Value (String),
+    object,
+    parseJSON,
+    withText,
+  )
+import Data.Aeson.Encoding (text, unsafeToEncoding)
+import Data.Aeson.Types (Parser)
+import Data.Binary (Binary (..))
+import Data.Bool (bool)
+import Data.ByteString (ByteString)
+import Data.ByteString qualified as BS
+import Data.ByteString.Builder (char7)
+import Data.Bytes.Get
+import Data.Bytes.Put
+import Data.Bytes.Serial
+import Data.Hashable
+import Data.Maybe (fromMaybe)
+import Data.Serialize (Serialize (..))
+import Data.String (IsString, fromString)
+import Data.String.Conversions (cs)
+import GHC.Generics (Generic)
+import Haskoin.Address.Base58
+import Haskoin.Crypto.Hash
+import Haskoin.Network.Data
+import Haskoin.Util
+
+-- | Elliptic curve public key type with expected serialized compression flag.
+data PublicKey = PublicKey
+  { point :: !PubKey,
+    compress :: !Bool
+  }
+  deriving (Generic, Show, Read, Hashable, Eq, NFData)
+
+instance MarshalJSON Ctx PublicKey where
+  marshalValue ctx = String . encodeHex . runPutS . marshalPut ctx
+
+  marshalEncoding ctx = hexEncoding . runPutL . marshalPut ctx
+
+  unmarshalValue ctx =
+    withText "PublicKey" $ \t -> do
+      bs <- maybe (fail "Expected hex public key") return $ decodeHex t
+      either fail return $ unmarshal ctx bs
+
+instance Marshal Ctx PublicKey where
+  marshalGet ctx = do
+    c <-
+      lookAhead $
+        getWord8 >>= \case
+          0x02 -> return True
+          0x03 -> return True
+          0x04 -> return False
+          _ -> fail "Not a public key"
+    bs <- getByteString $ if c then 33 else 65
+    case importPubKey ctx bs of
+      Nothing -> fail "Could not decode public key"
+      Just k -> return $ PublicKey k c
+
+  marshalPut ctx pk =
+    putByteString $ exportPubKey ctx pk.compress pk.point
+
+-- | Wrap a public key from secp256k1 library adding information about compression.
+wrapPubKey :: Bool -> PubKey -> PublicKey
+wrapPubKey c p = PublicKey p c
+
+-- | Derives a public key from a private key. This function will preserve
+-- compression flag.
+derivePublicKey :: Ctx -> PrivateKey -> PublicKey
+derivePublicKey ctx (PrivateKey d c) = PublicKey (derivePubKey ctx d) c
+
+-- | Tweak a public key.
+tweakPubKey :: Ctx -> PubKey -> Hash256 -> Maybe PubKey
+tweakPubKey ctx p =
+  tweakAddPubKey ctx p <=< tweak . runPutS . serialize
+
+-- | Elliptic curve private key type with expected public key compression
+-- information. Compression information is stored in private key WIF formats and
+-- needs to be preserved to generate the correct address from the corresponding
+-- public key.
+data PrivateKey = PrivateKey
+  { key :: !SecKey,
+    compress :: !Bool
+  }
+  deriving (Eq, Show, Read, Generic, NFData)
+
+instance Serial PrivateKey where
+  serialize p = do
+    putByteString p.key.get
+    serialize p.compress
+  deserialize = do
+    k <- getByteString 32
+    c <- deserialize
+    return PrivateKey {key = SecKey k, compress = c}
+
+instance MarshalJSON Network PrivateKey where
+  marshalValue net = String . toWif net
+  marshalEncoding net = text . toWif net
+  unmarshalValue net =
+    withText "PrivateKey" $
+      maybe (fail "Could not decode WIF") return . fromWif net
+
+-- | Wrap private key with corresponding public key compression flag.
+wrapSecKey :: Bool -> SecKey -> PrivateKey
+wrapSecKey c d = PrivateKey d c
+
+-- | Tweak a private key.
+tweakSecKey :: Ctx -> SecKey -> Hash256 -> Maybe SecKey
+tweakSecKey ctx k =
+  tweakAddSecKey ctx k <=< tweak . runPutS . serialize
+
+-- | Decode Casascius mini private keys (22 or 30 characters).
+fromMiniKey :: ByteString -> Maybe PrivateKey
+fromMiniKey bs = do
+  guard checkShortKey
+  wrapSecKey False <$> (secKey . runPutS . serialize . sha256) bs
+  where
+    checkHash = runPutS $ serialize $ sha256 $ bs `BS.append` "?"
+    checkShortKey = BS.length bs `elem` [22, 30] && BS.head checkHash == 0x00
+
+-- | Decode private key from WIF (wallet import format) string.
+fromWif :: Network -> Base58 -> Maybe PrivateKey
+fromWif net wif = do
+  bs <- decodeBase58Check wif
+  -- Check that this is a private key
+  guard (BS.head bs == net.secretPrefix)
+  case BS.length bs of
+    -- Uncompressed format
+    33 -> wrapSecKey False <$> (secKey . BS.tail) bs
+    -- Compressed format
+    34 -> do
+      guard $ BS.last bs == 0x01
+      wrapSecKey True <$> (secKey . BS.tail . BS.init) bs
+    -- Bad length
+    _ -> Nothing
+
+-- | Encode private key into a WIF string.
+toWif :: Network -> PrivateKey -> Base58
+toWif net (PrivateKey k c) =
+  encodeBase58Check . BS.cons net.secretPrefix $
+    if c then k.get `BS.snoc` 0x01 else k.get
diff --git a/src/Haskoin/Crypto/Keys/Extended.hs b/src/Haskoin/Crypto/Keys/Extended.hs
new file mode 100644
--- /dev/null
+++ b/src/Haskoin/Crypto/Keys/Extended.hs
@@ -0,0 +1,1057 @@
+{-# LANGUAGE DeriveAnyClass #-}
+{-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE DerivingStrategies #-}
+{-# LANGUAGE DuplicateRecordFields #-}
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
+{-# LANGUAGE ImportQualifiedPost #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE NamedFieldPuns #-}
+{-# LANGUAGE OverloadedRecordDot #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE NoFieldSelectors #-}
+
+-- |
+-- Module      : Haskoin.Keys.Extended
+-- Copyright   : No rights reserved
+-- License     : MIT
+-- Maintainer  : jprupp@protonmail.ch
+-- Stability   : experimental
+-- Portability : POSIX
+--
+-- BIP-32 extended keys.
+module Haskoin.Crypto.Keys.Extended
+  ( -- * Extended Keys
+    XPubKey (..),
+    XPrvKey (..),
+    ChainCode,
+    KeyIndex,
+    Fingerprint,
+    fingerprintToText,
+    textToFingerprint,
+    DerivationException (..),
+    makeXPrvKey,
+    deriveXPubKey,
+    prvSubKey,
+    pubSubKey,
+    hardSubKey,
+    xPrvIsHard,
+    xPubIsHard,
+    xPrvChild,
+    xPubChild,
+    xPubID,
+    xPrvID,
+    xPubFP,
+    xPrvFP,
+    xPubAddr,
+    xPubWitnessAddr,
+    xPubCompatWitnessAddr,
+    xPubExport,
+    xPrvExport,
+    xPubImport,
+    xPrvImport,
+    xPrvWif,
+
+    -- ** Helper Functions
+    prvSubKeys,
+    pubSubKeys,
+    hardSubKeys,
+    deriveAddr,
+    deriveWitnessAddr,
+    deriveCompatWitnessAddr,
+    deriveAddrs,
+    deriveWitnessAddrs,
+    deriveCompatWitnessAddrs,
+    deriveMSAddr,
+    deriveMSAddrs,
+    cycleIndex,
+
+    -- ** Derivation Paths
+    DerivPathI (..),
+    AnyDeriv,
+    HardDeriv,
+    SoftDeriv,
+    HardOrAny,
+    AnyOrSoft,
+    DerivPath,
+    HardPath,
+    SoftPath,
+    Bip32PathIndex (..),
+    derivePath,
+    derivePubPath,
+    toHard,
+    toSoft,
+    toGeneric,
+    (++/),
+    pathToStr,
+    listToPath,
+    pathToList,
+
+    -- *** Derivation Path Parser
+    XKey (..),
+    ParsedPath (..),
+    parsePath,
+    parseHard,
+    parseSoft,
+    applyPath,
+    derivePathAddr,
+    derivePathAddrs,
+    derivePathMSAddr,
+    derivePathMSAddrs,
+    concatBip32Segments,
+  )
+where
+
+import Control.Applicative
+import Control.DeepSeq
+import Control.Exception (Exception, throw)
+import Control.Monad (guard, mzero, unless, (<=<))
+import Crypto.Secp256k1
+import Data.Aeson as Aeson
+  ( FromJSON,
+    ToJSON (..),
+    Value (String),
+    parseJSON,
+    toJSON,
+    withText,
+  )
+import Data.Aeson.Encoding (Encoding, string, text)
+import Data.Aeson.Types (Parser)
+import Data.Binary (Binary (get, put))
+import Data.Bits (clearBit, setBit, testBit)
+import Data.ByteString (ByteString)
+import Data.ByteString qualified as B
+import Data.Bytes.Get
+  ( MonadGet
+      ( getByteString,
+        getWord32be,
+        getWord8
+      ),
+    runGetS,
+  )
+import Data.Bytes.Put
+  ( MonadPut
+      ( putByteString,
+        putWord32be,
+        putWord8
+      ),
+    runPutS,
+  )
+import Data.Bytes.Serial (Serial (..))
+import Data.Either (fromRight)
+import Data.Hashable (Hashable)
+import Data.List (foldl')
+import Data.List.Split (splitOn)
+import Data.Maybe (fromMaybe)
+import Data.Serialize (Serialize (..))
+import Data.Serialize qualified as S
+import Data.String (IsString, fromString)
+import Data.String.Conversions (cs)
+import Data.Text qualified as Text
+import Data.Typeable (Typeable)
+import Data.Word (Word32, Word8)
+import GHC.Generics (Generic)
+import Haskoin.Address
+import Haskoin.Crypto.Hash
+import Haskoin.Crypto.Keys.Common
+import Haskoin.Crypto.Keys.Extended.Internal
+import Haskoin.Network.Data
+import Haskoin.Script.Standard
+import Haskoin.Util
+import Text.Read as Read
+  ( Lexeme (Ident, Number, String),
+    Read (readPrec),
+    lexP,
+    parens,
+    pfail,
+  )
+import Text.Read.Lex (numberToInteger)
+
+-- | A derivation exception is thrown in the very unlikely event that a
+-- derivation is invalid.
+newtype DerivationException = DerivationException String
+  deriving (Eq, Read, Show, Typeable, Generic)
+  deriving newtype (NFData)
+
+instance Exception DerivationException
+
+-- | Chain code as specified in BIP-32.
+type ChainCode = Hash256
+
+-- | Index of key as specified in BIP-32.
+type KeyIndex = Word32
+
+-- | Data type representing an extended BIP32 private key. An extended key
+-- is a node in a tree of key derivations. It has a depth in the tree, a
+-- parent node and an index to differentiate it from other siblings.
+data XPrvKey = XPrvKey
+  { -- | depth in the tree
+    depth :: !Word8,
+    -- | fingerprint of parent
+    parent :: !Fingerprint,
+    -- | derivation index
+    index :: !KeyIndex,
+    -- | chain code
+    chain :: !ChainCode,
+    -- | private key of this node
+    key :: !SecKey
+  }
+  deriving (Generic, Eq, Show, Read, NFData, Hashable)
+
+instance Marshal Network XPrvKey where
+  marshalGet net = do
+    ver <- getWord32be
+    unless (ver == net.xPrvPrefix) $
+      fail "Get: Invalid version for extended private key"
+    XPrvKey
+      <$> getWord8
+      <*> deserialize
+      <*> getWord32be
+      <*> deserialize
+      <*> getPadPrvKey
+
+  marshalPut net k = do
+    putWord32be net.xPrvPrefix
+    putWord8 k.depth
+    serialize k.parent
+    putWord32be k.index
+    serialize $ k.chain
+    putPadPrvKey k.key
+
+instance MarshalJSON Network XPrvKey where
+  marshalValue net = Aeson.String . xPrvExport net
+
+  marshalEncoding net = text . xPrvExport net
+
+  unmarshalValue net =
+    withText "XPrvKey" $ \t ->
+      case xPrvImport net t of
+        Nothing -> fail "could not read xprv"
+        Just x -> return x
+
+-- | Data type representing an extended BIP32 public key.
+data XPubKey = XPubKey
+  { -- | depth in the tree
+    depth :: !Word8,
+    -- | fingerprint of parent
+    parent :: !Fingerprint,
+    -- | derivation index
+    index :: !KeyIndex,
+    -- | chain code
+    chain :: !ChainCode,
+    -- | public key of this node
+    key :: !PubKey
+  }
+  deriving (Generic, Eq, Show, Read, Hashable, NFData)
+
+instance Marshal (Network, Ctx) XPubKey where
+  marshalGet (net, ctx) = do
+    ver <- getWord32be
+    unless (ver == net.xPubPrefix) $
+      fail "Get: Invalid version for extended public key"
+    XPubKey
+      <$> getWord8
+      <*> deserialize
+      <*> getWord32be
+      <*> deserialize
+      <*> ((\PublicKey {point} -> point) <$> marshalGet ctx)
+
+  marshalPut (net, ctx) k = do
+    putWord32be net.xPubPrefix
+    putWord8 k.depth
+    serialize k.parent
+    putWord32be k.index
+    serialize k.chain
+    marshalPut ctx $ wrapPubKey True k.key
+
+instance MarshalJSON (Network, Ctx) XPubKey where
+  unmarshalValue (net, ctx) =
+    withText "XPubKey" $ \t ->
+      case xPubImport net ctx t of
+        Nothing -> fail "could not read xpub"
+        Just x -> return x
+
+  marshalValue (net, ctx) = Aeson.String . xPubExport net ctx
+
+  marshalEncoding (net, ctx) = text . xPubExport net ctx
+
+-- | Build a BIP32 compatible extended private key from a bytestring. This will
+-- produce a root node (@depth=0@ and @parent=0@).
+makeXPrvKey :: ByteString -> XPrvKey
+makeXPrvKey bs =
+  XPrvKey 0 (Fingerprint 0) 0 c k
+  where
+    (p, c) = split512 $ hmac512 "Bitcoin seed" bs
+    k = fromMaybe err (secKey (runPutS (serialize p)))
+    err = throw $ DerivationException "Invalid seed"
+
+-- | Derive an extended public key from an extended private key. This function
+-- will preserve the depth, parent, index and chaincode fields of the extended
+-- private keys.
+deriveXPubKey :: Ctx -> XPrvKey -> XPubKey
+deriveXPubKey ctx (XPrvKey d p i c k) = XPubKey d p i c (derivePubKey ctx k)
+
+-- | Compute a private, soft child key derivation. A private soft derivation
+-- will allow the equivalent extended public key to derive the public key for
+-- this child. Given a parent key /m/ and a derivation index /i/, this function
+-- will compute /m\/i/.
+--
+-- Soft derivations allow for more flexibility such as read-only wallets.
+-- However, care must be taken not the leak both the parent extended public key
+-- and one of the extended child private keys as this would compromise the
+-- extended parent private key.
+prvSubKey ::
+  Ctx ->
+  -- | extended parent private key
+  XPrvKey ->
+  -- | child derivation index
+  KeyIndex ->
+  -- | extended child private key
+  XPrvKey
+prvSubKey ctx xkey child
+  | child >= 0 && child < 0x80000000 =
+      XPrvKey (xkey.depth + 1) (xPrvFP ctx xkey) child c k
+  | otherwise = error "Invalid child derivation index"
+  where
+    pK = (deriveXPubKey ctx xkey).key
+    m = B.append (exportPubKey ctx True pK) (runPutS (serialize child))
+    (a, c) = split512 $ hmac512 (runPutS $ serialize xkey.chain) m
+    k = fromMaybe err $ tweakSecKey ctx xkey.key a
+    err = throw $ DerivationException "Invalid prvSubKey derivation"
+
+-- | Compute a public, soft child key derivation. Given a parent key /M/
+-- and a derivation index /i/, this function will compute /M\/i/.
+pubSubKey ::
+  Ctx ->
+  -- | extended parent public key
+  XPubKey ->
+  -- | child derivation index
+  KeyIndex ->
+  -- | extended child public key
+  XPubKey
+pubSubKey ctx xKey child
+  | child >= 0 && child < 0x80000000 =
+      XPubKey (xKey.depth + 1) (xPubFP ctx xKey) child c pK
+  | otherwise = error "Invalid child derivation index"
+  where
+    m = B.append (exportPubKey ctx True xKey.key) (runPutS $ serialize child)
+    (a, c) = split512 $ hmac512 (runPutS $ serialize xKey.chain) m
+    pK = fromMaybe err $ tweakPubKey ctx xKey.key a
+    err = throw $ DerivationException "Invalid pubSubKey derivation"
+
+-- | Compute a hard child key derivation. Hard derivations can only be computed
+-- for private keys. Hard derivations do not allow the parent public key to
+-- derive the child public keys. However, they are safer as a breach of the
+-- parent public key and child private keys does not lead to a breach of the
+-- parent private key. Given a parent key /m/ and a derivation index /i/, this
+-- function will compute /m\/i'/.
+hardSubKey ::
+  Ctx ->
+  -- | extended parent private key
+  XPrvKey ->
+  -- | child derivation index
+  KeyIndex ->
+  -- | extended child private key
+  XPrvKey
+hardSubKey ctx xkey child
+  | child >= 0 && child < 0x80000000 =
+      XPrvKey (xkey.depth + 1) (xPrvFP ctx xkey) i c k
+  | otherwise = error "Invalid child derivation index"
+  where
+    i = setBit child 31
+    m = B.append (bsPadPrvKey xkey.key) (runPutS $ serialize i)
+    (a, c) = split512 $ hmac512 (runPutS $ serialize xkey.chain) m
+    k = fromMaybe err $ tweakSecKey ctx xkey.key a
+    err = throw $ DerivationException "Invalid hardSubKey derivation"
+
+-- | Returns true if the extended private key was derived through a hard
+-- derivation.
+xPrvIsHard :: XPrvKey -> Bool
+xPrvIsHard k = testBit k.index 31
+
+-- | Returns true if the extended public key was derived through a hard
+-- derivation.
+xPubIsHard :: XPubKey -> Bool
+xPubIsHard k = testBit k.index 31
+
+-- | Returns the derivation index of this extended private key without the hard
+-- bit set.
+xPrvChild :: XPrvKey -> KeyIndex
+xPrvChild k = clearBit k.index 31
+
+-- | Returns the derivation index of this extended public key without the hard
+-- bit set.
+xPubChild :: XPubKey -> KeyIndex
+xPubChild k = clearBit k.index 31
+
+-- | Computes the key identifier of an extended private key.
+xPrvID :: Ctx -> XPrvKey -> Hash160
+xPrvID ctx = xPubID ctx . deriveXPubKey ctx
+
+-- | Computes the key identifier of an extended public key.
+xPubID :: Ctx -> XPubKey -> Hash160
+xPubID ctx =
+  ripemd160
+    . runPutS
+    . serialize
+    . sha256
+    . exportPubKey ctx True
+    . (.key)
+
+-- | Computes the key fingerprint of an extended private key.
+xPrvFP :: Ctx -> XPrvKey -> Fingerprint
+xPrvFP ctx =
+  fromRight err
+    . runGetS deserialize
+    . B.take 4
+    . runPutS
+    . serialize
+    . xPrvID ctx
+  where
+    err = error "Could not decode xPrvFP"
+
+-- | Computes the key fingerprint of an extended public key.
+xPubFP :: Ctx -> XPubKey -> Fingerprint
+xPubFP ctx =
+  fromRight err
+    . runGetS deserialize
+    . B.take 4
+    . runPutS
+    . serialize
+    . xPubID ctx
+  where
+    err = error "Could not decode xPubFP"
+
+-- | Compute a standard P2PKH address for an extended public key.
+xPubAddr :: Ctx -> XPubKey -> Address
+xPubAddr ctx xkey = pubKeyAddr ctx (wrapPubKey True xkey.key)
+
+-- | Compute a SegWit P2WPKH address for an extended public key.
+xPubWitnessAddr :: Ctx -> XPubKey -> Address
+xPubWitnessAddr ctx xkey =
+  pubKeyWitnessAddr ctx (wrapPubKey True xkey.key)
+
+-- | Compute a backwards-compatible SegWit P2SH-P2WPKH address for an extended
+-- public key.
+xPubCompatWitnessAddr :: Ctx -> XPubKey -> Address
+xPubCompatWitnessAddr ctx xkey =
+  pubKeyCompatWitnessAddr ctx (wrapPubKey True xkey.key)
+
+-- | Exports an extended private key to the BIP32 key export format ('Base58').
+xPrvExport :: Network -> XPrvKey -> Base58
+xPrvExport net = encodeBase58Check . marshal net
+
+-- | Exports an extended public key to the BIP32 key export format ('Base58').
+xPubExport :: Network -> Ctx -> XPubKey -> Base58
+xPubExport net ctx = encodeBase58Check . marshal (net, ctx)
+
+-- | Decodes a BIP32 encoded extended private key. This function will fail if
+-- invalid base 58 characters are detected or if the checksum fails.
+xPrvImport :: Network -> Base58 -> Maybe XPrvKey
+xPrvImport net =
+  eitherToMaybe . unmarshal net <=< decodeBase58Check
+
+-- | Decodes a BIP32 encoded extended public key. This function will fail if
+-- invalid base 58 characters are detected or if the checksum fails.
+xPubImport :: Network -> Ctx -> Base58 -> Maybe XPubKey
+xPubImport net ctx =
+  eitherToMaybe . unmarshal (net, ctx) <=< decodeBase58Check
+
+-- | Export an extended private key to WIF (Wallet Import Format).
+xPrvWif :: Network -> XPrvKey -> Base58
+xPrvWif net xkey = toWif net (wrapSecKey True xkey.key)
+
+{- Derivation helpers -}
+
+-- | Cyclic list of all private soft child key derivations of a parent key
+-- starting from an offset index.
+prvSubKeys :: Ctx -> XPrvKey -> KeyIndex -> [(XPrvKey, KeyIndex)]
+prvSubKeys ctx k = map (\i -> (prvSubKey ctx k i, i)) . cycleIndex
+
+-- | Cyclic list of all public soft child key derivations of a parent key
+-- starting from an offset index.
+pubSubKeys :: Ctx -> XPubKey -> KeyIndex -> [(XPubKey, KeyIndex)]
+pubSubKeys ctx k = map (\i -> (pubSubKey ctx k i, i)) . cycleIndex
+
+-- | Cyclic list of all hard child key derivations of a parent key starting
+-- from an offset index.
+hardSubKeys :: Ctx -> XPrvKey -> KeyIndex -> [(XPrvKey, KeyIndex)]
+hardSubKeys ctx k = map (\i -> (hardSubKey ctx k i, i)) . cycleIndex
+
+-- | Derive a standard address from an extended public key and an index.
+deriveAddr :: Ctx -> XPubKey -> KeyIndex -> (Address, PubKey)
+deriveAddr ctx k i =
+  (xPubAddr ctx key, key.key)
+  where
+    key = pubSubKey ctx k i
+
+-- | Derive a SegWit P2WPKH address from an extended public key and an index.
+deriveWitnessAddr :: Ctx -> XPubKey -> KeyIndex -> (Address, PubKey)
+deriveWitnessAddr ctx k i =
+  (xPubWitnessAddr ctx key, key.key)
+  where
+    key = pubSubKey ctx k i
+
+-- | Derive a backwards-compatible SegWit P2SH-P2WPKH address from an extended
+-- public key and an index.
+deriveCompatWitnessAddr :: Ctx -> XPubKey -> KeyIndex -> (Address, PubKey)
+deriveCompatWitnessAddr ctx k i =
+  (xPubCompatWitnessAddr ctx key, key.key)
+  where
+    key = pubSubKey ctx k i
+
+-- | Cyclic list of all addresses derived from a public key starting from an
+-- offset index.
+deriveAddrs :: Ctx -> XPubKey -> KeyIndex -> [(Address, PubKey, KeyIndex)]
+deriveAddrs ctx k =
+  map f . cycleIndex
+  where
+    f i = let (a, key) = deriveAddr ctx k i in (a, key, i)
+
+-- | Cyclic list of all SegWit P2WPKH addresses derived from a public key
+-- starting from an offset index.
+deriveWitnessAddrs ::
+  Ctx -> XPubKey -> KeyIndex -> [(Address, PubKey, KeyIndex)]
+deriveWitnessAddrs ctx k =
+  map f . cycleIndex
+  where
+    f i = let (a, key) = deriveWitnessAddr ctx k i in (a, key, i)
+
+-- | Cyclic list of all backwards-compatible SegWit P2SH-P2WPKH addresses
+-- derived from a public key starting from an offset index.
+deriveCompatWitnessAddrs ::
+  Ctx -> XPubKey -> KeyIndex -> [(Address, PubKey, KeyIndex)]
+deriveCompatWitnessAddrs ctx k =
+  map f . cycleIndex
+  where
+    f i = let (a, key) = deriveCompatWitnessAddr ctx k i in (a, key, i)
+
+-- | Derive a multisig address from a list of public keys, the number of
+-- required signatures /m/ and a derivation index. The derivation type is a
+-- public, soft derivation.
+deriveMSAddr ::
+  Ctx -> [XPubKey] -> Int -> KeyIndex -> (Address, RedeemScript)
+deriveMSAddr ctx keys m i = (payToScriptAddress ctx rdm, rdm)
+  where
+    rdm = sortMulSig ctx $ PayMulSig k m
+    k = map (wrapPubKey True . (.key) . flip (pubSubKey ctx) i) keys
+
+-- | Cyclic list of all multisig addresses derived from a list of public keys,
+-- a number of required signatures /m/ and starting from an offset index. The
+-- derivation type is a public, soft derivation.
+deriveMSAddrs ::
+  Ctx ->
+  [XPubKey] ->
+  Int ->
+  KeyIndex ->
+  [(Address, RedeemScript, KeyIndex)]
+deriveMSAddrs ctx keys m = map f . cycleIndex
+  where
+    f i =
+      let (a, rdm) = deriveMSAddr ctx keys m i
+       in (a, rdm, i)
+
+-- | Helper function to go through derivation indices.
+cycleIndex :: KeyIndex -> [KeyIndex]
+cycleIndex i
+  | i == 0 = cycle [0 .. 0x7fffffff]
+  | i < 0x80000000 = cycle $ [i .. 0x7fffffff] ++ [0 .. (i - 1)]
+  | otherwise = error $ "cycleIndex: invalid index " ++ show i
+
+{- Derivation Paths -}
+
+-- | Phantom type signaling a hardened derivation path that can only be computed
+-- from private extended key.
+data HardDeriv deriving (Generic, NFData)
+
+-- | Phantom type signaling no knowledge about derivation path: can be hardened or not.
+data AnyDeriv deriving (Generic, NFData)
+
+-- | Phantom type signaling derivation path including only non-hardened paths
+-- that can be computed from an extended public key.
+data SoftDeriv deriving (Generic, NFData)
+
+-- | Hardened derivation path. Can be computed from extended private key only.
+type HardPath = DerivPathI HardDeriv
+
+-- | Any derivation path.
+type DerivPath = DerivPathI AnyDeriv
+
+-- | Non-hardened derivation path can be computed from extended public key.
+type SoftPath = DerivPathI SoftDeriv
+
+-- | Helper class to perform validations on a hardened derivation path.
+class HardOrAny a
+
+instance HardOrAny HardDeriv
+
+instance HardOrAny AnyDeriv
+
+-- | Helper class to perform validations on a non-hardened derivation path.
+class AnyOrSoft a
+
+instance AnyOrSoft AnyDeriv
+
+instance AnyOrSoft SoftDeriv
+
+-- | Data type representing a derivation path. Two constructors are provided
+-- for specifying soft or hard derivations. The path /\/0\/1'\/2/ for example can be
+-- expressed as @'Deriv' :\/ 0 :| 1 :\/ 2@. The 'HardOrAny' and 'AnyOrSoft' type
+-- classes are used to constrain the valid values for the phantom type /t/. If
+-- you mix hard '(:|)' and soft '(:\/)' paths, the only valid type for /t/ is 'AnyDeriv'.
+-- Otherwise, /t/ can be 'HardDeriv' if you only have hard derivation or 'SoftDeriv'
+-- if you only have soft derivations.
+--
+-- Using this type is as easy as writing the required derivation like in these
+-- example:
+--
+-- > Deriv :/ 0 :/ 1 :/ 2 :: SoftPath
+-- > Deriv :| 0 :| 1 :| 2 :: HardPath
+-- > Deriv :| 0 :/ 1 :/ 2 :: DerivPath
+data DerivPathI t where
+  (:|) :: (HardOrAny t) => !(DerivPathI t) -> !KeyIndex -> DerivPathI t
+  (:/) :: (AnyOrSoft t) => !(DerivPathI t) -> !KeyIndex -> DerivPathI t
+  Deriv :: DerivPathI t
+
+instance NFData (DerivPathI t) where
+  rnf (a :| b) = rnf a `seq` rnf b
+  rnf (a :/ b) = rnf a `seq` rnf b
+  rnf Deriv = ()
+
+instance Eq (DerivPathI t) where
+  (nextA :| iA) == (nextB :| iB) = iA == iB && nextA == nextB
+  (nextA :/ iA) == (nextB :/ iB) = iA == iB && nextA == nextB
+  Deriv == Deriv = True
+  _ == _ = False
+
+instance Ord (DerivPathI t) where
+  -- Same hardness on each side
+  (nextA :| iA) `compare` (nextB :| iB) =
+    if nextA == nextB then iA `compare` iB else nextA `compare` nextB
+  (nextA :/ iA) `compare` (nextB :/ iB) =
+    if nextA == nextB then iA `compare` iB else nextA `compare` nextB
+  -- Different hardness: hard paths are LT soft paths
+  (nextA :/ _iA) `compare` (nextB :| _iB) =
+    if nextA == nextB then LT else nextA `compare` nextB
+  (nextA :| _iA) `compare` (nextB :/ _iB) =
+    if nextA == nextB then GT else nextA `compare` nextB
+  Deriv `compare` Deriv = EQ
+  Deriv `compare` _ = LT
+  _ `compare` Deriv = GT
+
+instance Serial DerivPath where
+  deserialize = listToPath <$> getList getWord32be
+  serialize = putList putWord32be . pathToList
+
+instance Serialize DerivPath where
+  put = serialize
+  get = deserialize
+
+instance Binary DerivPath where
+  put = serialize
+  get = deserialize
+
+instance Serial HardPath where
+  deserialize =
+    maybe
+      (fail "Could not decode hard path")
+      return
+      . toHard
+      . listToPath
+      =<< getList getWord32be
+  serialize = putList putWord32be . pathToList
+
+instance Serialize HardPath where
+  put = serialize
+  get = deserialize
+
+instance Binary HardPath where
+  put = serialize
+  get = deserialize
+
+instance Serial SoftPath where
+  deserialize =
+    maybe
+      (fail "Could not decode soft path")
+      return
+      . toSoft
+      . listToPath
+      =<< getList getWord32be
+  serialize = putList putWord32be . pathToList
+
+instance Serialize SoftPath where
+  put = serialize
+  get = deserialize
+
+instance Binary SoftPath where
+  put = serialize
+  get = deserialize
+
+-- | Get a list of derivation indices from a derivation path.
+pathToList :: DerivPathI t -> [KeyIndex]
+pathToList =
+  reverse . go
+  where
+    go (next :| i) = setBit i 31 : go next
+    go (next :/ i) = i : go next
+    go _ = []
+
+-- | Convert a list of derivation indices to a derivation path.
+listToPath :: [KeyIndex] -> DerivPath
+listToPath =
+  go . reverse
+  where
+    go (i : is)
+      | testBit i 31 = go is :| clearBit i 31
+      | otherwise = go is :/ i
+    go [] = Deriv
+
+-- | Convert a derivation path to a human-readable string.
+pathToStr :: DerivPathI t -> String
+pathToStr p =
+  case p of
+    next :| i -> concat [pathToStr next, "/", show i, "'"]
+    next :/ i -> concat [pathToStr next, "/", show i]
+    Deriv -> ""
+
+-- | Turn a derivation path into a hard derivation path. Will fail if the path
+-- contains soft derivations.
+toHard :: DerivPathI t -> Maybe HardPath
+toHard p = case p of
+  next :| i -> (:| i) <$> toHard next
+  Deriv -> Just Deriv
+  _ -> Nothing
+
+-- | Turn a derivation path into a soft derivation path. Will fail if the path
+-- has hard derivations.
+toSoft :: DerivPathI t -> Maybe SoftPath
+toSoft p = case p of
+  next :/ i -> (:/ i) <$> toSoft next
+  Deriv -> Just Deriv
+  _ -> Nothing
+
+-- | Make a derivation path generic.
+toGeneric :: DerivPathI t -> DerivPath
+toGeneric p = case p of
+  next :/ i -> toGeneric next :/ i
+  next :| i -> toGeneric next :| i
+  Deriv -> Deriv
+
+-- | Append two derivation paths together. The result will be a mixed
+-- derivation path.
+(++/) :: DerivPathI t1 -> DerivPathI t2 -> DerivPath
+(++/) p1 p2 =
+  go id (toGeneric p2) $ toGeneric p1
+  where
+    go f p = case p of
+      next :/ i -> go (f . (:/ i)) $ toGeneric next
+      next :| i -> go (f . (:| i)) $ toGeneric next
+      _ -> f
+
+-- | Derive a private key from a derivation path
+derivePath :: Ctx -> DerivPathI t -> XPrvKey -> XPrvKey
+derivePath ctx = go id
+  where
+    -- Build the full derivation function starting from the end
+    go f p = case p of
+      next :| i -> go (f . flip (hardSubKey ctx) i) next
+      next :/ i -> go (f . flip (prvSubKey ctx) i) next
+      _ -> f
+
+-- | Derive a public key from a soft derivation path
+derivePubPath :: Ctx -> SoftPath -> XPubKey -> XPubKey
+derivePubPath ctx = go id
+  where
+    -- Build the full derivation function starting from the end
+    go f p = case p of
+      next :/ i -> go (f . flip (pubSubKey ctx) i) next
+      _ -> f
+
+instance Show DerivPath where
+  showsPrec d p =
+    showParen (d > 10) $
+      showString "DerivPath " . shows (pathToStr p)
+
+instance Read DerivPath where
+  readPrec = parens $ do
+    Ident "DerivPath" <- lexP
+    Read.String str <- lexP
+    maybe pfail (return . (.get)) (parsePath str)
+
+instance Show HardPath where
+  showsPrec d p =
+    showParen (d > 10) $
+      showString "HardPath " . shows (pathToStr p)
+
+instance Read HardPath where
+  readPrec = parens $ do
+    Ident "HardPath" <- lexP
+    Read.String str <- lexP
+    maybe pfail return $ parseHard str
+
+instance Show SoftPath where
+  showsPrec d p =
+    showParen (d > 10) $
+      showString "SoftPath " . shows (pathToStr p)
+
+instance Read SoftPath where
+  readPrec = parens $ do
+    Ident "SoftPath" <- lexP
+    Read.String str <- lexP
+    maybe pfail return $ parseSoft str
+
+instance IsString ParsedPath where
+  fromString =
+    fromMaybe e . parsePath
+    where
+      e = error "Could not parse derivation path"
+
+instance IsString DerivPath where
+  fromString =
+    (.get) . fromMaybe e . parsePath
+    where
+      e = error "Could not parse derivation path"
+
+instance IsString HardPath where
+  fromString =
+    fromMaybe e . parseHard
+    where
+      e = error "Could not parse hard derivation path"
+
+instance IsString SoftPath where
+  fromString =
+    fromMaybe e . parseSoft
+    where
+      e = error "Could not parse soft derivation path"
+
+instance FromJSON ParsedPath where
+  parseJSON = withText "ParsedPath" $ \str -> case parsePath $ cs str of
+    Just p -> return p
+    _ -> mzero
+
+instance FromJSON DerivPath where
+  parseJSON = withText "DerivPath" $ \str -> case parsePath $ cs str of
+    Just p -> return p.get
+    _ -> mzero
+
+instance FromJSON HardPath where
+  parseJSON = withText "HardPath" $ \str -> case parseHard $ cs str of
+    Just p -> return p
+    _ -> mzero
+
+instance FromJSON SoftPath where
+  parseJSON = withText "SoftPath" $ \str -> case parseSoft $ cs str of
+    Just p -> return p
+    _ -> mzero
+
+instance ToJSON (DerivPathI t) where
+  toJSON = Aeson.String . cs . pathToStr
+  toEncoding = string . pathToStr
+
+instance ToJSON ParsedPath where
+  toJSON (ParsedPrv p) = Aeson.String . cs . ("m" ++) . pathToStr $ p
+  toJSON (ParsedPub p) = Aeson.String . cs . ("M" ++) . pathToStr $ p
+  toJSON (ParsedEmpty p) = Aeson.String . cs . ("" ++) . pathToStr $ p
+  toEncoding (ParsedPrv p) = text . cs . ("m" ++) . pathToStr $ p
+  toEncoding (ParsedPub p) = text . cs . ("M" ++) . pathToStr $ p
+  toEncoding (ParsedEmpty p) = text . cs . ("" ++) . pathToStr $ p
+
+{- Parsing derivation paths of the form m/1/2'/3 or M/1/2'/3 -}
+
+-- | Type for parsing derivation paths of the form /m\/1\/2'\/3/ or
+-- /M\/1\/2'\/3/.
+data ParsedPath
+  = ParsedPrv {get :: !DerivPath}
+  | ParsedPub {get :: !DerivPath}
+  | ParsedEmpty {get :: !DerivPath}
+  deriving (Eq, Generic, NFData)
+
+instance Show ParsedPath where
+  showsPrec d p = showParen (d > 10) $ showString "ParsedPath " . shows f
+    where
+      f =
+        case p of
+          ParsedPrv d' -> "m" <> pathToStr d'
+          ParsedPub d' -> "M" <> pathToStr d'
+          ParsedEmpty d' -> pathToStr d'
+
+instance Read ParsedPath where
+  readPrec = parens $ do
+    Ident "ParsedPath" <- lexP
+    Read.String str <- lexP
+    maybe pfail return $ parsePath str
+
+-- | Parse derivation path string for extended key.
+-- Forms: /m\/0'\/2/, /M\/2\/3\/4/.
+parsePath :: String -> Maybe ParsedPath
+parsePath str = do
+  res <- concatBip32Segments <$> mapM parseBip32PathIndex xs
+  case x of
+    "m" -> Just $ ParsedPrv res
+    "M" -> Just $ ParsedPub res
+    "" -> Just $ ParsedEmpty res
+    _ -> Nothing
+  where
+    (x : xs) = splitOn "/" str
+
+-- | Concatenate derivation path indices into a derivation path.
+concatBip32Segments :: [Bip32PathIndex] -> DerivPath
+concatBip32Segments = foldl' appendBip32Segment Deriv
+
+-- | Append an extra derivation path index element into an existing path.
+appendBip32Segment :: DerivPath -> Bip32PathIndex -> DerivPath
+appendBip32Segment d (Bip32SoftIndex i) = d :/ i
+appendBip32Segment d (Bip32HardIndex i) = d :| i
+
+-- | Parse a BIP32 derivation path index element from a string.
+parseBip32PathIndex :: String -> Maybe Bip32PathIndex
+parseBip32PathIndex segment = case reads segment of
+  [(i, "")] -> guard (is31Bit i) >> return (Bip32SoftIndex i)
+  [(i, "'")] -> guard (is31Bit i) >> return (Bip32HardIndex i)
+  _ -> Nothing
+
+-- | Type for BIP32 path index element.
+data Bip32PathIndex
+  = Bip32HardIndex KeyIndex
+  | Bip32SoftIndex KeyIndex
+  deriving (Eq, Generic, NFData)
+
+instance Show Bip32PathIndex where
+  showsPrec d (Bip32HardIndex i) =
+    showParen (d > 10) $
+      showString "Bip32HardIndex " . shows i
+  showsPrec d (Bip32SoftIndex i) =
+    showParen (d > 10) $
+      showString "Bip32SoftIndex " . shows i
+
+instance Read Bip32PathIndex where
+  readPrec = h <|> s
+    where
+      h =
+        parens $ do
+          Ident "Bip32HardIndex" <- lexP
+          Number n <- lexP
+          maybe
+            pfail
+            (return . Bip32HardIndex . fromIntegral)
+            (numberToInteger n)
+      s =
+        parens $ do
+          Ident "Bip32SoftIndex" <- lexP
+          Number n <- lexP
+          maybe
+            pfail
+            (return . Bip32SoftIndex . fromIntegral)
+            (numberToInteger n)
+
+-- | Test whether the number could be a valid BIP32 derivation index.
+is31Bit :: (Integral a) => a -> Bool
+is31Bit i = i >= 0 && i < 0x80000000
+
+-- | Helper function to parse a hard path.
+parseHard :: String -> Maybe HardPath
+parseHard = toHard . (.get) <=< parsePath
+
+-- | Helper function to parse a soft path.
+parseSoft :: String -> Maybe SoftPath
+parseSoft = toSoft . (.get) <=< parsePath
+
+-- | Data type representing a private or public key with its respective network.
+data XKey
+  = XPrv
+      { xprv :: !XPrvKey,
+        net :: !Network
+      }
+  | XPub
+      { xpub :: !XPubKey,
+        net :: !Network
+      }
+  deriving (Show, Read, Eq, Generic, NFData)
+
+-- | Apply a parsed path to an extended key to derive the new key defined in the
+-- path. If the path starts with /m/, a private key will be returned and if the
+-- path starts with /M/, a public key will be returned. Private derivations on a
+-- public key, and public derivations with a hard segment, return an error
+-- value.
+applyPath :: Ctx -> ParsedPath -> XKey -> Either String XKey
+applyPath ctx path key =
+  case (path, key) of
+    (ParsedPrv _, XPrv k n) -> return $ XPrv (derivPrvF k) n
+    (ParsedPrv _, XPub {}) -> Left "applyPath: Invalid public key"
+    (ParsedPub _, XPrv k n) -> return $ XPub (deriveXPubKey ctx (derivPrvF k)) n
+    (ParsedPub _, XPub k n) -> derivPubFE >>= \f -> return $ XPub (f k) n
+    -- For empty parsed paths, we take a hint from the provided key
+    (ParsedEmpty _, XPrv k n) -> return $ XPrv (derivPrvF k) n
+    (ParsedEmpty _, XPub k n) -> derivPubFE >>= \f -> return $ XPub (f k) n
+  where
+    derivPrvF = goPrv id path.get
+    derivPubFE = goPubE id path.get
+    -- Build the full private derivation function starting from the end
+    goPrv f p =
+      case p of
+        next :| i -> goPrv (f . flip (hardSubKey ctx) i) next
+        next :/ i -> goPrv (f . flip (prvSubKey ctx) i) next
+        Deriv -> f
+    -- Build the full public derivation function starting from the end
+    goPubE f p =
+      case p of
+        next :/ i -> goPubE (f . flip (pubSubKey ctx) i) next
+        Deriv -> Right f
+        _ -> Left "applyPath: Invalid hard derivation"
+
+{- Helpers for derivation paths and addresses -}
+
+-- | Derive an address from a given parent path.
+derivePathAddr :: Ctx -> XPubKey -> SoftPath -> KeyIndex -> (Address, PubKey)
+derivePathAddr ctx key path = deriveAddr ctx (derivePubPath ctx path key)
+
+-- | Cyclic list of all addresses derived from a given parent path and starting
+-- from the given offset index.
+derivePathAddrs ::
+  Ctx -> XPubKey -> SoftPath -> KeyIndex -> [(Address, PubKey, KeyIndex)]
+derivePathAddrs ctx key path = deriveAddrs ctx (derivePubPath ctx path key)
+
+-- | Derive a multisig address from a given parent path. The number of required
+-- signatures (m in m of n) is also needed.
+derivePathMSAddr ::
+  Ctx ->
+  [XPubKey] ->
+  SoftPath ->
+  Int ->
+  KeyIndex ->
+  (Address, RedeemScript)
+derivePathMSAddr ctx keys path =
+  deriveMSAddr ctx $ map (derivePubPath ctx path) keys
+
+-- | Cyclic list of all multisig addresses derived from a given parent path and
+-- starting from the given offset index. The number of required signatures
+-- (m in m of n) is also needed.
+derivePathMSAddrs ::
+  Ctx ->
+  [XPubKey] ->
+  SoftPath ->
+  Int ->
+  KeyIndex ->
+  [(Address, RedeemScript, KeyIndex)]
+derivePathMSAddrs ctx keys path =
+  deriveMSAddrs ctx $ map (derivePubPath ctx path) keys
+
+{- Utilities for extended keys -}
+
+-- | De-serialize HDW-specific private key.
+getPadPrvKey :: (MonadGet m) => m SecKey
+getPadPrvKey = do
+  pad <- getWord8
+  unless (pad == 0x00) $ fail "Private key must be padded with 0x00"
+  bs <- getByteString 32
+  case secKey bs of
+    Nothing -> fail $ "Could not decode secret key: " ++ cs (encodeHex bs)
+    Just x -> return x
+
+-- | Serialize HDW-specific private key.
+putPadPrvKey :: (MonadPut m) => SecKey -> m ()
+putPadPrvKey p = putWord8 0x00 >> putByteString p.get
+
+bsPadPrvKey :: SecKey -> ByteString
+bsPadPrvKey = runPutS . putPadPrvKey
diff --git a/src/Haskoin/Crypto/Keys/Extended/Internal.hs b/src/Haskoin/Crypto/Keys/Extended/Internal.hs
new file mode 100644
--- /dev/null
+++ b/src/Haskoin/Crypto/Keys/Extended/Internal.hs
@@ -0,0 +1,88 @@
+{-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
+{-# LANGUAGE ImportQualifiedPost #-}
+{-# LANGUAGE OverloadedRecordDot #-}
+{-# LANGUAGE NoFieldSelectors #-}
+
+module Haskoin.Crypto.Keys.Extended.Internal
+  ( Fingerprint (..),
+    fingerprintToText,
+    textToFingerprint,
+  )
+where
+
+import Control.DeepSeq (NFData)
+import Control.Monad ((>=>))
+import Data.Aeson
+  ( FromJSON (parseJSON),
+    ToJSON (toEncoding, toJSON),
+    withText,
+  )
+import Data.Aeson.Encoding (text)
+import Data.Binary (Binary (..))
+import Data.Bytes.Get (getWord32be)
+import Data.Bytes.Put (putWord32be)
+import Data.Bytes.Serial (Serial (..))
+import Data.Either (fromRight)
+import Data.Hashable (Hashable)
+import Data.Maybe (fromMaybe)
+import Data.Serialize (Serialize (..))
+import Data.Serialize qualified as S
+import Data.String (IsString (..))
+import Data.Text (Text)
+import Data.Text qualified as Text
+import Data.Typeable (Typeable)
+import Data.Word (Word32)
+import GHC.Generics (Generic)
+import Haskoin.Util.Helpers (decodeHex, encodeHex)
+import Text.Read (readEither, readPrec)
+
+-- | Fingerprint of parent
+newtype Fingerprint = Fingerprint {get :: Word32}
+  deriving (Eq, Ord, Hashable, Typeable, Generic, NFData)
+
+fingerprintToText :: Fingerprint -> Text
+fingerprintToText = encodeHex . S.encode
+
+textToFingerprint :: Text -> Either String Fingerprint
+textToFingerprint =
+  maybe (Left "Fingerprint: invalid hex") Right . decodeHex >=> S.decode
+
+instance Show Fingerprint where
+  show = show . Text.unpack . encodeHex . S.encode
+
+instance Read Fingerprint where
+  readPrec =
+    readPrec
+      >>= maybe (fail "Fingerprint: invalid hex") pure . decodeHex
+      >>= either (fail . ("Fingerprint: " <>)) pure . S.decode
+
+instance IsString Fingerprint where
+  fromString =
+    fromRight decodeError
+      . S.decode
+      . fromMaybe hexError
+      . decodeHex
+      . Text.pack
+    where
+      decodeError = error "Fingerprint literal: Unable to decode"
+      hexError = error "Fingerprint literal: Invalid hex"
+
+instance Serial Fingerprint where
+  serialize = putWord32be . (.get)
+  deserialize = Fingerprint <$> getWord32be
+
+instance Binary Fingerprint where
+  put = serialize
+  get = deserialize
+
+instance Serialize Fingerprint where
+  put = serialize
+  get = deserialize
+
+instance FromJSON Fingerprint where
+  parseJSON = withText "Fingerprint" $ either fail pure . textToFingerprint
+
+instance ToJSON Fingerprint where
+  toJSON = toJSON . fingerprintToText
+  toEncoding = text . fingerprintToText
diff --git a/src/Haskoin/Crypto/Keys/Mnemonic.hs b/src/Haskoin/Crypto/Keys/Mnemonic.hs
new file mode 100644
--- /dev/null
+++ b/src/Haskoin/Crypto/Keys/Mnemonic.hs
@@ -0,0 +1,2236 @@
+{-# LANGUAGE ImportQualifiedPost #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE NoFieldSelectors #-}
+
+-- |
+-- Module      : Haskoin.Keys.Mnemonic
+-- Copyright   : No rights reserved
+-- License     : MIT
+-- Maintainer  : jprupp@protonmail.ch
+-- Stability   : experimental
+-- Portability : POSIX
+--
+-- Mnemonic keys (BIP-39). Only English dictionary.
+module Haskoin.Crypto.Keys.Mnemonic
+  ( -- * Mnemonic Sentences
+    Entropy,
+    Mnemonic,
+    Passphrase,
+    Seed,
+    toMnemonic,
+    fromMnemonic,
+    mnemonicToSeed,
+    wordList,
+    wordListMap,
+  )
+where
+
+import Control.Monad (when)
+import Crypto.Hash (SHA256 (..), hashWith)
+import Crypto.KDF.PBKDF2 (Parameters (..), fastPBKDF2_SHA512)
+import Data.Bits (shiftL, shiftR)
+import Data.ByteArray qualified as BA
+import Data.ByteString (ByteString)
+import Data.ByteString qualified as B
+import Data.List
+import Data.Map.Strict qualified as M
+import Data.Maybe
+import Data.String.Conversions (cs)
+import Data.Text (Text)
+import Data.Text qualified as T
+import Data.Text.Encoding qualified as E
+import Data.Vector (Vector, (!))
+import Data.Vector qualified as V
+import Haskoin.Util
+
+-- | Random data used to create a mnemonic sentence. Use a good entropy source.
+-- You will get your coins stolen if you don't. You have been warned.
+type Entropy = ByteString
+
+-- | Human-readable mnemonic sentence.
+type Mnemonic = Text
+
+-- | Optional passphrase for mnemnoic sentence.
+type Passphrase = Text
+
+-- | Seed for a private key from a mnemonic sentence.
+type Seed = ByteString
+
+-- | Mnemonic key checksum.
+type Checksum = ByteString
+
+-- | Paremeters for PBKDF2 function.
+pbkdfParams :: Parameters
+pbkdfParams = Parameters {iterCounts = 2048, outputLength = 64}
+
+-- | Provide intial 'Entropy' as a 'ByteString' of length multiple of 4 bytes.
+-- Output a 'Mnemonic' sentence.
+toMnemonic :: Entropy -> Either String Mnemonic
+toMnemonic ent = do
+  when (B.null ent) $
+    Left "toMnemonic: entropy can not be empty"
+  when (remainder /= 0) $
+    Left "toMnemonic: entropy must be multiples of 4 bytes"
+  when (cs_len > 16) $
+    Left "toMnemonic: maximum entropy is 64 bytes (512 bits)"
+  return ms
+  where
+    (cs_len, remainder) = B.length ent `quotRem` 4
+    c = calcCS cs_len ent
+    indices = bsToIndices $ ent `B.append` c
+    ms = T.unwords $ map (wordList !) indices
+
+-- | Revert 'toMnemonic'. Do not use this to generate a 'Seed'. Instead use
+-- 'mnemonicToSeed'. This outputs the original 'Entropy' used to generate a
+-- 'Mnemonic' sentence.
+fromMnemonic :: Mnemonic -> Either String Entropy
+fromMnemonic ms = do
+  when (T.null ms) $
+    Left "fromMnemonic: empty mnemonic"
+  when (word_count > 48) $
+    Left $
+      "fromMnemonic: too many words: " ++ show word_count
+  when (word_count `mod` 3 /= 0) $
+    Left $
+      "fromMnemonic: wrong number of words:" ++ show word_count
+  ms_bs <- indicesToBS =<< getIndices ms_words
+  let (ms_ent, ms_cs) = B.splitAt (ent_len * 4) ms_bs
+      ms_cs_num = numCS cs_len ms_cs
+      ent_cs_num = numCS cs_len $ calcCS cs_len ms_ent
+  when (ent_cs_num /= ms_cs_num) $
+    Left $
+      "fromMnemonic: checksum failed: " ++ sh ent_cs_num ms_cs_num
+  return ms_ent
+  where
+    ms_words = T.words ms
+    word_count = length ms_words
+    (ent_len, cs_len) = (word_count * 11) `quotRem` 32
+    sh cs_a cs_b = show cs_a ++ " /= " ++ show cs_b
+
+-- | Compute 'Checksum'.
+calcCS :: Int -> Entropy -> Checksum
+calcCS len = getBits len . BA.convert . hashWith SHA256
+
+numCS :: Int -> Entropy -> Integer
+numCS len =
+  shiftCS . bsToInteger
+  where
+    shiftCS = case 8 - len `mod` 8 of
+      8 -> id
+      x -> flip shiftR x
+
+-- | Turn an arbitrary sequence of characters into a 512-bit 'Seed'. Use
+-- 'mnemonicToSeed' to get a seed from a 'Mnemonic' sentence. Warning: Does not
+-- perform NFKD normalization.
+anyToSeed :: Passphrase -> Mnemonic -> Seed
+anyToSeed pf ms =
+  fastPBKDF2_SHA512
+    pbkdfParams
+    (E.encodeUtf8 ms)
+    ("mnemonic" `mappend` E.encodeUtf8 pf)
+
+-- | Get a 512-bit 'Seed' from a 'Mnemonic' sentence. Will validate checksum.
+-- 'Passphrase' can be used to protect the 'Mnemonic'. Use an empty string as
+-- 'Passphrase' if none is required.
+mnemonicToSeed :: Passphrase -> Mnemonic -> Either String Seed
+mnemonicToSeed pf ms = do
+  ent <- fromMnemonic ms
+  mnm <- toMnemonic ent
+  return $ anyToSeed pf mnm
+
+-- | Get indices of words in word list.
+getIndices :: [Text] -> Either String [Int]
+getIndices ws
+  | null n = return $ catMaybes i
+  | otherwise = Left $ "getIndices: words not found: " ++ cs w
+  where
+    i = map (`M.lookup` wordListMap) ws
+    n = elemIndices Nothing i
+    w = T.unwords $ map (ws !!) n
+
+-- | Turn a list of 11-bit numbers into a 'ByteString'
+indicesToBS :: [Int] -> Either String ByteString
+indicesToBS is = do
+  when lrg $ Left "indicesToBS: index larger or equal than 2048"
+  return . pad . integerToBS $ foldl' f 0 is `shiftL` shift_width
+  where
+    lrg = isJust $ find (>= 2048) is
+    (q, r) = (length is * 11) `quotRem` 8
+    shift_width =
+      if r == 0
+        then 0
+        else 8 - r
+    bl =
+      if r == 0
+        then q
+        else q + 1 -- length of resulting ByteString
+    pad bs = B.append (B.replicate (bl - B.length bs) 0x00) bs
+    f acc x = (acc `shiftL` 11) + fromIntegral x
+
+-- | Turn a 'ByteString' into a list of 11-bit numbers.
+bsToIndices :: ByteString -> [Int]
+bsToIndices bs =
+  reverse . go q $ bsToInteger bs `shiftR` r
+  where
+    (q, r) = (B.length bs * 8) `quotRem` 11
+    go 0 _ = []
+    go n i = fromIntegral (i `mod` 2048) : go (n - 1) (i `shiftR` 11)
+
+-- | Map of words to their position (index) in the word list.
+wordListMap :: M.Map Text Int
+wordListMap = V.ifoldr' (flip M.insert) M.empty wordList
+
+-- | Standard English dictionary from BIP-39 specification.
+wordList :: Vector Text
+wordList =
+  V.fromListN
+    2048
+    [ "abandon",
+      "ability",
+      "able",
+      "about",
+      "above",
+      "absent",
+      "absorb",
+      "abstract",
+      "absurd",
+      "abuse",
+      "access",
+      "accident",
+      "account",
+      "accuse",
+      "achieve",
+      "acid",
+      "acoustic",
+      "acquire",
+      "across",
+      "act",
+      "action",
+      "actor",
+      "actress",
+      "actual",
+      "adapt",
+      "add",
+      "addict",
+      "address",
+      "adjust",
+      "admit",
+      "adult",
+      "advance",
+      "advice",
+      "aerobic",
+      "affair",
+      "afford",
+      "afraid",
+      "again",
+      "age",
+      "agent",
+      "agree",
+      "ahead",
+      "aim",
+      "air",
+      "airport",
+      "aisle",
+      "alarm",
+      "album",
+      "alcohol",
+      "alert",
+      "alien",
+      "all",
+      "alley",
+      "allow",
+      "almost",
+      "alone",
+      "alpha",
+      "already",
+      "also",
+      "alter",
+      "always",
+      "amateur",
+      "amazing",
+      "among",
+      "amount",
+      "amused",
+      "analyst",
+      "anchor",
+      "ancient",
+      "anger",
+      "angle",
+      "angry",
+      "animal",
+      "ankle",
+      "announce",
+      "annual",
+      "another",
+      "answer",
+      "antenna",
+      "antique",
+      "anxiety",
+      "any",
+      "apart",
+      "apology",
+      "appear",
+      "apple",
+      "approve",
+      "april",
+      "arch",
+      "arctic",
+      "area",
+      "arena",
+      "argue",
+      "arm",
+      "armed",
+      "armor",
+      "army",
+      "around",
+      "arrange",
+      "arrest",
+      "arrive",
+      "arrow",
+      "art",
+      "artefact",
+      "artist",
+      "artwork",
+      "ask",
+      "aspect",
+      "assault",
+      "asset",
+      "assist",
+      "assume",
+      "asthma",
+      "athlete",
+      "atom",
+      "attack",
+      "attend",
+      "attitude",
+      "attract",
+      "auction",
+      "audit",
+      "august",
+      "aunt",
+      "author",
+      "auto",
+      "autumn",
+      "average",
+      "avocado",
+      "avoid",
+      "awake",
+      "aware",
+      "away",
+      "awesome",
+      "awful",
+      "awkward",
+      "axis",
+      "baby",
+      "bachelor",
+      "bacon",
+      "badge",
+      "bag",
+      "balance",
+      "balcony",
+      "ball",
+      "bamboo",
+      "banana",
+      "banner",
+      "bar",
+      "barely",
+      "bargain",
+      "barrel",
+      "base",
+      "basic",
+      "basket",
+      "battle",
+      "beach",
+      "bean",
+      "beauty",
+      "because",
+      "become",
+      "beef",
+      "before",
+      "begin",
+      "behave",
+      "behind",
+      "believe",
+      "below",
+      "belt",
+      "bench",
+      "benefit",
+      "best",
+      "betray",
+      "better",
+      "between",
+      "beyond",
+      "bicycle",
+      "bid",
+      "bike",
+      "bind",
+      "biology",
+      "bird",
+      "birth",
+      "bitter",
+      "black",
+      "blade",
+      "blame",
+      "blanket",
+      "blast",
+      "bleak",
+      "bless",
+      "blind",
+      "blood",
+      "blossom",
+      "blouse",
+      "blue",
+      "blur",
+      "blush",
+      "board",
+      "boat",
+      "body",
+      "boil",
+      "bomb",
+      "bone",
+      "bonus",
+      "book",
+      "boost",
+      "border",
+      "boring",
+      "borrow",
+      "boss",
+      "bottom",
+      "bounce",
+      "box",
+      "boy",
+      "bracket",
+      "brain",
+      "brand",
+      "brass",
+      "brave",
+      "bread",
+      "breeze",
+      "brick",
+      "bridge",
+      "brief",
+      "bright",
+      "bring",
+      "brisk",
+      "broccoli",
+      "broken",
+      "bronze",
+      "broom",
+      "brother",
+      "brown",
+      "brush",
+      "bubble",
+      "buddy",
+      "budget",
+      "buffalo",
+      "build",
+      "bulb",
+      "bulk",
+      "bullet",
+      "bundle",
+      "bunker",
+      "burden",
+      "burger",
+      "burst",
+      "bus",
+      "business",
+      "busy",
+      "butter",
+      "buyer",
+      "buzz",
+      "cabbage",
+      "cabin",
+      "cable",
+      "cactus",
+      "cage",
+      "cake",
+      "call",
+      "calm",
+      "camera",
+      "camp",
+      "can",
+      "canal",
+      "cancel",
+      "candy",
+      "cannon",
+      "canoe",
+      "canvas",
+      "canyon",
+      "capable",
+      "capital",
+      "captain",
+      "car",
+      "carbon",
+      "card",
+      "cargo",
+      "carpet",
+      "carry",
+      "cart",
+      "case",
+      "cash",
+      "casino",
+      "castle",
+      "casual",
+      "cat",
+      "catalog",
+      "catch",
+      "category",
+      "cattle",
+      "caught",
+      "cause",
+      "caution",
+      "cave",
+      "ceiling",
+      "celery",
+      "cement",
+      "census",
+      "century",
+      "cereal",
+      "certain",
+      "chair",
+      "chalk",
+      "champion",
+      "change",
+      "chaos",
+      "chapter",
+      "charge",
+      "chase",
+      "chat",
+      "cheap",
+      "check",
+      "cheese",
+      "chef",
+      "cherry",
+      "chest",
+      "chicken",
+      "chief",
+      "child",
+      "chimney",
+      "choice",
+      "choose",
+      "chronic",
+      "chuckle",
+      "chunk",
+      "churn",
+      "cigar",
+      "cinnamon",
+      "circle",
+      "citizen",
+      "city",
+      "civil",
+      "claim",
+      "clap",
+      "clarify",
+      "claw",
+      "clay",
+      "clean",
+      "clerk",
+      "clever",
+      "click",
+      "client",
+      "cliff",
+      "climb",
+      "clinic",
+      "clip",
+      "clock",
+      "clog",
+      "close",
+      "cloth",
+      "cloud",
+      "clown",
+      "club",
+      "clump",
+      "cluster",
+      "clutch",
+      "coach",
+      "coast",
+      "coconut",
+      "code",
+      "coffee",
+      "coil",
+      "coin",
+      "collect",
+      "color",
+      "column",
+      "combine",
+      "come",
+      "comfort",
+      "comic",
+      "common",
+      "company",
+      "concert",
+      "conduct",
+      "confirm",
+      "congress",
+      "connect",
+      "consider",
+      "control",
+      "convince",
+      "cook",
+      "cool",
+      "copper",
+      "copy",
+      "coral",
+      "core",
+      "corn",
+      "correct",
+      "cost",
+      "cotton",
+      "couch",
+      "country",
+      "couple",
+      "course",
+      "cousin",
+      "cover",
+      "coyote",
+      "crack",
+      "cradle",
+      "craft",
+      "cram",
+      "crane",
+      "crash",
+      "crater",
+      "crawl",
+      "crazy",
+      "cream",
+      "credit",
+      "creek",
+      "crew",
+      "cricket",
+      "crime",
+      "crisp",
+      "critic",
+      "crop",
+      "cross",
+      "crouch",
+      "crowd",
+      "crucial",
+      "cruel",
+      "cruise",
+      "crumble",
+      "crunch",
+      "crush",
+      "cry",
+      "crystal",
+      "cube",
+      "culture",
+      "cup",
+      "cupboard",
+      "curious",
+      "current",
+      "curtain",
+      "curve",
+      "cushion",
+      "custom",
+      "cute",
+      "cycle",
+      "dad",
+      "damage",
+      "damp",
+      "dance",
+      "danger",
+      "daring",
+      "dash",
+      "daughter",
+      "dawn",
+      "day",
+      "deal",
+      "debate",
+      "debris",
+      "decade",
+      "december",
+      "decide",
+      "decline",
+      "decorate",
+      "decrease",
+      "deer",
+      "defense",
+      "define",
+      "defy",
+      "degree",
+      "delay",
+      "deliver",
+      "demand",
+      "demise",
+      "denial",
+      "dentist",
+      "deny",
+      "depart",
+      "depend",
+      "deposit",
+      "depth",
+      "deputy",
+      "derive",
+      "describe",
+      "desert",
+      "design",
+      "desk",
+      "despair",
+      "destroy",
+      "detail",
+      "detect",
+      "develop",
+      "device",
+      "devote",
+      "diagram",
+      "dial",
+      "diamond",
+      "diary",
+      "dice",
+      "diesel",
+      "diet",
+      "differ",
+      "digital",
+      "dignity",
+      "dilemma",
+      "dinner",
+      "dinosaur",
+      "direct",
+      "dirt",
+      "disagree",
+      "discover",
+      "disease",
+      "dish",
+      "dismiss",
+      "disorder",
+      "display",
+      "distance",
+      "divert",
+      "divide",
+      "divorce",
+      "dizzy",
+      "doctor",
+      "document",
+      "dog",
+      "doll",
+      "dolphin",
+      "domain",
+      "donate",
+      "donkey",
+      "donor",
+      "door",
+      "dose",
+      "double",
+      "dove",
+      "draft",
+      "dragon",
+      "drama",
+      "drastic",
+      "draw",
+      "dream",
+      "dress",
+      "drift",
+      "drill",
+      "drink",
+      "drip",
+      "drive",
+      "drop",
+      "drum",
+      "dry",
+      "duck",
+      "dumb",
+      "dune",
+      "during",
+      "dust",
+      "dutch",
+      "duty",
+      "dwarf",
+      "dynamic",
+      "eager",
+      "eagle",
+      "early",
+      "earn",
+      "earth",
+      "easily",
+      "east",
+      "easy",
+      "echo",
+      "ecology",
+      "economy",
+      "edge",
+      "edit",
+      "educate",
+      "effort",
+      "egg",
+      "eight",
+      "either",
+      "elbow",
+      "elder",
+      "electric",
+      "elegant",
+      "element",
+      "elephant",
+      "elevator",
+      "elite",
+      "else",
+      "embark",
+      "embody",
+      "embrace",
+      "emerge",
+      "emotion",
+      "employ",
+      "empower",
+      "empty",
+      "enable",
+      "enact",
+      "end",
+      "endless",
+      "endorse",
+      "enemy",
+      "energy",
+      "enforce",
+      "engage",
+      "engine",
+      "enhance",
+      "enjoy",
+      "enlist",
+      "enough",
+      "enrich",
+      "enroll",
+      "ensure",
+      "enter",
+      "entire",
+      "entry",
+      "envelope",
+      "episode",
+      "equal",
+      "equip",
+      "era",
+      "erase",
+      "erode",
+      "erosion",
+      "error",
+      "erupt",
+      "escape",
+      "essay",
+      "essence",
+      "estate",
+      "eternal",
+      "ethics",
+      "evidence",
+      "evil",
+      "evoke",
+      "evolve",
+      "exact",
+      "example",
+      "excess",
+      "exchange",
+      "excite",
+      "exclude",
+      "excuse",
+      "execute",
+      "exercise",
+      "exhaust",
+      "exhibit",
+      "exile",
+      "exist",
+      "exit",
+      "exotic",
+      "expand",
+      "expect",
+      "expire",
+      "explain",
+      "expose",
+      "express",
+      "extend",
+      "extra",
+      "eye",
+      "eyebrow",
+      "fabric",
+      "face",
+      "faculty",
+      "fade",
+      "faint",
+      "faith",
+      "fall",
+      "false",
+      "fame",
+      "family",
+      "famous",
+      "fan",
+      "fancy",
+      "fantasy",
+      "farm",
+      "fashion",
+      "fat",
+      "fatal",
+      "father",
+      "fatigue",
+      "fault",
+      "favorite",
+      "feature",
+      "february",
+      "federal",
+      "fee",
+      "feed",
+      "feel",
+      "female",
+      "fence",
+      "festival",
+      "fetch",
+      "fever",
+      "few",
+      "fiber",
+      "fiction",
+      "field",
+      "figure",
+      "file",
+      "film",
+      "filter",
+      "final",
+      "find",
+      "fine",
+      "finger",
+      "finish",
+      "fire",
+      "firm",
+      "first",
+      "fiscal",
+      "fish",
+      "fit",
+      "fitness",
+      "fix",
+      "flag",
+      "flame",
+      "flash",
+      "flat",
+      "flavor",
+      "flee",
+      "flight",
+      "flip",
+      "float",
+      "flock",
+      "floor",
+      "flower",
+      "fluid",
+      "flush",
+      "fly",
+      "foam",
+      "focus",
+      "fog",
+      "foil",
+      "fold",
+      "follow",
+      "food",
+      "foot",
+      "force",
+      "forest",
+      "forget",
+      "fork",
+      "fortune",
+      "forum",
+      "forward",
+      "fossil",
+      "foster",
+      "found",
+      "fox",
+      "fragile",
+      "frame",
+      "frequent",
+      "fresh",
+      "friend",
+      "fringe",
+      "frog",
+      "front",
+      "frost",
+      "frown",
+      "frozen",
+      "fruit",
+      "fuel",
+      "fun",
+      "funny",
+      "furnace",
+      "fury",
+      "future",
+      "gadget",
+      "gain",
+      "galaxy",
+      "gallery",
+      "game",
+      "gap",
+      "garage",
+      "garbage",
+      "garden",
+      "garlic",
+      "garment",
+      "gas",
+      "gasp",
+      "gate",
+      "gather",
+      "gauge",
+      "gaze",
+      "general",
+      "genius",
+      "genre",
+      "gentle",
+      "genuine",
+      "gesture",
+      "ghost",
+      "giant",
+      "gift",
+      "giggle",
+      "ginger",
+      "giraffe",
+      "girl",
+      "give",
+      "glad",
+      "glance",
+      "glare",
+      "glass",
+      "glide",
+      "glimpse",
+      "globe",
+      "gloom",
+      "glory",
+      "glove",
+      "glow",
+      "glue",
+      "goat",
+      "goddess",
+      "gold",
+      "good",
+      "goose",
+      "gorilla",
+      "gospel",
+      "gossip",
+      "govern",
+      "gown",
+      "grab",
+      "grace",
+      "grain",
+      "grant",
+      "grape",
+      "grass",
+      "gravity",
+      "great",
+      "green",
+      "grid",
+      "grief",
+      "grit",
+      "grocery",
+      "group",
+      "grow",
+      "grunt",
+      "guard",
+      "guess",
+      "guide",
+      "guilt",
+      "guitar",
+      "gun",
+      "gym",
+      "habit",
+      "hair",
+      "half",
+      "hammer",
+      "hamster",
+      "hand",
+      "happy",
+      "harbor",
+      "hard",
+      "harsh",
+      "harvest",
+      "hat",
+      "have",
+      "hawk",
+      "hazard",
+      "head",
+      "health",
+      "heart",
+      "heavy",
+      "hedgehog",
+      "height",
+      "hello",
+      "helmet",
+      "help",
+      "hen",
+      "hero",
+      "hidden",
+      "high",
+      "hill",
+      "hint",
+      "hip",
+      "hire",
+      "history",
+      "hobby",
+      "hockey",
+      "hold",
+      "hole",
+      "holiday",
+      "hollow",
+      "home",
+      "honey",
+      "hood",
+      "hope",
+      "horn",
+      "horror",
+      "horse",
+      "hospital",
+      "host",
+      "hotel",
+      "hour",
+      "hover",
+      "hub",
+      "huge",
+      "human",
+      "humble",
+      "humor",
+      "hundred",
+      "hungry",
+      "hunt",
+      "hurdle",
+      "hurry",
+      "hurt",
+      "husband",
+      "hybrid",
+      "ice",
+      "icon",
+      "idea",
+      "identify",
+      "idle",
+      "ignore",
+      "ill",
+      "illegal",
+      "illness",
+      "image",
+      "imitate",
+      "immense",
+      "immune",
+      "impact",
+      "impose",
+      "improve",
+      "impulse",
+      "inch",
+      "include",
+      "income",
+      "increase",
+      "index",
+      "indicate",
+      "indoor",
+      "industry",
+      "infant",
+      "inflict",
+      "inform",
+      "inhale",
+      "inherit",
+      "initial",
+      "inject",
+      "injury",
+      "inmate",
+      "inner",
+      "innocent",
+      "input",
+      "inquiry",
+      "insane",
+      "insect",
+      "inside",
+      "inspire",
+      "install",
+      "intact",
+      "interest",
+      "into",
+      "invest",
+      "invite",
+      "involve",
+      "iron",
+      "island",
+      "isolate",
+      "issue",
+      "item",
+      "ivory",
+      "jacket",
+      "jaguar",
+      "jar",
+      "jazz",
+      "jealous",
+      "jeans",
+      "jelly",
+      "jewel",
+      "job",
+      "join",
+      "joke",
+      "journey",
+      "joy",
+      "judge",
+      "juice",
+      "jump",
+      "jungle",
+      "junior",
+      "junk",
+      "just",
+      "kangaroo",
+      "keen",
+      "keep",
+      "ketchup",
+      "key",
+      "kick",
+      "kid",
+      "kidney",
+      "kind",
+      "kingdom",
+      "kiss",
+      "kit",
+      "kitchen",
+      "kite",
+      "kitten",
+      "kiwi",
+      "knee",
+      "knife",
+      "knock",
+      "know",
+      "lab",
+      "label",
+      "labor",
+      "ladder",
+      "lady",
+      "lake",
+      "lamp",
+      "language",
+      "laptop",
+      "large",
+      "later",
+      "latin",
+      "laugh",
+      "laundry",
+      "lava",
+      "law",
+      "lawn",
+      "lawsuit",
+      "layer",
+      "lazy",
+      "leader",
+      "leaf",
+      "learn",
+      "leave",
+      "lecture",
+      "left",
+      "leg",
+      "legal",
+      "legend",
+      "leisure",
+      "lemon",
+      "lend",
+      "length",
+      "lens",
+      "leopard",
+      "lesson",
+      "letter",
+      "level",
+      "liar",
+      "liberty",
+      "library",
+      "license",
+      "life",
+      "lift",
+      "light",
+      "like",
+      "limb",
+      "limit",
+      "link",
+      "lion",
+      "liquid",
+      "list",
+      "little",
+      "live",
+      "lizard",
+      "load",
+      "loan",
+      "lobster",
+      "local",
+      "lock",
+      "logic",
+      "lonely",
+      "long",
+      "loop",
+      "lottery",
+      "loud",
+      "lounge",
+      "love",
+      "loyal",
+      "lucky",
+      "luggage",
+      "lumber",
+      "lunar",
+      "lunch",
+      "luxury",
+      "lyrics",
+      "machine",
+      "mad",
+      "magic",
+      "magnet",
+      "maid",
+      "mail",
+      "main",
+      "major",
+      "make",
+      "mammal",
+      "man",
+      "manage",
+      "mandate",
+      "mango",
+      "mansion",
+      "manual",
+      "maple",
+      "marble",
+      "march",
+      "margin",
+      "marine",
+      "market",
+      "marriage",
+      "mask",
+      "mass",
+      "master",
+      "match",
+      "material",
+      "math",
+      "matrix",
+      "matter",
+      "maximum",
+      "maze",
+      "meadow",
+      "mean",
+      "measure",
+      "meat",
+      "mechanic",
+      "medal",
+      "media",
+      "melody",
+      "melt",
+      "member",
+      "memory",
+      "mention",
+      "menu",
+      "mercy",
+      "merge",
+      "merit",
+      "merry",
+      "mesh",
+      "message",
+      "metal",
+      "method",
+      "middle",
+      "midnight",
+      "milk",
+      "million",
+      "mimic",
+      "mind",
+      "minimum",
+      "minor",
+      "minute",
+      "miracle",
+      "mirror",
+      "misery",
+      "miss",
+      "mistake",
+      "mix",
+      "mixed",
+      "mixture",
+      "mobile",
+      "model",
+      "modify",
+      "mom",
+      "moment",
+      "monitor",
+      "monkey",
+      "monster",
+      "month",
+      "moon",
+      "moral",
+      "more",
+      "morning",
+      "mosquito",
+      "mother",
+      "motion",
+      "motor",
+      "mountain",
+      "mouse",
+      "move",
+      "movie",
+      "much",
+      "muffin",
+      "mule",
+      "multiply",
+      "muscle",
+      "museum",
+      "mushroom",
+      "music",
+      "must",
+      "mutual",
+      "myself",
+      "mystery",
+      "myth",
+      "naive",
+      "name",
+      "napkin",
+      "narrow",
+      "nasty",
+      "nation",
+      "nature",
+      "near",
+      "neck",
+      "need",
+      "negative",
+      "neglect",
+      "neither",
+      "nephew",
+      "nerve",
+      "nest",
+      "net",
+      "network",
+      "neutral",
+      "never",
+      "news",
+      "next",
+      "nice",
+      "night",
+      "noble",
+      "noise",
+      "nominee",
+      "noodle",
+      "normal",
+      "north",
+      "nose",
+      "notable",
+      "note",
+      "nothing",
+      "notice",
+      "novel",
+      "now",
+      "nuclear",
+      "number",
+      "nurse",
+      "nut",
+      "oak",
+      "obey",
+      "object",
+      "oblige",
+      "obscure",
+      "observe",
+      "obtain",
+      "obvious",
+      "occur",
+      "ocean",
+      "october",
+      "odor",
+      "off",
+      "offer",
+      "office",
+      "often",
+      "oil",
+      "okay",
+      "old",
+      "olive",
+      "olympic",
+      "omit",
+      "once",
+      "one",
+      "onion",
+      "online",
+      "only",
+      "open",
+      "opera",
+      "opinion",
+      "oppose",
+      "option",
+      "orange",
+      "orbit",
+      "orchard",
+      "order",
+      "ordinary",
+      "organ",
+      "orient",
+      "original",
+      "orphan",
+      "ostrich",
+      "other",
+      "outdoor",
+      "outer",
+      "output",
+      "outside",
+      "oval",
+      "oven",
+      "over",
+      "own",
+      "owner",
+      "oxygen",
+      "oyster",
+      "ozone",
+      "pact",
+      "paddle",
+      "page",
+      "pair",
+      "palace",
+      "palm",
+      "panda",
+      "panel",
+      "panic",
+      "panther",
+      "paper",
+      "parade",
+      "parent",
+      "park",
+      "parrot",
+      "party",
+      "pass",
+      "patch",
+      "path",
+      "patient",
+      "patrol",
+      "pattern",
+      "pause",
+      "pave",
+      "payment",
+      "peace",
+      "peanut",
+      "pear",
+      "peasant",
+      "pelican",
+      "pen",
+      "penalty",
+      "pencil",
+      "people",
+      "pepper",
+      "perfect",
+      "permit",
+      "person",
+      "pet",
+      "phone",
+      "photo",
+      "phrase",
+      "physical",
+      "piano",
+      "picnic",
+      "picture",
+      "piece",
+      "pig",
+      "pigeon",
+      "pill",
+      "pilot",
+      "pink",
+      "pioneer",
+      "pipe",
+      "pistol",
+      "pitch",
+      "pizza",
+      "place",
+      "planet",
+      "plastic",
+      "plate",
+      "play",
+      "please",
+      "pledge",
+      "pluck",
+      "plug",
+      "plunge",
+      "poem",
+      "poet",
+      "point",
+      "polar",
+      "pole",
+      "police",
+      "pond",
+      "pony",
+      "pool",
+      "popular",
+      "portion",
+      "position",
+      "possible",
+      "post",
+      "potato",
+      "pottery",
+      "poverty",
+      "powder",
+      "power",
+      "practice",
+      "praise",
+      "predict",
+      "prefer",
+      "prepare",
+      "present",
+      "pretty",
+      "prevent",
+      "price",
+      "pride",
+      "primary",
+      "print",
+      "priority",
+      "prison",
+      "private",
+      "prize",
+      "problem",
+      "process",
+      "produce",
+      "profit",
+      "program",
+      "project",
+      "promote",
+      "proof",
+      "property",
+      "prosper",
+      "protect",
+      "proud",
+      "provide",
+      "public",
+      "pudding",
+      "pull",
+      "pulp",
+      "pulse",
+      "pumpkin",
+      "punch",
+      "pupil",
+      "puppy",
+      "purchase",
+      "purity",
+      "purpose",
+      "purse",
+      "push",
+      "put",
+      "puzzle",
+      "pyramid",
+      "quality",
+      "quantum",
+      "quarter",
+      "question",
+      "quick",
+      "quit",
+      "quiz",
+      "quote",
+      "rabbit",
+      "raccoon",
+      "race",
+      "rack",
+      "radar",
+      "radio",
+      "rail",
+      "rain",
+      "raise",
+      "rally",
+      "ramp",
+      "ranch",
+      "random",
+      "range",
+      "rapid",
+      "rare",
+      "rate",
+      "rather",
+      "raven",
+      "raw",
+      "razor",
+      "ready",
+      "real",
+      "reason",
+      "rebel",
+      "rebuild",
+      "recall",
+      "receive",
+      "recipe",
+      "record",
+      "recycle",
+      "reduce",
+      "reflect",
+      "reform",
+      "refuse",
+      "region",
+      "regret",
+      "regular",
+      "reject",
+      "relax",
+      "release",
+      "relief",
+      "rely",
+      "remain",
+      "remember",
+      "remind",
+      "remove",
+      "render",
+      "renew",
+      "rent",
+      "reopen",
+      "repair",
+      "repeat",
+      "replace",
+      "report",
+      "require",
+      "rescue",
+      "resemble",
+      "resist",
+      "resource",
+      "response",
+      "result",
+      "retire",
+      "retreat",
+      "return",
+      "reunion",
+      "reveal",
+      "review",
+      "reward",
+      "rhythm",
+      "rib",
+      "ribbon",
+      "rice",
+      "rich",
+      "ride",
+      "ridge",
+      "rifle",
+      "right",
+      "rigid",
+      "ring",
+      "riot",
+      "ripple",
+      "risk",
+      "ritual",
+      "rival",
+      "river",
+      "road",
+      "roast",
+      "robot",
+      "robust",
+      "rocket",
+      "romance",
+      "roof",
+      "rookie",
+      "room",
+      "rose",
+      "rotate",
+      "rough",
+      "round",
+      "route",
+      "royal",
+      "rubber",
+      "rude",
+      "rug",
+      "rule",
+      "run",
+      "runway",
+      "rural",
+      "sad",
+      "saddle",
+      "sadness",
+      "safe",
+      "sail",
+      "salad",
+      "salmon",
+      "salon",
+      "salt",
+      "salute",
+      "same",
+      "sample",
+      "sand",
+      "satisfy",
+      "satoshi",
+      "sauce",
+      "sausage",
+      "save",
+      "say",
+      "scale",
+      "scan",
+      "scare",
+      "scatter",
+      "scene",
+      "scheme",
+      "school",
+      "science",
+      "scissors",
+      "scorpion",
+      "scout",
+      "scrap",
+      "screen",
+      "script",
+      "scrub",
+      "sea",
+      "search",
+      "season",
+      "seat",
+      "second",
+      "secret",
+      "section",
+      "security",
+      "seed",
+      "seek",
+      "segment",
+      "select",
+      "sell",
+      "seminar",
+      "senior",
+      "sense",
+      "sentence",
+      "series",
+      "service",
+      "session",
+      "settle",
+      "setup",
+      "seven",
+      "shadow",
+      "shaft",
+      "shallow",
+      "share",
+      "shed",
+      "shell",
+      "sheriff",
+      "shield",
+      "shift",
+      "shine",
+      "ship",
+      "shiver",
+      "shock",
+      "shoe",
+      "shoot",
+      "shop",
+      "short",
+      "shoulder",
+      "shove",
+      "shrimp",
+      "shrug",
+      "shuffle",
+      "shy",
+      "sibling",
+      "sick",
+      "side",
+      "siege",
+      "sight",
+      "sign",
+      "silent",
+      "silk",
+      "silly",
+      "silver",
+      "similar",
+      "simple",
+      "since",
+      "sing",
+      "siren",
+      "sister",
+      "situate",
+      "six",
+      "size",
+      "skate",
+      "sketch",
+      "ski",
+      "skill",
+      "skin",
+      "skirt",
+      "skull",
+      "slab",
+      "slam",
+      "sleep",
+      "slender",
+      "slice",
+      "slide",
+      "slight",
+      "slim",
+      "slogan",
+      "slot",
+      "slow",
+      "slush",
+      "small",
+      "smart",
+      "smile",
+      "smoke",
+      "smooth",
+      "snack",
+      "snake",
+      "snap",
+      "sniff",
+      "snow",
+      "soap",
+      "soccer",
+      "social",
+      "sock",
+      "soda",
+      "soft",
+      "solar",
+      "soldier",
+      "solid",
+      "solution",
+      "solve",
+      "someone",
+      "song",
+      "soon",
+      "sorry",
+      "sort",
+      "soul",
+      "sound",
+      "soup",
+      "source",
+      "south",
+      "space",
+      "spare",
+      "spatial",
+      "spawn",
+      "speak",
+      "special",
+      "speed",
+      "spell",
+      "spend",
+      "sphere",
+      "spice",
+      "spider",
+      "spike",
+      "spin",
+      "spirit",
+      "split",
+      "spoil",
+      "sponsor",
+      "spoon",
+      "sport",
+      "spot",
+      "spray",
+      "spread",
+      "spring",
+      "spy",
+      "square",
+      "squeeze",
+      "squirrel",
+      "stable",
+      "stadium",
+      "staff",
+      "stage",
+      "stairs",
+      "stamp",
+      "stand",
+      "start",
+      "state",
+      "stay",
+      "steak",
+      "steel",
+      "stem",
+      "step",
+      "stereo",
+      "stick",
+      "still",
+      "sting",
+      "stock",
+      "stomach",
+      "stone",
+      "stool",
+      "story",
+      "stove",
+      "strategy",
+      "street",
+      "strike",
+      "strong",
+      "struggle",
+      "student",
+      "stuff",
+      "stumble",
+      "style",
+      "subject",
+      "submit",
+      "subway",
+      "success",
+      "such",
+      "sudden",
+      "suffer",
+      "sugar",
+      "suggest",
+      "suit",
+      "summer",
+      "sun",
+      "sunny",
+      "sunset",
+      "super",
+      "supply",
+      "supreme",
+      "sure",
+      "surface",
+      "surge",
+      "surprise",
+      "surround",
+      "survey",
+      "suspect",
+      "sustain",
+      "swallow",
+      "swamp",
+      "swap",
+      "swarm",
+      "swear",
+      "sweet",
+      "swift",
+      "swim",
+      "swing",
+      "switch",
+      "sword",
+      "symbol",
+      "symptom",
+      "syrup",
+      "system",
+      "table",
+      "tackle",
+      "tag",
+      "tail",
+      "talent",
+      "talk",
+      "tank",
+      "tape",
+      "target",
+      "task",
+      "taste",
+      "tattoo",
+      "taxi",
+      "teach",
+      "team",
+      "tell",
+      "ten",
+      "tenant",
+      "tennis",
+      "tent",
+      "term",
+      "test",
+      "text",
+      "thank",
+      "that",
+      "theme",
+      "then",
+      "theory",
+      "there",
+      "they",
+      "thing",
+      "this",
+      "thought",
+      "three",
+      "thrive",
+      "throw",
+      "thumb",
+      "thunder",
+      "ticket",
+      "tide",
+      "tiger",
+      "tilt",
+      "timber",
+      "time",
+      "tiny",
+      "tip",
+      "tired",
+      "tissue",
+      "title",
+      "toast",
+      "tobacco",
+      "today",
+      "toddler",
+      "toe",
+      "together",
+      "toilet",
+      "token",
+      "tomato",
+      "tomorrow",
+      "tone",
+      "tongue",
+      "tonight",
+      "tool",
+      "tooth",
+      "top",
+      "topic",
+      "topple",
+      "torch",
+      "tornado",
+      "tortoise",
+      "toss",
+      "total",
+      "tourist",
+      "toward",
+      "tower",
+      "town",
+      "toy",
+      "track",
+      "trade",
+      "traffic",
+      "tragic",
+      "train",
+      "transfer",
+      "trap",
+      "trash",
+      "travel",
+      "tray",
+      "treat",
+      "tree",
+      "trend",
+      "trial",
+      "tribe",
+      "trick",
+      "trigger",
+      "trim",
+      "trip",
+      "trophy",
+      "trouble",
+      "truck",
+      "true",
+      "truly",
+      "trumpet",
+      "trust",
+      "truth",
+      "try",
+      "tube",
+      "tuition",
+      "tumble",
+      "tuna",
+      "tunnel",
+      "turkey",
+      "turn",
+      "turtle",
+      "twelve",
+      "twenty",
+      "twice",
+      "twin",
+      "twist",
+      "two",
+      "type",
+      "typical",
+      "ugly",
+      "umbrella",
+      "unable",
+      "unaware",
+      "uncle",
+      "uncover",
+      "under",
+      "undo",
+      "unfair",
+      "unfold",
+      "unhappy",
+      "uniform",
+      "unique",
+      "unit",
+      "universe",
+      "unknown",
+      "unlock",
+      "until",
+      "unusual",
+      "unveil",
+      "update",
+      "upgrade",
+      "uphold",
+      "upon",
+      "upper",
+      "upset",
+      "urban",
+      "urge",
+      "usage",
+      "use",
+      "used",
+      "useful",
+      "useless",
+      "usual",
+      "utility",
+      "vacant",
+      "vacuum",
+      "vague",
+      "valid",
+      "valley",
+      "valve",
+      "van",
+      "vanish",
+      "vapor",
+      "various",
+      "vast",
+      "vault",
+      "vehicle",
+      "velvet",
+      "vendor",
+      "venture",
+      "venue",
+      "verb",
+      "verify",
+      "version",
+      "very",
+      "vessel",
+      "veteran",
+      "viable",
+      "vibrant",
+      "vicious",
+      "victory",
+      "video",
+      "view",
+      "village",
+      "vintage",
+      "violin",
+      "virtual",
+      "virus",
+      "visa",
+      "visit",
+      "visual",
+      "vital",
+      "vivid",
+      "vocal",
+      "voice",
+      "void",
+      "volcano",
+      "volume",
+      "vote",
+      "voyage",
+      "wage",
+      "wagon",
+      "wait",
+      "walk",
+      "wall",
+      "walnut",
+      "want",
+      "warfare",
+      "warm",
+      "warrior",
+      "wash",
+      "wasp",
+      "waste",
+      "water",
+      "wave",
+      "way",
+      "wealth",
+      "weapon",
+      "wear",
+      "weasel",
+      "weather",
+      "web",
+      "wedding",
+      "weekend",
+      "weird",
+      "welcome",
+      "west",
+      "wet",
+      "whale",
+      "what",
+      "wheat",
+      "wheel",
+      "when",
+      "where",
+      "whip",
+      "whisper",
+      "wide",
+      "width",
+      "wife",
+      "wild",
+      "will",
+      "win",
+      "window",
+      "wine",
+      "wing",
+      "wink",
+      "winner",
+      "winter",
+      "wire",
+      "wisdom",
+      "wise",
+      "wish",
+      "witness",
+      "wolf",
+      "woman",
+      "wonder",
+      "wood",
+      "wool",
+      "word",
+      "work",
+      "world",
+      "worry",
+      "worth",
+      "wrap",
+      "wreck",
+      "wrestle",
+      "wrist",
+      "write",
+      "wrong",
+      "yard",
+      "year",
+      "yellow",
+      "you",
+      "young",
+      "youth",
+      "zebra",
+      "zero",
+      "zone",
+      "zoo"
+    ]
diff --git a/src/Haskoin/Crypto/Signature.hs b/src/Haskoin/Crypto/Signature.hs
new file mode 100644
--- /dev/null
+++ b/src/Haskoin/Crypto/Signature.hs
@@ -0,0 +1,106 @@
+{-# LANGUAGE DuplicateRecordFields #-}
+{-# LANGUAGE ImportQualifiedPost #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE OverloadedRecordDot #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+-- |
+-- Module      : Haskoin.Crypto.Signature
+-- Copyright   : No rights reserved
+-- License     : MIT
+-- Maintainer  : jprupp@protonmail.ch
+-- Stability   : experimental
+-- Portability : POSIX
+--
+-- ECDSA signatures using secp256k1 curve. Uses functions from upstream secp256k1
+-- library.
+module Haskoin.Crypto.Signature
+  ( -- * Signatures
+    signHash,
+    verifyHashSig,
+    isCanonicalHalfOrder,
+    decodeStrictSig,
+    exportSig,
+  )
+where
+
+import Control.Monad (guard, unless, when)
+import Crypto.Secp256k1
+import Data.Aeson
+import Data.Aeson.Encoding
+import Data.Binary (Binary (..))
+import Data.ByteString (ByteString)
+import Data.ByteString qualified as B
+import Data.ByteString.Lazy qualified as L
+import Data.Bytes.Get
+import Data.Bytes.Put
+import Data.Bytes.Serial
+import Data.Maybe (fromMaybe, isNothing)
+import Data.Serialize (Serialize (..))
+import Data.Text qualified as T
+import Haskoin.Crypto.Hash
+import Haskoin.Util.Helpers
+import Haskoin.Util.Marshal
+import Numeric (showHex)
+
+-- | Convert 256-bit hash into a 'Msg' for signing or verification.
+hashToMsg :: Hash256 -> Msg
+hashToMsg =
+  fromMaybe e . msg . runPutS . serialize
+  where
+    e = error "Could not convert 32-byte hash to secp256k1 message"
+
+-- | Sign a 256-bit hash using secp256k1 elliptic curve.
+signHash :: Ctx -> SecKey -> Hash256 -> Sig
+signHash ctx k = signMsg ctx k . hashToMsg
+
+-- | Verify an ECDSA signature for a 256-bit hash.
+verifyHashSig :: Ctx -> Hash256 -> Sig -> PubKey -> Bool
+verifyHashSig ctx h s p = verifySig ctx p norm (hashToMsg h)
+  where
+    norm = fromMaybe s (normalizeSig ctx s)
+
+instance Marshal Ctx Sig where
+  marshalGet ctx = do
+    l <- lookAhead $ do
+      t <- getWord8
+      -- 0x30 is DER sequence type
+      unless (t == 0x30) $
+        fail $
+          "Bad DER identifier byte 0x" ++ showHex t ". Expecting 0x30"
+      l <- getWord8
+      when (l == 0x00) $ fail "Indeterminate form unsupported"
+      when (l >= 0x80) $ fail "Multi-octect length not supported"
+      return $ fromIntegral l
+    bs <- getByteString $ l + 2
+    case decodeStrictSig ctx bs of
+      Just s -> return s
+      Nothing -> fail "Invalid signature"
+
+  marshalPut ctx s = putByteString $ exportSig ctx s
+
+instance MarshalJSON Ctx Sig where
+  marshalValue ctx = String . encodeHex . exportSig ctx
+  marshalEncoding ctx = hexEncoding . L.fromStrict . exportSig ctx
+  unmarshalValue ctx =
+    withText "Sig" $ \t ->
+      case decodeHex t >>= importSig ctx of
+        Nothing -> fail $ "Could not decode signature: " <> T.unpack t
+        Just s -> return s
+
+-- | Is canonical half order.
+isCanonicalHalfOrder :: Ctx -> Sig -> Bool
+isCanonicalHalfOrder ctx = isNothing . normalizeSig ctx
+
+-- | Decode signature strictly.
+decodeStrictSig :: Ctx -> ByteString -> Maybe Sig
+decodeStrictSig ctx bs = do
+  g <- importSig ctx bs
+  -- <http://www.secg.org/sec1-v2.pdf Section 4.1.4>
+  -- 4.1.4.1 (r and s can not be zero)
+  let compact = exportCompactSig ctx g
+  let zero = B.replicate 32 0
+  guard $ B.take 32 compact.get /= zero
+  guard $ (B.take 32 . B.drop 32) compact.get /= zero
+  guard $ isCanonicalHalfOrder ctx g
+  return g
diff --git a/src/Haskoin/Network.hs b/src/Haskoin/Network.hs
new file mode 100644
--- /dev/null
+++ b/src/Haskoin/Network.hs
@@ -0,0 +1,27 @@
+{-# LANGUAGE DuplicateRecordFields #-}
+
+-- |
+-- Module      : Haskoin.Network
+-- Copyright   : No rights reserved
+-- License     : MIT
+-- Maintainer  : jprupp@protonmail.ch
+-- Stability   : experimental
+-- Portability : POSIX
+--
+-- This module provides basic types used for the Bitcoin networking protocol
+-- together with 'Data.Serialize' instances for efficiently serializing and
+-- de-serializing them.
+module Haskoin.Network
+  ( module Data,
+    module Constants,
+    module Common,
+    module Message,
+    module Bloom,
+  )
+where
+
+import Haskoin.Network.Bloom as Bloom
+import Haskoin.Network.Common as Common
+import Haskoin.Network.Constants as Constants
+import Haskoin.Network.Data as Data
+import Haskoin.Network.Message as Message
diff --git a/src/Haskoin/Network/Bloom.hs b/src/Haskoin/Network/Bloom.hs
new file mode 100644
--- /dev/null
+++ b/src/Haskoin/Network/Bloom.hs
@@ -0,0 +1,345 @@
+{-# LANGUAGE DeriveAnyClass #-}
+{-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE DerivingStrategies #-}
+{-# LANGUAGE DuplicateRecordFields #-}
+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
+{-# LANGUAGE ImportQualifiedPost #-}
+{-# LANGUAGE OverloadedRecordDot #-}
+{-# LANGUAGE RecordWildCards #-}
+{-# LANGUAGE NoFieldSelectors #-}
+
+-- |
+-- Module      : Haskoin.Network.Bloom
+-- Copyright   : No rights reserved
+-- License     : MIT
+-- Maintainer  : jprupp@protonmail.ch
+-- Stability   : experimental
+-- Portability : POSIX
+--
+-- Bloom filters are used to reduce data transfer when synchronizing thin cients.
+-- When bloom filters are used a client will obtain filtered blocks that only
+-- contain transactions that pass the bloom filter. Transactions announced via inv
+-- messages also pass the filter.
+module Haskoin.Network.Bloom
+  ( -- * Bloom Filters
+    BloomFlags (..),
+    BloomFilter (..),
+    FilterLoad (..),
+    FilterAdd (..),
+    bloomCreate,
+    bloomInsert,
+    bloomContains,
+    isBloomValid,
+    isBloomEmpty,
+    isBloomFull,
+    acceptsFilters,
+    bloomRelevantUpdate,
+  )
+where
+
+import Control.DeepSeq
+import Control.Monad (forM_, replicateM)
+import Crypto.Secp256k1 (Ctx)
+import Data.Binary (Binary (..))
+import Data.Bits
+import Data.ByteString (ByteString)
+import Data.ByteString qualified as BS
+import Data.Bytes.Get
+import Data.Bytes.Put
+import Data.Bytes.Serial
+import Data.Foldable qualified as F
+import Data.Hash.Murmur (murmur3)
+import Data.List (foldl')
+import Data.Sequence qualified as S
+import Data.Serialize (Serialize (..))
+import Data.Word
+import GHC.Generics (Generic)
+import Haskoin.Network.Common
+import Haskoin.Script.Standard
+import Haskoin.Transaction.Common
+import Haskoin.Util.Marshal
+
+-- | 20,000 items with fp rate < 0.1% or 10,000 items and <0.0001%
+maxBloomSize :: Int
+maxBloomSize = 36000
+
+maxHashFuncs :: Word32
+maxHashFuncs = 50
+
+ln2Squared :: Double
+ln2Squared = 0.4804530139182014246671025263266649717305529515945455
+
+ln2 :: Double
+ln2 = 0.6931471805599453094172321214581765680755001343602552
+
+bitMask :: [Word8]
+bitMask = [0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80]
+
+-- | The bloom flags are used to tell the remote peer how to auto-update
+-- the provided bloom filter.
+data BloomFlags
+  = -- | never update
+    BloomUpdateNone
+  | -- | auto-update on all outputs
+    BloomUpdateAll
+  | -- | auto-update on pay-to-pubkey or pay-to-multisig (default)
+    BloomUpdateP2PubKeyOnly
+  deriving (Eq, Show, Read, Generic, NFData)
+
+instance Serial BloomFlags where
+  deserialize = go =<< getWord8
+    where
+      go 0 = return BloomUpdateNone
+      go 1 = return BloomUpdateAll
+      go 2 = return BloomUpdateP2PubKeyOnly
+      go _ = fail "BloomFlags get: Invalid bloom flag"
+
+  serialize f = putWord8 $ case f of
+    BloomUpdateNone -> 0
+    BloomUpdateAll -> 1
+    BloomUpdateP2PubKeyOnly -> 2
+
+instance Binary BloomFlags where
+  get = deserialize
+  put = serialize
+
+instance Serialize BloomFlags where
+  get = deserialize
+  put = serialize
+
+-- | A bloom filter is a probabilistic data structure that SPV clients send to
+-- other peers to filter the set of transactions received from them. Bloom
+-- filters can have false positives but not false negatives. Some transactions
+-- that pass the filter may not be relevant to the receiving peer. By
+-- controlling the false positive rate, SPV nodes can trade off bandwidth
+-- versus privacy.
+data BloomFilter = BloomFilter
+  { -- | bloom filter data
+    array :: !(S.Seq Word8),
+    -- | number of hash functions for this filter
+    functions :: !Word32,
+    -- | hash function random nonce
+    tweak :: !Word32,
+    -- | bloom filter auto-update flags
+    flags :: !BloomFlags
+  }
+  deriving (Eq, Show, Read, Generic, NFData)
+
+instance Serial BloomFilter where
+  deserialize =
+    BloomFilter
+      <$> (S.fromList <$> (readDat =<< deserialize))
+      <*> getWord32le
+      <*> getWord32le
+      <*> deserialize
+    where
+      readDat (VarInt len) = replicateM (fromIntegral len) getWord8
+
+  serialize BloomFilter {..} = do
+    putVarInt $ S.length array
+    mapM_ putWord8 (F.toList array)
+    putWord32le functions
+    putWord32le tweak
+    serialize flags
+
+instance Binary BloomFilter where
+  put = serialize
+  get = deserialize
+
+instance Serialize BloomFilter where
+  put = serialize
+  get = deserialize
+
+-- | Set a new bloom filter on the peer connection.
+newtype FilterLoad = FilterLoad {filter :: BloomFilter}
+  deriving (Eq, Show, Read, Generic)
+  deriving newtype (NFData)
+
+instance Serial FilterLoad where
+  deserialize = FilterLoad <$> deserialize
+  serialize (FilterLoad f) = serialize f
+
+instance Binary FilterLoad where
+  put = serialize
+  get = deserialize
+
+instance Serialize FilterLoad where
+  put = serialize
+  get = deserialize
+
+-- | Add the given data element to the connections current filter without
+-- requiring a completely new one to be set.
+newtype FilterAdd = FilterAdd {get :: ByteString}
+  deriving (Eq, Show, Read, Generic)
+  deriving newtype (NFData)
+
+instance Serial FilterAdd where
+  deserialize = do
+    (VarInt len) <- deserialize
+    dat <- getByteString $ fromIntegral len
+    return $ FilterAdd dat
+
+  serialize (FilterAdd bs) = do
+    putVarInt $ BS.length bs
+    putByteString bs
+
+instance Binary FilterAdd where
+  put = serialize
+  get = deserialize
+
+instance Serialize FilterAdd where
+  put = serialize
+  get = deserialize
+
+-- | Build a bloom filter that will provide the given false positive rate when
+-- the given number of elements have been inserted.
+bloomCreate ::
+  -- | number of elements
+  Int ->
+  -- | false positive rate
+  Double ->
+  -- | random nonce (tweak) for the hash function
+  Word32 ->
+  -- | bloom filter flags
+  BloomFlags ->
+  -- | bloom filter
+  BloomFilter
+bloomCreate numElem fpRate =
+  BloomFilter (S.replicate bloomSize 0) numHashF
+  where
+    -- Bloom filter size in bytes
+    bloomSize = truncate $ min a b / 8
+    -- Suggested size in bits
+    a = -1 / ln2Squared * fromIntegral numElem * log fpRate
+    -- Maximum size in bits
+    b = fromIntegral $ maxBloomSize * 8
+    numHashF = truncate $ min c (fromIntegral maxHashFuncs)
+    -- Suggested number of hash functions
+    c = fromIntegral bloomSize * 8 / fromIntegral numElem * ln2
+
+bloomHash :: BloomFilter -> Word32 -> ByteString -> Word32
+bloomHash b hashNum bs =
+  murmur3 seed bs `mod` (fromIntegral (S.length (b.array)) * 8)
+  where
+    seed = hashNum * 0xfba4c795 + b.tweak
+
+-- | Insert arbitrary data into a bloom filter. Returns the new bloom filter
+-- containing the new data.
+bloomInsert ::
+  -- | Original bloom filter
+  BloomFilter ->
+  -- | New data to insert
+  ByteString ->
+  -- | Bloom filter containing the new data
+  BloomFilter
+bloomInsert b bs
+  | isBloomFull b = b
+  | otherwise = b {array = dat}
+  where
+    idxs = map (\i -> bloomHash b i bs) [0 .. b.functions - 1]
+    upd s i =
+      S.adjust
+        (.|. bitMask !! fromIntegral (7 .&. i))
+        (fromIntegral $ i `shiftR` 3)
+        s
+    dat = foldl upd b.array idxs
+
+-- | Tests if some arbitrary data matches the filter. This can be either because
+-- the data was inserted into the filter or because it is a false positive.
+bloomContains ::
+  -- | Bloom filter
+  BloomFilter ->
+  -- | Data that will be checked against the given bloom filter
+  ByteString ->
+  -- | Returns True if the data matches the filter
+  Bool
+bloomContains b bs
+  | isBloomFull b = True
+  | isBloomEmpty b = False
+  | otherwise = all isSet idxs
+  where
+    s = b.array
+    idxs = map (\i -> bloomHash b i bs) [0 .. b.functions - 1]
+    isSet i =
+      S.index s (fromIntegral $ i `shiftR` 3)
+        .&. (bitMask !! fromIntegral (7 .&. i))
+        /= 0
+
+-- | Checks if any of the outputs of a tx is in the current bloom filter.
+-- If it is, add the txid and vout as an outpoint (i.e. so that
+-- a future tx that spends the output won't be missed).
+bloomRelevantUpdate ::
+  Ctx ->
+  -- | Bloom filter
+  BloomFilter ->
+  -- | Tx that may (or may not) have relevant outputs
+  Tx ->
+  -- | Returns an updated bloom filter adding relevant output
+  Maybe BloomFilter
+bloomRelevantUpdate ctx b tx
+  | isBloomFull b || isBloomEmpty b = Nothing
+  | b.flags == BloomUpdateNone = Nothing
+  | not (null matchOuts) = Just $ foldl' addRelevant b matchOuts
+  | otherwise = Nothing
+  where
+    -- TxHash if we end up inserting an outpoint
+
+    h = txHash tx
+    -- Decode the scriptOutpus and add vOuts in case we make them outpoints
+    decodedOutputScripts = traverse (unmarshal ctx . (.script)) tx.outputs
+    err = error "Error Decoding output script"
+    idxOutputScripts = either (const err) (zip [0 ..]) decodedOutputScripts
+    -- Check if any txOuts were contained in the bloom filter
+    matchFilter =
+      filter (\(_, op) -> any (bloomContains b) (encodeScriptOut op))
+    matchOuts = matchFilter idxOutputScripts
+    addRelevant :: BloomFilter -> (Word32, ScriptOutput) -> BloomFilter
+    addRelevant bf (id', scriptOut) =
+      case (b.flags, scriptType) of
+        -- We filtered out BloomUpdateNone so we insert any PayPk or PayMulSig
+
+        (_, True) -> bloomInsert bf outpoint
+        (BloomUpdateAll, _) -> bloomInsert bf outpoint
+        _ -> error "Error Updating Bloom Filter with relevant outpoint"
+      where
+        outpoint = runPutS $ serialize $ OutPoint {hash = h, index = id'}
+        scriptType = (\s -> isPayPK s || isPayMulSig s) scriptOut
+    -- Encodes a scriptOutput so it can be checked agains the Bloom Filter
+    encodeScriptOut :: ScriptOutput -> [ByteString]
+    encodeScriptOut (PayPK pk) =
+      return $ marshal ctx pk
+    encodeScriptOut (PayPKHash ph) =
+      return . runPutS $ serialize ph
+    encodeScriptOut (PayMulSig outputMuSig _) =
+      map (marshal ctx) outputMuSig
+    encodeScriptOut (PayScriptHash sh) =
+      return . runPutS $ serialize sh
+    encodeScriptOut (PayWitnessPKHash ph) =
+      return . runPutS $ serialize ph
+    encodeScriptOut (PayWitnessScriptHash sh) =
+      return . runPutS $ serialize sh
+    encodeScriptOut (PayWitness _ wd) =
+      return wd
+    encodeScriptOut (DataCarrier dat) =
+      return dat
+
+-- | Returns True if the filter is empty (all bytes set to 0x00)
+isBloomEmpty :: BloomFilter -> Bool
+isBloomEmpty b = all (== 0x00) $ F.toList b.array
+
+-- | Returns True if the filter is full (all bytes set to 0xff)
+isBloomFull :: BloomFilter -> Bool
+isBloomFull b = all (== 0xff) $ F.toList b.array
+
+-- | Tests if a given bloom filter is valid.
+isBloomValid ::
+  -- | Bloom filter to test
+  BloomFilter ->
+  -- | True if the given filter is valid
+  Bool
+isBloomValid BloomFilter {..} =
+  S.length array <= maxBloomSize && functions <= maxHashFuncs
+
+-- | Does the peer with these version services accept bloom filters?
+acceptsFilters :: Word64 -> Bool
+acceptsFilters srv = srv .&. (1 `shiftL` 2) /= 0
diff --git a/src/Haskoin/Network/Common.hs b/src/Haskoin/Network/Common.hs
new file mode 100644
--- /dev/null
+++ b/src/Haskoin/Network/Common.hs
@@ -0,0 +1,779 @@
+{-# LANGUAGE DeriveAnyClass #-}
+{-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE DerivingStrategies #-}
+{-# LANGUAGE DuplicateRecordFields #-}
+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
+{-# LANGUAGE ImportQualifiedPost #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE NoFieldSelectors #-}
+
+-- |
+-- Module      : Haskoin.Network.Common
+-- Copyright   : No rights reserved
+-- License     : MIT
+-- Maintainer  : jprupp@protonmail.ch
+-- Stability   : experimental
+-- Portability : POSIX
+--
+-- Common functions and data types related to peer-to-peer network.
+module Haskoin.Network.Common
+  ( -- * Network Data Types
+    Addr (..),
+    NetworkAddressTime,
+    Alert (..),
+    GetData (..),
+    Inv (..),
+    InvVector (..),
+    InvType (..),
+    HostAddress,
+    hostToSockAddr,
+    sockToHostAddress,
+    NetworkAddress (..),
+    NotFound (..),
+    Ping (..),
+    Pong (..),
+    Reject (..),
+    RejectCode (..),
+    VarInt (..),
+    VarString (..),
+    Version (..),
+    MessageCommand (..),
+    reject,
+    nodeNone,
+    nodeNetwork,
+    nodeGetUTXO,
+    nodeBloom,
+    nodeWitness,
+    nodeXThin,
+    commandToString,
+    stringToCommand,
+    putVarInt,
+  )
+where
+
+import Control.DeepSeq
+import Control.Monad (forM_, liftM2, replicateM, unless)
+import Data.Binary (Binary (..))
+import Data.Bits (shiftL)
+import Data.ByteString (ByteString)
+import Data.ByteString qualified as B
+import Data.ByteString.Char8 as C (replicate)
+import Data.Bytes.Get
+import Data.Bytes.Put
+import Data.Bytes.Serial
+import Data.Serialize (Serialize (..))
+import Data.String
+import Data.String.Conversions (cs)
+import Data.Word (Word32, Word64)
+import GHC.Generics (Generic)
+import Haskoin.Crypto.Hash
+import Network.Socket (SockAddr (..))
+import Text.Read as R
+
+-- | Network address with a timestamp.
+type NetworkAddressTime = (Word32, NetworkAddress)
+
+-- | Provides information about known nodes in the bitcoin network. An 'Addr'
+-- type is sent inside a 'Message' as a response to a 'GetAddr' message.
+newtype Addr = Addr
+  { -- List of addresses of other nodes on the network with timestamps.
+    list :: [NetworkAddressTime]
+  }
+  deriving (Eq, Show, Generic)
+  deriving newtype (NFData)
+
+instance Serial Addr where
+  deserialize = Addr <$> (repList =<< deserialize)
+    where
+      repList (VarInt c) = replicateM (fromIntegral c) action
+      action = liftM2 (,) getWord32le deserialize
+
+  serialize (Addr xs) = do
+    putVarInt $ length xs
+    forM_ xs $ \(a, b) -> putWord32le a >> serialize b
+
+instance Binary Addr where
+  get = deserialize
+  put = serialize
+
+instance Serialize Addr where
+  get = deserialize
+  put = serialize
+
+-- | Data type describing signed messages that can be sent between bitcoin
+-- nodes to display important notifications to end users about the health of
+-- the network.
+data Alert = Alert
+  { -- | Alert payload.
+    payload :: !VarString,
+    -- | ECDSA signature of the payload
+    signature :: !VarString
+  }
+  deriving (Eq, Show, Read, Generic, NFData)
+
+instance Serial Alert where
+  deserialize = Alert <$> deserialize <*> deserialize
+  serialize (Alert p s) = serialize p >> serialize s
+
+instance Binary Alert where
+  put = serialize
+  get = deserialize
+
+instance Serialize Alert where
+  put = serialize
+  get = deserialize
+
+-- | The 'GetData' type is used to retrieve information on a specific object
+-- ('Block' or 'Tx') identified by the objects hash. The payload of a 'GetData'
+-- request is a list of 'InvVector' which represent all the hashes of objects
+-- that a node wants. The response to a 'GetBlock' message will be either a
+-- 'Block' or a 'Tx' message depending on the type of the object referenced by
+-- the hash. Usually, 'GetData' messages are sent after a node receives an 'Inv'
+-- message that contains unknown object hashes.
+newtype GetData = GetData
+  { -- | list of object hashes
+    list :: [InvVector]
+  }
+  deriving (Eq, Show, Generic)
+  deriving newtype (NFData)
+
+instance Serial GetData where
+  deserialize = GetData <$> (repList =<< deserialize)
+    where
+      repList (VarInt c) = replicateM (fromIntegral c) deserialize
+
+  serialize (GetData xs) = do
+    putVarInt $ length xs
+    forM_ xs serialize
+
+instance Binary GetData where
+  get = deserialize
+  put = serialize
+
+instance Serialize GetData where
+  get = deserialize
+  put = serialize
+
+-- | 'Inv' messages are used by nodes to advertise their knowledge of new
+-- objects by publishing a list of hashes to a peer. 'Inv' messages can be sent
+-- unsolicited or in response to a 'GetBlocks' message.
+newtype Inv = Inv
+  { -- | inventory
+    list :: [InvVector]
+  }
+  deriving (Eq, Show, Generic)
+  deriving newtype (NFData)
+
+instance Serial Inv where
+  deserialize = Inv <$> (repList =<< deserialize)
+    where
+      repList (VarInt c) = replicateM (fromIntegral c) deserialize
+
+  serialize (Inv xs) = do
+    putVarInt $ length xs
+    forM_ xs serialize
+
+instance Binary Inv where
+  get = deserialize
+  put = serialize
+
+instance Serialize Inv where
+  get = deserialize
+  put = serialize
+
+-- | Data type identifying the type of an inventory vector. SegWit types are
+-- only used in 'GetData' messages, not 'Inv'.
+data InvType
+  = -- | error
+    InvError
+  | -- | transaction
+    InvTx
+  | -- | block
+    InvBlock
+  | -- | filtered block
+    InvMerkleBlock
+  | -- | segwit transaction
+    InvWitnessTx
+  | -- | segwit block
+    InvWitnessBlock
+  | -- | segwit filtered block
+    InvWitnessMerkleBlock
+  | -- | unknown inv type
+    InvType Word32
+  deriving (Eq, Show, Read, Generic, NFData)
+
+instance Serial InvType where
+  deserialize = go =<< getWord32le
+    where
+      go x =
+        case x of
+          0 -> return InvError
+          1 -> return InvTx
+          2 -> return InvBlock
+          3 -> return InvMerkleBlock
+          _
+            | x == 1 `shiftL` 30 + 1 -> return InvWitnessTx
+            | x == 1 `shiftL` 30 + 2 -> return InvWitnessBlock
+            | x == 1 `shiftL` 30 + 3 -> return InvWitnessMerkleBlock
+            | otherwise -> return (InvType x)
+  serialize x =
+    putWord32le $
+      case x of
+        InvError -> 0
+        InvTx -> 1
+        InvBlock -> 2
+        InvMerkleBlock -> 3
+        InvWitnessTx -> 1 `shiftL` 30 + 1
+        InvWitnessBlock -> 1 `shiftL` 30 + 2
+        InvWitnessMerkleBlock -> 1 `shiftL` 30 + 3
+        InvType w -> w
+
+instance Binary InvType where
+  get = deserialize
+  put = serialize
+
+instance Serialize InvType where
+  get = deserialize
+  put = serialize
+
+-- | Invectory vectors represent hashes identifying objects such as a 'Block' or
+-- a 'Tx'. They notify other peers about new data or data they have otherwise
+-- requested.
+data InvVector = InvVector
+  { -- | type of object
+    invType :: !InvType,
+    -- | 256-bit hash of object
+    invHash :: !Hash256
+  }
+  deriving (Eq, Show, Generic, NFData)
+
+instance Serial InvVector where
+  deserialize = InvVector <$> deserialize <*> deserialize
+  serialize (InvVector t h) = serialize t >> serialize h
+
+instance Binary InvVector where
+  get = deserialize
+  put = serialize
+
+instance Serialize InvVector where
+  get = deserialize
+  put = serialize
+
+newtype HostAddress
+  = HostAddress ByteString
+  deriving (Eq, Show, Ord, Generic)
+  deriving newtype (NFData)
+
+instance Serial HostAddress where
+  serialize (HostAddress bs) = putByteString bs
+  deserialize = HostAddress <$> getByteString 18
+
+instance Binary HostAddress where
+  get = deserialize
+  put = serialize
+
+instance Serialize HostAddress where
+  get = deserialize
+  put = serialize
+
+-- | Data type describing a bitcoin network address. Addresses are stored in
+-- IPv6 format. IPv4 addresses are mapped to IPv6 using IPv4 mapped IPv6
+-- addresses: <http://en.wikipedia.org/wiki/IPv6#IPv4-mapped_IPv6_addresses>.
+data NetworkAddress = NetworkAddress
+  { -- | bitmask of services available for this address
+    services :: !Word64,
+    -- | address and port information
+    address :: !HostAddress
+  }
+  deriving (Eq, Show, Generic, NFData)
+
+hostToSockAddr :: HostAddress -> SockAddr
+hostToSockAddr (HostAddress bs) =
+  case runGetS getSockAddr bs of
+    Left e -> error e
+    Right x -> x
+
+sockToHostAddress :: SockAddr -> HostAddress
+sockToHostAddress = HostAddress . runPutS . putSockAddr
+
+putSockAddr :: (MonadPut m) => SockAddr -> m ()
+putSockAddr (SockAddrInet6 p _ (a, b, c, d) _) = do
+  putWord32be a
+  putWord32be b
+  putWord32be c
+  putWord32be d
+  putWord16be (fromIntegral p)
+putSockAddr (SockAddrInet p a) = do
+  putWord32be 0x00000000
+  putWord32be 0x00000000
+  putWord32be 0x0000ffff
+  putWord32host a
+  putWord16be (fromIntegral p)
+putSockAddr _ = error "Invalid address type"
+
+getSockAddr :: (MonadGet m) => m SockAddr
+getSockAddr = do
+  a <- getWord32be
+  b <- getWord32be
+  c <- getWord32be
+  if a == 0x00000000 && b == 0x00000000 && c == 0x0000ffff
+    then do
+      d <- getWord32host
+      p <- getWord16be
+      return $ SockAddrInet (fromIntegral p) d
+    else do
+      d <- getWord32be
+      p <- getWord16be
+      return $ SockAddrInet6 (fromIntegral p) 0 (a, b, c, d) 0
+
+instance Serial NetworkAddress where
+  deserialize = NetworkAddress <$> getWord64le <*> deserialize
+  serialize (NetworkAddress s a) = putWord64le s >> serialize a
+
+instance Binary NetworkAddress where
+  get = deserialize
+  put = serialize
+
+instance Serialize NetworkAddress where
+  get = deserialize
+  put = serialize
+
+-- | A 'NotFound' message is returned as a response to a 'GetData' message
+-- whe one of the requested objects could not be retrieved. This could happen,
+-- for example, if a tranasaction was requested and was not available in the
+-- memory pool of the receiving node.
+newtype NotFound = NotFound
+  { -- | Inventory vectors related to this request
+    list :: [InvVector]
+  }
+  deriving (Eq, Show, Generic)
+  deriving newtype (NFData)
+
+instance Serial NotFound where
+  deserialize = NotFound <$> (repList =<< deserialize)
+    where
+      repList (VarInt c) = replicateM (fromIntegral c) deserialize
+
+  serialize (NotFound xs) = do
+    putVarInt $ length xs
+    forM_ xs serialize
+
+instance Binary NotFound where
+  get = deserialize
+  put = serialize
+
+instance Serialize NotFound where
+  get = deserialize
+  put = serialize
+
+-- | A 'Ping' message is sent to bitcoin peers to check if a connection is still
+-- open.
+newtype Ping = Ping
+  { -- | A random nonce used to identify the recipient of the ping
+    -- request once a Pong response is received.
+    nonce :: Word64
+  }
+  deriving (Eq, Show, Read, Generic)
+  deriving newtype (NFData)
+
+-- | A Pong message is sent as a response to a ping message.
+newtype Pong = Pong
+  { -- | nonce from corresponding 'Ping'
+    nonce :: Word64
+  }
+  deriving (Eq, Show, Read, Generic)
+  deriving newtype (NFData)
+
+instance Serial Ping where
+  deserialize = Ping <$> getWord64le
+  serialize (Ping n) = putWord64le n
+
+instance Serial Pong where
+  deserialize = Pong <$> getWord64le
+  serialize (Pong n) = putWord64le n
+
+instance Binary Ping where
+  get = deserialize
+  put = serialize
+
+instance Binary Pong where
+  get = deserialize
+  put = serialize
+
+instance Serialize Ping where
+  get = deserialize
+  put = serialize
+
+instance Serialize Pong where
+  get = deserialize
+  put = serialize
+
+-- | The 'Reject' message is sent when messages are rejected by a peer.
+data Reject = Reject
+  { -- | type of message rejected
+    message :: !MessageCommand,
+    -- | rejection code
+    code :: !RejectCode,
+    -- | text reason for rejection
+    reason :: !VarString,
+    -- | extra data such as block or tx hash
+    extra :: !ByteString
+  }
+  deriving (Eq, Show, Read, Generic, NFData)
+
+-- | Rejection code associated to the 'Reject' message.
+data RejectCode
+  = RejectMalformed
+  | RejectInvalid
+  | RejectObsolete
+  | RejectDuplicate
+  | RejectNonStandard
+  | RejectDust
+  | RejectInsufficientFee
+  | RejectCheckpoint
+  deriving (Eq, Show, Read, Generic, NFData)
+
+instance Serial RejectCode where
+  deserialize =
+    getWord8 >>= \code -> case code of
+      0x01 -> return RejectMalformed
+      0x10 -> return RejectInvalid
+      0x11 -> return RejectObsolete
+      0x12 -> return RejectDuplicate
+      0x40 -> return RejectNonStandard
+      0x41 -> return RejectDust
+      0x42 -> return RejectInsufficientFee
+      0x43 -> return RejectCheckpoint
+      _ ->
+        fail $
+          unwords
+            [ "Reject get: Invalid code",
+              show code
+            ]
+
+  serialize code = putWord8 $ case code of
+    RejectMalformed -> 0x01
+    RejectInvalid -> 0x10
+    RejectObsolete -> 0x11
+    RejectDuplicate -> 0x12
+    RejectNonStandard -> 0x40
+    RejectDust -> 0x41
+    RejectInsufficientFee -> 0x42
+    RejectCheckpoint -> 0x43
+
+instance Binary RejectCode where
+  put = serialize
+  get = deserialize
+
+instance Serialize RejectCode where
+  put = serialize
+  get = deserialize
+
+-- | Convenience function to build a 'Reject' message.
+reject :: MessageCommand -> RejectCode -> ByteString -> Reject
+reject cmd code reason =
+  Reject cmd code (VarString reason) B.empty
+
+instance Serial Reject where
+  deserialize =
+    deserialize >>= \(VarString bs) ->
+      Reject (stringToCommand bs)
+        <$> deserialize
+        <*> deserialize
+        <*> maybeData
+    where
+      maybeData =
+        isEmpty >>= \done ->
+          if done
+            then return B.empty
+            else getByteString 32
+  serialize (Reject cmd code reason dat) = do
+    serialize $ VarString $ commandToString cmd
+    serialize code
+    serialize reason
+    unless (B.null dat) $ putByteString dat
+
+instance Binary Reject where
+  put = serialize
+  get = deserialize
+
+instance Serialize Reject where
+  put = serialize
+  get = deserialize
+
+-- | Data type representing a variable-length integer. The 'VarInt' type
+-- usually precedes an array or a string that can vary in length.
+newtype VarInt = VarInt {get :: Word64}
+  deriving (Eq, Show, Read, Generic)
+  deriving newtype (NFData)
+
+instance Serial VarInt where
+  deserialize = VarInt <$> (getWord8 >>= go)
+    where
+      go 0xff = getWord64le
+      go 0xfe = fromIntegral <$> getWord32le
+      go 0xfd = fromIntegral <$> getWord16le
+      go x = return $ fromIntegral x
+
+  serialize (VarInt x)
+    | x < 0xfd =
+        putWord8 $ fromIntegral x
+    | x <= 0xffff = do
+        putWord8 0xfd
+        putWord16le $ fromIntegral x
+    | x <= 0xffffffff = do
+        putWord8 0xfe
+        putWord32le $ fromIntegral x
+    | otherwise = do
+        putWord8 0xff
+        putWord64le x
+
+instance Binary VarInt where
+  put = serialize
+  get = deserialize
+
+instance Serialize VarInt where
+  put = serialize
+  get = deserialize
+
+putVarInt :: (MonadPut m, Integral a) => a -> m ()
+putVarInt = serialize . VarInt . fromIntegral
+
+-- | Data type for serialization of variable-length strings.
+newtype VarString = VarString {get :: ByteString}
+  deriving (Eq, Show, Read, Generic)
+  deriving newtype (NFData)
+
+instance Serial VarString where
+  deserialize = VarString <$> (readBS =<< deserialize)
+    where
+      readBS (VarInt len) = getByteString (fromIntegral len)
+
+  serialize (VarString bs) = do
+    putVarInt $ B.length bs
+    putByteString bs
+
+instance Binary VarString where
+  put = serialize
+  get = deserialize
+
+instance Serialize VarString where
+  put = serialize
+  get = deserialize
+
+-- | When a bitcoin node creates an outgoing connection to another node,
+-- the first message it will send is a 'Version' message. The other node
+-- will similarly respond with it's own 'Version' message.
+data Version = Version
+  { -- | protocol version
+    version :: !Word32,
+    -- | features supported by this connection
+    services :: !Word64,
+    -- | unix timestamp
+    timestamp :: !Word64,
+    -- | network address of remote node
+    addrRecv :: !NetworkAddress,
+    -- | network address of sending node
+    addrSend :: !NetworkAddress,
+    -- | random nonce to detect connection to self
+    nonce :: !Word64,
+    -- | user agent string
+    userAgent :: !VarString,
+    -- | height of the last block in sending node
+    startHeight :: !Word32,
+    -- | relay transactions flag (BIP-37)
+    relay :: !Bool
+  }
+  deriving (Eq, Show, Generic, NFData)
+
+instance Serial Version where
+  deserialize =
+    Version
+      <$> getWord32le
+      <*> getWord64le
+      <*> getWord64le
+      <*> deserialize
+      <*> deserialize
+      <*> getWord64le
+      <*> deserialize
+      <*> getWord32le
+      <*> (go =<< isEmpty)
+    where
+      go True = return True
+      go False = getBool
+
+  serialize (Version v s t ar as n ua sh r) = do
+    putWord32le v
+    putWord64le s
+    putWord64le t
+    serialize ar
+    serialize as
+    putWord64le n
+    serialize ua
+    putWord32le sh
+    putBool r
+
+instance Binary Version where
+  put = serialize
+  get = deserialize
+
+instance Serialize Version where
+  put = serialize
+  get = deserialize
+
+-- | 0x00 is 'False', anything else is 'True'.
+getBool :: (MonadGet m) => m Bool
+getBool = go =<< getWord8
+  where
+    go 0 = return False
+    go _ = return True
+
+putBool :: (MonadPut m) => Bool -> m ()
+putBool True = putWord8 1
+putBool False = putWord8 0
+
+-- | A 'MessageCommand' is included in a 'MessageHeader' in order to identify
+-- the type of message present in the payload. This allows the message
+-- de-serialization code to know how to decode a particular message payload.
+-- Every valid 'Message' constructor has a corresponding 'MessageCommand'
+-- constructor.
+data MessageCommand
+  = MCVersion
+  | MCVerAck
+  | MCAddr
+  | MCInv
+  | MCGetData
+  | MCNotFound
+  | MCGetBlocks
+  | MCGetHeaders
+  | MCTx
+  | MCBlock
+  | MCMerkleBlock
+  | MCHeaders
+  | MCGetAddr
+  | MCFilterLoad
+  | MCFilterAdd
+  | MCFilterClear
+  | MCPing
+  | MCPong
+  | MCAlert
+  | MCMempool
+  | MCReject
+  | MCSendHeaders
+  | MCOther ByteString
+  deriving (Eq, Generic, NFData)
+
+instance Show MessageCommand where
+  showsPrec _ = shows . commandToString
+
+instance Read MessageCommand where
+  readPrec = do
+    String str <- lexP
+    return (stringToCommand (cs str))
+
+instance Serial MessageCommand where
+  deserialize = go <$> getByteString 12
+    where
+      go bs =
+        let str = unpackCommand bs
+         in stringToCommand str
+  serialize mc = putByteString $ packCommand $ commandToString mc
+
+instance Binary MessageCommand where
+  put = serialize
+  get = deserialize
+
+instance Serialize MessageCommand where
+  put = serialize
+  get = deserialize
+
+instance IsString MessageCommand where
+  fromString str = stringToCommand (cs str)
+
+-- | Read a 'MessageCommand' from its string representation.
+stringToCommand :: ByteString -> MessageCommand
+stringToCommand str = case str of
+  "version" -> MCVersion
+  "verack" -> MCVerAck
+  "addr" -> MCAddr
+  "inv" -> MCInv
+  "getdata" -> MCGetData
+  "notfound" -> MCNotFound
+  "getblocks" -> MCGetBlocks
+  "getheaders" -> MCGetHeaders
+  "tx" -> MCTx
+  "block" -> MCBlock
+  "merkleblock" -> MCMerkleBlock
+  "headers" -> MCHeaders
+  "getaddr" -> MCGetAddr
+  "filterload" -> MCFilterLoad
+  "filteradd" -> MCFilterAdd
+  "filterclear" -> MCFilterClear
+  "ping" -> MCPing
+  "pong" -> MCPong
+  "alert" -> MCAlert
+  "mempool" -> MCMempool
+  "reject" -> MCReject
+  "sendheaders" -> MCSendHeaders
+  _ -> MCOther str
+
+-- | Convert a 'MessageCommand' to its string representation.
+commandToString :: MessageCommand -> ByteString
+commandToString mc = case mc of
+  MCVersion -> "version"
+  MCVerAck -> "verack"
+  MCAddr -> "addr"
+  MCInv -> "inv"
+  MCGetData -> "getdata"
+  MCNotFound -> "notfound"
+  MCGetBlocks -> "getblocks"
+  MCGetHeaders -> "getheaders"
+  MCTx -> "tx"
+  MCBlock -> "block"
+  MCMerkleBlock -> "merkleblock"
+  MCHeaders -> "headers"
+  MCGetAddr -> "getaddr"
+  MCFilterLoad -> "filterload"
+  MCFilterAdd -> "filteradd"
+  MCFilterClear -> "filterclear"
+  MCPing -> "ping"
+  MCPong -> "pong"
+  MCAlert -> "alert"
+  MCMempool -> "mempool"
+  MCReject -> "reject"
+  MCSendHeaders -> "sendheaders"
+  MCOther c -> c
+
+-- | Pack a string 'MessageCommand' so that it is exactly 12-bytes long.
+packCommand :: ByteString -> ByteString
+packCommand s =
+  B.take 12 $
+    s `mappend` C.replicate 12 '\NUL'
+
+-- | Undo packing done by 'packCommand'.
+unpackCommand :: ByteString -> ByteString
+unpackCommand = B.takeWhile (/= 0)
+
+-- | Node offers no services.
+nodeNone :: Word64
+nodeNone = 0
+
+-- | Services indicate node is a full node that can serve full blocks.
+nodeNetwork :: Word64
+nodeNetwork = 1
+
+-- | Services indicate node allows to request 'UTXO' set.
+nodeGetUTXO :: Word64
+nodeGetUTXO = 1 `shiftL` 1
+
+-- | Services indicate node accepts bloom filters.
+nodeBloom :: Word64
+nodeBloom = 1 `shiftL` 2
+
+-- | Services indicate SegWit-capable node.
+nodeWitness :: Word64
+nodeWitness = 1 `shiftL` 3
+
+-- | Services indicate Xtreme Thinblocks compatibility.
+nodeXThin :: Word64
+nodeXThin = 1 `shiftL` 4
diff --git a/src/Haskoin/Network/Constants.hs b/src/Haskoin/Network/Constants.hs
new file mode 100644
--- /dev/null
+++ b/src/Haskoin/Network/Constants.hs
@@ -0,0 +1,582 @@
+{-# LANGUAGE CPP #-}
+{-# LANGUAGE DuplicateRecordFields #-}
+{-# LANGUAGE OverloadedRecordDot #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE NoFieldSelectors #-}
+
+-- |
+-- Module      : Haskoin.Constants
+-- Copyright   : No rights reserved
+-- License     : MIT
+-- Maintainer  : jprupp@protonmail.ch
+-- Stability   : experimental
+-- Portability : POSIX
+--
+-- Network constants for various networks, including Bitcoin SegWit (BTC), Bitcoin
+-- Cash (BCH), and corresponding public test and private regression test networks.
+module Haskoin.Network.Constants
+  ( -- * Constants
+    btc,
+    btcTest,
+    btcRegTest,
+    bch,
+    bchTest,
+    bchTest4,
+    bchRegTest,
+    allNets,
+    netByName,
+  )
+where
+
+import Control.DeepSeq
+import Data.Binary (Binary (..))
+import Data.ByteString (ByteString)
+import Data.Bytes.Get
+import Data.Bytes.Put
+import Data.Bytes.Serial
+import Data.List
+import Data.Maybe
+import Data.Serialize (Serialize (..))
+import Data.String
+import Data.Text (Text)
+import Data.Word (Word32, Word64, Word8)
+import GHC.Generics (Generic)
+import Haskoin.Block
+import Haskoin.Network.Common
+import Haskoin.Network.Data
+import Haskoin.Transaction
+import Text.Read
+
+-- | Version of Haskoin Core package.
+versionString :: (IsString a) => a
+
+#ifdef CURRENT_PACKAGE_VERSION
+versionString = CURRENT_PACKAGE_VERSION
+#else
+versionString = "Unavailable"
+#endif
+
+-- | Query known networks by name.
+netByName :: String -> Maybe Network
+netByName str = find ((== str) . (.name)) allNets
+
+-- | Bitcoin SegWit network. Symbol: BTC.
+btc :: Network
+btc =
+  Network
+    { name = "btc",
+      addrPrefix = 0,
+      scriptPrefix = 5,
+      secretPrefix = 128,
+      xPubPrefix = 0x0488b21e,
+      xPrvPrefix = 0x0488ade4,
+      magic = 0xf9beb4d9,
+      genesisHeader =
+        BlockHeader
+          0x01
+          "0000000000000000000000000000000000000000000000000000000000000000"
+          "3ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a"
+          1231006505
+          0x1d00ffff
+          2083236893,
+      -- Hash 000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f
+      maxBlockSize = 1000000,
+      maxSatoshi = 2100000000000000,
+      userAgent =
+        "/haskoin-btc:" <> versionString <> "/",
+      defaultPort = 8333,
+      minDiffBlocks = False,
+      powNoRetarget = False,
+      powLimit =
+        0x00000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffff,
+      bip34Block =
+        ( 227931,
+          "000000000000024b89b42a942fe0d9fea3bb44ab7bd1b19115dd6a759c0808b8"
+        ),
+      bip65Height = 388381,
+      bip66Height = 363725,
+      targetTimespan = 14 * 24 * 60 * 60,
+      targetSpacing = 10 * 60,
+      checkpoints =
+        [ ( 11111,
+            "0000000069e244f73d78e8fd29ba2fd2ed618bd6fa2ee92559f542fdb26e7c1d"
+          ),
+          ( 33333,
+            "000000002dd5588a74784eaa7ab0507a18ad16a236e7b1ce69f00d7ddfb5d0a6"
+          ),
+          ( 74000,
+            "0000000000573993a3c9e41ce34471c079dcf5f52a0e824a81e7f953b8661a20"
+          ),
+          ( 105000,
+            "00000000000291ce28027faea320c8d2b054b2e0fe44a773f3eefb151d6bdc97"
+          ),
+          ( 134444,
+            "00000000000005b12ffd4cd315cd34ffd4a594f430ac814c91184a0d42d2b0fe"
+          ),
+          ( 168000,
+            "000000000000099e61ea72015e79632f216fe6cb33d7899acb35b75c8303b763"
+          ),
+          ( 193000,
+            "000000000000059f452a5f7340de6682a977387c17010ff6e6c3bd83ca8b1317"
+          ),
+          ( 210000,
+            "000000000000048b95347e83192f69cf0366076336c639f9b7228e9ba171342e"
+          ),
+          ( 216116,
+            "00000000000001b4f4b433e81ee46494af945cf96014816a4e2370f11b23df4e"
+          ),
+          ( 225430,
+            "00000000000001c108384350f74090433e7fcf79a606b8e797f065b130575932"
+          ),
+          ( 250000,
+            "000000000000003887df1f29024b06fc2200b55f8af8f35453d7be294df2d214"
+          ),
+          ( 279000,
+            "0000000000000001ae8c72a0b0c301f67e3afca10e819efa9041e458e9bd7e40"
+          ),
+          ( 295000,
+            "00000000000000004d9b4ef50f0f9d686fd69db2e03af35a100370c64632a983"
+          )
+        ],
+      seeds =
+        [ "seed.bitcoin.sipa.be", -- Pieter Wuille
+          "dnsseed.bluematt.me", -- Matt Corallo
+          "dnsseed.bitcoin.dashjr.org", -- Luke Dashjr
+          "seed.bitcoinstats.com", -- Chris Decker
+          "seed.bitcoin.jonasschnelli.ch", -- Jonas Schnelli
+          "seed.btc.petertodd.org", -- Peter Todd
+          "seed.bitcoin.sprovoost.nl", -- Sjors Provoost
+          "dnsseed.emzy.de", -- Stephan Oeste
+          "seed.bitcoin.wiz.biz" -- Jason Maurice
+        ],
+      bip44Coin = 0,
+      sigHashForkId = Nothing,
+      edaHeight = Nothing,
+      daaHeight = Nothing,
+      asertActivationTime = Nothing,
+      asertHalfLife = 0,
+      segWit = True,
+      cashAddrPrefix = Nothing,
+      bech32Prefix = Just "bc",
+      replaceByFee = True,
+      halvingInterval = 210000
+    }
+
+-- | Testnet for Bitcoin SegWit network.
+btcTest :: Network
+btcTest =
+  Network
+    { name = "btctest",
+      addrPrefix = 111,
+      scriptPrefix = 196,
+      secretPrefix = 239,
+      xPubPrefix = 0x043587cf,
+      xPrvPrefix = 0x04358394,
+      magic = 0x0b110907,
+      genesisHeader =
+        BlockHeader
+          0x01
+          "0000000000000000000000000000000000000000000000000000000000000000"
+          "3ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a"
+          1296688602
+          486604799
+          414098458,
+      -- Hash 000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943
+      maxBlockSize = 1000000,
+      maxSatoshi = 2100000000000000,
+      userAgent = "/haskoin-btc-test:" <> versionString <> "/",
+      defaultPort = 18333,
+      minDiffBlocks = True,
+      powNoRetarget = False,
+      powLimit =
+        0x00000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffff,
+      bip34Block =
+        ( 21111,
+          "0000000023b3a96d3484e5abb3755c413e7d41500f8e2a5c3f0dd01299cd8ef8"
+        ),
+      bip65Height = 581885,
+      bip66Height = 330776,
+      targetTimespan = 14 * 24 * 60 * 60,
+      targetSpacing = 10 * 60,
+      checkpoints =
+        [ ( 546,
+            "000000002a936ca763904c3c35fce2f3556c559c0214345d31b1bcebf76acb70"
+          )
+        ],
+      seeds =
+        [ "testnet-seed.bitcoin.jonasschnelli.ch",
+          "seed.tbtc.petertodd.org",
+          "seed.testnet.bitcoin.sprovoost.nl",
+          "testnet-seed.bluematt.me"
+        ],
+      bip44Coin = 1,
+      sigHashForkId = Nothing,
+      edaHeight = Nothing,
+      daaHeight = Nothing,
+      asertActivationTime = Nothing,
+      asertHalfLife = 0,
+      segWit = True,
+      cashAddrPrefix = Nothing,
+      bech32Prefix = Just "tb",
+      replaceByFee = True,
+      halvingInterval = 210000
+    }
+
+-- | RegTest for Bitcoin SegWit network.
+btcRegTest :: Network
+btcRegTest =
+  Network
+    { name = "btcreg",
+      addrPrefix = 111,
+      scriptPrefix = 196,
+      secretPrefix = 239,
+      xPubPrefix = 0x043587cf,
+      xPrvPrefix = 0x04358394,
+      magic = 0xfabfb5da,
+      genesisHeader =
+        BlockHeader
+          -- 0f9188f13cb7b2c71f2a335e3a4fc328bf5beb436012afca590b1a11466e2206
+          0x01
+          "0000000000000000000000000000000000000000000000000000000000000000"
+          "3ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a"
+          1296688602
+          0x207fffff
+          2,
+      maxBlockSize = 1000000,
+      maxSatoshi = 2100000000000000,
+      userAgent = "/haskoin-btc-regtest:" <> versionString <> "/",
+      defaultPort = 18444,
+      minDiffBlocks = True,
+      powNoRetarget = True,
+      powLimit =
+        0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff,
+      bip34Block =
+        ( 100000000,
+          "0000000000000000000000000000000000000000000000000000000000000000"
+        ),
+      bip65Height = 1351,
+      bip66Height = 1251,
+      targetTimespan = 14 * 24 * 60 * 60,
+      targetSpacing = 10 * 60,
+      checkpoints = [],
+      seeds = ["localhost"],
+      bip44Coin = 1,
+      sigHashForkId = Nothing,
+      edaHeight = Nothing,
+      daaHeight = Nothing,
+      asertActivationTime = Nothing,
+      asertHalfLife = 0,
+      segWit = True,
+      cashAddrPrefix = Nothing,
+      bech32Prefix = Just "bcrt",
+      replaceByFee = True,
+      halvingInterval = 150
+    }
+
+-- | Bitcoin Cash network. Symbol: BCH.
+bch :: Network
+bch =
+  Network
+    { name = "bch",
+      addrPrefix = 0,
+      scriptPrefix = 5,
+      secretPrefix = 128,
+      xPubPrefix = 0x0488b21e,
+      xPrvPrefix = 0x0488ade4,
+      magic = 0xe3e1f3e8,
+      genesisHeader =
+        BlockHeader
+          0x01
+          "0000000000000000000000000000000000000000000000000000000000000000"
+          "3ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a"
+          1231006505
+          0x1d00ffff
+          2083236893,
+      -- Hash 000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f
+      maxBlockSize = 32000000,
+      maxSatoshi = 2100000000000000,
+      userAgent = "/haskoin-bch:" <> versionString <> "/",
+      defaultPort = 8333,
+      minDiffBlocks = False,
+      powNoRetarget = False,
+      powLimit =
+        0x00000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffff,
+      bip34Block =
+        ( 227931,
+          "000000000000024b89b42a942fe0d9fea3bb44ab7bd1b19115dd6a759c0808b8"
+        ),
+      bip65Height = 388381,
+      bip66Height = 363725,
+      targetTimespan = 14 * 24 * 60 * 60,
+      targetSpacing = 10 * 60,
+      checkpoints =
+        [ ( 11111,
+            "0000000069e244f73d78e8fd29ba2fd2ed618bd6fa2ee92559f542fdb26e7c1d"
+          ),
+          ( 33333,
+            "000000002dd5588a74784eaa7ab0507a18ad16a236e7b1ce69f00d7ddfb5d0a6"
+          ),
+          ( 74000,
+            "0000000000573993a3c9e41ce34471c079dcf5f52a0e824a81e7f953b8661a20"
+          ),
+          ( 105000,
+            "00000000000291ce28027faea320c8d2b054b2e0fe44a773f3eefb151d6bdc97"
+          ),
+          ( 134444,
+            "00000000000005b12ffd4cd315cd34ffd4a594f430ac814c91184a0d42d2b0fe"
+          ),
+          ( 168000,
+            "000000000000099e61ea72015e79632f216fe6cb33d7899acb35b75c8303b763"
+          ),
+          ( 193000,
+            "000000000000059f452a5f7340de6682a977387c17010ff6e6c3bd83ca8b1317"
+          ),
+          ( 210000,
+            "000000000000048b95347e83192f69cf0366076336c639f9b7228e9ba171342e"
+          ),
+          ( 216116,
+            "00000000000001b4f4b433e81ee46494af945cf96014816a4e2370f11b23df4e"
+          ),
+          ( 225430,
+            "00000000000001c108384350f74090433e7fcf79a606b8e797f065b130575932"
+          ),
+          ( 250000,
+            "000000000000003887df1f29024b06fc2200b55f8af8f35453d7be294df2d214"
+          ),
+          ( 279000,
+            "0000000000000001ae8c72a0b0c301f67e3afca10e819efa9041e458e9bd7e40"
+          ),
+          ( 295000,
+            "00000000000000004d9b4ef50f0f9d686fd69db2e03af35a100370c64632a983"
+          ),
+          -- UAHF fork block.
+
+          ( 478559,
+            "000000000000000000651ef99cb9fcbe0dadde1d424bd9f15ff20136191a5eec"
+          ),
+          -- Nov, 13 DAA activation block.
+
+          ( 504031,
+            "0000000000000000011ebf65b60d0a3de80b8175be709d653b4c1a1beeb6ab9c"
+          )
+        ],
+      seeds =
+        [ "seed.bitcoinabc.org",
+          "seed-bch.bitcoinforks.org",
+          "btccash-seeder.bitcoinunlimited.info",
+          "seed.bchd.cash",
+          "seed.bch.loping.net",
+          "dnsseed.electroncash.de"
+        ],
+      bip44Coin = 145,
+      sigHashForkId = Just 0,
+      edaHeight = Just 478559,
+      daaHeight = Just 404031,
+      asertActivationTime = Just 1605441600,
+      asertHalfLife = 60 * 60 * 10,
+      segWit = False,
+      cashAddrPrefix = Just "bitcoincash",
+      bech32Prefix = Nothing,
+      replaceByFee = False,
+      halvingInterval = 210000
+    }
+
+-- | Testnet for Bitcoin Cash network.
+bchTest4 :: Network
+bchTest4 =
+  Network
+    { name = "bchtest4",
+      addrPrefix = 111,
+      scriptPrefix = 196,
+      secretPrefix = 239,
+      xPubPrefix = 0x043587cf,
+      xPrvPrefix = 0x04358394,
+      magic = 0xe2b7daaf,
+      genesisHeader =
+        BlockHeader
+          0x01
+          "0000000000000000000000000000000000000000000000000000000000000000"
+          "3ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a"
+          1597811185
+          0x1d00ffff
+          114152193,
+      -- Hash 000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943
+      maxBlockSize = 2000000,
+      maxSatoshi = 2100000000000000,
+      userAgent = "/haskoin-bch-test4:" <> versionString <> "/",
+      defaultPort = 28333,
+      minDiffBlocks = True,
+      powNoRetarget = False,
+      powLimit =
+        0x00000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffff,
+      bip34Block =
+        ( 2,
+          "00000000b0c65b1e03baace7d5c093db0d6aac224df01484985ffd5e86a1a20c"
+        ),
+      bip65Height = 3,
+      bip66Height = 4,
+      targetTimespan = 14 * 24 * 60 * 60,
+      targetSpacing = 10 * 60,
+      checkpoints =
+        [ ( 5000,
+            "000000009f092d074574a216faec682040a853c4f079c33dfd2c3ef1fd8108c4"
+          ),
+          -- Axion activation
+
+          ( 16845,
+            "00000000fb325b8f34fe80c96a5f708a08699a68bbab82dba4474d86bd743077"
+          ),
+          ( 38000,
+            "000000000015197537e59f339e3b1bbf81a66f691bd3d7aa08560fc7bf5113fb"
+          ),
+          ( 54700,
+            "00000000009af4379d87f17d0f172ee4769b48839a5a3a3e81d69da4322518b8"
+          )
+        ],
+      seeds =
+        [ "testnet4-seed-bch.bitcoinforks.org",
+          "testnet4-seed-bch.toom.im",
+          "seed.tbch4.loping.net",
+          "testnet4-seed.flowee.cash"
+        ],
+      bip44Coin = 1,
+      sigHashForkId = Just 0,
+      edaHeight = Just 7,
+      daaHeight = Just 3000,
+      asertActivationTime = Just 1605441600,
+      asertHalfLife = 60 * 60,
+      segWit = False,
+      cashAddrPrefix = Just "bchtest",
+      bech32Prefix = Nothing,
+      replaceByFee = False,
+      halvingInterval = 210000
+    }
+
+-- | Testnet for Bitcoin Cash network.
+bchTest :: Network
+bchTest =
+  Network
+    { name = "bchtest",
+      addrPrefix = 111,
+      scriptPrefix = 196,
+      secretPrefix = 239,
+      xPubPrefix = 0x043587cf,
+      xPrvPrefix = 0x04358394,
+      magic = 0xf4e5f3f4,
+      genesisHeader =
+        BlockHeader
+          0x01
+          "0000000000000000000000000000000000000000000000000000000000000000"
+          "3ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a"
+          1296688602
+          486604799
+          414098458,
+      -- Hash 000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943
+      maxBlockSize = 32000000,
+      maxSatoshi = 2100000000000000,
+      userAgent = "/haskoin-bch-test:" <> versionString <> "/",
+      defaultPort = 18333,
+      minDiffBlocks = True,
+      powNoRetarget = False,
+      powLimit =
+        0x00000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffff,
+      bip34Block =
+        ( 21111,
+          "0000000023b3a96d3484e5abb3755c413e7d41500f8e2a5c3f0dd01299cd8ef8"
+        ),
+      bip65Height = 581885,
+      bip66Height = 330776,
+      targetTimespan = 14 * 24 * 60 * 60,
+      targetSpacing = 10 * 60,
+      checkpoints =
+        [ ( 546,
+            "000000002a936ca763904c3c35fce2f3556c559c0214345d31b1bcebf76acb70"
+          ),
+          -- UAHF fork block.
+
+          ( 1155876,
+            "00000000000e38fef93ed9582a7df43815d5c2ba9fd37ef70c9a0ea4a285b8f5"
+          ),
+          -- Nov, 13. DAA activation block.
+
+          ( 1188697,
+            "0000000000170ed0918077bde7b4d36cc4c91be69fa09211f748240dabe047fb"
+          )
+        ],
+      seeds =
+        [ "testnet-seed.bitcoinabc.org",
+          "testnet-seed-bch.bitcoinforks.org",
+          "testnet-seed.bchd.cash",
+          "seed.tbch.loping.net"
+        ],
+      bip44Coin = 1,
+      sigHashForkId = Just 0,
+      edaHeight = Just 1155876,
+      daaHeight = Just 1188697,
+      asertActivationTime = Just 1605441600,
+      asertHalfLife = 60 * 60,
+      segWit = False,
+      cashAddrPrefix = Just "bchtest",
+      bech32Prefix = Nothing,
+      replaceByFee = False,
+      halvingInterval = 210000
+    }
+
+-- | RegTest for Bitcoin Cash network.
+bchRegTest :: Network
+bchRegTest =
+  Network
+    { name = "bchreg",
+      addrPrefix = 111,
+      scriptPrefix = 196,
+      secretPrefix = 239,
+      xPubPrefix = 0x043587cf,
+      xPrvPrefix = 0x04358394,
+      magic = 0xdab5bffa,
+      genesisHeader =
+        BlockHeader
+          -- 0f9188f13cb7b2c71f2a335e3a4fc328bf5beb436012afca590b1a11466e2206
+          0x01
+          "0000000000000000000000000000000000000000000000000000000000000000"
+          "3ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a"
+          1296688602
+          0x207fffff
+          2,
+      maxBlockSize = 1000000,
+      maxSatoshi = 2100000000000000,
+      userAgent = "/haskoin-bch-regtest:" <> versionString <> "/",
+      defaultPort = 18444,
+      minDiffBlocks = True,
+      powNoRetarget = True,
+      powLimit =
+        0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff,
+      bip34Block =
+        ( 100000000,
+          "0000000000000000000000000000000000000000000000000000000000000000"
+        ),
+      bip65Height = 1351,
+      bip66Height = 1251,
+      targetTimespan = 14 * 24 * 60 * 60,
+      targetSpacing = 10 * 60,
+      checkpoints =
+        [ ( 0,
+            "0f9188f13cb7b2c71f2a335e3a4fc328bf5beb436012afca590b1a11466e2206"
+          )
+        ],
+      seeds = ["localhost"],
+      bip44Coin = 1,
+      sigHashForkId = Just 0,
+      edaHeight = Nothing,
+      daaHeight = Just 0,
+      asertActivationTime = Just 1605441600,
+      asertHalfLife = 2 * 24 * 60 * 60,
+      segWit = False,
+      cashAddrPrefix = Just "bchreg",
+      bech32Prefix = Nothing,
+      replaceByFee = False,
+      halvingInterval = 150
+    }
+
+-- | List of all networks supported by this library.
+allNets :: [Network]
+allNets = [btc, bch, btcTest, bchTest4, bchTest, btcRegTest, bchRegTest]
diff --git a/src/Haskoin/Network/Data.hs b/src/Haskoin/Network/Data.hs
new file mode 100644
--- /dev/null
+++ b/src/Haskoin/Network/Data.hs
@@ -0,0 +1,89 @@
+{-# LANGUAGE DeriveAnyClass #-}
+{-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE NoFieldSelectors #-}
+
+module Haskoin.Network.Data
+  ( Network (..),
+  )
+where
+
+import Control.DeepSeq
+import Data.Binary (Binary (..))
+import Data.ByteString (ByteString)
+import Data.Serialize (Serialize (..))
+import Data.Text (Text)
+import Data.Word (Word32, Word64, Word8)
+import GHC.Generics (Generic)
+import Haskoin.Block.Common
+
+-- | Network definition.
+data Network = Network
+  { -- | lowercase alphanumeric and dashes
+    name :: !String,
+    -- | prefix for 'Base58' P2PKH addresses
+    addrPrefix :: !Word8,
+    -- | prefix for 'Base58' P2SH addresses
+    scriptPrefix :: !Word8,
+    -- | prefix for WIF private key
+    secretPrefix :: !Word8,
+    -- | prefix for extended public key
+    xPubPrefix :: !Word32,
+    -- | prefix for extended private key
+    xPrvPrefix :: !Word32,
+    -- | network magic
+    magic :: !Word32,
+    -- | genesis block header
+    genesisHeader :: !BlockHeader,
+    -- | maximum block size in bytes
+    maxBlockSize :: !Int,
+    -- | maximum amount of satoshi
+    maxSatoshi :: !Word64,
+    -- | user agent string
+    userAgent :: !ByteString,
+    -- | default port for P2P connections
+    defaultPort :: !Int,
+    -- | allow min difficulty blocks (testnet)
+    minDiffBlocks :: !Bool,
+    -- | do not retarget difficulty (regtest)
+    powNoRetarget :: !Bool,
+    -- | proof-of-work target higest possible value
+    powLimit :: !Integer,
+    -- | block at which BIP34 activates
+    bip34Block :: !(BlockHeight, BlockHash),
+    -- | block at which BIP65 activates
+    bip65Height :: !BlockHeight,
+    -- | block at which BIP66 activates
+    bip66Height :: !BlockHeight,
+    -- | time between difficulty retargets
+    targetTimespan :: !Word32,
+    -- | time between blocks
+    targetSpacing :: !Word32,
+    -- | checkpoints
+    checkpoints :: ![(BlockHeight, BlockHash)],
+    -- | BIP44 derivation path root
+    bip44Coin :: !Word32,
+    -- | peer-to-peer network seeds
+    seeds :: ![String],
+    -- | fork id for replay protection
+    sigHashForkId :: !(Maybe Word32),
+    -- | EDA start block height
+    edaHeight :: !(Maybe Word32),
+    -- | DAA start block height
+    daaHeight :: !(Maybe Word32),
+    -- | asert3-2d algorithm activation time
+    -- TODO: Replace with block height after fork
+    asertActivationTime :: !(Maybe Word32),
+    -- | asert3-2d algorithm halflife (not used for non-BCH networks)
+    asertHalfLife :: !Integer,
+    -- | segregated witness active
+    segWit :: !Bool,
+    -- | 'CashAddr' prefix (for Bitcoin Cash)
+    cashAddrPrefix :: !(Maybe Text),
+    -- | 'Bech32' prefix (for SegWit network)
+    bech32Prefix :: !(Maybe Text),
+    -- | Replace-By-Fee (BIP-125)
+    replaceByFee :: !Bool,
+    -- | Subsidy halving interval
+    halvingInterval :: !Word32
+  }
+  deriving (Eq, Show, Read, Generic, NFData)
diff --git a/src/Haskoin/Network/Message.hs b/src/Haskoin/Network/Message.hs
new file mode 100644
--- /dev/null
+++ b/src/Haskoin/Network/Message.hs
@@ -0,0 +1,224 @@
+{-# LANGUAGE DeriveAnyClass #-}
+{-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE DuplicateRecordFields #-}
+{-# LANGUAGE ImportQualifiedPost #-}
+{-# LANGUAGE OverloadedRecordDot #-}
+{-# LANGUAGE NoFieldSelectors #-}
+
+-- |
+-- Module      : Haskoin.Network.Message
+-- Copyright   : No rights reserved
+-- License     : MIT
+-- Maintainer  : jprupp@protonmail.ch
+-- Stability   : experimental
+-- Portability : POSIX
+--
+-- Peer-to-peer network message serialization.
+module Haskoin.Network.Message
+  ( -- * Network Message
+    Message (..),
+    MessageHeader (..),
+    msgType,
+    putMessage,
+    getMessage,
+  )
+where
+
+import Control.DeepSeq
+import Control.Monad (unless)
+import Data.Binary (Binary (..))
+import Data.ByteString (ByteString)
+import Data.ByteString qualified as B
+import Data.Bytes.Get
+import Data.Bytes.Put
+import Data.Bytes.Serial
+import Data.Serialize (Serialize (..))
+import Data.Word (Word32)
+import GHC.Generics (Generic)
+import Haskoin.Block.Common
+import Haskoin.Block.Merkle
+import Haskoin.Crypto.Hash
+import Haskoin.Network.Bloom
+import Haskoin.Network.Common
+import Haskoin.Network.Data
+import Haskoin.Transaction.Common
+
+-- | Data type representing the header of a 'Message'. All messages sent between
+-- nodes contain a message header.
+data MessageHeader = MessageHeader
+  { -- | magic bytes identify network
+    magic :: !Word32,
+    -- | message type
+    cmd :: !MessageCommand,
+    -- | length of payload
+    size :: !Word32,
+    -- | checksum of payload
+    checksum :: !CheckSum32
+  }
+  deriving (Eq, Show, Generic, NFData)
+
+instance Serial MessageHeader where
+  deserialize =
+    MessageHeader
+      <$> getWord32be
+      <*> deserialize
+      <*> getWord32le
+      <*> deserialize
+
+  serialize (MessageHeader m c l chk) = do
+    putWord32be m
+    serialize c
+    putWord32le l
+    serialize chk
+
+instance Binary MessageHeader where
+  put = serialize
+  get = deserialize
+
+instance Serialize MessageHeader where
+  put = serialize
+  get = deserialize
+
+-- | The 'Message' type is used to identify all the valid messages that can be
+-- sent between bitcoin peers. Only values of type 'Message' will be accepted
+-- by other bitcoin peers as bitcoin protocol messages need to be correctly
+-- serialized with message headers. Serializing a 'Message' value will
+-- include the 'MessageHeader' with the correct checksum value automatically.
+-- No need to add the 'MessageHeader' separately.
+data Message
+  = MVersion !Version
+  | MVerAck
+  | MAddr !Addr
+  | MInv !Inv
+  | MGetData !GetData
+  | MNotFound !NotFound
+  | MGetBlocks !GetBlocks
+  | MGetHeaders !GetHeaders
+  | MTx !Tx
+  | MBlock !Block
+  | MMerkleBlock !MerkleBlock
+  | MHeaders !Headers
+  | MGetAddr
+  | MFilterLoad !FilterLoad
+  | MFilterAdd !FilterAdd
+  | MFilterClear
+  | MPing !Ping
+  | MPong !Pong
+  | MAlert !Alert
+  | MMempool
+  | MReject !Reject
+  | MSendHeaders
+  | MOther !ByteString !ByteString
+  deriving (Eq, Show, Generic, NFData)
+
+-- | Get 'MessageCommand' assocated with a message.
+msgType :: Message -> MessageCommand
+msgType (MVersion _) = MCVersion
+msgType MVerAck = MCVerAck
+msgType (MAddr _) = MCAddr
+msgType (MInv _) = MCInv
+msgType (MGetData _) = MCGetData
+msgType (MNotFound _) = MCNotFound
+msgType (MGetBlocks _) = MCGetBlocks
+msgType (MGetHeaders _) = MCGetHeaders
+msgType (MTx _) = MCTx
+msgType (MBlock _) = MCBlock
+msgType (MMerkleBlock _) = MCMerkleBlock
+msgType (MHeaders _) = MCHeaders
+msgType (MFilterLoad _) = MCFilterLoad
+msgType (MFilterAdd _) = MCFilterAdd
+msgType MFilterClear = MCFilterClear
+msgType (MPing _) = MCPing
+msgType (MPong _) = MCPong
+msgType (MAlert _) = MCAlert
+msgType MMempool = MCMempool
+msgType (MReject _) = MCReject
+msgType MSendHeaders = MCSendHeaders
+msgType MGetAddr = MCGetAddr
+msgType (MOther c _) = MCOther c
+
+-- | Deserializer for network messages.
+getMessage :: (MonadGet m) => Network -> m Message
+getMessage net = do
+  (MessageHeader mgc cmd len chk) <- deserialize
+  bs <- lookAhead $ getByteString $ fromIntegral len
+  unless
+    (mgc == net.magic)
+    (fail $ "get: Invalid network magic bytes: " ++ show mgc)
+  unless
+    (checkSum32 bs == chk)
+    (fail $ "get: Invalid message checksum: " ++ show chk)
+  if len > 0
+    then do
+      bs <- ensure (fromIntegral len)
+      let f = case cmd of
+            MCVersion -> MVersion <$> deserialize
+            MCAddr -> MAddr <$> deserialize
+            MCInv -> MInv <$> deserialize
+            MCGetData -> MGetData <$> deserialize
+            MCNotFound -> MNotFound <$> deserialize
+            MCGetBlocks -> MGetBlocks <$> deserialize
+            MCGetHeaders -> MGetHeaders <$> deserialize
+            MCTx -> MTx <$> deserialize
+            MCBlock -> MBlock <$> deserialize
+            MCMerkleBlock -> MMerkleBlock <$> deserialize
+            MCHeaders -> MHeaders <$> deserialize
+            MCFilterLoad -> MFilterLoad <$> deserialize
+            MCFilterAdd -> MFilterAdd <$> deserialize
+            MCPing -> MPing <$> deserialize
+            MCPong -> MPong <$> deserialize
+            MCAlert -> MAlert <$> deserialize
+            MCReject -> MReject <$> deserialize
+            MCOther c -> MOther c <$> getByteString (fromIntegral len)
+            _ ->
+              fail $
+                "get: command "
+                  ++ show cmd
+                  ++ " should not carry a payload"
+      either fail return (runGetS f bs)
+    else case cmd of
+      MCGetAddr -> return MGetAddr
+      MCVerAck -> return MVerAck
+      MCFilterClear -> return MFilterClear
+      MCMempool -> return MMempool
+      MCSendHeaders -> return MSendHeaders
+      MCOther c -> return (MOther c B.empty)
+      _ ->
+        fail $
+          "get: command "
+            ++ show cmd
+            ++ " is expected to carry a payload"
+
+-- | Serializer for network messages.
+putMessage :: (MonadPut m) => Network -> Message -> m ()
+putMessage net msg = do
+  let (cmd, payload) =
+        case msg of
+          MVersion m -> (MCVersion, runPutS $ serialize m)
+          MVerAck -> (MCVerAck, B.empty)
+          MAddr m -> (MCAddr, runPutS $ serialize m)
+          MInv m -> (MCInv, runPutS $ serialize m)
+          MGetData m -> (MCGetData, runPutS $ serialize m)
+          MNotFound m -> (MCNotFound, runPutS $ serialize m)
+          MGetBlocks m -> (MCGetBlocks, runPutS $ serialize m)
+          MGetHeaders m -> (MCGetHeaders, runPutS $ serialize m)
+          MTx m -> (MCTx, runPutS $ serialize m)
+          MBlock m -> (MCBlock, runPutS $ serialize m)
+          MMerkleBlock m -> (MCMerkleBlock, runPutS $ serialize m)
+          MHeaders m -> (MCHeaders, runPutS $ serialize m)
+          MGetAddr -> (MCGetAddr, B.empty)
+          MFilterLoad m -> (MCFilterLoad, runPutS $ serialize m)
+          MFilterAdd m -> (MCFilterAdd, runPutS $ serialize m)
+          MFilterClear -> (MCFilterClear, B.empty)
+          MPing m -> (MCPing, runPutS $ serialize m)
+          MPong m -> (MCPong, runPutS $ serialize m)
+          MAlert m -> (MCAlert, runPutS $ serialize m)
+          MMempool -> (MCMempool, B.empty)
+          MReject m -> (MCReject, runPutS $ serialize m)
+          MSendHeaders -> (MCSendHeaders, B.empty)
+          MOther c p -> (MCOther c, p)
+      chk = checkSum32 payload
+      len = fromIntegral $ B.length payload
+      header = MessageHeader net.magic cmd len chk
+  serialize header
+  putByteString payload
diff --git a/src/Haskoin/Script.hs b/src/Haskoin/Script.hs
new file mode 100644
--- /dev/null
+++ b/src/Haskoin/Script.hs
@@ -0,0 +1,21 @@
+-- |
+-- Module      : Haskoin.Script
+-- Copyright   : No rights reserved
+-- License     : MIT
+-- Maintainer  : jprupp@protonmail.ch
+-- Stability   : experimental
+-- Portability : POSIX
+--
+-- This module provides functions for parsing and evaluating bitcoin
+-- transaction scripts. Data types are provided for building and
+-- deconstructing all of the standard input and output script types.
+module Haskoin.Script
+  ( module Common,
+    module Standard,
+    module SigHash,
+  )
+where
+
+import Haskoin.Script.Common as Common
+import Haskoin.Script.SigHash as SigHash
+import Haskoin.Script.Standard as Standard
diff --git a/src/Haskoin/Script/Common.hs b/src/Haskoin/Script/Common.hs
new file mode 100644
--- /dev/null
+++ b/src/Haskoin/Script/Common.hs
@@ -0,0 +1,598 @@
+{-# LANGUAGE DeriveAnyClass #-}
+{-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE DerivingStrategies #-}
+{-# LANGUAGE DuplicateRecordFields #-}
+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE NoFieldSelectors #-}
+
+-- |
+-- Module      : Haskoin.Script.Common
+-- Copyright   : No rights reserved
+-- License     : MIT
+-- Maintainer  : jprupp@protonmail.ch
+-- Stability   : experimental
+-- Portability : POSIX
+--
+-- Common script-related functions and data types.
+module Haskoin.Script.Common
+  ( -- * Scripts
+    ScriptOp (..),
+    Script (..),
+    PushDataType (..),
+    isPushOp,
+    opPushData,
+    intToScriptOp,
+    scriptOpToInt,
+  )
+where
+
+import Control.DeepSeq
+import Control.Monad
+import Data.Aeson
+import Data.Aeson.Encoding
+import Data.Binary (Binary (..))
+import Data.ByteString (ByteString)
+import qualified Data.ByteString as B
+import Data.ByteString.Builder (char7)
+import Data.Bytes.Get
+import Data.Bytes.Put
+import Data.Bytes.Serial
+import Data.Either (fromRight)
+import Data.Hashable
+import Data.Serialize (Serialize (..))
+import Data.Word (Word8)
+import GHC.Generics (Generic)
+import Haskoin.Util (hexBuilder)
+import Haskoin.Util.Helpers (decodeHex, encodeHex, hexEncoding)
+
+-- | Data type representing a transaction script. Scripts are defined as lists
+-- of script operators 'ScriptOp'. Scripts are used to:
+--
+-- * Define the spending conditions in the output of a transaction.
+-- * Provide signatures in the input of a transaction (except SegWit).
+--
+-- SigWit only: the segregated witness data structure, and not the input script,
+-- contains signatures and redeem script for pay-to-witness-script and
+-- pay-to-witness-public-key-hash transactions.
+newtype Script = Script
+  { -- | script operators defining this script
+    ops :: [ScriptOp]
+  }
+  deriving (Eq, Show, Read, Generic)
+  deriving newtype (Hashable, NFData)
+
+instance FromJSON Script where
+  parseJSON = withText "script" $ \t -> do
+    bs <- maybe mzero return (decodeHex t)
+    either fail return (runGetS deserialize bs)
+
+instance ToJSON Script where
+  toJSON = String . encodeHex . runPutS . serialize
+  toEncoding = hexEncoding . runPutL . serialize
+
+instance Serial Script where
+  deserialize =
+    Script <$> getScriptOps
+    where
+      getScriptOps = do
+        empty <- isEmpty
+        if empty
+          then return []
+          else (:) <$> deserialize <*> getScriptOps
+
+  serialize (Script ops) = forM_ ops serialize
+
+instance Binary Script where
+  put = serialize
+  get = deserialize
+
+instance Serialize Script where
+  put = serialize
+  get = deserialize
+
+-- | Data type representing the type of an OP_PUSHDATA opcode.
+data PushDataType
+  = -- | next opcode bytes is data to be pushed
+    OPCODE
+  | -- | next byte contains number of bytes of data to be pushed
+    OPDATA1
+  | -- | next two bytes contains number of bytes to be pushed
+    OPDATA2
+  | -- | next four bytes contains the number of bytes to be pushed
+    OPDATA4
+  deriving (Show, Read, Eq, Generic, Hashable, NFData)
+
+-- | Data type representing an operator allowed inside a 'Script'.
+data ScriptOp
+  = -- Pushing Data
+    OP_PUSHDATA
+      !ByteString
+      !PushDataType
+  | OP_0
+  | OP_1NEGATE
+  | OP_RESERVED
+  | OP_1
+  | OP_2
+  | OP_3
+  | OP_4
+  | OP_5
+  | OP_6
+  | OP_7
+  | OP_8
+  | OP_9
+  | OP_10
+  | OP_11
+  | OP_12
+  | OP_13
+  | OP_14
+  | OP_15
+  | OP_16
+  | -- Flow control
+    OP_NOP
+  | OP_VER -- reserved
+  | OP_IF
+  | OP_NOTIF
+  | OP_VERIF -- resreved
+  | OP_VERNOTIF -- reserved
+  | OP_ELSE
+  | OP_ENDIF
+  | OP_VERIFY
+  | OP_RETURN
+  | -- Stack operations
+    OP_TOALTSTACK
+  | OP_FROMALTSTACK
+  | OP_IFDUP
+  | OP_DEPTH
+  | OP_DROP
+  | OP_DUP
+  | OP_NIP
+  | OP_OVER
+  | OP_PICK
+  | OP_ROLL
+  | OP_ROT
+  | OP_SWAP
+  | OP_TUCK
+  | OP_2DROP
+  | OP_2DUP
+  | OP_3DUP
+  | OP_2OVER
+  | OP_2ROT
+  | OP_2SWAP
+  | -- Splice
+    OP_CAT
+  | OP_SUBSTR
+  | OP_LEFT
+  | OP_RIGHT
+  | OP_SIZE
+  | -- Bitwise logic
+    OP_INVERT
+  | OP_AND
+  | OP_OR
+  | OP_XOR
+  | OP_EQUAL
+  | OP_EQUALVERIFY
+  | OP_RESERVED1
+  | OP_RESERVED2
+  | -- Arithmetic
+    OP_1ADD
+  | OP_1SUB
+  | OP_2MUL
+  | OP_2DIV
+  | OP_NEGATE
+  | OP_ABS
+  | OP_NOT
+  | OP_0NOTEQUAL
+  | OP_ADD
+  | OP_SUB
+  | OP_MUL
+  | OP_DIV
+  | OP_MOD
+  | OP_LSHIFT
+  | OP_RSHIFT
+  | OP_BOOLAND
+  | OP_BOOLOR
+  | OP_NUMEQUAL
+  | OP_NUMEQUALVERIFY
+  | OP_NUMNOTEQUAL
+  | OP_LESSTHAN
+  | OP_GREATERTHAN
+  | OP_LESSTHANOREQUAL
+  | OP_GREATERTHANOREQUAL
+  | OP_MIN
+  | OP_MAX
+  | OP_WITHIN
+  | -- Crypto
+    OP_RIPEMD160
+  | OP_SHA1
+  | OP_SHA256
+  | OP_HASH160
+  | OP_HASH256
+  | OP_CODESEPARATOR
+  | OP_CHECKSIG
+  | OP_CHECKSIGVERIFY
+  | OP_CHECKMULTISIG
+  | OP_CHECKMULTISIGVERIFY
+  | -- Expansion
+    OP_NOP1
+  | OP_CHECKLOCKTIMEVERIFY
+  | OP_CHECKSEQUENCEVERIFY
+  | OP_NOP4
+  | OP_NOP5
+  | OP_NOP6
+  | OP_NOP7
+  | OP_NOP8
+  | OP_NOP9
+  | OP_NOP10
+  | -- Bitcoin Cash Nov 2018 hard fork
+    OP_CHECKDATASIG
+  | OP_CHECKDATASIGVERIFY
+  | -- Bitcoin Cash May 2020 hard fork
+    OP_REVERSEBYTES
+  | -- Other
+    OP_PUBKEYHASH
+  | OP_PUBKEY
+  | OP_INVALIDOPCODE !Word8
+  deriving (Show, Read, Eq, Generic, Hashable, NFData)
+
+instance Serial ScriptOp where
+  deserialize = go . fromIntegral =<< getWord8
+    where
+      go op
+        | op == 0x00 = return OP_0
+        | op <= 0x4b = do
+            payload <- getByteString (fromIntegral op)
+            return $ OP_PUSHDATA payload OPCODE
+        | op == 0x4c = do
+            len <- getWord8
+            payload <- getByteString (fromIntegral len)
+            return $ OP_PUSHDATA payload OPDATA1
+        | op == 0x4d = do
+            len <- getWord16le
+            payload <- getByteString (fromIntegral len)
+            return $ OP_PUSHDATA payload OPDATA2
+        | op == 0x4e = do
+            len <- getWord32le
+            payload <- getByteString (fromIntegral len)
+            return $ OP_PUSHDATA payload OPDATA4
+        | op == 0x4f = return OP_1NEGATE
+        | op == 0x50 = return OP_RESERVED
+        | op == 0x51 = return OP_1
+        | op == 0x52 = return OP_2
+        | op == 0x53 = return OP_3
+        | op == 0x54 = return OP_4
+        | op == 0x55 = return OP_5
+        | op == 0x56 = return OP_6
+        | op == 0x57 = return OP_7
+        | op == 0x58 = return OP_8
+        | op == 0x59 = return OP_9
+        | op == 0x5a = return OP_10
+        | op == 0x5b = return OP_11
+        | op == 0x5c = return OP_12
+        | op == 0x5d = return OP_13
+        | op == 0x5e = return OP_14
+        | op == 0x5f = return OP_15
+        | op == 0x60 = return OP_16
+        -- Flow control
+        | op == 0x61 = return OP_NOP
+        | op == 0x62 = return OP_VER -- reserved
+        | op == 0x63 = return OP_IF
+        | op == 0x64 = return OP_NOTIF
+        | op == 0x65 = return OP_VERIF -- reserved
+        | op == 0x66 = return OP_VERNOTIF -- reserved
+        | op == 0x67 = return OP_ELSE
+        | op == 0x68 = return OP_ENDIF
+        | op == 0x69 = return OP_VERIFY
+        | op == 0x6a = return OP_RETURN
+        -- Stack
+        | op == 0x6b = return OP_TOALTSTACK
+        | op == 0x6c = return OP_FROMALTSTACK
+        | op == 0x6d = return OP_2DROP
+        | op == 0x6e = return OP_2DUP
+        | op == 0x6f = return OP_3DUP
+        | op == 0x70 = return OP_2OVER
+        | op == 0x71 = return OP_2ROT
+        | op == 0x72 = return OP_2SWAP
+        | op == 0x73 = return OP_IFDUP
+        | op == 0x74 = return OP_DEPTH
+        | op == 0x75 = return OP_DROP
+        | op == 0x76 = return OP_DUP
+        | op == 0x77 = return OP_NIP
+        | op == 0x78 = return OP_OVER
+        | op == 0x79 = return OP_PICK
+        | op == 0x7a = return OP_ROLL
+        | op == 0x7b = return OP_ROT
+        | op == 0x7c = return OP_SWAP
+        | op == 0x7d = return OP_TUCK
+        -- Splice
+        | op == 0x7e = return OP_CAT
+        | op == 0x7f = return OP_SUBSTR
+        | op == 0x80 = return OP_LEFT
+        | op == 0x81 = return OP_RIGHT
+        | op == 0x82 = return OP_SIZE
+        -- Bitwise logic
+        | op == 0x83 = return OP_INVERT
+        | op == 0x84 = return OP_AND
+        | op == 0x85 = return OP_OR
+        | op == 0x86 = return OP_XOR
+        | op == 0x87 = return OP_EQUAL
+        | op == 0x88 = return OP_EQUALVERIFY
+        | op == 0x89 = return OP_RESERVED1
+        | op == 0x8a = return OP_RESERVED2
+        -- Arithmetic
+        | op == 0x8b = return OP_1ADD
+        | op == 0x8c = return OP_1SUB
+        | op == 0x8d = return OP_2MUL
+        | op == 0x8e = return OP_2DIV
+        | op == 0x8f = return OP_NEGATE
+        | op == 0x90 = return OP_ABS
+        | op == 0x91 = return OP_NOT
+        | op == 0x92 = return OP_0NOTEQUAL
+        | op == 0x93 = return OP_ADD
+        | op == 0x94 = return OP_SUB
+        | op == 0x95 = return OP_MUL
+        | op == 0x96 = return OP_DIV
+        | op == 0x97 = return OP_MOD
+        | op == 0x98 = return OP_LSHIFT
+        | op == 0x99 = return OP_RSHIFT
+        | op == 0x9a = return OP_BOOLAND
+        | op == 0x9b = return OP_BOOLOR
+        | op == 0x9c = return OP_NUMEQUAL
+        | op == 0x9d = return OP_NUMEQUALVERIFY
+        | op == 0x9e = return OP_NUMNOTEQUAL
+        | op == 0x9f = return OP_LESSTHAN
+        | op == 0xa0 = return OP_GREATERTHAN
+        | op == 0xa1 = return OP_LESSTHANOREQUAL
+        | op == 0xa2 = return OP_GREATERTHANOREQUAL
+        | op == 0xa3 = return OP_MIN
+        | op == 0xa4 = return OP_MAX
+        | op == 0xa5 = return OP_WITHIN
+        -- Crypto
+        | op == 0xa6 = return OP_RIPEMD160
+        | op == 0xa7 = return OP_SHA1
+        | op == 0xa8 = return OP_SHA256
+        | op == 0xa9 = return OP_HASH160
+        | op == 0xaa = return OP_HASH256
+        | op == 0xab = return OP_CODESEPARATOR
+        | op == 0xac = return OP_CHECKSIG
+        | op == 0xad = return OP_CHECKSIGVERIFY
+        | op == 0xae = return OP_CHECKMULTISIG
+        | op == 0xaf = return OP_CHECKMULTISIGVERIFY
+        -- More NOPs
+        | op == 0xb0 = return OP_NOP1
+        | op == 0xb1 = return OP_CHECKLOCKTIMEVERIFY
+        | op == 0xb2 = return OP_CHECKSEQUENCEVERIFY
+        | op == 0xb3 = return OP_NOP4
+        | op == 0xb4 = return OP_NOP5
+        | op == 0xb5 = return OP_NOP6
+        | op == 0xb6 = return OP_NOP7
+        | op == 0xb7 = return OP_NOP8
+        | op == 0xb8 = return OP_NOP9
+        | op == 0xb9 = return OP_NOP10
+        -- Bitcoin Cash Nov 2018 hard fork
+        | op == 0xba = return OP_CHECKDATASIG
+        | op == 0xbb = return OP_CHECKDATASIGVERIFY
+        -- Bitcoin Cash May 2020 hard fork
+        | op == 0xbc = return OP_REVERSEBYTES
+        -- Constants
+        | op == 0xfd = return OP_PUBKEYHASH
+        | op == 0xfe = return OP_PUBKEY
+        | otherwise = return $ OP_INVALIDOPCODE op
+
+  serialize op = case op of
+    (OP_PUSHDATA payload optype) -> do
+      let len = B.length payload
+      case optype of
+        OPCODE -> do
+          unless (len <= 0x4b) $
+            error "OP_PUSHDATA OPCODE: Payload size too big"
+          putWord8 $ fromIntegral len
+        OPDATA1 -> do
+          unless (len <= 0xff) $
+            error "OP_PUSHDATA OPDATA1: Payload size too big"
+          putWord8 0x4c
+          putWord8 $ fromIntegral len
+        OPDATA2 -> do
+          unless (len <= 0xffff) $
+            error "OP_PUSHDATA OPDATA2: Payload size too big"
+          putWord8 0x4d
+          putWord16le $ fromIntegral len
+        OPDATA4 -> do
+          unless (len <= 0x7fffffff) $
+            error "OP_PUSHDATA OPDATA4: Payload size too big"
+          putWord8 0x4e
+          putWord32le $ fromIntegral len
+      putByteString payload
+
+    -- Constants
+    OP_0 -> putWord8 0x00
+    OP_1NEGATE -> putWord8 0x4f
+    OP_RESERVED -> putWord8 0x50
+    OP_1 -> putWord8 0x51
+    OP_2 -> putWord8 0x52
+    OP_3 -> putWord8 0x53
+    OP_4 -> putWord8 0x54
+    OP_5 -> putWord8 0x55
+    OP_6 -> putWord8 0x56
+    OP_7 -> putWord8 0x57
+    OP_8 -> putWord8 0x58
+    OP_9 -> putWord8 0x59
+    OP_10 -> putWord8 0x5a
+    OP_11 -> putWord8 0x5b
+    OP_12 -> putWord8 0x5c
+    OP_13 -> putWord8 0x5d
+    OP_14 -> putWord8 0x5e
+    OP_15 -> putWord8 0x5f
+    OP_16 -> putWord8 0x60
+    -- Crypto Constants
+    OP_PUBKEY -> putWord8 0xfe
+    OP_PUBKEYHASH -> putWord8 0xfd
+    -- Invalid Opcodes
+    (OP_INVALIDOPCODE x) -> putWord8 x
+    -- Flow Control
+    OP_NOP -> putWord8 0x61
+    OP_VER -> putWord8 0x62
+    OP_IF -> putWord8 0x63
+    OP_NOTIF -> putWord8 0x64
+    OP_VERIF -> putWord8 0x65
+    OP_VERNOTIF -> putWord8 0x66
+    OP_ELSE -> putWord8 0x67
+    OP_ENDIF -> putWord8 0x68
+    OP_VERIFY -> putWord8 0x69
+    OP_RETURN -> putWord8 0x6a
+    -- Stack Operations
+    OP_TOALTSTACK -> putWord8 0x6b
+    OP_FROMALTSTACK -> putWord8 0x6c
+    OP_2DROP -> putWord8 0x6d
+    OP_2DUP -> putWord8 0x6e
+    OP_3DUP -> putWord8 0x6f
+    OP_2OVER -> putWord8 0x70
+    OP_2ROT -> putWord8 0x71
+    OP_2SWAP -> putWord8 0x72
+    OP_IFDUP -> putWord8 0x73
+    OP_DEPTH -> putWord8 0x74
+    OP_DROP -> putWord8 0x75
+    OP_DUP -> putWord8 0x76
+    OP_NIP -> putWord8 0x77
+    OP_OVER -> putWord8 0x78
+    OP_PICK -> putWord8 0x79
+    OP_ROLL -> putWord8 0x7a
+    OP_ROT -> putWord8 0x7b
+    OP_SWAP -> putWord8 0x7c
+    OP_TUCK -> putWord8 0x7d
+    -- Splice
+    OP_CAT -> putWord8 0x7e
+    OP_SUBSTR -> putWord8 0x7f
+    OP_LEFT -> putWord8 0x80
+    OP_RIGHT -> putWord8 0x81
+    OP_SIZE -> putWord8 0x82
+    -- Bitwise Logic
+    OP_INVERT -> putWord8 0x83
+    OP_AND -> putWord8 0x84
+    OP_OR -> putWord8 0x85
+    OP_XOR -> putWord8 0x86
+    OP_EQUAL -> putWord8 0x87
+    OP_EQUALVERIFY -> putWord8 0x88
+    OP_RESERVED1 -> putWord8 0x89
+    OP_RESERVED2 -> putWord8 0x8a
+    -- Arithmetic
+    OP_1ADD -> putWord8 0x8b
+    OP_1SUB -> putWord8 0x8c
+    OP_2MUL -> putWord8 0x8d
+    OP_2DIV -> putWord8 0x8e
+    OP_NEGATE -> putWord8 0x8f
+    OP_ABS -> putWord8 0x90
+    OP_NOT -> putWord8 0x91
+    OP_0NOTEQUAL -> putWord8 0x92
+    OP_ADD -> putWord8 0x93
+    OP_SUB -> putWord8 0x94
+    OP_MUL -> putWord8 0x95
+    OP_DIV -> putWord8 0x96
+    OP_MOD -> putWord8 0x97
+    OP_LSHIFT -> putWord8 0x98
+    OP_RSHIFT -> putWord8 0x99
+    OP_BOOLAND -> putWord8 0x9a
+    OP_BOOLOR -> putWord8 0x9b
+    OP_NUMEQUAL -> putWord8 0x9c
+    OP_NUMEQUALVERIFY -> putWord8 0x9d
+    OP_NUMNOTEQUAL -> putWord8 0x9e
+    OP_LESSTHAN -> putWord8 0x9f
+    OP_GREATERTHAN -> putWord8 0xa0
+    OP_LESSTHANOREQUAL -> putWord8 0xa1
+    OP_GREATERTHANOREQUAL -> putWord8 0xa2
+    OP_MIN -> putWord8 0xa3
+    OP_MAX -> putWord8 0xa4
+    OP_WITHIN -> putWord8 0xa5
+    -- Crypto
+    OP_RIPEMD160 -> putWord8 0xa6
+    OP_SHA1 -> putWord8 0xa7
+    OP_SHA256 -> putWord8 0xa8
+    OP_HASH160 -> putWord8 0xa9
+    OP_HASH256 -> putWord8 0xaa
+    OP_CODESEPARATOR -> putWord8 0xab
+    OP_CHECKSIG -> putWord8 0xac
+    OP_CHECKSIGVERIFY -> putWord8 0xad
+    OP_CHECKMULTISIG -> putWord8 0xae
+    OP_CHECKMULTISIGVERIFY -> putWord8 0xaf
+    -- More NOPs
+    OP_NOP1 -> putWord8 0xb0
+    OP_CHECKLOCKTIMEVERIFY -> putWord8 0xb1
+    OP_CHECKSEQUENCEVERIFY -> putWord8 0xb2
+    OP_NOP4 -> putWord8 0xb3
+    OP_NOP5 -> putWord8 0xb4
+    OP_NOP6 -> putWord8 0xb5
+    OP_NOP7 -> putWord8 0xb6
+    OP_NOP8 -> putWord8 0xb7
+    OP_NOP9 -> putWord8 0xb8
+    OP_NOP10 -> putWord8 0xb9
+    -- Bitcoin Cash Nov 2018 hard fork
+    OP_CHECKDATASIG -> putWord8 0xba
+    OP_CHECKDATASIGVERIFY -> putWord8 0xbb
+    -- Bitcoin Cash May 2020 hard fork
+    OP_REVERSEBYTES -> putWord8 0xbc
+
+instance Binary ScriptOp where
+  put = serialize
+  get = deserialize
+
+instance Serialize ScriptOp where
+  put = serialize
+  get = deserialize
+
+-- | Check whether opcode is only data.
+isPushOp :: ScriptOp -> Bool
+isPushOp op = case op of
+  OP_PUSHDATA _ _ -> True
+  OP_0 -> True
+  OP_1NEGATE -> True
+  OP_1 -> True
+  OP_2 -> True
+  OP_3 -> True
+  OP_4 -> True
+  OP_5 -> True
+  OP_6 -> True
+  OP_7 -> True
+  OP_8 -> True
+  OP_9 -> True
+  OP_10 -> True
+  OP_11 -> True
+  OP_12 -> True
+  OP_13 -> True
+  OP_14 -> True
+  OP_15 -> True
+  OP_16 -> True
+  _ -> False
+
+-- | Optimally encode data using one of the 4 types of data pushing opcodes.
+opPushData :: ByteString -> ScriptOp
+opPushData bs
+  | len <= 0x4b = OP_PUSHDATA bs OPCODE
+  | len <= 0xff = OP_PUSHDATA bs OPDATA1
+  | len <= 0xffff = OP_PUSHDATA bs OPDATA2
+  | len <= 0xffffffff = OP_PUSHDATA bs OPDATA4
+  | otherwise = error "opPushData: payload size too big"
+  where
+    len = B.length bs
+
+-- | Transforms integers @[1 .. 16]@ to 'ScriptOp' @[OP_1 .. OP_16]@.
+intToScriptOp :: Int -> ScriptOp
+intToScriptOp i
+  | i `elem` [1 .. 16] = op
+  | otherwise = err
+  where
+    op =
+      fromRight err
+        . runGetS deserialize
+        . B.singleton
+        . fromIntegral
+        $ i + 0x50
+    err = error $ "intToScriptOp: Invalid integer " ++ show i
+
+-- | Decode 'ScriptOp' @[OP_1 .. OP_16]@ to integers @[1 .. 16]@. This functions
+-- fails for other values of 'ScriptOp'
+scriptOpToInt :: ScriptOp -> Either String Int
+scriptOpToInt s
+  | res `elem` [1 .. 16] = return res
+  | otherwise = Left $ "scriptOpToInt: invalid opcode " ++ show s
+  where
+    res = fromIntegral (B.head $ runPutS $ serialize s) - 0x50
diff --git a/src/Haskoin/Script/SigHash.hs b/src/Haskoin/Script/SigHash.hs
new file mode 100644
--- /dev/null
+++ b/src/Haskoin/Script/SigHash.hs
@@ -0,0 +1,350 @@
+{-# LANGUAGE DeriveAnyClass #-}
+{-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE DerivingStrategies #-}
+{-# LANGUAGE DuplicateRecordFields #-}
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
+{-# LANGUAGE ImportQualifiedPost #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE OverloadedRecordDot #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE RecordWildCards #-}
+{-# LANGUAGE NoFieldSelectors #-}
+
+-- |
+-- Module      : Haskoin.Script.SigHash
+-- Copyright   : No rights reserved
+-- License     : MIT
+-- Maintainer  : jprupp@protonmail.ch
+-- Stability   : experimental
+-- Portability : POSIX
+--
+-- Transaction signatures and related functions.
+module Haskoin.Script.SigHash
+  ( -- * Script Signatures
+    SigHash (..),
+    SigHashFlag (..),
+    sigHashAll,
+    sigHashNone,
+    sigHashSingle,
+    anyoneCanPay,
+    hasForkIdFlag,
+    setAnyoneCanPay,
+    setForkIdFlag,
+    isSigHashAll,
+    isSigHashNone,
+    isSigHashSingle,
+    isSigHashUnknown,
+    sigHashAddForkId,
+    sigHashGetForkId,
+    sigHashAddNetworkId,
+    txSigHash,
+    txSigHashForkId,
+    TxSignature (..),
+    decodeTxSig,
+    encodeTxSig,
+  )
+where
+
+import Control.DeepSeq
+import Control.Monad
+import Crypto.Secp256k1
+import Data.Aeson
+import Data.Bits
+import Data.Bool (bool)
+import Data.ByteString (ByteString)
+import Data.ByteString qualified as B
+import Data.Bytes.Get
+import Data.Bytes.Put
+import Data.Bytes.Serial
+import Data.Hashable
+import Data.Maybe
+import Data.Scientific
+import Data.Word
+import GHC.Generics (Generic)
+import Haskoin.Crypto.Hash
+import Haskoin.Crypto.Signature
+import Haskoin.Network.Common
+import Haskoin.Network.Data
+import Haskoin.Script.Common
+import Haskoin.Transaction.Common
+import Haskoin.Util
+
+-- | Constant representing a SIGHASH flag that controls what is being signed.
+data SigHashFlag
+  = -- | sign all outputs
+    SIGHASH_ALL
+  | -- | sign no outputs
+    SIGHASH_NONE
+  | -- | sign the output index corresponding to the input
+    SIGHASH_SINGLE
+  | -- | replay protection for Bitcoin Cash transactions
+    SIGHASH_FORKID
+  | -- | new inputs can be added
+    SIGHASH_ANYONECANPAY
+  deriving (Eq, Ord, Show, Read, Generic)
+
+instance NFData SigHashFlag
+
+instance Hashable SigHashFlag
+
+instance Enum SigHashFlag where
+  fromEnum SIGHASH_ALL = 0x01
+  fromEnum SIGHASH_NONE = 0x02
+  fromEnum SIGHASH_SINGLE = 0x03
+  fromEnum SIGHASH_FORKID = 0x40
+  fromEnum SIGHASH_ANYONECANPAY = 0x80
+  toEnum 0x01 = SIGHASH_ALL
+  toEnum 0x02 = SIGHASH_NONE
+  toEnum 0x03 = SIGHASH_SINGLE
+  toEnum 0x40 = SIGHASH_FORKID
+  toEnum 0x80 = SIGHASH_ANYONECANPAY
+  toEnum _ = error "Not a valid sighash flag"
+
+-- | Data type representing the different ways a transaction can be signed.
+-- When producing a signature, a hash of the transaction is used as the message
+-- to be signed. The 'SigHash' parameter controls which parts of the
+-- transaction are used or ignored to produce the transaction hash. The idea is
+-- that if some part of a transaction is not used to produce the transaction
+-- hash, then you can change that part of the transaction after producing a
+-- signature without invalidating that signature.
+--
+-- If the 'SIGHASH_ANYONECANPAY' flag is set (true), then only the current input
+-- is signed. Otherwise, all of the inputs of a transaction are signed. The
+-- default value for 'SIGHASH_ANYONECANPAY' is unset (false).
+newtype SigHash
+  = SigHash Word32
+  deriving (Eq, Ord, Enum, Show, Read, Generic)
+  deriving newtype (Bits, Integral, Num, Real, Hashable, NFData)
+
+instance FromJSON SigHash where
+  parseJSON =
+    withScientific "sighash" $
+      maybe mzero (return . SigHash) . toBoundedInteger
+
+instance ToJSON SigHash where
+  toJSON = Number . fromIntegral
+  toEncoding (SigHash n) = toEncoding n
+
+-- | SIGHASH_NONE as a byte.
+sigHashNone :: SigHash
+sigHashNone = fromIntegral $ fromEnum SIGHASH_NONE
+
+-- | SIGHASH_ALL as a byte.
+sigHashAll :: SigHash
+sigHashAll = fromIntegral $ fromEnum SIGHASH_ALL
+
+-- | SIGHASH_SINGLE as a byte.
+sigHashSingle :: SigHash
+sigHashSingle = fromIntegral $ fromEnum SIGHASH_SINGLE
+
+-- | SIGHASH_FORKID as a byte.
+sigHashForkId :: SigHash
+sigHashForkId = fromIntegral $ fromEnum SIGHASH_FORKID
+
+-- | SIGHASH_ANYONECANPAY as a byte.
+sigHashAnyoneCanPay :: SigHash
+sigHashAnyoneCanPay = fromIntegral $ fromEnum SIGHASH_ANYONECANPAY
+
+-- | Set SIGHASH_FORKID flag.
+setForkIdFlag :: SigHash -> SigHash
+setForkIdFlag = (.|. sigHashForkId)
+
+-- | Set SIGHASH_ANYONECANPAY flag.
+setAnyoneCanPay :: SigHash -> SigHash
+setAnyoneCanPay = (.|. sigHashAnyoneCanPay)
+
+-- | Is the SIGHASH_FORKID flag set?
+hasForkIdFlag :: SigHash -> Bool
+hasForkIdFlag = (/= 0) . (.&. sigHashForkId)
+
+-- | Is the SIGHASH_ANYONECANPAY flag set?
+anyoneCanPay :: SigHash -> Bool
+anyoneCanPay = (/= 0) . (.&. sigHashAnyoneCanPay)
+
+-- | Returns 'True' if the 'SigHash' has the value 'SIGHASH_ALL'.
+isSigHashAll :: SigHash -> Bool
+isSigHashAll = (== sigHashAll) . (.&. 0x1f)
+
+-- | Returns 'True' if the 'SigHash' has the value 'SIGHASH_NONE'.
+isSigHashNone :: SigHash -> Bool
+isSigHashNone = (== sigHashNone) . (.&. 0x1f)
+
+-- | Returns 'True' if the 'SigHash' has the value 'SIGHASH_SINGLE'.
+isSigHashSingle :: SigHash -> Bool
+isSigHashSingle = (== sigHashSingle) . (.&. 0x1f)
+
+-- | Returns 'True' if the 'SigHash' has the value 'SIGHASH_UNKNOWN'.
+isSigHashUnknown :: SigHash -> Bool
+isSigHashUnknown =
+  (`notElem` [sigHashAll, sigHashNone, sigHashSingle]) . (.&. 0x1f)
+
+-- | Add a fork id to a 'SigHash'.
+sigHashAddForkId :: SigHash -> Word32 -> SigHash
+sigHashAddForkId sh w = (fromIntegral w `shiftL` 8) .|. (sh .&. 0x000000ff)
+
+-- | Add fork id of a particular network to a 'SigHash'.
+sigHashAddNetworkId :: Network -> SigHash -> SigHash
+sigHashAddNetworkId net =
+  (`sigHashAddForkId` fromMaybe 0 net.sigHashForkId)
+
+-- | Get fork id from 'SigHash'.
+sigHashGetForkId :: SigHash -> Word32
+sigHashGetForkId (SigHash n) = fromIntegral $ n `shiftR` 8
+
+-- | Computes the hash that will be used for signing a transaction.
+txSigHash ::
+  Network ->
+  -- | transaction to sign
+  Tx ->
+  -- | script from output being spent
+  Script ->
+  -- | value of output being spent
+  Word64 ->
+  -- | index of input being signed
+  Int ->
+  -- | what to sign
+  SigHash ->
+  -- | hash to be signed
+  Hash256
+txSigHash net tx out v i sh
+  | hasForkIdFlag sh && isJust net.sigHashForkId =
+      txSigHashForkId net tx out v i sh
+  | otherwise = do
+      let newIn = buildInputs tx.inputs fout i sh
+      -- When SigSingle and input index > outputs, then sign integer 1
+      fromMaybe one $ do
+        newOut <- buildOutputs tx.outputs i sh
+        let newTx = Tx tx.version newIn newOut [] tx.locktime
+        return . doubleSHA256 . runPutS $ do
+          serialize newTx
+          putWord32le $ fromIntegral sh
+  where
+    fout = Script $ filter (/= OP_CODESEPARATOR) out.ops
+    one = "0100000000000000000000000000000000000000000000000000000000000000"
+
+-- | Build transaction inputs for computing sighashes.
+buildInputs :: [TxIn] -> Script -> Int -> SigHash -> [TxIn]
+buildInputs txins out i sh
+  | anyoneCanPay sh = [serialOut (txins !! i)]
+  | isSigHashAll sh || isSigHashUnknown sh = single
+  | otherwise = zipWith noSeq single [0 ..]
+  where
+    serialOut TxIn {..} = TxIn {script = runPutS $ serialize out, ..}
+    emptyIn TxIn {..} = TxIn {script = B.empty, ..}
+    emptyIns = map emptyIn txins
+    single = updateIndex i emptyIns serialOut
+    noSeq TxIn {..} j = TxIn {sequence = if i == j then sequence else 0, ..}
+
+-- | Build transaction outputs for computing sighashes.
+buildOutputs :: [TxOut] -> Int -> SigHash -> Maybe [TxOut]
+buildOutputs txos i sh
+  | isSigHashAll sh || isSigHashUnknown sh = return txos
+  | isSigHashNone sh = return []
+  | i >= length txos = Nothing
+  | otherwise = return $ buffer ++ [txos !! i]
+  where
+    buffer = replicate i $ TxOut maxBound B.empty
+
+-- | Compute the hash that will be used for signing a transaction. This
+-- function is used when the 'SIGHASH_FORKID' flag is set.
+txSigHashForkId ::
+  Network ->
+  -- | transaction to sign
+  Tx ->
+  -- | script from output being spent
+  Script ->
+  -- | value of output being spent
+  Word64 ->
+  -- | index of input being signed
+  Int ->
+  -- | what to sign
+  SigHash ->
+  -- | hash to be signed
+  Hash256
+txSigHashForkId net tx out v i sh =
+  doubleSHA256 . runPutS $ do
+    putWord32le tx.version
+    serialize hashPrevouts
+    serialize hashSequence
+    serialize (tx.inputs !! i).outpoint
+    putScript out
+    putWord64le v
+    putWord32le (tx.inputs !! i).sequence
+    serialize hashOutputs
+    putWord32le tx.locktime
+    putWord32le $ fromIntegral $ sigHashAddNetworkId net sh
+  where
+    hashPrevouts
+      | not (anyoneCanPay sh) =
+          doubleSHA256 . runPutS $ mapM_ (serialize . (.outpoint)) tx.inputs
+      | otherwise = zeros
+    hashSequence
+      | not (anyoneCanPay sh || isSigHashSingle sh || isSigHashNone sh) =
+          doubleSHA256 . runPutS $ mapM_ (putWord32le . (.sequence)) tx.inputs
+      | otherwise = zeros
+    hashOutputs
+      | not (isSigHashSingle sh || isSigHashNone sh) =
+          doubleSHA256 . runPutS $ mapM_ serialize tx.outputs
+      | isSigHashSingle sh && i < length tx.outputs =
+          doubleSHA256 . runPutS $ serialize $ tx.outputs !! i
+      | otherwise = zeros
+    putScript s = do
+      let encodedScript = runPutS $ serialize s
+      putVarInt $ B.length encodedScript
+      putByteString encodedScript
+    zeros :: Hash256
+    zeros = "0000000000000000000000000000000000000000000000000000000000000000"
+
+-- | Data type representing a signature together with a 'SigHash'. The 'SigHash'
+-- is serialized as one byte at the end of an ECDSA 'Sig'. All signatures in
+-- transaction inputs are of type 'TxSignature'.
+data TxSignature
+  = TxSignature
+      { sig :: !Sig,
+        hash :: !SigHash
+      }
+  | TxSignatureEmpty
+  deriving (Eq, Show, Read, Generic, NFData)
+
+instance Marshal (Network, Ctx) TxSignature where
+  marshalPut (net, ctx) TxSignatureEmpty = return ()
+  marshalPut (net, ctx) (TxSignature sig (SigHash n)) = do
+    marshalPut ctx sig
+    putWord8 (fromIntegral n)
+
+  marshalGet (net, ctx) =
+    bool decode empty =<< isEmpty
+    where
+      empty = return TxSignatureEmpty
+      decode = do
+        sig <- marshalGet ctx
+        sh <- fromIntegral <$> getWord8
+        when (isSigHashUnknown sh) $
+          fail "Non-canonical signature: unknown hashtype byte"
+        when (isNothing net.sigHashForkId && hasForkIdFlag sh) $
+          fail "Non-canonical signature: invalid network for forkId"
+        return $ TxSignature sig sh
+
+instance MarshalJSON (Network, Ctx) TxSignature where
+  marshalValue (net, ctx) = String . encodeHex . encodeTxSig net ctx
+  marshalEncoding s = hexEncoding . runPutL . marshalPut s
+  unmarshalValue (net, ctx) =
+    withText "TxSignature" $ \t ->
+      case decodeHex t of
+        Nothing -> fail "Cannot decode hex signature"
+        Just b -> case decodeTxSig net ctx b of
+          Left e -> fail e
+          Right s -> return s
+
+encodeTxSig :: Network -> Ctx -> TxSignature -> ByteString
+encodeTxSig net ctx = runPutS . marshalPut (net, ctx)
+
+decodeTxSig :: Network -> Ctx -> ByteString -> Either String TxSignature
+decodeTxSig net ctx =
+  runGetS $ do
+    sig <- marshalGet (net, ctx)
+    e <- isEmpty
+    unless e $
+      fail "Non-canonical signature: multiple hashtype bytes"
+    return sig
diff --git a/src/Haskoin/Script/Standard.hs b/src/Haskoin/Script/Standard.hs
new file mode 100644
--- /dev/null
+++ b/src/Haskoin/Script/Standard.hs
@@ -0,0 +1,435 @@
+{-# LANGUAGE DeriveAnyClass #-}
+{-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE DuplicateRecordFields #-}
+{-# LANGUAGE FlexibleContexts #-}
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE ImportQualifiedPost #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE OverloadedRecordDot #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE NoFieldSelectors #-}
+
+-- |
+-- Module      : Haskoin.Script.Standard
+-- Copyright   : No rights reserved
+-- License     : MIT
+-- Maintainer  : jprupp@protonmail.ch
+-- Stability   : experimental
+-- Portability : POSIX
+--
+-- Standard scripts like pay-to-public-key, pay-to-public-key-hash,
+-- pay-to-script-hash, pay-to-multisig and corresponding SegWit variants.
+module Haskoin.Script.Standard
+  ( -- * Standard Script Outputs
+    ScriptOutput (..),
+    RedeemScript,
+    isPayPK,
+    isPayPKHash,
+    isPayMulSig,
+    isPayScriptHash,
+    isPayWitness,
+    isPayWitnessPKHash,
+    isPayWitnessScriptHash,
+    isDataCarrier,
+    encodeOutput,
+    decodeOutput,
+    toP2SH,
+    toP2WSH,
+    sortMulSig,
+
+    -- * Standard Script Inputs
+    ScriptInput (..),
+    SimpleInput (..),
+    encodeInput,
+    decodeInput,
+    isSpendPK,
+    isSpendPKHash,
+    isSpendMulSig,
+    isScriptHashInput,
+  )
+where
+
+import Control.Applicative ((<|>))
+import Control.DeepSeq
+import Control.Monad (guard, liftM2, (<=<))
+import Crypto.Secp256k1
+import Data.Aeson (ToJSON (..), Value (..), withText)
+import Data.Aeson.Encoding (Encoding, text)
+import Data.Aeson.Types (Parser)
+import Data.ByteString (ByteString)
+import Data.ByteString qualified as B
+import Data.Bytes.Get
+import Data.Bytes.Put
+import Data.Bytes.Serial
+import Data.Function (on)
+import Data.Hashable
+import Data.List (sortBy)
+import Data.Maybe (fromJust, isJust)
+import Data.Word (Word8)
+import GHC.Generics (Generic)
+import Haskoin.Crypto.Hash
+import Haskoin.Crypto.Keys.Common
+import Haskoin.Network.Data
+import Haskoin.Script.Common
+import Haskoin.Script.SigHash
+import Haskoin.Util
+
+-- | Data type describing standard transaction output scripts. Output scripts
+-- provide the conditions that must be fulfilled for someone to spend the funds
+-- in a transaction output.
+data ScriptOutput
+  = -- | pay to public key
+    PayPK {key :: !PublicKey}
+  | -- | pay to public key hash
+    PayPKHash {hash160 :: !Hash160}
+  | -- | multisig
+    PayMulSig
+      { keys :: ![PublicKey],
+        required :: !Int
+      }
+  | -- | pay to a script hash
+    PayScriptHash {hash160 :: !Hash160}
+  | -- | pay to witness public key hash
+    PayWitnessPKHash {hash160 :: !Hash160}
+  | -- | pay to witness script hash
+    PayWitnessScriptHash {hash256 :: !Hash256}
+  | -- | another pay to witness address
+    PayWitness
+      { version :: !Word8,
+        bytes :: !ByteString
+      }
+  | -- | provably unspendable data carrier
+    DataCarrier {bytes :: !ByteString}
+  deriving (Eq, Show, Read, Generic, NFData)
+
+instance MarshalJSON Ctx ScriptOutput where
+  unmarshalValue ctx =
+    withText "ScriptOutput" $ \t ->
+      case decodeHex t of
+        Nothing -> fail "Could not decode hex script"
+        Just bs -> either fail return $ unmarshal ctx bs
+
+  marshalValue ctx = String . encodeHex . marshal ctx
+
+  marshalEncoding ctx = hexEncoding . runPutL . marshalPut ctx
+
+-- | Is script a pay-to-public-key output?
+isPayPK :: ScriptOutput -> Bool
+isPayPK (PayPK _) = True
+isPayPK _ = False
+
+-- | Is script a pay-to-pub-key-hash output?
+isPayPKHash :: ScriptOutput -> Bool
+isPayPKHash (PayPKHash _) = True
+isPayPKHash _ = False
+
+-- | Is script a pay-to-multi-sig output?
+isPayMulSig :: ScriptOutput -> Bool
+isPayMulSig (PayMulSig _ _) = True
+isPayMulSig _ = False
+
+-- | Is script a pay-to-script-hash output?
+isPayScriptHash :: ScriptOutput -> Bool
+isPayScriptHash (PayScriptHash _) = True
+isPayScriptHash _ = False
+
+-- | Is script a pay-to-witness-pub-key-hash output?
+isPayWitnessPKHash :: ScriptOutput -> Bool
+isPayWitnessPKHash (PayWitnessPKHash _) = True
+isPayWitnessPKHash _ = False
+
+-- | Is script a pay-to-witness-script-hash output?
+isPayWitnessScriptHash :: ScriptOutput -> Bool
+isPayWitnessScriptHash (PayWitnessScriptHash _) = True
+isPayWitnessScriptHash _ = False
+
+-- | Is script paying to a different type of witness address?
+isPayWitness :: ScriptOutput -> Bool
+isPayWitness (PayWitness _ _) = True
+isPayWitness _ = False
+
+-- | Is script a data carrier output?
+isDataCarrier :: ScriptOutput -> Bool
+isDataCarrier (DataCarrier _) = True
+isDataCarrier _ = False
+
+-- | Tries to decode a 'ScriptOutput' from a 'Script'. This can fail if the
+-- script is not recognized as any of the standard output types.
+decodeOutput :: Ctx -> Script -> Either String ScriptOutput
+decodeOutput ctx s = case s.ops of
+  -- Pay to PubKey
+  [OP_PUSHDATA bs _, OP_CHECKSIG] ->
+    PayPK <$> unmarshal ctx bs
+  -- Pay to PubKey Hash
+  [OP_DUP, OP_HASH160, OP_PUSHDATA bs _, OP_EQUALVERIFY, OP_CHECKSIG] ->
+    PayPKHash <$> runGetS deserialize bs
+  -- Pay to Script Hash
+  [OP_HASH160, OP_PUSHDATA bs _, OP_EQUAL] ->
+    PayScriptHash <$> runGetS deserialize bs
+  -- Pay to Witness
+  [OP_0, OP_PUSHDATA bs OPCODE]
+    | B.length bs == 20 ->
+        PayWitnessPKHash <$> runGetS deserialize bs
+    | B.length bs == 32 ->
+        PayWitnessScriptHash <$> runGetS deserialize bs
+    | B.length bs /= 20 && B.length bs /= 32 ->
+        Left
+          "decodeOutput: invalid version 0 segwit \
+          \(must be 20 or 32 bytes)"
+  -- Other Witness
+  [ver, OP_PUSHDATA bs _]
+    | Just wv <- opWitnessVersion ver,
+      B.length bs >= 2,
+      B.length bs <= 40 ->
+        Right $ PayWitness wv bs
+  -- Provably unspendable data carrier output
+  [OP_RETURN, OP_PUSHDATA bs _] -> Right $ DataCarrier bs
+  -- Pay to MultiSig Keys
+  _ -> case matchPayMulSig ctx s of
+    Right x -> return x
+    Left _ -> Left "decodeOutput: Non-standard output"
+
+witnessVersionOp :: Word8 -> Maybe ScriptOp
+witnessVersionOp 0 = Just OP_0
+witnessVersionOp 1 = Just OP_1
+witnessVersionOp 2 = Just OP_2
+witnessVersionOp 3 = Just OP_3
+witnessVersionOp 4 = Just OP_4
+witnessVersionOp 5 = Just OP_5
+witnessVersionOp 6 = Just OP_6
+witnessVersionOp 7 = Just OP_7
+witnessVersionOp 8 = Just OP_8
+witnessVersionOp 9 = Just OP_9
+witnessVersionOp 10 = Just OP_10
+witnessVersionOp 11 = Just OP_11
+witnessVersionOp 12 = Just OP_12
+witnessVersionOp 13 = Just OP_13
+witnessVersionOp 14 = Just OP_14
+witnessVersionOp 15 = Just OP_15
+witnessVersionOp 16 = Just OP_16
+witnessVersionOp _ = Nothing
+
+opWitnessVersion :: ScriptOp -> Maybe Word8
+opWitnessVersion OP_0 = Just 0
+opWitnessVersion OP_1 = Just 1
+opWitnessVersion OP_2 = Just 2
+opWitnessVersion OP_3 = Just 3
+opWitnessVersion OP_4 = Just 4
+opWitnessVersion OP_5 = Just 5
+opWitnessVersion OP_6 = Just 6
+opWitnessVersion OP_7 = Just 7
+opWitnessVersion OP_8 = Just 8
+opWitnessVersion OP_9 = Just 9
+opWitnessVersion OP_10 = Just 10
+opWitnessVersion OP_11 = Just 11
+opWitnessVersion OP_12 = Just 12
+opWitnessVersion OP_13 = Just 13
+opWitnessVersion OP_14 = Just 14
+opWitnessVersion OP_15 = Just 15
+opWitnessVersion OP_16 = Just 16
+opWitnessVersion _ = Nothing
+
+-- | Computes a 'Script' from a standard 'ScriptOutput'.
+encodeOutput :: Ctx -> ScriptOutput -> Script
+encodeOutput ctx s = Script $ case s of
+  -- Pay to PubKey
+  (PayPK k) -> [opPushData $ marshal ctx k, OP_CHECKSIG]
+  -- Pay to PubKey Hash Address
+  (PayPKHash h) ->
+    [ OP_DUP,
+      OP_HASH160,
+      opPushData $ runPutS $ serialize h,
+      OP_EQUALVERIFY,
+      OP_CHECKSIG
+    ]
+  -- Pay to MultiSig Keys
+  (PayMulSig ps r)
+    | r <= length ps ->
+        let opM = intToScriptOp r
+            opN = intToScriptOp $ length ps
+            keys = map (opPushData . marshal ctx) ps
+         in opM : keys ++ [opN, OP_CHECKMULTISIG]
+    | otherwise -> error "encodeOutput: PayMulSig r must be <= than pkeys"
+  -- Pay to Script Hash Address
+  (PayScriptHash h) ->
+    [OP_HASH160, opPushData $ runPutS $ serialize h, OP_EQUAL]
+  -- Pay to Witness PubKey Hash Address
+  (PayWitnessPKHash h) ->
+    [OP_0, opPushData $ runPutS $ serialize h]
+  (PayWitnessScriptHash h) ->
+    [OP_0, opPushData $ runPutS $ serialize h]
+  (PayWitness v h) ->
+    [ case witnessVersionOp v of
+        Nothing -> error "encodeOutput: invalid witness version"
+        Just c -> c,
+      opPushData h
+    ]
+  -- Provably unspendable output
+  (DataCarrier d) -> [OP_RETURN, opPushData d]
+
+instance Marshal Ctx ScriptOutput where
+  marshalGet ctx = do
+    script <- deserialize
+    case decodeOutput ctx script of
+      Left e -> fail e
+      Right o -> return o
+  marshalPut ctx = serialize . encodeOutput ctx
+
+-- | Encode script as pay-to-script-hash script
+toP2SH :: Script -> ScriptOutput
+toP2SH = PayScriptHash . addressHash . runPutS . serialize
+
+-- | Encode script as a pay-to-witness-script-hash script
+toP2WSH :: Script -> ScriptOutput
+toP2WSH = PayWitnessScriptHash . sha256 . runPutS . serialize
+
+-- | Match @[OP_N, PubKey1, ..., PubKeyM, OP_M, OP_CHECKMULTISIG]@
+matchPayMulSig :: Ctx -> Script -> Either String ScriptOutput
+matchPayMulSig ctx (Script ops) = case splitAt (length ops - 2) ops of
+  (m : xs, [n, OP_CHECKMULTISIG]) -> do
+    (intM, intN) <- liftM2 (,) (scriptOpToInt m) (scriptOpToInt n)
+    if intM <= intN && length xs == intN
+      then liftM2 PayMulSig (go xs) (return intM)
+      else Left "matchPayMulSig: Invalid M or N parameters"
+  _ -> Left "matchPayMulSig: script did not match output template"
+  where
+    go (OP_PUSHDATA bs _ : xs) =
+      liftM2 (:) (unmarshal ctx bs) (go xs)
+    go [] =
+      Right []
+    go _ =
+      Left "matchPayMulSig: invalid multisig opcode"
+
+-- | Sort the public keys of a multisig output in ascending order by comparing
+-- their compressed serialized representations. Refer to BIP-67.
+sortMulSig :: Ctx -> ScriptOutput -> ScriptOutput
+sortMulSig ctx out = case out of
+  PayMulSig keys r ->
+    PayMulSig
+      (sortBy (compare `on` marshal ctx) keys)
+      r
+  _ -> error "Can only call orderMulSig on PayMulSig scripts"
+
+-- | Data type describing standard transaction input scripts. Input scripts
+-- provide the signing data required to unlock the coins of the output they are
+-- trying to spend, except in pay-to-witness-public-key-hash and
+-- pay-to-script-hash transactions.
+data SimpleInput
+  = SpendPK
+      { -- | transaction signature
+        signature :: !TxSignature
+      }
+  | SpendPKHash
+      { -- | embedded signature
+        signature :: !TxSignature,
+        -- | public key
+        key :: !PublicKey
+      }
+  | SpendMulSig
+      { -- | list of signatures
+        signatures :: ![TxSignature]
+      }
+  deriving (Eq, Show, Read, Generic, NFData)
+
+-- | Returns true if the input script is spending from a pay-to-public-key
+-- output.
+isSpendPK :: ScriptInput -> Bool
+isSpendPK (RegularInput (SpendPK _)) = True
+isSpendPK _ = False
+
+-- | Returns true if the input script is spending from a pay-to-public-key-hash
+-- output.
+isSpendPKHash :: ScriptInput -> Bool
+isSpendPKHash (RegularInput (SpendPKHash _ _)) = True
+isSpendPKHash _ = False
+
+-- | Returns true if the input script is spending a multisig output.
+isSpendMulSig :: ScriptInput -> Bool
+isSpendMulSig (RegularInput (SpendMulSig _)) = True
+isSpendMulSig _ = False
+
+-- | Returns true if the input script is spending a pay-to-script-hash output.
+isScriptHashInput :: ScriptInput -> Bool
+isScriptHashInput (ScriptHashInput _ _) = True
+isScriptHashInput _ = False
+
+-- | A redeem script is the output script serialized into the spending input
+-- script. It must be included in inputs that spend pay-to-script-hash outputs.
+type RedeemScript = ScriptOutput
+
+-- | Standard input script high-level representation.
+data ScriptInput
+  = RegularInput
+      { -- | get wrapped simple input
+        get :: !SimpleInput
+      }
+  | ScriptHashInput
+      { -- | get simple input associated with redeem script
+        get :: !SimpleInput,
+        -- | redeem script
+        redeem :: !RedeemScript
+      }
+  deriving (Show, Read, Eq, Generic, NFData)
+
+-- | Heuristic to decode an input script into one of the standard types.
+decodeSimpleInput :: Network -> Ctx -> Script -> Either String SimpleInput
+decodeSimpleInput net ctx (Script ops) =
+  maybeToEither errMsg $ matchPK ops <|> matchPKHash ops <|> matchMulSig ops
+  where
+    matchPK [op] = SpendPK <$> f op
+    matchPK _ = Nothing
+    matchPKHash [op, OP_PUSHDATA pub _] =
+      SpendPKHash <$> f op <*> eitherToMaybe (unmarshal ctx pub)
+    matchPKHash _ = Nothing
+    matchMulSig (x : xs) = do
+      guard $ x == OP_0
+      SpendMulSig <$> mapM f xs
+    matchMulSig _ = Nothing
+    f OP_0 = return TxSignatureEmpty
+    f (OP_PUSHDATA "" OPCODE) = f OP_0
+    f (OP_PUSHDATA bs _) = eitherToMaybe $ decodeTxSig net ctx bs
+    f _ = Nothing
+    errMsg = "decodeInput: Could not decode script input"
+
+-- | Heuristic to decode a 'ScriptInput' from a 'Script'. This function fails if
+-- the script can not be parsed as a standard script input.
+decodeInput :: Network -> Ctx -> Script -> Either String ScriptInput
+decodeInput net ctx s@(Script ops) =
+  maybeToEither errMsg $ matchSimpleInput <|> matchPayScriptHash
+  where
+    matchSimpleInput =
+      RegularInput <$> eitherToMaybe (decodeSimpleInput net ctx s)
+    matchPayScriptHash =
+      case splitAt (length s.ops - 1) ops of
+        (is, [OP_PUSHDATA bs _]) -> do
+          rdm <- eitherToMaybe $ unmarshal ctx bs
+          inp <- eitherToMaybe $ decodeSimpleInput net ctx $ Script is
+          return $ ScriptHashInput inp rdm
+        _ -> Nothing
+    errMsg = "decodeInput: Could not decode script input"
+
+instance Marshal (Network, Ctx) ScriptInput where
+  marshalGet (net, ctx) =
+    deserialize >>= either fail return . decodeInput net ctx
+
+  marshalPut (net, ctx) =
+    serialize . encodeInput net ctx
+
+-- | Encode a standard input into a script.
+encodeInput :: Network -> Ctx -> ScriptInput -> Script
+encodeInput net ctx s = case s of
+  RegularInput ri -> encodeSimpleInput net ctx ri
+  ScriptHashInput i o ->
+    Script $ (encodeSimpleInput net ctx i).ops ++ [opPushData $ marshal ctx o]
+
+-- | Encode a standard 'SimpleInput' into opcodes as an input 'Script'.
+encodeSimpleInput :: Network -> Ctx -> SimpleInput -> Script
+encodeSimpleInput net ctx s =
+  Script $
+    case s of
+      SpendPK ts -> [f ts]
+      SpendPKHash ts p -> [f ts, opPushData $ marshal ctx p]
+      SpendMulSig xs -> OP_0 : map f xs
+  where
+    f TxSignatureEmpty = OP_0
+    f ts = opPushData $ encodeTxSig net ctx ts
diff --git a/src/Haskoin/Transaction.hs b/src/Haskoin/Transaction.hs
new file mode 100644
--- /dev/null
+++ b/src/Haskoin/Transaction.hs
@@ -0,0 +1,27 @@
+{-# LANGUAGE DuplicateRecordFields #-}
+
+-- |
+-- Module      : Haskoin.Transaction
+-- Copyright   : No rights reserved
+-- License     : MIT
+-- Maintainer  : jprupp@protonmail.ch
+-- Stability   : experimental
+-- Portability : POSIX
+--
+-- Transactions and related code.
+module Haskoin.Transaction
+  ( module Common,
+    module Builder,
+    module Segwit,
+    module Taproot,
+    module Partial,
+    module Genesis,
+  )
+where
+
+import Haskoin.Transaction.Builder as Builder
+import Haskoin.Transaction.Common as Common
+import Haskoin.Transaction.Genesis as Genesis
+import Haskoin.Transaction.Partial as Partial
+import Haskoin.Transaction.Segwit as Segwit
+import Haskoin.Transaction.Taproot as Taproot
diff --git a/src/Haskoin/Transaction/Builder.hs b/src/Haskoin/Transaction/Builder.hs
new file mode 100644
--- /dev/null
+++ b/src/Haskoin/Transaction/Builder.hs
@@ -0,0 +1,624 @@
+{-# LANGUAGE DuplicateRecordFields #-}
+{-# LANGUAGE FlexibleContexts #-}
+{-# LANGUAGE ImportQualifiedPost #-}
+{-# LANGUAGE LambdaCase #-}
+{-# LANGUAGE OverloadedRecordDot #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE RecordWildCards #-}
+{-# LANGUAGE NoFieldSelectors #-}
+
+-- |
+-- Module      : Haskoin.Transaction.Builder
+-- Copyright   : No rights reserved
+-- License     : MIT
+-- Maintainer  : jprupp@protonmail.ch
+-- Stability   : experimental
+-- Portability : POSIX
+--
+-- Code to simplify transaction creation, signing, fee calculation and coin
+-- selection.
+module Haskoin.Transaction.Builder
+  ( -- * Transaction Builder
+    buildAddrTx,
+    buildTx,
+    buildInput,
+    SigInput (..),
+    signTx,
+    signNestedWitnessTx,
+    makeSignature,
+    signInput,
+    signNestedInput,
+    verifyStdTx,
+    mergeTxs,
+    sigKeys,
+    mergeTxInput,
+    findSigInput,
+    verifyStdInput,
+
+    -- * Coin Selection
+    Coin (..),
+    chooseCoins,
+    chooseCoinsSink,
+    chooseMSCoins,
+    chooseMSCoinsSink,
+    countMulSig,
+    greedyAddSink,
+    guessTxFee,
+    guessMSTxFee,
+    guessTxSize,
+    guessMSSize,
+  )
+where
+
+import Control.Applicative ((<|>))
+import Control.Arrow (first)
+import Control.Monad (foldM, unless)
+import Control.Monad.Identity (runIdentity)
+import Crypto.Secp256k1
+import Data.ByteString qualified as B
+import Data.Bytes.Get
+import Data.Bytes.Put
+import Data.Bytes.Serial
+import Data.Conduit (ConduitT, Void, await, runConduit, (.|))
+import Data.Conduit.List (sourceList)
+import Data.Either (fromRight, rights)
+import Data.List (nub)
+import Data.Maybe (catMaybes, fromJust, isJust)
+import Data.String.Conversions (cs)
+import Data.Text (Text)
+import Data.Word (Word64)
+import Haskoin.Address
+import Haskoin.Crypto.Hash (Hash256, addressHash)
+import Haskoin.Crypto.Keys.Common
+import Haskoin.Crypto.Signature
+import Haskoin.Network.Common
+import Haskoin.Network.Data
+import Haskoin.Script
+import Haskoin.Transaction.Builder.Sign (SigInput, buildInput, makeSignature, sigKeys)
+import Haskoin.Transaction.Builder.Sign qualified as Sign
+import Haskoin.Transaction.Common
+import Haskoin.Transaction.Segwit
+import Haskoin.Util
+
+-- | Any type can be used as a Coin if it can provide a value in Satoshi.
+-- The value is used in coin selection algorithms.
+class Coin c where
+  coinValue :: c -> Word64
+
+-- | Coin selection algorithm for normal (non-multisig) transactions. This
+-- function returns the selected coins together with the amount of change to
+-- send back to yourself, taking the fee into account.
+chooseCoins ::
+  (Coin c) =>
+  -- | value to send
+  Word64 ->
+  -- | fee per byte
+  Word64 ->
+  -- | number of outputs (including change)
+  Int ->
+  -- | try to find better solutions
+  Bool ->
+  -- | list of ordered coins to choose from
+  [c] ->
+  -- | coin selection and change
+  Either String ([c], Word64)
+chooseCoins target fee nOut continue coins =
+  runIdentity . runConduit $
+    sourceList coins .| chooseCoinsSink target fee nOut continue
+
+-- | Coin selection algorithm for normal (non-multisig) transactions. This
+-- function returns the selected coins together with the amount of change to
+-- send back to yourself, taking the fee into account. This version uses a Sink
+-- for conduit-based coin selection.
+chooseCoinsSink ::
+  (Monad m, Coin c) =>
+  -- | value to send
+  Word64 ->
+  -- | fee per byte
+  Word64 ->
+  -- | number of outputs (including change)
+  Int ->
+  -- | try to find better solution
+  Bool ->
+  -- | coin selection and change
+  ConduitT c Void m (Either String ([c], Word64))
+chooseCoinsSink target fee nOut continue
+  | target > 0 =
+      maybeToEither err
+        <$> greedyAddSink target (guessTxFee fee nOut) continue
+  | otherwise = return $ Left "chooseCoins: Target must be > 0"
+  where
+    err = "chooseCoins: No solution found"
+
+-- | Coin selection algorithm for multisig transactions. This function returns
+-- the selected coins together with the amount of change to send back to
+-- yourself, taking the fee into account. This function assumes all the coins
+-- are script hash outputs that send funds to a multisignature address.
+chooseMSCoins ::
+  (Coin c) =>
+  -- | value to send
+  Word64 ->
+  -- | fee per byte
+  Word64 ->
+  -- | m of n multisig
+  (Int, Int) ->
+  -- | number of outputs (including change)
+  Int ->
+  -- | try to find better solution
+  Bool ->
+  [c] ->
+  -- | coin selection change amount
+  Either String ([c], Word64)
+chooseMSCoins target fee ms nOut continue coins =
+  runIdentity . runConduit $
+    sourceList coins .| chooseMSCoinsSink target fee ms nOut continue
+
+-- | Coin selection algorithm for multisig transactions. This function returns
+-- the selected coins together with the amount of change to send back to
+-- yourself, taking the fee into account. This function assumes all the coins
+-- are script hash outputs that send funds to a multisignature address. This
+-- version uses a Sink if you need conduit-based coin selection.
+chooseMSCoinsSink ::
+  (Monad m, Coin c) =>
+  -- | value to send
+  Word64 ->
+  -- | fee per byte
+  Word64 ->
+  -- | m of n multisig
+  (Int, Int) ->
+  -- | number of outputs (including change)
+  Int ->
+  -- | try to find better solution
+  Bool ->
+  -- | coin selection and change
+  ConduitT c Void m (Either String ([c], Word64))
+chooseMSCoinsSink target fee ms nOut continue
+  | target > 0 =
+      maybeToEither err
+        <$> greedyAddSink target (guessMSTxFee fee ms nOut) continue
+  | otherwise = return $ Left "chooseMSCoins: Target must be > 0"
+  where
+    err = "chooseMSCoins: No solution found"
+
+-- | Select coins greedily by starting from an empty solution. If the 'continue'
+-- flag is set, the algorithm will try to find a better solution in the stream
+-- after a solution is found. If the next solution found is not strictly better
+-- than the previously found solution, the algorithm stops and returns the
+-- previous solution. If the continue flag is not set, the algorithm will return
+-- the first solution it finds in the stream.
+greedyAddSink ::
+  (Monad m, Coin c) =>
+  -- | value to send
+  Word64 ->
+  -- | coin count to fee function
+  (Int -> Word64) ->
+  -- | try to find better solutions
+  Bool ->
+  -- | coin selection and change
+  ConduitT c Void m (Maybe ([c], Word64))
+greedyAddSink target guessFee continue =
+  go [] 0 [] 0
+  where
+    -- The goal is the value we must reach (including the fee) for a certain
+    -- amount of selected coins.
+    goal c = target + guessFee c
+    go acc aTot ps pTot =
+      await >>= \case
+        -- A coin is available in the stream
+        Just coin -> do
+          let val = coinValue coin
+          -- We have reached the goal using this coin
+          if val + aTot >= goal (length acc + 1)
+            then -- If we want to continue searching for better solutions
+
+              if continue
+                then -- This solution is the first one or
+                -- This solution is better than the previous one
+
+                  if pTot == 0 || val + aTot < pTot
+                    then -- Continue searching for better solutions in the stream
+                      go [] 0 (coin : acc) (val + aTot)
+                    else -- Otherwise, we stop here and return the previous
+                    -- solution
+                      return $ Just (ps, pTot - goal (length ps))
+                else -- Otherwise, return this solution
+
+                  return $
+                    Just (coin : acc, val + aTot - goal (length acc + 1))
+            else -- We have not yet reached the goal. Add the coin to the
+            -- accumulator
+              go (coin : acc) (val + aTot) ps pTot
+        -- We reached the end of the stream
+        Nothing ->
+          return $
+            if null ps
+              then -- If no solution was found, return Nothing
+                Nothing
+              else -- If we have a solution, return it
+                Just (ps, pTot - goal (length ps))
+
+-- | Estimate tranasction fee to pay based on transaction size estimation.
+guessTxFee :: Word64 -> Int -> Int -> Word64
+guessTxFee byteFee nOut nIn =
+  byteFee * fromIntegral (guessTxSize nIn [] nOut 0)
+
+-- | Same as 'guessTxFee' but for multisig transactions.
+guessMSTxFee :: Word64 -> (Int, Int) -> Int -> Int -> Word64
+guessMSTxFee byteFee ms nOut nIn =
+  byteFee * fromIntegral (guessTxSize 0 (replicate nIn ms) nOut 0)
+
+-- | Computes an upper bound on the size of a transaction based on some known
+-- properties of the transaction.
+guessTxSize ::
+  -- | number of regular transaction inputs
+  Int ->
+  -- | multisig m of n for each input
+  [(Int, Int)] ->
+  -- | number of P2PKH outputs
+  Int ->
+  -- | number of P2SH outputs
+  Int ->
+  -- | upper bound on transaction size
+  Int
+guessTxSize pki msi pkout msout =
+  8 + inpLen + inp + outLen + out
+  where
+    inpLen =
+      B.length
+        . runPutS
+        . serialize
+        . VarInt
+        . fromIntegral
+        $ length msi + pki
+    outLen =
+      B.length
+        . runPutS
+        . serialize
+        . VarInt
+        . fromIntegral
+        $ pkout + msout
+    inp = pki * 148 + sum (map guessMSSize msi)
+    -- (20: hash160) + (5: opcodes) +
+    -- (1: script len) + (8: Word64)
+    out =
+      pkout * 34
+        +
+        -- (20: hash160) + (3: opcodes) +
+        -- (20: hash160) + (3: opcodes) +
+        -- (20: hash160) + (3: opcodes) +
+        -- (20: hash160) + (3: opcodes) +
+        -- (1: script len) + (8: Word64)
+        -- (1: script len) + (8: Word64)
+        -- (1: script len) + (8: Word64)
+        -- (1: script len) + (8: Word64)
+
+        -- (20: hash160) + (3: opcodes) +
+
+        -- (20: hash160) + (3: opcodes) +
+        -- (1: script len) + (8: Word64)
+        -- (1: script len) + (8: Word64)
+
+        -- (20: hash160) + (3: opcodes) +
+        -- (20: hash160) + (3: opcodes) +
+        -- (1: script len) + (8: Word64)
+        -- (1: script len) + (8: Word64)
+
+        -- (20: hash160) + (3: opcodes) +
+        -- (1: script len) + (8: Word64)
+        msout * 32
+
+-- | Size of a multisig P2SH input.
+guessMSSize :: (Int, Int) -> Int
+guessMSSize (m, n) =
+  -- OutPoint (36) + Sequence (4) + Script
+  40
+    + fromIntegral (B.length $ runPutS . serialize $ VarInt $ fromIntegral scp)
+    + scp
+  where
+    -- OP_M + n*PubKey + OP_N + OP_CHECKMULTISIG
+
+    rdm =
+      fromIntegral $
+        B.length $
+          runPutS $
+            serialize $
+              opPushData $
+                B.replicate (n * 34 + 3) 0
+    -- Redeem + m*sig + OP_0
+    scp = rdm + m * 73 + 1
+
+{- Build a new Tx -}
+
+-- | Build a transaction by providing a list of outpoints as inputs
+-- and a list of recipient addresses and amounts as outputs.
+buildAddrTx :: Network -> Ctx -> [OutPoint] -> [(Text, Word64)] -> Either String Tx
+buildAddrTx net ctx ops rcps =
+  buildTx ctx ops <$> mapM f rcps
+  where
+    f (aTxt, v) =
+      maybeToEither ("buildAddrTx: Invalid address " <> cs aTxt) $ do
+        a <- textToAddr net aTxt
+        let o = addressToOutput a
+        return (o, v)
+
+-- | Build a transaction by providing a list of outpoints as inputs
+-- and a list of 'ScriptOutput' and amounts as outputs.
+buildTx :: Ctx -> [OutPoint] -> [(ScriptOutput, Word64)] -> Tx
+buildTx ctx ops rcpts =
+  Tx 1 (toIn <$> ops) (toOut <$> rcpts) [] 0
+  where
+    toIn op = TxIn op B.empty maxBound
+    toOut (o, v) = TxOut v $ marshal ctx o
+
+-- | Sign a transaction by providing the 'SigInput' signing parameters and a
+-- list of private keys. The signature is computed deterministically as defined
+-- in RFC-6979.
+--
+-- Example: P2SH-P2WKH
+--
+-- > sigIn = SigInput (PayWitnessPKHash h) 100000 op sigHashAll Nothing
+-- > signedTx = signTx btc unsignedTx [sigIn] [key]
+--
+-- Example: P2SH-P2WSH multisig
+--
+-- > sigIn = SigInput (PayWitnessScriptHash h) 100000 op sigHashAll (Just $ PayMulSig [p1,p2,p3] 2)
+-- > signedTx = signTx btc unsignedTx [sigIn] [k1,k3]
+signTx ::
+  Network ->
+  Ctx ->
+  -- | transaction to sign
+  Tx ->
+  -- | signing parameters
+  [SigInput] ->
+  -- | private keys to sign with
+  [SecKey] ->
+  -- | signed transaction
+  Either String Tx
+signTx net ctx tx si = Sign.signTx net ctx tx $ notNested <$> si
+  where
+    notNested s = (s, False)
+
+-- | This function differs from 'signTx' by assuming all segwit inputs are
+-- P2SH-nested.  Use the same signing parameters for segwit inputs as in 'signTx'.
+signNestedWitnessTx ::
+  Network ->
+  Ctx ->
+  -- | transaction to sign
+  Tx ->
+  -- | signing parameters
+  [SigInput] ->
+  -- | private keys to sign with
+  [SecKey] ->
+  -- | signed transaction
+  Either String Tx
+signNestedWitnessTx net ctx tx si = Sign.signTx net ctx tx $ nested <$> si
+  where
+    -- NOTE: the nesting flag is ignored for non-segwit inputs
+    nested s = (s, True)
+
+-- | Sign a single input in a transaction deterministically (RFC-6979).
+signInput ::
+  Network -> Ctx -> Tx -> Int -> SigInput -> PrivateKey -> Either String Tx
+signInput net ctx tx i si =
+  Sign.signInput net ctx tx i (si, False)
+
+-- | Like 'signInput' but treat segwit inputs as nested
+signNestedInput ::
+  Network -> Ctx -> Tx -> Int -> SigInput -> PrivateKey -> Either String Tx
+signNestedInput net ctx tx i si =
+  Sign.signInput net ctx tx i (si, True)
+
+-- | Order the 'SigInput' with respect to the transaction inputs. This allows
+-- the user to provide the 'SigInput' in any order. Users can also provide only
+-- a partial set of 'SigInput' entries.
+findSigInput :: [SigInput] -> [TxIn] -> [(SigInput, Int)]
+findSigInput = Sign.findInputIndex (.outpoint)
+
+{- Merge multisig transactions -}
+
+-- | Merge partially-signed multisig transactions.  This function does not
+-- support segwit and P2SH-segwit inputs.  Use PSBTs to merge transactions with
+-- segwit inputs.
+mergeTxs ::
+  Network ->
+  Ctx ->
+  [Tx] ->
+  [(ScriptOutput, Word64, OutPoint)] ->
+  Either String Tx
+mergeTxs net ctx txs os
+  | null txs = Left "Transaction list is empty"
+  | length (nub emptyTxs) /= 1 = Left "Transactions do not match"
+  | length txs == 1 = return $ head txs
+  | otherwise = foldM (mergeTxInput net ctx txs) (head emptyTxs) outs
+  where
+    zipOp = zip (matchTemplate os (head txs).inputs f) [0 ..]
+    outs =
+      map (first $ (\(o, v, _) -> (o, v)) . fromJust) $
+        filter (isJust . fst) zipOp
+    f (_, _, o) txin = o == txin.outpoint
+    emptyTxs = map (\tx -> foldl clearInput tx outs) txs
+    ins is i = updateIndex i is (\TxIn {..} -> TxIn {script = B.empty, ..})
+    clearInput tx (_, i) =
+      Tx tx.version (ins tx.inputs i) tx.outputs [] tx.locktime
+
+-- | Merge input from partially-signed multisig transactions.  This function
+-- does not support segwit and P2SH-segwit inputs.
+mergeTxInput ::
+  Network ->
+  Ctx ->
+  [Tx] ->
+  Tx ->
+  ((ScriptOutput, Word64), Int) ->
+  Either String Tx
+mergeTxInput net ctx txs tx ((so, val), i) = do
+  -- Ignore transactions with empty inputs
+  let ins = map ((.script) . (!! i) . (.inputs)) txs
+  sigRes <- mapM extractSigs $ filter (not . B.null) ins
+  let rdm = snd $ head sigRes
+  unless (all ((== rdm) . snd) sigRes) $ Left "Redeem scripts do not match"
+  si <- marshal (net, ctx) <$> go (nub $ concatMap fst sigRes) so rdm
+  let ins' = updateIndex i tx.inputs (\TxIn {..} -> TxIn {script = si, ..})
+  return $ Tx tx.version ins' tx.outputs [] tx.locktime
+  where
+    go allSigs out rdmM =
+      case out of
+        PayMulSig msPubs r ->
+          let sigs =
+                take r $
+                  catMaybes $
+                    matchTemplate allSigs msPubs $
+                      f out
+           in return $ RegularInput $ SpendMulSig sigs
+        PayScriptHash _ ->
+          case rdmM of
+            Just rdm -> do
+              si <- go allSigs rdm Nothing
+              return $ ScriptHashInput si.get rdm
+            _ -> Left "Invalid output script type"
+        _ -> Left "Invalid output script type"
+    extractSigs si =
+      case unmarshal (net, ctx) si of
+        Right (RegularInput (SpendMulSig sigs)) ->
+          Right (sigs, Nothing)
+        Right (ScriptHashInput (SpendMulSig sigs) rdm) ->
+          Right (sigs, Just rdm)
+        _ -> Left "Invalid script input type"
+    f out (TxSignature x sh) p =
+      verifyHashSig
+        ctx
+        (txSigHash net tx (encodeOutput ctx out) val i sh)
+        x
+        p.point
+    f _ TxSignatureEmpty _ = False
+
+{- Tx verification -}
+
+-- | Verify if a transaction is valid and all of its inputs are standard.
+verifyStdTx ::
+  Network -> Ctx -> Tx -> [(ScriptOutput, Word64, OutPoint)] -> Bool
+verifyStdTx net ctx tx xs =
+  not (null tx.inputs) && all go (zip (matchTemplate xs tx.inputs f) [0 ..])
+  where
+    f (_, _, o) txin = o == txin.outpoint
+    go (Just (so, val, _), i) = verifyStdInput net ctx tx i so val
+    go _ = False
+
+-- | Verify if a transaction input is valid and standard.
+verifyStdInput :: Network -> Ctx -> Tx -> Int -> ScriptOutput -> Word64 -> Bool
+verifyStdInput net ctx tx i so0 val
+  | isSegwit so0 =
+      fromRight False $ (inp == mempty &&) . verifySegwitInput so0 <$> wp so0
+  | otherwise =
+      or $
+        rights
+          [ verifyLegacyInput so0 <$> unmarshal (net, ctx) inp,
+            nestedScriptOutput >>= \so -> verifyNestedInput so0 so <$> wp so
+          ]
+  where
+    inp = (tx.inputs !! i).script
+    theTxSigHash so = Sign.makeSigHash net ctx tx i so val
+
+    ws :: WitnessStack
+    ws
+      | length tx.witness > i = tx.witness !! i
+      | otherwise = []
+
+    wp :: ScriptOutput -> Either String (Maybe ScriptOutput, SimpleInput)
+    wp so = decodeWitnessInput net ctx =<< viewWitnessProgram net ctx so ws
+
+    nestedScriptOutput :: Either String ScriptOutput
+    nestedScriptOutput =
+      runGetS deserialize inp >>= dec . ops
+      where
+        ops (Script ops') = ops'
+        dec = \case
+          [OP_PUSHDATA bs _] -> unmarshal ctx bs
+          _ -> Left "nestedScriptOutput: not a nested output"
+
+    verifyLegacyInput :: ScriptOutput -> ScriptInput -> Bool
+    verifyLegacyInput so si = case (so, si) of
+      (PayPK pub, RegularInput (SpendPK (TxSignature sig sh))) ->
+        verifyHashSig ctx (theTxSigHash so sh Nothing) sig pub.point
+      (PayPKHash h, RegularInput (SpendPKHash (TxSignature sig sh) pub)) ->
+        pubKeyAddr ctx pub == p2pkhAddr h
+          && verifyHashSig ctx (theTxSigHash so sh Nothing) sig pub.point
+      (PayMulSig pubs r, RegularInput (SpendMulSig sigs)) ->
+        countMulSig net ctx tx out val i ((.point) <$> pubs) sigs == r
+      (PayScriptHash h, ScriptHashInput si' rdm) ->
+        payToScriptAddress ctx rdm == p2shAddr h && verifyLegacyInput rdm (RegularInput si')
+      _ -> False
+      where
+        out = encodeOutput ctx so
+
+    verifySegwitInput ::
+      ScriptOutput -> (Maybe ScriptOutput, SimpleInput) -> Bool
+    verifySegwitInput so (rdm, si) = case (so, rdm, si) of
+      ( PayWitnessPKHash h,
+        Nothing,
+        SpendPKHash (TxSignature sig sh) pub
+        ) ->
+          let keytest = pubKeyWitnessAddr ctx pub == p2wpkhAddr h
+              sighash = theTxSigHash so sh Nothing
+              pkpoint = pub.point
+              verify = verifyHashSig ctx sighash sig pkpoint
+           in keytest && verify
+      ( PayWitnessScriptHash h,
+        Just rdm'@(PayPK pub),
+        SpendPK (TxSignature sig sh)
+        ) ->
+          let keytest = payToWitnessScriptAddress ctx rdm' == p2wshAddr h
+              sighash = theTxSigHash so sh $ Just rdm'
+              pkpoint = pub.point
+              verify = verifyHashSig ctx sighash sig pkpoint
+           in keytest && verify
+      ( PayWitnessScriptHash h,
+        Just rdm'@(PayPKHash kh),
+        SpendPKHash (TxSignature sig sh) pub
+        ) ->
+          let keytest = payToWitnessScriptAddress ctx rdm' == p2wshAddr h
+              addrtest = addressHash (marshal ctx pub) == kh
+              pkpoint = pub.point
+              sighash = theTxSigHash so sh $ Just rdm'
+              verify = verifyHashSig ctx sighash sig pkpoint
+           in keytest && addrtest && verify
+      ( PayWitnessScriptHash h,
+        Just rdm'@(PayMulSig pubs r),
+        SpendMulSig sigs
+        ) ->
+          let keytest = payToWitnessScriptAddress ctx rdm' == p2wshAddr h
+              pkpoints = (.point) <$> pubs
+              hashfun sh = theTxSigHash so sh $ Just rdm'
+              verify = countMulSig' ctx hashfun pkpoints sigs == r
+           in keytest && verify
+      _ -> False
+
+    verifyNestedInput ::
+      ScriptOutput -> ScriptOutput -> (Maybe RedeemScript, SimpleInput) -> Bool
+    verifyNestedInput so so' x = case so of
+      PayScriptHash h -> payToScriptAddress ctx so' == p2shAddr h && verifySegwitInput so' x
+      _ -> False
+
+-- | Count the number of valid signatures for a multi-signature transaction.
+countMulSig ::
+  Network ->
+  Ctx ->
+  Tx ->
+  Script ->
+  Word64 ->
+  Int ->
+  [PubKey] ->
+  [TxSignature] ->
+  Int
+countMulSig net ctx tx out val i =
+  countMulSig' ctx h
+  where
+    h = txSigHash net tx out val i
+
+countMulSig' :: Ctx -> (SigHash -> Hash256) -> [PubKey] -> [TxSignature] -> Int
+countMulSig' _ _ [] _ = 0
+countMulSig' _ _ _ [] = 0
+countMulSig' ctx h (_ : pubs) (TxSignatureEmpty : sigs) =
+  countMulSig' ctx h pubs sigs
+countMulSig' ctx h (pub : pubs) sigs@(TxSignature sig sh : sigs')
+  | verifyHashSig ctx (h sh) sig pub = 1 + countMulSig' ctx h pubs sigs'
+  | otherwise = countMulSig' ctx h pubs sigs
diff --git a/src/Haskoin/Transaction/Builder/Sign.hs b/src/Haskoin/Transaction/Builder/Sign.hs
new file mode 100644
--- /dev/null
+++ b/src/Haskoin/Transaction/Builder/Sign.hs
@@ -0,0 +1,322 @@
+{-# LANGUAGE DeriveAnyClass #-}
+{-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE DuplicateRecordFields #-}
+{-# LANGUAGE FlexibleContexts #-}
+{-# LANGUAGE LambdaCase #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE OverloadedRecordDot #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE RecordWildCards #-}
+{-# LANGUAGE NoFieldSelectors #-}
+
+-- |
+-- Module      : Haskoin.Transaction.Builder.Sign
+-- Copyright   : No rights reserved
+-- License     : MIT
+-- Maintainer  : jprupp@protonmail.ch
+-- Stability   : experimental
+-- Portability : POSIX
+--
+-- Types and logic for signing transactions.
+module Haskoin.Transaction.Builder.Sign
+  ( SigInput (..),
+    makeSignature,
+    makeSigHash,
+    signTx,
+    findInputIndex,
+    signInput,
+    buildInput,
+    sigKeys,
+  )
+where
+
+import Control.DeepSeq
+import Control.Monad
+import Crypto.Secp256k1
+import Data.Aeson
+import Data.Aeson.Encoding
+import Data.Bytes.Get
+import Data.Bytes.Put
+import Data.Bytes.Serial
+import Data.Either
+import Data.Hashable
+import Data.List
+import Data.Maybe
+import Data.Word
+import GHC.Generics
+import Haskoin.Address
+import Haskoin.Crypto.Hash
+import Haskoin.Crypto.Keys.Common
+import Haskoin.Crypto.Signature
+import Haskoin.Network.Data
+import Haskoin.Script
+import Haskoin.Transaction.Common
+import Haskoin.Transaction.Segwit
+import Haskoin.Util
+
+-- | Data type used to specify the signing parameters of a transaction input.
+-- To sign an input, the previous output script, outpoint and sighash are
+-- required. When signing a pay to script hash output, an additional redeem
+-- script is required.
+data SigInput = SigInput
+  { -- | output script to spend
+    -- ^ output script value
+    script :: !ScriptOutput,
+    -- | output script value
+    -- ^ outpoint to spend
+    value :: !Word64,
+    -- | outpoint to spend
+    -- ^ signature type
+    outpoint :: !OutPoint,
+    -- | signature type
+    -- ^ redeem script
+    sighash :: !SigHash,
+    -- | redeem script
+    redeem :: !(Maybe RedeemScript)
+  }
+  deriving (Show, Read, Eq, Generic, NFData)
+
+instance MarshalJSON Ctx SigInput where
+  marshalValue ctx (SigInput s v o h r) =
+    object $
+      [ "pkscript" .= marshalValue ctx s,
+        "value" .= v,
+        "outpoint" .= o,
+        "sighash" .= h
+      ]
+        ++ [ "redeem" .= marshalValue ctx r
+             | r <- maybeToList r
+           ]
+
+  marshalEncoding ctx (SigInput s v o h r) =
+    pairs $
+      mconcat
+        [ "pkscript" `pair` marshalEncoding ctx s,
+          "value" `pair` word64 v,
+          "outpoint" `pair` toEncoding o,
+          "sighash" `pair` toEncoding h,
+          maybe mempty (pair "redeem" . marshalEncoding ctx) r
+        ]
+
+  unmarshalValue ctx =
+    withObject "SigInput" $ \o ->
+      SigInput
+        <$> (unmarshalValue ctx =<< o .: "pkscript")
+        <*> o .: "value"
+        <*> o .: "outpoint"
+        <*> o .: "sighash"
+        <*> (mapM (unmarshalValue ctx) =<< o .:? "redeem")
+
+-- | Sign a transaction by providing the 'SigInput' signing parameters and a
+-- list of private keys. The signature is computed deterministically as defined
+-- in RFC-6979.
+signTx ::
+  Network ->
+  Ctx ->
+  -- | transaction to sign
+  Tx ->
+  -- | signing parameters, with nesting flag
+  [(SigInput, Bool)] ->
+  -- | private keys to sign with
+  [SecKey] ->
+  -- | signed transaction
+  Either String Tx
+signTx net ctx otx sigis allKeys
+  | null ti = Left "signTx: Transaction has no inputs"
+  | otherwise = foldM go otx $ findInputIndex ((.outpoint) . fst) sigis ti
+  where
+    ti = otx.inputs
+    go tx (sigi@(SigInput so _ _ _ rdmM, _), i) = do
+      keys <- sigKeys ctx so rdmM allKeys
+      foldM (\t k -> signInput net ctx t i sigi k) tx keys
+
+-- | Sign a single input in a transaction deterministically (RFC-6979).  The
+-- nesting flag only affects the behavior of segwit inputs.
+signInput ::
+  Network ->
+  Ctx ->
+  Tx ->
+  Int ->
+  -- | boolean flag: nest input
+  (SigInput, Bool) ->
+  PrivateKey ->
+  Either String Tx
+signInput net ctx tx i (sigIn@(SigInput so val _ _ rdmM), nest) key = do
+  let sig = makeSignature net ctx tx i sigIn key
+  si <- buildInput net ctx tx i so val rdmM sig $ derivePublicKey ctx key
+  w <- updatedWitnessData net ctx tx i so si
+  return tx {inputs = nextTxIn so si, witness = w}
+  where
+    f si TxIn {..} = TxIn {script = marshal (net, ctx) si, ..}
+    g so' TxIn {..} = TxIn {script = pkScript so', ..}
+    pkScript so' = runPutS . serialize . opPushData $ marshal ctx so'
+    nextTxIn so' si
+      | isSegwit so' && nest = updateIndex i tx.inputs (g so')
+      | isSegwit so' = tx.inputs
+      | otherwise = updateIndex i tx.inputs (f si)
+
+-- | Add the witness data of the transaction given segwit parameters for an input.
+--
+-- @since 0.11.0.0
+updatedWitnessData ::
+  Network ->
+  Ctx ->
+  Tx ->
+  Int ->
+  ScriptOutput ->
+  ScriptInput ->
+  Either String WitnessData
+updatedWitnessData net ctx tx i so si
+  | isSegwit so =
+      updateWitness . toWitnessStack net ctx =<< calcWitnessProgram net ctx so si
+  | otherwise =
+      return tx.witness
+  where
+    updateWitness w
+      | null tx.witness = return $ updateIndex i defaultStack (const w)
+      | length tx.witness /= n = Left "Invalid number of witness stacks"
+      | otherwise = return $ updateIndex i tx.witness (const w)
+    defaultStack = replicate n $ toWitnessStack net ctx EmptyWitnessProgram
+    n = length tx.inputs
+
+-- | Associate an input index to each value in a list
+findInputIndex ::
+  -- | extract an outpoint
+  (a -> OutPoint) ->
+  -- | input list
+  [a] ->
+  -- | reference list of inputs
+  [TxIn] ->
+  [(a, Int)]
+findInputIndex getOutPoint as ti =
+  mapMaybe g $ zip (matchTemplate as ti f) [0 ..]
+  where
+    f s txin = getOutPoint s == txin.outpoint
+    g (Just s, i) = Just (s, i)
+    g (Nothing, _) = Nothing
+
+-- | Find from the list of provided private keys which one is required to sign
+-- the 'ScriptOutput'.
+sigKeys ::
+  Ctx ->
+  ScriptOutput ->
+  Maybe RedeemScript ->
+  [SecKey] ->
+  Either String [PrivateKey]
+sigKeys ctx so rdmM keys =
+  case (so, rdmM) of
+    (PayPK p, Nothing) ->
+      return . map fst . maybeToList $ find ((== p) . snd) zipKeys
+    (PayPKHash h, Nothing) -> return $ keyByHash h
+    (PayMulSig ps r, Nothing) ->
+      return $ map fst $ take r $ filter ((`elem` ps) . snd) zipKeys
+    (PayScriptHash _, Just rdm) -> sigKeys ctx rdm Nothing keys
+    (PayWitnessPKHash h, _) -> return $ keyByHash h
+    (PayWitnessScriptHash _, Just rdm) -> sigKeys ctx rdm Nothing keys
+    _ -> Left "sigKeys: Could not decode output script"
+  where
+    zipKeys =
+      [ (prv, pub)
+        | k <- keys,
+          t <- [True, False],
+          let prv = wrapSecKey t k,
+          let pub = derivePublicKey ctx prv
+      ]
+    keyByHash h = fmap fst . maybeToList . findKey h $ zipKeys
+    findKey h = find $ (== h) . (.hash160) . pubKeyAddr ctx . snd
+
+-- | Construct an input for a transaction given a signature, public key and data
+-- about the previous output.
+buildInput ::
+  Network ->
+  Ctx ->
+  -- | transaction where input will be added
+  Tx ->
+  -- | input index where signature will go
+  Int ->
+  -- | output script being spent
+  ScriptOutput ->
+  -- | amount of previous output
+  Word64 ->
+  -- | redeem script if pay-to-script-hash
+  Maybe RedeemScript ->
+  TxSignature ->
+  PublicKey ->
+  Either String ScriptInput
+buildInput net ctx tx i so val rdmM sig pub = do
+  when (i >= length tx.inputs) $ Left "buildInput: Invalid input index"
+  case (so, rdmM) of
+    (PayScriptHash _, Just rdm) ->
+      buildScriptHashInput rdm
+    (PayWitnessScriptHash _, Just rdm) ->
+      buildScriptHashInput rdm
+    (PayWitnessPKHash _, Nothing) ->
+      return . RegularInput $ SpendPKHash sig pub
+    (_, Nothing) ->
+      buildRegularInput so
+    _ -> Left "buildInput: Invalid output/redeem script combination"
+  where
+    buildRegularInput = \case
+      PayPK _ -> return $ RegularInput $ SpendPK sig
+      PayPKHash _ -> return $ RegularInput $ SpendPKHash sig pub
+      PayMulSig msPubs r -> do
+        let mSigs = take r $ catMaybes $ matchTemplate allSigs msPubs f
+            allSigs = nub $ sig : parseExistingSigs net ctx tx so i
+        return $ RegularInput $ SpendMulSig mSigs
+      _ -> Left "buildInput: Invalid output/redeem script combination"
+    buildScriptHashInput rdm = do
+      inp <- buildRegularInput rdm
+      return $ ScriptHashInput inp.get rdm
+    f (TxSignature x sh) p =
+      verifyHashSig
+        ctx
+        (makeSigHash net ctx tx i so val sh rdmM)
+        x
+        p.point
+    f TxSignatureEmpty _ = False
+
+-- | Apply heuristics to extract the signatures for a particular input that are
+-- embedded in the transaction.
+--
+-- @since 0.11.0.0
+parseExistingSigs :: Network -> Ctx -> Tx -> ScriptOutput -> Int -> [TxSignature]
+parseExistingSigs net ctx tx so i = insSigs <> witSigs
+  where
+    insSigs = case unmarshal (net, ctx) scp of
+      Right (ScriptHashInput (SpendMulSig xs) _) -> xs
+      Right (RegularInput (SpendMulSig xs)) -> xs
+      _ -> []
+    scp = (tx.inputs !! i).script
+    witSigs
+      | not $ isSegwit so = []
+      | null tx.witness = []
+      | otherwise = rights $ decodeTxSig net ctx <$> (tx.witness !! i)
+
+-- | Produce a structured representation of a deterministic (RFC-6979) signature over an input.
+makeSignature :: Network -> Ctx -> Tx -> Int -> SigInput -> PrivateKey -> TxSignature
+makeSignature net ctx tx i (SigInput so val _ sh rdmM) key =
+  TxSignature (signHash ctx key.key m) sh
+  where
+    m = makeSigHash net ctx tx i so val sh rdmM
+
+-- | A function which selects the digest algorithm and parameters as appropriate
+--
+-- @since 0.11.0.0
+makeSigHash ::
+  Network ->
+  Ctx ->
+  Tx ->
+  Int ->
+  ScriptOutput ->
+  Word64 ->
+  SigHash ->
+  Maybe RedeemScript ->
+  Hash256
+makeSigHash net ctx tx i so val sh rdmM = h net tx (encodeOutput ctx so') val i sh
+  where
+    so' = case so of
+      PayWitnessPKHash h' -> PayPKHash h'
+      _ -> fromMaybe so rdmM
+    h
+      | isSegwit so = txSigHashForkId
+      | otherwise = txSigHash
diff --git a/src/Haskoin/Transaction/Common.hs b/src/Haskoin/Transaction/Common.hs
new file mode 100644
--- /dev/null
+++ b/src/Haskoin/Transaction/Common.hs
@@ -0,0 +1,429 @@
+{-# LANGUAGE DeriveAnyClass #-}
+{-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE DerivingStrategies #-}
+{-# LANGUAGE DuplicateRecordFields #-}
+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
+{-# LANGUAGE ImportQualifiedPost #-}
+{-# LANGUAGE OverloadedRecordDot #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE RecordWildCards #-}
+{-# LANGUAGE NoFieldSelectors #-}
+
+-- |
+-- Module      : Haskoin.Transaction.Common
+-- Copyright   : No rights reserved
+-- License     : MIT
+-- Maintainer  : jprupp@protonmail.ch
+-- Stability   : experimental
+-- Portability : POSIX
+--
+-- Code related to transactions parsing and serialization.
+module Haskoin.Transaction.Common
+  ( -- * Transactions
+    Tx (..),
+    TxIn (..),
+    TxOut (..),
+    OutPoint (..),
+    TxHash (..),
+    WitnessData,
+    WitnessStack,
+    WitnessStackItem,
+    txHash,
+    hexToTxHash,
+    txHashToHex,
+    nosigTxHash,
+    nullOutPoint,
+  )
+where
+
+import Control.Applicative ((<|>))
+import Control.DeepSeq
+import Control.Monad (forM_, guard, liftM2, mzero, replicateM, unless, when, (<=<))
+import Data.Aeson as A
+import Data.Aeson.Encoding qualified as E
+import Data.Binary (Binary (..))
+import Data.Bool (bool)
+import Data.ByteString (ByteString)
+import Data.ByteString qualified as B
+import Data.ByteString.Builder (char7)
+import Data.ByteString.Lazy qualified as BL
+import Data.Bytes.Get
+import Data.Bytes.Put
+import Data.Bytes.Serial
+import Data.Hashable (Hashable)
+import Data.Maybe (fromMaybe)
+import Data.Serialize (Serialize (..))
+import Data.String (IsString, fromString)
+import Data.String.Conversions (cs)
+import Data.Text (Text)
+import Data.Word (Word32, Word64)
+import GHC.Generics (Generic)
+import Haskoin.Crypto.Hash
+import Haskoin.Network.Common
+import Haskoin.Util.Helpers
+import Text.Read as R
+
+-- | Transaction id: hash of transaction excluding witness data.
+newtype TxHash = TxHash {get :: Hash256}
+  deriving (Eq, Ord, Generic)
+  deriving newtype (Hashable, NFData)
+
+instance Serial TxHash where
+  serialize (TxHash h) = serialize h
+  deserialize = TxHash <$> deserialize
+
+instance Serialize TxHash where
+  put = serialize
+  get = deserialize
+
+instance Binary TxHash where
+  put = serialize
+  get = deserialize
+
+instance Show TxHash where
+  showsPrec _ = shows . txHashToHex
+
+instance Read TxHash where
+  readPrec = do
+    R.String str <- R.lexP
+    maybe R.pfail return $ hexToTxHash $ cs str
+
+instance IsString TxHash where
+  fromString s =
+    let e = error "Could not read transaction hash from hex string"
+     in fromMaybe e $ hexToTxHash $ cs s
+
+instance FromJSON TxHash where
+  parseJSON =
+    withText "txid" $
+      maybe mzero return . hexToTxHash
+
+instance ToJSON TxHash where
+  toJSON = A.String . txHashToHex
+  toEncoding = hexEncoding . BL.reverse . runPutL . serialize
+
+-- | Transaction hash excluding signatures.
+nosigTxHash :: Tx -> TxHash
+nosigTxHash Tx {..} =
+  TxHash . doubleSHA256 . runPutS $ serialize tx
+  where
+    tx = Tx {inputs = map clr inputs, ..}
+    clr TxIn {..} = TxIn {script = B.empty, ..}
+
+-- | Convert transaction hash to hex form, reversing bytes.
+txHashToHex :: TxHash -> Text
+txHashToHex (TxHash h) =
+  encodeHex . B.reverse . runPutS $ serialize h
+
+-- | Convert transaction hash from hex, reversing bytes.
+hexToTxHash :: Text -> Maybe TxHash
+hexToTxHash hex = do
+  bs <- B.reverse <$> decodeHex hex
+  h <- either (const Nothing) Just (runGetS deserialize bs)
+  return $ TxHash h
+
+-- | Witness stack for SegWit transactions.
+type WitnessData = [WitnessStack]
+
+-- | Witness stack for SegWit transactions.
+type WitnessStack = [WitnessStackItem]
+
+-- | Witness stack item for SegWit transactions.
+type WitnessStackItem = ByteString
+
+-- | Data type representing a transaction.
+data Tx = Tx
+  { -- | transaction data format version
+    version :: !Word32,
+    -- | list of transaction inputs
+    inputs :: ![TxIn],
+    -- | list of transaction outputs
+    outputs :: ![TxOut],
+    -- | witness data for the transaction
+    witness :: !WitnessData,
+    -- | earliest mining height or time
+    locktime :: !Word32
+  }
+  deriving (Show, Read, Eq, Ord, Generic, Hashable, NFData)
+
+-- | Compute transaction hash.
+txHash :: Tx -> TxHash
+txHash tx = TxHash . doubleSHA256 . runPutS $ serialize tx {witness = []}
+
+instance IsString Tx where
+  fromString =
+    fromMaybe e . (eitherToMaybe . runGetS deserialize <=< decodeHex) . cs
+    where
+      e = error "Could not read transaction from hex string"
+
+instance Serial Tx where
+  deserialize = isWitnessTx >>= bool parseLegacyTx parseWitnessTx
+  serialize tx
+    | null tx.witness = putLegacyTx tx
+    | otherwise = putWitnessTx tx
+
+instance Binary Tx where
+  put = serialize
+  get = deserialize
+
+instance Serialize Tx where
+  put = serialize
+  get = deserialize
+
+putInOut :: (MonadPut m) => Tx -> m ()
+putInOut tx = do
+  putVarInt $ length tx.inputs
+  mapM_ serialize tx.inputs
+  putVarInt $ length tx.outputs
+  mapM_ serialize tx.outputs
+
+-- | Non-SegWit transaction serializer.
+putLegacyTx :: (MonadPut m) => Tx -> m ()
+putLegacyTx tx = do
+  putWord32le tx.version
+  putInOut tx
+  putWord32le tx.locktime
+
+-- | Witness transaciton serializer.
+putWitnessTx :: (MonadPut m) => Tx -> m ()
+putWitnessTx tx = do
+  putWord32le tx.version
+  putWord8 0x00
+  putWord8 0x01
+  putInOut tx
+  putWitnessData tx.witness
+  putWord32le tx.locktime
+
+isWitnessTx :: (MonadGet m) => m Bool
+isWitnessTx = lookAhead $ do
+  _ <- getWord32le
+  m <- getWord8
+  f <- getWord8
+  return (m == 0x00 && f == 0x01)
+
+-- | Non-SegWit transaction deseralizer.
+parseLegacyTx :: (MonadGet m) => m Tx
+parseLegacyTx = do
+  version <- getWord32le
+  inputs <- rl =<< deserialize
+  outputs <- rl =<< deserialize
+  when (length inputs == 0x00 && length outputs == 0x01) $
+    fail "Witness transaction"
+  locktime <- getWord32le
+  return Tx {witness = [], ..}
+  where
+    rl (VarInt c) = replicateM (fromIntegral c) deserialize
+
+-- | Witness transaction deserializer.
+parseWitnessTx :: (MonadGet m) => m Tx
+parseWitnessTx = do
+  version <- getWord32le
+  m <- getWord8
+  f <- getWord8
+  unless (m == 0x00 && f == 0x01) $ fail "Not a witness transaction"
+  inputs <- replicateList =<< deserialize
+  outputs <- replicateList =<< deserialize
+  witness <- parseWitnessData $ length inputs
+  locktime <- getWord32le
+  return Tx {..}
+  where
+    replicateList (VarInt c) = replicateM (fromIntegral c) deserialize
+
+-- | Witness data deserializer. Requires count of inputs.
+parseWitnessData :: (MonadGet m) => Int -> m WitnessData
+parseWitnessData n = replicateM n parseWitnessStack
+  where
+    parseWitnessStack = do
+      VarInt i <- deserialize
+      replicateM (fromIntegral i) parseWitnessStackItem
+    parseWitnessStackItem = do
+      VarInt i <- deserialize
+      getByteString $ fromIntegral i
+
+-- | Witness data serializer.
+putWitnessData :: (MonadPut m) => WitnessData -> m ()
+putWitnessData = mapM_ putWitnessStack
+  where
+    putWitnessStack ws = do
+      putVarInt $ length ws
+      mapM_ putWitnessStackItem ws
+    putWitnessStackItem bs = do
+      putVarInt $ B.length bs
+      putByteString bs
+
+instance FromJSON Tx where
+  parseJSON = withObject "Tx" $ \o ->
+    Tx
+      <$> o .: "version"
+      <*> o .: "inputs"
+      <*> o .: "outputs"
+      <*> (mapM (mapM f) =<< o .: "witnessdata")
+      <*> o .: "locktime"
+    where
+      f = maybe mzero return . decodeHex
+
+instance ToJSON Tx where
+  toJSON (Tx v i o w l) =
+    object
+      [ "version" .= v,
+        "inputs" .= i,
+        "outputs" .= o,
+        "witnessdata" .= fmap (fmap encodeHex) w,
+        "locktime" .= l
+      ]
+  toEncoding (Tx v i o w l) =
+    pairs $
+      mconcat
+        [ "version" `E.pair` E.word32 v,
+          "inputs" `E.pair` E.list toEncoding i,
+          "outputs" `E.pair` E.list toEncoding o,
+          "witnessdata" `E.pair` E.list (E.list f) w,
+          "locktime" `E.pair` E.word32 l
+        ]
+    where
+      f = hexEncoding . BL.fromStrict
+
+-- | Data type representing a transaction input.
+data TxIn = TxIn
+  { -- | output being spent
+    outpoint :: !OutPoint,
+    -- | signatures and redeem script
+    script :: !ByteString,
+    -- | lock-time using sequence numbers (BIP-68)
+    sequence :: !Word32
+  }
+  deriving (Eq, Show, Read, Ord, Generic, Hashable, NFData)
+
+instance Serial TxIn where
+  deserialize =
+    TxIn <$> deserialize <*> (readBS =<< deserialize) <*> getWord32le
+    where
+      readBS (VarInt len) = getByteString $ fromIntegral len
+
+  serialize (TxIn o s q) = do
+    serialize o
+    putVarInt $ B.length s
+    putByteString s
+    putWord32le q
+
+instance Binary TxIn where
+  get = deserialize
+  put = serialize
+
+instance Serialize TxIn where
+  get = deserialize
+  put = serialize
+
+instance FromJSON TxIn where
+  parseJSON =
+    withObject "TxIn" $ \o ->
+      TxIn
+        <$> o .: "prevoutput"
+        <*> (maybe mzero return . decodeHex =<< o .: "inputscript")
+        <*> o .: "sequence"
+
+instance ToJSON TxIn where
+  toJSON (TxIn o s q) =
+    object
+      [ "prevoutput" .= o,
+        "inputscript" .= encodeHex s,
+        "sequence" .= q
+      ]
+  toEncoding (TxIn o s q) =
+    pairs $
+      mconcat
+        [ "prevoutput" `E.pair` toEncoding o,
+          "inputscript" `E.pair` hexEncoding (BL.fromStrict s),
+          "sequence" `E.pair` E.word32 q
+        ]
+
+-- | Data type representing a transaction output.
+data TxOut = TxOut
+  { -- | value of output is satoshi
+    value :: !Word64,
+    -- | pubkey script
+    script :: !ByteString
+  }
+  deriving (Eq, Show, Read, Ord, Generic, Hashable, NFData)
+
+instance Serial TxOut where
+  deserialize = do
+    val <- getWord64le
+    VarInt len <- deserialize
+    TxOut val <$> getByteString (fromIntegral len)
+
+  serialize (TxOut o s) = do
+    putWord64le o
+    putVarInt $ B.length s
+    putByteString s
+
+instance Binary TxOut where
+  put = serialize
+  get = deserialize
+
+instance Serialize TxOut where
+  put = serialize
+  get = deserialize
+
+instance FromJSON TxOut where
+  parseJSON =
+    withObject "TxOut" $ \o -> do
+      value <- o .: "value"
+      t <- o .: "outputscript"
+      script <- maybe mzero return (decodeHex t)
+      return TxOut {..}
+
+instance ToJSON TxOut where
+  toJSON (TxOut o s) =
+    object ["value" .= o, "outputscript" .= encodeHex s]
+  toEncoding (TxOut o s) =
+    pairs $
+      mconcat
+        [ "value" `E.pair` E.word64 o,
+          "outputscript" `E.pair` hexEncoding (BL.fromStrict s)
+        ]
+
+-- | The 'OutPoint' refers to a transaction output being spent.
+data OutPoint = OutPoint
+  { -- | hash of previous transaction
+    hash :: !TxHash,
+    -- | position of output in previous transaction
+    index :: !Word32
+  }
+  deriving (Show, Read, Eq, Ord, Generic, Hashable, NFData)
+
+instance Serial OutPoint where
+  deserialize = do
+    (h, i) <- liftM2 (,) deserialize getWord32le
+    return $ OutPoint h i
+  serialize (OutPoint h i) = serialize h >> putWord32le i
+
+instance Binary OutPoint where
+  put = serialize
+  get = deserialize
+
+instance Serialize OutPoint where
+  put = serialize
+  get = deserialize
+
+instance FromJSON OutPoint where
+  parseJSON =
+    withObject "OutPoint" $ \o ->
+      OutPoint <$> o .: "txid" <*> o .: "index"
+
+instance ToJSON OutPoint where
+  toJSON (OutPoint h i) = object ["txid" .= h, "index" .= i]
+  toEncoding (OutPoint h i) =
+    pairs $
+      mconcat
+        [ "txid" `E.pair` toEncoding h,
+          "index" `E.pair` E.word32 i
+        ]
+
+-- | Outpoint used in coinbase transactions.
+nullOutPoint :: OutPoint
+nullOutPoint =
+  OutPoint
+    { hash = "0000000000000000000000000000000000000000000000000000000000000000",
+      index = maxBound
+    }
diff --git a/src/Haskoin/Transaction/Genesis.hs b/src/Haskoin/Transaction/Genesis.hs
new file mode 100644
--- /dev/null
+++ b/src/Haskoin/Transaction/Genesis.hs
@@ -0,0 +1,42 @@
+{-# LANGUAGE OverloadedStrings #-}
+
+-- |
+-- Module      : Haskoin.Transaction.Genesis
+-- Copyright   : No rights reserved
+-- License     : UNLICENSE
+-- Maintainer  : jprupp@protonmail.ch
+-- Stability   : experimental
+-- Portability : POSIX
+--
+-- Code related to transactions parsing and serialization.
+module Haskoin.Transaction.Genesis (genesisTx) where
+
+import Crypto.Secp256k1 (Ctx, importPubKey)
+import Data.Bytes.Get (runGetS)
+import Haskoin.Crypto.Keys.Common
+import Haskoin.Script.Standard
+import Haskoin.Transaction.Common
+import Haskoin.Util
+
+-- | Transaction from Genesis block.
+genesisTx :: Ctx -> Tx
+genesisTx ctx =
+  Tx 1 [txin] [txout] [] locktime
+  where
+    txin = TxIn outpoint inputBS maxBound
+    txout = TxOut 5000000000 (marshal ctx output)
+    locktime = 0
+    outpoint = OutPoint z maxBound
+    Just inputBS =
+      decodeHex
+        "04ffff001d0104455468652054696d65732030332f4a616e2f323030392043686\
+        \16e63656c6c6f72206f6e206272696e6b206f66207365636f6e64206261696c6f\
+        \757420666f722062616e6b73"
+    Just pubKeyBS =
+      decodeHex
+        "04678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb\
+        \649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5f"
+    Right pubKey =
+      unmarshal ctx pubKeyBS
+    output = PayPK pubKey
+    z = "0000000000000000000000000000000000000000000000000000000000000000"
diff --git a/src/Haskoin/Transaction/Partial.hs b/src/Haskoin/Transaction/Partial.hs
new file mode 100644
--- /dev/null
+++ b/src/Haskoin/Transaction/Partial.hs
@@ -0,0 +1,949 @@
+{-# LANGUAGE DeriveAnyClass #-}
+{-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE DerivingStrategies #-}
+{-# LANGUAGE DuplicateRecordFields #-}
+{-# LANGUAGE FlexibleContexts #-}
+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
+{-# LANGUAGE ImportQualifiedPost #-}
+{-# LANGUAGE NamedFieldPuns #-}
+{-# LANGUAGE OverloadedRecordDot #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE RecordWildCards #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+{-# LANGUAGE NoFieldSelectors #-}
+
+-- |
+-- Module      : Haskoin.Transaction.Partial
+-- Copyright   : No rights reserved
+-- License     : MIT
+-- Maintainer  : matt@bitnomial.com
+-- Stability   : experimental
+-- Portability : POSIX
+--
+-- Code related to PSBT parsing and serialization.
+module Haskoin.Transaction.Partial
+  ( -- * Partially-Signed Transactions
+    PSBT (..),
+    getPSBT,
+    putPSBT,
+    Input (..),
+    getInput,
+    putInput,
+    Output (..),
+    getOutput,
+    putOutput,
+    UnknownMap (..),
+    Key (..),
+    merge,
+    mergeMany,
+    mergeInput,
+    mergeOutput,
+    complete,
+    finalTransaction,
+    emptyPSBT,
+    emptyInput,
+    emptyOutput,
+
+    -- ** Signing
+    PSBTSigner,
+    getSignerKey,
+    secKeySigner,
+    xPrvSigner,
+    signPSBT,
+  )
+where
+
+import Control.Applicative ((<|>))
+import Control.DeepSeq (NFData)
+import Control.Monad (foldM, guard, replicateM, unless, void)
+import Crypto.Secp256k1
+import Data.Binary (Binary (..))
+import Data.ByteString (ByteString)
+import Data.ByteString qualified as B
+import Data.Bytes.Get
+import Data.Bytes.Put
+import Data.Bytes.Serial (Serial (..))
+import Data.Either (fromRight)
+import Data.HashMap.Strict (HashMap)
+import Data.HashMap.Strict qualified as HashMap
+import Data.Hashable (Hashable)
+import Data.List (foldl')
+import Data.Maybe (fromMaybe, isJust)
+import Data.Serialize (Get, Put, Serialize (..))
+import Data.Serialize qualified as S
+import Data.String.Conversions (cs)
+import GHC.Generics (Generic)
+import GHC.Word (Word32, Word8)
+import Haskoin.Address (Address (..), pubKeyAddr)
+import Haskoin.Crypto.Keys.Common
+import Haskoin.Crypto.Keys.Extended
+import Haskoin.Network.Common
+import Haskoin.Network.Data
+import Haskoin.Script.Common
+import Haskoin.Script.SigHash
+import Haskoin.Script.Standard
+import Haskoin.Transaction.Builder.Sign
+import Haskoin.Transaction.Common
+import Haskoin.Transaction.Segwit
+import Haskoin.Util
+import Numeric (showHex)
+
+-- | PSBT data type as specified in
+-- [BIP-174](https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki).
+-- This contains an unsigned transaction, inputs and outputs, and unspecified
+-- extra data. There is one input per input in the unsigned transaction, and one
+-- output per output in the unsigned transaction. The inputs and outputs in the
+-- 'PSBT' line up by index with the inputs and outputs in
+-- the unsigned transaction.
+data PSBT = PSBT
+  { unsignedTransaction :: Tx,
+    globalUnknown :: UnknownMap,
+    inputs :: [Input],
+    outputs :: [Output]
+  }
+  deriving (Show, Read, Eq, Generic)
+  deriving anyclass (NFData)
+
+-- | Inputs contain all of the data needed to sign a transaction and all of the
+-- resulting signature data after signing.
+data Input = Input
+  { nonWitnessUtxo :: Maybe Tx,
+    witnessUtxo :: Maybe TxOut,
+    partialSigs :: HashMap PublicKey ByteString,
+    sigHashType :: Maybe SigHash,
+    inputRedeemScript :: Maybe Script,
+    inputWitnessScript :: Maybe Script,
+    inputHDKeypaths :: HashMap PublicKey (Fingerprint, [KeyIndex]),
+    finalScriptSig :: Maybe Script,
+    finalScriptWitness :: Maybe WitnessStack,
+    inputUnknown :: UnknownMap
+  }
+  deriving (Show, Read, Eq, Generic)
+  deriving anyclass (NFData)
+
+-- | Outputs can contain information needed to spend the output at a later date.
+data Output = Output
+  { outputRedeemScript :: Maybe Script,
+    outputWitnessScript :: Maybe Script,
+    outputHDKeypaths :: HashMap PublicKey (Fingerprint, [KeyIndex]),
+    outputUnknown :: UnknownMap
+  }
+  deriving (Show, Read, Eq, Generic)
+  deriving anyclass (NFData)
+
+-- | A map of raw PSBT keys to byte strings for extra data. The 'keyType' field
+-- cannot overlap with any of the reserved 'keyType' fields specified in the
+-- PSBT specification.
+newtype UnknownMap = UnknownMap {unknownMap :: HashMap Key ByteString}
+  deriving (Show, Read, Eq, Generic)
+  deriving newtype (Semigroup, Monoid, NFData)
+
+-- | Raw keys for the map type used in PSBTs.
+data Key = Key
+  { keyType :: Word8,
+    key :: ByteString
+  }
+  deriving (Show, Read, Eq, Generic, NFData, Hashable)
+
+-- | Take two 'PSBT's and merge them. The
+-- 'unsignedTransaction' field in both must be the same.
+merge ::
+  PSBT ->
+  PSBT ->
+  Maybe PSBT
+merge psbt1 psbt2
+  | psbt1.unsignedTransaction == psbt2.unsignedTransaction =
+      Just $
+        psbt1
+          { globalUnknown = psbt1.globalUnknown <> psbt2.globalUnknown,
+            inputs = zipWith mergeInput psbt1.inputs psbt2.inputs,
+            outputs = zipWith mergeOutput psbt1.outputs psbt2.outputs
+          }
+merge _ _ = Nothing
+
+-- | A version of 'merge' for a collection of PSBTs.
+--
+-- @since 0.21.0
+mergeMany :: [PSBT] -> Maybe PSBT
+mergeMany (psbt : psbts) = foldM merge psbt psbts
+mergeMany _ = Nothing
+
+mergeInput :: Input -> Input -> Input
+mergeInput a b =
+  Input
+    { nonWitnessUtxo =
+        if isJust witUtx
+          then Nothing
+          else a.nonWitnessUtxo <|> b.nonWitnessUtxo,
+      witnessUtxo =
+        witUtx,
+      sigHashType =
+        a.sigHashType <|> b.sigHashType,
+      partialSigs =
+        a.partialSigs <> b.partialSigs,
+      inputHDKeypaths =
+        a.inputHDKeypaths <> b.inputHDKeypaths,
+      inputUnknown =
+        a.inputUnknown <> b.inputUnknown,
+      inputRedeemScript =
+        a.inputRedeemScript <|> b.inputRedeemScript,
+      inputWitnessScript =
+        a.inputWitnessScript <|> b.inputWitnessScript,
+      finalScriptSig =
+        a.finalScriptSig <|> b.finalScriptSig,
+      finalScriptWitness =
+        a.finalScriptWitness <|> b.finalScriptWitness
+    }
+  where
+    witUtx = a.witnessUtxo <|> b.witnessUtxo
+
+mergeOutput :: Output -> Output -> Output
+mergeOutput a b =
+  Output
+    { outputRedeemScript =
+        a.outputRedeemScript <|> b.outputRedeemScript,
+      outputWitnessScript =
+        a.outputWitnessScript <|> b.outputWitnessScript,
+      outputHDKeypaths =
+        a.outputHDKeypaths <> b.outputHDKeypaths,
+      outputUnknown =
+        a.outputUnknown <> b.outputUnknown
+    }
+
+-- | A abstraction which covers varying key configurations.  Use the 'Semigroup'
+-- instance to create signers for sets of keys: `signerA <> signerB` can sign
+-- anything for which `signerA` or `signerB` could sign.
+--
+-- @since 0.21@
+newtype PSBTSigner = PSBTSigner
+  { unPSBTSigner ::
+      PublicKey ->
+      Maybe (Fingerprint, DerivPath) ->
+      Maybe SecKey
+  }
+
+instance Semigroup PSBTSigner where
+  PSBTSigner signer1 <> PSBTSigner signer2 =
+    PSBTSigner $ \pubKey origin ->
+      signer1 pubKey origin <|> signer2 pubKey origin
+
+instance Monoid PSBTSigner where
+  mempty = PSBTSigner $ \_ _ -> Nothing
+
+-- | Fetch the secret key for the given 'PublicKey' if possible.
+--
+-- @since 0.21@
+getSignerKey :: PSBTSigner -> PublicKey -> Maybe (Fingerprint, DerivPath) -> Maybe SecKey
+getSignerKey = (.unPSBTSigner)
+
+-- | This signer can sign for one key.
+--
+-- @since 0.21@
+secKeySigner :: Ctx -> SecKey -> PSBTSigner
+secKeySigner ctx theSecKey =
+  PSBTSigner signer
+  where
+    signer requiredKey _
+      | requiredKey.point == derivePubKey ctx theSecKey = Just theSecKey
+      | otherwise = Nothing
+
+-- | This signer can sign with any child key, provided that derivation information is present.
+--
+-- @since 0.21@
+xPrvSigner ::
+  Ctx ->
+  XPrvKey ->
+  -- | Origin data, if the input key is explicitly a child key
+  Maybe (Fingerprint, DerivPath) ->
+  PSBTSigner
+xPrvSigner ctx xprv origin = PSBTSigner signer
+  where
+    signer pubKey (Just hdData)
+      | result@(Just theSecKey) <- maybe noOrigin onOrigin origin hdData,
+        pubKey.point == derivePubKey ctx theSecKey =
+          result
+    signer _ _ = Nothing
+
+    noOrigin (fp, path)
+      | thisFP == fp = Just $ deriveSecKey path
+      | otherwise = Nothing
+
+    onOrigin (originFP, originPath) (fp, path)
+      | thisFP == fp = Just $ deriveSecKey path
+      | originFP == fp =
+          deriveSecKey <$> adjustPath (pathToList originPath) (pathToList path)
+      | otherwise = Nothing
+
+    deriveSecKey path = (derivePath ctx path xprv).key
+
+    thisFP = xPubFP ctx $ deriveXPubKey ctx xprv
+
+    -- The origin path should be a prefix of the target path if we match the
+    -- origin fingerprint.  We need to remove this prefix.
+    adjustPath :: [KeyIndex] -> [KeyIndex] -> Maybe DerivPath
+    adjustPath (originIx : originTail) (thisIx : thisTail)
+      | originIx == thisIx = adjustPath originTail thisTail
+      | otherwise = Nothing
+    adjustPath [] thePath = Just $ listToPath thePath
+    adjustPath _ _ = Nothing
+
+-- | Update a PSBT with signatures when possible.  This function uses
+-- 'inputHDKeypaths' in order to calculate secret keys.
+--
+-- @since 0.21@
+signPSBT ::
+  Network ->
+  Ctx ->
+  PSBTSigner ->
+  PSBT ->
+  PSBT
+signPSBT net ctx signer PSBT {..} =
+  PSBT {inputs = f <$> zip [0 ..] inputs, ..}
+  where
+    f = addSigsForInput net ctx signer unsignedTransaction
+
+addSigsForInput :: Network -> Ctx -> PSBTSigner -> Tx -> (Int, Input) -> Input
+addSigsForInput net ctx signer tx (ix, input) =
+  maybe input (onPrevTxOut net ctx signer tx ix input) $
+    Left <$> input.nonWitnessUtxo <|> Right <$> input.witnessUtxo
+
+onPrevTxOut ::
+  Network ->
+  Ctx ->
+  PSBTSigner ->
+  Tx ->
+  Int ->
+  Input ->
+  Either Tx TxOut ->
+  Input
+onPrevTxOut net ctx signer tx ix input prevTxData =
+  input
+    { partialSigs = newSigs <> input.partialSigs
+    }
+  where
+    newSigs = HashMap.mapWithKey sigForInput sigKeys
+    sigForInput thePubKey theSecKey =
+      encodeTxSig net ctx . makeSignature net ctx tx ix theSigInput $
+        PrivateKey theSecKey thePubKey.compress
+
+    theSigInput =
+      SigInput
+        { -- Must be the segwit input script for segwit spends (even nested)
+          script = fromMaybe theInputScript segwitInput,
+          value = prevTxOut.value,
+          outpoint = thePrevOutPoint,
+          sighash = fromMaybe sigHashAll input.sigHashType,
+          -- Must be the witness script for segwit spends (even nested)
+          redeem = theWitnessScript <|> theRedeemScript
+        }
+
+    prevTxOut =
+      let rf = ((!! (fromIntegral . (.index)) thePrevOutPoint) . (.outputs))
+       in either rf id prevTxData
+    thePrevOutPoint = (tx.inputs !! ix).outpoint
+
+    segwitInput = justWhen isSegwit theInputScript <|> (justWhen isSegwit =<< theRedeemScript)
+
+    theInputScript = fromRight inputScriptErr $ (unmarshal ctx . (.script)) prevTxOut
+    inputScriptErr = error "addSigsForInput: Unable to decode input script"
+
+    theRedeemScript = case decodeOutput ctx <$> input.inputRedeemScript of
+      Just (Right script) -> Just script
+      Just Left {} -> error "addSigsForInput: Unable to decode redeem script"
+      _ -> Nothing
+
+    theWitnessScript = case decodeOutput ctx <$> input.inputWitnessScript of
+      Just (Right script) -> Just script
+      Just Left {} -> error "addSigsForInput: Unable to decode witness script"
+      _ -> Nothing
+
+    sigKeys = HashMap.mapMaybeWithKey getSignerKey input.inputHDKeypaths
+    getSignerKey pubKey (fp, ixs) = (.unPSBTSigner) signer pubKey $ Just (fp, listToPath ixs)
+
+-- | Take partial signatures from all of the 'Input's and finalize the signature.
+complete ::
+  Ctx ->
+  PSBT ->
+  PSBT
+complete ctx PSBT {..} =
+  PSBT {inputs = map (completeInput . analyzeInputs) (indexed inputs), ..}
+  where
+    analyzeInputs (i, input) =
+      (,)
+        (outputScript =<< input.witnessUtxo <|> nonWitScript)
+        input
+      where
+        nonWitScript = getPrevOut i =<< input.nonWitnessUtxo
+
+    getPrevOut i tx =
+      (tx.outputs !!?) . fromIntegral . (.outpoint.index)
+        =<< unsignedTransaction.inputs !!? i
+    xs !!? i = lookup i $ indexed xs
+
+    outputScript = eitherToMaybe . unmarshal ctx . (.script)
+
+    completeInput (Nothing, input) = input
+    completeInput (Just script, input) = pruneInputFields $ completeSig ctx input script
+
+    -- If we have final scripts, we can get rid of data for signing following
+    -- the Bitcoin Core implementation.
+    pruneInputFields input
+      | isJust input.finalScriptSig || isJust input.finalScriptWitness =
+          input
+            { partialSigs = mempty,
+              inputHDKeypaths = mempty,
+              inputRedeemScript = Nothing,
+              inputWitnessScript = Nothing,
+              sigHashType = Nothing
+            }
+      | otherwise = input
+
+    indexed :: [a] -> [(Word32, a)]
+    indexed = zip [0 ..]
+
+completeSig :: Ctx -> Input -> ScriptOutput -> Input
+completeSig ctx input (PayPK k) =
+  input
+    { finalScriptSig =
+        eitherToMaybe . runGetS deserialize
+          =<< HashMap.lookup k input.partialSigs
+    }
+completeSig ctx input (PayPKHash h)
+  | [(k, sig)] <- HashMap.toList input.partialSigs,
+    PubKeyAddress h == pubKeyAddr ctx k =
+      input
+        { finalScriptSig =
+            Just $
+              Script
+                [ opPushData sig,
+                  opPushData (marshal ctx k)
+                ]
+        }
+completeSig ctx input (PayMulSig pubKeys m)
+  | length sigs >= m =
+      input {finalScriptSig = Just finalSig}
+  where
+    sigs = collectSigs m pubKeys input
+    finalSig = Script $ OP_0 : map opPushData sigs
+completeSig ctx input (PayScriptHash h)
+  | Just rdmScript <- input.inputRedeemScript,
+    PayScriptHash h == toP2SH rdmScript,
+    Right decodedScript <- decodeOutput ctx rdmScript,
+    not (isPayScriptHash decodedScript) =
+      pushScript rdmScript $ completeSig ctx input decodedScript
+  where
+    pushScript rdmScript updatedInput =
+      updatedInput
+        { finalScriptSig =
+            Just $
+              fromMaybe (Script mempty) updatedInput.finalScriptSig
+                `scriptAppend` serializedRedeemScript rdmScript
+        }
+    scriptAppend (Script script1) (Script script2) = Script $ script1 <> script2
+completeSig ctx input (PayWitnessPKHash h)
+  | [(k, sig)] <- HashMap.toList input.partialSigs,
+    PubKeyAddress h == pubKeyAddr ctx k =
+      input {finalScriptWitness = Just [sig, marshal ctx k]}
+completeSig ctx input (PayWitnessScriptHash h)
+  | Just witScript <- input.inputWitnessScript,
+    PayWitnessScriptHash h == toP2WSH witScript,
+    Right decodedScript <- decodeOutput ctx witScript =
+      completeWitnessSig ctx input decodedScript
+completeSig _ input _ = input
+
+serializedRedeemScript :: Script -> Script
+serializedRedeemScript = Script . pure . opPushData . runPutS . serialize
+
+completeWitnessSig :: Ctx -> Input -> ScriptOutput -> Input
+completeWitnessSig ctx input script@(PayMulSig pubKeys m)
+  | length sigs >= m =
+      input {finalScriptWitness = Just finalWit}
+  where
+    sigs = collectSigs m pubKeys input
+    finalWit = mempty : sigs <> [marshal ctx script]
+completeWitnessSig _ input _ = input
+
+collectSigs :: Int -> [PublicKey] -> Input -> [ByteString]
+collectSigs m pubKeys input =
+  take m . reverse $ foldl' lookupKey [] pubKeys
+  where
+    lookupKey sigs key =
+      maybe sigs (: sigs) $
+        HashMap.lookup key input.partialSigs
+
+-- | Take a finalized 'PSBT' and produce the signed final
+-- transaction. You may need to call 'complete' on the
+-- 'PSBT' before producing the final transaction.
+finalTransaction :: PSBT -> Tx
+finalTransaction psbt =
+  setInputs
+    . foldl' finalizeInput ([], [])
+    $ zip tx.inputs psbt.inputs
+  where
+    tx = psbt.unsignedTransaction
+    hasWitness = any (isJust . (.finalScriptWitness)) psbt.inputs
+    setInputs (ins, witData) =
+      tx
+        { inputs = reverse ins,
+          witness = if hasWitness then reverse witData else []
+        }
+    finalizeInput (ins, witData) (TxIn {..}, psbtInput) =
+      ( TxIn
+          { script =
+              maybe
+                mempty
+                (runPutS . serialize)
+                psbtInput.finalScriptSig,
+            ..
+          }
+          : ins,
+        fromMaybe [] psbtInput.finalScriptWitness : witData
+      )
+
+-- | Take an unsigned transaction and produce an empty
+-- 'PSBT'
+emptyPSBT :: Tx -> PSBT
+emptyPSBT tx =
+  PSBT
+    { unsignedTransaction = tx,
+      globalUnknown = mempty,
+      inputs = replicate (length tx.inputs) emptyInput,
+      outputs = replicate (length tx.outputs) emptyOutput
+    }
+
+emptyInput :: Input
+emptyInput =
+  Input
+    Nothing
+    Nothing
+    HashMap.empty
+    Nothing
+    Nothing
+    Nothing
+    HashMap.empty
+    Nothing
+    Nothing
+    (UnknownMap HashMap.empty)
+
+emptyOutput :: Output
+emptyOutput = Output Nothing Nothing HashMap.empty (UnknownMap HashMap.empty)
+
+getPSBT :: (MonadGet m) => Ctx -> m PSBT
+getPSBT ctx = do
+  magic <- getBytes 4
+  unless (magic == "psbt") $
+    fail $
+      "Expected magic = 'psbt' but got '" ++ cs magic ++ "'"
+  headerSep <- getWord8
+  unless (headerSep == 0xff) $
+    fail $
+      "Expected headerSep = 0xff but got 0x" ++ showHex headerSep ""
+
+  keySize <- getWord8
+  unless (keySize == 1) $
+    fail $
+      "Expected keySize = 1 but got " ++ show keySize
+  globalUnsignedTxType <- getWord8
+  unless (globalUnsignedTxType == 0x00) $
+    fail $
+      "Expected globalUnsignedTxType = 0x00 but got 0x"
+        ++ showHex globalUnsignedTxType ""
+  unsignedTransaction <- getSizedBytes deserialize
+  unless (all (B.null . (.script)) unsignedTransaction.inputs) $
+    fail $
+      "Not all inputs from unsignedTransaction have empty scripts"
+  unless (null unsignedTransaction.witness) $
+    fail $
+      "Not all witnesses from unsignedTransaction are empty"
+
+  globalUnknown <- deserialize
+  globalEnd <- getWord8
+  unless (globalEnd == 0x00) $
+    fail $
+      "Expected globalEnd == 0x00 but got 0x" ++ showHex globalEnd ""
+
+  inputs <-
+    replicateM
+      (length unsignedTransaction.inputs)
+      (getInput ctx)
+  outputs <-
+    replicateM
+      (length unsignedTransaction.outputs)
+      (getOutput ctx)
+
+  return
+    PSBT
+      { unsignedTransaction,
+        globalUnknown,
+        inputs,
+        outputs
+      }
+
+putPSBT :: (MonadPut m) => Ctx -> PSBT -> m ()
+putPSBT
+  ctx
+  PSBT
+    { unsignedTransaction,
+      globalUnknown,
+      inputs,
+      outputs
+    } = do
+    putByteString "psbt"
+    putWord8 0xff -- Header separator
+    putWord8 0x01 -- Key size
+    putWord8 0x00 -- Unsigned Transaction type
+    putSizedBytes $ S.encode unsignedTransaction
+    serialize globalUnknown
+    putWord8 0x00 -- Global end
+    mapM_ (putInput ctx) inputs
+    mapM_ (putOutput ctx) outputs
+
+instance Serial Key where
+  deserialize = do
+    VarInt keySize <- deserialize
+    unless (keySize > 0) $
+      fail $
+        "Expected keySize > 0 but got " ++ show keySize
+    t <- getWord8
+    k <- getBytes (fromIntegral keySize - 1)
+    return (Key t k)
+
+  serialize (Key t k) = do
+    putVarInt $ 1 + B.length k
+    putWord8 t
+    putByteString k
+
+instance Binary Key where
+  put = serialize
+  get = deserialize
+
+instance Serialize Key where
+  put = serialize
+  get = deserialize
+
+instance Serial UnknownMap where
+  deserialize = go HashMap.empty
+    where
+      getItem m = do
+        k <- deserialize
+        VarString v <- deserialize
+        go $ HashMap.insert k v m
+      go m = do
+        isEnd <- lookAhead getWord8
+        if isEnd == 0x00
+          then return (UnknownMap m)
+          else getItem m
+
+  serialize (UnknownMap m) =
+    void $
+      HashMap.traverseWithKey
+        (\k v -> serialize k >> serialize (VarString v))
+        m
+
+getInput :: (MonadGet m) => Ctx -> m Input
+getInput ctx =
+  getMap (getInputItem ctx) setInputUnknown emptyInput
+  where
+    setInputUnknown f input =
+      input
+        { inputUnknown =
+            UnknownMap (f input.inputUnknown.unknownMap)
+        }
+
+putInput :: (MonadPut m) => Ctx -> Input -> m ()
+putInput
+  ctx
+  Input
+    { nonWitnessUtxo,
+      witnessUtxo,
+      partialSigs,
+      sigHashType,
+      inputRedeemScript,
+      inputWitnessScript,
+      inputHDKeypaths,
+      finalScriptSig,
+      finalScriptWitness,
+      inputUnknown
+    } = do
+    whenJust
+      (putKeyValue InNonWitnessUtxo . S.encode)
+      nonWitnessUtxo
+    whenJust
+      (putKeyValue InWitnessUtxo . S.encode)
+      witnessUtxo
+    putPartialSig partialSigs
+    whenJust
+      putSigHash
+      sigHashType
+    whenJust
+      (putKeyValue InRedeemScript . S.encode)
+      inputRedeemScript
+    whenJust
+      (putKeyValue InWitnessScript . S.encode)
+      inputWitnessScript
+    putHDPath ctx InBIP32Derivation inputHDKeypaths
+    whenJust
+      (putKeyValue InFinalScriptSig . S.encode)
+      finalScriptSig
+    whenJust
+      (putKeyValue InFinalScriptWitness . putFinalScriptWitness)
+      finalScriptWitness
+    serialize inputUnknown
+    putWord8 0x00
+    where
+      putPartialSig =
+        putPubKeyMap ctx serialize InPartialSig . fmap VarString
+      putSigHash sigHash = do
+        putKey InSigHashType
+        putWord8 0x04
+        putWord32le (fromIntegral sigHash)
+      putFinalScriptWitness witnessStack = runPutS $ do
+        serialize $ (VarInt . fromIntegral . length) witnessStack
+        mapM_ (serialize . VarString) witnessStack
+
+getOutput :: (MonadGet m) => Ctx -> m Output
+getOutput ctx = getMap (getOutputItem ctx) setOutputUnknown emptyOutput
+  where
+    setOutputUnknown f output =
+      output
+        { outputUnknown =
+            UnknownMap (f output.outputUnknown.unknownMap)
+        }
+
+putOutput :: (MonadPut m) => Ctx -> Output -> m ()
+putOutput
+  ctx
+  Output
+    { outputRedeemScript,
+      outputWitnessScript,
+      outputHDKeypaths,
+      outputUnknown
+    } = do
+    whenJust
+      (putKeyValue OutRedeemScript . S.encode)
+      outputRedeemScript
+    whenJust
+      (putKeyValue OutWitnessScript . S.encode)
+      outputWitnessScript
+    putHDPath
+      ctx
+      OutBIP32Derivation
+      outputHDKeypaths
+    serialize outputUnknown
+    putWord8 0x00
+
+putSizedBytes :: (MonadPut m) => ByteString -> m ()
+putSizedBytes bs = do
+  putVarInt (B.length bs)
+  putByteString bs
+
+getSizedBytes :: (MonadGet m) => Get a -> m a
+getSizedBytes =
+  getNested $ (\(VarInt i) -> fromIntegral i) <$> deserialize
+
+putKeyValue :: (Enum t, MonadPut m) => t -> ByteString -> m ()
+putKeyValue t v = do
+  putKey t
+  putSizedBytes v
+
+putKey :: (Enum t, MonadPut m) => t -> m ()
+putKey t = do
+  putVarInt (1 :: Word8)
+  putWord8 (enumWord8 t)
+
+getMap ::
+  (Bounded t, Enum t, MonadGet m) =>
+  (Int -> a -> t -> m a) ->
+  ((HashMap Key ByteString -> HashMap Key ByteString) -> a -> a) ->
+  a ->
+  m a
+getMap getMapItem setUnknown = go
+  where
+    getItem keySize m (Right t) =
+      getMapItem (fromIntegral keySize - 1) m t >>= go
+    getItem keySize m (Left t) = do
+      k <- getBytes (fromIntegral keySize - 1)
+      VarString v <- deserialize
+      go $ setUnknown (HashMap.insert (Key t k) v) m
+    go m = do
+      keySize <- (\(VarInt i) -> i) <$> deserialize
+      if keySize == 0
+        then return m
+        else getItem keySize m . word8Enum =<< getWord8
+
+data InputType
+  = InNonWitnessUtxo
+  | InWitnessUtxo
+  | InPartialSig
+  | InSigHashType
+  | InRedeemScript
+  | InWitnessScript
+  | InBIP32Derivation
+  | InFinalScriptSig
+  | InFinalScriptWitness
+  deriving (Show, Eq, Enum, Bounded, Generic)
+  deriving anyclass (NFData)
+
+data OutputType
+  = OutRedeemScript
+  | OutWitnessScript
+  | OutBIP32Derivation
+  deriving (Show, Eq, Enum, Bounded, Generic)
+  deriving anyclass (NFData)
+
+getInputItem ::
+  (MonadGet m) =>
+  Ctx ->
+  Int ->
+  Input ->
+  InputType ->
+  m Input
+getInputItem ctx 0 input@Input {nonWitnessUtxo = Nothing} InNonWitnessUtxo = do
+  utxo <- getSizedBytes deserialize
+  return input {nonWitnessUtxo = Just utxo}
+getInputItem ctx 0 input@Input {witnessUtxo = Nothing} InWitnessUtxo = do
+  utxo <- getSizedBytes deserialize
+  return input {witnessUtxo = Just utxo}
+getInputItem ctx keySize input InPartialSig = do
+  (k, v) <- getPartialSig
+  return
+    input
+      { partialSigs = HashMap.insert k v input.partialSigs
+      }
+  where
+    getPartialSig =
+      (,)
+        <$> isolate keySize (marshalGet ctx :: Get PublicKey)
+        <*> ((\(VarString s) -> s) <$> deserialize)
+getInputItem ctx 0 input@Input {sigHashType = Nothing} InSigHashType = do
+  VarInt size <- deserialize
+  unless (size == 0x04) $
+    fail $
+      "Expected size == 0x04 but got 0x" ++ showHex size ""
+  sigHash <- fromIntegral <$> getWord32le
+  return $ input {sigHashType = Just sigHash}
+getInputItem ctx 0 input@Input {inputRedeemScript = Nothing} InRedeemScript = do
+  script <- getSizedBytes deserialize
+  return $ input {inputRedeemScript = Just script}
+getInputItem ctx 0 input@Input {inputWitnessScript = Nothing} InWitnessScript = do
+  script <- getSizedBytes deserialize
+  return $ input {inputWitnessScript = Just script}
+getInputItem ctx keySize input InBIP32Derivation = do
+  (k, v) <- getHDPath ctx keySize
+  return
+    input
+      { inputHDKeypaths = HashMap.insert k v input.inputHDKeypaths
+      }
+getInputItem ctx 0 input@Input {finalScriptSig = Nothing} InFinalScriptSig = do
+  script <- getSizedBytes deserialize
+  return $ input {finalScriptSig = Just script}
+getInputItem ctx 0 input@Input {finalScriptWitness = Nothing} InFinalScriptWitness = do
+  scripts <- map (\(VarString s) -> s) <$> getVarIntList
+  return $ input {finalScriptWitness = Just scripts}
+  where
+    getVarIntList = getSizedBytes $ do
+      VarInt n <- deserialize -- Item count
+      replicateM (fromIntegral n) deserialize
+getInputItem ctx keySize input inputType =
+  fail "Incorrect key size for input item or item already existed"
+
+getOutputItem :: (MonadGet m) => Ctx -> Int -> Output -> OutputType -> m Output
+getOutputItem ctx 0 output@Output {outputRedeemScript = Nothing} OutRedeemScript = do
+  script <- getSizedBytes deserialize
+  return $ output {outputRedeemScript = Just script}
+getOutputItem ctx 0 output@Output {outputWitnessScript = Nothing} OutWitnessScript = do
+  script <- getSizedBytes deserialize
+  return $ output {outputWitnessScript = Just script}
+getOutputItem ctx keySize output OutBIP32Derivation = do
+  (k, v) <- getHDPath ctx keySize
+  return $ output {outputHDKeypaths = HashMap.insert k v output.outputHDKeypaths}
+getOutputItem ctx keySize output outputType =
+  fail "Incorrect key size for output item or item already existed"
+
+getHDPath ::
+  forall m.
+  (MonadGet m) =>
+  Ctx ->
+  Int ->
+  m (PublicKey, (Fingerprint, [KeyIndex]))
+getHDPath ctx keySize =
+  (,)
+    <$> isolate keySize (marshalGet ctx :: Get PublicKey)
+    <*> ((\(PSBTHDPath s) -> s) <$> deserialize)
+
+putHDPath ::
+  (Enum t, MonadPut m) =>
+  Ctx ->
+  t ->
+  HashMap PublicKey (Fingerprint, [KeyIndex]) ->
+  m ()
+putHDPath ctx t = putPubKeyMap ctx serialize t . fmap PSBTHDPath
+
+newtype PSBTHDPath = PSBTHDPath {unPSBTHDPath :: (Fingerprint, [KeyIndex])}
+  deriving (Show, Eq, Generic)
+  deriving newtype (NFData)
+
+instance Serial PSBTHDPath where
+  deserialize = do
+    VarInt valueSize <- deserialize
+    unless (valueSize `mod` 4 == 0) $
+      fail $
+        "Expected valueSize = 4 but got " ++ show valueSize
+    let numIndices = (fromIntegral valueSize - 4) `div` 4
+    PSBTHDPath
+      <$> isolate
+        (fromIntegral valueSize)
+        ((,) <$> deserialize <*> getKeyIndexList numIndices)
+    where
+      getKeyIndexList n = replicateM n getWord32le
+
+  serialize (PSBTHDPath (fp, kis)) = do
+    putVarInt (B.length bs)
+    putByteString bs
+    where
+      bs = runPutS $ serialize fp >> mapM_ putWord32le kis
+
+instance Binary PSBTHDPath where
+  put = serialize
+  get = deserialize
+
+instance Serialize PSBTHDPath where
+  put = serialize
+  get = deserialize
+
+putPubKeyMap ::
+  (Enum t, MonadPut m) =>
+  Ctx ->
+  (a -> m ()) ->
+  t ->
+  HashMap PublicKey a ->
+  m ()
+putPubKeyMap ctx f t =
+  void . HashMap.traverseWithKey putItem
+  where
+    putItem k v = do
+      serialize $ Key (enumWord8 t) (marshal ctx k)
+      f v
+
+enumWord8 :: (Enum a) => a -> Word8
+enumWord8 = fromIntegral . fromEnum
+
+word8Enum :: forall a. (Bounded a, Enum a) => Word8 -> Either Word8 a
+word8Enum n | n <= enumWord8 (maxBound :: a) = Right . toEnum $ fromIntegral n
+word8Enum n = Left n
+
+whenJust :: (Monad m) => (a -> m ()) -> Maybe a -> m ()
+whenJust = maybe (return ())
+
+justWhen :: (a -> Bool) -> a -> Maybe a
+justWhen test x = if test x then Just x else Nothing
+
+isolate :: (MonadGet m) => Int -> Get a -> m a
+isolate length getVal = do
+  bs <- getByteString length
+  either fail return (runGetS getVal bs)
+
+getNested :: (MonadGet m) => m Int -> Get a -> m a
+getNested getLength getVal = do
+  length <- getLength
+  isolate length getVal
diff --git a/src/Haskoin/Transaction/Segwit.hs b/src/Haskoin/Transaction/Segwit.hs
new file mode 100644
--- /dev/null
+++ b/src/Haskoin/Transaction/Segwit.hs
@@ -0,0 +1,175 @@
+{-# LANGUAGE DuplicateRecordFields #-}
+{-# LANGUAGE LambdaCase #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE TupleSections #-}
+{-# LANGUAGE NoFieldSelectors #-}
+
+-- |
+-- Module      : Haskoin.Transaction.Segwit
+-- Copyright   : No rights reserved
+-- License     : MIT
+-- Maintainer  : jprupp@protonmail.ch
+-- Stability   : experimental
+-- Portability : POSIX
+--
+-- Types to represent segregated witness data and auxilliary functions to
+-- manipulate it.  See [BIP 141](https://github.com/bitcoin/bips/blob/master/bip-0141.mediawiki)
+-- and [BIP 143](https://github.com/bitcoin/bips/blob/master/bip-0143.mediawiki) for
+-- details.
+module Haskoin.Transaction.Segwit
+  ( -- * Segwit
+    WitnessProgram (..),
+    WitnessProgramPKH (..),
+    WitnessProgramSH (..),
+    isSegwit,
+    viewWitnessProgram,
+    decodeWitnessInput,
+    calcWitnessProgram,
+    simpleInputStack,
+    toWitnessStack,
+  )
+where
+
+import Crypto.Secp256k1
+import Data.ByteString (ByteString)
+import Data.Bytes.Get (runGetS)
+import Data.Bytes.Put (runPutS)
+import Data.Bytes.Serial (Serial (deserialize, serialize))
+import Haskoin.Crypto.Keys.Common
+import Haskoin.Network.Data
+import Haskoin.Script.Common
+import Haskoin.Script.SigHash
+import Haskoin.Script.Standard
+import Haskoin.Transaction.Common
+import Haskoin.Util.Marshal
+
+-- | Test if a 'ScriptOutput' is P2WPKH or P2WSH
+--
+-- @since 0.11.0.0
+isSegwit :: ScriptOutput -> Bool
+isSegwit = \case
+  PayWitnessPKHash {} -> True
+  PayWitnessScriptHash {} -> True
+  _ -> False
+
+-- | High level represenation of a (v0) witness program
+--
+-- @since 0.11.0.0
+data WitnessProgram
+  = P2WPKH WitnessProgramPKH
+  | P2WSH WitnessProgramSH
+  | EmptyWitnessProgram
+  deriving (Eq)
+
+-- | Encode a witness program
+--
+-- @since 0.11.0.0
+toWitnessStack :: Network -> Ctx -> WitnessProgram -> WitnessStack
+toWitnessStack net ctx = \case
+  P2WPKH (WitnessProgramPKH sig key) ->
+    [encodeTxSig net ctx sig, marshal ctx key]
+  P2WSH (WitnessProgramSH stack scr) ->
+    stack <> [runPutS (serialize scr)]
+  EmptyWitnessProgram ->
+    mempty
+
+-- | High level representation of a P2WPKH witness
+--
+-- @since 0.11.0.0
+data WitnessProgramPKH = WitnessProgramPKH
+  { signature :: !TxSignature,
+    key :: !PublicKey
+  }
+  deriving (Eq)
+
+-- | High-level representation of a P2WSH witness
+--
+-- @since 0.11.0.0
+data WitnessProgramSH = WitnessProgramSH
+  { stack :: ![ByteString],
+    script :: !Script
+  }
+  deriving (Eq, Show)
+
+-- | Calculate the witness program from the transaction data
+--
+-- @since 0.11.0.0
+viewWitnessProgram ::
+  Network ->
+  Ctx ->
+  ScriptOutput ->
+  WitnessStack ->
+  Either String WitnessProgram
+viewWitnessProgram net ctx so witness = case so of
+  PayWitnessPKHash _ | length witness == 2 -> do
+    sig <- decodeTxSig net ctx (head witness)
+    pubkey <- unmarshal ctx $ witness !! 1
+    return . P2WPKH $ WitnessProgramPKH sig pubkey
+  PayWitnessScriptHash _ | not (null witness) -> do
+    redeemScript <- runGetS deserialize $ last witness
+    return . P2WSH $ WitnessProgramSH (init witness) redeemScript
+  _
+    | null witness -> return EmptyWitnessProgram
+    | otherwise -> Left "viewWitnessProgram: Invalid witness program"
+
+-- | Analyze the witness, trying to match it with standard input structures
+--
+-- @since 0.11.0.0
+decodeWitnessInput ::
+  Network ->
+  Ctx ->
+  WitnessProgram ->
+  Either String (Maybe ScriptOutput, SimpleInput)
+decodeWitnessInput net ctx = \case
+  P2WPKH (WitnessProgramPKH sig key) -> return (Nothing, SpendPKHash sig key)
+  P2WSH (WitnessProgramSH st scr) -> do
+    so <- decodeOutput ctx scr
+    fmap (Just so,) $ case (so, st) of
+      (PayPK _, [sigBS]) ->
+        SpendPK <$> decodeTxSig net ctx sigBS
+      (PayPKHash _, [sigBS, keyBS]) ->
+        SpendPKHash
+          <$> decodeTxSig net ctx sigBS
+          <*> unmarshal ctx keyBS
+      (PayMulSig _ _, "" : sigsBS) ->
+        SpendMulSig
+          <$> traverse (decodeTxSig net ctx) sigsBS
+      _ -> Left "decodeWitnessInput: Non-standard script output"
+  EmptyWitnessProgram -> Left "decodeWitnessInput: Empty witness program"
+
+-- | Create the witness program for a standard input
+--
+-- @since 0.11.0.0
+calcWitnessProgram ::
+  Network ->
+  Ctx ->
+  ScriptOutput ->
+  ScriptInput ->
+  Either String WitnessProgram
+calcWitnessProgram net ctx so si = case (so, si) of
+  (PayWitnessPKHash {}, RegularInput (SpendPKHash sig pk)) ->
+    Right $ p2wpkh sig pk
+  (PayScriptHash {}, RegularInput (SpendPKHash sig pk)) ->
+    Right $ p2wpkh sig pk
+  (PayWitnessScriptHash {}, ScriptHashInput i o) ->
+    Right $ p2wsh i o
+  (PayScriptHash {}, ScriptHashInput i o) ->
+    Right $ p2wsh i o
+  _ -> Left "calcWitnessProgram: Invalid segwit SigInput"
+  where
+    p2wpkh sig =
+      P2WPKH . WitnessProgramPKH sig
+    p2wsh i =
+      P2WSH . WitnessProgramSH (simpleInputStack net ctx i) . encodeOutput ctx
+
+-- | Create the witness stack required to spend a standard P2WSH input
+--
+-- @since 0.11.0.0
+simpleInputStack :: Network -> Ctx -> SimpleInput -> [ByteString]
+simpleInputStack net ctx = \case
+  SpendPK sig -> [f sig]
+  SpendPKHash sig k -> [f sig, marshal ctx k]
+  SpendMulSig sigs -> "" : fmap f sigs
+  where
+    f TxSignatureEmpty = ""
+    f sig = encodeTxSig net ctx sig
diff --git a/src/Haskoin/Transaction/Taproot.hs b/src/Haskoin/Transaction/Taproot.hs
new file mode 100644
--- /dev/null
+++ b/src/Haskoin/Transaction/Taproot.hs
@@ -0,0 +1,315 @@
+{-# LANGUAGE DuplicateRecordFields #-}
+{-# LANGUAGE ImportQualifiedPost #-}
+{-# LANGUAGE LambdaCase #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE NamedFieldPuns #-}
+{-# LANGUAGE OverloadedRecordDot #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE RecordWildCards #-}
+{-# LANGUAGE TypeApplications #-}
+{-# LANGUAGE NoFieldSelectors #-}
+
+-- |
+-- Module      : Haskoin.Transaction.Taproot
+-- Copyright   : No rights reserved
+-- License     : MIT
+-- Maintainer  : jprupp@protonmail.ch
+-- Stability   : experimental
+-- Portability : POSIX
+--
+-- This module provides support for reperesenting full taproot outputs and parsing
+-- taproot witnesses.  For reference see BIPS 340, 341, and 342.
+module Haskoin.Transaction.Taproot
+  ( XOnlyPubKey (..),
+    TapLeafVersion,
+    MAST (..),
+    mastCommitment,
+    getMerkleProofs,
+    TaprootOutput (..),
+    taprootOutputKey,
+    taprootScriptOutput,
+    TaprootWitness (..),
+    ScriptPathData (..),
+    viewTaprootWitness,
+    encodeTaprootWitness,
+    verifyScriptPathData,
+  )
+where
+
+import Control.Applicative (many)
+import Control.Monad ((<=<))
+import Crypto.Hash
+  ( Digest,
+    SHA256,
+    digestFromByteString,
+    hashFinalize,
+    hashUpdate,
+    hashUpdates,
+  )
+import Crypto.Secp256k1
+import Data.Aeson
+  ( FromJSON (parseJSON),
+    ToJSON (toJSON),
+    Value (String),
+    withText,
+  )
+import Data.Aeson.Types (Parser, Value)
+import Data.Binary (Binary (..))
+import Data.Bits ((.&.), (.|.))
+import Data.Bool (bool)
+import Data.ByteArray qualified as BA
+import Data.ByteString (ByteString)
+import Data.ByteString qualified as BS
+import Data.Bytes.Get (MonadGet, getBytes, runGetS)
+import Data.Bytes.Put (MonadPut, putByteString, runPutL, runPutS)
+import Data.Bytes.Serial (Serial (..), deserialize, serialize)
+import Data.Bytes.VarInt (VarInt (VarInt))
+import Data.Foldable (foldl')
+import Data.Maybe (fromMaybe, mapMaybe)
+import Data.Serialize (Serialize, get, getByteString, getWord8, put)
+import Data.Word (Word8)
+import Haskoin.Crypto.Hash
+import Haskoin.Crypto.Keys.Common
+import Haskoin.Crypto.Keys.Extended
+import Haskoin.Script.Common
+import Haskoin.Script.Standard
+import Haskoin.Transaction.Common
+import Haskoin.Util
+
+-- | An x-only pubkey corresponds to the keys @(x,y)@ and @(x, -y)@.  The
+-- equality test only checks the x-coordinate.  An x-only pubkey serializes to 32
+-- bytes.
+--
+-- @since 0.21.0
+newtype XOnlyPubKey = XOnlyPubKey {point :: PubKey}
+  deriving (Read, Show)
+
+instance Eq XOnlyPubKey where
+  XOnlyPubKey k1 == XOnlyPubKey k2 = f k1 == f k2
+    where
+      f = BS.take 32 . (.get)
+
+instance Marshal Ctx XOnlyPubKey where
+  marshalPut ctx (XOnlyPubKey pk) =
+    putByteString
+      . BS.drop 1
+      . marshal ctx
+      $ PublicKey pk True
+
+  marshalGet ctx =
+    either fail (pure . XOnlyPubKey . (\PublicKey {point} -> point))
+      . unmarshal ctx
+      . BS.cons 0x02
+      =<< getBytes 32
+
+instance MarshalJSON Ctx XOnlyPubKey where
+  unmarshalValue ctx =
+    withText "XOnlyPubKey" $ either fail pure . (des <=< hex)
+    where
+      hex = maybe (Left "Unable to decode hex") Right . decodeHex
+      des = runGetS $ marshalGet ctx
+
+  marshalValue ctx =
+    String . encodeHex . marshal ctx
+
+  marshalEncoding ctx =
+    hexEncoding . runPutL . marshalPut ctx
+
+-- | @since 0.21.0
+type TapLeafVersion = Word8
+
+-- | Merklized Abstract Syntax Tree.  This type can represent trees where only a
+-- subset of the leaves are known.  Note that the tree is invariant under swapping
+-- branches at an internal node.
+--
+-- @since 0.21.0
+data MAST
+  = MASTBranch MAST MAST
+  | MASTLeaf TapLeafVersion Script
+  | MASTCommitment (Digest SHA256)
+  deriving (Show)
+
+-- | Get the inclusion proofs for the leaves in the tree.  The proof is ordered
+-- leaf-to-root.
+--
+-- @since 0.21.0
+getMerkleProofs :: MAST -> [(TapLeafVersion, Script, [Digest SHA256])]
+getMerkleProofs = getProofs mempty
+  where
+    getProofs proof = \case
+      MASTBranch branchL branchR ->
+        (updateProof proof (mastCommitment branchR) <$> getMerkleProofs branchL)
+          <> (updateProof proof (mastCommitment branchL) <$> getMerkleProofs branchR)
+      MASTLeaf v s -> [(v, s, proof)]
+      MASTCommitment {} -> mempty
+    updateProof proofInit branchCommitment (v, s, proofTail) =
+      (v, s, reverse $ proofInit <> (branchCommitment : proofTail))
+
+-- | Calculate the root hash for this tree.
+--
+-- @since 0.21.0
+mastCommitment :: MAST -> Digest SHA256
+mastCommitment = \case
+  MASTBranch leftBranch rightBranch ->
+    hashBranch (mastCommitment leftBranch) (mastCommitment rightBranch)
+  MASTLeaf leafVersion leafScript -> leafHash leafVersion leafScript
+  MASTCommitment theCommitment -> theCommitment
+
+hashBranch :: Digest SHA256 -> Digest SHA256 -> Digest SHA256
+hashBranch hashA hashB =
+  hashFinalize $
+    hashUpdates
+      (initTaggedHash "TapBranch")
+      [ min hashA hashB,
+        max hashA hashB
+      ]
+
+leafHash :: TapLeafVersion -> Script -> Digest SHA256
+leafHash leafVersion leafScript =
+  hashFinalize
+    . hashUpdate (initTaggedHash "TapLeaf")
+    . runPutS
+    $ do
+      serialize leafVersion
+      serialize $ VarInt (BS.length scriptBytes)
+      putByteString scriptBytes
+  where
+    scriptBytes = runPutS $ serialize leafScript
+
+-- | Representation of a full taproot output.
+--
+-- @since 0.21.0
+data TaprootOutput = TaprootOutput
+  { internalKey :: PubKey,
+    mast :: Maybe MAST
+  }
+
+-- | @since 0.21.0
+taprootOutputKey :: Ctx -> TaprootOutput -> PubKey
+taprootOutputKey ctx TaprootOutput {..} =
+  fromMaybe keyFail $
+    tweak commitment >>= tweakAddPubKey ctx internalKey
+  where
+    commitment =
+      taprootCommitment ctx internalKey $
+        mastCommitment <$> mast
+    keyFail = error "haskoin-core taprootOutputKey: key derivation failed"
+
+taprootCommitment :: Ctx -> PubKey -> Maybe (Digest SHA256) -> ByteString
+taprootCommitment ctx internalKey merkleRoot =
+  BA.convert
+    . hashFinalize
+    . maybe id (flip hashUpdate) merkleRoot
+    . (`hashUpdate` keyBytes)
+    $ initTaggedHash "TapTweak"
+  where
+    keyBytes = runPutS . marshalPut ctx $ XOnlyPubKey internalKey
+
+-- | Generate the output script for a taproot output
+--
+-- @since 0.21.0
+taprootScriptOutput :: Ctx -> TaprootOutput -> ScriptOutput
+taprootScriptOutput ctx =
+  PayWitness 0x01
+    . runPutS
+    . marshalPut ctx
+    . XOnlyPubKey
+    . taprootOutputKey ctx
+
+-- | Comprehension of taproot witness data
+--
+-- @since 0.21.0
+data TaprootWitness
+  = -- | Signature
+    KeyPathSpend ByteString
+  | ScriptPathSpend ScriptPathData
+  deriving (Eq)
+
+-- | @since 0.21.0
+data ScriptPathData = ScriptPathData
+  { annex :: Maybe ByteString,
+    stack :: [ByteString],
+    script :: Script,
+    extIsOdd :: Bool,
+    -- | This value is masked by 0xFE
+    leafVersion :: Word8,
+    internalKey :: PubKey,
+    control :: [ByteString]
+  }
+  deriving (Eq)
+
+-- | Try to interpret a 'WitnessStack' as taproot witness data.
+--
+-- @since 0.21.0
+viewTaprootWitness :: Ctx -> WitnessStack -> Maybe TaprootWitness
+viewTaprootWitness ctx witnessStack = case reverse witnessStack of
+  [sig] -> Just $ KeyPathSpend sig
+  annexA : remainingStack
+    | 0x50 : _ <- BS.unpack annexA ->
+        parseSpendPathData (Just annexA) remainingStack
+  remainingStack -> parseSpendPathData Nothing remainingStack
+  where
+    parseSpendPathData annex = \case
+      scriptBytes : controlBytes : stack -> do
+        script <- eitherToMaybe $ runGetS deserialize scriptBytes
+        (v, internalKey, control) <- deconstructControl controlBytes
+        let extIsOdd = odd v
+            leafVersion = v .&. 0xFE
+        pure $ ScriptPathSpend ScriptPathData {..}
+      _ -> Nothing
+    deconstructControl = eitherToMaybe . runGetS deserializeControl
+    deserializeControl = do
+      v <- getWord8
+      XOnlyPubKey k <- marshalGet ctx
+      proof <- many $ getByteString 32
+      pure (v, k, proof)
+
+-- | Transform the high-level representation of taproot witness data into a witness stack
+--
+-- @since 0.21.0
+encodeTaprootWitness :: Ctx -> TaprootWitness -> WitnessStack
+encodeTaprootWitness ctx = \case
+  KeyPathSpend signature -> pure signature
+  ScriptPathSpend scriptPathData -> wit scriptPathData
+  where
+    wit d = (.stack) d <> [script d, keys d, annex d]
+    keys d = mconcat [verpar d, xonlyk d, ctrl d]
+    script = runPutS . serialize . (.script)
+    verpar d = BS.pack [(.leafVersion) d .|. parity d]
+    xonlyk = runPutS . marshalPut ctx . XOnlyPubKey . (.internalKey)
+    annex = fromMaybe mempty . (.annex)
+    ctrl = mconcat . (.control)
+    parity = bool 0 1 . (.extIsOdd)
+
+-- | Verify that the script path spend is valid, except for script execution.
+--
+-- @since 0.21.0
+verifyScriptPathData ::
+  Ctx ->
+  -- | Output key
+  PubKey ->
+  ScriptPathData ->
+  Bool
+verifyScriptPathData ctx outkey spd = fromMaybe False $ do
+  tweak commitment
+    >>= fmap onComputedKey
+      . tweakAddPubKey ctx spd.internalKey
+  where
+    onComputedKey computedKey =
+      XOnlyPubKey outkey == XOnlyPubKey computedKey
+        && expectedParity == keyParity ctx computedKey
+    commitment =
+      taprootCommitment ctx spd.internalKey (Just merkleRoot)
+    merkleRoot =
+      foldl' hashBranch theLeafHash $
+        mapMaybe (digestFromByteString @SHA256) spd.control
+    theLeafHash =
+      (leafHash <$> (.&. 0xFE) . (.leafVersion) <*> (.script))
+        spd
+    expectedParity = bool 0 1 spd.extIsOdd
+
+keyParity :: Ctx -> PubKey -> Word8
+keyParity ctx key =
+  case BS.unpack . marshal ctx $ PublicKey key True of
+    0x02 : _ -> 0x00
+    _ -> 0x01
diff --git a/src/Haskoin/Util.hs b/src/Haskoin/Util.hs
new file mode 100644
--- /dev/null
+++ b/src/Haskoin/Util.hs
@@ -0,0 +1,17 @@
+-- |
+-- Module      : Haskoin.Util
+-- Copyright   : No rights reserved
+-- License     : MIT
+-- Maintainer  : jprupp@protonmail.ch
+-- Stability   : experimental
+-- Portability : POSIX
+--
+-- Marshalling and helper functions.
+module Haskoin.Util
+  ( module Marshal,
+    module Helpers,
+  )
+where
+
+import Haskoin.Util.Helpers as Helpers
+import Haskoin.Util.Marshal as Marshal
diff --git a/src/Haskoin/Util/Arbitrary.hs b/src/Haskoin/Util/Arbitrary.hs
new file mode 100644
--- /dev/null
+++ b/src/Haskoin/Util/Arbitrary.hs
@@ -0,0 +1,23 @@
+-- |
+-- Module      : Haskoin.Test
+-- Copyright   : No rights reserved
+-- License     : MIT
+-- Maintainer  : jprupp@protonmail.ch
+-- Stability   : experimental
+-- Portability : POSIX
+--
+-- Arbitrary instances for testing.
+module Haskoin.Util.Arbitrary
+  ( module X,
+  )
+where
+
+import Haskoin.Util.Arbitrary.Address as X
+import Haskoin.Util.Arbitrary.Block as X
+import Haskoin.Util.Arbitrary.Crypto as X
+import Haskoin.Util.Arbitrary.Keys as X
+import Haskoin.Util.Arbitrary.Message as X
+import Haskoin.Util.Arbitrary.Network as X
+import Haskoin.Util.Arbitrary.Script as X
+import Haskoin.Util.Arbitrary.Transaction as X
+import Haskoin.Util.Arbitrary.Util as X
diff --git a/src/Haskoin/Util/Arbitrary/Address.hs b/src/Haskoin/Util/Arbitrary/Address.hs
new file mode 100644
--- /dev/null
+++ b/src/Haskoin/Util/Arbitrary/Address.hs
@@ -0,0 +1,65 @@
+{-# LANGUAGE TupleSections #-}
+
+-- |
+-- Module      : Haskoin.Test.Address
+-- Copyright   : No rights reserved
+-- License     : MIT
+-- Maintainer  : jprupp@protonmail.ch
+-- Stability   : experimental
+-- Portability : POSIX
+module Haskoin.Util.Arbitrary.Address where
+
+import qualified Data.ByteString as B
+import Haskoin.Address
+import Haskoin.Network.Constants
+import Haskoin.Network.Data
+import Haskoin.Util.Arbitrary.Crypto
+import Haskoin.Util.Arbitrary.Util
+import Test.QuickCheck
+
+-- | Arbitrary pay-to-public-key-hash or pay-to-script-hash address.
+arbitraryAddress :: Gen Address
+arbitraryAddress = oneof [arbitraryPubKeyAddress, arbitraryScriptAddress]
+
+-- | Arbitrary address including pay-to-witness
+arbitraryAddressAll :: Gen Address
+arbitraryAddressAll =
+  oneof
+    [ arbitraryPubKeyAddress,
+      arbitraryScriptAddress,
+      arbitraryWitnessPubKeyAddress,
+      arbitraryWitnessScriptAddress,
+      arbitraryWitnessAddress
+    ]
+
+-- | Arbitrary valid combination of (Network, Address)
+arbitraryNetAddress :: Gen (Network, Address)
+arbitraryNetAddress = do
+  net <- arbitraryNetwork
+  if net `elem` [bch, bchTest, bchTest4, bchRegTest]
+    then (net,) <$> arbitraryAddress
+    else (net,) <$> arbitraryAddressAll
+
+-- | Arbitrary pay-to-public-key-hash address.
+arbitraryPubKeyAddress :: Gen Address
+arbitraryPubKeyAddress = PubKeyAddress <$> arbitraryHash160
+
+-- | Arbitrary pay-to-script-hash address.
+arbitraryScriptAddress :: Gen Address
+arbitraryScriptAddress = ScriptAddress <$> arbitraryHash160
+
+-- | Arbitrary pay-to-witness public key hash
+arbitraryWitnessPubKeyAddress :: Gen Address
+arbitraryWitnessPubKeyAddress = WitnessPubKeyAddress <$> arbitraryHash160
+
+-- | Arbitrary pay-to-witness script hash
+arbitraryWitnessScriptAddress :: Gen Address
+arbitraryWitnessScriptAddress = WitnessPubKeyAddress <$> arbitraryHash160
+
+arbitraryWitnessAddress :: Gen Address
+arbitraryWitnessAddress = do
+  ver <- choose (1, 16)
+  len <- choose (2, 40)
+  ws <- vectorOf len arbitrary
+  let bs = B.pack ws
+  return $ WitnessAddress ver bs
diff --git a/src/Haskoin/Util/Arbitrary/Block.hs b/src/Haskoin/Util/Arbitrary/Block.hs
new file mode 100644
--- /dev/null
+++ b/src/Haskoin/Util/Arbitrary/Block.hs
@@ -0,0 +1,87 @@
+-- |
+-- Module      : Haskoin.Test.Block
+-- Copyright   : No rights reserved
+-- License     : MIT
+-- Maintainer  : jprupp@protonmail.ch
+-- Stability   : experimental
+-- Portability : POSIX
+module Haskoin.Util.Arbitrary.Block where
+
+import qualified Data.HashMap.Strict as HashMap
+import Haskoin.Block
+import Haskoin.Crypto (Ctx)
+import Haskoin.Network.Data
+import Haskoin.Util.Arbitrary.Crypto
+import Haskoin.Util.Arbitrary.Network
+import Haskoin.Util.Arbitrary.Transaction
+import Haskoin.Util.Arbitrary.Util
+import Test.QuickCheck
+
+-- | Block full or arbitrary transactions.
+arbitraryBlock :: Network -> Ctx -> Gen Block
+arbitraryBlock net ctx = do
+  h <- arbitraryBlockHeader
+  c <- choose (0, 10)
+  txs <- vectorOf c (arbitraryTx net ctx)
+  return $ Block h txs
+
+-- | Block header with random hash.
+arbitraryBlockHeader :: Gen BlockHeader
+arbitraryBlockHeader =
+  BlockHeader
+    <$> arbitrary
+    <*> arbitraryBlockHash
+    <*> arbitraryHash256
+    <*> arbitrary
+    <*> arbitrary
+    <*> arbitrary
+
+-- | Arbitrary block hash.
+arbitraryBlockHash :: Gen BlockHash
+arbitraryBlockHash = BlockHash <$> arbitraryHash256
+
+-- | Arbitrary 'GetBlocks' object with at least one block hash.
+arbitraryGetBlocks :: Gen GetBlocks
+arbitraryGetBlocks =
+  GetBlocks
+    <$> arbitrary
+    <*> listOf1 arbitraryBlockHash
+    <*> arbitraryBlockHash
+
+-- | Arbitrary 'GetHeaders' object with at least one block header.
+arbitraryGetHeaders :: Gen GetHeaders
+arbitraryGetHeaders =
+  GetHeaders
+    <$> arbitrary
+    <*> listOf1 arbitraryBlockHash
+    <*> arbitraryBlockHash
+
+-- | Arbitrary 'Headers' object with at least one block header.
+arbitraryHeaders :: Gen Headers
+arbitraryHeaders =
+  Headers <$> listOf1 ((,) <$> arbitraryBlockHeader <*> arbitraryVarInt)
+
+-- | Arbitrary 'MerkleBlock' with at least one hash.
+arbitraryMerkleBlock :: Gen MerkleBlock
+arbitraryMerkleBlock = do
+  bh <- arbitraryBlockHeader
+  ntx <- arbitrary
+  hashes <- listOf1 arbitraryHash256
+  c <- choose (1, 10)
+  flags <- vectorOf (c * 8) arbitrary
+  return $ MerkleBlock bh ntx hashes flags
+
+-- | Arbitrary 'BlockNode'
+arbitraryBlockNode :: Gen BlockNode
+arbitraryBlockNode =
+  BlockNode
+    <$> arbitraryBlockHeader
+    <*> choose (0, maxBound)
+    <*> arbitrarySizedNatural
+    <*> arbitraryBlockHash
+
+-- | Arbitrary 'HeaderMemory'
+arbitraryHeaderMemory :: Gen HeaderMemory
+arbitraryHeaderMemory = do
+  ls <- listOf $ (,) <$> arbitrary <*> arbitraryBSS
+  HeaderMemory (HashMap.fromList ls) <$> arbitraryBlockNode
diff --git a/src/Haskoin/Util/Arbitrary/Crypto.hs b/src/Haskoin/Util/Arbitrary/Crypto.hs
new file mode 100644
--- /dev/null
+++ b/src/Haskoin/Util/Arbitrary/Crypto.hs
@@ -0,0 +1,32 @@
+-- |
+-- Module      : Haskoin.Test.Crypto
+-- Copyright   : No rights reserved
+-- License     : MIT
+-- Maintainer  : jprupp@protonmail.ch
+-- Stability   : experimental
+-- Portability : POSIX
+module Haskoin.Util.Arbitrary.Crypto where
+
+import Haskoin.Crypto.Hash
+import Haskoin.Util.Arbitrary.Util
+import Test.QuickCheck
+
+-- | Arbitrary 160-bit hash.
+arbitraryHash160 :: Gen Hash160
+arbitraryHash160 =
+  ripemd160 <$> arbitraryBSn 20
+
+-- | Arbitrary 256-bit hash.
+arbitraryHash256 :: Gen Hash256
+arbitraryHash256 =
+  sha256 <$> arbitraryBSn 32
+
+-- | Arbitrary 512-bit hash.
+arbitraryHash512 :: Gen Hash512
+arbitraryHash512 =
+  sha512 <$> arbitraryBSn 64
+
+-- | Arbitrary 32-bit checksum.
+arbitraryCheckSum32 :: Gen CheckSum32
+arbitraryCheckSum32 =
+  checkSum32 <$> arbitraryBSn 4
diff --git a/src/Haskoin/Util/Arbitrary/Keys.hs b/src/Haskoin/Util/Arbitrary/Keys.hs
new file mode 100644
--- /dev/null
+++ b/src/Haskoin/Util/Arbitrary/Keys.hs
@@ -0,0 +1,102 @@
+-- |
+-- Module      : Haskoin.Test.Keys
+-- Copyright   : No rights reserved
+-- License     : MIT
+-- Maintainer  : jprupp@protonmail.ch
+-- Stability   : experimental
+-- Portability : POSIX
+module Haskoin.Util.Arbitrary.Keys where
+
+import Crypto.Secp256k1
+import Data.Bits (clearBit)
+import Data.Coerce (coerce)
+import Data.List (foldl')
+import Data.Word (Word32)
+import Haskoin.Crypto.Hash
+import Haskoin.Crypto.Keys.Common
+import Haskoin.Crypto.Keys.Extended
+import Haskoin.Crypto.Keys.Extended.Internal (Fingerprint (..))
+import Haskoin.Crypto.Signature
+import Haskoin.Util.Arbitrary.Crypto
+import Test.QuickCheck
+
+-- | Arbitrary private key with arbitrary compressed flag.
+arbitraryPrivateKey :: Gen PrivateKey
+arbitraryPrivateKey = wrapSecKey <$> arbitrary <*> arbitrary
+
+-- | Arbitrary public key, either compressed or not.
+arbitraryPublicKey :: Ctx -> Gen PublicKey
+arbitraryPublicKey ctx = snd <$> arbitraryKeyPair ctx
+
+-- | Arbitrary keypair, both either compressed or not.
+arbitraryKeyPair :: Ctx -> Gen (PrivateKey, PublicKey)
+arbitraryKeyPair ctx = do
+  k <- arbitraryPrivateKey
+  return (k, derivePublicKey ctx k)
+
+arbitraryFingerprint :: Gen Fingerprint
+arbitraryFingerprint = Fingerprint <$> arbitrary
+
+-- | Arbitrary extended private key.
+arbitraryXPrvKey :: Gen XPrvKey
+arbitraryXPrvKey =
+  XPrvKey
+    <$> arbitrary
+    <*> arbitraryFingerprint
+    <*> arbitrary
+    <*> arbitraryHash256
+    <*> arbitrary
+
+-- | Arbitrary extended public key.
+arbitraryXPubKey :: Ctx -> Gen XPubKey
+arbitraryXPubKey ctx = snd <$> arbitraryXKeyPair ctx
+
+-- | Arbitrary extended public key with its corresponding private key.
+arbitraryXKeyPair :: Ctx -> Gen (XPrvKey, XPubKey)
+arbitraryXKeyPair ctx = (\k -> (k, deriveXPubKey ctx k)) <$> arbitraryXPrvKey
+
+{- Custom derivations -}
+
+-- | Arbitrary derivation index with last bit unset.
+genIndex :: Gen Word32
+genIndex = (`clearBit` 31) <$> arbitrary
+
+-- | Arbitrary BIP-32 path index. Can be hardened or not.
+arbitraryBip32PathIndex :: Gen Bip32PathIndex
+arbitraryBip32PathIndex =
+  oneof
+    [ Bip32SoftIndex <$> genIndex,
+      Bip32HardIndex <$> genIndex
+    ]
+
+-- | Arbitrary BIP-32 derivation path composed of only hardened derivations.
+arbitraryHardPath :: Gen HardPath
+arbitraryHardPath = foldl' (:|) Deriv <$> listOf genIndex
+
+-- | Arbitrary BIP-32 derivation path composed of only non-hardened derivations.
+arbitrarySoftPath :: Gen SoftPath
+arbitrarySoftPath = foldl' (:/) Deriv <$> listOf genIndex
+
+-- | Arbitrary derivation path composed of hardened and non-hardened derivations.
+arbitraryDerivPath :: Gen DerivPath
+arbitraryDerivPath = concatBip32Segments <$> listOf arbitraryBip32PathIndex
+
+-- | Arbitrary parsed derivation path. Can contain 'ParsedPrv', 'ParsedPub' or
+-- 'ParsedEmpty' elements.
+arbitraryParsedPath :: Gen ParsedPath
+arbitraryParsedPath =
+  oneof
+    [ ParsedPrv <$> arbitraryDerivPath,
+      ParsedPub <$> arbitraryDerivPath,
+      ParsedEmpty <$> arbitraryDerivPath
+    ]
+
+-- | Arbitrary message hash, private key, nonce and corresponding signature. The
+-- signature is generated with a random message, random private key and a random
+-- nonce.
+arbitrarySignature :: Ctx -> Gen (Hash256, SecKey, Sig)
+arbitrarySignature ctx = do
+  m <- arbitraryHash256
+  key <- arbitrary
+  let sig = signHash ctx key m
+  return (m, key, sig)
diff --git a/src/Haskoin/Util/Arbitrary/Message.hs b/src/Haskoin/Util/Arbitrary/Message.hs
new file mode 100644
--- /dev/null
+++ b/src/Haskoin/Util/Arbitrary/Message.hs
@@ -0,0 +1,53 @@
+-- |
+-- Module      : Haskoin.Test.Message
+-- Copyright   : No rights reserved
+-- License     : MIT
+-- Maintainer  : jprupp@protonmail.ch
+-- Stability   : experimental
+-- Portability : POSIX
+module Haskoin.Util.Arbitrary.Message where
+
+import Haskoin.Crypto (Ctx)
+import Haskoin.Network.Data
+import Haskoin.Network.Message
+import Haskoin.Util.Arbitrary.Block
+import Haskoin.Util.Arbitrary.Crypto
+import Haskoin.Util.Arbitrary.Network
+import Haskoin.Util.Arbitrary.Transaction
+import Test.QuickCheck
+
+-- | Arbitrary 'MessageHeader'.
+arbitraryMessageHeader :: Gen MessageHeader
+arbitraryMessageHeader =
+  MessageHeader
+    <$> arbitrary
+    <*> arbitraryMessageCommand
+    <*> arbitrary
+    <*> arbitraryCheckSum32
+
+-- | Arbitrary 'Message'.
+arbitraryMessage :: Network -> Ctx -> Gen Message
+arbitraryMessage net ctx =
+  oneof
+    [ MVersion <$> arbitraryVersion,
+      return MVerAck,
+      MAddr <$> arbitraryAddr1,
+      MInv <$> arbitraryInv1,
+      MGetData <$> arbitraryGetData,
+      MNotFound <$> arbitraryNotFound,
+      MGetBlocks <$> arbitraryGetBlocks,
+      MGetHeaders <$> arbitraryGetHeaders,
+      MTx <$> arbitraryTx net ctx,
+      MBlock <$> arbitraryBlock net ctx,
+      MMerkleBlock <$> arbitraryMerkleBlock,
+      MHeaders <$> arbitraryHeaders,
+      return MGetAddr,
+      MFilterLoad <$> arbitraryFilterLoad,
+      MFilterAdd <$> arbitraryFilterAdd,
+      return MFilterClear,
+      MPing <$> arbitraryPing,
+      MPong <$> arbitraryPong,
+      MAlert <$> arbitraryAlert,
+      MReject <$> arbitraryReject,
+      return MSendHeaders
+    ]
diff --git a/src/Haskoin/Util/Arbitrary/Network.hs b/src/Haskoin/Util/Arbitrary/Network.hs
new file mode 100644
--- /dev/null
+++ b/src/Haskoin/Util/Arbitrary/Network.hs
@@ -0,0 +1,181 @@
+-- |
+-- Module      : Haskoin.Test.Network
+-- Copyright   : No rights reserved
+-- License     : MIT
+-- Maintainer  : jprupp@protonmail.ch
+-- Stability   : experimental
+-- Portability : POSIX
+module Haskoin.Util.Arbitrary.Network where
+
+import qualified Data.ByteString as BS (empty, pack)
+import qualified Data.ByteString.Char8 as C8
+import Data.Word (Word16, Word32)
+import Haskoin.Network
+import Haskoin.Util.Arbitrary.Crypto
+import Haskoin.Util.Arbitrary.Util
+import Network.Socket (SockAddr (..))
+import Test.QuickCheck
+
+-- | Arbitrary 'VarInt'.
+arbitraryVarInt :: Gen VarInt
+arbitraryVarInt = VarInt <$> arbitrary
+
+-- | Arbitrary 'VarString'.
+arbitraryVarString :: Gen VarString
+arbitraryVarString = VarString <$> arbitraryBS
+
+-- | Arbitrary 'NetworkAddress'.
+arbitraryNetworkAddress :: Gen NetworkAddress
+arbitraryNetworkAddress = do
+  s <- arbitrary
+  a <- arbitrary
+  p <- arbitrary
+  d <-
+    oneof
+      [ do
+          b <- arbitrary
+          c <- arbitrary
+          d <- arbitrary
+          return $ SockAddrInet6 (fromIntegral p) 0 (a, b, c, d) 0,
+        return $ SockAddrInet (fromIntegral (p :: Word16)) a
+      ]
+  let n = sockToHostAddress d
+  return $ NetworkAddress s n
+
+-- | Arbitrary 'NetworkAddressTime'.
+arbitraryNetworkAddressTime :: Gen (Word32, NetworkAddress)
+arbitraryNetworkAddressTime = (,) <$> arbitrary <*> arbitraryNetworkAddress
+
+-- | Arbitrary 'InvType'.
+arbitraryInvType :: Gen InvType
+arbitraryInvType = elements [InvError, InvTx, InvBlock, InvMerkleBlock]
+
+-- | Arbitrary 'InvVector'.
+arbitraryInvVector :: Gen InvVector
+arbitraryInvVector = InvVector <$> arbitraryInvType <*> arbitraryHash256
+
+-- | Arbitrary non-empty 'Inv'.
+arbitraryInv1 :: Gen Inv
+arbitraryInv1 = Inv <$> listOf1 arbitraryInvVector
+
+-- | Arbitrary 'Version'.
+arbitraryVersion :: Gen Version
+arbitraryVersion =
+  Version
+    <$> arbitrary
+    <*> arbitrary
+    <*> arbitrary
+    <*> arbitraryNetworkAddress
+    <*> arbitraryNetworkAddress
+    <*> arbitrary
+    <*> arbitraryVarString
+    <*> arbitrary
+    <*> arbitrary
+
+-- | Arbitrary non-empty 'Addr'.
+arbitraryAddr1 :: Gen Addr
+arbitraryAddr1 = Addr <$> listOf1 arbitraryNetworkAddressTime
+
+-- | Arbitrary 'Alert' with random payload and signature. Signature is not
+-- valid.
+arbitraryAlert :: Gen Alert
+arbitraryAlert = Alert <$> arbitraryVarString <*> arbitraryVarString
+
+-- | Arbitrary 'Reject'.
+arbitraryReject :: Gen Reject
+arbitraryReject = do
+  m <- arbitraryMessageCommand
+  c <- arbitraryRejectCode
+  s <- arbitraryVarString
+  d <-
+    oneof
+      [ return BS.empty,
+        BS.pack <$> vectorOf 32 arbitrary
+      ]
+  return $ Reject m c s d
+
+-- | Arbitrary 'RejectCode'.
+arbitraryRejectCode :: Gen RejectCode
+arbitraryRejectCode =
+  elements
+    [ RejectMalformed,
+      RejectInvalid,
+      RejectInvalid,
+      RejectDuplicate,
+      RejectNonStandard,
+      RejectDust,
+      RejectInsufficientFee,
+      RejectCheckpoint
+    ]
+
+-- | Arbitrary non-empty 'GetData'.
+arbitraryGetData :: Gen GetData
+arbitraryGetData = GetData <$> listOf1 arbitraryInvVector
+
+-- | Arbitrary 'NotFound'.
+arbitraryNotFound :: Gen NotFound
+arbitraryNotFound = NotFound <$> listOf1 arbitraryInvVector
+
+-- | Arbitrary 'Ping'.
+arbitraryPing :: Gen Ping
+arbitraryPing = Ping <$> arbitrary
+
+-- | Arbitrary 'Pong'.
+arbitraryPong :: Gen Pong
+arbitraryPong = Pong <$> arbitrary
+
+-- | Arbitrary bloom filter flags.
+arbitraryBloomFlags :: Gen BloomFlags
+arbitraryBloomFlags =
+  elements
+    [ BloomUpdateNone,
+      BloomUpdateAll,
+      BloomUpdateP2PubKeyOnly
+    ]
+
+-- | Arbitrary bloom filter with its corresponding number of elements
+-- and false positive rate.
+arbitraryBloomFilter :: Gen (Int, Double, BloomFilter)
+arbitraryBloomFilter = do
+  n <- choose (0, 100000)
+  fp <- choose (1e-8, 1)
+  tweak <- arbitrary
+  fl <- arbitraryBloomFlags
+  return (n, fp, bloomCreate n fp tweak fl)
+
+-- | Arbitrary 'FilterLoad'.
+arbitraryFilterLoad :: Gen FilterLoad
+arbitraryFilterLoad = do
+  (_, _, bf) <- arbitraryBloomFilter
+  return $ FilterLoad bf
+
+-- | Arbitrary 'FilterAdd'.
+arbitraryFilterAdd :: Gen FilterAdd
+arbitraryFilterAdd = FilterAdd <$> arbitraryBS
+
+-- | Arbitrary 'MessageCommand'.
+arbitraryMessageCommand :: Gen MessageCommand
+arbitraryMessageCommand = do
+  ASCIIString str <- arbitrary
+  elements
+    [ MCVersion,
+      MCVerAck,
+      MCAddr,
+      MCInv,
+      MCGetData,
+      MCNotFound,
+      MCGetBlocks,
+      MCGetHeaders,
+      MCTx,
+      MCBlock,
+      MCMerkleBlock,
+      MCHeaders,
+      MCGetAddr,
+      MCFilterLoad,
+      MCFilterAdd,
+      MCFilterClear,
+      MCPing,
+      MCPong,
+      MCAlert,
+      MCOther (C8.take 12 (C8.pack (filter (/= '\NUL') str)))
+    ]
diff --git a/src/Haskoin/Util/Arbitrary/Script.hs b/src/Haskoin/Util/Arbitrary/Script.hs
new file mode 100644
--- /dev/null
+++ b/src/Haskoin/Util/Arbitrary/Script.hs
@@ -0,0 +1,420 @@
+{-# LANGUAGE DuplicateRecordFields #-}
+{-# LANGUAGE ImportQualifiedPost #-}
+{-# LANGUAGE LambdaCase #-}
+{-# LANGUAGE OverloadedRecordDot #-}
+
+-- |
+-- Module      : Haskoin.Test.Script
+-- Copyright   : No rights reserved
+-- License     : MIT
+-- Maintainer  : jprupp@protonmail.ch
+-- Stability   : experimental
+-- Portability : POSIX
+module Haskoin.Util.Arbitrary.Script where
+
+import Crypto.Secp256k1
+import Data.ByteString qualified as B
+import Data.Maybe
+import Data.Word
+import Haskoin.Address
+import Haskoin.Crypto.Keys.Common
+import Haskoin.Network.Constants
+import Haskoin.Network.Data
+import Haskoin.Script
+import Haskoin.Transaction.Common
+import Haskoin.Util
+import Haskoin.Util.Arbitrary.Address
+import Haskoin.Util.Arbitrary.Crypto
+import Haskoin.Util.Arbitrary.Keys
+import Haskoin.Util.Arbitrary.Util
+import Test.QuickCheck
+
+-- | Arbitrary 'Script' with random script ops.
+arbitraryScript :: Gen Script
+arbitraryScript = Script <$> listOf arbitraryScriptOp
+
+-- | Arbitrary 'ScriptOp' (push operations have random data).
+arbitraryScriptOp :: Gen ScriptOp
+arbitraryScriptOp =
+  oneof
+    -- Pushing Data
+    [ opPushData <$> arbitraryBS1,
+      return OP_0,
+      return OP_1NEGATE,
+      return OP_RESERVED,
+      return OP_1,
+      return OP_2,
+      return OP_3,
+      return OP_4,
+      return OP_5,
+      return OP_6,
+      return OP_7,
+      return OP_8,
+      return OP_9,
+      return OP_10,
+      return OP_11,
+      return OP_12,
+      return OP_13,
+      return OP_14,
+      return OP_15,
+      return OP_16,
+      -- Flow control
+      return OP_NOP,
+      return OP_VER,
+      return OP_IF,
+      return OP_NOTIF,
+      return OP_VERIF,
+      return OP_VERNOTIF,
+      return OP_ELSE,
+      return OP_ENDIF,
+      return OP_VERIFY,
+      return OP_RETURN,
+      -- Stack operations
+      return OP_TOALTSTACK,
+      return OP_FROMALTSTACK,
+      return OP_IFDUP,
+      return OP_DEPTH,
+      return OP_DROP,
+      return OP_DUP,
+      return OP_NIP,
+      return OP_OVER,
+      return OP_PICK,
+      return OP_ROLL,
+      return OP_ROT,
+      return OP_SWAP,
+      return OP_TUCK,
+      return OP_2DROP,
+      return OP_2DUP,
+      return OP_3DUP,
+      return OP_2OVER,
+      return OP_2ROT,
+      return OP_2SWAP,
+      -- Splice
+      return OP_CAT,
+      return OP_SUBSTR,
+      return OP_LEFT,
+      return OP_RIGHT,
+      return OP_SIZE,
+      -- Bitwise logic
+      return OP_INVERT,
+      return OP_AND,
+      return OP_OR,
+      return OP_XOR,
+      return OP_EQUAL,
+      return OP_EQUALVERIFY,
+      return OP_RESERVED1,
+      return OP_RESERVED2,
+      -- Arithmetic
+      return OP_1ADD,
+      return OP_1SUB,
+      return OP_2MUL,
+      return OP_2DIV,
+      return OP_NEGATE,
+      return OP_ABS,
+      return OP_NOT,
+      return OP_0NOTEQUAL,
+      return OP_ADD,
+      return OP_SUB,
+      return OP_MUL,
+      return OP_DIV,
+      return OP_MOD,
+      return OP_LSHIFT,
+      return OP_RSHIFT,
+      return OP_BOOLAND,
+      return OP_BOOLOR,
+      return OP_NUMEQUAL,
+      return OP_NUMEQUALVERIFY,
+      return OP_NUMNOTEQUAL,
+      return OP_LESSTHAN,
+      return OP_GREATERTHAN,
+      return OP_LESSTHANOREQUAL,
+      return OP_GREATERTHANOREQUAL,
+      return OP_MIN,
+      return OP_MAX,
+      return OP_WITHIN,
+      -- Crypto
+      return OP_RIPEMD160,
+      return OP_SHA1,
+      return OP_SHA256,
+      return OP_HASH160,
+      return OP_HASH256,
+      return OP_CODESEPARATOR,
+      return OP_CHECKSIG,
+      return OP_CHECKSIGVERIFY,
+      return OP_CHECKMULTISIG,
+      return OP_CHECKMULTISIGVERIFY,
+      -- Expansion
+      return OP_NOP1,
+      return OP_CHECKLOCKTIMEVERIFY,
+      return OP_CHECKSEQUENCEVERIFY,
+      return OP_NOP4,
+      return OP_NOP5,
+      return OP_NOP6,
+      return OP_NOP7,
+      return OP_NOP8,
+      return OP_NOP9,
+      return OP_NOP10,
+      -- Bitcoin Cash Nov 2018 hard fork
+      return OP_CHECKDATASIG,
+      return OP_CHECKDATASIGVERIFY,
+      -- Bitcoin Cash May 2020 hard fork
+      return OP_REVERSEBYTES,
+      -- Other
+      return OP_PUBKEYHASH,
+      return OP_PUBKEY,
+      return $ OP_INVALIDOPCODE 0xff
+    ]
+
+-- | Arbtirary 'ScriptOp' with a value in @[OP_1 .. OP_16]@.
+arbitraryIntScriptOp :: Gen ScriptOp
+arbitraryIntScriptOp =
+  elements
+    [ OP_1,
+      OP_2,
+      OP_3,
+      OP_4,
+      OP_5,
+      OP_6,
+      OP_7,
+      OP_8,
+      OP_9,
+      OP_10,
+      OP_11,
+      OP_12,
+      OP_13,
+      OP_14,
+      OP_15,
+      OP_16
+    ]
+
+-- | Arbitrary 'PushDataType'.
+arbitraryPushDataType :: Gen PushDataType
+arbitraryPushDataType = elements [OPCODE, OPDATA1, OPDATA2, OPDATA4]
+
+-- | Arbitrary 'SigHash' (including invalid/unknown sighash codes).
+arbitrarySigHash :: Gen SigHash
+arbitrarySigHash = fromIntegral <$> (arbitrary :: Gen Word32)
+
+-- | Arbitrary valid 'SigHash'.
+arbitraryValidSigHash :: Network -> Gen SigHash
+arbitraryValidSigHash net = do
+  sh <- elements [sigHashAll, sigHashNone, sigHashSingle]
+  f1 <-
+    elements $
+      if isJust net.sigHashForkId
+        then [id, setForkIdFlag]
+        else [id]
+  f2 <- elements [id, setAnyoneCanPay]
+  return $ f1 $ f2 sh
+
+arbitrarySigHashFlag :: Gen SigHashFlag
+arbitrarySigHashFlag =
+  elements
+    [ SIGHASH_ALL,
+      SIGHASH_NONE,
+      SIGHASH_SINGLE,
+      SIGHASH_FORKID,
+      SIGHASH_ANYONECANPAY
+    ]
+
+-- | Arbitrary message hash, private key and corresponding 'TxSignature'. The
+-- signature is generated deterministically using a random message and a random
+-- private key.
+arbitraryTxSignature :: Network -> Ctx -> Gen (TxHash, SecKey, TxSignature)
+arbitraryTxSignature net ctx = do
+  (m, key, sig) <- arbitrarySignature ctx
+  sh <- (fromIntegral <$> (arbitrary :: Gen Word8)) `suchThat` filterBad
+  let txsig = TxSignature sig sh
+  return (TxHash m, key, txsig)
+  where
+    filterBad sh =
+      not $
+        isSigHashUnknown sh
+          || isNothing net.sigHashForkId && hasForkIdFlag sh
+
+-- | Arbitrary transaction signature that could also be empty.
+arbitraryTxSignatureEmpty :: Network -> Ctx -> Gen TxSignature
+arbitraryTxSignatureEmpty net ctx =
+  frequency
+    [ (1, return TxSignatureEmpty),
+      (10, lst3 <$> arbitraryTxSignature net ctx)
+    ]
+
+-- | Arbitrary m of n parameters.
+arbitraryMSParam :: Gen (Int, Int)
+arbitraryMSParam = do
+  m <- choose (1, 16)
+  n <- choose (m, 16)
+  return (m, n)
+
+-- | Arbitrary 'ScriptOutput' (Can by any valid type).
+arbitraryScriptOutput :: Network -> Ctx -> Gen ScriptOutput
+arbitraryScriptOutput net ctx =
+  oneof $
+    [ arbitraryPKOutput ctx,
+      arbitraryPKHashOutput,
+      arbitraryMSOutput ctx,
+      arbitrarySHOutput,
+      arbitraryDCOutput
+    ]
+      ++ if net.segWit
+        then
+          [ arbitraryWPKHashOutput,
+            arbitraryWSHOutput,
+            arbitraryWitOutput
+          ]
+        else []
+
+-- | Arbitrary 'ScriptOutput' of type 'PayPK', 'PayPKHash' or 'PayMS'
+-- (Not 'PayScriptHash', 'DataCarrier', or SegWit)
+arbitrarySimpleOutput :: Ctx -> Gen ScriptOutput
+arbitrarySimpleOutput ctx =
+  oneof
+    [ arbitraryPKOutput ctx,
+      arbitraryPKHashOutput,
+      arbitraryMSOutput ctx
+    ]
+
+-- | Arbitrary 'ScriptOutput' of type 'PayPK'
+arbitraryPKOutput :: Ctx -> Gen ScriptOutput
+arbitraryPKOutput ctx = PayPK . snd <$> arbitraryKeyPair ctx
+
+-- | Arbitrary 'ScriptOutput' of type 'PayPKHash'
+arbitraryPKHashOutput :: Gen ScriptOutput
+arbitraryPKHashOutput = PayPKHash <$> arbitraryHash160
+
+-- | Arbitrary 'PayWitnessPKHash' output.
+arbitraryWPKHashOutput :: Gen ScriptOutput
+arbitraryWPKHashOutput = PayWitnessPKHash <$> arbitraryHash160
+
+-- | Arbitrary 'PayWitnessScriptHash' output.
+arbitraryWSHOutput :: Gen ScriptOutput
+arbitraryWSHOutput = PayWitnessScriptHash <$> arbitraryHash256
+
+arbitraryWitOutput :: Gen ScriptOutput
+arbitraryWitOutput = do
+  ver <- choose (1, 16)
+  len <- choose (2, 40)
+  ws <- vectorOf len arbitrary
+  let bs = B.pack ws
+  return $ PayWitness ver bs
+
+-- | Arbitrary 'ScriptOutput' of type 'PayMS'.
+arbitraryMSOutput :: Ctx -> Gen ScriptOutput
+arbitraryMSOutput ctx = do
+  (m, n) <- arbitraryMSParam
+  keys <- map snd <$> vectorOf n (arbitraryKeyPair ctx)
+  return $ PayMulSig keys m
+
+-- | Arbitrary 'ScriptOutput' of type 'PayMS', only using compressed keys.
+arbitraryMSOutputC :: Ctx -> Gen ScriptOutput
+arbitraryMSOutputC ctx = do
+  (m, n) <- arbitraryMSParam
+  keys <-
+    map snd
+      <$> vectorOf n (arbitraryKeyPair ctx `suchThat` ((.compress) . snd))
+  return $ PayMulSig keys m
+
+-- | Arbitrary 'ScriptOutput' of type 'PayScriptHash'.
+arbitrarySHOutput :: Gen ScriptOutput
+arbitrarySHOutput = PayScriptHash . (.hash160) <$> arbitraryScriptAddress
+
+-- | Arbitrary 'ScriptOutput' of type 'DataCarrier'.
+arbitraryDCOutput :: Gen ScriptOutput
+arbitraryDCOutput = DataCarrier <$> arbitraryBS1
+
+-- | Arbitrary 'ScriptInput'.
+arbitraryScriptInput :: Network -> Ctx -> Gen ScriptInput
+arbitraryScriptInput net ctx =
+  oneof
+    [ arbitraryPKInput net ctx,
+      arbitraryPKHashInput net ctx,
+      arbitraryMSInput net ctx,
+      arbitrarySHInput net ctx
+    ]
+
+-- | Arbitrary 'ScriptInput' of type 'SpendPK', 'SpendPKHash' or 'SpendMulSig'
+-- (not 'ScriptHashInput')
+arbitrarySimpleInput :: Network -> Ctx -> Gen ScriptInput
+arbitrarySimpleInput net ctx =
+  oneof
+    [ arbitraryPKInput net ctx,
+      arbitraryPKHashInput net ctx,
+      arbitraryMSInput net ctx
+    ]
+
+-- | Arbitrary 'ScriptInput' of type 'SpendPK'.
+arbitraryPKInput :: Network -> Ctx -> Gen ScriptInput
+arbitraryPKInput net ctx = RegularInput . SpendPK <$> arbitraryTxSignatureEmpty net ctx
+
+-- | Arbitrary 'ScriptInput' of type 'SpendPK'.
+arbitraryPKHashInput :: Network -> Ctx -> Gen ScriptInput
+arbitraryPKHashInput net ctx = do
+  sig <- arbitraryTxSignatureEmpty net ctx
+  key <- snd <$> arbitraryKeyPair ctx
+  return $ RegularInput $ SpendPKHash sig key
+
+-- | Like 'arbitraryPKHashInput' without empty signatures.
+arbitraryPKHashInputFull :: Network -> Ctx -> Gen ScriptInput
+arbitraryPKHashInputFull net ctx = do
+  sig <- lst3 <$> arbitraryTxSignature net ctx
+  key <- snd <$> arbitraryKeyPair ctx
+  return $ RegularInput $ SpendPKHash sig key
+
+-- | Like above but only compressed.
+arbitraryPKHashInputFullC :: Network -> Ctx -> Gen ScriptInput
+arbitraryPKHashInputFullC net ctx = do
+  sig <- lst3 <$> arbitraryTxSignature net ctx
+  key <- fmap snd $ arbitraryKeyPair ctx `suchThat` ((.compress) . snd)
+  return $ RegularInput $ SpendPKHash sig key
+
+-- | Arbitrary 'ScriptInput' of type 'SpendMulSig'.
+arbitraryMSInput :: Network -> Ctx -> Gen ScriptInput
+arbitraryMSInput net ctx = do
+  m <- fst <$> arbitraryMSParam
+  sigs <- vectorOf m (arbitraryTxSignatureEmpty net ctx)
+  return $ RegularInput $ SpendMulSig sigs
+
+-- | Arbitrary 'ScriptInput' of type 'ScriptHashInput'.
+arbitrarySHInput :: Network -> Ctx -> Gen ScriptInput
+arbitrarySHInput net ctx = do
+  i <- arbitrarySimpleInput net ctx
+  ScriptHashInput i.get <$> arbitrarySimpleOutput ctx
+
+-- | Arbitrary 'ScriptInput' of type 'ScriptHashInput' containing a
+-- 'RedeemScript' of type 'PayMulSig' and an input of type 'SpendMulSig'.
+arbitraryMulSigSHInput :: Network -> Ctx -> Gen ScriptInput
+arbitraryMulSigSHInput net ctx =
+  arbitraryMSOutput ctx >>= \case
+    rdm@(PayMulSig _ m) -> do
+      sigs <- vectorOf m (arbitraryTxSignatureEmpty net ctx)
+      return $ ScriptHashInput (SpendMulSig sigs) rdm
+    _ -> undefined
+
+-- | Arbitrary 'ScriptInput' of type 'ScriptHashInput' containing a
+-- 'RedeemScript' of type 'PayMulSig' and an input of type 'SpendMulSig'.
+arbitraryMulSigSHInputC :: Network -> Ctx -> Gen ScriptInput
+arbitraryMulSigSHInputC net ctx =
+  arbitraryMSOutputC ctx >>= \case
+    rdm@(PayMulSig _ m) -> do
+      sigs <- vectorOf m (arbitraryTxSignatureEmpty net ctx)
+      return $ ScriptHashInput (SpendMulSig sigs) rdm
+    _ -> undefined
+
+-- | Like 'arbitraryMulSigSHCInput' with no empty signatures.
+arbitraryMulSigSHInputFull :: Network -> Ctx -> Gen ScriptInput
+arbitraryMulSigSHInputFull net ctx =
+  arbitraryMSOutput ctx >>= \case
+    rdm@(PayMulSig _ m) -> do
+      sigs <- map lst3 <$> vectorOf m (arbitraryTxSignature net ctx)
+      return $ ScriptHashInput (SpendMulSig sigs) rdm
+    _ -> undefined
+
+-- | Like 'arbitraryMulSigSHCInput' with no empty signatures.
+arbitraryMulSigSHInputFullC :: Network -> Ctx -> Gen ScriptInput
+arbitraryMulSigSHInputFullC net ctx =
+  arbitraryMSOutputC ctx >>= \case
+    rdm@(PayMulSig _ m) -> do
+      sigs <- map lst3 <$> vectorOf m (arbitraryTxSignature net ctx)
+      return $ ScriptHashInput (SpendMulSig sigs) rdm
+    _ -> undefined
diff --git a/src/Haskoin/Util/Arbitrary/Transaction.hs b/src/Haskoin/Util/Arbitrary/Transaction.hs
new file mode 100644
--- /dev/null
+++ b/src/Haskoin/Util/Arbitrary/Transaction.hs
@@ -0,0 +1,283 @@
+{-# LANGUAGE DuplicateRecordFields #-}
+{-# LANGUAGE ImportQualifiedPost #-}
+{-# LANGUAGE OverloadedRecordDot #-}
+
+-- |
+-- Module      : Haskoin.Test.Transaction
+-- Copyright   : No rights reserved
+-- License     : MIT
+-- Maintainer  : jprupp@protonmail.ch
+-- Stability   : experimental
+-- Portability : POSIX
+module Haskoin.Util.Arbitrary.Transaction where
+
+import Control.Monad
+import Data.ByteString qualified as BS
+import Data.Either (fromRight)
+import Data.List (nub, nubBy, permutations)
+import Data.Word (Word64)
+import Haskoin.Address
+import Haskoin.Crypto (Ctx)
+import Haskoin.Crypto.Keys.Common
+import Haskoin.Network.Constants
+import Haskoin.Network.Data
+import Haskoin.Script
+import Haskoin.Transaction
+import Haskoin.Util
+import Haskoin.Util.Arbitrary.Crypto
+import Haskoin.Util.Arbitrary.Keys
+import Haskoin.Util.Arbitrary.Script
+import Haskoin.Util.Arbitrary.Util
+import Test.QuickCheck
+
+-- | Wrapped coin value for testing.
+newtype TestCoin = TestCoin {getTestCoin :: Word64}
+  deriving (Eq, Show)
+
+instance Coin TestCoin where
+  coinValue = getTestCoin
+
+-- | Arbitrary transaction hash (for non-existent transaction).
+arbitraryTxHash :: Gen TxHash
+arbitraryTxHash = TxHash <$> arbitraryHash256
+
+-- | Arbitrary amount of Satoshi as 'Word64' (Between 1 and 21e14)
+arbitrarySatoshi :: Network -> Gen TestCoin
+arbitrarySatoshi net = TestCoin <$> choose (1, net.maxSatoshi)
+
+-- | Arbitrary 'OutPoint'.
+arbitraryOutPoint :: Gen OutPoint
+arbitraryOutPoint = OutPoint <$> arbitraryTxHash <*> arbitrary
+
+-- | Arbitrary 'TxOut'.
+arbitraryTxOut :: Network -> Ctx -> Gen TxOut
+arbitraryTxOut net ctx =
+  TxOut
+    <$> (getTestCoin <$> arbitrarySatoshi net)
+    <*> (marshal ctx <$> arbitraryScriptOutput net ctx)
+
+-- | Arbitrary 'TxIn'.
+arbitraryTxIn :: Network -> Ctx -> Gen TxIn
+arbitraryTxIn net ctx =
+  TxIn
+    <$> arbitraryOutPoint
+    <*> (marshal (net, ctx) <$> arbitraryScriptInput net ctx)
+    <*> arbitrary
+
+-- | Arbitrary transaction. Can be regular or with witnesses.
+arbitraryTx :: Network -> Ctx -> Gen Tx
+arbitraryTx net ctx =
+  oneof [arbitraryLegacyTx net ctx, arbitraryWitnessTx net ctx]
+
+-- | Arbitrary regular transaction.
+arbitraryLegacyTx :: Network -> Ctx -> Gen Tx
+arbitraryLegacyTx net ctx = arbitraryWLTx net ctx False
+
+-- | Arbitrary witness transaction (witness data is fake).
+arbitraryWitnessTx :: Network -> Ctx -> Gen Tx
+arbitraryWitnessTx net ctx = arbitraryWLTx net ctx True
+
+-- | Arbitrary witness or legacy transaction.
+arbitraryWLTx :: Network -> Ctx -> Bool -> Gen Tx
+arbitraryWLTx net ctx wit = do
+  ni <- choose (1, 5)
+  no <- choose (1, 5)
+  inps <- vectorOf ni (arbitraryTxIn net ctx)
+  outs <- vectorOf no (arbitraryTxOut net ctx)
+  let uniqueInps = nubBy (\a b -> a.outpoint == b.outpoint) inps
+  w <-
+    if wit
+      then vectorOf (length uniqueInps) (listOf arbitraryBS)
+      else return []
+  Tx <$> arbitrary <*> pure uniqueInps <*> pure outs <*> pure w <*> arbitrary
+
+-- | Arbitrary transaction containing only inputs of type 'SpendPKHash',
+-- 'SpendScriptHash' (multisig) and outputs of type 'PayPKHash' and 'PaySH'.
+-- Only compressed public keys are used.
+arbitraryAddrOnlyTx :: Network -> Ctx -> Gen Tx
+arbitraryAddrOnlyTx net ctx = do
+  ni <- choose (1, 5)
+  no <- choose (1, 5)
+  inps <- vectorOf ni (arbitraryAddrOnlyTxIn net ctx)
+  outs <- vectorOf no (arbitraryAddrOnlyTxOut net ctx)
+  Tx <$> arbitrary <*> pure inps <*> pure outs <*> pure [] <*> arbitrary
+
+-- | Like 'arbitraryAddrOnlyTx' without empty signatures in the inputs.
+arbitraryAddrOnlyTxFull :: Network -> Ctx -> Gen Tx
+arbitraryAddrOnlyTxFull net ctx = do
+  ni <- choose (1, 5)
+  no <- choose (1, 5)
+  inps <- vectorOf ni (arbitraryAddrOnlyTxInFull net ctx)
+  outs <- vectorOf no (arbitraryAddrOnlyTxOut net ctx)
+  Tx <$> arbitrary <*> pure inps <*> pure outs <*> pure [] <*> arbitrary
+
+-- | Arbitrary TxIn that can only be of type 'SpendPKHash' or 'SpendScriptHash'
+-- (multisig). Only compressed public keys are used.
+arbitraryAddrOnlyTxIn :: Network -> Ctx -> Gen TxIn
+arbitraryAddrOnlyTxIn net ctx = do
+  inp <- oneof [arbitraryPKHashInput net ctx, arbitraryMulSigSHInput net ctx]
+  TxIn <$> arbitraryOutPoint <*> pure (marshal (net, ctx) inp) <*> arbitrary
+
+-- | like 'arbitraryAddrOnlyTxIn' with no empty signatures.
+arbitraryAddrOnlyTxInFull :: Network -> Ctx -> Gen TxIn
+arbitraryAddrOnlyTxInFull net ctx = do
+  inp <-
+    oneof [arbitraryPKHashInputFullC net ctx, arbitraryMulSigSHInputFullC net ctx]
+  TxIn <$> arbitraryOutPoint <*> pure (marshal (net, ctx) inp) <*> arbitrary
+
+-- | Arbitrary 'TxOut' that can only be of type 'PayPKHash' or 'PaySH'.
+arbitraryAddrOnlyTxOut :: Network -> Ctx -> Gen TxOut
+arbitraryAddrOnlyTxOut net ctx = do
+  v <- getTestCoin <$> arbitrarySatoshi net
+  out <- oneof [arbitraryPKHashOutput, arbitrarySHOutput]
+  return $ TxOut v $ marshal ctx out
+
+-- | Arbitrary 'SigInput' with the corresponding private keys used
+-- to generate the 'ScriptOutput' or 'RedeemScript'.
+arbitrarySigInput :: Network -> Ctx -> Gen (SigInput, [PrivateKey])
+arbitrarySigInput net ctx =
+  oneof
+    [ wrapKey <$> arbitraryPKSigInput net ctx,
+      wrapKey <$> arbitraryPKHashSigInput net ctx,
+      arbitraryMSSigInput net ctx,
+      arbitrarySHSigInput net ctx,
+      wrapKey <$> arbitraryWPKHSigInput net ctx,
+      arbitraryWSHSigInput net ctx
+    ]
+
+-- | Arbitrary 'SigInput' with a 'ScriptOutput' of type 'PayPK'.
+arbitraryPKSigInput :: Network -> Ctx -> Gen (SigInput, PrivateKey)
+arbitraryPKSigInput net ctx = arbitraryAnyInput net ctx False
+
+-- | Arbitrary 'SigInput' with a 'ScriptOutput' of type 'PayPKHash'.
+arbitraryPKHashSigInput :: Network -> Ctx -> Gen (SigInput, PrivateKey)
+arbitraryPKHashSigInput net ctx = arbitraryAnyInput net ctx True
+
+-- | Arbitrary 'SigInput'.
+arbitraryAnyInput :: Network -> Ctx -> Bool -> Gen (SigInput, PrivateKey)
+arbitraryAnyInput net ctx pkh = do
+  (k, p) <- arbitraryKeyPair ctx
+  let out
+        | pkh = PayPKHash (pubKeyAddr ctx p).hash160
+        | otherwise = PayPK p
+  (val, op, sh) <- arbitraryInputStuff net
+  return (SigInput out val op sh Nothing, k)
+
+-- | Arbitrary value, out point and sighash for an input.
+arbitraryInputStuff :: Network -> Gen (Word64, OutPoint, SigHash)
+arbitraryInputStuff net = do
+  val <- getTestCoin <$> arbitrarySatoshi net
+  op <- arbitraryOutPoint
+  sh <- arbitraryValidSigHash net
+  return (val, op, sh)
+
+-- | Arbitrary 'SigInput' with a 'ScriptOutput' of type 'PayMulSig'.
+arbitraryMSSigInput :: Network -> Ctx -> Gen (SigInput, [PrivateKey])
+arbitraryMSSigInput net ctx = do
+  (m, n) <- arbitraryMSParam
+  ks <- vectorOf n (arbitraryKeyPair ctx)
+  let out = PayMulSig (map snd ks) m
+  (val, op, sh) <- arbitraryInputStuff net
+  perm <- choose (0, n - 1)
+  let ksPerm = map fst $ take m $ permutations ks !! perm
+  return (SigInput out val op sh Nothing, ksPerm)
+
+-- | Arbitrary 'SigInput' with 'ScriptOutput' of type 'PaySH' and a
+-- 'RedeemScript'.
+arbitrarySHSigInput :: Network -> Ctx -> Gen (SigInput, [PrivateKey])
+arbitrarySHSigInput net ctx = do
+  (SigInput rdm val op sh _, ks) <-
+    oneof
+      [ wrapKey <$> arbitraryPKSigInput net ctx,
+        wrapKey <$> arbitraryPKHashSigInput net ctx,
+        arbitraryMSSigInput net ctx
+      ]
+  let out = PayScriptHash (payToScriptAddress ctx rdm).hash160
+  return (SigInput out val op sh $ Just rdm, ks)
+
+arbitraryWPKHSigInput :: Network -> Ctx -> Gen (SigInput, PrivateKey)
+arbitraryWPKHSigInput net ctx = do
+  (k, p) <- arbitraryKeyPair ctx
+  (val, op, sh) <- arbitraryInputStuff net
+  let out = PayWitnessPKHash (pubKeyAddr ctx p).hash160
+  return (SigInput out val op sh Nothing, k)
+
+arbitraryWSHSigInput :: Network -> Ctx -> Gen (SigInput, [PrivateKey])
+arbitraryWSHSigInput net ctx = do
+  (SigInput rdm val op sh _, ks) <-
+    oneof
+      [ wrapKey <$> arbitraryPKSigInput net ctx,
+        wrapKey <$> arbitraryPKHashSigInput net ctx,
+        arbitraryMSSigInput net ctx
+      ]
+  let out = PayWitnessScriptHash (payToWitnessScriptAddress ctx rdm).hash256
+  return (SigInput out val op sh $ Just rdm, ks)
+
+-- | Arbitrary 'Tx' (empty 'TxIn'), 'SigInputs' and private keys that can be
+-- passed to 'signTx' or 'detSignTx' to fully sign the 'Tx'.
+arbitrarySigningData :: Network -> Ctx -> Gen (Tx, [SigInput], [PrivateKey])
+arbitrarySigningData net ctx = do
+  v <- arbitrary
+  ni <- choose (1, 5)
+  no <- choose (1, 5)
+  sigis <- vectorOf ni (arbitrarySigInput net ctx)
+  let uSigis = nubBy (\(a, _) (b, _) -> a.outpoint == b.outpoint) sigis
+  inps <- forM uSigis $ \(s, _) -> TxIn s.outpoint BS.empty <$> arbitrary
+  outs <- vectorOf no (arbitraryTxOut net ctx)
+  l <- arbitrary
+  perm <- choose (0, length inps - 1)
+  let tx = Tx v (permutations inps !! perm) outs [] l
+      keys = concatMap snd uSigis
+  return (tx, map fst uSigis, keys)
+
+-- | Arbitrary transaction with empty inputs.
+arbitraryEmptyTx :: Network -> Ctx -> Gen Tx
+arbitraryEmptyTx net ctx = do
+  v <- arbitrary
+  no <- choose (1, 5)
+  ni <- choose (1, 5)
+  outs <- vectorOf no (arbitraryTxOut net ctx)
+  ops <- vectorOf ni arbitraryOutPoint
+  t <- arbitrary
+  s <- arbitrary
+  return $ Tx v (map (\op -> TxIn op BS.empty s) (nub ops)) outs [] t
+
+-- | Arbitrary partially-signed transactions.
+arbitraryPartialTxs ::
+  Network -> Ctx -> Gen ([Tx], [(ScriptOutput, Word64, OutPoint, Int, Int)])
+arbitraryPartialTxs net ctx = do
+  tx <- arbitraryEmptyTx net ctx
+  res <-
+    forM (map (.outpoint) tx.inputs) $ \op -> do
+      (so, val, rdmM, prvs, m, n) <- arbitraryData
+      txs <- mapM (singleSig so val rdmM tx op . (.key)) prvs
+      return (txs, (so, val, op, m, n))
+  return (concatMap fst res, map snd res)
+  where
+    singleSig so val rdmM tx op prv = do
+      sh <- arbitraryValidSigHash net
+      let sigi = SigInput so val op sh rdmM
+      return . fromRight (error "Could not decode transaction") $
+        signTx net ctx tx [sigi] [prv]
+    arbitraryData = do
+      (m, n) <- arbitraryMSParam
+      val <- getTestCoin <$> arbitrarySatoshi net
+      nPrv <- choose (m, n)
+      keys <- vectorOf n (arbitraryKeyPair ctx)
+      perm <- choose (0, length keys - 1)
+      let pubKeys = map snd keys
+          prvKeys = take nPrv $ permutations (map fst keys) !! perm
+      let so = PayMulSig pubKeys m
+      elements
+        [ (so, val, Nothing, prvKeys, m, n),
+          ( PayScriptHash (payToScriptAddress ctx so).hash160,
+            val,
+            Just so,
+            prvKeys,
+            m,
+            n
+          )
+        ]
+
+wrapKey :: (SigInput, PrivateKey) -> (SigInput, [PrivateKey])
+wrapKey (s, k) = (s, [k])
diff --git a/src/Haskoin/Util/Arbitrary/Util.hs b/src/Haskoin/Util/Arbitrary/Util.hs
new file mode 100644
--- /dev/null
+++ b/src/Haskoin/Util/Arbitrary/Util.hs
@@ -0,0 +1,254 @@
+{-# LANGUAGE ExistentialQuantification #-}
+{-# LANGUAGE OverloadedRecordDot #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+-- |
+-- Module      : Haskoin.Test.Util
+-- Copyright   : No rights reserved
+-- License     : MIT
+-- Maintainer  : jprupp@protonmail.ch
+-- Stability   : experimental
+-- Portability : POSIX
+module Haskoin.Util.Arbitrary.Util
+  ( arbitraryBS,
+    arbitraryBS1,
+    arbitraryBSn,
+    arbitraryBSS,
+    arbitraryBSS1,
+    arbitraryBSSn,
+    arbitraryMaybe,
+    arbitraryNetwork,
+    arbitraryUTCTime,
+    ReadBox (..),
+    JsonBox (..),
+    MarshalJsonBox (..),
+    SerialBox (..),
+    MarshalBox (..),
+    IdentityTests (..),
+    testIdentity,
+    testSerial,
+    testRead,
+    testJson,
+  )
+where
+
+import Control.Monad (forM_, (<=<))
+import qualified Data.Aeson as A
+import qualified Data.Aeson.Encoding as A
+import qualified Data.Aeson.Types as A
+import Data.ByteString (ByteString, pack)
+import Data.ByteString.Lazy (fromStrict, toStrict)
+import qualified Data.ByteString.Short as BSS
+import Data.Bytes.Get
+import Data.Bytes.Put
+import Data.Bytes.Serial
+import Data.Default
+import qualified Data.Map.Strict as Map
+import Data.Proxy
+import Data.Time.Clock (UTCTime (..))
+import Data.Time.Clock.POSIX (posixSecondsToUTCTime)
+import qualified Data.Typeable as T
+import Data.Word (Word32)
+import Haskoin.Crypto (Ctx)
+import Haskoin.Network.Constants
+import Haskoin.Network.Data
+import Haskoin.Util
+import Test.Hspec (Spec, describe, shouldBe, shouldSatisfy)
+import Test.Hspec.QuickCheck (prop)
+import Test.QuickCheck
+
+-- | Arbitrary strict 'ByteString'.
+arbitraryBS :: Gen ByteString
+arbitraryBS = pack <$> arbitrary
+
+-- | Arbitrary non-empty strict 'ByteString'
+arbitraryBS1 :: Gen ByteString
+arbitraryBS1 = pack <$> listOf1 arbitrary
+
+-- | Arbitrary strict 'ByteString' of a given length
+arbitraryBSn :: Int -> Gen ByteString
+arbitraryBSn n = pack <$> vectorOf n arbitrary
+
+-- | Arbitrary 'ShortByteString'.
+arbitraryBSS :: Gen BSS.ShortByteString
+arbitraryBSS = BSS.pack <$> arbitrary
+
+-- | Arbitrary non-empty 'ShortByteString'
+arbitraryBSS1 :: Gen BSS.ShortByteString
+arbitraryBSS1 = BSS.pack <$> listOf1 arbitrary
+
+-- | Arbitrary 'ShortByteString' of a given length
+arbitraryBSSn :: Int -> Gen BSS.ShortByteString
+arbitraryBSSn n = BSS.pack <$> vectorOf n arbitrary
+
+-- | Arbitrary UTCTime that generates dates after 01 Jan 1970 01:00:00 CET
+arbitraryUTCTime :: Gen UTCTime
+arbitraryUTCTime = do
+  w <- arbitrary :: Gen Word32
+  return $ posixSecondsToUTCTime $ realToFrac w
+
+-- | Generate a Maybe from a Gen a
+arbitraryMaybe :: Gen a -> Gen (Maybe a)
+arbitraryMaybe g =
+  frequency
+    [ (1, return Nothing),
+      (5, Just <$> g)
+    ]
+
+-- | Generate an Network
+arbitraryNetwork :: Gen Network
+arbitraryNetwork = elements allNets
+
+-- Helpers for creating Serial and JSON Identity tests
+
+instance Show Ctx where
+  show _ = "Ctx"
+
+data ReadBox
+  = forall a.
+    (Read a, Show a, Eq a, T.Typeable a) =>
+    ReadBox (Gen a)
+
+data JsonBox
+  = forall a.
+    (Show a, Eq a, T.Typeable a, A.ToJSON a, A.FromJSON a) =>
+    JsonBox (Gen a)
+
+data MarshalJsonBox
+  = forall s a.
+    (Show a, Show s, Eq a, T.Typeable a, MarshalJSON s a) =>
+    MarshalJsonBox (Gen (s, a))
+
+data SerialBox
+  = forall a.
+    (Show a, Eq a, T.Typeable a, Serial a) =>
+    SerialBox (Gen a)
+
+data MarshalBox
+  = forall s a.
+    (Show a, Show s, Eq a, T.Typeable a, Marshal s a) =>
+    MarshalBox (Gen (s, a))
+
+data IdentityTests = IdentityTests
+  { readTests :: [ReadBox],
+    jsonTests :: [JsonBox],
+    marshalJsonTests :: [MarshalJsonBox],
+    serialTests :: [SerialBox],
+    marshalTests :: [MarshalBox]
+  }
+
+instance Default IdentityTests where
+  def =
+    IdentityTests
+      { readTests = [],
+        jsonTests = [],
+        marshalJsonTests = [],
+        serialTests = [],
+        marshalTests = []
+      }
+
+testIdentity :: IdentityTests -> Spec
+testIdentity t = do
+  describe "Read/Show Encoding" $
+    forM_ t.readTests $
+      \(ReadBox g) -> testRead g
+  describe "Data.Aeson Encoding" $
+    forM_ t.jsonTests $
+      \(JsonBox g) -> testJson g
+  describe "MarshalJSON Encoding" $
+    forM_ t.marshalJsonTests $
+      \(MarshalJsonBox g) -> testMarshalJson g
+  describe "Binary Encoding" $
+    forM_ t.serialTests $
+      \(SerialBox g) -> testSerial g
+  describe "Marshal Encoding" $
+    forM_ t.marshalTests $
+      \(MarshalBox g) -> testMarshal g
+
+-- | Generate Read/Show identity tests
+testRead ::
+  (Eq a, Read a, Show a, T.Typeable a) => Gen a -> Spec
+testRead gen =
+  prop ("read/show identity for " <> name) $
+    forAll gen $
+      \x -> (read . show) x `shouldBe` x
+  where
+    name = show $ T.typeRep $ proxy gen
+    proxy :: Gen a -> Proxy a
+    proxy = const Proxy
+
+-- | Generate binary identity tests
+testSerial ::
+  (Eq a, Show a, T.Typeable a, Serial a) => Gen a -> Spec
+testSerial gen =
+  prop ("Binary encoding/decoding identity for " <> name) $
+    forAll gen $ \x -> do
+      (runGetL deserialize . runPutL . serialize) x `shouldBe` x
+      (runGetL deserialize . fromStrict . runPutS . serialize) x `shouldBe` x
+      (runGetS deserialize . runPutS . serialize) x `shouldBe` Right x
+      (runGetS deserialize . toStrict . runPutL . serialize) x `shouldBe` Right x
+  where
+    name = show $ T.typeRep $ proxy gen
+    proxy :: Gen a -> Proxy a
+    proxy = const Proxy
+
+-- | Generate Marshal identity tests
+testMarshal ::
+  (Eq a, Show a, Show s, T.Typeable a, Marshal s a) =>
+  Gen (s, a) ->
+  Spec
+testMarshal gen = do
+  prop ("Marshal marshalPut/marshalGet identity for " <> name) $
+    forAll gen $ \(s, a) -> do
+      (unmarshal s . marshal s) a `shouldBe` Right a
+      (unmarshalLazy s . marshalLazy s) a `shouldBe` a
+  where
+    name = show $ T.typeRep $ proxy gen
+    proxy :: Gen (s, a) -> Proxy a
+    proxy = const Proxy
+
+-- | Generate Data.Aeson identity tests
+testJson ::
+  (Eq a, Show a, T.Typeable a, A.ToJSON a, A.FromJSON a) => Gen a -> Spec
+testJson gen = do
+  prop ("Data.Aeson toJSON/fromJSON identity for " <> name) $
+    forAll gen (`shouldSatisfy` jsonID)
+  prop ("Data.Aeson toEncoding/fromJSON identity for " <> name) $
+    forAll gen (`shouldSatisfy` encodingID)
+  where
+    name = show $ T.typeRep $ proxy gen
+    proxy :: Gen a -> Proxy a
+    proxy = const Proxy
+    jsonID x = (A.fromJSON . A.toJSON) (toMap x) == A.Success (toMap x)
+    encodingID x =
+      (A.decode . A.encodingToLazyByteString . A.toEncoding) (toMap x)
+        == Just (toMap x)
+
+-- | Generate MarshalJSON identity tests
+testMarshalJson ::
+  (Eq a, Show a, Show s, T.Typeable a, MarshalJSON s a) =>
+  Gen (s, a) ->
+  Spec
+testMarshalJson gen = do
+  prop ("MarshalJSON marshalValue/unmarshalValue identity for " <> name) $
+    forAll gen $
+      \(s, a) -> a `shouldSatisfy` marshalJsonID s
+  prop ("MarshalJSON marshalEncoding/unmarshalValue identity for " <> name) $
+    forAll gen $
+      \(s, a) -> a `shouldSatisfy` marshalEncodingID s
+  where
+    name = show $ T.typeRep $ proxy gen
+    proxy :: Gen (s, a) -> Proxy a
+    proxy = const Proxy
+    marshalJsonID s a =
+      A.parseMaybe (unmarshalValue s) (marshalValue s a) == Just a
+    marshalEncodingID s a = unmarshalJSON s (marshalJSON s a) == Just a
+
+toMap :: a -> Map.Map String a
+toMap = Map.singleton "object"
+
+toMapE :: A.Encoding -> A.Encoding
+toMapE = A.pairs . A.pair "object"
+
+fromMap :: Map.Map String a -> a
+fromMap = (Map.! "object")
diff --git a/src/Haskoin/Util/Helpers.hs b/src/Haskoin/Util/Helpers.hs
new file mode 100644
--- /dev/null
+++ b/src/Haskoin/Util/Helpers.hs
@@ -0,0 +1,426 @@
+{-# LANGUAGE CPP #-}
+{-# LANGUAGE ImportQualifiedPost #-}
+{-# LANGUAGE LambdaCase #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+
+-- |
+-- Module      : Haskoin.Util
+-- Copyright   : No rights reserved
+-- License     : MIT
+-- Maintainer  : jprupp@protonmail.ch
+-- Stability   : experimental
+-- Portability : POSIX
+--
+-- This module defines various utility functions used across the library.
+module Haskoin.Util.Helpers
+  ( -- * ByteString Helpers
+    bsToInteger,
+    integerToBS,
+    hexEncoding,
+    hexBuilder,
+    encodeHex,
+    encodeHexLazy,
+    decodeHex,
+    decodeHexLazy,
+    getBits,
+
+    -- * Maybe & Either Helpers
+    eitherToMaybe,
+    maybeToEither,
+    liftEither,
+    liftMaybe,
+
+    -- * Other Helpers
+    updateIndex,
+    matchTemplate,
+    convertBits,
+
+    -- * Triples
+    fst3,
+    snd3,
+    lst3,
+
+    -- * JSON Utilities
+    dropFieldLabel,
+    dropSumLabels,
+
+    -- * Serialization Helpers
+    putList,
+    getList,
+    putMaybe,
+    getMaybe,
+    putLengthBytes,
+    getLengthBytes,
+    putInteger,
+    getInteger,
+    putInt32be,
+    getInt32be,
+    putInt64be,
+    getInt64be,
+    getIntMap,
+    putIntMap,
+    getTwo,
+    putTwo,
+
+    -- * Test Helpers
+    prepareContext,
+    customCerealID,
+    readTestFile,
+    readTestFileParser,
+  )
+where
+
+import Control.Monad
+import Control.Monad.Except (ExceptT (..), liftEither)
+import Crypto.Secp256k1
+import Data.Aeson (eitherDecodeFileStrict)
+import Data.Aeson.Encoding
+import Data.Aeson.Types
+import Data.Base16.Types
+import Data.Bits
+import Data.ByteString (ByteString)
+import Data.ByteString qualified as B
+import Data.ByteString.Base16
+import Data.ByteString.Builder
+import Data.ByteString.Lazy qualified as LB
+import Data.ByteString.Lazy.Base16 qualified as LB16
+import Data.Bytes.Get
+import Data.Bytes.Put
+import Data.Bytes.Serial
+import Data.Char (toLower)
+import Data.Int
+import Data.IntMap (IntMap)
+import Data.IntMap qualified as IntMap
+import Data.List
+import Data.Serialize qualified as S
+import Data.Text (Text)
+import Data.Text.Encoding qualified as T
+import Data.Text.Lazy qualified as LT
+import Data.Text.Lazy.Encoding qualified as LT
+import Data.Word
+import Test.Hspec
+
+-- ByteString helpers
+
+-- | Decode a big endian 'Integer' from a 'ByteString'.
+bsToInteger :: ByteString -> Integer
+bsToInteger = B.foldr f 0 . B.reverse
+  where
+    f w n = toInteger w .|. shiftL n 8
+
+-- | Encode an 'Integer' to a 'ByteString' as big endian.
+integerToBS :: Integer -> ByteString
+integerToBS 0 = B.pack [0]
+integerToBS i
+  | i > 0 = B.reverse $ B.unfoldr f i
+  | otherwise = error "integerToBS not defined for negative values"
+  where
+    f 0 = Nothing
+    f x = Just (fromInteger x :: Word8, x `shiftR` 8)
+
+hexEncoding :: LB.ByteString -> Encoding
+hexEncoding b =
+  unsafeToEncoding $
+    char7 '"' <> hexBuilder b <> char7 '"'
+
+hexBuilder :: LB.ByteString -> Builder
+hexBuilder = lazyByteStringHex
+
+encodeHex :: ByteString -> Text
+encodeHex = extractBase16 . encodeBase16
+
+-- | Encode as string of human-readable hex characters.
+encodeHexLazy :: LB.ByteString -> LT.Text
+encodeHexLazy = extractBase16 . LB16.encodeBase16
+
+decodeHex :: Text -> Maybe ByteString
+decodeHex t =
+  if isBase16 u8
+    then Just . decodeBase16 $ assertBase16 u8
+    else Nothing
+  where
+    u8 = T.encodeUtf8 t
+
+-- | Decode string of human-readable hex characters.
+decodeHexLazy :: LT.Text -> Maybe LB.ByteString
+decodeHexLazy t =
+  if LB16.isBase16 u8
+    then Just . LB16.decodeBase16 $ assertBase16 u8
+    else Nothing
+  where
+    u8 = LT.encodeUtf8 t
+
+-- | Obtain 'Int' bits from beginning of 'ByteString'. Resulting 'ByteString'
+-- will be smallest required to hold that many bits, padded with zeroes to the
+-- right.
+getBits :: Int -> ByteString -> ByteString
+getBits b bs
+  | r == 0 = B.take q bs
+  | otherwise = i `B.snoc` l
+  where
+    (q, r) = b `quotRem` 8
+    s = B.take (q + 1) bs
+    i = B.init s
+    l = B.last s .&. (0xff `shiftL` (8 - r)) -- zero unneeded bits
+
+-- Maybe and Either monad helpers
+
+-- | Transform an 'Either' value into a 'Maybe' value. 'Right' is mapped to
+-- 'Just' and 'Left' is mapped to 'Nothing'. The value inside 'Left' is lost.
+eitherToMaybe :: Either a b -> Maybe b
+eitherToMaybe (Right b) = Just b
+eitherToMaybe _ = Nothing
+
+-- | Transform a 'Maybe' value into an 'Either' value. 'Just' is mapped to
+-- 'Right' and 'Nothing' is mapped to 'Left'. Default 'Left' required.
+maybeToEither :: b -> Maybe a -> Either b a
+maybeToEither err = maybe (Left err) Right
+
+-- | Lift a 'Maybe' computation into the 'ExceptT' monad.
+liftMaybe :: (Monad m) => b -> Maybe a -> ExceptT b m a
+liftMaybe err = liftEither . maybeToEither err
+
+-- Various helpers
+
+-- | Applies a function to only one element of a list defined by its index.  If
+-- the index is out of the bounds of the list, the original list is returned.
+updateIndex ::
+  -- | index of the element to change
+  Int ->
+  -- | list of elements
+  [a] ->
+  -- | function to apply
+  (a -> a) ->
+  -- | result with one element changed
+  [a]
+updateIndex i xs f
+  | i < 0 || i >= length xs = xs
+  | otherwise = l ++ (f h : r)
+  where
+    (l, h : r) = splitAt i xs
+
+-- | Use the list @[b]@ as a template and try to match the elements of @[a]@
+-- against it. For each element of @[b]@ return the (first) matching element of
+-- @[a]@, or 'Nothing'. Output list has same size as @[b]@ and contains results
+-- in same order. Elements of @[a]@ can only appear once.
+matchTemplate ::
+  -- | input list
+  [a] ->
+  -- | list to serve as a template
+  [b] ->
+  -- | comparison function
+  (a -> b -> Bool) ->
+  [Maybe a]
+matchTemplate [] bs _ = replicate (length bs) Nothing
+matchTemplate _ [] _ = []
+matchTemplate as (b : bs) f = case break (`f` b) as of
+  (l, r : rs) -> Just r : matchTemplate (l ++ rs) bs f
+  _ -> Nothing : matchTemplate as bs f
+
+-- | Returns the first value of a triple.
+fst3 :: (a, b, c) -> a
+fst3 (a, _, _) = a
+
+-- | Returns the second value of a triple.
+snd3 :: (a, b, c) -> b
+snd3 (_, b, _) = b
+
+-- | Returns the last value of a triple.
+lst3 :: (a, b, c) -> c
+lst3 (_, _, c) = c
+
+-- | Field label goes lowercase and first @n@ characters get removed.
+dropFieldLabel :: Int -> Options
+dropFieldLabel n =
+  defaultOptions
+    { fieldLabelModifier = map toLower . drop n
+    }
+
+-- | Transformation from 'dropFieldLabel' is applied with argument @f@, plus
+-- constructor tags are lowercased and first @c@ characters removed. @tag@ is
+-- used as the name of the object field name that will hold the transformed
+-- constructor tag as its value.
+dropSumLabels :: Int -> Int -> String -> Options
+dropSumLabels c f tag =
+  (dropFieldLabel f)
+    { constructorTagModifier = map toLower . drop c,
+      sumEncoding = defaultTaggedObject {tagFieldName = tag}
+    }
+
+-- | Convert from one power-of-two base to another, as long as it fits in a
+-- 'Word'.
+convertBits :: Bool -> Int -> Int -> [Word] -> ([Word], Bool)
+convertBits pad frombits tobits i = (reverse yout, rem')
+  where
+    (xacc, xbits, xout) = foldl' outer (0, 0, []) i
+    (yout, rem')
+      | pad && xbits /= 0 =
+          let xout' = (xacc `shiftL` (tobits - xbits)) .&. maxv : xout
+           in (xout', False)
+      | pad = (xout, False)
+      | xbits /= 0 = (xout, True)
+      | otherwise = (xout, False)
+    maxv = 1 `shiftL` tobits - 1
+    max_acc = 1 `shiftL` (frombits + tobits - 1) - 1
+    outer (acc, bits, out) it =
+      let acc' = ((acc `shiftL` frombits) .|. it) .&. max_acc
+          bits' = bits + frombits
+          (out', bits'') = inner acc' out bits'
+       in (acc', bits'', out')
+    inner acc out bits
+      | bits >= tobits =
+          let bits' = bits - tobits
+              out' = ((acc `shiftR` bits') .&. maxv) : out
+           in inner acc out' bits'
+      | otherwise = (out, bits)
+
+--
+-- Serialization helpers
+--
+
+putInt32be :: (MonadPut m) => Int32 -> m ()
+putInt32be n
+  | n < 0 = putWord32be (complement (fromIntegral (abs n)) + 1)
+  | otherwise = putWord32be (fromIntegral (abs n))
+
+getInt32be :: (MonadGet m) => m Int32
+getInt32be = do
+  n <- getWord32be
+  if testBit n 31
+    then return (negate (complement (fromIntegral n) + 1))
+    else return (fromIntegral n)
+
+putInt64be :: (MonadPut m) => Int64 -> m ()
+putInt64be n
+  | n < 0 = putWord64be (complement (fromIntegral (abs n)) + 1)
+  | otherwise = putWord64be (fromIntegral (abs n))
+
+getInt64be :: (MonadGet m) => m Int64
+getInt64be = do
+  n <- getWord64be
+  if testBit n 63
+    then return (negate (complement (fromIntegral n) + 1))
+    else return (fromIntegral n)
+
+putInteger :: (MonadPut m) => Integer -> m ()
+putInteger n
+  | n >= lo && n <= hi = do
+      putWord8 0x00
+      putInt32be (fromIntegral n)
+  | otherwise = do
+      putWord8 0x01
+      putWord8 (fromIntegral (signum n))
+      let len = (nrBits (abs n) + 7) `div` 8
+      putWord64be (fromIntegral len)
+      mapM_ putWord8 (unroll (abs n))
+  where
+    lo = fromIntegral (minBound :: Int32)
+    hi = fromIntegral (maxBound :: Int32)
+
+getInteger :: (MonadGet m) => m Integer
+getInteger =
+  getWord8 >>= \case
+    0 -> fromIntegral <$> getInt32be
+    _ -> do
+      sign <- getWord8
+      bytes <- getList getWord8
+      let v = roll bytes
+      return $! if sign == 0x01 then v else -v
+
+putMaybe :: (MonadPut m) => (a -> m ()) -> Maybe a -> m ()
+putMaybe f Nothing = putWord8 0x00
+putMaybe f (Just x) = putWord8 0x01 >> f x
+
+getMaybe :: (MonadGet m) => m a -> m (Maybe a)
+getMaybe f =
+  getWord8 >>= \case
+    0x00 -> return Nothing
+    0x01 -> Just <$> f
+    _ -> fail "Not a Maybe"
+
+putLengthBytes :: (MonadPut m) => ByteString -> m ()
+putLengthBytes bs = do
+  putWord64be (fromIntegral (B.length bs))
+  putByteString bs
+
+getLengthBytes :: (MonadGet m) => m ByteString
+getLengthBytes = do
+  len <- fromIntegral <$> getWord64be
+  getByteString len
+
+--
+-- Fold and unfold an Integer to and from a list of its bytes
+--
+unroll :: (Integral a, Bits a) => a -> [Word8]
+unroll = unfoldr step
+  where
+    step 0 = Nothing
+    step i = Just (fromIntegral i, i `shiftR` 8)
+
+roll :: (Integral a, Bits a) => [Word8] -> a
+roll = foldr unstep 0
+  where
+    unstep b a = a `shiftL` 8 .|. fromIntegral b
+
+nrBits :: (Ord a, Integral a) => a -> Int
+nrBits k =
+  let expMax = until (\e -> 2 ^ e > k) (* 2) 1
+      findNr :: Int -> Int -> Int
+      findNr lo hi
+        | mid == lo = hi
+        | 2 ^ mid <= k = findNr mid hi
+        | 2 ^ mid > k = findNr lo mid
+        where
+          mid = (lo + hi) `div` 2
+   in findNr (expMax `div` 2) expMax
+
+-- | Read as a list of pairs of int and element.
+getIntMap :: (MonadGet m) => m Int -> m a -> m (IntMap a)
+getIntMap i m = IntMap.fromList <$> getList (getTwo i m)
+
+putIntMap :: (MonadPut m) => (Int -> m ()) -> (a -> m ()) -> IntMap a -> m ()
+putIntMap f g = putList (putTwo f g) . IntMap.toAscList
+
+putTwo :: (MonadPut m) => (a -> m ()) -> (b -> m ()) -> (a, b) -> m ()
+putTwo f g (x, y) = f x >> g y
+
+getTwo :: (MonadGet m) => m a -> m b -> m (a, b)
+getTwo f g = (,) <$> f <*> g
+
+putList :: (MonadPut m) => (a -> m ()) -> [a] -> m ()
+putList f ls = do
+  putWord64be (fromIntegral (length ls))
+  mapM_ f ls
+
+getList :: (MonadGet m) => m a -> m [a]
+getList f = do
+  l <- fromIntegral <$> getWord64be
+  replicateM l f
+
+--
+-- Test Helpers
+--
+
+prepareContext :: (Ctx -> SpecWith a) -> SpecWith a
+prepareContext go = do
+  ctx <- runIO $ do
+    ctx <- createContext
+    randomizeContext ctx
+    return ctx
+  afterAll_ (destroyContext ctx) (go ctx)
+
+customCerealID :: (Eq a) => S.Get a -> S.Putter a -> a -> Bool
+customCerealID g p a = (S.runGet g . S.runPut . p) a == Right a
+
+readTestFile :: (FromJSON a) => FilePath -> IO a
+readTestFile fp =
+  eitherDecodeFileStrict ("data/" <> fp) >>= either (error . message) return
+  where
+    message aesonErr = "Could not read test file " <> fp <> ": " <> aesonErr
+
+readTestFileParser :: (Value -> Parser a) -> FilePath -> IO a
+readTestFileParser p fp = do
+  v <- readTestFile fp
+  case parse p v of
+    Error s -> error s
+    Success x -> return x
diff --git a/src/Haskoin/Util/Marshal.hs b/src/Haskoin/Util/Marshal.hs
new file mode 100644
--- /dev/null
+++ b/src/Haskoin/Util/Marshal.hs
@@ -0,0 +1,44 @@
+{-# LANGUAGE FunctionalDependencies #-}
+{-# LANGUAGE ImportQualifiedPost #-}
+
+module Haskoin.Util.Marshal where
+
+import Control.Monad
+import Crypto.Secp256k1
+import Data.Aeson
+import Data.Aeson.Encoding
+import Data.Aeson.Types
+import Data.ByteString
+import Data.ByteString.Builder
+import Data.ByteString.Lazy qualified as Lazy
+import Data.Bytes.Get
+import Data.Bytes.Put
+import Data.Bytes.Serial
+
+class Marshal s a | a -> s where
+  marshalPut :: (MonadPut m) => s -> a -> m ()
+  marshalGet :: (MonadGet m) => s -> m a
+
+marshal :: (Marshal s a) => s -> a -> ByteString
+marshal s = runPutS . marshalPut s
+
+marshalLazy :: (Marshal s a) => s -> a -> Lazy.ByteString
+marshalLazy s = runPutL . marshalPut s
+
+unmarshal :: (Marshal s a) => s -> ByteString -> Either String a
+unmarshal = runGetS . marshalGet
+
+unmarshalLazy :: (Marshal s a) => s -> Lazy.ByteString -> a
+unmarshalLazy = runGetL . marshalGet
+
+class MarshalJSON s a | a -> s where
+  marshalValue :: s -> a -> Value
+  marshalEncoding :: s -> a -> Encoding
+  marshalEncoding x = value . marshalValue x
+  unmarshalValue :: s -> Value -> Parser a
+
+marshalJSON :: (MarshalJSON s a) => s -> a -> Lazy.ByteString
+marshalJSON s = toLazyByteString . fromEncoding . marshalEncoding s
+
+unmarshalJSON :: (MarshalJSON s a) => s -> Lazy.ByteString -> Maybe a
+unmarshalJSON s = parseMaybe (unmarshalValue s) <=< decode
diff --git a/src/Network/Haskoin/Address.hs b/src/Network/Haskoin/Address.hs
deleted file mode 100644
--- a/src/Network/Haskoin/Address.hs
+++ /dev/null
@@ -1,299 +0,0 @@
-{-# LANGUAGE DeriveAnyClass    #-}
-{-# LANGUAGE DeriveGeneric     #-}
-{-# LANGUAGE FlexibleContexts  #-}
-{-# LANGUAGE OverloadedStrings #-}
-{-|
-Module      : Network.Haskoin.Address
-Copyright   : No rights reserved
-License     : UNLICENSE
-Maintainer  : jprupp@protonmail.ch
-Stability   : experimental
-Portability : POSIX
-
-Base58, CashAddr, Bech32 address and WIF private key serialization support.
--}
-module Network.Haskoin.Address
-    ( Address(..)
-    , isPubKeyAddress
-    , isScriptAddress
-    , isWitnessPubKeyAddress
-    , isWitnessScriptAddress
-    , addrToString
-    , stringToAddr
-    , addrToJSON
-    , addrFromJSON
-    , pubKeyAddr
-    , pubKeyWitnessAddr
-    , pubKeyCompatWitnessAddr
-    , p2pkhAddr
-    , p2wpkhAddr
-    , p2shAddr
-    , p2wshAddr
-    , inputAddress
-    , outputAddress
-    , addressToScript
-    , addressToScriptBS
-    , addressToOutput
-    , payToScriptAddress
-    , payToWitnessScriptAddress
-    , payToNestedScriptAddress
-    , scriptToAddress
-    , scriptToAddressBS
-      -- * Private Key Wallet Import Format (WIF)
-    , fromWif
-    , toWif
-    ) where
-
-import           Control.Applicative
-import           Control.DeepSeq
-import           Control.Monad
-import           Data.Aeson                       as A
-import           Data.Aeson.Types
-import           Data.ByteString                  (ByteString)
-import qualified Data.ByteString                  as B
-import           Data.Hashable
-import           Data.Maybe
-import           Data.Serialize                   as S
-import           Data.Text                        (Text)
-import           GHC.Generics                     (Generic)
-import           Network.Haskoin.Address.Base58
-import           Network.Haskoin.Address.Bech32
-import           Network.Haskoin.Address.CashAddr
-import           Network.Haskoin.Constants
-import           Network.Haskoin.Crypto
-import           Network.Haskoin.Keys.Common
-import           Network.Haskoin.Script
-import           Network.Haskoin.Util
-
--- | Address format for Bitcoin and Bitcoin Cash.
-data Address
-    -- | pay to public key hash (regular)
-    = PubKeyAddress { getAddrHash160 :: !Hash160
-                      -- ^ RIPEMD160 hash of public key's SHA256 hash
-                     }
-    -- | pay to script hash
-    | ScriptAddress { getAddrHash160 :: !Hash160
-                      -- ^ RIPEMD160 hash of script's SHA256 hash
-                     }
-    -- | pay to witness public key hash
-    | WitnessPubKeyAddress { getAddrHash160 :: !Hash160
-                             -- ^ RIPEMD160 hash of public key's SHA256 hash
-                            }
-    -- | pay to witness script hash
-    | WitnessScriptAddress { getAddrHash256 :: !Hash256
-                             -- ^ HASH256 hash of script
-                            }
-    deriving (Eq, Ord, Generic, Show, Read, Serialize, Hashable, NFData)
-
--- | 'Address' pays to a public key hash.
-isPubKeyAddress :: Address -> Bool
-isPubKeyAddress PubKeyAddress {} = True
-isPubKeyAddress _                = False
-
--- | 'Address' pays to a script hash.
-isScriptAddress :: Address -> Bool
-isScriptAddress ScriptAddress {} = True
-isScriptAddress _                = False
-
--- | 'Address' pays to a witness public key hash. Only valid for SegWit
--- networks.
-isWitnessPubKeyAddress :: Address -> Bool
-isWitnessPubKeyAddress WitnessPubKeyAddress {} = True
-isWitnessPubKeyAddress _                       = False
-
--- | 'Address' pays to a witness script hash. Only valid for SegWit networks.
-isWitnessScriptAddress :: Address -> Bool
-isWitnessScriptAddress WitnessScriptAddress {} = True
-isWitnessScriptAddress _                       = False
-
--- | Deserializer for binary 'Base58' addresses.
-base58get :: Network -> Get Address
-base58get net = do
-    pfx <- getWord8
-    addr <- S.get
-    f pfx addr
-  where
-    f x a
-        | x == getAddrPrefix net = return $ PubKeyAddress a
-        | x == getScriptPrefix net = return $ ScriptAddress a
-        | otherwise = fail "Does not recognize address prefix"
-
--- | Binary serializer for 'Base58' addresses.
-base58put :: Network -> Putter Address
-base58put net (PubKeyAddress h) = do
-        putWord8 (getAddrPrefix net)
-        put h
-base58put net (ScriptAddress h) = do
-        putWord8 (getScriptPrefix net)
-        put h
-base58put _ _ = error "Cannot serialize this address as Base58"
-
-addrToJSON :: Network -> Address -> Value
-addrToJSON net a = toJSON (addrToString net a)
-
--- | JSON parsing for Bitcoin addresses. Works with 'Base58', 'CashAddr' and
--- 'Bech32'.
-addrFromJSON :: Network -> Value -> Parser Address
-addrFromJSON net =
-    withText "address" $ \t ->
-        case stringToAddr net t of
-            Nothing -> fail "could not decode address"
-            Just x  -> return x
-
--- | Convert address to human-readable string. Uses 'Base58', 'Bech32', or
--- 'CashAddr' depending on network.
-addrToString :: Network -> Address -> Maybe Text
-addrToString net a@PubKeyAddress {getAddrHash160 = h}
-    | isNothing (getCashAddrPrefix net) =
-        Just . encodeBase58Check . runPut $ base58put net a
-    | otherwise =
-        cashAddrEncode net 0 (S.encode h)
-addrToString net a@ScriptAddress {getAddrHash160 = h}
-    | isNothing (getCashAddrPrefix net) =
-        Just . encodeBase58Check . runPut $ base58put net a
-    | otherwise =
-        cashAddrEncode net 1 (S.encode h)
-addrToString net WitnessPubKeyAddress {getAddrHash160 = h} = do
-    hrp <- getBech32Prefix net
-    segwitEncode hrp 0 (B.unpack (S.encode h))
-addrToString net WitnessScriptAddress {getAddrHash256 = h} = do
-    hrp <- getBech32Prefix net
-    segwitEncode hrp 0 (B.unpack (S.encode h))
-
--- | Parse 'Base58', 'Bech32' or 'CashAddr' address, depending on network.
-stringToAddr :: Network -> Text -> Maybe Address
-stringToAddr net bs = cash <|> segwit <|> b58
-  where
-    b58 = eitherToMaybe . runGet (base58get net) =<< decodeBase58Check bs
-    cash = cashAddrDecode net bs >>= \(ver, bs') -> case ver of
-        0 -> do
-            h <- eitherToMaybe (S.decode bs')
-            return $ PubKeyAddress h
-        1 -> do
-            h <- eitherToMaybe (S.decode bs')
-            return $ ScriptAddress h
-        _ -> Nothing
-    segwit = do
-        hrp <- getBech32Prefix net
-        (ver, bs') <- segwitDecode hrp bs
-        guard (ver == 0)
-        let bs'' = B.pack bs'
-        case B.length bs'' of
-            20 -> do
-                h <- eitherToMaybe (S.decode bs'')
-                return $ WitnessPubKeyAddress h
-            32 -> do
-                h <- eitherToMaybe (S.decode bs'')
-                return $ WitnessScriptAddress h
-            _ -> Nothing
-
--- | Obtain a standard pay-to-public-key-hash address from a public key.
-pubKeyAddr :: PubKeyI -> Address
-pubKeyAddr = PubKeyAddress . addressHash . S.encode
-
--- | Obtain a standard pay-to-public-key-hash (P2PKH) address from a 'Hash160'.
-p2pkhAddr :: Hash160 -> Address
-p2pkhAddr = PubKeyAddress
-
--- | Obtain a SegWit pay-to-witness-public-key-hash (P2WPKH) address from a
--- public key.
-pubKeyWitnessAddr :: PubKeyI -> Address
-pubKeyWitnessAddr = WitnessPubKeyAddress . addressHash . S.encode
-
--- | Obtain a backwards-compatible SegWit P2SH-P2WPKH address from a public key.
-pubKeyCompatWitnessAddr :: PubKeyI -> Address
-pubKeyCompatWitnessAddr =
-    p2shAddr .
-    addressHash . encodeOutputBS . PayWitnessPKHash . addressHash . S.encode
-
--- | Obtain a SegWit pay-to-witness-public-key-hash (P2WPKH) address from a
--- 'Hash160'.
-p2wpkhAddr :: Hash160 -> Address
-p2wpkhAddr = WitnessPubKeyAddress
-
--- | Obtain a standard pay-to-script-hash (P2SH) address from a 'Hash160'.
-p2shAddr :: Hash160 -> Address
-p2shAddr = ScriptAddress
-
--- | Obtain a SegWit pay-to-witness-script-hash (P2WSH) address from a 'Hash256'
-p2wshAddr :: Hash256 -> Address
-p2wshAddr = WitnessScriptAddress
-
--- | Compute a standard pay-to-script-hash (P2SH) address for an output script.
-payToScriptAddress :: ScriptOutput -> Address
-payToScriptAddress = p2shAddr . addressHash . encodeOutputBS
-
--- | Compute a SegWit pay-to-witness-script-hash (P2WSH) address for an output
--- script.
-payToWitnessScriptAddress :: ScriptOutput -> Address
-payToWitnessScriptAddress = p2wshAddr . sha256 . encodeOutputBS
-
--- | Compute a backwards-compatible SegWit P2SH-P2WSH address.
-payToNestedScriptAddress :: ScriptOutput -> Address
-payToNestedScriptAddress =
-    p2shAddr . addressHash . encodeOutputBS . toP2WSH . encodeOutput
-
--- | Encode an output script from an address. Will fail if using a
--- pay-to-witness address on a non-SegWit network.
-addressToOutput :: Address -> ScriptOutput
-addressToOutput (PubKeyAddress h)        = PayPKHash h
-addressToOutput (ScriptAddress h)        = PayScriptHash h
-addressToOutput (WitnessPubKeyAddress h) = PayWitnessPKHash h
-addressToOutput (WitnessScriptAddress h) = PayWitnessScriptHash h
-
--- | Get output script AST for an 'Address'.
-addressToScript :: Address -> Script
-addressToScript = encodeOutput . addressToOutput
-
--- | Encode address as output script in 'ByteString' form.
-addressToScriptBS :: Address -> ByteString
-addressToScriptBS = S.encode . addressToScript
-
--- | Decode an output script into an 'Address' if it has such representation.
-scriptToAddress :: Script -> Either String Address
-scriptToAddress =
-    maybeToEither "Could not decode address" . outputAddress <=< decodeOutput
-
--- | Decode a serialized script into an 'Address'.
-scriptToAddressBS :: ByteString -> Either String Address
-scriptToAddressBS =
-    maybeToEither "Could not decode address" . outputAddress <=< decodeOutputBS
-
--- | Get the 'Address' of a 'ScriptOutput'.
-outputAddress :: ScriptOutput -> Maybe Address
-outputAddress (PayPKHash h)            = Just $ PubKeyAddress h
-outputAddress (PayScriptHash h)        = Just $ ScriptAddress h
-outputAddress (PayPK k)                = Just $ pubKeyAddr k
-outputAddress (PayWitnessPKHash h)     = Just $ WitnessPubKeyAddress h
-outputAddress (PayWitnessScriptHash h) = Just $ WitnessScriptAddress h
-outputAddress _                        = Nothing
-
--- | Infer the 'Address' of a 'ScriptInput'.
-inputAddress :: ScriptInput -> Maybe Address
-inputAddress (RegularInput (SpendPKHash _ key)) = Just $ pubKeyAddr key
-inputAddress (ScriptHashInput _ rdm) = Just $ payToScriptAddress rdm
-inputAddress _ = Nothing
-
--- | Decode private key from WIF (wallet import format) string.
-fromWif :: Network -> Base58 -> Maybe SecKeyI
-fromWif net wif = do
-    bs <- decodeBase58Check wif
-    -- Check that this is a private key
-    guard (B.head bs == getSecretPrefix net)
-    case B.length bs of
-        -- Uncompressed format
-        33 -> wrapSecKey False <$> secKey (B.tail bs)
-        -- Compressed format
-        34 -> do
-            guard $ B.last bs == 0x01
-            wrapSecKey True <$> secKey (B.tail $ B.init bs)
-        -- Bad length
-        _  -> Nothing
-
--- | Encode private key into a WIF string.
-toWif :: Network -> SecKeyI -> Base58
-toWif net (SecKeyI k c) =
-    encodeBase58Check . B.cons (getSecretPrefix net) $
-    if c
-        then getSecKey k `B.snoc` 0x01
-        else getSecKey k
diff --git a/src/Network/Haskoin/Address/Base58.hs b/src/Network/Haskoin/Address/Base58.hs
deleted file mode 100644
--- a/src/Network/Haskoin/Address/Base58.hs
+++ /dev/null
@@ -1,102 +0,0 @@
-{-# LANGUAGE OverloadedStrings #-}
-{-|
-Module      : Network.Haskoin.Address.Base58
-Copyright   : No rights reserved
-License     : UNLICENSE
-Maintainer  : jprupp@protonmail.ch
-Stability   : experimental
-Portability : POSIX
-
-Support for legacy 'Base58' addresses. Superseded by Bech32 for Bitcoin SegWit
-(BTC) and CashAddr for Bitcoin Cash (BCH).
--}
-module Network.Haskoin.Address.Base58
-    ( Base58
-    , encodeBase58
-    , decodeBase58
-    , encodeBase58Check
-    , decodeBase58Check
-    ) where
-
-import           Control.Monad
-import           Data.ByteString             (ByteString)
-import qualified Data.ByteString             as BS
-import qualified Data.ByteString.Char8       as C
-import           Data.Maybe                  (fromMaybe, isJust, listToMaybe)
-import           Data.Serialize              as S
-import           Data.String.Conversions     (cs)
-import           Data.Text                   (Text)
-import qualified Data.Text                   as T
-import           Network.Haskoin.Crypto.Hash
-import           Network.Haskoin.Util
-import           Numeric                     (readInt, showIntAtBase)
-
--- | 'Base58' classic Bitcoin address format.
-type Base58 = Text
-
--- | Symbols for Base58 encoding.
-b58Data :: ByteString
-b58Data = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
-
--- | Convert a number less than or equal to provided integer into a 'Base58'
--- character.
-b58 :: Int -> Char
-b58 = C.index b58Data
-
--- | Convert a 'Base58' character into the number it represents.
-b58' :: Char -> Maybe Int
-b58' = flip C.elemIndex b58Data
-
--- | Encode an arbitrary-length 'Integer' into a 'Base58' string. Leading zeroes
--- will not be part of the resulting string.
-encodeBase58I :: Integer -> Base58
-encodeBase58I i = cs $ showIntAtBase 58 b58 i ""
-
--- | Decode a 'Base58' string into an arbitrary-length 'Integer'.
-decodeBase58I :: Base58 -> Maybe Integer
-decodeBase58I s =
-    case go of
-        Just (r,[]) -> Just r
-        _           -> Nothing
-  where
-    p  = isJust . b58'
-    f  = fromMaybe e . b58'
-    go = listToMaybe $ readInt 58 p f (cs s)
-    e  = error "Could not decode base58"
-
--- | Encode an arbitrary 'ByteString' into a its 'Base58' representation,
--- preserving leading zeroes.
-encodeBase58 :: ByteString -> Base58
-encodeBase58 bs =
-    l `mappend` r
-  where
-    (z, b) = BS.span (== 0) bs
-    l = cs $ BS.replicate (BS.length z) (BS.index b58Data 0) -- preserve leading 0's
-    r | BS.null b = T.empty
-      | otherwise = encodeBase58I $ bsToInteger b
-
--- | Decode a 'Base58'-encoded 'Text' to a 'ByteString'.
-decodeBase58 :: Base58 -> Maybe ByteString
-decodeBase58 t =
-    BS.append prefix <$> r
-  where
-    (z, b) = BS.span (== BS.index b58Data 0) (cs t)
-    prefix = BS.replicate (BS.length z) 0 -- preserve leading 1's
-    r | BS.null b = Just BS.empty
-      | otherwise = integerToBS <$> decodeBase58I (cs b)
-
--- | Computes a checksum for the input 'ByteString' and encodes the input and
--- the checksum as 'Base58'.
-encodeBase58Check :: ByteString -> Base58
-encodeBase58Check bs =
-    encodeBase58 $ BS.append bs $ encode $ checkSum32 bs
-
--- | Decode a 'Base58'-encoded string that contains a checksum. This function
--- returns 'Nothing' if the input string contains invalid 'Base58' characters or
--- if the checksum fails.
-decodeBase58Check :: Base58 -> Maybe ByteString
-decodeBase58Check bs = do
-    rs <- decodeBase58 bs
-    let (res, chk) = BS.splitAt (BS.length rs - 4) rs
-    guard $ chk == encode (checkSum32 res)
-    return res
diff --git a/src/Network/Haskoin/Address/Bech32.hs b/src/Network/Haskoin/Address/Bech32.hs
deleted file mode 100644
--- a/src/Network/Haskoin/Address/Bech32.hs
+++ /dev/null
@@ -1,228 +0,0 @@
-{-# LANGUAGE OverloadedStrings #-}
-{-|
-Module      : Network.Haskoin.Address.Base58
-Copyright   : No rights reserved
-License     : UNLICENSE
-Maintainer  : jprupp@protonmail.ch
-Stability   : experimental
-Portability : POSIX
-
-Support for Bitcoin SegWit (BTC) Bech32 addresses. This module is a modified
-version of Marko Bencun's reference implementation.
--}
-module Network.Haskoin.Address.Bech32
-    ( HRP
-    , Bech32
-    , Data
-    , bech32Encode
-    , bech32Decode
-    , toBase32
-    , toBase256
-    , segwitEncode
-    , segwitDecode
-    , Word5(..)
-    , word5
-    , fromWord5
-    ) where
-
-import           Control.Monad         (guard)
-import           Data.Array            (Array, assocs, bounds, listArray, (!),
-                                        (//))
-import           Data.Bits             (Bits, testBit, unsafeShiftL,
-                                        unsafeShiftR, xor, (.&.), (.|.))
-import qualified Data.ByteString       as B
-import           Data.Char             (toUpper)
-import           Data.Foldable         (foldl')
-import           Data.Functor.Identity (Identity, runIdentity)
-import           Data.Ix               (Ix (..))
-import           Data.Text             (Text)
-import qualified Data.Text             as T
-import qualified Data.Text.Encoding    as E
-import           Data.Word             (Word8)
-
--- | Bech32 human-readable string.
-type Bech32 = Text
-
--- | Human-readable part of 'Bech32' address.
-type HRP = Text
-
--- | Data part of 'Bech32' address.
-type Data = [Word8]
-
-(.>>.), (.<<.) :: Bits a => a -> Int -> a
-(.>>.) = unsafeShiftR
-(.<<.) = unsafeShiftL
-
--- | Five-bit word for Bech32.
-newtype Word5 =
-    UnsafeWord5 Word8
-    deriving (Eq, Ord)
-
-instance Ix Word5 where
-    range (UnsafeWord5 m, UnsafeWord5 n) = map UnsafeWord5 $ range (m, n)
-    index (UnsafeWord5 m, UnsafeWord5 n) (UnsafeWord5 i) = index (m, n) i
-    inRange (m, n) i = m <= i && i <= n
-
--- | Convert an integer number into a five-bit word.
-word5 :: Integral a => a -> Word5
-word5 x = UnsafeWord5 (fromIntegral x .&. 31)
-{-# INLINE word5 #-}
-{-# SPECIALIZE INLINE word5 :: Word8 -> Word5 #-}
-
--- | Convert a five-bit word into a number.
-fromWord5 :: Num a => Word5 -> a
-fromWord5 (UnsafeWord5 x) = fromIntegral x
-{-# INLINE fromWord5 #-}
-{-# SPECIALIZE INLINE fromWord5 :: Word5 -> Word8 #-}
-
--- | 'Bech32' character map as array of five-bit integers to character.
-charset :: Array Word5 Char
-charset =
-    listArray (UnsafeWord5 0, UnsafeWord5 31) "qpzry9x8gf2tvdw0s3jn54khce6mua7l"
-
--- | Convert a character to its five-bit value from 'Bech32' 'charset'.
-charsetMap :: Char -> Maybe Word5
-charsetMap c
-    | inRange (bounds inv) upperC = inv ! upperC
-    | otherwise = Nothing
-  where
-    upperC = toUpper c
-    inv = listArray ('0', 'Z') (repeat Nothing) // map swap (assocs charset)
-    swap (a, b) = (toUpper b, Just a)
-
--- | Calculate or validate 'Bech32' checksum.
-bech32Polymod :: [Word5] -> Word
-bech32Polymod values = foldl' go 1 values .&. 0x3fffffff
-  where
-    go chk value =
-        foldl' xor chk' [g | (g, i) <- zip generator [25 ..], testBit chk i]
-      where
-        generator = [0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3]
-        chk' = chk .<<. 5 `xor` fromWord5 value
-
--- | Convert human-readable part of 'Bech32' string into a list of five-bit
--- words.
-bech32HRPExpand :: HRP -> [Word5]
-bech32HRPExpand hrp =
-    map (UnsafeWord5 . (.>>. 5)) hrpBytes ++
-    [UnsafeWord5 0] ++ map word5 hrpBytes
-  where
-    hrpBytes = B.unpack $ E.encodeUtf8 hrp
-
--- | Calculate checksum for a string of five-bit words.
-bech32CreateChecksum :: HRP -> [Word5] -> [Word5]
-bech32CreateChecksum hrp dat = [word5 (polymod .>>. i) | i <- [25,20 .. 0]]
-  where
-    values = bech32HRPExpand hrp ++ dat
-    polymod =
-        bech32Polymod (values ++ map UnsafeWord5 [0, 0, 0, 0, 0, 0]) `xor` 1
-
--- | Verify checksum for a human-readable part and string of five-bit words.
-bech32VerifyChecksum :: HRP -> [Word5] -> Bool
-bech32VerifyChecksum hrp dat = bech32Polymod (bech32HRPExpand hrp ++ dat) == 1
-
--- | Maximum length of a Bech32 result.
-maxBech32Length :: Int
-maxBech32Length = 90
-
--- | Encode string of five-bit words into 'Bech32' using a provided
--- human-readable part. Can fail if 'HRP' is invalid or result would be longer
--- than 90 characters.
-bech32Encode :: HRP -> [Word5] -> Maybe Bech32
-bech32Encode hrp dat = do
-    guard $ checkHRP hrp
-    let dat' = dat ++ bech32CreateChecksum (T.toLower hrp) dat
-        rest = map (charset !) dat'
-        result = T.concat [T.toLower hrp, T.pack "1", T.pack rest]
-    guard $ T.length result <= maxBech32Length
-    return result
-
--- | Check that human-readable part is valid for a 'Bech32' string.
-checkHRP :: HRP -> Bool
-checkHRP hrp = not (T.null hrp) && T.all (\char -> char >= '\x21' && char <= '\x7e') hrp
-
--- | Decode human-readable 'Bech32' string into a human-readable part and a
--- string of five-bit words.
-bech32Decode :: Bech32 -> Maybe (HRP, [Word5])
-bech32Decode bech32 = do
-    guard $ T.length bech32 <= maxBech32Length
-    guard $ T.toUpper bech32 == bech32 || lowerBech32 == bech32
-    let (hrp, dat) = T.breakOnEnd "1" lowerBech32
-    guard $ T.length dat >= 6
-    hrp' <- T.stripSuffix "1" hrp
-    guard $ checkHRP hrp'
-    dat' <- mapM charsetMap $ T.unpack dat
-    guard $ bech32VerifyChecksum hrp' dat'
-    return (hrp', take (T.length dat - 6) dat')
-  where
-    lowerBech32 = T.toLower bech32
-
-type Pad f = Int -> Int -> Word -> [[Word]] -> f [[Word]]
-
-yesPadding :: Pad Identity
-yesPadding _ 0 _ result        = return result
-yesPadding _ _ padValue result = return $ [padValue] : result
-{-# INLINE yesPadding #-}
-
-noPadding :: Pad Maybe
-noPadding frombits bits padValue result = do
-    guard $ bits < frombits && padValue == 0
-    return result
-{-# INLINE noPadding #-}
-
--- | Big endian conversion of a bytestring from base \(2^{frombits}\) to base
--- \(2^{tobits}\). {frombits} and {twobits} must be positive and
--- \(2^{frombits}\) and \(2^{tobits}\) must be smaller than the size of Word.
--- Every value in 'dat' must be strictly smaller than \(2^{frombits}\).
-convertBits :: Functor f => [Word] -> Int -> Int -> Pad f -> f [Word]
-convertBits dat frombits tobits pad = concat . reverse <$> go dat 0 0 []
-  where
-    go [] acc bits result =
-        let padValue = (acc .<<. (tobits - bits)) .&. maxv
-        in pad frombits bits padValue result
-    go (value:dat') acc bits result =
-        go dat' acc' (bits' `rem` tobits) (result' : result)
-      where
-        acc' = (acc .<<. frombits) .|. fromIntegral value
-        bits' = bits + frombits
-        result' =
-            [ (acc' .>>. b) .&. maxv
-            | b <- [bits' - tobits,bits' - 2 * tobits .. 0]
-            ]
-    maxv = (1 .<<. tobits) - 1
-{-# INLINE convertBits #-}
-
--- | Convert from eight-bit to five-bit word string, adding padding as required.
-toBase32 :: [Word8] -> [Word5]
-toBase32 dat =
-    map word5 $ runIdentity $ convertBits (map fromIntegral dat) 8 5 yesPadding
-
--- | Convert from five-bit word string to eight-bit word string, ignoring padding.
-toBase256 :: [Word5] -> Maybe [Word8]
-toBase256 dat =
-    map fromIntegral <$> convertBits (map fromWord5 dat) 5 8 noPadding
-
--- | Check if witness version and program are valid.
-segwitCheck :: Word8 -> Data -> Bool
-segwitCheck witver witprog =
-    witver <= 16 &&
-    if witver == 0
-        then length witprog == 20 || length witprog == 32
-        else length witprog >= 2 && length witprog <= 40
-
--- | Decode SegWit 'Bech32' address from a string and expected human-readable part.
-segwitDecode :: HRP -> Bech32 -> Maybe (Word8, Data)
-segwitDecode hrp addr = do
-    (hrp', dat) <- bech32Decode addr
-    guard $ (hrp == hrp') && not (null dat)
-    let (UnsafeWord5 witver:datBase32) = dat
-    decoded <- toBase256 datBase32
-    guard $ segwitCheck witver decoded
-    return (witver, decoded)
-
--- | Encode 'Data' as a SegWit 'Bech32' address. Needs human-readable part and
--- witness program version.
-segwitEncode :: HRP -> Word8 -> Data -> Maybe Text
-segwitEncode hrp witver witprog = do
-    guard $ segwitCheck witver witprog
-    bech32Encode hrp $ UnsafeWord5 witver : toBase32 witprog
diff --git a/src/Network/Haskoin/Address/CashAddr.hs b/src/Network/Haskoin/Address/CashAddr.hs
deleted file mode 100644
--- a/src/Network/Haskoin/Address/CashAddr.hs
+++ /dev/null
@@ -1,198 +0,0 @@
-{-# LANGUAGE OverloadedStrings #-}
-{-|
-Module      : Network.Haskoin.Address.CashAddr
-Copyright   : No rights reserved
-License     : UNLICENSE
-Maintainer  : jprupp@protonmail.ch
-Stability   : experimental
-Portability : POSIX
-
-Support for Bitcoin Cash (BCH) CashAddr format.
--}
-module Network.Haskoin.Address.CashAddr
-    ( CashPrefix
-    , CashVersion
-    , CashAddr
-    , Cash32
-    , cashAddrDecode
-    , cashAddrEncode
-    , cash32decodeType
-    , cash32encodeType
-    , cash32decode
-    , cash32encode
-    ) where
-
-import           Control.Monad
-import           Data.Bits
-import           Data.ByteString            (ByteString)
-import qualified Data.ByteString            as B
-import qualified Data.ByteString.Char8      as C
-import           Data.Char
-import           Data.List
-import           Data.Text                  (Text)
-import qualified Data.Text                  as T
-import qualified Data.Text.Encoding         as E
-import           Data.Word
-import           Network.Haskoin.Constants
-import           Network.Haskoin.Util
-
--- | 'CashAddr' prefix, usually shown before the colon in addresses, but sometimes
--- omitted. It is used in the checksum calculation to avoid parsing an address
--- from the wrong network.
-type CashPrefix = Text
-
--- | 'CashAddr' version, until new address schemes appear it will be zero.
-type CashVersion = Word8
-
--- | High level 'CashAddr' human-reabale string, with explicit or implicit prefix.
-type CashAddr = Text
-
--- | Low level 'Cash32' is the human-readable low-level encoding used by 'CashAddr'. It
--- need not encode a valid address but any binary data.
-type Cash32 = Text
-
--- | Symbols for encoding 'Cash32' data in human-readable strings.
-charset :: String
-charset = "qpzry9x8gf2tvdw0s3jn54khce6mua7l"
-
--- | Get the 32-bit number associated with this 'Cash32' character.
-base32char :: Char -> Maybe Word8
-base32char = fmap fromIntegral . (`elemIndex` charset)
-
--- | High-Level: decode 'CashAddr' string if it is valid for the
--- provided 'Network'. Prefix may be omitted from the string.
-cashAddrDecode :: Network -> CashAddr -> Maybe (CashVersion, ByteString)
-cashAddrDecode net ca = do
-    epfx <- getCashAddrPrefix net
-    let (cpfx, cdat) = T.breakOnEnd ":" (T.toLower ca)
-    guard (T.null cpfx || T.init cpfx == epfx)
-    (dpfx, ver, bs) <- cash32decodeType (epfx <> ":" <> cdat)
-    guard (dpfx == epfx)
-    return (ver, bs)
-
--- | High-Level: encode 'CashAddr' string for the provided network and hash.
--- Fails if the 'CashVersion' or length of hash 'ByteString' is invalid.
-cashAddrEncode :: Network -> CashVersion -> ByteString -> Maybe CashAddr
-cashAddrEncode net cv bs = do
-    pfx <- getCashAddrPrefix net
-    cash32encodeType pfx cv bs
-
--- | Mid-Level: decode 'CashAddr' string containing arbitrary prefix, plus a
--- version byte before the 'ByteString' that encodes type and length.
-cash32decodeType :: Cash32 -> Maybe (CashPrefix, CashVersion, ByteString)
-cash32decodeType ca' = do
-    guard (T.toUpper ca' == ca' || ca == ca')
-    (dpfx, bs) <- cash32decode ca
-    guard (not (B.null bs))
-    let vb = B.head bs
-        pay = B.tail bs
-    (ver, len) <- decodeVersionByte vb
-    guard (B.length pay == len)
-    return (dpfx, ver, pay)
-  where
-    ca = T.toLower ca'
-
--- | Mid-Level: encode 'CashAddr' string containing arbitrary prefix and
--- 'CashVersion'. Length must be among those allowed by the standard.
-cash32encodeType :: CashPrefix -> CashVersion -> ByteString -> Maybe Cash32
-cash32encodeType pfx cv bs = do
-    let len = B.length bs
-    vb <- encodeVersionByte cv len
-    let pl = vb `B.cons` bs
-    return (cash32encode pfx pl)
-
--- | Low-Level: decode 'Cash32' string. 'CashPrefix' must be part of the string.
--- No version or hash length validation is performed.
-cash32decode :: Cash32 -> Maybe (CashPrefix, ByteString)
-cash32decode text = do
-    let bs = C.map toLower bs'
-    guard (C.map toUpper bs' == bs' || bs == bs')
-    let (pfx', dat) = C.breakEnd (== ':') bs
-    pfx <-
-        if B.null pfx' || pfx' == C.singleton ':'
-            then Nothing
-            else Just (B.init pfx')
-    b32 <- B.pack <$> mapM base32char (C.unpack dat)
-    let px = B.map (.&. 0x1f) pfx
-        pd = px <> B.singleton 0 <> b32
-        cs = cash32Polymod pd
-        bb = B.take (B.length b32 - 8) b32
-    guard (verifyCash32Polymod cs)
-    let out = toBase256 bb
-    return (E.decodeUtf8 pfx, out)
-  where
-    bs' = E.encodeUtf8 text
-
--- | Low-Level: encode 'Cash32' string for 'CashPrefix' provided. Can encode
--- arbitrary data. No prefix or length validation is performed.
-cash32encode :: CashPrefix -> ByteString -> Cash32
-cash32encode pfx bs =
-    let b32 = toBase32 bs
-        px = B.map (.&. 0x1f) (E.encodeUtf8 pfx)
-        pd = px <> B.singleton 0 <> b32 <> B.replicate 8 0
-        cs = cash32Polymod pd
-        c32 = B.map f (b32 <> cs)
-        f = fromIntegral . ord . (charset !!) . fromIntegral
-    in pfx <> ":" <> E.decodeUtf8 c32
-
--- | Convert base of 'ByteString' from eight bits per byte to five bits per
--- byte, adding padding as necessary.
-toBase32 :: ByteString -> ByteString
-toBase32 =
-    B.pack .
-    map fromIntegral . fst . convertBits True 8 5 . map fromIntegral . B.unpack
-
--- | Convert base of 'ByteString' from five to eight bits per byte. Ignore
--- padding to be symmetric with respect to 'toBase32' function.
-toBase256 :: ByteString -> ByteString
-toBase256 =
-    B.pack .
-    map fromIntegral . fst . convertBits False 5 8 . map fromIntegral . B.unpack
-
--- | Obtain 'CashVersion' and payload length from 'CashAddr' version byte.
-decodeVersionByte :: Word8 -> Maybe (CashVersion, Int)
-decodeVersionByte vb = do
-    guard (vb .&. 0x80 == 0)
-    return (ver, len)
-  where
-    ver = vb `shiftR` 3
-    len = ls !! fromIntegral (vb .&. 0x07)
-    ls = [20, 24, 28, 32, 40, 48, 56, 64]
-
--- | Encode 'CashVersion' and length into version byte. Fail if version is
--- larger than five bits, or length incorrect, since that is invalid.
-encodeVersionByte :: CashVersion -> Int -> Maybe Word8
-encodeVersionByte ver len = do
-    guard (ver == ver .&. 0x0f)
-    l <- case len of
-        20 -> Just 0
-        24 -> Just 1
-        28 -> Just 2
-        32 -> Just 3
-        40 -> Just 4
-        48 -> Just 5
-        56 -> Just 6
-        64 -> Just 7
-        _  -> Nothing
-    return ((ver `shiftL` 3) .|. l)
-
--- | Calculate or validate checksum from base32 'ByteString' (excluding prefix).
-cash32Polymod :: ByteString -> ByteString
-cash32Polymod v =
-    B.pack
-        [fromIntegral (polymod `shiftR` (5 * (7 - i))) .&. 0x1f | i <- [0 .. 7]]
-  where
-    polymod = B.foldl' outer (1 :: Word64) v `xor` 1
-    outer c d =
-        let c0 = (fromIntegral (c `shiftR` 35) :: Word8)
-            c' = ((c .&. 0x07ffffffff) `shiftL` 5) `xor` fromIntegral d
-        in foldl' (inner c0) c' (zip [0 ..] generator)
-    generator =
-        [0x98f2bc8e61, 0x79b76d99e2, 0xf33e5fb3c4, 0xae2eabe2a8, 0x1e4f43e470]
-    inner c0 c (b, g)
-        | c0 `testBit` b = c `xor` g
-        | otherwise = c
-
--- | Validate that polymod 'ByteString' (eight bytes) is equal to zero.
-verifyCash32Polymod :: ByteString -> Bool
-verifyCash32Polymod = (== B.replicate 8 0)
diff --git a/src/Network/Haskoin/Block.hs b/src/Network/Haskoin/Block.hs
deleted file mode 100644
--- a/src/Network/Haskoin/Block.hs
+++ /dev/null
@@ -1,43 +0,0 @@
-{-|
-Module      : Network.Haskoin.Block
-Copyright   : No rights reserved
-License     : UNLICENSE
-Maintainer  : jprupp@protonmail.ch
-Stability   : experimental
-Portability : POSIX
-
-Most functions relating to blocks are exported by this module.
--}
-module Network.Haskoin.Block
-    ( module Network.Haskoin.Block.Common
-      -- * Block Header Chain
-    , BlockWork
-    , BlockHeaders(..)
-    , BlockNode(..)
-    , HeaderMemory(..)
-    , BlockMap
-    , getAncestor
-    , isGenesis
-    , initialChain
-    , genesisMap
-    , genesisNode
-    , genesisBlock
-    , connectBlocks
-    , connectBlock
-    , parentBlock
-    , splitPoint
-    , blockLocator
-      -- * Merkle Blocks
-    , MerkleBlock(..)
-    , MerkleRoot
-    , FlagBits
-    , PartialMerkleTree
-    , buildMerkleRoot
-    , buildPartialMerkle
-    , merkleBlockTxs
-    , testMerkleRoot
-    ) where
-
-import           Network.Haskoin.Block.Headers
-import           Network.Haskoin.Block.Merkle
-import           Network.Haskoin.Block.Common
diff --git a/src/Network/Haskoin/Block/Common.hs b/src/Network/Haskoin/Block/Common.hs
deleted file mode 100644
--- a/src/Network/Haskoin/Block/Common.hs
+++ /dev/null
@@ -1,339 +0,0 @@
-{-# LANGUAGE DeriveAnyClass #-}
-{-# LANGUAGE DeriveGeneric  #-}
-{-|
-Module      : Network.Haskoin.Block.Common
-Copyright   : No rights reserved
-License     : UNLICENSE
-Maintainer  : jprupp@protonmail.ch
-Stability   : experimental
-Portability : POSIX
-
-Common data types and functions to handle blocks from the block chain.
--}
-module Network.Haskoin.Block.Common
-    ( Block(..)
-    , BlockHeight
-    , Timestamp
-    , BlockHeader(..)
-    , headerHash
-    , BlockLocator
-    , GetBlocks(..)
-    , GetHeaders(..)
-    , BlockHeaderCount
-    , BlockHash(..)
-    , blockHashToHex
-    , hexToBlockHash
-    , Headers(..)
-    , decodeCompact
-    , encodeCompact
-    ) where
-
-import           Control.DeepSeq
-import           Control.Monad                      (forM_, liftM2, mzero,
-                                                     replicateM)
-import           Data.Aeson                         (FromJSON, ToJSON,
-                                                     Value (String), parseJSON,
-                                                     toJSON, withText)
-import           Data.Bits                          (shiftL, shiftR, (.&.),
-                                                     (.|.))
-import qualified Data.ByteString                    as B
-import           Data.Hashable                      (Hashable)
-import           Data.Maybe                         (fromMaybe)
-import           Data.Serialize                     (Serialize, decode, encode,
-                                                     get, put)
-import           Data.Serialize.Get                 (getWord32le)
-import           Data.Serialize.Put                 (Put, putWord32le)
-import           Data.String                        (IsString, fromString)
-import           Data.String.Conversions            (cs)
-import           Data.Text                          (Text)
-import           Data.Word                          (Word32)
-import           GHC.Generics                       (Generic)
-import           Network.Haskoin.Crypto.Hash
-import           Network.Haskoin.Network.Common
-import           Network.Haskoin.Transaction.Common
-import           Network.Haskoin.Util
-import qualified Text.Read                          as R
-
--- | Height of a block in the block chain, starting at 0 for Genesis.
-type BlockHeight = Word32
-
--- | Block timestamp as Unix time (seconds since 1970-01-01 00:00 UTC).
-type Timestamp = Word32
-
--- | Block header and transactions.
-data Block =
-    Block { blockHeader :: !BlockHeader
-          , blockTxns   :: ![Tx]
-          } deriving (Eq, Show, Read, Generic, Hashable, NFData)
-
-instance Serialize Block where
-    get = do
-        header     <- get
-        (VarInt c) <- get
-        txs        <- replicateM (fromIntegral c) get
-        return $ Block header txs
-    put (Block h txs) = do
-        put h
-        putVarInt $ length txs
-        forM_ txs put
-
-instance ToJSON Block where
-    toJSON = String . encodeHex . encode
-
-instance FromJSON Block where
-    parseJSON =
-        withText "Block" $ \t -> do
-            bin <-
-                case decodeHex t of
-                    Nothing  -> mzero
-                    Just bin -> return bin
-            case decode bin of
-                Left e  -> fail e
-                Right h -> return h
-
-instance ToJSON BlockHeader where
-    toJSON = String . encodeHex . encode
-
-instance FromJSON BlockHeader where
-    parseJSON =
-        withText "BlockHeader" $ \t -> do
-            bin <-
-                case decodeHex t of
-                    Nothing  -> mzero
-                    Just bin -> return bin
-            case decode bin of
-                Left e  -> fail e
-                Right h -> return h
-
--- | Block header hash. To be serialized reversed for display purposes.
-newtype BlockHash = BlockHash
-    { getBlockHash :: Hash256 }
-    deriving (Eq, Ord, Generic, Hashable, Serialize, NFData)
-
-instance Show BlockHash where
-    showsPrec _ = shows . blockHashToHex
-
-instance Read BlockHash where
-    readPrec = do
-        R.String str <- R.lexP
-        maybe R.pfail return $ hexToBlockHash $ cs str
-
-instance IsString BlockHash where
-    fromString s =
-        let e = error "Could not read block hash from hex string"
-        in fromMaybe e $ hexToBlockHash $ cs s
-
-instance FromJSON BlockHash where
-    parseJSON = withText "block hash" $
-        maybe mzero return . hexToBlockHash
-
-instance ToJSON BlockHash where
-    toJSON = String . blockHashToHex
-
--- | Block hashes are reversed with respect to the in-memory byte order in a
--- block hash when displayed.
-blockHashToHex :: BlockHash -> Text
-blockHashToHex (BlockHash h) = encodeHex (B.reverse (encode h))
-
--- | Convert a human-readable hex block hash into a 'BlockHash'. Bytes are
--- reversed as normal.
-hexToBlockHash :: Text -> Maybe BlockHash
-hexToBlockHash hex = do
-    bs <- B.reverse <$> decodeHex hex
-    h <- eitherToMaybe (decode bs)
-    return $ BlockHash h
-
--- | Data type recording information of a 'Block'. The hash of a block is
--- defined as the hash of this data structure, serialized. The block mining
--- process involves finding a partial hash collision by varying the nonce in the
--- 'BlockHeader' and/or additional entropy in the coinbase 'Transaction' of this
--- 'Block'. Variations in the coinbase will result in different merkle roots in
--- the 'BlockHeader'.
-data BlockHeader =
-    BlockHeader { blockVersion   :: !Word32      --  4 bytes
-                  -- | hash of the previous block (parent)
-                , prevBlock      :: !BlockHash   -- 32 bytes
-                  -- | root of the merkle tree of transactions
-                , merkleRoot     :: !Hash256     -- 32 bytes
-                  -- | unix timestamp
-                , blockTimestamp :: !Timestamp   --  4 bytes
-                  -- | difficulty target
-                , blockBits      :: !Word32      --  4 bytes
-                  -- | random nonce
-                , bhNonce        :: !Word32      --  4 bytes
-                } deriving (Eq, Ord, Show, Read, Generic, Hashable, NFData)
-                                                 -- 80 bytes
-
--- | Compute hash of 'BlockHeader'.
-headerHash :: BlockHeader -> BlockHash
-headerHash = BlockHash . doubleSHA256 . encode
-
-instance Serialize BlockHeader where
-    get = do
-        v <- getWord32le
-        p <- get
-        m <- get
-        t <- getWord32le
-        b <- getWord32le
-        n <- getWord32le
-        return BlockHeader
-            { blockVersion   = v
-            , prevBlock      = p
-            , merkleRoot     = m
-            , blockTimestamp = t
-            , blockBits      = b
-            , bhNonce        = n
-            }
-
-    put (BlockHeader v p m bt bb n) = do
-        putWord32le v
-        put         p
-        put         m
-        putWord32le bt
-        putWord32le bb
-        putWord32le n
-
--- | A block locator is a set of block headers, denser towards the best block
--- and sparser towards the genesis block. It starts at the highest block known.
--- It is used by a node to synchronize against the network. When the locator is
--- provided to a peer, it will send back block hashes starting from the first
--- block in the locator that it recognizes.
-type BlockLocator = [BlockHash]
-
--- | Data type representing a getblocks message request. It is used in the
--- bitcoin protocol to retrieve blocks from a peer by providing it a
--- 'BlockLocator' object. The response to a 'GetBlocks' message is an 'Inv'
--- message containing a list of block hashes that the peer believes this node is
--- missing. The number of block hashes in that inv message will end at the stop
--- block hash, at at the tip of the chain, or after 500 entries, whichever comes
--- earlier.
-data GetBlocks =
-    GetBlocks { -- | protocol version.
-                getBlocksVersion  :: !Word32
-                -- | block locator object
-              , getBlocksLocator  :: !BlockLocator
-                -- | hash of the last desired block
-              , getBlocksHashStop :: !BlockHash
-              } deriving (Eq, Show, Generic, NFData)
-
-instance Serialize GetBlocks where
-
-    get = GetBlocks <$> getWord32le
-                    <*> (repList =<< get)
-                    <*> get
-      where
-        repList (VarInt c) = replicateM (fromIntegral c) get
-
-    put (GetBlocks v xs h) = putGetBlockMsg v xs h
-
-putGetBlockMsg :: Word32 -> BlockLocator -> BlockHash -> Put
-putGetBlockMsg v xs h = do
-    putWord32le v
-    putVarInt $ length xs
-    forM_ xs put
-    put h
-
--- | Similar to the 'GetBlocks' message type but for retrieving block headers
--- only. The response to a 'GetHeaders' request is a 'Headers' message
--- containing a list of block headers. A maximum of 2000 block headers can be
--- returned. 'GetHeaders' is used by simplified payment verification (SPV)
--- clients to exclude block contents when synchronizing the block chain.
-data GetHeaders =
-    GetHeaders {
-                 -- | protocol version
-                 getHeadersVersion  :: !Word32
-                 -- | block locator object
-               , getHeadersBL       :: !BlockLocator
-                 -- | hash of the last desired block header
-               , getHeadersHashStop :: !BlockHash
-               } deriving (Eq, Show, Generic, NFData)
-
-instance Serialize GetHeaders where
-
-    get = GetHeaders <$> getWord32le
-                     <*> (repList =<< get)
-                     <*> get
-      where
-        repList (VarInt c) = replicateM (fromIntegral c) get
-
-    put (GetHeaders v xs h) = putGetBlockMsg v xs h
-
--- | 'BlockHeader' type with a transaction count as 'VarInt'
-type BlockHeaderCount = (BlockHeader, VarInt)
-
--- | The 'Headers' type is used to return a list of block headers in
--- response to a 'GetHeaders' message.
-newtype Headers =
-    Headers { -- | list of block headers with transaction count
-              headersList :: [BlockHeaderCount]
-            }
-    deriving (Eq, Show, Generic, NFData)
-
-instance Serialize Headers where
-
-    get = Headers <$> (repList =<< get)
-      where
-        repList (VarInt c) = replicateM (fromIntegral c) action
-        action = liftM2 (,) get get
-
-    put (Headers xs) = do
-        putVarInt $ length xs
-        forM_ xs $ \(a,b) -> put a >> put b
-
--- | Decode the compact number used in the difficulty target of a block.
---
--- The compact format is a representation of a whole number \(N\) using an
--- unsigned 32-bit number similar to a floating point format. The most
--- significant 8 bits are the unsigned exponent of base 256. This exponent can
--- be thought of as the number of bytes of \(N\). The lower 23 bits are the
--- mantissa. Bit number 24 represents the sign of \(N\).
---
--- \[
--- N = -1^{sign} \times mantissa \times 256^{exponent-3}
--- \]
-decodeCompact :: Word32 -> (Integer, Bool) -- ^ true means overflow
-decodeCompact nCompact = (if neg then res * (-1) else res, over)
-  where
-    nSize :: Int
-    nSize = fromIntegral nCompact `shiftR` 24
-    nWord' :: Word32
-    nWord' = nCompact .&. 0x007fffff
-    nWord :: Word32
-    nWord | nSize <= 3 = nWord' `shiftR` (8 * (3 - nSize))
-          | otherwise = nWord'
-    res :: Integer
-    res | nSize <= 3 = fromIntegral nWord
-        | otherwise = fromIntegral nWord `shiftL` (8 * (nSize - 3))
-    neg = nWord /= 0 && (nCompact .&. 0x00800000) /= 0
-    over = nWord /= 0 && (nSize > 34 ||
-                          nWord > 0xff && nSize > 33 ||
-                          nWord > 0xffff && nSize > 32)
-
--- | Encode an 'Integer' to the compact number format used in the difficulty
--- target of a block.
-encodeCompact :: Integer
-              -> Word32
-encodeCompact i = nCompact
-  where
-    i' = abs i
-    neg = i < 0
-    nSize' :: Int
-    nSize' = let f 0 = 0
-                 f n = 1 + f (n `shiftR` 8)
-             in f i'
-    nCompact''' :: Word32
-    nCompact'''
-        | nSize' <= 3 = fromIntegral $ (low64 .&. i') `shiftL` (8 * (3 - nSize'))
-        | otherwise = fromIntegral $ low64 .&. (i' `shiftR` (8 * (nSize' - 3)))
-    nCompact'' :: Word32
-    nSize :: Int
-    (nCompact'', nSize)
-        | nCompact''' .&. 0x00800000 /= 0 = (nCompact''' `shiftR` 8, nSize' + 1)
-        | otherwise = (nCompact''', nSize')
-    nCompact' :: Word32
-    nCompact' = nCompact'' .|. (fromIntegral nSize `shiftL` 24)
-    nCompact :: Word32
-    nCompact | neg && (nCompact' .&. 0x007fffff /= 0) = nCompact' .|. 0x00800000
-             | otherwise = nCompact'
-    low64 :: Integer
-    low64 = 0xffffffffffffffff
diff --git a/src/Network/Haskoin/Block/Headers.hs b/src/Network/Haskoin/Block/Headers.hs
deleted file mode 100644
--- a/src/Network/Haskoin/Block/Headers.hs
+++ /dev/null
@@ -1,741 +0,0 @@
-{-# LANGUAGE DeriveAnyClass    #-}
-{-# LANGUAGE DeriveGeneric     #-}
-{-# LANGUAGE FlexibleContexts  #-}
-{-# LANGUAGE FlexibleInstances #-}
-{-# LANGUAGE LambdaCase        #-}
-{-# LANGUAGE RecordWildCards   #-}
-{-|
-Module      : Network.Haskoin.Block.Headers
-Copyright   : No rights reserved
-License     : UNLICENSE
-Maintainer  : jprupp@protonmail.ch
-Stability   : experimental
-Portability : POSIX
-
-Block chain header synchronization and proof-of-work consensus functions.
--}
-module Network.Haskoin.Block.Headers
-    ( BlockNode(..)
-    , BlockHeaders(..)
-    , BlockWork
-    , genesisNode
-    , genesisBlock
-    , isGenesis
-    , chooseBest
-      -- * Header Chain Storage Functions
-    , parentBlock
-    , getParents
-    , getAncestor
-    , splitPoint
-    , connectBlocks
-    , connectBlock
-    , blockLocator
-      -- * In-Memory Header Chain Store
-    , HeaderMemory(..)
-    , ShortBlockHash
-    , BlockMap
-    , shortBlockHash
-    , initialChain
-    , genesisMap
-      -- * Helper Functions
-    , appendBlocks
-    , validBlock
-    , validCP
-    , afterLastCP
-    , bip34
-    , validVersion
-    , lastNoMinDiff
-    , nextWorkRequired
-    , nextEdaWorkRequired
-    , nextDaaWorkRequired
-    , computeTarget
-    , getSuitableBlock
-    , nextPowWorkRequired
-    , calcNextWork
-    , isValidPOW
-    , blockPOW
-    , headerWork
-    , diffInterval
-    , blockLocatorNodes
-    , mineBlock
-    , computeSubsidy
-    , ) where
-
-import           Control.Applicative                ((<|>))
-import           Control.DeepSeq
-import           Control.Monad                      (guard, unless, when)
-import           Control.Monad.Except               (ExceptT (..), runExceptT,
-                                                     throwError)
-import           Control.Monad.State.Strict         as State (StateT, get, gets,
-                                                              lift, modify)
-import           Control.Monad.Trans.Maybe
-import           Data.Bits                          (shiftL, shiftR, (.&.))
-import qualified Data.ByteString                    as B
-import           Data.ByteString.Short              (ShortByteString, fromShort,
-                                                     toShort)
-import           Data.Function                      (on)
-import           Data.Hashable
-import           Data.HashMap.Strict                (HashMap)
-import qualified Data.HashMap.Strict                as HashMap
-import           Data.List                          (sort, sortBy)
-import           Data.Maybe                         (fromMaybe, listToMaybe)
-import           Data.Serialize                     as S (Serialize (..),
-                                                          decode, encode, get,
-                                                          put)
-import           Data.Serialize.Get                 as S
-import           Data.Serialize.Put                 as S
-import           Data.Typeable                      (Typeable)
-import           Data.Word                          (Word32, Word64)
-import           GHC.Generics                       (Generic)
-import           Network.Haskoin.Block.Common
-import           Network.Haskoin.Constants
-import           Network.Haskoin.Crypto
-import           Network.Haskoin.Transaction.Common
-import           Network.Haskoin.Util
-
--- | Short version of the block hash. Uses the good end of the hash (the part
--- that doesn't have a long string of zeroes).
-type ShortBlockHash = Word64
-
--- | Memory-based map to a serialized 'BlockNode' data structure.
--- 'ShortByteString' is used to avoid memory fragmentation and make the data
--- structure compact.
-type BlockMap = HashMap ShortBlockHash ShortByteString
-
--- | Represents accumulated work in the block chain so far.
-type BlockWork = Integer
-
--- | Data structure representing a block header and its position in the
--- block chain.
-data BlockNode
-    -- | non-Genesis block header
-    = BlockNode { nodeHeader :: !BlockHeader
-                , nodeHeight :: !BlockHeight
-        -- | accumulated work so far
-                , nodeWork   :: !BlockWork
-        -- | akip magic block hash
-                , nodeSkip   :: !BlockHash }
-    -- | Genesis block header
-    | GenesisNode { nodeHeader :: !BlockHeader
-                  , nodeHeight :: !BlockHeight
-                  , nodeWork   :: !BlockWork }
-    deriving (Show, Read, Generic, Hashable, NFData)
-
-instance Serialize BlockNode where
-    get = do
-        nodeHeader <- S.get
-        nodeHeight <- getWord32le
-        nodeWork <- S.get
-        if nodeHeight == 0
-            then return GenesisNode {..}
-            else do
-                nodeSkip <- S.get
-                return BlockNode {..}
-    put bn = do
-        put $ nodeHeader bn
-        putWord32le $ nodeHeight bn
-        put $ nodeWork bn
-        case bn of
-            GenesisNode {} -> return ()
-            BlockNode {}   -> put $ nodeSkip bn
-
-instance Eq BlockNode where
-    (==) = (==) `on` nodeHeader
-
-instance Ord BlockNode where
-    compare = compare `on` nodeHeight
-
--- | Memory-based header tree.
-data HeaderMemory = HeaderMemory
-    { memoryHeaderMap  :: !BlockMap
-    , memoryBestHeader :: !BlockNode
-    } deriving (Eq, Typeable, Show, Read, Generic, Hashable, NFData)
-
--- | Typeclass for block header chain storage monad.
-class Monad m => BlockHeaders m where
-    -- | Add a new 'BlockNode' to the chain. Does not validate.
-    addBlockHeader :: BlockNode -> m ()
-    -- | Get a 'BlockNode' associated with a 'BlockHash'.
-    getBlockHeader :: BlockHash -> m (Maybe BlockNode)
-    -- | Locate the 'BlockNode' for the highest block in the chain
-    getBestBlockHeader :: m BlockNode
-    -- | Set the highest block in the chain.
-    setBestBlockHeader :: BlockNode -> m ()
-    -- | Add a continuous bunch of block headers the chain. Does not validate.
-    addBlockHeaders :: [BlockNode] -> m ()
-    addBlockHeaders = mapM_ addBlockHeader
-
-instance Monad m => BlockHeaders (StateT HeaderMemory m) where
-    addBlockHeader = modify . addBlockHeaderMemory
-    getBlockHeader bh = getBlockHeaderMemory bh <$> State.get
-    getBestBlockHeader = gets memoryBestHeader
-    setBestBlockHeader bn = modify $ \s -> s { memoryBestHeader = bn }
-
--- | Initialize memory-based chain.
-initialChain :: Network -> HeaderMemory
-initialChain net = HeaderMemory
-    { memoryHeaderMap = genesisMap net
-    , memoryBestHeader = genesisNode net
-    }
-
--- | Initialize map for memory-based chain.
-genesisMap :: Network -> BlockMap
-genesisMap net =
-    HashMap.singleton
-        (shortBlockHash (headerHash (getGenesisHeader net)))
-        (toShort (encode (genesisNode net)))
-
--- | Add block header to memory block map.
-addBlockHeaderMemory :: BlockNode -> HeaderMemory -> HeaderMemory
-addBlockHeaderMemory bn s@HeaderMemory{..} =
-    let bm' = addBlockToMap bn memoryHeaderMap
-    in s { memoryHeaderMap = bm' }
-
--- | Get block header from memory block map.
-getBlockHeaderMemory :: BlockHash -> HeaderMemory -> Maybe BlockNode
-getBlockHeaderMemory bh HeaderMemory {..} = do
-    bs <- shortBlockHash bh `HashMap.lookup` memoryHeaderMap
-    eitherToMaybe . decode $ fromShort bs
-
--- | Calculate short block hash taking eight non-zero bytes from the 16-byte
--- hash. This function will take the bytes that are not on the zero-side of the
--- hash, making colissions between short block hashes difficult.
-shortBlockHash :: BlockHash -> ShortBlockHash
-shortBlockHash = either error id . decode . B.take 8 . encode
-
--- | Add a block to memory-based block map.
-addBlockToMap :: BlockNode -> BlockMap -> BlockMap
-addBlockToMap node =
-    HashMap.insert
-    (shortBlockHash $ headerHash $ nodeHeader node)
-    (toShort $ encode node)
-
--- | Get the ancestor of the provided 'BlockNode' at the specified
--- 'BlockHeight'.
-getAncestor :: BlockHeaders m
-            => BlockHeight
-            -> BlockNode
-            -> m (Maybe BlockNode)
-getAncestor height node
-    | height > nodeHeight node = return Nothing
-    | otherwise = go node
-  where
-    e1 = error "Could not get skip header"
-    e2 = error "Could not get previous block header"
-    go walk
-        | nodeHeight walk > height =
-            let heightSkip = skipHeight (nodeHeight walk)
-                heightSkipPrev = skipHeight (nodeHeight walk - 1)
-             in if not (isGenesis walk) &&
-                   (heightSkip == height ||
-                    (heightSkip > height &&
-                     not
-                         (heightSkipPrev < heightSkip - 2 &&
-                          heightSkipPrev >= height)))
-                    then do
-                        walk' <- fromMaybe e1 <$> getBlockHeader (nodeSkip walk)
-                        go walk'
-                    else do
-                        walk' <-
-                            fromMaybe e2 <$>
-                            getBlockHeader (prevBlock (nodeHeader walk))
-                        go walk'
-        | otherwise = return $ Just walk
-
--- | Is the provided 'BlockNode' the Genesis block?
-isGenesis :: BlockNode -> Bool
-isGenesis GenesisNode{} = True
-isGenesis BlockNode{}   = False
-
--- | Build the genesis 'BlockNode' for the supplied 'Network'.
-genesisNode :: Network -> BlockNode
-genesisNode net =
-    GenesisNode
-        { nodeHeader = getGenesisHeader net
-        , nodeHeight = 0
-        , nodeWork = headerWork (getGenesisHeader net)
-        }
-
--- | Validate a list of continuous block headers and import them to the
--- block chain. Return 'Left' on failure with error information.
-connectBlocks :: BlockHeaders m
-              => Network
-              -> Timestamp       -- ^ current time
-              -> [BlockHeader]
-              -> m (Either String [BlockNode])
-connectBlocks _ _ [] = return $ Right []
-connectBlocks net t bhs@(bh:_) =
-    runExceptT $ do
-        unless (chained bhs) $
-            throwError "Blocks to connect do not form a chain"
-        par <-
-            maybeToExceptT
-                "Could not get parent block"
-                (MaybeT (parentBlock bh))
-        pars <- lift $ getParents 10 par
-        bb <- lift getBestBlockHeader
-        go par [] bb par pars bhs >>= \case
-            bns@(bn:_) -> do
-                lift $ addBlockHeaders bns
-                let bb' = chooseBest bn bb
-                when (bb' /= bb) $ lift $ setBestBlockHeader bb'
-                return bns
-            _ -> undefined
-  where
-    chained (h1:h2:hs) = headerHash h1 == prevBlock h2 && chained (h2 : hs)
-    chained _          = True
-    skipit lbh ls par
-        | sh == nodeHeight lbh = return lbh
-        | sh < nodeHeight lbh = do
-            skM <- lift $ getAncestor sh lbh
-            case skM of
-                Just sk -> return sk
-                Nothing ->
-                    throwError $
-                    "BUG: Could not get skip for block " ++
-                    show (headerHash $ nodeHeader par)
-        | otherwise = do
-            let sn = ls !! fromIntegral (nodeHeight par - sh)
-            when (nodeHeight sn /= sh) $
-                throwError "BUG: Node height not right in skip"
-            return sn
-      where
-        sh = skipHeight (nodeHeight par + 1)
-    go _ acc _ _ _ [] = return acc
-    go lbh acc bb par pars (h:hs) = do
-        sk <- skipit lbh acc par
-        bn <- ExceptT . return $ validBlock net t bb par pars h sk
-        go lbh (bn : acc) (chooseBest bn bb) bn (take 10 $ par : pars) hs
-
--- | Block's parent. If the block header is in the store, its parent must also
--- be there. No block header get deleted or pruned from the store.
-parentBlock :: BlockHeaders m
-            => BlockHeader
-            -> m (Maybe BlockNode)
-parentBlock bh = getBlockHeader (prevBlock bh)
-
--- | Validate and connect single block header to the block chain. Return 'Left' if fails
--- to be validated.
-connectBlock ::
-       BlockHeaders m
-    => Network
-    -> Timestamp -- ^ current time
-    -> BlockHeader
-    -> m (Either String BlockNode)
-connectBlock net t bh =
-    runExceptT $ do
-        par <-
-            maybeToExceptT
-                "Could not get parent block"
-                (MaybeT (parentBlock bh))
-        pars <- lift $ getParents 10 par
-        skM <- lift $ getAncestor (skipHeight (nodeHeight par + 1)) par
-        sk <-
-            case skM of
-                Just sk -> return sk
-                Nothing ->
-                    throwError $
-                    "BUG: Could not get skip for block " ++
-                    show (headerHash $ nodeHeader par)
-        bb <- lift getBestBlockHeader
-        bn <- ExceptT . return $ validBlock net t bb par pars bh sk
-        let bb' = chooseBest bb bn
-        lift $ addBlockHeader bn
-        when (bb /= bb') . lift $ setBestBlockHeader bb'
-        return bn
-
--- | Validate this block header. Build a 'BlockNode' if successful.
-validBlock :: Network
-           -> Timestamp     -- ^ current time
-           -> BlockNode     -- ^ best block
-           -> BlockNode     -- ^ immediate parent
-           -> [BlockNode]   -- ^ 10 parents above
-           -> BlockHeader   -- ^ header to validate
-           -> BlockNode     -- ^ skip node (black magic)
-           -> Either String BlockNode
-validBlock net t bb par pars bh sk = do
-    let mt = medianTime . map (blockTimestamp . nodeHeader) $ par : pars
-        nt = blockTimestamp bh
-        hh = headerHash bh
-        nv = blockVersion bh
-        ng = nodeHeight par + 1
-        aw = nodeWork par + headerWork bh
-    unless (isValidPOW net bh) $
-        Left $ "Proof of work failed: " ++ show (headerHash bh)
-    unless (nt <= t + 2 * 60 * 60) $
-        Left $ "Invalid header timestamp: " ++ show nt
-    unless (nt >= mt) $
-        Left $ "Block timestamp too early: " ++ show nt
-    unless (afterLastCP net (nodeHeight bb) ng) $
-        Left $ "Rewriting pre-checkpoint chain: " ++ show ng
-    unless (validCP net ng hh) $
-        Left $ "Rejected checkpoint: " ++ show ng
-    unless (bip34 net ng hh) $
-        Left $ "Rejected BIP-34 block: " ++ show hh
-    unless (validVersion net ng nv) $
-        Left $ "Invalid block version: " ++ show nv
-    return BlockNode { nodeHeader = bh
-                     , nodeHeight = ng
-                     , nodeWork = aw
-                     , nodeSkip = headerHash $ nodeHeader sk
-                     }
-
--- | Return the median of all provided timestamps. Can be unsorted. Error on
--- empty list.
-medianTime :: [Timestamp] -> Timestamp
-medianTime ts
-    | null ts = error "Cannot compute median time of empty header list"
-    | otherwise = sort ts !! (length ts `div` 2)
-
--- | Calculate the height of the skip (magic) block that corresponds to the
--- given height. The block hash of the ancestor at that height will be placed on
--- the 'BlockNode' structure to help locate ancestors at any height quickly.
-skipHeight :: BlockHeight -> BlockHeight
-skipHeight height
-    | height < 2 = 0
-    | height .&. 1 /= 0 = invertLowestOne (invertLowestOne $ height - 1) + 1
-    | otherwise = invertLowestOne height
-
--- | Part of the skip black magic calculation.
-invertLowestOne :: BlockHeight -> BlockHeight
-invertLowestOne height = height .&. (height - 1)
-
--- | Get a number of parents for the provided block.
-getParents :: BlockHeaders m
-           => Int
-           -> BlockNode
-           -> m [BlockNode]   -- ^ starts from immediate parent
-getParents = getpars []
-  where
-    getpars acc 0 _ = return $ reverse acc
-    getpars acc _ GenesisNode{} = return $ reverse acc
-    getpars acc n BlockNode{..} = do
-        parM <- getBlockHeader $ prevBlock nodeHeader
-        case parM of
-            Just bn -> getpars (bn : acc) (n - 1) bn
-            Nothing -> error "BUG: All non-genesis blocks should have a parent"
-
--- | Verify that checkpoint location is valid.
-validCP :: Network
-        -> BlockHeight  -- ^ new child height
-        -> BlockHash    -- ^ new child hash
-        -> Bool
-validCP net height newChildHash =
-    case lookup height (getCheckpoints net) of
-        Just cpHash -> cpHash == newChildHash
-        Nothing     -> True
-
--- | New block height above the last checkpoint imported. Used to prevent a
--- reorg below the highest checkpoint that was already imported.
-afterLastCP :: Network
-            -> BlockHeight  -- ^ best height
-            -> BlockHeight  -- ^ new imported block height
-            -> Bool
-afterLastCP net bestHeight newChildHeight =
-    case lM of
-        Just l  -> l < newChildHeight
-        Nothing -> True
-  where
-    lM =
-        listToMaybe . reverse $
-        [c | (c, _) <- getCheckpoints net, c <= bestHeight]
-
--- | This block should be at least version 2 (BIP34). Block height must be
--- included in the coinbase transaction to prevent non-unique transaction
--- hashes.
-bip34 :: Network
-      -> BlockHeight  -- ^ new child height
-      -> BlockHash    -- ^ new child hash
-      -> Bool
-bip34 net height hsh
-    | fst (getBip34Block net) == 0 = True
-    | fst (getBip34Block net) == height = snd (getBip34Block net) == hsh
-    | otherwise = True
-
--- | Check if the provided block height and version are valid.
-validVersion :: Network
-             -> BlockHeight  -- ^ new child height
-             -> Word32       -- ^ new child version
-             -> Bool
-validVersion net height version
-    | version < 2 = height < fst (getBip34Block net)
-    | version < 3 = height < getBip66Height net
-    | version < 4 = height < getBip65Height net
-    | otherwise = True
-
--- | Find last block with normal, as opposed to minimum difficulty (for test
--- networks).
-lastNoMinDiff :: BlockHeaders m => Network -> BlockNode -> m BlockNode
-lastNoMinDiff net bn@BlockNode {..} = do
-    let i = nodeHeight `mod` diffInterval net /= 0
-        c = encodeCompact (getPowLimit net)
-        l = blockBits nodeHeader == c
-        e1 =
-            error $
-            "Could not get block header for parent of " ++
-            show (headerHash nodeHeader)
-    if i && l
-        then do
-            bn' <- fromMaybe e1 <$> getBlockHeader (prevBlock nodeHeader)
-            lastNoMinDiff net bn'
-        else return bn
-
-lastNoMinDiff _ bn@GenesisNode{} = return bn
-
--- | Returns the work required on a block header given the previous block. This
--- coresponds to @bitcoind@ function @GetNextWorkRequired@ in @main.cpp@.
-nextWorkRequired :: BlockHeaders m
-                 => Network
-                 -> BlockNode
-                 -> BlockHeader
-                 -> m Word32
-nextWorkRequired net par bh = do
-    let mf = daa <|> eda <|> pow
-    case mf of
-        Just f -> f net par bh
-        Nothing ->
-            error
-                "Could not get an appropriate difficulty calculation algorithm"
-  where
-    daa = getDaaBlockHeight net >>= \daaHeight -> do
-        guard (nodeHeight par + 1 >= daaHeight)
-        return nextDaaWorkRequired
-    eda = getEdaBlockHeight net >>= \edaHeight -> do
-        guard (nodeHeight par + 1 >= edaHeight)
-        return nextEdaWorkRequired
-    pow = return nextPowWorkRequired
-
--- | Find out the next amount of work required according to the Emergency
--- Difficulty Adjustment (EDA) algorithm from Bitcoin Cash.
-nextEdaWorkRequired ::
-       BlockHeaders m => Network -> BlockNode -> BlockHeader -> m Word32
-nextEdaWorkRequired net par bh
-    | nodeHeight par + 1 `mod` diffInterval net == 0 =
-        nextWorkRequired net par bh
-    | minDifficulty = return (encodeCompact (getPowLimit net))
-    | blockBits (nodeHeader par) == encodeCompact (getPowLimit net) =
-        return (encodeCompact (getPowLimit net))
-    | otherwise = do
-        par6 <- fromMaybe e1 <$> getAncestor (nodeHeight par - 6) par
-        pars <- getParents 10 par
-        pars6 <- getParents 10 par6
-        let par6med =
-                medianTime $ map (blockTimestamp . nodeHeader) (par6 : pars6)
-            parmed = medianTime $ map (blockTimestamp . nodeHeader) (par : pars)
-            mtp6 = parmed - par6med
-        if mtp6 < 12 * 3600
-            then return $ blockBits (nodeHeader par)
-            else return $
-                 let (diff, _) = decodeCompact (blockBits (nodeHeader par))
-                     ndiff = diff + (diff `shiftR` 2)
-                  in if getPowLimit net > ndiff
-                         then encodeCompact (getPowLimit net)
-                         else encodeCompact ndiff
-  where
-    minDifficulty =
-        blockTimestamp bh >
-        blockTimestamp (nodeHeader par) + getTargetSpacing net * 2
-    e1 = error "Could not get seventh ancestor of block"
-
--- | Find the next amount of work required according to the Difficulty
--- Adjustment Algorithm (DAA) from Bitcoin Cash.
-nextDaaWorkRequired ::
-       BlockHeaders m => Network -> BlockNode -> BlockHeader -> m Word32
-nextDaaWorkRequired net par bh
-    | minDifficulty = return (encodeCompact (getPowLimit net))
-    | otherwise = do
-        let height = nodeHeight par
-        unless (height >= diffInterval net) $
-            error "Block height below difficulty interval"
-        l <- getSuitableBlock par
-        par144 <- fromMaybe e1 <$> getAncestor (height - 144) par
-        f <- getSuitableBlock par144
-        let nextTarget = computeTarget net f l
-        if nextTarget > getPowLimit net
-            then return $ encodeCompact (getPowLimit net)
-            else return $ encodeCompact nextTarget
-  where
-    e1 = error "Cannot get ancestor at parent - 144 height"
-    minDifficulty =
-        blockTimestamp bh >
-        blockTimestamp (nodeHeader par) + getTargetSpacing net * 2
-
--- | Compute Bitcoin Cash DAA target for a new block.
-computeTarget :: Network -> BlockNode -> BlockNode -> Integer
-computeTarget net f l =
-    let work = (nodeWork l - nodeWork f) * fromIntegral (getTargetSpacing net)
-        actualTimespan =
-            blockTimestamp (nodeHeader l) - blockTimestamp (nodeHeader f)
-        actualTimespan'
-            | actualTimespan > 288 * getTargetSpacing net =
-                288 * getTargetSpacing net
-            | actualTimespan < 72 * getTargetSpacing net =
-                72 * getTargetSpacing net
-            | otherwise = actualTimespan
-        work' = work `div` fromIntegral actualTimespan'
-     in 2 ^ (256 :: Integer) `div` work'
-
--- | Get suitable block for Bitcoin Cash DAA computation.
-getSuitableBlock :: BlockHeaders m => BlockNode -> m BlockNode
-getSuitableBlock par = do
-    unless (nodeHeight par >= 3) $ error "Block height is less than three"
-    blocks <- (par :) <$> getParents 2 par
-    return $ sortBy (compare `on` blockTimestamp . nodeHeader) blocks !! 1
-
--- | Returns the work required on a block header given the previous block. This
--- coresponds to bitcoind function GetNextWorkRequired in main.cpp.
-nextPowWorkRequired ::
-       BlockHeaders m => Network -> BlockNode -> BlockHeader -> m Word32
-nextPowWorkRequired net par bh
-    | nodeHeight par + 1 `mod` diffInterval net /= 0 =
-        if getAllowMinDifficultyBlocks net
-            then if ht > pt + delta
-                     then return $ encodeCompact (getPowLimit net)
-                     else do
-                         d <- lastNoMinDiff net par
-                         return $ blockBits $ nodeHeader d
-            else return $ blockBits $ nodeHeader par
-    | otherwise = do
-        let rh = nodeHeight par - (diffInterval net - 1)
-        a <- fromMaybe e1 <$> getAncestor rh par
-        let t = blockTimestamp $ nodeHeader a
-        return $ calcNextWork net (nodeHeader par) t
-  where
-    e1 = error "Could not get ancestor for block header"
-    pt = blockTimestamp $ nodeHeader par
-    ht = blockTimestamp bh
-    delta = getTargetSpacing net * 2
-
--- | Computes the work required for the first block in a new retarget period.
-calcNextWork :: Network
-             -> BlockHeader  -- ^ last block in previous retarget (parent)
-             -> Timestamp    -- ^ timestamp of first block in previous retarget
-             -> Word32
-calcNextWork net header time
-    | getPowNoRetargetting net = blockBits header
-    | new > getPowLimit net = encodeCompact (getPowLimit net)
-    | otherwise = encodeCompact new
-  where
-    s = blockTimestamp header - time
-    n | s < getTargetTimespan net `div` 4 = getTargetTimespan net `div` 4
-      | s > getTargetTimespan net * 4 = getTargetTimespan net * 4
-      | otherwise = s
-    l = fst $ decodeCompact $ blockBits header
-    new = l * fromIntegral n `div` fromIntegral (getTargetTimespan net)
-
--- | Returns True if the difficulty target (bits) of the header is valid and the
--- proof of work of the header matches the advertised difficulty target. This
--- function corresponds to the function @CheckProofOfWork@ from @bitcoind@ in
--- @main.cpp@.
-isValidPOW :: Network -> BlockHeader -> Bool
-isValidPOW net h
-    | target <= 0 || over || target > getPowLimit net = False
-    | otherwise = blockPOW (headerHash h) <= fromIntegral target
-  where
-    (target, over) = decodeCompact $ blockBits h
-
--- | Returns the proof of work of a block header hash as an 'Integer' number.
-blockPOW :: BlockHash -> Integer
-blockPOW =  bsToInteger . B.reverse . encode
-
--- | Returns the work represented by this block. Work is defined as the number
--- of tries needed to solve a block in the average case with respect to the
--- target.
-headerWork :: BlockHeader -> Integer
-headerWork bh = largestHash `div` (target + 1)
-  where
-    target      = fst $ decodeCompact $ blockBits bh
-    largestHash = 1 `shiftL` 256
-
--- | Number of blocks on average between difficulty cycles (2016 blocks).
-diffInterval :: Network -> Word32
-diffInterval net = getTargetTimespan net `div` getTargetSpacing net
-
--- | Compare two blocks to get the best.
-chooseBest :: BlockNode -> BlockNode -> BlockNode
-chooseBest b1 b2 | nodeWork b1 == nodeWork b2 =
-                       if nodeHeight b1 >= nodeHeight b2
-                       then b1
-                       else b2
-                 | nodeWork b1 > nodeWork b2 = b1
-                 | otherwise = b2
-
--- | Get list of blocks for a block locator.
-blockLocatorNodes :: BlockHeaders m => BlockNode -> m [BlockNode]
-blockLocatorNodes best =
-    reverse <$> go [] best 1
-  where
-    e1 = error "Could not get ancestor"
-    go loc bn n =
-        let loc' = bn : loc
-            n' = if length loc' > 10
-                 then n * 2
-                 else 1
-        in if nodeHeight bn < n'
-           then do a <- fromMaybe e1 <$> getAncestor 0 bn
-                   return $ a : loc'
-           else do let h = nodeHeight bn - n'
-                   bn' <- fromMaybe e1 <$> getAncestor h bn
-                   go loc' bn' n'
-
--- | Get block locator.
-blockLocator :: BlockHeaders m => BlockNode -> m BlockLocator
-blockLocator bn = map (headerHash . nodeHeader) <$> blockLocatorNodes bn
-
--- | Become rich beyond your wildest dreams.
-mineBlock :: Network -> Word32 -> BlockHeader -> BlockHeader
-mineBlock net seed h =
-    head
-        [ j
-        | i <- (+ seed) <$> [0 .. maxBound]
-        , let j = h {bhNonce = i}
-        , isValidPOW net j
-        ]
-
--- | Generate and append new blocks (mining). Only practical in regtest network.
-appendBlocks ::
-       Network
-    -> Word32 -- ^ random seed
-    -> BlockHeader
-    -> Int
-    -> [BlockHeader]
-appendBlocks _ _ _ 0 = []
-appendBlocks net seed bh i =
-    bh' : appendBlocks net seed bh' (i - 1)
-  where
-    bh' = mineBlock net seed bh
-        { prevBlock = headerHash bh
-          -- Just to make it different in every header
-        , merkleRoot = sha256 $ encode seed
-        }
-
--- | Find the last common block ancestor between provided block headers.
-splitPoint :: BlockHeaders m => BlockNode -> BlockNode -> m BlockNode
-splitPoint l r = do
-    let h = min (nodeHeight l) (nodeHeight r)
-    ll <- fromMaybe e <$> getAncestor h l
-    lr <- fromMaybe e <$> getAncestor h r
-    f ll lr
-  where
-    e = error "BUG: Could not get ancestor at lowest height"
-    f ll lr =
-        if ll == lr
-            then return lr
-            else do
-                let h = nodeHeight ll - 1
-                pl <- fromMaybe e <$> getAncestor h ll
-                pr <- fromMaybe e <$> getAncestor h lr
-                f pl pr
-
--- | Generate the entire Genesis block for 'Network'.
-genesisBlock :: Network -> Block
-genesisBlock net = Block (getGenesisHeader net) [genesisTx]
-
--- | Compute block subsidy at particular height.
-computeSubsidy :: Network -> BlockHeight -> Word64
-computeSubsidy net height =
-    let halvings = height `div` getHalvingInterval net
-        ini = 50 * 100 * 1000 * 1000
-     in if halvings >= 64
-            then 0
-            else ini `shiftR` fromIntegral halvings
diff --git a/src/Network/Haskoin/Block/Merkle.hs b/src/Network/Haskoin/Block/Merkle.hs
deleted file mode 100644
--- a/src/Network/Haskoin/Block/Merkle.hs
+++ /dev/null
@@ -1,256 +0,0 @@
-{-# LANGUAGE DeriveAnyClass #-}
-{-# LANGUAGE DeriveGeneric  #-}
-{-|
-Module      : Network.Haskoin.Block.Merkle
-Copyright   : No rights reserved
-License     : UNLICENSE
-Maintainer  : jprupp@protonmail.ch
-Stability   : experimental
-Portability : POSIX
-
-Function to deal with Merkle trees inside blocks.
--}
-module Network.Haskoin.Block.Merkle
-    ( MerkleBlock(..)
-    , MerkleRoot
-    , FlagBits
-    , PartialMerkleTree
-    , buildMerkleRoot
-    , merkleBlockTxs
-    , testMerkleRoot
-    -- * Helper functions
-    , buildPartialMerkle
-    , decodeMerkleFlags
-    , encodeMerkleFlags
-    , calcTreeHeight
-    , calcTreeWidth
-    , hash2
-    , calcHash
-    , traverseAndBuild
-    , traverseAndExtract
-    , extractMatches
-    , splitIn
-    , boolsToWord8
-    ) where
-
-import           Control.DeepSeq
-import           Control.Monad                      (forM_, replicateM, when)
-import           Data.Bits
-import qualified Data.ByteString                    as BS
-import           Data.Either                        (isRight)
-import           Data.Hashable
-import           Data.Maybe
-import           Data.Serialize                     (Serialize, encode, get,
-                                                     put)
-import           Data.Serialize.Get                 (getWord32le, getWord8)
-import           Data.Serialize.Put                 (putWord32le, putWord8)
-import           Data.Word                          (Word32, Word8)
-import           GHC.Generics
-import           Network.Haskoin.Block.Common
-import           Network.Haskoin.Constants
-import           Network.Haskoin.Crypto.Hash
-import           Network.Haskoin.Network.Common
-import           Network.Haskoin.Transaction.Common
-
--- | Hash of the block's Merkle root.
-type MerkleRoot        = Hash256
-
--- | Bits that are used to rebuild partial merkle tree transaction hash list.
-type FlagBits          = [Bool]
-
--- | Partial Merkle tree for a filtered block.
-type PartialMerkleTree = [Hash256]
-
--- | Filtered block: a block with a partial Merkle tree that only includes the
--- transactions that pass a bloom filter that was negotiated.
-data MerkleBlock =
-    MerkleBlock {
-                -- | block header
-                  merkleHeader    :: !BlockHeader
-                -- | total number of transactions in block
-                , merkleTotalTxns :: !Word32
-                -- | hashes in depth-first order
-                , mHashes         :: !PartialMerkleTree
-                -- | bits to rebuild partial merkle tree
-                , mFlags          :: !FlagBits
-                } deriving (Eq, Show, Read, Generic, Hashable, NFData)
-
-instance Serialize MerkleBlock where
-
-    get = do
-        header <- get
-        ntx    <- getWord32le
-        (VarInt matchLen) <- get
-        hashes <- replicateM (fromIntegral matchLen) get
-        (VarInt flagLen)  <- get
-        ws <- replicateM (fromIntegral flagLen) getWord8
-        return $ MerkleBlock header ntx hashes (decodeMerkleFlags ws)
-
-    put (MerkleBlock h ntx hashes flags) = do
-        put h
-        putWord32le ntx
-        putVarInt $ length hashes
-        forM_ hashes put
-        let ws = encodeMerkleFlags flags
-        putVarInt $ length ws
-        forM_ ws putWord8
-
--- | Unpack Merkle flags into 'FlagBits' structure.
-decodeMerkleFlags :: [Word8] -> FlagBits
-decodeMerkleFlags ws =
-    [ b | p <- [ 0 .. length ws * 8 - 1 ]
-        , b <- [ testBit (ws !! (p `div` 8)) (p `mod` 8) ]
-    ]
-
--- | Pack Merkle flags from 'FlagBits'.
-encodeMerkleFlags :: FlagBits -> [Word8]
-encodeMerkleFlags bs = map boolsToWord8 $ splitIn 8 bs
-
--- | Computes the height of a Merkle tree.
-calcTreeHeight :: Int -- ^ number of transactions (leaf nodes)
-               -> Int -- ^ height of the merkle tree
-calcTreeHeight ntx | ntx < 2 = 0
-                   | even ntx  = 1 + calcTreeHeight (ntx `div` 2)
-                   | otherwise = calcTreeHeight $ ntx + 1
-
--- | Computes the width of a Merkle tree at a specific height. The transactions
--- are at height 0.
-calcTreeWidth :: Int -- ^ number of transactions (leaf nodes)
-              -> Int -- ^ height at which we want to compute the width
-              -> Int -- ^ width of the Merkle tree
-calcTreeWidth ntx h = (ntx + (1 `shiftL` h) - 1) `shiftR` h
-
--- | Computes the root of a Merkle tree from a list of leaf node hashes.
-buildMerkleRoot :: [TxHash]   -- ^ transaction hashes (leaf nodes)
-                -> MerkleRoot -- ^ root of the Merkle tree
-buildMerkleRoot txs = calcHash (calcTreeHeight $ length txs) 0 txs
-
--- | Concatenate and compute double SHA256.
-hash2 :: Hash256 -> Hash256 -> Hash256
-hash2 a b = doubleSHA256 $ encode a `BS.append` encode b
-
--- | Computes the hash of a specific node in a Merkle tree.
-calcHash :: Int       -- ^ height of the node
-         -> Int       -- ^ position of the node (0 for the leftmost node)
-         -> [TxHash]  -- ^ transaction hashes (leaf nodes)
-         -> Hash256   -- ^ hash of the node at the specified position
-calcHash height pos txs
-    | height < 0 || pos < 0 = error "calcHash: Invalid parameters"
-    | height == 0 = getTxHash $ txs !! pos
-    | otherwise = hash2 left right
-  where
-    left = calcHash (height-1) (pos*2) txs
-    right | pos*2+1 < calcTreeWidth (length txs) (height-1) =
-                calcHash (height-1) (pos*2+1) txs
-          | otherwise = left
-
--- | Build a partial Merkle tree. Provide a list of tuples with all transaction
--- hashes in the block, and whether the transaction is to be included in the
--- partial tree. Returns a flag bits structure and the computed partial Merkle
--- tree.
-buildPartialMerkle ::
-       [(TxHash, Bool)] -- ^ transaction hash and whether to include
-    -> (FlagBits, PartialMerkleTree) -- ^ flag bits and partial Merkle tree
-buildPartialMerkle hs = traverseAndBuild (calcTreeHeight $ length hs) 0 hs
-
--- | Helper function to build partial Merkle tree. Used by 'buildPartialMerkle'
--- above.
-traverseAndBuild ::
-       Int -> Int -> [(TxHash, Bool)] -> (FlagBits, PartialMerkleTree)
-traverseAndBuild height pos txs
-    | height < 0 || pos < 0 = error "traverseAndBuild: Invalid parameters"
-    | height == 0 || not match = ([match], [calcHash height pos t])
-    | otherwise = (match : lb ++ rb, lh ++ rh)
-  where
-    t = map fst txs
-    s = pos `shiftL` height
-    e = min (length txs) $ (pos + 1) `shiftL` height
-    match = any snd $ take (e - s) $ drop s txs
-    (lb, lh) = traverseAndBuild (height - 1) (pos * 2) txs
-    (rb, rh)
-        | (pos * 2 + 1) < calcTreeWidth (length txs) (height - 1) =
-            traverseAndBuild (height - 1) (pos * 2 + 1) txs
-        | otherwise = ([], [])
-
--- | Helper function to extract transaction hashes from partial Merkle tree.
-traverseAndExtract :: Int -> Int -> Int -> FlagBits -> PartialMerkleTree
-                   -> Maybe (MerkleRoot, [TxHash], Int, Int)
-traverseAndExtract height pos ntx flags hashes
-    | null flags               = Nothing
-    | height == 0 || not match = leafResult
-    | isNothing leftM          = Nothing
-    | (pos*2+1) >= calcTreeWidth ntx (height-1) =
-        Just (hash2 lh lh, lm, lcf+1, lch)
-    | isNothing rightM         = Nothing
-    | otherwise =
-        Just (hash2 lh rh, lm ++ rm, lcf+rcf+1, lch+rch)
-  where
-    leafResult
-        | null hashes = Nothing
-        | otherwise = Just (h, [ TxHash h | height == 0 && match ], 1, 1)
-    (match:fs) = flags
-    (h:_)     = hashes
-    leftM  = traverseAndExtract (height-1) (pos*2) ntx fs hashes
-    (lh,lm,lcf,lch) = fromMaybe e leftM
-    rightM = traverseAndExtract (height-1) (pos*2+1) ntx
-                (drop lcf fs) (drop lch hashes)
-    (rh,rm,rcf,rch) = fromMaybe e rightM
-    e = error "traverseAndExtract: unexpected error extracting a Maybe value"
-
--- | Extracts the matching hashes from a partial merkle tree. This will return
--- the list of transaction hashes that have been included (set to true) in
--- a call to 'buildPartialMerkle'.
-extractMatches :: Network
-               -> FlagBits
-               -> PartialMerkleTree
-               -> Int -- ^ number of transaction at height 0 (leaf nodes)
-               -> Either String (MerkleRoot, [TxHash])
-               -- ^ Merkle root and list of matching transaction hashes
-extractMatches net flags hashes ntx
-    | ntx == 0 = Left
-        "extractMatches: number of transactions can not be 0"
-    | ntx > getMaxBlockSize net `div` 60 = Left
-        "extractMatches: number of transactions excessively high"
-    | length hashes > ntx = Left
-        "extractMatches: More hashes provided than the number of transactions"
-    | length flags < length hashes = Left
-        "extractMatches: At least one bit per node and one bit per hash"
-    | isNothing resM = Left
-        "extractMatches: traverseAndExtract failed"
-    | (nBitsUsed+7) `div` 8 /= (length flags+7) `div` 8 = Left
-        "extractMatches: All bits were not consumed"
-    | nHashUsed /= length hashes = Left $
-        "extractMatches: All hashes were not consumed: " ++ show nHashUsed
-    | otherwise = return (merkRoot, matches)
-  where
-    resM = traverseAndExtract (calcTreeHeight ntx) 0 ntx flags hashes
-    (merkRoot, matches, nBitsUsed, nHashUsed) = fromMaybe e resM
-    e = error "extractMatches: unexpected error extracting a Maybe value"
-
--- | Helper function to split a list in chunks 'Int' length. Last chunk may be
--- smaller.
-splitIn :: Int -> [a] -> [[a]]
-splitIn _ [] = []
-splitIn c xs = xs1 : splitIn c xs2
-  where
-    (xs1, xs2) = splitAt c xs
-
--- | Pack up to eight bools in a byte.
-boolsToWord8 :: [Bool] -> Word8
-boolsToWord8 [] = 0
-boolsToWord8 xs = foldl setBit 0 (map snd $ filter fst $ zip xs [0..7])
-
--- | Get matching transactions from Merkle block.
-merkleBlockTxs :: Network -> MerkleBlock -> Either String [TxHash]
-merkleBlockTxs net b =
-    let flags = mFlags b
-        hs = mHashes b
-        n = fromIntegral $ merkleTotalTxns b
-        merkle = merkleRoot $ merkleHeader b
-    in do (root, ths) <- extractMatches net flags hs n
-          when (root /= merkle) $ Left "merkleBlockTxs: Merkle root incorrect"
-          return ths
-
--- | Check if Merkle block root is valid against the block header.
-testMerkleRoot :: Network -> MerkleBlock -> Bool
-testMerkleRoot net = isRight . merkleBlockTxs net
diff --git a/src/Network/Haskoin/Constants.hs b/src/Network/Haskoin/Constants.hs
deleted file mode 100644
--- a/src/Network/Haskoin/Constants.hs
+++ /dev/null
@@ -1,540 +0,0 @@
-{-# LANGUAGE DeriveAnyClass    #-}
-{-# LANGUAGE DeriveGeneric     #-}
-{-# LANGUAGE OverloadedStrings #-}
-{-|
-Module      : Network.Haskoin.Constants
-Copyright   : No rights reserved
-License     : UNLICENSE
-Maintainer  : jprupp@protonmail.ch
-Stability   : experimental
-Portability : POSIX
-
-Network constants for various networks, including Bitcoin SegWit (BTC), Bitcoin
-Cash (BCH), and corresponding public test and private regression test networks.
--}
-module Network.Haskoin.Constants
-    ( Network(..)
-    , btc
-    , btcTest
-    , btcRegTest
-    , bch
-    , bchTest
-    , bchRegTest
-    , allNets
-    , netByName
-    , netByIdent
-    ) where
-
-import           Control.DeepSeq
-import           Data.ByteString              (ByteString)
-import           Data.List
-import           Data.Maybe
-import           Data.Serialize
-import           Data.String
-import           Data.Text                    (Text)
-import           Data.Version
-import           Data.Word                    (Word32, Word64, Word8)
-import           GHC.Generics                 (Generic)
-import           Network.Haskoin.Block.Common
-import           Paths_haskoin_core
-import           Text.Read
-
--- | Version of Haskoin Core package.
-versionString :: IsString a => a
-versionString = fromString (showVersion version)
-
--- | Constants for network.
-data Network = Network
-    { -- | lowercase alphanumeric and dashes
-      getNetworkName              :: !String
-      -- | network Haskell identifier
-    , getNetworkIdent             :: !String
-      -- | prefix for 'Base58' P2PKH addresses
-    , getAddrPrefix               :: !Word8
-      -- | prefix for 'Base58' P2SH addresses
-    , getScriptPrefix             :: !Word8
-      -- | prefix for WIF private key
-    , getSecretPrefix             :: !Word8
-      -- | prefix for extended public key
-    , getExtPubKeyPrefix          :: !Word32
-      -- | prefix for extended private key
-    , getExtSecretPrefix          :: !Word32
-      -- | network magic
-    , getNetworkMagic             :: !Word32
-      -- | genesis block header
-    , getGenesisHeader            :: !BlockHeader
-      -- | maximum block size in bytes
-    , getMaxBlockSize             :: !Int
-      -- | maximum amount of satoshi
-    , getMaxSatoshi               :: !Word64
-      -- | user agent string
-    , getHaskoinUserAgent         :: !ByteString
-      -- | default port for P2P connections
-    , getDefaultPort              :: !Int
-      -- | allow min difficulty blocks (testnet)
-    , getAllowMinDifficultyBlocks :: !Bool
-      -- | do not retarget difficulty (regtest)
-    , getPowNoRetargetting        :: !Bool
-      -- | proof-of-work target higest possible value
-    , getPowLimit                 :: !Integer
-      -- | block at which BIP34 activates
-    , getBip34Block               :: !(BlockHeight, BlockHash)
-      -- | block at which BIP65 activates
-    , getBip65Height              :: !BlockHeight
-      -- | block at which BIP66 activates
-    , getBip66Height              :: !BlockHeight
-      -- | time between difficulty retargets
-    , getTargetTimespan           :: !Word32
-      -- | time between blocks
-    , getTargetSpacing            :: !Word32
-      -- | checkpoints
-    , getCheckpoints              :: ![(BlockHeight, BlockHash)]
-      -- | BIP44 derivation path root
-    , getBip44Coin                :: !Word32
-      -- | peer-to-peer network seeds
-    , getSeeds                    :: ![String]
-      -- | fork id for replay protection
-    , getSigHashForkId            :: !(Maybe Word32)
-      -- | EDA start block height
-    , getEdaBlockHeight           :: !(Maybe Word32)
-      -- | DAA start block height
-    , getDaaBlockHeight           :: !(Maybe Word32)
-      -- | segregated witness active
-    , getSegWit                   :: !Bool
-      -- | 'CashAddr' prefix (for Bitcoin Cash)
-    , getCashAddrPrefix           :: !(Maybe Text)
-      -- | 'Bech32' prefix (for SegWit network)
-    , getBech32Prefix             :: !(Maybe Text)
-      -- | Replace-By-Fee (BIP-125)
-    , getReplaceByFee             :: !Bool
-      -- | Subsidy halving interval
-    , getHalvingInterval          :: !Word32
-    } deriving (Eq, Generic, NFData)
-
-instance Serialize Network where
-    put net =
-        putWord32be $ getNetworkMagic net
-    get = do
-        magic <- getWord32be
-        case find ((== magic) . getNetworkMagic) allNets of
-            Nothing  -> fail $ "Network magic unknown: " <> show magic
-            Just net -> return net
-
-instance Show Network where
-    show = getNetworkIdent
-
-instance Read Network where
-    readPrec = do
-        Ident str <- lexP
-        maybe pfail return (netByIdent str)
-
-instance IsString Network where
-    fromString = fromMaybe (error "Network name invalid") . netByName
-
--- | Query known networks by name.
-netByName :: String -> Maybe Network
-netByName str = find ((== str) . getNetworkName) allNets
-
--- | Query known networks by Haskell identifier.
-netByIdent :: String -> Maybe Network
-netByIdent str = find ((== str) . getNetworkIdent) allNets
-
--- | Bitcoin SegWit network. Symbol: BTC.
-btc :: Network
-btc =
-    Network
-    { getNetworkName = "btc"
-    , getNetworkIdent = "btc"
-    , getAddrPrefix = 0
-    , getScriptPrefix = 5
-    , getSecretPrefix = 128
-    , getExtPubKeyPrefix = 0x0488b21e
-    , getExtSecretPrefix = 0x0488ade4
-    , getNetworkMagic = 0xf9beb4d9
-    , getGenesisHeader =
-          BlockHeader
-              0x01
-              "0000000000000000000000000000000000000000000000000000000000000000"
-              "3ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a"
-              1231006505
-              0x1d00ffff
-              2083236893
-            -- Hash 000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f
-    , getMaxBlockSize = 1000000
-    , getMaxSatoshi = 2100000000000000
-    , getHaskoinUserAgent =
-          "/haskoin-btc:" <> versionString <> "/"
-    , getDefaultPort = 8333
-    , getAllowMinDifficultyBlocks = False
-    , getPowNoRetargetting = False
-    , getPowLimit =
-          0x00000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffff
-    , getBip34Block =
-          ( 227931
-          , "000000000000024b89b42a942fe0d9fea3bb44ab7bd1b19115dd6a759c0808b8")
-    , getBip65Height = 388381
-    , getBip66Height = 363725
-    , getTargetTimespan = 14 * 24 * 60 * 60
-    , getTargetSpacing = 10 * 60
-    , getCheckpoints =
-          [ ( 11111
-            , "0000000069e244f73d78e8fd29ba2fd2ed618bd6fa2ee92559f542fdb26e7c1d")
-          , ( 33333
-            , "000000002dd5588a74784eaa7ab0507a18ad16a236e7b1ce69f00d7ddfb5d0a6")
-          , ( 74000
-            , "0000000000573993a3c9e41ce34471c079dcf5f52a0e824a81e7f953b8661a20")
-          , ( 105000
-            , "00000000000291ce28027faea320c8d2b054b2e0fe44a773f3eefb151d6bdc97")
-          , ( 134444
-            , "00000000000005b12ffd4cd315cd34ffd4a594f430ac814c91184a0d42d2b0fe")
-          , ( 168000
-            , "000000000000099e61ea72015e79632f216fe6cb33d7899acb35b75c8303b763")
-          , ( 193000
-            , "000000000000059f452a5f7340de6682a977387c17010ff6e6c3bd83ca8b1317")
-          , ( 210000
-            , "000000000000048b95347e83192f69cf0366076336c639f9b7228e9ba171342e")
-          , ( 216116
-            , "00000000000001b4f4b433e81ee46494af945cf96014816a4e2370f11b23df4e")
-          , ( 225430
-            , "00000000000001c108384350f74090433e7fcf79a606b8e797f065b130575932")
-          , ( 250000
-            , "000000000000003887df1f29024b06fc2200b55f8af8f35453d7be294df2d214")
-          , ( 279000
-            , "0000000000000001ae8c72a0b0c301f67e3afca10e819efa9041e458e9bd7e40")
-          , ( 295000
-            , "00000000000000004d9b4ef50f0f9d686fd69db2e03af35a100370c64632a983")
-          ]
-    , getSeeds =
-          [ "seed.bitcoin.sipa.be" -- Pieter Wuille
-          , "dnsseed.bluematt.me" -- Matt Corallo
-          , "dnsseed.bitcoin.dashjr.org" -- Luke Dashjr
-          , "seed.bitcoinstats.com" -- Chris Decker
-          , "seed.bitcoin.jonasschnelli.ch" -- Jonas Schnelli
-          , "seed.btc.petertodd.org" -- Peter Todd
-          , "seed.bitcoin.sprovoost.nl" -- Sjors Provoost
-          ]
-    , getBip44Coin = 0
-    , getSigHashForkId = Nothing
-    , getEdaBlockHeight = Nothing
-    , getDaaBlockHeight = Nothing
-    , getSegWit = True
-    , getCashAddrPrefix = Nothing
-    , getBech32Prefix = Just "bc"
-    , getReplaceByFee = True
-    , getHalvingInterval = 210000
-    }
-
--- | Testnet for Bitcoin SegWit network.
-btcTest :: Network
-btcTest =
-    Network
-    { getNetworkName = "btctest"
-    , getNetworkIdent = "btcTest"
-    , getAddrPrefix = 111
-    , getScriptPrefix = 196
-    , getSecretPrefix = 239
-    , getExtPubKeyPrefix = 0x043587cf
-    , getExtSecretPrefix = 0x04358394
-    , getNetworkMagic = 0x0b110907
-    , getGenesisHeader =
-          BlockHeader
-              0x01
-              "0000000000000000000000000000000000000000000000000000000000000000"
-              "3ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a"
-              1296688602
-              486604799
-              414098458
-            -- Hash 000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943
-    , getMaxBlockSize = 1000000
-    , getMaxSatoshi = 2100000000000000
-    , getHaskoinUserAgent = "/haskoin-btc-test:" <> versionString <> "/"
-    , getDefaultPort = 18333
-    , getAllowMinDifficultyBlocks = True
-    , getPowNoRetargetting = False
-    , getPowLimit =
-          0x00000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffff
-    , getBip34Block =
-          ( 21111
-          , "0000000023b3a96d3484e5abb3755c413e7d41500f8e2a5c3f0dd01299cd8ef8")
-    , getBip65Height = 581885
-    , getBip66Height = 330776
-    , getTargetTimespan = 14 * 24 * 60 * 60
-    , getTargetSpacing = 10 * 60
-    , getCheckpoints =
-          [ ( 546
-            , "000000002a936ca763904c3c35fce2f3556c559c0214345d31b1bcebf76acb70")
-          ]
-    , getSeeds =
-          [ "testnet-seed.bitcoin.jonasschnelli.ch"
-          , "seed.tbtc.petertodd.org"
-          , "seed.testnet.bitcoin.sprovoost.nl"
-          , "testnet-seed.bluematt.me"
-          ]
-    , getBip44Coin = 1
-    , getSigHashForkId = Nothing
-    , getEdaBlockHeight = Nothing
-    , getDaaBlockHeight = Nothing
-    , getSegWit = True
-    , getCashAddrPrefix = Nothing
-    , getBech32Prefix = Just "tb"
-    , getReplaceByFee = True
-    , getHalvingInterval = 210000
-    }
-
--- | RegTest for Bitcoin SegWit network.
-btcRegTest :: Network
-btcRegTest =
-    Network
-    { getNetworkName = "btcreg"
-    , getNetworkIdent = "btcRegTest"
-    , getAddrPrefix = 111
-    , getScriptPrefix = 196
-    , getSecretPrefix = 239
-    , getExtPubKeyPrefix = 0x043587cf
-    , getExtSecretPrefix = 0x04358394
-    , getNetworkMagic = 0xfabfb5da
-    , getGenesisHeader =
-          BlockHeader
-           -- 0f9188f13cb7b2c71f2a335e3a4fc328bf5beb436012afca590b1a11466e2206
-              0x01
-              "0000000000000000000000000000000000000000000000000000000000000000"
-              "3ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a"
-              1296688602
-              0x207fffff
-              2
-    , getMaxBlockSize = 1000000
-    , getMaxSatoshi = 2100000000000000
-    , getHaskoinUserAgent = "/haskoin-btc-regtest:" <> versionString <> "/"
-    , getDefaultPort = 18444
-    , getAllowMinDifficultyBlocks = True
-    , getPowNoRetargetting = True
-    , getPowLimit =
-          0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
-    , getBip34Block =
-          ( 100000000
-          , "0000000000000000000000000000000000000000000000000000000000000000")
-    , getBip65Height = 1351
-    , getBip66Height = 1251
-    , getTargetTimespan = 14 * 24 * 60 * 60
-    , getTargetSpacing = 10 * 60
-    , getCheckpoints = []
-    , getSeeds = ["localhost"]
-    , getBip44Coin = 1
-    , getSigHashForkId = Nothing
-    , getEdaBlockHeight = Nothing
-    , getDaaBlockHeight = Nothing
-    , getSegWit = True
-    , getCashAddrPrefix = Nothing
-    , getBech32Prefix = Just "bcrt"
-    , getReplaceByFee = True
-    , getHalvingInterval = 150
-    }
-
--- | Bitcoin Cash network. Symbol: BCH.
-bch :: Network
-bch =
-    Network
-    { getNetworkName = "bch"
-    , getNetworkIdent = "bch"
-    , getAddrPrefix = 0
-    , getScriptPrefix = 5
-    , getSecretPrefix = 128
-    , getExtPubKeyPrefix = 0x0488b21e
-    , getExtSecretPrefix = 0x0488ade4
-    , getNetworkMagic = 0xe3e1f3e8
-    , getGenesisHeader =
-          BlockHeader
-              0x01
-              "0000000000000000000000000000000000000000000000000000000000000000"
-              "3ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a"
-              1231006505
-              0x1d00ffff
-              2083236893
-            -- Hash 000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f
-    , getMaxBlockSize = 32000000
-    , getMaxSatoshi = 2100000000000000
-    , getHaskoinUserAgent = "/haskoin-bch:" <> versionString <> "/"
-    , getDefaultPort = 8333
-    , getAllowMinDifficultyBlocks = False
-    , getPowNoRetargetting = False
-    , getPowLimit =
-          0x00000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffff
-    , getBip34Block =
-          ( 227931
-          , "000000000000024b89b42a942fe0d9fea3bb44ab7bd1b19115dd6a759c0808b8")
-    , getBip65Height = 388381
-    , getBip66Height = 363725
-    , getTargetTimespan = 14 * 24 * 60 * 60
-    , getTargetSpacing = 10 * 60
-    , getCheckpoints =
-          [ ( 11111
-            , "0000000069e244f73d78e8fd29ba2fd2ed618bd6fa2ee92559f542fdb26e7c1d")
-          , ( 33333
-            , "000000002dd5588a74784eaa7ab0507a18ad16a236e7b1ce69f00d7ddfb5d0a6")
-          , ( 74000
-            , "0000000000573993a3c9e41ce34471c079dcf5f52a0e824a81e7f953b8661a20")
-          , ( 105000
-            , "00000000000291ce28027faea320c8d2b054b2e0fe44a773f3eefb151d6bdc97")
-          , ( 134444
-            , "00000000000005b12ffd4cd315cd34ffd4a594f430ac814c91184a0d42d2b0fe")
-          , ( 168000
-            , "000000000000099e61ea72015e79632f216fe6cb33d7899acb35b75c8303b763")
-          , ( 193000
-            , "000000000000059f452a5f7340de6682a977387c17010ff6e6c3bd83ca8b1317")
-          , ( 210000
-            , "000000000000048b95347e83192f69cf0366076336c639f9b7228e9ba171342e")
-          , ( 216116
-            , "00000000000001b4f4b433e81ee46494af945cf96014816a4e2370f11b23df4e")
-          , ( 225430
-            , "00000000000001c108384350f74090433e7fcf79a606b8e797f065b130575932")
-          , ( 250000
-            , "000000000000003887df1f29024b06fc2200b55f8af8f35453d7be294df2d214")
-          , ( 279000
-            , "0000000000000001ae8c72a0b0c301f67e3afca10e819efa9041e458e9bd7e40")
-          , ( 295000
-            , "00000000000000004d9b4ef50f0f9d686fd69db2e03af35a100370c64632a983")
-            -- UAHF fork block.
-          , ( 478559
-            , "000000000000000000651ef99cb9fcbe0dadde1d424bd9f15ff20136191a5eec")
-            -- Nov, 13 DAA activation block.
-          , ( 504031
-            , "0000000000000000011ebf65b60d0a3de80b8175be709d653b4c1a1beeb6ab9c")
-          ]
-    , getSeeds =
-          [ "seed.bitcoinabc.org"
-          , "seed-abc.bitcoinforks.org"
-          , "btccash-seeder.bitcoinunlimited.info"
-          , "seed.bitprim.org"
-          , "seed.deadalnix.me"
-          , "seeder.criptolayer.net"
-          ]
-    , getBip44Coin = 145
-    , getSigHashForkId = Just 0
-    , getEdaBlockHeight = Just 478559
-    , getDaaBlockHeight = Just 404031
-    , getSegWit = False
-    , getCashAddrPrefix = Just "bitcoincash"
-    , getBech32Prefix = Nothing
-    , getReplaceByFee = False
-    , getHalvingInterval = 210000
-    }
-
--- | Testnet for Bitcoin Cash network.
-bchTest :: Network
-bchTest =
-    Network
-    { getNetworkName = "bchtest"
-    , getNetworkIdent = "bchTest"
-    , getAddrPrefix = 111
-    , getScriptPrefix = 196
-    , getSecretPrefix = 239
-    , getExtPubKeyPrefix = 0x043587cf
-    , getExtSecretPrefix = 0x04358394
-    , getNetworkMagic = 0xf4e5f3f4
-    , getGenesisHeader =
-          BlockHeader
-              0x01
-              "0000000000000000000000000000000000000000000000000000000000000000"
-              "3ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a"
-              1296688602
-              486604799
-              414098458
-            -- Hash 000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943
-    , getMaxBlockSize = 32000000
-    , getMaxSatoshi = 2100000000000000
-    , getHaskoinUserAgent = "/haskoin-bch-test:" <> versionString <> "/"
-    , getDefaultPort = 18333
-    , getAllowMinDifficultyBlocks = True
-    , getPowNoRetargetting = False
-    , getPowLimit =
-          0x00000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffff
-    , getBip34Block =
-          ( 21111
-          , "0000000023b3a96d3484e5abb3755c413e7d41500f8e2a5c3f0dd01299cd8ef8")
-    , getBip65Height = 581885
-    , getBip66Height = 330776
-    , getTargetTimespan = 14 * 24 * 60 * 60
-    , getTargetSpacing = 10 * 60
-    , getCheckpoints =
-          [ ( 546
-            , "000000002a936ca763904c3c35fce2f3556c559c0214345d31b1bcebf76acb70")
-            -- UAHF fork block.
-          , ( 1155876
-            , "00000000000e38fef93ed9582a7df43815d5c2ba9fd37ef70c9a0ea4a285b8f5")
-            -- Nov, 13. DAA activation block.
-          , ( 1188697
-            , "0000000000170ed0918077bde7b4d36cc4c91be69fa09211f748240dabe047fb")
-          ]
-    , getSeeds =
-          [ "testnet-seed.bitcoinabc.org"
-          , "testnet-seed-abc.bitcoinforks.org"
-          , "testnet-seed.bitprim.org"
-          , "testnet-seed.deadalnix.me"
-          , "testnet-seeder.criptolayer.net"
-          ]
-    , getBip44Coin = 1
-    , getSigHashForkId = Just 0
-    , getEdaBlockHeight = Just 1155876
-    , getDaaBlockHeight = Just 1188697
-    , getSegWit = False
-    , getCashAddrPrefix = Just "bchtest"
-    , getBech32Prefix = Nothing
-    , getReplaceByFee = False
-    , getHalvingInterval = 210000
-    }
-
--- | RegTest for Bitcoin Cash network.
-bchRegTest :: Network
-bchRegTest =
-    Network
-    { getNetworkName = "bchreg"
-    , getNetworkIdent = "bchRegTest"
-    , getAddrPrefix = 111
-    , getScriptPrefix = 196
-    , getSecretPrefix = 239
-    , getExtPubKeyPrefix = 0x043587cf
-    , getExtSecretPrefix = 0x04358394
-    , getNetworkMagic = 0xdab5bffa
-    , getGenesisHeader =
-          BlockHeader
-           -- 0f9188f13cb7b2c71f2a335e3a4fc328bf5beb436012afca590b1a11466e2206
-              0x01
-              "0000000000000000000000000000000000000000000000000000000000000000"
-              "3ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a"
-              1296688602
-              0x207fffff
-              2
-    , getMaxBlockSize = 1000000
-    , getMaxSatoshi = 2100000000000000
-    , getHaskoinUserAgent = "/haskoin-bch-regtest:" <> versionString <> "/"
-    , getDefaultPort = 18444
-    , getAllowMinDifficultyBlocks = True
-    , getPowNoRetargetting = True
-    , getPowLimit =
-          0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
-    , getBip34Block =
-          ( 100000000
-          , "0000000000000000000000000000000000000000000000000000000000000000")
-    , getBip65Height = 1351
-    , getBip66Height = 1251
-    , getTargetTimespan = 14 * 24 * 60 * 60
-    , getTargetSpacing = 10 * 60
-    , getCheckpoints =
-          [ ( 0
-            , "0f9188f13cb7b2c71f2a335e3a4fc328bf5beb436012afca590b1a11466e2206")
-          ]
-    , getSeeds = ["localhost"]
-    , getBip44Coin = 1
-    , getSigHashForkId = Just 0
-    , getEdaBlockHeight = Nothing
-    , getDaaBlockHeight = Just 0
-    , getSegWit = False
-    , getCashAddrPrefix = Just "bchreg"
-    , getBech32Prefix = Nothing
-    , getReplaceByFee = False
-    , getHalvingInterval = 150
-    }
-
--- | List of all networks supported by this library.
-allNets :: [Network]
-allNets = [btc, bch, btcTest, bchTest, btcRegTest, bchRegTest]
diff --git a/src/Network/Haskoin/Crypto.hs b/src/Network/Haskoin/Crypto.hs
deleted file mode 100644
--- a/src/Network/Haskoin/Crypto.hs
+++ /dev/null
@@ -1,22 +0,0 @@
-{-|
-Module      : Network.Haskoin.Crypto
-Copyright   : No rights reserved
-License     : UNLICENSE
-Maintainer  : jprupp@protonmail.ch
-Stability   : experimental
-Portability : POSIX
-
-Hashing functions and ECDSA signatures.
--}
-module Network.Haskoin.Crypto
-    ( -- * Hashes
-      module Hash
-      -- * Signatures
-    , module Signature
-      -- * Secp256k1 (re-exported)
-    , module Secp256k1
-    ) where
-
-import           Crypto.Secp256k1                 as Secp256k1
-import           Network.Haskoin.Crypto.Hash      as Hash
-import           Network.Haskoin.Crypto.Signature as Signature
diff --git a/src/Network/Haskoin/Crypto/Hash.hs b/src/Network/Haskoin/Crypto/Hash.hs
deleted file mode 100644
--- a/src/Network/Haskoin/Crypto/Hash.hs
+++ /dev/null
@@ -1,202 +0,0 @@
-{-# LANGUAGE DeriveGeneric #-}
-{-# LANGUAGE DeriveAnyClass #-}
-{-|
-Module      : Network.Haskoin.Crypto.Hash
-Copyright   : No rights reserved
-License     : UNLICENSE
-Maintainer  : jprupp@protonmail.ch
-Stability   : experimental
-Portability : POSIX
-
-Hashing functions and corresponding data types. Uses functions from the
-cryptonite library.
--}
-module Network.Haskoin.Crypto.Hash
-    ( -- * Hashes
-      Hash512(getHash512)
-    , Hash256(getHash256)
-    , Hash160(getHash160)
-    , CheckSum32(getCheckSum32)
-    , sha512
-    , sha256
-    , ripemd160
-    , sha1
-    , doubleSHA256
-    , addressHash
-    , checkSum32
-    , hmac512
-    , hmac256
-    , split512
-    , join512
-    ) where
-
-import           Control.DeepSeq
-import           Crypto.Hash             (RIPEMD160 (..), SHA1 (..),
-                                          SHA256 (..), SHA512 (..), hashWith)
-import           Crypto.MAC.HMAC         (HMAC, hmac)
-import           Data.ByteArray          (ByteArrayAccess)
-import qualified Data.ByteArray          as BA
-import           Data.ByteString         (ByteString)
-import qualified Data.ByteString         as BS
-import           Data.ByteString.Short   (ShortByteString)
-import qualified Data.ByteString.Short   as BSS
-import           Data.Either             (fromRight)
-import           Data.Hashable           (Hashable)
-import           Data.Serialize          (Serialize (..), decode)
-import qualified Data.Serialize.Get      as Get
-import qualified Data.Serialize.Put      as Put
-import           Data.String             (IsString, fromString)
-import           Data.String.Conversions (cs)
-import           Data.Word               (Word32)
-import           GHC.Generics            (Generic)
-import           Network.Haskoin.Util
-import           Text.Read               as R
-
--- | 'Word32' wrapped for type-safe 32-bit checksums.
-newtype CheckSum32 = CheckSum32
-    { getCheckSum32 :: Word32
-    } deriving (Eq, Ord, Serialize, Show, Read, Hashable, Generic, NFData)
-
--- | Type for 512-bit hashes.
-newtype Hash512 = Hash512 { getHash512 :: ShortByteString }
-    deriving (Eq, Ord, Hashable, Generic, NFData)
-
--- | Type for 256-bit hashes.
-newtype Hash256 = Hash256 { getHash256 :: ShortByteString }
-    deriving (Eq, Ord, Hashable, Generic, NFData)
-
--- | Type for 160-bit hashes.
-newtype Hash160 = Hash160 { getHash160 :: ShortByteString }
-    deriving (Eq, Ord, Hashable, Generic, NFData)
-
-instance Show Hash512 where
-    showsPrec _ = shows . encodeHex . BSS.fromShort . getHash512
-
-instance Read Hash512 where
-    readPrec = do
-        R.String str <- lexP
-        maybe pfail return $ Hash512 . BSS.toShort <$> decodeHex (cs str)
-
-instance Show Hash256 where
-    showsPrec _ = shows . encodeHex . BSS.fromShort . getHash256
-
-instance Read Hash256 where
-    readPrec = do
-        R.String str <- lexP
-        maybe pfail return $ Hash256 . BSS.toShort <$> decodeHex (cs str)
-
-instance Show Hash160 where
-    showsPrec _ = shows . encodeHex . BSS.fromShort . getHash160
-
-instance Read Hash160 where
-    readPrec = do
-        R.String str <- lexP
-        maybe pfail return $ Hash160 . BSS.toShort <$> decodeHex (cs str)
-
-instance IsString Hash512 where
-    fromString str =
-        case decodeHex $ cs str of
-            Nothing -> e
-            Just bs ->
-                case BS.length bs of
-                    64 -> Hash512 (BSS.toShort bs)
-                    _  -> e
-      where
-        e = error "Could not decode hash from hex string"
-
-instance Serialize Hash512 where
-    get = Hash512 <$> Get.getShortByteString 64
-    put = Put.putShortByteString . getHash512
-
-instance IsString Hash256 where
-    fromString str =
-        case decodeHex $ cs str of
-            Nothing -> e
-            Just bs ->
-                case BS.length bs of
-                    32 -> Hash256 (BSS.toShort bs)
-                    _  -> e
-      where
-        e = error "Could not decode hash from hex string"
-
-instance Serialize Hash256 where
-    get = Hash256 <$> Get.getShortByteString 32
-    put = Put.putShortByteString . getHash256
-
-instance IsString Hash160 where
-    fromString str =
-        case decodeHex $ cs str of
-            Nothing -> e
-            Just bs ->
-                case BS.length bs of
-                    20 -> Hash160 (BSS.toShort bs)
-                    _  -> e
-      where
-        e = error "Could not decode hash from hex string"
-
-instance Serialize Hash160 where
-    get = Hash160 <$> Get.getShortByteString 20
-    put = Put.putShortByteString . getHash160
-
--- | Calculate SHA512 hash.
-sha512 :: ByteArrayAccess b => b -> Hash512
-sha512 = Hash512 . BSS.toShort . BA.convert . hashWith SHA512
-
--- | Calculate SHA256 hash.
-sha256 :: ByteArrayAccess b => b -> Hash256
-sha256 = Hash256 . BSS.toShort . BA.convert . hashWith SHA256
-
--- | Calculate RIPEMD160 hash.
-ripemd160 :: ByteArrayAccess b => b -> Hash160
-ripemd160 = Hash160 . BSS.toShort . BA.convert . hashWith RIPEMD160
-
--- | Claculate SHA1 hash.
-sha1 :: ByteArrayAccess b => b -> Hash160
-sha1 = Hash160 . BSS.toShort . BA.convert . hashWith SHA1
-
--- | Compute two rounds of SHA-256.
-doubleSHA256 :: ByteArrayAccess b => b -> Hash256
-doubleSHA256 =
-    Hash256 . BSS.toShort . BA.convert . hashWith SHA256 . hashWith SHA256
-
--- | Compute SHA-256 followed by RIPMED-160.
-addressHash :: ByteArrayAccess b => b -> Hash160
-addressHash =
-    Hash160 . BSS.toShort . BA.convert . hashWith RIPEMD160 . hashWith SHA256
-
-{- CheckSum -}
-
--- | Computes a 32 bit checksum.
-checkSum32 :: ByteArrayAccess b => b -> CheckSum32
-checkSum32 = fromRight (error "Could not decode bytes as CheckSum32")
-             . decode
-             . BS.take 4
-             . BA.convert
-             . hashWith SHA256
-             . hashWith SHA256
-
-{- HMAC -}
-
--- | Computes HMAC over SHA-512.
-hmac512 :: ByteString -> ByteString -> Hash512
-hmac512 key msg =
-    Hash512 $ BSS.toShort $ BA.convert (hmac key msg :: HMAC SHA512)
-
--- | Computes HMAC over SHA-256.
-hmac256 :: (ByteArrayAccess k, ByteArrayAccess m) => k -> m -> Hash256
-hmac256 key msg =
-    Hash256 $ BSS.toShort $ BA.convert (hmac key msg :: HMAC SHA256)
-
--- | Split a 'Hash512' into a pair of 'Hash256'.
-split512 :: Hash512 -> (Hash256, Hash256)
-split512 h =
-    (Hash256 (BSS.toShort a), Hash256 (BSS.toShort b))
-  where
-    (a, b) = BS.splitAt 32 . BSS.fromShort $ getHash512 h
-
--- | Join a pair of 'Hash256' into a 'Hash512'.
-join512 :: (Hash256, Hash256) -> Hash512
-join512 (a, b) =
-    Hash512 .
-    BSS.toShort $
-        BSS.fromShort (getHash256 a) `BS.append` BSS.fromShort (getHash256 b)
diff --git a/src/Network/Haskoin/Crypto/Signature.hs b/src/Network/Haskoin/Crypto/Signature.hs
deleted file mode 100644
--- a/src/Network/Haskoin/Crypto/Signature.hs
+++ /dev/null
@@ -1,90 +0,0 @@
-{-# LANGUAGE OverloadedStrings #-}
-{-|
-Module      : Network.Haskoin.Crypto.Signature
-Copyright   : No rights reserved
-License     : UNLICENSE
-Maintainer  : jprupp@protonmail.ch
-Stability   : experimental
-Portability : POSIX
-
-ECDSA signatures using secp256k1 curve. Uses functions from upstream secp256k1
-library.
--}
-module Network.Haskoin.Crypto.Signature
-    ( putSig
-    , getSig
-    , signHash
-    , verifyHashSig
-    , isCanonicalHalfOrder
-    , decodeStrictSig
-    , exportSig
-    ) where
-
-import           Control.Monad               (guard, unless, when)
-import           Crypto.Secp256k1
-import           Data.ByteString             (ByteString)
-import qualified Data.ByteString             as BS
-import           Data.ByteString.Short       (toShort)
-import           Data.Maybe                  (fromMaybe)
-import           Data.Serialize              as S
-import           Data.Serialize.Put          (Putter, putByteString)
-import           Network.Haskoin.Crypto.Hash
-import           Numeric                     (showHex)
-
--- | Convert 256-bit hash into a 'Msg' for signing or verification.
-hashToMsg :: Hash256 -> Msg
-hashToMsg =
-    fromMaybe e . msg . encode
-  where
-    e = error "Could not convert 32-byte hash to secp256k1 message"
-
--- | Sign a 256-bit hash using secp256k1 elliptic curve.
-signHash :: SecKey -> Hash256 -> Sig
-signHash k = signMsg k . hashToMsg
-
--- | Verify an ECDSA signature for a 256-bit hash.
-verifyHashSig :: Hash256 -> Sig -> PubKey -> Bool
-verifyHashSig h s p =
-    verifySig p g m
-  where
-    (g, _) = normalizeSig s
-    m = hashToMsg h
-
--- | Deserialize an ECDSA signature as commonly encoded in Bitcoin.
-getSig :: Get Sig
-getSig = do
-        l <- lookAhead $ do
-            t <- getWord8
-            -- 0x30 is DER sequence type
-            unless (t == 0x30) $ fail $
-                "Bad DER identifier byte 0x" ++ showHex t ". Expecting 0x30"
-            l <- getWord8
-            when (l == 0x00) $ fail "Indeterminate form unsupported"
-            when (l >= 0x80) $ fail "Multi-octect length not supported"
-            return $ fromIntegral l
-        bs <- getByteString $ l + 2
-        case decodeStrictSig bs of
-            Just s  -> return s
-            Nothing -> fail "Invalid signature"
-
--- | Serialize an ECDSA signature for Bitcoin use.
-putSig :: Putter Sig
-putSig s = putByteString $ exportSig s
-
--- | Is canonical half order.
-isCanonicalHalfOrder :: Sig -> Bool
-isCanonicalHalfOrder = not . snd . normalizeSig
-
--- | Decode signature strictly.
-decodeStrictSig :: ByteString -> Maybe Sig
-decodeStrictSig bs = do
-    g <- importSig bs
-    let compact = exportCompactSig g
-    -- <http://www.secg.org/sec1-v2.pdf Section 4.1.4>
-    -- 4.1.4.1 (r and s can not be zero)
-    guard $ getCompactSigR compact /= zero
-    guard $ getCompactSigS compact /= zero
-    guard $ isCanonicalHalfOrder g
-    return g
-  where
-    zero = toShort $ BS.replicate 32 0
diff --git a/src/Network/Haskoin/Keys.hs b/src/Network/Haskoin/Keys.hs
deleted file mode 100644
--- a/src/Network/Haskoin/Keys.hs
+++ /dev/null
@@ -1,18 +0,0 @@
-{-|
-Module      : Network.Haskoin.Keys
-Copyright   : No rights reserved
-License     : UNLICENSE
-Maintainer  : jprupp@protonmail.ch
-Stability   : experimental
-Portability : POSIX
-
-ECDSA private and public keys, extended keys (BIP-32) and mnemonic sentences
-(BIP-39).
--}
-module Network.Haskoin.Keys
-    ( module X
-    ) where
-
-import           Network.Haskoin.Keys.Common   as X
-import           Network.Haskoin.Keys.Extended as X
-import           Network.Haskoin.Keys.Mnemonic as X
diff --git a/src/Network/Haskoin/Keys/Common.hs b/src/Network/Haskoin/Keys/Common.hs
deleted file mode 100644
--- a/src/Network/Haskoin/Keys/Common.hs
+++ /dev/null
@@ -1,134 +0,0 @@
-{-# LANGUAGE DeriveAnyClass    #-}
-{-# LANGUAGE DeriveGeneric     #-}
-{-# LANGUAGE FlexibleContexts  #-}
-{-# LANGUAGE FlexibleInstances #-}
-{-# LANGUAGE OverloadedStrings #-}
-{-|
-Module      : Network.Haskoin.Keys.Common
-Copyright   : No rights reserved
-License     : UNLICENSE
-Maintainer  : jprupp@protonmail.ch
-Stability   : experimental
-Portability : POSIX
-
-ECDSA private and public key functions.
--}
-module Network.Haskoin.Keys.Common
-    ( -- * Public & Private Keys
-      PubKeyI(..)
-    , SecKeyI(..)
-    , exportPubKey
-    , importPubKey
-    , wrapPubKey
-    , derivePubKeyI
-    , wrapSecKey
-    , fromMiniKey
-    , tweakPubKey
-    , tweakSecKey
-    , secKeyPut
-    , secKeyGet
-    , getSecKey
-    , secKey
-    ) where
-
-import           Control.Applicative         ((<|>))
-import           Control.DeepSeq
-import           Control.Monad               (guard, mzero, (<=<))
-import           Crypto.Secp256k1
-import           Data.Aeson                  (FromJSON, ToJSON, Value (String),
-                                              parseJSON, toJSON, withText)
-import           Data.ByteString             (ByteString)
-import qualified Data.ByteString             as BS
-import           Data.Hashable
-import           Data.Maybe                  (fromMaybe)
-import           Data.Serialize              (Serialize, decode, encode, get,
-                                              put)
-import           Data.Serialize.Get          (Get, getByteString)
-import           Data.Serialize.Put          (Putter, putByteString)
-import           Data.String                 (IsString, fromString)
-import           Data.String.Conversions     (cs)
-import           GHC.Generics                (Generic)
-import           Network.Haskoin.Crypto.Hash
-import           Network.Haskoin.Util
-
--- | Elliptic curve public key type with expected serialized compression flag.
-data PubKeyI = PubKeyI
-    { pubKeyPoint      :: !PubKey
-    , pubKeyCompressed :: !Bool
-    } deriving (Generic, Eq, Show, Read, Hashable, NFData)
-
-instance IsString PubKeyI where
-    fromString str =
-        fromMaybe e $ eitherToMaybe . decode <=< decodeHex $ cs str
-      where
-        e = error "Could not decode public key"
-
-instance ToJSON PubKeyI where
-    toJSON = String . encodeHex . encode
-
-instance FromJSON PubKeyI where
-    parseJSON = withText "PubKeyI" $
-        maybe mzero return . (eitherToMaybe . decode =<<) . decodeHex
-
-instance Serialize PubKeyI where
-    get = c <|> u
-      where
-        c = do
-            bs <- getByteString 33
-            guard $ BS.head bs `BS.elem` BS.pack [0x02, 0x03]
-            maybe mzero return $ PubKeyI <$> importPubKey bs <*> pure True
-        u = do
-            bs <- getByteString 65
-            guard $ BS.head bs == 0x04
-            maybe mzero return $ PubKeyI <$> importPubKey bs <*> pure False
-
-    put pk = putByteString $ exportPubKey (pubKeyCompressed pk) (pubKeyPoint pk)
-
--- | Wrap a public key from secp256k1 library adding information about compression.
-wrapPubKey :: Bool -> PubKey -> PubKeyI
-wrapPubKey c p = PubKeyI p c
-
--- | Derives a public key from a private key. This function will preserve
--- compression flag.
-derivePubKeyI :: SecKeyI -> PubKeyI
-derivePubKeyI (SecKeyI d c) = PubKeyI (derivePubKey d) c
-
--- | Tweak a public key.
-tweakPubKey :: PubKey -> Hash256 -> Maybe PubKey
-tweakPubKey p h = tweakAddPubKey p =<< tweak (encode h)
-
--- | Elliptic curve private key type with expected public key compression
--- information. Compression information is stored in private key WIF formats and
--- needs to be preserved to generate the correct address from the corresponding
--- public key.
-data SecKeyI = SecKeyI
-    { secKeyData       :: !SecKey
-    , secKeyCompressed :: !Bool
-    } deriving (Eq, Show, Read, Generic, NFData)
-
--- | Wrap private key with corresponding public key compression flag.
-wrapSecKey :: Bool -> SecKey -> SecKeyI
-wrapSecKey c d = SecKeyI d c
-
--- | Deserialize 'SecKey'.
-secKeyGet :: Get SecKey
-secKeyGet = do
-    bs <- getByteString 32
-    maybe (fail "invalid private key") return (secKey bs)
-
--- | Serialize 'SecKey'.
-secKeyPut :: Putter SecKey
-secKeyPut = putByteString . getSecKey
-
--- | Decode Casascius mini private keys (22 or 30 characters).
-fromMiniKey :: ByteString -> Maybe SecKeyI
-fromMiniKey bs = do
-    guard checkShortKey
-    wrapSecKey False <$> secKey (encode (sha256 bs))
-  where
-    checkHash = encode $ sha256 $ bs `BS.append` "?"
-    checkShortKey = BS.length bs `elem` [22, 30] && BS.head checkHash == 0x00
-
--- | Tweak a private key.
-tweakSecKey :: SecKey -> Hash256 -> Maybe SecKey
-tweakSecKey key h = tweakAddSecKey key =<< tweak (encode h)
diff --git a/src/Network/Haskoin/Keys/Extended.hs b/src/Network/Haskoin/Keys/Extended.hs
deleted file mode 100644
--- a/src/Network/Haskoin/Keys/Extended.hs
+++ /dev/null
@@ -1,913 +0,0 @@
-{-# LANGUAGE DeriveAnyClass    #-}
-{-# LANGUAGE DeriveGeneric     #-}
-{-# LANGUAGE FlexibleInstances #-}
-{-# LANGUAGE GADTs             #-}
-{-# LANGUAGE OverloadedStrings #-}
-{-|
-Module      : Network.Haskoin.Keys.Extended
-Copyright   : No rights reserved
-License     : UNLICENSE
-Maintainer  : jprupp@protonmail.ch
-Stability   : experimental
-Portability : POSIX
-
-BIP-32 extended keys.
--}
-module Network.Haskoin.Keys.Extended
-    (
-      -- * Extended Keys
-      XPubKey(..)
-    , XPrvKey(..)
-    , ChainCode
-    , KeyIndex
-    , Fingerprint
-    , DerivationException(..)
-    , makeXPrvKey
-    , deriveXPubKey
-    , prvSubKey
-    , pubSubKey
-    , hardSubKey
-    , xPrvIsHard
-    , xPubIsHard
-    , xPrvChild
-    , xPubChild
-    , xPubID
-    , xPrvID
-    , xPubFP
-    , xPrvFP
-    , xPubAddr
-    , xPubWitnessAddr
-    , xPubCompatWitnessAddr
-    , xPubExport
-    , xPubToJSON
-    , xPubFromJSON
-    , xPrvExport
-    , xPrvToJSON
-    , xPrvFromJSON
-    , xPubImport
-    , xPrvImport
-    , xPrvWif
-    , putXPrvKey
-    , putXPubKey
-    , getXPrvKey
-    , getXPubKey
-
-      -- ** Helper Functions
-    , prvSubKeys
-    , pubSubKeys
-    , hardSubKeys
-    , deriveAddr
-    , deriveWitnessAddr
-    , deriveCompatWitnessAddr
-    , deriveAddrs
-    , deriveWitnessAddrs
-    , deriveCompatWitnessAddrs
-    , deriveMSAddr
-    , deriveMSAddrs
-    , cycleIndex
-
-      -- ** Derivation Paths
-    , DerivPathI(..)
-    , AnyDeriv, HardDeriv, SoftDeriv
-    , HardOrAny
-    , AnyOrSoft
-    , DerivPath
-    , HardPath
-    , SoftPath
-    , Bip32PathIndex (..)
-    , derivePath
-    , derivePubPath
-    , toHard
-    , toSoft
-    , toGeneric
-    , (++/)
-    , pathToStr
-    , listToPath
-    , pathToList
-
-      -- *** Derivation Path Parser
-    , XKey(..)
-    , ParsedPath(..)
-    , parsePath
-    , parseHard
-    , parseSoft
-    , applyPath
-    , derivePathAddr
-    , derivePathAddrs
-    , derivePathMSAddr
-    , derivePathMSAddrs
-    , concatBip32Segments
-    ) where
-
-import           Control.Applicative
-import           Control.DeepSeq
-import           Control.Exception              (Exception, throw)
-import           Control.Monad                  (guard, mzero, unless, (<=<))
-import           Crypto.Secp256k1
-import           Data.Aeson                     as A (FromJSON, ToJSON,
-                                                      Value (String), parseJSON,
-                                                      toJSON, withText)
-import           Data.Aeson.Types               (Parser)
-import           Data.Bits                      (clearBit, setBit, testBit)
-import           Data.ByteString                (ByteString)
-import qualified Data.ByteString                as B
-import           Data.Either                    (fromRight)
-import           Data.List                      (foldl')
-import           Data.List.Split                (splitOn)
-import           Data.Maybe                     (fromMaybe)
-import           Data.Monoid                    ((<>))
-import           Data.Serialize                 as S (Serialize, decode, encode,
-                                                      get, put)
-import           Data.Serialize.Get             (Get, getWord32be, getWord8,
-                                                 runGet)
-import           Data.Serialize.Put             (Putter, putWord32be, putWord8,
-                                                 runPut)
-import           Data.String                    (IsString, fromString)
-import           Data.String.Conversions        (cs)
-import           Data.Typeable                  (Typeable)
-import           Data.Word                      (Word32, Word8)
-import           GHC.Generics                   (Generic)
-import           Network.Haskoin.Address
-import           Network.Haskoin.Address.Base58
-import           Network.Haskoin.Constants
-import           Network.Haskoin.Crypto.Hash
-import           Network.Haskoin.Keys.Common
-import           Network.Haskoin.Script
-import           Network.Haskoin.Util
-import           Text.Read                      as R
-import           Text.Read.Lex
-
-
--- | A derivation exception is thrown in the very unlikely event that a
--- derivation is invalid.
-newtype DerivationException = DerivationException String
-    deriving (Eq, Read, Show, Typeable, Generic, NFData)
-
-instance Exception DerivationException
-
--- | Chain code as specified in BIP-32.
-type ChainCode = Hash256
-
--- | Index of key as specified in BIP-32.
-type KeyIndex = Word32
-
--- | Fingerprint of parent
-type Fingerprint = Word32
-
--- | Data type representing an extended BIP32 private key. An extended key
--- is a node in a tree of key derivations. It has a depth in the tree, a
--- parent node and an index to differentiate it from other siblings.
-data XPrvKey = XPrvKey
-    { xPrvDepth  :: !Word8       -- ^ depth in the tree
-    , xPrvParent :: !Fingerprint -- ^ fingerprint of parent
-    , xPrvIndex  :: !KeyIndex    -- ^ derivation index
-    , xPrvChain  :: !ChainCode   -- ^ chain code
-    , xPrvKey    :: !SecKey      -- ^ private key of this node
-    } deriving (Generic, Eq, Show, Read, NFData)
-
-xPrvToJSON :: Network -> XPrvKey -> Value
-xPrvToJSON net = A.String . xPrvExport net
-
--- | Data type representing an extended BIP32 public key.
-data XPubKey = XPubKey
-    { xPubDepth  :: !Word8     -- ^ depth in the tree
-    , xPubParent :: !Fingerprint    -- ^ fingerprint of parent
-    , xPubIndex  :: !KeyIndex  -- ^ derivation index
-    , xPubChain  :: !ChainCode -- ^ chain code
-    , xPubKey    :: !PubKey    -- ^ public key of this node
-    } deriving (Generic, Eq, Show, Read, NFData)
-
-
--- | Decode an extended public key from a JSON string
-xPubFromJSON :: Network -> Value -> Parser XPubKey
-xPubFromJSON net =
-    withText "xpub" $ \t ->
-        case xPubImport net t of
-            Nothing -> fail "could not read xpub"
-            Just x  -> return x
-
--- | Get JSON 'Value' from 'XPubKey'.
-xPubToJSON :: Network -> XPubKey -> Value
-xPubToJSON net = A.String . xPubExport net
-
--- | Decode an extended private key from a JSON string
-xPrvFromJSON :: Network -> Value -> Parser XPrvKey
-xPrvFromJSON net =
-    withText "xprv" $ \t ->
-        case xPrvImport net t of
-            Nothing -> fail "could not read xprv"
-            Just x  -> return x
-
--- | Build a BIP32 compatible extended private key from a bytestring. This will
--- produce a root node (@depth=0@ and @parent=0@).
-makeXPrvKey :: ByteString -> XPrvKey
-makeXPrvKey bs =
-    XPrvKey 0 0 0 c k
-  where
-    (p, c) = split512 $ hmac512 "Bitcoin seed" bs
-    k     = fromMaybe err (secKey (encode p))
-    err   = throw $ DerivationException "Invalid seed"
-
--- | Derive an extended public key from an extended private key. This function
--- will preserve the depth, parent, index and chaincode fields of the extended
--- private keys.
-deriveXPubKey :: XPrvKey -> XPubKey
-deriveXPubKey (XPrvKey d p i c k) = XPubKey d p i c (derivePubKey k)
-
--- | Compute a private, soft child key derivation. A private soft derivation
--- will allow the equivalent extended public key to derive the public key for
--- this child. Given a parent key /m/ and a derivation index /i/, this function
--- will compute /m\/i/.
---
--- Soft derivations allow for more flexibility such as read-only wallets.
--- However, care must be taken not the leak both the parent extended public key
--- and one of the extended child private keys as this would compromise the
--- extended parent private key.
-prvSubKey :: XPrvKey  -- ^ extended parent private key
-          -> KeyIndex -- ^ child derivation index
-          -> XPrvKey  -- ^ extended child private key
-prvSubKey xkey child
-    | child >= 0 && child < 0x80000000 =
-        XPrvKey (xPrvDepth xkey + 1) (xPrvFP xkey) child c k
-    | otherwise = error "Invalid child derivation index"
-  where
-    pK = xPubKey $ deriveXPubKey xkey
-    m = B.append (exportPubKey True pK) (encode child)
-    (a, c) = split512 $ hmac512 (encode $ xPrvChain xkey) m
-    k = fromMaybe err $ tweakSecKey (xPrvKey xkey) a
-    err = throw $ DerivationException "Invalid prvSubKey derivation"
-
--- | Compute a public, soft child key derivation. Given a parent key /M/
--- and a derivation index /i/, this function will compute /M\/i/.
-pubSubKey :: XPubKey  -- ^ extended parent public key
-          -> KeyIndex -- ^ child derivation index
-          -> XPubKey  -- ^ extended child public key
-pubSubKey xKey child
-    | child >= 0 && child < 0x80000000 =
-        XPubKey (xPubDepth xKey + 1) (xPubFP xKey) child c pK
-    | otherwise = error "Invalid child derivation index"
-  where
-    m      = B.append (exportPubKey True (xPubKey xKey)) (encode child)
-    (a, c) = split512 $ hmac512 (encode $ xPubChain xKey) m
-    pK     = fromMaybe err $ tweakPubKey (xPubKey xKey) a
-    err    = throw $ DerivationException "Invalid pubSubKey derivation"
-
--- | Compute a hard child key derivation. Hard derivations can only be computed
--- for private keys. Hard derivations do not allow the parent public key to
--- derive the child public keys. However, they are safer as a breach of the
--- parent public key and child private keys does not lead to a breach of the
--- parent private key. Given a parent key /m/ and a derivation index /i/, this
--- function will compute /m\/i'/.
-hardSubKey :: XPrvKey  -- ^ extended parent private key
-           -> KeyIndex -- ^ child derivation index
-           -> XPrvKey  -- ^ extended child private key
-hardSubKey xkey child
-    | child >= 0 && child < 0x80000000 =
-        XPrvKey (xPrvDepth xkey + 1) (xPrvFP xkey) i c k
-    | otherwise = error "Invalid child derivation index"
-  where
-    i      = setBit child 31
-    m      = B.append (bsPadPrvKey $ xPrvKey xkey) (encode i)
-    (a, c) = split512 $ hmac512 (encode $ xPrvChain xkey) m
-    k      = fromMaybe err $ tweakSecKey (xPrvKey xkey) a
-    err    = throw $ DerivationException "Invalid hardSubKey derivation"
-
--- | Returns true if the extended private key was derived through a hard
--- derivation.
-xPrvIsHard :: XPrvKey -> Bool
-xPrvIsHard k = testBit (xPrvIndex k) 31
-
--- | Returns true if the extended public key was derived through a hard
--- derivation.
-xPubIsHard :: XPubKey -> Bool
-xPubIsHard k = testBit (xPubIndex k) 31
-
--- | Returns the derivation index of this extended private key without the hard
--- bit set.
-xPrvChild :: XPrvKey -> KeyIndex
-xPrvChild k = clearBit (xPrvIndex k) 31
-
--- | Returns the derivation index of this extended public key without the hard
--- bit set.
-xPubChild :: XPubKey -> KeyIndex
-xPubChild k = clearBit (xPubIndex k) 31
-
--- | Computes the key identifier of an extended private key.
-xPrvID :: XPrvKey -> Hash160
-xPrvID = xPubID . deriveXPubKey
-
--- | Computes the key identifier of an extended public key.
-xPubID :: XPubKey -> Hash160
-xPubID = ripemd160 . encode . sha256 . exportPubKey True . xPubKey
-
--- | Computes the key fingerprint of an extended private key.
-xPrvFP :: XPrvKey -> Fingerprint
-xPrvFP =
-    fromRight err . decode . B.take 4 . encode . xPrvID
-  where
-    err = error "Could not decode xPrvFP"
-
--- | Computes the key fingerprint of an extended public key.
-xPubFP :: XPubKey -> Fingerprint
-xPubFP =
-    fromRight err . decode . B.take 4 . encode . xPubID
-  where
-    err = error "Could not decode xPubFP"
-
--- | Compute a standard P2PKH address for an extended public key.
-xPubAddr :: XPubKey -> Address
-xPubAddr xkey = pubKeyAddr (wrapPubKey True (xPubKey xkey))
-
--- | Compute a SegWit P2WPKH address for an extended public key.
-xPubWitnessAddr :: XPubKey -> Address
-xPubWitnessAddr xkey = pubKeyWitnessAddr (wrapPubKey True (xPubKey xkey))
-
--- | Compute a backwards-compatible SegWit P2SH-P2WPKH address for an extended
--- public key.
-xPubCompatWitnessAddr :: XPubKey -> Address
-xPubCompatWitnessAddr xkey =
-    pubKeyCompatWitnessAddr (wrapPubKey True (xPubKey xkey))
-
--- | Exports an extended private key to the BIP32 key export format ('Base58').
-xPrvExport :: Network -> XPrvKey -> Base58
-xPrvExport net = encodeBase58Check . runPut . putXPrvKey net
-
--- | Exports an extended public key to the BIP32 key export format ('Base58').
-xPubExport :: Network -> XPubKey -> Base58
-xPubExport net = encodeBase58Check . runPut . putXPubKey net
-
--- | Decodes a BIP32 encoded extended private key. This function will fail if
--- invalid base 58 characters are detected or if the checksum fails.
-xPrvImport :: Network -> Base58 -> Maybe XPrvKey
-xPrvImport net = eitherToMaybe . runGet (getXPrvKey net) <=< decodeBase58Check
-
--- | Decodes a BIP32 encoded extended public key. This function will fail if
--- invalid base 58 characters are detected or if the checksum fails.
-xPubImport :: Network -> Base58 -> Maybe XPubKey
-xPubImport net = eitherToMaybe . runGet (getXPubKey net) <=< decodeBase58Check
-
--- | Export an extended private key to WIF (Wallet Import Format).
-xPrvWif :: Network -> XPrvKey -> Base58
-xPrvWif net xkey = toWif net (wrapSecKey True (xPrvKey xkey))
-
--- | Parse a binary extended private key.
-getXPrvKey :: Network -> Get XPrvKey
-getXPrvKey net = do
-        ver <- getWord32be
-        unless (ver == getExtSecretPrefix net) $ fail
-            "Get: Invalid version for extended private key"
-        XPrvKey <$> getWord8
-                <*> getWord32be
-                <*> getWord32be
-                <*> S.get
-                <*> getPadPrvKey
-
--- | Serialize an extended private key.
-putXPrvKey :: Network -> Putter XPrvKey
-putXPrvKey net k = do
-        putWord32be  $ getExtSecretPrefix net
-        putWord8     $ xPrvDepth k
-        putWord32be  $ xPrvParent k
-        putWord32be  $ xPrvIndex k
-        put          $ xPrvChain k
-        putPadPrvKey $ xPrvKey k
-
--- | Parse a binary extended public key.
-getXPubKey :: Network -> Get XPubKey
-getXPubKey net = do
-        ver <- getWord32be
-        unless (ver == getExtPubKeyPrefix net) $ fail
-            "Get: Invalid version for extended public key"
-        XPubKey <$> getWord8
-                <*> getWord32be
-                <*> getWord32be
-                <*> S.get
-                <*> (pubKeyPoint <$> S.get)
-
--- | Serialize an extended public key.
-putXPubKey :: Network -> Putter XPubKey
-putXPubKey net k = do
-        putWord32be $ getExtPubKeyPrefix net
-        putWord8    $ xPubDepth k
-        putWord32be $ xPubParent k
-        putWord32be $ xPubIndex k
-        put         $ xPubChain k
-        put         $ wrapPubKey True (xPubKey k)
-
-{- Derivation helpers -}
-
--- | Cyclic list of all private soft child key derivations of a parent key
--- starting from an offset index.
-prvSubKeys :: XPrvKey -> KeyIndex -> [(XPrvKey, KeyIndex)]
-prvSubKeys k = map (\i -> (prvSubKey k i, i)) . cycleIndex
-
--- | Cyclic list of all public soft child key derivations of a parent key
--- starting from an offset index.
-pubSubKeys :: XPubKey -> KeyIndex -> [(XPubKey, KeyIndex)]
-pubSubKeys k = map (\i -> (pubSubKey k i, i)) . cycleIndex
-
--- | Cyclic list of all hard child key derivations of a parent key starting
--- from an offset index.
-hardSubKeys :: XPrvKey -> KeyIndex -> [(XPrvKey, KeyIndex)]
-hardSubKeys k = map (\i -> (hardSubKey k i, i)) . cycleIndex
-
--- | Derive a standard address from an extended public key and an index.
-deriveAddr :: XPubKey -> KeyIndex -> (Address, PubKey)
-deriveAddr k i =
-    (xPubAddr key, xPubKey key)
-  where
-    key = pubSubKey k i
-
--- | Derive a SegWit P2WPKH address from an extended public key and an index.
-deriveWitnessAddr :: XPubKey -> KeyIndex -> (Address, PubKey)
-deriveWitnessAddr k i =
-    (xPubWitnessAddr key, xPubKey key)
-  where
-    key = pubSubKey k i
-
--- | Derive a backwards-compatible SegWit P2SH-P2WPKH address from an extended
--- public key and an index.
-deriveCompatWitnessAddr :: XPubKey -> KeyIndex -> (Address, PubKey)
-deriveCompatWitnessAddr k i =
-    (xPubCompatWitnessAddr key, xPubKey key)
-  where
-    key = pubSubKey k i
-
--- | Cyclic list of all addresses derived from a public key starting from an
--- offset index.
-deriveAddrs :: XPubKey -> KeyIndex -> [(Address, PubKey, KeyIndex)]
-deriveAddrs k =
-    map f . cycleIndex
-  where
-    f i = let (a, key) = deriveAddr k i in (a, key, i)
-
--- | Cyclic list of all SegWit P2WPKH addresses derived from a public key
--- starting from an offset index.
-deriveWitnessAddrs :: XPubKey -> KeyIndex -> [(Address, PubKey, KeyIndex)]
-deriveWitnessAddrs k =
-    map f . cycleIndex
-  where
-    f i = let (a, key) = deriveWitnessAddr k i in (a, key, i)
-
--- | Cyclic list of all backwards-compatible SegWit P2SH-P2WPKH addresses
--- derived from a public key starting from an offset index.
-deriveCompatWitnessAddrs :: XPubKey -> KeyIndex -> [(Address, PubKey, KeyIndex)]
-deriveCompatWitnessAddrs k =
-    map f . cycleIndex
-  where
-    f i = let (a, key) = deriveCompatWitnessAddr k i in (a, key, i)
-
--- | Derive a multisig address from a list of public keys, the number of
--- required signatures /m/ and a derivation index. The derivation type is a
--- public, soft derivation.
-deriveMSAddr :: [XPubKey] -> Int -> KeyIndex -> (Address, RedeemScript)
-deriveMSAddr keys m i = (payToScriptAddress rdm, rdm)
-  where
-    rdm = sortMulSig $ PayMulSig k m
-    k = map (wrapPubKey True . xPubKey . flip pubSubKey i) keys
-
--- | Cyclic list of all multisig addresses derived from a list of public keys,
--- a number of required signatures /m/ and starting from an offset index. The
--- derivation type is a public, soft derivation.
-deriveMSAddrs :: [XPubKey] -> Int -> KeyIndex
-              -> [(Address, RedeemScript, KeyIndex)]
-deriveMSAddrs keys m = map f . cycleIndex
-  where
-    f i =
-        let (a, rdm) = deriveMSAddr keys m i
-         in (a, rdm, i)
-
--- | Helper function to go through derivation indices.
-cycleIndex :: KeyIndex -> [KeyIndex]
-cycleIndex i
-    | i == 0         = cycle [0..0x7fffffff]
-    | i < 0x80000000 = cycle $ [i..0x7fffffff] ++ [0..(i-1)]
-    | otherwise      = error $ "cycleIndex: invalid index " ++ show i
-
-{- Derivation Paths -}
-
--- | Phantom type signaling a hardened derivation path that can only be computed
--- from private extended key.
-data HardDeriv deriving (Generic, NFData)
-
--- | Phantom type signaling no knowledge about derivation path: can be hardened or not.
-data AnyDeriv deriving (Generic, NFData)
-
--- | Phantom type signaling derivation path including only non-hardened paths
--- that can be computed from an extended public key.
-data SoftDeriv deriving (Generic, NFData)
-
--- | Hardened derivation path. Can be computed from extended private key only.
-type HardPath = DerivPathI HardDeriv
-
--- | Any derivation path.
-type DerivPath = DerivPathI AnyDeriv
-
--- | Non-hardened derivation path can be computed from extended public key.
-type SoftPath = DerivPathI SoftDeriv
-
--- | Helper class to perform validations on a hardened derivation path.
-class HardOrAny a
-instance HardOrAny HardDeriv
-instance HardOrAny AnyDeriv
-
--- | Helper class to perform validations on a non-hardened derivation path.
-class AnyOrSoft a
-instance AnyOrSoft AnyDeriv
-instance AnyOrSoft SoftDeriv
-
--- | Data type representing a derivation path. Two constructors are provided
--- for specifying soft or hard derivations. The path /\/0\/1'\/2/ for example can be
--- expressed as @'Deriv' :\/ 0 :| 1 :\/ 2@. The 'HardOrAny' and 'AnyOrSoft' type
--- classes are used to constrain the valid values for the phantom type /t/. If
--- you mix hard '(:|)' and soft '(:\/)' paths, the only valid type for /t/ is 'AnyDeriv'.
--- Otherwise, /t/ can be 'HardDeriv' if you only have hard derivation or 'SoftDeriv'
--- if you only have soft derivations.
---
--- Using this type is as easy as writing the required derivation like in these
--- example:
---
--- > Deriv :/ 0 :/ 1 :/ 2 :: SoftPath
--- > Deriv :| 0 :| 1 :| 2 :: HardPath
--- > Deriv :| 0 :/ 1 :/ 2 :: DerivPath
-data DerivPathI t where
-    (:|)  :: HardOrAny t => !(DerivPathI t) -> !KeyIndex -> DerivPathI t
-    (:/)  :: AnyOrSoft t => !(DerivPathI t) -> !KeyIndex -> DerivPathI t
-    Deriv :: DerivPathI t
-
-instance NFData (DerivPathI t) where
-    rnf (a :| b) = rnf a `seq` rnf b `seq` ()
-    rnf (a :/ b) = rnf a `seq` rnf b `seq` ()
-    rnf Deriv = ()
-
-instance Eq (DerivPathI t) where
-    (nextA :| iA) == (nextB :| iB) = iA == iB && nextA == nextB
-    (nextA :/ iA) == (nextB :/ iB) = iA == iB && nextA == nextB
-    Deriv         == Deriv         = True
-    _             == _             = False
-
-instance Ord (DerivPathI t) where
-    -- Same hardness on each side
-    (nextA :| iA) `compare` (nextB :| iB) =
-        if nextA == nextB then iA `compare` iB else nextA `compare` nextB
-    (nextA :/ iA) `compare` (nextB :/ iB) =
-        if nextA == nextB then iA `compare` iB else nextA `compare` nextB
-
-    -- Different hardness: hard paths are LT soft paths
-    (nextA :/ iA) `compare` (nextB :| iB) =
-        if nextA == nextB then LT else nextA `compare` nextB
-    (nextA :| iA) `compare` (nextB :/ iB) =
-        if nextA == nextB then GT else nextA `compare` nextB
-
-    Deriv `compare` Deriv  = EQ
-    Deriv `compare` _      = LT
-    _     `compare` Deriv  = GT
-
-
-instance Serialize DerivPath where
-    get = listToPath <$> S.get
-    put = put . pathToList
-
--- | Get a list of derivation indices from a derivation path.
-pathToList :: DerivPathI t -> [KeyIndex]
-pathToList =
-    reverse . go
-  where
-    go (next :| i) = setBit i 31 : go next
-    go (next :/ i) = i : go next
-    go _           = []
-
--- | Convert a list of derivation indices to a derivation path.
-listToPath :: [KeyIndex] -> DerivPath
-listToPath =
-    go . reverse
-  where
-    go (i:is)
-        | testBit i 31 = go is :| clearBit i 31
-        | otherwise    = go is :/ i
-    go [] = Deriv
-
--- | Convert a derivation path to a human-readable string.
-pathToStr :: DerivPathI t -> String
-pathToStr p =
-    case p of
-        next :| i -> concat [ pathToStr next, "/", show i, "'" ]
-        next :/ i -> concat [ pathToStr next, "/", show i ]
-        Deriv     -> ""
-
--- | Turn a derivation path into a hard derivation path. Will fail if the path
--- contains soft derivations.
-toHard :: DerivPathI t -> Maybe HardPath
-toHard p = case p of
-    next :| i -> (:| i) <$> toHard next
-    Deriv     -> Just Deriv
-    _         -> Nothing
-
--- | Turn a derivation path into a soft derivation path. Will fail if the path
--- has hard derivations.
-toSoft :: DerivPathI t -> Maybe SoftPath
-toSoft p = case p of
-    next :/ i -> (:/ i) <$> toSoft next
-    Deriv     -> Just Deriv
-    _         -> Nothing
-
--- | Make a derivation path generic.
-toGeneric :: DerivPathI t -> DerivPath
-toGeneric p = case p of
-    next :/ i -> toGeneric next :/ i
-    next :| i -> toGeneric next :| i
-    Deriv     -> Deriv
-
--- | Append two derivation paths together. The result will be a mixed
--- derivation path.
-(++/) :: DerivPathI t1 -> DerivPathI t2 -> DerivPath
-(++/) p1 p2 =
-    go id (toGeneric p2) $ toGeneric p1
-  where
-    go f p = case p of
-        next :/ i -> go (f . (:/ i)) $ toGeneric next
-        next :| i -> go (f . (:| i)) $ toGeneric next
-        _         -> f
-
--- | Derive a private key from a derivation path
-derivePath :: DerivPathI t -> XPrvKey -> XPrvKey
-derivePath = go id
-  where
-    -- Build the full derivation function starting from the end
-    go f p = case p of
-        next :| i -> go (f . flip hardSubKey i) next
-        next :/ i -> go (f . flip prvSubKey i) next
-        _         -> f
-
--- | Derive a public key from a soft derivation path
-derivePubPath :: SoftPath -> XPubKey -> XPubKey
-derivePubPath = go id
-  where
-    -- Build the full derivation function starting from the end
-    go f p = case p of
-        next :/ i -> go (f . flip pubSubKey i) next
-        _         -> f
-
-instance Show DerivPath where
-    showsPrec d p = showParen (d > 10) $
-        showString "DerivPath " . shows (pathToStr p)
-
-instance Read DerivPath where
-    readPrec = parens $ do
-        R.Ident "DerivPath" <- lexP
-        R.String str <- lexP
-        maybe pfail return $ getParsedPath <$> parsePath str
-
-instance Show HardPath where
-    showsPrec d p = showParen (d > 10) $
-        showString "HardPath " . shows (pathToStr p)
-
-instance Read HardPath where
-    readPrec = parens $ do
-        R.Ident "HardPath" <- lexP
-        R.String str <- lexP
-        maybe pfail return $ parseHard str
-
-instance Show SoftPath where
-    showsPrec d p = showParen (d > 10) $
-        showString "SoftPath " . shows (pathToStr p)
-
-instance Read SoftPath where
-    readPrec = parens $ do
-        R.Ident "SoftPath" <- lexP
-        R.String str <- lexP
-        maybe pfail return $ parseSoft str
-
-instance IsString ParsedPath where
-    fromString =
-        fromMaybe e . parsePath
-      where
-        e = error "Could not parse derivation path"
-
-instance IsString DerivPath where
-    fromString =
-        getParsedPath . fromMaybe e . parsePath
-      where
-        e = error "Could not parse derivation path"
-
-instance IsString HardPath where
-    fromString =
-        fromMaybe e . parseHard
-      where
-        e = error "Could not parse hard derivation path"
-
-instance IsString SoftPath where
-    fromString =
-        fromMaybe e . parseSoft
-      where
-        e = error "Could not parse soft derivation path"
-
-instance FromJSON ParsedPath where
-    parseJSON = withText "ParsedPath" $ \str -> case parsePath $ cs str of
-        Just p -> return p
-        _      -> mzero
-
-instance FromJSON DerivPath where
-    parseJSON = withText "DerivPath" $ \str -> case parsePath $ cs str of
-        Just p -> return $ getParsedPath p
-        _      -> mzero
-
-instance FromJSON HardPath where
-    parseJSON = withText "HardPath" $ \str -> case parseHard $ cs str of
-        Just p -> return p
-        _      -> mzero
-
-instance FromJSON SoftPath where
-    parseJSON = withText "SoftPath" $ \str -> case parseSoft $ cs str of
-        Just p -> return p
-        _      -> mzero
-
-instance ToJSON (DerivPathI t) where
-    toJSON = A.String . cs . pathToStr
-
-instance ToJSON ParsedPath where
-    toJSON (ParsedPrv p)   = A.String . cs . ("m" ++) . pathToStr $ p
-    toJSON (ParsedPub p)   = A.String . cs . ("M" ++) . pathToStr $ p
-    toJSON (ParsedEmpty p) = A.String . cs . ("" ++) . pathToStr $ p
-
-{- Parsing derivation paths of the form m/1/2'/3 or M/1/2'/3 -}
-
--- | Type for parsing derivation paths of the form /m\/1\/2'\/3/ or
--- /M\/1\/2'\/3/.
-data ParsedPath = ParsedPrv   { getParsedPath :: !DerivPath }
-                | ParsedPub   { getParsedPath :: !DerivPath }
-                | ParsedEmpty { getParsedPath :: !DerivPath }
-  deriving (Eq, Generic, NFData)
-
-instance Show ParsedPath where
-    showsPrec d p = showParen (d > 10) $ showString "ParsedPath " . shows f
-      where
-        f =
-            case p of
-                ParsedPrv d'   -> "m" <> pathToStr d'
-                ParsedPub d'   -> "M" <> pathToStr d'
-                ParsedEmpty d' -> pathToStr d'
-
-instance Read ParsedPath where
-    readPrec = parens $ do
-        R.Ident "ParsedPath" <- lexP
-        R.String str <- lexP
-        maybe pfail return $ parsePath str
-
--- | Parse derivation path string for extended key.
--- Forms: /m\/0'\/2/, /M\/2\/3\/4/.
-parsePath :: String -> Maybe ParsedPath
-parsePath str = do
-    res <- concatBip32Segments <$> mapM parseBip32PathIndex xs
-    case x of
-        "m" -> Just $ ParsedPrv res
-        "M" -> Just $ ParsedPub res
-        ""  -> Just $ ParsedEmpty res
-        _   -> Nothing
-  where
-    (x : xs) = splitOn "/" str
-
--- | Concatenate derivation path indices into a derivation path.
-concatBip32Segments :: [Bip32PathIndex] -> DerivPath
-concatBip32Segments = foldl' appendBip32Segment Deriv
-
--- | Append an extra derivation path index element into an existing path.
-appendBip32Segment :: DerivPath -> Bip32PathIndex  -> DerivPath
-appendBip32Segment d (Bip32SoftIndex i) = d :/ i
-appendBip32Segment d (Bip32HardIndex i) = d :| i
-
--- | Parse a BIP32 derivation path index element from a string.
-parseBip32PathIndex :: String -> Maybe Bip32PathIndex
-parseBip32PathIndex segment = case reads segment of
-    [(i, "" )] -> guard (is31Bit i) >> return (Bip32SoftIndex i)
-    [(i, "'")] -> guard (is31Bit i) >> return (Bip32HardIndex i)
-    _          -> Nothing
-
--- | Type for BIP32 path index element.
-data Bip32PathIndex = Bip32HardIndex KeyIndex
-                    | Bip32SoftIndex KeyIndex
-  deriving (Eq, Generic, NFData)
-
-instance Show Bip32PathIndex where
-    showsPrec d (Bip32HardIndex i) = showParen (d > 10) $
-        showString "Bip32HardIndex " . shows i
-    showsPrec d (Bip32SoftIndex i) = showParen (d > 10) $
-        showString "Bip32SoftIndex " . shows i
-
-instance Read Bip32PathIndex where
-    readPrec = h <|> s
-      where
-        h =
-            parens $ do
-                R.Ident "Bip32HardIndex" <- lexP
-                R.Number n <- lexP
-                maybe pfail return $
-                    Bip32HardIndex . fromIntegral <$> numberToInteger n
-        s =
-            parens $ do
-                R.Ident "Bip32SoftIndex" <- lexP
-                R.Number n <- lexP
-                maybe pfail return $
-                    Bip32SoftIndex . fromIntegral <$> numberToInteger n
-
--- | Test whether the number could be a valid BIP32 derivation index.
-is31Bit :: (Integral a) => a -> Bool
-is31Bit i = i >= 0 && i < 0x80000000
-
--- | Helper function to parse a hard path.
-parseHard :: String -> Maybe HardPath
-parseHard = toHard . getParsedPath <=< parsePath
-
--- | Helper function to parse a soft path.
-parseSoft :: String -> Maybe SoftPath
-parseSoft = toSoft . getParsedPath <=< parsePath
-
--- | Data type representing a private or public key with its respective network.
-data XKey
-    = XPrv { getXKeyPrv :: !XPrvKey
-           , getXKeyNet :: !Network }
-    | XPub { getXKeyPub :: !XPubKey
-           , getXKeyNet :: !Network }
-    deriving (Eq, Show, Generic, NFData)
-
--- | Apply a parsed path to an extended key to derive the new key defined in the
--- path. If the path starts with /m/, a private key will be returned and if the
--- path starts with /M/, a public key will be returned. Private derivations on a
--- public key, and public derivations with a hard segment, return an error
--- value.
-applyPath :: ParsedPath -> XKey -> Either String XKey
-applyPath path key =
-    case (path, key) of
-        (ParsedPrv _, XPrv k n) -> return $ XPrv (derivPrvF k) n
-        (ParsedPrv _, XPub {}) -> Left "applyPath: Invalid public key"
-        (ParsedPub _, XPrv k n) -> return $ XPub (deriveXPubKey (derivPrvF k)) n
-        (ParsedPub _, XPub k n) -> derivPubFE >>= \f -> return $ XPub (f k) n
-    -- For empty parsed paths, we take a hint from the provided key
-        (ParsedEmpty _, XPrv k n) -> return $ XPrv (derivPrvF k) n
-        (ParsedEmpty _, XPub k n) -> derivPubFE >>= \f -> return $ XPub (f k) n
-  where
-    derivPrvF = goPrv id $ getParsedPath path
-    derivPubFE = goPubE id $ getParsedPath path
-    -- Build the full private derivation function starting from the end
-    goPrv f p =
-        case p of
-            next :| i -> goPrv (f . flip hardSubKey i) next
-            next :/ i -> goPrv (f . flip prvSubKey i) next
-            Deriv     -> f
-    -- Build the full public derivation function starting from the end
-    goPubE f p =
-        case p of
-            next :/ i -> goPubE (f . flip pubSubKey i) next
-            Deriv     -> Right f
-            _         -> Left "applyPath: Invalid hard derivation"
-
-{- Helpers for derivation paths and addresses -}
-
--- | Derive an address from a given parent path.
-derivePathAddr :: XPubKey -> SoftPath -> KeyIndex -> (Address, PubKey)
-derivePathAddr key path = deriveAddr (derivePubPath path key)
-
--- | Cyclic list of all addresses derived from a given parent path and starting
--- from the given offset index.
-derivePathAddrs ::
-       XPubKey -> SoftPath -> KeyIndex -> [(Address, PubKey, KeyIndex)]
-derivePathAddrs key path = deriveAddrs (derivePubPath path key)
-
--- | Derive a multisig address from a given parent path. The number of required
--- signatures (m in m of n) is also needed.
-derivePathMSAddr ::
-       [XPubKey]
-    -> SoftPath
-    -> Int
-    -> KeyIndex
-    -> (Address, RedeemScript)
-derivePathMSAddr keys path =
-    deriveMSAddr $ map (derivePubPath path) keys
-
--- | Cyclic list of all multisig addresses derived from a given parent path and
--- starting from the given offset index. The number of required signatures
--- (m in m of n) is also needed.
-derivePathMSAddrs ::
-       [XPubKey]
-    -> SoftPath
-    -> Int
-    -> KeyIndex
-    -> [(Address, RedeemScript, KeyIndex)]
-derivePathMSAddrs keys path =
-    deriveMSAddrs $ map (derivePubPath path) keys
-
-{- Utilities for extended keys -}
-
--- | De-serialize HDW-specific private key.
-getPadPrvKey :: Get SecKey
-getPadPrvKey = do
-    pad <- getWord8
-    unless (pad == 0x00) $ fail "Private key must be padded with 0x00"
-    secKeyGet
-
--- | Serialize HDW-specific private key.
-putPadPrvKey :: Putter SecKey
-putPadPrvKey p = putWord8 0x00 >> secKeyPut p
-
-bsPadPrvKey :: SecKey -> ByteString
-bsPadPrvKey = runPut . putPadPrvKey
diff --git a/src/Network/Haskoin/Keys/Mnemonic.hs b/src/Network/Haskoin/Keys/Mnemonic.hs
deleted file mode 100644
--- a/src/Network/Haskoin/Keys/Mnemonic.hs
+++ /dev/null
@@ -1,516 +0,0 @@
-{-# LANGUAGE OverloadedStrings #-}
-{-|
-Module      : Network.Haskoin.Keys.Mnemonic
-Copyright   : No rights reserved
-License     : UNLICENSE
-Maintainer  : jprupp@protonmail.ch
-Stability   : experimental
-Portability : POSIX
-
-Mnemonic keys (BIP-39). Only English dictionary.
--}
-module Network.Haskoin.Keys.Mnemonic
-    ( -- * Mnemonic Sentences
-      Entropy
-    , Mnemonic
-    , Passphrase
-    , Seed
-    , toMnemonic
-    , fromMnemonic
-    , mnemonicToSeed
-    ) where
-
-import           Control.Monad           (when)
-import           Crypto.Hash             (SHA256 (..), hashWith)
-import           Crypto.KDF.PBKDF2       (Parameters (..), fastPBKDF2_SHA512)
-import           Data.Bits               (shiftL, shiftR)
-import qualified Data.ByteArray          as BA
-import           Data.ByteString         (ByteString)
-import qualified Data.ByteString         as BS
-import           Data.List
-import qualified Data.Map.Strict         as M
-import           Data.Maybe
-import           Data.String.Conversions (cs)
-import           Data.Text               (Text)
-import qualified Data.Text               as T
-import qualified Data.Text.Encoding      as E
-import           Data.Vector             (Vector, (!))
-import qualified Data.Vector             as V
-import           Network.Haskoin.Util
-
--- | Random data used to create a mnemonic sentence. Use a good entropy source.
--- You will get your coins stolen if you don't. You have been warned.
-type Entropy = ByteString
-
--- | Human-readable mnemonic sentence.
-type Mnemonic = Text
-
--- | Optional passphrase for mnemnoic sentence.
-type Passphrase = ByteString
-
--- | Seed for a private key from a mnemonic sentence.
-type Seed = ByteString
-
--- | Mnemonic key checksum.
-type Checksum = ByteString
-
--- | Paremeters for PBKDF2 function.
-pbkdfParams :: Parameters
-pbkdfParams = Parameters {iterCounts = 2048, outputLength = 64}
-
--- | Provide intial 'Entropy' as a 'ByteString' of length multiple of 4 bytes.
--- Output a 'Mnemonic' sentence.
-toMnemonic :: Entropy -> Either String Mnemonic
-toMnemonic ent = do
-    when (BS.null ent) $
-        Left "toMnemonic: entropy can not be empty"
-    when (remainder /= 0) $
-        Left "toMnemonic: entropy must be multiples of 4 bytes"
-    when (cs_len > 16) $
-        Left "toMnemonic: maximum entropy is 64 bytes (512 bits)"
-    return ms
-  where
-    (cs_len, remainder) = BS.length ent `quotRem` 4
-    c = calcCS cs_len ent
-    indices = bsToIndices $ ent `BS.append` c
-    ms = T.unwords $ map (wl!) indices
-
--- | Revert 'toMnemonic'. Do not use this to generate a 'Seed'. Instead use
--- 'mnemonicToSeed'. This outputs the original 'Entropy' used to generate a
--- 'Mnemonic' sentence.
-fromMnemonic :: Mnemonic -> Either String Entropy
-fromMnemonic ms = do
-    when (T.null ms) $
-        Left "fromMnemonic: empty mnemonic"
-    when (word_count > 48) $
-        Left $ "fromMnemonic: too many words: " ++ show word_count
-    when (word_count `mod` 3 /= 0) $
-        Left $ "fromMnemonic: wrong number of words:" ++ show word_count
-    ms_bs <- indicesToBS =<< getIndices ms_words
-    let (ms_ent, ms_cs) = BS.splitAt (ent_len * 4) ms_bs
-        ms_cs_num = numCS cs_len ms_cs
-        ent_cs_num = numCS cs_len $ calcCS cs_len ms_ent
-    when (ent_cs_num /= ms_cs_num) $
-        Left $ "fromMnemonic: checksum failed: " ++ sh ent_cs_num ms_cs_num
-    return ms_ent
-  where
-    ms_words = T.words ms
-    word_count = length ms_words
-    (ent_len, cs_len) = (word_count * 11) `quotRem` 32
-    sh cs_a cs_b = show cs_a ++ " /= " ++ show cs_b
-
--- | Compute 'Checksum'.
-calcCS :: Int -> Entropy -> Checksum
-calcCS len = getBits len . BA.convert . hashWith SHA256
-
-numCS :: Int -> Entropy -> Integer
-numCS len =
-    shiftCS . bsToInteger
-  where
-    shiftCS = case 8 - len `mod` 8 of
-        8 -> id
-        x -> flip shiftR x
-
--- | Turn an arbitrary sequence of characters into a 512-bit 'Seed'. Use
--- 'mnemonicToSeed' to get a seed from a 'Mnemonic' sentence. Warning: Does not
--- perform NFKD normalization.
-anyToSeed :: Passphrase -> Mnemonic -> Seed
-anyToSeed pf ms =
-    fastPBKDF2_SHA512 pbkdfParams (E.encodeUtf8 ms) ("mnemonic" `mappend` pf)
-
--- | Get a 512-bit 'Seed' from a 'Mnemonic' sentence. Will validate checksum.
--- 'Passphrase' can be used to protect the 'Mnemonic'. Use an empty string as
--- 'Passphrase' if none is required.
-mnemonicToSeed :: Passphrase -> Mnemonic -> Either String Seed
-mnemonicToSeed pf ms = do
-    ent <- fromMnemonic ms
-    mnm <- toMnemonic ent
-    return $ anyToSeed pf mnm
-
--- | Get indices of words in word list.
-getIndices :: [Text] -> Either String [Int]
-getIndices ws
-    | null n = return $ catMaybes i
-    | otherwise = Left $ "getIndices: words not found: " ++ cs w
-  where
-    i = map (`M.lookup` wl') ws
-    n = elemIndices Nothing i
-    w = T.unwords $ map (ws !!) n
-
--- | Turn a list of 11-bit numbers into a 'ByteString'
-indicesToBS :: [Int] -> Either String ByteString
-indicesToBS is = do
-    when lrg $ Left "indicesToBS: index larger or equal than 2048"
-    return . pad . integerToBS $ foldl' f 0 is `shiftL` shift_width
-  where
-    lrg = isJust $ find (>= 2048) is
-    (q, r) = (length is * 11) `quotRem` 8
-    shift_width =
-        if r == 0
-            then 0
-            else 8 - r
-    bl =
-        if r == 0
-            then q
-            else q + 1 -- length of resulting ByteString
-    pad bs = BS.append (BS.replicate (bl - BS.length bs) 0x00) bs
-    f acc x = (acc `shiftL` 11) + fromIntegral x
-
--- | Turn a 'ByteString' into a list of 11-bit numbers.
-bsToIndices :: ByteString -> [Int]
-bsToIndices bs =
-    reverse . go q $ bsToInteger bs `shiftR` r
-  where
-    (q, r) = (BS.length bs * 8) `quotRem` 11
-    go 0 _ = []
-    go n i = fromIntegral (i `mod` 2048) : go (n - 1) (i `shiftR` 11)
-
-wl' :: M.Map Text Int
-wl' = V.ifoldr' (\i w m -> M.insert w i m) M.empty wl
-
--- | Standard English dictionary from BIP-39 specification.
-wl :: Vector Text
-wl = V.fromListN 2048
-    [ "abandon", "ability", "able", "about", "above", "absent"
-    , "absorb", "abstract", "absurd", "abuse", "access", "accident"
-    , "account", "accuse", "achieve", "acid", "acoustic", "acquire"
-    , "across", "act", "action", "actor", "actress", "actual"
-    , "adapt", "add", "addict", "address", "adjust", "admit"
-    , "adult", "advance", "advice", "aerobic", "affair", "afford"
-    , "afraid", "again", "age", "agent", "agree", "ahead"
-    , "aim", "air", "airport", "aisle", "alarm", "album"
-    , "alcohol", "alert", "alien", "all", "alley", "allow"
-    , "almost", "alone", "alpha", "already", "also", "alter"
-    , "always", "amateur", "amazing", "among", "amount", "amused"
-    , "analyst", "anchor", "ancient", "anger", "angle", "angry"
-    , "animal", "ankle", "announce", "annual", "another", "answer"
-    , "antenna", "antique", "anxiety", "any", "apart", "apology"
-    , "appear", "apple", "approve", "april", "arch", "arctic"
-    , "area", "arena", "argue", "arm", "armed", "armor"
-    , "army", "around", "arrange", "arrest", "arrive", "arrow"
-    , "art", "artefact", "artist", "artwork", "ask", "aspect"
-    , "assault", "asset", "assist", "assume", "asthma", "athlete"
-    , "atom", "attack", "attend", "attitude", "attract", "auction"
-    , "audit", "august", "aunt", "author", "auto", "autumn"
-    , "average", "avocado", "avoid", "awake", "aware", "away"
-    , "awesome", "awful", "awkward", "axis", "baby", "bachelor"
-    , "bacon", "badge", "bag", "balance", "balcony", "ball"
-    , "bamboo", "banana", "banner", "bar", "barely", "bargain"
-    , "barrel", "base", "basic", "basket", "battle", "beach"
-    , "bean", "beauty", "because", "become", "beef", "before"
-    , "begin", "behave", "behind", "believe", "below", "belt"
-    , "bench", "benefit", "best", "betray", "better", "between"
-    , "beyond", "bicycle", "bid", "bike", "bind", "biology"
-    , "bird", "birth", "bitter", "black", "blade", "blame"
-    , "blanket", "blast", "bleak", "bless", "blind", "blood"
-    , "blossom", "blouse", "blue", "blur", "blush", "board"
-    , "boat", "body", "boil", "bomb", "bone", "bonus"
-    , "book", "boost", "border", "boring", "borrow", "boss"
-    , "bottom", "bounce", "box", "boy", "bracket", "brain"
-    , "brand", "brass", "brave", "bread", "breeze", "brick"
-    , "bridge", "brief", "bright", "bring", "brisk", "broccoli"
-    , "broken", "bronze", "broom", "brother", "brown", "brush"
-    , "bubble", "buddy", "budget", "buffalo", "build", "bulb"
-    , "bulk", "bullet", "bundle", "bunker", "burden", "burger"
-    , "burst", "bus", "business", "busy", "butter", "buyer"
-    , "buzz", "cabbage", "cabin", "cable", "cactus", "cage"
-    , "cake", "call", "calm", "camera", "camp", "can"
-    , "canal", "cancel", "candy", "cannon", "canoe", "canvas"
-    , "canyon", "capable", "capital", "captain", "car", "carbon"
-    , "card", "cargo", "carpet", "carry", "cart", "case"
-    , "cash", "casino", "castle", "casual", "cat", "catalog"
-    , "catch", "category", "cattle", "caught", "cause", "caution"
-    , "cave", "ceiling", "celery", "cement", "census", "century"
-    , "cereal", "certain", "chair", "chalk", "champion", "change"
-    , "chaos", "chapter", "charge", "chase", "chat", "cheap"
-    , "check", "cheese", "chef", "cherry", "chest", "chicken"
-    , "chief", "child", "chimney", "choice", "choose", "chronic"
-    , "chuckle", "chunk", "churn", "cigar", "cinnamon", "circle"
-    , "citizen", "city", "civil", "claim", "clap", "clarify"
-    , "claw", "clay", "clean", "clerk", "clever", "click"
-    , "client", "cliff", "climb", "clinic", "clip", "clock"
-    , "clog", "close", "cloth", "cloud", "clown", "club"
-    , "clump", "cluster", "clutch", "coach", "coast", "coconut"
-    , "code", "coffee", "coil", "coin", "collect", "color"
-    , "column", "combine", "come", "comfort", "comic", "common"
-    , "company", "concert", "conduct", "confirm", "congress", "connect"
-    , "consider", "control", "convince", "cook", "cool", "copper"
-    , "copy", "coral", "core", "corn", "correct", "cost"
-    , "cotton", "couch", "country", "couple", "course", "cousin"
-    , "cover", "coyote", "crack", "cradle", "craft", "cram"
-    , "crane", "crash", "crater", "crawl", "crazy", "cream"
-    , "credit", "creek", "crew", "cricket", "crime", "crisp"
-    , "critic", "crop", "cross", "crouch", "crowd", "crucial"
-    , "cruel", "cruise", "crumble", "crunch", "crush", "cry"
-    , "crystal", "cube", "culture", "cup", "cupboard", "curious"
-    , "current", "curtain", "curve", "cushion", "custom", "cute"
-    , "cycle", "dad", "damage", "damp", "dance", "danger"
-    , "daring", "dash", "daughter", "dawn", "day", "deal"
-    , "debate", "debris", "decade", "december", "decide", "decline"
-    , "decorate", "decrease", "deer", "defense", "define", "defy"
-    , "degree", "delay", "deliver", "demand", "demise", "denial"
-    , "dentist", "deny", "depart", "depend", "deposit", "depth"
-    , "deputy", "derive", "describe", "desert", "design", "desk"
-    , "despair", "destroy", "detail", "detect", "develop", "device"
-    , "devote", "diagram", "dial", "diamond", "diary", "dice"
-    , "diesel", "diet", "differ", "digital", "dignity", "dilemma"
-    , "dinner", "dinosaur", "direct", "dirt", "disagree", "discover"
-    , "disease", "dish", "dismiss", "disorder", "display", "distance"
-    , "divert", "divide", "divorce", "dizzy", "doctor", "document"
-    , "dog", "doll", "dolphin", "domain", "donate", "donkey"
-    , "donor", "door", "dose", "double", "dove", "draft"
-    , "dragon", "drama", "drastic", "draw", "dream", "dress"
-    , "drift", "drill", "drink", "drip", "drive", "drop"
-    , "drum", "dry", "duck", "dumb", "dune", "during"
-    , "dust", "dutch", "duty", "dwarf", "dynamic", "eager"
-    , "eagle", "early", "earn", "earth", "easily", "east"
-    , "easy", "echo", "ecology", "economy", "edge", "edit"
-    , "educate", "effort", "egg", "eight", "either", "elbow"
-    , "elder", "electric", "elegant", "element", "elephant", "elevator"
-    , "elite", "else", "embark", "embody", "embrace", "emerge"
-    , "emotion", "employ", "empower", "empty", "enable", "enact"
-    , "end", "endless", "endorse", "enemy", "energy", "enforce"
-    , "engage", "engine", "enhance", "enjoy", "enlist", "enough"
-    , "enrich", "enroll", "ensure", "enter", "entire", "entry"
-    , "envelope", "episode", "equal", "equip", "era", "erase"
-    , "erode", "erosion", "error", "erupt", "escape", "essay"
-    , "essence", "estate", "eternal", "ethics", "evidence", "evil"
-    , "evoke", "evolve", "exact", "example", "excess", "exchange"
-    , "excite", "exclude", "excuse", "execute", "exercise", "exhaust"
-    , "exhibit", "exile", "exist", "exit", "exotic", "expand"
-    , "expect", "expire", "explain", "expose", "express", "extend"
-    , "extra", "eye", "eyebrow", "fabric", "face", "faculty"
-    , "fade", "faint", "faith", "fall", "false", "fame"
-    , "family", "famous", "fan", "fancy", "fantasy", "farm"
-    , "fashion", "fat", "fatal", "father", "fatigue", "fault"
-    , "favorite", "feature", "february", "federal", "fee", "feed"
-    , "feel", "female", "fence", "festival", "fetch", "fever"
-    , "few", "fiber", "fiction", "field", "figure", "file"
-    , "film", "filter", "final", "find", "fine", "finger"
-    , "finish", "fire", "firm", "first", "fiscal", "fish"
-    , "fit", "fitness", "fix", "flag", "flame", "flash"
-    , "flat", "flavor", "flee", "flight", "flip", "float"
-    , "flock", "floor", "flower", "fluid", "flush", "fly"
-    , "foam", "focus", "fog", "foil", "fold", "follow"
-    , "food", "foot", "force", "forest", "forget", "fork"
-    , "fortune", "forum", "forward", "fossil", "foster", "found"
-    , "fox", "fragile", "frame", "frequent", "fresh", "friend"
-    , "fringe", "frog", "front", "frost", "frown", "frozen"
-    , "fruit", "fuel", "fun", "funny", "furnace", "fury"
-    , "future", "gadget", "gain", "galaxy", "gallery", "game"
-    , "gap", "garage", "garbage", "garden", "garlic", "garment"
-    , "gas", "gasp", "gate", "gather", "gauge", "gaze"
-    , "general", "genius", "genre", "gentle", "genuine", "gesture"
-    , "ghost", "giant", "gift", "giggle", "ginger", "giraffe"
-    , "girl", "give", "glad", "glance", "glare", "glass"
-    , "glide", "glimpse", "globe", "gloom", "glory", "glove"
-    , "glow", "glue", "goat", "goddess", "gold", "good"
-    , "goose", "gorilla", "gospel", "gossip", "govern", "gown"
-    , "grab", "grace", "grain", "grant", "grape", "grass"
-    , "gravity", "great", "green", "grid", "grief", "grit"
-    , "grocery", "group", "grow", "grunt", "guard", "guess"
-    , "guide", "guilt", "guitar", "gun", "gym", "habit"
-    , "hair", "half", "hammer", "hamster", "hand", "happy"
-    , "harbor", "hard", "harsh", "harvest", "hat", "have"
-    , "hawk", "hazard", "head", "health", "heart", "heavy"
-    , "hedgehog", "height", "hello", "helmet", "help", "hen"
-    , "hero", "hidden", "high", "hill", "hint", "hip"
-    , "hire", "history", "hobby", "hockey", "hold", "hole"
-    , "holiday", "hollow", "home", "honey", "hood", "hope"
-    , "horn", "horror", "horse", "hospital", "host", "hotel"
-    , "hour", "hover", "hub", "huge", "human", "humble"
-    , "humor", "hundred", "hungry", "hunt", "hurdle", "hurry"
-    , "hurt", "husband", "hybrid", "ice", "icon", "idea"
-    , "identify", "idle", "ignore", "ill", "illegal", "illness"
-    , "image", "imitate", "immense", "immune", "impact", "impose"
-    , "improve", "impulse", "inch", "include", "income", "increase"
-    , "index", "indicate", "indoor", "industry", "infant", "inflict"
-    , "inform", "inhale", "inherit", "initial", "inject", "injury"
-    , "inmate", "inner", "innocent", "input", "inquiry", "insane"
-    , "insect", "inside", "inspire", "install", "intact", "interest"
-    , "into", "invest", "invite", "involve", "iron", "island"
-    , "isolate", "issue", "item", "ivory", "jacket", "jaguar"
-    , "jar", "jazz", "jealous", "jeans", "jelly", "jewel"
-    , "job", "join", "joke", "journey", "joy", "judge"
-    , "juice", "jump", "jungle", "junior", "junk", "just"
-    , "kangaroo", "keen", "keep", "ketchup", "key", "kick"
-    , "kid", "kidney", "kind", "kingdom", "kiss", "kit"
-    , "kitchen", "kite", "kitten", "kiwi", "knee", "knife"
-    , "knock", "know", "lab", "label", "labor", "ladder"
-    , "lady", "lake", "lamp", "language", "laptop", "large"
-    , "later", "latin", "laugh", "laundry", "lava", "law"
-    , "lawn", "lawsuit", "layer", "lazy", "leader", "leaf"
-    , "learn", "leave", "lecture", "left", "leg", "legal"
-    , "legend", "leisure", "lemon", "lend", "length", "lens"
-    , "leopard", "lesson", "letter", "level", "liar", "liberty"
-    , "library", "license", "life", "lift", "light", "like"
-    , "limb", "limit", "link", "lion", "liquid", "list"
-    , "little", "live", "lizard", "load", "loan", "lobster"
-    , "local", "lock", "logic", "lonely", "long", "loop"
-    , "lottery", "loud", "lounge", "love", "loyal", "lucky"
-    , "luggage", "lumber", "lunar", "lunch", "luxury", "lyrics"
-    , "machine", "mad", "magic", "magnet", "maid", "mail"
-    , "main", "major", "make", "mammal", "man", "manage"
-    , "mandate", "mango", "mansion", "manual", "maple", "marble"
-    , "march", "margin", "marine", "market", "marriage", "mask"
-    , "mass", "master", "match", "material", "math", "matrix"
-    , "matter", "maximum", "maze", "meadow", "mean", "measure"
-    , "meat", "mechanic", "medal", "media", "melody", "melt"
-    , "member", "memory", "mention", "menu", "mercy", "merge"
-    , "merit", "merry", "mesh", "message", "metal", "method"
-    , "middle", "midnight", "milk", "million", "mimic", "mind"
-    , "minimum", "minor", "minute", "miracle", "mirror", "misery"
-    , "miss", "mistake", "mix", "mixed", "mixture", "mobile"
-    , "model", "modify", "mom", "moment", "monitor", "monkey"
-    , "monster", "month", "moon", "moral", "more", "morning"
-    , "mosquito", "mother", "motion", "motor", "mountain", "mouse"
-    , "move", "movie", "much", "muffin", "mule", "multiply"
-    , "muscle", "museum", "mushroom", "music", "must", "mutual"
-    , "myself", "mystery", "myth", "naive", "name", "napkin"
-    , "narrow", "nasty", "nation", "nature", "near", "neck"
-    , "need", "negative", "neglect", "neither", "nephew", "nerve"
-    , "nest", "net", "network", "neutral", "never", "news"
-    , "next", "nice", "night", "noble", "noise", "nominee"
-    , "noodle", "normal", "north", "nose", "notable", "note"
-    , "nothing", "notice", "novel", "now", "nuclear", "number"
-    , "nurse", "nut", "oak", "obey", "object", "oblige"
-    , "obscure", "observe", "obtain", "obvious", "occur", "ocean"
-    , "october", "odor", "off", "offer", "office", "often"
-    , "oil", "okay", "old", "olive", "olympic", "omit"
-    , "once", "one", "onion", "online", "only", "open"
-    , "opera", "opinion", "oppose", "option", "orange", "orbit"
-    , "orchard", "order", "ordinary", "organ", "orient", "original"
-    , "orphan", "ostrich", "other", "outdoor", "outer", "output"
-    , "outside", "oval", "oven", "over", "own", "owner"
-    , "oxygen", "oyster", "ozone", "pact", "paddle", "page"
-    , "pair", "palace", "palm", "panda", "panel", "panic"
-    , "panther", "paper", "parade", "parent", "park", "parrot"
-    , "party", "pass", "patch", "path", "patient", "patrol"
-    , "pattern", "pause", "pave", "payment", "peace", "peanut"
-    , "pear", "peasant", "pelican", "pen", "penalty", "pencil"
-    , "people", "pepper", "perfect", "permit", "person", "pet"
-    , "phone", "photo", "phrase", "physical", "piano", "picnic"
-    , "picture", "piece", "pig", "pigeon", "pill", "pilot"
-    , "pink", "pioneer", "pipe", "pistol", "pitch", "pizza"
-    , "place", "planet", "plastic", "plate", "play", "please"
-    , "pledge", "pluck", "plug", "plunge", "poem", "poet"
-    , "point", "polar", "pole", "police", "pond", "pony"
-    , "pool", "popular", "portion", "position", "possible", "post"
-    , "potato", "pottery", "poverty", "powder", "power", "practice"
-    , "praise", "predict", "prefer", "prepare", "present", "pretty"
-    , "prevent", "price", "pride", "primary", "print", "priority"
-    , "prison", "private", "prize", "problem", "process", "produce"
-    , "profit", "program", "project", "promote", "proof", "property"
-    , "prosper", "protect", "proud", "provide", "public", "pudding"
-    , "pull", "pulp", "pulse", "pumpkin", "punch", "pupil"
-    , "puppy", "purchase", "purity", "purpose", "purse", "push"
-    , "put", "puzzle", "pyramid", "quality", "quantum", "quarter"
-    , "question", "quick", "quit", "quiz", "quote", "rabbit"
-    , "raccoon", "race", "rack", "radar", "radio", "rail"
-    , "rain", "raise", "rally", "ramp", "ranch", "random"
-    , "range", "rapid", "rare", "rate", "rather", "raven"
-    , "raw", "razor", "ready", "real", "reason", "rebel"
-    , "rebuild", "recall", "receive", "recipe", "record", "recycle"
-    , "reduce", "reflect", "reform", "refuse", "region", "regret"
-    , "regular", "reject", "relax", "release", "relief", "rely"
-    , "remain", "remember", "remind", "remove", "render", "renew"
-    , "rent", "reopen", "repair", "repeat", "replace", "report"
-    , "require", "rescue", "resemble", "resist", "resource", "response"
-    , "result", "retire", "retreat", "return", "reunion", "reveal"
-    , "review", "reward", "rhythm", "rib", "ribbon", "rice"
-    , "rich", "ride", "ridge", "rifle", "right", "rigid"
-    , "ring", "riot", "ripple", "risk", "ritual", "rival"
-    , "river", "road", "roast", "robot", "robust", "rocket"
-    , "romance", "roof", "rookie", "room", "rose", "rotate"
-    , "rough", "round", "route", "royal", "rubber", "rude"
-    , "rug", "rule", "run", "runway", "rural", "sad"
-    , "saddle", "sadness", "safe", "sail", "salad", "salmon"
-    , "salon", "salt", "salute", "same", "sample", "sand"
-    , "satisfy", "satoshi", "sauce", "sausage", "save", "say"
-    , "scale", "scan", "scare", "scatter", "scene", "scheme"
-    , "school", "science", "scissors", "scorpion", "scout", "scrap"
-    , "screen", "script", "scrub", "sea", "search", "season"
-    , "seat", "second", "secret", "section", "security", "seed"
-    , "seek", "segment", "select", "sell", "seminar", "senior"
-    , "sense", "sentence", "series", "service", "session", "settle"
-    , "setup", "seven", "shadow", "shaft", "shallow", "share"
-    , "shed", "shell", "sheriff", "shield", "shift", "shine"
-    , "ship", "shiver", "shock", "shoe", "shoot", "shop"
-    , "short", "shoulder", "shove", "shrimp", "shrug", "shuffle"
-    , "shy", "sibling", "sick", "side", "siege", "sight"
-    , "sign", "silent", "silk", "silly", "silver", "similar"
-    , "simple", "since", "sing", "siren", "sister", "situate"
-    , "six", "size", "skate", "sketch", "ski", "skill"
-    , "skin", "skirt", "skull", "slab", "slam", "sleep"
-    , "slender", "slice", "slide", "slight", "slim", "slogan"
-    , "slot", "slow", "slush", "small", "smart", "smile"
-    , "smoke", "smooth", "snack", "snake", "snap", "sniff"
-    , "snow", "soap", "soccer", "social", "sock", "soda"
-    , "soft", "solar", "soldier", "solid", "solution", "solve"
-    , "someone", "song", "soon", "sorry", "sort", "soul"
-    , "sound", "soup", "source", "south", "space", "spare"
-    , "spatial", "spawn", "speak", "special", "speed", "spell"
-    , "spend", "sphere", "spice", "spider", "spike", "spin"
-    , "spirit", "split", "spoil", "sponsor", "spoon", "sport"
-    , "spot", "spray", "spread", "spring", "spy", "square"
-    , "squeeze", "squirrel", "stable", "stadium", "staff", "stage"
-    , "stairs", "stamp", "stand", "start", "state", "stay"
-    , "steak", "steel", "stem", "step", "stereo", "stick"
-    , "still", "sting", "stock", "stomach", "stone", "stool"
-    , "story", "stove", "strategy", "street", "strike", "strong"
-    , "struggle", "student", "stuff", "stumble", "style", "subject"
-    , "submit", "subway", "success", "such", "sudden", "suffer"
-    , "sugar", "suggest", "suit", "summer", "sun", "sunny"
-    , "sunset", "super", "supply", "supreme", "sure", "surface"
-    , "surge", "surprise", "surround", "survey", "suspect", "sustain"
-    , "swallow", "swamp", "swap", "swarm", "swear", "sweet"
-    , "swift", "swim", "swing", "switch", "sword", "symbol"
-    , "symptom", "syrup", "system", "table", "tackle", "tag"
-    , "tail", "talent", "talk", "tank", "tape", "target"
-    , "task", "taste", "tattoo", "taxi", "teach", "team"
-    , "tell", "ten", "tenant", "tennis", "tent", "term"
-    , "test", "text", "thank", "that", "theme", "then"
-    , "theory", "there", "they", "thing", "this", "thought"
-    , "three", "thrive", "throw", "thumb", "thunder", "ticket"
-    , "tide", "tiger", "tilt", "timber", "time", "tiny"
-    , "tip", "tired", "tissue", "title", "toast", "tobacco"
-    , "today", "toddler", "toe", "together", "toilet", "token"
-    , "tomato", "tomorrow", "tone", "tongue", "tonight", "tool"
-    , "tooth", "top", "topic", "topple", "torch", "tornado"
-    , "tortoise", "toss", "total", "tourist", "toward", "tower"
-    , "town", "toy", "track", "trade", "traffic", "tragic"
-    , "train", "transfer", "trap", "trash", "travel", "tray"
-    , "treat", "tree", "trend", "trial", "tribe", "trick"
-    , "trigger", "trim", "trip", "trophy", "trouble", "truck"
-    , "true", "truly", "trumpet", "trust", "truth", "try"
-    , "tube", "tuition", "tumble", "tuna", "tunnel", "turkey"
-    , "turn", "turtle", "twelve", "twenty", "twice", "twin"
-    , "twist", "two", "type", "typical", "ugly", "umbrella"
-    , "unable", "unaware", "uncle", "uncover", "under", "undo"
-    , "unfair", "unfold", "unhappy", "uniform", "unique", "unit"
-    , "universe", "unknown", "unlock", "until", "unusual", "unveil"
-    , "update", "upgrade", "uphold", "upon", "upper", "upset"
-    , "urban", "urge", "usage", "use", "used", "useful"
-    , "useless", "usual", "utility", "vacant", "vacuum", "vague"
-    , "valid", "valley", "valve", "van", "vanish", "vapor"
-    , "various", "vast", "vault", "vehicle", "velvet", "vendor"
-    , "venture", "venue", "verb", "verify", "version", "very"
-    , "vessel", "veteran", "viable", "vibrant", "vicious", "victory"
-    , "video", "view", "village", "vintage", "violin", "virtual"
-    , "virus", "visa", "visit", "visual", "vital", "vivid"
-    , "vocal", "voice", "void", "volcano", "volume", "vote"
-    , "voyage", "wage", "wagon", "wait", "walk", "wall"
-    , "walnut", "want", "warfare", "warm", "warrior", "wash"
-    , "wasp", "waste", "water", "wave", "way", "wealth"
-    , "weapon", "wear", "weasel", "weather", "web", "wedding"
-    , "weekend", "weird", "welcome", "west", "wet", "whale"
-    , "what", "wheat", "wheel", "when", "where", "whip"
-    , "whisper", "wide", "width", "wife", "wild", "will"
-    , "win", "window", "wine", "wing", "wink", "winner"
-    , "winter", "wire", "wisdom", "wise", "wish", "witness"
-    , "wolf", "woman", "wonder", "wood", "wool", "word"
-    , "work", "world", "worry", "worth", "wrap", "wreck"
-    , "wrestle", "wrist", "write", "wrong", "yard", "year"
-    , "yellow", "you", "young", "youth", "zebra", "zero"
-    , "zone", "zoo"
-    ]
diff --git a/src/Network/Haskoin/Network.hs b/src/Network/Haskoin/Network.hs
deleted file mode 100644
--- a/src/Network/Haskoin/Network.hs
+++ /dev/null
@@ -1,21 +0,0 @@
-{-|
-Module      : Network.Haskoin.Network
-Copyright   : No rights reserved
-License     : UNLICENSE
-Maintainer  : jprupp@protonmail.ch
-Stability   : experimental
-Portability : POSIX
-
-This module provides basic types used for the Bitcoin networking protocol
-together with 'Data.Serialize' instances for efficiently serializing and
-de-serializing them.
--}
-module Network.Haskoin.Network
-    ( module Common
-    , module Message
-    , module Bloom
-    ) where
-
-import           Network.Haskoin.Network.Bloom   as Bloom
-import           Network.Haskoin.Network.Common  as Common
-import           Network.Haskoin.Network.Message as Message
diff --git a/src/Network/Haskoin/Network/Bloom.hs b/src/Network/Haskoin/Network/Bloom.hs
deleted file mode 100644
--- a/src/Network/Haskoin/Network/Bloom.hs
+++ /dev/null
@@ -1,263 +0,0 @@
-{-# LANGUAGE DeriveAnyClass #-}
-{-# LANGUAGE DeriveGeneric  #-}
-{-|
-Module      : Network.Haskoin.Network.Bloom
-Copyright   : No rights reserved
-License     : UNLICENSE
-Maintainer  : jprupp@protonmail.ch
-Stability   : experimental
-Portability : POSIX
-
-Bloom filters are used to reduce data transfer when synchronizing thin cients.
-When bloom filters are used a client will obtain filtered blocks that only
-contain transactions that pass the bloom filter. Transactions announced via inv
-messages also pass the filter.
--}
-module Network.Haskoin.Network.Bloom
-    ( -- * Bloom Filters
-      BloomFlags(..)
-    , BloomFilter(..)
-    , FilterLoad(..)
-    , FilterAdd(..)
-    , bloomCreate
-    , bloomInsert
-    , bloomContains
-    , isBloomValid
-    , isBloomEmpty
-    , isBloomFull
-    , acceptsFilters
-    , bloomRelevantUpdate
-    ) where
-
-import           Control.DeepSeq
-import           Control.Monad                      (forM_, replicateM)
-import           Data.Bits
-import           Data.ByteString                    (ByteString)
-import qualified Data.ByteString                    as BS
-import qualified Data.Foldable                      as F
-import           Data.Hash.Murmur                   (murmur3)
-import           Data.List                          (foldl')
-import qualified Data.Sequence                      as S
-import           Data.Serialize                     (Serialize, encode, get,
-                                                     put)
-import           Data.Serialize.Get                 (getByteString, getWord32le,
-                                                     getWord8)
-import           Data.Serialize.Put                 (putByteString, putWord32le,
-                                                     putWord8)
-import           Data.Word
-import           GHC.Generics                       (Generic)
-import           Network.Haskoin.Network.Common
-import           Network.Haskoin.Script.Common
-import           Network.Haskoin.Transaction.Common
--- | 20,000 items with fp rate < 0.1% or 10,000 items and <0.0001%
-maxBloomSize :: Int
-maxBloomSize = 36000
-
-maxHashFuncs :: Word32
-maxHashFuncs = 50
-
-ln2Squared :: Double
-ln2Squared = 0.4804530139182014246671025263266649717305529515945455
-
-ln2 :: Double
-ln2 = 0.6931471805599453094172321214581765680755001343602552
-
-bitMask :: [Word8]
-bitMask = [0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80]
-
--- | The bloom flags are used to tell the remote peer how to auto-update
--- the provided bloom filter.
-data BloomFlags
-    = BloomUpdateNone -- ^ never update
-    | BloomUpdateAll -- ^ auto-update on all outputs
-    | BloomUpdateP2PubKeyOnly
-    -- ^ auto-update on pay-to-pubkey or pay-to-multisig (default)
-    deriving (Eq, Show, Read, Generic, NFData)
-
-instance Serialize BloomFlags where
-    get = go =<< getWord8
-      where
-        go 0 = return BloomUpdateNone
-        go 1 = return BloomUpdateAll
-        go 2 = return BloomUpdateP2PubKeyOnly
-        go _ = fail "BloomFlags get: Invalid bloom flag"
-
-    put f = putWord8 $ case f of
-        BloomUpdateNone         -> 0
-        BloomUpdateAll          -> 1
-        BloomUpdateP2PubKeyOnly -> 2
-
--- | A bloom filter is a probabilistic data structure that SPV clients send to
--- other peers to filter the set of transactions received from them. Bloom
--- filters can have false positives but not false negatives. Some transactions
--- that pass the filter may not be relevant to the receiving peer. By
--- controlling the false positive rate, SPV nodes can trade off bandwidth
--- versus privacy.
-data BloomFilter = BloomFilter
-    { bloomData      :: !(S.Seq Word8)
-    -- ^ bloom filter data
-    , bloomHashFuncs :: !Word32
-    -- ^ number of hash functions for this filter
-    , bloomTweak     :: !Word32
-    -- ^ hash function random nonce
-    , bloomFlags     :: !BloomFlags
-    -- ^ bloom filter auto-update flags
-    }
-    deriving (Eq, Show, Read, Generic, NFData)
-
-instance Serialize BloomFilter where
-
-    get = BloomFilter <$> (S.fromList <$> (readDat =<< get))
-                      <*> getWord32le <*> getWord32le
-                      <*> get
-      where
-        readDat (VarInt len) = replicateM (fromIntegral len) getWord8
-
-    put (BloomFilter dat hashFuncs tweak flags) = do
-        putVarInt $ S.length dat
-        forM_ (F.toList dat) putWord8
-        putWord32le hashFuncs
-        putWord32le tweak
-        put flags
-
--- | Set a new bloom filter on the peer connection.
-newtype FilterLoad = FilterLoad { filterLoadBloomFilter :: BloomFilter }
-    deriving (Eq, Show, Read, Generic, NFData)
-
-instance Serialize FilterLoad where
-    get = FilterLoad <$> get
-    put (FilterLoad f) = put f
-
--- | Add the given data element to the connections current filter without
--- requiring a completely new one to be set.
-newtype FilterAdd = FilterAdd { getFilterData :: ByteString }
-    deriving (Eq, Show, Read, Generic, NFData)
-
-instance Serialize FilterAdd where
-    get = do
-        (VarInt len) <- get
-        dat <- getByteString $ fromIntegral len
-        return $ FilterAdd dat
-
-    put (FilterAdd bs) = do
-        putVarInt $ BS.length bs
-        putByteString bs
-
-
--- | Build a bloom filter that will provide the given false positive rate when
--- the given number of elements have been inserted.
-bloomCreate :: Int          -- ^ number of elements
-            -> Double       -- ^ false positive rate
-            -> Word32       -- ^ random nonce (tweak) for the hash function
-            -> BloomFlags   -- ^ bloom filter flags
-            -> BloomFilter  -- ^ bloom filter
-bloomCreate numElem fpRate =
-    BloomFilter (S.replicate bloomSize 0) numHashF
-  where
-    -- Bloom filter size in bytes
-    bloomSize = truncate $ min a b / 8
-    -- Suggested size in bits
-    a         = -1 / ln2Squared * fromIntegral numElem * log fpRate
-    -- Maximum size in bits
-    b         = fromIntegral $ maxBloomSize * 8
-    numHashF  = truncate $ min c (fromIntegral maxHashFuncs)
-    -- Suggested number of hash functions
-    c         = fromIntegral bloomSize * 8 / fromIntegral numElem * ln2
-
-bloomHash :: BloomFilter -> Word32 -> ByteString -> Word32
-bloomHash bfilter hashNum bs =
-    murmur3 seed bs `mod` (fromIntegral (S.length (bloomData bfilter)) * 8)
-  where
-    seed = hashNum * 0xfba4c795 + bloomTweak bfilter
-
--- | Insert arbitrary data into a bloom filter. Returns the new bloom filter
--- containing the new data.
-bloomInsert :: BloomFilter    -- ^ Original bloom filter
-            -> ByteString     -- ^ New data to insert
-            -> BloomFilter    -- ^ Bloom filter containing the new data
-bloomInsert bfilter bs
-    | isBloomFull bfilter = bfilter
-    | otherwise = bfilter { bloomData = newData }
-  where
-    idxs    = map (\i -> bloomHash bfilter i bs) [0..bloomHashFuncs bfilter - 1]
-    upd s i = S.adjust (.|. bitMask !! fromIntegral (7 .&. i))
-                       (fromIntegral $ i `shiftR` 3) s
-    newData = foldl upd (bloomData bfilter) idxs
-
--- | Tests if some arbitrary data matches the filter. This can be either because
--- the data was inserted into the filter or because it is a false positive.
-bloomContains :: BloomFilter    -- ^ Bloom filter
-              -> ByteString
-              -- ^ Data that will be checked against the given bloom filter
-              -> Bool
-              -- ^ Returns True if the data matches the filter
-bloomContains bfilter bs
-    | isBloomFull bfilter  = True
-    | isBloomEmpty bfilter = False
-    | otherwise            = all isSet idxs
-  where
-    s       = bloomData bfilter
-    idxs    = map (\i -> bloomHash bfilter i bs) [0..bloomHashFuncs bfilter - 1]
-    isSet i = S.index s (fromIntegral $ i `shiftR` 3)
-          .&. (bitMask !! fromIntegral (7 .&. i)) /= 0
-
--- | Checks if any of the outputs of a tx is in the current bloom filter.
--- If it is, add the txid and vout as an outpoint (i.e. so that
--- a future tx that spends the output won't be missed).
-bloomRelevantUpdate :: BloomFilter
-                    -- ^ Bloom filter
-                    -> Tx
-                    -- ^ Tx that may (or may not) have relevant outputs
-                    -> Maybe BloomFilter
-                    -- ^ Returns an updated bloom filter adding relevant output
-bloomRelevantUpdate bfilter tx
-    | isBloomFull bfilter || isBloomEmpty bfilter = Nothing
-    | bloomFlags bfilter == BloomUpdateNone = Nothing
-    | length matchOuts > 0 = Just $ foldl' (addRelevant) bfilter matchOuts
-    | otherwise = Nothing
-    where
-        -- TxHash if we end up inserting an outpoint
-        h = txHash tx
-        -- Decode the scriptOutpus and add vOuts in case we make them outpoints
-        decodedOutputScripts = traverse (decodeOutputBS . scriptOutput) $ txOut tx
-        err = error $ "Error Decoding output script"
-        idxOutputScripts = either (const err) (zip [0..]) decodedOutputScripts
-        -- Check if any txOuts were contained in the bloom filter
-        matchFilter = filter (\(_,op) -> bloomContains bfilter $ encodeScriptOut op)
-        matchOuts = matchFilter idxOutputScripts
-
-        addRelevant :: BloomFilter -> (Word32,ScriptOutput) -> BloomFilter
-        addRelevant bfilter (id,scriptOut) = case (bloomFlags bfilter,scriptType) of
-            -- We filtered out BloomUpdateNone so we insert any PayPk or PayMulSig
-            (_, True) -> bloomInsert bfilter outpoint
-            (BloomUpdateAll, _ ) -> bloomInsert bfilter outpoint
-            _ ->  error "Error Updating Bloom Filter with relevant outpoint"
-            where
-                outpoint = encode $ OutPoint{ outPointHash = h, outPointIndex = id}
-                scriptType = (\s -> isPayPK s ||isPayMulSig s) scriptOut
-
-        -- Encodes a scriptOutput so it can be checked agains the Bloom Filter
-        encodeScriptOut :: ScriptOutput -> ByteString
-        encodeScriptOut (PayMulSig outputMuSig _) = encode outputMuSig
-        encodeScriptOut (PayWitnessScriptHash scriptHash) = encode scriptHash
-        encodeScriptOut (DataCarrier getOutputData) = encode getOutputData
-        encodeScriptOut outputHash = (encode . getOutputHash) outputHash
-
--- | Returns True if the filter is empty (all bytes set to 0x00)
-isBloomEmpty :: BloomFilter -> Bool
-isBloomEmpty bfilter = all (== 0x00) $ F.toList $ bloomData bfilter
-
--- | Returns True if the filter is full (all bytes set to 0xff)
-isBloomFull :: BloomFilter -> Bool
-isBloomFull bfilter = all (== 0xff) $ F.toList $ bloomData bfilter
-
--- | Tests if a given bloom filter is valid.
-isBloomValid :: BloomFilter -- ^ Bloom filter to test
-             -> Bool        -- ^ True if the given filter is valid
-isBloomValid bfilter =
-    S.length (bloomData bfilter) <= maxBloomSize &&
-    bloomHashFuncs bfilter <= maxHashFuncs
-
--- | Does the peer with these version services accept bloom filters?
-acceptsFilters :: Word64 -> Bool
-acceptsFilters srv = srv .&. (1 `shiftL` 2) /= 0
diff --git a/src/Network/Haskoin/Network/Common.hs b/src/Network/Haskoin/Network/Common.hs
deleted file mode 100644
--- a/src/Network/Haskoin/Network/Common.hs
+++ /dev/null
@@ -1,613 +0,0 @@
-{-# LANGUAGE DeriveAnyClass    #-}
-{-# LANGUAGE DeriveGeneric     #-}
-{-# LANGUAGE LambdaCase        #-}
-{-# LANGUAGE OverloadedStrings #-}
-{-|
-Module      : Network.Haskoin.Network.Common
-Copyright   : No rights reserved
-License     : UNLICENSE
-Maintainer  : jprupp@protonmail.ch
-Stability   : experimental
-Portability : POSIX
-
-Common functions and data types related to peer-to-peer network.
--}
-module Network.Haskoin.Network.Common
-    ( -- * Network Data Types
-      Addr(..)
-    , NetworkAddressTime
-    , Alert(..)
-    , GetData(..)
-    , Inv(..)
-    , InvVector(..)
-    , InvType(..)
-    , HostAddress
-    , hostToSockAddr
-    , sockToHostAddress
-    , NetworkAddress(..)
-    , NotFound(..)
-    , Ping(..)
-    , Pong(..)
-    , Reject(..)
-    , RejectCode(..)
-    , VarInt(..)
-    , VarString(..)
-    , Version(..)
-    , MessageCommand(..)
-      -- ** Useful Functions
-    , reject
-    , nodeNone
-    , nodeNetwork
-    , nodeGetUTXO
-    , nodeBloom
-    , nodeWitness
-    , nodeXThin
-    , commandToString
-    , stringToCommand
-    , putVarInt
-    ) where
-
-import           Control.DeepSeq
-import           Control.Monad               (forM_, liftM2, replicateM, unless)
-import           Data.Bits                   (shiftL)
-import           Data.ByteString             (ByteString)
-import qualified Data.ByteString             as B
-import           Data.ByteString.Char8       as C (replicate)
-import           Data.Serialize              as S
-import           Data.String
-import           Data.String.Conversions     (cs)
-import           Data.Word                   (Word32, Word64)
-import           GHC.Generics                (Generic)
-import           Network.Haskoin.Crypto.Hash
-import           Network.Socket              (SockAddr (..))
-import           Text.Read                   as R
-
--- | Network address with a timestamp.
-type NetworkAddressTime = (Word32, NetworkAddress)
-
--- | Provides information about known nodes in the bitcoin network. An 'Addr'
--- type is sent inside a 'Message' as a response to a 'GetAddr' message.
-newtype Addr =
-    Addr { -- List of addresses of other nodes on the network with timestamps.
-           addrList :: [NetworkAddressTime]
-         }
-    deriving (Eq, Show, Generic, NFData)
-
-instance Serialize Addr where
-
-    get = Addr <$> (repList =<< S.get)
-      where
-        repList (VarInt c) = replicateM (fromIntegral c) action
-        action             = liftM2 (,) getWord32le S.get
-
-    put (Addr xs) = do
-        putVarInt $ length xs
-        forM_ xs $ \(a,b) -> putWord32le a >> put b
-
--- | Data type describing signed messages that can be sent between bitcoin
--- nodes to display important notifications to end users about the health of
--- the network.
-data Alert =
-    Alert {
-          -- | Alert payload.
-            alertPayload   :: !VarString
-          -- | ECDSA signature of the payload
-          , alertSignature :: !VarString
-          } deriving (Eq, Show, Read, Generic, NFData)
-
-instance Serialize Alert where
-    get = Alert <$> S.get <*> S.get
-    put (Alert p s) = put p >> put s
-
--- | The 'GetData' type is used to retrieve information on a specific object
--- ('Block' or 'Tx') identified by the objects hash. The payload of a 'GetData'
--- request is a list of 'InvVector' which represent all the hashes of objects
--- that a node wants. The response to a 'GetBlock' message will be either a
--- 'Block' or a 'Tx' message depending on the type of the object referenced by
--- the hash. Usually, 'GetData' messages are sent after a node receives an 'Inv'
--- message that contains unknown object hashes.
-newtype GetData =
-    GetData { -- | list of object hashes
-              getDataList :: [InvVector]
-            } deriving (Eq, Show, Generic, NFData)
-
-instance Serialize GetData where
-
-    get = GetData <$> (repList =<< S.get)
-      where
-        repList (VarInt c) = replicateM (fromIntegral c) S.get
-
-    put (GetData xs) = do
-        putVarInt $ length xs
-        forM_ xs put
-
--- | 'Inv' messages are used by nodes to advertise their knowledge of new
--- objects by publishing a list of hashes to a peer. 'Inv' messages can be sent
--- unsolicited or in response to a 'GetBlocks' message.
-newtype Inv =
-    Inv {
-        -- | inventory
-          invList :: [InvVector]
-        } deriving (Eq, Show, Generic, NFData)
-
-instance Serialize Inv where
-
-    get = Inv <$> (repList =<< S.get)
-      where
-        repList (VarInt c) = replicateM (fromIntegral c) S.get
-
-    put (Inv xs) = do
-        putVarInt $ length xs
-        forM_ xs put
-
--- | Data type identifying the type of an inventory vector. SegWit types are
--- only used in 'GetData' messages, not 'Inv'.
-data InvType
-    = InvError -- ^ error
-    | InvTx -- ^ transaction
-    | InvBlock -- ^ block
-    | InvMerkleBlock -- ^ filtered block
-    | InvWitnessTx -- ^ segwit transaction
-    | InvWitnessBlock -- ^ segwit block
-    | InvWitnessMerkleBlock -- ^ segwit filtere block
-    deriving (Eq, Show, Read, Generic, NFData)
-
-instance Serialize InvType where
-    get = go =<< getWord32le
-      where
-        go x =
-            case x of
-                0 -> return InvError
-                1 -> return InvTx
-                2 -> return InvBlock
-                3 -> return InvMerkleBlock
-                _
-                    | x == 1 `shiftL` 30 + 1 -> return InvWitnessTx
-                    | x == 1 `shiftL` 30 + 2 -> return InvWitnessBlock
-                    | x == 1 `shiftL` 30 + 3 -> return InvWitnessMerkleBlock
-                    | otherwise -> fail "bitcoinGet InvType: Invalid Type"
-    put x =
-        putWord32le $
-        case x of
-            InvError              -> 0
-            InvTx                 -> 1
-            InvBlock              -> 2
-            InvMerkleBlock        -> 3
-            InvWitnessTx          -> 1 `shiftL` 30 + 1
-            InvWitnessBlock       -> 1 `shiftL` 30 + 2
-            InvWitnessMerkleBlock -> 1 `shiftL` 30 + 3
-
--- | Invectory vectors represent hashes identifying objects such as a 'Block' or
--- a 'Tx'. They notify other peers about new data or data they have otherwise
--- requested.
-data InvVector =
-    InvVector {
-                -- | type of object
-                invType :: !InvType
-                -- | 256-bit hash of object
-              , invHash :: !Hash256
-              } deriving (Eq, Show, Generic, NFData)
-
-instance Serialize InvVector where
-    get = InvVector <$> S.get <*> S.get
-    put (InvVector t h) = put t >> put h
-
-newtype HostAddress =
-    HostAddress ByteString
-    deriving (Eq, Show, Ord, Generic, NFData)
-
-instance Serialize HostAddress where
-    put (HostAddress bs) = putByteString bs
-    get = HostAddress <$> getByteString 18
-
--- | Data type describing a bitcoin network address. Addresses are stored in
--- IPv6 format. IPv4 addresses are mapped to IPv6 using IPv4 mapped IPv6
--- addresses: <http://en.wikipedia.org/wiki/IPv6#IPv4-mapped_IPv6_addresses>.
-data NetworkAddress =
-    NetworkAddress { -- | bitmask of services available for this address
-                     naServices :: !Word64
-                     -- | address and port information
-                   , naAddress  :: !HostAddress
-                   } deriving (Eq, Show, Generic, NFData)
-
-hostToSockAddr :: HostAddress -> SockAddr
-hostToSockAddr (HostAddress bs) =
-    case runGet getSockAddr bs of
-        Left e  -> error e
-        Right x -> x
-
-sockToHostAddress :: SockAddr -> HostAddress
-sockToHostAddress = HostAddress . runPut . putSockAddr
-
-putSockAddr :: SockAddr -> Put
-putSockAddr (SockAddrInet6 p _ (a, b, c, d) _) = do
-    putWord32be a
-    putWord32be b
-    putWord32be c
-    putWord32be d
-    putWord16be (fromIntegral p)
-
-putSockAddr (SockAddrInet p a) = do
-    putWord32be 0x00000000
-    putWord32be 0x00000000
-    putWord32be 0x0000ffff
-    putWord32host a
-    putWord16be (fromIntegral p)
-
-putSockAddr _ = error "Invalid address type"
-
-getSockAddr :: Get SockAddr
-getSockAddr = do
-    a <- getWord32be
-    b <- getWord32be
-    c <- getWord32be
-    if a == 0x00000000 && b == 0x00000000 && c == 0x0000ffff
-        then do
-            d <- getWord32host
-            p <- getWord16be
-            return $ SockAddrInet (fromIntegral p) d
-        else do
-            d <- getWord32be
-            p <- getWord16be
-            return $ SockAddrInet6 (fromIntegral p) 0 (a, b, c, d) 0
-
-instance Serialize NetworkAddress where
-    get = NetworkAddress <$> getWord64le <*> S.get
-    put (NetworkAddress s a) = putWord64le s >> put a
-
--- | A 'NotFound' message is returned as a response to a 'GetData' message
--- whe one of the requested objects could not be retrieved. This could happen,
--- for example, if a tranasaction was requested and was not available in the
--- memory pool of the receiving node.
-newtype NotFound =
-    NotFound { -- | Inventory vectors related to this request
-               notFoundList :: [InvVector]
-             } deriving (Eq, Show, Generic, NFData)
-
-instance Serialize NotFound where
-
-    get = NotFound <$> (repList =<< S.get)
-      where
-        repList (VarInt c) = replicateM (fromIntegral c) S.get
-
-    put (NotFound xs) = do
-        putVarInt $ length xs
-        forM_ xs put
-
--- | A 'Ping' message is sent to bitcoin peers to check if a connection is still
--- open.
-newtype Ping =
-    Ping { -- | A random nonce used to identify the recipient of the ping
-           -- request once a Pong response is received.
-           pingNonce :: Word64
-         } deriving (Eq, Show, Read, Generic, NFData)
-
--- | A Pong message is sent as a response to a ping message.
-newtype Pong =
-    Pong {
-           -- | nonce from corresponding 'Ping'
-           pongNonce :: Word64
-         } deriving (Eq, Show, Read, Generic, NFData)
-
-instance Serialize Ping where
-    get = Ping <$> getWord64le
-    put (Ping n) = putWord64le n
-
-instance Serialize Pong where
-    get = Pong <$> getWord64le
-    put (Pong n) = putWord64le n
-
--- | The 'Reject' message is sent when messages are rejected by a peer.
-data Reject =
-    Reject {
-             -- | type of message rejected
-             rejectMessage :: !MessageCommand
-             -- | rejection code
-           , rejectCode    :: !RejectCode
-             -- | text reason for rejection
-           , rejectReason  :: !VarString
-             -- | extra data such as block or tx hash
-           , rejectData    :: !ByteString
-           } deriving (Eq, Show, Read, Generic, NFData)
-
--- | Rejection code associated to the 'Reject' message.
-data RejectCode
-    = RejectMalformed
-    | RejectInvalid
-    | RejectObsolete
-    | RejectDuplicate
-    | RejectNonStandard
-    | RejectDust
-    | RejectInsufficientFee
-    | RejectCheckpoint
-    deriving (Eq, Show, Read, Generic, NFData)
-
-instance Serialize RejectCode where
-
-    get = getWord8 >>= \code -> case code of
-        0x01 -> return RejectMalformed
-        0x10 -> return RejectInvalid
-        0x11 -> return RejectObsolete
-        0x12 -> return RejectDuplicate
-        0x40 -> return RejectNonStandard
-        0x41 -> return RejectDust
-        0x42 -> return RejectInsufficientFee
-        0x43 -> return RejectCheckpoint
-        _    -> fail $ unwords
-            [ "Reject get: Invalid code"
-            , show code
-            ]
-
-    put code = putWord8 $ case code of
-        RejectMalformed       -> 0x01
-        RejectInvalid         -> 0x10
-        RejectObsolete        -> 0x11
-        RejectDuplicate       -> 0x12
-        RejectNonStandard     -> 0x40
-        RejectDust            -> 0x41
-        RejectInsufficientFee -> 0x42
-        RejectCheckpoint      -> 0x43
-
--- | Convenience function to build a 'Reject' message.
-reject :: MessageCommand -> RejectCode -> ByteString -> Reject
-reject cmd code reason =
-    Reject cmd code (VarString reason) B.empty
-
-instance Serialize Reject where
-    get =
-        S.get >>= \(VarString bs) ->
-            Reject (stringToCommand bs) <$> S.get <*> S.get <*> maybeData
-      where
-        maybeData =
-            isEmpty >>= \done ->
-                if done
-                    then return B.empty
-                    else getByteString 32
-    put (Reject cmd code reason dat) = do
-        put $ VarString $ commandToString cmd
-        put code
-        put reason
-        unless (B.null dat) $ putByteString dat
-
--- | Data type representing a variable-length integer. The 'VarInt' type
--- usually precedes an array or a string that can vary in length.
-newtype VarInt = VarInt { getVarInt :: Word64 }
-    deriving (Eq, Show, Read, Generic, NFData)
-
-instance Serialize VarInt where
-
-    get = VarInt <$> ( getWord8 >>= go )
-      where
-        go 0xff = getWord64le
-        go 0xfe = fromIntegral <$> getWord32le
-        go 0xfd = fromIntegral <$> getWord16le
-        go x    = fromIntegral <$> return x
-
-    put (VarInt x)
-        | x < 0xfd =
-            putWord8 $ fromIntegral x
-        | x <= 0xffff = do
-            putWord8 0xfd
-            putWord16le $ fromIntegral x
-        | x <= 0xffffffff = do
-            putWord8 0xfe
-            putWord32le $ fromIntegral x
-        | otherwise = do
-            putWord8 0xff
-            putWord64le x
-
-putVarInt :: Integral a => a -> Put
-putVarInt = put . VarInt . fromIntegral
-
--- | Data type for serialization of variable-length strings.
-newtype VarString = VarString { getVarString :: ByteString }
-    deriving (Eq, Show, Read, Generic, NFData)
-
-instance Serialize VarString where
-
-    get = VarString <$> (readBS =<< S.get)
-      where
-        readBS (VarInt len) = getByteString (fromIntegral len)
-
-    put (VarString bs) = do
-        putVarInt $ B.length bs
-        putByteString bs
-
--- | When a bitcoin node creates an outgoing connection to another node,
--- the first message it will send is a 'Version' message. The other node
--- will similarly respond with it's own 'Version' message.
-data Version =
-    Version {
-              -- | protocol version
-              version     :: !Word32
-              -- | features supported by this connection
-            , services    :: !Word64
-              -- | unix timestamp
-            , timestamp   :: !Word64
-              -- | network address of remote node
-            , addrRecv    :: !NetworkAddress
-              -- | network address of sending node
-            , addrSend    :: !NetworkAddress
-              -- | random nonce to detect connection to self
-            , verNonce    :: !Word64
-              -- | user agent string
-            , userAgent   :: !VarString
-              -- | height of the last block in sending node
-            , startHeight :: !Word32
-              -- | relay transactions flag (BIP-37)
-            , relay       :: !Bool
-            } deriving (Eq, Show, Generic, NFData)
-
-instance Serialize Version where
-
-    get = Version <$> getWord32le
-                  <*> getWord64le
-                  <*> getWord64le
-                  <*> S.get
-                  <*> S.get
-                  <*> getWord64le
-                  <*> S.get
-                  <*> getWord32le
-                  <*> (go =<< isEmpty)
-      where
-        go True  = return True
-        go False = getBool
-
-    put (Version v s t ar as n ua sh r) = do
-        putWord32le v
-        putWord64le s
-        putWord64le t
-        put         ar
-        put         as
-        putWord64le n
-        put         ua
-        putWord32le sh
-        putBool     r
-
--- | 0x00 is 'False', anything else is 'True'.
-getBool :: Get Bool
-getBool = go =<< getWord8
-  where
-    go 0 = return False
-    go _ = return True
-
-putBool :: Bool -> Put
-putBool True  = putWord8 1
-putBool False = putWord8 0
-
--- | A 'MessageCommand' is included in a 'MessageHeader' in order to identify
--- the type of message present in the payload. This allows the message
--- de-serialization code to know how to decode a particular message payload.
--- Every valid 'Message' constructor has a corresponding 'MessageCommand'
--- constructor.
-data MessageCommand
-    = MCVersion
-    | MCVerAck
-    | MCAddr
-    | MCInv
-    | MCGetData
-    | MCNotFound
-    | MCGetBlocks
-    | MCGetHeaders
-    | MCTx
-    | MCBlock
-    | MCMerkleBlock
-    | MCHeaders
-    | MCGetAddr
-    | MCFilterLoad
-    | MCFilterAdd
-    | MCFilterClear
-    | MCPing
-    | MCPong
-    | MCAlert
-    | MCMempool
-    | MCReject
-    | MCSendHeaders
-    | MCOther ByteString
-    deriving (Eq, Generic, NFData)
-
-instance Show MessageCommand where
-    showsPrec _ = shows . commandToString
-
-instance Read MessageCommand where
-    readPrec = do
-        String str <- lexP
-        return (stringToCommand (cs str))
-
-instance Serialize MessageCommand where
-    get = go <$> getByteString 12
-      where
-        go bs =
-            let str = unpackCommand bs
-             in stringToCommand str
-    put mc = putByteString $ packCommand $ commandToString mc
-
-instance IsString MessageCommand where
-    fromString str = stringToCommand (cs str)
-
--- | Read a 'MessageCommand' from its string representation.
-stringToCommand :: ByteString -> MessageCommand
-stringToCommand str = case str of
-    "version"     -> MCVersion
-    "verack"      -> MCVerAck
-    "addr"        -> MCAddr
-    "inv"         -> MCInv
-    "getdata"     -> MCGetData
-    "notfound"    -> MCNotFound
-    "getblocks"   -> MCGetBlocks
-    "getheaders"  -> MCGetHeaders
-    "tx"          -> MCTx
-    "block"       -> MCBlock
-    "merkleblock" -> MCMerkleBlock
-    "headers"     -> MCHeaders
-    "getaddr"     -> MCGetAddr
-    "filterload"  -> MCFilterLoad
-    "filteradd"   -> MCFilterAdd
-    "filterclear" -> MCFilterClear
-    "ping"        -> MCPing
-    "pong"        -> MCPong
-    "alert"       -> MCAlert
-    "mempool"     -> MCMempool
-    "reject"      -> MCReject
-    "sendheaders" -> MCSendHeaders
-    _             -> MCOther str
-
--- | Convert a 'MessageCommand' to its string representation.
-commandToString :: MessageCommand -> ByteString
-commandToString mc = case mc of
-    MCVersion     -> "version"
-    MCVerAck      -> "verack"
-    MCAddr        -> "addr"
-    MCInv         -> "inv"
-    MCGetData     -> "getdata"
-    MCNotFound    -> "notfound"
-    MCGetBlocks   -> "getblocks"
-    MCGetHeaders  -> "getheaders"
-    MCTx          -> "tx"
-    MCBlock       -> "block"
-    MCMerkleBlock -> "merkleblock"
-    MCHeaders     -> "headers"
-    MCGetAddr     -> "getaddr"
-    MCFilterLoad  -> "filterload"
-    MCFilterAdd   -> "filteradd"
-    MCFilterClear -> "filterclear"
-    MCPing        -> "ping"
-    MCPong        -> "pong"
-    MCAlert       -> "alert"
-    MCMempool     -> "mempool"
-    MCReject      -> "reject"
-    MCSendHeaders -> "sendheaders"
-    MCOther c     -> c
-
--- | Pack a string 'MessageCommand' so that it is exactly 12-bytes long.
-packCommand :: ByteString -> ByteString
-packCommand s = B.take 12 $
-    s `mappend` C.replicate 12 '\NUL'
-
--- | Undo packing done by 'packCommand'.
-unpackCommand :: ByteString -> ByteString
-unpackCommand = B.takeWhile (/= 0)
-
--- | Node offers no services.
-nodeNone :: Word64
-nodeNone = 0
-
--- | Services indicate node is a full node that can serve full blocks.
-nodeNetwork :: Word64
-nodeNetwork = 1
-
--- | Services indicate node allows to request 'UTXO' set.
-nodeGetUTXO :: Word64
-nodeGetUTXO = 1 `shiftL` 1
-
--- | Services indicate node accepts bloom filters.
-nodeBloom :: Word64
-nodeBloom = 1 `shiftL` 2
-
--- | Services indicate SegWit-capable node.
-nodeWitness :: Word64
-nodeWitness = 1 `shiftL` 3
-
--- | Services indicate Xtreme Thinblocks compatibility.
-nodeXThin :: Word64
-nodeXThin = 1 `shiftL` 4
diff --git a/src/Network/Haskoin/Network/Message.hs b/src/Network/Haskoin/Network/Message.hs
deleted file mode 100644
--- a/src/Network/Haskoin/Network/Message.hs
+++ /dev/null
@@ -1,203 +0,0 @@
-{-# LANGUAGE DeriveAnyClass #-}
-{-# LANGUAGE DeriveGeneric  #-}
-{-|
-Module      : Network.Haskoin.Network.Message
-Copyright   : No rights reserved
-License     : UNLICENSE
-Maintainer  : jprupp@protonmail.ch
-Stability   : experimental
-Portability : POSIX
-
-Peer-to-peer network message serialization.
--}
-module Network.Haskoin.Network.Message
-    ( -- * Network Message
-      Message(..)
-    , MessageHeader(..)
-    , msgType
-    , putMessage
-    , getMessage
-    ) where
-
-import           Control.Monad                      (unless)
-import           Control.DeepSeq
-import           Data.ByteString                    (ByteString)
-import qualified Data.ByteString                    as BS
-import           Data.Serialize                     (Serialize, encode, get,
-                                                     put)
-import           Data.Serialize.Get                 (Get, getByteString,
-                                                     getWord32be, getWord32le,
-                                                     isolate, lookAhead)
-import           Data.Serialize.Put                 (Putter, putByteString,
-                                                     putWord32be, putWord32le)
-import           Data.Word                          (Word32)
-import           GHC.Generics                       (Generic)
-import           Network.Haskoin.Block.Common
-import           Network.Haskoin.Block.Merkle
-import           Network.Haskoin.Constants
-import           Network.Haskoin.Crypto.Hash
-import           Network.Haskoin.Network.Bloom
-import           Network.Haskoin.Network.Common
-import           Network.Haskoin.Transaction.Common
-
--- | Data type representing the header of a 'Message'. All messages sent between
--- nodes contain a message header.
-data MessageHeader = MessageHeader
-    { -- | magic bytes identify network
-      headMagic       :: !Word32
-      -- | message type
-    , headCmd         :: !MessageCommand
-      -- | length of payload
-    , headPayloadSize :: !Word32
-      -- | checksum of payload
-    , headChecksum    :: !CheckSum32
-    } deriving (Eq, Show, Generic, NFData)
-
-instance Serialize MessageHeader where
-
-    get = MessageHeader <$> getWord32be
-                        <*> get
-                        <*> getWord32le
-                        <*> get
-
-    put (MessageHeader m c l chk) = do
-        putWord32be m
-        put         c
-        putWord32le l
-        put         chk
-
--- | The 'Message' type is used to identify all the valid messages that can be
--- sent between bitcoin peers. Only values of type 'Message' will be accepted
--- by other bitcoin peers as bitcoin protocol messages need to be correctly
--- serialized with message headers. Serializing a 'Message' value will
--- include the 'MessageHeader' with the correct checksum value automatically.
--- No need to add the 'MessageHeader' separately.
-data Message
-    = MVersion !Version
-    | MVerAck
-    | MAddr !Addr
-    | MInv !Inv
-    | MGetData !GetData
-    | MNotFound !NotFound
-    | MGetBlocks !GetBlocks
-    | MGetHeaders !GetHeaders
-    | MTx !Tx
-    | MBlock !Block
-    | MMerkleBlock !MerkleBlock
-    | MHeaders !Headers
-    | MGetAddr
-    | MFilterLoad !FilterLoad
-    | MFilterAdd !FilterAdd
-    | MFilterClear
-    | MPing !Ping
-    | MPong !Pong
-    | MAlert !Alert
-    | MMempool
-    | MReject !Reject
-    | MSendHeaders
-    | MOther !ByteString !ByteString
-    deriving (Eq, Show, Generic, NFData)
-
--- | Get 'MessageCommand' assocated with a message.
-msgType :: Message -> MessageCommand
-msgType (MVersion _)     = MCVersion
-msgType MVerAck          = MCVerAck
-msgType (MAddr _)        = MCAddr
-msgType (MInv _)         = MCInv
-msgType (MGetData _)     = MCGetData
-msgType (MNotFound _)    = MCNotFound
-msgType (MGetBlocks _)   = MCGetBlocks
-msgType (MGetHeaders _)  = MCGetHeaders
-msgType (MTx _)          = MCTx
-msgType (MBlock _)       = MCBlock
-msgType (MMerkleBlock _) = MCMerkleBlock
-msgType (MHeaders _)     = MCHeaders
-
-msgType (MFilterLoad _)  = MCFilterLoad
-msgType (MFilterAdd _)   = MCFilterAdd
-msgType MFilterClear     = MCFilterClear
-msgType (MPing _)        = MCPing
-msgType (MPong _)        = MCPong
-msgType (MAlert _)       = MCAlert
-msgType MMempool         = MCMempool
-msgType (MReject _)      = MCReject
-msgType MSendHeaders     = MCSendHeaders
-msgType MGetAddr         = MCGetAddr
-msgType (MOther c _)     = MCOther c
-
--- | Deserializer for network messages.
-getMessage :: Network -> Get Message
-getMessage net = do
-    (MessageHeader mgc cmd len chk) <- get
-    bs <- lookAhead $ getByteString $ fromIntegral len
-    unless
-        (mgc == getNetworkMagic net)
-        (fail $ "get: Invalid network magic bytes: " ++ show mgc)
-    unless
-        (checkSum32 bs == chk)
-        (fail $ "get: Invalid message checksum: " ++ show chk)
-    if len > 0
-        then isolate (fromIntegral len) $
-             case cmd of
-                 MCVersion     -> MVersion <$> get
-                 MCAddr        -> MAddr <$> get
-                 MCInv         -> MInv <$> get
-                 MCGetData     -> MGetData <$> get
-                 MCNotFound    -> MNotFound <$> get
-                 MCGetBlocks   -> MGetBlocks <$> get
-                 MCGetHeaders  -> MGetHeaders <$> get
-                 MCTx          -> MTx <$> get
-                 MCBlock       -> MBlock <$> get
-                 MCMerkleBlock -> MMerkleBlock <$> get
-                 MCHeaders     -> MHeaders <$> get
-                 MCFilterLoad  -> MFilterLoad <$> get
-                 MCFilterAdd   -> MFilterAdd <$> get
-                 MCPing        -> MPing <$> get
-                 MCPong        -> MPong <$> get
-                 MCAlert       -> MAlert <$> get
-                 MCReject      -> MReject <$> get
-                 MCOther c     -> MOther c <$> getByteString (fromIntegral len)
-                 _             -> fail $ "get: command " ++ show cmd ++
-                                         " should not carry a payload"
-        else case cmd of
-                 MCGetAddr     -> return MGetAddr
-                 MCVerAck      -> return MVerAck
-                 MCFilterClear -> return MFilterClear
-                 MCMempool     -> return MMempool
-                 MCSendHeaders -> return MSendHeaders
-                 _             -> fail $ "get: command " ++ show cmd ++
-                                         " is expected to carry a payload"
-
--- | Serializer for network messages.
-putMessage :: Network -> Putter Message
-putMessage net msg = do
-    let (cmd, payload) =
-            case msg of
-                MVersion m     -> (MCVersion, encode m)
-                MVerAck        -> (MCVerAck, BS.empty)
-                MAddr m        -> (MCAddr, encode m)
-                MInv m         -> (MCInv, encode m)
-                MGetData m     -> (MCGetData, encode m)
-                MNotFound m    -> (MCNotFound, encode m)
-                MGetBlocks m   -> (MCGetBlocks, encode m)
-                MGetHeaders m  -> (MCGetHeaders, encode m)
-                MTx m          -> (MCTx, encode m)
-                MBlock m       -> (MCBlock, encode m)
-                MMerkleBlock m -> (MCMerkleBlock, encode m)
-                MHeaders m     -> (MCHeaders, encode m)
-                MGetAddr       -> (MCGetAddr, BS.empty)
-                MFilterLoad m  -> (MCFilterLoad, encode m)
-                MFilterAdd m   -> (MCFilterAdd, encode m)
-                MFilterClear   -> (MCFilterClear, BS.empty)
-                MPing m        -> (MCPing, encode m)
-                MPong m        -> (MCPong, encode m)
-                MAlert m       -> (MCAlert, encode m)
-                MMempool       -> (MCMempool, BS.empty)
-                MReject m      -> (MCReject, encode m)
-                MSendHeaders   -> (MCSendHeaders, BS.empty)
-                MOther c p     -> (MCOther c, p)
-        chk = checkSum32 payload
-        len = fromIntegral $ BS.length payload
-        header = MessageHeader (getNetworkMagic net) cmd len chk
-    put header
-    putByteString payload
diff --git a/src/Network/Haskoin/Script.hs b/src/Network/Haskoin/Script.hs
deleted file mode 100644
--- a/src/Network/Haskoin/Script.hs
+++ /dev/null
@@ -1,19 +0,0 @@
-{-|
-Module      : Network.Haskoin.Script
-Copyright   : No rights reserved
-License     : UNLICENSE
-Maintainer  : jprupp@protonmail.ch
-Stability   : experimental
-Portability : POSIX
-
-This module provides functions for parsing and evaluating bitcoin
-transaction scripts. Data types are provided for building and
-deconstructing all of the standard input and output script types.
--}
-module Network.Haskoin.Script
-    ( module X
-    ) where
-
-import           Network.Haskoin.Script.Common   as X
-import           Network.Haskoin.Script.SigHash  as X
-import           Network.Haskoin.Script.Standard as X
diff --git a/src/Network/Haskoin/Script/Common.hs b/src/Network/Haskoin/Script/Common.hs
deleted file mode 100644
--- a/src/Network/Haskoin/Script/Common.hs
+++ /dev/null
@@ -1,733 +0,0 @@
-{-# LANGUAGE DeriveAnyClass    #-}
-{-# LANGUAGE DeriveGeneric     #-}
-{-# LANGUAGE OverloadedStrings #-}
-{-|
-Module      : Network.Haskoin.Script.Common
-Copyright   : No rights reserved
-License     : UNLICENSE
-Maintainer  : jprupp@protonmail.ch
-Stability   : experimental
-Portability : POSIX
-
-Common script-related functions and data types.
--}
-module Network.Haskoin.Script.Common
-    ( ScriptOp(..)
-    , Script(..)
-    , PushDataType(..)
-    , ScriptOutput(..)
-    , isPayPK
-    , isPayPKHash
-    , isPayMulSig
-    , isPayScriptHash
-    , isPayWitnessPKHash
-    , isPayWitnessScriptHash
-    , isDataCarrier
-    , encodeOutput
-    , encodeOutputBS
-    , decodeOutput
-    , decodeOutputBS
-    , toP2SH
-    , toP2WSH
-    , isPushOp
-    , opPushData
-    , intToScriptOp
-    , scriptOpToInt
-    ) where
-
-import           Control.DeepSeq
-import           Control.Monad
-import           Data.Aeson                  as A
-import           Data.ByteString             (ByteString)
-import qualified Data.ByteString             as B
-import           Data.Hashable
-import           Data.Serialize              as S
-import           Data.Serialize.Get          (getByteString, getWord16le,
-                                              getWord32le, getWord8, isEmpty)
-import           Data.Serialize.Put          (putByteString, putWord16le,
-                                              putWord32le, putWord8)
-import           Data.Word                   (Word8)
-import           GHC.Generics                (Generic)
-import           Network.Haskoin.Crypto.Hash
-import           Network.Haskoin.Keys.Common
-import           Network.Haskoin.Util
-
--- | Data type representing a transaction script. Scripts are defined as lists
--- of script operators 'ScriptOp'. Scripts are used to:
---
--- * Define the spending conditions in the output of a transaction.
--- * Provide signatures in the input of a transaction (except SegWit).
---
--- SigWit only: the segregated witness data structure, and not the input script,
--- contains signatures and redeem script for pay-to-witness-script and
--- pay-to-witness-public-key-hash transactions.
-newtype Script =
-    Script {
-             -- | script operators defining this script
-             scriptOps :: [ScriptOp]
-           }
-    deriving (Eq, Show, Read, Generic, Hashable, NFData)
-
-instance Serialize Script where
-    get =
-        Script <$> getScriptOps
-      where
-        getScriptOps = do
-            empty <- isEmpty
-            if empty
-                then return []
-                else (:) <$> get <*> getScriptOps
-
-    put (Script ops) = forM_ ops put
-
--- | Data type representing the type of an OP_PUSHDATA opcode.
-data PushDataType
-    =
-      -- | next opcode bytes is data to be pushed
-      OPCODE
-      -- | next byte contains number of bytes of data to be pushed
-    | OPDATA1
-      -- | next two bytes contains number of bytes to be pushed
-    | OPDATA2
-      -- | next four bytes contains the number of bytes to be pushed
-    | OPDATA4
-    deriving (Show, Read, Eq, Generic, Hashable, NFData)
-
--- | Data type representing an operator allowed inside a 'Script'.
-data ScriptOp
-      -- Pushing Data
-    = OP_PUSHDATA !ByteString
-                  !PushDataType
-    | OP_0
-    | OP_1NEGATE
-    | OP_RESERVED
-    | OP_1
-    | OP_2
-    | OP_3
-    | OP_4
-    | OP_5
-    | OP_6
-    | OP_7
-    | OP_8
-    | OP_9
-    | OP_10
-    | OP_11
-    | OP_12
-    | OP_13
-    | OP_14
-    | OP_15
-    | OP_16
-      -- Flow control
-    | OP_NOP
-    | OP_VER -- reserved
-    | OP_IF
-    | OP_NOTIF
-    | OP_VERIF -- resreved
-    | OP_VERNOTIF -- reserved
-    | OP_ELSE
-    | OP_ENDIF
-    | OP_VERIFY
-    | OP_RETURN
-      -- Stack operations
-    | OP_TOALTSTACK
-    | OP_FROMALTSTACK
-    | OP_IFDUP
-    | OP_DEPTH
-    | OP_DROP
-    | OP_DUP
-    | OP_NIP
-    | OP_OVER
-    | OP_PICK
-    | OP_ROLL
-    | OP_ROT
-    | OP_SWAP
-    | OP_TUCK
-    | OP_2DROP
-    | OP_2DUP
-    | OP_3DUP
-    | OP_2OVER
-    | OP_2ROT
-    | OP_2SWAP
-      -- Splice
-    | OP_CAT
-    | OP_SUBSTR
-    | OP_LEFT
-    | OP_RIGHT
-    | OP_SIZE
-      -- Bitwise logic
-    | OP_INVERT
-    | OP_AND
-    | OP_OR
-    | OP_XOR
-    | OP_EQUAL
-    | OP_EQUALVERIFY
-    | OP_RESERVED1
-    | OP_RESERVED2
-      -- Arithmetic
-    | OP_1ADD
-    | OP_1SUB
-    | OP_2MUL
-    | OP_2DIV
-    | OP_NEGATE
-    | OP_ABS
-    | OP_NOT
-    | OP_0NOTEQUAL
-    | OP_ADD
-    | OP_SUB
-    | OP_MUL
-    | OP_DIV
-    | OP_MOD
-    | OP_LSHIFT
-    | OP_RSHIFT
-    | OP_BOOLAND
-    | OP_BOOLOR
-    | OP_NUMEQUAL
-    | OP_NUMEQUALVERIFY
-    | OP_NUMNOTEQUAL
-    | OP_LESSTHAN
-    | OP_GREATERTHAN
-    | OP_LESSTHANOREQUAL
-    | OP_GREATERTHANOREQUAL
-    | OP_MIN
-    | OP_MAX
-    | OP_WITHIN
-      -- Crypto
-    | OP_RIPEMD160
-    | OP_SHA1
-    | OP_SHA256
-    | OP_HASH160
-    | OP_HASH256
-    | OP_CODESEPARATOR
-    | OP_CHECKSIG
-    | OP_CHECKSIGVERIFY
-    | OP_CHECKMULTISIG
-    | OP_CHECKMULTISIGVERIFY
-      -- Expansion
-    | OP_NOP1
-    | OP_NOP2
-    | OP_NOP3
-    | OP_NOP4
-    | OP_NOP5
-    | OP_NOP6
-    | OP_NOP7
-    | OP_NOP8
-    | OP_NOP9
-    | OP_NOP10
-      -- Bitcoin Cash Nov 2018 hard fork
-    | OP_CHECKDATASIG
-    | OP_CHECKDATASIGVERIFY
-      -- Other
-    | OP_PUBKEYHASH
-    | OP_PUBKEY
-    | OP_INVALIDOPCODE !Word8
-    deriving (Show, Read, Eq, Generic, Hashable, NFData)
-
-instance Serialize ScriptOp where
-    get = go =<< (fromIntegral <$> getWord8)
-      where
-        go op
-            | op == 0x00 = return OP_0
-            | op <= 0x4b = do
-                payload <- getByteString (fromIntegral op)
-                return $ OP_PUSHDATA payload OPCODE
-            | op == 0x4c = do
-                len  <- getWord8
-                payload <- getByteString (fromIntegral len)
-                return $ OP_PUSHDATA payload OPDATA1
-            | op == 0x4d = do
-                len  <- getWord16le
-                payload <- getByteString (fromIntegral len)
-                return $ OP_PUSHDATA payload OPDATA2
-            | op == 0x4e = do
-                len  <- getWord32le
-                payload <- getByteString (fromIntegral len)
-                return $ OP_PUSHDATA payload OPDATA4
-
-            | op == 0x4f = return OP_1NEGATE
-            | op == 0x50 = return OP_RESERVED
-            | op == 0x51 = return OP_1
-            | op == 0x52 = return OP_2
-            | op == 0x53 = return OP_3
-            | op == 0x54 = return OP_4
-            | op == 0x55 = return OP_5
-            | op == 0x56 = return OP_6
-            | op == 0x57 = return OP_7
-            | op == 0x58 = return OP_8
-            | op == 0x59 = return OP_9
-            | op == 0x5a = return OP_10
-            | op == 0x5b = return OP_11
-            | op == 0x5c = return OP_12
-            | op == 0x5d = return OP_13
-            | op == 0x5e = return OP_14
-            | op == 0x5f = return OP_15
-            | op == 0x60 = return OP_16
-
-            -- Flow control
-            | op == 0x61 = return OP_NOP
-            | op == 0x62 = return OP_VER        -- reserved
-            | op == 0x63 = return OP_IF
-            | op == 0x64 = return OP_NOTIF
-            | op == 0x65 = return OP_VERIF      -- reserved
-            | op == 0x66 = return OP_VERNOTIF   -- reserved
-            | op == 0x67 = return OP_ELSE
-            | op == 0x68 = return OP_ENDIF
-            | op == 0x69 = return OP_VERIFY
-            | op == 0x6a = return OP_RETURN
-
-            -- Stack
-            | op == 0x6b = return OP_TOALTSTACK
-            | op == 0x6c = return OP_FROMALTSTACK
-            | op == 0x6d = return OP_2DROP
-            | op == 0x6e = return OP_2DUP
-            | op == 0x6f = return OP_3DUP
-            | op == 0x70 = return OP_2OVER
-            | op == 0x71 = return OP_2ROT
-            | op == 0x72 = return OP_2SWAP
-            | op == 0x73 = return OP_IFDUP
-            | op == 0x74 = return OP_DEPTH
-            | op == 0x75 = return OP_DROP
-            | op == 0x76 = return OP_DUP
-            | op == 0x77 = return OP_NIP
-            | op == 0x78 = return OP_OVER
-            | op == 0x79 = return OP_PICK
-            | op == 0x7a = return OP_ROLL
-            | op == 0x7b = return OP_ROT
-            | op == 0x7c = return OP_SWAP
-            | op == 0x7d = return OP_TUCK
-
-            -- Splice
-            | op == 0x7e = return OP_CAT
-            | op == 0x7f = return OP_SUBSTR
-            | op == 0x80 = return OP_LEFT
-            | op == 0x81 = return OP_RIGHT
-            | op == 0x82 = return OP_SIZE
-
-            -- Bitwise logic
-            | op == 0x83 = return OP_INVERT
-            | op == 0x84 = return OP_AND
-            | op == 0x85 = return OP_OR
-            | op == 0x86 = return OP_XOR
-            | op == 0x87 = return OP_EQUAL
-            | op == 0x88 = return OP_EQUALVERIFY
-            | op == 0x89 = return OP_RESERVED1
-            | op == 0x8a = return OP_RESERVED2
-
-            -- Arithmetic
-            | op == 0x8b = return OP_1ADD
-            | op == 0x8c = return OP_1SUB
-            | op == 0x8d = return OP_2MUL
-            | op == 0x8e = return OP_2DIV
-            | op == 0x8f = return OP_NEGATE
-            | op == 0x90 = return OP_ABS
-            | op == 0x91 = return OP_NOT
-            | op == 0x92 = return OP_0NOTEQUAL
-            | op == 0x93 = return OP_ADD
-            | op == 0x94 = return OP_SUB
-            | op == 0x95 = return OP_MUL
-            | op == 0x96 = return OP_DIV
-            | op == 0x97 = return OP_MOD
-            | op == 0x98 = return OP_LSHIFT
-            | op == 0x99 = return OP_RSHIFT
-            | op == 0x9a = return OP_BOOLAND
-            | op == 0x9b = return OP_BOOLOR
-            | op == 0x9c = return OP_NUMEQUAL
-            | op == 0x9d = return OP_NUMEQUALVERIFY
-            | op == 0x9e = return OP_NUMNOTEQUAL
-            | op == 0x9f = return OP_LESSTHAN
-            | op == 0xa0 = return OP_GREATERTHAN
-            | op == 0xa1 = return OP_LESSTHANOREQUAL
-            | op == 0xa2 = return OP_GREATERTHANOREQUAL
-            | op == 0xa3 = return OP_MIN
-            | op == 0xa4 = return OP_MAX
-            | op == 0xa5 = return OP_WITHIN
-
-            -- Crypto
-            | op == 0xa6 = return OP_RIPEMD160
-            | op == 0xa7 = return OP_SHA1
-            | op == 0xa8 = return OP_SHA256
-            | op == 0xa9 = return OP_HASH160
-            | op == 0xaa = return OP_HASH256
-            | op == 0xab = return OP_CODESEPARATOR
-            | op == 0xac = return OP_CHECKSIG
-            | op == 0xad = return OP_CHECKSIGVERIFY
-            | op == 0xae = return OP_CHECKMULTISIG
-            | op == 0xaf = return OP_CHECKMULTISIGVERIFY
-
-            -- More NOPs
-            | op == 0xb0 = return OP_NOP1
-            | op == 0xb1 = return OP_NOP2
-            | op == 0xb2 = return OP_NOP3
-            | op == 0xb3 = return OP_NOP4
-            | op == 0xb4 = return OP_NOP5
-            | op == 0xb5 = return OP_NOP6
-            | op == 0xb6 = return OP_NOP7
-            | op == 0xb7 = return OP_NOP8
-            | op == 0xb8 = return OP_NOP9
-            | op == 0xb9 = return OP_NOP10
-
-            -- Bitcoin Cash Nov 2018 hard fork
-            | op == 0xba = return OP_CHECKDATASIG
-            | op == 0xbb = return OP_CHECKDATASIGVERIFY
-
-            -- Constants
-            | op == 0xfd = return OP_PUBKEYHASH
-            | op == 0xfe = return OP_PUBKEY
-
-            | otherwise = return $ OP_INVALIDOPCODE op
-
-    put op = case op of
-
-        (OP_PUSHDATA payload optype)-> do
-            let len = B.length payload
-            case optype of
-                OPCODE -> do
-                    unless (len <= 0x4b) $
-                        error "OP_PUSHDATA OPCODE: Payload size too big"
-                    putWord8 $ fromIntegral len
-                OPDATA1 -> do
-                    unless (len <= 0xff) $
-                        error "OP_PUSHDATA OPDATA1: Payload size too big"
-                    putWord8 0x4c
-                    putWord8 $ fromIntegral len
-                OPDATA2 -> do
-                    unless (len <= 0xffff) $
-                        error "OP_PUSHDATA OPDATA2: Payload size too big"
-                    putWord8 0x4d
-                    putWord16le $ fromIntegral len
-                OPDATA4 -> do
-                    unless (len <= 0x7fffffff) $
-                        error "OP_PUSHDATA OPDATA4: Payload size too big"
-                    putWord8 0x4e
-                    putWord32le $ fromIntegral len
-            putByteString payload
-
-        -- Constants
-        OP_0                 -> putWord8 0x00
-        OP_1NEGATE           -> putWord8 0x4f
-        OP_RESERVED          -> putWord8 0x50
-        OP_1                 -> putWord8 0x51
-        OP_2                 -> putWord8 0x52
-        OP_3                 -> putWord8 0x53
-        OP_4                 -> putWord8 0x54
-        OP_5                 -> putWord8 0x55
-        OP_6                 -> putWord8 0x56
-        OP_7                 -> putWord8 0x57
-        OP_8                 -> putWord8 0x58
-        OP_9                 -> putWord8 0x59
-        OP_10                -> putWord8 0x5a
-        OP_11                -> putWord8 0x5b
-        OP_12                -> putWord8 0x5c
-        OP_13                -> putWord8 0x5d
-        OP_14                -> putWord8 0x5e
-        OP_15                -> putWord8 0x5f
-        OP_16                -> putWord8 0x60
-
-        -- Crypto Constants
-        OP_PUBKEY            -> putWord8 0xfe
-        OP_PUBKEYHASH        -> putWord8 0xfd
-
-        -- Invalid Opcodes
-        (OP_INVALIDOPCODE x) -> putWord8 x
-
-        -- Flow Control
-        OP_NOP               -> putWord8 0x61
-        OP_VER               -> putWord8 0x62
-        OP_IF                -> putWord8 0x63
-        OP_NOTIF             -> putWord8 0x64
-        OP_VERIF             -> putWord8 0x65
-        OP_VERNOTIF          -> putWord8 0x66
-        OP_ELSE              -> putWord8 0x67
-        OP_ENDIF             -> putWord8 0x68
-        OP_VERIFY            -> putWord8 0x69
-        OP_RETURN            -> putWord8 0x6a
-
-        -- Stack Operations
-        OP_TOALTSTACK        -> putWord8 0x6b
-        OP_FROMALTSTACK      -> putWord8 0x6c
-        OP_2DROP             -> putWord8 0x6d
-        OP_2DUP              -> putWord8 0x6e
-        OP_3DUP              -> putWord8 0x6f
-        OP_2OVER             -> putWord8 0x70
-        OP_2ROT              -> putWord8 0x71
-        OP_2SWAP             -> putWord8 0x72
-        OP_IFDUP             -> putWord8 0x73
-        OP_DEPTH             -> putWord8 0x74
-        OP_DROP              -> putWord8 0x75
-        OP_DUP               -> putWord8 0x76
-        OP_NIP               -> putWord8 0x77
-        OP_OVER              -> putWord8 0x78
-        OP_PICK              -> putWord8 0x79
-        OP_ROLL              -> putWord8 0x7a
-        OP_ROT               -> putWord8 0x7b
-        OP_SWAP              -> putWord8 0x7c
-        OP_TUCK              -> putWord8 0x7d
-
-        -- Splice
-        OP_CAT               -> putWord8 0x7e
-        OP_SUBSTR            -> putWord8 0x7f
-        OP_LEFT              -> putWord8 0x80
-        OP_RIGHT             -> putWord8 0x81
-        OP_SIZE              -> putWord8 0x82
-
-        -- Bitwise Logic
-        OP_INVERT            -> putWord8 0x83
-        OP_AND               -> putWord8 0x84
-        OP_OR                -> putWord8 0x85
-        OP_XOR               -> putWord8 0x86
-        OP_EQUAL             -> putWord8 0x87
-        OP_EQUALVERIFY       -> putWord8 0x88
-        OP_RESERVED1         -> putWord8 0x89
-        OP_RESERVED2         -> putWord8 0x8a
-
-        -- Arithmetic
-        OP_1ADD              -> putWord8 0x8b
-        OP_1SUB              -> putWord8 0x8c
-        OP_2MUL              -> putWord8 0x8d
-        OP_2DIV              -> putWord8 0x8e
-        OP_NEGATE            -> putWord8 0x8f
-        OP_ABS               -> putWord8 0x90
-        OP_NOT               -> putWord8 0x91
-        OP_0NOTEQUAL         -> putWord8 0x92
-        OP_ADD               -> putWord8 0x93
-        OP_SUB               -> putWord8 0x94
-        OP_MUL               -> putWord8 0x95
-        OP_DIV               -> putWord8 0x96
-        OP_MOD               -> putWord8 0x97
-        OP_LSHIFT            -> putWord8 0x98
-        OP_RSHIFT            -> putWord8 0x99
-        OP_BOOLAND           -> putWord8 0x9a
-        OP_BOOLOR            -> putWord8 0x9b
-        OP_NUMEQUAL          -> putWord8 0x9c
-        OP_NUMEQUALVERIFY    -> putWord8 0x9d
-        OP_NUMNOTEQUAL       -> putWord8 0x9e
-        OP_LESSTHAN          -> putWord8 0x9f
-        OP_GREATERTHAN       -> putWord8 0xa0
-        OP_LESSTHANOREQUAL   -> putWord8 0xa1
-        OP_GREATERTHANOREQUAL-> putWord8 0xa2
-        OP_MIN               -> putWord8 0xa3
-        OP_MAX               -> putWord8 0xa4
-        OP_WITHIN            -> putWord8 0xa5
-
-        -- Crypto
-        OP_RIPEMD160         -> putWord8 0xa6
-        OP_SHA1              -> putWord8 0xa7
-        OP_SHA256            -> putWord8 0xa8
-        OP_HASH160           -> putWord8 0xa9
-        OP_HASH256           -> putWord8 0xaa
-        OP_CODESEPARATOR     -> putWord8 0xab
-        OP_CHECKSIG          -> putWord8 0xac
-        OP_CHECKSIGVERIFY    -> putWord8 0xad
-        OP_CHECKMULTISIG     -> putWord8 0xae
-        OP_CHECKMULTISIGVERIFY -> putWord8 0xaf
-
-        -- More NOPs
-        OP_NOP1              -> putWord8 0xb0
-        OP_NOP2              -> putWord8 0xb1
-        OP_NOP3              -> putWord8 0xb2
-        OP_NOP4              -> putWord8 0xb3
-        OP_NOP5              -> putWord8 0xb4
-        OP_NOP6              -> putWord8 0xb5
-        OP_NOP7              -> putWord8 0xb6
-        OP_NOP8              -> putWord8 0xb7
-        OP_NOP9              -> putWord8 0xb8
-        OP_NOP10             -> putWord8 0xb9
-
-        -- Bitcoin Cash Nov 2018 hard fork
-        OP_CHECKDATASIG      -> putWord8 0xba
-        OP_CHECKDATASIGVERIFY -> putWord8 0xbb
-
-
--- | Check whether opcode is only data.
-isPushOp :: ScriptOp -> Bool
-isPushOp op = case op of
-    OP_PUSHDATA _ _ -> True
-    OP_0            -> True
-    OP_1NEGATE      -> True
-    OP_1            -> True
-    OP_2            -> True
-    OP_3            -> True
-    OP_4            -> True
-    OP_5            -> True
-    OP_6            -> True
-    OP_7            -> True
-    OP_8            -> True
-    OP_9            -> True
-    OP_10           -> True
-    OP_11           -> True
-    OP_12           -> True
-    OP_13           -> True
-    OP_14           -> True
-    OP_15           -> True
-    OP_16           -> True
-    _               -> False
-
--- | Optimally encode data using one of the 4 types of data pushing opcodes.
-opPushData :: ByteString -> ScriptOp
-opPushData bs
-    | len <= 0x4b       = OP_PUSHDATA bs OPCODE
-    | len <= 0xff       = OP_PUSHDATA bs OPDATA1
-    | len <= 0xffff     = OP_PUSHDATA bs OPDATA2
-    | len <= 0xffffffff = OP_PUSHDATA bs OPDATA4
-    | otherwise         = error "opPushData: payload size too big"
-  where
-    len = B.length bs
-
--- | Transforms integers @[1 .. 16]@ to 'ScriptOp' @[OP_1 .. OP_16]@.
-intToScriptOp :: Int -> ScriptOp
-intToScriptOp i
-    | i `elem` [1 .. 16] = op
-    | otherwise = err
-  where
-    op = either (const err) id . S.decode . B.singleton . fromIntegral $ i + 0x50
-    err = error $ "intToScriptOp: Invalid integer " ++ show i
-
--- | Decode 'ScriptOp' @[OP_1 .. OP_16]@ to integers @[1 .. 16]@. This functions
--- fails for other values of 'ScriptOp'
-scriptOpToInt :: ScriptOp -> Either String Int
-scriptOpToInt s
-    | res `elem` [1..16] = return res
-    | otherwise          = Left $ "scriptOpToInt: invalid opcode " ++ show s
-  where
-    res = fromIntegral (B.head $ S.encode s) - 0x50
-
--- | Data type describing standard transaction output scripts. Output scripts
--- provide the conditions that must be fulfilled for someone to spend the funds
--- in a transaction output.
-data ScriptOutput
-      -- | pay to public key
-    = PayPK { getOutputPubKey :: !PubKeyI }
-      -- | pay to public key hash
-    | PayPKHash { getOutputHash :: !Hash160 }
-      -- | multisig
-    | PayMulSig { getOutputMulSigKeys     :: ![PubKeyI]
-                , getOutputMulSigRequired :: !Int }
-      -- | pay to a script hash
-    | PayScriptHash { getOutputHash :: !Hash160 }
-      -- | pay to witness public key hash
-    | PayWitnessPKHash { getOutputHash :: !Hash160 }
-      -- | pay to witness script hash
-    | PayWitnessScriptHash { getScriptHash :: !Hash256 }
-      -- | provably unspendable data carrier
-    | DataCarrier { getOutputData :: !ByteString }
-    deriving (Eq, Show, Read, Generic, Hashable, NFData)
-
-instance FromJSON ScriptOutput where
-    parseJSON = withText "scriptoutput" $ \t -> either fail return $
-        maybeToEither "scriptoutput not hex" (decodeHex t) >>=
-        decodeOutputBS
-
-instance ToJSON ScriptOutput where
-    toJSON = String . encodeHex . encodeOutputBS
-
--- | Is script a pay-to-public-key output?
-isPayPK :: ScriptOutput -> Bool
-isPayPK (PayPK _) = True
-isPayPK _         = False
-
--- | Is script a pay-to-pub-key-hash output?
-isPayPKHash :: ScriptOutput -> Bool
-isPayPKHash (PayPKHash _) = True
-isPayPKHash _             = False
-
--- | Is script a pay-to-multi-sig output?
-isPayMulSig :: ScriptOutput -> Bool
-isPayMulSig (PayMulSig _ _) = True
-isPayMulSig _               = False
-
--- | Is script a pay-to-script-hash output?
-isPayScriptHash :: ScriptOutput -> Bool
-isPayScriptHash (PayScriptHash _) = True
-isPayScriptHash _                 = False
-
--- | Is script a pay-to-witness-pub-key-hash output?
-isPayWitnessPKHash :: ScriptOutput -> Bool
-isPayWitnessPKHash (PayWitnessPKHash _) = True
-isPayWitnessPKHash _                    = False
-
--- | Is script a pay-to-witness-script-hash output?
-isPayWitnessScriptHash :: ScriptOutput -> Bool
-isPayWitnessScriptHash (PayWitnessScriptHash _) = True
-isPayWitnessScriptHash _                        = False
-
--- | Is script a data carrier output?
-isDataCarrier :: ScriptOutput -> Bool
-isDataCarrier (DataCarrier _) = True
-isDataCarrier _               = False
-
--- | Tries to decode a 'ScriptOutput' from a 'Script'. This can fail if the
--- script is not recognized as any of the standard output types.
-decodeOutput :: Script -> Either String ScriptOutput
-decodeOutput s = case scriptOps s of
-    -- Pay to PubKey
-    [OP_PUSHDATA bs _, OP_CHECKSIG] -> PayPK <$> S.decode bs
-    -- Pay to PubKey Hash
-    [OP_DUP, OP_HASH160, OP_PUSHDATA bs _, OP_EQUALVERIFY, OP_CHECKSIG] ->
-        PayPKHash <$> S.decode bs
-    -- Pay to Script Hash
-    [OP_HASH160, OP_PUSHDATA bs _, OP_EQUAL] ->
-        PayScriptHash <$> S.decode  bs
-    -- Pay to Witness
-    [OP_0, OP_PUSHDATA bs OPCODE]
-      | B.length bs == 20 -> PayWitnessPKHash     <$> S.decode bs
-      | B.length bs == 32 -> PayWitnessScriptHash <$> S.decode bs
-    -- Provably unspendable data carrier output
-    [OP_RETURN, OP_PUSHDATA bs _] -> Right $ DataCarrier bs
-    -- Pay to MultiSig Keys
-    _ -> matchPayMulSig s
-
--- | Similar to 'decodeOutput' but decodes from a 'ByteString'.
-decodeOutputBS :: ByteString -> Either String ScriptOutput
-decodeOutputBS = decodeOutput <=< S.decode
-
--- | Computes a 'Script' from a standard 'ScriptOutput'.
-encodeOutput :: ScriptOutput -> Script
-encodeOutput s = Script $ case s of
-    -- Pay to PubKey
-    (PayPK k) -> [opPushData $ S.encode k, OP_CHECKSIG]
-    -- Pay to PubKey Hash Address
-    (PayPKHash h) ->
-        [ OP_DUP, OP_HASH160, opPushData $ S.encode h, OP_EQUALVERIFY, OP_CHECKSIG]
-    -- Pay to MultiSig Keys
-    (PayMulSig ps r)
-      | r <= length ps ->
-        let opM = intToScriptOp r
-            opN = intToScriptOp $ length ps
-            keys = map (opPushData . S.encode) ps
-            in opM : keys ++ [opN, OP_CHECKMULTISIG]
-      | otherwise -> error "encodeOutput: PayMulSig r must be <= than pkeys"
-    -- Pay to Script Hash Address
-    (PayScriptHash h) ->
-        [ OP_HASH160, opPushData $ S.encode h, OP_EQUAL]
-    -- Pay to Witness PubKey Hash Address
-    (PayWitnessPKHash h) ->
-        [ OP_0, opPushData $ S.encode h ]
-    (PayWitnessScriptHash h) ->
-        [ OP_0, opPushData $ S.encode h ]
-    -- Provably unspendable output
-    (DataCarrier d) -> [OP_RETURN, opPushData d]
-
--- | Similar to 'encodeOutput' but encodes to a ByteString
-encodeOutputBS :: ScriptOutput -> ByteString
-encodeOutputBS = S.encode . encodeOutput
-
--- | Encode script as pay-to-script-hash script
-toP2SH :: Script -> ScriptOutput
-toP2SH = PayScriptHash . addressHash . S.encode
-
--- | Encode script as a pay-to-witness-script-hash script
-toP2WSH :: Script -> ScriptOutput
-toP2WSH = PayWitnessScriptHash . sha256 . S.encode
-
--- | Match @[OP_N, PubKey1, ..., PubKeyM, OP_M, OP_CHECKMULTISIG]@
-matchPayMulSig :: Script -> Either String ScriptOutput
-matchPayMulSig (Script ops) = case splitAt (length ops - 2) ops of
-    (m:xs,[n,OP_CHECKMULTISIG]) -> do
-        (intM,intN) <- liftM2 (,) (scriptOpToInt m) (scriptOpToInt n)
-        if intM <= intN && length xs == intN
-            then liftM2 PayMulSig (go xs) (return intM)
-            else Left "matchPayMulSig: Invalid M or N parameters"
-    _ -> Left "matchPayMulSig: script did not match output template"
-  where
-    go (OP_PUSHDATA bs _:xs) = liftM2 (:) (S.decode bs) (go xs)
-    go []                    = return []
-    go  _                    = Left "matchPayMulSig: invalid multisig opcode"
diff --git a/src/Network/Haskoin/Script/SigHash.hs b/src/Network/Haskoin/Script/SigHash.hs
deleted file mode 100644
--- a/src/Network/Haskoin/Script/SigHash.hs
+++ /dev/null
@@ -1,319 +0,0 @@
-{-# LANGUAGE DeriveGeneric              #-}
-{-# LANGUAGE GeneralizedNewtypeDeriving #-}
-{-# LANGUAGE OverloadedStrings          #-}
-{-|
-Module      : Network.Haskoin.Script.SigHash
-Copyright   : No rights reserved
-License     : UNLICENSE
-Maintainer  : jprupp@protonmail.ch
-Stability   : experimental
-Portability : POSIX
-
-Transaction signatures and related functions.
--}
-module Network.Haskoin.Script.SigHash
-( SigHash
-, SigHashFlag(..)
-, sigHashAll
-, sigHashNone
-, sigHashSingle
-, hasAnyoneCanPayFlag
-, hasForkIdFlag
-, setAnyoneCanPayFlag
-, setForkIdFlag
-, isSigHashAll
-, isSigHashNone
-, isSigHashSingle
-, isSigHashUnknown
-, sigHashAddForkId
-, sigHashGetForkId
-, sigHashAddNetworkId
-, txSigHash
-, txSigHashForkId
-, TxSignature(..)
-, encodeTxSig
-, decodeTxSig
-) where
-
-import           Control.DeepSeq
-import           Control.Monad
-import           Crypto.Secp256k1
-import qualified Data.Aeson                         as J
-import           Data.Bits
-import           Data.ByteString                    (ByteString)
-import qualified Data.ByteString                    as BS
-import           Data.Hashable
-import           Data.Maybe
-import           Data.Scientific
-import           Data.Serialize
-import           Data.Serialize.Put                 (runPut)
-import           Data.Word
-import           GHC.Generics                       (Generic)
-import           Network.Haskoin.Constants
-import           Network.Haskoin.Crypto.Hash
-import           Network.Haskoin.Crypto.Signature
-import           Network.Haskoin.Network
-import           Network.Haskoin.Script.Common
-import           Network.Haskoin.Transaction.Common
-import           Network.Haskoin.Util
-
--- | Constant representing a SIGHASH flag that controls what is being signed.
-data SigHashFlag
-    = SIGHASH_ALL
-      -- ^ sign all outputs
-    | SIGHASH_NONE
-      -- ^ sign no outputs
-    | SIGHASH_SINGLE
-      -- ^ sign the output index corresponding to the input
-    | SIGHASH_FORKID
-      -- ^ replay protection for Bitcoin Cash transactions
-    | SIGHASH_ANYONECANPAY
-      -- ^ new inputs can be added
-    deriving (Eq, Ord, Show, Read, Generic)
-
-instance NFData SigHashFlag
-
-instance Hashable SigHashFlag
-
-instance Enum SigHashFlag where
-    fromEnum SIGHASH_ALL          = 0x01
-    fromEnum SIGHASH_NONE         = 0x02
-    fromEnum SIGHASH_SINGLE       = 0x03
-    fromEnum SIGHASH_FORKID       = 0x40
-    fromEnum SIGHASH_ANYONECANPAY = 0x80
-    toEnum 0x01 = SIGHASH_ALL
-    toEnum 0x02 = SIGHASH_NONE
-    toEnum 0x03 = SIGHASH_SINGLE
-    toEnum 0x40 = SIGHASH_FORKID
-    toEnum 0x80 = SIGHASH_ANYONECANPAY
-    toEnum _    = error "Not a valid sighash flag"
-
--- | Data type representing the different ways a transaction can be signed.
--- When producing a signature, a hash of the transaction is used as the message
--- to be signed. The 'SigHash' parameter controls which parts of the
--- transaction are used or ignored to produce the transaction hash. The idea is
--- that if some part of a transaction is not used to produce the transaction
--- hash, then you can change that part of the transaction after producing a
--- signature without invalidating that signature.
---
--- If the 'SIGHASH_ANYONECANPAY' flag is set (true), then only the current input
--- is signed. Otherwise, all of the inputs of a transaction are signed. The
--- default value for 'SIGHASH_ANYONECANPAY' is unset (false).
-newtype SigHash =
-    SigHash Word32
-    deriving ( Eq
-             , Ord
-             , Bits
-             , Enum
-             , Integral
-             , Num
-             , Real
-             , Show
-             , Read
-             , Generic
-             , Hashable
-             , NFData
-             )
-
-instance J.FromJSON SigHash where
-    parseJSON =
-        J.withScientific "sighash" $
-        maybe mzero (return . SigHash) . toBoundedInteger
-
-instance J.ToJSON SigHash where
-    toJSON = J.Number . fromIntegral
-
--- | SIGHASH_NONE as a byte.
-sigHashNone :: SigHash
-sigHashNone = fromIntegral $ fromEnum SIGHASH_NONE
-
--- | SIGHASH_ALL as a byte.
-sigHashAll :: SigHash
-sigHashAll = fromIntegral $ fromEnum SIGHASH_ALL
-
--- | SIGHASH_SINGLE as a byte.
-sigHashSingle :: SigHash
-sigHashSingle = fromIntegral $ fromEnum SIGHASH_SINGLE
-
--- | SIGHASH_FORKID as a byte.
-sigHashForkId :: SigHash
-sigHashForkId = fromIntegral $ fromEnum SIGHASH_FORKID
-
--- | SIGHASH_ANYONECANPAY as a byte.
-sigHashAnyoneCanPay :: SigHash
-sigHashAnyoneCanPay = fromIntegral $ fromEnum SIGHASH_ANYONECANPAY
-
--- | Set SIGHASH_FORKID flag.
-setForkIdFlag :: SigHash -> SigHash
-setForkIdFlag = (.|. sigHashForkId)
-
--- | Set SIGHASH_ANYONECANPAY flag.
-setAnyoneCanPayFlag :: SigHash -> SigHash
-setAnyoneCanPayFlag = (.|. sigHashAnyoneCanPay)
-
--- | Is the SIGHASH_FORKID flag set?
-hasForkIdFlag :: SigHash -> Bool
-hasForkIdFlag = (/= 0) . (.&. sigHashForkId)
-
--- | Is the SIGHASH_ANYONECANPAY flag set?
-hasAnyoneCanPayFlag :: SigHash -> Bool
-hasAnyoneCanPayFlag = (/= 0) . (.&. sigHashAnyoneCanPay)
-
--- | Returns 'True' if the 'SigHash' has the value 'SIGHASH_ALL'.
-isSigHashAll :: SigHash -> Bool
-isSigHashAll = (== sigHashAll) . (.&. 0x1f)
-
--- | Returns 'True' if the 'SigHash' has the value 'SIGHASH_NONE'.
-isSigHashNone :: SigHash -> Bool
-isSigHashNone = (== sigHashNone) . (.&. 0x1f)
-
--- | Returns 'True' if the 'SigHash' has the value 'SIGHASH_SINGLE'.
-isSigHashSingle :: SigHash -> Bool
-isSigHashSingle = (== sigHashSingle) . (.&. 0x1f)
-
--- | Returns 'True' if the 'SigHash' has the value 'SIGHASH_UNKNOWN'.
-isSigHashUnknown :: SigHash -> Bool
-isSigHashUnknown =
-    (`notElem` [sigHashAll, sigHashNone, sigHashSingle]) . (.&. 0x1f)
-
--- | Add a fork id to a 'SigHash'.
-sigHashAddForkId :: SigHash -> Word32 -> SigHash
-sigHashAddForkId sh w = (fromIntegral w `shiftL` 8) .|. (sh .&. 0x000000ff)
-
--- | Add fork id of a particular network to a 'SigHash'.
-sigHashAddNetworkId :: Network -> SigHash -> SigHash
-sigHashAddNetworkId net =
-    (`sigHashAddForkId` fromMaybe 0 (getSigHashForkId net))
-
--- | Get fork id from 'SigHash'.
-sigHashGetForkId :: SigHash -> Word32
-sigHashGetForkId (SigHash n) = fromIntegral $ n `shiftR` 8
-
--- | Computes the hash that will be used for signing a transaction.
-txSigHash :: Network
-          -> Tx      -- ^ transaction to sign
-          -> Script  -- ^ script from output being spent
-          -> Word64  -- ^ value of output being spent
-          -> Int     -- ^ index of input being signed
-          -> SigHash -- ^ what to sign
-          -> Hash256 -- ^ hash to be signed
-txSigHash net tx out v i sh
-    | hasForkIdFlag sh && isJust (getSigHashForkId net) =
-        txSigHashForkId net tx out v i sh
-    | otherwise = do
-        let newIn = buildInputs (txIn tx) fout i sh
-        -- When SigSingle and input index > outputs, then sign integer 1
-        fromMaybe one $ do
-            newOut <- buildOutputs (txOut tx) i sh
-            let newTx = Tx (txVersion tx) newIn newOut [] (txLockTime tx)
-            return $
-                doubleSHA256 $
-                runPut $ do
-                    put newTx
-                    putWord32le $ fromIntegral sh
-  where
-    fout = Script $ filter (/= OP_CODESEPARATOR) $ scriptOps out
-    one = "0100000000000000000000000000000000000000000000000000000000000000"
-
--- | Build transaction inputs for computing sighashes.
-buildInputs :: [TxIn] -> Script -> Int -> SigHash -> [TxIn]
-buildInputs txins out i sh
-    | hasAnyoneCanPayFlag sh =
-        [ (txins !! i) { scriptInput = encode out } ]
-    | isSigHashAll sh || isSigHashUnknown sh = single
-    | otherwise = zipWith noSeq single [0 ..]
-  where
-    emptyIn = map (\ti -> ti { scriptInput = BS.empty }) txins
-    single =
-        updateIndex i emptyIn $ \ti -> ti { scriptInput = encode out }
-    noSeq ti j =
-        if i == j
-        then ti
-        else ti { txInSequence = 0 }
-
--- | Build transaction outputs for computing sighashes.
-buildOutputs :: [TxOut] -> Int -> SigHash -> Maybe [TxOut]
-buildOutputs txos i sh
-    | isSigHashAll sh || isSigHashUnknown sh = return txos
-    | isSigHashNone sh = return []
-    | i >= length txos = Nothing
-    | otherwise = return $ buffer ++ [txos !! i]
-  where
-    buffer = replicate i $ TxOut maxBound BS.empty
-
--- | Compute the hash that will be used for signing a transaction. This
--- function is used when the 'SIGHASH_FORKID' flag is set.
-txSigHashForkId
-    :: Network
-    -> Tx      -- ^ transaction to sign
-    -> Script  -- ^ script from output being spent
-    -> Word64  -- ^ value of output being spent
-    -> Int     -- ^ index of input being signed
-    -> SigHash -- ^ what to sign
-    -> Hash256 -- ^ hash to be signed
-txSigHashForkId net tx out v i sh =
-    doubleSHA256 . runPut $ do
-        putWord32le $ txVersion tx
-        put hashPrevouts
-        put hashSequence
-        put $ prevOutput $ txIn tx !! i
-        putScript out
-        putWord64le v
-        putWord32le $ txInSequence $ txIn tx !! i
-        put hashOutputs
-        putWord32le $ txLockTime tx
-        putWord32le $ fromIntegral $ sigHashAddNetworkId net sh
-  where
-    hashPrevouts
-        | not $ hasAnyoneCanPayFlag sh =
-            doubleSHA256 $ runPut $ mapM_ (put . prevOutput) $ txIn tx
-        | otherwise = zeros
-    hashSequence
-        | not (hasAnyoneCanPayFlag sh) &&
-              not (isSigHashSingle sh) && not (isSigHashNone sh) =
-            doubleSHA256 $ runPut $ mapM_ (putWord32le . txInSequence) $ txIn tx
-        | otherwise = zeros
-    hashOutputs
-        | not (isSigHashSingle sh) && not (isSigHashNone sh) =
-            doubleSHA256 $ runPut $ mapM_ put $ txOut tx
-        | isSigHashSingle sh && i < length (txOut tx) =
-            doubleSHA256 $ encode $ txOut tx !! i
-        | otherwise = zeros
-    putScript s = do
-        let encodedScript = encode s
-        putVarInt $ BS.length encodedScript
-        putByteString encodedScript
-    zeros :: Hash256
-    zeros = "0000000000000000000000000000000000000000000000000000000000000000"
-
--- | Data type representing a signature together with a 'SigHash'. The 'SigHash'
--- is serialized as one byte at the end of an ECDSA 'Sig'. All signatures in
--- transaction inputs are of type 'TxSignature'.
-data TxSignature
-    = TxSignature { txSignature        :: !Sig
-                  , txSignatureSigHash :: !SigHash
-                  }
-    | TxSignatureEmpty
-    deriving (Eq, Show, Generic)
-
-instance NFData TxSignature
-
--- | Serialize a 'TxSignature'.
-encodeTxSig :: TxSignature -> ByteString
-encodeTxSig TxSignatureEmpty = error "Can not encode an empty signature"
-encodeTxSig (TxSignature sig (SigHash n)) =
-    runPut $ putSig sig >> putWord8 (fromIntegral n)
-
--- | Deserialize a 'TxSignature'.
-decodeTxSig :: Network -> ByteString -> Either String TxSignature
-decodeTxSig _   bs | BS.null bs = Left "Empty signature candidate"
-decodeTxSig net bs =
-    case decodeStrictSig $ BS.init bs of
-        Just sig -> do
-            let sh = fromIntegral $ BS.last bs
-            when (isSigHashUnknown sh) $
-                Left "Non-canonical signature: unknown hashtype byte"
-            when (isNothing (getSigHashForkId net) && hasForkIdFlag sh) $
-                Left "Non-canonical signature: invalid network for forkId"
-            return $ TxSignature sig sh
-        Nothing -> Left "Non-canonical signature: could not parse signature"
diff --git a/src/Network/Haskoin/Script/Standard.hs b/src/Network/Haskoin/Script/Standard.hs
deleted file mode 100644
--- a/src/Network/Haskoin/Script/Standard.hs
+++ /dev/null
@@ -1,178 +0,0 @@
-{-# LANGUAGE DeriveAnyClass    #-}
-{-# LANGUAGE DeriveGeneric     #-}
-{-# LANGUAGE OverloadedStrings #-}
-{-|
-Module      : Network.Haskoin.Script.Standard
-Copyright   : No rights reserved
-License     : UNLICENSE
-Maintainer  : jprupp@protonmail.ch
-Stability   : experimental
-Portability : POSIX
-
-Standard scripts like pay-to-public-key, pay-to-public-key-hash,
-pay-to-script-hash, pay-to-multisig and corresponding SegWit variants.
--}
-module Network.Haskoin.Script.Standard
-    ( ScriptInput(..)
-    , SimpleInput(..)
-    , RedeemScript
-    , encodeInput
-    , encodeInputBS
-    , decodeInput
-    , decodeInputBS
-    , sortMulSig
-    , scriptOpToInt
-    , isSpendPK
-    , isSpendPKHash
-    , isSpendMulSig
-    , isScriptHashInput
-    ) where
-
-import           Control.Applicative            ((<|>))
-import           Control.DeepSeq
-import           Control.Monad                  (guard, (<=<))
-import           Data.ByteString                (ByteString)
-import           Data.Function                  (on)
-import           Data.List                      (sortBy)
-import           Data.Serialize                 (decode, encode)
-import           GHC.Generics                   (Generic)
-import           Network.Haskoin.Constants
-import           Network.Haskoin.Keys.Common
-import           Network.Haskoin.Script.Common
-import           Network.Haskoin.Script.SigHash
-import           Network.Haskoin.Util
-
-
--- | Data type describing standard transaction input scripts. Input scripts
--- provide the signing data required to unlock the coins of the output they are
--- trying to spend, except in pay-to-witness-public-key-hash and
--- pay-to-script-hash transactions.
-data SimpleInput
-      -- | spend pay-to-public-key output
-    = SpendPK { getInputSig :: !TxSignature
-                  -- ^ transaction signature
-               }
-      -- | spend pay-to-public-key-hash output
-    | SpendPKHash { getInputSig :: !TxSignature
-                      -- ^ embedded signature
-                  , getInputKey :: !PubKeyI
-                      -- ^ public key
-                   }
-      -- | spend multisig output
-    | SpendMulSig { getInputMulSigKeys :: ![TxSignature]
-                      -- ^ list of signatures
-                   }
-    deriving (Eq, Show, Generic, NFData)
-
--- | Returns true if the input script is spending from a pay-to-public-key
--- output.
-isSpendPK :: ScriptInput -> Bool
-isSpendPK (RegularInput (SpendPK _)) = True
-isSpendPK _                          = False
-
--- | Returns true if the input script is spending from a pay-to-public-key-hash
--- output.
-isSpendPKHash :: ScriptInput -> Bool
-isSpendPKHash (RegularInput (SpendPKHash _ _)) = True
-isSpendPKHash _                                = False
-
--- | Returns true if the input script is spending a multisig output.
-isSpendMulSig :: ScriptInput -> Bool
-isSpendMulSig (RegularInput (SpendMulSig _)) = True
-isSpendMulSig _                              = False
-
--- | Returns true if the input script is spending a pay-to-script-hash output.
-isScriptHashInput :: ScriptInput -> Bool
-isScriptHashInput (ScriptHashInput _ _) = True
-isScriptHashInput _                     = False
-
--- | A redeem script is the output script serialized into the spending input
--- script. It must be included in inputs that spend pay-to-script-hash outputs.
-type RedeemScript = ScriptOutput
-
--- | Standard input script high-level representation.
-data ScriptInput
-    = RegularInput
-          { getRegularInput :: !SimpleInput
-            -- ^ get wrapped simple input
-          }
-    | ScriptHashInput
-          { getScriptHashInput  :: !SimpleInput
-            -- ^ get simple input associated with redeem script
-          , getScriptHashRedeem :: !RedeemScript
-            -- ^ redeem script
-          }
-    deriving (Eq, Show, Generic, NFData)
-
--- | Heuristic to decode an input script into one of the standard types.
-decodeSimpleInput :: Network -> Script -> Either String SimpleInput
-decodeSimpleInput net (Script ops) =
-    maybeToEither errMsg $ matchPK ops <|> matchPKHash ops <|> matchMulSig ops
-  where
-    matchPK [op] = SpendPK <$> f op
-    matchPK _    = Nothing
-    matchPKHash [op, OP_PUSHDATA pub _] =
-        SpendPKHash <$> f op <*> eitherToMaybe (decode pub)
-    matchPKHash _ = Nothing
-    matchMulSig (x:xs) = do
-        guard $ x == OP_0
-        SpendMulSig <$> mapM f xs
-    matchMulSig _ = Nothing
-    f OP_0                    = return TxSignatureEmpty
-    f (OP_PUSHDATA "" OPCODE) = f OP_0
-    f (OP_PUSHDATA bs _)      = eitherToMaybe $ decodeTxSig net bs
-    f _                       = Nothing
-    errMsg = "decodeInput: Could not decode script input"
-
--- | Heuristic to decode a 'ScriptInput' from a 'Script'. This function fails if
--- the script can not be parsed as a standard script input.
-decodeInput :: Network -> Script -> Either String ScriptInput
-decodeInput net s@(Script ops) =
-    maybeToEither errMsg $ matchSimpleInput <|> matchPayScriptHash
-  where
-    matchSimpleInput =
-        RegularInput <$> eitherToMaybe (decodeSimpleInput net s)
-    matchPayScriptHash =
-        case splitAt (length (scriptOps s) - 1) ops of
-            (is, [OP_PUSHDATA bs _]) -> do
-                rdm <- eitherToMaybe $ decodeOutputBS bs
-                inp <- eitherToMaybe $ decodeSimpleInput net $ Script is
-                return $ ScriptHashInput inp rdm
-            _ -> Nothing
-    errMsg = "decodeInput: Could not decode script input"
-
--- | Like 'decodeInput' but decodes directly from a serialized script
--- 'ByteString'.
-decodeInputBS :: Network -> ByteString -> Either String ScriptInput
-decodeInputBS net = decodeInput net <=< decode
-
--- | Encode a standard input into a script.
-encodeInput :: ScriptInput -> Script
-encodeInput s = case s of
-    RegularInput ri -> encodeSimpleInput ri
-    ScriptHashInput i o -> Script $
-        scriptOps (encodeSimpleInput i) ++ [opPushData $ encodeOutputBS o]
-
--- | Similar to 'encodeInput' but encodes directly to a serialized script
--- 'ByteString'.
-encodeInputBS :: ScriptInput -> ByteString
-encodeInputBS = encode . encodeInput
-
--- | Encode a standard 'SimpleInput' into opcodes as an input 'Script'.
-encodeSimpleInput :: SimpleInput -> Script
-encodeSimpleInput s =
-    Script $
-    case s of
-        SpendPK ts       -> [f ts]
-        SpendPKHash ts p -> [f ts, opPushData $ encode p]
-        SpendMulSig xs   -> OP_0 : map f xs
-  where
-    f TxSignatureEmpty = OP_0
-    f ts               = opPushData $ encodeTxSig ts
-
--- | Sort the public keys of a multisig output in ascending order by comparing
--- their compressed serialized representations. Refer to BIP-67.
-sortMulSig :: ScriptOutput -> ScriptOutput
-sortMulSig out = case out of
-    PayMulSig keys r -> PayMulSig (sortBy (compare `on` encode) keys) r
-    _ -> error "Can only call orderMulSig on PayMulSig scripts"
diff --git a/src/Network/Haskoin/Test.hs b/src/Network/Haskoin/Test.hs
deleted file mode 100644
--- a/src/Network/Haskoin/Test.hs
+++ /dev/null
@@ -1,23 +0,0 @@
-{-|
-Module      : Network.Haskoin.Test
-Copyright   : No rights reserved
-License     : UNLICENSE
-Maintainer  : jprupp@protonmail.ch
-Stability   : experimental
-Portability : POSIX
-
-Arbitrary instances for testing.
--}
-module Network.Haskoin.Test
-    ( module X
-    ) where
-
-import           Network.Haskoin.Test.Address     as X
-import           Network.Haskoin.Test.Block       as X
-import           Network.Haskoin.Test.Crypto      as X
-import           Network.Haskoin.Test.Keys        as X
-import           Network.Haskoin.Test.Message     as X
-import           Network.Haskoin.Test.Network     as X
-import           Network.Haskoin.Test.Script      as X
-import           Network.Haskoin.Test.Transaction as X
-import           Network.Haskoin.Test.Util        as X
diff --git a/src/Network/Haskoin/Test/Address.hs b/src/Network/Haskoin/Test/Address.hs
deleted file mode 100644
--- a/src/Network/Haskoin/Test/Address.hs
+++ /dev/null
@@ -1,25 +0,0 @@
-{-|
-Module      : Network.Haskoin.Test.Address
-Copyright   : No rights reserved
-License     : UNLICENSE
-Maintainer  : jprupp@protonmail.ch
-Stability   : experimental
-Portability : POSIX
--}
-module Network.Haskoin.Test.Address where
-
-import           Network.Haskoin.Address
-import           Network.Haskoin.Test.Crypto
-import           Test.QuickCheck
-
--- | Arbitrary pay-to-public-key-hash or pay-to-script-hash address.
-arbitraryAddress :: Gen Address
-arbitraryAddress = oneof [arbitraryPubKeyAddress, arbitraryScriptAddress]
-
--- | Arbitrary pay-to-public-key-hash address.
-arbitraryPubKeyAddress :: Gen Address
-arbitraryPubKeyAddress = p2pkhAddr <$> arbitraryHash160
-
--- | Arbitrary pay-to-script-hash address.
-arbitraryScriptAddress :: Gen Address
-arbitraryScriptAddress = p2shAddr <$> arbitraryHash160
diff --git a/src/Network/Haskoin/Test/Block.hs b/src/Network/Haskoin/Test/Block.hs
deleted file mode 100644
--- a/src/Network/Haskoin/Test/Block.hs
+++ /dev/null
@@ -1,69 +0,0 @@
-{-|
-Module      : Network.Haskoin.Test.Block
-Copyright   : No rights reserved
-License     : UNLICENSE
-Maintainer  : jprupp@protonmail.ch
-Stability   : experimental
-Portability : POSIX
--}
-module Network.Haskoin.Test.Block where
-
-import           Network.Haskoin.Block.Merkle
-import           Network.Haskoin.Block.Common
-import           Network.Haskoin.Constants
-import           Network.Haskoin.Test.Crypto
-import           Network.Haskoin.Test.Network
-import           Network.Haskoin.Test.Transaction
-import           Test.QuickCheck
-
--- | Block full or arbitrary transactions.
-arbitraryBlock :: Network -> Gen Block
-arbitraryBlock net = do
-    h   <- arbitraryBlockHeader
-    c   <- choose (0,10)
-    txs <- vectorOf c (arbitraryTx net)
-    return $ Block h txs
-
--- | Block header with random hash.
-arbitraryBlockHeader :: Gen BlockHeader
-arbitraryBlockHeader =
-    BlockHeader <$> arbitrary
-                      <*> arbitraryBlockHash
-                      <*> arbitraryHash256
-                      <*> arbitrary
-                      <*> arbitrary
-                      <*> arbitrary
-
--- | Arbitrary block hash.
-arbitraryBlockHash :: Gen BlockHash
-arbitraryBlockHash = BlockHash <$> arbitraryHash256
-
--- | Arbitrary 'GetBlocks' object with at least one block hash.
-arbitraryGetBlocks :: Gen GetBlocks
-arbitraryGetBlocks =
-    GetBlocks <$> arbitrary
-              <*> listOf1 arbitraryBlockHash
-              <*> arbitraryBlockHash
-
--- | Arbitrary 'GetHeaders' object with at least one block header.
-arbitraryGetHeaders :: Gen GetHeaders
-arbitraryGetHeaders =
-    GetHeaders <$> arbitrary
-               <*> listOf1 arbitraryBlockHash
-               <*> arbitraryBlockHash
-
--- | Arbitrary 'Headers' object with at least one block header.
-arbitraryHeaders :: Gen Headers
-arbitraryHeaders =
-    Headers <$> listOf1 ((,) <$> arbitraryBlockHeader <*> arbitraryVarInt)
-
--- | Arbitrary 'MerkleBlock' with at least one hash.
-arbitraryMerkleBlock :: Gen MerkleBlock
-arbitraryMerkleBlock = do
-    bh     <- arbitraryBlockHeader
-    ntx    <- arbitrary
-    hashes <- listOf1 arbitraryHash256
-    c      <- choose (1,10)
-    flags  <- vectorOf (c*8) arbitrary
-    return $ MerkleBlock bh ntx hashes flags
-
diff --git a/src/Network/Haskoin/Test/Crypto.hs b/src/Network/Haskoin/Test/Crypto.hs
deleted file mode 100644
--- a/src/Network/Haskoin/Test/Crypto.hs
+++ /dev/null
@@ -1,33 +0,0 @@
-{-|
-Module      : Network.Haskoin.Test.Crypto
-Copyright   : No rights reserved
-License     : UNLICENSE
-Maintainer  : jprupp@protonmail.ch
-Stability   : experimental
-Portability : POSIX
--}
-module Network.Haskoin.Test.Crypto where
-
-import           Network.Haskoin.Crypto.Hash
-import           Network.Haskoin.Test.Util
-import           Test.QuickCheck
-
--- | Arbitrary 160-bit hash.
-arbitraryHash160 :: Gen Hash160
-arbitraryHash160 =
-    ripemd160 <$> arbitraryBSn 20
-
--- | Arbitrary 256-bit hash.
-arbitraryHash256 :: Gen Hash256
-arbitraryHash256 =
-    sha256 <$> arbitraryBSn 32
-
--- | Arbitrary 512-bit hash.
-arbitraryHash512 :: Gen Hash512
-arbitraryHash512 =
-    sha512 <$> arbitraryBSn 64
-
--- | Arbitrary 32-bit checksum.
-arbitraryCheckSum32 :: Gen CheckSum32
-arbitraryCheckSum32 =
-    checkSum32 <$> arbitraryBSn 4
diff --git a/src/Network/Haskoin/Test/Keys.hs b/src/Network/Haskoin/Test/Keys.hs
deleted file mode 100644
--- a/src/Network/Haskoin/Test/Keys.hs
+++ /dev/null
@@ -1,85 +0,0 @@
-{-|
-Module      : Network.Haskoin.Test.Keys
-Copyright   : No rights reserved
-License     : UNLICENSE
-Maintainer  : jprupp@protonmail.ch
-Stability   : experimental
-Portability : POSIX
--}
-module Network.Haskoin.Test.Keys where
-
-import           Data.Bits                     (clearBit)
-import           Data.List                     (foldl')
-import           Data.Word                     (Word32)
-import           Network.Haskoin.Crypto
-import           Network.Haskoin.Keys.Common
-import           Network.Haskoin.Keys.Extended
-import           Network.Haskoin.Test.Crypto
-import           Test.QuickCheck
-
--- | Arbitrary private key with arbitrary compressed flag.
-arbitrarySecKeyI :: Gen SecKeyI
-arbitrarySecKeyI = wrapSecKey <$> arbitrary <*> arbitrary
-
--- | Arbitrary keypair, both either compressed or not.
-arbitraryKeyPair :: Gen (SecKeyI, PubKeyI)
-arbitraryKeyPair = do
-    k <- arbitrarySecKeyI
-    return (k, derivePubKeyI k)
-
--- | Arbitrary extended private key.
-arbitraryXPrvKey :: Gen XPrvKey
-arbitraryXPrvKey =
-    XPrvKey <$> arbitrary
-            <*> arbitrary
-            <*> arbitrary
-            <*> arbitraryHash256
-            <*> arbitrary
-
--- | Arbitrary extended public key with its corresponding private key.
-arbitraryXPubKey :: Gen (XPrvKey, XPubKey)
-arbitraryXPubKey = (\k -> (k, deriveXPubKey k)) <$> arbitraryXPrvKey
-
-{- Custom derivations -}
-
--- | Arbitrary derivation index with last bit unset.
-genIndex :: Gen Word32
-genIndex = (`clearBit` 31) <$> arbitrary
-
--- | Arbitrary BIP-32 path index. Can be hardened or not.
-arbitraryBip32PathIndex :: Gen Bip32PathIndex
-arbitraryBip32PathIndex =
-    oneof [ Bip32SoftIndex <$> genIndex
-          , Bip32HardIndex <$> genIndex
-          ]
-
--- | Arbitrary BIP-32 derivation path composed of only hardened derivations.
-arbitraryHardPath :: Gen HardPath
-arbitraryHardPath = foldl' (:|) Deriv <$> listOf genIndex
-
--- | Arbitrary BIP-32 derivation path composed of only non-hardened derivations.
-arbitrarySoftPath :: Gen SoftPath
-arbitrarySoftPath = foldl' (:/) Deriv <$> listOf genIndex
-
--- | Arbitrary derivation path composed of hardened and non-hardened derivations.
-arbitraryDerivPath :: Gen DerivPath
-arbitraryDerivPath = concatBip32Segments <$> listOf arbitraryBip32PathIndex
-
--- | Arbitrary parsed derivation path. Can contain 'ParsedPrv', 'ParsedPub' or
--- 'ParsedEmpty' elements.
-arbitraryParsedPath :: Gen ParsedPath
-arbitraryParsedPath =
-    oneof [ ParsedPrv <$> arbitraryDerivPath
-          , ParsedPub <$> arbitraryDerivPath
-          , ParsedEmpty <$> arbitraryDerivPath
-          ]
-
--- | Arbitrary message hash, private key, nonce and corresponding signature. The
--- signature is generated with a random message, random private key and a random
--- nonce.
-arbitrarySignature :: Gen (Hash256, SecKey, Sig)
-arbitrarySignature = do
-    m <- arbitraryHash256
-    key <- arbitrary
-    let sig = signHash key m
-    return (m, key, sig)
diff --git a/src/Network/Haskoin/Test/Message.hs b/src/Network/Haskoin/Test/Message.hs
deleted file mode 100644
--- a/src/Network/Haskoin/Test/Message.hs
+++ /dev/null
@@ -1,52 +0,0 @@
-{-|
-Module      : Network.Haskoin.Test.Message
-Copyright   : No rights reserved
-License     : UNLICENSE
-Maintainer  : jprupp@protonmail.ch
-Stability   : experimental
-Portability : POSIX
--}
-module Network.Haskoin.Test.Message where
-
-import           Network.Haskoin.Constants
-import           Network.Haskoin.Network.Message
-import           Network.Haskoin.Test.Block
-import           Network.Haskoin.Test.Crypto
-import           Network.Haskoin.Test.Network
-import           Network.Haskoin.Test.Transaction
-import           Test.QuickCheck
-
--- | Arbitrary 'MessageHeader'.
-arbitraryMessageHeader :: Gen MessageHeader
-arbitraryMessageHeader =
-    MessageHeader <$> arbitrary
-                  <*> arbitraryMessageCommand
-                  <*> arbitrary
-                  <*> arbitraryCheckSum32
-
--- | Arbitrary 'Message'.
-arbitraryMessage :: Network -> Gen Message
-arbitraryMessage net =
-    oneof
-        [ MVersion <$> arbitraryVersion
-        , return MVerAck
-        , MAddr <$> arbitraryAddr1
-        , MInv <$> arbitraryInv1
-        , MGetData <$> arbitraryGetData
-        , MNotFound <$> arbitraryNotFound
-        , MGetBlocks <$> arbitraryGetBlocks
-        , MGetHeaders <$> arbitraryGetHeaders
-        , MTx <$> arbitraryTx net
-        , MBlock <$> arbitraryBlock net
-        , MMerkleBlock <$> arbitraryMerkleBlock
-        , MHeaders <$> arbitraryHeaders
-        , return MGetAddr
-        , MFilterLoad <$> arbitraryFilterLoad
-        , MFilterAdd <$> arbitraryFilterAdd
-        , return MFilterClear
-        , MPing <$> arbitraryPing
-        , MPong <$> arbitraryPong
-        , MAlert <$> arbitraryAlert
-        , MReject <$> arbitraryReject
-        , return MSendHeaders
-        ]
diff --git a/src/Network/Haskoin/Test/Network.hs b/src/Network/Haskoin/Test/Network.hs
deleted file mode 100644
--- a/src/Network/Haskoin/Test/Network.hs
+++ /dev/null
@@ -1,178 +0,0 @@
-{-|
-Module      : Network.Haskoin.Test.Network
-Copyright   : No rights reserved
-License     : UNLICENSE
-Maintainer  : jprupp@protonmail.ch
-Stability   : experimental
-Portability : POSIX
--}
-module Network.Haskoin.Test.Network where
-
-import qualified Data.ByteString             as BS (empty, pack)
-import qualified Data.ByteString.Char8       as C8
-import           Data.Word                   (Word16, Word32)
-import           Network.Haskoin.Network
-import           Network.Haskoin.Test.Crypto
-import           Network.Haskoin.Test.Util
-import           Network.Socket              (SockAddr (..))
-import           Test.QuickCheck
-
--- | Arbitrary 'VarInt'.
-arbitraryVarInt :: Gen VarInt
-arbitraryVarInt = VarInt <$> arbitrary
-
--- | Arbitrary 'VarString'.
-arbitraryVarString :: Gen VarString
-arbitraryVarString = VarString <$> arbitraryBS
-
--- | Arbitrary 'NetworkAddress'.
-arbitraryNetworkAddress :: Gen NetworkAddress
-arbitraryNetworkAddress = do
-    s <- arbitrary
-    a <- arbitrary
-    p <- arbitrary
-    d <- oneof
-        [ do
-            b <- arbitrary
-            c <- arbitrary
-            d <- arbitrary
-            return $ SockAddrInet6 (fromIntegral p) 0 (a,b,c,d) 0
-        , return $ SockAddrInet (fromIntegral (p :: Word16)) a
-        ]
-    let n = sockToHostAddress d
-    return $ NetworkAddress s n
-
--- | Arbitrary 'NetworkAddressTime'.
-arbitraryNetworkAddressTime :: Gen (Word32, NetworkAddress)
-arbitraryNetworkAddressTime = (,) <$> arbitrary <*> arbitraryNetworkAddress
-
--- | Arbitrary 'InvType'.
-arbitraryInvType :: Gen InvType
-arbitraryInvType = elements [InvError, InvTx, InvBlock, InvMerkleBlock]
-
--- | Arbitrary 'InvVector'.
-arbitraryInvVector :: Gen InvVector
-arbitraryInvVector = InvVector <$> arbitraryInvType <*> arbitraryHash256
-
--- | Arbitrary non-empty 'Inv'.
-arbitraryInv1 :: Gen Inv
-arbitraryInv1 = Inv <$> listOf1 arbitraryInvVector
-
--- | Arbitrary 'Version'.
-arbitraryVersion :: Gen Version
-arbitraryVersion =
-    Version <$> arbitrary
-            <*> arbitrary
-            <*> arbitrary
-            <*> arbitraryNetworkAddress
-            <*> arbitraryNetworkAddress
-            <*> arbitrary
-            <*> arbitraryVarString
-            <*> arbitrary
-            <*> arbitrary
-
--- | Arbitrary non-empty 'Addr'.
-arbitraryAddr1 :: Gen Addr
-arbitraryAddr1 = Addr <$> listOf1 arbitraryNetworkAddressTime
-
--- | Arbitrary 'Alert' with random payload and signature. Signature is not
--- valid.
-arbitraryAlert :: Gen Alert
-arbitraryAlert = Alert <$> arbitraryVarString <*> arbitraryVarString
-
--- | Arbitrary 'Reject'.
-arbitraryReject :: Gen Reject
-arbitraryReject = do
-    m <- arbitraryMessageCommand
-    c <- arbitraryRejectCode
-    s <- arbitraryVarString
-    d <- oneof [ return BS.empty
-               , BS.pack <$> vectorOf 32 arbitrary
-               ]
-    return $ Reject m c s d
-
--- | Arbitrary 'RejectCode'.
-arbitraryRejectCode :: Gen RejectCode
-arbitraryRejectCode =
-    elements
-        [ RejectMalformed
-        , RejectInvalid
-        , RejectInvalid
-        , RejectDuplicate
-        , RejectNonStandard
-        , RejectDust
-        , RejectInsufficientFee
-        , RejectCheckpoint
-        ]
-
--- | Arbitrary non-empty 'GetData'.
-arbitraryGetData :: Gen GetData
-arbitraryGetData = GetData <$> listOf1 arbitraryInvVector
-
--- | Arbitrary 'NotFound'.
-arbitraryNotFound :: Gen NotFound
-arbitraryNotFound = NotFound <$> listOf1 arbitraryInvVector
-
--- | Arbitrary 'Ping'.
-arbitraryPing :: Gen Ping
-arbitraryPing = Ping <$> arbitrary
-
--- | Arbitrary 'Pong'.
-arbitraryPong :: Gen Pong
-arbitraryPong = Pong <$> arbitrary
-
--- | Arbitrary bloom filter flags.
-arbitraryBloomFlags :: Gen BloomFlags
-arbitraryBloomFlags =
-    elements
-        [ BloomUpdateNone
-        , BloomUpdateAll
-        , BloomUpdateP2PubKeyOnly
-        ]
-
--- | Arbitrary bloom filter with its corresponding number of elements
--- and false positive rate.
-arbitraryBloomFilter :: Gen (Int, Double, BloomFilter)
-arbitraryBloomFilter = do
-    n     <- choose (0,100000)
-    fp    <- choose (1e-8,1)
-    tweak <- arbitrary
-    fl    <- arbitraryBloomFlags
-    return (n, fp, bloomCreate n fp tweak fl)
-
--- | Arbitrary 'FilterLoad'.
-arbitraryFilterLoad :: Gen FilterLoad
-arbitraryFilterLoad = do
-    (_, _, bf) <- arbitraryBloomFilter
-    return $ FilterLoad bf
-
--- | Arbitrary 'FilterAdd'.
-arbitraryFilterAdd :: Gen FilterAdd
-arbitraryFilterAdd = FilterAdd <$> arbitraryBS
-
--- | Arbitrary 'MessageCommand'.
-arbitraryMessageCommand :: Gen MessageCommand
-arbitraryMessageCommand = do
-    ASCIIString str <- arbitrary
-    elements
-        [ MCVersion
-        , MCVerAck
-        , MCAddr
-        , MCInv
-        , MCGetData
-        , MCNotFound
-        , MCGetBlocks
-        , MCGetHeaders
-        , MCTx
-        , MCBlock
-        , MCMerkleBlock
-        , MCHeaders
-        , MCGetAddr
-        , MCFilterLoad
-        , MCFilterAdd
-        , MCFilterClear
-        , MCPing
-        , MCPong
-        , MCAlert
-        , MCOther (C8.take 12 (C8.pack (filter (/= '\NUL') str)))
-        ]
diff --git a/src/Network/Haskoin/Test/Script.hs b/src/Network/Haskoin/Test/Script.hs
deleted file mode 100644
--- a/src/Network/Haskoin/Test/Script.hs
+++ /dev/null
@@ -1,374 +0,0 @@
-{-# LANGUAGE LambdaCase #-}
-{-|
-Module      : Network.Haskoin.Test.Script
-Copyright   : No rights reserved
-License     : UNLICENSE
-Maintainer  : jprupp@protonmail.ch
-Stability   : experimental
-Portability : POSIX
--}
-module Network.Haskoin.Test.Script where
-
-import           Crypto.Secp256k1
-import           Data.Maybe
-import           Data.Word
-import           Network.Haskoin.Address
-import           Network.Haskoin.Constants
-import           Network.Haskoin.Keys.Common
-import           Network.Haskoin.Script
-import           Network.Haskoin.Test.Address
-import           Network.Haskoin.Test.Crypto
-import           Network.Haskoin.Test.Keys
-import           Network.Haskoin.Test.Util
-import           Network.Haskoin.Transaction.Common
-import           Network.Haskoin.Util
-import           Test.QuickCheck
-
--- | Arbitrary 'Script' with random script ops.
-arbitraryScript :: Gen Script
-arbitraryScript = Script <$> listOf arbitraryScriptOp
-
--- | Arbitrary 'ScriptOp' (push operations have random data).
-arbitraryScriptOp :: Gen ScriptOp
-arbitraryScriptOp =
-    oneof
-          -- Pushing Data
-        [ opPushData <$> arbitraryBS1
-        , return OP_0
-        , return OP_1NEGATE
-        , return OP_RESERVED
-        , return OP_1
-        , return OP_2
-        , return OP_3
-        , return OP_4
-        , return OP_5
-        , return OP_6
-        , return OP_7
-        , return OP_8
-        , return OP_9
-        , return OP_10
-        , return OP_11
-        , return OP_12
-        , return OP_13
-        , return OP_14
-        , return OP_15
-        , return OP_16
-        -- Flow control
-        , return OP_NOP
-        , return OP_VER
-        , return OP_IF
-        , return OP_NOTIF
-        , return OP_VERIF
-        , return OP_VERNOTIF
-        , return OP_ELSE
-        , return OP_ENDIF
-        , return OP_VERIFY
-        , return OP_RETURN
-        -- Stack operations
-        , return OP_TOALTSTACK
-        , return OP_FROMALTSTACK
-        , return OP_IFDUP
-        , return OP_DEPTH
-        , return OP_DROP
-        , return OP_DUP
-        , return OP_NIP
-        , return OP_OVER
-        , return OP_PICK
-        , return OP_ROLL
-        , return OP_ROT
-        , return OP_SWAP
-        , return OP_TUCK
-        , return OP_2DROP
-        , return OP_2DUP
-        , return OP_3DUP
-        , return OP_2OVER
-        , return OP_2ROT
-        , return OP_2SWAP
-        -- Splice
-        , return OP_CAT
-        , return OP_SUBSTR
-        , return OP_LEFT
-        , return OP_RIGHT
-        , return OP_SIZE
-        -- Bitwise logic
-        , return OP_INVERT
-        , return OP_AND
-        , return OP_OR
-        , return OP_XOR
-        , return OP_EQUAL
-        , return OP_EQUALVERIFY
-        , return OP_RESERVED1
-        , return OP_RESERVED2
-        -- Arithmetic
-        , return OP_1ADD
-        , return OP_1SUB
-        , return OP_2MUL
-        , return OP_2DIV
-        , return OP_NEGATE
-        , return OP_ABS
-        , return OP_NOT
-        , return OP_0NOTEQUAL
-        , return OP_ADD
-        , return OP_SUB
-        , return OP_MUL
-        , return OP_DIV
-        , return OP_MOD
-        , return OP_LSHIFT
-        , return OP_RSHIFT
-        , return OP_BOOLAND
-        , return OP_BOOLOR
-        , return OP_NUMEQUAL
-        , return OP_NUMEQUALVERIFY
-        , return OP_NUMNOTEQUAL
-        , return OP_LESSTHAN
-        , return OP_GREATERTHAN
-        , return OP_LESSTHANOREQUAL
-        , return OP_GREATERTHANOREQUAL
-        , return OP_MIN
-        , return OP_MAX
-        , return OP_WITHIN
-        -- Crypto
-        , return OP_RIPEMD160
-        , return OP_SHA1
-        , return OP_SHA256
-        , return OP_HASH160
-        , return OP_HASH256
-        , return OP_CODESEPARATOR
-        , return OP_CHECKSIG
-        , return OP_CHECKSIGVERIFY
-        , return OP_CHECKMULTISIG
-        , return OP_CHECKMULTISIGVERIFY
-        -- Expansion
-        , return OP_NOP1
-        , return OP_NOP2
-        , return OP_NOP3
-        , return OP_NOP4
-        , return OP_NOP5
-        , return OP_NOP6
-        , return OP_NOP7
-        , return OP_NOP8
-        , return OP_NOP9
-        , return OP_NOP10
-        -- Other
-        , return OP_PUBKEYHASH
-        , return OP_PUBKEY
-        , return $ OP_INVALIDOPCODE 0xff
-        ]
-
--- | Arbtirary 'ScriptOp' with a value in @[OP_1 .. OP_16]@.
-arbitraryIntScriptOp :: Gen ScriptOp
-arbitraryIntScriptOp =
-    elements
-        [ OP_1,  OP_2,  OP_3,  OP_4
-        , OP_5,  OP_6,  OP_7,  OP_8
-        , OP_9,  OP_10, OP_11, OP_12
-        , OP_13, OP_14, OP_15, OP_16
-        ]
-
--- | Arbitrary 'PushDataType'.
-arbitraryPushDataType :: Gen PushDataType
-arbitraryPushDataType = elements [OPCODE, OPDATA1, OPDATA2, OPDATA4]
-
--- | Arbitrary 'SigHash' (including invalid/unknown sighash codes).
-arbitrarySigHash :: Gen SigHash
-arbitrarySigHash = fromIntegral <$> (arbitrary :: Gen Word32)
-
--- | Arbitrary valid 'SigHash'.
-arbitraryValidSigHash :: Network -> Gen SigHash
-arbitraryValidSigHash net = do
-    sh <- elements [sigHashAll, sigHashNone, sigHashSingle]
-    f1 <-
-        elements $
-        if isJust (getSigHashForkId net)
-            then [id, setForkIdFlag]
-            else [id]
-    f2 <- elements [id, setAnyoneCanPayFlag]
-    return $ f1 $ f2 sh
-
--- | Arbitrary message hash, private key and corresponding 'TxSignature'. The
--- signature is generated deterministically using a random message and a random
--- private key.
-arbitraryTxSignature :: Network -> Gen (TxHash, SecKey, TxSignature)
-arbitraryTxSignature net = do
-    (m, key, sig) <- arbitrarySignature
-    sh <- (fromIntegral <$> (arbitrary :: Gen Word8)) `suchThat` filterBad
-    let txsig = TxSignature sig sh
-    return (TxHash m, key, txsig)
-  where
-    filterBad sh = not $
-        isSigHashUnknown sh ||
-        isNothing (getSigHashForkId net) && hasForkIdFlag sh
-
--- | Arbitrary transaction signature that could also be empty.
-arbitraryTxSignatureEmpty :: Network -> Gen TxSignature
-arbitraryTxSignatureEmpty net =
-    frequency [ (1, return TxSignatureEmpty)
-              , (10, lst3 <$> arbitraryTxSignature net)
-              ]
-
--- | Arbitrary m of n parameters.
-arbitraryMSParam :: Gen (Int, Int)
-arbitraryMSParam = do
-    m <- choose (1,16)
-    n <- choose (m,16)
-    return (m, n)
-
--- | Arbitrary 'ScriptOutput' (Can by any valid type).
-arbitraryScriptOutput :: Network -> Gen ScriptOutput
-arbitraryScriptOutput net =
-    oneof $
-    [ arbitraryPKOutput
-    , arbitraryPKHashOutput
-    , arbitraryMSOutput
-    , arbitrarySHOutput
-    , arbitraryDCOutput
-    ] ++
-    if getSegWit net
-        then [arbitraryWPKHashOutput, arbitraryWSHOutput]
-        else []
-
--- | Arbitrary 'ScriptOutput' of type 'PayPK', 'PayPKHash' or 'PayMS'
--- (Not 'PayScriptHash', 'DataCarrier', or SegWit)
-arbitrarySimpleOutput :: Gen ScriptOutput
-arbitrarySimpleOutput =
-    oneof
-        [ arbitraryPKOutput
-        , arbitraryPKHashOutput
-        , arbitraryMSOutput
-        ]
-
--- | Arbitrary 'ScriptOutput' of type 'PayPK'
-arbitraryPKOutput :: Gen ScriptOutput
-arbitraryPKOutput =  PayPK . snd <$> arbitraryKeyPair
-
--- | Arbitrary 'ScriptOutput' of type 'PayPKHash'
-arbitraryPKHashOutput :: Gen ScriptOutput
-arbitraryPKHashOutput = PayPKHash <$> arbitraryHash160
-
--- | Arbitrary 'PayWitnessPKHash' output.
-arbitraryWPKHashOutput :: Gen ScriptOutput
-arbitraryWPKHashOutput = PayWitnessPKHash <$> arbitraryHash160
-
--- | Arbitrary 'PayWitnessScriptHash' output.
-arbitraryWSHOutput :: Gen ScriptOutput
-arbitraryWSHOutput = PayWitnessScriptHash <$> arbitraryHash256
-
--- | Arbitrary 'ScriptOutput' of type 'PayMS'.
-arbitraryMSOutput :: Gen ScriptOutput
-arbitraryMSOutput = do
-    (m, n) <- arbitraryMSParam
-    keys <- map snd <$> vectorOf n arbitraryKeyPair
-    return $ PayMulSig keys m
-
--- | Arbitrary 'ScriptOutput' of type 'PayMS', only using compressed keys.
-arbitraryMSOutputC :: Gen ScriptOutput
-arbitraryMSOutputC = do
-    (m, n) <- arbitraryMSParam
-    keys <-
-        map snd <$>
-        vectorOf n (arbitraryKeyPair `suchThat` (pubKeyCompressed . snd))
-    return $ PayMulSig keys m
-
--- | Arbitrary 'ScriptOutput' of type 'PayScriptHash'.
-arbitrarySHOutput :: Gen ScriptOutput
-arbitrarySHOutput = PayScriptHash . getAddrHash160 <$> arbitraryScriptAddress
-
--- | Arbitrary 'ScriptOutput' of type 'DataCarrier'.
-arbitraryDCOutput :: Gen ScriptOutput
-arbitraryDCOutput = DataCarrier <$> arbitraryBS1
-
--- | Arbitrary 'ScriptInput'.
-arbitraryScriptInput :: Network -> Gen ScriptInput
-arbitraryScriptInput net =
-    oneof
-        [ arbitraryPKInput net
-        , arbitraryPKHashInput net
-        , arbitraryMSInput net
-        , arbitrarySHInput net
-        ]
-
--- | Arbitrary 'ScriptInput' of type 'SpendPK', 'SpendPKHash' or 'SpendMulSig'
--- (not 'ScriptHashInput')
-arbitrarySimpleInput :: Network -> Gen ScriptInput
-arbitrarySimpleInput net =
-    oneof
-        [ arbitraryPKInput net
-        , arbitraryPKHashInput net
-        , arbitraryMSInput net
-        ]
-
--- | Arbitrary 'ScriptInput' of type 'SpendPK'.
-arbitraryPKInput :: Network -> Gen ScriptInput
-arbitraryPKInput net = RegularInput . SpendPK <$> arbitraryTxSignatureEmpty net
-
--- | Arbitrary 'ScriptInput' of type 'SpendPK'.
-arbitraryPKHashInput :: Network -> Gen ScriptInput
-arbitraryPKHashInput net = do
-    sig <- arbitraryTxSignatureEmpty net
-    key <- snd <$> arbitraryKeyPair
-    return $ RegularInput $ SpendPKHash sig key
-
--- | Like 'arbitraryPKHashInput' without empty signatures.
-arbitraryPKHashInputFull :: Network -> Gen ScriptInput
-arbitraryPKHashInputFull net = do
-    sig <- lst3 <$> arbitraryTxSignature net
-    key <- snd <$> arbitraryKeyPair
-    return $ RegularInput $ SpendPKHash sig key
-
--- | Like above but only compressed.
-arbitraryPKHashInputFullC :: Network -> Gen ScriptInput
-arbitraryPKHashInputFullC net = do
-    sig <- lst3 <$> arbitraryTxSignature net
-    key <- fmap snd $ arbitraryKeyPair `suchThat` (pubKeyCompressed . snd)
-    return $ RegularInput $ SpendPKHash sig key
-
--- | Arbitrary 'ScriptInput' of type 'SpendMulSig'.
-arbitraryMSInput :: Network -> Gen ScriptInput
-arbitraryMSInput net = do
-    m    <- fst <$> arbitraryMSParam
-    sigs <- vectorOf m (arbitraryTxSignatureEmpty net)
-    return $ RegularInput $ SpendMulSig sigs
-
--- | Arbitrary 'ScriptInput' of type 'ScriptHashInput'.
-arbitrarySHInput :: Network -> Gen ScriptInput
-arbitrarySHInput net = do
-    i <- arbitrarySimpleInput net
-    ScriptHashInput (getRegularInput i) <$> arbitrarySimpleOutput
-
--- | Arbitrary 'ScriptInput' of type 'ScriptHashInput' containing a
--- 'RedeemScript' of type 'PayMulSig' and an input of type 'SpendMulSig'.
-arbitraryMulSigSHInput :: Network -> Gen ScriptInput
-arbitraryMulSigSHInput net =
-    arbitraryMSOutput >>= \case
-        rdm@(PayMulSig _ m) -> do
-            sigs <- vectorOf m (arbitraryTxSignatureEmpty net)
-            return $ ScriptHashInput (SpendMulSig sigs) rdm
-        _ -> undefined
-
--- | Arbitrary 'ScriptInput' of type 'ScriptHashInput' containing a
--- 'RedeemScript' of type 'PayMulSig' and an input of type 'SpendMulSig'.
-arbitraryMulSigSHInputC :: Network -> Gen ScriptInput
-arbitraryMulSigSHInputC net =
-    arbitraryMSOutputC >>= \case
-        rdm@(PayMulSig _ m) -> do
-          sigs <- vectorOf m (arbitraryTxSignatureEmpty net)
-          return $ ScriptHashInput (SpendMulSig sigs) rdm
-        _ -> undefined
-
--- | Like 'arbitraryMulSigSHCInput' with no empty signatures.
-arbitraryMulSigSHInputFull :: Network -> Gen ScriptInput
-arbitraryMulSigSHInputFull net =
-    arbitraryMSOutput >>= \case
-        rdm@(PayMulSig _ m) -> do
-            sigs <- map lst3 <$> vectorOf m (arbitraryTxSignature net)
-            return $ ScriptHashInput (SpendMulSig sigs) rdm
-        _ -> undefined
-
--- | Like 'arbitraryMulSigSHCInput' with no empty signatures.
-arbitraryMulSigSHInputFullC :: Network -> Gen ScriptInput
-arbitraryMulSigSHInputFullC net =
-    arbitraryMSOutputC >>= \case
-        rdm@(PayMulSig _ m) -> do
-            sigs <- map lst3 <$> vectorOf m (arbitraryTxSignature net)
-            return $ ScriptHashInput (SpendMulSig sigs) rdm
-        _ -> undefined
diff --git a/src/Network/Haskoin/Test/Transaction.hs b/src/Network/Haskoin/Test/Transaction.hs
deleted file mode 100644
--- a/src/Network/Haskoin/Test/Transaction.hs
+++ /dev/null
@@ -1,275 +0,0 @@
-{-|
-Module      : Network.Haskoin.Test.Transaction
-Copyright   : No rights reserved
-License     : UNLICENSE
-Maintainer  : jprupp@protonmail.ch
-Stability   : experimental
-Portability : POSIX
--}
-module Network.Haskoin.Test.Transaction where
-
-import           Control.Monad
-import qualified Data.ByteString             as BS
-import           Data.Either                 (fromRight)
-import           Data.List                   (nub, nubBy, permutations)
-import           Data.Word                   (Word64)
-import           Network.Haskoin.Address
-import           Network.Haskoin.Constants
-import           Network.Haskoin.Keys.Common
-import           Network.Haskoin.Script
-import           Network.Haskoin.Test.Crypto
-import           Network.Haskoin.Test.Keys
-import           Network.Haskoin.Test.Script
-import           Network.Haskoin.Test.Util
-import           Network.Haskoin.Transaction
-import           Test.QuickCheck
-
--- | Wrapped coin value for testing.
-newtype TestCoin = TestCoin { getTestCoin :: Word64 }
-    deriving (Eq, Show)
-
-instance Coin TestCoin where
-    coinValue = getTestCoin
-
--- | Arbitrary transaction hash (for non-existent transaction).
-arbitraryTxHash :: Gen TxHash
-arbitraryTxHash = TxHash <$> arbitraryHash256
-
--- | Arbitrary amount of Satoshi as 'Word64' (Between 1 and 21e14)
-arbitrarySatoshi :: Network -> Gen TestCoin
-arbitrarySatoshi net = TestCoin <$> choose (1, getMaxSatoshi net)
-
--- | Arbitrary 'OutPoint'.
-arbitraryOutPoint :: Gen OutPoint
-arbitraryOutPoint = OutPoint <$> arbitraryTxHash <*> arbitrary
-
--- | Arbitrary 'TxOut'.
-arbitraryTxOut :: Network -> Gen TxOut
-arbitraryTxOut net =
-    TxOut <$> (getTestCoin <$> arbitrarySatoshi net)
-          <*> (encodeOutputBS <$> arbitraryScriptOutput net)
-
--- | Arbitrary 'TxIn'.
-arbitraryTxIn :: Network -> Gen TxIn
-arbitraryTxIn net =
-    TxIn <$> arbitraryOutPoint
-         <*> (encodeInputBS <$> arbitraryScriptInput net)
-         <*> arbitrary
-
--- | Arbitrary transaction. Can be regular or with witnesses.
-arbitraryTx :: Network -> Gen Tx
-arbitraryTx net = oneof [arbitraryLegacyTx net, arbitraryWitnessTx net]
-
--- | Arbitrary regular transaction.
-arbitraryLegacyTx :: Network -> Gen Tx
-arbitraryLegacyTx net = arbitraryWLTx net False
-
--- | Arbitrary witness transaction (witness data is fake).
-arbitraryWitnessTx :: Network -> Gen Tx
-arbitraryWitnessTx net = arbitraryWLTx net True
-
--- | Arbitrary witness or legacy transaction.
-arbitraryWLTx :: Network -> Bool -> Gen Tx
-arbitraryWLTx net wit = do
-    ni <- choose (0, 5)
-    no <-
-        if wit
-            then choose (0, 5)
-            else choose
-                     ( if ni == 0
-                           then 2
-                           else 0
-                     , 5 -- avoid witness case
-                     )
-    inps <- vectorOf ni (arbitraryTxIn net)
-    outs <- vectorOf no (arbitraryTxOut net)
-    let uniqueInps = nubBy (\a b -> prevOutput a == prevOutput b) inps
-    w <- if wit then vectorOf (length uniqueInps) (listOf arbitraryBS) else return []
-    Tx <$> arbitrary <*> pure uniqueInps <*> pure outs <*> pure w <*> arbitrary
-
--- | Arbitrary transaction containing only inputs of type 'SpendPKHash',
--- 'SpendScriptHash' (multisig) and outputs of type 'PayPKHash' and 'PaySH'.
--- Only compressed public keys are used.
-arbitraryAddrOnlyTx :: Network -> Gen Tx
-arbitraryAddrOnlyTx net = do
-    ni <- choose (0, 5)
-    no <- choose (0, 5)
-    inps <- vectorOf ni (arbitraryAddrOnlyTxIn net)
-    outs <- vectorOf no (arbitraryAddrOnlyTxOut net)
-    Tx <$> arbitrary <*> pure inps <*> pure outs <*> pure [] <*> arbitrary
-
--- | Like 'arbitraryAddrOnlyTx' without empty signatures in the inputs.
-arbitraryAddrOnlyTxFull :: Network -> Gen Tx
-arbitraryAddrOnlyTxFull net = do
-    ni <- choose (0, 5)
-    no <- choose (0, 5)
-    inps <- vectorOf ni (arbitraryAddrOnlyTxInFull net)
-    outs <- vectorOf no (arbitraryAddrOnlyTxOut net)
-    Tx <$> arbitrary <*> pure inps <*> pure outs <*> pure [] <*> arbitrary
-
--- | Arbitrary TxIn that can only be of type 'SpendPKHash' or 'SpendScriptHash'
--- (multisig). Only compressed public keys are used.
-arbitraryAddrOnlyTxIn :: Network -> Gen TxIn
-arbitraryAddrOnlyTxIn net = do
-    inp <- oneof [arbitraryPKHashInput net, arbitraryMulSigSHInput net]
-    TxIn <$> arbitraryOutPoint <*> pure (encodeInputBS inp) <*> arbitrary
-
--- | like 'arbitraryAddrOnlyTxIn' with no empty signatures.
-arbitraryAddrOnlyTxInFull :: Network -> Gen TxIn
-arbitraryAddrOnlyTxInFull net = do
-    inp <-
-        oneof [arbitraryPKHashInputFullC net, arbitraryMulSigSHInputFullC net]
-    TxIn <$> arbitraryOutPoint <*> pure (encodeInputBS inp) <*> arbitrary
-
--- | Arbitrary 'TxOut' that can only be of type 'PayPKHash' or 'PaySH'.
-arbitraryAddrOnlyTxOut :: Network -> Gen TxOut
-arbitraryAddrOnlyTxOut net = do
-    v <- getTestCoin <$> arbitrarySatoshi net
-    out <- oneof [arbitraryPKHashOutput, arbitrarySHOutput]
-    return $ TxOut v $ encodeOutputBS out
-
--- | Arbitrary 'SigInput' with the corresponding private keys used
--- to generate the 'ScriptOutput' or 'RedeemScript'.
-arbitrarySigInput :: Network -> Gen (SigInput, [SecKeyI])
-arbitrarySigInput net =
-    oneof
-        [ wrapKey <$> arbitraryPKSigInput net
-        , wrapKey <$> arbitraryPKHashSigInput net
-        , arbitraryMSSigInput net
-        , arbitrarySHSigInput net
-        , wrapKey <$> arbitraryWPKHSigInput net
-        , arbitraryWSHSigInput net
-        ]
-
--- | Arbitrary 'SigInput' with a 'ScriptOutput' of type 'PayPK'.
-arbitraryPKSigInput :: Network -> Gen (SigInput, SecKeyI)
-arbitraryPKSigInput net = arbitraryAnyInput net False
-
--- | Arbitrary 'SigInput' with a 'ScriptOutput' of type 'PayPKHash'.
-arbitraryPKHashSigInput :: Network -> Gen (SigInput, SecKeyI)
-arbitraryPKHashSigInput net = arbitraryAnyInput net True
-
--- | Arbitrary 'SigInput'.
-arbitraryAnyInput :: Network -> Bool -> Gen (SigInput, SecKeyI)
-arbitraryAnyInput net pkh = do
-    (k, p) <- arbitraryKeyPair
-    let out | pkh = PayPKHash $ getAddrHash160 $ pubKeyAddr p
-            | otherwise = PayPK p
-    (val, op, sh) <- arbitraryInputStuff net
-    return (SigInput out val op sh Nothing, k)
-
--- | Arbitrary value, out point and sighash for an input.
-arbitraryInputStuff :: Network -> Gen (Word64, OutPoint, SigHash)
-arbitraryInputStuff net = do
-    val <- getTestCoin <$> arbitrarySatoshi net
-    op <- arbitraryOutPoint
-    sh <- arbitraryValidSigHash net
-    return (val, op, sh)
-
--- | Arbitrary 'SigInput' with a 'ScriptOutput' of type 'PayMulSig'.
-arbitraryMSSigInput :: Network -> Gen (SigInput, [SecKeyI])
-arbitraryMSSigInput net = do
-    (m, n) <- arbitraryMSParam
-    ks <- vectorOf n arbitraryKeyPair
-    let out = PayMulSig (map snd ks) m
-    (val, op, sh) <- arbitraryInputStuff net
-    perm <- choose (0, n - 1)
-    let ksPerm = map fst $ take m $ permutations ks !! perm
-    return (SigInput out val op sh Nothing, ksPerm)
-
--- | Arbitrary 'SigInput' with 'ScriptOutput' of type 'PaySH' and a
--- 'RedeemScript'.
-arbitrarySHSigInput :: Network -> Gen (SigInput, [SecKeyI])
-arbitrarySHSigInput net = do
-    (SigInput rdm val op sh _, ks) <- oneof
-        [ wrapKey <$> arbitraryPKSigInput net
-        , wrapKey <$> arbitraryPKHashSigInput net
-        , arbitraryMSSigInput net
-        ]
-    let out = PayScriptHash $ getAddrHash160 $ payToScriptAddress rdm
-    return (SigInput out val op sh $ Just rdm, ks)
-
-arbitraryWPKHSigInput :: Network -> Gen (SigInput, SecKeyI)
-arbitraryWPKHSigInput net = do
-    (k, p) <- arbitraryKeyPair
-    (val, op, sh) <- arbitraryInputStuff net
-    let out = PayWitnessPKHash . getAddrHash160 $ pubKeyAddr p
-    return (SigInput out val op sh Nothing, k)
-
-arbitraryWSHSigInput :: Network -> Gen (SigInput, [SecKeyI])
-arbitraryWSHSigInput net = do
-    (SigInput rdm val op sh _, ks) <- oneof
-        [ wrapKey <$> arbitraryPKSigInput net
-        , wrapKey <$> arbitraryPKHashSigInput net
-        , arbitraryMSSigInput net
-        ]
-    let out = PayWitnessScriptHash . getAddrHash256 $ payToWitnessScriptAddress rdm
-    return (SigInput out val op sh $ Just rdm, ks)
-
--- | Arbitrary 'Tx' (empty 'TxIn'), 'SigInputs' and private keys that can be
--- passed to 'signTx' or 'detSignTx' to fully sign the 'Tx'.
-arbitrarySigningData :: Network -> Gen (Tx, [SigInput], [SecKeyI])
-arbitrarySigningData net = do
-    v <- arbitrary
-    ni <- choose (1, 5)
-    no <- choose (1, 5)
-    sigis <- vectorOf ni (arbitrarySigInput net)
-    let uSigis = nubBy (\(a, _) (b, _) -> sigInputOP a == sigInputOP b) sigis
-    inps <- forM uSigis $ \(s, _) -> TxIn (sigInputOP s) BS.empty <$> arbitrary
-    outs <- vectorOf no (arbitraryTxOut net)
-    l <- arbitrary
-    perm <- choose (0, length inps - 1)
-    let tx = Tx v (permutations inps !! perm) outs [] l
-        keys = concatMap snd uSigis
-    return (tx, map fst uSigis, keys)
-
--- | Arbitrary transaction with empty inputs.
-arbitraryEmptyTx :: Network -> Gen Tx
-arbitraryEmptyTx net = do
-    v    <- arbitrary
-    no   <- choose (1,5)
-    ni   <- choose (1,5)
-    outs <- vectorOf no (arbitraryTxOut net)
-    ops  <- vectorOf ni arbitraryOutPoint
-    t    <- arbitrary
-    s    <- arbitrary
-    return $ Tx v (map (\op -> TxIn op BS.empty s) (nub ops)) outs [] t
-
--- | Arbitrary partially-signed transactions.
-arbitraryPartialTxs ::
-       Network -> Gen ([Tx], [(ScriptOutput, Word64, OutPoint, Int, Int)])
-arbitraryPartialTxs net = do
-    tx <- arbitraryEmptyTx net
-    res <-
-        forM (map prevOutput $ txIn tx) $ \op -> do
-            (so, val, rdmM, prvs, m, n) <- arbitraryData
-            txs <- mapM (singleSig so val rdmM tx op . secKeyData) prvs
-            return (txs, (so, val, op, m, n))
-    return (concatMap fst res, map snd res)
-  where
-    singleSig so val rdmM tx op prv = do
-        sh <- arbitraryValidSigHash net
-        let sigi = SigInput so val op sh rdmM
-        return . fromRight (error "Could not decode transaction") $
-            signTx net tx [sigi] [prv]
-    arbitraryData = do
-        (m, n) <- arbitraryMSParam
-        val <- getTestCoin <$> arbitrarySatoshi net
-        nPrv <- choose (m, n)
-        keys <- vectorOf n arbitraryKeyPair
-        perm <- choose (0, length keys - 1)
-        let pubKeys = map snd keys
-            prvKeys = take nPrv $ permutations (map fst keys) !! perm
-        let so = PayMulSig pubKeys m
-        elements
-            [ (so, val, Nothing, prvKeys, m, n)
-            , ( PayScriptHash $ getAddrHash160 $ payToScriptAddress so
-              , val
-              , Just so
-              , prvKeys
-              , m
-              , n)
-            ]
-
-wrapKey :: (SigInput, SecKeyI) -> (SigInput, [SecKeyI])
-wrapKey (s, k) = (s, [k])
diff --git a/src/Network/Haskoin/Test/Util.hs b/src/Network/Haskoin/Test/Util.hs
deleted file mode 100644
--- a/src/Network/Haskoin/Test/Util.hs
+++ /dev/null
@@ -1,40 +0,0 @@
-{-|
-Module      : Network.Haskoin.Test.Util
-Copyright   : No rights reserved
-License     : UNLICENSE
-Maintainer  : jprupp@protonmail.ch
-Stability   : experimental
-Portability : POSIX
--}
-module Network.Haskoin.Test.Util where
-
-import           Data.ByteString       (ByteString, pack)
-import           Data.Time.Clock       (UTCTime (..))
-import           Data.Time.Clock.POSIX (posixSecondsToUTCTime)
-import           Data.Word             (Word32)
-import           Test.QuickCheck
-
--- | Arbitrary strict 'ByteString'.
-arbitraryBS :: Gen ByteString
-arbitraryBS = pack <$> arbitrary
-
--- | Arbitrary non-empty strict ByteString
-arbitraryBS1 :: Gen ByteString
-arbitraryBS1 = pack <$> listOf1 arbitrary
-
--- | Arbitrary strict ByteString of a given length
-arbitraryBSn :: Int -> Gen ByteString
-arbitraryBSn n = pack <$> vectorOf n arbitrary
-
--- | Arbitrary UTCTime that generates dates after 01 Jan 1970 01:00:00 CET
-arbitraryUTCTime :: Gen UTCTime
-arbitraryUTCTime = do
-    w <- arbitrary :: Gen Word32
-    return $ posixSecondsToUTCTime $ realToFrac w
-
--- | Generate a Maybe from a Gen a
-arbitraryMaybe :: Gen a -> Gen (Maybe a)
-arbitraryMaybe g = frequency [ (1, return Nothing)
-                             , (5, Just <$> g)
-                             ]
-
diff --git a/src/Network/Haskoin/Transaction.hs b/src/Network/Haskoin/Transaction.hs
deleted file mode 100644
--- a/src/Network/Haskoin/Transaction.hs
+++ /dev/null
@@ -1,17 +0,0 @@
-{-|
-Module      : Network.Haskoin.Transaction
-Copyright   : No rights reserved
-License     : UNLICENSE
-Maintainer  : jprupp@protonmail.ch
-Stability   : experimental
-Portability : POSIX
-
-Transactions and related code.
--}
-module Network.Haskoin.Transaction
-    ( module Common
-    , module Builder
-    ) where
-
-import           Network.Haskoin.Transaction.Builder as Builder
-import           Network.Haskoin.Transaction.Common  as Common
diff --git a/src/Network/Haskoin/Transaction/Builder.hs b/src/Network/Haskoin/Transaction/Builder.hs
deleted file mode 100644
--- a/src/Network/Haskoin/Transaction/Builder.hs
+++ /dev/null
@@ -1,474 +0,0 @@
-{-# LANGUAGE LambdaCase        #-}
-{-# LANGUAGE OverloadedStrings #-}
-{-|
-Module      : Network.Haskoin.Transaction.Builder
-Copyright   : No rights reserved
-License     : UNLICENSE
-Maintainer  : jprupp@protonmail.ch
-Stability   : experimental
-Portability : POSIX
-
-Code to simplify transaction creation, signing, fee calculation and coin
-selection.
--}
-module Network.Haskoin.Transaction.Builder
-    ( -- * Transaction Creation & Signing
-      buildAddrTx
-    , buildTx
-    , buildInput
-    , SigInput(..)
-    , signTx
-    , signNestedWitnessTx
-    , makeSignature
-    , signInput
-    , signNestedInput
-    , verifyStdTx
-    , mergeTxs
-    , sigKeys
-    , mergeTxInput
-    , findSigInput
-    , verifyStdInput
-      -- * Coin Selection
-    , Coin(..)
-    , chooseCoins
-    , chooseCoinsSink
-    , chooseMSCoins
-    , chooseMSCoinsSink
-    , countMulSig
-    , greedyAddSink
-    , guessTxFee
-    , guessMSTxFee
-    , guessTxSize
-    , guessMSSize
-    ) where
-
-import           Control.Applicative                      ((<|>))
-import           Control.Arrow                            (first)
-import           Control.Monad                            (foldM, unless)
-import           Control.Monad.Identity                   (runIdentity)
-import           Crypto.Secp256k1
-import qualified Data.ByteString                          as B
-import           Data.Conduit                             (ConduitT, Void,
-                                                           await, runConduit,
-                                                           (.|))
-import           Data.Conduit.List                        (sourceList)
-import           Data.Either                              (fromRight)
-import           Data.List                                (nub)
-import           Data.Maybe                               (catMaybes, fromJust,
-                                                           isJust)
-import           Data.Serialize                           (decode, encode)
-import           Data.String.Conversions                  (cs)
-import           Data.Text                                (Text)
-import           Data.Word                                (Word64)
-import           Network.Haskoin.Address
-import           Network.Haskoin.Constants
-import           Network.Haskoin.Crypto.Hash              (Hash256, addressHash)
-import           Network.Haskoin.Crypto.Signature
-import           Network.Haskoin.Keys.Common
-import           Network.Haskoin.Network.Common
-import           Network.Haskoin.Script
-import           Network.Haskoin.Transaction.Builder.Sign (SigInput (..),
-                                                           buildInput,
-                                                           makeSigHash,
-                                                           makeSignature,
-                                                           sigKeys)
-import qualified Network.Haskoin.Transaction.Builder.Sign as S
-import           Network.Haskoin.Transaction.Common
-import           Network.Haskoin.Transaction.Segwit       (decodeWitnessInput,
-                                                           isSegwit,
-                                                           viewWitnessProgram)
-import           Network.Haskoin.Util
-
--- | Any type can be used as a Coin if it can provide a value in Satoshi.
--- The value is used in coin selection algorithms.
-class Coin c where
-    coinValue :: c -> Word64
-
--- | Coin selection algorithm for normal (non-multisig) transactions. This
--- function returns the selected coins together with the amount of change to
--- send back to yourself, taking the fee into account.
-chooseCoins :: Coin c
-            => Word64 -- ^ value to send
-            -> Word64 -- ^ fee per byte
-            -> Int    -- ^ number of outputs (including change)
-            -> Bool   -- ^ try to find better solutions
-            -> [c]    -- ^ list of ordered coins to choose from
-            -> Either String ([c], Word64)
-            -- ^ coin selection and change
-chooseCoins target fee nOut continue coins =
-    runIdentity . runConduit $
-    sourceList coins .| chooseCoinsSink target fee nOut continue
-
--- | Coin selection algorithm for normal (non-multisig) transactions. This
--- function returns the selected coins together with the amount of change to
--- send back to yourself, taking the fee into account. This version uses a Sink
--- for conduit-based coin selection.
-chooseCoinsSink :: (Monad m, Coin c)
-                => Word64 -- ^ value to send
-                -> Word64 -- ^ fee per byte
-                -> Int    -- ^ number of outputs (including change)
-                -> Bool   -- ^ try to find better solution
-                -> ConduitT c Void m (Either String ([c], Word64))
-                -- ^ coin selection and change
-chooseCoinsSink target fee nOut continue
-    | target > 0 =
-        maybeToEither err <$>
-            greedyAddSink target (guessTxFee fee nOut) continue
-    | otherwise = return $ Left "chooseCoins: Target must be > 0"
-  where
-    err = "chooseCoins: No solution found"
-
--- | Coin selection algorithm for multisig transactions. This function returns
--- the selected coins together with the amount of change to send back to
--- yourself, taking the fee into account. This function assumes all the coins
--- are script hash outputs that send funds to a multisignature address.
-chooseMSCoins :: Coin c
-              => Word64     -- ^ value to send
-              -> Word64     -- ^ fee per byte
-              -> (Int, Int) -- ^ m of n multisig
-              -> Int        -- ^ number of outputs (including change)
-              -> Bool       -- ^ try to find better solution
-              -> [c]
-              -> Either String ([c], Word64)
-              -- ^ coin selection change amount
-chooseMSCoins target fee ms nOut continue coins =
-    runIdentity . runConduit $
-        sourceList coins .| chooseMSCoinsSink target fee ms nOut continue
-
--- | Coin selection algorithm for multisig transactions. This function returns
--- the selected coins together with the amount of change to send back to
--- yourself, taking the fee into account. This function assumes all the coins
--- are script hash outputs that send funds to a multisignature address. This
--- version uses a Sink if you need conduit-based coin selection.
-chooseMSCoinsSink :: (Monad m, Coin c)
-                  => Word64     -- ^ value to send
-                  -> Word64     -- ^ fee per byte
-                  -> (Int, Int) -- ^ m of n multisig
-                  -> Int        -- ^ number of outputs (including change)
-                  -> Bool       -- ^ try to find better solution
-                  -> ConduitT c Void m (Either String ([c], Word64))
-                  -- ^ coin selection and change
-chooseMSCoinsSink target fee ms nOut continue
-    | target > 0 =
-        maybeToEither err <$>
-            greedyAddSink target (guessMSTxFee fee ms nOut) continue
-    | otherwise = return $ Left "chooseMSCoins: Target must be > 0"
-  where
-    err = "chooseMSCoins: No solution found"
-
--- | Select coins greedily by starting from an empty solution. If the 'continue'
--- flag is set, the algorithm will try to find a better solution in the stream
--- after a solution is found. If the next solution found is not strictly better
--- than the previously found solution, the algorithm stops and returns the
--- previous solution. If the continue flag is not set, the algorithm will return
--- the first solution it finds in the stream.
-greedyAddSink :: (Monad m, Coin c)
-              => Word64          -- ^ value to send
-              -> (Int -> Word64) -- ^ coin count to fee function
-              -> Bool            -- ^ try to find better solutions
-              -> ConduitT c Void m (Maybe ([c], Word64))
-              -- ^ coin selection and change
-greedyAddSink target guessFee continue =
-    go [] 0 [] 0
-  where
-    -- The goal is the value we must reach (including the fee) for a certain
-    -- amount of selected coins.
-    goal c = target + guessFee c
-    go acc aTot ps pTot = await >>= \case
-        -- A coin is available in the stream
-        Just coin -> do
-            let val = coinValue coin
-            -- We have reached the goal using this coin
-            if val + aTot >= goal (length acc + 1)
-                -- If we want to continue searching for better solutions
-                then if continue
-                    -- This solution is the first one or
-                    -- This solution is better than the previous one
-                    then if pTot == 0 || val + aTot < pTot
-                        -- Continue searching for better solutions in the stream
-                        then go [] 0 (coin:acc) (val + aTot)
-                        -- Otherwise, we stop here and return the previous
-                        -- solution
-                        else return $ Just (ps, pTot - goal (length ps))
-                    -- Otherwise, return this solution
-                    else return $
-                        Just (coin : acc, val + aTot - goal (length acc + 1))
-                -- We have not yet reached the goal. Add the coin to the
-                -- accumulator
-                else go (coin:acc) (val + aTot) ps pTot
-        -- We reached the end of the stream
-        Nothing ->
-            return $ if null ps
-                -- If no solution was found, return Nothing
-                then Nothing
-                -- If we have a solution, return it
-                else Just (ps, pTot - goal (length ps))
-
--- | Estimate tranasction fee to pay based on transaction size estimation.
-guessTxFee :: Word64 -> Int -> Int -> Word64
-guessTxFee byteFee nOut nIn =
-    byteFee * fromIntegral (guessTxSize nIn [] nOut 0)
-
--- | Same as 'guessTxFee' but for multisig transactions.
-guessMSTxFee :: Word64 -> (Int, Int) -> Int -> Int -> Word64
-guessMSTxFee byteFee ms nOut nIn =
-    byteFee * fromIntegral (guessTxSize 0 (replicate nIn ms) nOut 0)
-
--- | Computes an upper bound on the size of a transaction based on some known
--- properties of the transaction.
-guessTxSize :: Int         -- ^ number of regular transaction inputs
-            -> [(Int,Int)] -- ^ multisig m of n for each input
-            -> Int         -- ^ number of P2PKH outputs
-            -> Int         -- ^ number of P2SH outputs
-            -> Int         -- ^ upper bound on transaction size
-guessTxSize pki msi pkout msout =
-    8 + inpLen + inp + outLen + out
-  where
-    inpLen = B.length $ encode $ VarInt $ fromIntegral $ length msi + pki
-    outLen = B.length $ encode $ VarInt $ fromIntegral $ pkout + msout
-    inp = pki * 148 + sum (map guessMSSize msi)
-             -- (20: hash160) + (5: opcodes) +
-             -- (1: script len) + (8: Word64)
-    out =
-        pkout * 34 +
-             -- (20: hash160) + (3: opcodes) +
-             -- (1: script len) + (8: Word64)
-        msout * 32
-
--- | Size of a multisig P2SH input.
-guessMSSize :: (Int,Int) -> Int
-guessMSSize (m, n)
-    -- OutPoint (36) + Sequence (4) + Script
- = 40 + fromIntegral (B.length $ encode $ VarInt $ fromIntegral scp) + scp
-    -- OP_M + n*PubKey + OP_N + OP_CHECKMULTISIG
-  where
-    rdm =
-        fromIntegral $
-        B.length $ encode $ opPushData $ B.replicate (n * 34 + 3) 0
-    -- Redeem + m*sig + OP_0
-    scp = rdm + m * 73 + 1
-
-{- Build a new Tx -}
-
--- | Build a transaction by providing a list of outpoints as inputs
--- and a list of recipient addresses and amounts as outputs.
-buildAddrTx :: Network -> [OutPoint] -> [(Text, Word64)] -> Either String Tx
-buildAddrTx net xs ys = buildTx xs =<< mapM f ys
-  where
-    f (s, v) =
-        maybe (Left ("buildAddrTx: Invalid address " ++ cs s)) Right $ do
-            a <- stringToAddr net s
-            let o = addressToOutput a
-            return (o, v)
-
--- | Build a transaction by providing a list of outpoints as inputs
--- and a list of 'ScriptOutput' and amounts as outputs.
-buildTx :: [OutPoint] -> [(ScriptOutput, Word64)] -> Either String Tx
-buildTx xs ys =
-    mapM fo ys >>= \os -> return $ Tx 1 (map fi xs) os [] 0
-  where
-    fi outPoint = TxIn outPoint B.empty maxBound
-    fo (o, v)
-        | v <= 2100000000000000 = return $ TxOut v $ encodeOutputBS o
-        | otherwise =
-            Left $ "buildTx: Invalid amount " ++ show v
-
--- | Sign a transaction by providing the 'SigInput' signing parameters and a
--- list of private keys. The signature is computed deterministically as defined
--- in RFC-6979.
---
--- Example: P2SH-P2WKH
---
--- > sigIn = SigInput (PayWitnessPKHash h) 100000 op sigHashAll Nothing
--- > signedTx = signTx btc unsignedTx [sigIn] [key]
---
--- Example: P2SH-P2WSH multisig
---
--- > sigIn = SigInput (PayWitnessScriptHash h) 100000 op sigHashAll (Just $ PayMulSig [p1,p2,p3] 2)
--- > signedTx = signTx btc unsignedTx [sigIn] [k1,k3]
-signTx :: Network
-       -> Tx               -- ^ transaction to sign
-       -> [SigInput]       -- ^ signing parameters
-       -> [SecKey]         -- ^ private keys to sign with
-       -> Either String Tx -- ^ signed transaction
-signTx net tx si = S.signTx net tx $ notNested <$> si
-  where notNested s = (s, False)
-
--- | This function differs from 'signTx' by assuming all segwit inputs are
--- P2SH-nested.  Use the same signing parameters for segwit inputs as in 'signTx'.
-signNestedWitnessTx :: Network
-                    -> Tx               -- ^ transaction to sign
-                    -> [SigInput]       -- ^ signing parameters
-                    -> [SecKey]         -- ^ private keys to sign with
-                    -> Either String Tx -- ^ signed transaction
-signNestedWitnessTx net tx si = S.signTx net tx $ nested <$> si
-  where
-    -- NOTE: the nesting flag is ignored for non-segwit inputs
-    nested s = (s, True)
-
-
--- | Sign a single input in a transaction deterministically (RFC-6979).
-signInput :: Network -> Tx -> Int -> SigInput -> SecKeyI -> Either String Tx
-signInput net tx i si = S.signInput net tx i (si, False)
-
--- | Like 'signInput' but treat segwit inputs as nested
-signNestedInput :: Network -> Tx -> Int -> SigInput -> SecKeyI -> Either String Tx
-signNestedInput net tx i si = S.signInput net tx i (si, True)
-
--- | Order the 'SigInput' with respect to the transaction inputs. This allows
--- the user to provide the 'SigInput' in any order. Users can also provide only
--- a partial set of 'SigInput' entries.
-findSigInput :: [SigInput] -> [TxIn] -> [(SigInput, Int)]
-findSigInput = S.findInputIndex sigInputOP
-
-{- Merge multisig transactions -}
-
--- | Merge partially-signed multisig transactions.  This function does not
--- support segwit and P2SH-segwit inputs.  Use PSBTs to merge transactions with
--- segwit inputs.
-mergeTxs :: Network -> [Tx] -> [(ScriptOutput, Word64, OutPoint)] -> Either String Tx
-mergeTxs net txs os
-    | null txs = Left "Transaction list is empty"
-    | length (nub emptyTxs) /= 1 = Left "Transactions do not match"
-    | length txs == 1 = return $ head txs
-    | otherwise = foldM (mergeTxInput net txs) (head emptyTxs) outs
-  where
-    zipOp = zip (matchTemplate os (txIn $ head txs) f) [0..]
-    outs = map (first $ (\(o,v,_) -> (o,v)) . fromJust) $ filter (isJust . fst) zipOp
-    f (_, _, o) txin = o == prevOutput txin
-    emptyTxs = map (\tx -> foldl clearInput tx outs) txs
-    ins is i = updateIndex i is (\ti -> ti{ scriptInput = B.empty })
-    clearInput tx (_, i) =
-        Tx (txVersion tx) (ins (txIn tx) i) (txOut tx) [] (txLockTime tx)
-
--- | Merge input from partially-signed multisig transactions.  This function
--- does not support segwit and P2SH-segwit inputs.
-mergeTxInput ::
-       Network
-    -> [Tx]
-    -> Tx
-    -> ((ScriptOutput, Word64), Int)
-    -> Either String Tx
-mergeTxInput net txs tx ((so, val), i)
-    -- Ignore transactions with empty inputs
- = do
-    let ins = map (scriptInput . (!! i) . txIn) txs
-    sigRes <- mapM extractSigs $ filter (not . B.null) ins
-    let rdm = snd $ head sigRes
-    unless (all (== rdm) $ map snd sigRes) $ Left "Redeem scripts do not match"
-    si <- encodeInputBS <$> go (nub $ concatMap fst sigRes) so rdm
-    let ins' = updateIndex i (txIn tx) (\ti -> ti {scriptInput = si})
-    return $ Tx (txVersion tx) ins' (txOut tx) [] (txLockTime tx)
-  where
-    go allSigs out rdmM =
-        case out of
-            PayMulSig msPubs r ->
-                let sigs =
-                        take r $
-                        catMaybes $ matchTemplate allSigs msPubs $ f out
-                 in return $ RegularInput $ SpendMulSig sigs
-            PayScriptHash _ ->
-                case rdmM of
-                    Just rdm -> do
-                        si <- go allSigs rdm Nothing
-                        return $ ScriptHashInput (getRegularInput si) rdm
-                    _ -> Left "Invalid output script type"
-            _ -> Left "Invalid output script type"
-    extractSigs si =
-        case decodeInputBS net si of
-            Right (RegularInput (SpendMulSig sigs)) -> Right (sigs, Nothing)
-            Right (ScriptHashInput (SpendMulSig sigs) rdm) ->
-                Right (sigs, Just rdm)
-            _ -> Left "Invalid script input type"
-    f out (TxSignature x sh) p =
-        verifyHashSig
-            (txSigHash net tx (encodeOutput out) val i sh)
-            x
-            (pubKeyPoint p)
-    f _ TxSignatureEmpty _ = False
-
-{- Tx verification -}
-
--- | Verify if a transaction is valid and all of its inputs are standard.
-verifyStdTx :: Network -> Tx -> [(ScriptOutput, Word64, OutPoint)] -> Bool
-verifyStdTx net tx xs =
-    not (null (txIn tx)) && all go (zip (matchTemplate xs (txIn tx) f) [0 ..])
-  where
-    f (_, _, o) txin = o == prevOutput txin
-    go (Just (so, val, _), i) = verifyStdInput net tx i so val
-    go _                      = False
-
--- | Verify if a transaction input is valid and standard.
-verifyStdInput :: Network -> Tx -> Int -> ScriptOutput -> Word64 -> Bool
-verifyStdInput net tx i so0 val
-    | isSegwit so0 = fromRight False $ (inp == mempty &&) . verifySegwitInput so0 <$> wp so0
-    | otherwise    = fromRight False
-                   $ verifyLegacyInput so0 <$> decodeInputBS net inp
-                 <|> (nestedScriptOutput >>= \so -> verifyNestedInput so0 so <$> wp so)
-  where
-    verifyLegacyInput so si = case (so, si) of
-        (PayPK pub, RegularInput (SpendPK (TxSignature sig sh))) ->
-            verifyHashSig (theTxSigHash so sh Nothing) sig (pubKeyPoint pub)
-        (PayPKHash h, RegularInput (SpendPKHash (TxSignature sig sh) pub)) ->
-            pubKeyAddr pub == p2pkhAddr h &&
-            verifyHashSig (theTxSigHash so sh Nothing) sig (pubKeyPoint pub)
-        (PayMulSig pubs r, RegularInput (SpendMulSig sigs)) ->
-            countMulSig net tx out val i (pubKeyPoint <$> pubs) sigs == r
-        (PayScriptHash h, ScriptHashInput si' rdm) ->
-            payToScriptAddress rdm == p2shAddr h && verifyLegacyInput rdm (RegularInput si')
-        _ -> False
-      where out = encodeOutput so
-
-    verifySegwitInput so (rdm, si) = case (so, rdm, si) of
-        (PayWitnessPKHash h, Nothing, SpendPKHash (TxSignature sig sh) pub) ->
-            pubKeyWitnessAddr pub == p2wpkhAddr h &&
-            verifyHashSig (theTxSigHash so sh Nothing) sig (pubKeyPoint pub)
-        (PayWitnessScriptHash h, Just rdm@(PayPK pub), SpendPK (TxSignature sig sh)) ->
-            payToWitnessScriptAddress rdm == p2wshAddr h &&
-            verifyHashSig (theTxSigHash so sh $ Just rdm) sig (pubKeyPoint pub)
-        (PayWitnessScriptHash h, Just rdm@(PayPKHash kh), SpendPKHash (TxSignature sig sh) pub) ->
-            payToWitnessScriptAddress rdm == p2wshAddr h &&
-            addressHash (encode pub) == kh &&
-            verifyHashSig (theTxSigHash so sh $ Just rdm) sig (pubKeyPoint pub)
-        (PayWitnessScriptHash h, Just rdm@(PayMulSig pubs r), SpendMulSig sigs) ->
-            payToWitnessScriptAddress rdm == p2wshAddr h &&
-            countMulSig' (\sh -> theTxSigHash so sh $ Just rdm) (pubKeyPoint <$> pubs) sigs == r
-        _ -> False
-      where out = encodeOutput so
-
-    verifyNestedInput so so' x = case so of
-        PayScriptHash h -> payToScriptAddress so' == p2shAddr h && verifySegwitInput so' x
-        _               -> False
-
-    inp             = scriptInput $ txIn tx !! i
-    theTxSigHash so = makeSigHash net tx i so val
-
-    ws | length (txWitness tx) > i = txWitness tx !! i
-       | otherwise                 = []
-    wp so = decodeWitnessInput net =<< viewWitnessProgram net so ws
-
-    nestedScriptOutput = scriptOps <$> decode inp >>= \case
-        [OP_PUSHDATA bs _] -> decodeOutputBS bs
-        _                  -> Left "nestedScriptOutput: not a nested output"
-
--- | Count the number of valid signatures for a multi-signature transaction.
-countMulSig ::
-       Network
-    -> Tx
-    -> Script
-    -> Word64
-    -> Int
-    -> [PubKey]
-    -> [TxSignature]
-    -> Int
-countMulSig net tx out val i = countMulSig' h
-  where
-    h = txSigHash net tx out val i
-
-countMulSig' :: (SigHash -> Hash256) -> [PubKey] -> [TxSignature] -> Int
-countMulSig' h [] _ = 0
-countMulSig' h _ [] = 0
-countMulSig' h (_:pubs) (TxSignatureEmpty:sigs) = countMulSig' h pubs sigs
-countMulSig' h (pub:pubs) sigs@(TxSignature sig sh : sigs')
-    | verifyHashSig (h sh) sig pub = 1 + countMulSig' h pubs sigs'
-    | otherwise                    = countMulSig' h pubs sigs
diff --git a/src/Network/Haskoin/Transaction/Builder/Sign.hs b/src/Network/Haskoin/Transaction/Builder/Sign.hs
deleted file mode 100644
--- a/src/Network/Haskoin/Transaction/Builder/Sign.hs
+++ /dev/null
@@ -1,271 +0,0 @@
-{-# LANGUAGE DeriveAnyClass    #-}
-{-# LANGUAGE DeriveGeneric     #-}
-{-# LANGUAGE LambdaCase        #-}
-{-# LANGUAGE OverloadedStrings #-}
-{-|
-Module      : Network.Haskoin.Transaction.Builder.Sign
-Copyright   : No rights reserved
-License     : UNLICENSE
-Maintainer  : jprupp@protonmail.ch
-Stability   : experimental
-Portability : POSIX
-
-Types and logic for signing transactions.
--}
-module Network.Haskoin.Transaction.Builder.Sign
-    ( SigInput (..)
-    , makeSignature
-    , makeSigHash
-    , signTx
-    , findInputIndex
-    , signInput
-    , buildInput
-    , sigKeys
-    ) where
-
-import           Control.DeepSeq                    (NFData)
-import           Control.Monad                      (foldM, mzero, when)
-import           Data.Aeson                         (FromJSON, ToJSON,
-                                                     Value (Object), object,
-                                                     parseJSON, toJSON, (.:),
-                                                     (.:?), (.=))
-import           Data.Either                        (rights)
-import           Data.Hashable                      (Hashable)
-import           Data.List                          (find, nub)
-import           Data.Maybe                         (catMaybes, fromMaybe,
-                                                     mapMaybe, maybeToList)
-import qualified Data.Serialize                     as S
-import           Data.Word                          (Word64)
-import           GHC.Generics                       (Generic)
-
-import           Network.Haskoin.Address            (getAddrHash160, pubKeyAddr)
-import           Network.Haskoin.Constants          (Network)
-import           Network.Haskoin.Crypto             (Hash256, SecKey)
-import           Network.Haskoin.Crypto.Signature   (signHash, verifyHashSig)
-import           Network.Haskoin.Keys.Common        (PubKeyI (..), SecKeyI (..),
-                                                     derivePubKeyI, wrapSecKey)
-import           Network.Haskoin.Script.Common      (ScriptOutput (..),
-                                                     encodeOutput,
-                                                     encodeOutputBS, opPushData)
-import           Network.Haskoin.Script.SigHash     (SigHash, TxSignature (..),
-                                                     decodeTxSig, txSigHash,
-                                                     txSigHashForkId)
-import           Network.Haskoin.Script.Standard    (RedeemScript,
-                                                     ScriptInput (..),
-                                                     SimpleInput (..),
-                                                     decodeInputBS,
-                                                     encodeInputBS)
-import           Network.Haskoin.Transaction.Common (OutPoint, Tx (..),
-                                                     TxIn (..), WitnessData)
-import           Network.Haskoin.Transaction.Segwit (WitnessProgram (..),
-                                                     calcWitnessProgram,
-                                                     isSegwit, toWitnessStack)
-import           Network.Haskoin.Util               (matchTemplate, updateIndex)
-
--- | Data type used to specify the signing parameters of a transaction input.
--- To sign an input, the previous output script, outpoint and sighash are
--- required. When signing a pay to script hash output, an additional redeem
--- script is required.
-data SigInput = SigInput
-    { sigInputScript :: !ScriptOutput         -- ^ output script to spend
-    , sigInputValue  :: !Word64               -- ^ output script value
-    , sigInputOP     :: !OutPoint             -- ^ outpoint to spend
-    , sigInputSH     :: !SigHash              -- ^ signature type
-    , sigInputRedeem :: !(Maybe RedeemScript) -- ^ redeem script
-    } deriving (Eq, Show, Read, Generic, Hashable, NFData)
-
-instance ToJSON SigInput where
-    toJSON (SigInput so val op sh rdm) = object $
-        [ "pkscript" .= so
-        , "value"    .= val
-        , "outpoint" .= op
-        , "sighash"  .= sh
-        ] ++ [ "redeem" .= r | r <- maybeToList rdm ]
-
-instance FromJSON SigInput where
-    parseJSON (Object o) = do
-        so  <- o .: "pkscript"
-        val <- o .: "value"
-        op  <- o .: "outpoint"
-        sh  <- o .: "sighash"
-        rdm <- o .:? "redeem"
-        return $ SigInput so val op sh rdm
-    parseJSON _ = mzero
-
--- | Sign a transaction by providing the 'SigInput' signing parameters and a
--- list of private keys. The signature is computed deterministically as defined
--- in RFC-6979.
-signTx :: Network
-       -> Tx                 -- ^ transaction to sign
-       -> [(SigInput, Bool)] -- ^ signing parameters, with nesting flag
-       -> [SecKey]           -- ^ private keys to sign with
-       -> Either String Tx   -- ^ signed transaction
-signTx net otx sigis allKeys
-    | null ti   = Left "signTx: Transaction has no inputs"
-    | otherwise = foldM go otx $ findInputIndex (sigInputOP . fst) sigis ti
-  where
-    ti = txIn otx
-    go tx (sigi@(SigInput so _ _ _ rdmM, _), i) = do
-        keys <- sigKeys so rdmM allKeys
-        foldM (\t k -> signInput net t i sigi k) tx keys
-
--- | Sign a single input in a transaction deterministically (RFC-6979).  The
--- nesting flag only affects the behavior of segwit inputs.
-signInput ::
-       Network
-    -> Tx
-    -> Int
-    -> (SigInput, Bool) -- ^ boolean flag: nest input
-    -> SecKeyI
-    -> Either String Tx
-signInput net tx i (sigIn@(SigInput so val _ sh rdmM), nest) key = do
-    let sig = makeSignature net tx i sigIn key
-    si <- buildInput net tx i so val rdmM sig $ derivePubKeyI key
-    w  <- updatedWitnessData tx i so si
-    return tx { txIn      = nextTxIn so si
-              , txWitness = w
-              }
-  where
-    f si x = x {scriptInput = encodeInputBS si}
-    g so x = x {scriptInput = S.encode . opPushData $ encodeOutputBS so}
-    txis = txIn tx
-    nextTxIn so si
-        | isSegwit so && nest = updateIndex i txis (g so)
-        | isSegwit so         = txIn tx
-        | otherwise           = updateIndex i txis (f si)
-
--- | Add the witness data of the transaction given segwit parameters for an input.
---
--- @since 0.11.0.0
-updatedWitnessData :: Tx -> Int -> ScriptOutput -> ScriptInput -> Either String WitnessData
-updatedWitnessData tx i so si
-    | isSegwit so = updateWitness . toWitnessStack =<< calcWitnessProgram so si
-    | otherwise   = return $ txWitness tx
-  where
-    updateWitness w
-        | null $ txWitness tx        = return $ updateIndex i defaultStack (const w)
-        | length (txWitness tx) /= n = Left "Invalid number of witness stacks"
-        | otherwise                  = return $ updateIndex i (txWitness tx) (const w)
-    defaultStack = replicate n $ toWitnessStack EmptyWitnessProgram
-    n = length $ txIn tx
-
--- | Associate an input index to each value in a list
-findInputIndex ::
-       (a -> OutPoint) -- ^ extract an outpoint
-    -> [a]             -- ^ input list
-    -> [TxIn]          -- ^ reference list of inputs
-    -> [(a, Int)]
-findInputIndex getOutPoint as ti =
-    mapMaybe g $ zip (matchTemplate as ti f) [0..]
-  where
-    f s txin = getOutPoint s == prevOutput txin
-    g (Just s, i)  = Just (s,i)
-    g (Nothing, _) = Nothing
-
--- | Find from the list of provided private keys which one is required to sign
--- the 'ScriptOutput'.
-sigKeys ::
-       ScriptOutput
-    -> Maybe RedeemScript
-    -> [SecKey]
-    -> Either String [SecKeyI]
-sigKeys so rdmM keys =
-    case (so, rdmM) of
-        (PayPK p, Nothing) ->
-            return . map fst . maybeToList $ find ((== p) . snd) zipKeys
-        (PayPKHash h, Nothing) -> return $ keyByHash h
-        (PayMulSig ps r, Nothing) ->
-            return $ map fst $ take r $ filter ((`elem` ps) . snd) zipKeys
-        (PayScriptHash _, Just rdm) -> sigKeys rdm Nothing keys
-        (PayWitnessPKHash h, _) -> return $ keyByHash h
-        (PayWitnessScriptHash _, Just rdm) -> sigKeys rdm Nothing keys
-        _ -> Left "sigKeys: Could not decode output script"
-  where
-    zipKeys =
-        [ (prv, pub)
-        | k <- keys
-        , t <- [True, False]
-        , let prv = wrapSecKey t k
-        , let pub = derivePubKeyI prv
-        ]
-    keyByHash h = fmap fst . maybeToList . findKey h $ zipKeys
-    findKey h   = find $ (== h) . getAddrHash160 . pubKeyAddr . snd
-
--- | Construct an input for a transaction given a signature, public key and data
--- about the previous output.
-buildInput ::
-       Network
-    -> Tx                 -- ^ transaction where input will be added
-    -> Int                -- ^ input index where signature will go
-    -> ScriptOutput       -- ^ output script being spent
-    -> Word64             -- ^ amount of previous output
-    -> Maybe RedeemScript -- ^ redeem script if pay-to-script-hash
-    -> TxSignature
-    -> PubKeyI
-    -> Either String ScriptInput
-buildInput net tx i so val rdmM sig pub = do
-    when (i >= length (txIn tx)) $ Left "buildInput: Invalid input index"
-    case (so, rdmM) of
-        (PayScriptHash _, Just rdm)        -> buildScriptHashInput rdm
-        (PayWitnessScriptHash _, Just rdm) -> buildScriptHashInput rdm
-        (PayWitnessPKHash _, Nothing)      -> return . RegularInput $ SpendPKHash sig pub
-        (_, Nothing)                       -> buildRegularInput so
-        _ -> Left "buildInput: Invalid output/redeem script combination"
-  where
-    buildRegularInput = \case
-        PayPK _ -> return $ RegularInput $ SpendPK sig
-        PayPKHash _ -> return $ RegularInput $ SpendPKHash sig pub
-        PayMulSig msPubs r -> do
-            let mSigs   = take r $ catMaybes $ matchTemplate allSigs msPubs f
-                allSigs = nub $ sig : parseExistingSigs net tx so i
-            return $ RegularInput $ SpendMulSig mSigs
-        _ -> Left "buildInput: Invalid output/redeem script combination"
-    buildScriptHashInput rdm = do
-        inp <- buildRegularInput rdm
-        return $ ScriptHashInput (getRegularInput inp) rdm
-    f (TxSignature x sh) p =
-        verifyHashSig (makeSigHash net tx i so val sh rdmM) x (pubKeyPoint p)
-    f TxSignatureEmpty _ = False
-
--- | Apply heuristics to extract the signatures for a particular input that are
--- embedded in the transaction.
---
--- @since 0.11.0.0
-parseExistingSigs :: Network -> Tx -> ScriptOutput -> Int -> [TxSignature]
-parseExistingSigs net tx so i = insSigs <> witSigs
-  where
-    insSigs = case decodeInputBS net scp of
-            Right (ScriptHashInput (SpendMulSig xs) _) -> xs
-            Right (RegularInput (SpendMulSig xs))      -> xs
-            _                                          -> []
-    scp = scriptInput $ txIn tx !! i
-    witSigs
-        | not $ isSegwit so   = []
-        | null $ txWitness tx = []
-        | otherwise           = rights $ decodeTxSig net <$> (txWitness tx !! i)
-
--- | Produce a structured representation of a deterministic (RFC-6979) signature over an input.
-makeSignature :: Network -> Tx -> Int -> SigInput -> SecKeyI -> TxSignature
-makeSignature net tx i (SigInput so val _ sh rdmM) key = TxSignature (signHash (secKeyData key) m) sh
-  where
-    m = makeSigHash net tx i so val sh rdmM
-
--- | A function which selects the digest algorithm and parameters as appropriate
---
--- @since 0.11.0.0
-makeSigHash ::
-       Network
-    -> Tx
-    -> Int
-    -> ScriptOutput
-    -> Word64
-    -> SigHash
-    -> Maybe RedeemScript
-    -> Hash256
-makeSigHash net tx i so val sh rdmM = h net tx (encodeOutput so') val i sh
-  where
-    so' = case so of
-        PayWitnessPKHash h -> PayPKHash h
-        _                  -> fromMaybe so rdmM
-    h | isSegwit so = txSigHashForkId
-      | otherwise   = txSigHash
diff --git a/src/Network/Haskoin/Transaction/Common.hs b/src/Network/Haskoin/Transaction/Common.hs
deleted file mode 100644
--- a/src/Network/Haskoin/Transaction/Common.hs
+++ /dev/null
@@ -1,303 +0,0 @@
-{-# LANGUAGE DeriveAnyClass    #-}
-{-# LANGUAGE DeriveGeneric     #-}
-{-# LANGUAGE OverloadedStrings #-}
-{-|
-Module      : Network.Haskoin.Transaction.Common
-Copyright   : No rights reserved
-License     : UNLICENSE
-Maintainer  : jprupp@protonmail.ch
-Stability   : experimental
-Portability : POSIX
-
-Code related to transactions parsing and serialization.
--}
-module Network.Haskoin.Transaction.Common
-    ( Tx(..)
-    , TxIn(..)
-    , TxOut(..)
-    , OutPoint(..)
-    , TxHash(..)
-    , WitnessData
-    , WitnessStack
-    , WitnessStackItem
-    , txHash
-    , hexToTxHash
-    , txHashToHex
-    , nosigTxHash
-    , nullOutPoint
-    , genesisTx
-    ) where
-
-import           Control.Applicative            ((<|>))
-import           Control.DeepSeq
-import           Control.Monad                  (forM_, guard, liftM2, mzero,
-                                                 replicateM, (<=<))
-import           Data.Aeson                     as A
-import           Data.ByteString                (ByteString)
-import qualified Data.ByteString                as B
-import           Data.Hashable                  (Hashable)
-import           Data.Maybe                     (fromMaybe, maybe)
-import           Data.Serialize                 as S
-import           Data.String                    (IsString, fromString)
-import           Data.String.Conversions        (cs)
-import           Data.Text                      (Text)
-import           Data.Word                      (Word32, Word64)
-import           GHC.Generics                   (Generic)
-import           Network.Haskoin.Crypto.Hash
-import           Network.Haskoin.Network.Common
-import           Network.Haskoin.Script.Common
-import           Network.Haskoin.Util
-import           Text.Read                      as R
-
--- | Transaction id: hash of transaction excluding witness data.
-newtype TxHash = TxHash { getTxHash :: Hash256 }
-    deriving (Eq, Ord, Generic, Hashable, Serialize, NFData)
-
-instance Show TxHash where
-    showsPrec _ = shows . txHashToHex
-
-instance Read TxHash where
-    readPrec = do
-        R.String str <- R.lexP
-        maybe R.pfail return $ hexToTxHash $ cs str
-
-instance IsString TxHash where
-    fromString s =
-        let e = error "Could not read transaction hash from hex string"
-        in fromMaybe e $ hexToTxHash $ cs s
-
-instance FromJSON TxHash where
-    parseJSON = withText "txid" $
-        maybe mzero return . hexToTxHash
-
-instance ToJSON TxHash where
-    toJSON = A.String . txHashToHex
-
--- | Transaction hash excluding signatures.
-nosigTxHash :: Tx -> TxHash
-nosigTxHash tx =
-    TxHash $ doubleSHA256 $ S.encode tx { txIn = map clearInput $ txIn tx }
-  where
-    clearInput ti = ti { scriptInput = B.empty }
-
--- | Convert transaction hash to hex form, reversing bytes.
-txHashToHex :: TxHash -> Text
-txHashToHex (TxHash h) = encodeHex (B.reverse (S.encode h))
-
--- | Convert transaction hash from hex, reversing bytes.
-hexToTxHash :: Text -> Maybe TxHash
-hexToTxHash hex = do
-    bs <- B.reverse <$> decodeHex hex
-    h <- either (const Nothing) Just (S.decode bs)
-    return $ TxHash h
-
--- | Witness stack for SegWit transactions.
-type WitnessData = [WitnessStack]
--- | Witness stack for SegWit transactions.
-type WitnessStack = [WitnessStackItem]
--- | Witness stack item for SegWit transactions.
-type WitnessStackItem = ByteString
-
--- | Data type representing a transaction.
-data Tx = Tx
-    { -- | transaction data format version
-      txVersion  :: !Word32
-      -- | list of transaction inputs
-    , txIn       :: ![TxIn]
-      -- | list of transaction outputs
-    , txOut      :: ![TxOut]
-      -- | witness data for the transaction
-    , txWitness  :: !WitnessData
-      -- | earliest mining height or time
-    , txLockTime :: !Word32
-    } deriving (Show, Read, Eq, Ord, Generic, Hashable, NFData)
-
--- | Compute transaction hash.
-txHash :: Tx -> TxHash
-txHash tx = TxHash (doubleSHA256 (S.encode tx {txWitness = []}))
-
-instance IsString Tx where
-    fromString =
-        fromMaybe e . (eitherToMaybe . S.decode <=< decodeHex) . cs
-      where
-        e = error "Could not read transaction from hex string"
-
-instance Serialize Tx where
-    get = parseWitnessTx <|> parseLegacyTx
-    put tx
-        | null (txWitness tx) = putLegacyTx tx
-        | otherwise = putWitnessTx tx
-
-putInOut :: Tx -> Put
-putInOut tx = do
-    putVarInt $ length (txIn tx)
-    forM_ (txIn tx) put
-    putVarInt $ length (txOut tx)
-    forM_ (txOut tx) put
-
--- | Non-SegWit transaction serializer.
-putLegacyTx :: Tx -> Put
-putLegacyTx tx = do
-    putWord32le (txVersion tx)
-    putInOut tx
-    putWord32le (txLockTime tx)
-
--- | Witness transaciton serializer.
-putWitnessTx :: Tx -> Put
-putWitnessTx tx = do
-    putWord32le (txVersion tx)
-    putWord8 0x00
-    putWord8 0x01
-    putInOut tx
-    putWitnessData (txWitness tx)
-    putWord32le (txLockTime tx)
-
--- | Non-SegWit transaction deseralizer.
-parseLegacyTx :: Get Tx
-parseLegacyTx = do
-    v <- getWord32le
-    is <- replicateList =<< S.get
-    os <- replicateList =<< S.get
-    l <- getWord32le
-    return
-        Tx
-        {txVersion = v, txIn = is, txOut = os, txWitness = [], txLockTime = l}
-  where
-    replicateList (VarInt c) = replicateM (fromIntegral c) S.get
-
--- | Witness transaction deserializer.
-parseWitnessTx :: Get Tx
-parseWitnessTx = do
-    v <- getWord32le
-    m <- getWord8
-    f <- getWord8
-    guard $ m == 0x00
-    guard $ f == 0x01
-    is <- replicateList =<< S.get
-    os <- replicateList =<< S.get
-    w <- parseWitnessData $ length is
-    l <- getWord32le
-    return
-        Tx {txVersion = v, txIn = is, txOut = os, txWitness = w, txLockTime = l}
-  where
-    replicateList (VarInt c) = replicateM (fromIntegral c) S.get
-
--- | Witness data deserializer. Requires count of inputs.
-parseWitnessData :: Int -> Get WitnessData
-parseWitnessData n = replicateM n parseWitnessStack
-  where
-    parseWitnessStack = do
-        VarInt i <- S.get
-        replicateM (fromIntegral i) parseWitnessStackItem
-    parseWitnessStackItem = do
-        VarInt i <- S.get
-        getByteString $ fromIntegral i
-
--- | Witness data serializer.
-putWitnessData :: WitnessData -> Put
-putWitnessData = mapM_ putWitnessStack
-  where
-    putWitnessStack ws = do
-        putVarInt $ length ws
-        mapM_ putWitnessStackItem ws
-    putWitnessStackItem bs = do
-        putVarInt $ B.length bs
-        putByteString bs
-
-instance FromJSON Tx where
-    parseJSON = withText "Tx" $
-        maybe mzero return . (eitherToMaybe . S.decode <=< decodeHex)
-
-instance ToJSON Tx where
-    toJSON = A.String . encodeHex . S.encode
-
--- | Data type representing a transaction input.
-data TxIn =
-    TxIn {
-           -- | output being spent
-           prevOutput   :: !OutPoint
-           -- | signatures and redeem script
-         , scriptInput  :: !ByteString
-           -- | lock-time using sequence numbers (BIP-68)
-         , txInSequence :: !Word32
-         } deriving (Eq, Show, Read, Ord, Generic, Hashable, NFData)
-
-instance Serialize TxIn where
-    get =
-        TxIn <$> S.get <*> (readBS =<< S.get) <*> getWord32le
-      where
-        readBS (VarInt len) = getByteString $ fromIntegral len
-
-    put (TxIn o s q) = do
-        put o
-        putVarInt $ B.length s
-        putByteString s
-        putWord32le q
-
--- | Data type representing a transaction output.
-data TxOut =
-    TxOut {
-            -- | value of output is satoshi
-            outValue     :: !Word64
-            -- | pubkey script
-          , scriptOutput :: !ByteString
-          } deriving (Eq, Show, Read, Ord, Generic, Hashable, NFData)
-
-instance Serialize TxOut where
-    get = do
-        val <- getWord64le
-        (VarInt len) <- S.get
-        TxOut val <$> getByteString (fromIntegral len)
-
-    put (TxOut o s) = do
-        putWord64le o
-        putVarInt $ B.length s
-        putByteString s
-
--- | The 'OutPoint' refers to a transaction output being spent.
-data OutPoint = OutPoint
-    { -- | hash of previous transaction
-      outPointHash  :: !TxHash
-      -- | position of output in previous transaction
-    , outPointIndex :: !Word32
-    } deriving (Show, Read, Eq, Ord, Generic, Hashable, NFData)
-
-instance FromJSON OutPoint where
-    parseJSON = withText "OutPoint" $
-        maybe mzero return . (eitherToMaybe . S.decode <=< decodeHex)
-
-instance ToJSON OutPoint where
-    toJSON = A.String . encodeHex . S.encode
-
-instance Serialize OutPoint where
-    get = do
-        (h,i) <- liftM2 (,) S.get getWord32le
-        return $ OutPoint h i
-    put (OutPoint h i) = put h >> putWord32le i
-
--- | Outpoint used in coinbase transactions.
-nullOutPoint :: OutPoint
-nullOutPoint =
-    OutPoint
-    { outPointHash =
-          "0000000000000000000000000000000000000000000000000000000000000000"
-    , outPointIndex = maxBound
-    }
-
--- | Transaction from Genesis block.
-genesisTx :: Tx
-genesisTx =
-    Tx 1 [txin] [txout] [] locktime
-  where
-    txin = TxIn outpoint inputBS maxBound
-    txout = TxOut 5000000000 (encodeOutputBS output)
-    locktime = 0
-    outpoint = OutPoint z maxBound
-    Just inputBS = decodeHex $ fromString $
-        "04ffff001d0104455468652054696d65732030332f4a616e2f323030392043686" ++
-        "16e63656c6c6f72206f6e206272696e6b206f66207365636f6e64206261696c6f" ++
-        "757420666f722062616e6b73"
-    output = PayPK $ fromString $
-        "04678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb" ++
-        "649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5f"
-    z = "0000000000000000000000000000000000000000000000000000000000000000"
diff --git a/src/Network/Haskoin/Transaction/Partial.hs b/src/Network/Haskoin/Transaction/Partial.hs
deleted file mode 100644
--- a/src/Network/Haskoin/Transaction/Partial.hs
+++ /dev/null
@@ -1,496 +0,0 @@
-{-# LANGUAGE DeriveGeneric              #-}
-{-# LANGUAGE GeneralizedNewtypeDeriving #-}
-{-# LANGUAGE NamedFieldPuns             #-}
-{-# LANGUAGE OverloadedStrings          #-}
-{-# LANGUAGE ScopedTypeVariables        #-}
-{-|
-Module      : Network.Haskoin.Transaction.Partial
-Copyright   : No rights reserved
-License     : UNLICENSE
-Maintainer  : matt@bitnomial.com
-Stability   : experimental
-Portability : POSIX
-
-Code related to PSBT parsing and serialization.
--}
-module Network.Haskoin.Transaction.Partial
-    ( PartiallySignedTransaction (..)
-    , Input (..)
-    , Output (..)
-    , UnknownMap (..)
-    , Key (..)
-    , merge
-    , mergeInput
-    , mergeOutput
-    , complete
-    , finalTransaction
-    , emptyPSBT
-    , emptyInput
-    , emptyOutput
-    ) where
-
-import           Control.Applicative         ((<|>))
-import           Control.DeepSeq
-import           Control.Monad               (guard, replicateM, void)
-import           Data.ByteString             (ByteString)
-import qualified Data.ByteString             as B
-import           Data.Hashable               (Hashable)
-import           Data.HashMap.Strict         (HashMap)
-import qualified Data.HashMap.Strict         as HashMap
-import           Data.List                   (foldl')
-import           Data.Maybe                  (fromMaybe, isJust)
-import           Data.Serialize              as S
-import           GHC.Generics                (Generic)
-import           GHC.Word                    (Word32, Word8)
-import           Network.Haskoin.Address     (Address (..), pubKeyAddr)
-import           Network.Haskoin.Keys        (Fingerprint, KeyIndex, PubKeyI)
-import           Network.Haskoin.Network     (VarInt (..), VarString (..),
-                                              putVarInt)
-import           Network.Haskoin.Script      (Script (..), ScriptOp (..),
-                                              ScriptOutput (..), SigHash,
-                                              decodeOutput, decodeOutputBS,
-                                              encodeOutputBS, isPayScriptHash,
-                                              opPushData, toP2SH, toP2WSH)
-import           Network.Haskoin.Transaction (Tx (..), TxOut, WitnessStack,
-                                              outPointIndex, prevOutput,
-                                              scriptInput, scriptOutput)
-import           Network.Haskoin.Util        (eitherToMaybe)
-
--- | PSBT data type as specified in [BIP-174](https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki). This
--- contains an unsigned transaction, inputs and outputs, and unspecified extra data. There is one input per input in the
--- unsigned transaction, and one output per output in the unsigned transaction. The inputs and outputs in the
--- 'PartiallySignedTransaction' line up by index with the inputs and outputs in the unsigned transaction.
-data PartiallySignedTransaction = PartiallySignedTransaction
-    { unsignedTransaction :: Tx
-    , globalUnknown       :: UnknownMap
-    , inputs              :: [Input]
-    , outputs             :: [Output]
-    } deriving (Show, Eq, Generic)
-
-instance NFData PartiallySignedTransaction
-
--- | Inputs contain all of the data needed to sign a transaction and all of the resulting signature data after signing.
-data Input = Input
-    { nonWitnessUtxo     :: Maybe Tx
-    , witnessUtxo        :: Maybe TxOut
-    , partialSigs        :: HashMap PubKeyI ByteString
-    , sigHashType        :: Maybe SigHash
-    , inputRedeemScript  :: Maybe Script
-    , inputWitnessScript :: Maybe Script
-    , inputHDKeypaths    :: HashMap PubKeyI (Fingerprint, [KeyIndex])
-    , finalScriptSig     :: Maybe Script
-    , finalScriptWitness :: Maybe WitnessStack
-    , inputUnknown       :: UnknownMap
-    } deriving (Show, Eq, Generic)
-
-instance NFData Input
-
--- | Outputs can contain information needed to spend the output at a later date.
-data Output = Output
-    { outputRedeemScript  :: Maybe Script
-    , outputWitnessScript :: Maybe Script
-    , outputHDKeypaths    :: HashMap PubKeyI (Fingerprint, [KeyIndex])
-    , outputUnknown       :: UnknownMap
-    } deriving (Show, Eq, Generic)
-
-instance NFData Output
-
--- | A map of raw PSBT keys to byte strings for extra data. The 'keyType' field cannot overlap with any of the reserved
--- 'keyType' fields specified in the PSBT specification.
-newtype UnknownMap = UnknownMap { unknownMap :: HashMap Key ByteString }
-    deriving (Show, Eq, Semigroup, Monoid, Generic)
-
-instance NFData UnknownMap
-
--- | Raw keys for the map type used in PSBTs.
-data Key = Key
-    { keyType :: Word8
-    , key     :: ByteString
-    } deriving (Show, Eq, Generic)
-
-instance NFData Key
-
-instance Hashable Key
-
--- | Take two 'PartiallySignedTransaction's and merge them. The 'unsignedTransaction' field in both must be the same.
-merge :: PartiallySignedTransaction -> PartiallySignedTransaction -> Maybe PartiallySignedTransaction
-merge psbt1 psbt2
-    | unsignedTransaction psbt1 == unsignedTransaction psbt2
-    = Just $ psbt1
-        { globalUnknown = globalUnknown psbt1 <> globalUnknown psbt2
-        , inputs = zipWith mergeInput (inputs psbt1) (inputs psbt2)
-        , outputs = zipWith mergeOutput (outputs psbt1) (outputs psbt2)
-        }
-merge _ _ = Nothing
-
-mergeInput :: Input -> Input -> Input
-mergeInput a b = Input
-    { nonWitnessUtxo = if isJust witUtx then Nothing else nonWitnessUtxo a <|> nonWitnessUtxo b
-    , witnessUtxo = witUtx
-    , sigHashType = sigHashType a <|> sigHashType b
-    , partialSigs = partialSigs a <> partialSigs b
-    , inputHDKeypaths = inputHDKeypaths a <> inputHDKeypaths b
-    , inputUnknown = inputUnknown a <> inputUnknown b
-    , inputRedeemScript = inputRedeemScript a <|> inputRedeemScript b
-    , inputWitnessScript = inputWitnessScript a <|> inputWitnessScript b
-    , finalScriptSig = finalScriptSig a <|> finalScriptSig b
-    , finalScriptWitness = finalScriptWitness a <|> finalScriptWitness b
-    }
-  where
-    witUtx = witnessUtxo a <|> witnessUtxo b
-
-mergeOutput :: Output -> Output -> Output
-mergeOutput a b = Output
-    { outputRedeemScript = outputRedeemScript a <|> outputRedeemScript b
-    , outputWitnessScript = outputWitnessScript a <|> outputWitnessScript b
-    , outputHDKeypaths = outputHDKeypaths a <> outputHDKeypaths b
-    , outputUnknown = outputUnknown a <> outputUnknown b
-    }
-
--- | Take partial signatures from all of the 'Input's and finalize the signature.
-complete :: PartiallySignedTransaction -> PartiallySignedTransaction
-complete psbt = psbt { inputs = map (completeInput . analyzeInputs) (indexed $ inputs psbt) }
-  where
-    analyzeInputs (i, input) = (outputScript =<< witnessUtxo input <|> nonWitScript, input)
-      where
-        nonWitScript = getPrevOut i =<< nonWitnessUtxo input
-
-    getPrevOut i tx =
-       (txOut tx !!?) . fromIntegral . outPointIndex . prevOutput =<< txIn (unsignedTransaction psbt) !!? i
-    xs !!? i = lookup i $ indexed xs
-
-    outputScript = eitherToMaybe . decodeOutputBS . scriptOutput
-
-    completeInput (Nothing, input)     = input
-    completeInput (Just script, input) = completeSig input script
-
-    indexed :: [a] -> [(Word32, a)]
-    indexed = zip [0..]
-
-completeSig :: Input -> ScriptOutput -> Input
-completeSig input (PayPK k) =
-    input { finalScriptSig = eitherToMaybe . S.decode =<< HashMap.lookup k (partialSigs input) }
-completeSig input (PayPKHash h)
-    | [(k, sig)] <- HashMap.toList $ partialSigs input
-    , PubKeyAddress h == pubKeyAddr k
-    = input { finalScriptSig = Just $ Script [opPushData sig, opPushData (S.encode k)] }
-completeSig input (PayMulSig pubKeys m) | length sigs >= m = input { finalScriptSig = finalSig }
-  where
-    sigs = collectSigs m pubKeys input
-    finalSig = Script . (OP_0 :) . (map opPushData sigs <>) . pure . opPushData . S.encode <$> inputRedeemScript input
-completeSig input (PayScriptHash h)
-    | Just rdmScript <- inputRedeemScript input
-    , PayScriptHash h == toP2SH rdmScript
-    , Right decodedScript <- decodeOutput rdmScript
-    , not (isPayScriptHash decodedScript)
-    = completeSig input decodedScript
-completeSig input (PayWitnessPKHash h)
-    | [(k, sig)] <- HashMap.toList $ partialSigs input
-    , PubKeyAddress h == pubKeyAddr k
-    = input { finalScriptWitness = Just [sig, S.encode k]
-            , finalScriptSig = Script . pure . opPushData . S.encode <$> inputRedeemScript input
-            }
-completeSig input (PayWitnessScriptHash h)
-    | Just witScript <- inputWitnessScript input
-    , PayWitnessScriptHash h == toP2WSH witScript
-    , Right decodedScript <- decodeOutput witScript
-    = completeWitnessSig input decodedScript
-completeSig input _ = input
-
-completeWitnessSig :: Input -> ScriptOutput -> Input
-completeWitnessSig input script@(PayMulSig pubKeys m) | length sigs >= m = input
-    { finalScriptWitness = Just finalWit
-    , finalScriptSig = finalSig
-    }
-  where
-    sigs = collectSigs m pubKeys input
-    finalSig = Script . pure . opPushData . S.encode <$> inputRedeemScript input
-    finalWit = mempty : sigs <> [encodeOutputBS script]
-completeWitnessSig input _ = input
-
-collectSigs :: Int -> [PubKeyI] -> Input -> [ByteString]
-collectSigs m pubKeys input = take m . reverse $ foldl' lookupKey [] pubKeys
-  where
-    lookupKey sigs key = maybe sigs (:sigs) $ HashMap.lookup key (partialSigs input)
-
--- | Take a finalized 'PartiallySignedTransaction' and produce the signed final transaction.  You may need to call
--- 'complete' on the 'PartiallySignedTransaction' before producing the final transaction.
-finalTransaction :: PartiallySignedTransaction -> Tx
-finalTransaction psbt = setInputs . foldl' finalizeInput ([], []) $ zip (txIn tx) (inputs psbt)
-  where
-    tx = unsignedTransaction psbt
-    hasWitness = any (isJust . finalScriptWitness) (inputs psbt)
-    setInputs (ins, witData) = tx { txIn = reverse ins, txWitness = if hasWitness then reverse witData else [] }
-    finalizeInput (ins, witData) (txInput, psbtInput) = maybe finalWitness finalScript $ finalScriptSig psbtInput
-      where
-        finalScript script = (txInput { scriptInput = encode script }:ins, []:witData)
-        finalWitness = (ins, fromMaybe [] (finalScriptWitness psbtInput):witData)
-
--- | Take an unsigned transaction and produce an empty 'PartiallySignedTransaction'
-emptyPSBT :: Tx -> PartiallySignedTransaction
-emptyPSBT tx = PartiallySignedTransaction
-    { unsignedTransaction = tx
-    , globalUnknown = mempty
-    , inputs = replicate (length (txIn tx)) emptyInput
-    , outputs = replicate (length (txOut tx)) emptyOutput
-    }
-
-emptyInput :: Input
-emptyInput = Input
-    Nothing Nothing HashMap.empty Nothing
-    Nothing Nothing HashMap.empty
-    Nothing Nothing (UnknownMap HashMap.empty)
-
-emptyOutput :: Output
-emptyOutput = Output Nothing Nothing HashMap.empty (UnknownMap HashMap.empty)
-
-instance Serialize PartiallySignedTransaction where
-    get = do
-        magic <- getBytes 4
-        guard $ magic == "psbt"
-        headerSep <- getWord8
-        guard $ headerSep == 0xff
-
-        keySize <- getWord8
-        guard $ keySize == 1
-        globalUnsignedTxType <- getWord8
-        guard $ globalUnsignedTxType == 0x00
-        unsignedTransaction <- getSizedBytes
-        guard $ all (B.null . scriptInput) (txIn unsignedTransaction)
-        guard $ null (txWitness unsignedTransaction)
-
-        globalUnknown <- get
-        globalEnd <- getWord8
-        guard $ globalEnd == 0x00
-
-        inputs <- replicateM (length $ txIn unsignedTransaction) get
-        outputs <- replicateM (length $ txOut unsignedTransaction) get
-
-        return PartiallySignedTransaction { unsignedTransaction, globalUnknown, inputs, outputs }
-
-    put PartiallySignedTransaction{ unsignedTransaction, globalUnknown, inputs, outputs } = do
-        putByteString "psbt"
-        putWord8 0xff -- Header separator
-
-        putWord8 0x01 -- Key size
-        putWord8 0x00 -- Unsigned Transaction type
-        putSizedBytes unsignedTransaction
-        put globalUnknown
-        putWord8 0x00 -- Global end
-
-        mapM_ put inputs
-        mapM_ put outputs
-
-instance Serialize Key where
-    get = do
-        VarInt keySize <- get
-        guard $ keySize > 0
-        t <- getWord8
-        k <- getBytes (fromIntegral keySize - 1)
-        return (Key t k)
-
-    put (Key t k) = do
-        putVarInt $ 1 + B.length k
-        putWord8 t
-        putByteString k
-
-instance Serialize UnknownMap where
-    get = go HashMap.empty
-      where
-        getItem m = do
-            k <- get
-            VarString v <- get
-            go $ HashMap.insert k v m
-        go m = do
-            isEnd <- lookAhead getWord8
-            if isEnd == 0x00
-                then return (UnknownMap m)
-                else getItem m
-
-    put (UnknownMap m) = void $ HashMap.traverseWithKey (\k v -> put k >> put (VarString v)) m
-
-instance Serialize Input where
-    get = getMap getInputItem setInputUnknown emptyInput
-      where
-        setInputUnknown f input = input { inputUnknown = UnknownMap $ f (unknownMap $ inputUnknown input) }
-
-    put Input { nonWitnessUtxo, witnessUtxo, partialSigs, sigHashType
-                  , inputRedeemScript, inputWitnessScript, inputHDKeypaths
-                  , finalScriptSig, finalScriptWitness, inputUnknown
-                  } = do
-        whenJust (putKeyValue InNonWitnessUtxo) nonWitnessUtxo
-        whenJust (putKeyValue InWitnessUtxo) witnessUtxo
-        putPartialSig partialSigs
-        whenJust putSigHash sigHashType
-        whenJust (putKeyValue InRedeemScript) inputRedeemScript
-        whenJust (putKeyValue InWitnessScript) inputWitnessScript
-        putHDPath InBIP32Derivation inputHDKeypaths
-        whenJust (putKeyValue InFinalScriptSig) finalScriptSig
-        whenJust (putKeyValue InFinalScriptWitness) finalScriptWitness
-        put inputUnknown
-        putWord8 0x00
-      where
-        putPartialSig = putPubKeyMap InPartialSig . fmap VarString
-        putSigHash sigHash = do
-            putKey InSigHashType
-            putWord8 0x04
-            putWord32le (fromIntegral sigHash)
-
-instance Serialize Output where
-    get = getMap getOutputItem setOutputUnknown emptyOutput
-      where
-        setOutputUnknown f output = output { outputUnknown = UnknownMap $ f (unknownMap $ outputUnknown output) }
-
-    put Output{ outputRedeemScript, outputWitnessScript, outputHDKeypaths, outputUnknown } = do
-        whenJust (putKeyValue OutRedeemScript) outputRedeemScript
-        whenJust (putKeyValue OutWitnessScript) outputWitnessScript
-        putHDPath OutBIP32Derivation outputHDKeypaths
-        put outputUnknown
-        putWord8 0x00
-
-putSizedBytes :: Serialize a => a -> Put
-putSizedBytes a = putVarInt (B.length bs) >> putByteString bs
-  where bs = encode a
-
-getSizedBytes :: Serialize a => Get a
-getSizedBytes = getNested (fromIntegral . getVarInt <$> get) get
-
-putKeyValue :: (Enum t, Serialize v) => t -> v -> Put
-putKeyValue t v = putKey t >> putSizedBytes v
-
-putKey :: Enum t => t -> Put
-putKey t = putVarInt (1 :: Word8) >> putWord8 (enumWord8 t)
-
-getMap :: (Bounded t, Enum t)
-       => (Int -> a -> t -> Get a)
-       -> ((HashMap Key ByteString -> HashMap Key ByteString) -> a -> a)
-       -> a -> Get a
-getMap getMapItem setUnknown = go
-  where
-    getItem keySize m (Right t) = getMapItem (fromIntegral keySize - 1) m t >>= go
-    getItem keySize m (Left t)  = do
-        k <- getBytes (fromIntegral keySize - 1)
-        VarString v <- get
-        go $ setUnknown (HashMap.insert (Key t k) v) m
-    go m = do
-        keySize <- getVarInt <$> get
-        if keySize == 0
-            then return m
-            else getItem keySize m =<< (word8Enum <$> getWord8)
-
-data InputType
-    = InNonWitnessUtxo
-    | InWitnessUtxo
-    | InPartialSig
-    | InSigHashType
-    | InRedeemScript
-    | InWitnessScript
-    | InBIP32Derivation
-    | InFinalScriptSig
-    | InFinalScriptWitness
-    deriving (Show, Eq, Enum, Bounded, Generic)
-
-instance NFData InputType
-
-data OutputType
-    = OutRedeemScript
-    | OutWitnessScript
-    | OutBIP32Derivation
-    deriving (Show, Eq, Enum, Bounded, Generic)
-
-instance NFData OutputType
-
-getInputItem :: Int -> Input -> InputType -> Get Input
-getInputItem 0 input@Input{nonWitnessUtxo = Nothing} InNonWitnessUtxo = do
-    utxo <- getSizedBytes
-    return $ input { nonWitnessUtxo = Just utxo }
-getInputItem 0 input@Input{witnessUtxo = Nothing} InWitnessUtxo = do
-    utxo <- getSizedBytes
-    return $ input { witnessUtxo = Just utxo }
-getInputItem keySize input InPartialSig = do
-    (k, v) <- getPartialSig
-    return $ input { partialSigs = HashMap.insert k v (partialSigs input) }
-  where
-    getPartialSig = (,) <$> isolate keySize get <*> (getVarString <$> get)
-getInputItem 0 input@Input{sigHashType = Nothing} InSigHashType = do
-    VarInt size <- get
-    guard $ size == 0x04
-    sigHash <- fromIntegral <$> getWord32le
-    return $ input { sigHashType = Just sigHash }
-getInputItem 0 input@Input{inputRedeemScript = Nothing} InRedeemScript = do
-    script <- getSizedBytes
-    return $ input { inputRedeemScript = Just script }
-getInputItem 0 input@Input{inputWitnessScript = Nothing} InWitnessScript = do
-    script <- getSizedBytes
-    return $ input { inputWitnessScript = Just script }
-getInputItem keySize input InBIP32Derivation = do
-    (k, v) <- getHDPath keySize
-    return $ input { inputHDKeypaths = HashMap.insert k v (inputHDKeypaths input) }
-getInputItem 0 input@Input{finalScriptSig = Nothing} InFinalScriptSig = do
-    script <- getSizedBytes
-    return $ input { finalScriptSig = Just script }
-getInputItem 0 input@Input{finalScriptWitness = Nothing} InFinalScriptWitness = do
-    scripts <- map getVarString <$> getVarIntList
-    return $ input { finalScriptWitness = Just scripts }
-  where
-    getVarIntList = do
-        VarInt n <- get
-        replicateM (fromIntegral n) get
-getInputItem keySize input inputType = fail $
-    "Incorrect key size for input item or item already existed: " <>
-    show (keySize, input, inputType)
-
-getOutputItem :: Int -> Output -> OutputType -> Get Output
-getOutputItem 0 output@Output{outputRedeemScript = Nothing} OutRedeemScript = do
-    script <- getSizedBytes
-    return $ output { outputRedeemScript = Just script }
-getOutputItem 0 output@Output{outputWitnessScript = Nothing} OutWitnessScript = do
-    script <- getSizedBytes
-    return $ output { outputWitnessScript = Just script }
-getOutputItem keySize output OutBIP32Derivation = do
-    (k, v) <- getHDPath keySize
-    return $ output { outputHDKeypaths = HashMap.insert k v (outputHDKeypaths output) }
-getOutputItem keySize output outputType = fail $
-    "Incorrect key size for output item or item already existed: " <>
-    show (keySize, output, outputType)
-
-getHDPath :: Int -> Get (PubKeyI, (Fingerprint, [KeyIndex]))
-getHDPath keySize = (,) <$> isolate keySize get <*> (unPSBTHDPath <$> get)
-
-putHDPath :: Enum t => t -> HashMap PubKeyI (Fingerprint, [KeyIndex]) -> Put
-putHDPath t = putPubKeyMap t . fmap PSBTHDPath
-
-newtype PSBTHDPath = PSBTHDPath { unPSBTHDPath :: (Fingerprint, [KeyIndex]) }
-    deriving (Show, Eq, Generic)
-
-instance NFData PSBTHDPath
-
-instance Serialize PSBTHDPath where
-    get = do
-        VarInt valueSize <- get
-        guard $ valueSize `mod` 4 == 0
-        let numIndices = (fromIntegral valueSize - 4) `div` 4
-        PSBTHDPath <$>
-            isolate
-                (fromIntegral valueSize)
-                ((,) <$> getWord32le <*> getKeyIndexList numIndices)
-      where
-        getKeyIndexList n = replicateM n getWord32le
-    put (PSBTHDPath (fp, kis)) = putVarInt (B.length bs) >> putByteString bs
-      where
-        bs = runPut $ putWord32le fp >> mapM_ putWord32le kis
-
-putPubKeyMap :: (Serialize a, Enum t) => t -> HashMap PubKeyI a -> Put
-putPubKeyMap t = void . HashMap.traverseWithKey putItem
-  where
-    putItem k v = put (Key (enumWord8 t) (encode k)) >> put v
-
-enumWord8 :: Enum a => a -> Word8
-enumWord8 = fromIntegral . fromEnum
-
-word8Enum :: forall a. (Bounded a, Enum a) => Word8 -> Either Word8 a
-word8Enum n | n <= enumWord8 (maxBound :: a) = Right . toEnum $ fromIntegral n
-word8Enum n = Left n
-
-whenJust :: Monad m => (a -> m ()) -> Maybe a -> m ()
-whenJust = maybe (return ())
diff --git a/src/Network/Haskoin/Transaction/Segwit.hs b/src/Network/Haskoin/Transaction/Segwit.hs
deleted file mode 100644
--- a/src/Network/Haskoin/Transaction/Segwit.hs
+++ /dev/null
@@ -1,141 +0,0 @@
-{-# LANGUAGE LambdaCase        #-}
-{-# LANGUAGE OverloadedStrings #-}
-{-# LANGUAGE TupleSections     #-}
-{-|
-Module      : Network.Haskoin.Transaction.Segwit
-Copyright   : No rights reserved
-License     : UNLICENSE
-Maintainer  : jprupp@protonmail.ch
-Stability   : experimental
-Portability : POSIX
-
-Types to represent segregated witness data and auxilliary functions to
-manipulate it.  See [BIP 141](https://github.com/bitcoin/bips/blob/master/bip-0141.mediawiki)
-and [BIP 143](https://github.com/bitcoin/bips/blob/master/bip-0143.mediawiki) for
-details.
--}
-module Network.Haskoin.Transaction.Segwit
-    ( WitnessProgram (..)
-    , WitnessProgramPKH (..)
-    , WitnessProgramSH (..)
-    , isSegwit
-
-    , viewWitnessProgram
-    , decodeWitnessInput
-
-    , calcWitnessProgram
-    , simpleInputStack
-    , toWitnessStack
-    ) where
-
-import           Data.ByteString                    (ByteString)
-import qualified Data.Serialize                     as S
-import           Network.Haskoin.Constants          (Network)
-import           Network.Haskoin.Keys.Common        (PubKeyI)
-import           Network.Haskoin.Script.Common      (Script (..),
-                                                     ScriptOutput (..),
-                                                     decodeOutput, encodeOutput)
-import           Network.Haskoin.Script.SigHash     (TxSignature (..),
-                                                     decodeTxSig, encodeTxSig)
-import           Network.Haskoin.Script.Standard    (ScriptInput (..),
-                                                     SimpleInput (..))
-import           Network.Haskoin.Transaction.Common (WitnessStack)
-
--- | Test if a 'ScriptOutput' is P2WPKH or P2WSH
---
--- @since 0.11.0.0
-isSegwit :: ScriptOutput -> Bool
-isSegwit = \case
-    PayWitnessPKHash{}     -> True
-    PayWitnessScriptHash{} -> True
-    _                      -> False
-
--- | High level represenation of a (v0) witness program
---
--- @since 0.11.0.0
-data WitnessProgram = P2WPKH WitnessProgramPKH | P2WSH WitnessProgramSH | EmptyWitnessProgram
-    deriving (Eq, Show)
-
--- | Encode a witness program
---
--- @since 0.11.0.0
-toWitnessStack :: WitnessProgram -> WitnessStack
-toWitnessStack = \case
-    P2WPKH (WitnessProgramPKH sig key) -> [encodeTxSig sig, S.encode key]
-    P2WSH (WitnessProgramSH stack scr) -> stack <> [S.encode scr]
-    EmptyWitnessProgram                -> mempty
-
--- | High level representation of a P2WPKH witness
---
--- @since 0.11.0.0
-data WitnessProgramPKH = WitnessProgramPKH
-    { witnessSignature :: !TxSignature
-    , witnessPubKey    :: !PubKeyI
-    } deriving (Eq, Show)
-
--- | High-level representation of a P2WSH witness
---
--- @since 0.11.0.0
-data WitnessProgramSH = WitnessProgramSH
-    { witnessScriptHashStack  :: ![ByteString]
-    , witnessScriptHashScript :: !Script
-    } deriving (Eq, Show)
-
--- | Calculate the witness program from the transaction data
---
--- @since 0.11.0.0
-viewWitnessProgram :: Network -> ScriptOutput -> WitnessStack -> Either String WitnessProgram
-viewWitnessProgram net so witness = case so of
-    PayWitnessPKHash _ | length witness == 2 -> do
-        sig    <- decodeTxSig net $ head witness
-        pubkey <- S.decode $ witness !! 1
-        return . P2WPKH $ WitnessProgramPKH sig pubkey
-    PayWitnessScriptHash _ | not (null witness) -> do
-        redeemScript <- S.decode $ last witness
-        return . P2WSH $ WitnessProgramSH (init witness) redeemScript
-    _ | null witness -> return EmptyWitnessProgram
-      | otherwise    -> Left "viewWitnessProgram: Invalid witness program"
-
--- | Analyze the witness, trying to match it with standard input structures
---
--- @since 0.11.0.0
-decodeWitnessInput :: Network -> WitnessProgram -> Either String (Maybe ScriptOutput, SimpleInput)
-decodeWitnessInput net = \case
-    P2WPKH (WitnessProgramPKH sig key) -> return (Nothing, SpendPKHash sig key)
-    P2WSH (WitnessProgramSH st scr) -> do
-        so <- decodeOutput scr
-        fmap (Just so, ) $ case (so, st) of
-            (PayPK k, [sigBS]) ->
-                SpendPK <$> decodeTxSig net sigBS
-            (PayPKHash h, [sigBS, keyBS]) ->
-                SpendPKHash <$> decodeTxSig net sigBS <*> S.decode keyBS
-            (PayMulSig ps r, "" : sigsBS) ->
-                SpendMulSig <$> traverse (decodeTxSig net) sigsBS
-            _ -> Left "decodeWitnessInput: Non-standard script output"
-    EmptyWitnessProgram -> Left "decodeWitnessInput: Empty witness program"
-
--- | Create the witness program for a standard input
---
--- @since 0.11.0.0
-calcWitnessProgram :: ScriptOutput -> ScriptInput -> Either String WitnessProgram
-calcWitnessProgram so si = case (so, si) of
-    (PayWitnessPKHash{}, RegularInput (SpendPKHash sig pk)) -> p2wpkh sig pk
-    (PayScriptHash{}, RegularInput (SpendPKHash sig pk))    -> p2wpkh sig pk
-    (PayWitnessScriptHash{}, ScriptHashInput i o)           -> p2wsh i o
-    (PayScriptHash{}, ScriptHashInput i o)                  -> p2wsh i o
-    _ -> Left "calcWitnessProgram: Invalid segwit SigInput"
-  where
-    p2wpkh sig = return . P2WPKH . WitnessProgramPKH sig
-    p2wsh i o  = return . P2WSH $ WitnessProgramSH (simpleInputStack i) (encodeOutput o)
-
--- | Create the witness stack required to spend a standard P2WSH input
---
--- @since 0.11.0.0
-simpleInputStack :: SimpleInput -> [ByteString]
-simpleInputStack = \case
-    SpendPK sig       -> [f sig]
-    SpendPKHash sig k -> [f sig, S.encode k]
-    SpendMulSig sigs  -> "" : fmap f sigs
-  where
-    f TxSignatureEmpty = ""
-    f sig              = encodeTxSig sig
diff --git a/src/Network/Haskoin/Util.hs b/src/Network/Haskoin/Util.hs
deleted file mode 100644
--- a/src/Network/Haskoin/Util.hs
+++ /dev/null
@@ -1,196 +0,0 @@
-{-# LANGUAGE MultiParamTypeClasses #-}
-{-|
-Module      : Network.Haskoin.Util
-Copyright   : No rights reserved
-License     : UNLICENSE
-Maintainer  : jprupp@protonmail.ch
-Stability   : experimental
-Portability : POSIX
-
-This module defines various utility functions used across the library.
--}
-module Network.Haskoin.Util
-    (
-      -- * ByteString Helpers
-      bsToInteger
-    , integerToBS
-    , encodeHex
-    , decodeHex
-    , getBits
-
-      -- * Maybe & Either Helpers
-    , eitherToMaybe
-    , maybeToEither
-    , liftEither
-    , liftMaybe
-
-      -- * Other Helpers
-    , updateIndex
-    , matchTemplate
-    , convertBits
-
-      -- * Triples
-    , fst3
-    , snd3
-    , lst3
-
-      -- * JSON Utilities
-    , dropFieldLabel
-    , dropSumLabels
-
-    ) where
-
-import           Control.Monad          (guard)
-import           Control.Monad.Except   (ExceptT (..), liftEither)
-import           Data.Aeson.Types       (Options (..), SumEncoding (..),
-                                         defaultOptions, defaultTaggedObject)
-import           Data.Bits
-import           Data.ByteString        (ByteString)
-import qualified Data.ByteString        as BS
-import qualified Data.ByteString.Base16 as B16
-import           Data.Char              (toLower)
-import           Data.List
-import           Data.Text              (Text)
-import qualified Data.Text.Encoding     as E
-import           Data.Word              (Word8)
-
--- ByteString helpers
-
--- | Decode a big endian 'Integer' from a 'ByteString'.
-bsToInteger :: ByteString -> Integer
-bsToInteger = BS.foldr f 0 . BS.reverse
-  where
-    f w n = toInteger w .|. shiftL n 8
-
--- | Encode an 'Integer' to a 'ByteString' as big endian.
-integerToBS :: Integer -> ByteString
-integerToBS 0 = BS.pack [0]
-integerToBS i
-    | i > 0     = BS.reverse $ BS.unfoldr f i
-    | otherwise = error "integerToBS not defined for negative values"
-  where
-    f 0 = Nothing
-    f x = Just (fromInteger x :: Word8, x `shiftR` 8)
-
--- | Encode as string of human-readable hex characters.
-encodeHex :: ByteString -> Text
-encodeHex = E.decodeUtf8 . B16.encode
-
--- | Decode string of human-readable hex characters.
-decodeHex :: Text -> Maybe ByteString
-decodeHex text =
-    let (x, b) = B16.decode (E.encodeUtf8 text)
-    in guard (b == BS.empty) >> return x
-
--- | Obtain 'Int' bits from beginning of 'ByteString'. Resulting 'ByteString'
--- will be smallest required to hold that many bits, padded with zeroes to the
--- right.
-getBits :: Int -> ByteString -> ByteString
-getBits b bs
-    | r == 0 = BS.take q bs
-    | otherwise = i `BS.snoc` l
-  where
-    (q, r) = b `quotRem` 8
-    s = BS.take (q + 1) bs
-    i = BS.init s
-    l = BS.last s .&. (0xff `shiftL` (8 - r)) -- zero unneeded bits
-
--- Maybe and Either monad helpers
-
--- | Transform an 'Either' value into a 'Maybe' value. 'Right' is mapped to
--- 'Just' and 'Left' is mapped to 'Nothing'. The value inside 'Left' is lost.
-eitherToMaybe :: Either a b -> Maybe b
-eitherToMaybe (Right b) = Just b
-eitherToMaybe _         = Nothing
-
--- | Transform a 'Maybe' value into an 'Either' value. 'Just' is mapped to
--- 'Right' and 'Nothing' is mapped to 'Left'. Default 'Left' required.
-maybeToEither :: b -> Maybe a -> Either b a
-maybeToEither err = maybe (Left err) Right
-
--- | Lift a 'Maybe' computation into the 'ExceptT' monad.
-liftMaybe :: Monad m => b -> Maybe a -> ExceptT b m a
-liftMaybe err = liftEither . maybeToEither err
-
--- Various helpers
-
--- | Applies a function to only one element of a list defined by its index.  If
--- the index is out of the bounds of the list, the original list is returned.
-updateIndex :: Int      -- ^ index of the element to change
-            -> [a]      -- ^ list of elements
-            -> (a -> a) -- ^ function to apply
-            -> [a]      -- ^ result with one element changed
-updateIndex i xs f
-    | i < 0 || i >= length xs = xs
-    | otherwise = l ++ (f h : r)
-  where
-    (l,h:r) = splitAt i xs
-
--- | Use the list @[b]@ as a template and try to match the elements of @[a]@
--- against it. For each element of @[b]@ return the (first) matching element of
--- @[a]@, or 'Nothing'. Output list has same size as @[b]@ and contains results
--- in same order. Elements of @[a]@ can only appear once.
-matchTemplate :: [a]              -- ^ input list
-              -> [b]              -- ^ list to serve as a template
-              -> (a -> b -> Bool) -- ^ comparison function
-              -> [Maybe a]
-matchTemplate [] bs _ = replicate (length bs) Nothing
-matchTemplate _  [] _ = []
-matchTemplate as (b:bs) f = case break (`f` b) as of
-    (l,r:rs) -> Just r  : matchTemplate (l ++ rs) bs f
-    _        -> Nothing : matchTemplate as bs f
-
--- | Returns the first value of a triple.
-fst3 :: (a,b,c) -> a
-fst3 (a,_,_) = a
-
--- | Returns the second value of a triple.
-snd3 :: (a,b,c) -> b
-snd3 (_,b,_) = b
-
--- | Returns the last value of a triple.
-lst3 :: (a,b,c) -> c
-lst3 (_,_,c) = c
-
--- | Field label goes lowercase and first @n@ characters get removed.
-dropFieldLabel :: Int -> Options
-dropFieldLabel n = defaultOptions
-    { fieldLabelModifier = map toLower . drop n
-    }
-
--- | Transformation from 'dropFieldLabel' is applied with argument @f@, plus
--- constructor tags are lowercased and first @c@ characters removed. @tag@ is
--- used as the name of the object field name that will hold the transformed
--- constructor tag as its value.
-dropSumLabels :: Int -> Int -> String -> Options
-dropSumLabels c f tag = (dropFieldLabel f)
-    { constructorTagModifier = map toLower . drop c
-    , sumEncoding = defaultTaggedObject { tagFieldName = tag }
-    }
-
--- | Convert from one power-of-two base to another, as long as it fits in a
--- 'Word'.
-convertBits :: Bool -> Int -> Int -> [Word] -> ([Word], Bool)
-convertBits pad frombits tobits i = (reverse yout, rem')
-  where
-    (xacc, xbits, xout) = foldl' outer (0, 0, []) i
-    (yout, rem')
-        | pad && xbits /= 0 =
-            let xout' = (xacc `shiftL` (tobits - xbits)) .&. maxv : xout
-            in (xout', False)
-        | pad = (xout, False)
-        | xbits /= 0 = (xout, True)
-        | otherwise = (xout, False)
-    maxv = 1 `shiftL` tobits - 1
-    max_acc = 1 `shiftL` (frombits + tobits - 1) - 1
-    outer (acc, bits, out) it =
-        let acc' = ((acc `shiftL` frombits) .|. it) .&. max_acc
-            bits' = bits + frombits
-            (out', bits'') = inner acc' out bits'
-        in (acc', bits'', out')
-    inner acc out bits
-        | bits >= tobits =
-            let bits' = bits - tobits
-                out' = ((acc `shiftR` bits') .&. maxv) : out
-            in inner acc out' bits'
-        | otherwise = (out, bits)
diff --git a/test/Haskoin/Address/Bech32Spec.hs b/test/Haskoin/Address/Bech32Spec.hs
new file mode 100644
--- /dev/null
+++ b/test/Haskoin/Address/Bech32Spec.hs
@@ -0,0 +1,244 @@
+{-# LANGUAGE ImportQualifiedPost #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Haskoin.Address.Bech32Spec (spec) where
+
+import Control.Monad
+import Data.Bits (xor)
+import Data.ByteString (ByteString)
+import Data.ByteString qualified as ByteString
+import Data.Char (chr, ord, toLower)
+import Data.Maybe
+import Data.String.Conversions
+import Data.Text (Text, append, pack, snoc, uncons)
+import Data.Text qualified as Text
+import Data.Word (Word8)
+import Haskoin.Address
+import Haskoin.Address.Bech32
+import Haskoin.Util
+import Test.HUnit
+import Test.Hspec
+
+spec = do
+  describe "bech32 checksum" $ do
+    it "should be valid" $
+      forM_ validChecksums (uncurry testValidChecksum)
+    it "should be invalid" $
+      forM_ invalidChecksums testInvalidChecksum
+    it "should be case-insensitive" $
+      all ((== Just "test12hrzfj") . flip (bech32Encode Bech32) []) hrpCaseVariants
+  describe "bech32 address" $ do
+    it "should be valid" $
+      forM_ validChecksums (uncurry testValidChecksum)
+    it "should be invalid" $
+      forM_ invalidChecksums testInvalidChecksum
+    it "should be case-insensitive" $
+      all ((== Just "test12hrzfj") . flip (bech32Encode Bech32) []) hrpCaseVariants
+  describe "bech32 encoding/decoding" $ do
+    it "should not encode long data string" $
+      assert . isNothing $
+        bech32Encode Bech32 "bc" (replicate 82 (word5 (1 :: Word8)))
+    it "should not encode bad version number" $
+      assert $
+        isNothing $
+          segwitEncode "bc" 17 []
+    it "should not encode invalid length for version 0" $
+      assert $
+        isNothing $
+          segwitEncode "bc" 0 (replicate 30 1)
+    it "should relax length restrictions for versions other than 0" $
+      assert $
+        isJust $
+          segwitEncode "bc" 1 (replicate 30 1)
+    it "should not encode another long data string" $
+      assert $
+        isNothing $
+          segwitEncode "bc" 1 (replicate 41 1)
+    it "should not encode empty human readable part" $
+      assert $
+        isNothing $
+          bech32Encode Bech32 "" []
+    it "should not decode empty human-readable part" $
+      assert $
+        isNothing $
+          bech32Decode "10a06t8"
+    it "human-readable part should be case-insensitive" $
+      bech32Encode Bech32 "HRP" [] `shouldBe` bech32Encode Bech32 "hrp" []
+
+testValidChecksum :: Bech32Encoding -> Bech32 -> Assertion
+testValidChecksum enc checksum = case bech32Decode checksum of
+  Nothing -> assertFailure (show checksum)
+  Just (enc', resultHRP, resultData) -> do
+    assertEqual (show checksum ++ " encoding incorrect") enc enc'
+    -- test that a corrupted checksum fails decoding.
+    let (hrp, rest) = Text.breakOnEnd "1" checksum
+        Just (first, rest') = uncons rest
+        checksumCorrupted = (hrp `snoc` chr (ord first `xor` 1)) `append` rest'
+    assertBool (show checksum ++ " corrupted") $
+      isNothing (bech32Decode checksumCorrupted)
+    -- test that re-encoding the decoded checksum results in the same checksum.
+    let checksumEncoded = bech32Encode enc' resultHRP resultData
+        expectedChecksum = Just $ Text.toLower checksum
+    assertEqual
+      (show checksum ++ " re-encode")
+      expectedChecksum
+      checksumEncoded
+
+testInvalidChecksum :: Bech32 -> Assertion
+testInvalidChecksum checksum =
+  assertBool (show checksum) (isNothing $ bech32Decode checksum)
+
+testValidAddress :: (Text, Text) -> Assertion
+testValidAddress (address, hexscript) = do
+  let address' = Text.toLower address
+      hrp = Text.take 2 address'
+  case segwitDecode hrp address of
+    Nothing ->
+      assertFailure (Text.unpack address <> ": decode failed")
+    Just (witver, witprog) -> do
+      assertEqual
+        (show address)
+        (decodeHex hexscript)
+        (Just $ segwitScriptPubkey witver witprog)
+      assertEqual
+        (show address)
+        (Just address')
+        (segwitEncode hrp witver witprog)
+
+testInvalidAddress :: Text -> Assertion
+testInvalidAddress address = do
+  assertBool (show address) (isNothing $ segwitDecode "bc" address)
+  assertBool (show address) (isNothing $ segwitDecode "tb" address)
+
+segwitScriptPubkey :: Word8 -> [Word8] -> ByteString
+segwitScriptPubkey witver witprog =
+  ByteString.pack $ witver' : fromIntegral (length witprog) : witprog
+  where
+    witver' = if witver == 0 then 0 else witver + 0x50
+
+validChecksums :: [(Bech32Encoding, Text)]
+validChecksums =
+  [ ( Bech32,
+      "A12UEL5L"
+    ),
+    ( Bech32,
+      "an83characterlonghumanreadablepartthatcontainsthenumber1andtheexcludedcharactersbio1tt5tgs"
+    ),
+    ( Bech32,
+      "abcdef1qpzry9x8gf2tvdw0s3jn54khce6mua7lmqqqxw"
+    ),
+    ( Bech32,
+      "11qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqc8247j"
+    ),
+    ( Bech32,
+      "split1checkupstagehandshakeupstreamerranterredcaperred2y9e3w"
+    ),
+    ( Bech32m,
+      "A1LQFN3A"
+    ),
+    ( Bech32m,
+      "a1lqfn3a"
+    ),
+    ( Bech32m,
+      "an83characterlonghumanreadablepartthatcontainsthetheexcludedcharactersbioandnumber11sg7hg6"
+    ),
+    ( Bech32m,
+      "abcdef1l7aum6echk45nj3s0wdvt2fg8x9yrzpqzd3ryx"
+    ),
+    ( Bech32m,
+      "11llllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllludsr8"
+    ),
+    ( Bech32m,
+      "split1checkupstagehandshakeupstreamerranterredcaperredlc445v"
+    ),
+    ( Bech32m,
+      "?1v759aa"
+    )
+  ]
+
+invalidChecksums :: [Text]
+invalidChecksums =
+  [ " 1nwldj5",
+    "\DEL1axkwrx",
+    "an84characterslonghumanreadablepartthatcontainsthenumber1andtheexcludedcharactersbio1569pvx",
+    "pzry9x0s0muk",
+    "1pzry9x0s0muk",
+    "x1b4n0q5v",
+    "li1dgmt3",
+    "de1lg7wt\xFF"
+  ]
+
+validAddresses :: [(Text, Text)]
+validAddresses =
+  [ ( "BC1QW508D6QEJXTDG4Y5R3ZARVARY0C5XW7KV8F3T4",
+      "0014751e76e8199196d454941c45d1b3a323f1433bd6"
+    ),
+    ( "tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sl5k7",
+      "00201863143c14c5166804bd19203356da136c985678cd4d27a1b8c6329604903262"
+    ),
+    ( "tb1qqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvsesrxh6hy",
+      "0020000000c4a5cad46221b2a187905e5266362b99d5e91c6ce24d165dab93e86433"
+    ),
+    ( "BC1QW508D6QEJXTDG4Y5R3ZARVARY0C5XW7KV8F3T4",
+      "0014751e76e8199196d454941c45d1b3a323f1433bd6"
+    ),
+    ( "tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sl5k7",
+      "00201863143c14c5166804bd19203356da136c985678cd4d27a1b8c6329604903262"
+    ),
+    ( "bc1pw508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7kt5nd6y",
+      "5128751e76e8199196d454941c45d1b3a323f1433bd6751e76e8199196d454941c45d1b3a323f1433bd6"
+    ),
+    ( "BC1SW50QGDZ25J",
+      "6002751e"
+    ),
+    ( "bc1zw508d6qejxtdg4y5r3zarvaryvaxxpcs",
+      "5210751e76e8199196d454941c45d1b3a323"
+    ),
+    ( "tb1qqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvsesrxh6hy",
+      "0020000000c4a5cad46221b2a187905e5266362b99d5e91c6ce24d165dab93e86433"
+    ),
+    ( "tb1pqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvsesf3hn0c",
+      "5120000000c4a5cad46221b2a187905e5266362b99d5e91c6ce24d165dab93e86433"
+    ),
+    ( "bc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vqzk5jj0",
+      "512079be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"
+    )
+  ]
+
+invalidAddresses :: [Text]
+invalidAddresses =
+  [ "tc1qw508d6qejxtdg4y5r3zarvary0c5xw7kg3g4ty",
+    "bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t5",
+    "BC13W508D6QEJXTDG4Y5R3ZARVARY0C5XW7KN40WF2",
+    "bc1rw5uspcuh",
+    "bc10w508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7kw5rljs90",
+    "BC1QR508D6QEJXTDG4Y5R3ZARVARYV98GJ9P",
+    "tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sL5k7",
+    "bc1zw508d6qejxtdg4y5r3zarvaryvqyzf3du",
+    "tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3pjxtptv",
+    "bc1gmk9yu",
+    "tc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vq5zuyut",
+    "bc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vqh2y7hd",
+    "tb1z0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vqglt7rf",
+    "BC1S0XLXVLHEMJA6C4DQV22UAPCTQUPFHLXM9H8Z3K2E72Q4K9HCZ7VQ54WELL",
+    "bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kemeawh",
+    "tb1q0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vq24jc47",
+    "bc1p38j9r5y49hruaue7wxjce0updqjuyyx0kh56v8s25huc6995vvpql3jow4",
+    "BC130XLXVLHEMJA6C4DQV22UAPCTQUPFHLXM9H8Z3K2E72Q4K9HCZ7VQ7ZWS8R",
+    "bc1pw5dgrnzv",
+    "bc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7v8n0nx0muaewav253zgeav",
+    "tb1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vq47Zagq",
+    "bc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7v07qwwzcrf",
+    "tb1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vpggkg4j"
+  ]
+
+hrpCaseVariants :: [Text]
+hrpCaseVariants = map Text.pack hrpTestPermutations
+
+hrpTestPermutations :: [String]
+hrpTestPermutations = do
+  a <- ['t', 'T']
+  b <- ['e', 'E']
+  c <- ['s', 'S']
+  d <- ['t', 'T']
+  return [a, b, c, d]
diff --git a/test/Haskoin/Address/CashAddrSpec.hs b/test/Haskoin/Address/CashAddrSpec.hs
new file mode 100644
--- /dev/null
+++ b/test/Haskoin/Address/CashAddrSpec.hs
@@ -0,0 +1,315 @@
+{-# LANGUAGE ImportQualifiedPost #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Haskoin.Address.CashAddrSpec (spec) where
+
+import Control.Monad
+import Data.ByteString.Char8 qualified as Char8
+import Data.Maybe
+import Data.String.Conversions
+import Data.Text (Text)
+import Haskoin.Address
+import Haskoin.Network.Constants
+import Haskoin.Util
+import Test.HUnit
+import Test.Hspec
+
+spec :: Spec
+spec = do
+  describe "cashaddr checksum test vectors" $ do
+    it "prefix:x64nx6hz" $ do
+      let mpb = cash32decode "prefix:x64nx6hz"
+      mpb `shouldBe` Just ("prefix", "")
+    it "p:gpf8m4h7" $ do
+      let mpb = cash32decode "p:gpf8m4h7"
+      mpb `shouldBe` Just ("p", "")
+    it "bitcoincash:qpzry9x8gf2tvdw0s3jn54khce6mua7lcw20ayyn" $ do
+      let mpb =
+            cash32decode
+              "bitcoincash:qpzry9x8gf2tvdw0s3jn54khce6mua7lcw20ayyn"
+      mpb
+        `shouldBe` Just
+          ( "bitcoincash",
+            "\NULD2\DC4\199BT\182\&5\207\132e:V\215\198u\190w\223"
+          )
+    it "bchtest:testnetaddress4d6njnut" $ do
+      let mpb = cash32decode "bchtest:testnetaddress4d6njnut"
+      mpb `shouldBe` Just ("bchtest", "^`\185\229}kG\152")
+    it "bchreg:555555555555555555555555555555555555555555555udxmlmrz" $ do
+      let mpb =
+            cash32decode
+              "bchreg:555555555555555555555555555555555555555555555udxmlmrz"
+      mpb
+        `shouldBe` Just
+          ( "bchreg",
+            "\165)JR\148\165)JR\148\165)JR\148\165)JR\148\165)JR\148\165)J"
+          )
+  describe "cashaddr to base58 translation test vectors" $ do
+    it "1BpEi6DfDAUFd7GtittLSdBeYJvcoaVggu" $ do
+      let addr =
+            addrToText bch
+              =<< textToAddr btc "1BpEi6DfDAUFd7GtittLSdBeYJvcoaVggu"
+      addr
+        `shouldBe` Just "bitcoincash:qpm2qsznhks23z7629mms6s4cwef74vcwvy22gdx6a"
+    it "1KXrWXciRDZUpQwQmuM1DbwsKDLYAYsVLR" $ do
+      let addr =
+            addrToText bch
+              =<< textToAddr btc "1KXrWXciRDZUpQwQmuM1DbwsKDLYAYsVLR"
+      addr
+        `shouldBe` Just "bitcoincash:qr95sy3j9xwd2ap32xkykttr4cvcu7as4y0qverfuy"
+    it "16w1D5WRVKJuZUsSRzdLp9w3YGcgoxDXb" $ do
+      let addr =
+            addrToText bch
+              =<< textToAddr btc "16w1D5WRVKJuZUsSRzdLp9w3YGcgoxDXb"
+      addr
+        `shouldBe` Just "bitcoincash:qqq3728yw0y47sqn6l2na30mcw6zm78dzqre909m2r"
+    it "3CWFddi6m4ndiGyKqzYvsFYagqDLPVMTzC" $ do
+      let addr =
+            addrToText bch
+              =<< textToAddr btc "3CWFddi6m4ndiGyKqzYvsFYagqDLPVMTzC"
+      addr
+        `shouldBe` Just "bitcoincash:ppm2qsznhks23z7629mms6s4cwef74vcwvn0h829pq"
+    it "3LDsS579y7sruadqu11beEJoTjdFiFCdX4" $ do
+      let addr =
+            addrToText bch
+              =<< textToAddr btc "3LDsS579y7sruadqu11beEJoTjdFiFCdX4"
+      addr
+        `shouldBe` Just "bitcoincash:pr95sy3j9xwd2ap32xkykttr4cvcu7as4yc93ky28e"
+    it "31nwvkZwyPdgzjBJZXfDmSWsC4ZLKpYyUw" $ do
+      let addr =
+            addrToText bch
+              =<< textToAddr btc "31nwvkZwyPdgzjBJZXfDmSWsC4ZLKpYyUw"
+      addr
+        `shouldBe` Just "bitcoincash:pqq3728yw0y47sqn6l2na30mcw6zm78dzq5ucqzc37"
+  describe "base58 to cashaddr translation test vectors" $ do
+    it "bitcoincash:qpm2qsznhks23z7629mms6s4cwef74vcwvy22gdx6a" $ do
+      let addr =
+            addrToText btc
+              =<< textToAddr
+                bch
+                "bitcoincash:qpm2qsznhks23z7629mms6s4cwef74vcwvy22gdx6a"
+      addr `shouldBe` Just "1BpEi6DfDAUFd7GtittLSdBeYJvcoaVggu"
+    it "bitcoincash:qr95sy3j9xwd2ap32xkykttr4cvcu7as4y0qverfuy" $ do
+      let addr =
+            addrToText btc
+              =<< textToAddr
+                bch
+                "bitcoincash:qr95sy3j9xwd2ap32xkykttr4cvcu7as4y0qverfuy"
+      addr `shouldBe` Just "1KXrWXciRDZUpQwQmuM1DbwsKDLYAYsVLR"
+    it "bitcoincash:qqq3728yw0y47sqn6l2na30mcw6zm78dzqre909m2r" $ do
+      let addr =
+            addrToText btc
+              =<< textToAddr
+                bch
+                "bitcoincash:qqq3728yw0y47sqn6l2na30mcw6zm78dzqre909m2r"
+      addr `shouldBe` Just "16w1D5WRVKJuZUsSRzdLp9w3YGcgoxDXb"
+    it "bitcoincash:ppm2qsznhks23z7629mms6s4cwef74vcwvn0h829pq" $ do
+      let addr =
+            addrToText btc
+              =<< textToAddr
+                bch
+                "bitcoincash:ppm2qsznhks23z7629mms6s4cwef74vcwvn0h829pq"
+      addr `shouldBe` Just "3CWFddi6m4ndiGyKqzYvsFYagqDLPVMTzC"
+    it "bitcoincash:pr95sy3j9xwd2ap32xkykttr4cvcu7as4yc93ky28e" $ do
+      let addr =
+            addrToText btc
+              =<< textToAddr
+                bch
+                "bitcoincash:pr95sy3j9xwd2ap32xkykttr4cvcu7as4yc93ky28e"
+      addr `shouldBe` Just "3LDsS579y7sruadqu11beEJoTjdFiFCdX4"
+    it "bitcoincash:pqq3728yw0y47sqn6l2na30mcw6zm78dzq5ucqzc37" $ do
+      let addr =
+            addrToText btc
+              =<< textToAddr
+                bch
+                "bitcoincash:pqq3728yw0y47sqn6l2na30mcw6zm78dzq5ucqzc37"
+      addr `shouldBe` Just "31nwvkZwyPdgzjBJZXfDmSWsC4ZLKpYyUw"
+  describe "cashaddr larger test vectors" $
+    forM_ (zip [0 ..] vectors) $ \(i, vec) ->
+      it ("cashaddr test vector " <> show (i :: Int)) $ testCashAddr vec
+
+{- Various utilities -}
+
+testCashAddr :: (Int, CashVersion, Cash32, Text) -> Assertion
+testCashAddr (len, typ, addr, hex) = do
+  let mbs = decodeHex hex
+  assertBool "Could not decode hex payload from test vector" (isJust mbs)
+  let mlow = cash32decode addr
+  assertBool "Could not decode low level address" (isJust mlow)
+  let Just (_, lbs) = mlow
+  assertEqual "Low-level payload size incorrect" len (Char8.length lbs - 1)
+  assertEqual "Low-level payload doesn't match" bs (Char8.tail lbs)
+  let mdec = cash32decodeType addr
+  assertBool ("Could not decode test address: " <> cs addr) (isJust mdec)
+  assertEqual "Length doesn't match" len (Char8.length pay)
+  assertEqual "Version doesn't match" typ ver
+  assertEqual "Payload doesn't match" bs pay
+  where
+    Just bs = decodeHex hex
+    Just (_, ver, pay) = cash32decodeType addr
+
+-- | All vectors starting with @pref@ had the wrong version in the spec
+-- document.
+vectors :: [(Int, CashVersion, Text, Text)]
+vectors =
+  [ ( 20,
+      0,
+      "bitcoincash:qr6m7j9njldwwzlg9v7v53unlr4jkmx6eylep8ekg2",
+      "F5BF48B397DAE70BE82B3CCA4793F8EB2B6CDAC9"
+    ),
+    ( 20,
+      1,
+      "bchtest:pr6m7j9njldwwzlg9v7v53unlr4jkmx6eyvwc0uz5t",
+      "F5BF48B397DAE70BE82B3CCA4793F8EB2B6CDAC9"
+    ),
+    ( 20,
+      1,
+      "pref:pr6m7j9njldwwzlg9v7v53unlr4jkmx6ey65nvtks5",
+      "F5BF48B397DAE70BE82B3CCA4793F8EB2B6CDAC9"
+    ),
+    ( 20,
+      15,
+      "prefix:0r6m7j9njldwwzlg9v7v53unlr4jkmx6ey3qnjwsrf",
+      "F5BF48B397DAE70BE82B3CCA4793F8EB2B6CDAC9"
+    ),
+    ( 24,
+      0,
+      "bitcoincash:q9adhakpwzztepkpwp5z0dq62m6u5v5xtyj7j3h2ws4mr9g0",
+      "7ADBF6C17084BC86C1706827B41A56F5CA32865925E946EA"
+    ),
+    ( 24,
+      1,
+      "bchtest:p9adhakpwzztepkpwp5z0dq62m6u5v5xtyj7j3h2u94tsynr",
+      "7ADBF6C17084BC86C1706827B41A56F5CA32865925E946EA"
+    ),
+    ( 24,
+      1,
+      "pref:p9adhakpwzztepkpwp5z0dq62m6u5v5xtyj7j3h2khlwwk5v",
+      "7ADBF6C17084BC86C1706827B41A56F5CA32865925E946EA"
+    ),
+    ( 24,
+      15,
+      "prefix:09adhakpwzztepkpwp5z0dq62m6u5v5xtyj7j3h2p29kc2lp",
+      "7ADBF6C17084BC86C1706827B41A56F5CA32865925E946EA"
+    ),
+    ( 28,
+      0,
+      "bitcoincash:qgagf7w02x4wnz3mkwnchut2vxphjzccwxgjvvjmlsxqwkcw59jxxuz",
+      "3A84F9CF51AAE98A3BB3A78BF16A6183790B18719126325BFC0C075B"
+    ),
+    ( 28,
+      1,
+      "bchtest:pgagf7w02x4wnz3mkwnchut2vxphjzccwxgjvvjmlsxqwkcvs7md7wt",
+      "3A84F9CF51AAE98A3BB3A78BF16A6183790B18719126325BFC0C075B"
+    ),
+    ( 28,
+      1,
+      "pref:pgagf7w02x4wnz3mkwnchut2vxphjzccwxgjvvjmlsxqwkcrsr6gzkn",
+      "3A84F9CF51AAE98A3BB3A78BF16A6183790B18719126325BFC0C075B"
+    ),
+    ( 28,
+      15,
+      "prefix:0gagf7w02x4wnz3mkwnchut2vxphjzccwxgjvvjmlsxqwkc5djw8s9g",
+      "3A84F9CF51AAE98A3BB3A78BF16A6183790B18719126325BFC0C075B"
+    ),
+    ( 32,
+      0,
+      "bitcoincash:qvch8mmxy0rtfrlarg7ucrxxfzds5pamg73h7370aa87d80gyhqxq5nlegake",
+      "3173EF6623C6B48FFD1A3DCC0CC6489B0A07BB47A37F47CFEF4FE69DE825C060"
+    ),
+    ( 32,
+      1,
+      "bchtest:pvch8mmxy0rtfrlarg7ucrxxfzds5pamg73h7370aa87d80gyhqxq7fqng6m6",
+      "3173EF6623C6B48FFD1A3DCC0CC6489B0A07BB47A37F47CFEF4FE69DE825C060"
+    ),
+    ( 32,
+      1,
+      "pref:pvch8mmxy0rtfrlarg7ucrxxfzds5pamg73h7370aa87d80gyhqxq4k9m7qf9",
+      "3173EF6623C6B48FFD1A3DCC0CC6489B0A07BB47A37F47CFEF4FE69DE825C060"
+    ),
+    ( 32,
+      15,
+      "prefix:0vch8mmxy0rtfrlarg7ucrxxfzds5pamg73h7370aa87d80gyhqxqsh6jgp6w",
+      "3173EF6623C6B48FFD1A3DCC0CC6489B0A07BB47A37F47CFEF4FE69DE825C060"
+    ),
+    ( 40,
+      0,
+      "bitcoincash:qnq8zwpj8cq05n7pytfmskuk9r4gzzel8qtsvwz79zdskftrzxtar994cgutavfklv39gr3uvz",
+      "C07138323E00FA4FC122D3B85B9628EA810B3F381706385E289B0B25631197D194B5C238BEB136FB"
+    ),
+    ( 40,
+      1,
+      "bchtest:pnq8zwpj8cq05n7pytfmskuk9r4gzzel8qtsvwz79zdskftrzxtar994cgutavfklvmgm6ynej",
+      "C07138323E00FA4FC122D3B85B9628EA810B3F381706385E289B0B25631197D194B5C238BEB136FB"
+    ),
+    ( 40,
+      1,
+      "pref:pnq8zwpj8cq05n7pytfmskuk9r4gzzel8qtsvwz79zdskftrzxtar994cgutavfklv0vx5z0w3",
+      "C07138323E00FA4FC122D3B85B9628EA810B3F381706385E289B0B25631197D194B5C238BEB136FB"
+    ),
+    ( 40,
+      15,
+      "prefix:0nq8zwpj8cq05n7pytfmskuk9r4gzzel8qtsvwz79zdskftrzxtar994cgutavfklvwsvctzqy",
+      "C07138323E00FA4FC122D3B85B9628EA810B3F381706385E289B0B25631197D194B5C238BEB136FB"
+    ),
+    ( 48,
+      0,
+      "bitcoincash:qh3krj5607v3qlqh5c3wq3lrw3wnuxw0sp8dv0zugrrt5a3kj6ucysfz8kxwv2k53krr7n933jfsunqex2w82sl",
+      "E361CA9A7F99107C17A622E047E3745D3E19CF804ED63C5C40C6BA763696B98241223D8CE62AD48D863F4CB18C930E4C"
+    ),
+    ( 48,
+      1,
+      "bchtest:ph3krj5607v3qlqh5c3wq3lrw3wnuxw0sp8dv0zugrrt5a3kj6ucysfz8kxwv2k53krr7n933jfsunqnzf7mt6x",
+      "E361CA9A7F99107C17A622E047E3745D3E19CF804ED63C5C40C6BA763696B98241223D8CE62AD48D863F4CB18C930E4C"
+    ),
+    ( 48,
+      1,
+      "pref:ph3krj5607v3qlqh5c3wq3lrw3wnuxw0sp8dv0zugrrt5a3kj6ucysfz8kxwv2k53krr7n933jfsunqjntdfcwg",
+      "E361CA9A7F99107C17A622E047E3745D3E19CF804ED63C5C40C6BA763696B98241223D8CE62AD48D863F4CB18C930E4C"
+    ),
+    ( 48,
+      15,
+      "prefix:0h3krj5607v3qlqh5c3wq3lrw3wnuxw0sp8dv0zugrrt5a3kj6ucysfz8kxwv2k53krr7n933jfsunqakcssnmn",
+      "E361CA9A7F99107C17A622E047E3745D3E19CF804ED63C5C40C6BA763696B98241223D8CE62AD48D863F4CB18C930E4C"
+    ),
+    ( 56,
+      0,
+      "bitcoincash:qmvl5lzvdm6km38lgga64ek5jhdl7e3aqd9895wu04fvhlnare5937w4ywkq57juxsrhvw8ym5d8qx7sz7zz0zvcypqscw8jd03f",
+      "D9FA7C4C6EF56DC4FF423BAAE6D495DBFF663D034A72D1DC7D52CBFE7D1E6858F9D523AC0A7A5C34077638E4DD1A701BD017842789982041"
+    ),
+    ( 56,
+      1,
+      "bchtest:pmvl5lzvdm6km38lgga64ek5jhdl7e3aqd9895wu04fvhlnare5937w4ywkq57juxsrhvw8ym5d8qx7sz7zz0zvcypqs6kgdsg2g",
+      "D9FA7C4C6EF56DC4FF423BAAE6D495DBFF663D034A72D1DC7D52CBFE7D1E6858F9D523AC0A7A5C34077638E4DD1A701BD017842789982041"
+    ),
+    ( 56,
+      1,
+      "pref:pmvl5lzvdm6km38lgga64ek5jhdl7e3aqd9895wu04fvhlnare5937w4ywkq57juxsrhvw8ym5d8qx7sz7zz0zvcypqsammyqffl",
+      "D9FA7C4C6EF56DC4FF423BAAE6D495DBFF663D034A72D1DC7D52CBFE7D1E6858F9D523AC0A7A5C34077638E4DD1A701BD017842789982041"
+    ),
+    ( 56,
+      15,
+      "prefix:0mvl5lzvdm6km38lgga64ek5jhdl7e3aqd9895wu04fvhlnare5937w4ywkq57juxsrhvw8ym5d8qx7sz7zz0zvcypqsgjrqpnw8",
+      "D9FA7C4C6EF56DC4FF423BAAE6D495DBFF663D034A72D1DC7D52CBFE7D1E6858F9D523AC0A7A5C34077638E4DD1A701BD017842789982041"
+    ),
+    ( 64,
+      0,
+      "bitcoincash:qlg0x333p4238k0qrc5ej7rzfw5g8e4a4r6vvzyrcy8j3s5k0en7calvclhw46hudk5flttj6ydvjc0pv3nchp52amk97tqa5zygg96mtky5sv5w",
+      "D0F346310D5513D9E01E299978624BA883E6BDA8F4C60883C10F28C2967E67EC77ECC7EEEAEAFC6DA89FAD72D11AC961E164678B868AEEEC5F2C1DA08884175B"
+    ),
+    ( 64,
+      1,
+      "bchtest:plg0x333p4238k0qrc5ej7rzfw5g8e4a4r6vvzyrcy8j3s5k0en7calvclhw46hudk5flttj6ydvjc0pv3nchp52amk97tqa5zygg96mc773cwez",
+      "D0F346310D5513D9E01E299978624BA883E6BDA8F4C60883C10F28C2967E67EC77ECC7EEEAEAFC6DA89FAD72D11AC961E164678B868AEEEC5F2C1DA08884175B"
+    ),
+    ( 64,
+      1,
+      "pref:plg0x333p4238k0qrc5ej7rzfw5g8e4a4r6vvzyrcy8j3s5k0en7calvclhw46hudk5flttj6ydvjc0pv3nchp52amk97tqa5zygg96mg7pj3lh8",
+      "D0F346310D5513D9E01E299978624BA883E6BDA8F4C60883C10F28C2967E67EC77ECC7EEEAEAFC6DA89FAD72D11AC961E164678B868AEEEC5F2C1DA08884175B"
+    ),
+    ( 64,
+      15,
+      "prefix:0lg0x333p4238k0qrc5ej7rzfw5g8e4a4r6vvzyrcy8j3s5k0en7calvclhw46hudk5flttj6ydvjc0pv3nchp52amk97tqa5zygg96ms92w6845",
+      "D0F346310D5513D9E01E299978624BA883E6BDA8F4C60883C10F28C2967E67EC77ECC7EEEAEAFC6DA89FAD72D11AC961E164678B868AEEEC5F2C1DA08884175B"
+    )
+  ]
diff --git a/test/Haskoin/AddressSpec.hs b/test/Haskoin/AddressSpec.hs
new file mode 100644
--- /dev/null
+++ b/test/Haskoin/AddressSpec.hs
@@ -0,0 +1,197 @@
+{-# LANGUAGE ImportQualifiedPost #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Haskoin.AddressSpec (spec) where
+
+import Data.ByteString (ByteString)
+import Data.ByteString qualified as B
+import Data.Default (def)
+import Data.Maybe (fromJust, isJust)
+import Data.Text (Text)
+import Data.Text qualified as T
+import Haskoin.Address
+import Haskoin.Crypto
+import Haskoin.Network.Constants
+import Haskoin.Network.Data
+import Haskoin.Util
+import Haskoin.Util.Arbitrary
+import Test.HUnit
+import Test.Hspec
+import Test.Hspec.QuickCheck
+import Test.QuickCheck
+
+identityTests :: IdentityTests
+identityTests =
+  def
+    { readTests = [ReadBox arbitraryAddressAll],
+      serialTests = [SerialBox arbitraryAddressAll],
+      marshalJsonTests = [MarshalJsonBox arbitraryNetAddress]
+    }
+
+spec :: Spec
+spec = prepareContext $ \ctx -> do
+  testIdentity identityTests
+  describe "Address properties" $ do
+    prop "encodes and decodes base58 bytestring" $
+      forAll arbitraryBS $ \bs ->
+        decodeBase58 (encodeBase58 bs) == Just bs
+    prop "encodes and decodes base58 bytestring with checksum" $
+      forAll arbitraryBS $ \bs ->
+        decodeBase58Check (encodeBase58Check bs) == Just bs
+    prop "textToAddr . addrToText identity" $
+      forAll arbitraryNetAddress $ \(net, a) ->
+        (textToAddr net =<< addrToText net a) == Just a
+    prop "outputAddress . addressToOutput identity" $
+      forAll arbitraryAddress $ \a ->
+        outputAddress ctx (addressToOutput a) == Just a
+  describe "Address vectors" $ do
+    it "Passes Base58 vectors 1" $
+      mapM_ testVector vectors
+    it "Passes Base58 vectors 2" $
+      mapM_ testBase58Vector base58Vectors
+    it "Passes Base58 invalid decoding vectors" $
+      mapM_ testBase58InvalidVector base58InvalidVectors
+    it "Passes Base58Check invalid decoding vectors" $
+      mapM_ testBase58ChkInvalidVector base58ChkInvalidVectors
+    it "Passes addresses witness p2sh(pwpkh) vectors" $
+      mapM_ (testCompatWitnessVector ctx) compatWitnessVectors
+
+testVector :: (ByteString, Text, Text) -> Assertion
+testVector (bs, e, chk) = do
+  assertEqual "encodeBase58" e b58
+  assertEqual "encodeBase58Check" chk b58Chk
+  assertEqual "decodeBase58" (Just bs) (decodeBase58 b58)
+  assertEqual "decodeBase58Check" (Just bs) (decodeBase58Check b58Chk)
+  where
+    b58 = encodeBase58 bs
+    b58Chk = encodeBase58Check bs
+
+vectors :: [(ByteString, Text, Text)]
+vectors =
+  [ (B.empty, "", "3QJmnh"),
+    (B.pack [0], "1", "1Wh4bh"),
+    (B.pack [0, 0, 0, 0], "1111", "11114bdQda"),
+    (B.pack [0, 0, 1, 0, 0], "11LUw", "113CUwsFVuo"),
+    (B.pack [255], "5Q", "VrZDWwe"),
+    ( B.pack [0, 0, 0, 0] `B.append` B.pack [1 .. 255],
+      "1111cWB5HCBdLjAuqGGReWE3R3CguuwSjw6RHn39s2yuDRTS5N\
+      \sBgNiFpWgAnEx6VQi8csexkgYw3mdYrMHr8x9i7aEwP8kZ7vcc\
+      \XWqKDvGv3u1GxFKPuAkn8JCPPGDMf3vMMnbzm6Nh9zh1gcNsMv\
+      \H3ZNLmP5fSG6DGbbi2tuwMWPthr4boWwCxf7ewSgNQeacyozhK\
+      \DDQQ1qL5fQFUW52QKUZDZ5fw3KXNQJMcNTcaB723LchjeKun7M\
+      \uGW5qyCBZYzA1KjofN1gYBV3NqyhQJ3Ns746GNuf9N2pQPmHz4\
+      \xpnSrrfCvy6TVVz5d4PdrjeshsWQwpZsZGzvbdAdN8MKV5QsBDY",
+      "111151KWPPBRzdWPr1ASeu172gVgLf1YfUp6VJyk6K9t4cLqYt\
+      \FHcMa2iX8S3NJEprUcW7W5LvaPRpz7UG7puBj5STE3nKhCGt5e\
+      \ckYq7mMn5nT7oTTic2BAX6zDdqrmGCnkszQkzkz8e5QLGDjf7K\
+      \eQgtEDm4UER6DMSdBjFQVa6cHrrJn9myVyyhUrsVnfUk2WmNFZ\
+      \vkWv3Tnvzo2cJ1xW62XDfUgYz1pd97eUGGPuXvDFfLsBVd1dfd\
+      \UhPwxW7pMPgdWHTmg5uqKGFF6vE4xXpAqZTbTxRZjCDdTn68c2\
+      \wrcxApm8hq3JX65Hix7VtcD13FF8b7BzBtwjXq1ze6NMjKgUcq\
+      \pJTN9vt"
+    )
+  ]
+
+-- Test vectors from:
+-- https://github.com/bitcoin/bitcoin/blob/master/src/test/data/base58_encode_decode.json
+
+testBase58Vector :: (Text, Text) -> Assertion
+testBase58Vector (a, b) = do
+  assertEqual "encodeBase58 match" b (encodeBase58 bsA)
+  assertEqual "decodeBase58 match" a (encodeHex bsB)
+  assertEqual "bytestring match" bsA bsB
+  where
+    bsA = fromJust $ decodeHex a
+    bsB = fromJust $ decodeBase58 b
+
+base58Vectors :: [(Text, Text)]
+base58Vectors =
+  [ ("", ""),
+    ("61", "2g"),
+    ("626262", "a3gV"),
+    ("636363", "aPEr"),
+    ( "73696d706c792061206c6f6e6720737472696e67",
+      "2cFupjhnEsSn59qHXstmK2ffpLv2"
+    ),
+    ( "00eb15231dfceb60925886b67d065299925915aeb172c06647",
+      "1NS17iag9jJgTHD1VXjvLCEnZuQ3rJDE9L"
+    ),
+    ("516b6fcd0f", "ABnLTmg"),
+    ("bf4f89001e670274dd", "3SEo3LWLoPntC"),
+    ("572e4794", "3EFU7m"),
+    ("ecac89cad93923c02321", "EJDM8drfXA6uyA"),
+    ("10c8511e", "Rt5zm"),
+    ("00000000000000000000", "1111111111"),
+    ( "000111d38e5fc9071ffcd20b4a763cc9ae4f252bb4e48fd66a835e252a\
+      \da93ff480d6dd43dc62a641155a5",
+      "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
+    ),
+    ( "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c\
+      \1d1e1f202122232425262728292a2b2c2d2e2f30313233343536373839\
+      \3a3b3c3d3e3f404142434445464748494a4b4c4d4e4f50515253545556\
+      \5758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f70717273\
+      \7475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f90\
+      \9192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacad\
+      \aeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9ca\
+      \cbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7\
+      \e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+      "1cWB5HCBdLjAuqGGReWE3R3CguuwSjw6RHn39s2yuDRTS5NsBgNiFpWgAn\
+      \Ex6VQi8csexkgYw3mdYrMHr8x9i7aEwP8kZ7vccXWqKDvGv3u1GxFKPuAk\
+      \n8JCPPGDMf3vMMnbzm6Nh9zh1gcNsMvH3ZNLmP5fSG6DGbbi2tuwMWPthr\
+      \4boWwCxf7ewSgNQeacyozhKDDQQ1qL5fQFUW52QKUZDZ5fw3KXNQJMcNTc\
+      \aB723LchjeKun7MuGW5qyCBZYzA1KjofN1gYBV3NqyhQJ3Ns746GNuf9N2\
+      \pQPmHz4xpnSrrfCvy6TVVz5d4PdrjeshsWQwpZsZGzvbdAdN8MKV5QsBDY"
+    )
+  ]
+
+-- Test vectors from:
+-- https://github.com/bitcoin/bitcoin/blob/master/src/test/base58_tests.cpp
+
+testBase58InvalidVector :: (Text, Maybe Text) -> Assertion
+testBase58InvalidVector (a, resM) =
+  assertEqual "decodeBase58 invalid match" resM (encodeHex <$> decodeBase58 a)
+
+base58InvalidVectors :: [(Text, Maybe Text)]
+base58InvalidVectors =
+  [ ("invalid", Nothing),
+    ("\0invalid", Nothing),
+    ("good", Just "768320"),
+    ("bad0IOl", Nothing),
+    ("goodbad0IOl", Nothing),
+    ("good\0bad0IOl", Nothing)
+    --  Haskoin does not remove white spaces before decoding base58 strings
+    -- , (" \t\n\v\f\r skip \r\f\v\n\t a", Nothing)
+    -- , (" \t\n\v\f\r skip \r\f\v\n\t ", Just "971a55")
+  ]
+
+testBase58ChkInvalidVector :: (Text, Maybe Text) -> Assertion
+testBase58ChkInvalidVector (a, resM) =
+  assertEqual
+    "decodeBase58Check invalid match"
+    resM
+    (encodeHex <$> decodeBase58Check a)
+
+base58ChkInvalidVectors :: [(Text, Maybe Text)]
+base58ChkInvalidVectors =
+  [ ("3vQB7B6MrGQZaxCuFg4oh", Just "68656c6c6f20776f726c64"),
+    ("3vQB7B6MrGQZaxCuFg4oi", Nothing),
+    ("3vQB7B6MrGQZaxCuFg4oh0IOl", Nothing),
+    ("3vQB7B6MrGQZaxCuFg4oh\00IOl", Nothing)
+  ]
+
+testCompatWitnessVector :: Ctx -> (Network, Text, Text) -> Assertion
+testCompatWitnessVector ctx (net, seckey, addr) = do
+  let seckeyM = fromWif net seckey
+  assertBool "decode seckey" (isJust seckeyM)
+  let pubkey = derivePublicKey ctx (fromJust seckeyM)
+  let addrM = addrToText btcTest (pubKeyCompatWitnessAddr ctx pubkey)
+  assertBool "address can be encoded" (isJust addrM)
+  assertEqual "witness address matches" addr (fromJust addrM)
+
+compatWitnessVectors :: [(Network, Text, Text)]
+compatWitnessVectors =
+  [ ( btcTest,
+      "cNUnpYpMsJXYCERYBciJnsWBpcYEFjdcbq6dxj4SskGhs7uHuJ7Q",
+      "2N6PDTueBHvXzW61B4oe5SW1D3v2Z3Vpbvw"
+    )
+  ]
diff --git a/test/Haskoin/BlockSpec.hs b/test/Haskoin/BlockSpec.hs
new file mode 100644
--- /dev/null
+++ b/test/Haskoin/BlockSpec.hs
@@ -0,0 +1,421 @@
+{-# LANGUAGE DuplicateRecordFields #-}
+{-# LANGUAGE OverloadedRecordDot #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Haskoin.BlockSpec
+  ( spec,
+  )
+where
+
+import Control.Monad
+import Control.Monad.State.Strict
+import Data.Default (def)
+import Data.Either (fromRight)
+import Data.Maybe (fromJust)
+import Data.String (fromString)
+import Data.String.Conversions (cs)
+import Data.Text (Text)
+import Data.Word (Word32)
+import Haskoin.Block
+import Haskoin.Crypto
+import Haskoin.Network.Constants
+import Haskoin.Network.Data
+import Haskoin.Transaction
+import Haskoin.Util
+import Haskoin.Util.Arbitrary
+import Test.HUnit hiding (State)
+import Test.Hspec
+import Test.Hspec.QuickCheck
+import Test.QuickCheck
+import Text.Printf (printf)
+
+identityTests :: Ctx -> IdentityTests
+identityTests ctx =
+  def
+    { readTests =
+        [ ReadBox (flip arbitraryBlock ctx =<< arbitraryNetwork),
+          ReadBox arbitraryBlockHash,
+          ReadBox arbitraryBlockHeader,
+          ReadBox arbitraryGetBlocks,
+          ReadBox arbitraryGetHeaders,
+          ReadBox arbitraryHeaders,
+          ReadBox arbitraryMerkleBlock,
+          ReadBox arbitraryBlockNode,
+          ReadBox arbitraryHeaderMemory
+        ],
+      jsonTests =
+        [ JsonBox (flip arbitraryBlock ctx =<< arbitraryNetwork),
+          JsonBox arbitraryBlockHash,
+          JsonBox arbitraryBlockHeader
+        ],
+      serialTests =
+        [ SerialBox (flip arbitraryBlock ctx =<< arbitraryNetwork),
+          SerialBox arbitraryBlockHash,
+          SerialBox arbitraryBlockHeader,
+          SerialBox arbitraryGetBlocks,
+          SerialBox arbitraryGetHeaders,
+          SerialBox arbitraryHeaders,
+          SerialBox arbitraryMerkleBlock,
+          SerialBox arbitraryBlockNode
+        ]
+    }
+
+myTime :: Timestamp
+myTime = 1499083075
+
+withChain :: Network -> State HeaderMemory a -> a
+withChain net f = evalState f (initialChain net)
+
+chain :: (BlockHeaders m) => Network -> BlockHeader -> Int -> m ()
+chain net bh i = do
+  bnsE <- connectBlocks net myTime bhs
+  either error (const $ return ()) bnsE
+  where
+    bhs = appendBlocks net 6 bh i
+
+spec :: Spec
+spec = prepareContext $ \ctx -> do
+  testIdentity $ identityTests ctx
+  describe "blockchain headers" $ do
+    it "gets best block on bchRegTest" $
+      let net = bchRegTest
+          bb =
+            withChain net $ do
+              chain net net.genesisHeader 100
+              getBestBlockHeader
+       in bb.height `shouldBe` 100
+    it "builds a block locator on bchRegTest" $
+      let net = bchRegTest
+          loc =
+            withChain net $ do
+              chain net net.genesisHeader 100
+              bb <- getBestBlockHeader
+              blockLocatorNodes bb
+          heights = map (.height) loc
+       in heights `shouldBe` [100, 99 .. 90] <> [88, 84, 76, 60, 28, 0]
+    it "follows split chains on bchRegTest" $
+      let net = bchRegTest
+          bb = withChain net $ splitChain net >> getBestBlockHeader
+       in bb.height `shouldBe` 4035
+  describe "block hash" $ do
+    prop "encodes and decodes block hash" $
+      forAll arbitraryBlockHash $ \h ->
+        hexToBlockHash (blockHashToHex h) == Just h
+    prop "from string block hash" $
+      forAll arbitraryBlockHash $ \h ->
+        fromString (cs $ blockHashToHex h) == h
+  describe "merkle trees" $ do
+    prop "builds tree of right width at height 1" testTreeWidth
+    prop "builds tree of right width at height 0" testBaseWidth
+    prop "builds and extracts partial merkle tree" $
+      forAll arbitraryNetwork $ \net ->
+        forAll
+          (listOf1 ((,) <$> arbitraryTxHash <*> arbitrary))
+          (buildExtractTree net)
+    it "merkle root test vectors" $ mapM_ runMerkleVector merkleVectors
+  describe "compact number" $ do
+    it "compact number local vectors" testCompact
+    it "compact number imported vectors" testCompactBitcoinCore
+  describe "asert" $
+    mapM_
+      ( \x ->
+          asertTests $
+            "test_vectors_aserti3-2d_run" ++ printf "%02d" x ++ ".txt"
+      )
+      [(1 :: Int) .. 12]
+  describe "helper functions" $ do
+    it "computes bitcoin block subsidy correctly" (testSubsidy btc)
+    it "computes regtest block subsidy correctly" (testSubsidy btcRegTest)
+
+-- 0 → → 2015 → → → → → → → 4031
+--       ↓
+--       → → 2035 → → → → → → 4035*
+--           ↓
+--           → → 2185
+splitChain :: Network -> State HeaderMemory ()
+splitChain net = do
+  start <- go 1 net.genesisHeader 2015
+  e 2015 (head start)
+  tail1 <- go 2 (head start).header 2016
+  e 4031 (head tail1)
+  tail2 <- go 3 (head start).header 20
+  e 2035 (head tail2)
+  tail3 <- go 4 (head tail2).header 2000
+  e 4035 (head tail3)
+  tail4 <- go 5 (head tail2).header 150
+  e 2185 (head tail4)
+  sp1 <- splitPoint (head tail1) (head tail3)
+  unless (sp1 == head start) $
+    error $
+      "Split point wrong between blocks 4031 and 4035: "
+        ++ show sp1.height
+  sp2 <- splitPoint (head tail4) (head tail3)
+  unless (sp2 == head tail2) $
+    error $
+      "Split point wrong between blocks 2185 and 4035: "
+        ++ show sp2.height
+  where
+    e n bn@BlockNode {} =
+      unless (bn.height == n) $
+        error $
+          "Node height "
+            ++ show bn.height
+            ++ " of first chunk should be "
+            ++ show n
+    go seed start n = do
+      let bhs = appendBlocks net seed start n
+      bnE <- connectBlocks net myTime bhs
+      case bnE of
+        Right bn -> return bn
+        Left ex -> error ex
+
+{- Merkle Trees -}
+
+testTreeWidth :: Int -> Property
+testTreeWidth i = i /= 0 ==> calcTreeWidth (abs i) (calcTreeHeight $ abs i) == 1
+
+testBaseWidth :: Int -> Property
+testBaseWidth i = i /= 0 ==> calcTreeWidth (abs i) 0 == abs i
+
+buildExtractTree :: Network -> [(TxHash, Bool)] -> Bool
+buildExtractTree net txs =
+  r == buildMerkleRoot (map fst txs) && m == map fst (filter snd txs)
+  where
+    (f, h) = buildPartialMerkle txs
+    (r, m) =
+      fromRight (error "Could not extract matches from Merkle tree") $
+        extractMatches net f h (length txs)
+
+testCompact :: Assertion
+testCompact = do
+  assertEqual "vector 1" 0x05123456 (encodeCompact 0x1234560000)
+  assertEqual "vector 2" (0x1234560000, False) (decodeCompact 0x05123456)
+  assertEqual "vector 3" 0x0600c0de (encodeCompact 0xc0de000000)
+  assertEqual "vector 4" (0xc0de000000, False) (decodeCompact 0x0600c0de)
+  assertEqual "vector 5" 0x05c0de00 (encodeCompact (-0x40de000000))
+  assertEqual "vector 6" (-0x40de000000, False) (decodeCompact 0x05c0de00)
+
+testCompactBitcoinCore :: Assertion
+testCompactBitcoinCore = do
+  assertEqual "zero" (0, False) (decodeCompact 0x00000000)
+  assertEqual
+    "zero (encode · decode)"
+    0x00000000
+    (encodeCompact . fst $ decodeCompact 0x00000000)
+  assertEqual "rounds to zero" (0, False) (decodeCompact 0x00123456)
+  assertEqual "rounds to zero" (0, False) (decodeCompact 0x01003456)
+  assertEqual "rounds to zero" (0, False) (decodeCompact 0x02000056)
+  assertEqual "rounds to zero" (0, False) (decodeCompact 0x03000000)
+  assertEqual "rounds to zero" (0, False) (decodeCompact 0x04000000)
+  assertEqual "rounds to zero" (0, False) (decodeCompact 0x00923456)
+  assertEqual "rounds to zero" (0, False) (decodeCompact 0x01803456)
+  assertEqual "rounds to zero" (0, False) (decodeCompact 0x02800056)
+  assertEqual "rounds to zero" (0, False) (decodeCompact 0x03800000)
+  assertEqual "rounds to zero" (0, False) (decodeCompact 0x04800000)
+  assertEqual "vector 1 (decode)" (0x12, False) (decodeCompact 0x01123456)
+  assertEqual
+    "vector 1 (encode · decode)"
+    0x01120000
+    (encodeCompact . fst $ decodeCompact 0x01123456)
+  assertEqual "0x80 bit set" 0x02008000 (encodeCompact 0x80)
+  assertEqual
+    "vector 2 (negative) (decode)"
+    (-0x7e, False)
+    (decodeCompact 0x01fedcba)
+  assertEqual
+    "vector 2 (negative) (encode · decode)"
+    0x01fe0000
+    (encodeCompact . fst $ decodeCompact 0x01fedcba)
+  assertEqual "vector 3 (decode)" (0x1234, False) (decodeCompact 0x02123456)
+  assertEqual
+    "vector 3 (encode · decode)"
+    0x02123400
+    (encodeCompact . fst $ decodeCompact 0x02123456)
+  assertEqual "vector 4 (decode)" (0x123456, False) (decodeCompact 0x03123456)
+  assertEqual
+    "vector 4 (encode · decode)"
+    0x03123456
+    (encodeCompact . fst $ decodeCompact 0x03123456)
+  assertEqual
+    "vector 5 (decode)"
+    (0x12345600, False)
+    (decodeCompact 0x04123456)
+  assertEqual
+    "vector 5 (encode · decode)"
+    0x04123456
+    (encodeCompact . fst $ decodeCompact 0x04123456)
+  assertEqual
+    "vector 6 (decode)"
+    (-0x12345600, False)
+    (decodeCompact 0x04923456)
+  assertEqual
+    "vector 6 (encode · decode)"
+    0x04923456
+    (encodeCompact . fst $ decodeCompact 0x04923456)
+  assertEqual
+    "vector 7 (decode)"
+    (0x92340000, False)
+    (decodeCompact 0x05009234)
+  assertEqual
+    "vector 7 (encode · decode)"
+    0x05009234
+    (encodeCompact . fst $ decodeCompact 0x05009234)
+  assertEqual
+    "vector 8 (decode)"
+    ( 0x1234560000000000000000000000000000000000000000000000000000000000,
+      False
+    )
+    (decodeCompact 0x20123456)
+  assertEqual
+    "vector 8 (encode · decode)"
+    0x20123456
+    (encodeCompact . fst $ decodeCompact 0x20123456)
+  assertBool "vector 9 (decode) (overflow)" (snd $ decodeCompact 0xff123456)
+  assertBool
+    "vector 9 (decode) (positive)"
+    ((> 0) . fst $ decodeCompact 0xff123456)
+
+runMerkleVector :: (Text, [Text]) -> Assertion
+runMerkleVector (r, hs) =
+  assertBool "merkle vector" $
+    buildMerkleRoot (map f hs) == (f r).get
+  where
+    f = fromJust . hexToTxHash
+
+merkleVectors :: [(Text, [Text])]
+merkleVectors =
+  -- Block 000000000000cd7e8cf6510303dde76121a1a791c15dba0be4be7022b07cf9e1
+  [ ( "fb6698ac95b754256c5e71b4fbe07638cb6ca83ee67f44e181b91727f09f4b1f",
+      [ "dd96fdcfaec994bf583af650ff6022980ee0ba1686d84d0a3a2d24eabf34bc52",
+        "1bc216f786a564378710ae589916fc8e092ddfb9f24fe6c47b733550d476d5d9",
+        "a1db0b0194426064b067899ff2d975fb277fd52dbb1a38370800c76dd6503d41",
+        "d69f7fb0e668fbd437d1bf5211cc34d7eb8746f50cfddf705fe10bc2f8f7035f",
+        "5b4057cd80be7df5ed2ac42b776897ed3c26e3a01e4072075b8129c587094ef6",
+        "ed6dabcfba0ef43c50d89a8a0e4b236b1bc6585d4c3bbf49728b55f44312d6bc",
+        "056aaa9a3c635909c794e9b0acc7dccb0456c59a84c6b08417335bee4515e3d3",
+        "05bae5f1d1c874171692e1fc06f664e63eb143d3f096601ef938e4a9012eee66",
+        "b5e48e94e3f2fba197b3f591e01f47e185d7834d669529d44078e41c671aab0f",
+        "3b56aeadfc0c5484fd507bc89f13f2e5f61c42e0a4ae9062eda9a9aeef7db6a4",
+        "2affa187e1ebb94a2a86578b9f64951e854ff3d346fef259acfb6d0f5212e0d3"
+      ]
+    ),
+    -- Block 00000000000007cc4b6f07bfed72bccc1ed8dd031a93969a4c22211f784457d4
+
+    ( "886fea311d2dc64c315519f2d647e43998d780d2170f77e53dc0d85bf2ee680c",
+      [ "c9c9e5211512629fd111cc071d745b8c79bf486b4ea95489eb5de08b5d786b8e",
+        "20beb0ee30dfd323ade790ce9a46ae7a174f9ea44ce22a17c4d4eb23b7016f51",
+        "d4cb7dd741e78a8f57e12f6c8ddb0361ff2a5bf9365bd7d7df761060847daf9a",
+        "ddbfa6fdd29d4b47aeaadf82a4bf0a93d58cd7d8401fabf860a1ae8eeb51f42e",
+        "9d82bafe44abee248b968c86f165051c8413482c232659795335c52922dab471",
+        "86035372d31b53efd848cea7231aa9738c209aff64d3c59b1619341afb5b6ba3",
+        "11e7a7393d9658813dfaebc04fa6d4b73bac8d641bffa7067da879523d43d030",
+        "2f676b9aa5bc0ebf3395032c84c466e40cac29f80434cd1138e31c2d0fcc5c13",
+        "37567d559fbfae07fda9a90de0ce30b202128bc8ebdfef5ad2b53e865a3478c2",
+        "0b8e6c1200c454361e94e261738429e9c9b8dcffd85ec8511bbf5dc7e2e0ada8"
+      ]
+    ),
+    -- Block 00000000839a8e6886ab5951d76f411475428afc90947ee320161bbf18eb6048
+
+    ( "0e3e2357e806b6cdb1f70b54c3a3a17b6714ee1f0e68bebb44a74b1efd512098",
+      ["0e3e2357e806b6cdb1f70b54c3a3a17b6714ee1f0e68bebb44a74b1efd512098"]
+    ),
+    -- Block 000000000004d160ac1f7b775d7c1823345aeadd5fcb29ca2ad2403bb7babd4c
+
+    ( "aae018650f513fc42d55b2210ec3ceeeb194fb1261d37989de07451fc0cbac5c",
+      [ "a4454f22831acd7904a9902c5070a3ee4bf4c2b13bc6b2dc66735dd3c4414028",
+        "45297f334278885108dd38a0b689ed95a4373dd3f7e4413e6aebdc2654fb771b"
+      ]
+    ),
+    -- Block 000000000001d1b13a7e86ddb20da178f20d6da5cd037a29c2a15b8b84cc774e
+
+    ( "ca3580505feb87544760ac14a5859659e23be05f765bbed9f86a3c9aad1a5d0c",
+      [ "60702384c6e9d34ff03c2b3e726bdc649befe603216815bd0a2974921d0d9549",
+        "11f40f58941d2a81a1616a3b84b7dd8b9d07e68750827de488c11a18f54220bb",
+        "d78e82527aa8cf16e375010bc666362c0258d3c0da1885a1871121706da8b633"
+      ]
+    ),
+    -- Block 0000000000000630a4e2266a31776e952a19b7c99a6387917d9de9032f608021
+
+    ( "dcce8be0a9a41e7bb726c5b49d957d90b5308e3dc5dce070ccbc8996e265a6c2",
+      [ "c0f58ff12cd1023b05f8f7035cc62bf50958ddb216a4e0eb5471deb7ef25fe81",
+        "24e5bbf9008641b8fcf3d076fef66c28c695362ba9f6a6042f8275a98414ee92",
+        "e8e1f72abad5e34dabc0f6de46a484b17a9af857d1c41de19482fadf6f7f4b27",
+        "540e4d34d9fd9e5ec02853054be7ad9260379bc23388489049cca1b0f7cf518a",
+        "324444835c5fe0545f98c4240011b75e6ea1bb76f41829e4cfbe7f75b6cee924",
+        "e7d31437ac21bceb0c222a82b2723e2b8a7654147e33397679f041537022a4b2",
+        "a8b5768d8b33525ee89d546a6a6897f8e42ba9d56a2c5e871a5d2ab40258dc95",
+        "7ba712b31bae8d45810a5cda3838c7e7fb9abd6e88bb4b3ee79be9ea2f714bb4",
+        "2ae1c4d927b06edaa626b230976ad8062bbae24da9378d1de2409da5ab08a26d",
+        "3c417dc8087d6878003624b74431e17fec9ca761389034b1b1e0f32cbfb11f4f",
+        "de6de7beae8d8c98c7d46b4409d5460e58e3204d8b4caed256c7471998595909",
+        "c7c3c211402b7c4379f7b01fadc67260ee58d11e8d0bcce3d68cb45f3467e99d",
+        "77aa2717e727a096d81074bd46ae59462692d20a1acc1a01b2535518ae5aeb53",
+        "4859a710bb673aca46208bbd59d1000ae990dafff5f70b56f0853aeeaea3948b",
+        "38deca6991988e461b83aa0d49ffef0f304c4b760371682d152eeb8c56a48174",
+        "648f4f50dada3574e2dfe2dc68956b01dd97d543859a3540bbe1ef5418d0e494",
+        "9cd7be42c2f0cd8bf38738c162cd05108e213ec7958bf2571cb627872963f5c4",
+        "6740e0dd8b97e23864af41839fc197238d2f0dbefce9a82c657556be65c465fa",
+        "f75c2e4b70db4b0aabc44b77af1ae75d305340fcf6e7b5f806ddcba4aa42b55d",
+        "e125c488636749da68e6696b97525a77146c0777c7946927e37afd513d74a4e6",
+        "c20526f119aea10880af631eba7f0b60385a22e0b0c402fe8508d41952e58be9",
+        "6456c023c7e245f5c57a168633a23f57f4fadb651115f807694a6bed14ae3b55",
+        "98b26e364e2888c9f264e4b5e13103c89608609774eb07ce933d8a2a45d19776",
+        "2efaa4f167bb65ba5684f8076cd9279fd67fd9c67388c8862809bab5542e637d",
+        "ec44eeb84d8d976d77079a822710b4dfdb11a2d9a03d8cc00bab0ae424e84666",
+        "410730d9f807d81ac48b8eafac6f1d36642c1c370241b367a35f0bac6ac7c05f",
+        "e95a7d0d477fd3db22756a3fd390a50c7bc48dc9e946fea9d24bd0866b3bb0e9",
+        "a72fec99d14939216628aaf7a0afc4c017113bcae964e777e6b508864eeaacc4",
+        "8548433310fcf75dbbc042121e8318c678e0a017534786dd322a91cebe8d213f"
+      ]
+    )
+  ]
+
+testSubsidy :: Network -> Assertion
+testSubsidy net = go (2 * 50 * 100 * 1000 * 1000) 0
+  where
+    go previous_subsidy halvings = do
+      let height = halvings * net.halvingInterval
+          subsidy = computeSubsidy net height
+      if halvings >= 64
+        then subsidy `shouldBe` 0
+        else do
+          subsidy `shouldBe` (previous_subsidy `div` 2)
+          go subsidy (halvings + 1)
+
+data AsertBlock = AsertBlock Int Integer Integer Word32
+
+data AsertVector = AsertVector String Integer Integer Word32 [AsertBlock]
+
+readAsertVector :: FilePath -> IO AsertVector
+readAsertVector p = do
+  (d : ah : apt : ab : _ : _ : _ : _ : xs) <- lines <$> readFile ("data/" ++ p)
+  let desc = drop 16 d
+      anchor_height = read (words ah !! 3)
+      anchor_parent_time = read (words apt !! 4)
+      anchor_nbits = read (words ab !! 3)
+      blocks = map (f . words) (init xs)
+  return $
+    AsertVector
+      desc
+      anchor_height
+      anchor_parent_time
+      anchor_nbits
+      blocks
+  where
+    f [i, h, t, g] = AsertBlock (read i) (read h) (read t) (read g)
+    f _ = undefined
+
+asertTests :: FilePath -> SpecWith ()
+asertTests file = do
+  v@(AsertVector d _ _ _ _) <- runIO $ readAsertVector file
+  it d $ testAsertBits v
+
+testAsertBits :: AsertVector -> Assertion
+testAsertBits (AsertVector _ anchor_height anchor_parent_time anchor_bits blocks) =
+  forM_ blocks $ \(AsertBlock _ h t g) ->
+    computeAsertBits
+      (2 * 24 * 60 * 60)
+      anchor_bits
+      (t - anchor_parent_time)
+      (h - anchor_height)
+      `shouldBe` g
diff --git a/test/Haskoin/Crypto/HashSpec.hs b/test/Haskoin/Crypto/HashSpec.hs
new file mode 100644
--- /dev/null
+++ b/test/Haskoin/Crypto/HashSpec.hs
@@ -0,0 +1,401 @@
+{-# LANGUAGE ImportQualifiedPost #-}
+{-# LANGUAGE OverloadedRecordDot #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE NoFieldSelectors #-}
+
+module Haskoin.Crypto.HashSpec (spec) where
+
+import Data.Bits
+import Data.ByteString (ByteString)
+import Data.ByteString.Builder
+import Data.ByteString.Char8 qualified as Char8
+import Data.ByteString.Lazy qualified as Lazy
+import Data.ByteString.Short qualified as Short
+import Data.Bytes.Get
+import Data.Bytes.Put
+import Data.Bytes.Serial
+import Data.Default (def)
+import Data.Maybe (fromJust)
+import Data.String (fromString)
+import Data.String.Conversions
+import Data.Text (Text)
+import Data.Word
+import Haskoin.Block
+import Haskoin.Crypto
+import Haskoin.Util
+import Haskoin.Util.Arbitrary
+import Test.HUnit
+import Test.Hspec
+import Test.Hspec.QuickCheck
+import Test.QuickCheck
+
+identityTests :: IdentityTests
+identityTests =
+  def
+    { readTests =
+        [ ReadBox arbitraryHash160,
+          ReadBox arbitraryHash256,
+          ReadBox arbitraryHash512,
+          ReadBox arbitraryCheckSum32
+        ],
+      serialTests =
+        [ SerialBox arbitraryHash160,
+          SerialBox arbitraryHash256,
+          SerialBox arbitraryHash512,
+          SerialBox arbitraryCheckSum32
+        ]
+    }
+
+spec :: Spec
+spec =
+  describe "Hash" $ do
+    testIdentity identityTests
+    describe "Property Tests" $ do
+      prop "join512( split512(h) ) == h" $
+        forAll arbitraryHash256 $
+          forAll arbitraryHash256 . joinSplit512
+      prop "decodeCompact . encodeCompact i == i" decEncCompact
+      prop "from string Hash512" $
+        forAll arbitraryHash512 $ \h ->
+          fromString (cs $ encodeHex $ runPutS $ serialize h) == h
+      prop "from string Hash256" $
+        forAll arbitraryHash256 $ \h ->
+          fromString (cs $ encodeHex $ runPutS $ serialize h) == h
+      prop "from string Hash160" $
+        forAll arbitraryHash160 $ \h ->
+          fromString (cs $ encodeHex $ runPutS $ serialize h) == h
+    describe "Test Vectors" $ do
+      it "Passes RIPEMD160 test vectors" $
+        mapM_ (testVector ripemd160 (.get)) ripemd160Vectors
+      it "Passes SHA1 test vectors" $
+        mapM_ (testVector sha1 (.get)) sha1Vectors
+      it "Passes SHA256 test vectors" $
+        mapM_ (testVector sha256 (.get)) sha256Vectors
+      it "Passes SHA512 test vectors" $
+        mapM_ (testVector sha512 (.get)) sha512Vectors
+      it "Passes HMAC_SHA256 test vectors" $
+        mapM_ (testHMACVector hmac256 (.get)) hmacSha256Vectors
+      it "Passes HMAC_SHA512 test vectors" $
+        mapM_ (testHMACVector hmac512 (.get)) hmacSha512Vectors
+
+joinSplit512 :: Hash256 -> Hash256 -> Bool
+joinSplit512 a b = split512 (join512 (a, b)) == (a, b)
+
+-- After encoding and decoding, we may loose precision so the new result is >=
+-- to the old one.
+decEncCompact :: Integer -> Bool
+decEncCompact i
+  -- Integer completely fits inside the mantisse
+  | abs i <= 0x007fffff = decodeCompact (encodeCompact i) == (i, False)
+  -- Otherwise precision will be lost and the decoded result will
+  -- be smaller than the original number
+  | i >= 0 = fst (decodeCompact (encodeCompact i)) < i
+  | otherwise = fst (decodeCompact (encodeCompact i)) > i
+
+-- Test vectors from:
+-- https://github.com/bitcoin/bitcoin/blob/master/src/test/crypto_tests.cpp
+
+testVector ::
+  (ByteString -> a) ->
+  (a -> Short.ShortByteString) ->
+  (ByteString, Text) ->
+  Assertion
+testVector f1 f2 (i, res) =
+  assertEqual "Hash matches" res (encodeHex (Short.fromShort $ f2 $ f1 i))
+
+testHMACVector ::
+  (ByteString -> ByteString -> a) ->
+  (a -> Short.ShortByteString) ->
+  (Text, Text, Text) ->
+  Assertion
+testHMACVector f1 f2 (k, m, res) =
+  assertEqual "Hash matches" res (encodeHex (Short.fromShort $ f2 $ f1 bsK bsM))
+  where
+    bsK = fromJust $ decodeHex k
+    bsM = fromJust $ decodeHex m
+
+longTestString :: ByteString
+longTestString =
+  Lazy.toStrict $! toLazyByteString $! go [0 .. 199999]
+  where
+    go :: [Word32] -> Builder
+    go [] = mempty
+    go (i : is) =
+      let i1 = fromIntegral $! i
+          i2 = fromIntegral $! i `shiftR` 4
+          i3 = fromIntegral $! i `shiftR` 8
+          i4 = fromIntegral $! i `shiftR` 12
+          i5 = fromIntegral $! i `shiftR` 16
+       in word8 i1 <> word8 i2 <> word8 i3 <> word8 i4 <> word8 i5 <> go is
+
+ripemd160Vectors :: [(ByteString, Text)]
+ripemd160Vectors =
+  [ ("", "9c1185a5c5e9fc54612808977ee8f548b2258d31"),
+    ("abc", "8eb208f7e05d987a9b044a8e98c6b087f15a0bfc"),
+    ("message digest", "5d0689ef49d2fae572b881b123a85ffa21595f36"),
+    ("secure hash algorithm", "20397528223b6a5f4cbc2808aba0464e645544f9"),
+    ( "RIPEMD160 is considered to be safe",
+      "a7d78608c7af8a8e728778e81576870734122b66"
+    ),
+    ( "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq",
+      "12a053384a9c0c88e405a06c27dcf49ada62eb2b"
+    ),
+    ( "For this sample, this 63-byte string will be used as input data",
+      "de90dbfee14b63fb5abf27c2ad4a82aaa5f27a11"
+    ),
+    ( "This is exactly 64 bytes long, not counting the terminating byte",
+      "eda31d51d3a623b81e19eb02e24ff65d27d67b37"
+    ),
+    (Char8.replicate 1000000 'a', "52783243c1697bdbe16d37f97f68f08325dc1528"),
+    (longTestString, "464243587bd146ea835cdf57bdae582f25ec45f1")
+  ]
+
+sha1Vectors :: [(ByteString, Text)]
+sha1Vectors =
+  [ ("", "da39a3ee5e6b4b0d3255bfef95601890afd80709"),
+    ("abc", "a9993e364706816aba3e25717850c26c9cd0d89d"),
+    ("message digest", "c12252ceda8be8994d5fa0290a47231c1d16aae3"),
+    ("secure hash algorithm", "d4d6d2f0ebe317513bbd8d967d89bac5819c2f60"),
+    ( "SHA1 is considered to be safe",
+      "f2b6650569ad3a8720348dd6ea6c497dee3a842a"
+    ),
+    ( "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq",
+      "84983e441c3bd26ebaae4aa1f95129e5e54670f1"
+    ),
+    ( "For this sample, this 63-byte string will be used as input data",
+      "4f0ea5cd0585a23d028abdc1a6684e5a8094dc49"
+    ),
+    ( "This is exactly 64 bytes long, not counting the terminating byte",
+      "fb679f23e7d1ce053313e66e127ab1b444397057"
+    ),
+    (Char8.replicate 1000000 'a', "34aa973cd4c4daa4f61eeb2bdbad27316534016f"),
+    (longTestString, "b7755760681cbfd971451668f32af5774f4656b5")
+  ]
+
+sha256Vectors :: [(ByteString, Text)]
+sha256Vectors =
+  [ ("", "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"),
+    ( "abc",
+      "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"
+    ),
+    ( "message digest",
+      "f7846f55cf23e14eebeab5b4e1550cad5b509e3348fbc4efa3a1413d393cb650"
+    ),
+    ( "secure hash algorithm",
+      "f30ceb2bb2829e79e4ca9753d35a8ecc00262d164cc077080295381cbd643f0d"
+    ),
+    ( "SHA256 is considered to be safe",
+      "6819d915c73f4d1e77e4e1b52d1fa0f9cf9beaead3939f15874bd988e2a23630"
+    ),
+    ( "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq",
+      "248d6a61d20638b8e5c026930c3e6039a33ce45964ff2167f6ecedd419db06c1"
+    ),
+    ( "For this sample, this 63-byte string will be used as input data",
+      "f08a78cbbaee082b052ae0708f32fa1e50c5c421aa772ba5dbb406a2ea6be342"
+    ),
+    ( "This is exactly 64 bytes long, not counting the terminating byte",
+      "ab64eff7e88e2e46165e29f2bce41826bd4c7b3552f6b382a9e7d3af47c245f8"
+    ),
+    ( "As Bitcoin relies on 80 byte header hashes, we want to have an example for that.",
+      "7406e8de7d6e4fffc573daef05aefb8806e7790f55eab5576f31349743cca743"
+    ),
+    ( Char8.replicate 1000000 'a',
+      "cdc76e5c9914fb9281a1c7e284d73e67f1809a48a497200e046d39ccc7112cd0"
+    ),
+    ( longTestString,
+      "a316d55510b49662420f49d145d42fb83f31ef8dc016aa4e32df049991a91e26"
+    )
+  ]
+
+sha512Vectors :: [(ByteString, Text)]
+sha512Vectors =
+  [ ( "",
+      "cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d1\
+      \3c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e"
+    ),
+    ( "abc",
+      "ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a219299\
+      \2a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f"
+    ),
+    ( "message digest",
+      "107dbf389d9e9f71a3a95f6c055b9251bc5268c2be16d6c13492ea45b0199f3309e164\
+      \55ab1e96118e8a905d5597b72038ddb372a89826046de66687bb420e7c"
+    ),
+    ( "secure hash algorithm",
+      "7746d91f3de30c68cec0dd693120a7e8b04d8073cb699bdce1a3f64127bca7a3d5db50\
+      \2e814bb63c063a7a5043b2df87c61133395f4ad1edca7fcf4b30c3236e"
+    ),
+    ( "SHA512 is considered to be safe",
+      "099e6468d889e1c79092a89ae925a9499b5408e01b66cb5b0a3bd0dfa51a99646b4a39\
+      \01caab1318189f74cd8cf2e941829012f2449df52067d3dd5b978456c2"
+    ),
+    ( "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq",
+      "204a8fc6dda82f0a0ced7beb8e08a41657c16ef468b228a8279be331a703c33596fd15\
+      \c13b1b07f9aa1d3bea57789ca031ad85c7a71dd70354ec631238ca3445"
+    ),
+    ( "For this sample, this 63-byte string will be used as input data",
+      "b3de4afbc516d2478fe9b518d063bda6c8dd65fc38402dd81d1eb7364e72fb6e6663cf\
+      \6d2771c8f5a6da09601712fb3d2a36c6ffea3e28b0818b05b0a8660766"
+    ),
+    ( "This is exactly 64 bytes long, not counting the terminating byte",
+      "70aefeaa0e7ac4f8fe17532d7185a289bee3b428d950c14fa8b713ca09814a387d2458\
+      \70e007a80ad97c369d193e41701aa07f3221d15f0e65a1ff970cedf030"
+    ),
+    ( "abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmn\
+      \opjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu",
+      "8e959b75dae313da8cf4f72814fc143f8f7779c6eb9f7fa17299aeadb6889018501d28\
+      \9e4900f7e4331b99dec4b5433ac7d329eeb6dd26545e96e55b874be909"
+    ),
+    ( Char8.replicate 1000000 'a',
+      "e718483d0ce769644e2e42c7bc15b4638e1f98b13b2044285632a803afa973ebde0ff2\
+      \44877ea60a4cb0432ce577c31beb009c5c2c49aa2e4eadb217ad8cc09b"
+    ),
+    ( longTestString,
+      "40cac46c147e6131c5193dd5f34e9d8bb4951395f27b08c558c65ff4ba2de59437de8c\
+      \3ef5459d76a52cedc02dc499a3c9ed9dedbfb3281afd9653b8a112fafc"
+    )
+  ]
+
+-- test cases 1, 2, 3, 4, 6 and 7 of RFC 4231
+hmacSha256Vectors :: [(Text, Text, Text)]
+hmacSha256Vectors =
+  [ ( "0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b",
+      "4869205468657265",
+      "b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7"
+    ),
+    ( "4a656665",
+      "7768617420646f2079612077616e7420666f72206e6f7468696e673f",
+      "5bdcc146bf60754e6a042426089575c75a003f089d2739839dec58b964ec3843"
+    ),
+    ( "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
+      "dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd\
+      \dddddddddddddddddddddddddddddd",
+      "773ea91e36800e46854db8ebd09181a72959098b3ef8c122d9635514ced565fe"
+    ),
+    ( "0102030405060708090a0b0c0d0e0f10111213141516171819",
+      "cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd\
+      \cdcdcdcdcdcdcdcdcdcdcdcdcdcdcd",
+      "82558a389a443c0ea4cc819899f2083a85f0faa3e578f8077a2e3ff46729665b"
+    ),
+    ( "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
+      \aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
+      \aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
+      \aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
+      "54657374205573696e67204c6172676572205468616e20426c6f636b2d53697a65204b\
+      \6579202d2048617368204b6579204669727374",
+      "60e431591ee0b67f0d8a26aacbf5b77f8e0bc6213728c5140546040f0ee37f54"
+    ),
+    ( "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
+      \aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
+      \aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
+      \aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
+      "5468697320697320612074657374207573696e672061206c6172676572207468616e20\
+      \626c6f636b2d73697a65206b657920616e642061206c6172676572207468616e20626c\
+      \6f636b2d73697a6520646174612e20546865206b6579206e6565647320746f20626520\
+      \686173686564206265666f7265206265696e6720757365642062792074686520484d41\
+      \4320616c676f726974686d2e",
+      "9b09ffa71b942fcb27635fbcd5b0e944bfdc63644f0713938a7f51535c3a35e2"
+    ),
+    -- Test case with key length 63 bytes.
+
+    ( "4a6566654a6566654a6566654a6566654a6566654a6566654a6566654a6566654a6566\
+      \654a6566654a6566654a6566654a6566654a6566654a6566654a6566",
+      "7768617420646f2079612077616e7420666f72206e6f7468696e673f",
+      "9de4b546756c83516720a4ad7fe7bdbeac4298c6fdd82b15f895a6d10b0769a6"
+    ),
+    -- Test case with key length 64 bytes.
+
+    ( "4a6566654a6566654a6566654a6566654a6566654a6566654a6566654a6566654a6566\
+      \654a6566654a6566654a6566654a6566654a6566654a6566654a656665",
+      "7768617420646f2079612077616e7420666f72206e6f7468696e673f",
+      "528c609a4c9254c274585334946b7c2661bad8f1fc406b20f6892478d19163dd"
+    ),
+    -- Test case with key length 65 bytes.
+
+    ( "4a6566654a6566654a6566654a6566654a6566654a6566654a6566654a6566654a6566\
+      \654a6566654a6566654a6566654a6566654a6566654a6566654a6566654a",
+      "7768617420646f2079612077616e7420666f72206e6f7468696e673f",
+      "d06af337f359a2330deffb8e3cbe4b5b7aa8ca1f208528cdbd245d5dc63c4483"
+    )
+  ]
+
+-- test cases 1, 2, 3, 4, 6 and 7 of RFC 4231
+hmacSha512Vectors :: [(Text, Text, Text)]
+hmacSha512Vectors =
+  [ ( "0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b",
+      "4869205468657265",
+      "87aa7cdea5ef619d4ff0b4241a1d6cb02379f4e2ce4ec2787ad0b30545e17cde\
+      \daa833b7d6b8a702038b274eaea3f4e4be9d914eeb61f1702e696c203a126854"
+    ),
+    ( "4a656665",
+      "7768617420646f2079612077616e7420666f72206e6f7468696e673f",
+      "164b7a7bfcf819e2e395fbe73b56e0a387bd64222e831fd610270cd7ea250554\
+      \9758bf75c05a994a6d034f65f8f0e6fdcaeab1a34d4a6b4b636e070a38bce737"
+    ),
+    ( "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
+      "dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd\
+      \dddddddddddddddddddddddddddddddddddd",
+      "fa73b0089d56a284efb0f0756c890be9b1b5dbdd8ee81a3655f83e33b2279d39\
+      \bf3e848279a722c806b485a47e67c807b946a337bee8942674278859e13292fb"
+    ),
+    ( "0102030405060708090a0b0c0d0e0f10111213141516171819",
+      "cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd\
+      \cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd",
+      "b0ba465637458c6990e5a8c5f61d4af7e576d97ff94b872de76f8050361ee3db\
+      \a91ca5c11aa25eb4d679275cc5788063a5f19741120c4f2de2adebeb10a298dd"
+    ),
+    ( "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
+      \aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
+      \aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
+      \aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
+      \aaaaaa",
+      "54657374205573696e67204c6172676572205468616e20426c6f636b2d53697a\
+      \65204b6579202d2048617368204b6579204669727374",
+      "80b24263c7c1a3ebb71493c1dd7be8b49b46d1f41b4aeec1121b013783f8f352\
+      \6b56d037e05f2598bd0fd2215d6a1e5295e64f73f63f0aec8b915a985d786598"
+    ),
+    ( "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
+      \aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
+      \aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
+      \aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
+      \aaaaaa",
+      "5468697320697320612074657374207573696e672061206c6172676572207468\
+      \616e20626c6f636b2d73697a65206b657920616e642061206c61726765722074\
+      \68616e20626c6f636b2d73697a6520646174612e20546865206b6579206e6565\
+      \647320746f20626520686173686564206265666f7265206265696e6720757365\
+      \642062792074686520484d414320616c676f726974686d2e",
+      "e37b6a775dc87dbaa4dfa9f96e5e3ffddebd71f8867289865df5a32d20cdc944\
+      \b6022cac3c4982b10d5eeb55c3e4de15134676fb6de0446065c97440fa8c6a58"
+    ),
+    -- Test case with key length 127 bytes.
+
+    ( "4a6566654a6566654a6566654a6566654a6566654a6566654a6566654a656665\
+      \4a6566654a6566654a6566654a6566654a6566654a6566654a6566654a656665\
+      \4a6566654a6566654a6566654a6566654a6566654a6566654a6566654a656665\
+      \4a6566654a6566654a6566654a6566654a6566654a6566654a6566654a6566",
+      "7768617420646f2079612077616e7420666f72206e6f7468696e673f",
+      "267424dfb8eeb999f3e5ec39a4fe9fd14c923e6187e0897063e5c9e02b2e624a\
+      \c04413e762977df71a9fb5d562b37f89dfdfb930fce2ed1fa783bbc2a203d80e"
+    ),
+    -- Test case with key length 128 bytes.
+
+    ( "4a6566654a6566654a6566654a6566654a6566654a6566654a6566654a656665\
+      \4a6566654a6566654a6566654a6566654a6566654a6566654a6566654a656665\
+      \4a6566654a6566654a6566654a6566654a6566654a6566654a6566654a656665\
+      \4a6566654a6566654a6566654a6566654a6566654a6566654a6566654a656665",
+      "7768617420646f2079612077616e7420666f72206e6f7468696e673f",
+      "43aaac07bb1dd97c82c04df921f83b16a68d76815cd1a30d3455ad43a3d80484\
+      \2bb35462be42cc2e4b5902de4d204c1c66d93b47d1383e3e13a3788687d61258"
+    ),
+    -- Test case with key length 129 bytes.
+
+    ( "4a6566654a6566654a6566654a6566654a6566654a6566654a6566654a656665\
+      \4a6566654a6566654a6566654a6566654a6566654a6566654a6566654a656665\
+      \4a6566654a6566654a6566654a6566654a6566654a6566654a6566654a656665\
+      \4a6566654a6566654a6566654a6566654a6566654a6566654a6566654a656665\
+      \4a",
+      "7768617420646f2079612077616e7420666f72206e6f7468696e673f",
+      "0b273325191cfc1b4b71d5075c8fcad67696309d292b1dad2cd23983a35feb8e\
+      \fb29795e79f2ef27f68cb1e16d76178c307a67beaad9456fac5fdffeadb16e2c"
+    )
+  ]
diff --git a/test/Haskoin/Crypto/Keys/ExtendedSpec.hs b/test/Haskoin/Crypto/Keys/ExtendedSpec.hs
new file mode 100644
--- /dev/null
+++ b/test/Haskoin/Crypto/Keys/ExtendedSpec.hs
@@ -0,0 +1,602 @@
+{-# LANGUAGE FlexibleContexts #-}
+{-# LANGUAGE ImportQualifiedPost #-}
+{-# LANGUAGE OverloadedRecordDot #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE TupleSections #-}
+
+module Haskoin.Crypto.Keys.ExtendedSpec (spec) where
+
+import Control.Monad (forM_)
+import Data.Aeson as A
+import Data.Bits ((.&.))
+import Data.ByteString.Lazy.Char8 qualified as B8
+import Data.Bytes.Get
+import Data.Bytes.Put
+import Data.Bytes.Serial
+import Data.Default (def)
+import Data.Either (isLeft)
+import Data.Maybe (fromJust, isJust, isNothing)
+import Data.String (fromString)
+import Data.String.Conversions (cs)
+import Data.Text (Text)
+import Data.Word (Word32)
+import Haskoin.Address
+import Haskoin.Crypto
+import Haskoin.Network.Constants
+import Haskoin.Util
+import Haskoin.Util.Arbitrary
+import Test.HUnit
+import Test.Hspec
+import Test.Hspec.QuickCheck
+import Test.QuickCheck hiding ((.&.))
+
+identityTests :: Ctx -> IdentityTests
+identityTests ctx =
+  def
+    { readTests =
+        [ ReadBox arbitraryXPrvKey,
+          ReadBox (arbitraryXPubKey ctx),
+          ReadBox arbitraryDerivPath,
+          ReadBox arbitraryHardPath,
+          ReadBox arbitrarySoftPath,
+          ReadBox arbitraryParsedPath,
+          ReadBox arbitraryBip32PathIndex
+        ],
+      jsonTests =
+        [ JsonBox arbitraryDerivPath,
+          JsonBox arbitraryHardPath,
+          JsonBox arbitrarySoftPath,
+          JsonBox arbitraryParsedPath
+        ],
+      marshalJsonTests =
+        [ MarshalJsonBox $ (,) <$> arbitraryNetwork <*> arbitraryXPrvKey,
+          MarshalJsonBox $
+            (,) <$> ((,ctx) <$> arbitraryNetwork) <*> arbitraryXPubKey ctx
+        ],
+      serialTests =
+        [ SerialBox arbitraryDerivPath,
+          SerialBox arbitraryHardPath,
+          SerialBox arbitrarySoftPath
+        ],
+      marshalTests =
+        [ MarshalBox $ (,) <$> arbitraryNetwork <*> arbitraryXPrvKey,
+          MarshalBox $
+            (,) <$> ((,ctx) <$> arbitraryNetwork) <*> arbitraryXPubKey ctx
+        ]
+    }
+
+spec :: Spec
+spec = prepareContext $ \ctx -> do
+  testIdentity $ identityTests ctx
+  describe "Custom identity tests" $ do
+    prop "encodes and decodes extended private key" $
+      forAll arbitraryNetwork $ \net ->
+        forAll arbitraryXPrvKey $
+          customCerealID (marshalGet net) (marshalPut net)
+    prop "encodes and decodes extended public key" $
+      forAll arbitraryNetwork $ \net ->
+        forAll (arbitraryXPubKey ctx) $
+          customCerealID (marshalGet (net, ctx)) (marshalPut (net, ctx))
+  describe "bip32 subkey derivation vector 1" $ vectorSpec ctx m1 vector1
+  describe "bip32 subkey derivation vector 2" $ vectorSpec ctx m2 vector2
+  describe "bip32 subkey derivation vector 3" $ vectorSpec ctx m3 vector3
+  describe "bip32 subkey derivation using string path" $ do
+    it "either derivations" $ testApplyPath ctx
+    it "either derivations" $ testBadApplyPath ctx
+    it "dublic derivations" $ testDerivePubPath ctx
+    it "private derivations" $ testDerivePrvPath ctx
+    it "path parsing" testParsePath
+    it "from json" testFromJsonPath
+    it "to json" testToJsonPath
+  describe "Derivation Paths" $ do
+    prop "from string derivation path" $
+      forAll arbitraryDerivPath $
+        \p -> fromString (cs $ pathToStr p) == p
+    prop "from string hard derivation path" $
+      forAll arbitraryHardPath $
+        \p -> fromString (cs $ pathToStr p) == p
+    prop "from string soft derivation path" $
+      forAll arbitrarySoftPath $
+        \p -> fromString (cs $ pathToStr p) == p
+    prop "from and to lists of derivation paths" $
+      forAll arbitraryDerivPath $
+        \p -> listToPath (pathToList p) == p
+    prop "from and to lists of hard derivation paths" $
+      forAll arbitraryHardPath $ \p ->
+        toHard (listToPath $ pathToList p) == Just p
+    prop "from and to lists of soft derivation paths" $
+      forAll arbitrarySoftPath $ \p ->
+        toSoft (listToPath $ pathToList p) == Just p
+  describe "Extended Keys" $ do
+    let net = btc
+    prop "computes pubkey of a subkey is subkey of the pubkey" $
+      forAll arbitraryXPrvKey $
+        pubKeyOfSubKeyIsSubKeyOfPubKey ctx
+    prop "exports and imports extended private key" $
+      forAll arbitraryXPrvKey $ \k ->
+        xPrvImport net (xPrvExport net k) == Just k
+    prop "exports and imports extended public key" $
+      forAll (arbitraryXPubKey ctx) $ \k ->
+        xPubImport net ctx (xPubExport net ctx k) == Just k
+
+pubKeyOfSubKeyIsSubKeyOfPubKey :: Ctx -> XPrvKey -> Word32 -> Bool
+pubKeyOfSubKeyIsSubKeyOfPubKey ctx k i =
+  deriveXPubKey ctx (prvSubKey ctx k i') == pubSubKey ctx (deriveXPubKey ctx k) i'
+  where
+    i' = fromIntegral $ i .&. 0x7fffffff -- make it a public derivation
+
+testFromJsonPath :: Assertion
+testFromJsonPath =
+  sequence_ $ do
+    path <- jsonPathVectors
+    return $
+      assertEqual
+        path
+        (Just [fromString path :: DerivPath])
+        (A.decode $ B8.pack $ "[\"" ++ path ++ "\"]")
+
+testToJsonPath :: Assertion
+testToJsonPath =
+  sequence_ $ do
+    path <- jsonPathVectors
+    return $
+      assertEqual
+        path
+        (B8.pack $ "[\"" ++ path ++ "\"]")
+        (A.encode [fromString path :: ParsedPath])
+
+jsonPathVectors :: [String]
+jsonPathVectors =
+  [ "m",
+    "m/0",
+    "m/0'",
+    "M/0'",
+    "m/2147483647",
+    "M/2147483647",
+    "m/1/2/3/4/5/6/7/8",
+    "M/1/2/3/4/5/6/7/8",
+    "m/1'/2'/3/4",
+    "M/1'/2'/3/4"
+  ]
+
+testParsePath :: Assertion
+testParsePath =
+  sequence_ $ do
+    (path, t) <- parsePathVectors
+    return $ assertBool path (t $ parsePath path)
+
+parsePathVectors :: [(String, Maybe ParsedPath -> Bool)]
+parsePathVectors =
+  [ ("m", isJust),
+    ("m/0'", isJust),
+    ("M/0'", isJust),
+    ("m/2147483648", isNothing),
+    ("m/2147483647", isJust),
+    ("M/2147483648", isNothing),
+    ("M/2147483647", isJust),
+    ("M/-1", isNothing),
+    ("M/-2147483648", isNothing),
+    ("m/1/2/3/4/5/6/7/8", isJust),
+    ("M/1/2/3/4/5/6/7/8", isJust),
+    ("m/1'/2'/3/4", isJust),
+    ("M/1'/2'/3/4", isJust),
+    ("m/1/2'/3/4'", isJust),
+    ("M/1/2'/3/4'", isJust),
+    ("meh", isNothing),
+    ("infinity", isNothing),
+    ("NaN", isNothing)
+  ]
+
+testApplyPath :: Ctx -> Assertion
+testApplyPath ctx =
+  sequence_ $ do
+    (key, path, final) <- applyPathVectors ctx
+    return $
+      assertEqual path final $
+        applyPath ctx (fromJust $ parsePath path) key
+
+testBadApplyPath :: Ctx -> Assertion
+testBadApplyPath ctx =
+  sequence_ $ do
+    (key, path) <- badApplyPathVectors ctx
+    return $
+      assertBool path $
+        isLeft $
+          applyPath ctx (fromJust $ parsePath path) key
+
+testDerivePubPath :: Ctx -> Assertion
+testDerivePubPath ctx =
+  sequence_ $ do
+    (key, path, final) <- derivePubPathVectors ctx
+    return $
+      assertEqual path final $
+        derivePubPath ctx (fromString path :: SoftPath) key
+
+testDerivePrvPath :: Ctx -> Assertion
+testDerivePrvPath ctx =
+  sequence_ $ do
+    (key, path, final) <- derivePrvPathVectors ctx
+    return $
+      assertEqual path final $
+        derivePath ctx (fromString path :: DerivPath) key
+
+derivePubPathVectors :: Ctx -> [(XPubKey, String, XPubKey)]
+derivePubPathVectors ctx =
+  [ (xpub, "M", xpub),
+    (xpub, "M/8", pubSubKey ctx xpub 8),
+    (xpub, "M/8/30/1", foldl (pubSubKey ctx) xpub [8, 30, 1])
+  ]
+  where
+    xprv =
+      fromJust $
+        xPrvImport
+          btc
+          "xprv9s21ZrQH143K46iDVRSyFfGfMgQjzC4BV3ZUfNbG7PHQrJjE53ofAn5gYkp6KQ\
+          \WzGmb8oageSRxBY8s4rjr9VXPVp2HQDbwPt4H31Gg4LpB"
+    xpub = deriveXPubKey ctx xprv
+
+derivePrvPathVectors :: Ctx -> [(XPrvKey, String, XPrvKey)]
+derivePrvPathVectors ctx =
+  [ (xprv, "m", xprv),
+    (xprv, "M", xprv),
+    (xprv, "m/8'", hardSubKey ctx xprv 8),
+    (xprv, "M/8'", hardSubKey ctx xprv 8),
+    ( xprv,
+      "m/8'/30/1",
+      foldl (prvSubKey ctx) (hardSubKey ctx xprv 8) [30, 1]
+    ),
+    ( xprv,
+      "M/8'/30/1",
+      foldl (prvSubKey ctx) (hardSubKey ctx xprv 8) [30, 1]
+    ),
+    ( xprv,
+      "m/3/20",
+      foldl (prvSubKey ctx) xprv [3, 20]
+    ),
+    ( xprv,
+      "M/3/20",
+      foldl (prvSubKey ctx) xprv [3, 20]
+    )
+  ]
+  where
+    xprv =
+      fromJust $
+        xPrvImport
+          btc
+          "xprv9s21ZrQH143K46iDVRSyFfGfMgQjzC4BV3ZUfNbG7PHQrJjE53ofAn5gYkp6KQ\
+          \WzGmb8oageSRxBY8s4rjr9VXPVp2HQDbwPt4H31Gg4LpB"
+
+applyPathVectors :: Ctx -> [(XKey, String, Either String XKey)]
+applyPathVectors ctx =
+  [ (XPrv xprv btc, "m", Right (XPrv xprv btc)),
+    (XPrv xprv btc, "M", Right (XPub xpub btc)),
+    (XPrv xprv btc, "m/8'", Right (XPrv (hardSubKey ctx xprv 8) btc)),
+    ( XPrv xprv btc,
+      "M/8'",
+      Right (XPub (deriveXPubKey ctx (hardSubKey ctx xprv 8)) btc)
+    ),
+    ( XPrv xprv btc,
+      "m/8'/30/1",
+      Right (XPrv (foldl (prvSubKey ctx) (hardSubKey ctx xprv 8) [30, 1]) btc)
+    ),
+    ( XPrv xprv btc,
+      "M/8'/30/1",
+      Right
+        ( XPub
+            (deriveXPubKey ctx (foldl (prvSubKey ctx) (hardSubKey ctx xprv 8) [30, 1]))
+            btc
+        )
+    ),
+    (XPrv xprv btc, "m/3/20", Right (XPrv (foldl (prvSubKey ctx) xprv [3, 20]) btc)),
+    ( XPrv xprv btc,
+      "M/3/20",
+      Right (XPub (deriveXPubKey ctx (foldl (prvSubKey ctx) xprv [3, 20])) btc)
+    ),
+    ( XPub xpub btc,
+      "M/3/20",
+      Right (XPub (deriveXPubKey ctx (foldl (prvSubKey ctx) xprv [3, 20])) btc)
+    )
+  ]
+  where
+    xprv =
+      fromJust $
+        xPrvImport
+          btc
+          "xprv9s21ZrQH143K46iDVRSyFfGfMgQjzC4BV3ZUfNbG7PHQrJjE53ofAn5gYkp6KQ\
+          \WzGmb8oageSRxBY8s4rjr9VXPVp2HQDbwPt4H31Gg4LpB"
+    xpub = deriveXPubKey ctx xprv
+
+badApplyPathVectors :: Ctx -> [(XKey, String)]
+badApplyPathVectors ctx =
+  [ (XPub xpub btc, "m/8'"),
+    (XPub xpub btc, "M/8'"),
+    (XPub xpub btc, "M/1/2/3'/4/5")
+  ]
+  where
+    xprv =
+      fromJust $
+        xPrvImport
+          btc
+          "xprv9s21ZrQH143K46iDVRSyFfGfMgQjzC4BV3ZUfNbG7PHQrJjE53ofAn5gYkp6KQ\
+          \WzGmb8oageSRxBY8s4rjr9VXPVp2HQDbwPt4H31Gg4LpB"
+    xpub = deriveXPubKey ctx xprv
+
+-- BIP 0032 Test Vectors
+-- https://en.bitcoin.it/wiki/BIP_0032_TestVectors
+
+bip44Addr :: DerivPath
+bip44Addr = Deriv :| 44 :| 0 :| 0 :/ 0 :/ 0
+
+vectorSpec :: Ctx -> TestKey -> [TestVector] -> Spec
+vectorSpec ctx mTxt vecTxt =
+  forM_ (parseVector ctx mTxt vecTxt) $ \(d, m, v) ->
+    it ("chain " <> cs d) $ runVector ctx m v
+
+runVector :: Ctx -> XPrvKey -> TestVector -> Assertion
+runVector ctx m v = do
+  assertBool "xPrvID" $ encodeHex ((runPutS . serialize) (xPrvID ctx m)) == head v
+  assertBool "xPrvFP" $ encodeHex ((runPutS . serialize) (xPrvFP ctx m)) == v !! 1
+  assertBool "xPrvAddr" $
+    addrToText btc (xPubAddr ctx $ deriveXPubKey ctx m) == Just (v !! 2)
+  assertBool "bip44Addr" $
+    addrToText btc (xPubAddr ctx $ deriveXPubKey ctx $ derivePath ctx bip44Addr m)
+      == Just (v !! 3)
+  assertBool "prvKey" $ encodeHex m.key.get == v !! 4
+  assertBool "xPrvWIF" $ xPrvWif btc m == v !! 5
+  assertBool "pubKey" $
+    encodeHex (exportPubKey ctx True (deriveXPubKey ctx m).key) == v !! 6
+  assertBool "chain code" $ encodeHex (runPutS (serialize m.chain)) == v !! 7
+  assertBool "Hex PubKey" $
+    encodeHex (marshal (btc, ctx) (deriveXPubKey ctx m)) == v !! 8
+  assertBool "Hex PrvKey" $ encodeHex (marshal btc m) == v !! 9
+  assertBool "Base58 PubKey" $ xPubExport btc ctx (deriveXPubKey ctx m) == v !! 10
+  assertBool "Base58 PrvKey" $ xPrvExport btc m == v !! 11
+
+-- This function was used to generate addition data for the test vectors
+genVector :: Ctx -> XPrvKey -> [(Text, Text)]
+genVector ctx m =
+  [ ("xPrvID", encodeHex (runPutS . serialize $ xPrvID ctx m)),
+    ("xPrvFP", encodeHex (runPutS . serialize $ xPrvFP ctx m)),
+    ("xPrvAddr", fromJust $ addrToText btc (xPubAddr ctx $ deriveXPubKey ctx m)),
+    ( "bip44Addr",
+      fromJust $
+        addrToText btc (xPubAddr ctx $ deriveXPubKey ctx $ derivePath ctx bip44Addr m)
+    ),
+    ("prvKey", encodeHex m.key.get),
+    ("xPrvWIF", xPrvWif btc m),
+    ("pubKey", encodeHex (exportPubKey ctx True (deriveXPubKey ctx m).key)),
+    ("chain code", encodeHex ((runPutS . serialize) m.chain)),
+    ("Hex PubKey", encodeHex (marshal (btc, ctx) (deriveXPubKey ctx m))),
+    ("Hex PrvKey", encodeHex (marshal btc m))
+  ]
+
+parseVector :: Ctx -> TestKey -> [TestVector] -> [(Text, XPrvKey, TestVector)]
+parseVector ctx mTxt vs =
+  go <$> vs
+  where
+    mast = makeXPrvKey $ fromJust $ decodeHex mTxt
+    go (d : vec) =
+      let deriv = ((.get) . fromJust . parsePath) (cs d)
+       in (d, derivePath ctx deriv mast, vec)
+    go _ = undefined
+
+type TestVector = [Text]
+
+type TestKey = Text
+
+m1 :: TestKey
+m1 = "000102030405060708090a0b0c0d0e0f"
+
+vector1 :: [TestVector]
+vector1 =
+  [ [ "m",
+      "3442193e1bb70916e914552172cd4e2dbc9df811",
+      "3442193e",
+      "15mKKb2eos1hWa6tisdPwwDC1a5J1y9nma",
+      "1NQpH6Nf8QtR2HphLRcvuVqfhXBXsiWn8r",
+      "e8f32e723decf4051aefac8e2c93c9c5b214313817cdb01a1494b917c8436b35",
+      "L52XzL2cMkHxqxBXRyEpnPQZGUs3uKiL3R11XbAdHigRzDozKZeW",
+      "0339a36013301597daef41fbe593a02cc513d0b55527ec2df1050e2e8ff49c85c2",
+      "873dff81c02f525623fd1fe5167eac3a55a049de3d314bb42ee227ffed37d508",
+      "0488b21e000000000000000000873dff81c02f525623fd1fe5167eac3a55a049de3d314bb42ee227ffed37d5080339a36013301597daef41fbe593a02cc513d0b55527ec2df1050e2e8ff49c85c2",
+      "0488ade4000000000000000000873dff81c02f525623fd1fe5167eac3a55a049de3d314bb42ee227ffed37d50800e8f32e723decf4051aefac8e2c93c9c5b214313817cdb01a1494b917c8436b35",
+      "xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8",
+      "xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi"
+    ],
+    [ "m/0'",
+      "5c1bd648ed23aa5fd50ba52b2457c11e9e80a6a7",
+      "5c1bd648",
+      "19Q2WoS5hSS6T8GjhK8KZLMgmWaq4neXrh",
+      "1DDVw6BRKUv9U8Hzg5rGsia13nDrgJQpBd",
+      "edb2e14f9ee77d26dd93b4ecede8d16ed408ce149b6cd80b0715a2d911a0afea",
+      "L5BmPijJjrKbiUfG4zbiFKNqkvuJ8usooJmzuD7Z8dkRoTThYnAT",
+      "035a784662a4a20a65bf6aab9ae98a6c068a81c52e4b032c0fb5400c706cfccc56",
+      "47fdacbd0f1097043b78c63c20c34ef4ed9a111d980047ad16282c7ae6236141",
+      "0488b21e013442193e8000000047fdacbd0f1097043b78c63c20c34ef4ed9a111d980047ad16282c7ae6236141035a784662a4a20a65bf6aab9ae98a6c068a81c52e4b032c0fb5400c706cfccc56",
+      "0488ade4013442193e8000000047fdacbd0f1097043b78c63c20c34ef4ed9a111d980047ad16282c7ae623614100edb2e14f9ee77d26dd93b4ecede8d16ed408ce149b6cd80b0715a2d911a0afea",
+      "xpub68Gmy5EdvgibQVfPdqkBBCHxA5htiqg55crXYuXoQRKfDBFA1WEjWgP6LHhwBZeNK1VTsfTFUHCdrfp1bgwQ9xv5ski8PX9rL2dZXvgGDnw",
+      "xprv9uHRZZhk6KAJC1avXpDAp4MDc3sQKNxDiPvvkX8Br5ngLNv1TxvUxt4cV1rGL5hj6KCesnDYUhd7oWgT11eZG7XnxHrnYeSvkzY7d2bhkJ7"
+    ],
+    [ "m/0'/1",
+      "bef5a2f9a56a94aab12459f72ad9cf8cf19c7bbe",
+      "bef5a2f9",
+      "1JQheacLPdM5ySCkrZkV66G2ApAXe1mqLj",
+      "1KMg6dRggXSkpz9fFyU76ru83TUSwPePEZ",
+      "3c6cb8d0f6a264c91ea8b5030fadaa8e538b020f0a387421a12de9319dc93368",
+      "KyFAjQ5rgrKvhXvNMtFB5PCSKUYD1yyPEe3xr3T34TZSUHycXtMM",
+      "03501e454bf00751f24b1b489aa925215d66af2234e3891c3b21a52bedb3cd711c",
+      "2a7857631386ba23dacac34180dd1983734e444fdbf774041578e9b6adb37c19",
+      "0488b21e025c1bd648000000012a7857631386ba23dacac34180dd1983734e444fdbf774041578e9b6adb37c1903501e454bf00751f24b1b489aa925215d66af2234e3891c3b21a52bedb3cd711c",
+      "0488ade4025c1bd648000000012a7857631386ba23dacac34180dd1983734e444fdbf774041578e9b6adb37c19003c6cb8d0f6a264c91ea8b5030fadaa8e538b020f0a387421a12de9319dc93368",
+      "xpub6ASuArnXKPbfEwhqN6e3mwBcDTgzisQN1wXN9BJcM47sSikHjJf3UFHKkNAWbWMiGj7Wf5uMash7SyYq527Hqck2AxYysAA7xmALppuCkwQ",
+      "xprv9wTYmMFdV23N2TdNG573QoEsfRrWKQgWeibmLntzniatZvR9BmLnvSxqu53Kw1UmYPxLgboyZQaXwTCg8MSY3H2EU4pWcQDnRnrVA1xe8fs"
+    ],
+    [ "m/0'/1/2'",
+      "ee7ab90cde56a8c0e2bb086ac49748b8db9dce72",
+      "ee7ab90c",
+      "1NjxqbA9aZWnh17q1UW3rB4EPu79wDXj7x",
+      "1WykKhR25y7VDT21nZEwUUKSKDz9pENJh",
+      "cbce0d719ecf7431d88e6a89fa1483e02e35092af60c042b1df2ff59fa424dca",
+      "L43t3od1Gh7Lj55Bzjj1xDAgJDcL7YFo2nEcNaMGiyRZS1CidBVU",
+      "0357bfe1e341d01c69fe5654309956cbea516822fba8a601743a012a7896ee8dc2",
+      "04466b9cc8e161e966409ca52986c584f07e9dc81f735db683c3ff6ec7b1503f",
+      "0488b21e03bef5a2f98000000204466b9cc8e161e966409ca52986c584f07e9dc81f735db683c3ff6ec7b1503f0357bfe1e341d01c69fe5654309956cbea516822fba8a601743a012a7896ee8dc2",
+      "0488ade403bef5a2f98000000204466b9cc8e161e966409ca52986c584f07e9dc81f735db683c3ff6ec7b1503f00cbce0d719ecf7431d88e6a89fa1483e02e35092af60c042b1df2ff59fa424dca",
+      "xpub6D4BDPcP2GT577Vvch3R8wDkScZWzQzMMUm3PWbmWvVJrZwQY4VUNgqFJPMM3No2dFDFGTsxxpG5uJh7n7epu4trkrX7x7DogT5Uv6fcLW5",
+      "xprv9z4pot5VBttmtdRTWfWQmoH1taj2axGVzFqSb8C9xaxKymcFzXBDptWmT7FwuEzG3ryjH4ktypQSAewRiNMjANTtpgP4mLTj34bhnZX7UiM"
+    ],
+    [ "m/0'/1/2'/2",
+      "d880d7d893848509a62d8fb74e32148dac68412f",
+      "d880d7d8",
+      "1LjmJcdPnDHhNTUgrWyhLGnRDKxQjoxAgt",
+      "1asQ3smHhv2nv5R6hPpiUfkEorJpsdwwx",
+      "0f479245fb19a38a1954c5c7c0ebab2f9bdfd96a17563ef28a6a4b1a2a764ef4",
+      "KwjQsVuMjbCP2Zmr3VaFaStav7NvevwjvvkqrWd5Qmh1XVnCteBR",
+      "02e8445082a72f29b75ca48748a914df60622a609cacfce8ed0e35804560741d29",
+      "cfb71883f01676f587d023cc53a35bc7f88f724b1f8c2892ac1275ac822a3edd",
+      "0488b21e04ee7ab90c00000002cfb71883f01676f587d023cc53a35bc7f88f724b1f8c2892ac1275ac822a3edd02e8445082a72f29b75ca48748a914df60622a609cacfce8ed0e35804560741d29",
+      "0488ade404ee7ab90c00000002cfb71883f01676f587d023cc53a35bc7f88f724b1f8c2892ac1275ac822a3edd000f479245fb19a38a1954c5c7c0ebab2f9bdfd96a17563ef28a6a4b1a2a764ef4",
+      "xpub6FHa3pjLCk84BayeJxFW2SP4XRrFd1JYnxeLeU8EqN3vDfZmbqBqaGJAyiLjTAwm6ZLRQUMv1ZACTj37sR62cfN7fe5JnJ7dh8zL4fiyLHV",
+      "xprvA2JDeKCSNNZky6uBCviVfJSKyQ1mDYahRjijr5idH2WwLsEd4Hsb2Tyh8RfQMuPh7f7RtyzTtdrbdqqsunu5Mm3wDvUAKRHSC34sJ7in334"
+    ],
+    [ "m/0'/1/2'/2/1000000000",
+      "d69aa102255fed74378278c7812701ea641fdf32",
+      "d69aa102",
+      "1LZiqrop2HGR4qrH1ULZPyBpU6AUP49Uam",
+      "1HXJog342VFdc68AB9Cb6LwVmCjvcLMiwm",
+      "471b76e389e528d6de6d816857e012c5455051cad6660850e58372a6c3e6e7c8",
+      "Kybw8izYevo5xMh1TK7aUr7jHFCxXS1zv8p3oqFz3o2zFbhRXHYs",
+      "022a471424da5e657499d1ff51cb43c47481a03b1e77f951fe64cec9f5a48f7011",
+      "c783e67b921d2beb8f6b389cc646d7263b4145701dadd2161548a8b078e65e9e",
+      "0488b21e05d880d7d83b9aca00c783e67b921d2beb8f6b389cc646d7263b4145701dadd2161548a8b078e65e9e022a471424da5e657499d1ff51cb43c47481a03b1e77f951fe64cec9f5a48f7011",
+      "0488ade405d880d7d83b9aca00c783e67b921d2beb8f6b389cc646d7263b4145701dadd2161548a8b078e65e9e00471b76e389e528d6de6d816857e012c5455051cad6660850e58372a6c3e6e7c8",
+      "xpub6H1LXWLaKsWFhvm6RVpEL9P4KfRZSW7abD2ttkWP3SSQvnyA8FSVqNTEcYFgJS2UaFcxupHiYkro49S8yGasTvXEYBVPamhGW6cFJodrTHy",
+      "xprvA41z7zogVVwxVSgdKUHDy1SKmdb533PjDz7J6N6mV6uS3ze1ai8FHa8kmHScGpWmj4WggLyQjgPie1rFSruoUihUZREPSL39UNdE3BBDu76"
+    ]
+  ]
+
+m2 :: TestKey
+m2 = "fffcf9f6f3f0edeae7e4e1dedbd8d5d2cfccc9c6c3c0bdbab7b4b1aeaba8a5a29f9c999693908d8a8784817e7b7875726f6c696663605d5a5754514e4b484542"
+
+vector2 :: [TestVector]
+vector2 =
+  [ [ "m",
+      "bd16bee53961a47d6ad888e29545434a89bdfe95",
+      "bd16bee5",
+      "1JEoxevbLLG8cVqeoGKQiAwoWbNYSUyYjg",
+      "148CGtv7bwcC933EHtcDfzDQVneur1R8Y1",
+      "4b03d6fc340455b363f51020ad3ecca4f0850280cf436c70c727923f6db46c3e",
+      "KyjXhyHF9wTphBkfpxjL8hkDXDUSbE3tKANT94kXSyh6vn6nKaoy",
+      "03cbcaa9c98c877a26977d00825c956a238e8dddfbd322cce4f74b0b5bd6ace4a7",
+      "60499f801b896d83179a4374aeb7822aaeaceaa0db1f85ee3e904c4defbd9689",
+      "0488b21e00000000000000000060499f801b896d83179a4374aeb7822aaeaceaa0db1f85ee3e904c4defbd968903cbcaa9c98c877a26977d00825c956a238e8dddfbd322cce4f74b0b5bd6ace4a7",
+      "0488ade400000000000000000060499f801b896d83179a4374aeb7822aaeaceaa0db1f85ee3e904c4defbd9689004b03d6fc340455b363f51020ad3ecca4f0850280cf436c70c727923f6db46c3e",
+      "xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB",
+      "xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U"
+    ],
+    [ "m/0",
+      "5a61ff8eb7aaca3010db97ebda76121610b78096",
+      "5a61ff8e",
+      "19EuDJdgfRkwCmRzbzVBHZWQG9QNWhftbZ",
+      "1KVyTSpsBGYs7NdyZmArEpVTfWJQSgiDCx",
+      "abe74a98f6c7eabee0428f53798f0ab8aa1bd37873999041703c742f15ac7e1e",
+      "L2ysLrR6KMSAtx7uPqmYpoTeiRzydXBattRXjXz5GDFPrdfPzKbj",
+      "02fc9e5af0ac8d9b3cecfe2a888e2117ba3d089d8585886c9c826b6b22a98d12ea",
+      "f0909affaa7ee7abe5dd4e100598d4dc53cd709d5a5c2cac40e7412f232f7c9c",
+      "0488b21e01bd16bee500000000f0909affaa7ee7abe5dd4e100598d4dc53cd709d5a5c2cac40e7412f232f7c9c02fc9e5af0ac8d9b3cecfe2a888e2117ba3d089d8585886c9c826b6b22a98d12ea",
+      "0488ade401bd16bee500000000f0909affaa7ee7abe5dd4e100598d4dc53cd709d5a5c2cac40e7412f232f7c9c00abe74a98f6c7eabee0428f53798f0ab8aa1bd37873999041703c742f15ac7e1e",
+      "xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH",
+      "xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt"
+    ],
+    [ "m/0/2147483647'",
+      "d8ab493736da02f11ed682f88339e720fb0379d1",
+      "d8ab4937",
+      "1Lke9bXGhn5VPrBuXgN12uGUphrttUErmk",
+      "14MFLsfx1nc4RKiaH9khqDTNL9CRz3q347",
+      "877c779ad9687164e9c2f4f0f4ff0340814392330693ce95a58fe18fd52e6e93",
+      "L1m5VpbXmMp57P3knskwhoMTLdhAAaXiHvnGLMribbfwzVRpz2Sr",
+      "03c01e7425647bdefa82b12d9bad5e3e6865bee0502694b94ca58b666abc0a5c3b",
+      "be17a268474a6bb9c61e1d720cf6215e2a88c5406c4aee7b38547f585c9a37d9",
+      "0488b21e025a61ff8effffffffbe17a268474a6bb9c61e1d720cf6215e2a88c5406c4aee7b38547f585c9a37d903c01e7425647bdefa82b12d9bad5e3e6865bee0502694b94ca58b666abc0a5c3b",
+      "0488ade4025a61ff8effffffffbe17a268474a6bb9c61e1d720cf6215e2a88c5406c4aee7b38547f585c9a37d900877c779ad9687164e9c2f4f0f4ff0340814392330693ce95a58fe18fd52e6e93",
+      "xpub6ASAVgeehLbnwdqV6UKMHVzgqAG8Gr6riv3Fxxpj8ksbH9ebxaEyBLZ85ySDhKiLDBrQSARLq1uNRts8RuJiHjaDMBU4Zn9h8LZNnBC5y4a",
+      "xprv9wSp6B7kry3Vj9m1zSnLvN3xH8RdsPP1Mh7fAaR7aRLcQMKTR2vidYEeEg2mUCTAwCd6vnxVrcjfy2kRgVsFawNzmjuHc2YmYRmagcEPdU9"
+    ],
+    [ "m/0/2147483647'/1",
+      "78412e3a2296a40de124307b6485bd19833e2e34",
+      "78412e3a",
+      "1BxrAr2pHpeBheusmd6fHDP2tSLAUa3qsW",
+      "19ou31MGyGW9VFx7woKBqwLe5JHhQBYaDD",
+      "704addf544a06e5ee4bea37098463c23613da32020d604506da8c0518e1da4b7",
+      "KzyzXnznxSv249b4KuNkBwowaN3akiNeEHy5FWoPCJpStZbEKXN2",
+      "03a7d1d856deb74c508e05031f9895dab54626251b3806e16b4bd12e781a7df5b9",
+      "f366f48f1ea9f2d1d3fe958c95ca84ea18e4c4ddb9366c336c927eb246fb38cb",
+      "0488b21e03d8ab493700000001f366f48f1ea9f2d1d3fe958c95ca84ea18e4c4ddb9366c336c927eb246fb38cb03a7d1d856deb74c508e05031f9895dab54626251b3806e16b4bd12e781a7df5b9",
+      "0488ade403d8ab493700000001f366f48f1ea9f2d1d3fe958c95ca84ea18e4c4ddb9366c336c927eb246fb38cb00704addf544a06e5ee4bea37098463c23613da32020d604506da8c0518e1da4b7",
+      "xpub6DF8uhdarytz3FWdA8TvFSvvAh8dP3283MY7p2V4SeE2wyWmG5mg5EwVvmdMVCQcoNJxGoWaU9DCWh89LojfZ537wTfunKau47EL2dhHKon",
+      "xprv9zFnWC6h2cLgpmSA46vutJzBcfJ8yaJGg8cX1e5StJh45BBciYTRXSd25UEPVuesF9yog62tGAQtHjXajPPdbRCHuWS6T8XA2ECKADdw4Ef"
+    ],
+    [ "m/0/2147483647'/1/2147483646'",
+      "31a507b815593dfc51ffc7245ae7e5aee304246e",
+      "31a507b8",
+      "15XVotxCAV7sRx1PSCkQNsGw3W9jT9A94R",
+      "18GYmRm4nyjk8ydvoVXFxMWQvxhksEFDZR",
+      "f1c7c871a54a804afe328b4c83a1c33b8e5ff48f5087273f04efa83b247d6a2d",
+      "L5KhaMvPYRW1ZoFmRjUtxxPypQ94m6BcDrPhqArhggdaTbbAFJEF",
+      "02d2b36900396c9282fa14628566582f206a5dd0bcc8d5e892611806cafb0301f0",
+      "637807030d55d01f9a0cb3a7839515d796bd07706386a6eddf06cc29a65a0e29",
+      "0488b21e0478412e3afffffffe637807030d55d01f9a0cb3a7839515d796bd07706386a6eddf06cc29a65a0e2902d2b36900396c9282fa14628566582f206a5dd0bcc8d5e892611806cafb0301f0",
+      "0488ade40478412e3afffffffe637807030d55d01f9a0cb3a7839515d796bd07706386a6eddf06cc29a65a0e2900f1c7c871a54a804afe328b4c83a1c33b8e5ff48f5087273f04efa83b247d6a2d",
+      "xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL",
+      "xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc"
+    ],
+    [ "m/0/2147483647'/1/2147483646'/2",
+      "26132fdbe7bf89cbc64cf8dafa3f9f88b8666220",
+      "26132fdb",
+      "14UKfRV9ZPUp6ZC9PLhqbRtxdihW9em3xt",
+      "1758mgwNZhyzpRLe4u7FjqpJtqKpaGhXh7",
+      "bb7d39bdb83ecf58f2fd82b6d918341cbef428661ef01ab97c28a4842125ac23",
+      "L3WAYNAZPxx1fr7KCz7GN9nD5qMBnNiqEJNJMU1z9MMaannAt4aK",
+      "024d902e1a2fc7a8755ab5b694c575fce742c48d9ff192e63df5193e4c7afe1f9c",
+      "9452b549be8cea3ecb7a84bec10dcfd94afe4d129ebfd3b3cb58eedf394ed271",
+      "0488b21e0531a507b8000000029452b549be8cea3ecb7a84bec10dcfd94afe4d129ebfd3b3cb58eedf394ed271024d902e1a2fc7a8755ab5b694c575fce742c48d9ff192e63df5193e4c7afe1f9c",
+      "0488ade40531a507b8000000029452b549be8cea3ecb7a84bec10dcfd94afe4d129ebfd3b3cb58eedf394ed27100bb7d39bdb83ecf58f2fd82b6d918341cbef428661ef01ab97c28a4842125ac23",
+      "xpub6FnCn6nSzZAw5Tw7cgR9bi15UV96gLZhjDstkXXxvCLsUXBGXPdSnLFbdpq8p9HmGsApME5hQTZ3emM2rnY5agb9rXpVGyy3bdW6EEgAtqt",
+      "xprvA2nrNbFZABcdryreWet9Ea4LvTJcGsqrMzxHx98MMrotbir7yrKCEXw7nadnHM8Dq38EGfSh6dqA9QWTyefMLEcBYJUuekgW4BYPJcr9E7j"
+    ]
+  ]
+
+m3 :: TestKey
+m3 = "4b381541583be4423346c643850da4b320e46a87ae3d2a4e6da11eba819cd4acba45d239319ac14f863b8d5ab5a0d0c64d2e8a1e7d1457df2e5a3c51c73235be"
+
+vector3 :: [TestVector]
+vector3 =
+  [ [ "m",
+      "41d63b50d8dd5e730cdf4c79a56fc929a757c548",
+      "41d63b50",
+      "1717ZYpXhZW5CqAbWSjDJbCey3FyKUmCSf",
+      "17rxURoF96VhmkcEGCj5LNQkmN9HVhWb7F",
+      "00ddb80b067e0d4993197fe10f2657a844a384589847602d56f0c629c81aae32",
+      "KwFPqAq9SKx1sPg15Qk56mqkHwrfGPuywtLUxoWPkiTSBoxCs8am",
+      "03683af1ba5743bdfc798cf814efeeab2735ec52d95eced528e692b8e34c4e5669",
+      "01d28a3e53cffa419ec122c968b3259e16b65076495494d97cae10bbfec3c36f",
+      "0488b21e00000000000000000001d28a3e53cffa419ec122c968b3259e16b65076495494d97cae10bbfec3c36f03683af1ba5743bdfc798cf814efeeab2735ec52d95eced528e692b8e34c4e5669",
+      "0488ade400000000000000000001d28a3e53cffa419ec122c968b3259e16b65076495494d97cae10bbfec3c36f0000ddb80b067e0d4993197fe10f2657a844a384589847602d56f0c629c81aae32",
+      "xpub661MyMwAqRbcEZVB4dScxMAdx6d4nFc9nvyvH3v4gJL378CSRZiYmhRoP7mBy6gSPSCYk6SzXPTf3ND1cZAceL7SfJ1Z3GC8vBgp2epUt13",
+      "xprv9s21ZrQH143K25QhxbucbDDuQ4naNntJRi4KUfWT7xo4EKsHt2QJDu7KXp1A3u7Bi1j8ph3EGsZ9Xvz9dGuVrtHHs7pXeTzjuxBrCmmhgC6"
+    ],
+    [ "m/0'",
+      "c61368bb50e066acd95bd04a0b23d3837fb75698",
+      "c61368bb",
+      "1K4L3YxEwg8HkSEapM4iSiGuR6HeQ53KPX",
+      "13QeQVJNNakdUU55P1fc8xPMkkeYVzn4o6",
+      "491f7a2eebc7b57028e0d3faa0acda02e75c33b03c48fb288c41e2ea44e1daef",
+      "KyfrPaeirL5yYAoZvfzyoKXSdszeLqg5vb6dNy9ymvjzZrMZY8GW",
+      "026557fdda1d5d43d79611f784780471f086d58e8126b8c40acb82272a7712e7f2",
+      "e5fea12a97b927fc9dc3d2cb0d1ea1cf50aa5a1fdc1f933e8906bb38df3377bd",
+      "0488b21e0141d63b5080000000e5fea12a97b927fc9dc3d2cb0d1ea1cf50aa5a1fdc1f933e8906bb38df3377bd026557fdda1d5d43d79611f784780471f086d58e8126b8c40acb82272a7712e7f2",
+      "0488ade40141d63b5080000000e5fea12a97b927fc9dc3d2cb0d1ea1cf50aa5a1fdc1f933e8906bb38df3377bd00491f7a2eebc7b57028e0d3faa0acda02e75c33b03c48fb288c41e2ea44e1daef",
+      "xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y",
+      "xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L"
+    ]
+  ]
diff --git a/test/Haskoin/Crypto/Keys/MnemonicSpec.hs b/test/Haskoin/Crypto/Keys/MnemonicSpec.hs
new file mode 100644
--- /dev/null
+++ b/test/Haskoin/Crypto/Keys/MnemonicSpec.hs
@@ -0,0 +1,637 @@
+{-# LANGUAGE ImportQualifiedPost #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Haskoin.Crypto.Keys.MnemonicSpec (spec) where
+
+import Control.Monad (forM_, zipWithM_)
+import Data.Bits (shiftR, (.&.))
+import Data.ByteString qualified as BS
+import Data.Either (fromRight)
+import Data.List (isPrefixOf)
+import Data.Maybe (fromJust)
+import Data.Serialize (Serialize, encode)
+import Data.Text (Text)
+import Data.Text qualified as T
+import Data.Word (Word32, Word64)
+import Haskoin (btc)
+import Haskoin.Crypto
+import Haskoin.Util
+import Haskoin.Util.Arbitrary
+import Test.HUnit
+import Test.Hspec
+import Test.QuickCheck hiding ((.&.))
+
+spec :: Spec
+spec =
+  describe "mnemonic" $ do
+    it "entropy to mnemonic sentence" toMnemonicTest
+    it "mnemonic sentence to entropy" fromMnemonicTest
+    it "mnemonic sentence to seed" mnemonicToSeedTest
+    it "mnemonic sentence with invalid checksum" fromMnemonicInvalidTest
+    it "empty mnemonic sentence is invalid" $ sequence_ [emptyMnemonicTest]
+    it "generate 12 words" $ property toMnemonic128
+    it "generate 18 words" $ property toMnemonic160
+    it "generate 24 words" $ property toMnemonic256
+    it "generate 48 words" $ property toMnemonic512
+    it "generate any number of words" $ property toMnemonicVar
+    it "encode and decode 128-bit entropy" $ property fromToMnemonic128
+    it "encode and decode 160-bit entropy" $ property fromToMnemonic160
+    it "encode and decode 256-bit entropy" $ property fromToMnemonic256
+    it "encode and decode 512-bit entropy" $ property fromToMnemonic512
+    it "encode and decode n-bit entropy" $ property fromToMnemonicVar
+    it "convert 128-bit mnemonic to seed" $ property mnemonicToSeed128
+    it "convert 160-bit mnemonic to seed" $ property mnemonicToSeed160
+    it "convert 256-bit mnemonic to seed" $ property mnemonicToSeed256
+    it "convert 512-bit mnemonic to seed" $ property mnemonicToSeed512
+    it "convert n-bit mnemonic to seed" $ property mnemonicToSeedVar
+    it "get bits" $ property getBitsByteCount
+    it "get end bits" $ property getBitsEndBits
+    it "passes the Trezor vectors" trezorVectorTests
+
+toMnemonicTest :: Assertion
+toMnemonicTest = zipWithM_ f ents mss
+  where
+    f e m = assertEqual "" m . h $ e
+    h =
+      fromRight (error "Could not decode mnemonic sentence")
+        . toMnemonic
+        . fromJust
+        . decodeHex
+
+fromMnemonicTest :: Assertion
+fromMnemonicTest = zipWithM_ f ents mss
+  where
+    f e = assertEqual "" e . h
+    h =
+      encodeHex
+        . fromRight (error "Could not decode mnemonic sentence")
+        . fromMnemonic
+
+mnemonicToSeedTest :: Assertion
+mnemonicToSeedTest = zipWithM_ f mss seeds
+  where
+    f m s = assertEqual "" s . h $ m
+    h =
+      encodeHex
+        . fromRight (error "Could not decode mnemonic seed")
+        . mnemonicToSeed "TREZOR"
+
+fromMnemonicInvalidTest :: Assertion
+fromMnemonicInvalidTest = mapM_ f invalidMss
+  where
+    f = assertBool "" . h
+    h m = case fromMnemonic m of
+      Right _ -> False
+      Left err -> "fromMnemonic: checksum failed:" `isPrefixOf` err
+
+emptyMnemonicTest :: Assertion
+emptyMnemonicTest =
+  assertBool "" $
+    case fromMnemonic "" of
+      Right _ -> False
+      Left err -> "fromMnemonic: empty mnemonic" `isPrefixOf` err
+
+trezorVectorTests :: Assertion
+trezorVectorTests =
+  forM_ trezorVectors $ \(entTxt, mnem, seedTxt, xprvTxt) -> do
+    let ent = fromJust $ decodeHex entTxt
+        seed = fromJust $ decodeHex seedTxt
+        xprv = fromJust $ xPrvImport btc xprvTxt
+    assertEqual "toMnemonic" (Right mnem) $ toMnemonic ent
+    assertEqual "mnemonicToSeed" (Right seed) $ mnemonicToSeed "TREZOR" mnem
+    assertEqual "xPrv" xprv $ makeXPrvKey seed
+
+ents :: [Text]
+ents =
+  [ "00000000000000000000000000000000",
+    "7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f",
+    "80808080808080808080808080808080",
+    "ffffffffffffffffffffffffffffffff",
+    "000000000000000000000000000000000000000000000000",
+    "7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f",
+    "808080808080808080808080808080808080808080808080",
+    "ffffffffffffffffffffffffffffffffffffffffffffffff",
+    "0000000000000000000000000000000000000000000000000000000000000000",
+    "7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f",
+    "8080808080808080808080808080808080808080808080808080808080808080",
+    "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
+    "77c2b00716cec7213839159e404db50d",
+    "b63a9c59a6e641f288ebc103017f1da9f8290b3da6bdef7b",
+    "3e141609b97933b66a060dcddc71fad1d91677db872031e85f4c015c5e7e8982",
+    "0460ef47585604c5660618db2e6a7e7f",
+    "72f60ebac5dd8add8d2a25a797102c3ce21bc029c200076f",
+    "2c85efc7f24ee4573d2b81a6ec66cee209b2dcbd09d8eddc51e0215b0b68e416",
+    "eaebabb2383351fd31d703840b32e9e2",
+    "7ac45cfe7722ee6c7ba84fbc2d5bd61b45cb2fe5eb65aa78",
+    "4fa1a8bc3e6d80ee1316050e862c1812031493212b7ec3f3bb1b08f168cabeef",
+    "18ab19a9f54a9274f03e5209a2ac8a91",
+    "18a2e1d81b8ecfb2a333adcb0c17a5b9eb76cc5d05db91a4",
+    "15da872c95a13dd738fbf50e427583ad61f18fd99f628c417a61cf8343c90419"
+  ]
+
+mss :: [Mnemonic]
+mss =
+  [ "abandon abandon abandon abandon abandon abandon abandon abandon abandon\
+    \ abandon abandon about",
+    "legal winner thank year wave sausage worth useful legal winner thank\
+    \ yellow",
+    "letter advice cage absurd amount doctor acoustic avoid letter advice\
+    \ cage above",
+    "zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo wrong",
+    "abandon abandon abandon abandon abandon abandon abandon abandon abandon\
+    \ abandon abandon abandon abandon abandon abandon abandon abandon agent",
+    "legal winner thank year wave sausage worth useful legal winner thank\
+    \ year wave sausage worth useful legal will",
+    "letter advice cage absurd amount doctor acoustic avoid letter advice\
+    \ cage absurd amount doctor acoustic avoid letter always",
+    "zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo\
+    \ when",
+    "abandon abandon abandon abandon abandon abandon abandon abandon abandon\
+    \ abandon abandon abandon abandon abandon abandon abandon abandon abandon\
+    \ abandon abandon abandon abandon abandon art",
+    "legal winner thank year wave sausage worth useful legal winner thank\
+    \ year wave sausage worth useful legal winner thank year wave sausage\
+    \ worth title",
+    "letter advice cage absurd amount doctor acoustic avoid letter advice\
+    \ cage absurd amount doctor acoustic avoid letter advice cage absurd\
+    \ amount doctor acoustic bless",
+    "zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo\
+    \ zoo zoo zoo zoo zoo vote",
+    "jelly better achieve collect unaware mountain thought cargo oxygen act\
+    \ hood bridge",
+    "renew stay biology evidence goat welcome casual join adapt armor shuffle\
+    \ fault little machine walk stumble urge swap",
+    "dignity pass list indicate nasty swamp pool script soccer toe leaf photo\
+    \ multiply desk host tomato cradle drill spread actor shine dismiss\
+    \ champion exotic",
+    "afford alter spike radar gate glance object seek swamp infant panel\
+    \ yellow",
+    "indicate race push merry suffer human cruise dwarf pole review arch keep\
+    \ canvas theme poem divorce alter left",
+    "clutch control vehicle tonight unusual clog visa ice plunge glimpse\
+    \ recipe series open hour vintage deposit universe tip job dress radar\
+    \ refuse motion taste",
+    "turtle front uncle idea crush write shrug there lottery flower risk\
+    \ shell",
+    "kiss carry display unusual confirm curtain upgrade antique rotate hello\
+    \ void custom frequent obey nut hole price segment",
+    "exile ask congress lamp submit jacket era scheme attend cousin alcohol\
+    \ catch course end lucky hurt sentence oven short ball bird grab wing top",
+    "board flee heavy tunnel powder denial science ski answer betray cargo\
+    \ cat",
+    "board blade invite damage undo sun mimic interest slam gaze truly\
+    \ inherit resist great inject rocket museum chief",
+    "beyond stage sleep clip because twist token leaf atom beauty genius food\
+    \ business side grid unable middle armed observe pair crouch tonight away\
+    \ coconut"
+  ]
+
+seeds :: [Text]
+seeds =
+  [ "c55257c360c07c72029aebc1b53c05ed0362ada38ead3e3e9efa3708e53495531f09a69\
+    \87599d18264c1e1c92f2cf141630c7a3c4ab7c81b2f001698e7463b04",
+    "2e8905819b8723fe2c1d161860e5ee1830318dbf49a83bd451cfb8440c28bd6fa457fe1\
+    \296106559a3c80937a1c1069be3a3a5bd381ee6260e8d9739fce1f607",
+    "d71de856f81a8acc65e6fc851a38d4d7ec216fd0796d0a6827a3ad6ed5511a30fa280f1\
+    \2eb2e47ed2ac03b5c462a0358d18d69fe4f985ec81778c1b370b652a8",
+    "ac27495480225222079d7be181583751e86f571027b0497b5b5d11218e0a8a133325729\
+    \17f0f8e5a589620c6f15b11c61dee327651a14c34e18231052e48c069",
+    "035895f2f481b1b0f01fcf8c289c794660b289981a78f8106447707fdd9666ca06da5a9\
+    \a565181599b79f53b844d8a71dd9f439c52a3d7b3e8a79c906ac845fa",
+    "f2b94508732bcbacbcc020faefecfc89feafa6649a5491b8c952cede496c214a0c7b3c3\
+    \92d168748f2d4a612bada0753b52a1c7ac53c1e93abd5c6320b9e95dd",
+    "107d7c02a5aa6f38c58083ff74f04c607c2d2c0ecc55501dadd72d025b751bc27fe913f\
+    \fb796f841c49b1d33b610cf0e91d3aa239027f5e99fe4ce9e5088cd65",
+    "0cd6e5d827bb62eb8fc1e262254223817fd068a74b5b449cc2f667c3f1f985a76379b43\
+    \348d952e2265b4cd129090758b3e3c2c49103b5051aac2eaeb890a528",
+    "bda85446c68413707090a52022edd26a1c9462295029f2e60cd7c4f2bbd3097170af7a4\
+    \d73245cafa9c3cca8d561a7c3de6f5d4a10be8ed2a5e608d68f92fcc8",
+    "bc09fca1804f7e69da93c2f2028eb238c227f2e9dda30cd63699232578480a4021b146a\
+    \d717fbb7e451ce9eb835f43620bf5c514db0f8add49f5d121449d3e87",
+    "c0c519bd0e91a2ed54357d9d1ebef6f5af218a153624cf4f2da911a0ed8f7a09e2ef61a\
+    \f0aca007096df430022f7a2b6fb91661a9589097069720d015e4e982f",
+    "dd48c104698c30cfe2b6142103248622fb7bb0ff692eebb00089b32d22484e1613912f0\
+    \a5b694407be899ffd31ed3992c456cdf60f5d4564b8ba3f05a69890ad",
+    "b5b6d0127db1a9d2226af0c3346031d77af31e918dba64287a1b44b8ebf63cdd52676f6\
+    \72a290aae502472cf2d602c051f3e6f18055e84e4c43897fc4e51a6ff",
+    "9248d83e06f4cd98debf5b6f010542760df925ce46cf38a1bdb4e4de7d21f5c39366941\
+    \c69e1bdbf2966e0f6e6dbece898a0e2f0a4c2b3e640953dfe8b7bbdc5",
+    "ff7f3184df8696d8bef94b6c03114dbee0ef89ff938712301d27ed8336ca89ef9635da2\
+    \0af07d4175f2bf5f3de130f39c9d9e8dd0472489c19b1a020a940da67",
+    "65f93a9f36b6c85cbe634ffc1f99f2b82cbb10b31edc7f087b4f6cb9e976e9faf76ff41\
+    \f8f27c99afdf38f7a303ba1136ee48a4c1e7fcd3dba7aa876113a36e4",
+    "3bbf9daa0dfad8229786ace5ddb4e00fa98a044ae4c4975ffd5e094dba9e0bb289349db\
+    \e2091761f30f382d4e35c4a670ee8ab50758d2c55881be69e327117ba",
+    "fe908f96f46668b2d5b37d82f558c77ed0d69dd0e7e043a5b0511c48c2f1064694a956f\
+    \86360c93dd04052a8899497ce9e985ebe0c8c52b955e6ae86d4ff4449",
+    "bdfb76a0759f301b0b899a1e3985227e53b3f51e67e3f2a65363caedf3e32fde42a66c4\
+    \04f18d7b05818c95ef3ca1e5146646856c461c073169467511680876c",
+    "ed56ff6c833c07982eb7119a8f48fd363c4a9b1601cd2de736b01045c5eb8ab4f57b079\
+    \403485d1c4924f0790dc10a971763337cb9f9c62226f64fff26397c79",
+    "095ee6f817b4c2cb30a5a797360a81a40ab0f9a4e25ecd672a3f58a0b5ba0687c096a6b\
+    \14d2c0deb3bdefce4f61d01ae07417d502429352e27695163f7447a8c",
+    "6eff1bb21562918509c73cb990260db07c0ce34ff0e3cc4a8cb3276129fbcb300bddfe0\
+    \05831350efd633909f476c45c88253276d9fd0df6ef48609e8bb7dca8",
+    "f84521c777a13b61564234bf8f8b62b3afce27fc4062b51bb5e62bdfecb23864ee6ecf0\
+    \7c1d5a97c0834307c5c852d8ceb88e7c97923c0a3b496bedd4e5f88a9",
+    "b15509eaa2d09d3efd3e006ef42151b30367dc6e3aa5e44caba3fe4d3e352e65101fbdb\
+    \86a96776b91946ff06f8eac594dc6ee1d3e82a42dfe1b40fef6bcc3fd"
+  ]
+
+invalidMss :: [Mnemonic]
+invalidMss =
+  [ "abandon abandon abandon abandon abandon abandon abandon abandon abandon\
+    \ abandon abandon abandon",
+    "legal winner thank year wave sausage worth useful legal winner thank\
+    \ thank",
+    "letter advice cage absurd amount doctor acoustic avoid letter advice\
+    \ cage sausage",
+    "zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo",
+    "abandon abandon abandon abandon abandon abandon abandon abandon abandon\
+    \ abandon abandon abandon abandon abandon abandon abandon abandon abandon",
+    "legal winner thank year wave sausage worth useful legal winner thank\
+    \ year wave sausage worth useful legal letter",
+    "letter advice cage absurd amount doctor acoustic avoid letter advice\
+    \ cage absurd amount doctor acoustic avoid letter abandon",
+    "zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo\
+    \ zoo",
+    "abandon abandon abandon abandon abandon abandon abandon abandon abandon\
+    \ abandon abandon abandon abandon abandon abandon abandon abandon abandon\
+    \ abandon abandon abandon abandon abandon abandon",
+    "legal winner thank year wave sausage worth useful legal winner thank\
+    \ year wave sausage worth useful legal winner thank year wave sausage\
+    \ worth letter",
+    "letter advice cage absurd amount doctor acoustic avoid letter advice\
+    \ cage absurd amount doctor acoustic avoid letter advice cage absurd\
+    \ amount doctor acoustic letter",
+    "zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo\
+    \ zoo zoo zoo zoo zoo zoo",
+    "jelly better achieve collect unaware mountain thought cargo oxygen act\
+    \ hood zoo",
+    "renew stay biology evidence goat welcome casual join adapt armor shuffle\
+    \ fault little machine walk stumble urge zoo",
+    "dignity pass list indicate nasty swamp pool script soccer toe leaf photo\
+    \ multiply desk host tomato cradle drill spread actor shine dismiss\
+    \ champion zoo",
+    "afford alter spike radar gate glance object seek swamp infant panel\
+    \ zoo",
+    "indicate race push merry suffer human cruise dwarf pole review arch keep\
+    \ canvas theme poem divorce alter zoo",
+    "clutch control vehicle tonight unusual clog visa ice plunge glimpse\
+    \ recipe series open hour vintage deposit universe tip job dress radar\
+    \ refuse motion zoo",
+    "turtle front uncle idea crush write shrug there lottery flower risk\
+    \ zoo",
+    "kiss carry display unusual confirm curtain upgrade antique rotate hello\
+    \ void custom frequent obey nut hole price zoo",
+    "exile ask congress lamp submit jacket era scheme attend cousin alcohol\
+    \ catch course end lucky hurt sentence oven short ball bird grab wing zoo",
+    "board flee heavy tunnel powder denial science ski answer betray cargo\
+    \ zoo",
+    "board blade invite damage undo sun mimic interest slam gaze truly\
+    \ inherit resist great inject rocket museum zoo",
+    "beyond stage sleep clip because twist token leaf atom beauty genius food\
+    \ business side grid unable middle armed observe pair crouch tonight away\
+    \ zoo"
+  ]
+
+-- Vectors from https://github.com/trezor/python-mnemonic/blob/master/vectors.json
+trezorVectors :: [(Text, Mnemonic, Text, Text)]
+trezorVectors =
+  [ ( "00000000000000000000000000000000",
+      "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about",
+      "c55257c360c07c72029aebc1b53c05ed0362ada38ead3e3e9efa3708e53495531f09a6987599d18264c1e1c92f2cf141630c7a3c4ab7c81b2f001698e7463b04",
+      "xprv9s21ZrQH143K3h3fDYiay8mocZ3afhfULfb5GX8kCBdno77K4HiA15Tg23wpbeF1pLfs1c5SPmYHrEpTuuRhxMwvKDwqdKiGJS9XFKzUsAF"
+    ),
+    ( "7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f",
+      "legal winner thank year wave sausage worth useful legal winner thank yellow",
+      "2e8905819b8723fe2c1d161860e5ee1830318dbf49a83bd451cfb8440c28bd6fa457fe1296106559a3c80937a1c1069be3a3a5bd381ee6260e8d9739fce1f607",
+      "xprv9s21ZrQH143K2gA81bYFHqU68xz1cX2APaSq5tt6MFSLeXnCKV1RVUJt9FWNTbrrryem4ZckN8k4Ls1H6nwdvDTvnV7zEXs2HgPezuVccsq"
+    ),
+    ( "80808080808080808080808080808080",
+      "letter advice cage absurd amount doctor acoustic avoid letter advice cage above",
+      "d71de856f81a8acc65e6fc851a38d4d7ec216fd0796d0a6827a3ad6ed5511a30fa280f12eb2e47ed2ac03b5c462a0358d18d69fe4f985ec81778c1b370b652a8",
+      "xprv9s21ZrQH143K2shfP28KM3nr5Ap1SXjz8gc2rAqqMEynmjt6o1qboCDpxckqXavCwdnYds6yBHZGKHv7ef2eTXy461PXUjBFQg6PrwY4Gzq"
+    ),
+    ( "ffffffffffffffffffffffffffffffff",
+      "zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo wrong",
+      "ac27495480225222079d7be181583751e86f571027b0497b5b5d11218e0a8a13332572917f0f8e5a589620c6f15b11c61dee327651a14c34e18231052e48c069",
+      "xprv9s21ZrQH143K2V4oox4M8Zmhi2Fjx5XK4Lf7GKRvPSgydU3mjZuKGCTg7UPiBUD7ydVPvSLtg9hjp7MQTYsW67rZHAXeccqYqrsx8LcXnyd"
+    ),
+    ( "000000000000000000000000000000000000000000000000",
+      "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon agent",
+      "035895f2f481b1b0f01fcf8c289c794660b289981a78f8106447707fdd9666ca06da5a9a565181599b79f53b844d8a71dd9f439c52a3d7b3e8a79c906ac845fa",
+      "xprv9s21ZrQH143K3mEDrypcZ2usWqFgzKB6jBBx9B6GfC7fu26X6hPRzVjzkqkPvDqp6g5eypdk6cyhGnBngbjeHTe4LsuLG1cCmKJka5SMkmU"
+    ),
+    ( "7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f",
+      "legal winner thank year wave sausage worth useful legal winner thank year wave sausage worth useful legal will",
+      "f2b94508732bcbacbcc020faefecfc89feafa6649a5491b8c952cede496c214a0c7b3c392d168748f2d4a612bada0753b52a1c7ac53c1e93abd5c6320b9e95dd",
+      "xprv9s21ZrQH143K3Lv9MZLj16np5GzLe7tDKQfVusBni7toqJGcnKRtHSxUwbKUyUWiwpK55g1DUSsw76TF1T93VT4gz4wt5RM23pkaQLnvBh7"
+    ),
+    ( "808080808080808080808080808080808080808080808080",
+      "letter advice cage absurd amount doctor acoustic avoid letter advice cage absurd amount doctor acoustic avoid letter always",
+      "107d7c02a5aa6f38c58083ff74f04c607c2d2c0ecc55501dadd72d025b751bc27fe913ffb796f841c49b1d33b610cf0e91d3aa239027f5e99fe4ce9e5088cd65",
+      "xprv9s21ZrQH143K3VPCbxbUtpkh9pRG371UCLDz3BjceqP1jz7XZsQ5EnNkYAEkfeZp62cDNj13ZTEVG1TEro9sZ9grfRmcYWLBhCocViKEJae"
+    ),
+    ( "ffffffffffffffffffffffffffffffffffffffffffffffff",
+      "zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo when",
+      "0cd6e5d827bb62eb8fc1e262254223817fd068a74b5b449cc2f667c3f1f985a76379b43348d952e2265b4cd129090758b3e3c2c49103b5051aac2eaeb890a528",
+      "xprv9s21ZrQH143K36Ao5jHRVhFGDbLP6FCx8BEEmpru77ef3bmA928BxsqvVM27WnvvyfWywiFN8K6yToqMaGYfzS6Db1EHAXT5TuyCLBXUfdm"
+    ),
+    ( "0000000000000000000000000000000000000000000000000000000000000000",
+      "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon art",
+      "bda85446c68413707090a52022edd26a1c9462295029f2e60cd7c4f2bbd3097170af7a4d73245cafa9c3cca8d561a7c3de6f5d4a10be8ed2a5e608d68f92fcc8",
+      "xprv9s21ZrQH143K32qBagUJAMU2LsHg3ka7jqMcV98Y7gVeVyNStwYS3U7yVVoDZ4btbRNf4h6ibWpY22iRmXq35qgLs79f312g2kj5539ebPM"
+    ),
+    ( "7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f",
+      "legal winner thank year wave sausage worth useful legal winner thank year wave sausage worth useful legal winner thank year wave sausage worth title",
+      "bc09fca1804f7e69da93c2f2028eb238c227f2e9dda30cd63699232578480a4021b146ad717fbb7e451ce9eb835f43620bf5c514db0f8add49f5d121449d3e87",
+      "xprv9s21ZrQH143K3Y1sd2XVu9wtqxJRvybCfAetjUrMMco6r3v9qZTBeXiBZkS8JxWbcGJZyio8TrZtm6pkbzG8SYt1sxwNLh3Wx7to5pgiVFU"
+    ),
+    ( "8080808080808080808080808080808080808080808080808080808080808080",
+      "letter advice cage absurd amount doctor acoustic avoid letter advice cage absurd amount doctor acoustic avoid letter advice cage absurd amount doctor acoustic bless",
+      "c0c519bd0e91a2ed54357d9d1ebef6f5af218a153624cf4f2da911a0ed8f7a09e2ef61af0aca007096df430022f7a2b6fb91661a9589097069720d015e4e982f",
+      "xprv9s21ZrQH143K3CSnQNYC3MqAAqHwxeTLhDbhF43A4ss4ciWNmCY9zQGvAKUSqVUf2vPHBTSE1rB2pg4avopqSiLVzXEU8KziNnVPauTqLRo"
+    ),
+    ( "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
+      "zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo vote",
+      "dd48c104698c30cfe2b6142103248622fb7bb0ff692eebb00089b32d22484e1613912f0a5b694407be899ffd31ed3992c456cdf60f5d4564b8ba3f05a69890ad",
+      "xprv9s21ZrQH143K2WFF16X85T2QCpndrGwx6GueB72Zf3AHwHJaknRXNF37ZmDrtHrrLSHvbuRejXcnYxoZKvRquTPyp2JiNG3XcjQyzSEgqCB"
+    ),
+    ( "9e885d952ad362caeb4efe34a8e91bd2",
+      "ozone drill grab fiber curtain grace pudding thank cruise elder eight picnic",
+      "274ddc525802f7c828d8ef7ddbcdc5304e87ac3535913611fbbfa986d0c9e5476c91689f9c8a54fd55bd38606aa6a8595ad213d4c9c9f9aca3fb217069a41028",
+      "xprv9s21ZrQH143K2oZ9stBYpoaZ2ktHj7jLz7iMqpgg1En8kKFTXJHsjxry1JbKH19YrDTicVwKPehFKTbmaxgVEc5TpHdS1aYhB2s9aFJBeJH"
+    ),
+    ( "6610b25967cdcca9d59875f5cb50b0ea75433311869e930b",
+      "gravity machine north sort system female filter attitude volume fold club stay feature office ecology stable narrow fog",
+      "628c3827a8823298ee685db84f55caa34b5cc195a778e52d45f59bcf75aba68e4d7590e101dc414bc1bbd5737666fbbef35d1f1903953b66624f910feef245ac",
+      "xprv9s21ZrQH143K3uT8eQowUjsxrmsA9YUuQQK1RLqFufzybxD6DH6gPY7NjJ5G3EPHjsWDrs9iivSbmvjc9DQJbJGatfa9pv4MZ3wjr8qWPAK"
+    ),
+    ( "68a79eaca2324873eacc50cb9c6eca8cc68ea5d936f98787c60c7ebc74e6ce7c",
+      "hamster diagram private dutch cause delay private meat slide toddler razor book happy fancy gospel tennis maple dilemma loan word shrug inflict delay length",
+      "64c87cde7e12ecf6704ab95bb1408bef047c22db4cc7491c4271d170a1b213d20b385bc1588d9c7b38f1b39d415665b8a9030c9ec653d75e65f847d8fc1fc440",
+      "xprv9s21ZrQH143K2XTAhys3pMNcGn261Fi5Ta2Pw8PwaVPhg3D8DWkzWQwjTJfskj8ofb81i9NP2cUNKxwjueJHHMQAnxtivTA75uUFqPFeWzk"
+    ),
+    ( "c0ba5a8e914111210f2bd131f3d5e08d",
+      "scheme spot photo card baby mountain device kick cradle pact join borrow",
+      "ea725895aaae8d4c1cf682c1bfd2d358d52ed9f0f0591131b559e2724bb234fca05aa9c02c57407e04ee9dc3b454aa63fbff483a8b11de949624b9f1831a9612",
+      "xprv9s21ZrQH143K3FperxDp8vFsFycKCRcJGAFmcV7umQmcnMZaLtZRt13QJDsoS5F6oYT6BB4sS6zmTmyQAEkJKxJ7yByDNtRe5asP2jFGhT6"
+    ),
+    ( "6d9be1ee6ebd27a258115aad99b7317b9c8d28b6d76431c3",
+      "horn tenant knee talent sponsor spell gate clip pulse soap slush warm silver nephew swap uncle crack brave",
+      "fd579828af3da1d32544ce4db5c73d53fc8acc4ddb1e3b251a31179cdb71e853c56d2fcb11aed39898ce6c34b10b5382772db8796e52837b54468aeb312cfc3d",
+      "xprv9s21ZrQH143K3R1SfVZZLtVbXEB9ryVxmVtVMsMwmEyEvgXN6Q84LKkLRmf4ST6QrLeBm3jQsb9gx1uo23TS7vo3vAkZGZz71uuLCcywUkt"
+    ),
+    ( "9f6a2878b2520799a44ef18bc7df394e7061a224d2c33cd015b157d746869863",
+      "panda eyebrow bullet gorilla call smoke muffin taste mesh discover soft ostrich alcohol speed nation flash devote level hobby quick inner drive ghost inside",
+      "72be8e052fc4919d2adf28d5306b5474b0069df35b02303de8c1729c9538dbb6fc2d731d5f832193cd9fb6aeecbc469594a70e3dd50811b5067f3b88b28c3e8d",
+      "xprv9s21ZrQH143K2WNnKmssvZYM96VAr47iHUQUTUyUXH3sAGNjhJANddnhw3i3y3pBbRAVk5M5qUGFr4rHbEWwXgX4qrvrceifCYQJbbFDems"
+    ),
+    ( "23db8160a31d3e0dca3688ed941adbf3",
+      "cat swing flag economy stadium alone churn speed unique patch report train",
+      "deb5f45449e615feff5640f2e49f933ff51895de3b4381832b3139941c57b59205a42480c52175b6efcffaa58a2503887c1e8b363a707256bdd2b587b46541f5",
+      "xprv9s21ZrQH143K4G28omGMogEoYgDQuigBo8AFHAGDaJdqQ99QKMQ5J6fYTMfANTJy6xBmhvsNZ1CJzRZ64PWbnTFUn6CDV2FxoMDLXdk95DQ"
+    ),
+    ( "8197a4a47f0425faeaa69deebc05ca29c0a5b5cc76ceacc0",
+      "light rule cinnamon wrap drastic word pride squirrel upgrade then income fatal apart sustain crack supply proud access",
+      "4cbdff1ca2db800fd61cae72a57475fdc6bab03e441fd63f96dabd1f183ef5b782925f00105f318309a7e9c3ea6967c7801e46c8a58082674c860a37b93eda02",
+      "xprv9s21ZrQH143K3wtsvY8L2aZyxkiWULZH4vyQE5XkHTXkmx8gHo6RUEfH3Jyr6NwkJhvano7Xb2o6UqFKWHVo5scE31SGDCAUsgVhiUuUDyh"
+    ),
+    ( "066dca1a2bb7e8a1db2832148ce9933eea0f3ac9548d793112d9a95c9407efad",
+      "all hour make first leader extend hole alien behind guard gospel lava path output census museum junior mass reopen famous sing advance salt reform",
+      "26e975ec644423f4a4c4f4215ef09b4bd7ef924e85d1d17c4cf3f136c2863cf6df0a475045652c57eb5fb41513ca2a2d67722b77e954b4b3fc11f7590449191d",
+      "xprv9s21ZrQH143K3rEfqSM4QZRVmiMuSWY9wugscmaCjYja3SbUD3KPEB1a7QXJoajyR2T1SiXU7rFVRXMV9XdYVSZe7JoUXdP4SRHTxsT1nzm"
+    ),
+    ( "f30f8c1da665478f49b001d94c5fc452",
+      "vessel ladder alter error federal sibling chat ability sun glass valve picture",
+      "2aaa9242daafcee6aa9d7269f17d4efe271e1b9a529178d7dc139cd18747090bf9d60295d0ce74309a78852a9caadf0af48aae1c6253839624076224374bc63f",
+      "xprv9s21ZrQH143K2QWV9Wn8Vvs6jbqfF1YbTCdURQW9dLFKDovpKaKrqS3SEWsXCu6ZNky9PSAENg6c9AQYHcg4PjopRGGKmdD313ZHszymnps"
+    ),
+    ( "c10ec20dc3cd9f652c7fac2f1230f7a3c828389a14392f05",
+      "scissors invite lock maple supreme raw rapid void congress muscle digital elegant little brisk hair mango congress clump",
+      "7b4a10be9d98e6cba265566db7f136718e1398c71cb581e1b2f464cac1ceedf4f3e274dc270003c670ad8d02c4558b2f8e39edea2775c9e232c7cb798b069e88",
+      "xprv9s21ZrQH143K4aERa2bq7559eMCCEs2QmmqVjUuzfy5eAeDX4mqZffkYwpzGQRE2YEEeLVRoH4CSHxianrFaVnMN2RYaPUZJhJx8S5j6puX"
+    ),
+    ( "f585c11aec520db57dd353c69554b21a89b20fb0650966fa0a9d6f74fd989d8f",
+      "void come effort suffer camp survey warrior heavy shoot primary clutch crush open amazing screen patrol group space point ten exist slush involve unfold",
+      "01f5bced59dec48e362f2c45b5de68b9fd6c92c6634f44d6d40aab69056506f0e35524a518034ddc1192e1dacd32c1ed3eaa3c3b131c88ed8e7e54c49a5d0998",
+      "xprv9s21ZrQH143K39rnQJknpH1WEPFJrzmAqqasiDcVrNuk926oizzJDDQkdiTvNPr2FYDYzWgiMiC63YmfPAa2oPyNB23r2g7d1yiK6WpqaQS"
+    )
+  ]
+
+binWordsToBS :: (Serialize a) => [a] -> BS.ByteString
+binWordsToBS = foldr f BS.empty
+  where
+    f b a = a `BS.append` encode b
+
+{- Encode mnemonic -}
+
+toMnemonic128 :: (Word64, Word64) -> Bool
+toMnemonic128 (a, b) = l == 12
+  where
+    bs = encode a `BS.append` encode b
+    l =
+      length
+        . T.words
+        . fromRight (error "Could not decode mnemonic senttence")
+        $ toMnemonic bs
+
+toMnemonic160 :: (Word32, Word64, Word64) -> Bool
+toMnemonic160 (a, b, c) = l == 15
+  where
+    bs = BS.concat [encode a, encode b, encode c]
+    l =
+      length
+        . T.words
+        . fromRight (error "Could not decode mnemonic sentence")
+        $ toMnemonic bs
+
+toMnemonic256 :: (Word64, Word64, Word64, Word64) -> Bool
+toMnemonic256 (a, b, c, d) = l == 24
+  where
+    bs = BS.concat [encode a, encode b, encode c, encode d]
+    l =
+      length
+        . T.words
+        . fromRight (error "Could not decode mnemonic sentence")
+        $ toMnemonic bs
+
+toMnemonic512 ::
+  ((Word64, Word64, Word64, Word64), (Word64, Word64, Word64, Word64)) -> Bool
+toMnemonic512 ((a, b, c, d), (e, f, g, h)) = l == 48
+  where
+    bs =
+      BS.concat
+        [ encode a,
+          encode b,
+          encode c,
+          encode d,
+          encode e,
+          encode f,
+          encode g,
+          encode h
+        ]
+    l =
+      length
+        . T.words
+        . fromRight (error "Could not decode mnemonic sentence")
+        $ toMnemonic bs
+
+toMnemonicVar :: [Word32] -> Property
+toMnemonicVar ls = not (null ls) && length ls <= 8 ==> l == wc
+  where
+    bs = binWordsToBS ls
+    bl = BS.length bs
+    cb = bl `div` 4
+    wc = (cb + bl * 8) `div` 11
+    l =
+      length
+        . T.words
+        . fromRight (error "Could not decode mnemonic sentence")
+        $ toMnemonic bs
+
+{- Encode/Decode -}
+
+fromToMnemonic128 :: (Word64, Word64) -> Bool
+fromToMnemonic128 (a, b) = bs == bs'
+  where
+    bs = encode a `BS.append` encode b
+    bs' =
+      fromRight
+        (error "Could not decode mnemonic entropy")
+        (fromMnemonic =<< toMnemonic bs)
+
+fromToMnemonic160 :: (Word32, Word64, Word64) -> Bool
+fromToMnemonic160 (a, b, c) = bs == bs'
+  where
+    bs = BS.concat [encode a, encode b, encode c]
+    bs' =
+      fromRight
+        (error "Could not decode mnemonic entropy")
+        (fromMnemonic =<< toMnemonic bs)
+
+fromToMnemonic256 :: (Word64, Word64, Word64, Word64) -> Bool
+fromToMnemonic256 (a, b, c, d) = bs == bs'
+  where
+    bs = BS.concat [encode a, encode b, encode c, encode d]
+    bs' =
+      fromRight
+        (error "Could not decode mnemonic entropy")
+        (fromMnemonic =<< toMnemonic bs)
+
+fromToMnemonic512 ::
+  ((Word64, Word64, Word64, Word64), (Word64, Word64, Word64, Word64)) -> Bool
+fromToMnemonic512 ((a, b, c, d), (e, f, g, h)) = bs == bs'
+  where
+    bs =
+      BS.concat
+        [ encode a,
+          encode b,
+          encode c,
+          encode d,
+          encode e,
+          encode f,
+          encode g,
+          encode h
+        ]
+    bs' =
+      fromRight
+        (error "Could not decode mnemonic entropy")
+        (fromMnemonic =<< toMnemonic bs)
+
+fromToMnemonicVar :: [Word32] -> Property
+fromToMnemonicVar ls = not (null ls) && length ls <= 8 ==> bs == bs'
+  where
+    bs = binWordsToBS ls
+    bs' =
+      fromRight
+        (error "Could not decode mnemonic entropy")
+        (fromMnemonic =<< toMnemonic bs)
+
+{- Mnemonic to seed -}
+
+mnemonicToSeed128 :: (Word64, Word64) -> Bool
+mnemonicToSeed128 (a, b) = l == 64
+  where
+    bs = encode a `BS.append` encode b
+    seed =
+      fromRight
+        (error "Could not decode mnemonic seed")
+        (mnemonicToSeed "" =<< toMnemonic bs)
+    l = BS.length seed
+
+mnemonicToSeed160 :: (Word32, Word64, Word64) -> Bool
+mnemonicToSeed160 (a, b, c) = l == 64
+  where
+    bs = BS.concat [encode a, encode b, encode c]
+    seed =
+      fromRight
+        (error "Could not decode mnemonic seed")
+        (mnemonicToSeed "" =<< toMnemonic bs)
+    l = BS.length seed
+
+mnemonicToSeed256 :: (Word64, Word64, Word64, Word64) -> Bool
+mnemonicToSeed256 (a, b, c, d) = l == 64
+  where
+    bs = BS.concat [encode a, encode b, encode c, encode d]
+    seed =
+      fromRight
+        (error "Could not decode mnemonic seed")
+        (mnemonicToSeed "" =<< toMnemonic bs)
+    l = BS.length seed
+
+mnemonicToSeed512 ::
+  ((Word64, Word64, Word64, Word64), (Word64, Word64, Word64, Word64)) -> Bool
+mnemonicToSeed512 ((a, b, c, d), (e, f, g, h)) = l == 64
+  where
+    bs =
+      BS.concat
+        [ encode a,
+          encode b,
+          encode c,
+          encode d,
+          encode e,
+          encode f,
+          encode g,
+          encode h
+        ]
+    seed =
+      fromRight
+        (error "Could not decode mnemonic seed")
+        (mnemonicToSeed "" =<< toMnemonic bs)
+    l = BS.length seed
+
+mnemonicToSeedVar :: [Word32] -> Property
+mnemonicToSeedVar ls = not (null ls) && length ls <= 16 ==> l == 64
+  where
+    bs = binWordsToBS ls
+    seed =
+      fromRight
+        (error "Could not decode mnemonic seed")
+        (mnemonicToSeed "" =<< toMnemonic bs)
+    l = BS.length seed
+
+{- Get bits from ByteString -}
+
+data ByteCountGen = ByteCountGen BS.ByteString Int deriving (Show)
+
+instance Arbitrary ByteCountGen where
+  arbitrary = do
+    bs <- arbitraryBS
+    i <- choose (0, BS.length bs * 8)
+    return $ ByteCountGen bs i
+
+getBitsByteCount :: ByteCountGen -> Bool
+getBitsByteCount (ByteCountGen bs i) = BS.length bits == l
+  where
+    (q, r) = i `quotRem` 8
+    bits = getBits i bs
+    l = if r == 0 then q else q + 1
+
+getBitsEndBits :: ByteCountGen -> Bool
+getBitsEndBits (ByteCountGen bs i) =
+  (r == 0) || (BS.last bits .&. (0xff `shiftR` r) == 0x00)
+  where
+    r = i `mod` 8
+    bits = getBits i bs
diff --git a/test/Haskoin/Crypto/KeysSpec.hs b/test/Haskoin/Crypto/KeysSpec.hs
new file mode 100644
--- /dev/null
+++ b/test/Haskoin/Crypto/KeysSpec.hs
@@ -0,0 +1,276 @@
+{-# LANGUAGE DuplicateRecordFields #-}
+{-# LANGUAGE ImportQualifiedPost #-}
+{-# LANGUAGE OverloadedRecordDot #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Haskoin.Crypto.KeysSpec (spec) where
+
+import Control.Lens
+import Control.Monad
+import Data.Aeson as A
+import Data.Aeson.Lens
+import Data.ByteString qualified as B
+import Data.ByteString.Char8 qualified as C
+import Data.Bytes.Get
+import Data.Bytes.Put
+import Data.Bytes.Serial
+import Data.Default (def)
+import Data.Maybe
+import Data.Serialize qualified as S
+import Data.String (fromString)
+import Data.String.Conversions (cs)
+import Data.Text (Text)
+import Haskoin.Address
+import Haskoin.Crypto
+import Haskoin.Network.Constants
+import Haskoin.Script
+import Haskoin.Util
+import Haskoin.Util.Arbitrary
+import Test.HUnit
+import Test.Hspec
+import Test.Hspec.QuickCheck
+import Test.QuickCheck
+
+identityTests :: Ctx -> IdentityTests
+identityTests ctx =
+  def
+    { readTests =
+        [ ReadBox (arbitrary :: Gen SecKey),
+          ReadBox arbitraryPrivateKey,
+          ReadBox (arbitraryPublicKey ctx)
+        ],
+      marshalJsonTests =
+        [ MarshalJsonBox $ (,) <$> arbitraryNetwork <*> arbitraryPrivateKey,
+          MarshalJsonBox ((,) ctx <$> arbitraryPublicKey ctx)
+        ],
+      serialTests =
+        [ SerialBox arbitraryPrivateKey
+        ],
+      marshalTests =
+        [ MarshalBox ((,) ctx <$> arbitraryPublicKey ctx)
+        ]
+    }
+
+spec :: Spec
+spec = prepareContext $ \ctx -> do
+  describe "Key pair property checks" $ do
+    testIdentity $ identityTests ctx
+    prop "Public key is canonical" $
+      forAll (arbitraryKeyPair ctx) (isCanonicalPubKey ctx . snd)
+    prop "Key pair key show . read identity" $
+      forAll (arbitraryKeyPair ctx) $ \x ->
+        (read . show) x == x
+    prop "Public key binary serialization" $ do
+      forAll (arbitraryKeyPair ctx) $ \(sec, pub) ->
+        (unmarshal ctx . marshal ctx) pub == Right pub
+    prop "fromWif . toWif identity" $
+      forAll arbitraryNetwork $ \net ->
+        forAll (arbitraryKeyPair ctx) $ \(key, _) ->
+          (fromWif net . toWif net) key == Just key
+    prop "WIF binary identity" $
+      forAll arbitraryNetwork $ \net ->
+        forAll (arbitraryKeyPair ctx) $ \(key, _) ->
+          (runGetS deserialize . runPutS . serialize) key == Right key
+  describe "Bitcoin core vectors /src/test/key_tests.cpp" $ do
+    it "Passes WIF decoding tests" testPrivkey
+    it "Passes SecKey compression tests" testPrvKeyCompressed
+    it "Passes PubKey compression tests" $ testKeyCompressed ctx
+    it "Passes address matching tests" $ testMatchingAddress ctx
+    it "Passes signature verification" $ testSigs ctx
+    it "Passes deterministic signing tests" $ testDetSigning ctx
+  describe "MiniKey vectors" $
+    it "Passes MiniKey decoding tests" testMiniKey
+  describe "key_io_valid.json vectors" $ do
+    vectors <- runIO (readTestFile "key_io_valid.json" :: IO [(Text, Text, A.Value)])
+    it "Passes the key_io_valid.json vectors" $
+      mapM_ (testKeyIOValidVector ctx) vectors
+  describe "key_io_invalid.json vectors" $ do
+    vectors <- runIO (readTestFile "key_io_invalid.json" :: IO [[Text]])
+    it "Passes the key_io_invalid.json vectors" $
+      mapM_ (testKeyIOInvalidVector ctx) vectors
+
+-- github.com/bitcoin/bitcoin/blob/master/src/script.cpp
+-- from function IsCanonicalPubKey
+isCanonicalPubKey :: Ctx -> PublicKey -> Bool
+isCanonicalPubKey ctx p =
+  not $
+    -- Non-canonical public key: too short
+    (B.length bs < 33)
+      ||
+      -- Non-canonical public key: invalid length for uncompressed key
+      (B.index bs 0 == 4 && B.length bs /= 65)
+      ||
+      -- Non-canonical public key: invalid length for compressed key
+      (B.index bs 0 `elem` [2, 3] && B.length bs /= 33)
+      ||
+      -- Non-canonical public key: compressed nor uncompressed
+      (B.index bs 0 `notElem` [2, 3, 4])
+  where
+    bs = marshal ctx p
+
+testMiniKey :: Assertion
+testMiniKey =
+  assertEqual "fromMiniKey" (Just res) (go "S6c56bnXQiBjk9mqSYE7ykVQ7NzrRy")
+  where
+    go = fmap (encodeHex . (.key.get)) . fromMiniKey
+    res = "4c7a9640c72dc2099f23715d0c8a0d8a35f8906e3cab61dd3f78b67bf887c9ab"
+
+-- Test vectors from:
+-- https://github.com/bitcoin/bitcoin/blob/master/src/test/key_io_tests.cpp
+
+testKeyIOValidVector :: Ctx -> (Text, Text, A.Value) -> Assertion
+testKeyIOValidVector ctx (a, payload, obj)
+  | disabled = return () -- There are invalid version 1 bech32 addresses
+  | isPrv = do
+      -- Test from WIF to SecKey
+      let isComp = obj ^?! key "isCompressed" . _Bool
+          prvKeyM = fromWif net a
+          prvKeyHexM = encodeHex . (.key.get) <$> prvKeyM
+      assertBool "Valid PrvKey" $ isJust prvKeyM
+      assertEqual "Valid compression" (Just isComp) ((.compress) <$> prvKeyM)
+      assertEqual "WIF matches payload" (Just payload) prvKeyHexM
+      let prvAsPubM :: Maybe ScriptOutput
+          prvAsPubM = (eitherToMaybe . unmarshal ctx <=< decodeHex) a
+      assertBool "PrvKey is invalid ScriptOutput" $ isNothing prvAsPubM
+      -- Test from SecKey to WIF
+      let secM = secKey =<< decodeHex payload
+          wifM = toWif net . wrapSecKey isComp <$> secM
+      assertEqual "Payload matches WIF" (Just a) wifM
+  | otherwise = do
+      -- Test Addr to Script
+      let addrM = textToAddr net a
+          scriptM = encodeHex . marshal ctx . addressToOutput <$> addrM
+      assertBool ("Valid Address " <> cs a) $ isJust addrM
+      assertEqual "Address matches payload" (Just payload) scriptM
+      let pubAsWifM = fromWif net a
+          pubAsSecM = secKey =<< decodeHex a
+      assertBool "Address is invalid Wif" $ isNothing pubAsWifM
+      assertBool "Address is invalid PrvKey" $ isNothing pubAsSecM
+      -- Test Script to Addr
+      let outM = eitherToMaybe . unmarshal ctx =<< decodeHex payload
+          resM = addrToText net =<< outputAddress ctx =<< outM
+      assertEqual "Payload matches address" (Just a) resM
+  where
+    isPrv = obj ^?! key "isPrivkey" . _Bool
+    disabled = fromMaybe False $ obj ^? key "disabled" . _Bool
+    chain = obj ^?! key "chain" . _String
+    net =
+      case chain of
+        "main" -> btc
+        "test" -> btcTest
+        "regtest" -> btcRegTest
+        _ -> error "Invalid chain key in key_io_valid.json"
+
+testKeyIOInvalidVector :: Ctx -> [Text] -> Assertion
+testKeyIOInvalidVector ctx [a] = do
+  let wifMs = (`fromWif` a) <$> allNets
+      secKeyM = (secKey <=< decodeHex) a
+      scriptM :: Maybe ScriptOutput
+      scriptM = (eitherToMaybe . unmarshal ctx <=< decodeHex) a
+  assertBool "Payload is invalid WIF" $ all isNothing wifMs
+  assertBool "Payload is invalid SecKey" $ isNothing secKeyM
+  assertBool "Payload is invalid Script" $ isNothing scriptM
+testKeyIOInvalidVector _ _ = assertFailure "Invalid test vector"
+
+-- Test vectors from:
+-- https://github.com/bitcoin/bitcoin/blob/master/src/test/key_tests.cpp
+
+testPrivkey :: Assertion
+testPrivkey = do
+  assertBool "Key 1" $ isJust $ fromWif btc strSecret1
+  assertBool "Key 2" $ isJust $ fromWif btc strSecret2
+  assertBool "Key 1C" $ isJust $ fromWif btc strSecret1C
+  assertBool "Key 2C" $ isJust $ fromWif btc strSecret2C
+  assertBool "Bad key" $ isNothing $ fromWif btc strAddressBad
+
+testPrvKeyCompressed :: Assertion
+testPrvKeyCompressed = do
+  assertBool "Key 1" $ not sec1.compress
+  assertBool "Key 2" $ not sec2.compress
+  assertBool "Key 1C" sec1C.compress
+  assertBool "Key 2C" sec2C.compress
+
+testKeyCompressed :: Ctx -> Assertion
+testKeyCompressed ctx = do
+  assertBool "Key 1" $ not (pub1 ctx).compress
+  assertBool "Key 2" $ not (pub2 ctx).compress
+  assertBool "Key 1C" (pub1C ctx).compress
+  assertBool "Key 2C" (pub2C ctx).compress
+
+testMatchingAddress :: Ctx -> Assertion
+testMatchingAddress ctx = do
+  assertEqual "Key 1" (Just addr1) $ addrToText btc (pubKeyAddr ctx (pub1 ctx))
+  assertEqual "Key 2" (Just addr2) $ addrToText btc (pubKeyAddr ctx (pub2 ctx))
+  assertEqual "Key 1C" (Just addr1C) $ addrToText btc (pubKeyAddr ctx (pub1C ctx))
+  assertEqual "Key 2C" (Just addr2C) $ addrToText btc (pubKeyAddr ctx (pub2C ctx))
+
+testSigs :: Ctx -> Assertion
+testSigs ctx = forM_ sigMsg $ testSignature ctx . doubleSHA256
+
+sigMsg :: [B.ByteString]
+sigMsg =
+  [ mconcat ["Very secret message ", C.pack (show (i :: Int)), ": 11"]
+    | i <- [0 .. 15]
+  ]
+
+testSignature :: Ctx -> Hash256 -> Assertion
+testSignature ctx h = do
+  let sign1 = signHash ctx sec1.key h
+      sign2 = signHash ctx sec2.key h
+      sign1C = signHash ctx sec1C.key h
+      sign2C = signHash ctx sec2C.key h
+  assertBool "Key 1, Sign1" $ verifyHashSig ctx h sign1 (pub1 ctx).point
+  assertBool "Key 1, Sign2" $ not $ verifyHashSig ctx h sign2 (pub1 ctx).point
+  assertBool "Key 1, Sign1C" $ verifyHashSig ctx h sign1C (pub1 ctx).point
+  assertBool "Key 1, Sign2C" $ not $ verifyHashSig ctx h sign2C (pub1 ctx).point
+  assertBool "Key 2, Sign1" $ not $ verifyHashSig ctx h sign1 (pub2 ctx).point
+  assertBool "Key 2, Sign2" $ verifyHashSig ctx h sign2 (pub2 ctx).point
+  assertBool "Key 2, Sign1C" $ not $ verifyHashSig ctx h sign1C (pub2 ctx).point
+  assertBool "Key 2, Sign2C" $ verifyHashSig ctx h sign2C (pub2 ctx).point
+  assertBool "Key 1C, Sign1" $ verifyHashSig ctx h sign1 (pub1C ctx).point
+  assertBool "Key 1C, Sign2" $ not $ verifyHashSig ctx h sign2 (pub1C ctx).point
+  assertBool "Key 1C, Sign1C" $ verifyHashSig ctx h sign1C (pub1C ctx).point
+  assertBool "Key 1C, Sign2C" $ not $ verifyHashSig ctx h sign2C (pub1C ctx).point
+  assertBool "Key 2C, Sign1" $ not $ verifyHashSig ctx h sign1 (pub2C ctx).point
+  assertBool "Key 2C, Sign2" $ verifyHashSig ctx h sign2 (pub2C ctx).point
+  assertBool "Key 2C, Sign1C" $ not $ verifyHashSig ctx h sign1C (pub2C ctx).point
+  assertBool "Key 2C, Sign2C" $ verifyHashSig ctx h sign2C (pub2C ctx).point
+
+testDetSigning :: Ctx -> Assertion
+testDetSigning ctx = do
+  let m = doubleSHA256 ("Very deterministic message" :: B.ByteString)
+  assertEqual
+    "Det sig 1"
+    (signHash ctx sec1.key m)
+    (signHash ctx sec1C.key m)
+  assertEqual
+    "Det sig 2"
+    (signHash ctx sec2.key m)
+    (signHash ctx sec2C.key m)
+
+strSecret1, strSecret2, strSecret1C, strSecret2C :: Text
+strSecret1 = "5HxWvvfubhXpYYpS3tJkw6fq9jE9j18THftkZjHHfmFiWtmAbrj"
+strSecret2 = "5KC4ejrDjv152FGwP386VD1i2NYc5KkfSMyv1nGy1VGDxGHqVY3"
+strSecret1C = "Kwr371tjA9u2rFSMZjTNun2PXXP3WPZu2afRHTcta6KxEUdm1vEw"
+strSecret2C = "L3Hq7a8FEQwJkW1M2GNKDW28546Vp5miewcCzSqUD9kCAXrJdS3g"
+
+sec1, sec2, sec1C, sec2C :: PrivateKey
+sec1 = fromJust $ fromWif btc strSecret1
+sec2 = fromJust $ fromWif btc strSecret2
+sec1C = fromJust $ fromWif btc strSecret1C
+sec2C = fromJust $ fromWif btc strSecret2C
+
+addr1, addr2, addr1C, addr2C :: Text
+addr1 = "1QFqqMUD55ZV3PJEJZtaKCsQmjLT6JkjvJ"
+addr2 = "1F5y5E5FMc5YzdJtB9hLaUe43GDxEKXENJ"
+addr1C = "1NoJrossxPBKfCHuJXT4HadJrXRE9Fxiqs"
+addr2C = "1CRj2HyM1CXWzHAXLQtiGLyggNT9WQqsDs"
+
+strAddressBad :: Text
+strAddressBad = "1HV9Lc3sNHZxwj4Zk6fB38tEmBryq2cBiF"
+
+pub1, pub2, pub1C, pub2C :: Ctx -> PublicKey
+pub1 ctx = derivePublicKey ctx sec1
+pub2 ctx = derivePublicKey ctx sec2
+pub1C ctx = derivePublicKey ctx sec1C
+pub2C ctx = derivePublicKey ctx sec2C
diff --git a/test/Haskoin/Crypto/SignatureSpec.hs b/test/Haskoin/Crypto/SignatureSpec.hs
new file mode 100644
--- /dev/null
+++ b/test/Haskoin/Crypto/SignatureSpec.hs
@@ -0,0 +1,449 @@
+{-# LANGUAGE ImportQualifiedPost #-}
+{-# LANGUAGE OverloadedRecordDot #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Haskoin.Crypto.SignatureSpec (spec) where
+
+import Control.Monad
+import Data.Bits (testBit)
+import Data.ByteString (ByteString)
+import Data.ByteString qualified as BS
+import Data.Map.Strict (Map)
+import Data.Map.Strict qualified as Map
+import Data.Maybe
+import Data.Serialize as S
+import Data.String.Conversions (cs)
+import Data.Text (Text)
+import Haskoin.Address
+import Haskoin.Crypto
+import Haskoin.Network.Constants
+import Haskoin.Script
+import Haskoin.Transaction
+import Haskoin.Util
+import Haskoin.Util.Arbitrary
+import Test.HUnit
+import Test.Hspec
+import Test.Hspec.QuickCheck
+import Test.QuickCheck
+
+spec :: Spec
+spec = prepareContext $ \ctx -> do
+  describe "Signature property checks" $ do
+    prop "verifies signature" $
+      forAll (arbitrarySignature ctx) $ \(m, key', sig) ->
+        verifyHashSig ctx m sig (derivePubKey ctx key')
+    prop "s component less than half order" $
+      forAll (arbitrarySignature ctx) $
+        isCanonicalHalfOrder ctx . lst3
+    prop "encoded signature is canonical" $
+      forAll (arbitrarySignature ctx) $
+        testIsCanonical ctx . lst3
+    prop "decodeStrictSig . exportSig identity" $
+      forAll (arbitrarySignature ctx) $
+        (\s -> decodeStrictSig ctx (exportSig ctx s) == Just s) . lst3
+    prop "importSig . exportSig identity" $
+      forAll (arbitrarySignature ctx) $
+        (\s -> importSig ctx (exportSig ctx s) == Just s) . lst3
+    prop "signature JSON identity" $
+      forAll (arbitrarySignature ctx) $
+        (\s -> (unmarshalJSON ctx . marshalJSON ctx) s == Just s) . lst3
+    prop "getSig . putSig identity" $
+      forAll (arbitrarySignature ctx) $
+        (\s -> unmarshal ctx (marshal ctx s) == Right s) . lst3
+  describe "Signature vectors" $ do
+    it "passes RFC6979 test vectors" $
+      checkDistSig ctx $ \file1 file2 -> do
+        vectors <- readTestFile file1 :: IO [(Text, Text, Text)]
+        vectorsDER <- readTestFile file2 :: IO [(Text, Text, Text)]
+        mapM_ (testRFC6979Vector ctx . toVector) vectors
+        mapM_ (testRFC6979DERVector ctx . toVector) vectorsDER
+  describe "BIP143 signature vectors" $ do
+    it "agrees with BIP143 p2wpkh example" $
+      testBip143p2wpkh ctx
+    it "agrees with BIP143 p2sh-p2wpkh example" $
+      testBip143p2shp2wpkh ctx
+    it "builds a p2wsh multisig transaction" $
+      testP2WSHMulsig ctx
+    it "agrees with BIP143 p2sh-p2wsh multisig example" $
+      testBip143p2shp2wpkhMulsig ctx
+
+-- github.com/bitcoin/bitcoin/blob/master/src/script.cpp
+-- from function IsCanonicalSignature
+testIsCanonical :: Ctx -> Sig -> Bool
+testIsCanonical ctx sig =
+  not $
+    -- Non-canonical signature: too short
+    (len < 8)
+      ||
+      -- Non-canonical signature: too long
+      (len > 72)
+      ||
+      -- Non-canonical signature: wrong type
+      (BS.index s 0 /= 0x30)
+      ||
+      -- Non-canonical signature: wrong length marker
+      (BS.index s 1 /= len - 2)
+      ||
+      -- Non-canonical signature: S length misplaced
+      (5 + rlen >= len)
+      ||
+      -- Non-canonical signature: R+S length mismatch
+      (rlen + slen + 6 /= len)
+      ||
+      -- Non-canonical signature: R value type mismatch
+      (BS.index s 2 /= 0x02)
+      ||
+      -- Non-canonical signature: R length is zero
+      (rlen == 0)
+      ||
+      -- Non-canonical signature: R value negative
+      testBit (BS.index s 4) 7
+      ||
+      -- Non-canonical signature: R value excessively padded
+      ( rlen > 1
+          && BS.index s 4 == 0
+          && not (testBit (BS.index s 5) 7)
+      )
+      ||
+      -- Non-canonical signature: S value type mismatch
+      (BS.index s (fromIntegral rlen + 4) /= 0x02)
+      ||
+      -- Non-canonical signature: S length is zero
+      (slen == 0)
+      ||
+      -- Non-canonical signature: S value negative
+      testBit (BS.index s (fromIntegral rlen + 6)) 7
+      ||
+      -- Non-canonical signature: S value excessively padded
+      ( slen > 1
+          && BS.index s (fromIntegral rlen + 6) == 0
+          && not (testBit (BS.index s (fromIntegral rlen + 7)) 7)
+      )
+  where
+    s = exportSig ctx sig
+    len = fromIntegral $ BS.length s
+    rlen = BS.index s 3
+    slen = BS.index s (fromIntegral rlen + 5)
+
+-- RFC6979 note: Different libraries of libsecp256k1 use different constants
+-- to produce a nonce. Thus, their deterministric signatures will be different.
+-- We still want to test against fixed signatures so we need a way to switch
+-- between implementations. We check the output of signMsg 1 0
+
+data ValidImpl
+  = ImplCore
+  | ImplCash
+
+implSig :: Ctx -> Text
+implSig ctx =
+  encodeHex $
+    exportSig ctx $
+      signMsg
+        ctx
+        "0000000000000000000000000000000000000000000000000000000000000001"
+        "0000000000000000000000000000000000000000000000000000000000000000"
+
+-- We have test vectors for these cases
+validImplMap :: Map Text ValidImpl
+validImplMap =
+  Map.fromList
+    [ ( "3045022100a0b37f8fba683cc68f6574cd43b39f0343a50008bf6ccea9d13231\
+        \d9e7e2e1e4022011edc8d307254296264aebfc3dc76cd8b668373a072fd64665\
+        \b50000e9fcce52",
+        ImplCore
+      ),
+      ( "304402200581361d23e645be9e3efe63a9a2ac2e8dd0c70ba3ac8554c9befe06\
+        \0ad0b36202207d8172f1e259395834793d81b17e986f1e6131e4734969d2f4ae\
+        \3a9c8bc42965",
+        ImplCash
+      )
+    ]
+
+getImpl :: Ctx -> Maybe ValidImpl
+getImpl ctx = implSig ctx `Map.lookup` validImplMap
+
+rfc6979files :: ValidImpl -> (FilePath, FilePath)
+rfc6979files ImplCore = ("rfc6979core.json", "rfc6979DERcore.json")
+rfc6979files ImplCash = ("rfc6979cash.json", "rfc6979DERcash.json")
+
+checkDistSig :: Ctx -> (FilePath -> FilePath -> Assertion) -> Assertion
+checkDistSig ctx go =
+  case rfc6979files <$> getImpl ctx of
+    Just (file1, file2) -> go file1 file2
+    _ -> assertFailure "invalid RFC6979 signature"
+
+{- Trezor RFC 6979 Test Vectors -}
+-- github.com/trezor/python-ecdsa/blob/master/ecdsa/test_pyecdsa.py
+
+toVector :: (Text, Text, Text) -> (SecKey, ByteString, Text)
+toVector (prv, m, res) = (fromJust $ (secKey <=< decodeHex) prv, cs m, res)
+
+testRFC6979Vector :: Ctx -> (SecKey, ByteString, Text) -> Assertion
+testRFC6979Vector ctx (prv, m, res) = do
+  assertEqual "RFC 6979 Vector" res (encodeHex (exportCompactSig ctx s).get)
+  assertBool "Signature is valid" $ verifyHashSig ctx h s (derivePubKey ctx prv)
+  assertBool "Signature is canonical" $ testIsCanonical ctx s
+  assertBool "Signature is normalized" $ isCanonicalHalfOrder ctx s
+  where
+    h = sha256 m
+    s = signHash ctx prv h
+
+-- Test vectors from:
+-- https://crypto.stackexchange.com/questions/20838/request-for-data-to-test-deterministic-ecdsa-signature-algorithm-for-secp256k1
+
+testRFC6979DERVector :: Ctx -> (SecKey, ByteString, Text) -> Assertion
+testRFC6979DERVector ctx (prv, m, res) = do
+  assertEqual "RFC 6979 DER Vector" res (encodeHex $ exportSig ctx s)
+  assertBool "DER Signature is valid" $ verifyHashSig ctx h s (derivePubKey ctx prv)
+  assertBool "DER Signature is canonical" $ testIsCanonical ctx s
+  assertBool "DER Signature is normalized" $ isCanonicalHalfOrder ctx s
+  where
+    h = sha256 m
+    s = signHash ctx prv h
+
+-- Reproduce the P2WPKH example from BIP 143
+testBip143p2wpkh :: Ctx -> Assertion
+testBip143p2wpkh ctx =
+  case getImpl ctx of
+    Just ImplCore ->
+      assertEqual "BIP143 Core p2wpkh" (Right signedTxCore) generatedSignedTx
+    Just ImplCash ->
+      assertEqual "BIP143 ABC p2wpkh" (Right signedTxCash) generatedSignedTx
+    Nothing -> assertFailure "Invalid secp256k1 library"
+  where
+    signedTxCore =
+      "01000000000102fff7f7881a8099afa6940d42d1e7f6362bec38171ea3edf433\
+      \541db4e4ad969f00000000494830450221008b9d1dc26ba6a9cb62127b02742f\
+      \a9d754cd3bebf337f7a55d114c8e5cdd30be022040529b194ba3f9281a99f2b1\
+      \c0a19c0489bc22ede944ccf4ecbab4cc618ef3ed01eeffffffef51e1b804cc89\
+      \d182d279655c3aa89e815b1b309fe287d9b2b55d57b90ec68a0100000000ffff\
+      \ffff02202cb206000000001976a9148280b37df378db99f66f85c95a783a76ac\
+      \7a6d5988ac9093510d000000001976a9143bde42dbee7e4dbe6a21b2d50ce2f0\
+      \167faa815988ac000247304402203609e17b84f6a7d30c80bfa610b5b4542f32\
+      \a8a0d5447a12fb1366d7f01cc44a0220573a954c4518331561406f90300e8f33\
+      \58f51928d43c212a8caed02de67eebee0121025476c2e83188368da1ff3e292e\
+      \7acafcdb3566bb0ad253f62fc70f07aeee635711000000"
+    signedTxCash =
+      "01000000000102fff7f7881a8099afa6940d42d1e7f6362bec38171ea3edf433\
+      \541db4e4ad969f000000004847304402200fbc9dad97500334e47c2dca50096a\
+      \2117c01952c2870102e320823d21c36229022007cb36c2b141d11c08ef81d948\
+      \f148332fc09fe8f6d226aaaf8ba6ae0d8a66ba01eeffffffef51e1b804cc89d1\
+      \82d279655c3aa89e815b1b309fe287d9b2b55d57b90ec68a0100000000ffffff\
+      \ff02202cb206000000001976a9148280b37df378db99f66f85c95a783a76ac7a\
+      \6d5988ac9093510d000000001976a9143bde42dbee7e4dbe6a21b2d50ce2f016\
+      \7faa815988ac0002473044022011cb891cee521eb1fc7aef681655a881288553\
+      \fc024cff9cee5007bae5e6b8c602200b89d60ee2f98aa9a645dad59cd680b4b6\
+      \25f343efcd3e7fb70852100ef601890121025476c2e83188368da1ff3e292e7a\
+      \cafcdb3566bb0ad253f62fc70f07aeee635711000000"
+    unsignedTx =
+      "0100000002fff7f7881a8099afa6940d42d1e7f6362bec38171ea3edf433541d\
+      \b4e4ad969f0000000000eeffffffef51e1b804cc89d182d279655c3aa89e815b\
+      \1b309fe287d9b2b55d57b90ec68a0100000000ffffffff02202cb20600000000\
+      \1976a9148280b37df378db99f66f85c95a783a76ac7a6d5988ac9093510d0000\
+      \00001976a9143bde42dbee7e4dbe6a21b2d50ce2f0167faa815988ac11000000"
+    Just key0 =
+      secHexKey
+        "bbc27228ddcb9209d7fd6f36b02f7dfa6252af40bb2f1cbc7a557da8027ff866"
+    pubKey0 = toPubKey ctx key0
+    Just key1 =
+      secHexKey
+        "619c335025c7f4012e556c2a58b2506e30b8511b53ade95ea316fd8c3286feb9"
+    [op0, op1] = (.outpoint) <$> unsignedTx.inputs
+    sigIn0 = SigInput (PayPK pubKey0) 625000000 op0 sigHashAll Nothing
+    WitnessPubKeyAddress h = pubKeyWitnessAddr ctx $ toPubKey ctx key1
+    sigIn1 = SigInput (PayWitnessPKHash h) 600000000 op1 sigHashAll Nothing
+    generatedSignedTx = signTx btc ctx unsignedTx [sigIn0, sigIn1] [key0, key1]
+
+-- Reproduce the P2SH-P2WPKH example from BIP 143
+testBip143p2shp2wpkh :: Ctx -> Assertion
+testBip143p2shp2wpkh ctx =
+  case getImpl ctx of
+    Just ImplCore ->
+      assertEqual "BIP143 Core p2sh-p2wpkh" (Right signedTxCore) generatedSignedTx
+    Just ImplCash ->
+      assertEqual "BIP143 Cash p2sh-p2wpkh" (Right signedTxCash) generatedSignedTx
+    Nothing -> assertFailure "Invalid secp256k1 library"
+  where
+    signedTxCore =
+      "01000000000101db6b1b20aa0fd7b23880be2ecbd4a98130974cf4748fb66092\
+      \ac4d3ceb1a5477010000001716001479091972186c449eb1ded22b78e40d009b\
+      \df0089feffffff02b8b4eb0b000000001976a914a457b684d7f0d539a46a45bb\
+      \c043f35b59d0d96388ac0008af2f000000001976a914fd270b1ee6abcaea97fe\
+      \a7ad0402e8bd8ad6d77c88ac02473044022047ac8e878352d3ebbde1c94ce3a1\
+      \0d057c24175747116f8288e5d794d12d482f0220217f36a485cae903c713331d\
+      \877c1f64677e3622ad4010726870540656fe9dcb012103ad1d8e89212f0b92c7\
+      \4d23bb710c00662ad1470198ac48c43f7d6f93a2a2687392040000"
+    signedTxCash =
+      "01000000000101db6b1b20aa0fd7b23880be2ecbd4a98130974cf4748fb66092\
+      \ac4d3ceb1a5477010000001716001479091972186c449eb1ded22b78e40d009b\
+      \df0089feffffff02b8b4eb0b000000001976a914a457b684d7f0d539a46a45bb\
+      \c043f35b59d0d96388ac0008af2f000000001976a914fd270b1ee6abcaea97fe\
+      \a7ad0402e8bd8ad6d77c88ac024730440220091c78fd1e21535f6ddc45515e4c\
+      \afca15cdf344765d72c1529fb82d3ada2d1802204a980d5e37d0b04f5e1185a0\
+      \f97295c383764e9a4b08d8bd1161b33c6719139a012103ad1d8e89212f0b92c7\
+      \4d23bb710c00662ad1470198ac48c43f7d6f93a2a2687392040000"
+    unsignedTx =
+      "0100000001db6b1b20aa0fd7b23880be2ecbd4a98130974cf4748fb66092ac4d\
+      \3ceb1a54770100000000feffffff02b8b4eb0b000000001976a914a457b684d7\
+      \f0d539a46a45bbc043f35b59d0d96388ac0008af2f000000001976a914fd270b\
+      \1ee6abcaea97fea7ad0402e8bd8ad6d77c88ac92040000"
+    Just key0 =
+      secHexKey
+        "eb696a065ef48a2192da5b28b694f87544b30fae8327c4510137a922f32c6dcf"
+    op0 = (head unsignedTx.inputs).outpoint
+    WitnessPubKeyAddress h = pubKeyWitnessAddr ctx $ toPubKey ctx key0
+    sigIn0 = SigInput (PayWitnessPKHash h) 1000000000 op0 sigHashAll Nothing
+    generatedSignedTx = signNestedWitnessTx btc ctx unsignedTx [sigIn0] [key0]
+
+-- P2WSH multisig example (tested against bitcoin-core 0.19.0.1)
+testP2WSHMulsig :: Ctx -> Assertion
+testP2WSHMulsig ctx =
+  case getImpl ctx of
+    Just ImplCore ->
+      assertEqual "Core p2wsh multisig" (Right signedTxCore) generatedSignedTx
+    Just ImplCash ->
+      assertEqual "Cash p2wsh multisig" (Right signedTxCash) generatedSignedTx
+    Nothing -> assertFailure "Invalid secp256k1 library"
+  where
+    signedTxCore =
+      "01000000000101d2e34df5d7ee565208eddd231548916b9b0e99f4f5071f8961\
+      \34a448c5fb07bf0100000000ffffffff01f0b9f505000000001976a9143d5a35\
+      \2cab583b12fbcb26d1269b4a2c951a33ad88ac0400483045022100fad4fedd2b\
+      \b4c439c64637eb8e9150d9020a7212808b8dc0578d5ff5b4ad65fe0220714640\
+      \f261b37eb3106310bf853f4b706e51436fb6b64c2ab00768814eb55b98014730\
+      \44022100baff4e4ceea4022b9725a2e6f6d77997a554f858165b91ac8c16c983\
+      \3008bee9021f5f70ebc3f8580dc0a5e96451e3697bdf1f1f5883944f0f33ab0c\
+      \fb272354040169522102ba46d3bb8db74c77c6cf082db57fc0548058fcdea811\
+      \549e186526e3d10caf6721038ac8aef2dd9cea5e7d66e2f6e23f177a6c21f69e\
+      \a311fa0c85d81badb6b37ceb2103d96d2bfbbc040faaf93491d69e2bfe9695e2\
+      \d8e007a7f26db96c2ee42db15dc953ae00000000"
+    signedTxCash =
+      "01000000000101d2e34df5d7ee565208eddd231548916b9b0e99f4f5071f8961\
+      \34a448c5fb07bf0100000000ffffffff01f0b9f505000000001976a9143d5a35\
+      \2cab583b12fbcb26d1269b4a2c951a33ad88ac0400483045022100b79bf3714a\
+      \50f8f0e2f946034361ba4f6567b796d55910d89e98720d2e99f98c0220134879\
+      \518002df23e80a058475fa8b10bc4182bedfecd5f85e446a00f211ea53014830\
+      \45022100ce3c77480d664430a7544c1a962d1ae31151109a528a37e5bccc92ba\
+      \2e460ad10220317bc9a71d0c3471058d16d4c3b1ea99616208db6b9b9040fb81\
+      \0a7fa27f72b40169522102ba46d3bb8db74c77c6cf082db57fc0548058fcdea8\
+      \11549e186526e3d10caf6721038ac8aef2dd9cea5e7d66e2f6e23f177a6c21f6\
+      \9ea311fa0c85d81badb6b37ceb2103d96d2bfbbc040faaf93491d69e2bfe9695\
+      \e2d8e007a7f26db96c2ee42db15dc953ae00000000"
+    unsignedTx =
+      "0100000001d2e34df5d7ee565208eddd231548916b9b0e99f4f5071f896134a4\
+      \48c5fb07bf0100000000ffffffff01f0b9f505000000001976a9143d5a352cab\
+      \583b12fbcb26d1269b4a2c951a33ad88ac00000000"
+    op0 = (head unsignedTx.inputs).outpoint
+    Just keys =
+      traverse
+        secHexKey
+        [ "3030303030303030303030303030303030303030303030303030303030303031",
+          "3030303030303030303030303030303030303030303030303030303030303032",
+          "3030303030303030303030303030303030303030303030303030303030303033"
+        ]
+    rdm = PayMulSig (toPubKey ctx <$> keys) 2
+    sigIn =
+      SigInput
+        (toP2WSH $ encodeOutput ctx rdm)
+        100000000
+        op0
+        sigHashAll
+        (Just rdm)
+    generatedSignedTx = signTx btc ctx unsignedTx [sigIn] (take 2 keys)
+
+-- Reproduce the P2SH-P2WSH multisig example from BIP 143
+testBip143p2shp2wpkhMulsig :: Ctx -> Assertion
+testBip143p2shp2wpkhMulsig ctx =
+  case getImpl ctx of
+    Just ImplCore ->
+      assertEqual
+        "BIP143 Core p2sh-p2wsh multisig"
+        (Right signedTxCore)
+        generatedSignedTx
+    Just ImplCash ->
+      assertEqual
+        "BIP143 Core p2sh-p2wsh multisig"
+        (Right signedTxCash)
+        generatedSignedTx
+    Nothing -> assertFailure "Invalid secp256k1 library"
+  where
+    signedTxCore =
+      "0100000000010136641869ca081e70f394c6948e8af409e18b619df2ed74aa10\
+      \6c1ca29787b96e0100000023220020a16b5755f7f6f96dbd65f5f0d6ab9418b8\
+      \9af4b1f14a1bb8a09062c35f0dcb54ffffffff0200e9a435000000001976a914\
+      \389ffce9cd9ae88dcc0631e88a821ffdbe9bfe2688acc0832f05000000001976\
+      \a9147480a33f950689af511e6e84c138dbbd3c3ee41588ac080047304402206a\
+      \c44d672dac41f9b00e28f4df20c52eeb087207e8d758d76d92c6fab3b73e2b02\
+      \20367750dbbe19290069cba53d096f44530e4f98acaa594810388cf7409a1870\
+      \ce01473044022068c7946a43232757cbdf9176f009a928e1cd9a1a8c212f15c1\
+      \e11ac9f2925d9002205b75f937ff2f9f3c1246e547e54f62e027f64eefa26955\
+      \78cc6432cdabce271502473044022059ebf56d98010a932cf8ecfec54c48e613\
+      \9ed6adb0728c09cbe1e4fa0915302e022007cd986c8fa870ff5d2b3a89139c9f\
+      \e7e499259875357e20fcbb15571c76795403483045022100fbefd94bd0a488d5\
+      \0b79102b5dad4ab6ced30c4069f1eaa69a4b5a763414067e02203156c6a5c9cf\
+      \88f91265f5a942e96213afae16d83321c8b31bb342142a14d163814830450221\
+      \00a5263ea0553ba89221984bd7f0b13613db16e7a70c549a86de0cc0444141a4\
+      \07022005c360ef0ae5a5d4f9f2f87a56c1546cc8268cab08c73501d6b3be2e1e\
+      \1a8a08824730440220525406a1482936d5a21888260dc165497a90a15669636d\
+      \8edca6b9fe490d309c022032af0c646a34a44d1f4576bf6a4a74b67940f8faa8\
+      \4c7df9abe12a01a11e2b4783cf56210307b8ae49ac90a048e9b53357a2354b33\
+      \34e9c8bee813ecb98e99a7e07e8c3ba32103b28f0c28bfab54554ae8c658ac5c\
+      \3e0ce6e79ad336331f78c428dd43eea8449b21034b8113d703413d57761b8b97\
+      \81957b8c0ac1dfe69f492580ca4195f50376ba4a21033400f6afecb833092a9a\
+      \21cfdf1ed1376e58c5d1f47de74683123987e967a8f42103a6d48b1131e94ba0\
+      \4d9737d61acdaa1322008af9602b3b14862c07a1789aac162102d8b661b0b330\
+      \2ee2f162b09e07a55ad5dfbe673a9f01d9f0c19617681024306b56ae00000000"
+    signedTxCash =
+      "0100000000010136641869ca081e70f394c6948e8af409e18b619df2ed74aa10\
+      \6c1ca29787b96e0100000023220020a16b5755f7f6f96dbd65f5f0d6ab9418b8\
+      \9af4b1f14a1bb8a09062c35f0dcb54ffffffff0200e9a435000000001976a914\
+      \389ffce9cd9ae88dcc0631e88a821ffdbe9bfe2688acc0832f05000000001976\
+      \a9147480a33f950689af511e6e84c138dbbd3c3ee41588ac0800483045022100\
+      \b70b684ef0d17b51adf71c0dae932beca5d447dd5eec03394328436bdba836e7\
+      \0220208ebfd7408d21e41da11d8287655528385429d3fe300bee241f10944339\
+      \5b580147304402204b5f9bc06c8f0a252b9842ea44785853beb1638002cec5f2\
+      \489d73e5f6f5109302204f3b132b32638835d4b1a651e7d18dc93c10192db553\
+      \999932af6a8e3d8a153202483045022100e0ed8d3a245a138c751d74e1359aee\
+      \6a52476ddf33a3a9a5f0c2ad30147319650220581318187061ad0f48fc4f5c85\
+      \1822e554d59977005b8de4b78bf2ce2fe8399703483045022100a0a40abc581e\
+      \4b725775a3aa93bf0f0fd9a02ad3aa0f93483214784a47ba5387022069151c30\
+      \f85a7e20c8671107c5af884ee4c5a82bd06398327fa68a993f7cc64b81473044\
+      \022016d828460f6fab3cf89ae4b87c8f02c11c798cf739967f3b7406e7367c29\
+      \ae8b022079e82b822eb6c37a66efabc3f0b40a2b98c52f848d36463f6623cbdc\
+      \fe675812824730440220225a14ba7434858dbb5e6e0a0969ddf3b5455edaabf9\
+      \9f5773d1f59e7816b918022047ed1ab87840a74f7e9489f3af051e5fd26b790f\
+      \b308c79f4b0ed73c0422795d83cf56210307b8ae49ac90a048e9b53357a2354b\
+      \3334e9c8bee813ecb98e99a7e07e8c3ba32103b28f0c28bfab54554ae8c658ac\
+      \5c3e0ce6e79ad336331f78c428dd43eea8449b21034b8113d703413d57761b8b\
+      \9781957b8c0ac1dfe69f492580ca4195f50376ba4a21033400f6afecb833092a\
+      \9a21cfdf1ed1376e58c5d1f47de74683123987e967a8f42103a6d48b1131e94b\
+      \a04d9737d61acdaa1322008af9602b3b14862c07a1789aac162102d8b661b0b3\
+      \302ee2f162b09e07a55ad5dfbe673a9f01d9f0c19617681024306b56ae00000000"
+    unsignedTx =
+      "010000000136641869ca081e70f394c6948e8af409e18b619df2ed74aa106c1c\
+      \a29787b96e0100000000ffffffff0200e9a435000000001976a914389ffce9cd\
+      \9ae88dcc0631e88a821ffdbe9bfe2688acc0832f05000000001976a9147480a3\
+      \3f950689af511e6e84c138dbbd3c3ee41588ac00000000"
+    op0 = (head unsignedTx.inputs).outpoint
+    rawKeys =
+      [ "730fff80e1413068a05b57d6a58261f07551163369787f349438ea38ca80fac6",
+        "11fa3d25a17cbc22b29c44a484ba552b5a53149d106d3d853e22fdd05a2d8bb3",
+        "77bf4141a87d55bdd7f3cd0bdccf6e9e642935fec45f2f30047be7b799120661",
+        "14af36970f5025ea3e8b5542c0f8ebe7763e674838d08808896b63c3351ffe49",
+        "fe9a95c19eef81dde2b95c1284ef39be497d128e2aa46916fb02d552485e0323",
+        "428a7aee9f0c2af0cd19af3cf1c78149951ea528726989b2e83e4778d2c3f890"
+      ]
+    Just keys = traverse secHexKey rawKeys
+    rdm = PayMulSig (toPubKey ctx <$> keys) 6
+    sigIn sh = SigInput (toP2WSH $ encodeOutput ctx rdm) 987654321 op0 sh (Just rdm)
+    sigHashesA = [sigHashAll, sigHashNone, sigHashSingle]
+    sigHashesB = setAnyoneCanPay <$> sigHashesA
+    sigIns = sigIn <$> (sigHashesA <> sigHashesB)
+    generatedSignedTx = foldM addSig unsignedTx $ zip sigIns keys
+    addSig tx (sigIn', key') = signNestedWitnessTx btc ctx tx [sigIn'] [key']
+
+secHexKey :: Text -> Maybe SecKey
+secHexKey = decodeHex >=> secKey
+
+toPubKey :: Ctx -> SecKey -> PublicKey
+toPubKey ctx = derivePublicKey ctx . wrapSecKey True
diff --git a/test/Haskoin/NetworkSpec.hs b/test/Haskoin/NetworkSpec.hs
new file mode 100644
--- /dev/null
+++ b/test/Haskoin/NetworkSpec.hs
@@ -0,0 +1,190 @@
+{-# LANGUAGE DuplicateRecordFields #-}
+{-# LANGUAGE OverloadedRecordDot #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Haskoin.NetworkSpec (spec) where
+
+import Data.Bytes.Get
+import Data.Bytes.Put
+import Data.Bytes.Serial
+import Data.Default (def)
+import Data.Maybe (fromJust)
+import Data.Text (Text)
+import Data.Word (Word32)
+import Haskoin.Address
+import Haskoin.Crypto
+import Haskoin.Network
+import Haskoin.Network.Constants
+import Haskoin.Transaction
+import Haskoin.Util
+import Haskoin.Util.Arbitrary
+import Test.HUnit (Assertion, assertBool, assertEqual)
+import Test.Hspec
+import Test.Hspec.QuickCheck
+import Test.QuickCheck
+
+identityTests :: IdentityTests
+identityTests =
+  def
+    { serialTests =
+        [ SerialBox arbitraryVarInt,
+          SerialBox arbitraryVarString,
+          SerialBox arbitraryNetworkAddress,
+          SerialBox arbitraryInvType,
+          SerialBox arbitraryInvVector,
+          SerialBox arbitraryInv1,
+          SerialBox arbitraryVersion,
+          SerialBox arbitraryAddr1,
+          SerialBox arbitraryAlert,
+          SerialBox arbitraryReject,
+          SerialBox arbitraryRejectCode,
+          SerialBox arbitraryGetData,
+          SerialBox arbitraryNotFound,
+          SerialBox arbitraryPing,
+          SerialBox arbitraryPong,
+          SerialBox arbitraryMessageCommand,
+          SerialBox arbitraryMessageHeader,
+          SerialBox arbitraryBloomFlags,
+          SerialBox arbitraryBloomFilter,
+          SerialBox arbitraryFilterLoad,
+          SerialBox arbitraryFilterAdd
+        ]
+    }
+
+spec :: Spec
+spec = prepareContext $ \ctx -> do
+  testIdentity identityTests
+  describe "Custom identity tests" $ do
+    prop "Data.Serialize Encoding for type Message" $
+      forAll arbitraryNetwork $ \net ->
+        forAll (arbitraryMessage net ctx) $
+          customCerealID (getMessage net) (putMessage net)
+  describe "bloom filters" $ do
+    it "bloom filter vector 1" bloomFilter1
+    it "bloom filter vector 2" bloomFilter2
+    it "bloom filter vector 3" $ bloomFilter3 ctx
+  describe "relevant bloom filter update" $ do
+    it "Relevant Update" $ relevantOutputUpdated ctx
+    it "Irrelevant Update" $ irrelevantOutputNotUpdated ctx
+
+bloomFilter :: Word32 -> Text -> Assertion
+bloomFilter n x = do
+  assertBool "Bloom filter doesn't contain vector 1" $ bloomContains f1 v1
+  assertBool "Bloom filter contains something it should not" $
+    not $
+      bloomContains f1 v2
+  assertBool "Bloom filter doesn't contain vector 3" $ bloomContains f3 v3
+  assertBool "Bloom filter doesn't contain vector 4" $ bloomContains f4 v4
+  assertBool "Bloom filter serialization is incorrect" $
+    runPutS (serialize f4) == bs
+  where
+    f0 = bloomCreate 3 0.01 n BloomUpdateAll
+    f1 = bloomInsert f0 v1
+    f3 = bloomInsert f1 v3
+    f4 = bloomInsert f3 v4
+    v1 = fromJust $ decodeHex "99108ad8ed9bb6274d3980bab5a85c048f0950c8"
+    v2 = fromJust $ decodeHex "19108ad8ed9bb6274d3980bab5a85c048f0950c8"
+    v3 = fromJust $ decodeHex "b5a2c786d9ef4658287ced5914b37a1b4aa32eee"
+    v4 = fromJust $ decodeHex "b9300670b4c5366e95b2699e8b18bc75e5f729c5"
+    bs = fromJust $ decodeHex x
+
+bloomFilter1 :: Assertion
+bloomFilter1 = bloomFilter 0 "03614e9b050000000000000001"
+
+bloomFilter2 :: Assertion
+bloomFilter2 = bloomFilter 2147483649 "03ce4299050000000100008001"
+
+bloomFilter3 :: Ctx -> Assertion
+bloomFilter3 ctx =
+  assertBool "Bloom filter serialization is incorrect" $
+    runPutS (serialize f2) == bs
+  where
+    f0 = bloomCreate 2 0.001 0 BloomUpdateAll
+    f1 = bloomInsert f0 $ marshal ctx p
+    f2 = bloomInsert f1 $ runPutS $ serialize (pubKeyAddr ctx p).hash160
+    k = fromJust $ fromWif btc "5Kg1gnAjaLfKiwhhPpGS3QfRg2m6awQvaj98JCZBZQ5SuS2F15C"
+    p = derivePublicKey ctx k
+    bs = fromJust $ decodeHex "038fc16b080000000000000001"
+
+relevantOutputUpdated :: Ctx -> Assertion
+relevantOutputUpdated ctx =
+  assertBool "Bloom filter output updated" $
+    any (bloomContains bf2) spendTxInput
+  where
+    bf0 = bloomCreate 10 0.000001 0 BloomUpdateAll
+    relevantOutputHash = fromJust $ decodeHex "03f47604ea2736334151081e13265b4fe38e6fa8"
+    bf1 = bloomInsert bf0 relevantOutputHash
+    bf2 = fromJust $ bloomRelevantUpdate ctx bf1 relevantTx
+    spendTxInput = runPutS . serialize . (.outpoint) <$> spendRelevantTx.inputs
+
+irrelevantOutputNotUpdated :: Ctx -> Assertion
+irrelevantOutputNotUpdated ctx = assertEqual "Bloom filter not updated" Nothing bf2
+  where
+    bf0 = bloomCreate 10 0.000001 0 BloomUpdateAll
+    relevantOutputHash = fromJust $ decodeHex "03f47604ea2736334151081e13265b4fe38e6fa8"
+    bf1 = bloomInsert bf0 relevantOutputHash
+    bf2 = bloomRelevantUpdate ctx bf1 unrelatedTx
+
+-- Random transaction (57dc904f32ad4daab7b321dd469e8791ad09df784cdd273a73985150a4f225e9)
+relevantTx :: Tx
+relevantTx =
+  Tx
+    { version = 1,
+      inputs =
+        [ TxIn
+            { outpoint = OutPoint "35fe9017b7e3af592920b56fa06ac02faf0c52cdb19dcb416129ac71c95d060e" 1,
+              script = fromJust $ decodeHex "473044022032fc8eef299b7e94b9a986a6aa2dcb9733ab804bef80df995e443b9c1f8c604202203335df7a2e2b4789451cdb4b2b05a786a81c51519eb6a567fd6fe8cd7b2d33fe014104272502dc63a512dad1473cb82a71be9baf4f4303abd1ff6028fc8a78e1f3aec1218907119dec14f07354850758ff0948e88a904fa411c4df7d5444414ec64ad6",
+              sequence = 4294967295
+            }
+        ],
+      outputs =
+        [ TxOut {value = 100000000, script = fromJust $ decodeHex "76a91403f47604ea2736334151081e13265b4fe38e6fa888ac"},
+          TxOut {value = 107980000, script = fromJust $ decodeHex "76a91481cc186a2f4a69f633ed4bf10ef4a78be13effdd88ac"}
+        ],
+      witness = [],
+      locktime = 0
+    }
+
+-- Transaction that spends above (fd6e3b693b844aa431fad46765c1aa019a6b13aebfa9dae916b3ffa43283a300)
+spendRelevantTx :: Tx
+spendRelevantTx =
+  Tx
+    { version = 1,
+      inputs =
+        [ TxIn
+            { outpoint = OutPoint "57dc904f32ad4daab7b321dd469e8791ad09df784cdd273a73985150a4f225e9" 0,
+              script = fromJust $ decodeHex "483045022100ecc334821e4e94cc2fdc841d5ad147d5bb942b993ba81460cc446e0410afa811022015fcbc542b734dbb61a05ec06012095096de5839c50808fe56f2b315e877c20d012103fb64e5792fa586172339b776b7017d3d529358cb73be6406a1fc994228d14f88",
+              sequence = 4294967295
+            },
+          TxIn
+            { outpoint = OutPoint "cfee6a8d6e68e8fd16df6fff010afffcd19d7e075aa7b707dd1bae6adc420042" 0,
+              script = fromJust $ decodeHex "47304402200e6bb95fa606f254d17089d83c4ceeb19c5d1699b4faddcd4f1f1568286e6b650220087fb8439f31e1b30e47710d095422405f601d6151f2f93e125e1a08a6e29ad4012103b49252e8fc6d5b49c8d14ee71fab45591df4a126a6c453c724f3d356e38f0cee",
+              sequence = 4294967295
+            }
+        ],
+      outputs =
+        [ TxOut {value = 3851100, script = fromJust $ decodeHex "76a914a297cae82a9a3b932bf023ae274fe2585295c9ca88ac"},
+          TxOut {value = 111000000, script = fromJust $ decodeHex "76a9148f952c38600a61385974acc30a64f74407f9801488ac"}
+        ],
+      witness = [],
+      locktime = 0
+    }
+
+-- This random transaction is unrelated to the others
+unrelatedTx :: Tx
+unrelatedTx =
+  Tx
+    { version = 1,
+      inputs =
+        [ TxIn
+            { outpoint = OutPoint "3ec3a71431c68e5d978a5fb4a0a1081d8bee8384d8aa4c06b1fbaf9413e2214f" 20,
+              script = fromJust $ decodeHex "483045022100ec9c202c9d3140b973aca9d7f21a82138aa4cfa43fddc5419098ac5e26a6f152022010848fd688f290ae010fb5cb493410caa03145fc12445900ec1ad2bde33aecd9012102c7445e72d723f99a0064526c28269d07f47c8fd81531a94a8d3bf5ebd5e23ef1",
+              sequence = 4294967295
+            }
+        ],
+      outputs =
+        [ TxOut {value = 12600000, script = fromJust $ decodeHex "76a9148fef3b7051de8cc44e966159e7ea37f4520187e888ac"}
+        ],
+      witness = [],
+      locktime = 0
+    }
diff --git a/test/Haskoin/ScriptSpec.hs b/test/Haskoin/ScriptSpec.hs
new file mode 100644
--- /dev/null
+++ b/test/Haskoin/ScriptSpec.hs
@@ -0,0 +1,472 @@
+{-# LANGUAGE DuplicateRecordFields #-}
+{-# LANGUAGE ImportQualifiedPost #-}
+{-# LANGUAGE OverloadedRecordDot #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Haskoin.ScriptSpec (spec) where
+
+import Control.Monad
+import Data.Aeson as A
+import Data.ByteString (ByteString)
+import Data.ByteString qualified as B
+import Data.Bytes.Get
+import Data.Bytes.Put
+import Data.Bytes.Serial
+import Data.Default (def)
+import Data.Either
+import Data.List
+import Data.Maybe
+import Data.String
+import Data.String.Conversions (cs)
+import Data.Text (Text)
+import Data.Word
+import Haskoin.Address
+import Haskoin.Crypto
+import Haskoin.Network.Constants
+import Haskoin.Network.Data
+import Haskoin.Script
+import Haskoin.Transaction
+import Haskoin.Util
+import Haskoin.Util.Arbitrary
+import Test.HUnit as HUnit
+import Test.Hspec
+import Test.Hspec.QuickCheck
+import Test.QuickCheck
+import Text.Read
+
+identityTests :: Ctx -> IdentityTests
+identityTests ctx =
+  def
+    { readTests =
+        [ ReadBox arbitrarySigHash,
+          ReadBox arbitrarySigHashFlag,
+          ReadBox arbitraryScript,
+          ReadBox arbitraryPushDataType,
+          ReadBox arbitraryScriptOp,
+          ReadBox ((`arbitraryScriptOutput` ctx) =<< arbitraryNetwork),
+          ReadBox ((`arbitraryScriptInput` ctx) =<< arbitraryNetwork)
+        ],
+      jsonTests =
+        [ JsonBox arbitraryScript,
+          JsonBox arbitraryOutPoint,
+          JsonBox arbitrarySigHash
+        ],
+      marshalJsonTests =
+        [ MarshalJsonBox $ do
+            n <- arbitraryNetwork
+            (_, _, ts) <- arbitraryTxSignature n ctx
+            return ((n, ctx), ts),
+          MarshalJsonBox $ do
+            n <- arbitraryNetwork
+            o <- arbitraryScriptOutput n ctx
+            return (ctx, o),
+          MarshalJsonBox $ do
+            n <- arbitraryNetwork
+            (i, _) <- arbitrarySigInput n ctx
+            return (ctx, i)
+        ],
+      serialTests =
+        [ SerialBox arbitraryScriptOp,
+          SerialBox arbitraryScript
+        ],
+      marshalTests =
+        [ MarshalBox $ do
+            n <- arbitraryNetwork
+            (_, _, ts) <- arbitraryTxSignature n ctx
+            return ((n, ctx), ts),
+          MarshalBox $ do
+            n <- arbitraryNetwork
+            o <- arbitraryScriptOutput n ctx
+            return (ctx, o),
+          MarshalBox $ do
+            n <- arbitraryNetwork
+            i <- arbitraryScriptInput n ctx
+            return ((n, ctx), i)
+        ]
+    }
+
+spec :: Spec
+spec = prepareContext $ \ctx -> do
+  testIdentity $ identityTests ctx
+  describe "btc scripts" $ props btc ctx
+  describe "bch scripts" $ props bch ctx
+  describe "multi signatures" $
+    zipWithM_ (curry (mapMulSigVector ctx)) mulSigVectors [0 ..]
+  describe "signature decoding" $
+    zipWithM_ (curry (sigDecodeMap btc ctx)) scriptSigSignatures [0 ..]
+  describe "SigHashFlag fromEnum/toEnum" $
+    prop "fromEnum/toEnum" $
+      forAll arbitrarySigHashFlag $
+        \f -> toEnum (fromEnum f) `shouldBe` f
+  describe "Script vectors" $
+    it "Can encode script vectors" encodeScriptVector
+
+props :: Network -> Ctx -> Spec
+props net ctx = do
+  standardSpec net ctx
+  strictSigSpec net ctx
+  scriptSpec net ctx
+  txSigHashForkIdSpec net
+  forkIdScriptSpec net ctx
+  sigHashSpec net ctx
+  txSigHashSpec net
+
+standardSpec :: Network -> Ctx -> Spec
+standardSpec net ctx = do
+  prop "has intToScriptOp . scriptOpToInt identity" $
+    forAll arbitraryIntScriptOp $ \i ->
+      intToScriptOp <$> scriptOpToInt i `shouldBe` Right i
+  prop "has decodeOutput . encodeOutput identity" $
+    forAll (arbitraryScriptOutput net ctx) $ \so ->
+      decodeOutput ctx (encodeOutput ctx so) `shouldBe` Right so
+  prop "has decodeInput . encodeOutput identity" $
+    forAll (arbitraryScriptInput net ctx) $ \si ->
+      (decodeInput net ctx . encodeInput net ctx) si `shouldBe` Right si
+  prop "can sort multisig scripts" $
+    forAll (arbitraryMSOutput ctx) $ \out ->
+      let keyList = map (marshal ctx) (sortMulSig ctx out).keys
+          isSorted xs = xs == sort xs
+       in keyList `shouldSatisfy` isSorted
+  it "can decode inputs with empty signatures" $ do
+    decodeInput net ctx (Script [OP_0])
+      `shouldBe` Right (RegularInput (SpendPK TxSignatureEmpty))
+    decodeInput net ctx (Script [opPushData ""])
+      `shouldBe` Right (RegularInput (SpendPK TxSignatureEmpty))
+    let Just sk = secKey (B.replicate 32 1)
+        pk = derivePublicKey ctx (wrapSecKey True sk)
+    decodeInput net ctx (Script [OP_0, opPushData $ marshal ctx pk])
+      `shouldBe` Right (RegularInput (SpendPKHash TxSignatureEmpty pk))
+    decodeInput net ctx (Script [OP_0, OP_0])
+      `shouldBe` Right (RegularInput (SpendMulSig [TxSignatureEmpty]))
+    decodeInput net ctx (Script [OP_0, OP_0, OP_0, OP_0])
+      `shouldBe` Right (RegularInput (SpendMulSig $ replicate 3 TxSignatureEmpty))
+
+scriptSpec :: Network -> Ctx -> Spec
+scriptSpec net ctx =
+  when (net.name == "btc") $
+    it "can verify standard scripts from script_tests.json file" $ do
+      xs <- readTestFile "script_tests.json" :: IO [A.Value]
+      let vectorsA =
+            mapMaybe (A.decode . A.encode) xs ::
+              [(String, String, String, String, String)]
+          vectorsB =
+            mapMaybe (A.decode . A.encode) xs ::
+              [([Word64], String, String, String, String, String)]
+          vectors =
+            map (\(a, b, c, d, e) -> ([0], a, b, c, d, e)) vectorsA
+              <> vectorsB
+      length vectors `shouldBe` 86
+      forM_ vectors $ \([val], siStr, soStr, flags, res, desc) ->
+        -- We can disable specific tests by adding a DISABLED flag in the data
+        unless ("DISABLED" `isInfixOf` flags) $ do
+          let _strict =
+                any
+                  (`isInfixOf` flags)
+                  ["DERSIG", "STRICTENC", "NULLDUMMY"]
+              scriptSig = parseScript siStr
+              scriptPubKey = parseScript soStr
+              out = unmarshal ctx scriptPubKey
+              tx = spendTx scriptPubKey 0 scriptSig
+              sat = val * 100000000
+              ver o = verifyStdInput net ctx tx 0 o sat
+              valid = either (const False) ver out
+          assertBool desc $ if res == "OK" then valid else not valid
+
+forkIdScriptSpec :: Network -> Ctx -> Spec
+forkIdScriptSpec net ctx =
+  when (isJust net.sigHashForkId) $
+    it "can verify scripts from forkid_script_tests.json file" $ do
+      xs <- readTestFile "forkid_script_tests.json" :: IO [A.Value]
+      let vectors =
+            mapMaybe (A.decode . A.encode) xs ::
+              [ ( [Word64],
+                  String,
+                  String,
+                  String,
+                  String,
+                  String
+                )
+              ]
+      length vectors `shouldBe` 3
+      forM_ vectors $ \([valBTC], siStr, soStr, _, res, _) -> do
+        let val = valBTC * 100000000
+            scriptSig = parseScript siStr
+            scriptPubKey = parseScript soStr
+            out = unmarshal ctx scriptPubKey
+            tx = spendTx scriptPubKey val scriptSig
+            ver o = verifyStdInput net ctx tx 0 o val
+            valid = either (const False) ver out
+        case res of
+          "OK" -> valid `shouldBe` True
+          _ -> valid `shouldBe` False
+
+creditTx :: ByteString -> Word64 -> Tx
+creditTx scriptPubKey val =
+  Tx 1 [txI] [txO] [] 0
+  where
+    txO = TxOut {value = val, script = scriptPubKey}
+    txI =
+      TxIn
+        { outpoint = nullOutPoint,
+          script = runPutS $ serialize $ Script [OP_0, OP_0],
+          sequence = maxBound
+        }
+
+spendTx :: ByteString -> Word64 -> ByteString -> Tx
+spendTx scriptPubKey val scriptSig =
+  Tx 1 [txI] [txO] [] 0
+  where
+    txO = TxOut {value = val, script = B.empty}
+    txI =
+      TxIn
+        { outpoint = OutPoint (txHash $ creditTx scriptPubKey val) 0,
+          script = scriptSig,
+          sequence = maxBound
+        }
+
+parseScript :: String -> ByteString
+parseScript str =
+  B.concat $ fromMaybe err $ mapM f $ words str
+  where
+    f = decodeHex . cs . dropHex . replaceToken
+    dropHex ('0' : 'x' : xs) = xs
+    dropHex xs = xs
+    err = error $ "Could not decode script: " <> str
+
+replaceToken :: String -> String
+replaceToken str = case readMaybe $ "OP_" <> str of
+  Just opcode -> "0x" <> cs (encodeHex $ runPutS $ serialize (opcode :: ScriptOp))
+  _ -> str
+
+strictSigSpec :: Network -> Ctx -> Spec
+strictSigSpec net ctx =
+  when (net.name == "btc") $ do
+    it "can decode strict signatures" $ do
+      xs <- readTestFile "sig_strict.json"
+      let vectors = mapMaybe decodeHex xs
+      length vectors `shouldBe` 3
+      forM_ vectors $ \sig ->
+        let eitherSig :: Either String TxSignature
+            eitherSig = decodeTxSig net ctx sig
+         in eitherSig `shouldSatisfy` isRight
+    it "can detect non-strict signatures" $ do
+      xs <- readTestFile "sig_nonstrict.json"
+      let vectors = mapMaybe decodeHex xs
+      length vectors `shouldBe` 17
+      forM_ vectors $ \sig ->
+        let eitherSig = decodeTxSig net ctx sig
+         in eitherSig `shouldSatisfy` isLeft
+
+txSigHashSpec :: Network -> Spec
+txSigHashSpec net =
+  when (net.name == "btc") $
+    it "can produce valid sighashes from sighash.json test vectors" $ do
+      xs <- readTestFile "sighash.json" :: IO [A.Value]
+      let vectors =
+            mapMaybe (A.decode . A.encode) xs ::
+              [ ( String,
+                  String,
+                  Int,
+                  Integer,
+                  String
+                )
+              ]
+      length vectors `shouldBe` 500
+      forM_ vectors $ \(txStr, scpStr, i, shI, resStr) -> do
+        let tx = fromString txStr
+            s =
+              fromMaybe (error $ "Could not decode script: " <> cs scpStr) $
+                eitherToMaybe . runGetS deserialize =<< decodeHex (cs scpStr)
+            sh = fromIntegral shI
+            res =
+              eitherToMaybe . runGetS deserialize . B.reverse
+                =<< decodeHex (cs resStr)
+        Just (txSigHash net tx s 0 i sh) `shouldBe` res
+
+txSigHashForkIdSpec :: Network -> Spec
+txSigHashForkIdSpec net =
+  when (net.name == "btc") $
+    it "can produce valid sighashes from forkid_sighash.json test vectors" $ do
+      xs <- readTestFile "forkid_sighash.json" :: IO [A.Value]
+      let vectors =
+            mapMaybe (A.decode . A.encode) xs ::
+              [ ( String,
+                  String,
+                  Int,
+                  Word64,
+                  Integer,
+                  String
+                )
+              ]
+      length vectors `shouldBe` 13
+      forM_ vectors $ \(txStr, scpStr, i, val, shI, resStr) -> do
+        let tx = fromString txStr
+            s =
+              fromMaybe (error $ "Could not decode script: " <> cs scpStr) $
+                eitherToMaybe . runGetS deserialize =<< decodeHex (cs scpStr)
+            sh = fromIntegral shI
+            res = eitherToMaybe . runGetS deserialize =<< decodeHex (cs resStr)
+        Just (txSigHashForkId net tx s val i sh) `shouldBe` res
+
+sigHashSpec :: Network -> Ctx -> Spec
+sigHashSpec net ctx = do
+  it "can correctly show" $ do
+    show (0x00 :: SigHash) `shouldBe` "SigHash " <> show (0x00 :: Word32)
+    show (0x01 :: SigHash) `shouldBe` "SigHash " <> show (0x01 :: Word32)
+    show (0xff :: SigHash) `shouldBe` "SigHash " <> show (0xff :: Word32)
+    show (0xabac3344 :: SigHash)
+      `shouldBe` "SigHash "
+        <> show (0xabac3344 :: Word32)
+  it "can add a forkid" $ do
+    0x00 `sigHashAddForkId` 0x00 `shouldBe` 0x00
+    0xff `sigHashAddForkId` 0x00ffffff `shouldBe` 0xffffffff
+    0xffff `sigHashAddForkId` 0x00aaaaaa `shouldBe` 0xaaaaaaff
+    0xffff `sigHashAddForkId` 0xaaaaaaaa `shouldBe` 0xaaaaaaff
+    0xffff `sigHashAddForkId` 0x00004444 `shouldBe` 0x004444ff
+    0xff01 `sigHashAddForkId` 0x44440000 `shouldBe` 0x44000001
+    0xff03 `sigHashAddForkId` 0x00550000 `shouldBe` 0x55000003
+  it "can extract a forkid" $ do
+    sigHashGetForkId 0x00000000 `shouldBe` 0x00000000
+    sigHashGetForkId 0x80000000 `shouldBe` 0x00800000
+    sigHashGetForkId 0xffffffff `shouldBe` 0x00ffffff
+    sigHashGetForkId 0xabac3403 `shouldBe` 0x00abac34
+  it "can build some vectors" $ do
+    sigHashAll `shouldBe` 0x01
+    sigHashNone `shouldBe` 0x02
+    sigHashSingle `shouldBe` 0x03
+    setForkIdFlag sigHashAll `shouldBe` 0x41
+    setAnyoneCanPay sigHashAll `shouldBe` 0x81
+    setAnyoneCanPay (setForkIdFlag sigHashAll) `shouldBe` 0xc1
+  it "can test flags" $ do
+    hasForkIdFlag sigHashAll `shouldBe` False
+    hasForkIdFlag (setForkIdFlag sigHashAll) `shouldBe` True
+    anyoneCanPay sigHashAll `shouldBe` False
+    anyoneCanPay (setAnyoneCanPay sigHashAll) `shouldBe` True
+    isSigHashAll sigHashNone `shouldBe` False
+    isSigHashAll sigHashAll `shouldBe` True
+    isSigHashNone sigHashSingle `shouldBe` False
+    isSigHashNone sigHashNone `shouldBe` True
+    isSigHashSingle sigHashAll `shouldBe` False
+    isSigHashSingle sigHashSingle `shouldBe` True
+    isSigHashUnknown sigHashAll `shouldBe` False
+    isSigHashUnknown sigHashNone `shouldBe` False
+    isSigHashUnknown sigHashSingle `shouldBe` False
+    isSigHashUnknown 0x00 `shouldBe` True
+    isSigHashUnknown 0x04 `shouldBe` True
+  it "can decodeTxSig . encode a TxSignature" $
+    property $
+      forAll (arbitraryTxSignature net ctx) $ \(_, _, ts) ->
+        let f = decodeTxSig net ctx . encodeTxSig net ctx
+         in f ts `shouldBe` Right ts
+  it "can produce the sighash one" $
+    property $
+      forAll (arbitraryTx net ctx) $
+        forAll arbitraryScript . testSigHashOne net
+
+testSigHashOne :: Network -> Tx -> Script -> Word64 -> Bool -> Property
+testSigHashOne net tx s val acp =
+  not (null tx.inputs) ==>
+    if length tx.inputs > length tx.outputs
+      then res `shouldBe` one
+      else res `shouldNotBe` one
+  where
+    res = txSigHash net tx s val (length tx.inputs - 1) (f sigHashSingle)
+    one = "0100000000000000000000000000000000000000000000000000000000000000"
+    f =
+      if acp
+        then setAnyoneCanPay
+        else id
+
+{- Parse tests from bitcoin-qt repository -}
+
+mapMulSigVector :: Ctx -> ((Text, Text), Int) -> Spec
+mapMulSigVector ctx (v, i) =
+  it name $ runMulSigVector ctx v
+  where
+    name = "check multisig vector " <> show i
+
+runMulSigVector :: Ctx -> (Text, Text) -> Assertion
+runMulSigVector ctx (a, ops) = assertBool "multisig vector" $ Just a == b
+  where
+    s = do
+      s' <- decodeHex ops
+      eitherToMaybe $ runGetS deserialize s'
+    b = do
+      o <- s
+      d <- eitherToMaybe $ decodeOutput ctx o
+      addrToText btc $ payToScriptAddress ctx d
+
+sigDecodeMap :: Network -> Ctx -> (Text, Int) -> Spec
+sigDecodeMap net ctx (_, i) =
+  it ("check signature " ++ show i) func
+  where
+    func = testSigDecode net ctx $ scriptSigSignatures !! i
+
+testSigDecode :: Network -> Ctx -> Text -> Assertion
+testSigDecode net ctx str =
+  let bs = fromJust $ decodeHex str
+      eitherSig = decodeTxSig net ctx bs
+   in assertBool
+        ( unwords
+            [ "Decode failed:",
+              fromLeft (error "Decode did not fail") eitherSig
+            ]
+        )
+        $ isRight eitherSig
+
+mulSigVectors :: [(Text, Text)]
+mulSigVectors =
+  [ ( "3QJmV3qfvL9SuYo34YihAf3sRCW3qSinyC",
+      "52410491bba2510912a5bd37da1fb5b1673010e43d2c6d812c514e91bfa9f2eb\
+      \129e1c183329db55bd868e209aac2fbc02cb33d98fe74bf23f0c235d6126b1d8\
+      \334f864104865c40293a680cb9c020e7b1e106d8c1916d3cef99aa431a56d253\
+      \e69256dac09ef122b1a986818a7cb624532f062c1d1f8722084861c5c3291ccf\
+      \fef4ec687441048d2455d2403e08708fc1f556002f1b6cd83f992d085097f997\
+      \4ab08a28838f07896fbab08f39495e15fa6fad6edbfb1e754e35fa1c7844c41f\
+      \322a1863d4621353ae"
+    )
+  ]
+
+scriptSigSignatures :: [Text]
+scriptSigSignatures =
+  -- Signature in input of txid
+  -- 1983a69265920c24f89aac81942b1a59f7eb30821a8b3fb258f88882b6336053
+  [ "304402205ca6249f43538908151fe67b26d020306c0e59fa206cf9f3ccf641f333\
+    \57119d02206c82f244d04ac0a48024fb9cc246b66e58598acf206139bdb7b75a29\
+    \41a2b1e401"
+    -- Signature in input of txid
+    -- fb0a1d8d34fa5537e461ac384bac761125e1bfa7fec286fa72511240fa66864d.
+    -- Strange DER sizes, but in Blockchain. Now invalid as Haskoin can only
+    -- decode strict signatures.
+    -- "3048022200002b83d59c1d23c08efd82ee0662fec23309c3adbcbd1f0b8695378d\
+    -- \b4b14e736602220000334a96676e58b1bb01784cb7c556dd8ce1c220171904da22\
+    -- \e18fe1e7d1510db501"
+  ]
+
+encodeScriptVector :: Assertion
+encodeScriptVector =
+  assertEqual "Encode script" res (encodeHex $ runPutS $ serialize s)
+  where
+    res =
+      "514104cc71eb30d653c0c3163990c47b976f3fb3f37cccdcbedb169a1dfef58b\
+      \bfbfaff7d8a473e7e2e6d317b87bafe8bde97e3cf8f065dec022b51d11fcdd0d\
+      \348ac4410461cbdcc5409fb4b4d42b51d33381354d80e550078cb532a34bfa2f\
+      \cfdeb7d76519aecc62770f5b0e4ef8551946d8a540911abe3e7854a26f39f58b\
+      \25c15342af52ae"
+    s =
+      Script
+        [ OP_1,
+          opPushData $
+            d
+              "04cc71eb30d653c0c3163990c47b976f3fb3f37cccdcbedb169a1dfef5\
+              \8bbfbfaff7d8a473e7e2e6d317b87bafe8bde97e3cf8f065dec022b51d\
+              \11fcdd0d348ac4",
+          opPushData $
+            d
+              "0461cbdcc5409fb4b4d42b51d33381354d80e550078cb532a34bfa2fcf\
+              \deb7d76519aecc62770f5b0e4ef8551946d8a540911abe3e7854a26f39\
+              \f58b25c15342af",
+          OP_2,
+          OP_CHECKMULTISIG
+        ]
+    d = fromJust . decodeHex
diff --git a/test/Haskoin/Transaction/PartialSpec.hs b/test/Haskoin/Transaction/PartialSpec.hs
new file mode 100644
--- /dev/null
+++ b/test/Haskoin/Transaction/PartialSpec.hs
@@ -0,0 +1,615 @@
+{-# LANGUAGE DataKinds #-}
+{-# LANGUAGE DuplicateRecordFields #-}
+{-# LANGUAGE ImportQualifiedPost #-}
+{-# LANGUAGE NamedFieldPuns #-}
+{-# LANGUAGE OverloadedRecordDot #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE RecordWildCards #-}
+{-# LANGUAGE TypeApplications #-}
+
+module Haskoin.Transaction.PartialSpec (spec) where
+
+import Control.Monad ((<=<))
+import Data.Aeson (FromJSON, parseJSON, withObject, (.:))
+import Data.Bifunctor (first)
+import Data.ByteString (ByteString)
+import Data.ByteString.Base64 (decodeBase64Untyped)
+import Data.Bytes.Get
+import Data.Bytes.Put
+import Data.Bytes.Serial
+import Data.Either (fromRight, isLeft, isRight)
+import Data.HashMap.Strict (fromList, singleton)
+import Data.Maybe (fromJust, isJust)
+import Data.Serialize
+import Data.Text (Text)
+import Data.Text qualified as Text
+import Data.Text.Encoding (encodeUtf8)
+import Haskoin.Address
+import Haskoin.Crypto
+import Haskoin.Network.Constants
+import Haskoin.Network.Data
+import Haskoin.Script
+import Haskoin.Transaction
+import Haskoin.Util
+import Haskoin.Util.Arbitrary
+import Test.HUnit (Assertion, assertBool, assertEqual)
+import Test.Hspec
+import Test.QuickCheck
+
+spec :: Spec
+spec = prepareContext $ \ctx ->
+  describe "PSBT unit tests" $ do
+    it "encodes trivial PSBT" $
+      (encodeHex . runPut . putPSBT ctx) trivialPSBT == trivialPSBTHex
+    it "decodes trivial PSBT" $
+      decodeHexPSBT ctx trivialPSBTHex == Right trivialPSBT
+    it "encodes and decodes non-empty transactions" $
+      (runGet (getPSBT ctx) . runPut . putPSBT ctx) nonEmptyTransactionPSBT == Right nonEmptyTransactionPSBT
+    it "does not decode invalid BIP test vectors" $
+      mapM_ (invalidVecTest ctx) invalidVec
+    it "encodes valid BIP test vectors" $
+      mapM_ (uncurry (encodeVecTest ctx)) validEncodeVec
+    it "decodes valid BIP test vectors" $
+      mapM_ (uncurry (decodeVecTest ctx)) $
+        zip [1 ..] validVec
+    it "decodes vector 2" $ vec2Test ctx
+    it "decodes vector 3" $ vec3Test ctx
+    it "decodes vector 4" $ vec4Test ctx
+    it "decodes vector 5" $ vec5Test ctx
+    it "decodes vector 6" $ vec6Test ctx
+    it "signed and finalized P2PKH PSBTs verify" $
+      property $
+        forAll (arbitraryKeyPair ctx) $
+          verifyNonWitnessPSBT btc ctx . unfinalizedPkhPSBT btc ctx
+    it "signed and finalized multisig PSBTs verify" $
+      property $
+        forAll (arbitraryMultiSig ctx) $
+          verifyNonWitnessPSBT btc ctx . unfinalizedMsPSBT btc ctx
+    it "encodes and decodes PSBT with final witness script" $
+      (fmap (encodeHex . runPut . putPSBT ctx) . decodeHexPSBT ctx) validVec7Hex == Right validVec7Hex
+    it "handles complex PSBTs correctly" $ complexPsbtTest ctx
+    it "calculates keys properly" $ psbtSignerTest ctx
+
+vec2Test :: Ctx -> Assertion
+vec2Test ctx = do
+  psbt <- decodeHexPSBTM ctx "Cannot parse validVec2" validVec2Hex
+  assertEqual "2 inputs" 2 (length psbt.inputs)
+  assertEqual "2 outputs" 2 (length psbt.outputs)
+  assertBool "final script sig" $ isJust (head psbt.inputs).finalScriptSig
+
+  let rdmScript = fromJust (psbt.inputs !! 1).inputRedeemScript
+  assertBool "p2wpkh" $ (isPayWitnessPKHash <$> decodeOutput ctx rdmScript) == Right True
+
+  let scrptPubKey = witnessScriptPubKey ctx (psbt.inputs !! 1)
+      rdmScriptP2SH = toP2SH rdmScript
+  assertEqual "redeem script pubkey equal" rdmScriptP2SH scrptPubKey
+  assertEqual "expected redeem script" (expectedOut ctx) rdmScriptP2SH
+
+  mapM_ (assertEqual "outputs are empty" emptyOutput) psbt.outputs
+
+vec3Test :: Ctx -> Assertion
+vec3Test ctx = do
+  psbt <- decodeHexPSBTM ctx "Cannot parse validVec3" validVec3Hex
+  assertEqual "1 input" 1 (length psbt.inputs)
+  assertEqual "2 outputs" 2 (length psbt.outputs)
+  let (txInput : _) = psbt.unsignedTransaction.inputs
+      (firstInput : _) = psbt.inputs
+      Just utx = firstInput.nonWitnessUtxo
+      OutPoint prevHash prevVOut = txInput.outpoint
+  assertEqual "txids of inputs match" prevHash (txHash utx)
+  let prevOutputKey =
+        fromRight (error "Could not decode key")
+          . unmarshal ctx
+          . (.script)
+          $ utx.outputs !! fromIntegral prevVOut
+  assertBool "p2pkh" $ isPayPKHash prevOutputKey
+  assertEqual "sighash type" sigHashAll (fromJust firstInput.sigHashType)
+
+vec4Test :: Ctx -> Assertion
+vec4Test ctx = do
+  psbt <- decodeHexPSBTM ctx "Cannot parse validVec4" validVec4Hex
+  assertEqual "2 inputs" 2 (length psbt.inputs)
+  assertEqual "2 outputs" 2 (length psbt.outputs)
+  let (firstInput : _) = psbt.inputs
+      (_ : secondInput : _) = psbt.inputs
+  assertEqual "first input not final script sig" Nothing firstInput.finalScriptSig
+  assertEqual "second input not final script sig" Nothing secondInput.finalScriptSig
+
+  let Just rdmScript = secondInput.inputRedeemScript
+  assertBool "p2wpkh" $ (isPayWitnessPKHash <$> decodeOutput ctx rdmScript) == Right True
+
+  let scrptPubKey = witnessScriptPubKey ctx secondInput
+      rdmScriptP2SH = toP2SH rdmScript
+  assertEqual "redeem script pubkey equal" rdmScriptP2SH scrptPubKey
+  assertEqual "expected redeem script" (expectedOut ctx) rdmScriptP2SH
+
+  assertBool "all non-empty outputs" $ emptyOutput `notElem` psbt.outputs
+
+vec5Test :: Ctx -> Assertion
+vec5Test ctx = do
+  psbt <- decodeHexPSBTM ctx "Cannot parse validVec5" validVec5Hex
+  assertEqual "Correctly decode PSBT" expectedPsbt psbt
+  let (input : _) = psbt.inputs
+
+  let Just rdmScript = input.inputRedeemScript
+  assertBool "p2wsh" $ (isPayWitnessScriptHash <$> decodeOutput ctx rdmScript) == Right True
+
+  let scrptPubKey = witnessScriptPubKey ctx input
+      rdmScriptP2SH = toP2SH rdmScript
+  assertEqual "redeem script pubkey equal" rdmScriptP2SH scrptPubKey
+  assertEqual "expected redeem script" expectedOut2 rdmScriptP2SH
+  where
+    expectedOut2 =
+      fromRight (error "could not decode expected output")
+        . unmarshal ctx
+        . fromJust
+        $ decodeHex "a9146345200f68d189e1adc0df1c4d16ea8f14c0dbeb87"
+    -- From the bitcoind decodepsbt rpc call
+    expectedPsbt =
+      PSBT
+        { unsignedTransaction =
+            Tx
+              { version = 2,
+                inputs =
+                  [ TxIn
+                      { outpoint =
+                          OutPoint
+                            { hash = "39bc5c3b33d66ce3d7852a7942331e3ec10f8ba50f225fc41fb5dfa523239a27",
+                              index = 0
+                            },
+                        script = "",
+                        sequence = 4294967295
+                      }
+                  ],
+                outputs =
+                  [ TxOut
+                      { value = 199908000,
+                        script = (fromJust . decodeHex) "76a914ffe9c0061097cc3b636f2cb0460fa4fc427d2b4588ac"
+                      }
+                  ],
+                witness = mempty,
+                locktime = 0
+              },
+          globalUnknown = mempty,
+          inputs =
+            [ Input
+                { nonWitnessUtxo = Nothing,
+                  witnessUtxo =
+                    Just
+                      ( TxOut
+                          { value = 199909013,
+                            script = (fromJust . decodeHex) "a9146345200f68d189e1adc0df1c4d16ea8f14c0dbeb87"
+                          }
+                      ),
+                  partialSigs =
+                    fromList
+                      [ ( PublicKey
+                            { point =
+                                fromJust $
+                                  importPubKey ctx
+                                    =<< decodeHex
+                                      "03b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd46",
+                              compress = True
+                            },
+                          (fromJust . decodeHex) "304302200424b58effaaa694e1559ea5c93bbfd4a89064224055cdf070b6771469442d07021f5c8eb0fea6516d60b8acb33ad64ede60e8785bfb3aa94b99bdf86151db9a9a01"
+                        )
+                      ],
+                  sigHashType = Nothing,
+                  inputRedeemScript =
+                    Just
+                      . fromRight (error "vec5Test: Could not decode redeem script")
+                      . decode
+                      . fromJust
+                      $ decodeHex "0020771fd18ad459666dd49f3d564e3dbc42f4c84774e360ada16816a8ed488d5681",
+                  inputWitnessScript =
+                    Just
+                      . fromRight (error "vec5Test: Could not decode witness script")
+                      . decode
+                      . fromJust
+                      $ decodeHex "522103b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd462103de55d1e1dac805e3f8a58c1fbf9b94c02f3dbaafe127fefca4995f26f82083bd52ae",
+                  inputHDKeypaths =
+                    fromList
+                      [ ( PublicKey
+                            { point =
+                                fromJust $
+                                  importPubKey ctx
+                                    =<< decodeHex
+                                      "03b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd46",
+                              compress = True
+                            },
+                          ("b4a6ba67", [hardIndex 0, hardIndex 0, hardIndex 4])
+                        ),
+                        ( PublicKey
+                            { point =
+                                fromJust $
+                                  importPubKey ctx
+                                    =<< decodeHex
+                                      "03de55d1e1dac805e3f8a58c1fbf9b94c02f3dbaafe127fefca4995f26f82083bd",
+                              compress = True
+                            },
+                          ("b4a6ba67", [hardIndex 0, hardIndex 0, hardIndex 5])
+                        )
+                      ],
+                  finalScriptSig = Nothing,
+                  finalScriptWitness = Nothing,
+                  inputUnknown = mempty
+                }
+            ],
+          outputs =
+            [ Output
+                { outputRedeemScript = Nothing,
+                  outputWitnessScript = Nothing,
+                  outputHDKeypaths = mempty,
+                  outputUnknown = mempty
+                }
+            ]
+        }
+    hardIndex = (+ 2 ^ 31)
+
+vec6Test :: Ctx -> Assertion
+vec6Test ctx = do
+  psbt <- decodeHexPSBTM ctx "Cannot parse validVec6" validVec6Hex
+  assertEqual "1 input" 1 (length psbt.inputs)
+  assertEqual "1 output" 1 (length psbt.outputs)
+
+  let tx = psbt.unsignedTransaction
+  assertEqual "correct transaction" "75c5c9665a570569ad77dd1279e6fd4628a093c4dcbf8d41532614044c14c115" (txHash tx)
+
+  assertEqual "correct unknowns" expectedUnknowns (head psbt.inputs).inputUnknown
+  where
+    expectedUnknowns =
+      UnknownMap $
+        singleton
+          (Key 0x0f (fromJust $ decodeHex "010203040506070809"))
+          (fromJust $ decodeHex "0102030405060708090a0b0c0d0e0f")
+
+complexPsbtTest :: Ctx -> Assertion
+complexPsbtTest ctx = do
+  complex <- readTestFileParser (parseComplexJSON ctx) "complex_psbt.json"
+
+  let computedCombinedPsbt = mergeMany $ complexSignedPsbts complex
+      expectedCombinedPsbt = stripRedundantUtxos $ complexCombinedPsbt complex
+  assertEqual "combined psbt" computedCombinedPsbt (Just expectedCombinedPsbt)
+
+  let computedCompletePsbt = complete ctx $ complexCombinedPsbt complex
+      expectedCompletePsbt = complexCompletePsbt complex
+  assertEqual "complete psbt" computedCompletePsbt expectedCompletePsbt
+
+  let computedFinalTx = finalTransaction $ complexCompletePsbt complex
+  assertEqual "final tx" computedFinalTx (complexFinalTx complex)
+  where
+    stripRedundantUtxos PSBT {..} = PSBT {inputs = stripRedundantUtxo <$> inputs, ..}
+    stripRedundantUtxo input
+      | Just {} <- input.witnessUtxo = input {nonWitnessUtxo = Nothing}
+      | otherwise = input
+
+psbtSignerTest :: Ctx -> Assertion
+psbtSignerTest ctx = do
+  assertEqual "recover explicit secret key" (Just theSecKey) (getSignerKey signer thePubKey Nothing)
+  assertEqual
+    "recover key for origin path"
+    (Just originPathSecKey)
+    (getSignerKey signer originPathPubKey (Just (rootFP, originKeyPath)))
+  assertEqual
+    "recover key for direct path"
+    (Just directPathSecKey)
+    (getSignerKey signer directPathPubKey (Just (keyFP, directPath)))
+  where
+    signer = secKeySigner ctx theSecKey <> xPrvSigner ctx xprv (Just origin)
+
+    Just theSecKey = secKey "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
+    thePubKey = PublicKey {point = derivePubKey ctx theSecKey, compress = True}
+
+    rootXPrv = makeXPrvKey "PSBTSignerTest"
+    rootFP = xPubFP ctx $ deriveXPubKey ctx rootXPrv
+    xprv = derivePath ctx keyPath rootXPrv
+    keyFP = xPubFP ctx $ deriveXPubKey ctx xprv
+    keyPath = Deriv :| 444
+    origin = (rootFP, keyPath)
+
+    originKeyPath = Deriv :| 444 :/ 0
+    originPathSecKey = (derivePath ctx originKeyPath rootXPrv).key
+    originPathPubKey = PublicKey {point = derivePubKey ctx originPathSecKey, compress = True}
+
+    directPath = Deriv :/ 1
+    directPathSecKey = (derivePath ctx directPath xprv).key
+    directPathPubKey = PublicKey {point = derivePubKey ctx directPathSecKey, compress = True}
+
+expectedOut :: Ctx -> ScriptOutput
+expectedOut ctx =
+  fromRight (error "could not decode expected output")
+    . unmarshal ctx
+    . fromJust
+    $ decodeHex "a9143545e6e33b832c47050f24d3eeb93c9c03948bc787"
+
+witnessScriptPubKey :: Ctx -> Input -> ScriptOutput
+witnessScriptPubKey ctx =
+  fromRight (error "could not decode witness utxo")
+    . unmarshal ctx
+    . (.script)
+    . fromJust
+    . (.witnessUtxo)
+
+decodeHexPSBT :: Ctx -> Text -> Either String PSBT
+decodeHexPSBT ctx = runGet (getPSBT ctx) . fromJust . decodeHex
+
+decodeHexPSBTM :: (Monad m, MonadFail m) => Ctx -> String -> Text -> m PSBT
+decodeHexPSBTM ctx errMsg = either (fail . (errMsg <>) . (": " <>)) return . decodeHexPSBT ctx
+
+hexScript :: Text -> ByteString
+hexScript =
+  either (error "Could not decode script") encodeScript
+    . runGetS deserialize
+    . fromJust
+    . decodeHex
+  where
+    encodeScript :: Script -> ByteString
+    encodeScript = runPutS . serialize
+
+invalidVecTest :: Ctx -> Text -> Assertion
+invalidVecTest ctx = assertBool "invalid psbt" . isLeft . decodeHexPSBT ctx
+
+decodeVecTest :: Ctx -> Int -> Text -> Assertion
+decodeVecTest ctx i = assertBool (show i <> " decodes correctly") . isRight . decodeHexPSBT ctx
+
+encodeVecTest :: Ctx -> PSBT -> Text -> Assertion
+encodeVecTest ctx psbt hex =
+  assertEqual
+    "encodes correctly"
+    ((runPut . putPSBT ctx) psbt)
+    ((fromJust . decodeHex) hex)
+
+trivialPSBT :: PSBT
+trivialPSBT =
+  PSBT
+    { unsignedTransaction = Tx {version = 2, inputs = [], outputs = [], witness = [], locktime = 0},
+      globalUnknown = UnknownMap mempty,
+      inputs = [],
+      outputs = []
+    }
+
+trivialPSBTHex :: Text
+trivialPSBTHex = "70736274ff01000a0200000000000000000000"
+
+nonEmptyTransactionPSBT :: PSBT
+nonEmptyTransactionPSBT = emptyPSBT testTx1
+
+verifyNonWitnessPSBT :: Network -> Ctx -> PSBT -> Bool
+verifyNonWitnessPSBT net ctx psbt =
+  verifyStdTx net ctx (finalTransaction (complete ctx psbt)) sigData
+  where
+    sigData = inputSigData =<< zip psbt.inputs psbt.unsignedTransaction.inputs
+    decodeOutScript = fromRight (error "Could not parse output script") . unmarshal ctx
+    inputSigData (input@Input {}, txInput@TxIn {}) =
+      map
+        (\(TxOut val script) -> (decodeOutScript script, val, txInput.outpoint))
+        (fromJust input.nonWitnessUtxo).outputs
+
+unfinalizedPkhPSBT :: Network -> Ctx -> (PrivateKey, PublicKey) -> PSBT
+unfinalizedPkhPSBT net ctx (prvKey, pubKey) =
+  let PSBT {..} = emptyPSBT currTx
+   in PSBT {inputs = [input], ..}
+  where
+    input =
+      emptyInput
+        { nonWitnessUtxo = Just prevTx,
+          partialSigs = singleton pubKey sig
+        }
+    currTx =
+      unfinalizedTx (txHash prevTx)
+    prevTx =
+      testUtxo [prevOut]
+    prevOutScript =
+      addressToScript ctx (pubKeyAddr ctx pubKey)
+    prevOut =
+      TxOut
+        { value = 200000000,
+          script = runPutS (serialize prevOutScript)
+        }
+    h = txSigHash net currTx prevOutScript prevOut.value 0 sigHashAll
+    sig =
+      encodeTxSig net ctx $
+        TxSignature (signHash ctx prvKey.key h) sigHashAll
+
+arbitraryMultiSig :: Ctx -> Gen ([(PrivateKey, PublicKey)], Int)
+arbitraryMultiSig ctx = do
+  (m, n) <- arbitraryMSParam
+  keys <- vectorOf n (arbitraryKeyPair ctx)
+  return (keys, m)
+
+unfinalizedMsPSBT :: Network -> Ctx -> ([(PrivateKey, PublicKey)], Int) -> PSBT
+unfinalizedMsPSBT net ctx (keys, m) =
+  let PSBT {..} = emptyPSBT currTx
+   in PSBT {inputs = [input], ..}
+  where
+    input =
+      emptyInput
+        { nonWitnessUtxo = Just prevTx,
+          partialSigs = sigs,
+          inputRedeemScript = Just prevOutScript
+        }
+    currTx = unfinalizedTx (txHash prevTx)
+    prevTx = testUtxo [prevOut]
+    prevOutScript = encodeOutput ctx $ PayMulSig (map snd keys) m
+    prevOut =
+      TxOut
+        { value = 200000000,
+          script = marshal ctx (toP2SH prevOutScript)
+        }
+    h = txSigHash net currTx prevOutScript prevOut.value 0 sigHashAll
+    sigs = fromList $ map sig keys
+    sig (prvKey@PrivateKey {key}, pubKey) =
+      let sh = signHash ctx key h
+          ts = TxSignature sh sigHashAll
+       in (pubKey, encodeTxSig net ctx ts)
+
+unfinalizedTx :: TxHash -> Tx
+unfinalizedTx prevHash =
+  Tx
+    { version = 2,
+      inputs =
+        [ TxIn
+            { outpoint = OutPoint prevHash 0,
+              script = "",
+              sequence = 4294967294
+            }
+        ],
+      outputs =
+        [ TxOut {value = 99999699, script = hexScript "76a914d0c59903c5bac2868760e90fd521a4665aa7652088ac"},
+          TxOut {value = 100000000, script = hexScript "a9143545e6e33b832c47050f24d3eeb93c9c03948bc787"}
+        ],
+      witness = [],
+      locktime = 1257139
+    }
+
+invalidVec :: [Text]
+invalidVec =
+  [ "0200000001268171371edff285e937adeea4b37b78000c0566cbb3ad64641713ca42171bf6000000006a473044022070b2245123e6bf474d60c5b50c043d4c691a5d2435f09a34a7662a9dc251790a022001329ca9dacf280bdf30740ec0390422422c81cb45839457aeb76fc12edd95b3012102657d118d3357b8e0f4c2cd46db7b39f6d9c38d9a70abcb9b2de5dc8dbfe4ce31feffffff02d3dff505000000001976a914d0c59903c5bac2868760e90fd521a4665aa7652088ac00e1f5050000000017a9143545e6e33b832c47050f24d3eeb93c9c03948bc787b32e1300",
+    "70736274ff0100750200000001268171371edff285e937adeea4b37b78000c0566cbb3ad64641713ca42171bf60000000000feffffff02d3dff505000000001976a914d0c59903c5bac2868760e90fd521a4665aa7652088ac00e1f5050000000017a9143545e6e33b832c47050f24d3eeb93c9c03948bc787b32e1300000100fda5010100000000010289a3c71eab4d20e0371bbba4cc698fa295c9463afa2e397f8533ccb62f9567e50100000017160014be18d152a9b012039daf3da7de4f53349eecb985ffffffff86f8aa43a71dff1448893a530a7237ef6b4608bbb2dd2d0171e63aec6a4890b40100000017160014fe3e9ef1a745e974d902c4355943abcb34bd5353ffffffff0200c2eb0b000000001976a91485cff1097fd9e008bb34af709c62197b38978a4888ac72fef84e2c00000017a914339725ba21efd62ac753a9bcd067d6c7a6a39d05870247304402202712be22e0270f394f568311dc7ca9a68970b8025fdd3b240229f07f8a5f3a240220018b38d7dcd314e734c9276bd6fb40f673325bc4baa144c800d2f2f02db2765c012103d2e15674941bad4a996372cb87e1856d3652606d98562fe39c5e9e7e413f210502483045022100d12b852d85dcd961d2f5f4ab660654df6eedcc794c0c33ce5cc309ffb5fce58d022067338a8e0e1725c197fb1a88af59f51e44e4255b20167c8684031c05d1f2592a01210223b72beef0965d10be0778efecd61fcac6f79a4ea169393380734464f84f2ab30000000000",
+    "70736274ff0100fd0a010200000002ab0949a08c5af7c49b8212f417e2f15ab3f5c33dcf153821a8139f877a5b7be4000000006a47304402204759661797c01b036b25928948686218347d89864b719e1f7fcf57d1e511658702205309eabf56aa4d8891ffd111fdf1336f3a29da866d7f8486d75546ceedaf93190121035cdc61fc7ba971c0b501a646a2a83b102cb43881217ca682dc86e2d73fa88292feffffffab0949a08c5af7c49b8212f417e2f15ab3f5c33dcf153821a8139f877a5b7be40100000000feffffff02603bea0b000000001976a914768a40bbd740cbe81d988e71de2a4d5c71396b1d88ac8e240000000000001976a9146f4620b553fa095e721b9ee0efe9fa039cca459788ac00000000000001012000e1f5050000000017a9143545e6e33b832c47050f24d3eeb93c9c03948bc787010416001485d13537f2e265405a34dbafa9e3dda01fb82308000000",
+    "70736274ff000100fda5010100000000010289a3c71eab4d20e0371bbba4cc698fa295c9463afa2e397f8533ccb62f9567e50100000017160014be18d152a9b012039daf3da7de4f53349eecb985ffffffff86f8aa43a71dff1448893a530a7237ef6b4608bbb2dd2d0171e63aec6a4890b40100000017160014fe3e9ef1a745e974d902c4355943abcb34bd5353ffffffff0200c2eb0b000000001976a91485cff1097fd9e008bb34af709c62197b38978a4888ac72fef84e2c00000017a914339725ba21efd62ac753a9bcd067d6c7a6a39d05870247304402202712be22e0270f394f568311dc7ca9a68970b8025fdd3b240229f07f8a5f3a240220018b38d7dcd314e734c9276bd6fb40f673325bc4baa144c800d2f2f02db2765c012103d2e15674941bad4a996372cb87e1856d3652606d98562fe39c5e9e7e413f210502483045022100d12b852d85dcd961d2f5f4ab660654df6eedcc794c0c33ce5cc309ffb5fce58d022067338a8e0e1725c197fb1a88af59f51e44e4255b20167c8684031c05d1f2592a01210223b72beef0965d10be0778efecd61fcac6f79a4ea169393380734464f84f2ab30000000000",
+    "70736274ff0100750200000001268171371edff285e937adeea4b37b78000c0566cbb3ad64641713ca42171bf60000000000feffffff02d3dff505000000001976a914d0c59903c5bac2868760e90fd521a4665aa7652088ac00e1f5050000000017a9143545e6e33b832c47050f24d3eeb93c9c03948bc787b32e1300000100fda5010100000000010289a3c71eab4d20e0371bbba4cc698fa295c9463afa2e397f8533ccb62f9567e50100000017160014be18d152a9b012039daf3da7de4f53349eecb985ffffffff86f8aa43a71dff1448893a530a7237ef6b4608bbb2dd2d0171e63aec6a4890b40100000017160014fe3e9ef1a745e974d902c4355943abcb34bd5353ffffffff0200c2eb0b000000001976a91485cff1097fd9e008bb34af709c62197b38978a4888ac72fef84e2c00000017a914339725ba21efd62ac753a9bcd067d6c7a6a39d05870247304402202712be22e0270f394f568311dc7ca9a68970b8025fdd3b240229f07f8a5f3a240220018b38d7dcd314e734c9276bd6fb40f673325bc4baa144c800d2f2f02db2765c012103d2e15674941bad4a996372cb87e1856d3652606d98562fe39c5e9e7e413f210502483045022100d12b852d85dcd961d2f5f4ab660654df6eedcc794c0c33ce5cc309ffb5fce58d022067338a8e0e1725c197fb1a88af59f51e44e4255b20167c8684031c05d1f2592a01210223b72beef0965d10be0778efecd61fcac6f79a4ea169393380734464f84f2ab30000000001003f0200000001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000ffffffff010000000000000000036a010000000000000000",
+    "70736274ff020001550200000001279a2323a5dfb51fc45f220fa58b0fc13e1e3342792a85d7e36cd6333b5cbc390000000000ffffffff01a05aea0b000000001976a914ffe9c0061097cc3b636f2cb0460fa4fc427d2b4588ac0000000000010120955eea0b0000000017a9146345200f68d189e1adc0df1c4d16ea8f14c0dbeb87220203b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd4646304302200424b58effaaa694e1559ea5c93bbfd4a89064224055cdf070b6771469442d07021f5c8eb0fea6516d60b8acb33ad64ede60e8785bfb3aa94b99bdf86151db9a9a010104220020771fd18ad459666dd49f3d564e3dbc42f4c84774e360ada16816a8ed488d5681010547522103b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd462103de55d1e1dac805e3f8a58c1fbf9b94c02f3dbaafe127fefca4995f26f82083bd52ae220603b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd4610b4a6ba67000000800000008004000080220603de55d1e1dac805e3f8a58c1fbf9b94c02f3dbaafe127fefca4995f26f82083bd10b4a6ba670000008000000080050000800000",
+    "70736274ff0100550200000001279a2323a5dfb51fc45f220fa58b0fc13e1e3342792a85d7e36cd6333b5cbc390000000000ffffffff01a05aea0b000000001976a914ffe9c0061097cc3b636f2cb0460fa4fc427d2b4588ac000000000002010020955eea0b0000000017a9146345200f68d189e1adc0df1c4d16ea8f14c0dbeb87220203b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd4646304302200424b58effaaa694e1559ea5c93bbfd4a89064224055cdf070b6771469442d07021f5c8eb0fea6516d60b8acb33ad64ede60e8785bfb3aa94b99bdf86151db9a9a010104220020771fd18ad459666dd49f3d564e3dbc42f4c84774e360ada16816a8ed488d5681010547522103b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd462103de55d1e1dac805e3f8a58c1fbf9b94c02f3dbaafe127fefca4995f26f82083bd52ae220603b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd4610b4a6ba67000000800000008004000080220603de55d1e1dac805e3f8a58c1fbf9b94c02f3dbaafe127fefca4995f26f82083bd10b4a6ba670000008000000080050000800000",
+    "70736274ff0100550200000001279a2323a5dfb51fc45f220fa58b0fc13e1e3342792a85d7e36cd6333b5cbc390000000000ffffffff01a05aea0b000000001976a914ffe9c0061097cc3b636f2cb0460fa4fc427d2b4588ac0000000000010120955eea0b0000000017a9146345200f68d189e1adc0df1c4d16ea8f14c0dbeb87210203b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd46304302200424b58effaaa694e1559ea5c93bbfd4a89064224055cdf070b6771469442d07021f5c8eb0fea6516d60b8acb33ad64ede60e8785bfb3aa94b99bdf86151db9a9a010104220020771fd18ad459666dd49f3d564e3dbc42f4c84774e360ada16816a8ed488d5681010547522103b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd462103de55d1e1dac805e3f8a58c1fbf9b94c02f3dbaafe127fefca4995f26f82083bd52ae220603b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd4610b4a6ba67000000800000008004000080220603de55d1e1dac805e3f8a58c1fbf9b94c02f3dbaafe127fefca4995f26f82083bd10b4a6ba670000008000000080050000800000",
+    "70736274ff0100550200000001279a2323a5dfb51fc45f220fa58b0fc13e1e3342792a85d7e36cd6333b5cbc390000000000ffffffff01a05aea0b000000001976a914ffe9c0061097cc3b636f2cb0460fa4fc427d2b4588ac0000000000010120955eea0b0000000017a9146345200f68d189e1adc0df1c4d16ea8f14c0dbeb87220203b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd4646304302200424b58effaaa694e1559ea5c93bbfd4a89064224055cdf070b6771469442d07021f5c8eb0fea6516d60b8acb33ad64ede60e8785bfb3aa94b99bdf86151db9a9a01020400220020771fd18ad459666dd49f3d564e3dbc42f4c84774e360ada16816a8ed488d5681010547522103b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd462103de55d1e1dac805e3f8a58c1fbf9b94c02f3dbaafe127fefca4995f26f82083bd52ae220603b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd4610b4a6ba67000000800000008004000080220603de55d1e1dac805e3f8a58c1fbf9b94c02f3dbaafe127fefca4995f26f82083bd10b4a6ba670000008000000080050000800000",
+    "70736274ff0100550200000001279a2323a5dfb51fc45f220fa58b0fc13e1e3342792a85d7e36cd6333b5cbc390000000000ffffffff01a05aea0b000000001976a914ffe9c0061097cc3b636f2cb0460fa4fc427d2b4588ac0000000000010120955eea0b0000000017a9146345200f68d189e1adc0df1c4d16ea8f14c0dbeb87220203b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd4646304302200424b58effaaa694e1559ea5c93bbfd4a89064224055cdf070b6771469442d07021f5c8eb0fea6516d60b8acb33ad64ede60e8785bfb3aa94b99bdf86151db9a9a010104220020771fd18ad459666dd49f3d564e3dbc42f4c84774e360ada16816a8ed488d568102050047522103b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd462103de55d1e1dac805e3f8a58c1fbf9b94c02f3dbaafe127fefca4995f26f82083bd52ae220603b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd4610b4a6ba67000000800000008004000080220603de55d1e1dac805e3f8a58c1fbf9b94c02f3dbaafe127fefca4995f26f82083bd10b4a6ba670000008000000080050000800000",
+    "70736274ff0100550200000001279a2323a5dfb51fc45f220fa58b0fc13e1e3342792a85d7e36cd6333b5cbc390000000000ffffffff01a05aea0b000000001976a914ffe9c0061097cc3b636f2cb0460fa4fc427d2b4588ac0000000000010120955eea0b0000000017a9146345200f68d189e1adc0df1c4d16ea8f14c0dbeb87220203b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd4646304302200424b58effaaa694e1559ea5c93bbfd4a89064224055cdf070b6771469442d07021f5c8eb0fea6516d60b8acb33ad64ede60e8785bfb3aa94b99bdf86151db9a9a010104220020771fd18ad459666dd49f3d564e3dbc42f4c84774e360ada16816a8ed488d5681010547522103b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd462103de55d1e1dac805e3f8a58c1fbf9b94c02f3dbaafe127fefca4995f26f82083bd52ae210603b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd10b4a6ba67000000800000008004000080220603de55d1e1dac805e3f8a58c1fbf9b94c02f3dbaafe127fefca4995f26f82083bd10b4a6ba670000008000000080050000800000",
+    "70736274ff01009a020000000258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd750000000000ffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d0100000000ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f0000000000020000bb0200000001aad73931018bd25f84ae400b68848be09db706eac2ac18298babee71ab656f8b0000000048473044022058f6fc7c6a33e1b31548d481c826c015bd30135aad42cd67790dab66d2ad243b02204a1ced2604c6735b6393e5b41691dd78b00f0c5942fb9f751856faa938157dba01feffffff0280f0fa020000000017a9140fb9463421696b82c833af241c78c17ddbde493487d0f20a270100000017a91429ca74f8a08f81999428185c97b5d852e4063f6187650000000107da00473044022074018ad4180097b873323c0015720b3684cc8123891048e7dbcd9b55ad679c99022073d369b740e3eb53dcefa33823c8070514ca55a7dd9544f157c167913261118c01483045022100f61038b308dc1da865a34852746f015772934208c6d24454393cd99bdf2217770220056e675a675a6d0a02b85b14e5e29074d8a25a9b5760bea2816f661910a006ea01475221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752ae0001012000c2eb0b0000000017a914b7f5faf40e3d40a5a459b1db3535f2b72fa921e8870107232200208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b20289030108da0400473044022062eb7a556107a7c73f45ac4ab5a1dddf6f7075fb1275969a7f383efff784bcb202200c05dbb7470dbf2f08557dd356c7325c1ed30913e996cd3840945db12228da5f01473044022065f45ba5998b59a27ffe1a7bed016af1f1f90d54b3aa8f7450aa5f56a25103bd02207f724703ad1edb96680b284b56d4ffcb88f7fb759eabbe08aa30f29b851383d20147522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae00220203a9a4c37f5996d3aa25dbac6b570af0650394492942460b354753ed9eeca5877110d90c6a4f000000800000008004000080002202027f6399757d2eff55a136ad02c684b1838b6556e5f1b6b34282a94b6b5005109610d90c6a4f00000080000000800500008000",
+    "70736274ff01009a020000000258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd750000000000ffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d0100000000ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f00000000000100bb0200000001aad73931018bd25f84ae400b68848be09db706eac2ac18298babee71ab656f8b0000000048473044022058f6fc7c6a33e1b31548d481c826c015bd30135aad42cd67790dab66d2ad243b02204a1ced2604c6735b6393e5b41691dd78b00f0c5942fb9f751856faa938157dba01feffffff0280f0fa020000000017a9140fb9463421696b82c833af241c78c17ddbde493487d0f20a270100000017a91429ca74f8a08f81999428185c97b5d852e4063f618765000000020700da00473044022074018ad4180097b873323c0015720b3684cc8123891048e7dbcd9b55ad679c99022073d369b740e3eb53dcefa33823c8070514ca55a7dd9544f157c167913261118c01483045022100f61038b308dc1da865a34852746f015772934208c6d24454393cd99bdf2217770220056e675a675a6d0a02b85b14e5e29074d8a25a9b5760bea2816f661910a006ea01475221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752ae0001012000c2eb0b0000000017a914b7f5faf40e3d40a5a459b1db3535f2b72fa921e8870107232200208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b20289030108da0400473044022062eb7a556107a7c73f45ac4ab5a1dddf6f7075fb1275969a7f383efff784bcb202200c05dbb7470dbf2f08557dd356c7325c1ed30913e996cd3840945db12228da5f01473044022065f45ba5998b59a27ffe1a7bed016af1f1f90d54b3aa8f7450aa5f56a25103bd02207f724703ad1edb96680b284b56d4ffcb88f7fb759eabbe08aa30f29b851383d20147522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae00220203a9a4c37f5996d3aa25dbac6b570af0650394492942460b354753ed9eeca5877110d90c6a4f000000800000008004000080002202027f6399757d2eff55a136ad02c684b1838b6556e5f1b6b34282a94b6b5005109610d90c6a4f00000080000000800500008000",
+    "70736274ff01009a020000000258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd750000000000ffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d0100000000ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f00000000000100bb0200000001aad73931018bd25f84ae400b68848be09db706eac2ac18298babee71ab656f8b0000000048473044022058f6fc7c6a33e1b31548d481c826c015bd30135aad42cd67790dab66d2ad243b02204a1ced2604c6735b6393e5b41691dd78b00f0c5942fb9f751856faa938157dba01feffffff0280f0fa020000000017a9140fb9463421696b82c833af241c78c17ddbde493487d0f20a270100000017a91429ca74f8a08f81999428185c97b5d852e4063f6187650000000107da00473044022074018ad4180097b873323c0015720b3684cc8123891048e7dbcd9b55ad679c99022073d369b740e3eb53dcefa33823c8070514ca55a7dd9544f157c167913261118c01483045022100f61038b308dc1da865a34852746f015772934208c6d24454393cd99bdf2217770220056e675a675a6d0a02b85b14e5e29074d8a25a9b5760bea2816f661910a006ea01475221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752ae0001012000c2eb0b0000000017a914b7f5faf40e3d40a5a459b1db3535f2b72fa921e8870107232200208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b2028903020800da0400473044022062eb7a556107a7c73f45ac4ab5a1dddf6f7075fb1275969a7f383efff784bcb202200c05dbb7470dbf2f08557dd356c7325c1ed30913e996cd3840945db12228da5f01473044022065f45ba5998b59a27ffe1a7bed016af1f1f90d54b3aa8f7450aa5f56a25103bd02207f724703ad1edb96680b284b56d4ffcb88f7fb759eabbe08aa30f29b851383d20147522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae00220203a9a4c37f5996d3aa25dbac6b570af0650394492942460b354753ed9eeca5877110d90c6a4f000000800000008004000080002202027f6399757d2eff55a136ad02c684b1838b6556e5f1b6b34282a94b6b5005109610d90c6a4f00000080000000800500008000",
+    "70736274ff01009a020000000258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd750000000000ffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d0100000000ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f00000000000100bb0200000001aad73931018bd25f84ae400b68848be09db706eac2ac18298babee71ab656f8b0000000048473044022058f6fc7c6a33e1b31548d481c826c015bd30135aad42cd67790dab66d2ad243b02204a1ced2604c6735b6393e5b41691dd78b00f0c5942fb9f751856faa938157dba01feffffff0280f0fa020000000017a9140fb9463421696b82c833af241c78c17ddbde493487d0f20a270100000017a91429ca74f8a08f81999428185c97b5d852e4063f6187650000000107da00473044022074018ad4180097b873323c0015720b3684cc8123891048e7dbcd9b55ad679c99022073d369b740e3eb53dcefa33823c8070514ca55a7dd9544f157c167913261118c01483045022100f61038b308dc1da865a34852746f015772934208c6d24454393cd99bdf2217770220056e675a675a6d0a02b85b14e5e29074d8a25a9b5760bea2816f661910a006ea01475221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752ae0001012000c2eb0b0000000017a914b7f5faf40e3d40a5a459b1db3535f2b72fa921e8870107232200208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b20289030108da0400473044022062eb7a556107a7c73f45ac4ab5a1dddf6f7075fb1275969a7f383efff784bcb202200c05dbb7470dbf2f08557dd356c7325c1ed30913e996cd3840945db12228da5f01473044022065f45ba5998b59a27ffe1a7bed016af1f1f90d54b3aa8f7450aa5f56a25103bd02207f724703ad1edb96680b284b56d4ffcb88f7fb759eabbe08aa30f29b851383d20147522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae00210203a9a4c37f5996d3aa25dbac6b570af0650394492942460b354753ed9eeca58710d90c6a4f000000800000008004000080002202027f6399757d2eff55a136ad02c684b1838b6556e5f1b6b34282a94b6b5005109610d90c6a4f00000080000000800500008000",
+    "70736274ff0100730200000001301ae986e516a1ec8ac5b4bc6573d32f83b465e23ad76167d68b38e730b4dbdb0000000000ffffffff02747b01000000000017a91403aa17ae882b5d0d54b25d63104e4ffece7b9ea2876043993b0000000017a914b921b1ba6f722e4bfa83b6557a3139986a42ec8387000000000001011f00ca9a3b00000000160014d2d94b64ae08587eefc8eeb187c601e939f9037c0203000100000000010016001462e9e982fff34dd8239610316b090cd2a3b747cb000100220020876bad832f1d168015ed41232a9ea65a1815d9ef13c0ef8759f64b5b2b278a65010125512103b7ce23a01c5b4bf00a642537cdfabb315b668332867478ef51309d2bd57f8a8751ae00",
+    "70736274ff0100730200000001301ae986e516a1ec8ac5b4bc6573d32f83b465e23ad76167d68b38e730b4dbdb0000000000ffffffff02747b01000000000017a91403aa17ae882b5d0d54b25d63104e4ffece7b9ea2876043993b0000000017a914b921b1ba6f722e4bfa83b6557a3139986a42ec8387000000000001011f00ca9a3b00000000160014d2d94b64ae08587eefc8eeb187c601e939f9037c0002000016001462e9e982fff34dd8239610316b090cd2a3b747cb000100220020876bad832f1d168015ed41232a9ea65a1815d9ef13c0ef8759f64b5b2b278a65010125512103b7ce23a01c5b4bf00a642537cdfabb315b668332867478ef51309d2bd57f8a8751ae00",
+    "70736274ff0100730200000001301ae986e516a1ec8ac5b4bc6573d32f83b465e23ad76167d68b38e730b4dbdb0000000000ffffffff02747b01000000000017a91403aa17ae882b5d0d54b25d63104e4ffece7b9ea2876043993b0000000017a914b921b1ba6f722e4bfa83b6557a3139986a42ec8387000000000001011f00ca9a3b00000000160014d2d94b64ae08587eefc8eeb187c601e939f9037c00010016001462e9e982fff34dd8239610316b090cd2a3b747cb000100220020876bad832f1d168015ed41232a9ea65a1815d9ef13c0ef8759f64b5b2b278a6521010025512103b7ce23a01c5b4bf00a642537cdfabb315b668332867478ef51309d2bd57f8a8751ae00"
+  ]
+
+validEncodeVec :: [(PSBT, Text)]
+validEncodeVec = [(validVec1, validVec1Hex)]
+
+testTx1 :: Tx
+testTx1 =
+  Tx
+    { version = 2,
+      inputs =
+        [ TxIn
+            { outpoint = OutPoint "f61b1742ca13176464adb3cb66050c00787bb3a4eead37e985f2df1e37718126" 0,
+              script = "",
+              sequence = 4294967294
+            }
+        ],
+      outputs =
+        [ TxOut {value = 99999699, script = hexScript "76a914d0c59903c5bac2868760e90fd521a4665aa7652088ac"},
+          TxOut {value = 100000000, script = hexScript "a9143545e6e33b832c47050f24d3eeb93c9c03948bc787"}
+        ],
+      witness = [],
+      locktime = 1257139
+    }
+
+testUtxo :: [TxOut] -> Tx
+testUtxo prevOuts =
+  Tx
+    { version = 1,
+      inputs =
+        [ TxIn
+            { outpoint = OutPoint "e567952fb6cc33857f392efa3a46c995a28f69cca4bb1b37e0204dab1ec7a389" 1,
+              script = hexScript "160014be18d152a9b012039daf3da7de4f53349eecb985",
+              sequence = 4294967295
+            },
+          TxIn
+            { outpoint = OutPoint "b490486aec3ae671012dddb2bb08466bef37720a533a894814ff1da743aaf886" 1,
+              script = hexScript "160014fe3e9ef1a745e974d902c4355943abcb34bd5353",
+              sequence = 4294967295
+            }
+        ],
+      outputs = prevOuts,
+      witness =
+        [ [ fromJust $ decodeHex "304402202712be22e0270f394f568311dc7ca9a68970b8025fdd3b240229f07f8a5f3a240220018b38d7dcd314e734c9276bd6fb40f673325bc4baa144c800d2f2f02db2765c01",
+            fromJust $ decodeHex "03d2e15674941bad4a996372cb87e1856d3652606d98562fe39c5e9e7e413f2105"
+          ],
+          [ fromJust $ decodeHex "3045022100d12b852d85dcd961d2f5f4ab660654df6eedcc794c0c33ce5cc309ffb5fce58d022067338a8e0e1725c197fb1a88af59f51e44e4255b20167c8684031c05d1f2592a01",
+            fromJust $ decodeHex "0223b72beef0965d10be0778efecd61fcac6f79a4ea169393380734464f84f2ab3"
+          ]
+        ],
+      locktime = 0
+    }
+
+testUtxo1 :: Tx
+testUtxo1 =
+  testUtxo
+    [ TxOut {value = 200000000, script = hexScript "76a91485cff1097fd9e008bb34af709c62197b38978a4888ac"},
+      TxOut {value = 190303501938, script = hexScript "a914339725ba21efd62ac753a9bcd067d6c7a6a39d0587"}
+    ]
+
+validVec1 :: PSBT
+validVec1 =
+  let PSBT {..} = emptyPSBT testTx1
+   in PSBT {inputs = [emptyInput {nonWitnessUtxo = Just testUtxo1}], ..}
+
+validVec :: [Text]
+validVec = [validVec1Hex, validVec2Hex, validVec3Hex, validVec4Hex, validVec5Hex, validVec6Hex]
+
+validVec1Hex :: Text
+validVec1Hex = "70736274ff0100750200000001268171371edff285e937adeea4b37b78000c0566cbb3ad64641713ca42171bf60000000000feffffff02d3dff505000000001976a914d0c59903c5bac2868760e90fd521a4665aa7652088ac00e1f5050000000017a9143545e6e33b832c47050f24d3eeb93c9c03948bc787b32e1300000100fda5010100000000010289a3c71eab4d20e0371bbba4cc698fa295c9463afa2e397f8533ccb62f9567e50100000017160014be18d152a9b012039daf3da7de4f53349eecb985ffffffff86f8aa43a71dff1448893a530a7237ef6b4608bbb2dd2d0171e63aec6a4890b40100000017160014fe3e9ef1a745e974d902c4355943abcb34bd5353ffffffff0200c2eb0b000000001976a91485cff1097fd9e008bb34af709c62197b38978a4888ac72fef84e2c00000017a914339725ba21efd62ac753a9bcd067d6c7a6a39d05870247304402202712be22e0270f394f568311dc7ca9a68970b8025fdd3b240229f07f8a5f3a240220018b38d7dcd314e734c9276bd6fb40f673325bc4baa144c800d2f2f02db2765c012103d2e15674941bad4a996372cb87e1856d3652606d98562fe39c5e9e7e413f210502483045022100d12b852d85dcd961d2f5f4ab660654df6eedcc794c0c33ce5cc309ffb5fce58d022067338a8e0e1725c197fb1a88af59f51e44e4255b20167c8684031c05d1f2592a01210223b72beef0965d10be0778efecd61fcac6f79a4ea169393380734464f84f2ab300000000000000"
+
+validVec2Hex :: Text
+validVec2Hex = "70736274ff0100a00200000002ab0949a08c5af7c49b8212f417e2f15ab3f5c33dcf153821a8139f877a5b7be40000000000feffffffab0949a08c5af7c49b8212f417e2f15ab3f5c33dcf153821a8139f877a5b7be40100000000feffffff02603bea0b000000001976a914768a40bbd740cbe81d988e71de2a4d5c71396b1d88ac8e240000000000001976a9146f4620b553fa095e721b9ee0efe9fa039cca459788ac000000000001076a47304402204759661797c01b036b25928948686218347d89864b719e1f7fcf57d1e511658702205309eabf56aa4d8891ffd111fdf1336f3a29da866d7f8486d75546ceedaf93190121035cdc61fc7ba971c0b501a646a2a83b102cb43881217ca682dc86e2d73fa882920001012000e1f5050000000017a9143545e6e33b832c47050f24d3eeb93c9c03948bc787010416001485d13537f2e265405a34dbafa9e3dda01fb82308000000"
+
+validVec3Hex :: Text
+validVec3Hex = "70736274ff0100750200000001268171371edff285e937adeea4b37b78000c0566cbb3ad64641713ca42171bf60000000000feffffff02d3dff505000000001976a914d0c59903c5bac2868760e90fd521a4665aa7652088ac00e1f5050000000017a9143545e6e33b832c47050f24d3eeb93c9c03948bc787b32e1300000100fda5010100000000010289a3c71eab4d20e0371bbba4cc698fa295c9463afa2e397f8533ccb62f9567e50100000017160014be18d152a9b012039daf3da7de4f53349eecb985ffffffff86f8aa43a71dff1448893a530a7237ef6b4608bbb2dd2d0171e63aec6a4890b40100000017160014fe3e9ef1a745e974d902c4355943abcb34bd5353ffffffff0200c2eb0b000000001976a91485cff1097fd9e008bb34af709c62197b38978a4888ac72fef84e2c00000017a914339725ba21efd62ac753a9bcd067d6c7a6a39d05870247304402202712be22e0270f394f568311dc7ca9a68970b8025fdd3b240229f07f8a5f3a240220018b38d7dcd314e734c9276bd6fb40f673325bc4baa144c800d2f2f02db2765c012103d2e15674941bad4a996372cb87e1856d3652606d98562fe39c5e9e7e413f210502483045022100d12b852d85dcd961d2f5f4ab660654df6eedcc794c0c33ce5cc309ffb5fce58d022067338a8e0e1725c197fb1a88af59f51e44e4255b20167c8684031c05d1f2592a01210223b72beef0965d10be0778efecd61fcac6f79a4ea169393380734464f84f2ab30000000001030401000000000000"
+
+validVec4Hex :: Text
+validVec4Hex = "70736274ff0100a00200000002ab0949a08c5af7c49b8212f417e2f15ab3f5c33dcf153821a8139f877a5b7be40000000000feffffffab0949a08c5af7c49b8212f417e2f15ab3f5c33dcf153821a8139f877a5b7be40100000000feffffff02603bea0b000000001976a914768a40bbd740cbe81d988e71de2a4d5c71396b1d88ac8e240000000000001976a9146f4620b553fa095e721b9ee0efe9fa039cca459788ac00000000000100df0200000001268171371edff285e937adeea4b37b78000c0566cbb3ad64641713ca42171bf6000000006a473044022070b2245123e6bf474d60c5b50c043d4c691a5d2435f09a34a7662a9dc251790a022001329ca9dacf280bdf30740ec0390422422c81cb45839457aeb76fc12edd95b3012102657d118d3357b8e0f4c2cd46db7b39f6d9c38d9a70abcb9b2de5dc8dbfe4ce31feffffff02d3dff505000000001976a914d0c59903c5bac2868760e90fd521a4665aa7652088ac00e1f5050000000017a9143545e6e33b832c47050f24d3eeb93c9c03948bc787b32e13000001012000e1f5050000000017a9143545e6e33b832c47050f24d3eeb93c9c03948bc787010416001485d13537f2e265405a34dbafa9e3dda01fb8230800220202ead596687ca806043edc3de116cdf29d5e9257c196cd055cf698c8d02bf24e9910b4a6ba670000008000000080020000800022020394f62be9df19952c5587768aeb7698061ad2c4a25c894f47d8c162b4d7213d0510b4a6ba6700000080010000800200008000"
+
+validVec5Hex :: Text
+validVec5Hex = "70736274ff0100550200000001279a2323a5dfb51fc45f220fa58b0fc13e1e3342792a85d7e36cd6333b5cbc390000000000ffffffff01a05aea0b000000001976a914ffe9c0061097cc3b636f2cb0460fa4fc427d2b4588ac0000000000010120955eea0b0000000017a9146345200f68d189e1adc0df1c4d16ea8f14c0dbeb87220203b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd4646304302200424b58effaaa694e1559ea5c93bbfd4a89064224055cdf070b6771469442d07021f5c8eb0fea6516d60b8acb33ad64ede60e8785bfb3aa94b99bdf86151db9a9a010104220020771fd18ad459666dd49f3d564e3dbc42f4c84774e360ada16816a8ed488d5681010547522103b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd462103de55d1e1dac805e3f8a58c1fbf9b94c02f3dbaafe127fefca4995f26f82083bd52ae220603b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd4610b4a6ba67000000800000008004000080220603de55d1e1dac805e3f8a58c1fbf9b94c02f3dbaafe127fefca4995f26f82083bd10b4a6ba670000008000000080050000800000"
+
+validVec6Hex :: Text
+validVec6Hex = "70736274ff01003f0200000001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000ffffffff010000000000000000036a010000000000000a0f0102030405060708090f0102030405060708090a0b0c0d0e0f0000"
+
+-- Example of a PSBT with a `finalWitnessScript`
+validVec7Hex :: Text
+validVec7Hex = "70736274ff0100520200000001815dd29e16fd2f567a040ce24f5337fb9cfd0c05bacd8890714a33edc7cbbc920000000000ffffffff0192f1052a01000000160014ef9ade26f63015d57f4ecdb268d1a9b8d6cd8872000000000001008402000000010000000000000000000000000000000000000000000000000000000000000000ffffffff03510101ffffffff0200f2052a010000001600145f4ffa19dbbe464561c50fc4d8d8ac0b41009dd20000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90000000001011f00f2052a010000001600145f4ffa19dbbe464561c50fc4d8d8ac0b41009dd201086b02473044022026a9f7afdb0128970bb3577e536ec3d3dc10c1e82650d11c9da1df9003b31d0202202258b11f962f12e0897c642cd6f38a0181db17197f3693a530c9431eb44dde7d0121033dc786e9628bb6c41c08fceb9b37458ad7a95e7e6b04e0bde45b6879398c3ac100220203a6affb58dda998a4ffdce652feb91038fdfc78c748ae687372e11292af8d312d101c4c5bfc00000080000000800100008000"
+
+data ComplexPSBT = ComplexPSBT
+  { complexSignedPsbts :: [PSBT],
+    complexCombinedPsbt :: PSBT,
+    complexCompletePsbt :: PSBT,
+    complexFinalTx :: Tx
+  }
+  deriving (Eq, Show)
+
+parseComplexJSON ctx = withObject "ComplexPSBT" $ \obj -> do
+  ComplexPSBT
+    <$> sequence
+      [ psbtField "miner_psbt" obj,
+        psbtField "p2pkh_psbt" obj,
+        psbtField "p2sh_ms_1_psbt" obj,
+        psbtField "p2sh_ms_2_psbt" obj,
+        psbtField "p2sh_pk_psbt" obj,
+        psbtField "p2sh_wsh_pk_psbt" obj,
+        psbtField "p2sh_wsh_ms_1_psbt" obj,
+        psbtField "p2sh_wsh_ms_2_psbt" obj,
+        psbtField "p2wpkh_psbt" obj,
+        psbtField "p2wsh_pk_psbt" obj,
+        psbtField "p2wsh_ms_1_psbt" obj,
+        psbtField "p2wsh_ms_2_psbt" obj
+      ]
+    <*> psbtField "combined_psbt" obj
+    <*> psbtField "complete_psbt" obj
+    <*> (obj .: "final_tx" >>= parseTx)
+  where
+    parseTx =
+      either fail pure
+        . ( decode
+              <=< maybe (Left "hex") Right
+                . decodeHex
+          )
+    parsePsbt =
+      either fail pure
+        . ( runGet (getPSBT ctx)
+              <=< first Text.unpack . decodeBase64Untyped
+          )
+        . encodeUtf8
+    psbtField fieldName obj =
+      obj .: fieldName >>= parsePsbt
diff --git a/test/Haskoin/Transaction/TaprootSpec.hs b/test/Haskoin/Transaction/TaprootSpec.hs
new file mode 100644
--- /dev/null
+++ b/test/Haskoin/Transaction/TaprootSpec.hs
@@ -0,0 +1,181 @@
+{-# LANGUAGE DuplicateRecordFields #-}
+{-# LANGUAGE ImportQualifiedPost #-}
+{-# LANGUAGE LambdaCase #-}
+{-# LANGUAGE NamedFieldPuns #-}
+{-# LANGUAGE OverloadedRecordDot #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Haskoin.Transaction.TaprootSpec (spec) where
+
+import Control.Applicative ((<|>))
+import Control.Monad (zipWithM, (<=<))
+import Data.Aeson
+import Data.Aeson.Types
+import Data.ByteArray qualified as BA
+import Data.ByteString (ByteString)
+import Data.ByteString qualified as BS
+import Data.Bytes.Get (runGetS)
+import Data.Bytes.Put (runPutS)
+import Data.Bytes.Serial (deserialize, serialize)
+import Data.Text (Text)
+import Data.Word (Word8)
+import Haskoin
+import Test.HUnit (assertBool, (@?=))
+import Test.Hspec (Spec, describe, it, runIO)
+
+spec :: Spec
+spec = prepareContext $ \ctx -> do
+  TestVector {testScriptPubKey} <-
+    runIO $
+      readTestFileParser (testVectorParseJSON ctx) "bip341.json"
+  describe "Taproot" $ do
+    it "should calculate the correct hashes" $
+      mapM_ testHashes testScriptPubKey
+    it "should build the correct output key" $
+      mapM_ (testOutputKey ctx) testScriptPubKey
+    it "should build the correct script output" $
+      mapM_ (testScriptOutput ctx) testScriptPubKey
+    it "should calculate the correct control blocks" $
+      mapM_ (testControlBlocks ctx) testScriptPubKey
+    it "should arrive at the correct address" $
+      mapM_ (testAddress ctx) testScriptPubKey
+
+testHashes :: TestScriptPubKey -> IO ()
+testHashes testData =
+  mapM_ checkMASTDetails $ ((.mast) . tspkGiven) testData
+  where
+    checkMASTDetails theMAST = do
+      -- Leaf hashes
+      (Just . getLeafHashes) theMAST @?= (spkiLeafHashes . tspkIntermediary) testData
+      -- Merkle root
+      (Just . BA.convert . mastCommitment) theMAST @?= (spkiMerkleRoot . tspkIntermediary) testData
+
+    getLeafHashes = \case
+      MASTBranch branchL branchR -> getLeafHashes branchL <> getLeafHashes branchR
+      leaf@MASTLeaf {} -> [BA.convert $ mastCommitment leaf]
+      MASTCommitment {} -> mempty -- The test vectors have complete trees
+
+testOutputKey :: Ctx -> TestScriptPubKey -> IO ()
+testOutputKey ctx testData = do
+  XOnlyPubKey (taprootOutputKey ctx theOutput) @?= theOutputKey
+  where
+    theOutput = tspkGiven testData
+    theOutputKey = XOnlyPubKey . spkiTweakedPubKey $ tspkIntermediary testData
+
+testScriptOutput :: Ctx -> TestScriptPubKey -> IO ()
+testScriptOutput ctx testData =
+  taprootScriptOutput ctx (tspkGiven testData) @?= (spkeScriptPubKey . tspkExpected) testData
+
+testControlBlocks :: Ctx -> TestScriptPubKey -> IO ()
+testControlBlocks ctx testData = do
+  mapM_ onExamples exampleControlBlocks
+  mapM_ checkVerification scriptPathSpends
+  where
+    theOutput = tspkGiven testData
+    theOutputKey = taprootOutputKey ctx theOutput
+    exampleControlBlocks = spkeControlBlocks $ tspkExpected testData
+    calculatedControlBlocks =
+      (!! 1) . encodeTaprootWitness ctx . ScriptPathSpend <$> scriptPathSpends
+    scriptPathSpends =
+      mkScriptPathSpend <$> maybe mempty getMerkleProofs theOutput.mast
+    mkScriptPathSpend (leafVersion, script, proof) =
+      ScriptPathData
+        { annex = Nothing,
+          stack = mempty,
+          script,
+          extIsOdd = odd $ keyParity ctx theOutputKey,
+          leafVersion,
+          internalKey = theOutput.internalKey,
+          control = BA.convert <$> proof
+        }
+    onExamples = zipWithM (@?=) calculatedControlBlocks
+    checkVerification = assertBool "Script verifies" . verifyScriptPathData ctx theOutputKey
+
+keyParity :: Ctx -> PubKey -> Word8
+keyParity ctx key =
+  case BS.unpack . marshal ctx $ PublicKey key True of
+    0x02 : _ -> 0x00
+    _ -> 0x01
+
+testAddress :: Ctx -> TestScriptPubKey -> IO ()
+testAddress ctx testData =
+  computedAddress @?= (Just . spkeAddress . tspkExpected) testData
+  where
+    computedAddress =
+      (addrToText btc <=< outputAddress ctx)
+        . taprootScriptOutput ctx
+        $ tspkGiven testData
+
+newtype SpkGiven = SpkGiven {unSpkGiven :: TaprootOutput}
+
+spkGivenParseJSON :: Ctx -> Value -> Parser SpkGiven
+spkGivenParseJSON ctx = withObject "SpkGiven" $ \obj -> do
+  pxopk@XOnlyPubKey {} <- unmarshalValue ctx =<< obj .: "internalPubkey"
+  tree <- traverse parseScriptTree =<< obj .:? "scriptTree"
+  return $ SpkGiven $ TaprootOutput pxopk.point tree
+  where
+    parseScriptTree v =
+      parseScriptLeaf v
+        <|> parseScriptBranch v
+        <|> fail "Unable to parse scriptTree"
+    parseScriptLeaf = withObject "ScriptTree leaf" $ \obj ->
+      MASTLeaf
+        <$> obj .: "leafVersion"
+        <*> (obj .: "script" >>= hexScript)
+    parseScriptBranch v =
+      parseJSON v >>= \case
+        [v1, v2] -> MASTBranch <$> parseScriptTree v1 <*> parseScriptTree v2
+        _ -> fail "ScriptTree branch"
+    hexScript = either fail pure . runGetS deserialize <=< jsonHex
+
+data SpkIntermediary = SpkIntermediary
+  { spkiLeafHashes :: Maybe [ByteString],
+    spkiMerkleRoot :: Maybe ByteString,
+    spkiTweakedPubKey :: PubKey
+  }
+
+spkIntermediaryParseJSON :: Ctx -> Value -> Parser SpkIntermediary
+spkIntermediaryParseJSON ctx = withObject "SpkIntermediary" $ \obj ->
+  SpkIntermediary
+    <$> (obj .:? "leafHashes" >>= (traverse . traverse) jsonHex)
+    <*> (obj .: "merkleRoot" >>= traverse jsonHex)
+    <*> fmap
+      (\(XOnlyPubKey k) -> k)
+      (unmarshalValue ctx =<< obj .: "tweakedPubkey")
+
+data SpkExpected = SpkExpected
+  { spkeScriptPubKey :: ScriptOutput,
+    spkeControlBlocks :: Maybe [ByteString],
+    spkeAddress :: Text
+  }
+
+spkExpectedParseJSON :: Ctx -> Value -> Parser SpkExpected
+spkExpectedParseJSON ctx = withObject "SpkExpected" $ \obj ->
+  SpkExpected
+    <$> (unmarshalValue ctx =<< obj .: "scriptPubKey")
+    <*> ((traverse . traverse) jsonHex =<< obj .:? "scriptPathControlBlocks")
+    <*> obj .: "bip350Address"
+
+data TestScriptPubKey = TestScriptPubKey
+  { tspkGiven :: TaprootOutput,
+    tspkIntermediary :: SpkIntermediary,
+    tspkExpected :: SpkExpected
+  }
+
+testScriptPubKeyParseJSON :: Ctx -> Value -> Parser TestScriptPubKey
+testScriptPubKeyParseJSON ctx = withObject "TestScriptPubKey" $ \obj -> do
+  given <- unSpkGiven <$> (spkGivenParseJSON ctx =<< obj .: "given")
+  inter <- spkIntermediaryParseJSON ctx =<< obj .: "intermediary"
+  expect <- spkExpectedParseJSON ctx =<< obj .: "expected"
+  return $ TestScriptPubKey given inter expect
+
+newtype TestVector = TestVector
+  { testScriptPubKey :: [TestScriptPubKey]
+  }
+
+testVectorParseJSON :: Ctx -> Value -> Parser TestVector
+testVectorParseJSON ctx = withObject "TestVector" $ \obj ->
+  TestVector <$> (mapM (testScriptPubKeyParseJSON ctx) =<< obj .: "scriptPubKey")
+
+jsonHex :: Text -> Parser ByteString
+jsonHex = maybe (fail "Unable to decode hex") pure . decodeHex
diff --git a/test/Haskoin/TransactionSpec.hs b/test/Haskoin/TransactionSpec.hs
new file mode 100644
--- /dev/null
+++ b/test/Haskoin/TransactionSpec.hs
@@ -0,0 +1,347 @@
+{-# LANGUAGE DuplicateRecordFields #-}
+{-# LANGUAGE FlexibleContexts #-}
+{-# LANGUAGE ImportQualifiedPost #-}
+{-# LANGUAGE OverloadedRecordDot #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE RecordWildCards #-}
+
+module Haskoin.TransactionSpec (spec) where
+
+import Control.Monad (unless)
+import Data.ByteString qualified as B
+import Data.Bytes.Get
+import Data.Bytes.Put
+import Data.Bytes.Serial
+import Data.Default (def)
+import Data.Either
+import Data.Maybe
+import Data.String (fromString)
+import Data.String.Conversions
+import Data.Text (Text)
+import Data.Word (Word32, Word64)
+import Haskoin.Address
+import Haskoin.Crypto
+import Haskoin.Network.Constants
+import Haskoin.Network.Data
+import Haskoin.Script
+import Haskoin.Transaction
+import Haskoin.Util
+import Haskoin.Util.Arbitrary
+import Test.HUnit
+import Test.Hspec
+import Test.Hspec.QuickCheck
+import Test.QuickCheck
+
+identityTests :: Ctx -> IdentityTests
+identityTests ctx =
+  def
+    { readTests =
+        [ ReadBox arbitraryTxHash,
+          ReadBox $ flip arbitraryTx ctx =<< arbitraryNetwork,
+          ReadBox $ flip arbitraryTxIn ctx =<< arbitraryNetwork,
+          ReadBox $ flip arbitraryTxOut ctx =<< arbitraryNetwork,
+          ReadBox arbitraryOutPoint
+        ],
+      jsonTests =
+        [ JsonBox arbitraryTxHash,
+          JsonBox $ flip arbitraryTx ctx =<< arbitraryNetwork,
+          JsonBox $ flip arbitraryWitnessTx ctx =<< arbitraryNetwork,
+          JsonBox $ flip arbitraryLegacyTx ctx =<< arbitraryNetwork,
+          JsonBox $ flip arbitraryTxIn ctx =<< arbitraryNetwork,
+          JsonBox $ flip arbitraryTxOut ctx =<< arbitraryNetwork,
+          JsonBox arbitraryOutPoint
+        ],
+      serialTests =
+        [ SerialBox $ flip arbitraryTx ctx =<< arbitraryNetwork,
+          SerialBox $ flip arbitraryWitnessTx ctx =<< arbitraryNetwork,
+          SerialBox $ flip arbitraryLegacyTx ctx =<< arbitraryNetwork,
+          SerialBox $ flip arbitraryTxIn ctx =<< arbitraryNetwork,
+          SerialBox $ flip arbitraryTxOut ctx =<< arbitraryNetwork,
+          SerialBox arbitraryOutPoint
+        ]
+    }
+
+spec :: Spec
+spec = prepareContext $ \ctx -> do
+  testIdentity $ identityTests ctx
+  describe "Transaction properties" $ do
+    prop "decode and encode txid" $
+      forAll arbitraryTxHash $
+        \h -> hexToTxHash (txHashToHex h) == Just h
+    prop "from string transaction id" $
+      forAll arbitraryTxHash $
+        \h -> fromString (cs $ txHashToHex h) == h
+    prop "building address tx" $
+      forAll arbitraryNetwork $ \net ->
+        forAll arbitraryAddress $
+          forAll (arbitrarySatoshi net) . testBuildAddrTx net ctx
+    prop "guess transaction size" $
+      forAll arbitraryNetwork $ \net ->
+        forAll (arbitraryAddrOnlyTxFull net ctx) (testGuessSize net ctx)
+    prop "choose coins" $
+      forAll arbitraryNetwork $ \net ->
+        forAll (listOf (arbitrarySatoshi net)) testChooseCoins
+    prop "choose multisig coins" $
+      forAll arbitraryNetwork $ \net ->
+        forAll arbitraryMSParam $
+          forAll (listOf (arbitrarySatoshi net)) . testChooseMSCoins
+    prop "sign and validate transaction" $
+      forAll arbitraryNetwork $ \net ->
+        forAll (arbitrarySigningData net ctx) (testDetSignTx net ctx)
+    prop "sign and validate (nested) transaction" $
+      forAll arbitraryNetwork $ \net ->
+        forAll (arbitrarySigningData net ctx) (testDetSignNestedTx net ctx)
+    prop "merge partially signed transactions" $
+      forAll arbitraryNetwork $ \net ->
+        property $ forAll (arbitraryPartialTxs net ctx) (testMergeTx net ctx)
+  describe "Transaction vectors" $ do
+    it "compute txid from tx" $ mapM_ testTxidVector txidVectors
+    it "build pkhash transaction (generated from bitcoind)" $
+      mapM_ (testPKHashVector ctx) pkHashVectors
+
+-- Txid Vectors
+
+testTxidVector :: (Text, Text) -> Assertion
+testTxidVector (tid, tx) =
+  assertEqual "txid" (Just tid) (txHashToHex . txHash <$> txM)
+  where
+    txM = eitherToMaybe . runGetS deserialize =<< decodeHex tx
+
+txidVectors :: [(Text, Text)]
+txidVectors =
+  [ ( "23b397edccd3740a74adb603c9756370fafcde9bcc4483eb271ecad09a94dd63",
+      "0100000001b14bdcbc3e01bdaad36cc08e81e69c82e1060bc14e518db2b49aa4\
+      \3ad90ba26000000000490047304402203f16c6f40162ab686621ef3000b04e75\
+      \418a0c0cb2d8aebeac894ae360ac1e780220ddc15ecdfc3507ac48e1681a33eb\
+      \60996631bf6bf5bc0a0682c4db743ce7ca2b01ffffffff0140420f0000000000\
+      \1976a914660d4ef3a743e3e696ad990364e555c271ad504b88ac00000000"
+    ),
+    ( "c99c49da4c38af669dea436d3e73780dfdb6c1ecf9958baa52960e8baee30e73",
+      "01000000010276b76b07f4935c70acf54fbf1f438a4c397a9fb7e633873c4dd3\
+      \bc062b6b40000000008c493046022100d23459d03ed7e9511a47d13292d3430a\
+      \04627de6235b6e51a40f9cd386f2abe3022100e7d25b080f0bb8d8d5f878bba7\
+      \d54ad2fda650ea8d158a33ee3cbd11768191fd004104b0e2c879e4daf7b9ab68\
+      \350228c159766676a14f5815084ba166432aab46198d4cca98fa3e9981d0a90b\
+      \2effc514b76279476550ba3663fdcaff94c38420e9d5000000000100093d0000\
+      \0000001976a9149a7b0f3b80c6baaeedce0a0842553800f832ba1f88ac000000\
+      \00"
+    ),
+    ( "f7fdd091fa6d8f5e7a8c2458f5c38faffff2d3f1406b6e4fe2c99dcc0d2d1cbb",
+      "01000000023d6cf972d4dff9c519eff407ea800361dd0a121de1da8b6f4138a2\
+      \f25de864b4000000008a4730440220ffda47bfc776bcd269da4832626ac332ad\
+      \fca6dd835e8ecd83cd1ebe7d709b0e022049cffa1cdc102a0b56e0e04913606c\
+      \70af702a1149dc3b305ab9439288fee090014104266abb36d66eb4218a6dd31f\
+      \09bb92cf3cfa803c7ea72c1fc80a50f919273e613f895b855fb7465ccbc8919a\
+      \d1bd4a306c783f22cd3227327694c4fa4c1c439affffffff21ebc9ba20594737\
+      \864352e95b727f1a565756f9d365083eb1a8596ec98c97b7010000008a473044\
+      \0220503ff10e9f1e0de731407a4a245531c9ff17676eda461f8ceeb8c06049fa\
+      \2c810220c008ac34694510298fa60b3f000df01caa244f165b727d4896eb84f8\
+      \1e46bcc4014104266abb36d66eb4218a6dd31f09bb92cf3cfa803c7ea72c1fc8\
+      \0a50f919273e613f895b855fb7465ccbc8919ad1bd4a306c783f22cd32273276\
+      \94c4fa4c1c439affffffff01f0da5200000000001976a914857ccd42dded6df3\
+      \2949d4646dfa10a92458cfaa88ac00000000"
+    ),
+    ( "afd9c17f8913577ec3509520bd6e5d63e9c0fd2a5f70c787993b097ba6ca9fae",
+      "010000000370ac0a1ae588aaf284c308d67ca92c69a39e2db81337e563bf40c5\
+      \9da0a5cf63000000006a4730440220360d20baff382059040ba9be98947fd678\
+      \fb08aab2bb0c172efa996fd8ece9b702201b4fb0de67f015c90e7ac8a193aeab\
+      \486a1f587e0f54d0fb9552ef7f5ce6caec032103579ca2e6d107522f012cd00b\
+      \52b9a65fb46f0c57b9b8b6e377c48f526a44741affffffff7d815b6447e35fbe\
+      \a097e00e028fb7dfbad4f3f0987b4734676c84f3fcd0e804010000006b483045\
+      \022100c714310be1e3a9ff1c5f7cacc65c2d8e781fc3a88ceb063c6153bf9506\
+      \50802102200b2d0979c76e12bb480da635f192cc8dc6f905380dd4ac1ff35a4f\
+      \68f462fffd032103579ca2e6d107522f012cd00b52b9a65fb46f0c57b9b8b6e3\
+      \77c48f526a44741affffffff3f1f097333e4d46d51f5e77b53264db8f7f5d2e1\
+      \8217e1099957d0f5af7713ee010000006c493046022100b663499ef73273a378\
+      \8dea342717c2640ac43c5a1cf862c9e09b206fcb3f6bb8022100b09972e75972\
+      \d9148f2bdd462e5cb69b57c1214b88fc55ca638676c07cfc10d8032103579ca2\
+      \e6d107522f012cd00b52b9a65fb46f0c57b9b8b6e377c48f526a44741affffff\
+      \ff0380841e00000000001976a914bfb282c70c4191f45b5a6665cad1682f2c9c\
+      \fdfb88ac80841e00000000001976a9149857cc07bed33a5cf12b9c5e0500b675\
+      \d500c81188ace0fd1c00000000001976a91443c52850606c872403c0601e69fa\
+      \34b26f62db4a88ac00000000"
+    )
+  ]
+
+-- Build address transactions vectors generated from bitcoin-core raw tx API
+
+testPKHashVector :: Ctx -> ([(Text, Word32)], [(Text, Word64)], Text) -> Assertion
+testPKHashVector ctx (is, os, res) =
+  assertEqual
+    "Build PKHash Tx"
+    (Right res)
+    (encodeHex . runPutS . serialize <$> txE)
+  where
+    txE = buildAddrTx btc ctx (map f is) os
+    f (tid, ix) = OutPoint (fromJust $ hexToTxHash tid) ix
+
+pkHashVectors :: [([(Text, Word32)], [(Text, Word64)], Text)]
+pkHashVectors =
+  [ ( [ ( "eb29eba154166f6541ebcc9cbdf5088756e026af051f123bcfb526df594549db",
+          14
+        )
+      ],
+      [("14LsRquZfURNFrzpcLVGdaHTfAPjjwiSPb", 90000000)],
+      "0100000001db494559df26b5cf3b121f05af26e0568708f5bd9ccceb41656f1654\
+      \a1eb29eb0e00000000ffffffff01804a5d05000000001976a91424aa604689cc58\
+      \2292b97668bedd91dd5bf9374c88ac00000000"
+    ),
+    ( [ ( "eb29eba154166f6541ebcc9cbdf5088756e026af051f123bcfb526df594549db",
+          0
+        ),
+        ( "0001000000000000000000000000000000000000000000000000000000000000",
+          2147483647
+        )
+      ],
+      [ ("14LsRquZfURNFrzpcLVGdaHTfAPjjwiSPb", 1),
+        ("19VCgS642vzEA1sdByoSn6GsWBwraV8D4n", 2100000000000000)
+      ],
+      "0100000002db494559df26b5cf3b121f05af26e0568708f5bd9ccceb41656f1654\
+      \a1eb29eb0000000000ffffffff0000000000000000000000000000000000000000\
+      \000000000000000000000100ffffff7f00ffffffff0201000000000000001976a9\
+      \1424aa604689cc582292b97668bedd91dd5bf9374c88ac0040075af07507001976\
+      \a9145d16672f53981ff21c5f42b40d1954993cbca54f88ac00000000"
+    ),
+    ( [ ( "eb29eba154166f6541ebcc9cbdf5088756e026af051f123bcfb526df594549db",
+          0
+        ),
+        ( "0001000000000000000000000000000000000000000000000000000000000000",
+          2147483647
+        )
+      ],
+      [],
+      "0100000002db494559df26b5cf3b121f05af26e0568708f5bd9ccceb41656f1654a\
+      \1eb29eb0000000000ffffffff000000000000000000000000000000000000000000\
+      \0000000000000000000100ffffff7f00ffffffff0000000000"
+    ),
+    ( [],
+      [ ("14LsRquZfURNFrzpcLVGdaHTfAPjjwiSPb", 1),
+        ("19VCgS642vzEA1sdByoSn6GsWBwraV8D4n", 2100000000000000)
+      ],
+      "01000000000201000000000000001976a91424aa604689cc582292b97668bedd91d\
+      \d5bf9374c88ac0040075af07507001976a9145d16672f53981ff21c5f42b40d1954\
+      \993cbca54f88ac00000000"
+    )
+  ]
+
+-- Transaction Properties --
+
+testBuildAddrTx :: Network -> Ctx -> Address -> TestCoin -> Bool
+testBuildAddrTx net ctx a (TestCoin v)
+  | isPubKeyAddress a = PayPKHash a.hash160 == out
+  | isScriptAddress a = PayScriptHash a.hash160 == out
+  | otherwise = undefined
+  where
+    out = either error id $ do
+      tx <- buildAddrTx net ctx [] [(fromJust (addrToText net a), v)]
+      unmarshal ctx (head tx.outputs).script
+
+-- We compute an upper bound but it should be close enough to the real size
+-- We give 2 bytes of slack on every signature (1 on r and 1 on s)
+testGuessSize :: Network -> Ctx -> Tx -> Bool
+testGuessSize net ctx tx =
+  guess >= len && guess <= len + 2 * delta
+  where
+    delta = pki + sum (map fst msi)
+    guess = guessTxSize pki msi pkout msout
+    len = B.length $ runPutS $ serialize tx
+    ins = map f tx.inputs
+    f i = either error id $ unmarshal (net, ctx) i.script
+    pki = length $ filter isSpendPKHash ins
+    msi = concatMap shData ins
+    shData (ScriptHashInput _ (PayMulSig keys r)) = [(r, length keys)]
+    shData _ = []
+    out = map (either error id . unmarshal ctx . (.script)) tx.outputs
+    pkout = length $ filter isPayPKHash out
+    msout = length $ filter isPayScriptHash out
+
+testChooseCoins :: [TestCoin] -> Word64 -> Word64 -> Int -> Property
+testChooseCoins coins target byteFee nOut =
+  nOut >= 0 ==>
+    case chooseCoins target byteFee nOut True coins of
+      Right (chosen, change) ->
+        let outSum = sum $ map coinValue chosen
+            fee = guessTxFee byteFee nOut (length chosen)
+         in outSum == target + change + fee
+      Left _ ->
+        let fee = guessTxFee byteFee nOut (length coins)
+         in target == 0 || s < target + fee
+  where
+    s = sum $ map coinValue coins
+
+testChooseMSCoins ::
+  (Int, Int) ->
+  [TestCoin] ->
+  Word64 ->
+  Word64 ->
+  Int ->
+  Property
+testChooseMSCoins (m, n) coins target byteFee nOut =
+  nOut >= 0 ==>
+    case chooseMSCoins target byteFee (m, n) nOut True coins of
+      Right (chosen, change) ->
+        let outSum = sum $ map coinValue chosen
+            fee = guessMSTxFee byteFee (m, n) nOut (length chosen)
+         in outSum == target + change + fee
+      Left _ ->
+        let fee = guessMSTxFee byteFee (m, n) nOut (length coins)
+         in target == 0 || s < target + fee
+  where
+    s = sum $ map coinValue coins
+
+{- Signing Transactions -}
+
+testDetSignTx :: Network -> Ctx -> (Tx, [SigInput], [PrivateKey]) -> Bool
+testDetSignTx net ctx (tx, sigis, prv) =
+  not verify1 && not verify2 && verify3
+  where
+    verify1 = verifyStdTx net ctx tx verData
+    verify2 = verifyStdTx net ctx txSigP verData
+    verify3 = verifyStdTx net ctx txSigC verData
+    txSigP = either error id $ signTx net ctx tx sigis (map (.key) (tail prv))
+    txSigC = either error id $ signTx net ctx txSigP sigis [(head prv).key]
+    sigData SigInput {..} = (script, value, outpoint)
+    verData = map sigData sigis
+
+testDetSignNestedTx :: Network -> Ctx -> (Tx, [SigInput], [PrivateKey]) -> Bool
+testDetSignNestedTx net ctx (tx, sigis, prv) =
+  not verify1 && not verify2 && verify3
+  where
+    verify1 = verifyStdTx net ctx tx verData
+    verify2 = verifyStdTx net ctx txSigP verData
+    verify3 = verifyStdTx net ctx txSigC verData
+    txSigP =
+      either error id $
+        signNestedWitnessTx net ctx tx sigis ((.key) <$> tail prv)
+    txSigC =
+      either error id $
+        signNestedWitnessTx net ctx txSigP sigis [(head prv).key]
+    verData = handleSegwit <$> sigis
+    handleSegwit (SigInput s v o _ _)
+      | isSegwit s = (toP2SH (encodeOutput ctx s), v, o)
+      | otherwise = (s, v, o)
+
+testMergeTx :: Network -> Ctx -> ([Tx], [(ScriptOutput, Word64, OutPoint, Int, Int)]) -> Bool
+testMergeTx net ctx (txs, os) =
+  and
+    [ isRight mergeRes,
+      length mergedTx.inputs == length os,
+      if enoughSigs
+        then isValid
+        else not isValid,
+      -- Signature count == min (length txs) (sum required signatures)
+      sum (map snd sigMap) == min (length txs) (sum (map fst sigMap))
+    ]
+  where
+    outs = map (\(so, val, op, _, _) -> (so, val, op)) os
+    mergeRes = mergeTxs net ctx txs outs
+    mergedTx = fromRight (error "Could not merge") mergeRes
+    isValid = verifyStdTx net ctx mergedTx outs
+    enoughSigs = all (\(m, c) -> c >= m) sigMap
+    sigFun (_, _, _, m, _) inp = (m, sigCnt inp)
+    sigMap = zipWith sigFun os mergedTx.inputs
+    sigCnt inp =
+      case unmarshal (net, ctx) inp.script of
+        Right (RegularInput (SpendMulSig sigs)) -> length sigs
+        Right (ScriptHashInput (SpendMulSig sigs) _) -> length sigs
+        _ -> error "Invalid input script type"
diff --git a/test/Haskoin/UtilSpec.hs b/test/Haskoin/UtilSpec.hs
new file mode 100644
--- /dev/null
+++ b/test/Haskoin/UtilSpec.hs
@@ -0,0 +1,78 @@
+{-# LANGUAGE ImportQualifiedPost #-}
+
+module Haskoin.UtilSpec (spec) where
+
+import Data.Aeson
+import Data.Aeson.Encoding (encodingToLazyByteString)
+import Data.Aeson.Types (Parser, parse)
+import Data.ByteString (ByteString)
+import Data.ByteString qualified as B
+import Data.Default (def)
+import Data.Either (fromLeft, fromRight, isLeft, isRight)
+import Data.Foldable (toList)
+import Data.List (permutations)
+import Data.Map.Strict (singleton)
+import Data.Maybe
+import Data.Sequence qualified as Seq
+import Data.Serialize as S
+import Haskoin.Crypto
+import Haskoin.Util
+import Haskoin.Util.Arbitrary
+import Test.Hspec
+import Test.Hspec.QuickCheck
+import Test.QuickCheck (forAll)
+
+identityTests :: IdentityTests
+identityTests =
+  def
+    { readTests =
+        [ ReadBox arbitraryNetwork
+        ]
+    }
+
+spec :: Spec
+spec =
+  describe "utility functions" $ do
+    testIdentity identityTests
+    prop "bsToInteger . integerToBS" getPutInteger
+    prop "decodeHex . encodeHex" $ forAll arbitraryBS fromToHex
+    prop "compare updateIndex with Data.Sequence" testUpdateIndex
+    prop "matchTemplate" testMatchTemplate
+    prop "testing matchTemplate with two lists" testMatchTemplateLen
+    prop "test eitherToMaybe" testEitherToMaybe
+    prop "test maybeToEither" testMaybeToEither
+
+{- Various utilities -}
+
+getPutInteger :: Integer -> Bool
+getPutInteger i = bsToInteger (integerToBS $ abs i) == abs i
+
+fromToHex :: ByteString -> Bool
+fromToHex bs = decodeHex (encodeHex bs) == Just bs
+
+testUpdateIndex :: [Int] -> Int -> Int -> Bool
+testUpdateIndex xs v i =
+  updateIndex i xs (const v) == toList (Seq.update i v $ Seq.fromList xs)
+
+testMatchTemplate :: [Int] -> Int -> Bool
+testMatchTemplate as i = catMaybes res == bs
+  where
+    res = matchTemplate as bs (==)
+    idx =
+      if null as
+        then 0
+        else i `mod` length as
+    bs = permutations as !! idx
+
+testMatchTemplateLen :: [Int] -> [Int] -> Bool
+testMatchTemplateLen as bs = length bs == length res
+  where
+    res = matchTemplate as bs (==)
+
+testEitherToMaybe :: Either String Int -> Bool
+testEitherToMaybe (Right v) = eitherToMaybe (Right v) == Just v
+testEitherToMaybe e = isNothing (eitherToMaybe e)
+
+testMaybeToEither :: Maybe Int -> String -> Bool
+testMaybeToEither (Just v) str = maybeToEither str (Just v) == Right v
+testMaybeToEither m str = maybeToEither str m == Left str
diff --git a/test/Network/Haskoin/Address/Bech32Spec.hs b/test/Network/Haskoin/Address/Bech32Spec.hs
deleted file mode 100644
--- a/test/Network/Haskoin/Address/Bech32Spec.hs
+++ /dev/null
@@ -1,155 +0,0 @@
-{-# LANGUAGE OverloadedStrings #-}
-module Network.Haskoin.Address.Bech32Spec
-    ( spec
-    ) where
-
-import           Control.Monad
-import           Data.Bits                      (xor)
-import           Data.ByteString                (ByteString)
-import qualified Data.ByteString                as B
-import           Data.Char                      (chr, ord, toLower)
-import           Data.Maybe
-import           Data.String.Conversions
-import           Data.Text                      (Text, append, pack, snoc,
-                                                 uncons)
-import qualified Data.Text                      as T
-import           Data.Word                      (Word8)
-import           Network.Haskoin.Address
-import           Network.Haskoin.Address.Bech32
-import           Network.Haskoin.Util
-import           Test.Hspec
-import           Test.HUnit
-
-spec = do
-    describe "bech32 checksum" $ do
-        it "should have valid checksum" $ forM_ validChecksums testValidChecksum
-        it "should have invalid checksum" $
-            forM_ invalidChecksums testInvalidChecksum
-        it "should be a valid address" $ forM_ validAddresses testValidAddress
-        it "should be an invalid address" $
-            forM_ invalidAddresses testInvalidAddress
-        it "should be same for the input in different case" $
-            all (== Just "test12hrzfj") . map (flip bech32Encode []) $ hrpCaseVariants
-    describe "more encoding/decoding cases" $ do
-        it "length > 90" $
-            assert $
-            isNothing $
-            bech32Encode "bc" (replicate 82 (word5 (1 :: Word8)))
-        it "segwit version bounds" $
-            assert $ isNothing $ segwitEncode "bc" 17 []
-        it "segwit prog len version 0" $
-            assert $ isNothing $ segwitEncode "bc" 0 (replicate 30 1)
-        it "segwit prog len version != 0" $
-            assert $ isJust $ segwitEncode "bc" 1 (replicate 30 1)
-        it "segwit prog len version != 0" $
-            assert $ isNothing $ segwitEncode "bc" 1 (replicate 41 1)
-        it "empty HRP encode" $ assert $ isNothing $ bech32Encode "" []
-        it "empty HRP encode" $
-            assert $ isNothing $ bech32Decode "10a06t8"
-        it "hrp lowercased" $
-            bech32Encode "HRP" [] `shouldBe` bech32Encode "hrp" []
-
-
-testValidChecksum :: Bech32 -> Assertion
-testValidChecksum checksum = case bech32Decode checksum of
-    Nothing                      -> assertFailure (show checksum)
-    Just (resultHRP, resultData) -> do
-        -- test that a corrupted checksum fails decoding.
-        let (hrp, rest)         = T.breakOnEnd "1" checksum
-            Just (first, rest') = uncons rest
-            checksumCorrupted   = (hrp `snoc` chr (ord first `xor` 1)) `append` rest'
-        assertBool (show checksum ++ " corrupted")
-            $ isNothing (bech32Decode checksumCorrupted)
-        -- test that re-encoding the decoded checksum results in the same checksum.
-        let checksumEncoded  = bech32Encode resultHRP resultData
-            expectedChecksum = Just $ T.toLower checksum
-        assertEqual (show checksum ++ " re-encode")
-                    expectedChecksum
-                    checksumEncoded
-
-testInvalidChecksum :: Bech32 -> Assertion
-testInvalidChecksum checksum =
-    assertBool (show checksum) (isNothing $ bech32Decode checksum)
-
-testValidAddress :: (Text, Text) -> Assertion
-testValidAddress (address, hexscript) = do
-    let address' = T.toLower address
-        hrp      = T.take 2 address'
-    case segwitDecode hrp address of
-        Nothing                -> assertFailure "decode failed"
-        Just (witver, witprog) -> do
-            assertEqual (show address)
-                        (decodeHex hexscript)
-                        (Just $ segwitScriptPubkey witver witprog)
-            assertEqual (show address)
-                        (Just address')
-                        (segwitEncode hrp witver witprog)
-
-testInvalidAddress :: Text -> Assertion
-testInvalidAddress address = do
-    assertBool (show address) (isNothing $ segwitDecode "bc" address)
-    assertBool (show address) (isNothing $ segwitDecode "tb" address)
-
-segwitScriptPubkey :: Word8 -> [Word8] -> ByteString
-segwitScriptPubkey witver witprog =
-    B.pack $ witver' : fromIntegral (length witprog) : witprog
-    where witver' = if witver == 0 then 0 else witver + 0x50
-
-validChecksums :: [Text]
-validChecksums =
-    [ "A12UEL5L"
-    , "an83characterlonghumanreadablepartthatcontainsthenumber1andtheexcludedcharactersbio1tt5tgs"
-    , "abcdef1qpzry9x8gf2tvdw0s3jn54khce6mua7lmqqqxw"
-    , "11qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqc8247j"
-    , "split1checkupstagehandshakeupstreamerranterredcaperred2y9e3w"
-    ]
-
-invalidChecksums :: [Text]
-invalidChecksums =
-    [ " 1nwldj5"
-    , "\DEL1axkwrx"
-    , "an84characterslonghumanreadablepartthatcontainsthenumber1andtheexcludedcharactersbio1569pvx"
-    , "pzry9x0s0muk"
-    , "1pzry9x0s0muk"
-    , "x1b4n0q5v"
-    , "li1dgmt3"
-    , "de1lg7wt\xFF"
-    ]
-
-validAddresses :: [(Text, Text)]
-validAddresses =
-    [ ("BC1QW508D6QEJXTDG4Y5R3ZARVARY0C5XW7KV8F3T4", "0014751e76e8199196d454941c45d1b3a323f1433bd6")
-    , ("tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sl5k7"
-      ,"00201863143c14c5166804bd19203356da136c985678cd4d27a1b8c6329604903262")
-    , ("bc1pw508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7k7grplx"
-      ,"5128751e76e8199196d454941c45d1b3a323f1433bd6751e76e8199196d454941c45d1b3a323f1433bd6")
-    , ("BC1SW50QA3JX3S", "6002751e")
-    , ("bc1zw508d6qejxtdg4y5r3zarvaryvg6kdaj", "5210751e76e8199196d454941c45d1b3a323")
-    , ("tb1qqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvsesrxh6hy"
-      ,"0020000000c4a5cad46221b2a187905e5266362b99d5e91c6ce24d165dab93e86433")
-    ]
-
-invalidAddresses :: [Text]
-invalidAddresses =
-    [ "tc1qw508d6qejxtdg4y5r3zarvary0c5xw7kg3g4ty"
-    , "bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t5"
-    , "BC13W508D6QEJXTDG4Y5R3ZARVARY0C5XW7KN40WF2"
-    , "bc1rw5uspcuh"
-    , "bc10w508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7kw5rljs90"
-    , "BC1QR508D6QEJXTDG4Y5R3ZARVARYV98GJ9P"
-    , "tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sL5k7"
-    , "bc1zw508d6qejxtdg4y5r3zarvaryvqyzf3du"
-    , "tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3pjxtptv"
-    , "bc1gmk9yu"
-    ]
-
-hrpCaseVariants:: [Text]
-hrpCaseVariants = map T.pack hrpTestPermutations
-
-hrpTestPermutations :: [String]
-hrpTestPermutations = do
-    a <- ['t', 'T']
-    b <- ['e', 'E']
-    c <- ['s', 'S']
-    d <- ['t', 'T']
-    return [a, b, c, d]
diff --git a/test/Network/Haskoin/Address/CashAddrSpec.hs b/test/Network/Haskoin/Address/CashAddrSpec.hs
deleted file mode 100644
--- a/test/Network/Haskoin/Address/CashAddrSpec.hs
+++ /dev/null
@@ -1,280 +0,0 @@
-{-# LANGUAGE OverloadedStrings #-}
-module Network.Haskoin.Address.CashAddrSpec (spec) where
-
-import           Control.Monad
-import qualified Data.ByteString.Char8            as C
-import           Data.Maybe
-import           Data.String.Conversions
-import           Data.Text                        (Text)
-import           Network.Haskoin.Address
-import           Network.Haskoin.Address.CashAddr
-import           Network.Haskoin.Constants
-import           Network.Haskoin.Util
-import           Test.Hspec
-import           Test.HUnit
-
-spec :: Spec
-spec = do
-    describe "cashaddr checksum test vectors" $ do
-        it "prefix:x64nx6hz" $ do
-            let mpb = cash32decode "prefix:x64nx6hz"
-            mpb `shouldBe` Just ("prefix", "")
-        it "p:gpf8m4h7" $ do
-            let mpb = cash32decode "p:gpf8m4h7"
-            mpb `shouldBe` Just ("p", "")
-        it "bitcoincash:qpzry9x8gf2tvdw0s3jn54khce6mua7lcw20ayyn" $ do
-            let mpb =
-                    cash32decode
-                        "bitcoincash:qpzry9x8gf2tvdw0s3jn54khce6mua7lcw20ayyn"
-            mpb `shouldBe`
-                Just
-                    ( "bitcoincash"
-                    , "\NULD2\DC4\199BT\182\&5\207\132e:V\215\198u\190w\223")
-        it "bchtest:testnetaddress4d6njnut" $ do
-            let mpb = cash32decode "bchtest:testnetaddress4d6njnut"
-            mpb `shouldBe` Just ("bchtest", "^`\185\229}kG\152")
-        it "bchreg:555555555555555555555555555555555555555555555udxmlmrz" $ do
-            let mpb =
-                    cash32decode
-                        "bchreg:555555555555555555555555555555555555555555555udxmlmrz"
-            mpb `shouldBe`
-                Just
-                    ( "bchreg"
-                    , "\165)JR\148\165)JR\148\165)JR\148\165)JR\148\165)JR\148\165)J")
-    describe "cashaddr to base58 translation test vectors" $ do
-        it "1BpEi6DfDAUFd7GtittLSdBeYJvcoaVggu" $ do
-            let addr =
-                    addrToString bch =<<
-                    stringToAddr btc "1BpEi6DfDAUFd7GtittLSdBeYJvcoaVggu"
-            addr `shouldBe`
-                Just "bitcoincash:qpm2qsznhks23z7629mms6s4cwef74vcwvy22gdx6a"
-        it "1KXrWXciRDZUpQwQmuM1DbwsKDLYAYsVLR" $ do
-            let addr =
-                    addrToString bch =<<
-                    stringToAddr btc "1KXrWXciRDZUpQwQmuM1DbwsKDLYAYsVLR"
-            addr `shouldBe`
-                Just "bitcoincash:qr95sy3j9xwd2ap32xkykttr4cvcu7as4y0qverfuy"
-        it "16w1D5WRVKJuZUsSRzdLp9w3YGcgoxDXb" $ do
-            let addr =
-                    addrToString bch =<<
-                    stringToAddr btc "16w1D5WRVKJuZUsSRzdLp9w3YGcgoxDXb"
-            addr `shouldBe`
-                Just "bitcoincash:qqq3728yw0y47sqn6l2na30mcw6zm78dzqre909m2r"
-        it "3CWFddi6m4ndiGyKqzYvsFYagqDLPVMTzC" $ do
-            let addr =
-                    addrToString bch =<<
-                    stringToAddr btc "3CWFddi6m4ndiGyKqzYvsFYagqDLPVMTzC"
-            addr `shouldBe`
-                Just "bitcoincash:ppm2qsznhks23z7629mms6s4cwef74vcwvn0h829pq"
-        it "3LDsS579y7sruadqu11beEJoTjdFiFCdX4" $ do
-            let addr =
-                    addrToString bch =<<
-                    stringToAddr btc "3LDsS579y7sruadqu11beEJoTjdFiFCdX4"
-            addr `shouldBe`
-                Just "bitcoincash:pr95sy3j9xwd2ap32xkykttr4cvcu7as4yc93ky28e"
-        it "31nwvkZwyPdgzjBJZXfDmSWsC4ZLKpYyUw" $ do
-            let addr =
-                    addrToString bch =<<
-                    stringToAddr btc "31nwvkZwyPdgzjBJZXfDmSWsC4ZLKpYyUw"
-            addr `shouldBe`
-                Just "bitcoincash:pqq3728yw0y47sqn6l2na30mcw6zm78dzq5ucqzc37"
-    describe "base58 to cashaddr translation test vectors" $ do
-        it "bitcoincash:qpm2qsznhks23z7629mms6s4cwef74vcwvy22gdx6a" $ do
-            let addr =
-                    addrToString btc =<<
-                    stringToAddr
-                        bch
-                        "bitcoincash:qpm2qsznhks23z7629mms6s4cwef74vcwvy22gdx6a"
-            addr `shouldBe` Just "1BpEi6DfDAUFd7GtittLSdBeYJvcoaVggu"
-        it "bitcoincash:qr95sy3j9xwd2ap32xkykttr4cvcu7as4y0qverfuy" $ do
-            let addr =
-                    addrToString btc =<<
-                    stringToAddr
-                        bch
-                        "bitcoincash:qr95sy3j9xwd2ap32xkykttr4cvcu7as4y0qverfuy"
-            addr `shouldBe` Just "1KXrWXciRDZUpQwQmuM1DbwsKDLYAYsVLR"
-        it "bitcoincash:qqq3728yw0y47sqn6l2na30mcw6zm78dzqre909m2r" $ do
-            let addr =
-                    addrToString btc =<<
-                    stringToAddr
-                        bch
-                        "bitcoincash:qqq3728yw0y47sqn6l2na30mcw6zm78dzqre909m2r"
-            addr `shouldBe` Just "16w1D5WRVKJuZUsSRzdLp9w3YGcgoxDXb"
-        it "bitcoincash:ppm2qsznhks23z7629mms6s4cwef74vcwvn0h829pq" $ do
-            let addr =
-                    addrToString btc =<<
-                    stringToAddr
-                        bch
-                        "bitcoincash:ppm2qsznhks23z7629mms6s4cwef74vcwvn0h829pq"
-            addr `shouldBe` Just "3CWFddi6m4ndiGyKqzYvsFYagqDLPVMTzC"
-        it "bitcoincash:pr95sy3j9xwd2ap32xkykttr4cvcu7as4yc93ky28e" $ do
-            let addr =
-                    addrToString btc =<<
-                    stringToAddr
-                        bch
-                        "bitcoincash:pr95sy3j9xwd2ap32xkykttr4cvcu7as4yc93ky28e"
-            addr `shouldBe` Just "3LDsS579y7sruadqu11beEJoTjdFiFCdX4"
-        it "bitcoincash:pqq3728yw0y47sqn6l2na30mcw6zm78dzq5ucqzc37" $ do
-            let addr =
-                    addrToString btc =<<
-                    stringToAddr
-                        bch
-                        "bitcoincash:pqq3728yw0y47sqn6l2na30mcw6zm78dzq5ucqzc37"
-            addr `shouldBe` Just "31nwvkZwyPdgzjBJZXfDmSWsC4ZLKpYyUw"
-    describe "cashaddr larger test vectors" $
-        forM_ (zip [0 ..] vectors) $ \(i, vec) ->
-            it ("cashaddr test vector " <> show (i :: Int)) $ testCashAddr vec
-
-{- Various utilities -}
-
-testCashAddr :: (Int, CashVersion, Cash32, Text) -> Assertion
-testCashAddr (len, typ, addr, hex) = do
-    let mbs = decodeHex hex
-    assertBool "Could not decode hex payload from test vector" (isJust mbs)
-    let mlow = cash32decode addr
-    assertBool "Could not decode low level address" (isJust mlow)
-    let Just (_, lbs) = mlow
-    assertEqual "Low-level payload size incorrect" len (C.length lbs - 1)
-    assertEqual "Low-level payload doesn't match" bs (C.tail lbs)
-    let mdec = cash32decodeType addr
-    assertBool ("Could not decode test address: " <> cs addr) (isJust mdec)
-    assertEqual "Length doesn't match" len (C.length pay)
-    assertEqual "Version doesn't match" typ ver
-    assertEqual "Payload doesn't match" bs pay
-  where
-    Just bs = decodeHex hex
-    Just (_, ver, pay) = cash32decodeType addr
-
--- | All vectors starting with @pref@ had the wrong version in the spec
--- document.
-vectors :: [(Int, CashVersion, Text, Text)]
-vectors =
-    [ ( 20
-      , 0
-      , "bitcoincash:qr6m7j9njldwwzlg9v7v53unlr4jkmx6eylep8ekg2"
-      , "F5BF48B397DAE70BE82B3CCA4793F8EB2B6CDAC9")
-    , ( 20
-      , 1
-      , "bchtest:pr6m7j9njldwwzlg9v7v53unlr4jkmx6eyvwc0uz5t"
-      , "F5BF48B397DAE70BE82B3CCA4793F8EB2B6CDAC9")
-    , ( 20
-      , 1
-      , "pref:pr6m7j9njldwwzlg9v7v53unlr4jkmx6ey65nvtks5"
-      , "F5BF48B397DAE70BE82B3CCA4793F8EB2B6CDAC9")
-    , ( 20
-      , 15
-      , "prefix:0r6m7j9njldwwzlg9v7v53unlr4jkmx6ey3qnjwsrf"
-      , "F5BF48B397DAE70BE82B3CCA4793F8EB2B6CDAC9")
-    , ( 24
-      , 0
-      , "bitcoincash:q9adhakpwzztepkpwp5z0dq62m6u5v5xtyj7j3h2ws4mr9g0"
-      , "7ADBF6C17084BC86C1706827B41A56F5CA32865925E946EA")
-    , ( 24
-      , 1
-      , "bchtest:p9adhakpwzztepkpwp5z0dq62m6u5v5xtyj7j3h2u94tsynr"
-      , "7ADBF6C17084BC86C1706827B41A56F5CA32865925E946EA")
-    , ( 24
-      , 1
-      , "pref:p9adhakpwzztepkpwp5z0dq62m6u5v5xtyj7j3h2khlwwk5v"
-      , "7ADBF6C17084BC86C1706827B41A56F5CA32865925E946EA")
-    , ( 24
-      , 15
-      , "prefix:09adhakpwzztepkpwp5z0dq62m6u5v5xtyj7j3h2p29kc2lp"
-      , "7ADBF6C17084BC86C1706827B41A56F5CA32865925E946EA")
-    , ( 28
-      , 0
-      , "bitcoincash:qgagf7w02x4wnz3mkwnchut2vxphjzccwxgjvvjmlsxqwkcw59jxxuz"
-      , "3A84F9CF51AAE98A3BB3A78BF16A6183790B18719126325BFC0C075B")
-    , ( 28
-      , 1
-      , "bchtest:pgagf7w02x4wnz3mkwnchut2vxphjzccwxgjvvjmlsxqwkcvs7md7wt"
-      , "3A84F9CF51AAE98A3BB3A78BF16A6183790B18719126325BFC0C075B")
-    , ( 28
-      , 1
-      , "pref:pgagf7w02x4wnz3mkwnchut2vxphjzccwxgjvvjmlsxqwkcrsr6gzkn"
-      , "3A84F9CF51AAE98A3BB3A78BF16A6183790B18719126325BFC0C075B")
-    , ( 28
-      , 15
-      , "prefix:0gagf7w02x4wnz3mkwnchut2vxphjzccwxgjvvjmlsxqwkc5djw8s9g"
-      , "3A84F9CF51AAE98A3BB3A78BF16A6183790B18719126325BFC0C075B")
-    , ( 32
-      , 0
-      , "bitcoincash:qvch8mmxy0rtfrlarg7ucrxxfzds5pamg73h7370aa87d80gyhqxq5nlegake"
-      , "3173EF6623C6B48FFD1A3DCC0CC6489B0A07BB47A37F47CFEF4FE69DE825C060")
-    , ( 32
-      , 1
-      , "bchtest:pvch8mmxy0rtfrlarg7ucrxxfzds5pamg73h7370aa87d80gyhqxq7fqng6m6"
-      , "3173EF6623C6B48FFD1A3DCC0CC6489B0A07BB47A37F47CFEF4FE69DE825C060")
-    , ( 32
-      , 1
-      , "pref:pvch8mmxy0rtfrlarg7ucrxxfzds5pamg73h7370aa87d80gyhqxq4k9m7qf9"
-      , "3173EF6623C6B48FFD1A3DCC0CC6489B0A07BB47A37F47CFEF4FE69DE825C060")
-    , ( 32
-      , 15
-      , "prefix:0vch8mmxy0rtfrlarg7ucrxxfzds5pamg73h7370aa87d80gyhqxqsh6jgp6w"
-      , "3173EF6623C6B48FFD1A3DCC0CC6489B0A07BB47A37F47CFEF4FE69DE825C060")
-    , ( 40
-      , 0
-      , "bitcoincash:qnq8zwpj8cq05n7pytfmskuk9r4gzzel8qtsvwz79zdskftrzxtar994cgutavfklv39gr3uvz"
-      , "C07138323E00FA4FC122D3B85B9628EA810B3F381706385E289B0B25631197D194B5C238BEB136FB")
-    , ( 40
-      , 1
-      , "bchtest:pnq8zwpj8cq05n7pytfmskuk9r4gzzel8qtsvwz79zdskftrzxtar994cgutavfklvmgm6ynej"
-      , "C07138323E00FA4FC122D3B85B9628EA810B3F381706385E289B0B25631197D194B5C238BEB136FB")
-    , ( 40
-      , 1
-      , "pref:pnq8zwpj8cq05n7pytfmskuk9r4gzzel8qtsvwz79zdskftrzxtar994cgutavfklv0vx5z0w3"
-      , "C07138323E00FA4FC122D3B85B9628EA810B3F381706385E289B0B25631197D194B5C238BEB136FB")
-    , ( 40
-      , 15
-      , "prefix:0nq8zwpj8cq05n7pytfmskuk9r4gzzel8qtsvwz79zdskftrzxtar994cgutavfklvwsvctzqy"
-      , "C07138323E00FA4FC122D3B85B9628EA810B3F381706385E289B0B25631197D194B5C238BEB136FB")
-    , ( 48
-      , 0
-      , "bitcoincash:qh3krj5607v3qlqh5c3wq3lrw3wnuxw0sp8dv0zugrrt5a3kj6ucysfz8kxwv2k53krr7n933jfsunqex2w82sl"
-      , "E361CA9A7F99107C17A622E047E3745D3E19CF804ED63C5C40C6BA763696B98241223D8CE62AD48D863F4CB18C930E4C")
-    , ( 48
-      , 1
-      , "bchtest:ph3krj5607v3qlqh5c3wq3lrw3wnuxw0sp8dv0zugrrt5a3kj6ucysfz8kxwv2k53krr7n933jfsunqnzf7mt6x"
-      , "E361CA9A7F99107C17A622E047E3745D3E19CF804ED63C5C40C6BA763696B98241223D8CE62AD48D863F4CB18C930E4C")
-    , ( 48
-      , 1
-      , "pref:ph3krj5607v3qlqh5c3wq3lrw3wnuxw0sp8dv0zugrrt5a3kj6ucysfz8kxwv2k53krr7n933jfsunqjntdfcwg"
-      , "E361CA9A7F99107C17A622E047E3745D3E19CF804ED63C5C40C6BA763696B98241223D8CE62AD48D863F4CB18C930E4C")
-    , ( 48
-      , 15
-      , "prefix:0h3krj5607v3qlqh5c3wq3lrw3wnuxw0sp8dv0zugrrt5a3kj6ucysfz8kxwv2k53krr7n933jfsunqakcssnmn"
-      , "E361CA9A7F99107C17A622E047E3745D3E19CF804ED63C5C40C6BA763696B98241223D8CE62AD48D863F4CB18C930E4C")
-    , ( 56
-      , 0
-      , "bitcoincash:qmvl5lzvdm6km38lgga64ek5jhdl7e3aqd9895wu04fvhlnare5937w4ywkq57juxsrhvw8ym5d8qx7sz7zz0zvcypqscw8jd03f"
-      , "D9FA7C4C6EF56DC4FF423BAAE6D495DBFF663D034A72D1DC7D52CBFE7D1E6858F9D523AC0A7A5C34077638E4DD1A701BD017842789982041")
-    , ( 56
-      , 1
-      , "bchtest:pmvl5lzvdm6km38lgga64ek5jhdl7e3aqd9895wu04fvhlnare5937w4ywkq57juxsrhvw8ym5d8qx7sz7zz0zvcypqs6kgdsg2g"
-      , "D9FA7C4C6EF56DC4FF423BAAE6D495DBFF663D034A72D1DC7D52CBFE7D1E6858F9D523AC0A7A5C34077638E4DD1A701BD017842789982041")
-    , ( 56
-      , 1
-      , "pref:pmvl5lzvdm6km38lgga64ek5jhdl7e3aqd9895wu04fvhlnare5937w4ywkq57juxsrhvw8ym5d8qx7sz7zz0zvcypqsammyqffl"
-      , "D9FA7C4C6EF56DC4FF423BAAE6D495DBFF663D034A72D1DC7D52CBFE7D1E6858F9D523AC0A7A5C34077638E4DD1A701BD017842789982041")
-    , ( 56
-      , 15
-      , "prefix:0mvl5lzvdm6km38lgga64ek5jhdl7e3aqd9895wu04fvhlnare5937w4ywkq57juxsrhvw8ym5d8qx7sz7zz0zvcypqsgjrqpnw8"
-      , "D9FA7C4C6EF56DC4FF423BAAE6D495DBFF663D034A72D1DC7D52CBFE7D1E6858F9D523AC0A7A5C34077638E4DD1A701BD017842789982041")
-    , ( 64
-      , 0
-      , "bitcoincash:qlg0x333p4238k0qrc5ej7rzfw5g8e4a4r6vvzyrcy8j3s5k0en7calvclhw46hudk5flttj6ydvjc0pv3nchp52amk97tqa5zygg96mtky5sv5w"
-      , "D0F346310D5513D9E01E299978624BA883E6BDA8F4C60883C10F28C2967E67EC77ECC7EEEAEAFC6DA89FAD72D11AC961E164678B868AEEEC5F2C1DA08884175B")
-    , ( 64
-      , 1
-      , "bchtest:plg0x333p4238k0qrc5ej7rzfw5g8e4a4r6vvzyrcy8j3s5k0en7calvclhw46hudk5flttj6ydvjc0pv3nchp52amk97tqa5zygg96mc773cwez"
-      , "D0F346310D5513D9E01E299978624BA883E6BDA8F4C60883C10F28C2967E67EC77ECC7EEEAEAFC6DA89FAD72D11AC961E164678B868AEEEC5F2C1DA08884175B")
-    , ( 64
-      , 1
-      , "pref:plg0x333p4238k0qrc5ej7rzfw5g8e4a4r6vvzyrcy8j3s5k0en7calvclhw46hudk5flttj6ydvjc0pv3nchp52amk97tqa5zygg96mg7pj3lh8"
-      , "D0F346310D5513D9E01E299978624BA883E6BDA8F4C60883C10F28C2967E67EC77ECC7EEEAEAFC6DA89FAD72D11AC961E164678B868AEEEC5F2C1DA08884175B")
-    , ( 64
-      , 15
-      , "prefix:0lg0x333p4238k0qrc5ej7rzfw5g8e4a4r6vvzyrcy8j3s5k0en7calvclhw46hudk5flttj6ydvjc0pv3nchp52amk97tqa5zygg96ms92w6845"
-      , "D0F346310D5513D9E01E299978624BA883E6BDA8F4C60883C10F28C2967E67EC77ECC7EEEAEAFC6DA89FAD72D11AC961E164678B868AEEEC5F2C1DA08884175B")
-    ]
diff --git a/test/Network/Haskoin/AddressSpec.hs b/test/Network/Haskoin/AddressSpec.hs
deleted file mode 100644
--- a/test/Network/Haskoin/AddressSpec.hs
+++ /dev/null
@@ -1,91 +0,0 @@
-{-# LANGUAGE OverloadedStrings #-}
-module Network.Haskoin.AddressSpec (spec) where
-
-import           Data.Aeson
-import           Data.Aeson.Types
-import           Data.ByteString                (ByteString)
-import qualified Data.ByteString                as BS (append, empty, pack)
-import           Data.Either                    (isRight)
-import           Data.Maybe                     (isJust, fromJust)
-import qualified Data.Serialize                 as S
-import           Data.Text                      (Text)
-import           Network.Haskoin.Address
-import           Network.Haskoin.Address.Base58
-import           Network.Haskoin.Constants
-import           Network.Haskoin.Keys           (derivePubKeyI)
-import           Network.Haskoin.Test
-import           Test.Hspec
-import           Test.HUnit                     (Assertion, assertBool,
-                                                 assertEqual)
-import           Test.QuickCheck
-
-spec :: Spec
-spec = do
-    let net = btc
-    describe "btc address" . it "base58 encodings" $ mapM_ runVector vectors
-    describe "btc-test address" $ props btcTest
-    describe "bch address" $ props bch
-    describe "bch-test address" $ props bchTest
-    describe "bch-regtest address" $ props bchRegTest
-    describe "json serialization" . it "encodes and decodes address" $
-        forAll arbitraryAddress (testCustom (addrFromJSON net) (addrToJSON net))
-    describe "witness address vectors" . it "p2sh(pwpkh)" $
-        mapM_ testCompatWitness compatWitnessVectors
-
-props :: Network -> Spec
-props net = do
-    it "encodes and decodes base58 bytestring" $
-        property $
-        forAll arbitraryBS $ \bs -> decodeBase58 (encodeBase58 bs) == Just bs
-    it "encodes and decodes base58 bytestring with checksum" $
-        property $
-        forAll arbitraryBS $ \bs ->
-            decodeBase58Check (encodeBase58Check bs) == Just bs
-    it "encodes and decodes address" $
-        property $
-        forAll arbitraryAddress $ \a ->
-            (stringToAddr net =<< addrToString net a) == Just a
-    it "shows and reads address" $
-        property $ forAll arbitraryAddress $ \a -> read (show a) == a
-
-runVector :: (ByteString, Text, Text) -> Assertion
-runVector (bs, e, chk) = do
-    assertEqual "encodeBase58" e b58
-    assertEqual "encodeBase58Check" chk b58Chk
-    assertEqual "decodeBase58" (Just bs) (decodeBase58 b58)
-    assertEqual "decodeBase58Check" (Just bs) (decodeBase58Check b58Chk)
-  where
-    b58    = encodeBase58 bs
-    b58Chk = encodeBase58Check bs
-
-vectors :: [(ByteString, Text, Text)]
-vectors =
-    [ ( BS.empty, "", "3QJmnh" )
-    , ( BS.pack [0], "1", "1Wh4bh" )
-    , ( BS.pack [0,0,0,0], "1111", "11114bdQda" )
-    , ( BS.pack [0,0,1,0,0], "11LUw", "113CUwsFVuo" )
-    , ( BS.pack [255], "5Q", "VrZDWwe" )
-    , ( BS.pack [0,0,0,0] `BS.append` BS.pack [1..255]
-      , "1111cWB5HCBdLjAuqGGReWE3R3CguuwSjw6RHn39s2yuDRTS5NsBgNiFpWgAnEx6VQi8csexkgYw3mdYrMHr8x9i7aEwP8kZ7vccXWqKDvGv3u1GxFKPuAkn8JCPPGDMf3vMMnbzm6Nh9zh1gcNsMvH3ZNLmP5fSG6DGbbi2tuwMWPthr4boWwCxf7ewSgNQeacyozhKDDQQ1qL5fQFUW52QKUZDZ5fw3KXNQJMcNTcaB723LchjeKun7MuGW5qyCBZYzA1KjofN1gYBV3NqyhQJ3Ns746GNuf9N2pQPmHz4xpnSrrfCvy6TVVz5d4PdrjeshsWQwpZsZGzvbdAdN8MKV5QsBDY"
-      , "111151KWPPBRzdWPr1ASeu172gVgLf1YfUp6VJyk6K9t4cLqYtFHcMa2iX8S3NJEprUcW7W5LvaPRpz7UG7puBj5STE3nKhCGt5eckYq7mMn5nT7oTTic2BAX6zDdqrmGCnkszQkzkz8e5QLGDjf7KeQgtEDm4UER6DMSdBjFQVa6cHrrJn9myVyyhUrsVnfUk2WmNFZvkWv3Tnvzo2cJ1xW62XDfUgYz1pd97eUGGPuXvDFfLsBVd1dfdUhPwxW7pMPgdWHTmg5uqKGFF6vE4xXpAqZTbTxRZjCDdTn68c2wrcxApm8hq3JX65Hix7VtcD13FF8b7BzBtwjXq1ze6NMjKgUcqpJTN9vt"
-      )
-    ]
-
-testCustom :: Eq a => (Value -> Parser a) -> (a -> Value) -> a -> Bool
-testCustom f g x = parseMaybe f (g x) == Just x
-
-compatWitnessVectors :: [(Network, Text, Text)]
-compatWitnessVectors =
-    [ ( btcTest
-      , "cNUnpYpMsJXYCERYBciJnsWBpcYEFjdcbq6dxj4SskGhs7uHuJ7Q"
-      , "2N6PDTueBHvXzW61B4oe5SW1D3v2Z3Vpbvw")
-    ]
-
-testCompatWitness :: (Network, Text, Text) -> Assertion
-testCompatWitness (net, seckey, addr) = do
-    let seckeyM = fromWif net seckey
-    assertBool "decode seckey" (isJust seckeyM)
-    let pubkey = derivePubKeyI (fromJust seckeyM)
-    let addrM = addrToString btcTest (pubKeyCompatWitnessAddr pubkey)
-    assertBool "address can be encoded" (isJust addrM)
-    assertEqual "witness address matches" addr (fromJust addrM)
diff --git a/test/Network/Haskoin/BlockSpec.hs b/test/Network/Haskoin/BlockSpec.hs
deleted file mode 100644
--- a/test/Network/Haskoin/BlockSpec.hs
+++ /dev/null
@@ -1,359 +0,0 @@
-{-# LANGUAGE OverloadedStrings #-}
-module Network.Haskoin.BlockSpec
-    ( spec
-    ) where
-
-import           Control.Monad.State.Strict
-import           Data.Aeson                    as A
-import           Data.Either                   (fromRight)
-import           Data.Map.Strict               (singleton)
-import           Data.Maybe                    (fromJust)
-import           Data.Serialize                as S
-import           Data.String                   (fromString)
-import           Data.String.Conversions       (cs)
-import           Data.Text                     (Text)
-import           Network.Haskoin.Block
-import           Network.Haskoin.Block.Headers
-import           Network.Haskoin.Block.Merkle
-import           Network.Haskoin.Constants
-import           Network.Haskoin.Test
-import           Network.Haskoin.Transaction
-import           Test.Hspec
-import           Test.HUnit                    hiding (State)
-import           Test.QuickCheck
-
-myTime :: Timestamp
-myTime = 1499083075
-
-withChain :: Network -> State HeaderMemory a -> a
-withChain net f = evalState f (initialChain net)
-
-chain :: BlockHeaders m => Network -> BlockHeader -> Int -> m ()
-chain net bh i = do
-    bnsE <- connectBlocks net myTime bhs
-    either error (const $ return ()) bnsE
-  where
-    bhs = appendBlocks net 6 bh i
-
-spec :: Spec
-spec = do
-    let net = bchRegTest
-    describe "blockchain headers" $ do
-        it "gets best block" $
-            let bb =
-                    withChain net $ do
-                        chain net (getGenesisHeader net) 100
-                        getBestBlockHeader
-             in nodeHeight bb `shouldBe` 100
-        it "builds a block locator" $
-            let loc =
-                    withChain net $ do
-                        chain net (getGenesisHeader net) 100
-                        bb <- getBestBlockHeader
-                        blockLocatorNodes bb
-                heights = map nodeHeight loc
-             in heights `shouldBe` [100,99 .. 90] <> [88, 84, 76, 60, 28, 0]
-        it "follows split chains" $
-            let bb = withChain net $ splitChain net >> getBestBlockHeader
-             in nodeHeight bb `shouldBe` 4035
-    describe "block hash" $ do
-        it "encodes and decodes block hash" $
-            property $
-            forAll arbitraryBlockHash $ \h ->
-                hexToBlockHash (blockHashToHex h) == Just h
-        it "from string block hash" $
-            property $
-            forAll arbitraryBlockHash $ \h ->
-                fromString (cs $ blockHashToHex h) == h
-        it "show and read block hash" $
-            property $ forAll arbitraryBlockHash $ \h -> read (show h) == h
-        it "json block hash" $ property $ forAll arbitraryBlockHash testID
-    describe "merkle trees" $ do
-        let net' = btc
-        it "builds tree of right width at height 1" $ property testTreeWidth
-        it "builds tree of right width at height 0" $ property testBaseWidth
-        it "builds and extracts partial merkle tree" $
-            property $
-            forAll
-                (listOf1 ((,) <$> arbitraryTxHash <*> arbitrary))
-                (buildExtractTree net')
-        it "merkle root test vectors" $
-            mapM_ runMerkleVector merkleVectors
-    describe "compact number" $ do
-        it "compact number local vectors" testCompact
-        it "compact number imported vectors" testCompactBitcoinCore
-    describe "block serialization" $ do
-        it "encodes and decodes block" $
-            property $ forAll (arbitraryBlock net) cerealID
-        it "encodes and decodes block JSON" $
-            property $ forAll (arbitraryBlock net) testID
-        it "encodes and decodes block header" $
-            property $ forAll arbitraryBlockHeader cerealID
-        it "encodes and decodes block header JSON" $
-            property $ forAll arbitraryBlockHeader testID
-        it "encodes and decodes getblocks" $
-            property $ forAll arbitraryGetBlocks cerealID
-        it "encodes and decodes getheaders" $
-            property $ forAll arbitraryGetHeaders cerealID
-        it "encodes and decdoes headers" $
-            property $ forAll arbitraryHeaders cerealID
-        it "encodes and decodes merkle block" $
-            property $ forAll arbitraryMerkleBlock cerealID
-    describe "helper functions" $ do
-        it "computes bitcoin block subsidy correctly" (testSubsidy btc)
-        it "computes regtest block subsidy correctly" (testSubsidy btcRegTest)
-
--- 0 → → 2015 → → → → → → → 4031
---       ↓
---       → → 2035 → → → → → → 4035*
---           ↓
---           → → 2185
-splitChain :: Network -> State HeaderMemory ()
-splitChain net = do
-    start <- go 1 (getGenesisHeader net) 2015
-    e 2015 (head start)
-    tail1 <- go 2 (nodeHeader $ head start) 2016
-    e 4031 (head tail1)
-    tail2 <- go 3 (nodeHeader $ head start) 20
-    e 2035 (head tail2)
-    tail3 <- go 4 (nodeHeader $ head tail2) 2000
-    e 4035 (head tail3)
-    tail4 <- go 5 (nodeHeader $ head tail2) 150
-    e 2185 (head tail4)
-    sp1 <- splitPoint (head tail1) (head tail3)
-    unless (sp1 == head start) $
-        error $
-        "Split point wrong between blocks 4031 and 4035: " ++
-        show (nodeHeight sp1)
-    sp2 <- splitPoint (head tail4) (head tail3)
-    unless (sp2 == head tail2) $
-        error $
-        "Split point wrong between blocks 2185 and 4035: " ++
-        show (nodeHeight sp2)
-  where
-    e n bn =
-        unless (nodeHeight bn == n) $
-        error $
-        "Node height " ++
-        show (nodeHeight bn) ++ " of first chunk should be " ++ show n
-    go seed start n = do
-        let bhs = appendBlocks net seed start n
-        bnE <- connectBlocks net myTime bhs
-        case bnE of
-            Right bn -> return bn
-            Left ex  -> error ex
-
-{- Merkle Trees -}
-
-testTreeWidth :: Int -> Property
-testTreeWidth i = i /= 0 ==> calcTreeWidth (abs i) (calcTreeHeight $ abs i) == 1
-
-testBaseWidth :: Int -> Property
-testBaseWidth i = i /= 0 ==> calcTreeWidth (abs i) 0 == abs i
-
-buildExtractTree :: Network -> [(TxHash, Bool)] -> Bool
-buildExtractTree net txs =
-    r == buildMerkleRoot (map fst txs) && m == map fst (filter snd txs)
-  where
-    (f, h) = buildPartialMerkle txs
-    (r, m) =
-        fromRight (error "Could not extract matches from Merkle tree") $
-        extractMatches net f h (length txs)
-
-testCompact :: Assertion
-testCompact = do
-    assertEqual "vector 1" 0x05123456 (encodeCompact 0x1234560000)
-    assertEqual "vector 2" (0x1234560000, False) (decodeCompact 0x05123456)
-    assertEqual "vector 3" 0x0600c0de (encodeCompact 0xc0de000000)
-    assertEqual "vector 4" (0xc0de000000, False) (decodeCompact 0x0600c0de)
-    assertEqual "vector 5" 0x05c0de00 (encodeCompact (-0x40de000000))
-    assertEqual "vector 6" (-0x40de000000, False) (decodeCompact 0x05c0de00)
-
-testCompactBitcoinCore :: Assertion
-testCompactBitcoinCore = do
-    assertEqual "zero" (0, False) (decodeCompact 0x00000000)
-    assertEqual
-        "zero (encode · decode)"
-        0x00000000
-        (encodeCompact . fst $ decodeCompact 0x00000000)
-    assertEqual "rounds to zero" (0, False) (decodeCompact 0x00123456)
-    assertEqual "rounds to zero" (0, False) (decodeCompact 0x01003456)
-    assertEqual "rounds to zero" (0, False) (decodeCompact 0x02000056)
-    assertEqual "rounds to zero" (0, False) (decodeCompact 0x03000000)
-    assertEqual "rounds to zero" (0, False) (decodeCompact 0x04000000)
-    assertEqual "rounds to zero" (0, False) (decodeCompact 0x00923456)
-    assertEqual "rounds to zero" (0, False) (decodeCompact 0x01803456)
-    assertEqual "rounds to zero" (0, False) (decodeCompact 0x02800056)
-    assertEqual "rounds to zero" (0, False) (decodeCompact 0x03800000)
-    assertEqual "rounds to zero" (0, False) (decodeCompact 0x04800000)
-    assertEqual "vector 1 (decode)" (0x12, False) (decodeCompact 0x01123456)
-    assertEqual
-        "vector 1 (encode · decode)"
-        0x01120000
-        (encodeCompact . fst $ decodeCompact 0x01123456)
-    assertEqual "0x80 bit set" 0x02008000 (encodeCompact 0x80)
-    assertEqual
-        "vector 2 (negative) (decode)"
-        (-0x7e, False)
-        (decodeCompact 0x01fedcba)
-    assertEqual
-        "vector 2 (negative) (encode · decode)"
-        0x01fe0000
-        (encodeCompact . fst $ decodeCompact 0x01fedcba)
-    assertEqual "vector 3 (decode)" (0x1234, False) (decodeCompact 0x02123456)
-    assertEqual
-        "vector 3 (encode · decode)"
-        0x02123400
-        (encodeCompact . fst $ decodeCompact 0x02123456)
-    assertEqual "vector 4 (decode)" (0x123456, False) (decodeCompact 0x03123456)
-    assertEqual
-        "vector 4 (encode · decode)"
-        0x03123456
-        (encodeCompact . fst $ decodeCompact 0x03123456)
-    assertEqual
-        "vector 5 (decode)"
-        (0x12345600, False)
-        (decodeCompact 0x04123456)
-    assertEqual
-        "vector 5 (encode · decode)"
-        0x04123456
-        (encodeCompact . fst $ decodeCompact 0x04123456)
-    assertEqual
-        "vector 6 (decode)"
-        (-0x12345600, False)
-        (decodeCompact 0x04923456)
-    assertEqual
-        "vector 6 (encode · decode)"
-        0x04923456
-        (encodeCompact . fst $ decodeCompact 0x04923456)
-    assertEqual
-        "vector 7 (decode)"
-        (0x92340000, False)
-        (decodeCompact 0x05009234)
-    assertEqual
-        "vector 7 (encode · decode)"
-        0x05009234
-        (encodeCompact . fst $ decodeCompact 0x05009234)
-    assertEqual
-        "vector 8 (decode)"
-        ( 0x1234560000000000000000000000000000000000000000000000000000000000
-        , False)
-        (decodeCompact 0x20123456)
-    assertEqual
-        "vector 8 (encode · decode)"
-        0x20123456
-        (encodeCompact . fst $ decodeCompact 0x20123456)
-    assertBool "vector 9 (decode) (overflow)" (snd $ decodeCompact 0xff123456)
-    assertBool
-        "vector 9 (decode) (positive)"
-        ((> 0) . fst $ decodeCompact 0xff123456)
-
-runMerkleVector :: (Text, [Text]) -> Assertion
-runMerkleVector (r, hs) =
-    assertBool "merkle vector" $
-        buildMerkleRoot (map f hs) == getTxHash (f r)
-  where
-    f = fromJust . hexToTxHash
-
-merkleVectors :: [(Text, [Text])]
-merkleVectors =
-      -- Block 000000000000cd7e8cf6510303dde76121a1a791c15dba0be4be7022b07cf9e1
-    [ ( "fb6698ac95b754256c5e71b4fbe07638cb6ca83ee67f44e181b91727f09f4b1f"
-      , [ "dd96fdcfaec994bf583af650ff6022980ee0ba1686d84d0a3a2d24eabf34bc52"
-        , "1bc216f786a564378710ae589916fc8e092ddfb9f24fe6c47b733550d476d5d9"
-        , "a1db0b0194426064b067899ff2d975fb277fd52dbb1a38370800c76dd6503d41"
-        , "d69f7fb0e668fbd437d1bf5211cc34d7eb8746f50cfddf705fe10bc2f8f7035f"
-        , "5b4057cd80be7df5ed2ac42b776897ed3c26e3a01e4072075b8129c587094ef6"
-        , "ed6dabcfba0ef43c50d89a8a0e4b236b1bc6585d4c3bbf49728b55f44312d6bc"
-        , "056aaa9a3c635909c794e9b0acc7dccb0456c59a84c6b08417335bee4515e3d3"
-        , "05bae5f1d1c874171692e1fc06f664e63eb143d3f096601ef938e4a9012eee66"
-        , "b5e48e94e3f2fba197b3f591e01f47e185d7834d669529d44078e41c671aab0f"
-        , "3b56aeadfc0c5484fd507bc89f13f2e5f61c42e0a4ae9062eda9a9aeef7db6a4"
-        , "2affa187e1ebb94a2a86578b9f64951e854ff3d346fef259acfb6d0f5212e0d3"
-        ]
-      )
-      -- Block 00000000000007cc4b6f07bfed72bccc1ed8dd031a93969a4c22211f784457d4
-    , ( "886fea311d2dc64c315519f2d647e43998d780d2170f77e53dc0d85bf2ee680c"
-      , [ "c9c9e5211512629fd111cc071d745b8c79bf486b4ea95489eb5de08b5d786b8e"
-        , "20beb0ee30dfd323ade790ce9a46ae7a174f9ea44ce22a17c4d4eb23b7016f51"
-        , "d4cb7dd741e78a8f57e12f6c8ddb0361ff2a5bf9365bd7d7df761060847daf9a"
-        , "ddbfa6fdd29d4b47aeaadf82a4bf0a93d58cd7d8401fabf860a1ae8eeb51f42e"
-        , "9d82bafe44abee248b968c86f165051c8413482c232659795335c52922dab471"
-        , "86035372d31b53efd848cea7231aa9738c209aff64d3c59b1619341afb5b6ba3"
-        , "11e7a7393d9658813dfaebc04fa6d4b73bac8d641bffa7067da879523d43d030"
-        , "2f676b9aa5bc0ebf3395032c84c466e40cac29f80434cd1138e31c2d0fcc5c13"
-        , "37567d559fbfae07fda9a90de0ce30b202128bc8ebdfef5ad2b53e865a3478c2"
-        , "0b8e6c1200c454361e94e261738429e9c9b8dcffd85ec8511bbf5dc7e2e0ada8"
-        ]
-      )
-      -- Block 00000000839a8e6886ab5951d76f411475428afc90947ee320161bbf18eb6048
-    , ( "0e3e2357e806b6cdb1f70b54c3a3a17b6714ee1f0e68bebb44a74b1efd512098"
-      , [ "0e3e2357e806b6cdb1f70b54c3a3a17b6714ee1f0e68bebb44a74b1efd512098" ]
-      )
-      -- Block 000000000004d160ac1f7b775d7c1823345aeadd5fcb29ca2ad2403bb7babd4c
-    , ( "aae018650f513fc42d55b2210ec3ceeeb194fb1261d37989de07451fc0cbac5c"
-      , [ "a4454f22831acd7904a9902c5070a3ee4bf4c2b13bc6b2dc66735dd3c4414028"
-        , "45297f334278885108dd38a0b689ed95a4373dd3f7e4413e6aebdc2654fb771b"
-        ]
-      )
-      -- Block 000000000001d1b13a7e86ddb20da178f20d6da5cd037a29c2a15b8b84cc774e
-    , ( "ca3580505feb87544760ac14a5859659e23be05f765bbed9f86a3c9aad1a5d0c"
-      , [ "60702384c6e9d34ff03c2b3e726bdc649befe603216815bd0a2974921d0d9549"
-        , "11f40f58941d2a81a1616a3b84b7dd8b9d07e68750827de488c11a18f54220bb"
-        , "d78e82527aa8cf16e375010bc666362c0258d3c0da1885a1871121706da8b633"
-        ]
-      )
-      -- Block 0000000000000630a4e2266a31776e952a19b7c99a6387917d9de9032f608021
-    , ( "dcce8be0a9a41e7bb726c5b49d957d90b5308e3dc5dce070ccbc8996e265a6c2"
-      , [ "c0f58ff12cd1023b05f8f7035cc62bf50958ddb216a4e0eb5471deb7ef25fe81"
-        , "24e5bbf9008641b8fcf3d076fef66c28c695362ba9f6a6042f8275a98414ee92"
-        , "e8e1f72abad5e34dabc0f6de46a484b17a9af857d1c41de19482fadf6f7f4b27"
-        , "540e4d34d9fd9e5ec02853054be7ad9260379bc23388489049cca1b0f7cf518a"
-        , "324444835c5fe0545f98c4240011b75e6ea1bb76f41829e4cfbe7f75b6cee924"
-        , "e7d31437ac21bceb0c222a82b2723e2b8a7654147e33397679f041537022a4b2"
-        , "a8b5768d8b33525ee89d546a6a6897f8e42ba9d56a2c5e871a5d2ab40258dc95"
-        , "7ba712b31bae8d45810a5cda3838c7e7fb9abd6e88bb4b3ee79be9ea2f714bb4"
-        , "2ae1c4d927b06edaa626b230976ad8062bbae24da9378d1de2409da5ab08a26d"
-        , "3c417dc8087d6878003624b74431e17fec9ca761389034b1b1e0f32cbfb11f4f"
-        , "de6de7beae8d8c98c7d46b4409d5460e58e3204d8b4caed256c7471998595909"
-        , "c7c3c211402b7c4379f7b01fadc67260ee58d11e8d0bcce3d68cb45f3467e99d"
-        , "77aa2717e727a096d81074bd46ae59462692d20a1acc1a01b2535518ae5aeb53"
-        , "4859a710bb673aca46208bbd59d1000ae990dafff5f70b56f0853aeeaea3948b"
-        , "38deca6991988e461b83aa0d49ffef0f304c4b760371682d152eeb8c56a48174"
-        , "648f4f50dada3574e2dfe2dc68956b01dd97d543859a3540bbe1ef5418d0e494"
-        , "9cd7be42c2f0cd8bf38738c162cd05108e213ec7958bf2571cb627872963f5c4"
-        , "6740e0dd8b97e23864af41839fc197238d2f0dbefce9a82c657556be65c465fa"
-        , "f75c2e4b70db4b0aabc44b77af1ae75d305340fcf6e7b5f806ddcba4aa42b55d"
-        , "e125c488636749da68e6696b97525a77146c0777c7946927e37afd513d74a4e6"
-        , "c20526f119aea10880af631eba7f0b60385a22e0b0c402fe8508d41952e58be9"
-        , "6456c023c7e245f5c57a168633a23f57f4fadb651115f807694a6bed14ae3b55"
-        , "98b26e364e2888c9f264e4b5e13103c89608609774eb07ce933d8a2a45d19776"
-        , "2efaa4f167bb65ba5684f8076cd9279fd67fd9c67388c8862809bab5542e637d"
-        , "ec44eeb84d8d976d77079a822710b4dfdb11a2d9a03d8cc00bab0ae424e84666"
-        , "410730d9f807d81ac48b8eafac6f1d36642c1c370241b367a35f0bac6ac7c05f"
-        , "e95a7d0d477fd3db22756a3fd390a50c7bc48dc9e946fea9d24bd0866b3bb0e9"
-        , "a72fec99d14939216628aaf7a0afc4c017113bcae964e777e6b508864eeaacc4"
-        , "8548433310fcf75dbbc042121e8318c678e0a017534786dd322a91cebe8d213f"
-        ]
-      )
-    ]
-
-testID :: (FromJSON a, ToJSON a, Eq a) => a -> Bool
-testID x =
-    (A.decode . A.encode) (singleton ("object" :: String) x) ==
-    Just (singleton ("object" :: String) x)
-
-cerealID :: (Serialize a, Eq a) => a -> Bool
-cerealID x = S.decode (S.encode x) == Right x
-
-testSubsidy :: Network -> Assertion
-testSubsidy net = go (2 * 50 * 100 * 1000 * 1000) 0
-  where
-    go previous_subsidy halvings = do
-        let height = halvings * getHalvingInterval net
-            subsidy = computeSubsidy net height
-        if halvings >= 64
-            then subsidy `shouldBe` 0
-            else do
-                subsidy `shouldBe` (previous_subsidy `div` 2)
-                go subsidy (halvings + 1)
diff --git a/test/Network/Haskoin/Crypto/HashSpec.hs b/test/Network/Haskoin/Crypto/HashSpec.hs
deleted file mode 100644
--- a/test/Network/Haskoin/Crypto/HashSpec.hs
+++ /dev/null
@@ -1,1269 +0,0 @@
-{-# LANGUAGE OverloadedStrings #-}
-module Network.Haskoin.Crypto.HashSpec (spec) where
-
-import           Data.ByteString         (ByteString)
-import           Data.Map.Strict         (singleton)
-import           Data.Maybe              (fromJust)
-import           Data.Serialize          as S
-import           Data.String             (fromString)
-import           Data.String.Conversions
-import           Network.Haskoin.Block
-import           Network.Haskoin.Crypto
-import           Network.Haskoin.Test
-import           Network.Haskoin.Util
-import           Test.Hspec
-import           Test.HUnit              (Assertion, assertBool)
-import           Test.QuickCheck
-
--- Test vectors from NIST
--- http://csrc.nist.gov/groups/STM/cavp/documents/drbg/drbgtestvectors.zip
--- About 1/3 of HMAC DRBG SHA-256 test vectors are tested here
-
-spec :: Spec
-spec =
-    describe "hash" $ do
-        it "join512( split512(h) ) == h" $
-            property $
-            forAll arbitraryHash256 $ forAll arbitraryHash256 . joinSplit512
-        it "decodeCompact . encodeCompact i == i" $ property decEncCompact
-        it "from string 64-byte hash" $
-            property $
-            forAll arbitraryHash512 $ \h ->
-                fromString (cs $ encodeHex $ encode h) == h
-        it "from string 32-byte hash" $
-            property $
-            forAll arbitraryHash256 $ \h ->
-                fromString (cs $ encodeHex $ encode h) == h
-        it "from string 20-byte hash" $
-            property $
-            forAll arbitraryHash160 $ \h ->
-                fromString (cs $ encodeHex $ encode h) == h
-        it "encodes and decodes bytestring" $
-            property $ forAll arbitraryBS cerealID
-        it "encodes and decodes hash160" $
-            property $ forAll arbitraryHash160 cerealID
-        it "encodes and decodes hash256" $
-            property $ forAll arbitraryHash256 cerealID
-        it "encodes and decodes hash512" $
-            property $ forAll arbitraryHash512 cerealID
-
-type TestVector = [ByteString]
-
-{-
-    [SHA-256]
-    [PredictionResistance = False]
-    [EntropyInputLen = 256]
-    [NonceLen = 128]
-    [PersonalizationStringLen = 0]
-    [AdditionalInputLen = 0]
-    [ReturnedBitsLen = 1024]
--}
-
-joinSplit512 :: Hash256 -> Hash256 -> Bool
-joinSplit512 a b = split512 (join512 (a, b)) == (a, b)
-
--- After encoding and decoding, we may loose precision so the new result is >=
--- to the old one.
-decEncCompact :: Integer -> Bool
-decEncCompact i
-    -- Integer completely fits inside the mantisse
-    | abs i <= 0x007fffff = decodeCompact (encodeCompact i) == (i, False)
-    -- Otherwise precision will be lost and the decoded result will
-    -- be smaller than the original number
-    | i >= 0              = fst (decodeCompact (encodeCompact i)) < i
-    | otherwise           = fst (decodeCompact (encodeCompact i)) > i
-
-cerealID :: (Serialize a, Eq a) => a -> Bool
-cerealID x = decode (encode x) == Right x
-
-t1 :: [TestVector]
-t1 =
-    [
-    -- COUNT = 0
-    [ "ca851911349384bffe89de1cbdc46e6831e44d34a4fb935ee285dd14b71a7488"
-    , "659ba96c601dc69fc902940805ec0ca8"
-    , ""
-    , ""
-    , ""
-    , "e528e9abf2dece54d47c7e75e5fe302149f817ea9fb4bee6f4199697d04d5b89d54fbb978a15b5c443c9ec21036d2460b6f73ebad0dc2aba6e624abf07745bc107694bb7547bb0995f70de25d6b29e2d3011bb19d27676c07162c8b5ccde0668961df86803482cb37ed6d5c0bb8d50cf1f50d476aa0458bdaba806f48be9dcb8"
-    ],
-    -- COUNT = 1
-    [ "79737479ba4e7642a221fcfd1b820b134e9e3540a35bb48ffae29c20f5418ea3"
-    , "3593259c092bef4129bc2c6c9e19f343"
-    , ""
-    , ""
-    , ""
-    , "cf5ad5984f9e43917aa9087380dac46e410ddc8a7731859c84e9d0f31bd43655b924159413e2293b17610f211e09f770f172b8fb693a35b85d3b9e5e63b1dc252ac0e115002e9bedfb4b5b6fd43f33b8e0eafb2d072e1a6fee1f159df9b51e6c8da737e60d5032dd30544ec51558c6f080bdbdab1de8a939e961e06b5f1aca37"
-    ],
-    -- COUNT = 2
-    [ "b340907445b97a8b589264de4a17c0bea11bb53ad72f9f33297f05d2879d898d"
-    , "65cb27735d83c0708f72684ea58f7ee5"
-    , ""
-    , ""
-    , ""
-    , "75183aaaf3574bc68003352ad655d0e9ce9dd17552723b47fab0e84ef903694a32987eeddbdc48efd24195dbdac8a46ba2d972f5808f23a869e71343140361f58b243e62722088fe10a98e43372d252b144e00c89c215a76a121734bdc485486f65c0b16b8963524a3a70e6f38f169c12f6cbdd169dd48fe4421a235847a23ff"
-    ],
-    -- COUNT = 3
-    [ "8e159f60060a7d6a7e6fe7c9f769c30b98acb1240b25e7ee33f1da834c0858e7"
-    , "c39d35052201bdcce4e127a04f04d644"
-    , ""
-    , ""
-    , ""
-    , "62910a77213967ea93d6457e255af51fc79d49629af2fccd81840cdfbb4910991f50a477cbd29edd8a47c4fec9d141f50dfde7c4d8fcab473eff3cc2ee9e7cc90871f180777a97841597b0dd7e779eff9784b9cc33689fd7d48c0dcd341515ac8fecf5c55a6327aea8d58f97220b7462373e84e3b7417a57e80ce946d6120db5"
-    ],
-    -- COUNT = 4
-    [ "74755f196305f7fb6689b2fe6835dc1d81484fc481a6b8087f649a1952f4df6a"
-    , "c36387a544a5f2b78007651a7b74b749"
-    , ""
-    , ""
-    , ""
-    , "b2896f3af4375dab67e8062d82c1a005ef4ed119d13a9f18371b1b873774418684805fd659bfd69964f83a5cfe08667ddad672cafd16befffa9faed49865214f703951b443e6dca22edb636f3308380144b9333de4bcb0735710e4d9266786342fc53babe7bdbe3c01a3addb7f23c63ce2834729fabbd419b47beceb4a460236"
-    ],
-    -- COUNT = 5
-    [ "4b222718f56a3260b3c2625a4cf80950b7d6c1250f170bd5c28b118abdf23b2f"
-    , "7aed52d0016fcaef0b6492bc40bbe0e9"
-    , ""
-    , ""
-    , ""
-    , "a6da029b3665cd39fd50a54c553f99fed3626f4902ffe322dc51f0670dfe8742ed48415cf04bbad5ed3b23b18b7892d170a7dcf3ef8052d5717cb0c1a8b3010d9a9ea5de70ae5356249c0e098946030c46d9d3d209864539444374d8fbcae068e1d6548fa59e6562e6b2d1acbda8da0318c23752ebc9be0c1c1c5b3cf66dd967"
-    ],
-    -- COUNT = 6
-    [ "b512633f27fb182a076917e39888ba3ff35d23c3742eb8f3c635a044163768e0"
-    , "e2c39b84629a3de5c301db5643af1c21"
-    , ""
-    , ""
-    , ""
-    , "fb931d0d0194a97b48d5d4c231fdad5c61aedf1c3a55ac24983ecbf38487b1c93396c6b86ff3920cfa8c77e0146de835ea5809676e702dee6a78100da9aa43d8ec0bf5720befa71f82193205ac2ea403e8d7e0e6270b366dc4200be26afd9f63b7e79286a35c688c57cbff55ac747d4c28bb80a2b2097b3b62ea439950d75dff"
-    ],
-    -- COUNT = 7
-    [ "aae3ffc8605a975befefcea0a7a286642bc3b95fb37bd0eb0585a4cabf8b3d1e"
-    , "9504c3c0c4310c1c0746a036c91d9034"
-    , ""
-    , ""
-    , ""
-    , "2819bd3b0d216dad59ddd6c354c4518153a2b04374b07c49e64a8e4d055575dfbc9a8fcde68bd257ff1ba5c6000564b46d6dd7ecd9c5d684fd757df62d85211575d3562d7814008ab5c8bc00e7b5a649eae2318665b55d762de36eba00c2906c0e0ec8706edb493e51ca5eb4b9f015dc932f262f52a86b11c41e9a6d5b3bd431"
-    ],
-    -- COUNT = 8
-    [ "b9475210b79b87180e746df704b3cbc7bf8424750e416a7fbb5ce3ef25a82cc6"
-    , "24baf03599c10df6ef44065d715a93f7"
-    , ""
-    , ""
-    , ""
-    , "ae12d784f796183c50db5a1a283aa35ed9a2b685dacea97c596ff8c294906d1b1305ba1f80254eb062b874a8dfffa3378c809ab2869aa51a4e6a489692284a25038908a347342175c38401193b8afc498077e10522bec5c70882b7f760ea5946870bd9fc72961eedbe8bff4fd58c7cc1589bb4f369ed0d3bf26c5bbc62e0b2b2"
-    ],
-    -- COUNT = 9
-    [ "27838eb44ceccb4e36210703ebf38f659bc39dd3277cd76b7a9bcd6bc964b628"
-    , "39cfe0210db2e7b0eb52a387476e7ea1"
-    , ""
-    , ""
-    , ""
-    , "e5e72a53605d2aaa67832f97536445ab774dd9bff7f13a0d11fd27bf6593bfb52309f2d4f09d147192199ea584503181de87002f4ee085c7dc18bf32ce5315647a3708e6f404d6588c92b2dda599c131aa350d18c747b33dc8eda15cf40e95263d1231e1b4b68f8d829f86054d49cfdb1b8d96ab0465110569c8583a424a099a"
-    ],
-    -- COUNT = 10
-    [ "d7129e4f47008ad60c9b5d081ff4ca8eb821a6e4deb91608bf4e2647835373a5"
-    , "a72882773f78c2fc4878295840a53012"
-    , ""
-    , ""
-    , ""
-    , "0cbf48585c5de9183b7ff76557f8fc9ebcfdfde07e588a8641156f61b7952725bbee954f87e9b937513b16bba0f2e523d095114658e00f0f3772175acfcb3240a01de631c19c5a834c94cc58d04a6837f0d2782fa53d2f9f65178ee9c837222494c799e64c60406069bd319549b889fa00a0032dd7ba5b1cc9edbf58de82bfcd"
-    ],
-    -- COUNT = 11
-    [ "67fe5e300c513371976c80de4b20d4473889c9f1214bce718bc32d1da3ab7532"
-    , "e256d88497738a33923aa003a8d7845c"
-    , ""
-    , ""
-    , ""
-    , "b44660d64ef7bcebc7a1ab71f8407a02285c7592d755ae6766059e894f694373ed9c776c0cfc8594413eefb400ed427e158d687e28da3ecc205e0f7370fb089676bbb0fa591ec8d916c3d5f18a3eb4a417120705f3e2198154cd60648dbfcfc901242e15711cacd501b2c2826abe870ba32da785ed6f1fdc68f203d1ab43a64f"
-    ],
-    -- COUNT = 12
-    [ "de8142541255c46d66efc6173b0fe3ffaf5936c897a3ce2e9d5835616aafa2cb"
-    , "d01f9002c407127bc3297a561d89b81d"
-    , ""
-    , ""
-    , ""
-    , "64d1020929d74716446d8a4e17205d0756b5264867811aa24d0d0da8644db25d5cde474143c57d12482f6bf0f31d10af9d1da4eb6d701bdd605a8db74fb4e77f79aaa9e450afda50b18d19fae68f03db1d7b5f1738d2fdce9ad3ee9461b58ee242daf7a1d72c45c9213eca34e14810a9fca5208d5c56d8066bab1586f1513de7"
-    ],
-    -- COUNT = 13
-    [ "4a8e0bd90bdb12f7748ad5f147b115d7385bb1b06aee7d8b76136a25d779bcb7"
-    , "7f3cce4af8c8ce3c45bdf23c6b181a00"
-    , ""
-    , ""
-    , ""
-    , "320c7ca4bbeb7af977bc054f604b5086a3f237aa5501658112f3e7a33d2231f5536d2c85c1dad9d9b0bf7f619c81be4854661626839c8c10ae7fdc0c0b571be34b58d66da553676167b00e7d8e49f416aacb2926c6eb2c66ec98bffae20864cf92496db15e3b09e530b7b9648be8d3916b3c20a3a779bec7d66da63396849aaf"
-    ],
-    -- COUNT = 14
-    [ "451ed024bc4b95f1025b14ec3616f5e42e80824541dc795a2f07500f92adc665"
-    , "2f28e6ee8de5879db1eccd58c994e5f0"
-    , ""
-    , ""
-    , ""
-    , "3fb637085ab75f4e95655faae95885166a5fbb423bb03dbf0543be063bcd48799c4f05d4e522634d9275fe02e1edd920e26d9accd43709cb0d8f6e50aa54a5f3bdd618be23cf73ef736ed0ef7524b0d14d5bef8c8aec1cf1ed3e1c38a808b35e61a44078127c7cb3a8fd7addfa50fcf3ff3bc6d6bc355d5436fe9b71eb44f7fd"
-    ]
-    ]
-
-{-
-    [SHA-256]
-    [PredictionResistance = False]
-    [EntropyInputLen = 256]
-    [NonceLen = 128]
-    [PersonalizationStringLen = 0]
-    [AdditionalInputLen = 256]
-    [ReturnedBitsLen = 1024]
--}
-
-t2 :: [TestVector]
-t2 =
-    [
-    -- COUNT = 0
-    [ "d3cc4d1acf3dde0c4bd2290d262337042dc632948223d3a2eaab87da44295fbd"
-    , "0109b0e729f457328aa18569a9224921"
-    , ""
-    , "3c311848183c9a212a26f27f8c6647e40375e466a0857cc39c4e47575d53f1f6"
-    , "fcb9abd19ccfbccef88c9c39bfb3dd7b1c12266c9808992e305bc3cff566e4e4"
-    , "9c7b758b212cd0fcecd5daa489821712e3cdea4467b560ef5ddc24ab47749a1f1ffdbbb118f4e62fcfca3371b8fbfc5b0646b83e06bfbbab5fac30ea09ea2bc76f1ea568c9be0444b2cc90517b20ca825f2d0eccd88e7175538b85d90ab390183ca6395535d34473af6b5a5b88f5a59ee7561573337ea819da0dcc3573a22974"
-    ],
-    -- COUNT = 1
-    [ "f97a3cfd91faa046b9e61b9493d436c4931f604b22f1081521b3419151e8ff06"
-    , "11f3a7d43595357d58120bd1e2dd8aed"
-    , ""
-    , "517289afe444a0fe5ed1a41dbbb5eb17150079bdd31e29cf2ff30034d8268e3b"
-    , "88028d29ef80b4e6f0fe12f91d7449fe75062682e89c571440c0c9b52c42a6e0"
-    , "c6871cff0824fe55ea7689a52229886730450e5d362da5bf590dcf9acd67fed4cb32107df5d03969a66b1f6494fdf5d63d5b4d0d34ea7399a07d0116126d0d518c7c55ba46e12f62efc8fe28a51c9d428e6d371d7397ab319fc73ded4722e5b4f30004032a6128df5e7497ecf82ca7b0a50e867ef6728a4f509a8c859087039c"
-    ],
-    -- COUNT = 2
-    [ "0f2f23d64f481cabec7abb01db3aabf125c3173a044b9bf26844300b69dcac8b"
-    , "9a5ae13232b43aa19cfe8d7958b4b590"
-    , ""
-    , "ec4c7a62acab73385f567da10e892ff395a0929f959231a5628188ce0c26e818"
-    , "6b97b8c6b6bb8935e676c410c17caa8042aa3145f856d0a32b641e4ae5298648"
-    , "7480a361058bd9afa3db82c9d7586e42269102013f6ec5c269b6d05f17987847748684766b44918fd4b65e1648622fc0e0954178b0279dfc9fa99b66c6f53e51c4860131e9e0644287a4afe4ca8e480417e070db68008a97c3397e4b320b5d1a1d7e1d18a95cfedd7d1e74997052bf649d132deb9ec53aae7dafdab55e6dae93"
-    ],
-    -- COUNT = 3
-    [ "53c56660c78481be9c63284e005fcc14fbc7fb27732c9bf1366d01a426765a31"
-    , "dc7a14d0eb5b0b3534e717a0b3c64614"
-    , ""
-    , "3aa848706ecb877f5bedf4ffc332d57c22e08747a47e75cff6f0fd1316861c95"
-    , "9a401afa739b8f752fddacd291e0b854f5eff4a55b515e20cb319852189d3722"
-    , "5c0eb420e0bf41ce9323e815310e4e8303cd677a8a8b023f31f0d79f0ca15aeb636099a369fd074d69889865eac1b72ab3cbfebdb8cf460b00072802e2ec648b1349a5303be4ccaadd729f1a9ea17482fd026aaeb93f1602bc1404b9853adde40d6c34b844cf148bc088941ecfc1642c8c0b9778e45f3b07e06e21ee2c9e0300"
-    ],
-    -- COUNT = 4
-    [ "f63c804404902db334c54bb298fc271a21d7acd9f770278e089775710bf4fdd7"
-    , "3e45009ea9cb2a36ba1aa4bf39178200"
-    , ""
-    , "d165a13dc8cc43f3f0952c3f5d3de4136954d983683d4a3e6d2dc4c89bf23423"
-    , "75106bc86d0336df85097f6af8e80e2da59046a03fa65b06706b8bbc7ffc6785"
-    , "6363139bba32c22a0f5cd23ca6d437b5669b7d432f786b8af445471bee0b2d24c9d5f2f93717cbe00d1f010cc3b9c515fc9f7336d53d4d26ba5c0d76a90186663c8582eb739c7b6578a3328bf68dc2cec2cd89b3a90201f6993adcc854df0f5c6974d0f5570765a15fe03dbce28942dd2fd16ba2027e68abac83926969349af8"
-    ],
-    -- COUNT = 5
-    [ "2aaca9147da66c176615726b69e3e851cc3537f5f279fe7344233d8e44cfc99d"
-    , "4e171f080af9a6081bee9f183ac9e340"
-    , ""
-    , "d75a2a6eb66c3833e50f5ec3d2e434cf791448d618026d0c360806d120ded669"
-    , "b643b74c15b37612e6577ed7ca2a4c67a78d560af9eb50a4108fca742e87b8d6"
-    , "501dcdc977f4ba856f24eaa4968b374bebb3166b280334cb510232c31ebffde10fa47b7840ef3fe3b77725c2272d3a1d4219baf23e0290c622271edcced58838cf428f0517425d2e19e0d8c89377eecfc378245f283236fafa466c914b99672ceafab369e8889a0c866d8bd639db9fb797254262c6fd44cfa9045ad6340a60ef"
-    ],
-    -- COUNT = 6
-    [ "a2e4cd48a5cf918d6f55942d95fcb4e8465cdc4f77b7c52b6fae5b16a25ca306"
-    , "bef036716440db6e6d333d9d760b7ca8"
-    , ""
-    , "bfa591c7287f3f931168f95e38869441d1f9a11035ad8ea625bb61b9ea17591c"
-    , "c00c735463bca215adc372cb892b05e939bf669583341c06d4e31d0e5b363a37"
-    , "e7d136af69926a5421d4266ee0420fd729f2a4f7c295d3c966bdfa05268180b508b8a2852d1b3a06fd2ab3e13c54005123ef319f42d0c6d3a575e6e7e1496cb28aacadbcf83740fba8f35fcee04bb2ed8a51db3d3362b01094a62fb57e33c99a432f29fce6676cffbbcc05107e794e75e44a02d5e6d9d748c5fbff00a0178d65"
-    ],
-    -- COUNT = 7
-    [ "95a67771cba69011a79776e713145d309edae56fad5fd6d41d83eaff89df6e5e"
-    , "be5b5164e31ecc51ba6f7c3c5199eb33"
-    , ""
-    , "065f693b229a7c4fd373cd15b3807552dd9bf98c5485cef361949d4e7d774b53"
-    , "9afb62406f0e812c4f156d58b19a656c904813c1b4a45a0029ae7f50731f8014"
-    , "f61b61a6e79a41183e8ed6647899d2dc85cdaf5c3abf5c7f3bf37685946dc28f4923dc842f2d4326bd6ce0d50a84cb3ba869d72a36e246910eba6512ba36cd7ed3a5437c9245b00a344308c792b668b458d3c3e16dee2fbec41867da31084d46d8ec168de2148ef64fc5b72069abf5a6ada1ead2b7146bb793ff1c9c3690fa56"
-    ],
-    -- COUNT = 8
-    [ "a459e1815cbca4514ec8094d5ab2414a557ba6fe10e613c345338d0521e4bf90"
-    , "62221392e2552e76cd0d36df6e6068eb"
-    , ""
-    , "0a3642b02b23b3ef62c701a63401124022f5b896de86dab6e6c7451497aa1dcc"
-    , "c80514865901371c45ba92d9f95d50bb7c9dd1768cb3dfbc45b968da94965c6e"
-    , "464e6977b8adaef307c9623e41c357013249c9ffd77f405f3925cebb69f151ce8fbb6a277164002aee7858fc224f6499042aa1e6322deee9a5d133c31d640e12a7487c731ba03ad866a24675badb1d79220c40be689f79c2a0be93cb4dada3e0eac4ab140cb91998b6f11953e68f2319b050c40f71c34de9905ae41b2de1c2f6"
-    ],
-    -- COUNT = 9
-    [ "252c2cad613e002478162861880979ee4e323025eebb6fb2e0aa9f200e28e0a1"
-    , "d001bc9a8f2c8c242e4369df0c191989"
-    , ""
-    , "9bcfc61cb2bc000034bb3db980eb47c76fb5ecdd40553eff113368d639b947fd"
-    , "8b0565c767c2610ee0014582e9fbecb96e173005b60e9581503a6dca5637a26e"
-    , "e96c15fe8a60692b0a7d67171e0195ff6e1c87aab844221e71700d1bbee75feea695f6a740c9760bbe0e812ecf4061d8f0955bc0195e18c4fd1516ebca50ba6a6db86881737dbab8321707675479b87611db6af2c97ea361a5484555ead454defb1a64335de964fc803d40f3a6f057893d2afc25725754f4f00abc51920743dc"
-    ],
-    -- COUNT = 10
-    [ "8be0ca6adc8b3870c9d69d6021bc1f1d8eb9e649073d35ee6c5aa0b7e56ad8a5"
-    , "9d1265f7d51fdb65377f1e6edd6ae0e4"
-    , ""
-    , "da86167ac997c406bb7979f423986a84ec6614d6caa7afc10aff0699a9b2cf7f"
-    , "e4baa3c555950b53e2bfdba480cb4c94b59381bac1e33947e0c22e838a9534cf"
-    , "64384ecc4ea6b458efc227ca697eac5510092265520c0a0d8a0ccf9ed3ca9d58074671188c6a7ad16d0b050cdc072c125d7298d3a31d9f044a9ee40da0089a84fea28cc7f05f1716db952fad29a0e779635cb7a912a959be67be2f0a4170aace2981802e2ff6467e5b46f0ffbff3b42ba5935fd553c82482ac266acf1cd247d7"
-    ],
-    -- COUNT = 11
-    [ "d43a75b6adf26d60322284cb12ac38327792442aa8f040f60a2f331b33ac4a8f"
-    , "0682f8b091f811afacaacaec9b04d279"
-    , ""
-    , "7fd3b8f512940da7de5d80199d9a7b42670c04a945775a3dba869546cbb9bc65"
-    , "2575db20bc7aafc2a90a5dabab760db851d754777bc9f05616af1858b24ff3da"
-    , "0da7a8dc73c163014bf0841913d3067806456bbca6d5de92b85534c6545467313648d71ef17c923d090dc92cff8d4d1a9a2bb63e001dc2e8ab1a597999be3d6cf70ff63fee9985801395fbd4f4990430c4259fcae4fa1fcd73dc3187ccc102d04af7c07532885e5a226fc42809c48f22eecf4f6ab996ae4fcb144786957d9f41"
-    ],
-    -- COUNT = 12
-    [ "64352f236af5d32067a529a8fd05ba00a338c9de306371a0b00c36e610a48d18"
-    , "df99ed2c7608c870624b962a5dc68acd"
-    , ""
-    , "da416335e7aaf60cf3d06fb438735ce796aad09034f8969c8f8c3f81e32fef24"
-    , "a28c07c21a2297311adf172c19e83ca0a87731bdffb80548978d2d1cd82cf8a3"
-    , "132b9f25868729e3853d3c51f99a3b5fae6d4204bea70890daf62e042b776a526c8fb831b80a6d5d3f153237df1fd39b6fd9137963f5516d9cdd4e3f9195c46e9972c15d3edc6606e3368bde1594977fb88d0ca6e6f5f3d057ccadc7d7dab77dfc42658a1e972aa446b20d418286386a52dfc1c714d2ac548713268b0b709729"
-    ],
-    -- COUNT = 13
-    [ "282f4d2e05a2cd30e9087f5633089389449f04bac11df718c90bb351cd3653a5"
-    , "90a7daf3c0de9ea286081efc4a684dfb"
-    , ""
-    , "2630b4ccc7271cc379cb580b0aaede3d3aa8c1c7ba002cf791f0752c3d739007"
-    , "c31d69de499f1017be44e3d4fa77ecebc6a9b9934749fcf136f267b29115d2cc"
-    , "c899094520e0197c37b91dd50778e20a5b950decfb308d39f1db709447ae48f6101d9abe63a783fbb830eec1d359a5f61a2013728966d349213ee96382614aa4135058a967627183810c6622a2158cababe3b8ab99169c89e362108bf5955b4ffc47440f87e4bad0d36bc738e737e072e64d8842e7619f1be0af1141f05afe2d"
-    ],
-    -- COUNT = 14
-    [ "13c752b9e745ce77bbc7c0dbda982313d3fe66f903e83ebd8dbe4ff0c11380e9"
-    , "f1a533095d6174164bd7c82532464ae7"
-    , ""
-    , "4f53db89b9ba7fc00767bc751fb8f3c103fe0f76acd6d5c7891ab15b2b7cf67c"
-    , "582c2a7d34679088cca6bd28723c99aac07db46c332dc0153d1673256903b446"
-    , "6311f4c0c4cd1f86bd48349abb9eb930d4f63df5e5f7217d1d1b91a71d8a6938b0ad2b3e897bd7e3d8703db125fab30e03464fad41e5ddf5bf9aeeb5161b244468cfb26a9d956931a5412c97d64188b0da1bd907819c686f39af82e91cfeef0cbffb5d1e229e383bed26d06412988640706815a6e820796876f416653e464961"
-    ]
-    ]
-
-{-
-    [SHA-256]
-    [PredictionResistance = False]
-    [EntropyInputLen = 256]
-    [NonceLen = 128]
-    [PersonalizationStringLen = 256]
-    [AdditionalInputLen = 0]
-    [ReturnedBitsLen = 1024]
--}
-
-t3 :: [TestVector]
-t3 =
-    [
-    -- COUNT = 0
-    [ "5cacc68165a2e2ee20812f35ec73a79dbf30fd475476ac0c44fc6174cdac2b55"
-    , "6f885496c1e63af620becd9e71ecb824"
-    , "e72dd8590d4ed5295515c35ed6199e9d211b8f069b3058caa6670b96ef1208d0"
-    , ""
-    , ""
-    , "f1012cf543f94533df27fedfbf58e5b79a3dc517a9c402bdbfc9a0c0f721f9d53faf4aafdc4b8f7a1b580fcaa52338d4bd95f58966a243cdcd3f446ed4bc546d9f607b190dd69954450d16cd0e2d6437067d8b44d19a6af7a7cfa8794e5fbd728e8fb2f2e8db5dd4ff1aa275f35886098e80ff844886060da8b1e7137846b23b"
-    ],
-    -- COUNT = 1
-    [ "8df013b4d103523073917ddf6a869793059e9943fc8654549e7ab22f7c29f122"
-    , "da2625af2ddd4abcce3cf4fa4659d84e"
-    , "b571e66d7c338bc07b76ad3757bb2f9452bf7e07437ae8581ce7bc7c3ac651a9"
-    , ""
-    , ""
-    , "b91cba4cc84fa25df8610b81b641402768a2097234932e37d590b1154cbd23f97452e310e291c45146147f0da2d81761fe90fba64f94419c0f662b28c1ed94da487bb7e73eec798fbcf981b791d1be4f177a8907aa3c401643a5b62b87b89d66b3a60e40d4a8e4e9d82af6d2700e6f535cdb51f75c321729103741030ccc3a56"
-    ],
-    -- COUNT = 2
-    [ "565b2b77937ba46536b0f693b3d5e4a8a24563f9ef1f676e8b5b2ef17823832f"
-    , "4ef3064ec29f5b7f9686d75a23d170e3"
-    , "3b722433226c9dba745087270ab3af2c909425ba6d39f5ce46f07256068319d9"
-    , ""
-    , ""
-    , "d144ee7f8363d128872f82c15663fe658413cd42651098e0a7c51a970de75287ec943f9061e902280a5a9e183a7817a44222d198fbfab184881431b4adf35d3d1019da5a90b3696b2349c8fba15a56d0f9d010a88e3f9eeedb67a69bcaa71281b41afa11af576b765e66858f0eb2e4ec4081609ec81da81df0a0eb06787340ea"
-    ],
-    -- COUNT = 3
-    [ "fc3832a91b1dcdcaa944f2d93cbceb85c267c491b7b59d017cde4add79a836b6"
-    , "d5e76ce9eabafed06e33a913e395c5e0"
-    , "ffc5f6eefd51da64a0f67b5f0cf60d7ab43fc7836bca650022a0cee57a43c148"
-    , ""
-    , ""
-    , "0e713c6cc9a4dbd4249201d12b7bf5c69c3e18eb504bf3252db2f43675e17d99b6a908400cea304011c2e54166dae1f20260008efe4e06a87e0ce525ca482bca223a902a14adcf2374a739a5dfeaf14cadd72efa4d55d15154c974d9521535bcb70658c5b6c944020afb04a87b223b4b8e5d89821704a9985bb010405ba8f3d4"
-    ],
-    -- COUNT = 4
-    [ "8009eb2cb49fdf16403bcdfd4a9f952191062acb9cc111eca019f957fb9f4451"
-    , "355598866952394b1eddd85d59f81c9d"
-    , "09ff1d4b97d83b223d002e05f754be480d13ba968e5aac306d71cc9fc49cc2dd"
-    , ""
-    , ""
-    , "9550903c2f02cf77c8f9c9a37041d0040ee1e3ef65ba1a1fbbcf44fb7a2172bd6b3aaabe850281c3a1778277bacd09614dfefececac64338ae24a1bf150cbf9d9541173a82ecba08aa19b75abb779eb10efa4257d5252e8afcac414bc3bb5d3006b6f36fb9daea4c8c359ef6cdbeff27c1068571dd3c89dc87eda9190086888d"
-    ],
-    -- COUNT = 5
-    [ "a6e4c9a8bd6da23b9c2b10a7748fd08c4f782fadbac7ea501c17efdc6f6087bd"
-    , "acdc47edf1d3b21d0aec7631abb6d7d5"
-    , "c16ee0908a5886dccf332fbc61de9ec7b7972d2c4c83c477409ce8a15c623294"
-    , ""
-    , ""
-    , "a52f93ccb363e2bdf0903622c3caedb7cffd04b726052b8d455744c71b76dee1b71db9880dc3c21850489cb29e412d7d80849cfa9151a151dcbf32a32b4a54cac01d3200200ed66a3a5e5c131a49655ffbf1a8824ff7f265690dffb4054df46a707b9213924c631c5bce379944c856c4f7846e281ac89c64fad3a49909dfb92b"
-    ],
-    -- COUNT = 6
-    [ "59d6307460a9bdd392dfc0904973991d585696010a71e52d590a5039b4849fa4"
-    , "34a0aafb95917cbf8c38fc5548373c05"
-    , "0407b7c57bc11361747c3d67526c36e228028a5d0b145d66ab9a2fe4b07507a0"
-    , ""
-    , ""
-    , "299aba0661315211b09d2861855d0b4b125ab24649461341af6abd903ed6f025223b3299f2126fcad44c675166d800619cf49540946b12138989417904324b0ddad121327211a297f11259c9c34ce4c70c322a653675f78d385e4e2443f8058d141195e17e0bd1b9d44bf3e48c376e6eb44ef020b11cf03eb141c46ecb43cf3d"
-    ],
-    -- COUNT = 7
-    [ "9ae3506aadbc8358696ba1ba17e876e1157b7048235921503d36d9211b430342"
-    , "9abf7d66afee5d2b811cba358bbc527d"
-    , "0d645f6238e9ceb038e4af9772426ca110c5be052f8673b8b5a65c4e53d2f519"
-    , ""
-    , ""
-    , "5f032c7fec6320fe423b6f38085cbad59d826085afe915247b3d546c4c6b174554dd4877c0d671de9554b505393a44e71f209b70f991ac8aa6e08f983fff2a4c817b0cd26c12b2c929378506489a75b2025b358cb5d0400821e7e252ac6376cd94a40c911a7ed8b6087e3de5fa39fa6b314c3ba1c593b864ce4ff281a97c325b"
-    ],
-    -- COUNT = 8
-    [ "96ae3b8775b36da2a29b889ad878941f43c7d51295d47440cd0e3c4999193109"
-    , "1fe022a6fc0237b055d4d6a7036b18d5"
-    , "1e40e97362d0a823d3964c26b81ab53825c56446c5261689011886f19b08e5c2"
-    , ""
-    , ""
-    , "e707cd14b06ce1e6dbcceaedbf08d88891b03f44ad6a797bd12fdeb557d0151df9346a028dec004844ca46adec3051dafb345895fa9f4604d8a13c8ff66ae093fa63c4d9c0816d55a0066d31e8404c841e87b6b2c7b5ae9d7afb6840c2f7b441bf2d3d8bd3f40349c1c014347c1979213c76103e0bece26ad7720601eff42275"
-    ],
-    -- COUNT = 9
-    [ "33f5120396336e51ee3b0b619b5f873db05ca57cda86aeae2964f51480d14992"
-    , "6f1f6e9807ba5393edcf3cb4e4bb6113"
-    , "3709605af44d90196867c927512aa8ba31837063337b4879408d91a05c8efa9f"
-    , ""
-    , ""
-    , "8b8291126ded9acef12516025c99ccce225d844308b584b872c903c7bc6467599a1cead003dc4c70f6d519f5b51ce0da57f53da90dbe8f666a1a1dde297727fee2d44cebd1301fc1ca75956a3fcae0d374e0df6009b668fd21638d2b733e6902d22d5bfb4af1b455975e08eef0ebe4dc87705801e7776583c8de11672729f723"
-    ],
-    -- COUNT = 10
-    [ "ad300b799005f290fee7f930eebce158b98fb6cb449987fe433f955456b35300"
-    , "06aa2514e4bd114edf7ac105cfef2772"
-    , "87ada711465e4169da2a74c931afb9b5a5b190d07b7af342aa99570401c3ee8a"
-    , ""
-    , ""
-    , "80d7c606ff49415a3a92ba1f2943235c01339c8f9cd0b0511fbfdf3ef23c42ffff008524193faaa4b7f2f2eb0cfa221d9df89bd373fe4e158ec06fad3ecf1eb48b8239b0bb826ee69d773883a3e8edac66254610ff70b6609836860e39ea1f3bfa04596fee1f2baca6cebb244774c6c3eb4af1f02899eba8f4188f91776de16f"
-    ],
-    -- COUNT = 11
-    [ "130b044e2c15ab89375e54b72e7baae6d4cad734b013a090f4df057e634f6ff0"
-    , "65fd6ac602cd44107d705dbc066e52b6"
-    , "f374aba16f34d54aae5e494505b67d3818ef1c08ea24967a76876d4361379aec"
-    , ""
-    , ""
-    , "5d179534fb0dba3526993ed8e27ec9f915183d967336bb24352c67f4ab5d7935d3168e57008da851515efbaecb69904b6d899d3bfa6e9805659aef2942c4903875b8fcbc0d1d24d1c075f0ff667c1fc240d8b410dff582fa71fa30878955ce2ed786ef32ef852706e62439b69921f26e84e0f54f62b938f04905f05fcd7c2204"
-    ],
-    -- COUNT = 12
-    [ "716430e999964b35459c17921fe5f60e09bd9ab234cb8f4ba4932bec4a60a1d5"
-    , "9533b711e061b07d505da707cafbca03"
-    , "372ae616d1a1fc45c5aecad0939c49b9e01c93bfb40c835eebd837af747f079d"
-    , ""
-    , ""
-    , "a80d6a1b2d0ce01fe0d26e70fb73da20d45841cf01bfbd50b90d2751a46114c0e758cb787d281a0a9cf62f5c8ce2ee7ca74fefff330efe74926acca6d6f0646e4e3c1a1e52fce1d57b88beda4a5815896f25f38a652cc240deb582921c8b1d03a1da966dd04c2e7eee274df2cd1837096b9f7a0d89a82434076bc30173229a60"
-    ],
-    -- COUNT = 13
-    [ "7679f154296e6d580854826539003a82d1c54e2e062c619d00da6c6ac820789b"
-    , "55d12941b0896462e7d888e5322a99a3"
-    , "ba4d1ed696f58ef64596c76cee87cc1ca83069a79e7982b9a06f9d62f4209faf"
-    , ""
-    , ""
-    , "10dc7cd2bb68c2c28f76d1b04ae2aa287071e04c3b688e1986b05cc1209f691daa55868ebb05b633c75a40a32b49663185fe5bb8f906008347ef51590530948b87613920014802e5864e0758f012e1eae31f0c4c031ef823aecfb2f8a73aaa946fc507037f9050b277bdeaa023123f9d22da1606e82cb7e56de34bf009eccb46"
-    ],
-    -- COUNT = 14
-    [ "8ca4a964e1ff68753db86753d09222e09b888b500be46f2a3830afa9172a1d6d"
-    , "a59394e0af764e2f21cf751f623ffa6c"
-    , "eb8164b3bf6c1750a8de8528af16cffdf400856d82260acd5958894a98afeed5"
-    , ""
-    , ""
-    , "fc5701b508f0264f4fdb88414768e1afb0a5b445400dcfdeddd0eba67b4fea8c056d79a69fd050759fb3d626b29adb8438326fd583f1ba0475ce7707bd294ab01743d077605866425b1cbd0f6c7bba972b30fbe9fce0a719b044fcc1394354895a9f8304a2b5101909808ddfdf66df6237142b6566588e4e1e8949b90c27fc1f"
-    ]
-    ]
-
-{-
-    [SHA-256]
-    [PredictionResistance = False]
-    [EntropyInputLen = 256]
-    [NonceLen = 128]
-    [PersonalizationStringLen = 256]
-    [AdditionalInputLen = 256]
-    [ReturnedBitsLen = 1024]
--}
-
-t4 :: [TestVector]
-t4 =
-    [
-    -- COUNT = 0
-    [ "5d3286bc53a258a53ba781e2c4dcd79a790e43bbe0e89fb3eed39086be34174b"
-    , "c5422294b7318952ace7055ab7570abf"
-    , "2dba094d008e150d51c4135bb2f03dcde9cbf3468a12908a1b025c120c985b9d"
-    , "793a7ef8f6f0482beac542bb785c10f8b7b406a4de92667ab168ecc2cf7573c6"
-    , "2238cdb4e23d629fe0c2a83dd8d5144ce1a6229ef41dabe2a99ff722e510b530"
-    , "d04678198ae7e1aeb435b45291458ffde0891560748b43330eaf866b5a6385e74c6fa5a5a44bdb284d436e98d244018d6acedcdfa2e9f499d8089e4db86ae89a6ab2d19cb705e2f048f97fb597f04106a1fa6a1416ad3d859118e079a0c319eb95686f4cbcce3b5101c7a0b010ef029c4ef6d06cdfac97efb9773891688c37cf"
-    ],
-    -- COUNT = 1
-    [ "c2a566a9a1817b15c5c3b778177ac87c24e797be0a845f11c2fe399dd37732f2"
-    , "cb1894eb2b97b3c56e628329516f86ec"
-    , "13ce4d8dd2db9796f94156c8e8f0769b0aa1c82c1323b61536603bca37c9ee29"
-    , "413dd83fe56835abd478cb9693d67635901c40239a266462d3133b83e49c820b"
-    , "d5c4a71f9d6d95a1bedf0bd2247c277d1f84a4e57a4a8825b82a2d097de63ef1"
-    , "b3a3698d777699a0dd9fa3f0a9fa57832d3cefac5df24437c6d73a0fe41040f1729038aef1e926352ea59de120bfb7b073183a34106efed6278ff8ad844ba0448115dfddf3319a82de6bb11d80bd871a9acd35c73645e1270fb9fe4fa88ec0e465409ea0cba809fe2f45e04943a2e396bbb7dd2f4e0795303524cc9cc5ea54a1"
-    ],
-    -- COUNT = 2
-    [ "a33288a96f41dd54b945e060c8bd0c094f1e28267cc1dcbba52063c1a9d54c4d"
-    , "36918c977e1a7276a2bb475591c367b7"
-    , "6aa528c940962638dc2201738850fd1fe6f5d0eb9f687ff1af39d9c7b36830d9"
-    , "37ee633a635e43af59abdb1762c7ea45bfe060ec1d9077ecd2a43a658673f3c7"
-    , "2eb96f2e28fa9f674bb03ade703b8f791ee5356e2ee85c7ed5bda96325256c61"
-    , "db2f91932767eb846961ce5321c7003431870508e8c6f8d432ca1f9cee5cdc1aed6e0f133d317eb6990c4b3b0a360cdfb5b43a6e712bd46bca04c414868fab22c6a49c4b89c812697c3a7fbfc8ddf10c8aa5ebf13a09fd114eb2a02a07f69786f3ce7fd30231f22779bc8db103b13fa546dbc45a89a86275281172761683d384"
-    ],
-    -- COUNT = 3
-    [ "5f37b6e47e1776e735adc03d4b999879477ff4a206231924033d94c0114f911b"
-    , "7d12d62c79c9f6234ae0314156947459"
-    , "92d4d9fab5f8bf5119f2663a9df7334f50dcde74fb9d7732f7eba56501e60d54"
-    , "c9aef0d7a9ba7345d08b6d5b5ce5645c7495b8685e6b93846ffcf470f5abd40d"
-    , "50d9d1f5074f7d9f1a24a9c63aa47b94da5ba78db1b0f18e4d4fe45c6875813c"
-    , "20d942bbd7d98700faa37e94d53bf74f2d6bd1d8c95c0b88d842c4857797d59e7c8788aeeac29740122f208f703bf35dc32b0035db0648384feb6aa17a3274bc09b2d2b746c5a06fd82f4469fb86131a49482cb7be7d9b4b95042394cfb18b13f333ec0fe5c227bf1d8f33ecb2e42e358b6c3e034cb585331bd1d27f638029b9"
-    ],
-    -- COUNT = 4
-    [ "2311c5afd64c584484b2729e84db80c0b4063fe9ca7edc83350488d7e67264a0"
-    , "6a6dfd975a0dc7b72df1f107c4b3b3a6"
-    , "2abd870ec5fe26ed14dfa57a3309f920131b70580c3639af2645cd1af93db1b1"
-    , "c6e532a3b25653b6002aed5269cc2118749306e736bde039d4d569d4f967773f"
-    , "5e7d26c4da769c373092b2b4f72b109fe34bdb7d169ea38f78ebae5df4a15759"
-    , "cacaeb1b4ac2305d8714eb50cbe1c67c5a2c0bbc7938fdfdcafef7c85fc40becbf777a4cfb6f14c6eee320943a493d2b0a744a6eb3c256ee9a3763037437df9adce3e2260f0c35e958af0edb5a81debd8bdaf2b8bb2b98b9186e5a222a21609ff58df4cbe1d4898d10d6e7c46f31f5cb1041bfd83a5fb27d5c56c961e91403fc"
-    ],
-    -- COUNT = 5
-    [ "362ece9d330e1172a8f9e50258476d0c79c3ee50346524ba12d970ee3a6ef8c5"
-    , "cf11bcb4d9d51311ceacfca8705e833f"
-    , "abb5a8edde02e526449284ecc31bc713383df3ed085f752e3b6a32f305861eed"
-    , "746302ab1f4a86b17546bea762e929360f2e95c7788a63545a264ef997c8c65e"
-    , "b907c5b2a8833a48e56e819228ce9a050b41b3309f5ca37bed720311d92b33af"
-    , "73c7131a558350590053580873ef956ff952f2aa6ff1bea452e013d1bc2afddea2311756dbe756e63ba6258480c48f3f6c1319b5f572f67ca530af09e39413d1d432bea8f89206619618cb0e7c88e9f2033639d0eb0efc20616b64f940da99b88231984c3fb23f19e890576f555fde394dbd4351f17a7ffd5c369379001bda03"
-    ],
-    -- COUNT = 6
-    [ "cf614bc29946bc0095f415e8bdeda10aab05392f9cc9187a86ea6ec95ee422e1"
-    , "77fb5ec22dc0432cc13f4693e2e3bd9a"
-    , "e4ce77914ffbc5fddf1fb51edfafdc196109139b84c741354135ec8d314c7c43"
-    , "e1e83ee1205acaf6164dc287aec08e5b32789e5be818078db39e53cad589db51"
-    , "4e20c0226d5e1e7e805679f03f72452b5bea2d0ba41e0c12329bf60eb3016dd1"
-    , "838fdf1418a746aa52ae4005d90c3fd301f648c5770ffef2a9f3912e37a93850cc4b8bfcce910aead0cb75958823b1a62e283901c5e4a3980e4ea36257458e2e4953555819b8852a26489b1d74821f80c9908469b43f124ff7ea62497c36159a47353098a1b9ec32e54800d6704371cc37f357ad74aacc203e9b6db97f94d0c4"
-    ],
-    -- COUNT = 7
-    [ "a8da1d3e233f393fd44d204c200202f7d01896e72c5ac652940cfd15b5d4b0bd"
-    , "0a112b4cb0890af0a495e0f49fcf6874"
-    , "d2e32799bc822b8d033299bdf63dc35774f7649e935d25be5b10512c430d1bda"
-    , "920a82d76fcd2cd106ada64bba232b7b2344f3afe6b1d1d20ee8795144571009"
-    , "eeaac5878275372025f8231febed64db6a11273c3c00d625fc80a95f18ad7d3f"
-    , "5f6dae489b53d89027b2cc333c700f090152d77b3eaf01d47f56ce6eca9893ef877b4cb560fab0fbdb34e3d1c6cd8480b33c053d2661a10aa531df4961b97d659c7492584236582b3fe701055efa59c328194cd1e07fcffd910d9ee01b7b9e8c8fda7f7ac01a8e203b8b26eb8078a9b9a5021562c44af24089e3ef84c1d5a6bd"
-    ],
-    -- COUNT = 8
-    [ "a77b1ed4ecaa650374e1052c405f1d88881c25c87d13dbe1334d8c1a847fa76b"
-    , "05c143e2f145db216fe7be9ed23635d0"
-    , "b5c750968ff09ed251d4a1c05342ac843db5246b19045728a634fa4f6e752e54"
-    , "ff5937bcd01a363696bf8e40adc8e4ab3e56dbf7e7d09451c99e538785fe6697"
-    , "4acb34eea8266badcf8f6557a0eecf3eb4d7a295c876d6175598cb66a388efb8"
-    , "ec13eadfcc84e77d2a2efa1a2cd8b1355587cb27feb3d19d75b37f0446333ddb8236e751c63b7a6e595ec24a25051a696dbe8c062dd8896d1446db228a2f10e8094ee07e7ee648ed6bebb2f5ec5aae24c9c640665c28355cc11c116795ecc070790f7fdfc4398900311b6695d5da0175091ed1828d2731085bfb4a20bd86cce0"
-    ],
-    -- COUNT = 9
-    [ "491686c781e83eb4e21d9989e8d718100b0d21a2c56295888baef1a65f219651"
-    , "499085296d21065feabf3106101c8d6f"
-    , "d208a72f9ae34f0817669fb04f49239dd31700f3dc9a93db8d75fb79f9b686c1"
-    , "9ffc61893a293a864008fdd56d3292600d9e2ec8a1ea8f34ac5931e968905a23"
-    , "4ff3a397dfdae0912032a302a5e7a07dceca8d9013a21545689319b7c024cd07"
-    , "3c258ebf2203fca3b322ad1b016e21c7f5c148425f81e4fb0a0e462dce9dfa569c37a006527768297a5b68461b08912642a341b88c85597e30e7561206886098c4e2d861f11513f0ffdbbc78d3a2dd60c105abbb33c5e05ae27081b690fb8b3610917aa9bf1a4ad74481b5ff8334f14e5ad6a6a1eb2259476078076fb7e3a992"
-    ],
-    -- COUNT = 10
-    [ "36a5267eeeb5a1a7d46de0f8f9281f73cd9611f01198fdaa78c5315205e5a177"
-    , "b66b5337970df36219321badacc624eb"
-    , "c2a7b164949da102bece44a423197682ff97627d1fe9654266b8527f64e5b386"
-    , "a977e2d8637b019c74063d163bb25387dc56f4eb40e502cefc5ae6ad26a6abdc"
-    , "c5c9819557b1e7d8a86fa8c60be42993edc3ef539c13d9a51fb64b0de06e145e"
-    , "b471711a4fc7ab7247e65d2c2fe49a50169187187b7978cd2fdb0f8318be3ec55fc68ed4577ad9b42cbb57100b5d35ac86c244c4c93a5b28c1a11c2dfe905d608ec7804dec5bb15cf8d79695534d5e13a6a7e18a887ec9cf184da0cbbc6267f3a952a769403bafcdbb559401be0d8b3300ea7258b4026fc892175efd55ba1a67"
-    ],
-    -- COUNT = 11
-    [ "a76b0366df89e4073a6b6b9c04da1d6817ce26f1c4825cad4097bdf4d7b9445e"
-    , "773d3cc3290176773847869be528d1a4"
-    , "1bfd3bcfb9287a5ad055d1b2b8615fa81c94ac24bc1c219a0f8de58789e0404a"
-    , "edd879fa56f21d93029da875b683ce50f6fdc4c0da41da051d000eed2afefefa"
-    , "f528ffd29160039260133ed9654589ce60e39e7f667c34f82cda65ddcf5fff14"
-    , "39d1ff8848e74dd2cdc6b818ad69823878062116fdf1679942f892c7e191be1c4b6ea268ecdff001b22af0d510f30c2c25b90fc34927f46e3f45d36b0e1848b3a5d54c36c7c65ee7287d325dfbb51b56a438feb6650ce13df88bf06b87ac4a35d2a199ea888629fb0d83f82f0ea160dc79ed220d8ef195b9e80c542f60c2d320"
-    ],
-    -- COUNT = 12
-    [ "46571e1df43e5e141235e2a9ec85bb0faf1dc0566031e14d41a2fbd0315653ec"
-    , "b60ef6a3347967519aabeaf748e4e991"
-    , "759fd8593e3688b23c4a003b655311770d670789878570eb3b155a8e6c2d8c45"
-    , "033128460b449e1accb0e9c54508759ddc2538bc64b51e6277553f0c60a02723"
-    , "a5e4a717240bdeac18a0c0e231a11dc04a47d7550f342fa9a7a5ff334eb9327d"
-    , "9d222df1d530ea7f8f2297a0c79d637da570b48042ecddded75956bba0f0e70b271ffa3c9a53bada6ee1b8a4203c22bfde82a5e2eb1b150f54c6483458569422c1a34a8997d42cc09750167a78bf52a0bd158397af9f83caabe689185c099bf0a9a4853dd3cf8b8e89efebb6a27dba873e65e9927741b22968f2875789b44e01"
-    ],
-    -- COUNT = 13
-    [ "d63980e63bbe4ac08d2ac5646bf085b82c75995e3fdfc23bb9cc734cd85ca7d2"
-    , "d33ed1dcae13fb634ba08272d6697590"
-    , "acd0da070072a5340c4f5f4395568e1a36374e074196ae87f3692ee40487e1df"
-    , "f567677b5e12e26f3544be3da9314c88fc475bf84804a89a51f12b191392c02b"
-    , "c01cc7873e93c86e2bfb8fc984cfc2eab5cc58eeef018fedb5cba5aedd386156"
-    , "b133446f633bcb40724bbf9fa187c39a44b9c094a0a0d40e98977e5466dc2c9adf62a5f4551eeb6406a14658de8a0ed7487c3bf6277e811101284a941745ce16176acc875f1435e14161772fa84609e8123c53dd03cbb868030835c0d11d8d6aa04a1b6f908248b028997737f54735ec4ed7a81fc868199ffb61a779d9340334"
-    ],
-    -- COUNT = 14
-    [ "3d99f9b7ac3a2fbe9cf15d960bf41f5588fc4db1e0d2a5c9c0fe9059f03593fb"
-    , "411f504bb63a9b3afa7ffa1357bb48be"
-    , "0bb5ebd55981a25ba69164da49fa92f2871fd3fc65eb30d0f0d0b8d798a4f8f2"
-    , "288e948a551284eb3cb23e26299955c2fb8f063c132a92683c1615ecaed80f30"
-    , "d975b22f79e34acf5db25a2a167ef60a10682dd9964e15533d75f7fa9efc5dcb"
-    , "ee8d707eea9bc7080d58768c8c64a991606bb808600cafab834db8bc884f866941b4a7eb8d0334d876c0f1151bccc7ce8970593dad0c1809075ce6dbca54c4d4667227331eeac97f83ccb76901762f153c5e8562a8ccf12c8a1f2f480ec6f1975ac097a49770219107d4edea54fb5ee23a8403874929d073d7ef0526a647011a"
-    ]
-    ]
-
-{- Reseed test vectors -}
-
-{-
-    [SHA-256]
-    [PredictionResistance = False]
-    [EntropyInputLen = 256]
-    [NonceLen = 128]
-    [PersonalizationStringLen = 0]
-    [AdditionalInputLen = 0]
-    [ReturnedBitsLen = 1024]
--}
-
-r1 :: [TestVector]
-r1 =
-    [
-    -- COUNT = 0
-    [ "06032cd5eed33f39265f49ecb142c511da9aff2af71203bffaf34a9ca5bd9c0d"
-    , "0e66f71edc43e42a45ad3c6fc6cdc4df"
-    , ""
-    , "01920a4e669ed3a85ae8a33b35a74ad7fb2a6bb4cf395ce00334a9c9a5a5d552"
-    , ""
-    , ""
-    , ""
-    , "76fc79fe9b50beccc991a11b5635783a83536add03c157fb30645e611c2898bb2b1bc215000209208cd506cb28da2a51bdb03826aaf2bd2335d576d519160842e7158ad0949d1a9ec3e66ea1b1a064b005de914eac2e9d4f2d72a8616a80225422918250ff66a41bd2f864a6a38cc5b6499dc43f7f2bd09e1e0f8f5885935124"
-    ],
-    -- COUNT = 1
-    [ "aadcf337788bb8ac01976640726bc51635d417777fe6939eded9ccc8a378c76a"
-    , "9ccc9d80c89ac55a8cfe0f99942f5a4d"
-    , ""
-    , "03a57792547e0c98ea1776e4ba80c007346296a56a270a35fd9ea2845c7e81e2"
-    , ""
-    , ""
-    , ""
-    , "17d09f40a43771f4a2f0db327df637dea972bfff30c98ebc8842dc7a9e3d681c61902f71bffaf5093607fbfba9674a70d048e562ee88f027f630a78522ec6f706bb44ae130e05c8d7eac668bf6980d99b4c0242946452399cb032cc6f9fd96284709bd2fa565b9eb9f2004be6c9ea9ff9128c3f93b60dc30c5fc8587a10de68c"
-    ],
-    -- COUNT = 2
-    [ "62cda441dd802c7652c00b99cac3652a64fc75388dc9adcf763530ac31df9214"
-    , "5fdc897a0c1c482204ef07e0805c014b"
-    , ""
-    , "bd9bbf717467bf4b5db2aa344dd0d90997c8201b2265f4451270128f5ac05a1a"
-    , ""
-    , ""
-    , ""
-    , "7e41f9647a5e6750eb8acf13a02f23f3be77611e51992cedb6602c314531aff2a6e4c557da0777d4e85faefcb143f1a92e0dbac8de8b885ced62a124f0b10620f1409ae87e228994b830eca638ccdceedd3fcd07d024b646704f44d5d9c4c3a7b705f37104b45b9cfc2d933ae43c12f53e3e6f798c51be5f640115d45cf919a4"
-    ],
-    -- COUNT = 3
-    [ "6bdc6ca8eef0e3533abd02580ebbc8a92f382c5b1c8e3eaa12566ecfb90389a3"
-    , "8f8481cc7735827477e0e4acb7f4a0fa"
-    , ""
-    , "72eca6f1560720e6bd1ff0152c12eeff1f959462fd62c72b7dde96abcb7f79fb"
-    , ""
-    , ""
-    , ""
-    , "d5a2e2f254b5ae65590d4fd1ff5c758e425be4bacdeede7989669f0a22d34274fdfc2bf87135e30abdae2691629c2f6f425bd4e119904d4785ecd9328f15259563e5a71f915ec0c02b66655471067b01016fdf934a47b017e07c21332641400bbe5719050dba22c020b9b2d2cdb933dbc70f76fec4b1d83980fd1a13c4565836"
-    ],
-    -- COUNT = 4
-    [ "096ef37294d369face1add3eb8b425895e921626495705c5a03ee566b34158ec"
-    , "6e2e0825534d2989715cc85956e0148d"
-    , ""
-    , "1b4f7125f472c253837fa787d5acf0382a3b89c3f41c211d263052402dcc62c5"
-    , ""
-    , ""
-    , ""
-    , "4541f24f759b5f2ac2b57b51125077cc740b3859a719a9bab1196e6c0ca2bd057af9d3892386a1813fc8875d8d364f15e7fd69d1cc6659470415278164df656295ba9cfcee79f6cbe26ee136e6b45ec224ad379c6079b10a2e0cb5f7f785ef0ab7a7c3fcd9cb6506054d20e2f3ec610cbba9b045a248af56e4f6d3f0c8d96a23"
-    ],
-    -- COUNT = 5
-    [ "a7dccdd431ae5726b83585b54eae4108f7b7a25c70187c0acbb94c96cc277aa8"
-    , "94c8f4b8e195a47356a89a50d1389ab5"
-    , ""
-    , "51733eee2e922f4055e53939e222e71fae730eb037443db2c7679708abb86a65"
-    , ""
-    , ""
-    , ""
-    , "99ba2691a622afecc9472418e6a8f9f1cdc1e3583c3bc7a2a650a1ab79dcbccbd656636c573179276e782569420c97438c06be898867f628b1c01eb570263d2c0f09c7aab536f6fba7df6aad19e05c236b645674667c03d1b6a04d7fc11177fe78933b309679f5bf26a4632b9a13e314c4bf4532428d3d95c689002b6dc1fbb1"
-    ],
-    -- COUNT = 6
-    [ "c286425ecf543a49bcc9196b0db1a80bc54e4948adba6f41712a350a02891fa6"
-    , "957a659a4ec2e0b7ad185483c220fd61"
-    , ""
-    , "08c2129813eea0776fba72788fdf2718759cc3c4207fa20a5fe23ac6e32cc28e"
-    , ""
-    , ""
-    , ""
-    , "8e1020a4fd84c99e0fc7e3f7ce48de5ed9ec9a5c2ccd624dbe6f30e2f688a31dc55957630357a5d48ca2a456241a28bfb16d8bb000877697a7ce24d9ad4d22b0c15117996f1f270b94f46d7a9bdfa7608fa1dd849177a9b8049e51b6b7a2742623854a1fddb5efc447eed1ea1aed6f02b4b2754ecf71ea0509da2e54f524a7e7"
-    ],
-    -- COUNT = 7
-    [ "02818bd7c1ec456ace55beeba99f646a6d3aa0ea78356ea726b763ff0dd2d656"
-    , "c482687d508c9b5c2a75f7ce390014e8"
-    , ""
-    , "cf319bfa63980e3cb997fd28771bb5614e3acb1149ba45c133ffbbab17433193"
-    , ""
-    , ""
-    , ""
-    , "19a231ff26c1865ce75d7a7185c30dd0b333126433d0c8cbf1be0d2b384d4eb3a8aff03540fbfa5f5496521a4e4a64071b44c78bd0b7e68fac9e5695c5c13fd3b9dbe7f7739781a4c8f0b980f1b17d99bce17ceb52b56866ae02456ffef83399c8cf7826f3c45c8a19315890919d20f40fc4e18d07e9c8ccd16c3327b5988f71"
-    ],
-    -- COUNT = 8
-    [ "77a5c86d99be7bc2502870f4025f9f7563e9174ec67c5f481f21fcf2b41cae4b"
-    , "ed044ad72ee822506a6d0b1211502967"
-    , ""
-    , "778100749f01a4d35c3b4a958aafe296877e0acafd089f50bc7797a42a33ab71"
-    , ""
-    , ""
-    , ""
-    , "831a4da566f46289904893ef1cc1cd4ad19ee48f3857e2b69e936d10afbdc29822e85d02663d346ef3e09a848b1d9cc04f4c4c6e3b3b0e56a034e2334d34ca08f8097be307ba41d020bc94f8c1937fe85644eeb5592c2b5a2138f7ded9a5b44b200c8b5beb27597c790f94d660eb61e8248391edc3ae2d77656cbe8354275b13"
-    ],
-    -- COUNT = 9
-    [ "0ea458cff8bfd1dd8b1addcba9c01317d53039e533104e32f96e7d342e6c7b9b"
-    , "935a4b66fc74c2a48757a99c399e64e3"
-    , ""
-    , "6c5f3708e7b714c4ed139b4fa9e8c763af01773484005109a85e33653bb0ce98"
-    , ""
-    , ""
-    , ""
-    , "373a37af84fddec13645a9768d6a785ae5a2589d64cd9b37980dde2541499210c4f408335de1d585349064f3f53a2b4c5ec6dc2a09591f99ad9fad528ac83474164b45497bf167f81e66fa08463ffea917f6891e48f149fafc20622bb1172f34886feb45c26fd446a4a4e2891b4bc594186896141aaaeeb301b49e7c1a26fec7"
-    ],
-    -- COUNT = 10
-    [ "bfb68be4ce1756d25bdfad5e0c2f8bec29360901cc4da51d423d1591cc57e1ba"
-    , "98afe4bd194c143e099680c504cceaab"
-    , ""
-    , "b97caf210e82498c3408790d41c320dd4a72007778389b44b7bc3c1c4b8c53f8"
-    , ""
-    , ""
-    , ""
-    , "409e0aa949fb3b38231bf8732e7959e943a338ea399026b744df15cbfeff8d71b3da023dcce059a88cf0d4b7475f628e4764c8bef13c70cfbbbb6da2a18aabcad919db09d04fc59765edb165147c88dd473a0f3c5ee19237ca955697e001ba654c5ee0bd26761b49333154426bc63286298a8be634fe0d72cfdeef0f3fc48eca"
-    ],
-    -- COUNT = 11
-    [ "4f6880a64610004463031d67d7924fa446c39138d4d41007e8df3d65691a9367"
-    , "6b33b2c13600f4b1df6ca3d1960e8dd4"
-    , ""
-    , "57b87b8c8f48312b5333d43b367730c0a5ad4725a16778fcb53fe136d136cbfd"
-    , ""
-    , ""
-    , ""
-    , "73d0f324ed186e2ad06bd1800e262bdbda79ba54e626761bd60f74f43e3bb62958ec1e2f1d940af163e1cadc124e7ebaba2f72e67efd746c7f6d0cad53ef03d859d93cff778a32ee5be172fe7fdbdc232ded360d704a6fa0f70bebe942e56478345492f49dc5c6fc346b88a58947ad250e688e8c626fe1efe7624620e571976e"
-    ],
-    -- COUNT = 12
-    [ "aae352e111843219cae8f70e7b8f6eb9bb53d246cbec1e4f07d42757143295b4"
-    , "b84485dccd1bf93210e322eafcbebcd9"
-    , ""
-    , "f9237f00d744d8fbff21b9d0043c258e8731817e6a5fb7b4bf5011680e5bc642"
-    , ""
-    , ""
-    , ""
-    , "cfb28b93522c7d61d8d3ce3f080e435e4c83c7e13a9dab788db8fef0407267a14fbc9324e090e24df5491fedfa81116869983938d4d4d7324a310c3af33a6f7938f602c5e4e63f1771cdaabdab0782b5affb54eb53047c109a9606739dd0065bd21eca33132986554878354f5f9f852e674dd690163b0ff74c7a25e6bae8ce39"
-    ],
-    -- COUNT = 13
-    [ "589e79e339b7d2a1b879f0b0e1a7d1ad2474eaa8025b070f1ffa877b7124d4ff"
-    , "0961ed64dbd62065d96e75de6d2ff9d6"
-    , ""
-    , "e928388d3af48c2968527a4d2f9c2626fbc3f3f5a5d84e0583ab6f78e7f8b081"
-    , ""
-    , ""
-    , ""
-    , "fce6ced1ecf474d181ab331f79c3d2cc8a768ec2818de5b3fc7cf418322716d6a6853733561a497c0c25cb288d2c9fcfbca891bafd5a834c85f3603f402acf1a7b1ea92db847ed5c252a862ad4ab5e259715f1fc81da67f5230bf8be50ee8069758095f7d0e559e03f2c6072290e61794458437609e473eb66580cddaad19b71"
-    ],
-    -- COUNT = 14
-    [ "714277d408ad87fde317f0a94732fce62f1352bdc90936673b4f1daa0925aa26"
-    , "d16582a99f23010b4248b88d86485419"
-    , ""
-    , "bd9fc7cb2fd5063b2c3c0c4f346ad2e3879371a9c805e59b9f2cd2cc2a40894f"
-    , ""
-    , ""
-    , ""
-    , "62ef7a431288252e0d736c1d4e36cc9ac37107dcd0d0e971a22444a4adae73a41eff0b11c8625e118dbc9226142fd0a6aa10ac9b190919bda44e7248d6c88874612abd77fb3716ea515a2d563237c446e2a282e7c3b0a3aef27d3427cc7d0a7d38714659c3401dbc91d3595159318ebca01ae7d7fd1c89f6ad6b604173b0c744"
-    ]
-    ]
-
-{-
-    [SHA-256]
-    [PredictionResistance = False]
-    [EntropyInputLen = 256]
-    [NonceLen = 128]
-    [PersonalizationStringLen = 0]
-    [AdditionalInputLen = 256]
-    [ReturnedBitsLen = 1024]
--}
-
-r2 :: [TestVector]
-r2 =
-    [
-    -- COUNT = 0
-    [ "05ac9fc4c62a02e3f90840da5616218c6de5743d66b8e0fbf833759c5928b53d"
-    , "2b89a17904922ed8f017a63044848545"
-    , ""
-    , "2791126b8b52ee1fd9392a0a13e0083bed4186dc649b739607ac70ec8dcecf9b"
-    , "43bac13bae715092cf7eb280a2e10a962faf7233c41412f69bc74a35a584e54c"
-    , "3f2fed4b68d506ecefa21f3f5bb907beb0f17dbc30f6ffbba5e5861408c53a1e"
-    , "529030df50f410985fde068df82b935ec23d839cb4b269414c0ede6cffea5b68"
-    , "02ddff5173da2fcffa10215b030d660d61179e61ecc22609b1151a75f1cbcbb4363c3a89299b4b63aca5e581e73c860491010aa35de3337cc6c09ebec8c91a6287586f3a74d9694b462d2720ea2e11bbd02af33adefb4a16e6b370fa0effd57d607547bdcfbb7831f54de7073ad2a7da987a0016a82fa958779a168674b56524"
-    ],
-    -- COUNT = 1
-    [ "1bea3296f24e9242b96ed00648ac6255007c91f7c1a5088b2482c28c834942bf"
-    , "71073136a5cc1eb5b5fa09e1790a0bed"
-    , ""
-    , "d714329f3fbea1df9d0b0b0d88dfe3774beb63d011935923d048e521b710dc6f"
-    , "4ef872fd211a426ea1085ab39eb220cc698fdfeabe49b8835d620ab7885de7a4"
-    , "d74d1669e89875852d9ccbf11c20fe3c13a621ebcb3f7edeea39a2b3379fdcf5"
-    , "0c8aa67ca310bd8e58c16aba35880f747266dbf624e88ec8f9ee9be5d08fdeb1"
-    , "ce95b98f13adcdf7a32aa34709d6e02f658ae498d2ab01ce920f69e7e42c4be1d005acf0ca6b17891dfafc620dd4cd3894f8492a5c846089b9b452483eb0b91f3649ec0b6f98d1aaabc2e42cd39c2b25081b85ab50cb723007a0fd83550f32c210b7c4150b5a6bb3b0c9e3c971a09d43acb48e410a77f824b957092aa8ef98bc"
-    ],
-    -- COUNT = 2
-    [ "a7ea449b49db48601fc3a3d5d77081fab092b8d420ed1b266f704f94352dd726"
-    , "d11a159b60af8d20a0e37d27e6c74aa3"
-    , ""
-    , "50916ab47e8cb5dc843f9fba80639103711f86be8e3aa94f8a64a3fe0e6e5b35"
-    , "e2bb6768120555e7b9e0d573537a82f8f32f54560e1050b6abb1588fb3441e66"
-    , "a50cec9d1ecddb2c163d24019e81c31a2b350ccd3ad8181fd31bb8d1f64fa50e"
-    , "591dbbd48b51abced67f9c6269cf0133cd3dcbb5cfafcb6ef758569c555a5773"
-    , "0a464abcc8685158372d544635b953fcb1d3821c30aaa93982f9b788935f00f88115aad61d5cee003b3d1cb50f3e961a501e2dd0fc7e1724778b184a4bdf9f64e110dda7446e5544a30bd49a400ea1a5411800e1edfeea349323618afc5dc5782dc4b71d2da4d6a4785f8dd346feb9c8740ffd26bf644e3e4323ff24c30b9f10"
-    ],
-    -- COUNT = 3
-    [ "14683ec508a29d7812e0f04a3e9d87897000dc07b4fbcfda58eb7cdabc492e58"
-    , "b2243e744eb980b3ece25ce76383fd46"
-    , ""
-    , "18590e0ef4ee2bdae462f76d9324b3002559f74c370cfccf96a571d6955703a7"
-    , "9ea3ccca1e8d791d22fcda621fc4d51b882df32d94ea8f20ee449313e6909b78"
-    , "16366a578b5ea4d0cb547790ef5b4fd45d7cd845bc8a7c45e99419c8737debb4"
-    , "a68caa29a53f1ba857e484d095805dc319fe6963e4c4daaf355f722eba746b92"
-    , "c4e7532ee816789c2d3da9ff9f4b37139a8515dbf8f9e1d0bf00c12addd79ebbd76236f75f2aa705a09f7955038ebff0d566911c5ea13214e2c2eeb46d23ad86a33b60f7b9448d63eec3e1d59f48b39552857447dc5d7944667a230e3dbfa30ca322f6eacaf7536a286706a627c5083c32de0658b9073857c30fb1d86eb8ad1b"
-    ],
-    -- COUNT = 4
-    [ "fa261fb230e2822458532ca2d5c39758750e6819a6fcebef10579ba995096959"
-    , "564e1c9fbcb12878df2bd49202cbf821"
-    , ""
-    , "bf7de29e99e7f0e1b9f96f3b1902fb4049c8c6234d20de8316ebe66d97725457"
-    , "8b7326621f6afbd44a726de48d03bcc5331f7306026c229ea9523497fbeaa88d"
-    , "33b00b31623d6160c4c6740363a96481be14b19bc47be95641227284c366922a"
-    , "2d812c8203575790ad6b6f2ed91a49d57460de779a3e881bef3be12e8766dc91"
-    , "5574e0b4efc17e8ce136e592beabfe32551072bddd740929e698467b40b3991f028a22c760f7034853cc53007e3793e3c4a600d9e9d94528f8dc09aeba86146cdde2b7f71255ae0efc529b49be2205979dba6525bfe155e8819e8e2aeeaa285704242da90b4c4535101cc47d94b0e388a1b2e63ad0cbe158b9e1bbae9cc0007c"
-    ],
-    -- COUNT = 5
-    [ "61f1471ced56aa04c57e1b512307d4cb92497d9592d7e9e35356e99d585cab1b"
-    , "84714e960c403a4fac06b2828cc564d9"
-    , ""
-    , "7bf97db3c102edc81596d4757045fe6bdc008f35792fc6290b77d889c09c33a8"
-    , "5b8bdc41f76d98cfa71ed976ea3994706375c8841adb8b6b3b6418e3132e8832"
-    , "94c8a8fdf38a6ccb8571c89420d899adab169214bb0dfcd43a04622e289935b2"
-    , "8a4b46e0a7a55907365f82d4ab9376509bd44728cab8cbafb0da901012ad8dcd"
-    , "933eb159a6af7455b60e40586c064f05f1970f564281b1ebc4662701ac1f299e4eb908c4afcb2e065191281ab576f684aefedd6904bad04d96bd93c0516c62a496c3073a0cda0676a11cc08866b0cc74f62cb9d3db48673b2c3fbeada69f922b4b795ccba22df12ef7125909381f7d681f6b9caba02fb913c5437b98c040c576"
-    ],
-    -- COUNT = 6
-    [ "a1d5bb7d70621dee6b668b28c56d5610c2f8ced30284cc3e0e48de331af05062"
-    , "88a49e3e54c5ea54c98b95de81bcc807"
-    , ""
-    , "b4e2426e98f6eed97a6cdf690a89ee109e84c3dca16c883c26fa4ac671638d8d"
-    , "5bd1e086ed228cfd8b55c1731fea40c3a63d022599ca2da4bb23118f4821ba62"
-    , "b754b53ac226e8ebe47a3d31496ec822de06fca2e7ef5bf1dec6c83d05368ec3"
-    , "fa7e76b2805d90b3d89fff545010d84f67aa3a2c9eb2ba232e75f4d53267dac3"
-    , "df6b2460688fa537df3ddfe5575fca5eb8abad56cbc4e5a618a2b4a7daf6e215c3a497974c502f9d0ec35de3fc2ea5d4f10de9b2aee66dcc7e7ae6357983095959b817f0383e3030771bd2ed97406acf78a1a4a5f30fa0992289c9202e69e3eb1eabe227c11409ff430f6dfca1a923a8b17bc4b87e908007f5e9759c41482b01"
-    ],
-    -- COUNT = 7
-    [ "68f21d14525d56233c7e263482d344c388a840103a77fb20ac60ce463cabdc79"
-    , "59fa80ae570f3e0c60ac7e2578cec3cb"
-    , ""
-    , "7584b4166530442f06e241dd904f562167e2fdae3247ab853a4a9d4884a5fa46"
-    , "f6a5482f139045c5389c9246d772c782c4ebf79c3a84b5cf779f458a69a52914"
-    , "9d37b1ce99f8079993ddf0bd54bab218016685b22655a678ce4300105f3a45b7"
-    , "4c97c67026ff43c2ee730e7b2ce8cce4794fd0588deb16185fa6792ddd0d46de"
-    , "e5f8874be0a8345aabf2f829a7c06bb40e60869508c2bdef071d73692c0265f6a5bf9ca6cf47d75cbd9df88b9cb236cdfce37d2fd4913f177dbd41887dae116edfbdad4fd6e4c1a51aad9f9d6afe7fcafced45a4913d742a7ec00fd6170d63a68f986d8c2357765e4d38835d3fea301afab43a50bd9edd2dec6a979732b25292"
-    ],
-    -- COUNT = 8
-    [ "7988146cbf9598d74cf88dc314af6b25c3f7de96ae9892fb0756318cea01987e"
-    , "280bc1ae9bfdf8a73c2df07b82a32c9c"
-    , ""
-    , "2bbc607085232e5e12ccf7c0c19a5dc80e45eb4b3d4a147fe941fa6c13333474"
-    , "f3f5c1bb5da59252861753c4980c23f72be1732f899fdea7183b5c024c858a12"
-    , "44d0cfc4f56ab38fa465a659151b3461b65b2462d1ad6b3463b5cf96ad9dc577"
-    , "34fb9a3cdacc834ff6241474c4f6e73ed6f5d9ea0337ab2b7468f01ad8a26e93"
-    , "4caec9e760c4d468e47613fe50de4a366ae20ba76793744a4e14433ea4de79dc188601eb86c803b094641ab2337b99d459d37decc7d27473057be45ba848868ee0fb5f1cf303d2fcd0b3e0c36f65a65f81b3fee8778a1f22302e25dfe34e6d587fa8864e621121880f7cd55f350531c4ce0530099eec2d0059706dcd657708d9"
-    ],
-    -- COUNT = 9
-    [ "1c974c953fa2a057c9fc9409a6843f6f839aa544bca4fa11e48afd77931d4656"
-    , "ed7c08285464af7a5dbdc10b944a1270"
-    , ""
-    , "78146ad135acb836360d36afc50653dcc36c21662da2a6f6ae05222e75f34000"
-    , "263c4984c238ded333c86472866353817379502157172cfa51371d82b1efd7b5"
-    , "79b591529f9a26a0d7c8f8fd64e354b0c134ef1f757e43f9463b3dbb7a3da1ab"
-    , "7d8f7204b0b5401ddce9e88dcf5facb9a44660a9f5f1c862748e7269c29f7964"
-    , "72e2ca257b9edaf59b50e05a144f56fb517832fb9ad3489b1e664e3d5412cbf6b2883e891703b2e73aff9ab56da1009fcdef010ab4cdab996795c8f7c47fb1192bb160353997ad39d7d5fd0e2efc9103a7c3f158246afd53fe53ca6782f809698ef5f1f0d85536780a3fd6a8bafa475891c09213088bd1a3dc169257c34a517a"
-    ],
-    -- COUNT = 10
-    [ "56216d71984a77154569122c777ce57e1d101a6025b28163a25971d39c1c5d0f"
-    , "5cd148ba7e54f4975ac8e3e0f9b5d06a"
-    , ""
-    , "3580f8ca974626c77259c6e37383cb8150b4d0ab0b30e377bed0dc9d1ff1a1bf"
-    , "15633e3a62b21594d49d3d26c4c3509f96011d4dbb9d48bbbea1b61c453f6abe"
-    , "6068eaca85c14165b101bb3e8c387c41d3f298918c7f3da2a28786ab0738a6fc"
-    , "e34f92d2b6aeeeea4ff49bfe7e4b1f462eabb853f0e86fbae0e8b3d51409ce49"
-    , "587fdb856abc19ede9078797ecb44099e07aadcd83acdcb2b090601d653f4a14c68ab2ebdda63578c5633a825bae4c0c818f89aac58d30fd7b0b5d459a0f3d86fcad78f4bb14dfff08ad81e4ea9f487cb426e91d6e80dfed436ba38fce8d6f21ca2151c92dd5c323b077d6139c66395558f0537026c4a028affa271ef4e7ea23"
-    ],
-    -- COUNT = 11
-    [ "83eb48bedc1e9294866ab8e5322ef83f6f271f8188e8fdabe5817788bd31570d"
-    , "d6ed90bc692237f132441ede857a6629"
-    , ""
-    , "a4e5e127f992bd5ca79ee56bb8a9bccf74c21814bfaf97ffd052211e802e12e4"
-    , "84136e403d9ed7f4515c188213abcfaca35715fa55de6d734aec63c4606a68f1"
-    , "fe9d8ef26e2d2e94b99943148392b2b33a581b4b97a8d7a0ecd41660a61dd10b"
-    , "594dad642183ce2cdc9494d6bcb358e0e7b767c5a0fa33e456971b8754a9abd5"
-    , "86715d43ba95fbbca9b7193ea977a820f4b61ba1b7e3b8d161b6c51b09dfd5040d94c04338b14d97ed25af577186b36ae7251a486c8a2d24a35e84a95c89d669d49e307b4a368b72164135ac54d020a970a180dfbed135d2c86f01270846d5301bd73db2c431a8aa10a0a3d03d146e5fafb9a2aa0b4efc80edab06ff3b532236"
-    ],
-    -- COUNT = 12
-    [ "ba2c94203dab2e6499d8c50dca7b5c34a6b4764834f9816631aa21b9f9c37361"
-    , "67db133bdefb25e395085bceee5a0afc"
-    , ""
-    , "fa8984d16d35302cda35a3a355ab9242ec96fec0652d39282d4a0abf0a80df87"
-    , "b6fed10255a3fea6772ae1ae6d9f6cbb9bfaa34804e58a5b786f9bc60b348ccd"
-    , "445e072244edc716d3528f0e0a20ff0cd8f819c0d031736c8da122748f24d6c6"
-    , "1f856e403c4fa035bac9aa81a20e347c7d8b213aab699d69d9d6186a06ac45c1"
-    , "79f33fc36b3b47d9ac805bdbbe699909a8d0beb689a8b2723c291bd5bf7f3ce61343d4722a14e4add36312dbb0594910c8828aff1abc159915d498106f9ffb31147478d8c9ef75d1536ba5036506b313f6e85033f8f6fea2a4de817c867a59378c53c70a2f108275daedd415c05b61c4fd5d48c54be9adb9dea6c40a2ec99ee0"
-    ],
-    -- COUNT = 13
-    [ "0db4c51492db4fe973b4bb1c52a1e873b58fc6bb37a3a4bfc252b03b994495d1"
-    , "a2a3900f169bba3f78a42526c700de62"
-    , ""
-    , "29d5aab356876447e3a20d81c7e3fc6975e2b984180a91493044442999e1ca3a"
-    , "40b34183b4e72cdff5952b317b3d45943d0fdcfa0527f3563055f7c73ae8f892"
-    , "dc94220c99ffb595c7c4d6de8de5a6bb4b38847169e24a557ef6d879ad84149d"
-    , "b2376626fd2f5218b3ed4a5609b43aa24d371cd2176ea017c2b99cf868060021"
-    , "f0bd6bc4c506d9427a09352d9c1970b146360732841a6323f4cb602c87dedfb5ff7e6964b9144933af3c5c83017ccd6a94bdca467a504564aaa7b452591a16ff6a1e7e94ddc98f9a58016cdcb8caaed6c80671ba48cc81a832d341093dda1d4e5001ec6bf66348b21e3692a13df92538ad572bb2023822072fc95f9590293ffc"
-    ],
-    --  COUNT = 14
-    [ "593845f0adfeffa7c169f8a610147ae8a08c0072fc0c14c3977d3de0d00b55af"
-    , "9e0eb2507342ee01c02beadee7d077bd"
-    , ""
-    , "aefe591697eab678c52e20013aa424b95cfd217b259757fbe17335563f5b5706"
-    , "cbb5be0ef9bf0555ee58955c4d971fb9baa6d6070c3f7244a4eb88b48f0793bf"
-    , "6dd878394abdc0402146ba07005327c55f4d821bfebca08d04e66824e3760ab4"
-    , "ba86a691d6cbf452b1e2fd1dfb5d31ef9ea5b8be92c4988dc5f560733b371f69"
-    , "00735cbfafac5df82e5cb28fc619b01e2ba9571dc0023d26f09c37fb37d0e809066165a97e532bf86fa7d148078e865fe1a09e27a6889be1533b459cd9cd229494b5cf4d2abf28c38180278d47281f13820276ec85effb8d45284eb9eef5d179ab4880023ab2bd08ee3f766f990286bf32430c042f5521bbfd0c7ee09e2254d7"
-    ]
-    ]
-
-{-
-    [SHA-256]
-    [PredictionResistance = False]
-    [EntropyInputLen = 256]
-    [NonceLen = 128]
-    [PersonalizationStringLen = 256]
-    [AdditionalInputLen = 0]
-    [ReturnedBitsLen = 1024]
--}
-
-r3 :: [TestVector]
-r3 =
-    [
-    -- COUNT = 0
-    [ "fa0ee1fe39c7c390aa94159d0de97564342b591777f3e5f6a4ba2aea342ec840"
-    , "dd0820655cb2ffdb0da9e9310a67c9e5"
-    , "f2e58fe60a3afc59dad37595415ffd318ccf69d67780f6fa0797dc9aa43e144c"
-    , "e0629b6d7975ddfa96a399648740e60f1f9557dc58b3d7415f9ba9d4dbb501f6"
-    , ""
-    , ""
-    , ""
-    , "f92d4cf99a535b20222a52a68db04c5af6f5ffc7b66a473a37a256bd8d298f9b4aa4af7e8d181e02367903f93bdb744c6c2f3f3472626b40ce9bd6a70e7b8f93992a16a76fab6b5f162568e08ee6c3e804aefd952ddd3acb791c50f2ad69e9a04028a06a9c01d3a62aca2aaf6efe69ed97a016213a2dd642b4886764072d9cbe"
-    ],
-    -- COUNT = 1
-    [ "cff72f345115376a57f4db8a5c9f64053e7379171a5a1e81e82aad3448d17d44"
-    , "d1e971ec795d098b3dae14ffcbeecfd9"
-    , "6ec0c798c240f22740cad7e27b41f5e42dccaf66def3b7f341c4d827294f83c9"
-    , "45ec80f0c00cad0ff0b7616d2a930af3f5cf23cd61be7fbf7c65be0031e93e38"
-    , ""
-    , ""
-    , ""
-    , "17a7901e2550de088f472518d377cc4cc6979f4a64f4975c74344215e4807a1234eefef99f64cb8abc3fb86209f6fc7ddd03e94f83746c5abe5360cdde4f2525ccf7167e6f0befae05b38fd6089a2ab83719874ce8f670480d5f3ed9bf40538a15aaad112db1618a58b10687b68875f00f139a72bdf043f736e4a320c06efd2c"
-    ],
-    -- COUNT = 2
-    [ "b7099b06fc7a8a74c58219729db6b0f780d7b4fa307bc3d3f9f22bfb763596a3"
-    , "b8772059a135a6b61da72f375411de26"
-    , "2ac1bfb24e0b8c6ac2803e89261822b7f72a0320df2b199171b79bcbdb40b719"
-    , "9aec4f56ec5e96fbd96048b9a63ac8d047aedbbeea7712e241133b1a357ecfc4"
-    , ""
-    , ""
-    , ""
-    , "0e1f2bfef778f5e5be671ecb4971624ec784ed2732abc4fbb98a8b482fb68737df91fd15acfad2951403ac77c5ca3edffc1e03398ae6cf6ac24a91678db5c7290abc3fa001aa02d50399326f85d2b8942199a1575f6746364740a5910552c639804d7530c0d41339345a58ff0080eccf1711895192a3817a8dc3f00f28cc10cc"
-    ],
-    -- COUNT = 3
-    [ "7ba02a734c8744b15ef8b4074fe639b32e4431762ab5b7cd4d5df675ea90672b"
-    , "8a424f32108607c8f1f45d97f500ee12"
-    , "3ad627433f465187c48141e30c2678106091e7a680229a534b851b8d46feb957"
-    , "d8f02b59b6a3dd276bc69cba68efcf11ab83ead1397afd9841786bd1bb5da97a"
-    , ""
-    , ""
-    , ""
-    , "1fb91186ba4b4459d994b4b9f4ca252c7be6294d6cdb5fe56f8ff784d4b190a1c6456e0a41223bbbdf83ed8e7cfbfa765d9d8bc7ea5f4d79ea7eccb4928081a21de4cca36620d6267f55d9a352b76fc0a57375884112c31f65ff28e76d315698c29e6c4c05cb58b0a07ae66143b4abc78b9d25c78b4121e1e45bef1a6c1793e2"
-    ],
-    -- COUNT = 4
-    [ "9a8865dfe053ae77cb6a9365b88f34eec17ea5cbfb0b1f04d1459e7fa9c4f3cb"
-    , "180c0a74da3ec464df11fac172d1c632"
-    , "336372ec82d0d68befad83691966ef6ffc65105388eb2d6eed826c2285037c77"
-    , "75b95108eff1fabe83613e1c4de575e72a5cdc4bb9311dd006f971a052386692"
-    , ""
-    , ""
-    , ""
-    , "3c683f6d4f8f5a4018d01633dfee74266aaa68ed6fc649e81b64dfdf5f75e75d5c058d66cf5fd01a4f143a6ff695517a4a43bd3adfd1fb2c28ba9a41063140bedbffdb4d21b1ace1550d59209ec61f1e2dbacb2a9116a79cb1410bf2deca5218080aacd9c68e1d6557721a8913e23f617e30f2e594f61267d5ed81464ee730b2"
-    ],
-    -- COUNT = 5
-    [ "22c1af2f2a4c885f06988567da9fc90f34f80f6dd5101c281beef497a6a1b2f8"
-    , "3fafdecf79a4174801f133131629037b"
-    , "80327dac486111b8a8b2c8e8381fb2d713a67695c2e660b2b0d4af696cc3e1de"
-    , "f95a0e4bd24f0e2e9e444f511b7632868ead0d5bb3846771264e03f8ab8ed074"
-    , ""
-    , ""
-    , ""
-    , "77a7fea2f35a188f6d1bfdd49b569d8c45e2dd431d35a18c6f432c724f1e33ae92cb89a9cf91519e50705a53199f5b572dc85c1aef8f28fb52dc7986228f66954d54eda84a86962cf25cf765bd9949876349291b1aae5f88fcf4b376912d205add4f53b2770c657946c0d824281f441509153f48356d9d43f8a927e0693db8fc"
-    ],
-    -- COUNT = 6
-    [ "d0840e3a8d629d5b883d33e053a341b21c674e67e1999f068c497ecfaabfd6f6"
-    , "071de7244ecb2fdf7ab27f2d84aa7b7a"
-    , "90d609527fad96ffe64ab153860346f3d237c8940555ae17b47842d82d3b0943"
-    , "1dd1a8b59856c49a388f594c5f42cc2e4a56b3ccb8a65e7066e44c12f4344d50"
-    , ""
-    , ""
-    , ""
-    , "7ab28a9b2d3ae999195553e6550cced4c2daccbe7ec9dcbb0d467fabba185b727fbfd9830242cd098f4db3cf4a85e8bf8e8d5974b62b28550922b32ed5bfc1a522b6605cf93bf8d90bdec1c5b9e59c6fc37a817d437068a87254be1f7c4618ada46fbc3a2efb02e44524e21d91be7534cf05fbfd858304b706d6a91ea1cc6ad5"
-    ],
-    -- COUNT = 7
-    [ "2e2dd56869104492767a59778652831919e1c8b970f84e824ae4116597a0ab7f"
-    , "01c42a7e983641de46c82fd09b4f2f76"
-    , "bcd9e1508fcc22820a8be07180fea5045367333b569e111b011cd57dc1858765"
-    , "7306507cd3ca7eec667e640d270cfbb033063d97520b6b7e38ff3cea0e79d12b"
-    , ""
-    , ""
-    , ""
-    , "b915726c7b8c5dc3975f1a334684b973abf6a9495d930088cf5d071548e4fd29a67b55cc561ed6949ad28150a9fb4307c1fa5f783a7ea872e8d7c7e67ff0c2906081ee915737d813c25be5c30b952a36f393e6baa56ab01adc2b4776ad7b5d036a53659877c7a4e5220a897d6c0799af37beeed91173fbe9c613c3b6b9bb28e5"
-    ],
-    -- COUNT = 8
-    [ "d1aab0f16bd47a5ccd67c22e094daa3735eae21aa57f0bcd9e053d9d0d545cb8"
-    , "199310dfe1b01265b8c0d2b46d6c7c9f"
-    , "625b4b8f4de72ea9cb6f70556322dc2a19d6b2b32de623f557e419a084ba60fd"
-    , "f50cabae4e060f3971096b78e550cda2837a26a693d905db2d992d589b268f44"
-    , ""
-    , ""
-    , ""
-    , "987e1fdfe004c619cf1e9034576707eccd849400e19c87a1fef5b0179ec51c42a2f8c45d7942d0023a023c89f188b2634362703985695369863322f58619c50a7385a2dc91fc78f94b59f0131dc2b56a0d7c699d427285da1c104b0ad1739da10d8071c23993787045dc21f0070e1e9aa1658fc8e3add73dac7262e80e0aa2ee"
-    ],
-    -- COUNT = 9
-    [ "449480eaa100aff6f48dc6286a5a81b9728b084864f78a9da98f606a00a6a41f"
-    , "e53c6c5ac3da9f4726389a03f97bb640"
-    , "6b8fedc084d8e28d333aef6db3702b6351f0d24e30908cccb63794282655886b"
-    , "73a6d64e1966ae324388dc12c14544e9dc5ae4fcb331e99d350c456ff16f9aa0"
-    , ""
-    , ""
-    , ""
-    , "a06912d362da7eb25598857f6d65344c3e23ec3deb80c6e43158845b95eaeca241c0bbbd67ac385e24693444455cc1c2c08c1134d956b8bc93b28be9c2d3322b3e09252979dfb8d39d04c94f81bebda5c73110605a237b561216bda9ee9bdee1cc0c7728bcc8304682334ca944e467a27a85313fa5395a9c790e35defd2edb12"
-    ],
-    -- COUNT = 10
-    [ "9a6174166e97aa4981ddf580bc01c96754b9f0ba042750aabfda1cffe56e8581"
-    , "d7512ff6b7db7ce141b2bb01dcd0425e"
-    , "ed75288f23275f9422444da5d3b53ccb3c4ac8acfb659a1e9b7655c2db52f879"
-    , "6888b9277e57dc57663d402eba8d03cf56a070dc868e6a128b18040002baf690"
-    , ""
-    , ""
-    , ""
-    , "03519dfb2ff88cc2b53eecc48ae2a18ddcf91a5d69d5aefcdda8444e6df790a5240e67b2a4de75b4bb8a31f0f8aeb5e785ffb7a1341bb52fe00a05ee66fa2d44ea9956e055f9ffa6647c3bfe851ab364ade71a0d356de710ddafb7622b1da1bc53fd4d3210407289c68d8aeb346bf15806dbe787e781b94f63da3e1f61b5ac60"
-    ],
-    -- COUNT = 11
-    [ "9c6ae1002ee1b0add0be563ce50f899da936e13efa620d08c2688c192514763a"
-    , "fde7db5160c73044be73e9d4c1b22d86"
-    , "8fdaaeffd64e53f7b4374d902d441209964e12b65d29afec258e65db6de167ca"
-    , "bcc28fd58e397f53f494ad8132df82c5d8c4c22ea0b7139bd81eeba65667bb69"
-    , ""
-    , ""
-    , ""
-    , "021d938c9b4db780c7d8134aeff1053e5b8843370b8ae9a6749fca7199d809810f1bc8dfa49426470c30c3616f903e35fbacb23420a32f1bee567cc32300f704246ddc0217f236ef52c3ec9e2433ca66f05c25721f7661c43f22c1a125ed5db531bd0836eb435c27eefc7424ce9d845e1d4cc4c503097b4ffca788e674a5cb53"
-    ],
-    -- COUNT = 12
-    [ "fe96a85b69d46b540918927bb609dc57642eeaefd46bb5da2163a0bc60294b58"
-    , "22195a410d24db45589448dfe979d3fd"
-    , "20f698833a4472fd7b78fb9b0c4eb68604f166a2694c4af48dac2b2376790e1e"
-    , "09cb870879d3f734214f6a4bd2e08c62a2a954bebe559416d8c3551aafe71d6a"
-    , ""
-    , ""
-    , ""
-    , "d3e96dbe29e1fcb8ed83b19dbfb240e6f41679fbe83853aa71446617e63e5af78cf98b331d15bccb8c673c4e5d5dcec467a1fe26a6cd1696d0c9bc49f78139d051287df7f3ae0dbb4bbf581cb8211931063c3f4612ced53f59d1b4ebb875729139f5d2a7d60642e8f2835eed888b7e3e49c0dffd012cd746abfa3e1c5c2308c6"
-    ],
-    -- COUNT = 13
-    [ "a4fd693ff0a8af24bcec352d3196549fd0da5ee5d99ca58416ca03ce4c50f38e"
-    , "8cd67f2bf71d4366ce61396642531ff5"
-    , "368969c15a4849d7593be8b162113b9298a535c148ff668a9e8b147fb3af4eba"
-    , "83d2be9a0d74e6a42159ae630acebf4e15271ef7f14f3de14752be0e0e822b11"
-    , ""
-    , ""
-    , ""
-    , "e9188fc0eaec74b2608e21e3a40be94aaf4ae08eb684de8f8bba2d5fd3b073aa5531c938c0fc628da65725c54b5c68bb91d7d326565e96685e0a4e7b220c50e0caf1628edba5bd755b31894f8cb90afa76e88c5eb9e61b4932444c1397dee3e32241a3fb70a3929e49f6da02eea54812abb3d6b5cee18f03af1e0b4958430ab3"
-    ],
-    -- COUNT = 14
-    [ "254ff5687a6dad3f1d237dc762f58d24ef2e2c084d0a48d26a3dc81e5490cda3"
-    , "f2ec392acca491e03ce47b95963a49fc"
-    , "f806b9b4a56682c61b55cb6a334caf87ffe135adfea6d0c3fc22b39898fbd078"
-    , "b8494b1c1f1752fb6f80d732a89b08115857f7cc96e7dff05ebb822706889917"
-    , ""
-    , ""
-    , ""
-    , "0e527e00494d55564f9d9b28e7110f9a61ce36c883b5be2dcb055444164cdddd1a9f2731716f22d6ff476ce413c77abfc0e946871d5481345c2e97b4bfdd12ac03df606fc56bdb99ac7b71a69b5b9160373bbec3e9dde477180af454e7acc6bc58dc0afb4281c0de4354c1bf599054e3800c6d60d892858865b5361f50bfca9b"
-    ]
-    ]
-
-{-
-    [SHA-256]
-    [PredictionResistance = False]
-    [EntropyInputLen = 256]
-    [NonceLen = 128]
-    [PersonalizationStringLen = 256]
-    [AdditionalInputLen = 256]
-    [ReturnedBitsLen = 1024]
--}
-
-r4 :: [TestVector]
-r4 =
-    [
-    -- COUNT = 0
-    [ "cdb0d9117cc6dbc9ef9dcb06a97579841d72dc18b2d46a1cb61e314012bdf416"
-    , "d0c0d01d156016d0eb6b7e9c7c3c8da8"
-    , "6f0fb9eab3f9ea7ab0a719bfa879bf0aaed683307fda0c6d73ce018b6e34faaa"
-    , "8ec6f7d5a8e2e88f43986f70b86e050d07c84b931bcf18e601c5a3eee3064c82"
-    , "1ab4ca9014fa98a55938316de8ba5a68c629b0741bdd058c4d70c91cda5099b3"
-    , "16e2d0721b58d839a122852abd3bf2c942a31c84d82fca74211871880d7162ff"
-    , "53686f042a7b087d5d2eca0d2a96de131f275ed7151189f7ca52deaa78b79fb2"
-    , "dda04a2ca7b8147af1548f5d086591ca4fd951a345ce52b3cd49d47e84aa31a183e31fbc42a1ff1d95afec7143c8008c97bc2a9c091df0a763848391f68cb4a366ad89857ac725a53b303ddea767be8dc5f605b1b95f6d24c9f06be65a973a089320b3cc42569dcfd4b92b62a993785b0301b3fc452445656fce22664827b88f"
-    ],
-    -- COUNT = 1
-    [ "3e42348bf76c0559cce9a44704308c85d9c205b676af0ac6ba377a5da12d3244"
-    , "9af783973c632a490f03dbb4b4852b1e"
-    , "2e51c7a8ac70adc37fc7e40d59a8e5bf8dfd8f7b027c77e6ec648bd0c41a78de"
-    , "45718ac567fd2660b91c8f5f1f8f186c58c6284b6968eadc9810b7beeca148a1"
-    , "63a107246a2070739aa4bed6746439d8c2ce678a54fc887c5aba29c502da7ba9"
-    , "e4576291b1cde51c5044fdc5375624cebf63333c58c7457ca7490da037a9556e"
-    , "b5a3fbd57784b15fd875e0b0c5e59ec5f089829fac51620aa998fff003534d6f"
-    , "c624d26087ffb8f39836c067ba37217f1977c47172d5dcb7d40193a1cfe20158b774558cbee8eb6f9c62d629e1bcf70a1439e46c5709ba4c94a006ba94994796e10660d6cb1e150a243f7ba5d35c8572fd96f43c08490131797e86d3ed8467b692f92f668631b1d32862c3dc43bfba686fe72fdd947db2792463e920522eb4bc"
-    ],
-    -- COUNT = 2
-    [ "b63fdd83c674699ba473faab9c358434771c5fa0348ca0faf7ebd7cf5891826b"
-    , "5fd204e2598d9626edab4158a8cfd95f"
-    , "2a5dfad8494306d9d4648a805c4602216a746ae3493492693a50a86d1ba05c64"
-    , "adea5ba92f8010bb1a6a4b6fae2caa0b384165adf721253afd635d6021f764af"
-    , "07c69d8d2b8aa1454c5c48083dd41477fda6bfcf0385638379933a60ed2e0a77"
-    , "a14e902247a3d6493d3fbc8519518b71a660e5502cf7ecfc796cfaa5b4ee4baa"
-    , "60e690e4a1eba14aec5187112a383e9991347fab7bac7cb2a40a52579a0d2718"
-    , "792b47b6ed221623bb187d63e3f039c6983d94efd5771dc9b4c40bee65924513485a6332baeda6a96f9bb431f592d73462b61d9d914a72b56fa9d87597426fb246424ebcd7abd51b2eefec8f5b839c0b3c34015342ace296b5f2218fa194b50aea1c89663460292c92c45f112ddbf6b9406f6e7ccee9c47ed2d90a27be5dd73e"
-    ],
-    -- COUNT = 3
-    [ "dab85f98eaf0cfba013b97de4d9c264ca6fe120366cb83e8b3113c68b34e39d5"
-    , "d05108e1028ae67b4ea63bdc6d75eb88"
-    , "09fed3822f6f5e5b9e575d31dc215de1607b0dfc927412618c2d8f79166dbaba"
-    , "1794885a64470744198b7d0bc24472ffe8daf3c7eb219df6ddf180e484fe0aa5"
-    , "8d74d01b582f70b92f53b43468084e1586d9b36465d333d5faaf6911e62fe40e"
-    , "ef7f6b6eb479ab05b3f9ab6dd72eac8b1e86d887f1bcae363cae386d0275a06f"
-    , "7442b2a792a6a29559bb8a515d56916ee18200580aa02e1237dd358619382d8f"
-    , "49d2cbfa0897b7d961c293c1e572fb26f28e7b956e746f6eda90454c1370a29e25303ceadc7837514dc638553b487ef9487c977c10625409178ad6506d103c487a66655d08659d92a4d5994d1c8ddb28fe60f2e49577d6e80cae1478068c98268f45e6293c9326c7f726ec89601351c0a26fd3a6549f8a41c6f58692c86594c0"
-    ],
-    -- COUNT = 4
-    [ "0f0aa84ef12e10ae2b279e799c683441862457b9bc25581c2cd3d5b58a5b3246"
-    , "f74f4230c2427a52f01f39e825d250ac"
-    , "d02b2f53da48b923c2921e0f75bd7e6139d7030aead5aeebe46c20b9ca47a38a"
-    , "5222b26e79f7c3b7066d581185b1a1f6376796f3d67f59d025dd2a7b1886d258"
-    , "d11512457bf3b92d1b1c0923989911f58f74e136b1436f00bad440dd1d6f1209"
-    , "54d9ea7d40b7255ef3d0ab16ea9fdf29b9a281920962b5c72d97b0e371b9d816"
-    , "601cef261da8864f1e30196c827143e4c363d3fa865b808e9450b13e251d47fa"
-    , "e9847cefea3b88062ea63f92dc9e96767ce9202a6e049c98dc1dcbc6d707687bd0e98ed2cc215780c454936292e44a7c6856d664581220b8c8ca1d413a2b81120380bfd0da5ff2bf737b602727709523745c2ced8daef6f47d1e93ef9bc141a135674cba23045e1f99aa78f8cead12eeffff20de2008878b1f806a2652db565a"
-    ],
-    -- COUNT = 5
-    [ "6a868ce39a3adcd189bd704348ba732936628f083de8208640dbd42731447d4e"
-    , "efdde4e22b376e5e7385e79024350699"
-    , "f7285cd5647ff0e2c71a9b54b57f04392641a4bde4a4024fa11c859fecaad713"
-    , "0174f7f456ac06c1d789facc071701f8b60e9accebced73a634a6ad0e1a697d4"
-    , "5463bb2241d10c970b68c3abc356c0fe5ef87439fc6457c5ee94be0a3fb89834"
-    , "3ab62cdbc638c1b2b50533d28f31b1758c3b8435fe24bb6d4740005a73e54ce6"
-    , "2dbf4c9123e97177969139f5d06466c272f60d067fefadf326ccc47971115469"
-    , "8afce49dccc4ff64c65a83d8c0638bd8e3b7c13c52c3c59d110a8198753e96da512c7e03aeed30918706f3ad3b819e6571cfa87369c179fb9c9bbc88110baa490032a9d41f9931434e80c40ae0051400b7498810d769fb42dddbc7aa19bdf79603172efe9c0f5d1a65372b463a31178cbae581fa287f39c4fbf8434051b7419f"
-    ],
-    -- COUNT = 6
-    [ "bb6b339eae26072487084ec9e4b53f2f1d4267d205042e74c77fb9ca0591ba50"
-    , "c0e7bf6eb07feccbc494af4098e59d30"
-    , "34aeec7ed0cae83701b6477709c8654a1114212401dc91cbe7de39d71f0c06e1"
-    , "f47fc60afbeb807236f7974d837335bc0b22288ef09ddfcb684e16b4c36a050b"
-    , "e8071ccd84ac4527e5c6e85b0709ed867776f25ae0e04180dcb7105ecd3e3490"
-    , "fbac45b5952200ad7c4232500f2417a1c14723bdd1cc078821bc2fe138b86597"
-    , "c4292d7dbef3ba7c18bf46bcf26776add22ab8ee206d6c722665dec6576b1bc0"
-    , "228aa2a314fcbfe63089ce953ac457093deaa39dd9ce2a4ece56a6028a476a98129be516d6979eff5587c032cdf4739d7ac712970f600fa781a8e542e399661183e34e4b90c59ec5dc5cad86f91083529d41c77b8f36c5a8e28ba1a548223a02eaed8426f6fe9f349ebec11bc743e767482e3472ec2799c1f530ebdc6c03bc4b"
-    ],
-    -- COUNT = 7
-    [ "be658e56f80436039e2a9c0a62952dd7d70842244b5ab10f3b8a87d36104e629"
-    , "33c9627455dfde91865aee93e5071147"
-    , "d3a6eb29b180b791984deb056d72c0608a2c9044237aecf100ccb03700064c5e"
-    , "bef24dc9a5aa23003d3825f9b2b00e7dab571ea6ad86415dbd30c0bbdce7b972"
-    , "047c29e4d1584fa70cb66e2aa148a2aa29837c5eee64dcac60fdba356cdf90bb"
-    , "41c4792161b1b00d410cb79cd56bd311a714fb78dc3471c25bdd7479f2e9a952"
-    , "cd4936d7bc3ea0e7201bcbefbc908215a97680ca6ce8672360aea600b6564308"
-    , "2c25557f6db07db057f56ad5b6dc0427d1a0e825c48c19a526f9a65087c6d1ead7c78363a61616c84f1022653af65173a3f9ec3275f2b0a0d0bc750194673c0eaa6c623cd88abb0c8979baee4cd85bfce2e4a20bfebf2c3be61676563767dfe229e0b7be67ad6fcd116dd0b460708b1b0e5c3d60f3dd8138030404d197375d75"
-    ],
-    -- COUNT = 8
-    [ "ae537f31a28ca14500e759716bc207983bfeab60b25079fa30b77b8d41244cb9"
-    , "fca9e27d8ab84cf9b9ce491ec5d8cb67"
-    , "8c9cb2b19aa3abe83c8fe7da96e9c11648252653a29dcd5bf0ac334ac587f032"
-    , "1eb52777be480f05115ae6370f30159a94d50ffcc64454678ab1d1ac6f166fa7"
-    , "9cdf6f1a2bc07acd4b0f43b5f2b892a1153e2669f237d257923636094fb40b54"
-    , "692d512722de6ba720fd23c8994ac63179b5f7e611addf9cfacd60e06e144a6a"
-    , "bbeea7b2bea821f339f494947c0b4bae8056119db69a3cbef21914953729cdef"
-    , "c0c4fb7080c0fbe425c1b756fb3a090cb0d08c7027d1bb82ed3b07613e2a757f83a78d42f9d8653954b489f800a5e058ebc4f5a1747526541d8448cb72e2232db20569dc96342c36672c4be625b363b4587f44557e58cedb4597cb57d006fda27e027818ae89e15b4c6382b9e7a4453290ea43163b4f9cae38b1023de6a47f7b"
-    ],
-    -- COUNT = 9
-    [ "2f8994c949e08862db0204008f55d3561f3e0362df13b9d9a70fda39938f2d33"
-    , "1bf3e94ea858160b832fe85d301256f5"
-    , "b46671cf7fa142e7012ed261e1fe86714711c246c7d1c0330fa692141e86d5d1"
-    , "5ecdb1e8fe12260b9bfe12d6e6f161474fa2311e12e39b0beb0fcd92a6737b73"
-    , "3ce9a29f0207d079e6dc81fb830356e555f96a23ea71424972ea9308965786d3"
-    , "db950000c0776cc0e049929ce021020adc42d29cd9b5d8f7117fbe6bde3e594f"
-    , "fc18ee6dd3dac2306774f0ac36cd789e33462d72a8c75df9057123db33e5f7bc"
-    , "8546362cc8af9b78dd6e8eb2c37db96e70708852bfd9380abedc7f324575a167bea18f632f3e19d099cfbf310773f9719eec036d2e09f393a023add8ebdc4fb87af43b2fe6c7eaa4d39f8022ce247aa45fdc84d1b92cacce6eae8252a03ec2ec5330c01f56d113fd2ec3d0240af0afcf13ddde205bb5e7c2d912dcb4aee5dcf3"
-    ],
-    -- COUNT = 10
-    [ "0c85e31487de1d7ba4a7b998ac56dc42c6dc0eae7bf5c8aaf1e4e78875f5fb47"
-    , "de878f728f73f83dc2a2f550b96c8b97"
-    , "9aac37bce1a6a81dc7934e23747991e3cf48c55ffe5a57781c41768a35220a01"
-    , "2d5ca8af1a70cfdccd015ee3bf0665dd1941fc6a7317b9d0d06658f5744cfbd9"
-    , "db881e6d0dc3b62793d7da5fe5a18e33be9b93f4a63a00a878dfbecf0d383bd2"
-    , "f743ce1b72f3de4c901369eed581c626ed3081ca707e6634fdaff46721ce0878"
-    , "cd52da3ec8a839c537dacdea8506a3eeee879de388ff5e513322d6d1bb3ff694"
-    , "a5bdd57cb8fde6298e7c5e563afcca60dd472eca484bd8c3cc17f3307be09b601744dd3ab9e8a44107c5868824575f850c0f399b280cf198006f83ede8c0b537e9be227fa140b65995ad9dfa1f2303d560c3b7f59bedd93c1282ea263924469411c2653f87fd814c74cb91c148430481d64bad0fec3cbb3dd1f39aa55c36f81b"
-    ],
-    -- COUNT = 11
-    [ "93161b2dc08cb0fd50171141c865a841ca935cfdd2b5907d6ff8ab0348c4ceb0"
-    , "5cb9f6e5912b90c3349a50ab881b35a1"
-    , "0dceb4a36326c4df1685df43fddeecb5d0c76f00eb44826694f27e610290f6e1"
-    , "d8e9be44b5f293482548d4787762ebfb03c73c40e45385e8b98907cd66f493dd"
-    , "105a8f85d6959f3e043ef508cfea21d52123f03b7aea8034c4eec761eaba1fee"
-    , "bf781f7e489d9b4b5aa5ee6d1796468af672a8d25f311edf3c4b4dbf433d703f"
-    , "c81d6bcf1e5bf37e39dda1735c6f193df115b1a854a12e7cafe060afe4589335"
-    , "4306628124d0100fade7eaaf5edf227d50771f9e5f2e1e983800eef9a39fde0b0c280e63c8728d836b5b93ea794a32c1c04cfc54bd5300e3febb5fe2e1023eded8d7cd180279a598f76823e8d5a7dffcc93a09deec5d1f80838e938fba4de9f47e94b99382ae55f116df9c3b3ddf7e50516e203645852a415796f03a86418107"
-    ],
-    -- COUNT = 12
-    [ "1ae12a5e4e9a4a5bfa79da30a9e6c62ffc639572ef1254194d129a16eb53c716"
-    , "5399b3481fdf24d373222267790a0fec"
-    , "8280cfdcd7a575816e0199e115da0ea77cae9d30b49c891a6c225e9037ba67e2"
-    , "681554ff702658122e91ba017450cfdfc8e3f4911153f7bcc428403e9c7b9d68"
-    , "226732b7a457cf0ac0ef09fd4f81296573b49a68de5e7ac3070e148c95e8e323"
-    , "45942b5e9a1a128e85e12c34596374ddc85fd7502e5633c7390fc6e6f1e5ef56"
-    , "6fc59929b41e77072886aff45f737b449b105ed7eacbd74c7cbfedf533dbeaa1"
-    , "b7547332e1509663fcfea2128f7f3a3df484cd8df034b00199157d35d61e35f1a9d481c7d2e81305616d70fc371ee459b0b2267d627e928590edcac3231898b24ef378aa9c3d381619f665379be76c7c1bd535505c563db3725f034786e35bdd90429305fd71d7bf680e8cdd6d4c348d97078f5cf5e89dee2dc410fad4f2a30f"
-    ],
-    -- COUNT = 13
-    [ "29e20d724dfa459960df21c6ec76b1e6cabd23a9e9456d6c591d7e4529da0ef8"
-    , "95df1f837eba47a1687aa5c4ddcf8aaf"
-    , "3713b601e164b1a51dda1ca9242ff477514648e90d311a06e10ce5aa15da5d7f"
-    , "2a2a312626ca3e20034fc4f28033c7d573f66ef61ab2ea0c7bf0411a9d247264"
-    , "ec68be33ac8ff3dd127e051604898c0f9a501271859376653a0516336180993d"
-    , "9935499661d699a00c622a875441b4df5204958fe95892c8ce67f7dfb2be3e4a"
-    , "256a4ba9e8f439d5487fa5eb45efcf1bc1120491724db3abe328d951f2739fc9"
-    , "73114cb3624d687d4cd49a6e769dfc7a3f8901dc41f6ad1df4ce480536fa82e52ae958d0528640d92b8bb981b755058e32c4733682e5c4c0df41f3505a1643a0dd49cfdeaf7a18adffca88256c6d2cceb838af6c92a64bc21cb7a760a0391291bfe3575e014fc156323f8eb5e86518c669dad8d29ad5fd4ef6e296f4a0764c26"
-    ],
-    -- COUNT = 14
-    [ "1353f3543eb1134980e061fc4382394975dbc74f1f1ea5ecc02780a813ac5ee6"
-    , "cf584db2447afbe2c8fa0c15575ee391"
-    , "345b0cc016f2765a8c33fc24f1dcfa182cbe29d7eacbcdc9bcda988521458fc2"
-    , "ba60219332a67b95d90ec9de6b8453d4c8af991ae9277461ff3af1b92fc985d3"
-    , "6964b9b9842aec9c7ec2aad926d701f30eec76fe699265ae2a7765d716958069"
-    , "6a03c28a9365c558c33d3fdc7e5ebf0b4d32caac70df71403fd70ced09757528"
-    , "a58546c72a0b4d47c9bd6c19e7cf4ab73b2d7ba36c6c6dc08606f608795ebd29"
-    , "5b029ef68b6799868b04dc28dbea26bc2fa9fcc8c2b2795aafeed0127b7297fa19a4ef2ba60c42ff8259d5a759f92bd90fdfb27145e82d798bb3ab7fd60bfaefb7aefb116ca2a4fa8b01d96a03c47c8d987fdd33c460e560b138891278313bb619d0c3c6f9d7c5a37e88fce83e94943705c6ff68e00484e74ad4097b0c9e5f10"
-    ]
-    ]
diff --git a/test/Network/Haskoin/Crypto/SignatureSpec.hs b/test/Network/Haskoin/Crypto/SignatureSpec.hs
deleted file mode 100644
--- a/test/Network/Haskoin/Crypto/SignatureSpec.hs
+++ /dev/null
@@ -1,165 +0,0 @@
-{-# LANGUAGE OverloadedStrings #-}
-module Network.Haskoin.Crypto.SignatureSpec (spec) where
-
-import           Data.Bits              (testBit)
-import           Data.ByteString        (ByteString)
-import qualified Data.ByteString        as BS (index, length)
-import           Data.Serialize         as S
-import           Data.Text              (Text)
-import           Network.Haskoin.Crypto (SecKey, Sig, decodeStrictSig,
-                                         derivePubKey, exportSig,
-                                         importCompactSig, isCanonicalHalfOrder,
-                                         sha256, signHash, verifyHashSig)
-import           Network.Haskoin.Test   (arbitrarySignature)
-import           Network.Haskoin.Util   (decodeHex, eitherToMaybe, lst3)
-import           Test.Hspec             (Spec, describe, it)
-import           Test.Hspec.QuickCheck  (prop)
-import           Test.HUnit             (Assertion, assertBool)
-import           Test.QuickCheck        (forAll)
-
-spec :: Spec
-spec = do
-    describe "signatures" $ do
-        prop "verify signature" $
-            forAll arbitrarySignature $ \(msg, key, sig) ->
-                verifyHashSig msg sig (derivePubKey key)
-        prop "s component less than half order" $
-            forAll arbitrarySignature $ isCanonicalHalfOrder . lst3
-        prop "encoded signature is canonical" $
-            forAll arbitrarySignature $ testIsCanonical . lst3
-        prop "encode signature and decode strictly" $
-            forAll arbitrarySignature $
-            (\s -> decodeStrictSig (exportSig s) == Just s) . lst3
-        prop "encodes and decodes signature" $
-            forAll arbitrarySignature $
-            (\s -> decodeStrictSig (exportSig s) == Just s) . lst3
-    describe "trezor rfc6979 test vectors" $ do
-        it "rfc6979 test vector 1" (testSigning $ head detVec)
-        it "rfc6979 test vector 2" (testSigning $ detVec !! 1)
-        it "rfc6979 test vector 3" (testSigning $ detVec !! 2)
-        it "rfc6979 test vector 4" (testSigning $ detVec !! 3)
-        it "rfc6979 test vector 5" (testSigning $ detVec !! 4)
-        it "rfc6979 test vector 6" (testSigning $ detVec !! 5)
-        it "rfc6979 test vector 7" (testSigning $ detVec !! 6)
-        it "rfc6979 test vector 8" (testSigning $ detVec !! 7)
-        it "rfc6979 test vector 9" (testSigning $ detVec !! 8)
-        it "rfc6979 test vector 10" (testSigning $ detVec !! 9)
-        it "rfc6979 test vector 11" (testSigning $ detVec !! 10)
-        it "rfc6979 test vector 12" (testSigning $ detVec !! 11)
-
--- No longer validating that generated signature is exactly equal to provided
--- one. Fedora includes libsecp256k1 from Bitcoin ABC that computes
--- deterministic signatures using a slightly different nonce generation
--- algorithm.
-testSigning :: (SecKey, ByteString, Text) -> Assertion
-testSigning (prv, msg, str) = do
-    assertBool "my sig valid" $ verifyHashSig msg' g (derivePubKey prv)
-    assertBool "valid sig" $ verifyHashSig msg' g' (derivePubKey prv)
-  where
-    Just g' = importCompactSig =<< eitherToMaybe . decode =<< decodeHex str
-    g = signHash prv msg'
-    msg' = sha256 msg
-
-
-{- ECDSA Canonical -}
-
--- github.com/bitcoin/bitcoin/blob/master/src/script.cpp
--- from function IsCanonicalSignature
-testIsCanonical :: Sig -> Bool
-testIsCanonical sig = not $
-    -- Non-canonical signature: too short
-    (len < 8) ||
-    -- Non-canonical signature: too long
-    (len > 72) ||
-    -- Non-canonical signature: wrong type
-    (BS.index s 0 /= 0x30) ||
-    -- Non-canonical signature: wrong length marker
-    (BS.index s 1 /= len - 2) ||
-    -- Non-canonical signature: S length misplaced
-    (5 + rlen >= len) ||
-    -- Non-canonical signature: R+S length mismatch
-    (rlen + slen + 6 /= len) ||
-    -- Non-canonical signature: R value type mismatch
-    (BS.index s 2 /= 0x02) ||
-    -- Non-canonical signature: R length is zero
-    (rlen == 0) ||
-    -- Non-canonical signature: R value negative
-    testBit (BS.index s 4) 7 ||
-    -- Non-canonical signature: R value excessively padded
-    (  rlen > 1
-    && BS.index s 4 == 0
-    && not (testBit (BS.index s 5) 7)
-    ) ||
-    -- Non-canonical signature: S value type mismatch
-    (BS.index s (fromIntegral rlen + 4) /= 0x02) ||
-    -- Non-canonical signature: S length is zero
-    (slen == 0) ||
-    -- Non-canonical signature: S value negative
-    testBit (BS.index s (fromIntegral rlen+6)) 7 ||
-    -- Non-canonical signature: S value excessively padded
-    (  slen > 1
-    && BS.index s (fromIntegral rlen + 6) == 0
-    && not (testBit (BS.index s (fromIntegral rlen + 7)) 7)
-    )
-  where
-    s = exportSig sig
-    len = fromIntegral $ BS.length s
-    rlen = BS.index s 3
-    slen = BS.index s (fromIntegral rlen + 5)
-
-
-{- Trezor RFC 6979 Test Vectors -}
--- github.com/trezor/python-ecdsa/blob/master/ecdsa/test_pyecdsa.py
-
-detVec :: [(SecKey, ByteString, Text)]
-detVec =
-    [
-      ( "0000000000000000000000000000000000000000000000000000000000000001"
-      , "Satoshi Nakamoto"
-      , "934b1ea10a4b3c1757e2b0c017d0b6143ce3c9a7e6a4a49860d7a6ab210ee3d82442ce9d2b916064108014783e923ec36b49743e2ffa1c4496f01a512aafd9e5"
-      )
-    , ( "0000000000000000000000000000000000000000000000000000000000000001"
-      , "All those moments will be lost in time, like tears in rain. Time to die..."
-      , "8600dbd41e348fe5c9465ab92d23e3db8b98b873beecd930736488696438cb6b547fe64427496db33bf66019dacbf0039c04199abb0122918601db38a72cfc21"
-      )
-    , ( "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140"
-      , "Satoshi Nakamoto"
-      , "fd567d121db66e382991534ada77a6bd3106f0a1098c231e47993447cd6af2d06b39cd0eb1bc8603e159ef5c20a5c8ad685a45b06ce9bebed3f153d10d93bed5"
-      )
-    , ( "f8b8af8ce3c7cca5e300d33939540c10d45ce001b8f252bfbc57ba0342904181"
-      , "Alan Turing"
-      , "7063ae83e7f62bbb171798131b4a0564b956930092b33b07b395615d9ec7e15c58dfcc1e00a35e1572f366ffe34ba0fc47db1e7189759b9fb233c5b05ab388ea"
-      )
-    , ( "e91671c46231f833a6406ccbea0e3e392c76c167bac1cb013f6f1013980455c2"
-      , "There is a computer disease that anybody who works with computers knows about. It's a very serious disease and it interferes completely with the work. The trouble with computers is that you 'play' with them!"
-      , "b552edd27580141f3b2a5463048cb7cd3e047b97c9f98076c32dbdf85a68718b279fa72dd19bfae05577e06c7c0c1900c371fcd5893f7e1d56a37d30174671f6"
-      )
-    , ( "0000000000000000000000000000000000000000000000000000000000000001"
-      , "Everything should be made as simple as possible, but not simpler."
-      , "33a69cd2065432a30f3d1ce4eb0d59b8ab58c74f27c41a7fdb5696ad4e6108c96f807982866f785d3f6418d24163ddae117b7db4d5fdf0071de069fa54342262"
-      )
-    , ( "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140"
-      , "Equations are more important to me, because politics is for the present, but an equation is something for eternity."
-      , "54c4a33c6423d689378f160a7ff8b61330444abb58fb470f96ea16d99d4a2fed07082304410efa6b2943111b6a4e0aaa7b7db55a07e9861d1fb3cb1f421044a5"
-      )
-    , ( "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140"
-      , "Not only is the Universe stranger than we think, it is stranger than we can think."
-      , "ff466a9f1b7b273e2f4c3ffe032eb2e814121ed18ef84665d0f515360dab3dd06fc95f5132e5ecfdc8e5e6e616cc77151455d46ed48f5589b7db7771a332b283"
-      )
-    , ( "0000000000000000000000000000000000000000000000000000000000000001"
-      , "How wonderful that we have met with a paradox. Now we have some hope of making progress."
-      , "c0dafec8251f1d5010289d210232220b03202cba34ec11fec58b3e93a85b91d375afdc06b7d6322a590955bf264e7aaa155847f614d80078a90292fe205064d3"
-      )
-    , ( "69ec59eaa1f4f2e36b639716b7c30ca86d9a5375c7b38d8918bd9c0ebc80ba64"
-      , "Computer science is no more about computers than astronomy is about telescopes."
-      , "7186363571d65e084e7f02b0b77c3ec44fb1b257dee26274c38c928986fea45d0de0b38e06807e46bda1f1e293f4f6323e854c86d58abdd00c46c16441085df6"
-      )
-    , ( "00000000000000000000000000007246174ab1e92e9149c6e446fe194d072637"
-      , "...if you aren't, at any given time, scandalized by code you wrote five or even three years ago, you're not learning anywhere near enough"
-      , "fbfe5076a15860ba8ed00e75e9bd22e05d230f02a936b653eb55b61c99dda4870e68880ebb0050fe4312b1b1eb0899e1b82da89baa5b895f612619edf34cbd37"
-      )
-    , ( "000000000000000000000000000000000000000000056916d0f9b31dc9b637f3"
-      , "The question of whether computers can think is like the question of whether submarines can swim."
-      , "cde1302d83f8dd835d89aef803c74a119f561fbaef3eb9129e45f30de86abbf906ce643f5049ee1f27890467b77a6a8e11ec4661cc38cd8badf90115fbd03cef"
-      )
-    ]
diff --git a/test/Network/Haskoin/CryptoSpec.hs b/test/Network/Haskoin/CryptoSpec.hs
deleted file mode 100644
--- a/test/Network/Haskoin/CryptoSpec.hs
+++ /dev/null
@@ -1,165 +0,0 @@
-{-# LANGUAGE OverloadedStrings #-}
-module Network.Haskoin.CryptoSpec (spec) where
-
-import           Control.Monad
-import           Data.ByteString           (ByteString)
-import qualified Data.ByteString.Char8     as C (pack)
-import           Data.Maybe                (fromMaybe, isJust, isNothing)
-import           Data.Text                 (Text)
-import           Network.Haskoin.Address
-import           Network.Haskoin.Constants
-import           Network.Haskoin.Crypto
-import           Network.Haskoin.Keys
-import           Network.Haskoin.Util
-import           Test.Hspec
-import           Test.HUnit                (Assertion, assertBool)
-
--- Unit tests copied from bitcoind implementation
--- https://github.com/bitcoin/bitcoin/blob/master/src/test/key_tests.cpp
-
-strSecret1 :: Text
-strSecret1  = "5HxWvvfubhXpYYpS3tJkw6fq9jE9j18THftkZjHHfmFiWtmAbrj"
-
-strSecret2 :: Text
-strSecret2  = "5KC4ejrDjv152FGwP386VD1i2NYc5KkfSMyv1nGy1VGDxGHqVY3"
-
-strSecret1C :: Text
-strSecret1C = "Kwr371tjA9u2rFSMZjTNun2PXXP3WPZu2afRHTcta6KxEUdm1vEw"
-
-strSecret2C :: Text
-strSecret2C = "L3Hq7a8FEQwJkW1M2GNKDW28546Vp5miewcCzSqUD9kCAXrJdS3g"
-
-addr1 :: Text
-addr1  = "1QFqqMUD55ZV3PJEJZtaKCsQmjLT6JkjvJ"
-
-addr2 :: Text
-addr2  = "1F5y5E5FMc5YzdJtB9hLaUe43GDxEKXENJ"
-
-addr1C :: Text
-addr1C = "1NoJrossxPBKfCHuJXT4HadJrXRE9Fxiqs"
-
-addr2C :: Text
-addr2C = "1CRj2HyM1CXWzHAXLQtiGLyggNT9WQqsDs"
-
-strAddressBad :: Text
-strAddressBad = "1HV9Lc3sNHZxwj4Zk6fB38tEmBryq2cBiF"
-
-sigMsg :: [ByteString]
-sigMsg =
-    [ mconcat ["Very secret message ", C.pack (show (i :: Int)), ": 11"]
-    | i <- [0..15]
-    ]
-
-sec1 :: SecKeyI
-sec1 =
-    fromMaybe (error "Could not decode WIF secret 1") (fromWif btc strSecret1)
-
-sec2 :: SecKeyI
-sec2 =
-    fromMaybe (error "Could not decode WIF secret 2") (fromWif btc strSecret2)
-
-sec1C :: SecKeyI
-sec1C =
-    fromMaybe
-        (error "Could not decode WIF compressed secret 1")
-        (fromWif btc strSecret1C)
-
-sec2C :: SecKeyI
-sec2C =
-    fromMaybe
-        (error "Could not decode WIF compressed secret 2")
-        (fromWif btc strSecret2C)
-
-pub1 :: PubKeyI
-pub1  = derivePubKeyI sec1
-
-pub2 :: PubKeyI
-pub2  = derivePubKeyI sec2
-
-pub1C :: PubKeyI
-pub1C = derivePubKeyI sec1C
-
-pub2C :: PubKeyI
-pub2C = derivePubKeyI sec2C
-
-spec :: Spec
-spec =
-    describe "bitcoind /src/test/key_tests.cpp" $ do
-        it "decode valid wif" checkPrivkey
-        it "decode invalid wif" checkInvalidKey
-        it "decode minikey format" checkMiniKey
-        it "check private key compression" checkPrvKeyCompressed
-        it "check public key compression" checkKeyCompressed
-        it "check matching address" checkMatchingAddress
-        it "check various signatures" sigCheck
-
-sigCheck :: Assertion
-sigCheck = forM_ sigMsg $ checkSignatures . doubleSHA256
-
-{- bitcoind /src/test/key_tests.cpp -}
-
-checkPrivkey :: Assertion
-checkPrivkey = do
-    assertBool "Key 1"  $ isJust $ fromWif btc strSecret1
-    assertBool "Key 2"  $ isJust $ fromWif btc strSecret2
-    assertBool "Key 1C" $ isJust $ fromWif btc strSecret1C
-    assertBool "Key 2C" $ isJust $ fromWif btc strSecret2C
-
-checkInvalidKey :: Assertion
-checkInvalidKey =
-    assertBool "Bad key" $ isNothing $ fromWif btc strAddressBad
-
-checkMiniKey :: Assertion
-checkMiniKey =
-    assertBool "Bad mini key" $
-    isJust res && fromMiniKey "S6c56bnXQiBjk9mqSYE7ykVQ7NzrRy" == res
-  where
-    res = do
-        bs <-
-            decodeHex
-                "4C7A9640C72DC2099F23715D0C8A0D8A35F8906E3CAB61DD3F78B67BF887C9AB"
-        wrapSecKey False <$> secKey bs
-
-checkPrvKeyCompressed :: Assertion
-checkPrvKeyCompressed = do
-    assertBool "Key 1"  $ not $ secKeyCompressed sec1
-    assertBool "Key 2"  $ not $ secKeyCompressed sec2
-    assertBool "Key 1C" $ secKeyCompressed sec1C
-    assertBool "Key 2C" $ secKeyCompressed sec2C
-
-checkKeyCompressed :: Assertion
-checkKeyCompressed = do
-    assertBool "Key 1"  $ not $ pubKeyCompressed pub1
-    assertBool "Key 2"  $ not $ pubKeyCompressed pub2
-    assertBool "Key 1C" $ pubKeyCompressed pub1C
-    assertBool "Key 2C" $ pubKeyCompressed pub2C
-
-checkMatchingAddress :: Assertion
-checkMatchingAddress = do
-    assertBool "Key 1"  $ Just addr1  == addrToString btc (pubKeyAddr pub1)
-    assertBool "Key 2"  $ Just addr2  == addrToString btc (pubKeyAddr pub2)
-    assertBool "Key 1C" $ Just addr1C == addrToString btc (pubKeyAddr pub1C)
-    assertBool "Key 2C" $ Just addr2C == addrToString btc (pubKeyAddr pub2C)
-
-checkSignatures :: Hash256 -> Assertion
-checkSignatures h = do
-    let sign1  = signHash (secKeyData sec1) h
-        sign2  = signHash (secKeyData sec2) h
-        sign1C = signHash (secKeyData sec1C) h
-        sign2C = signHash (secKeyData sec2C) h
-    assertBool "Key 1, Sign1"   $ verifyHashSig h sign1 (pubKeyPoint pub1)
-    assertBool "Key 1, Sign2"   $ not $ verifyHashSig h sign2 (pubKeyPoint pub1)
-    assertBool "Key 1, Sign1C"  $ verifyHashSig h sign1C (pubKeyPoint pub1)
-    assertBool "Key 1, Sign2C"  $ not $ verifyHashSig h sign2C (pubKeyPoint pub1)
-    assertBool "Key 2, Sign1"   $ not $ verifyHashSig h sign1 (pubKeyPoint pub2)
-    assertBool "Key 2, Sign2"   $ verifyHashSig h sign2 (pubKeyPoint pub2)
-    assertBool "Key 2, Sign1C"  $ not $ verifyHashSig h sign1C (pubKeyPoint pub2)
-    assertBool "Key 2, Sign2C"  $ verifyHashSig h sign2C (pubKeyPoint pub2)
-    assertBool "Key 1C, Sign1"  $ verifyHashSig h sign1 (pubKeyPoint pub1C)
-    assertBool "Key 1C, Sign2"  $ not $ verifyHashSig h sign2 (pubKeyPoint pub1C)
-    assertBool "Key 1C, Sign1C" $ verifyHashSig h sign1C (pubKeyPoint pub1C)
-    assertBool "Key 1C, Sign2C" $ not $ verifyHashSig h sign2C (pubKeyPoint pub1C)
-    assertBool "Key 2C, Sign1"  $ not $ verifyHashSig h sign1 (pubKeyPoint pub2C)
-    assertBool "Key 2C, Sign2"  $ verifyHashSig h sign2 (pubKeyPoint pub2C)
-    assertBool "Key 2C, Sign1C" $ not $ verifyHashSig h sign1C (pubKeyPoint pub2C)
-    assertBool "Key 2C, Sign2C" $ verifyHashSig h sign2C (pubKeyPoint pub2C)
diff --git a/test/Network/Haskoin/Keys/ExtendedSpec.hs b/test/Network/Haskoin/Keys/ExtendedSpec.hs
deleted file mode 100644
--- a/test/Network/Haskoin/Keys/ExtendedSpec.hs
+++ /dev/null
@@ -1,521 +0,0 @@
-{-# LANGUAGE OverloadedStrings #-}
-module Network.Haskoin.Keys.ExtendedSpec (spec) where
-
-import           Data.Aeson                 as A
-import           Data.Aeson.Types           as A
-import           Data.Bits                  ((.&.))
-import qualified Data.ByteString.Lazy.Char8 as B8
-import           Data.Either                (isLeft)
-import           Data.Map.Strict            (singleton)
-import           Data.Maybe                 (fromJust, isJust, isNothing)
-import           Data.Serialize             as S
-import           Data.String                (fromString)
-import           Data.String.Conversions    (cs)
-import           Data.Text                  (Text)
-import           Data.Word                  (Word32)
-import           Network.Haskoin.Address
-import           Network.Haskoin.Constants
-import           Network.Haskoin.Keys
-import           Network.Haskoin.Test
-import           Network.Haskoin.Util
-import           Test.Hspec
-import           Test.HUnit                 (Assertion, assertBool, assertEqual)
-import           Test.QuickCheck            hiding ((.&.))
-
-spec :: Spec
-spec = do
-    describe "bip32 derivation vector 1" $ do
-        it "chain m" $ runXKeyVec (head xKeyVec)
-        it "chain m/0'" $ runXKeyVec (xKeyVec !! 1)
-        it "chain m/0'/1" $ runXKeyVec (xKeyVec !! 2)
-        it "chain m/0'/1/2'" $ runXKeyVec (xKeyVec !! 3)
-        it "chain m/0'/1/2'/2" $ runXKeyVec (xKeyVec !! 4)
-        it "chain m/0'/1/2'/2/1000000000" $ runXKeyVec (xKeyVec !! 5)
-    describe "bip32 subkey derivation vector 2" $ do
-        it "chain m" $ runXKeyVec (head xKeyVec2)
-        it "chain m/0" $ runXKeyVec (xKeyVec2 !! 1)
-        it "chain m/0/2147483647'" $ runXKeyVec (xKeyVec2 !! 2)
-        it "chain m/0/2147483647'/1" $ runXKeyVec (xKeyVec2 !! 3)
-        it "chain m/0/2147483647'/1/2147483646'" $ runXKeyVec (xKeyVec2 !! 4)
-        it "Chain m/0/2147483647'/1/2147483646'/2" $ runXKeyVec (xKeyVec2 !! 5)
-    describe "bip32 subkey derivation using string path" $ do
-        it "either derivations" testApplyPath
-        it "either derivations" testBadApplyPath
-        it "dublic derivations" testDerivePubPath
-        it "private derivations" testDerivePrvPath
-        it "path parsing" testParsePath
-        it "from json" testFromJsonPath
-        it "to json" testToJsonPath
-    describe "extended keys" $ do
-        let net = btc
-        it "computes pubkey of a subkey is subkey of the pubkey" $
-            property $ forAll arbitraryXPrvKey pubKeyOfSubKeyIsSubKeyOfPubKey
-        it "exports and imports extended private key" $
-            property $
-            forAll arbitraryXPrvKey $ \k ->
-                xPrvImport net (xPrvExport net k) == Just k
-        it "exports and imports extended public key" $
-            property $
-            forAll arbitraryXPubKey $ \(_, k) ->
-                xPubImport net (xPubExport net k) == Just k
-        it "show and read derivation path" $
-            property $ forAll arbitraryDerivPath $ \p -> read (show p) == p
-        it "show and read hard derivation path" $
-            property $ forAll arbitraryHardPath $ \p -> read (show p) == p
-        it "show and read soft derivation path" $
-            property $ forAll arbitrarySoftPath $ \p -> read (show p) == p
-        it "from string derivation path" $
-            property $
-            forAll arbitraryDerivPath $ \p -> fromString (cs $ pathToStr p) == p
-        it "from string hard derivation path" $
-            property $
-            forAll arbitraryHardPath $ \p -> fromString (cs $ pathToStr p) == p
-        it "from string soft derivation path" $
-            property $
-            forAll arbitrarySoftPath $ \p -> fromString (cs $ pathToStr p) == p
-        it "from and to lists of derivation paths" $
-            property $
-            forAll arbitraryDerivPath $ \p -> listToPath (pathToList p) == p
-        it "from and to lists of hard derivation paths" $
-            property $
-            forAll arbitraryHardPath $ \p ->
-                toHard (listToPath $ pathToList p) == Just p
-        it "from and to lists of soft derivation paths" $
-            property $
-            forAll arbitrarySoftPath $ \p ->
-                toSoft (listToPath $ pathToList p) == Just p
-        it "read and show parsed path" $
-            property $ forAll arbitraryParsedPath $ \p -> read (show p) == p
-        it "encodes and decodes extended private key" $
-            forAll
-                arbitraryXPrvKey
-                (testCustom (xPrvFromJSON net) (xPrvToJSON net))
-        it "encodes and decodes extended public key" $
-            forAll
-                arbitraryXPubKey
-                (testCustom (xPubFromJSON net) (xPubToJSON net) . snd)
-        it "encodes and decodes derivation path" $
-            forAll arbitraryDerivPath testID
-        it "encodes and decodes parsed derivation path" $
-            forAll arbitraryParsedPath testID
-        it "encodes and decodes extended private key" $
-            property $
-            forAll arbitraryXPrvKey $
-            testPutGet (getXPrvKey net) (putXPrvKey net)
-        it "shows and reads extended private key" $
-            property $
-            forAll arbitraryXPrvKey $ \k -> read (show k) `shouldBe` k
-        it "shows and reads extended private key" $
-            property $
-            forAll arbitraryXPubKey $ \(prv, pub) ->
-                read (show (prv, pub)) `shouldBe` (prv, pub)
-
-testFromJsonPath :: Assertion
-testFromJsonPath =
-    sequence_ $ do
-        path <- jsonPathVectors
-        return $
-            assertEqual
-                path
-                (Just [fromString path :: DerivPath])
-                (A.decode $ B8.pack $ "[\"" ++ path ++ "\"]")
-
-testToJsonPath :: Assertion
-testToJsonPath =
-    sequence_ $ do
-        path <- jsonPathVectors
-        return $
-            assertEqual
-                path
-                (B8.pack $ "[\"" ++ path ++ "\"]")
-                (A.encode [fromString path :: ParsedPath])
-
-jsonPathVectors :: [String]
-jsonPathVectors =
-    [ "m"
-    , "m/0"
-    , "m/0'"
-    , "M/0'"
-    , "m/2147483647"
-    , "M/2147483647"
-    , "m/1/2/3/4/5/6/7/8"
-    , "M/1/2/3/4/5/6/7/8"
-    , "m/1'/2'/3/4"
-    , "M/1'/2'/3/4"
-    ]
-
-testParsePath :: Assertion
-testParsePath =
-    sequence_ $ do
-        (path, t) <- parsePathVectors
-        return $ assertBool path (t $ parsePath path)
-
-parsePathVectors :: [(String, Maybe ParsedPath -> Bool)]
-parsePathVectors =
-    [ ("m", isJust)
-    , ("m/0'", isJust)
-    , ("M/0'", isJust)
-    , ("m/2147483648", isNothing)
-    , ("m/2147483647", isJust)
-    , ("M/2147483648", isNothing)
-    , ("M/2147483647", isJust)
-    , ("M/-1", isNothing)
-    , ("M/-2147483648", isNothing)
-    , ("m/1/2/3/4/5/6/7/8", isJust)
-    , ("M/1/2/3/4/5/6/7/8", isJust)
-    , ("m/1'/2'/3/4", isJust)
-    , ("M/1'/2'/3/4", isJust)
-    , ("m/1/2'/3/4'", isJust)
-    , ("M/1/2'/3/4'", isJust)
-    , ("meh", isNothing)
-    , ("infinity", isNothing)
-    , ("NaN", isNothing)
-    ]
-
-testApplyPath :: Assertion
-testApplyPath =
-    sequence_ $ do
-        (key, path, final) <- applyPathVectors
-        return $
-            assertEqual path final $ applyPath (fromJust $ parsePath path) key
-
-testBadApplyPath :: Assertion
-testBadApplyPath =
-    sequence_ $ do
-        (key, path) <- badApplyPathVectors
-        return $
-            assertBool path $ isLeft $ applyPath (fromJust $ parsePath path) key
-
-testDerivePubPath :: Assertion
-testDerivePubPath =
-    sequence_ $ do
-        (key, path, final) <- derivePubPathVectors
-        return $
-            assertEqual path final $
-            derivePubPath (fromString path :: SoftPath) key
-
-testDerivePrvPath :: Assertion
-testDerivePrvPath =
-    sequence_ $ do
-        (key, path, final) <- derivePrvPathVectors
-        return $
-            assertEqual path final $
-            derivePath (fromString path :: DerivPath) key
-
-derivePubPathVectors :: [(XPubKey, String, XPubKey)]
-derivePubPathVectors =
-    [ ( xpub, "M", xpub )
-    , ( xpub, "M/8", pubSubKey xpub 8 )
-    , ( xpub, "M/8/30/1", foldl pubSubKey xpub [8,30,1] )
-    ]
-  where
-    xprv = fromJust $ xPrvImport btc
-        "xprv9s21ZrQH143K46iDVRSyFfGfMgQjzC4BV3ZUfNbG7PHQrJjE53ofAn5gYkp6KQ\
-        \WzGmb8oageSRxBY8s4rjr9VXPVp2HQDbwPt4H31Gg4LpB"
-    xpub = deriveXPubKey xprv
-
-derivePrvPathVectors :: [(XPrvKey, String, XPrvKey)]
-derivePrvPathVectors =
-    [ ( xprv, "m", xprv )
-    , ( xprv, "M", xprv )
-    , ( xprv, "m/8'", hardSubKey xprv 8 )
-    , ( xprv, "M/8'", hardSubKey xprv 8 )
-    , ( xprv, "m/8'/30/1"
-      , foldl prvSubKey (hardSubKey xprv 8) [30,1]
-      )
-    , ( xprv, "M/8'/30/1"
-      , foldl prvSubKey (hardSubKey xprv 8) [30,1]
-      )
-    , ( xprv, "m/3/20"
-      , foldl prvSubKey xprv [3,20]
-      )
-    , ( xprv, "M/3/20"
-      , foldl prvSubKey xprv [3,20]
-      )
-    ]
-  where
-    xprv = fromJust $ xPrvImport btc
-        "xprv9s21ZrQH143K46iDVRSyFfGfMgQjzC4BV3ZUfNbG7PHQrJjE53ofAn5gYkp6KQ\
-        \WzGmb8oageSRxBY8s4rjr9VXPVp2HQDbwPt4H31Gg4LpB"
-
-applyPathVectors :: [(XKey, String, Either String XKey)]
-applyPathVectors =
-    [ (XPrv xprv btc, "m", Right (XPrv xprv btc))
-    , (XPrv xprv btc, "M", Right (XPub xpub btc))
-    , (XPrv xprv btc, "m/8'", Right (XPrv (hardSubKey xprv 8) btc))
-    , ( XPrv xprv btc
-      , "M/8'"
-      , Right (XPub (deriveXPubKey (hardSubKey xprv 8)) btc))
-    , ( XPrv xprv btc
-      , "m/8'/30/1"
-      , Right (XPrv (foldl prvSubKey (hardSubKey xprv 8) [30, 1]) btc))
-    , ( XPrv xprv btc
-      , "M/8'/30/1"
-      , Right
-            (XPub
-                 (deriveXPubKey (foldl prvSubKey (hardSubKey xprv 8) [30, 1]))
-                 btc))
-    , (XPrv xprv btc, "m/3/20", Right (XPrv (foldl prvSubKey xprv [3, 20]) btc))
-    , ( XPrv xprv btc
-      , "M/3/20"
-      , Right (XPub (deriveXPubKey (foldl prvSubKey xprv [3, 20])) btc))
-    , ( XPub xpub btc
-      , "M/3/20"
-      , Right (XPub (deriveXPubKey (foldl prvSubKey xprv [3, 20])) btc))
-    ]
-  where
-    xprv =
-        fromJust $
-        xPrvImport
-            btc
-            "xprv9s21ZrQH143K46iDVRSyFfGfMgQjzC4BV3ZUfNbG7PHQrJjE53ofAn5gYkp6KQ\
-        \WzGmb8oageSRxBY8s4rjr9VXPVp2HQDbwPt4H31Gg4LpB"
-    xpub = deriveXPubKey xprv
-
-badApplyPathVectors :: [(XKey, String)]
-badApplyPathVectors =
-    [ (XPub xpub btc, "m/8'")
-    , (XPub xpub btc, "M/8'")
-    , (XPub xpub btc, "M/1/2/3'/4/5")
-    ]
-  where
-    xprv =
-        fromJust $
-        xPrvImport
-            btc
-            "xprv9s21ZrQH143K46iDVRSyFfGfMgQjzC4BV3ZUfNbG7PHQrJjE53ofAn5gYkp6KQ\
-        \WzGmb8oageSRxBY8s4rjr9VXPVp2HQDbwPt4H31Gg4LpB"
-    xpub = deriveXPubKey xprv
-
-
-runXKeyVec :: ([Text], XPrvKey) -> Assertion
-runXKeyVec (v, m) = do
-    assertBool "xPrvID" $ encodeHex (S.encode $ xPrvID m) == head v
-    assertBool "xPrvFP" $ encodeHex (S.encode $ xPrvFP m) == v !! 1
-    assertBool "xPrvAddr" $
-        addrToString btc (xPubAddr $ deriveXPubKey m) == Just (v !! 2)
-    assertBool "prvKey" $ encodeHex (getSecKey $ xPrvKey m) == v !! 3
-    assertBool "xPrvWIF" $ xPrvWif btc m == v !! 4
-    assertBool "pubKey" $
-        encodeHex (exportPubKey True $ xPubKey $ deriveXPubKey m) == v !! 5
-    assertBool "chain code" $ encodeHex (S.encode $ xPrvChain m) == v !! 6
-    assertBool "Hex PubKey" $
-        encodeHex (runPut $ putXPubKey btc $ deriveXPubKey m) == v !! 7
-    assertBool "Hex PrvKey" $ encodeHex (runPut (putXPrvKey btc m)) == v !! 8
-    assertBool "Base58 PubKey" $ xPubExport btc (deriveXPubKey m) == v !! 9
-    assertBool "Base58 PrvKey" $ xPrvExport btc m == v !! 10
-
--- BIP 0032 Test Vectors
--- https://en.bitcoin.it/wiki/BIP_0032_TestVectors
-
-xKeyVec :: [([Text], XPrvKey)]
-xKeyVec = zip xKeyResVec $ foldl f [m] der
-    where f acc d = acc ++ [d $ last acc]
-          m   = makeXPrvKey $ fromJust $ decodeHex m0
-          der = [ flip hardSubKey 0
-                , flip prvSubKey 1
-                , flip hardSubKey 2
-                , flip prvSubKey 2
-                , flip prvSubKey 1000000000
-                ]
-
-xKeyVec2 :: [([Text], XPrvKey)]
-xKeyVec2 = zip xKeyResVec2 $ foldl f [m] der
-    where f acc d = acc ++ [d $ last acc]
-          m   = makeXPrvKey $ fromJust $ decodeHex m1
-          der = [ flip prvSubKey 0
-                , flip hardSubKey 2147483647
-                , flip prvSubKey 1
-                , flip hardSubKey 2147483646
-                , flip prvSubKey 2
-                ]
-
-m0 :: Text
-m0 = "000102030405060708090a0b0c0d0e0f"
-
-xKeyResVec :: [[Text]]
-xKeyResVec =
-    [
-      -- m
-      [ "3442193e1bb70916e914552172cd4e2dbc9df811"
-      , "3442193e"
-      , "15mKKb2eos1hWa6tisdPwwDC1a5J1y9nma"
-      , "e8f32e723decf4051aefac8e2c93c9c5b214313817cdb01a1494b917c8436b35"
-      , "L52XzL2cMkHxqxBXRyEpnPQZGUs3uKiL3R11XbAdHigRzDozKZeW"
-      , "0339a36013301597daef41fbe593a02cc513d0b55527ec2df1050e2e8ff49c85c2"
-      , "873dff81c02f525623fd1fe5167eac3a55a049de3d314bb42ee227ffed37d508"
-      , "0488b21e000000000000000000873dff81c02f525623fd1fe5167eac3a55a049de3d314bb42ee227ffed37d5080339a36013301597daef41fbe593a02cc513d0b55527ec2df1050e2e8ff49c85c2"
-      , "0488ade4000000000000000000873dff81c02f525623fd1fe5167eac3a55a049de3d314bb42ee227ffed37d50800e8f32e723decf4051aefac8e2c93c9c5b214313817cdb01a1494b917c8436b35"
-      , "xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8"
-      , "xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi"
-      ]
-      -- m/0'
-    , [ "5c1bd648ed23aa5fd50ba52b2457c11e9e80a6a7"
-      , "5c1bd648"
-      , "19Q2WoS5hSS6T8GjhK8KZLMgmWaq4neXrh"
-      , "edb2e14f9ee77d26dd93b4ecede8d16ed408ce149b6cd80b0715a2d911a0afea"
-      , "L5BmPijJjrKbiUfG4zbiFKNqkvuJ8usooJmzuD7Z8dkRoTThYnAT"
-      , "035a784662a4a20a65bf6aab9ae98a6c068a81c52e4b032c0fb5400c706cfccc56"
-      , "47fdacbd0f1097043b78c63c20c34ef4ed9a111d980047ad16282c7ae6236141"
-      , "0488b21e013442193e8000000047fdacbd0f1097043b78c63c20c34ef4ed9a111d980047ad16282c7ae6236141035a784662a4a20a65bf6aab9ae98a6c068a81c52e4b032c0fb5400c706cfccc56"
-      , "0488ade4013442193e8000000047fdacbd0f1097043b78c63c20c34ef4ed9a111d980047ad16282c7ae623614100edb2e14f9ee77d26dd93b4ecede8d16ed408ce149b6cd80b0715a2d911a0afea"
-      , "xpub68Gmy5EdvgibQVfPdqkBBCHxA5htiqg55crXYuXoQRKfDBFA1WEjWgP6LHhwBZeNK1VTsfTFUHCdrfp1bgwQ9xv5ski8PX9rL2dZXvgGDnw"
-      , "xprv9uHRZZhk6KAJC1avXpDAp4MDc3sQKNxDiPvvkX8Br5ngLNv1TxvUxt4cV1rGL5hj6KCesnDYUhd7oWgT11eZG7XnxHrnYeSvkzY7d2bhkJ7"
-      ]
-      -- m/0'/1
-    , [ "bef5a2f9a56a94aab12459f72ad9cf8cf19c7bbe"
-      , "bef5a2f9"
-      , "1JQheacLPdM5ySCkrZkV66G2ApAXe1mqLj"
-      , "3c6cb8d0f6a264c91ea8b5030fadaa8e538b020f0a387421a12de9319dc93368"
-      , "KyFAjQ5rgrKvhXvNMtFB5PCSKUYD1yyPEe3xr3T34TZSUHycXtMM"
-      , "03501e454bf00751f24b1b489aa925215d66af2234e3891c3b21a52bedb3cd711c"
-      , "2a7857631386ba23dacac34180dd1983734e444fdbf774041578e9b6adb37c19"
-      , "0488b21e025c1bd648000000012a7857631386ba23dacac34180dd1983734e444fdbf774041578e9b6adb37c1903501e454bf00751f24b1b489aa925215d66af2234e3891c3b21a52bedb3cd711c"
-      , "0488ade4025c1bd648000000012a7857631386ba23dacac34180dd1983734e444fdbf774041578e9b6adb37c19003c6cb8d0f6a264c91ea8b5030fadaa8e538b020f0a387421a12de9319dc93368"
-      , "xpub6ASuArnXKPbfEwhqN6e3mwBcDTgzisQN1wXN9BJcM47sSikHjJf3UFHKkNAWbWMiGj7Wf5uMash7SyYq527Hqck2AxYysAA7xmALppuCkwQ"
-      , "xprv9wTYmMFdV23N2TdNG573QoEsfRrWKQgWeibmLntzniatZvR9BmLnvSxqu53Kw1UmYPxLgboyZQaXwTCg8MSY3H2EU4pWcQDnRnrVA1xe8fs"
-      ]
-      -- m/0'/1/2'
-    , [ "ee7ab90cde56a8c0e2bb086ac49748b8db9dce72"
-      , "ee7ab90c"
-      , "1NjxqbA9aZWnh17q1UW3rB4EPu79wDXj7x"
-      , "cbce0d719ecf7431d88e6a89fa1483e02e35092af60c042b1df2ff59fa424dca"
-      , "L43t3od1Gh7Lj55Bzjj1xDAgJDcL7YFo2nEcNaMGiyRZS1CidBVU"
-      , "0357bfe1e341d01c69fe5654309956cbea516822fba8a601743a012a7896ee8dc2"
-      , "04466b9cc8e161e966409ca52986c584f07e9dc81f735db683c3ff6ec7b1503f"
-      , "0488b21e03bef5a2f98000000204466b9cc8e161e966409ca52986c584f07e9dc81f735db683c3ff6ec7b1503f0357bfe1e341d01c69fe5654309956cbea516822fba8a601743a012a7896ee8dc2"
-      , "0488ade403bef5a2f98000000204466b9cc8e161e966409ca52986c584f07e9dc81f735db683c3ff6ec7b1503f00cbce0d719ecf7431d88e6a89fa1483e02e35092af60c042b1df2ff59fa424dca"
-      , "xpub6D4BDPcP2GT577Vvch3R8wDkScZWzQzMMUm3PWbmWvVJrZwQY4VUNgqFJPMM3No2dFDFGTsxxpG5uJh7n7epu4trkrX7x7DogT5Uv6fcLW5"
-      , "xprv9z4pot5VBttmtdRTWfWQmoH1taj2axGVzFqSb8C9xaxKymcFzXBDptWmT7FwuEzG3ryjH4ktypQSAewRiNMjANTtpgP4mLTj34bhnZX7UiM"
-      ]
-      -- m/0'/1/2'/2
-    , [ "d880d7d893848509a62d8fb74e32148dac68412f"
-      , "d880d7d8"
-      , "1LjmJcdPnDHhNTUgrWyhLGnRDKxQjoxAgt"
-      , "0f479245fb19a38a1954c5c7c0ebab2f9bdfd96a17563ef28a6a4b1a2a764ef4"
-      , "KwjQsVuMjbCP2Zmr3VaFaStav7NvevwjvvkqrWd5Qmh1XVnCteBR"
-      , "02e8445082a72f29b75ca48748a914df60622a609cacfce8ed0e35804560741d29"
-      , "cfb71883f01676f587d023cc53a35bc7f88f724b1f8c2892ac1275ac822a3edd"
-      , "0488b21e04ee7ab90c00000002cfb71883f01676f587d023cc53a35bc7f88f724b1f8c2892ac1275ac822a3edd02e8445082a72f29b75ca48748a914df60622a609cacfce8ed0e35804560741d29"
-      , "0488ade404ee7ab90c00000002cfb71883f01676f587d023cc53a35bc7f88f724b1f8c2892ac1275ac822a3edd000f479245fb19a38a1954c5c7c0ebab2f9bdfd96a17563ef28a6a4b1a2a764ef4"
-      , "xpub6FHa3pjLCk84BayeJxFW2SP4XRrFd1JYnxeLeU8EqN3vDfZmbqBqaGJAyiLjTAwm6ZLRQUMv1ZACTj37sR62cfN7fe5JnJ7dh8zL4fiyLHV"
-      , "xprvA2JDeKCSNNZky6uBCviVfJSKyQ1mDYahRjijr5idH2WwLsEd4Hsb2Tyh8RfQMuPh7f7RtyzTtdrbdqqsunu5Mm3wDvUAKRHSC34sJ7in334"
-      ]
-      -- m/0'/1/2'/2/1000000000
-    , [ "d69aa102255fed74378278c7812701ea641fdf32"
-      , "d69aa102"
-      , "1LZiqrop2HGR4qrH1ULZPyBpU6AUP49Uam"
-      , "471b76e389e528d6de6d816857e012c5455051cad6660850e58372a6c3e6e7c8"
-      , "Kybw8izYevo5xMh1TK7aUr7jHFCxXS1zv8p3oqFz3o2zFbhRXHYs"
-      , "022a471424da5e657499d1ff51cb43c47481a03b1e77f951fe64cec9f5a48f7011"
-      , "c783e67b921d2beb8f6b389cc646d7263b4145701dadd2161548a8b078e65e9e"
-      , "0488b21e05d880d7d83b9aca00c783e67b921d2beb8f6b389cc646d7263b4145701dadd2161548a8b078e65e9e022a471424da5e657499d1ff51cb43c47481a03b1e77f951fe64cec9f5a48f7011"
-      , "0488ade405d880d7d83b9aca00c783e67b921d2beb8f6b389cc646d7263b4145701dadd2161548a8b078e65e9e00471b76e389e528d6de6d816857e012c5455051cad6660850e58372a6c3e6e7c8"
-      , "xpub6H1LXWLaKsWFhvm6RVpEL9P4KfRZSW7abD2ttkWP3SSQvnyA8FSVqNTEcYFgJS2UaFcxupHiYkro49S8yGasTvXEYBVPamhGW6cFJodrTHy"
-      , "xprvA41z7zogVVwxVSgdKUHDy1SKmdb533PjDz7J6N6mV6uS3ze1ai8FHa8kmHScGpWmj4WggLyQjgPie1rFSruoUihUZREPSL39UNdE3BBDu76"
-      ]
-    ]
-
-m1 :: Text
-m1 = "fffcf9f6f3f0edeae7e4e1dedbd8d5d2cfccc9c6c3c0bdbab7b4b1aeaba8a5a29f9c999693908d8a8784817e7b7875726f6c696663605d5a5754514e4b484542"
-
-xKeyResVec2 :: [[Text]]
-xKeyResVec2 =
-    [
-      -- m
-      [ "bd16bee53961a47d6ad888e29545434a89bdfe95"
-      , "bd16bee5"
-      , "1JEoxevbLLG8cVqeoGKQiAwoWbNYSUyYjg"
-      , "4b03d6fc340455b363f51020ad3ecca4f0850280cf436c70c727923f6db46c3e"
-      , "KyjXhyHF9wTphBkfpxjL8hkDXDUSbE3tKANT94kXSyh6vn6nKaoy"
-      , "03cbcaa9c98c877a26977d00825c956a238e8dddfbd322cce4f74b0b5bd6ace4a7"
-      , "60499f801b896d83179a4374aeb7822aaeaceaa0db1f85ee3e904c4defbd9689"
-      , "0488b21e00000000000000000060499f801b896d83179a4374aeb7822aaeaceaa0db1f85ee3e904c4defbd968903cbcaa9c98c877a26977d00825c956a238e8dddfbd322cce4f74b0b5bd6ace4a7"
-      , "0488ade400000000000000000060499f801b896d83179a4374aeb7822aaeaceaa0db1f85ee3e904c4defbd9689004b03d6fc340455b363f51020ad3ecca4f0850280cf436c70c727923f6db46c3e"
-      , "xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB"
-      , "xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U"
-      ]
-      -- m/0
-    , [ "5a61ff8eb7aaca3010db97ebda76121610b78096"
-      , "5a61ff8e"
-      , "19EuDJdgfRkwCmRzbzVBHZWQG9QNWhftbZ"
-      , "abe74a98f6c7eabee0428f53798f0ab8aa1bd37873999041703c742f15ac7e1e"
-      , "L2ysLrR6KMSAtx7uPqmYpoTeiRzydXBattRXjXz5GDFPrdfPzKbj"
-      , "02fc9e5af0ac8d9b3cecfe2a888e2117ba3d089d8585886c9c826b6b22a98d12ea"
-      , "f0909affaa7ee7abe5dd4e100598d4dc53cd709d5a5c2cac40e7412f232f7c9c"
-      , "0488b21e01bd16bee500000000f0909affaa7ee7abe5dd4e100598d4dc53cd709d5a5c2cac40e7412f232f7c9c02fc9e5af0ac8d9b3cecfe2a888e2117ba3d089d8585886c9c826b6b22a98d12ea"
-      , "0488ade401bd16bee500000000f0909affaa7ee7abe5dd4e100598d4dc53cd709d5a5c2cac40e7412f232f7c9c00abe74a98f6c7eabee0428f53798f0ab8aa1bd37873999041703c742f15ac7e1e"
-      , "xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH"
-      , "xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt"
-      ]
-      -- m/0/2147483647'
-    , [ "d8ab493736da02f11ed682f88339e720fb0379d1"
-      , "d8ab4937"
-      , "1Lke9bXGhn5VPrBuXgN12uGUphrttUErmk"
-      , "877c779ad9687164e9c2f4f0f4ff0340814392330693ce95a58fe18fd52e6e93"
-      , "L1m5VpbXmMp57P3knskwhoMTLdhAAaXiHvnGLMribbfwzVRpz2Sr"
-      , "03c01e7425647bdefa82b12d9bad5e3e6865bee0502694b94ca58b666abc0a5c3b"
-      , "be17a268474a6bb9c61e1d720cf6215e2a88c5406c4aee7b38547f585c9a37d9"
-      , "0488b21e025a61ff8effffffffbe17a268474a6bb9c61e1d720cf6215e2a88c5406c4aee7b38547f585c9a37d903c01e7425647bdefa82b12d9bad5e3e6865bee0502694b94ca58b666abc0a5c3b"
-      , "0488ade4025a61ff8effffffffbe17a268474a6bb9c61e1d720cf6215e2a88c5406c4aee7b38547f585c9a37d900877c779ad9687164e9c2f4f0f4ff0340814392330693ce95a58fe18fd52e6e93"
-      , "xpub6ASAVgeehLbnwdqV6UKMHVzgqAG8Gr6riv3Fxxpj8ksbH9ebxaEyBLZ85ySDhKiLDBrQSARLq1uNRts8RuJiHjaDMBU4Zn9h8LZNnBC5y4a"
-      , "xprv9wSp6B7kry3Vj9m1zSnLvN3xH8RdsPP1Mh7fAaR7aRLcQMKTR2vidYEeEg2mUCTAwCd6vnxVrcjfy2kRgVsFawNzmjuHc2YmYRmagcEPdU9"
-      ]
-      -- m/0/2147483647'/1
-    , [ "78412e3a2296a40de124307b6485bd19833e2e34"
-      , "78412e3a"
-      , "1BxrAr2pHpeBheusmd6fHDP2tSLAUa3qsW"
-      , "704addf544a06e5ee4bea37098463c23613da32020d604506da8c0518e1da4b7"
-      , "KzyzXnznxSv249b4KuNkBwowaN3akiNeEHy5FWoPCJpStZbEKXN2"
-      , "03a7d1d856deb74c508e05031f9895dab54626251b3806e16b4bd12e781a7df5b9"
-      , "f366f48f1ea9f2d1d3fe958c95ca84ea18e4c4ddb9366c336c927eb246fb38cb"
-      , "0488b21e03d8ab493700000001f366f48f1ea9f2d1d3fe958c95ca84ea18e4c4ddb9366c336c927eb246fb38cb03a7d1d856deb74c508e05031f9895dab54626251b3806e16b4bd12e781a7df5b9"
-      , "0488ade403d8ab493700000001f366f48f1ea9f2d1d3fe958c95ca84ea18e4c4ddb9366c336c927eb246fb38cb00704addf544a06e5ee4bea37098463c23613da32020d604506da8c0518e1da4b7"
-      , "xpub6DF8uhdarytz3FWdA8TvFSvvAh8dP3283MY7p2V4SeE2wyWmG5mg5EwVvmdMVCQcoNJxGoWaU9DCWh89LojfZ537wTfunKau47EL2dhHKon"
-      , "xprv9zFnWC6h2cLgpmSA46vutJzBcfJ8yaJGg8cX1e5StJh45BBciYTRXSd25UEPVuesF9yog62tGAQtHjXajPPdbRCHuWS6T8XA2ECKADdw4Ef"
-      ]
-      -- m/0/2147483647'/1/2147483646'
-    , [ "31a507b815593dfc51ffc7245ae7e5aee304246e"
-      , "31a507b8"
-      , "15XVotxCAV7sRx1PSCkQNsGw3W9jT9A94R"
-      , "f1c7c871a54a804afe328b4c83a1c33b8e5ff48f5087273f04efa83b247d6a2d"
-      , "L5KhaMvPYRW1ZoFmRjUtxxPypQ94m6BcDrPhqArhggdaTbbAFJEF"
-      , "02d2b36900396c9282fa14628566582f206a5dd0bcc8d5e892611806cafb0301f0"
-      , "637807030d55d01f9a0cb3a7839515d796bd07706386a6eddf06cc29a65a0e29"
-      , "0488b21e0478412e3afffffffe637807030d55d01f9a0cb3a7839515d796bd07706386a6eddf06cc29a65a0e2902d2b36900396c9282fa14628566582f206a5dd0bcc8d5e892611806cafb0301f0"
-      , "0488ade40478412e3afffffffe637807030d55d01f9a0cb3a7839515d796bd07706386a6eddf06cc29a65a0e2900f1c7c871a54a804afe328b4c83a1c33b8e5ff48f5087273f04efa83b247d6a2d"
-      , "xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL"
-      , "xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc"
-      ]
-      -- m/0/2147483647'/1/2147483646'/2
-    , [ "26132fdbe7bf89cbc64cf8dafa3f9f88b8666220"
-      , "26132fdb"
-      , "14UKfRV9ZPUp6ZC9PLhqbRtxdihW9em3xt"
-      , "bb7d39bdb83ecf58f2fd82b6d918341cbef428661ef01ab97c28a4842125ac23"
-      , "L3WAYNAZPxx1fr7KCz7GN9nD5qMBnNiqEJNJMU1z9MMaannAt4aK"
-      , "024d902e1a2fc7a8755ab5b694c575fce742c48d9ff192e63df5193e4c7afe1f9c"
-      , "9452b549be8cea3ecb7a84bec10dcfd94afe4d129ebfd3b3cb58eedf394ed271"
-      , "0488b21e0531a507b8000000029452b549be8cea3ecb7a84bec10dcfd94afe4d129ebfd3b3cb58eedf394ed271024d902e1a2fc7a8755ab5b694c575fce742c48d9ff192e63df5193e4c7afe1f9c"
-      , "0488ade40531a507b8000000029452b549be8cea3ecb7a84bec10dcfd94afe4d129ebfd3b3cb58eedf394ed27100bb7d39bdb83ecf58f2fd82b6d918341cbef428661ef01ab97c28a4842125ac23"
-      , "xpub6FnCn6nSzZAw5Tw7cgR9bi15UV96gLZhjDstkXXxvCLsUXBGXPdSnLFbdpq8p9HmGsApME5hQTZ3emM2rnY5agb9rXpVGyy3bdW6EEgAtqt"
-      , "xprvA2nrNbFZABcdryreWet9Ea4LvTJcGsqrMzxHx98MMrotbir7yrKCEXw7nadnHM8Dq38EGfSh6dqA9QWTyefMLEcBYJUuekgW4BYPJcr9E7j"
-      ]
-    ]
-
-pubKeyOfSubKeyIsSubKeyOfPubKey :: XPrvKey -> Word32 -> Bool
-pubKeyOfSubKeyIsSubKeyOfPubKey k i =
-    deriveXPubKey (prvSubKey k i') == pubSubKey (deriveXPubKey k) i'
-  where
-    i' = fromIntegral $ i .&. 0x7fffffff -- make it a public derivation
-
-
-testID :: (FromJSON a, ToJSON a, Eq a) => a -> Bool
-testID x =
-    (A.decode . A.encode) (singleton ("object" :: String) x) ==
-    Just (singleton ("object" :: String) x)
-
-testCustom :: Eq a => (Value -> Parser a) -> (a -> Value) -> a -> Bool
-testCustom f g x = parseMaybe f (g x) == Just x
-
-testPutGet :: Eq a => Get a -> Putter a -> a -> Bool
-testPutGet g p a = runGet g (runPut (p a)) == Right a
diff --git a/test/Network/Haskoin/Keys/MnemonicSpec.hs b/test/Network/Haskoin/Keys/MnemonicSpec.hs
deleted file mode 100644
--- a/test/Network/Haskoin/Keys/MnemonicSpec.hs
+++ /dev/null
@@ -1,494 +0,0 @@
-{-# LANGUAGE OverloadedStrings #-}
-module Network.Haskoin.Keys.MnemonicSpec (spec) where
-
-import           Control.Monad           (zipWithM_)
-import           Data.Bits               (shiftR, (.&.))
-import           Data.ByteString         (ByteString)
-import qualified Data.ByteString         as BS
-import qualified Data.ByteString.Char8   as C
-import           Data.Either             (fromRight)
-import           Data.List               (isPrefixOf)
-import           Data.Maybe              (fromJust)
-import           Data.Serialize          (Serialize, encode)
-import           Data.String.Conversions (cs)
-import           Data.Text               (Text)
-import qualified Data.Text               as T
-import           Data.Word               (Word32, Word64)
-import           Network.Haskoin.Crypto
-import           Network.Haskoin.Keys
-import           Network.Haskoin.Test
-import           Network.Haskoin.Util
-import           Test.Hspec
-import           Test.HUnit
-import           Test.QuickCheck         hiding ((.&.))
-
-spec :: Spec
-spec =
-    describe "mnemonic" $ do
-        it "entropy to mnemonic sentence" toMnemonicTest
-        it "mnemonic sentence to entropy" fromMnemonicTest
-        it "mnemonic sentence to seed" mnemonicToSeedTest
-        it "mnemonic sentence with invalid checksum" fromMnemonicInvalidTest
-        it "empty mnemonic sentence is invalid" $ sequence_ [emptyMnemonicTest]
-        it "generate 12 words" $ property toMnemonic128
-        it "generate 18 words" $ property toMnemonic160
-        it "generate 24 words" $ property toMnemonic256
-        it "generate 48 words" $ property toMnemonic512
-        it "generate any number of words" $ property toMnemonicVar
-        it "encode and decode 128-bit entropy" $ property fromToMnemonic128
-        it "encode and decode 160-bit entropy" $ property fromToMnemonic160
-        it "encode and decode 256-bit entropy" $ property fromToMnemonic256
-        it "encode and decode 512-bit entropy" $ property fromToMnemonic512
-        it "encode and decode n-bit entropy" $ property fromToMnemonicVar
-        it "convert 128-bit mnemonic to seed" $ property mnemonicToSeed128
-        it "convert 160-bit mnemonic to seed" $ property mnemonicToSeed160
-        it "convert 256-bit mnemonic to seed" $ property mnemonicToSeed256
-        it "convert 512-bit mnemonic to seed" $ property mnemonicToSeed512
-        it "convert n-bit mnemonic to seed" $ property mnemonicToSeedVar
-        it "get bits" $ property getBitsByteCount
-        it "get end bits" $ property getBitsEndBits
-
-toMnemonicTest :: Assertion
-toMnemonicTest = zipWithM_ f ents mss
-  where
-    f e m = assertEqual "" m . h $ e
-    h =
-        fromRight (error "Could not decode mnemonic sentence") .
-        toMnemonic . fromJust . decodeHex
-
-fromMnemonicTest :: Assertion
-fromMnemonicTest = zipWithM_ f ents mss
-  where
-    f e = assertEqual "" e . h
-    h =
-        encodeHex .
-        fromRight (error "Could not decode mnemonic sentence") . fromMnemonic
-
-mnemonicToSeedTest :: Assertion
-mnemonicToSeedTest = zipWithM_ f mss seeds
-  where
-    f m s = assertEqual "" s . h $ m
-    h =
-        encodeHex .
-        fromRight (error "Could not decode mnemonic seed") .
-        mnemonicToSeed "TREZOR"
-
-fromMnemonicInvalidTest :: Assertion
-fromMnemonicInvalidTest = mapM_ f invalidMss
-  where
-    f = assertBool "" . h
-    h m = case fromMnemonic m of
-            Right _  -> False
-            Left err -> "fromMnemonic: checksum failed:" `isPrefixOf` err
-
-emptyMnemonicTest :: Assertion
-emptyMnemonicTest =
-    assertBool "" $
-    case fromMnemonic "" of
-        Right _  -> False
-        Left err -> "fromMnemonic: empty mnemonic" `isPrefixOf` err
-
-ents :: [Text]
-ents =
-    [ "00000000000000000000000000000000"
-    , "7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f"
-    , "80808080808080808080808080808080"
-    , "ffffffffffffffffffffffffffffffff"
-    , "000000000000000000000000000000000000000000000000"
-    , "7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f"
-    , "808080808080808080808080808080808080808080808080"
-    , "ffffffffffffffffffffffffffffffffffffffffffffffff"
-    , "0000000000000000000000000000000000000000000000000000000000000000"
-    , "7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f"
-    , "8080808080808080808080808080808080808080808080808080808080808080"
-    , "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
-    , "77c2b00716cec7213839159e404db50d"
-    , "b63a9c59a6e641f288ebc103017f1da9f8290b3da6bdef7b"
-    , "3e141609b97933b66a060dcddc71fad1d91677db872031e85f4c015c5e7e8982"
-    , "0460ef47585604c5660618db2e6a7e7f"
-    , "72f60ebac5dd8add8d2a25a797102c3ce21bc029c200076f"
-    , "2c85efc7f24ee4573d2b81a6ec66cee209b2dcbd09d8eddc51e0215b0b68e416"
-    , "eaebabb2383351fd31d703840b32e9e2"
-    , "7ac45cfe7722ee6c7ba84fbc2d5bd61b45cb2fe5eb65aa78"
-    , "4fa1a8bc3e6d80ee1316050e862c1812031493212b7ec3f3bb1b08f168cabeef"
-    , "18ab19a9f54a9274f03e5209a2ac8a91"
-    , "18a2e1d81b8ecfb2a333adcb0c17a5b9eb76cc5d05db91a4"
-    , "15da872c95a13dd738fbf50e427583ad61f18fd99f628c417a61cf8343c90419"
-    ]
-
-mss :: [Mnemonic]
-mss =
-    [ "abandon abandon abandon abandon abandon abandon abandon abandon abandon\
-      \ abandon abandon about"
-    , "legal winner thank year wave sausage worth useful legal winner thank\
-      \ yellow"
-    , "letter advice cage absurd amount doctor acoustic avoid letter advice\
-      \ cage above"
-    , "zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo wrong"
-    , "abandon abandon abandon abandon abandon abandon abandon abandon abandon\
-      \ abandon abandon abandon abandon abandon abandon abandon abandon agent"
-    , "legal winner thank year wave sausage worth useful legal winner thank\
-      \ year wave sausage worth useful legal will"
-    , "letter advice cage absurd amount doctor acoustic avoid letter advice\
-      \ cage absurd amount doctor acoustic avoid letter always"
-    , "zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo\
-      \ when"
-    , "abandon abandon abandon abandon abandon abandon abandon abandon abandon\
-      \ abandon abandon abandon abandon abandon abandon abandon abandon abandon\
-      \ abandon abandon abandon abandon abandon art"
-    , "legal winner thank year wave sausage worth useful legal winner thank\
-      \ year wave sausage worth useful legal winner thank year wave sausage\
-      \ worth title"
-    , "letter advice cage absurd amount doctor acoustic avoid letter advice\
-      \ cage absurd amount doctor acoustic avoid letter advice cage absurd\
-      \ amount doctor acoustic bless"
-    , "zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo\
-      \ zoo zoo zoo zoo zoo vote"
-    , "jelly better achieve collect unaware mountain thought cargo oxygen act\
-      \ hood bridge"
-    , "renew stay biology evidence goat welcome casual join adapt armor shuffle\
-      \ fault little machine walk stumble urge swap"
-    , "dignity pass list indicate nasty swamp pool script soccer toe leaf photo\
-      \ multiply desk host tomato cradle drill spread actor shine dismiss\
-      \ champion exotic"
-    , "afford alter spike radar gate glance object seek swamp infant panel\
-      \ yellow"
-    , "indicate race push merry suffer human cruise dwarf pole review arch keep\
-      \ canvas theme poem divorce alter left"
-    , "clutch control vehicle tonight unusual clog visa ice plunge glimpse\
-      \ recipe series open hour vintage deposit universe tip job dress radar\
-      \ refuse motion taste"
-    , "turtle front uncle idea crush write shrug there lottery flower risk\
-      \ shell"
-    , "kiss carry display unusual confirm curtain upgrade antique rotate hello\
-      \ void custom frequent obey nut hole price segment"
-    , "exile ask congress lamp submit jacket era scheme attend cousin alcohol\
-      \ catch course end lucky hurt sentence oven short ball bird grab wing top"
-    , "board flee heavy tunnel powder denial science ski answer betray cargo\
-      \ cat"
-    , "board blade invite damage undo sun mimic interest slam gaze truly\
-      \ inherit resist great inject rocket museum chief"
-    , "beyond stage sleep clip because twist token leaf atom beauty genius food\
-      \ business side grid unable middle armed observe pair crouch tonight away\
-      \ coconut"
-    ]
-
-seeds :: [Text]
-seeds =
-    [ "c55257c360c07c72029aebc1b53c05ed0362ada38ead3e3e9efa3708e53495531f09a69\
-      \87599d18264c1e1c92f2cf141630c7a3c4ab7c81b2f001698e7463b04"
-    , "2e8905819b8723fe2c1d161860e5ee1830318dbf49a83bd451cfb8440c28bd6fa457fe1\
-      \296106559a3c80937a1c1069be3a3a5bd381ee6260e8d9739fce1f607"
-    , "d71de856f81a8acc65e6fc851a38d4d7ec216fd0796d0a6827a3ad6ed5511a30fa280f1\
-      \2eb2e47ed2ac03b5c462a0358d18d69fe4f985ec81778c1b370b652a8"
-    , "ac27495480225222079d7be181583751e86f571027b0497b5b5d11218e0a8a133325729\
-      \17f0f8e5a589620c6f15b11c61dee327651a14c34e18231052e48c069"
-    , "035895f2f481b1b0f01fcf8c289c794660b289981a78f8106447707fdd9666ca06da5a9\
-      \a565181599b79f53b844d8a71dd9f439c52a3d7b3e8a79c906ac845fa"
-    , "f2b94508732bcbacbcc020faefecfc89feafa6649a5491b8c952cede496c214a0c7b3c3\
-      \92d168748f2d4a612bada0753b52a1c7ac53c1e93abd5c6320b9e95dd"
-    , "107d7c02a5aa6f38c58083ff74f04c607c2d2c0ecc55501dadd72d025b751bc27fe913f\
-      \fb796f841c49b1d33b610cf0e91d3aa239027f5e99fe4ce9e5088cd65"
-    , "0cd6e5d827bb62eb8fc1e262254223817fd068a74b5b449cc2f667c3f1f985a76379b43\
-      \348d952e2265b4cd129090758b3e3c2c49103b5051aac2eaeb890a528"
-    , "bda85446c68413707090a52022edd26a1c9462295029f2e60cd7c4f2bbd3097170af7a4\
-      \d73245cafa9c3cca8d561a7c3de6f5d4a10be8ed2a5e608d68f92fcc8"
-    , "bc09fca1804f7e69da93c2f2028eb238c227f2e9dda30cd63699232578480a4021b146a\
-      \d717fbb7e451ce9eb835f43620bf5c514db0f8add49f5d121449d3e87"
-    , "c0c519bd0e91a2ed54357d9d1ebef6f5af218a153624cf4f2da911a0ed8f7a09e2ef61a\
-      \f0aca007096df430022f7a2b6fb91661a9589097069720d015e4e982f"
-    , "dd48c104698c30cfe2b6142103248622fb7bb0ff692eebb00089b32d22484e1613912f0\
-      \a5b694407be899ffd31ed3992c456cdf60f5d4564b8ba3f05a69890ad"
-    , "b5b6d0127db1a9d2226af0c3346031d77af31e918dba64287a1b44b8ebf63cdd52676f6\
-      \72a290aae502472cf2d602c051f3e6f18055e84e4c43897fc4e51a6ff"
-    , "9248d83e06f4cd98debf5b6f010542760df925ce46cf38a1bdb4e4de7d21f5c39366941\
-      \c69e1bdbf2966e0f6e6dbece898a0e2f0a4c2b3e640953dfe8b7bbdc5"
-    , "ff7f3184df8696d8bef94b6c03114dbee0ef89ff938712301d27ed8336ca89ef9635da2\
-      \0af07d4175f2bf5f3de130f39c9d9e8dd0472489c19b1a020a940da67"
-    , "65f93a9f36b6c85cbe634ffc1f99f2b82cbb10b31edc7f087b4f6cb9e976e9faf76ff41\
-      \f8f27c99afdf38f7a303ba1136ee48a4c1e7fcd3dba7aa876113a36e4"
-    , "3bbf9daa0dfad8229786ace5ddb4e00fa98a044ae4c4975ffd5e094dba9e0bb289349db\
-      \e2091761f30f382d4e35c4a670ee8ab50758d2c55881be69e327117ba"
-    , "fe908f96f46668b2d5b37d82f558c77ed0d69dd0e7e043a5b0511c48c2f1064694a956f\
-      \86360c93dd04052a8899497ce9e985ebe0c8c52b955e6ae86d4ff4449"
-    , "bdfb76a0759f301b0b899a1e3985227e53b3f51e67e3f2a65363caedf3e32fde42a66c4\
-      \04f18d7b05818c95ef3ca1e5146646856c461c073169467511680876c"
-    , "ed56ff6c833c07982eb7119a8f48fd363c4a9b1601cd2de736b01045c5eb8ab4f57b079\
-      \403485d1c4924f0790dc10a971763337cb9f9c62226f64fff26397c79"
-    , "095ee6f817b4c2cb30a5a797360a81a40ab0f9a4e25ecd672a3f58a0b5ba0687c096a6b\
-      \14d2c0deb3bdefce4f61d01ae07417d502429352e27695163f7447a8c"
-    , "6eff1bb21562918509c73cb990260db07c0ce34ff0e3cc4a8cb3276129fbcb300bddfe0\
-      \05831350efd633909f476c45c88253276d9fd0df6ef48609e8bb7dca8"
-    , "f84521c777a13b61564234bf8f8b62b3afce27fc4062b51bb5e62bdfecb23864ee6ecf0\
-      \7c1d5a97c0834307c5c852d8ceb88e7c97923c0a3b496bedd4e5f88a9"
-    , "b15509eaa2d09d3efd3e006ef42151b30367dc6e3aa5e44caba3fe4d3e352e65101fbdb\
-      \86a96776b91946ff06f8eac594dc6ee1d3e82a42dfe1b40fef6bcc3fd"
-    ]
-
-invalidMss :: [Mnemonic]
-invalidMss =
-    [ "abandon abandon abandon abandon abandon abandon abandon abandon abandon\
-      \ abandon abandon abandon"
-    , "legal winner thank year wave sausage worth useful legal winner thank\
-      \ thank"
-    , "letter advice cage absurd amount doctor acoustic avoid letter advice\
-      \ cage sausage"
-    , "zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo"
-    , "abandon abandon abandon abandon abandon abandon abandon abandon abandon\
-      \ abandon abandon abandon abandon abandon abandon abandon abandon abandon"
-    , "legal winner thank year wave sausage worth useful legal winner thank\
-      \ year wave sausage worth useful legal letter"
-    , "letter advice cage absurd amount doctor acoustic avoid letter advice\
-      \ cage absurd amount doctor acoustic avoid letter abandon"
-    , "zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo\
-      \ zoo"
-    , "abandon abandon abandon abandon abandon abandon abandon abandon abandon\
-      \ abandon abandon abandon abandon abandon abandon abandon abandon abandon\
-      \ abandon abandon abandon abandon abandon abandon"
-    , "legal winner thank year wave sausage worth useful legal winner thank\
-      \ year wave sausage worth useful legal winner thank year wave sausage\
-      \ worth letter"
-    , "letter advice cage absurd amount doctor acoustic avoid letter advice\
-      \ cage absurd amount doctor acoustic avoid letter advice cage absurd\
-      \ amount doctor acoustic letter"
-    , "zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo\
-      \ zoo zoo zoo zoo zoo zoo"
-    , "jelly better achieve collect unaware mountain thought cargo oxygen act\
-      \ hood zoo"
-    , "renew stay biology evidence goat welcome casual join adapt armor shuffle\
-      \ fault little machine walk stumble urge zoo"
-    , "dignity pass list indicate nasty swamp pool script soccer toe leaf photo\
-      \ multiply desk host tomato cradle drill spread actor shine dismiss\
-      \ champion zoo"
-    , "afford alter spike radar gate glance object seek swamp infant panel\
-      \ zoo"
-    , "indicate race push merry suffer human cruise dwarf pole review arch keep\
-      \ canvas theme poem divorce alter zoo"
-    , "clutch control vehicle tonight unusual clog visa ice plunge glimpse\
-      \ recipe series open hour vintage deposit universe tip job dress radar\
-      \ refuse motion zoo"
-    , "turtle front uncle idea crush write shrug there lottery flower risk\
-      \ zoo"
-    , "kiss carry display unusual confirm curtain upgrade antique rotate hello\
-      \ void custom frequent obey nut hole price zoo"
-    , "exile ask congress lamp submit jacket era scheme attend cousin alcohol\
-      \ catch course end lucky hurt sentence oven short ball bird grab wing zoo"
-    , "board flee heavy tunnel powder denial science ski answer betray cargo\
-      \ zoo"
-    , "board blade invite damage undo sun mimic interest slam gaze truly\
-      \ inherit resist great inject rocket museum zoo"
-    , "beyond stage sleep clip because twist token leaf atom beauty genius food\
-      \ business side grid unable middle armed observe pair crouch tonight away\
-      \ zoo"
-    ]
-
-binWordsToBS :: Serialize a => [a] -> BS.ByteString
-binWordsToBS = foldr f BS.empty
-  where
-    f b a = a `BS.append` encode b
-
-{- Encode mnemonic -}
-
-toMnemonic128 :: (Word64, Word64) -> Bool
-toMnemonic128 (a, b) = l == 12
-  where
-    bs = encode a `BS.append` encode b
-    l =
-        length .
-        T.words . fromRight (error "Could not decode mnemonic senttence") $
-        toMnemonic bs
-
-toMnemonic160 :: (Word32, Word64, Word64) -> Bool
-toMnemonic160 (a, b, c) = l == 15
-  where
-    bs = BS.concat [encode a, encode b, encode c]
-    l =
-        length .
-        T.words . fromRight (error "Could not decode mnemonic sentence") $
-        toMnemonic bs
-
-toMnemonic256 :: (Word64, Word64, Word64, Word64) -> Bool
-toMnemonic256 (a, b, c, d) = l == 24
-  where
-    bs = BS.concat [encode a, encode b, encode c, encode d]
-    l =
-        length .
-        T.words . fromRight (error "Could not decode mnemonic sentence") $
-        toMnemonic bs
-
-toMnemonic512 ::
-    ((Word64, Word64, Word64, Word64), (Word64, Word64, Word64, Word64)) -> Bool
-toMnemonic512 ((a, b, c, d), (e, f, g, h)) = l == 48
-  where
-    bs =
-        BS.concat
-            [ encode a
-            , encode b
-            , encode c
-            , encode d
-            , encode e
-            , encode f
-            , encode g
-            , encode h
-            ]
-    l =
-        length .
-        T.words . fromRight (error "Could not decode mnemonic sentence") $
-        toMnemonic bs
-
-toMnemonicVar :: [Word32] -> Property
-toMnemonicVar ls = not (null ls) && length ls <= 8 ==> l == wc
-  where
-    bs = binWordsToBS ls
-    bl = BS.length bs
-    cb = bl `div` 4
-    wc = (cb + bl * 8) `div` 11
-    l =
-        length . T.words .
-        fromRight (error "Could not decode mnemonic sentence") $
-        toMnemonic bs
-
-{- Encode/Decode -}
-
-fromToMnemonic128 :: (Word64, Word64) -> Bool
-fromToMnemonic128 (a, b) = bs == bs'
-  where
-    bs = encode a `BS.append` encode b
-    bs' =
-        fromRight
-            (error "Could not decode mnemonic entropy")
-            (fromMnemonic =<< toMnemonic bs)
-
-fromToMnemonic160 :: (Word32, Word64, Word64) -> Bool
-fromToMnemonic160 (a, b, c) = bs == bs'
-  where
-    bs = BS.concat [encode a, encode b, encode c]
-    bs' =
-        fromRight
-            (error "Could not decode mnemonic entropy")
-            (fromMnemonic =<< toMnemonic bs)
-
-fromToMnemonic256 :: (Word64, Word64, Word64, Word64) -> Bool
-fromToMnemonic256 (a, b, c, d) = bs == bs'
-  where
-    bs = BS.concat [encode a, encode b, encode c, encode d]
-    bs' =
-        fromRight
-            (error "Could not decode mnemonic entropy")
-            (fromMnemonic =<< toMnemonic bs)
-
-fromToMnemonic512 ::
-    ((Word64, Word64, Word64, Word64), (Word64, Word64, Word64, Word64)) -> Bool
-fromToMnemonic512 ((a, b, c, d), (e, f, g, h)) = bs == bs'
-  where
-    bs =
-        BS.concat
-            [ encode a
-            , encode b
-            , encode c
-            , encode d
-            , encode e
-            , encode f
-            , encode g
-            , encode h
-            ]
-    bs' =
-        fromRight
-            (error "Could not decode mnemonic entropy")
-            (fromMnemonic =<< toMnemonic bs)
-
-fromToMnemonicVar :: [Word32] -> Property
-fromToMnemonicVar ls = not (null ls) && length ls <= 8 ==> bs == bs'
-  where
-    bs = binWordsToBS ls
-    bs' =
-        fromRight
-            (error "Could not decode mnemonic entropy")
-            (fromMnemonic =<< toMnemonic bs)
-
-{- Mnemonic to seed -}
-
-mnemonicToSeed128 :: (Word64, Word64) -> Bool
-mnemonicToSeed128 (a, b) = l == 64
-  where
-    bs = encode a `BS.append` encode b
-    seed =
-        fromRight
-            (error "Could not decode mnemonic seed")
-            (mnemonicToSeed "" =<< toMnemonic bs)
-    l = BS.length seed
-
-mnemonicToSeed160 :: (Word32, Word64, Word64) -> Bool
-mnemonicToSeed160 (a, b, c) = l == 64
-  where
-    bs = BS.concat [encode a, encode b, encode c]
-    seed =
-        fromRight
-            (error "Could not decode mnemonic seed")
-            (mnemonicToSeed "" =<< toMnemonic bs)
-    l = BS.length seed
-
-mnemonicToSeed256 :: (Word64, Word64, Word64, Word64) -> Bool
-mnemonicToSeed256 (a, b, c, d) = l == 64
-  where
-    bs = BS.concat [encode a, encode b, encode c, encode d]
-    seed =
-        fromRight
-            (error "Could not decode mnemonic seed")
-            (mnemonicToSeed "" =<< toMnemonic bs)
-    l = BS.length seed
-
-mnemonicToSeed512 ::
-    ((Word64, Word64, Word64, Word64), (Word64, Word64, Word64, Word64)) -> Bool
-mnemonicToSeed512 ((a, b, c, d), (e, f, g, h)) = l == 64
-  where
-    bs =
-        BS.concat
-            [ encode a
-            , encode b
-            , encode c
-            , encode d
-            , encode e
-            , encode f
-            , encode g
-            , encode h
-            ]
-    seed =
-        fromRight
-            (error "Could not decode mnemonic seed")
-            (mnemonicToSeed "" =<< toMnemonic bs)
-    l = BS.length seed
-
-mnemonicToSeedVar :: [Word32] -> Property
-mnemonicToSeedVar ls = not (null ls) && length ls <= 16 ==> l == 64
-  where
-    bs = binWordsToBS ls
-    seed =
-        fromRight
-            (error "Could not decode mnemonic seed")
-            (mnemonicToSeed "" =<< toMnemonic bs)
-    l = BS.length seed
-
-{- Get bits from ByteString -}
-
-data ByteCountGen = ByteCountGen BS.ByteString Int deriving Show
-
-instance Arbitrary ByteCountGen where
-    arbitrary = do
-        bs <- arbitraryBS
-        i <- choose (0, BS.length bs * 8)
-        return $ ByteCountGen bs i
-
-getBitsByteCount :: ByteCountGen -> Bool
-getBitsByteCount (ByteCountGen bs i) = BS.length bits == l
-  where
-    (q, r) = i `quotRem` 8
-    bits = getBits i bs
-    l = if r == 0 then q else q + 1
-
-getBitsEndBits :: ByteCountGen -> Bool
-getBitsEndBits (ByteCountGen bs i) =
-    (r == 0) || (BS.last bits .&. (0xff `shiftR` r) == 0x00)
-  where
-    r = i `mod` 8
-    bits = getBits i bs
diff --git a/test/Network/Haskoin/KeysSpec.hs b/test/Network/Haskoin/KeysSpec.hs
deleted file mode 100644
--- a/test/Network/Haskoin/KeysSpec.hs
+++ /dev/null
@@ -1,72 +0,0 @@
-module Network.Haskoin.KeysSpec (spec) where
-
-import           Data.Aeson                as A
-import qualified Data.ByteString           as BS
-import           Data.Map.Strict           (singleton)
-import           Data.Serialize            as S
-import           Data.String               (fromString)
-import           Data.String.Conversions   (cs)
-import           Network.Haskoin.Address
-import           Network.Haskoin.Constants
-import           Network.Haskoin.Crypto
-import           Network.Haskoin.Keys
-import           Network.Haskoin.Test
-import           Network.Haskoin.Util
-import           Test.Hspec
-import           Test.QuickCheck
-
-spec :: Spec
-spec =
-    describe "keys" $ do
-        let net = btc
-        it "is public key canonical" $
-            property $ forAll arbitraryKeyPair (isCanonicalPubKey . snd)
-        it "encode and decode wif private keys" $
-            property $
-            forAll arbitraryKeyPair $ \(pk, _) ->
-                fromWif net (toWif net pk) == Just pk
-        it "encode and decode serialized private key" $
-            property $ forAll arbitrary binaryPrvKey
-        it "read and show public key" $
-            property $ forAll arbitraryKeyPair $ \(_, k) -> read (show k) == k
-        it "read and show private key" $
-            property $ forAll arbitrarySecKeyI $ \k -> read (show k) == k
-        it "from string public key" $
-            property $
-            forAll arbitraryKeyPair $ \(_, k) ->
-                fromString (cs . encodeHex $ S.encode k) == k
-        it "encode and decode json public key" $
-            property $ forAll arbitraryKeyPair (testID . snd)
-        it "encodes and decodes serialized public key" $
-            property $ forAll arbitraryKeyPair $ cerealID . snd
-
-
--- github.com/bitcoin/bitcoin/blob/master/src/script.cpp
--- from function IsCanonicalPubKey
-isCanonicalPubKey :: PubKeyI -> Bool
-isCanonicalPubKey p = not $
-    -- Non-canonical public key: too short
-    (BS.length bs < 33) ||
-    -- Non-canonical public key: invalid length for uncompressed key
-    (BS.index bs 0 == 4 && BS.length bs /= 65) ||
-    -- Non-canonical public key: invalid length for compressed key
-    (BS.index bs 0 `elem` [2,3] && BS.length bs /= 33) ||
-    -- Non-canonical public key: compressed nor uncompressed
-    (BS.index bs 0 `notElem` [2,3,4])
-  where
-    bs = S.encode p
-
-{- Key formats -}
-
-binaryPrvKey :: SecKey -> Bool
-binaryPrvKey k =
-    Right k == runGet secKeyGet (runPut (secKeyPut k)) &&
-    Just k == secKey (getSecKey k)
-
-testID :: (FromJSON a, ToJSON a, Eq a) => a -> Bool
-testID x =
-    (A.decode . A.encode) (singleton ("object" :: String) x) ==
-    Just (singleton ("object" :: String) x)
-
-cerealID :: (Serialize a, Eq a) => a -> Bool
-cerealID x = S.decode (S.encode x) == Right x
diff --git a/test/Network/Haskoin/NetworkSpec.hs b/test/Network/Haskoin/NetworkSpec.hs
deleted file mode 100644
--- a/test/Network/Haskoin/NetworkSpec.hs
+++ /dev/null
@@ -1,187 +0,0 @@
-{-# LANGUAGE OverloadedStrings #-}
-module Network.Haskoin.NetworkSpec (spec) where
-
-import           Data.Maybe                  (fromJust)
-import           Data.Serialize              as S
-import           Data.Text                   (Text)
-import           Data.Word                   (Word32)
-import           Network.Haskoin.Address
-import           Network.Haskoin.Constants
-import           Network.Haskoin.Keys
-import           Network.Haskoin.Network
-import           Network.Haskoin.Test
-import           Network.Haskoin.Transaction
-import           Network.Haskoin.Util
-import           Test.Hspec
-import           Test.HUnit                  (Assertion, assertBool,
-                                              assertEqual)
-import           Test.QuickCheck
-
-spec :: Spec
-spec = do
-    let net = btc
-    describe "bloom filters" $ do
-        it "bloom filter vector 1" bloomFilter1
-        it "bloom filter vector 2" bloomFilter2
-        it "bloom filter vector 3" bloomFilter3
-    describe "relevant bloom filter update" $ do
-        it "Relevant Update" relevantOutputUpdated
-        it "Irrelevant Update" irrelevantOutputNotUpdated
-    describe "serialization of protocol types" $ do
-        it "encodes and decodes varint" $
-            property $ forAll arbitraryVarInt cerealID
-        it "encodes and decodes varstring" $
-            property $ forAll arbitraryVarString cerealID
-        it "encodes and decodes network address" $
-            property $ forAll arbitraryNetworkAddress cerealID
-        it "encodes and decodes invtype" $
-            property $ forAll arbitraryInvType cerealID
-        it "encodes and decodes invvector" $
-            property $ forAll arbitraryInvVector cerealID
-        it "encodes and decodes inv" $ property $ forAll arbitraryInv1 cerealID
-        it "encodes and decodes version" $
-            property $ forAll arbitraryVersion cerealID
-        it "encodes and decodes addr" $ property $ forAll arbitraryAddr1 cerealID
-        it "encodes and decodes alert" $ property $ forAll arbitraryAlert cerealID
-        it "encodes and decodes reject" $
-            property $ forAll arbitraryReject cerealID
-        it "encodes and decodes getdata" $
-            property $ forAll arbitraryGetData cerealID
-        it "encodes and decodes notfound" $
-            property $ forAll arbitraryNotFound cerealID
-        it "encodes and decodes ping" $ property $ forAll arbitraryPing cerealID
-        it "encodes and decodes pong" $ property $ forAll arbitraryPong cerealID
-        it "encodes and decodes message command" $
-            property $ forAll arbitraryMessageCommand cerealID
-        it "encodes and decodes message header" $
-            property $ forAll arbitraryMessageHeader cerealID
-        it "encodes and decodes message" $
-            property $
-            forAll (arbitraryMessage net) $
-            testPutGet (getMessage net) (putMessage net)
-    describe "serialization of bloom types" $ do
-        it "encodes and decodes bloom flags" $
-            property $ forAll arbitraryBloomFlags cerealID
-        it "encodes and decodes bloom filter" $
-            property $ forAll arbitraryBloomFilter $ cerealID . lst3
-        it "encodes and decodes filterload" $
-            property $ forAll arbitraryFilterLoad cerealID
-        it "encodes and decodes filteradd" $
-            property $ forAll arbitraryFilterAdd cerealID
-
-bloomFilter :: Word32 -> Text -> Assertion
-bloomFilter n x = do
-    assertBool "Bloom filter doesn't contain vector 1" $ bloomContains f1 v1
-    assertBool "Bloom filter contains something it should not" $
-        not $ bloomContains f1 v2
-    assertBool "Bloom filter doesn't contain vector 3" $ bloomContains f3 v3
-    assertBool "Bloom filter doesn't contain vector 4" $ bloomContains f4 v4
-    assertBool "Bloom filter serialization is incorrect" $
-        S.encode f4 == bs
-  where
-    f0 = bloomCreate 3 0.01 n BloomUpdateAll
-    f1 = bloomInsert f0 v1
-    f3 = bloomInsert f1 v3
-    f4 = bloomInsert f3 v4
-    v1 = fromJust $ decodeHex "99108ad8ed9bb6274d3980bab5a85c048f0950c8"
-    v2 = fromJust $ decodeHex "19108ad8ed9bb6274d3980bab5a85c048f0950c8"
-    v3 = fromJust $ decodeHex "b5a2c786d9ef4658287ced5914b37a1b4aa32eee"
-    v4 = fromJust $ decodeHex "b9300670b4c5366e95b2699e8b18bc75e5f729c5"
-    bs = fromJust $ decodeHex x
-
-bloomFilter1 :: Assertion
-bloomFilter1 = bloomFilter 0 "03614e9b050000000000000001"
-
-bloomFilter2 :: Assertion
-bloomFilter2 = bloomFilter 2147483649 "03ce4299050000000100008001"
-
-bloomFilter3 :: Assertion
-bloomFilter3 =
-    assertBool "Bloom filter serialization is incorrect" $
-        S.encode f2 == bs
-  where
-    f0 = bloomCreate 2 0.001 0 BloomUpdateAll
-    f1 = bloomInsert f0 $ S.encode p
-    f2 = bloomInsert f1 $ S.encode $ getAddrHash160 $ pubKeyAddr p
-    k = fromJust $ fromWif btc "5Kg1gnAjaLfKiwhhPpGS3QfRg2m6awQvaj98JCZBZQ5SuS2F15C"
-    p = derivePubKeyI k
-    bs = fromJust $ decodeHex "038fc16b080000000000000001"
-
-cerealID :: (Serialize a, Eq a) => a -> Bool
-cerealID x = S.decode (S.encode x) == Right x
-
-testPutGet :: Eq a => Get a -> Putter a -> a -> Bool
-testPutGet g p a = runGet g (runPut (p a)) == Right a
-
-relevantOutputUpdated :: Assertion
-relevantOutputUpdated = assertBool "Bloom filter output updated" $
-    any (bloomContains bf2) spendTxInput
-    where
-        bf0 = bloomCreate 10 0.000001 0 BloomUpdateAll
-        relevantOutputHash = fromJust $ decodeHex"03f47604ea2736334151081e13265b4fe38e6fa8"
-        bf1 = bloomInsert bf0 relevantOutputHash
-        bf2 = fromJust $ bloomRelevantUpdate bf1 relevantTx
-        spendTxInput = (encode .prevOutput) <$> txIn spendRelevantTx
-
-irrelevantOutputNotUpdated :: Assertion
-irrelevantOutputNotUpdated = assertEqual "Bloom filter not updated" Nothing bf2
-    where
-        bf0 = bloomCreate 10 0.000001 0 BloomUpdateAll
-        relevantOutputHash = fromJust $ decodeHex"03f47604ea2736334151081e13265b4fe38e6fa8"
-        bf1 = bloomInsert bf0 relevantOutputHash
-        bf2 = bloomRelevantUpdate bf1 unrelatedTx
-        spendTxInput = (encode .prevOutput) <$> txIn spendRelevantTx
-
--- Random transaction (57dc904f32ad4daab7b321dd469e8791ad09df784cdd273a73985150a4f225e9)
-relevantTx :: Tx
-relevantTx = Tx
-        { txVersion = 1
-        , txIn = [ TxIn
-            { prevOutput = OutPoint "35fe9017b7e3af592920b56fa06ac02faf0c52cdb19dcb416129ac71c95d060e" 1
-            , scriptInput = fromJust $ decodeHex "473044022032fc8eef299b7e94b9a986a6aa2dcb9733ab804bef80df995e443b9c1f8c604202203335df7a2e2b4789451cdb4b2b05a786a81c51519eb6a567fd6fe8cd7b2d33fe014104272502dc63a512dad1473cb82a71be9baf4f4303abd1ff6028fc8a78e1f3aec1218907119dec14f07354850758ff0948e88a904fa411c4df7d5444414ec64ad6"
-            , txInSequence = 4294967295
-            } ]
-        , txOut =
-            [ TxOut { outValue = 100000000, scriptOutput = fromJust $ decodeHex "76a91403f47604ea2736334151081e13265b4fe38e6fa888ac" }
-            , TxOut { outValue = 107980000, scriptOutput = fromJust $ decodeHex "76a91481cc186a2f4a69f633ed4bf10ef4a78be13effdd88ac" }
-            ]
-        , txWitness = []
-        , txLockTime = 0
-        }
-
--- Transaction that spends above (fd6e3b693b844aa431fad46765c1aa019a6b13aebfa9dae916b3ffa43283a300)
-spendRelevantTx :: Tx
-spendRelevantTx = Tx
-        { txVersion = 1
-        , txIn = [ TxIn
-            { prevOutput = OutPoint "57dc904f32ad4daab7b321dd469e8791ad09df784cdd273a73985150a4f225e9" 0
-            , scriptInput = fromJust $ decodeHex "483045022100ecc334821e4e94cc2fdc841d5ad147d5bb942b993ba81460cc446e0410afa811022015fcbc542b734dbb61a05ec06012095096de5839c50808fe56f2b315e877c20d012103fb64e5792fa586172339b776b7017d3d529358cb73be6406a1fc994228d14f88"
-            , txInSequence = 4294967295
-            }, TxIn
-            { prevOutput = OutPoint "cfee6a8d6e68e8fd16df6fff010afffcd19d7e075aa7b707dd1bae6adc420042" 0
-            , scriptInput = fromJust $ decodeHex "47304402200e6bb95fa606f254d17089d83c4ceeb19c5d1699b4faddcd4f1f1568286e6b650220087fb8439f31e1b30e47710d095422405f601d6151f2f93e125e1a08a6e29ad4012103b49252e8fc6d5b49c8d14ee71fab45591df4a126a6c453c724f3d356e38f0cee"
-            , txInSequence = 4294967295
-            } ]
-        , txOut =
-            [ TxOut { outValue = 3851100, scriptOutput = fromJust $ decodeHex "76a914a297cae82a9a3b932bf023ae274fe2585295c9ca88ac" }
-            , TxOut { outValue = 111000000, scriptOutput = fromJust $ decodeHex "76a9148f952c38600a61385974acc30a64f74407f9801488ac" }
-            ]
-        , txWitness = []
-        , txLockTime = 0
-        }
-
--- This random transaction is unrelated to the others
-unrelatedTx :: Tx
-unrelatedTx = Tx
-        { txVersion = 1
-        , txIn = [ TxIn
-            { prevOutput = OutPoint "3ec3a71431c68e5d978a5fb4a0a1081d8bee8384d8aa4c06b1fbaf9413e2214f" 20
-            , scriptInput = fromJust $ decodeHex "483045022100ec9c202c9d3140b973aca9d7f21a82138aa4cfa43fddc5419098ac5e26a6f152022010848fd688f290ae010fb5cb493410caa03145fc12445900ec1ad2bde33aecd9012102c7445e72d723f99a0064526c28269d07f47c8fd81531a94a8d3bf5ebd5e23ef1"
-            , txInSequence = 4294967295
-            }]
-        , txOut =
-            [ TxOut { outValue = 12600000, scriptOutput = fromJust $ decodeHex "76a9148fef3b7051de8cc44e966159e7ea37f4520187e888ac" }
-            ]
-        , txWitness = []
-        , txLockTime = 0
-        }
diff --git a/test/Network/Haskoin/ScriptSpec.hs b/test/Network/Haskoin/ScriptSpec.hs
deleted file mode 100644
--- a/test/Network/Haskoin/ScriptSpec.hs
+++ /dev/null
@@ -1,406 +0,0 @@
-{-# LANGUAGE OverloadedStrings #-}
-module Network.Haskoin.ScriptSpec (spec) where
-
-import           Control.Monad
-import           Data.Aeson                  as A
-import           Data.ByteString             (ByteString)
-import qualified Data.ByteString             as B
-import qualified Data.ByteString.Lazy        as L
-import           Data.Either
-import           Data.List
-import           Data.Map.Strict             (singleton)
-import           Data.Maybe
-import           Data.Monoid                 ((<>))
-import           Data.Serialize              as S
-import           Data.String
-import           Data.String.Conversions     (cs)
-import           Data.Text                   (Text)
-import           Data.Word
-import           Network.Haskoin.Address
-import           Network.Haskoin.Constants
-import           Network.Haskoin.Keys
-import           Network.Haskoin.Script
-import           Network.Haskoin.Test
-import           Network.Haskoin.Transaction
-import           Network.Haskoin.Util
-import           Test.Hspec
-import           Test.HUnit                  as HUnit
-import           Test.QuickCheck
-import           Text.Read
-
-spec :: Spec
-spec = do
-    let net = btc
-    describe "btc scripts" $ props btc
-    describe "bch scripts" $ props bch
-    describe "multi signatures" $
-        zipWithM_ (curry mapMulSigVector) mulSigVectors [0 ..]
-    describe "signature decoding" $
-        zipWithM_ (curry (sigDecodeMap net)) scriptSigSignatures [0 ..]
-    describe "json serialization" $ do
-        it "encodes and decodes script output" $
-            forAll (arbitraryScriptOutput net) testID
-        it "encodes and decodes outpoint" $ forAll arbitraryOutPoint testID
-        it "encodes and decodes sighash" $ forAll arbitrarySigHash testID
-        it "encodes and decodes siginput" $
-            forAll (arbitrarySigInput net) (testID . fst)
-    describe "script serialization" $ do
-        it "encodes and decodes script op" $
-            property $ forAll arbitraryScriptOp cerealID
-        it "encodes and decodes script" $
-            property $ forAll arbitraryScript cerealID
-
-props :: Network -> Spec
-props net = do
-    standardSpec net
-    strictSigSpec net
-    scriptSpec net
-    txSigHashForkIdSpec net
-    forkIdScriptSpec net
-    sigHashSpec net
-    txSigHashSpec net
-
-cerealID :: (Serialize a, Eq a) => a -> Bool
-cerealID x = S.decode (S.encode x) == Right x
-
-standardSpec :: Network -> Spec
-standardSpec net = do
-    it "has intToScriptOp . scriptOpToInt identity" $
-        property $
-        forAll arbitraryIntScriptOp $ \i ->
-            intToScriptOp <$> scriptOpToInt i `shouldBe` Right i
-    it "has decodeOutput . encodeOutput identity" $
-        property $
-        forAll (arbitraryScriptOutput net) $ \so ->
-            decodeOutput (encodeOutput so) `shouldBe` Right so
-    it "has decodeInput . encodeOutput identity" $
-        property $
-        forAll (arbitraryScriptInput net) $ \si ->
-            decodeInput net (encodeInput si) `shouldBe` Right si
-    it "can sort multisig scripts" $
-        forAll arbitraryMSOutput $ \out ->
-            map S.encode (getOutputMulSigKeys (sortMulSig out)) `shouldSatisfy` \xs ->
-                xs == sort xs
-    it "can decode inputs with empty signatures" $ do
-        decodeInput net (Script [OP_0]) `shouldBe`
-            Right (RegularInput (SpendPK TxSignatureEmpty))
-        decodeInput net (Script [opPushData ""]) `shouldBe`
-            Right (RegularInput (SpendPK TxSignatureEmpty))
-        let pk =
-                derivePubKeyI $
-                wrapSecKey True $ fromJust $ secKey $ B.replicate 32 1
-        decodeInput net (Script [OP_0, opPushData $ S.encode pk]) `shouldBe`
-            Right (RegularInput (SpendPKHash TxSignatureEmpty pk))
-        decodeInput net (Script [OP_0, OP_0]) `shouldBe`
-            Right (RegularInput (SpendMulSig [TxSignatureEmpty]))
-        decodeInput net (Script [OP_0, OP_0, OP_0, OP_0]) `shouldBe`
-            Right (RegularInput (SpendMulSig $ replicate 3 TxSignatureEmpty))
-
-scriptSpec :: Network -> Spec
-scriptSpec net =
-    when (getNetworkName net == "btc") $
-    it "can verify standard scripts from script_tests.json file" $ do
-        xs <- readTestFile "script_tests" :: IO [A.Value]
-        let vectorsA =
-                mapMaybe (A.decode . A.encode) xs :: [( String
-                                                      , String
-                                                      , String
-                                                      , String
-                                                      , String)]
-            vectorsB =
-                mapMaybe (A.decode . A.encode) xs :: [( [Word64]
-                                                      , String
-                                                      , String
-                                                      , String
-                                                      , String
-                                                      , String)]
-            vectors =
-                map (\(a, b, c, d, e) -> ([0], a, b, c, d, e)) vectorsA <>
-                vectorsB
-        length vectors `shouldBe` 86
-        forM_ vectors $ \([val], siStr, soStr, flags, res, desc)
-          -- We can disable specific tests by adding a DISABLED flag in the data
-         ->
-            unless ("DISABLED" `isInfixOf` flags) $ do
-                let _strict =
-                        "DERSIG" `isInfixOf` flags ||
-                        "STRICTENC" `isInfixOf` flags ||
-                        "NULLDUMMY" `isInfixOf` flags
-                    scriptSig     = parseScript siStr
-                    scriptPubKey  = parseScript soStr
-                    decodedOutput = decodeOutputBS scriptPubKey
-                    ver = either (const False) $ \so ->
-                        verifyStdInput
-                            net
-                            (spendTx scriptPubKey 0 scriptSig)
-                            0
-                            so
-                            (val * 100000000)
-                case res of
-                    "OK" -> assertBool desc $ ver decodedOutput
-                    _    -> assertBool desc (not $ ver decodedOutput)
-
-forkIdScriptSpec :: Network -> Spec
-forkIdScriptSpec net =
-    when (isJust (getSigHashForkId net)) $
-    it "can verify scripts from forkid_script_tests.json file" $ do
-        xs <- readTestFile "forkid_script_tests" :: IO [A.Value]
-        let vectors =
-                mapMaybe (A.decode . A.encode) xs :: [( [Word64]
-                                                      , String
-                                                      , String
-                                                      , String
-                                                      , String
-                                                      , String)]
-        length vectors `shouldBe` 3
-        forM_ vectors $ \([valBTC], siStr, soStr, _, res, _) -> do
-            let val = valBTC * 100000000
-                scriptSig = parseScript siStr
-                scriptPubKey = parseScript soStr
-                decodedOutput =
-                    fromRight (error $ "Could not decode output: " <> soStr) $
-                    decodeOutputBS scriptPubKey
-                ver =
-                    verifyStdInput
-                        net
-                        (spendTx scriptPubKey val scriptSig)
-                        0
-                        decodedOutput
-                        val
-            case res of
-                "OK" -> ver `shouldBe` True
-                _    -> ver `shouldBe` False
-
-creditTx :: ByteString -> Word64 -> Tx
-creditTx scriptPubKey val =
-    Tx 1 [txI] [txO] [] 0
-  where
-    txO = TxOut {outValue = val, scriptOutput = scriptPubKey}
-    txI =
-        TxIn
-        { prevOutput = nullOutPoint
-        , scriptInput = S.encode $ Script [OP_0, OP_0]
-        , txInSequence = maxBound
-        }
-
-spendTx :: ByteString -> Word64 -> ByteString -> Tx
-spendTx scriptPubKey val scriptSig =
-    Tx 1 [txI] [txO] [] 0
-  where
-    txO = TxOut {outValue = val, scriptOutput = B.empty}
-    txI =
-        TxIn
-        { prevOutput = OutPoint (txHash $ creditTx scriptPubKey val) 0
-        , scriptInput = scriptSig
-        , txInSequence = maxBound
-        }
-
-parseScript :: String -> ByteString
-parseScript str =
-    B.concat $ fromMaybe err $ mapM f $ words str
-  where
-    f = decodeHex . cs . dropHex . replaceToken
-    dropHex ('0':'x':xs) = xs
-    dropHex xs           = xs
-    err = error $ "Could not decode script: " <> str
-
-replaceToken :: String -> String
-replaceToken str = case readMaybe $ "OP_" <> str of
-    Just opcode -> "0x" <> cs (encodeHex $ S.encode (opcode :: ScriptOp))
-    _           -> str
-
-strictSigSpec :: Network -> Spec
-strictSigSpec net =
-    when (getNetworkName net == "btc") $ do
-        it "can decode strict signatures" $ do
-            xs <- readTestFile "sig_strict"
-            let vectors = mapMaybe decodeHex xs
-            length vectors `shouldBe` 3
-            forM_ vectors $ \sig ->
-                decodeTxSig net sig `shouldSatisfy` isRight
-        it "can detect non-strict signatures" $ do
-            xs <- readTestFile "sig_nonstrict"
-            let vectors = mapMaybe decodeHex xs
-            length vectors `shouldBe` 17
-            forM_ vectors $ \sig ->
-                decodeTxSig net sig `shouldSatisfy` isLeft
-
-txSigHashSpec :: Network -> Spec
-txSigHashSpec net =
-    when (getNetworkName net == "btc") $
-    it "can produce valid sighashes from sighash.json test vectors" $ do
-        xs <- readTestFile "sighash" :: IO [A.Value]
-        let vectors =
-                mapMaybe (A.decode . A.encode) xs :: [( String
-                                                      , String
-                                                      , Int
-                                                      , Integer
-                                                      , String)]
-        length vectors `shouldBe` 500
-        forM_ vectors $ \(txStr, scpStr, i, shI, resStr) -> do
-            let tx = fromString txStr
-                s =
-                    fromMaybe (error $ "Could not decode script: " <> cs scpStr) $
-                    eitherToMaybe . S.decode =<< decodeHex (cs scpStr)
-                sh = fromIntegral shI
-                res =
-                    eitherToMaybe . S.decode . B.reverse =<<
-                    decodeHex (cs resStr)
-            Just (txSigHash net tx s 0 i sh) `shouldBe` res
-
-txSigHashForkIdSpec :: Network -> Spec
-txSigHashForkIdSpec net =
-    when (getNetworkName net == "btc") $
-    it "can produce valid sighashes from forkid_sighash.json test vectors" $ do
-        xs <- readTestFile "forkid_sighash" :: IO [A.Value]
-        let vectors =
-                mapMaybe (A.decode . A.encode) xs :: [( String
-                                                      , String
-                                                      , Int
-                                                      , Word64
-                                                      , Integer
-                                                      , String)]
-        length vectors `shouldBe` 13
-        forM_ vectors $ \(txStr, scpStr, i, val, shI, resStr) -> do
-            let tx = fromString txStr
-                s =
-                    fromMaybe (error $ "Could not decode script: " <> cs scpStr) $
-                    eitherToMaybe . S.decode =<< decodeHex (cs scpStr)
-                sh = fromIntegral shI
-                res = eitherToMaybe . S.decode =<< decodeHex (cs resStr)
-            Just (txSigHashForkId net tx s val i sh) `shouldBe` res
-
-sigHashSpec :: Network -> Spec
-sigHashSpec net = do
-    it "can read . show" $
-        property $ forAll arbitrarySigHash $ \sh -> read (show sh) `shouldBe` sh
-    it "can correctly show" $ do
-        show (0x00 :: SigHash) `shouldBe` "SigHash " <> show (0x00 :: Word32)
-        show (0x01 :: SigHash) `shouldBe` "SigHash " <> show (0x01 :: Word32)
-        show (0xff :: SigHash) `shouldBe` "SigHash " <> show (0xff :: Word32)
-        show (0xabac3344 :: SigHash) `shouldBe` "SigHash " <>
-            show (0xabac3344 :: Word32)
-    it "can add a forkid" $ do
-        0x00 `sigHashAddForkId` 0x00 `shouldBe` 0x00
-        0xff `sigHashAddForkId` 0x00ffffff `shouldBe` 0xffffffff
-        0xffff `sigHashAddForkId` 0x00aaaaaa `shouldBe` 0xaaaaaaff
-        0xffff `sigHashAddForkId` 0xaaaaaaaa `shouldBe` 0xaaaaaaff
-        0xffff `sigHashAddForkId` 0x00004444 `shouldBe` 0x004444ff
-        0xff01 `sigHashAddForkId` 0x44440000 `shouldBe` 0x44000001
-        0xff03 `sigHashAddForkId` 0x00550000 `shouldBe` 0x55000003
-    it "can extract a forkid" $ do
-        sigHashGetForkId 0x00000000 `shouldBe` 0x00000000
-        sigHashGetForkId 0x80000000 `shouldBe` 0x00800000
-        sigHashGetForkId 0xffffffff `shouldBe` 0x00ffffff
-        sigHashGetForkId 0xabac3403 `shouldBe` 0x00abac34
-    it "can build some vectors" $ do
-        sigHashAll `shouldBe` 0x01
-        sigHashNone `shouldBe` 0x02
-        sigHashSingle `shouldBe` 0x03
-        setForkIdFlag sigHashAll `shouldBe` 0x41
-        setAnyoneCanPayFlag sigHashAll `shouldBe` 0x81
-        setAnyoneCanPayFlag (setForkIdFlag sigHashAll) `shouldBe` 0xc1
-    it "can test flags" $ do
-        hasForkIdFlag sigHashAll `shouldBe` False
-        hasForkIdFlag (setForkIdFlag sigHashAll) `shouldBe` True
-        hasAnyoneCanPayFlag sigHashAll `shouldBe` False
-        hasAnyoneCanPayFlag (setAnyoneCanPayFlag sigHashAll) `shouldBe` True
-        isSigHashAll sigHashNone `shouldBe` False
-        isSigHashAll sigHashAll `shouldBe` True
-        isSigHashNone sigHashSingle `shouldBe` False
-        isSigHashNone sigHashNone `shouldBe` True
-        isSigHashSingle sigHashAll `shouldBe` False
-        isSigHashSingle sigHashSingle `shouldBe` True
-        isSigHashUnknown sigHashAll `shouldBe` False
-        isSigHashUnknown sigHashNone `shouldBe` False
-        isSigHashUnknown sigHashSingle `shouldBe` False
-        isSigHashUnknown 0x00 `shouldBe` True
-        isSigHashUnknown 0x04 `shouldBe` True
-    it "can decodeTxSig . encode a TxSignature" $
-        property $
-        forAll (arbitraryTxSignature net) $ \(_, _, ts) ->
-            decodeTxSig net (encodeTxSig ts) `shouldBe` Right ts
-    it "can produce the sighash one" $
-        property $
-        forAll (arbitraryTx net) $ forAll arbitraryScript . testSigHashOne net
-
-testSigHashOne :: Network -> Tx -> Script -> Word64 -> Bool -> Property
-testSigHashOne net tx s val acp =
-    not (null $ txIn tx) ==>
-    if length (txIn tx) > length (txOut tx)
-        then res `shouldBe` one
-        else res `shouldNotBe` one
-  where
-    res = txSigHash net tx s val (length (txIn tx) - 1) (f sigHashSingle)
-    one = "0100000000000000000000000000000000000000000000000000000000000000"
-    f =
-        if acp
-            then setAnyoneCanPayFlag
-            else id
-
-{-- Test Utilities --}
-
-readTestFile :: A.FromJSON a => FilePath -> IO a
-readTestFile fp = do
-    bs <- L.readFile $ "data/" <> fp <> ".json"
-    maybe (error $ "Could not read test file " <> fp) return $ A.decode bs
-
-{- Parse tests from bitcoin-qt repository -}
-
-mapMulSigVector :: ((Text, Text), Int) -> Spec
-mapMulSigVector (v, i) =
-    it name $ runMulSigVector v
-  where
-    name = "check multisig vector " <> show i
-
-runMulSigVector :: (Text, Text) -> Assertion
-runMulSigVector (a, ops) = assertBool "multisig vector" $ Just a == b
-  where
-    s = do
-        s' <- decodeHex ops
-        eitherToMaybe $ S.decode s'
-    b = do
-        o <- s
-        d <- eitherToMaybe $ decodeOutput o
-        addrToString btc $ payToScriptAddress d
-
-sigDecodeMap :: Network -> (Text, Int) -> Spec
-sigDecodeMap net (_, i) =
-    it ("check signature " ++ show i) func
-  where
-    func = testSigDecode net $ scriptSigSignatures !! i
-
-testSigDecode :: Network -> Text -> Assertion
-testSigDecode net str =
-    let bs = fromJust $ decodeHex str
-        eitherSig = decodeTxSig net bs
-    in assertBool
-           (unwords
-                [ "Decode failed:"
-                , fromLeft (error "Decode did not fail") eitherSig
-                ]) $
-       isRight eitherSig
-
-mulSigVectors :: [(Text, Text)]
-mulSigVectors =
-    [ ( "3QJmV3qfvL9SuYo34YihAf3sRCW3qSinyC"
-      , "52410491bba2510912a5bd37da1fb5b1673010e43d2c6d812c514e91bfa9f2eb129e1c183329db55bd868e209aac2fbc02cb33d98fe74bf23f0c235d6126b1d8334f864104865c40293a680cb9c020e7b1e106d8c1916d3cef99aa431a56d253e69256dac09ef122b1a986818a7cb624532f062c1d1f8722084861c5c3291ccffef4ec687441048d2455d2403e08708fc1f556002f1b6cd83f992d085097f9974ab08a28838f07896fbab08f39495e15fa6fad6edbfb1e754e35fa1c7844c41f322a1863d4621353ae"
-      )
-    ]
-
-scriptSigSignatures :: [Text]
-scriptSigSignatures =
-     -- Signature in input of txid 1983a69265920c24f89aac81942b1a59f7eb30821a8b3fb258f88882b6336053
-    [ "304402205ca6249f43538908151fe67b26d020306c0e59fa206cf9f3ccf641f33357119d02206c82f244d04ac0a48024fb9cc246b66e58598acf206139bdb7b75a2941a2b1e401"
-      -- Signature in input of txid
-      -- fb0a1d8d34fa5537e461ac384bac761125e1bfa7fec286fa72511240fa66864d.
-      -- Strange DER sizes, but in Blockchain. Now invalid as Haskoin can only
-      -- decode strict signatures.
-      -- "3048022200002b83d59c1d23c08efd82ee0662fec23309c3adbcbd1f0b8695378db4b14e736602220000334a96676e58b1bb01784cb7c556dd8ce1c220171904da22e18fe1e7d1510db501"
-    ]
-
-
-testID :: (FromJSON a, ToJSON a, Eq a) => a -> Bool
-testID x =
-    (A.decode . A.encode) (singleton ("object" :: String) x) ==
-    Just (singleton ("object" :: String) x)
diff --git a/test/Network/Haskoin/Transaction/PartialSpec.hs b/test/Network/Haskoin/Transaction/PartialSpec.hs
deleted file mode 100644
--- a/test/Network/Haskoin/Transaction/PartialSpec.hs
+++ /dev/null
@@ -1,336 +0,0 @@
-{-# LANGUAGE OverloadedStrings #-}
-
-module Network.Haskoin.Transaction.PartialSpec (spec) where
-
-import           Control.Monad.Fail                  (MonadFail)
-import           Data.ByteString                     (ByteString)
-import           Data.Either                         (fromRight, isLeft,
-                                                      isRight)
-import           Data.HashMap.Strict                 (fromList, singleton)
-import           Data.Maybe                          (fromJust, isJust)
-import           Data.Serialize                      as S
-import           Data.Text                           (Text)
-import           Test.Hspec
-import           Test.HUnit                          (Assertion, assertBool,
-                                                      assertEqual)
-import           Test.QuickCheck
-
-import           Network.Haskoin.Address
-import           Network.Haskoin.Constants
-import           Network.Haskoin.Crypto
-import           Network.Haskoin.Keys
-import           Network.Haskoin.Script
-import           Network.Haskoin.Test
-import           Network.Haskoin.Transaction
-import           Network.Haskoin.Transaction.Partial
-import           Network.Haskoin.Util
-
-spec :: Spec
-spec = describe "partially signed bitcoin transaction unit tests" $ do
-    it "encodes trivial psbt" $
-        encodeHex (S.encode trivialPSBT) == trivialPSBTHex
-    it "decodes trivial psbt" $
-        decodeHexPSBT trivialPSBTHex == Right trivialPSBT
-    it "encodes and decodes non-empty transactions" $
-        S.decode (S.encode nonEmptyTransactionPSBT) == Right nonEmptyTransactionPSBT
-    it "does not decode invalid bip vectors" $
-        mapM_ invalidVecTest invalidVec
-    it "encodes valid bip vecs" $
-        mapM_ (uncurry encodeVecTest) validEncodeVec
-    it "decodes valid bip vecs" $
-        mapM_ (uncurry decodeVecTest) $ zip [1..] validVec
-    it "decodes vector 2" vec2Test
-    it "decodes vector 3" vec3Test
-    it "decodes vector 4" vec4Test
-    it "decodes vector 5" vec5Test
-    it "decodes vector 6" vec6Test
-    it "signed and finalized p2pkh PSBTs verify" $ property $
-        forAll arbitraryKeyPair $ verifyNonWitnessPSBT btc . unfinalizedPkhPSBT btc
-    it "signed and finalized multisig PSBTs verify" $ property $
-        forAll arbitraryMultiSig $ verifyNonWitnessPSBT btc . unfinalizedMsPSBT btc
-
-vec2Test :: Assertion
-vec2Test = do
-    psbt <- decodeHexPSBTM "Cannot parse validVec2" validVec2Hex
-    assertEqual "2 inputs" 2 (length $ inputs psbt)
-    assertEqual "2 outputs" 2 (length $ outputs psbt)
-    assertBool "final script sig" $ isJust (finalScriptSig . head $ inputs psbt)
-
-    let rdmScript = fromJust . inputRedeemScript $ inputs psbt !! 1
-    assertBool "p2wpkh" $ (isPayWitnessPKHash <$> decodeOutput rdmScript) == Right True
-
-    let scrptPubKey = witnessScriptPubKey $ inputs psbt !! 1
-        rdmScriptP2SH = toP2SH rdmScript
-    assertEqual "redeem script pubkey equal" rdmScriptP2SH scrptPubKey
-    assertEqual "expected redeem script"  expectedOut rdmScriptP2SH
-
-    mapM_ (assertEqual "outputs are empty" emptyOutput) (outputs psbt)
-
-vec3Test :: Assertion
-vec3Test = do
-    psbt <- decodeHexPSBTM "Cannot parse validVec3" validVec3Hex
-    assertEqual "1 input" 1 (length $ inputs psbt)
-    assertEqual "2 outputs" 2 (length $ outputs psbt)
-    let txInput = head . txIn $ unsignedTransaction psbt
-        firstInput = head $ inputs psbt
-        Just utx = nonWitnessUtxo firstInput
-        OutPoint prevHash prevVOut = prevOutput txInput
-    assertEqual "txids of inputs match" prevHash (txHash utx)
-    let prevOutputKey = fromRight (error "Could not decode key")
-                      . decodeOutputBS . scriptOutput $ txOut utx !! fromIntegral prevVOut
-    assertBool "p2pkh" $ isPayPKHash prevOutputKey
-    assertEqual "sighash type" sigHashAll (fromJust $ sigHashType firstInput)
-
-vec4Test :: Assertion
-vec4Test = do
-    psbt <- decodeHexPSBTM "Cannot parse validVec4" validVec4Hex
-    assertEqual "2 inputs" 2 (length $ inputs psbt)
-    assertEqual "2 outputs" 2 (length $ outputs psbt)
-    let firstInput = head $ inputs psbt
-        secondInput = inputs psbt !! 1
-    assertEqual "first input not final script sig" Nothing (finalScriptSig firstInput)
-    assertEqual "second input not final script sig" Nothing (finalScriptSig secondInput)
-
-    let rdmScript = fromJust $ inputRedeemScript secondInput
-    assertBool "p2wpkh" $ (isPayWitnessPKHash <$> decodeOutput rdmScript) == Right True
-
-    let scrptPubKey = witnessScriptPubKey secondInput
-        rdmScriptP2SH = toP2SH rdmScript
-    assertEqual "redeem script pubkey equal" rdmScriptP2SH scrptPubKey
-    assertEqual "expected redeem script"  expectedOut rdmScriptP2SH
-
-    assertBool "all non-empty outputs" $ emptyOutput `notElem` outputs psbt
-
-vec5Test :: Assertion
-vec5Test = do
-    psbt <- decodeHexPSBTM "Cannot parse validVec5" validVec5Hex
-    assertEqual "1 input" 1 (length $ inputs psbt)
-    assertEqual "1 output" 1 (length $ outputs psbt)
-    let input = head $ inputs psbt
-    assertEqual "input not final script sig" Nothing (finalScriptSig input)
-
-    let rdmScript = fromJust $ inputRedeemScript input
-    assertBool "p2wsh" $ (isPayWitnessScriptHash <$> decodeOutput rdmScript) == Right True
-
-    let scrptPubKey = witnessScriptPubKey input
-        rdmScriptP2SH = toP2SH rdmScript
-    assertEqual "redeem script pubkey equal" rdmScriptP2SH scrptPubKey
-    assertEqual "expected redeem script"  expectedOut2 rdmScriptP2SH
-  where
-    expectedOut2 = fromRight (error "could not decode expected output")
-                . decodeOutputBS . fromJust $ decodeHex "a9146345200f68d189e1adc0df1c4d16ea8f14c0dbeb87"
-
-vec6Test :: Assertion
-vec6Test = do
-    psbt <- decodeHexPSBTM "Cannot parse validVec6" validVec6Hex
-    assertEqual "1 input" 1 (length $ inputs psbt)
-    assertEqual "1 output" 1 (length $ outputs psbt)
-
-    let tx = unsignedTransaction psbt
-    assertEqual "correct transaction" "75c5c9665a570569ad77dd1279e6fd4628a093c4dcbf8d41532614044c14c115" (txHash tx)
-
-    assertEqual "correct unknowns" expectedUnknowns (inputUnknown . head $ inputs psbt)
-  where
-    expectedUnknowns = UnknownMap $ singleton
-        (Key 0x0f (fromJust $ decodeHex "010203040506070809"))
-        (fromJust $ decodeHex "0102030405060708090a0b0c0d0e0f")
-
-expectedOut :: ScriptOutput
-expectedOut = fromRight (error "could not decode expected output")
-            . decodeOutputBS . fromJust $ decodeHex "a9143545e6e33b832c47050f24d3eeb93c9c03948bc787"
-
-witnessScriptPubKey :: Input -> ScriptOutput
-witnessScriptPubKey = fromRight (error "could not decode witness utxo")
-                    . decodeOutputBS . scriptOutput . fromJust . witnessUtxo
-
-decodeHexPSBT :: Text -> Either String PartiallySignedTransaction
-decodeHexPSBT = S.decode . fromJust . decodeHex
-
-decodeHexPSBTM :: (Monad m, MonadFail m) => String -> Text -> m PartiallySignedTransaction
-decodeHexPSBTM errMsg = either (fail . (errMsg <>) . (": " <>)) return . decodeHexPSBT
-
-hexScript :: Text -> ByteString
-hexScript =
-    either (error "Could not decode script") encodeScript . S.decode . fromJust . decodeHex
-  where
-    encodeScript :: Script -> ByteString
-    encodeScript = S.encode
-
-invalidVecTest :: Text -> Assertion
-invalidVecTest = assertBool "invalid psbt" . isLeft . decodeHexPSBT
-
-decodeVecTest :: Int -> Text -> Assertion
-decodeVecTest i = assertBool (show i <> " decodes correctly") . isRight . decodeHexPSBT
-
-encodeVecTest :: PartiallySignedTransaction -> Text -> Assertion
-encodeVecTest psbt hex = assertEqual "encodes correctly" (S.encode psbt) (fromJust $ decodeHex hex)
-
-trivialPSBT :: PartiallySignedTransaction
-trivialPSBT = PartiallySignedTransaction
-    { unsignedTransaction = Tx { txVersion = 2, txIn = [], txOut = [], txWitness = [], txLockTime = 0 }
-    , globalUnknown = UnknownMap mempty
-    , inputs = []
-    , outputs = []
-    }
-
-trivialPSBTHex :: Text
-trivialPSBTHex = "70736274ff01000a0200000000000000000000"
-
-nonEmptyTransactionPSBT :: PartiallySignedTransaction
-nonEmptyTransactionPSBT = emptyPSBT testTx1
-
-verifyNonWitnessPSBT :: Network -> PartiallySignedTransaction -> Bool
-verifyNonWitnessPSBT net psbt = verifyStdTx net (finalTransaction (complete psbt)) sigData
-  where
-    sigData = inputSigData =<< zip (inputs psbt) (txIn $ unsignedTransaction psbt)
-    decodeOutScript = fromRight (error "Could not parse output script") . decodeOutputBS
-    inputSigData (input, txInput) =
-        map (\(TxOut val script) -> (decodeOutScript script, val, prevOutput txInput))
-            (txOut . fromJust $ nonWitnessUtxo input)
-
-unfinalizedPkhPSBT :: Network -> (SecKeyI, PubKeyI) -> PartiallySignedTransaction
-unfinalizedPkhPSBT net (prvKey, pubKey) = (emptyPSBT currTx)
-    { inputs = [ emptyInput { nonWitnessUtxo = Just prevTx, partialSigs = singleton pubKey sig } ] }
-  where
-    currTx = unfinalizedTx (txHash prevTx)
-    prevTx = testUtxo [prevOut]
-    prevOutScript = addressToScript (pubKeyAddr pubKey)
-    prevOut = TxOut { outValue = 200000000, scriptOutput = S.encode prevOutScript }
-    h = txSigHash net currTx prevOutScript (outValue prevOut) 0 sigHashAll
-    sig = encodeTxSig $ TxSignature (signHash (secKeyData prvKey) h) sigHashAll
-
-arbitraryMultiSig :: Gen ([(SecKeyI, PubKeyI)], Int)
-arbitraryMultiSig = do
-    (m, n) <- arbitraryMSParam
-    keys <- vectorOf n arbitraryKeyPair
-    return (keys, m)
-
-unfinalizedMsPSBT :: Network -> ([(SecKeyI, PubKeyI)], Int) -> PartiallySignedTransaction
-unfinalizedMsPSBT net (keys, m) = (emptyPSBT currTx)
-    { inputs = [ emptyInput { nonWitnessUtxo = Just prevTx, partialSigs = sigs
-                            , inputRedeemScript = Just prevOutScript
-                            } ] }
-  where
-    currTx = unfinalizedTx (txHash prevTx)
-    prevTx = testUtxo [prevOut]
-    prevOutScript = encodeOutput $ PayMulSig (map snd keys) m
-    prevOut = TxOut { outValue = 200000000, scriptOutput = encodeOutputBS (toP2SH prevOutScript) }
-    h = txSigHash net currTx prevOutScript (outValue prevOut) 0 sigHashAll
-    sigs = fromList $ map sig keys
-    sig (prvKey, pubKey) = (pubKey, encodeTxSig $ TxSignature (signHash (secKeyData prvKey) h) sigHashAll)
-
-unfinalizedTx :: TxHash -> Tx
-unfinalizedTx prevHash = Tx
-        { txVersion = 2
-        , txIn = [ TxIn
-            { prevOutput = OutPoint prevHash 0
-            , scriptInput = ""
-            , txInSequence = 4294967294
-            } ]
-        , txOut =
-            [ TxOut { outValue = 99999699, scriptOutput = hexScript "76a914d0c59903c5bac2868760e90fd521a4665aa7652088ac" }
-            , TxOut { outValue = 100000000, scriptOutput = hexScript "a9143545e6e33b832c47050f24d3eeb93c9c03948bc787" }
-            ]
-        , txWitness = []
-        , txLockTime = 1257139
-        }
-
-invalidVec :: [Text]
-invalidVec =
-    [ "0200000001268171371edff285e937adeea4b37b78000c0566cbb3ad64641713ca42171bf6000000006a473044022070b2245123e6bf474d60c5b50c043d4c691a5d2435f09a34a7662a9dc251790a022001329ca9dacf280bdf30740ec0390422422c81cb45839457aeb76fc12edd95b3012102657d118d3357b8e0f4c2cd46db7b39f6d9c38d9a70abcb9b2de5dc8dbfe4ce31feffffff02d3dff505000000001976a914d0c59903c5bac2868760e90fd521a4665aa7652088ac00e1f5050000000017a9143545e6e33b832c47050f24d3eeb93c9c03948bc787b32e1300"
-    , "70736274ff0100750200000001268171371edff285e937adeea4b37b78000c0566cbb3ad64641713ca42171bf60000000000feffffff02d3dff505000000001976a914d0c59903c5bac2868760e90fd521a4665aa7652088ac00e1f5050000000017a9143545e6e33b832c47050f24d3eeb93c9c03948bc787b32e1300000100fda5010100000000010289a3c71eab4d20e0371bbba4cc698fa295c9463afa2e397f8533ccb62f9567e50100000017160014be18d152a9b012039daf3da7de4f53349eecb985ffffffff86f8aa43a71dff1448893a530a7237ef6b4608bbb2dd2d0171e63aec6a4890b40100000017160014fe3e9ef1a745e974d902c4355943abcb34bd5353ffffffff0200c2eb0b000000001976a91485cff1097fd9e008bb34af709c62197b38978a4888ac72fef84e2c00000017a914339725ba21efd62ac753a9bcd067d6c7a6a39d05870247304402202712be22e0270f394f568311dc7ca9a68970b8025fdd3b240229f07f8a5f3a240220018b38d7dcd314e734c9276bd6fb40f673325bc4baa144c800d2f2f02db2765c012103d2e15674941bad4a996372cb87e1856d3652606d98562fe39c5e9e7e413f210502483045022100d12b852d85dcd961d2f5f4ab660654df6eedcc794c0c33ce5cc309ffb5fce58d022067338a8e0e1725c197fb1a88af59f51e44e4255b20167c8684031c05d1f2592a01210223b72beef0965d10be0778efecd61fcac6f79a4ea169393380734464f84f2ab30000000000"
-    , "70736274ff0100fd0a010200000002ab0949a08c5af7c49b8212f417e2f15ab3f5c33dcf153821a8139f877a5b7be4000000006a47304402204759661797c01b036b25928948686218347d89864b719e1f7fcf57d1e511658702205309eabf56aa4d8891ffd111fdf1336f3a29da866d7f8486d75546ceedaf93190121035cdc61fc7ba971c0b501a646a2a83b102cb43881217ca682dc86e2d73fa88292feffffffab0949a08c5af7c49b8212f417e2f15ab3f5c33dcf153821a8139f877a5b7be40100000000feffffff02603bea0b000000001976a914768a40bbd740cbe81d988e71de2a4d5c71396b1d88ac8e240000000000001976a9146f4620b553fa095e721b9ee0efe9fa039cca459788ac00000000000001012000e1f5050000000017a9143545e6e33b832c47050f24d3eeb93c9c03948bc787010416001485d13537f2e265405a34dbafa9e3dda01fb82308000000"
-    , "70736274ff000100fda5010100000000010289a3c71eab4d20e0371bbba4cc698fa295c9463afa2e397f8533ccb62f9567e50100000017160014be18d152a9b012039daf3da7de4f53349eecb985ffffffff86f8aa43a71dff1448893a530a7237ef6b4608bbb2dd2d0171e63aec6a4890b40100000017160014fe3e9ef1a745e974d902c4355943abcb34bd5353ffffffff0200c2eb0b000000001976a91485cff1097fd9e008bb34af709c62197b38978a4888ac72fef84e2c00000017a914339725ba21efd62ac753a9bcd067d6c7a6a39d05870247304402202712be22e0270f394f568311dc7ca9a68970b8025fdd3b240229f07f8a5f3a240220018b38d7dcd314e734c9276bd6fb40f673325bc4baa144c800d2f2f02db2765c012103d2e15674941bad4a996372cb87e1856d3652606d98562fe39c5e9e7e413f210502483045022100d12b852d85dcd961d2f5f4ab660654df6eedcc794c0c33ce5cc309ffb5fce58d022067338a8e0e1725c197fb1a88af59f51e44e4255b20167c8684031c05d1f2592a01210223b72beef0965d10be0778efecd61fcac6f79a4ea169393380734464f84f2ab30000000000"
-    , "70736274ff0100750200000001268171371edff285e937adeea4b37b78000c0566cbb3ad64641713ca42171bf60000000000feffffff02d3dff505000000001976a914d0c59903c5bac2868760e90fd521a4665aa7652088ac00e1f5050000000017a9143545e6e33b832c47050f24d3eeb93c9c03948bc787b32e1300000100fda5010100000000010289a3c71eab4d20e0371bbba4cc698fa295c9463afa2e397f8533ccb62f9567e50100000017160014be18d152a9b012039daf3da7de4f53349eecb985ffffffff86f8aa43a71dff1448893a530a7237ef6b4608bbb2dd2d0171e63aec6a4890b40100000017160014fe3e9ef1a745e974d902c4355943abcb34bd5353ffffffff0200c2eb0b000000001976a91485cff1097fd9e008bb34af709c62197b38978a4888ac72fef84e2c00000017a914339725ba21efd62ac753a9bcd067d6c7a6a39d05870247304402202712be22e0270f394f568311dc7ca9a68970b8025fdd3b240229f07f8a5f3a240220018b38d7dcd314e734c9276bd6fb40f673325bc4baa144c800d2f2f02db2765c012103d2e15674941bad4a996372cb87e1856d3652606d98562fe39c5e9e7e413f210502483045022100d12b852d85dcd961d2f5f4ab660654df6eedcc794c0c33ce5cc309ffb5fce58d022067338a8e0e1725c197fb1a88af59f51e44e4255b20167c8684031c05d1f2592a01210223b72beef0965d10be0778efecd61fcac6f79a4ea169393380734464f84f2ab30000000001003f0200000001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000ffffffff010000000000000000036a010000000000000000"
-    , "70736274ff020001550200000001279a2323a5dfb51fc45f220fa58b0fc13e1e3342792a85d7e36cd6333b5cbc390000000000ffffffff01a05aea0b000000001976a914ffe9c0061097cc3b636f2cb0460fa4fc427d2b4588ac0000000000010120955eea0b0000000017a9146345200f68d189e1adc0df1c4d16ea8f14c0dbeb87220203b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd4646304302200424b58effaaa694e1559ea5c93bbfd4a89064224055cdf070b6771469442d07021f5c8eb0fea6516d60b8acb33ad64ede60e8785bfb3aa94b99bdf86151db9a9a010104220020771fd18ad459666dd49f3d564e3dbc42f4c84774e360ada16816a8ed488d5681010547522103b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd462103de55d1e1dac805e3f8a58c1fbf9b94c02f3dbaafe127fefca4995f26f82083bd52ae220603b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd4610b4a6ba67000000800000008004000080220603de55d1e1dac805e3f8a58c1fbf9b94c02f3dbaafe127fefca4995f26f82083bd10b4a6ba670000008000000080050000800000"
-    , "70736274ff0100550200000001279a2323a5dfb51fc45f220fa58b0fc13e1e3342792a85d7e36cd6333b5cbc390000000000ffffffff01a05aea0b000000001976a914ffe9c0061097cc3b636f2cb0460fa4fc427d2b4588ac000000000002010020955eea0b0000000017a9146345200f68d189e1adc0df1c4d16ea8f14c0dbeb87220203b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd4646304302200424b58effaaa694e1559ea5c93bbfd4a89064224055cdf070b6771469442d07021f5c8eb0fea6516d60b8acb33ad64ede60e8785bfb3aa94b99bdf86151db9a9a010104220020771fd18ad459666dd49f3d564e3dbc42f4c84774e360ada16816a8ed488d5681010547522103b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd462103de55d1e1dac805e3f8a58c1fbf9b94c02f3dbaafe127fefca4995f26f82083bd52ae220603b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd4610b4a6ba67000000800000008004000080220603de55d1e1dac805e3f8a58c1fbf9b94c02f3dbaafe127fefca4995f26f82083bd10b4a6ba670000008000000080050000800000"
-    , "70736274ff0100550200000001279a2323a5dfb51fc45f220fa58b0fc13e1e3342792a85d7e36cd6333b5cbc390000000000ffffffff01a05aea0b000000001976a914ffe9c0061097cc3b636f2cb0460fa4fc427d2b4588ac0000000000010120955eea0b0000000017a9146345200f68d189e1adc0df1c4d16ea8f14c0dbeb87210203b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd46304302200424b58effaaa694e1559ea5c93bbfd4a89064224055cdf070b6771469442d07021f5c8eb0fea6516d60b8acb33ad64ede60e8785bfb3aa94b99bdf86151db9a9a010104220020771fd18ad459666dd49f3d564e3dbc42f4c84774e360ada16816a8ed488d5681010547522103b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd462103de55d1e1dac805e3f8a58c1fbf9b94c02f3dbaafe127fefca4995f26f82083bd52ae220603b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd4610b4a6ba67000000800000008004000080220603de55d1e1dac805e3f8a58c1fbf9b94c02f3dbaafe127fefca4995f26f82083bd10b4a6ba670000008000000080050000800000"
-    , "70736274ff0100550200000001279a2323a5dfb51fc45f220fa58b0fc13e1e3342792a85d7e36cd6333b5cbc390000000000ffffffff01a05aea0b000000001976a914ffe9c0061097cc3b636f2cb0460fa4fc427d2b4588ac0000000000010120955eea0b0000000017a9146345200f68d189e1adc0df1c4d16ea8f14c0dbeb87220203b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd4646304302200424b58effaaa694e1559ea5c93bbfd4a89064224055cdf070b6771469442d07021f5c8eb0fea6516d60b8acb33ad64ede60e8785bfb3aa94b99bdf86151db9a9a01020400220020771fd18ad459666dd49f3d564e3dbc42f4c84774e360ada16816a8ed488d5681010547522103b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd462103de55d1e1dac805e3f8a58c1fbf9b94c02f3dbaafe127fefca4995f26f82083bd52ae220603b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd4610b4a6ba67000000800000008004000080220603de55d1e1dac805e3f8a58c1fbf9b94c02f3dbaafe127fefca4995f26f82083bd10b4a6ba670000008000000080050000800000"
-    , "70736274ff0100550200000001279a2323a5dfb51fc45f220fa58b0fc13e1e3342792a85d7e36cd6333b5cbc390000000000ffffffff01a05aea0b000000001976a914ffe9c0061097cc3b636f2cb0460fa4fc427d2b4588ac0000000000010120955eea0b0000000017a9146345200f68d189e1adc0df1c4d16ea8f14c0dbeb87220203b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd4646304302200424b58effaaa694e1559ea5c93bbfd4a89064224055cdf070b6771469442d07021f5c8eb0fea6516d60b8acb33ad64ede60e8785bfb3aa94b99bdf86151db9a9a010104220020771fd18ad459666dd49f3d564e3dbc42f4c84774e360ada16816a8ed488d568102050047522103b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd462103de55d1e1dac805e3f8a58c1fbf9b94c02f3dbaafe127fefca4995f26f82083bd52ae220603b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd4610b4a6ba67000000800000008004000080220603de55d1e1dac805e3f8a58c1fbf9b94c02f3dbaafe127fefca4995f26f82083bd10b4a6ba670000008000000080050000800000"
-    , "70736274ff0100550200000001279a2323a5dfb51fc45f220fa58b0fc13e1e3342792a85d7e36cd6333b5cbc390000000000ffffffff01a05aea0b000000001976a914ffe9c0061097cc3b636f2cb0460fa4fc427d2b4588ac0000000000010120955eea0b0000000017a9146345200f68d189e1adc0df1c4d16ea8f14c0dbeb87220203b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd4646304302200424b58effaaa694e1559ea5c93bbfd4a89064224055cdf070b6771469442d07021f5c8eb0fea6516d60b8acb33ad64ede60e8785bfb3aa94b99bdf86151db9a9a010104220020771fd18ad459666dd49f3d564e3dbc42f4c84774e360ada16816a8ed488d5681010547522103b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd462103de55d1e1dac805e3f8a58c1fbf9b94c02f3dbaafe127fefca4995f26f82083bd52ae210603b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd10b4a6ba67000000800000008004000080220603de55d1e1dac805e3f8a58c1fbf9b94c02f3dbaafe127fefca4995f26f82083bd10b4a6ba670000008000000080050000800000"
-    , "70736274ff01009a020000000258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd750000000000ffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d0100000000ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f0000000000020000bb0200000001aad73931018bd25f84ae400b68848be09db706eac2ac18298babee71ab656f8b0000000048473044022058f6fc7c6a33e1b31548d481c826c015bd30135aad42cd67790dab66d2ad243b02204a1ced2604c6735b6393e5b41691dd78b00f0c5942fb9f751856faa938157dba01feffffff0280f0fa020000000017a9140fb9463421696b82c833af241c78c17ddbde493487d0f20a270100000017a91429ca74f8a08f81999428185c97b5d852e4063f6187650000000107da00473044022074018ad4180097b873323c0015720b3684cc8123891048e7dbcd9b55ad679c99022073d369b740e3eb53dcefa33823c8070514ca55a7dd9544f157c167913261118c01483045022100f61038b308dc1da865a34852746f015772934208c6d24454393cd99bdf2217770220056e675a675a6d0a02b85b14e5e29074d8a25a9b5760bea2816f661910a006ea01475221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752ae0001012000c2eb0b0000000017a914b7f5faf40e3d40a5a459b1db3535f2b72fa921e8870107232200208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b20289030108da0400473044022062eb7a556107a7c73f45ac4ab5a1dddf6f7075fb1275969a7f383efff784bcb202200c05dbb7470dbf2f08557dd356c7325c1ed30913e996cd3840945db12228da5f01473044022065f45ba5998b59a27ffe1a7bed016af1f1f90d54b3aa8f7450aa5f56a25103bd02207f724703ad1edb96680b284b56d4ffcb88f7fb759eabbe08aa30f29b851383d20147522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae00220203a9a4c37f5996d3aa25dbac6b570af0650394492942460b354753ed9eeca5877110d90c6a4f000000800000008004000080002202027f6399757d2eff55a136ad02c684b1838b6556e5f1b6b34282a94b6b5005109610d90c6a4f00000080000000800500008000"
-    , "70736274ff01009a020000000258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd750000000000ffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d0100000000ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f00000000000100bb0200000001aad73931018bd25f84ae400b68848be09db706eac2ac18298babee71ab656f8b0000000048473044022058f6fc7c6a33e1b31548d481c826c015bd30135aad42cd67790dab66d2ad243b02204a1ced2604c6735b6393e5b41691dd78b00f0c5942fb9f751856faa938157dba01feffffff0280f0fa020000000017a9140fb9463421696b82c833af241c78c17ddbde493487d0f20a270100000017a91429ca74f8a08f81999428185c97b5d852e4063f618765000000020700da00473044022074018ad4180097b873323c0015720b3684cc8123891048e7dbcd9b55ad679c99022073d369b740e3eb53dcefa33823c8070514ca55a7dd9544f157c167913261118c01483045022100f61038b308dc1da865a34852746f015772934208c6d24454393cd99bdf2217770220056e675a675a6d0a02b85b14e5e29074d8a25a9b5760bea2816f661910a006ea01475221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752ae0001012000c2eb0b0000000017a914b7f5faf40e3d40a5a459b1db3535f2b72fa921e8870107232200208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b20289030108da0400473044022062eb7a556107a7c73f45ac4ab5a1dddf6f7075fb1275969a7f383efff784bcb202200c05dbb7470dbf2f08557dd356c7325c1ed30913e996cd3840945db12228da5f01473044022065f45ba5998b59a27ffe1a7bed016af1f1f90d54b3aa8f7450aa5f56a25103bd02207f724703ad1edb96680b284b56d4ffcb88f7fb759eabbe08aa30f29b851383d20147522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae00220203a9a4c37f5996d3aa25dbac6b570af0650394492942460b354753ed9eeca5877110d90c6a4f000000800000008004000080002202027f6399757d2eff55a136ad02c684b1838b6556e5f1b6b34282a94b6b5005109610d90c6a4f00000080000000800500008000"
-    , "70736274ff01009a020000000258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd750000000000ffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d0100000000ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f00000000000100bb0200000001aad73931018bd25f84ae400b68848be09db706eac2ac18298babee71ab656f8b0000000048473044022058f6fc7c6a33e1b31548d481c826c015bd30135aad42cd67790dab66d2ad243b02204a1ced2604c6735b6393e5b41691dd78b00f0c5942fb9f751856faa938157dba01feffffff0280f0fa020000000017a9140fb9463421696b82c833af241c78c17ddbde493487d0f20a270100000017a91429ca74f8a08f81999428185c97b5d852e4063f6187650000000107da00473044022074018ad4180097b873323c0015720b3684cc8123891048e7dbcd9b55ad679c99022073d369b740e3eb53dcefa33823c8070514ca55a7dd9544f157c167913261118c01483045022100f61038b308dc1da865a34852746f015772934208c6d24454393cd99bdf2217770220056e675a675a6d0a02b85b14e5e29074d8a25a9b5760bea2816f661910a006ea01475221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752ae0001012000c2eb0b0000000017a914b7f5faf40e3d40a5a459b1db3535f2b72fa921e8870107232200208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b2028903020800da0400473044022062eb7a556107a7c73f45ac4ab5a1dddf6f7075fb1275969a7f383efff784bcb202200c05dbb7470dbf2f08557dd356c7325c1ed30913e996cd3840945db12228da5f01473044022065f45ba5998b59a27ffe1a7bed016af1f1f90d54b3aa8f7450aa5f56a25103bd02207f724703ad1edb96680b284b56d4ffcb88f7fb759eabbe08aa30f29b851383d20147522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae00220203a9a4c37f5996d3aa25dbac6b570af0650394492942460b354753ed9eeca5877110d90c6a4f000000800000008004000080002202027f6399757d2eff55a136ad02c684b1838b6556e5f1b6b34282a94b6b5005109610d90c6a4f00000080000000800500008000"
-    , "70736274ff01009a020000000258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd750000000000ffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d0100000000ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f00000000000100bb0200000001aad73931018bd25f84ae400b68848be09db706eac2ac18298babee71ab656f8b0000000048473044022058f6fc7c6a33e1b31548d481c826c015bd30135aad42cd67790dab66d2ad243b02204a1ced2604c6735b6393e5b41691dd78b00f0c5942fb9f751856faa938157dba01feffffff0280f0fa020000000017a9140fb9463421696b82c833af241c78c17ddbde493487d0f20a270100000017a91429ca74f8a08f81999428185c97b5d852e4063f6187650000000107da00473044022074018ad4180097b873323c0015720b3684cc8123891048e7dbcd9b55ad679c99022073d369b740e3eb53dcefa33823c8070514ca55a7dd9544f157c167913261118c01483045022100f61038b308dc1da865a34852746f015772934208c6d24454393cd99bdf2217770220056e675a675a6d0a02b85b14e5e29074d8a25a9b5760bea2816f661910a006ea01475221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752ae0001012000c2eb0b0000000017a914b7f5faf40e3d40a5a459b1db3535f2b72fa921e8870107232200208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b20289030108da0400473044022062eb7a556107a7c73f45ac4ab5a1dddf6f7075fb1275969a7f383efff784bcb202200c05dbb7470dbf2f08557dd356c7325c1ed30913e996cd3840945db12228da5f01473044022065f45ba5998b59a27ffe1a7bed016af1f1f90d54b3aa8f7450aa5f56a25103bd02207f724703ad1edb96680b284b56d4ffcb88f7fb759eabbe08aa30f29b851383d20147522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae00210203a9a4c37f5996d3aa25dbac6b570af0650394492942460b354753ed9eeca58710d90c6a4f000000800000008004000080002202027f6399757d2eff55a136ad02c684b1838b6556e5f1b6b34282a94b6b5005109610d90c6a4f00000080000000800500008000"
-    , "70736274ff0100730200000001301ae986e516a1ec8ac5b4bc6573d32f83b465e23ad76167d68b38e730b4dbdb0000000000ffffffff02747b01000000000017a91403aa17ae882b5d0d54b25d63104e4ffece7b9ea2876043993b0000000017a914b921b1ba6f722e4bfa83b6557a3139986a42ec8387000000000001011f00ca9a3b00000000160014d2d94b64ae08587eefc8eeb187c601e939f9037c0203000100000000010016001462e9e982fff34dd8239610316b090cd2a3b747cb000100220020876bad832f1d168015ed41232a9ea65a1815d9ef13c0ef8759f64b5b2b278a65010125512103b7ce23a01c5b4bf00a642537cdfabb315b668332867478ef51309d2bd57f8a8751ae00"
-    , "70736274ff0100730200000001301ae986e516a1ec8ac5b4bc6573d32f83b465e23ad76167d68b38e730b4dbdb0000000000ffffffff02747b01000000000017a91403aa17ae882b5d0d54b25d63104e4ffece7b9ea2876043993b0000000017a914b921b1ba6f722e4bfa83b6557a3139986a42ec8387000000000001011f00ca9a3b00000000160014d2d94b64ae08587eefc8eeb187c601e939f9037c0002000016001462e9e982fff34dd8239610316b090cd2a3b747cb000100220020876bad832f1d168015ed41232a9ea65a1815d9ef13c0ef8759f64b5b2b278a65010125512103b7ce23a01c5b4bf00a642537cdfabb315b668332867478ef51309d2bd57f8a8751ae00"
-    , "70736274ff0100730200000001301ae986e516a1ec8ac5b4bc6573d32f83b465e23ad76167d68b38e730b4dbdb0000000000ffffffff02747b01000000000017a91403aa17ae882b5d0d54b25d63104e4ffece7b9ea2876043993b0000000017a914b921b1ba6f722e4bfa83b6557a3139986a42ec8387000000000001011f00ca9a3b00000000160014d2d94b64ae08587eefc8eeb187c601e939f9037c00010016001462e9e982fff34dd8239610316b090cd2a3b747cb000100220020876bad832f1d168015ed41232a9ea65a1815d9ef13c0ef8759f64b5b2b278a6521010025512103b7ce23a01c5b4bf00a642537cdfabb315b668332867478ef51309d2bd57f8a8751ae00"
-    ]
-
-
-validEncodeVec :: [(PartiallySignedTransaction, Text)]
-validEncodeVec = [(validVec1, validVec1Hex)]
-
-testTx1 :: Tx
-testTx1 = Tx
-        { txVersion = 2
-        , txIn = [ TxIn
-            { prevOutput = OutPoint "f61b1742ca13176464adb3cb66050c00787bb3a4eead37e985f2df1e37718126" 0
-            , scriptInput = ""
-            , txInSequence = 4294967294
-            } ]
-        , txOut =
-            [ TxOut { outValue = 99999699, scriptOutput = hexScript "76a914d0c59903c5bac2868760e90fd521a4665aa7652088ac" }
-            , TxOut { outValue = 100000000, scriptOutput = hexScript "a9143545e6e33b832c47050f24d3eeb93c9c03948bc787" }
-            ]
-        , txWitness = []
-        , txLockTime = 1257139
-        }
-
-testUtxo :: [TxOut] -> Tx
-testUtxo prevOuts = Tx
-        { txVersion = 1
-        , txIn =
-            [ TxIn
-                { prevOutput = OutPoint "e567952fb6cc33857f392efa3a46c995a28f69cca4bb1b37e0204dab1ec7a389" 1
-                , scriptInput = hexScript "160014be18d152a9b012039daf3da7de4f53349eecb985"
-                , txInSequence = 4294967295
-                }
-            , TxIn
-                { prevOutput = OutPoint "b490486aec3ae671012dddb2bb08466bef37720a533a894814ff1da743aaf886" 1
-                , scriptInput = hexScript "160014fe3e9ef1a745e974d902c4355943abcb34bd5353"
-                , txInSequence = 4294967295
-                }
-            ]
-        , txOut = prevOuts
-        , txWitness =
-            [
-              [ fromJust $ decodeHex "304402202712be22e0270f394f568311dc7ca9a68970b8025fdd3b240229f07f8a5f3a240220018b38d7dcd314e734c9276bd6fb40f673325bc4baa144c800d2f2f02db2765c01"
-              , fromJust $ decodeHex "03d2e15674941bad4a996372cb87e1856d3652606d98562fe39c5e9e7e413f2105"
-              ]
-            , [ fromJust $ decodeHex "3045022100d12b852d85dcd961d2f5f4ab660654df6eedcc794c0c33ce5cc309ffb5fce58d022067338a8e0e1725c197fb1a88af59f51e44e4255b20167c8684031c05d1f2592a01"
-              , fromJust $ decodeHex "0223b72beef0965d10be0778efecd61fcac6f79a4ea169393380734464f84f2ab3"
-              ]
-            ]
-        , txLockTime = 0
-        }
-
-testUtxo1 :: Tx
-testUtxo1 = testUtxo
-    [ TxOut { outValue = 200000000, scriptOutput = hexScript "76a91485cff1097fd9e008bb34af709c62197b38978a4888ac" }
-    , TxOut { outValue = 190303501938, scriptOutput = hexScript "a914339725ba21efd62ac753a9bcd067d6c7a6a39d0587" }
-    ]
-
-validVec1 :: PartiallySignedTransaction
-validVec1 = (emptyPSBT testTx1) { inputs = [ emptyInput { nonWitnessUtxo = Just testUtxo1 } ] }
-
-validVec :: [Text]
-validVec = [validVec1Hex, validVec2Hex, validVec3Hex, validVec4Hex, validVec5Hex, validVec6Hex]
-
-validVec1Hex :: Text
-validVec1Hex = "70736274ff0100750200000001268171371edff285e937adeea4b37b78000c0566cbb3ad64641713ca42171bf60000000000feffffff02d3dff505000000001976a914d0c59903c5bac2868760e90fd521a4665aa7652088ac00e1f5050000000017a9143545e6e33b832c47050f24d3eeb93c9c03948bc787b32e1300000100fda5010100000000010289a3c71eab4d20e0371bbba4cc698fa295c9463afa2e397f8533ccb62f9567e50100000017160014be18d152a9b012039daf3da7de4f53349eecb985ffffffff86f8aa43a71dff1448893a530a7237ef6b4608bbb2dd2d0171e63aec6a4890b40100000017160014fe3e9ef1a745e974d902c4355943abcb34bd5353ffffffff0200c2eb0b000000001976a91485cff1097fd9e008bb34af709c62197b38978a4888ac72fef84e2c00000017a914339725ba21efd62ac753a9bcd067d6c7a6a39d05870247304402202712be22e0270f394f568311dc7ca9a68970b8025fdd3b240229f07f8a5f3a240220018b38d7dcd314e734c9276bd6fb40f673325bc4baa144c800d2f2f02db2765c012103d2e15674941bad4a996372cb87e1856d3652606d98562fe39c5e9e7e413f210502483045022100d12b852d85dcd961d2f5f4ab660654df6eedcc794c0c33ce5cc309ffb5fce58d022067338a8e0e1725c197fb1a88af59f51e44e4255b20167c8684031c05d1f2592a01210223b72beef0965d10be0778efecd61fcac6f79a4ea169393380734464f84f2ab300000000000000"
-
-validVec2Hex :: Text
-validVec2Hex = "70736274ff0100a00200000002ab0949a08c5af7c49b8212f417e2f15ab3f5c33dcf153821a8139f877a5b7be40000000000feffffffab0949a08c5af7c49b8212f417e2f15ab3f5c33dcf153821a8139f877a5b7be40100000000feffffff02603bea0b000000001976a914768a40bbd740cbe81d988e71de2a4d5c71396b1d88ac8e240000000000001976a9146f4620b553fa095e721b9ee0efe9fa039cca459788ac000000000001076a47304402204759661797c01b036b25928948686218347d89864b719e1f7fcf57d1e511658702205309eabf56aa4d8891ffd111fdf1336f3a29da866d7f8486d75546ceedaf93190121035cdc61fc7ba971c0b501a646a2a83b102cb43881217ca682dc86e2d73fa882920001012000e1f5050000000017a9143545e6e33b832c47050f24d3eeb93c9c03948bc787010416001485d13537f2e265405a34dbafa9e3dda01fb82308000000"
-
-validVec3Hex :: Text
-validVec3Hex = "70736274ff0100750200000001268171371edff285e937adeea4b37b78000c0566cbb3ad64641713ca42171bf60000000000feffffff02d3dff505000000001976a914d0c59903c5bac2868760e90fd521a4665aa7652088ac00e1f5050000000017a9143545e6e33b832c47050f24d3eeb93c9c03948bc787b32e1300000100fda5010100000000010289a3c71eab4d20e0371bbba4cc698fa295c9463afa2e397f8533ccb62f9567e50100000017160014be18d152a9b012039daf3da7de4f53349eecb985ffffffff86f8aa43a71dff1448893a530a7237ef6b4608bbb2dd2d0171e63aec6a4890b40100000017160014fe3e9ef1a745e974d902c4355943abcb34bd5353ffffffff0200c2eb0b000000001976a91485cff1097fd9e008bb34af709c62197b38978a4888ac72fef84e2c00000017a914339725ba21efd62ac753a9bcd067d6c7a6a39d05870247304402202712be22e0270f394f568311dc7ca9a68970b8025fdd3b240229f07f8a5f3a240220018b38d7dcd314e734c9276bd6fb40f673325bc4baa144c800d2f2f02db2765c012103d2e15674941bad4a996372cb87e1856d3652606d98562fe39c5e9e7e413f210502483045022100d12b852d85dcd961d2f5f4ab660654df6eedcc794c0c33ce5cc309ffb5fce58d022067338a8e0e1725c197fb1a88af59f51e44e4255b20167c8684031c05d1f2592a01210223b72beef0965d10be0778efecd61fcac6f79a4ea169393380734464f84f2ab30000000001030401000000000000"
-
-validVec4Hex :: Text
-validVec4Hex = "70736274ff0100a00200000002ab0949a08c5af7c49b8212f417e2f15ab3f5c33dcf153821a8139f877a5b7be40000000000feffffffab0949a08c5af7c49b8212f417e2f15ab3f5c33dcf153821a8139f877a5b7be40100000000feffffff02603bea0b000000001976a914768a40bbd740cbe81d988e71de2a4d5c71396b1d88ac8e240000000000001976a9146f4620b553fa095e721b9ee0efe9fa039cca459788ac00000000000100df0200000001268171371edff285e937adeea4b37b78000c0566cbb3ad64641713ca42171bf6000000006a473044022070b2245123e6bf474d60c5b50c043d4c691a5d2435f09a34a7662a9dc251790a022001329ca9dacf280bdf30740ec0390422422c81cb45839457aeb76fc12edd95b3012102657d118d3357b8e0f4c2cd46db7b39f6d9c38d9a70abcb9b2de5dc8dbfe4ce31feffffff02d3dff505000000001976a914d0c59903c5bac2868760e90fd521a4665aa7652088ac00e1f5050000000017a9143545e6e33b832c47050f24d3eeb93c9c03948bc787b32e13000001012000e1f5050000000017a9143545e6e33b832c47050f24d3eeb93c9c03948bc787010416001485d13537f2e265405a34dbafa9e3dda01fb8230800220202ead596687ca806043edc3de116cdf29d5e9257c196cd055cf698c8d02bf24e9910b4a6ba670000008000000080020000800022020394f62be9df19952c5587768aeb7698061ad2c4a25c894f47d8c162b4d7213d0510b4a6ba6700000080010000800200008000"
-
-validVec5Hex :: Text
-validVec5Hex = "70736274ff0100550200000001279a2323a5dfb51fc45f220fa58b0fc13e1e3342792a85d7e36cd6333b5cbc390000000000ffffffff01a05aea0b000000001976a914ffe9c0061097cc3b636f2cb0460fa4fc427d2b4588ac0000000000010120955eea0b0000000017a9146345200f68d189e1adc0df1c4d16ea8f14c0dbeb87220203b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd4646304302200424b58effaaa694e1559ea5c93bbfd4a89064224055cdf070b6771469442d07021f5c8eb0fea6516d60b8acb33ad64ede60e8785bfb3aa94b99bdf86151db9a9a010104220020771fd18ad459666dd49f3d564e3dbc42f4c84774e360ada16816a8ed488d5681010547522103b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd462103de55d1e1dac805e3f8a58c1fbf9b94c02f3dbaafe127fefca4995f26f82083bd52ae220603b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd4610b4a6ba67000000800000008004000080220603de55d1e1dac805e3f8a58c1fbf9b94c02f3dbaafe127fefca4995f26f82083bd10b4a6ba670000008000000080050000800000"
-
-validVec6Hex :: Text
-validVec6Hex = "70736274ff01003f0200000001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000ffffffff010000000000000000036a010000000000000a0f0102030405060708090f0102030405060708090a0b0c0d0e0f0000"
diff --git a/test/Network/Haskoin/TransactionSpec.hs b/test/Network/Haskoin/TransactionSpec.hs
deleted file mode 100644
--- a/test/Network/Haskoin/TransactionSpec.hs
+++ /dev/null
@@ -1,396 +0,0 @@
-{-# LANGUAGE OverloadedStrings #-}
-
-module Network.Haskoin.TransactionSpec (spec) where
-
-import           Data.Aeson                         as A
-import qualified Data.ByteString                    as B
-import           Data.Either
-import           Data.Map.Strict                    (singleton)
-import           Data.Maybe
-import           Data.Serialize                     as S
-import           Data.String                        (fromString)
-import           Data.String.Conversions
-import           Data.Text                          (Text)
-import           Data.Word                          (Word32, Word64)
-import           Network.Haskoin.Address
-import           Network.Haskoin.Constants
-import           Network.Haskoin.Keys
-import           Network.Haskoin.Script
-import           Network.Haskoin.Test
-import           Network.Haskoin.Transaction
-import           Network.Haskoin.Transaction.Segwit (isSegwit)
-import           Network.Haskoin.Util
-import           Safe                               (readMay)
-import           Test.Hspec
-import           Test.HUnit                         (Assertion, assertBool)
-import           Test.QuickCheck
-
-spec :: Spec
-spec = do
-    let net = btc
-    describe "transaction unit tests" $ do
-        it "compute txid from tx" $
-            mapM_ runTxIDVec txIDVec
-        it "build pkhash transaction (generated from bitcoind)" $
-            mapM_ runPKHashVec pkHashVec
-        it "encode satoshi core script pubkey" tEncodeSatoshiCoreScriptPubKey
-        --
-        -- These tests depend on signatures matching exactly those in the spec.
-        -- Fedora includes a version of libsecp256k1 that computes signatures
-        -- using a slighlty different deterministic nonce algorithm.
-        --
-        -- it "agrees with BIP143 p2wpkh example" testBip143p2wpkh
-        -- it "agrees with BIP143 p2sh-p2wpkh example" testBip143p2shp2wpkh
-        -- it "builds a p2wsh multisig transaction" testP2WSHMulsig
-        -- it "agrees with BIP143 p2sh-p2wsh multisig example" testBip143p2shp2wpkhMulsig
-    describe "btc transaction" $ do
-        it "decode and encode txid" $
-            property $
-            forAll arbitraryTxHash $ \h -> hexToTxHash (txHashToHex h) == Just h
-        it "from string transaction id" $
-            property $
-            forAll arbitraryTxHash $ \h -> fromString (cs $ txHashToHex h) == h
-        it "building address tx" $
-            property $
-            forAll arbitraryAddress $
-            forAll (arbitrarySatoshi net) . testBuildAddrTx net
-        it "guess transaction size" $
-            property $ forAll (arbitraryAddrOnlyTxFull net) (testGuessSize net)
-        it "choose coins" $
-            property $ forAll (listOf (arbitrarySatoshi net)) testChooseCoins
-        it "choose multisig coins" $
-            property $
-            forAll arbitraryMSParam $
-            forAll (listOf (arbitrarySatoshi net)) . testChooseMSCoins
-        it "sign and validate transaction" $
-            property $ forAll (arbitrarySigningData net) (testDetSignTx net)
-        it "sign and validate (nested) transaction" $
-            property $ forAll (arbitrarySigningData net) (testDetSignNestedTx net)
-        it "merge partially signed transactions" $
-            property $ forAll (arbitraryPartialTxs net) (testMergeTx net)
-    describe "json serialization" $ do
-        it "encodes and decodes transaction" $
-            property $ forAll (arbitraryTx net) testID
-        it "encodes and decodes transaction hash" $
-            property $ forAll arbitraryTxHash testID
-    describe "transaction serialization" $ do
-        it "encodes and decodes tx input" $
-            property $ forAll (arbitraryTxIn net) cerealID
-        it "encodes and decodes tx output" $
-            property $ forAll (arbitraryTxOut net) cerealID
-        it "encodes and decodes outpoint" $
-            property $ forAll arbitraryOutPoint cerealID
-        it "encodes and decodes transaction" $
-            property $ forAll (arbitraryTx net) cerealID
-        it "encodes and decodes witness transaction" $
-            property $ forAll (arbitraryWitnessTx net) cerealID
-        it "encodes and decodes legacy transaction" $
-            property $ forAll (arbitraryLegacyTx net) cerealID
-
-cerealID :: (Serialize a, Eq a) => a -> Bool
-cerealID x = S.decode (S.encode x) == Right x
-
-runTxIDVec :: (Text, Text) -> Assertion
-runTxIDVec (tid, tx) = assertBool "txid" $ txHashToHex (txHash txBS) == tid
-  where
-    txBS = fromJust $ either (const Nothing) return . S.decode =<< decodeHex tx
-
-txIDVec :: [(Text, Text)]
-txIDVec =
-    [ ( "23b397edccd3740a74adb603c9756370fafcde9bcc4483eb271ecad09a94dd63"
-      , "0100000001b14bdcbc3e01bdaad36cc08e81e69c82e1060bc14e518db2b49aa43ad90ba26000000000490047304402203f16c6f40162ab686621ef3000b04e75418a0c0cb2d8aebeac894ae360ac1e780220ddc15ecdfc3507ac48e1681a33eb60996631bf6bf5bc0a0682c4db743ce7ca2b01ffffffff0140420f00000000001976a914660d4ef3a743e3e696ad990364e555c271ad504b88ac00000000"
-      )
-    , ( "c99c49da4c38af669dea436d3e73780dfdb6c1ecf9958baa52960e8baee30e73"
-      , "01000000010276b76b07f4935c70acf54fbf1f438a4c397a9fb7e633873c4dd3bc062b6b40000000008c493046022100d23459d03ed7e9511a47d13292d3430a04627de6235b6e51a40f9cd386f2abe3022100e7d25b080f0bb8d8d5f878bba7d54ad2fda650ea8d158a33ee3cbd11768191fd004104b0e2c879e4daf7b9ab68350228c159766676a14f5815084ba166432aab46198d4cca98fa3e9981d0a90b2effc514b76279476550ba3663fdcaff94c38420e9d5000000000100093d00000000001976a9149a7b0f3b80c6baaeedce0a0842553800f832ba1f88ac00000000"
-      )
-    , ( "f7fdd091fa6d8f5e7a8c2458f5c38faffff2d3f1406b6e4fe2c99dcc0d2d1cbb"
-      , "01000000023d6cf972d4dff9c519eff407ea800361dd0a121de1da8b6f4138a2f25de864b4000000008a4730440220ffda47bfc776bcd269da4832626ac332adfca6dd835e8ecd83cd1ebe7d709b0e022049cffa1cdc102a0b56e0e04913606c70af702a1149dc3b305ab9439288fee090014104266abb36d66eb4218a6dd31f09bb92cf3cfa803c7ea72c1fc80a50f919273e613f895b855fb7465ccbc8919ad1bd4a306c783f22cd3227327694c4fa4c1c439affffffff21ebc9ba20594737864352e95b727f1a565756f9d365083eb1a8596ec98c97b7010000008a4730440220503ff10e9f1e0de731407a4a245531c9ff17676eda461f8ceeb8c06049fa2c810220c008ac34694510298fa60b3f000df01caa244f165b727d4896eb84f81e46bcc4014104266abb36d66eb4218a6dd31f09bb92cf3cfa803c7ea72c1fc80a50f919273e613f895b855fb7465ccbc8919ad1bd4a306c783f22cd3227327694c4fa4c1c439affffffff01f0da5200000000001976a914857ccd42dded6df32949d4646dfa10a92458cfaa88ac00000000"
-      )
-    , ( "afd9c17f8913577ec3509520bd6e5d63e9c0fd2a5f70c787993b097ba6ca9fae"
-      , "010000000370ac0a1ae588aaf284c308d67ca92c69a39e2db81337e563bf40c59da0a5cf63000000006a4730440220360d20baff382059040ba9be98947fd678fb08aab2bb0c172efa996fd8ece9b702201b4fb0de67f015c90e7ac8a193aeab486a1f587e0f54d0fb9552ef7f5ce6caec032103579ca2e6d107522f012cd00b52b9a65fb46f0c57b9b8b6e377c48f526a44741affffffff7d815b6447e35fbea097e00e028fb7dfbad4f3f0987b4734676c84f3fcd0e804010000006b483045022100c714310be1e3a9ff1c5f7cacc65c2d8e781fc3a88ceb063c6153bf950650802102200b2d0979c76e12bb480da635f192cc8dc6f905380dd4ac1ff35a4f68f462fffd032103579ca2e6d107522f012cd00b52b9a65fb46f0c57b9b8b6e377c48f526a44741affffffff3f1f097333e4d46d51f5e77b53264db8f7f5d2e18217e1099957d0f5af7713ee010000006c493046022100b663499ef73273a3788dea342717c2640ac43c5a1cf862c9e09b206fcb3f6bb8022100b09972e75972d9148f2bdd462e5cb69b57c1214b88fc55ca638676c07cfc10d8032103579ca2e6d107522f012cd00b52b9a65fb46f0c57b9b8b6e377c48f526a44741affffffff0380841e00000000001976a914bfb282c70c4191f45b5a6665cad1682f2c9cfdfb88ac80841e00000000001976a9149857cc07bed33a5cf12b9c5e0500b675d500c81188ace0fd1c00000000001976a91443c52850606c872403c0601e69fa34b26f62db4a88ac00000000"
-      )
-    ]
-
-runPKHashVec :: ([(Text, Word32)], [(Text, Word64)], Text) -> Assertion
-runPKHashVec (xs, ys, res) =
-    assertBool "Build PKHash Tx" $ encodeHex (S.encode tx) == res
-  where
-    tx =
-        fromRight (error "Could not decode transaction") $
-        buildAddrTx btc (map f xs) ys
-    f (tid, ix) = OutPoint (fromJust $ hexToTxHash tid) ix
-
--- These test vectors have been generated from bitcoind raw transaction api
-
-pkHashVec :: [([(Text, Word32)], [(Text, Word64)], Text)]
-pkHashVec =
-    [
-      ( [("eb29eba154166f6541ebcc9cbdf5088756e026af051f123bcfb526df594549db",14)]
-      , [("14LsRquZfURNFrzpcLVGdaHTfAPjjwiSPb",90000000)]
-      , "0100000001db494559df26b5cf3b121f05af26e0568708f5bd9ccceb41656f1654a1eb29eb0e00000000ffffffff01804a5d05000000001976a91424aa604689cc582292b97668bedd91dd5bf9374c88ac00000000"
-      )
-    , ( [ ("eb29eba154166f6541ebcc9cbdf5088756e026af051f123bcfb526df594549db",0)
-        , ("0001000000000000000000000000000000000000000000000000000000000000",2147483647)
-        ]
-      , [ ("14LsRquZfURNFrzpcLVGdaHTfAPjjwiSPb",1)
-        , ("19VCgS642vzEA1sdByoSn6GsWBwraV8D4n",2100000000000000)
-        ]
-      , "0100000002db494559df26b5cf3b121f05af26e0568708f5bd9ccceb41656f1654a1eb29eb0000000000ffffffff0000000000000000000000000000000000000000000000000000000000000100ffffff7f00ffffffff0201000000000000001976a91424aa604689cc582292b97668bedd91dd5bf9374c88ac0040075af07507001976a9145d16672f53981ff21c5f42b40d1954993cbca54f88ac00000000"
-      )
-    , ( [ ("eb29eba154166f6541ebcc9cbdf5088756e026af051f123bcfb526df594549db",0)
-        , ("0001000000000000000000000000000000000000000000000000000000000000",2147483647)
-        ]
-      , []
-      , "0100000002db494559df26b5cf3b121f05af26e0568708f5bd9ccceb41656f1654a1eb29eb0000000000ffffffff0000000000000000000000000000000000000000000000000000000000000100ffffff7f00ffffffff0000000000"
-      )
-    , ( []
-      , [ ("14LsRquZfURNFrzpcLVGdaHTfAPjjwiSPb",1)
-        , ("19VCgS642vzEA1sdByoSn6GsWBwraV8D4n",2100000000000000)
-        ]
-      , "01000000000201000000000000001976a91424aa604689cc582292b97668bedd91dd5bf9374c88ac0040075af07507001976a9145d16672f53981ff21c5f42b40d1954993cbca54f88ac00000000"
-      )
-    ]
-
-tEncodeSatoshiCoreScriptPubKey :: Assertion
-tEncodeSatoshiCoreScriptPubKey = assertBool "tEncodeSatoshiCoreScriptPubKey" $
-  t1BsOutputScriptPubKey == encodeSatoshiCoreScriptPubKey t1SatoshiCoreJsonScriptPubKey
-  where
-    t1BsOutputScriptPubKey :: Text
-    t1BsOutputScriptPubKey = "514104cc71eb30d653c0c3163990c47b976f3fb3f37cccdcbedb169a1dfef58bbfbfaff7d8a473e7e2e6d317b87bafe8bde97e3cf8f065dec022b51d11fcdd0d348ac4410461cbdcc5409fb4b4d42b51d33381354d80e550078cb532a34bfa2fcfdeb7d76519aecc62770f5b0e4ef8551946d8a540911abe3e7854a26f39f58b25c15342af52ae"
-    t1SatoshiCoreJsonScriptPubKey :: String
-    t1SatoshiCoreJsonScriptPubKey = "1 0x41 0x04cc71eb30d653c0c3163990c47b976f3fb3f37cccdcbedb169a1dfef58bbfbfaff7d8a473e7e2e6d317b87bafe8bde97e3cf8f065dec022b51d11fcdd0d348ac4 0x41 0x0461cbdcc5409fb4b4d42b51d33381354d80e550078cb532a34bfa2fcfdeb7d76519aecc62770f5b0e4ef8551946d8a540911abe3e7854a26f39f58b25c15342af 2 OP_CHECKMULTISIG"
-
-encodeSatoshiCoreScriptPubKey :: String -> Text
-encodeSatoshiCoreScriptPubKey =
-  mconcat . map encodeSatoshiCoreScriptPiece . words
-  where
-    encodeSatoshiCoreScriptPiece :: String -> Text
-    encodeSatoshiCoreScriptPiece s = case (readMay ("OP_" ++ s) :: Maybe ScriptOp) of
-      Just op -> encodeHex . S.encode $ op
-      Nothing -> case take 2 s of
-          "OP" -> encodeHex . S.encode . (read :: String -> ScriptOp) $ s
-          "0x" -> (fromString . drop 2 :: String -> Text) s
-          _ -> case (readMay s :: Maybe Int) of -- can we get rid of this case now?
-            Just i  -> encodeHex . S.encode . intToScriptOp $ i
-            Nothing -> error $ "encodeSatoshiCoreScriptPubKey: " ++ s
-
---
--- Following tests depend on specific secp256k1 deterministic nonce computation.
--- Fedora distributes the Bitcoin ABC fork of this library, which computes nonces differently.
---
-
--- -- Reproduce the P2WPKH example from BIP 143
--- testBip143p2wpkh :: Assertion
--- testBip143p2wpkh = assertEqual "BIP143 p2wpkh" (Right exampleSignedTx) generatedSignedTx
---   where
---     exampleSignedTx = "01000000000102fff7f7881a8099afa6940d42d1e7f6362bec38171ea3edf433541db4e4ad969f00000000494830450221008b9d1dc26ba6a9cb62127b02742fa9d754cd3bebf337f7a55d114c8e5cdd30be022040529b194ba3f9281a99f2b1c0a19c0489bc22ede944ccf4ecbab4cc618ef3ed01eeffffffef51e1b804cc89d182d279655c3aa89e815b1b309fe287d9b2b55d57b90ec68a0100000000ffffffff02202cb206000000001976a9148280b37df378db99f66f85c95a783a76ac7a6d5988ac9093510d000000001976a9143bde42dbee7e4dbe6a21b2d50ce2f0167faa815988ac000247304402203609e17b84f6a7d30c80bfa610b5b4542f32a8a0d5447a12fb1366d7f01cc44a0220573a954c4518331561406f90300e8f3358f51928d43c212a8caed02de67eebee0121025476c2e83188368da1ff3e292e7acafcdb3566bb0ad253f62fc70f07aeee635711000000"
-
---     unsignedTx = "0100000002fff7f7881a8099afa6940d42d1e7f6362bec38171ea3edf433541db4e4ad969f0000000000eeffffffef51e1b804cc89d182d279655c3aa89e815b1b309fe287d9b2b55d57b90ec68a0100000000ffffffff02202cb206000000001976a9148280b37df378db99f66f85c95a783a76ac7a6d5988ac9093510d000000001976a9143bde42dbee7e4dbe6a21b2d50ce2f0167faa815988ac11000000"
-
---     Just key0 = secHexKey "bbc27228ddcb9209d7fd6f36b02f7dfa6252af40bb2f1cbc7a557da8027ff866"
---     pubKey0   = toPubKey key0
-
---     Just key1 = secHexKey "619c335025c7f4012e556c2a58b2506e30b8511b53ade95ea316fd8c3286feb9"
-
---     [op0, op1] = prevOutput <$> txIn unsignedTx
-
---     sigIn0 = SigInput (PayPK pubKey0) 625000000 op0 sigHashAll Nothing
-
---     WitnessPubKeyAddress h = pubKeyWitnessAddr $ toPubKey key1
---     sigIn1                 = SigInput (PayWitnessPKHash h) 600000000 op1 sigHashAll Nothing
-
---     generatedSignedTx = signTx btc unsignedTx [sigIn0, sigIn1] [key0, key1]
-
--- -- Reproduce the P2SH-P2WPKH example from BIP 143
--- testBip143p2shp2wpkh :: Assertion
--- testBip143p2shp2wpkh = assertEqual "BIP143 p2sh-p2wpkh" (Right exampleSignedTx) generatedSignedTx
---   where
---     exampleSignedTx = "01000000000101db6b1b20aa0fd7b23880be2ecbd4a98130974cf4748fb66092ac4d3ceb1a5477010000001716001479091972186c449eb1ded22b78e40d009bdf0089feffffff02b8b4eb0b000000001976a914a457b684d7f0d539a46a45bbc043f35b59d0d96388ac0008af2f000000001976a914fd270b1ee6abcaea97fea7ad0402e8bd8ad6d77c88ac02473044022047ac8e878352d3ebbde1c94ce3a10d057c24175747116f8288e5d794d12d482f0220217f36a485cae903c713331d877c1f64677e3622ad4010726870540656fe9dcb012103ad1d8e89212f0b92c74d23bb710c00662ad1470198ac48c43f7d6f93a2a2687392040000"
-
---     unsignedTx = "0100000001db6b1b20aa0fd7b23880be2ecbd4a98130974cf4748fb66092ac4d3ceb1a54770100000000feffffff02b8b4eb0b000000001976a914a457b684d7f0d539a46a45bbc043f35b59d0d96388ac0008af2f000000001976a914fd270b1ee6abcaea97fea7ad0402e8bd8ad6d77c88ac92040000"
-
---     Just key0 = secHexKey "eb696a065ef48a2192da5b28b694f87544b30fae8327c4510137a922f32c6dcf"
-
---     op0                    = prevOutput . head $ txIn unsignedTx
---     WitnessPubKeyAddress h = pubKeyWitnessAddr $ toPubKey key0
---     sigIn0                 = SigInput (PayWitnessPKHash h) 1000000000 op0 sigHashAll Nothing
-
---     generatedSignedTx = signNestedWitnessTx btc unsignedTx [sigIn0] [key0]
-
--- -- P2WSH multisig example (tested against bitcoin-core 0.19.0.1)
--- testP2WSHMulsig :: Assertion
--- testP2WSHMulsig = assertEqual "p2wsh multisig" (Right exampleSignedTx) generatedSignedTx
---   where
---     exampleSignedTx = "01000000000101d2e34df5d7ee565208eddd231548916b9b0e99f4f5071f896134a448c5fb07bf0100000000ffffffff01f0b9f505000000001976a9143d5a352cab583b12fbcb26d1269b4a2c951a33ad88ac0400483045022100fad4fedd2bb4c439c64637eb8e9150d9020a7212808b8dc0578d5ff5b4ad65fe0220714640f261b37eb3106310bf853f4b706e51436fb6b64c2ab00768814eb55b9801473044022100baff4e4ceea4022b9725a2e6f6d77997a554f858165b91ac8c16c9833008bee9021f5f70ebc3f8580dc0a5e96451e3697bdf1f1f5883944f0f33ab0cfb272354040169522102ba46d3bb8db74c77c6cf082db57fc0548058fcdea811549e186526e3d10caf6721038ac8aef2dd9cea5e7d66e2f6e23f177a6c21f69ea311fa0c85d81badb6b37ceb2103d96d2bfbbc040faaf93491d69e2bfe9695e2d8e007a7f26db96c2ee42db15dc953ae00000000"
-
---     unsignedTx = "0100000001d2e34df5d7ee565208eddd231548916b9b0e99f4f5071f896134a448c5fb07bf0100000000ffffffff01f0b9f505000000001976a9143d5a352cab583b12fbcb26d1269b4a2c951a33ad88ac00000000"
-
---     op0 = head $ prevOutput <$> txIn unsignedTx
-
---     Just keys = traverse secHexKey [ "3030303030303030303030303030303030303030303030303030303030303031"
---                                    , "3030303030303030303030303030303030303030303030303030303030303032"
---                                    , "3030303030303030303030303030303030303030303030303030303030303033"
---                                    ]
-
---     rdm               = PayMulSig (toPubKey <$> keys) 2
---     sigIn             = SigInput (toP2WSH $ encodeOutput rdm) 100000000 op0 sigHashAll (Just rdm)
---     generatedSignedTx = signTx btc unsignedTx [sigIn] (take 2 keys)
-
--- -- Reproduce the P2SH-P2WSH multisig example from BIP 143
--- testBip143p2shp2wpkhMulsig :: Assertion
--- testBip143p2shp2wpkhMulsig =
---     assertEqual "BIP143 p2sh-p2wsh multisig" (Right exampleSignedTx) generatedSignedTx
---   where
---     exampleSignedTx = "0100000000010136641869ca081e70f394c6948e8af409e18b619df2ed74aa106c1ca29787b96e0100000023220020a16b5755f7f6f96dbd65f5f0d6ab9418b89af4b1f14a1bb8a09062c35f0dcb54ffffffff0200e9a435000000001976a914389ffce9cd9ae88dcc0631e88a821ffdbe9bfe2688acc0832f05000000001976a9147480a33f950689af511e6e84c138dbbd3c3ee41588ac080047304402206ac44d672dac41f9b00e28f4df20c52eeb087207e8d758d76d92c6fab3b73e2b0220367750dbbe19290069cba53d096f44530e4f98acaa594810388cf7409a1870ce01473044022068c7946a43232757cbdf9176f009a928e1cd9a1a8c212f15c1e11ac9f2925d9002205b75f937ff2f9f3c1246e547e54f62e027f64eefa2695578cc6432cdabce271502473044022059ebf56d98010a932cf8ecfec54c48e6139ed6adb0728c09cbe1e4fa0915302e022007cd986c8fa870ff5d2b3a89139c9fe7e499259875357e20fcbb15571c76795403483045022100fbefd94bd0a488d50b79102b5dad4ab6ced30c4069f1eaa69a4b5a763414067e02203156c6a5c9cf88f91265f5a942e96213afae16d83321c8b31bb342142a14d16381483045022100a5263ea0553ba89221984bd7f0b13613db16e7a70c549a86de0cc0444141a407022005c360ef0ae5a5d4f9f2f87a56c1546cc8268cab08c73501d6b3be2e1e1a8a08824730440220525406a1482936d5a21888260dc165497a90a15669636d8edca6b9fe490d309c022032af0c646a34a44d1f4576bf6a4a74b67940f8faa84c7df9abe12a01a11e2b4783cf56210307b8ae49ac90a048e9b53357a2354b3334e9c8bee813ecb98e99a7e07e8c3ba32103b28f0c28bfab54554ae8c658ac5c3e0ce6e79ad336331f78c428dd43eea8449b21034b8113d703413d57761b8b9781957b8c0ac1dfe69f492580ca4195f50376ba4a21033400f6afecb833092a9a21cfdf1ed1376e58c5d1f47de74683123987e967a8f42103a6d48b1131e94ba04d9737d61acdaa1322008af9602b3b14862c07a1789aac162102d8b661b0b3302ee2f162b09e07a55ad5dfbe673a9f01d9f0c19617681024306b56ae00000000"
-
---     unsignedTx = "010000000136641869ca081e70f394c6948e8af409e18b619df2ed74aa106c1ca29787b96e0100000000ffffffff0200e9a435000000001976a914389ffce9cd9ae88dcc0631e88a821ffdbe9bfe2688acc0832f05000000001976a9147480a33f950689af511e6e84c138dbbd3c3ee41588ac00000000"
-
---     op0 = head $ prevOutput <$> txIn unsignedTx
-
---     rawKeys = [ "730fff80e1413068a05b57d6a58261f07551163369787f349438ea38ca80fac6"
---               , "11fa3d25a17cbc22b29c44a484ba552b5a53149d106d3d853e22fdd05a2d8bb3"
---               , "77bf4141a87d55bdd7f3cd0bdccf6e9e642935fec45f2f30047be7b799120661"
---               , "14af36970f5025ea3e8b5542c0f8ebe7763e674838d08808896b63c3351ffe49"
---               , "fe9a95c19eef81dde2b95c1284ef39be497d128e2aa46916fb02d552485e0323"
---               , "428a7aee9f0c2af0cd19af3cf1c78149951ea528726989b2e83e4778d2c3f890"
---               ]
-
---     Just keys@[key0, key1, key2, key3, key4, key5] = traverse secHexKey rawKeys
-
---     rdm      = PayMulSig (toPubKey <$> keys) 6
---     sigIn sh = SigInput (toP2WSH $ encodeOutput rdm) 987654321 op0 sh (Just rdm)
-
---     sigHashesA = [sigHashAll, sigHashNone, sigHashSingle]
---     sigHashesB = setAnyoneCanPayFlag <$> sigHashesA
---     sigIns     = sigIn <$> (sigHashesA <> sigHashesB)
-
---     generatedSignedTx      = foldM addSig unsignedTx $ zip sigIns keys
---     addSig tx (sigIn, key) = signNestedWitnessTx btc tx [sigIn] [key]
-
--- secHexKey :: Text -> Maybe SecKey
--- secHexKey = decodeHex >=> secKey
-
--- toPubKey :: SecKey -> PubKeyI
--- toPubKey = derivePubKeyI . wrapSecKey True
-
-{- Building Transactions -}
-
-testBuildAddrTx :: Network -> Address -> TestCoin -> Bool
-testBuildAddrTx net a (TestCoin v)
-    | isPubKeyAddress a = Right (PayPKHash (getAddrHash160 a)) == out
-    | isScriptAddress a = Right (PayScriptHash (getAddrHash160 a)) == out
-    | otherwise = undefined
-  where
-    tx = buildAddrTx net [] [(fromJust (addrToString net a), v)]
-    out =
-        decodeOutputBS $
-        scriptOutput $
-        head $ txOut (fromRight (error "Could not build transaction") tx)
-
-testGuessSize :: Network -> Tx -> Bool
-testGuessSize net tx
-    -- We compute an upper bound but it should be close enough to the real size
-    -- We give 2 bytes of slack on every signature (1 on r and 1 on s)
- = guess >= len && guess <= len + 2 * delta
-  where
-    delta = pki + sum (map fst msi)
-    guess = guessTxSize pki msi pkout msout
-    len = B.length $ S.encode tx
-    ins = map f $ txIn tx
-    f i =
-        fromRight (error "Could not decode input") $
-        decodeInputBS net $ scriptInput i
-    pki = length $ filter isSpendPKHash ins
-    msi = concatMap shData ins
-    shData (ScriptHashInput _ (PayMulSig keys r)) = [(r, length keys)]
-    shData _                                      = []
-    out =
-        map
-            (fromRight (error "Could not decode transaction output") .
-             decodeOutputBS . scriptOutput) $
-        txOut tx
-    pkout = length $ filter isPayPKHash out
-    msout = length $ filter isPayScriptHash out
-
-testChooseCoins :: [TestCoin] -> Word64 -> Word64 -> Int -> Property
-testChooseCoins coins target byteFee nOut = nOut >= 0 ==>
-    case chooseCoins target byteFee nOut True coins of
-        Right (chosen, change) ->
-            let outSum = sum $ map coinValue chosen
-                fee    = guessTxFee byteFee nOut (length chosen)
-            in outSum == target + change + fee
-        Left _ ->
-            let fee = guessTxFee byteFee nOut (length coins)
-            in target == 0 || s < target + fee
-  where
-    s  = sum $ map coinValue coins
-
-testChooseMSCoins :: (Int, Int) -> [TestCoin]
-                  -> Word64 -> Word64 -> Int -> Property
-testChooseMSCoins (m, n) coins target byteFee nOut = nOut >= 0 ==>
-    case chooseMSCoins target byteFee (m,n) nOut True coins of
-        Right (chosen,change) ->
-            let outSum = sum $ map coinValue chosen
-                fee    = guessMSTxFee byteFee (m,n) nOut (length chosen)
-            in outSum == target + change + fee
-        Left _ ->
-            let fee = guessMSTxFee byteFee (m,n) nOut (length coins)
-            in target == 0 || s < target + fee
-  where
-    s = sum $ map coinValue coins
-
-{- Signing Transactions -}
-
-testDetSignTx :: Network -> (Tx, [SigInput], [SecKeyI]) -> Bool
-testDetSignTx net  (tx, sigis, prv) =
-    not (verifyStdTx net tx verData) &&
-    not (verifyStdTx net txSigP verData) && verifyStdTx net txSigC verData
-  where
-    txSigP =
-        fromRight (error "Could not decode transaction") $
-        signTx net tx sigis (map secKeyData (tail prv))
-    txSigC =
-        fromRight (error "Could not decode transaction") $
-        signTx net txSigP sigis [secKeyData (head prv)]
-    verData = map (\(SigInput s v o _ _) -> (s, v, o)) sigis
-
-testDetSignNestedTx :: Network -> (Tx, [SigInput], [SecKeyI]) -> Bool
-testDetSignNestedTx net  (tx, sigis, prv) =
-    not (verifyStdTx net tx verData) &&
-    not (verifyStdTx net txSigP verData) && verifyStdTx net txSigC verData
-  where
-    txSigP =
-        fromRight (error "Could not decode transaction") $
-        signNestedWitnessTx net tx sigis (secKeyData <$> tail prv)
-    txSigC =
-        fromRight (error "Could not decode transaction") $
-        signNestedWitnessTx net txSigP sigis [secKeyData (head prv)]
-    verData = handleSegwit <$> sigis
-    handleSegwit (SigInput s v o _ _)
-        | isSegwit s = (toP2SH $ encodeOutput s, v, o)
-        | otherwise  = (s, v, o)
-
-testMergeTx :: Network -> ([Tx], [(ScriptOutput, Word64, OutPoint, Int, Int)]) -> Bool
-testMergeTx net (txs, os) = and
-    [ isRight mergeRes
-    , length (txIn mergedTx) == length os
-    , if enoughSigs then isValid else not isValid
-    -- Signature count == min (length txs) (sum required signatures)
-    , sum (map snd sigMap) == min (length txs) (sum (map fst sigMap))
-    ]
-  where
-    outs = map (\(so, val, op, _, _) -> (so, val, op)) os
-    mergeRes = mergeTxs net txs outs
-    mergedTx = fromRight (error "Could not merge") mergeRes
-    isValid = verifyStdTx net mergedTx outs
-    enoughSigs = all (\(m,c) -> c >= m) sigMap
-    sigMap = map (\((_,_,_,m,_), inp) -> (m, sigCnt inp)) $ zip os $ txIn mergedTx
-    sigCnt inp = case decodeInputBS net $ scriptInput inp of
-        Right (RegularInput (SpendMulSig sigs)) -> length sigs
-        Right (ScriptHashInput (SpendMulSig sigs) _) -> length sigs
-        _ -> error "Invalid input script type"
-
-testID :: (FromJSON a, ToJSON a, Eq a) => a -> Bool
-testID x =
-    (A.decode . A.encode) (singleton ("object" :: String) x) ==
-    Just (singleton ("object" :: String) x)
diff --git a/test/Network/Haskoin/UtilSpec.hs b/test/Network/Haskoin/UtilSpec.hs
deleted file mode 100644
--- a/test/Network/Haskoin/UtilSpec.hs
+++ /dev/null
@@ -1,64 +0,0 @@
-module Network.Haskoin.UtilSpec (spec) where
-
-import qualified Data.ByteString      as BS
-import           Data.Either          (fromLeft, fromRight, isLeft, isRight)
-import           Data.Foldable        (toList)
-import           Data.List            (permutations)
-import           Data.Maybe
-import qualified Data.Sequence        as Seq
-import           Network.Haskoin.Test
-import           Network.Haskoin.Util
-import           Test.Hspec
-import           Test.QuickCheck
-
-spec :: Spec
-spec =
-    describe "utility functions" $ do
-        it "bsToInteger . integerToBS" $ property getPutInteger
-        it "decodeHex . encodeHex" $ property $ forAll arbitraryBS fromToHex
-        it "compare updateIndex with Data.Sequence" $ property testUpdateIndex
-        it "matchTemplate" $ property testMatchTemplate
-        it "testing matchTemplate with two lists" $
-            property testMatchTemplateLen
-        it "either helper functions" $ property testEither
-
-{- Various utilities -}
-
-getPutInteger :: Integer -> Bool
-getPutInteger i = bsToInteger (integerToBS $ abs i) == abs i
-
-fromToHex :: BS.ByteString -> Bool
-fromToHex bs = decodeHex (encodeHex bs) == Just bs
-
-testUpdateIndex :: [Int] -> Int -> Int -> Bool
-testUpdateIndex xs v i =
-    updateIndex i xs (const v) == toList (Seq.update i v $ Seq.fromList xs)
-
-testMatchTemplate :: [Int] -> Int -> Bool
-testMatchTemplate as i = catMaybes res == bs
-  where
-    res = matchTemplate as bs (==)
-    idx =
-        if null as
-            then 0
-            else i `mod` length as
-    bs = permutations as !! idx
-
-testMatchTemplateLen :: [Int] -> [Int] -> Bool
-testMatchTemplateLen as bs = length bs == length res
-  where
-    res = matchTemplate as bs (==)
-
-testEither :: Either String Int -> Bool
-testEither e =
-    case e of
-        (Right v) ->
-            isRight e &&
-            not (isLeft e) &&
-            fromRight (error "Unexpected Left") e == v &&
-            eitherToMaybe e == Just v
-        (Left v) ->
-            isLeft e &&
-            not (isRight e) &&
-            fromLeft (error "Unexpected Right") e == v &&
-            isNothing (eitherToMaybe e)
