haskoin-core 0.18.0 → 1.2.2
raw patch · 92 files changed
Files
- CHANGELOG.md +293/−49
- README.md +5/−0
- data/bip341.json +452/−0
- data/complex_psbt.json +18/−0
- data/rfc6979DERabc.json +0/−102
- data/rfc6979DERcash.json +102/−0
- data/rfc6979abc.json +0/−62
- data/rfc6979cash.json +62/−0
- data/test_vectors_aserti3-2d_run01.txt +19/−0
- data/test_vectors_aserti3-2d_run02.txt +19/−0
- data/test_vectors_aserti3-2d_run03.txt +19/−0
- data/test_vectors_aserti3-2d_run04.txt +234/−0
- data/test_vectors_aserti3-2d_run05.txt +234/−0
- data/test_vectors_aserti3-2d_run06.txt +1009/−0
- data/test_vectors_aserti3-2d_run07.txt +1009/−0
- data/test_vectors_aserti3-2d_run08.txt +509/−0
- data/test_vectors_aserti3-2d_run09.txt +19/−0
- data/test_vectors_aserti3-2d_run10.txt +19/−0
- data/test_vectors_aserti3-2d_run11.txt +1009/−0
- data/test_vectors_aserti3-2d_run12.txt +10009/−0
- haskoin-core.cabal +52/−30
- src/Haskoin.hs +28/−30
- src/Haskoin/Address.hs +264/−207
- src/Haskoin/Address/Base58.hs +75/−53
- src/Haskoin/Address/Bech32.hs +143/−107
- src/Haskoin/Address/CashAddr.hs +127/−105
- src/Haskoin/Block.hs +18/−16
- src/Haskoin/Block/Common.hs +284/−203
- src/Haskoin/Block/Headers.hs +974/−902
- src/Haskoin/Block/Merkle.hs +201/−154
- src/Haskoin/Constants.hs +0/−562
- src/Haskoin/Crypto.hs +20/−16
- src/Haskoin/Crypto/Hash.hs +195/−130
- src/Haskoin/Crypto/Keys.hs +22/−0
- src/Haskoin/Crypto/Keys/Common.hs +185/−0
- src/Haskoin/Crypto/Keys/Extended.hs +1057/−0
- src/Haskoin/Crypto/Keys/Extended/Internal.hs +88/−0
- src/Haskoin/Crypto/Keys/Mnemonic.hs +2236/−0
- src/Haskoin/Crypto/Signature.hs +84/−66
- src/Haskoin/Keys.hs +0/−20
- src/Haskoin/Keys/Common.hs +0/−154
- src/Haskoin/Keys/Extended.hs +0/−940
- src/Haskoin/Keys/Mnemonic.hs +0/−516
- src/Haskoin/Network.hs +24/−18
- src/Haskoin/Network/Bloom.hs +251/−170
- src/Haskoin/Network/Common.hs +750/−582
- src/Haskoin/Network/Constants.hs +582/−0
- src/Haskoin/Network/Data.hs +89/−0
- src/Haskoin/Network/Message.hs +195/−172
- src/Haskoin/Script.hs +19/−19
- src/Haskoin/Script/Common.hs +598/−578
- src/Haskoin/Script/SigHash.hs +229/−198
- src/Haskoin/Script/Standard.hs +299/−271
- src/Haskoin/Transaction.hs +24/−18
- src/Haskoin/Transaction/Builder.hs +456/−312
- src/Haskoin/Transaction/Builder/Sign.hs +249/−198
- src/Haskoin/Transaction/Common.hs +323/−238
- src/Haskoin/Transaction/Genesis.hs +32/−27
- src/Haskoin/Transaction/Partial.hs +949/−501
- src/Haskoin/Transaction/Segwit.hs +125/−95
- src/Haskoin/Transaction/Taproot.hs +315/−0
- src/Haskoin/Util.hs +15/−205
- src/Haskoin/Util/Arbitrary.hs +21/−21
- src/Haskoin/Util/Arbitrary/Address.hs +30/−28
- src/Haskoin/Util/Arbitrary/Block.hs +51/−53
- src/Haskoin/Util/Arbitrary/Crypto.hs +14/−15
- src/Haskoin/Util/Arbitrary/Keys.hs +59/−42
- src/Haskoin/Util/Arbitrary/Message.hs +45/−44
- src/Haskoin/Util/Arbitrary/Network.hs +93/−90
- src/Haskoin/Util/Arbitrary/Script.hs +318/−299
- src/Haskoin/Util/Arbitrary/Transaction.hs +208/−195
- src/Haskoin/Util/Arbitrary/Util.hs +176/−129
- src/Haskoin/Util/Helpers.hs +426/−0
- src/Haskoin/Util/Marshal.hs +44/−0
- test/Haskoin/Address/Bech32Spec.hs +212/−122
- test/Haskoin/Address/CashAddrSpec.hs +296/−260
- test/Haskoin/AddressSpec.hs +149/−141
- test/Haskoin/BlockSpec.hs +344/−327
- test/Haskoin/Crypto/HashSpec.hs +348/−288
- test/Haskoin/Crypto/Keys/ExtendedSpec.hs +602/−0
- test/Haskoin/Crypto/Keys/MnemonicSpec.hs +637/−0
- test/Haskoin/Crypto/KeysSpec.hs +276/−0
- test/Haskoin/Crypto/SignatureSpec.hs +365/−342
- test/Haskoin/Keys/ExtendedSpec.hs +0/−578
- test/Haskoin/Keys/MnemonicSpec.hs +0/−490
- test/Haskoin/KeysSpec.hs +0/−257
- test/Haskoin/NetworkSpec.hs +148/−128
- test/Haskoin/ScriptSpec.hs +394/−361
- test/Haskoin/Transaction/PartialSpec.hs +526/−246
- test/Haskoin/Transaction/TaprootSpec.hs +181/−0
- test/Haskoin/TransactionSpec.hs +284/−263
- test/Haskoin/UtilSpec.hs +44/−46
CHANGELOG.md view
@@ -1,229 +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.18.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+## [0.17.6] - 2020-12-07+ ### Added+ - Serialize instances for `XPubKey` and `XPrvKey`. -## 0.17.5+## [0.17.5] - 2020-12-03+ ### Fixed+ - Handle special case in block header binary search function. -## 0.17.4+## [0.17.4] - 2020-12-03+ ### Fixed+ - Bounds check too restrictive in block header binary search function. -## 0.17.3+## [0.17.3] - 2020-11-17+ ### Changed+ - Reduce minimum version of text package dependency. -## 0.17.2+## [0.17.2] - 2020-11-17+ ### Changed+ - Update lists of seeds for all networks. -## 0.17.1+## [0.17.1] - 2020-11-02+ ### Changed-- Use the C-preprocessor to handle versions of `base16-bytestring` including 1.0- (with a breaking API change) -## 0.17.0+- 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+## [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.+- (breaking) rename `OP_NOP2` and `OP_NOP3` to `OP_CHECKLOCKTIMEVERIFY` and `OP_CHECKSEQUENCEVERIFY` resp. - Update to latest secp256k1 bindings. -## 0.14.1+## [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+## [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+## [0.13.6] - 2020-06-05+ ### Changed+ - Expose the Arbitrary test instances under Haskoin.Util.Arbitrary -## 0.13.5+## [0.13.5] - 2020-05-16+ ### Changed+ - Provide meaningful JSON instances for most types. -## 0.13.4+## [0.13.4] - 2020-05-14+ ### Added+ - Support for Bitcoin Cash May 2020 hard fork. -## 0.13.3+## [0.13.3] - 2020-05-08+ ### Changed+ - Improve code and documentation organisation. -## 0.13.2+## [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+## [0.13.1] - 2020-05-06+ ### Changed+ - Faster JSON serialization. -## 0.13.0+## [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-### Added-- Support for signing segwit transactions.+## [0.12.0] - 2020-04-10 -## 0.11.0 ### 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.@@ -239,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`.@@ -249,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.
README.md view
@@ -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)
+ data/bip341.json view
@@ -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"+ }+ }+ ]+}
+ data/complex_psbt.json view
@@ -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": "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+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+DkPefrwKHgPD6AgAAAAAZdqkUR664XnoZgccHtvyw3iuQCyPLYuqIrGUAAAABB2pHMEQCIHgvOV3R7nb4M5UnPrIUvanuIQBOdr6IlRuGOYIofIUOAiAgBDSVf3LguuLKEKUMjPl/8nnVHhOIC4INjpI82ZKWrgEhA1AQ8r2ipZlqr27axs2J6YdzD4rRt2IOrCmnJ5XVE6YjAAA=",+ "p2sh_pk_psbt": "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",+ "p2sh_ms_1_psbt": "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",+ "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": "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/LDeK5ALI8ti6oisZQAAAAEBK4Dw+gIAAAAAIgAgvnOo3OeUTjP10JgLepzp4olyPr52+65XfA+h2tB4LN4iAgNGwCIF9DnFfN9EGoy1X0wzlSHI0DetgEoV7WjjOR64e0cwRAIgDBEq46N1jCUq9YxIs/91a5PkOsPno4hlHHwfgFQgnXwCIGPmeb3vK2mxTFcP1jFQluNy6oiLQUloLNuzeo0SrakPAQEFaVIhAvgxAXPwShF/o8L/llk5CNHscsPp2oSAmf2R2vEYxIrzIQNGwCIF9DnFfN9EGoy1X0wzlSHI0DetgEoV7WjjOR64eyEDTPJGkwhviQtdILDD8xUoOa4ARhrRspdx5FmhZ2xH1XJTriIGAvgxAXPwShF/o8L/llk5CNHscsPp2oSAmf2R2vEYxIrzBKWoJxoiBgNGwCIF9DnFfN9EGoy1X0wzlSHI0DetgEoV7WjjOR64ewQq46psIgYDTPJGkwhviQtdILDD8xUoOa4ARhrRspdx5FmhZ2xH1XIE9+uLBQABAP1pAQIAAAABF1DTcrjCVIkcU3+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+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+DkPefrwKHgPD6AgAAAAAZdqkUR664XnoZgccHtvyw3iuQCyPLYuqIrGUAAAAAAA==",+ "p2wsh_ms_2_psbt": "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",+ "combined_psbt": "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",+ "complete_psbt": "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",+ "final_tx": "02000000000109d308a737d201c9483642a6dc36fd4d55f883f1487c3211069c1f62fc57fa48500000000000ffffffffd308a737d201c9483642a6dc36fd4d55f883f1487c3211069c1f62fc57fa485001000000232200208637ab26e75a3150d2aafa9d5d4fd9ba4db83f952ae98c7301f3a25752f479e5ffffffffd308a737d201c9483642a6dc36fd4d55f883f1487c3211069c1f62fc57fa48500200000000ffffffffd308a737d201c9483642a6dc36fd4d55f883f1487c3211069c1f62fc57fa48500300000000ffffffffd308a737d201c9483642a6dc36fd4d55f883f1487c3211069c1f62fc57fa48500400000000ffffffffd308a737d201c9483642a6dc36fd4d55f883f1487c3211069c1f62fc57fa48500500000023220020b9d25e328ff72a0302abdd98e790099ed3f326775a1a9c7b31458b9f2d955f63ffffffffd308a737d201c9483642a6dc36fd4d55f883f1487c3211069c1f62fc57fa485006000000fc0047304402207b0a8e5b2163005382621e4810d59e0e5c729b574a7abe4f2dec0670be202177022068203b4a9ffe67ac05bca14569887050d6a3727e0bbc254a972b7a1c2f3fa039014730440220736d307da0c73188ab312e55d12c3026e0804a5e2a17dd99a3d86a3abca7375e0220499b86bb24e19f106ef883e5a3e38b2e62dc7554da2a447204ccf383bed4bd67014c695221031546a19b617dc6a83810d8e07f334ce351b418613eaf2635aa427b1526a5c7f821033956816eb50d404b3d5f3279915a17a20728a46363df05a7f29d46d554062650210389993845fe8d4bd284242c6db6c76370b091ebcf170433f7304f6ec1cca9480c53aeffffffffd308a737d201c9483642a6dc36fd4d55f883f1487c3211069c1f62fc57fa4850070000006c47304402205ba47300dd590f445cb56443ce6c13f0bcd07129de44764a26b83705f66262a602205bba7cd49fe34aa1c06e83ead381d64382dcb0109adcd9157250656d781929ff01232102628c8b13d6ad3290b100d40ae1cdecae515efc3f1abd5ef854c0755cb187d1f6acffffffffd308a737d201c9483642a6dc36fd4d55f883f1487c3211069c1f62fc57fa4850080000006a4730440220782f395dd1ee76f83395273eb214bda9ee21004e76be88951b863982287c850e0220200434957f72e0bae2ca10a50c8cf97ff279d51e13880b820d8e923cd99296ae0121035010f2bda2a5996aaf6edac6cd89e987730f8ad1b7620eac29a72795d513a623ffffffff01c041c81700000000160014f3a3640c6de0a3a5c14b72164e98f96ca79d65fa024730440220407fc97f300771f0499b7eb8f2cdd8705ce7a615fc893a1f2c25e90a322ff84202201f7c69db3033aebf7febb0809df36a750d065db56b9fb0f1e4492500ee7c1f440121028ec9808ed77394c3ff6e94c1c0f83fa0baaec915eff20566c7e8b09758f08beb0247304402207434a180083d825e5f034279832541fa3a140894a7950119d04ee4b64310c1e302206218a7134dc2e19d88b1fb931cd1577d988f1aa004690c17193a63bcd4a84dd4012321034f208b00923a0c6c5a91d735206b0a63e746d63a8b232b6aa9bce9832f3f4da5ac02473044022020a694a50573f2261baf3593afcc10ac9e6e74fb4b36a8db8273ed976195c0a302205e3c5c64ed8ab19aa1a1934195c89f4424b34d99f9fa62c781654b55a7c58427012321020ae496cfca86e51c88c45438b70697ade7969429d79ceefde7a824fc585d0f7fac02473044022074645c573a73d6e9eceac7f2b61b0d411df41f6691ef612f5f2d25209b6cc4c10220114fab0a3dfd58a7e7bed5aa828844e8cfd521450b2428330cd2507227b22b91012103693192b81b9cbefdd05321eb4c460264a1081d64396a76332f3db3b121bdd433040047304402203c168f021ee2bc3aeea53b2b289b2add195162fa090d3859e1f4ada6551dbc5302204badf65a27f22c22023eb0bf46e39f190fd3c794c98881ee1f9d44e5a1a5ca140147304402200c112ae3a3758c252af58c48b3ff756b93e43ac3e7a388651c7c1f8054209d7c022063e679bdef2b69b14c570fd6315096e372ea888b4149682cdbb37a8d12ada90f0169522102f8310173f04a117fa3c2ff96593908d1ec72c3e9da848099fd91daf118c48af3210346c02205f439c57cdf441a8cb55f4c339521c8d037ad804a15ed68e3391eb87b21034cf24693086f890b5d20b0c3f3152839ae00461ad1b29771e459a1676c47d57253ae040047304402201fc9a9292a2a92db4e99081fca2bf4c7062715c48b05c487b844a49e677e14b102207153528156c535e1500e1980593ba37ac649f8b2c1f8693a0a47beaf2dd4c67a01473044022026b040772ec6a96f2e29f457c89e9520c22f47e368945e0ad5f1af0435ea8079022028b4d895852c3efb4ad7b76133ccdee0f425431781e8b2f098014ade4c7369fc01695221021cf20f5e699d4b29397b2485f322f21aeb93264ab8532bc61127978418d49ed12102c06f94c7526991f26901f227e3ab7b398749bc0c307b0a4891ddc25ac88af51c210378fc18eb885a72bc6c3e3ebec5eade1bce0ef68a4863b2b7ceba2d96e511972f53ae00000000000000"+}
− data/rfc6979DERabc.json
@@ -1,102 +0,0 @@-[- [- "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"- ]-]
+ data/rfc6979DERcash.json view
@@ -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"+ ]+]
− data/rfc6979abc.json
@@ -1,62 +0,0 @@-[- [- "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"- ]-]
+ data/rfc6979cash.json view
@@ -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"+ ]+]
+ data/test_vectors_aserti3-2d_run01.txt view
@@ -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+
+ data/test_vectors_aserti3-2d_run02.txt view
@@ -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+
+ data/test_vectors_aserti3-2d_run03.txt view
@@ -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+
+ data/test_vectors_aserti3-2d_run04.txt view
@@ -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+
+ data/test_vectors_aserti3-2d_run05.txt view
@@ -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+
+ data/test_vectors_aserti3-2d_run06.txt view
@@ -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+
+ data/test_vectors_aserti3-2d_run07.txt view
@@ -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+
+ data/test_vectors_aserti3-2d_run08.txt view
@@ -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+
+ data/test_vectors_aserti3-2d_run09.txt view
@@ -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+
+ data/test_vectors_aserti3-2d_run10.txt view
@@ -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+
+ data/test_vectors_aserti3-2d_run11.txt view
@@ -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+
+ data/test_vectors_aserti3-2d_run12.txt view
@@ -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+
haskoin-core.cabal view
@@ -1,44 +1,54 @@ cabal-version: 1.12 --- This file has been generated from package.yaml by hpack version 0.34.2.+-- This file has been generated from package.yaml by hpack version 0.38.0. -- -- see: https://github.com/sol/hpack------ hash: 9861fe70166471990a6ee8fcf42174a9d40cda5b4e28119073d56a221e78fee3 name: haskoin-core-version: 0.18.0+version: 1.2.2 synopsis: Bitcoin & Bitcoin Cash library for Haskell-description: Please see the README on GitHub at <https://github.com/haskoin/haskoin-core#readme>+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: 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/rfc6979abc.json+ data/rfc6979cash.json data/rfc6979core.json- data/rfc6979DERabc.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:@@ -51,17 +61,18 @@ Haskoin.Block.Common Haskoin.Block.Headers Haskoin.Block.Merkle- Haskoin.Constants Haskoin.Crypto Haskoin.Crypto.Hash+ Haskoin.Crypto.Keys+ Haskoin.Crypto.Keys.Common+ Haskoin.Crypto.Keys.Extended+ Haskoin.Crypto.Keys.Mnemonic Haskoin.Crypto.Signature- Haskoin.Keys- Haskoin.Keys.Common- Haskoin.Keys.Extended- Haskoin.Keys.Mnemonic Haskoin.Network Haskoin.Network.Bloom Haskoin.Network.Common+ Haskoin.Network.Constants+ Haskoin.Network.Data Haskoin.Network.Message Haskoin.Script Haskoin.Script.Common@@ -74,6 +85,7 @@ Haskoin.Transaction.Genesis Haskoin.Transaction.Partial Haskoin.Transaction.Segwit+ Haskoin.Transaction.Taproot Haskoin.Util Haskoin.Util.Arbitrary Haskoin.Util.Arbitrary.Address@@ -85,8 +97,10 @@ Haskoin.Util.Arbitrary.Script Haskoin.Util.Arbitrary.Transaction Haskoin.Util.Arbitrary.Util+ Haskoin.Util.Helpers+ Haskoin.Util.Marshal other-modules:- Paths_haskoin_core+ Haskoin.Crypto.Keys.Extended.Internal hs-source-dirs: src build-depends:@@ -94,12 +108,15 @@ , aeson >=1.4.6.0 , array >=0.5.4.0 , base >=4.9 && <5- , base16-bytestring >=0.1.1.6+ , base16 >=1.0+ , binary >=0.8.8+ , bytes >=0.17 , bytestring >=0.10.10.0- , cereal >=0.5.8.1+ , cereal >=0.5.8 , conduit >=1.3.1.2 , containers >=0.6.2.1- , cryptonite >=0.26+ , 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@@ -110,7 +127,7 @@ , network >=3.1.1.1 , safe >=0.3.18 , scientific >=0.3.6.2- , secp256k1-haskell >=0.4.0+ , secp256k1-haskell >=1.2.0 , split >=0.2.3.3 , string-conversions >=0.4.0.1 , text >=1.2.3.0@@ -129,13 +146,14 @@ Haskoin.AddressSpec Haskoin.BlockSpec Haskoin.Crypto.HashSpec+ Haskoin.Crypto.Keys.ExtendedSpec+ Haskoin.Crypto.Keys.MnemonicSpec+ Haskoin.Crypto.KeysSpec Haskoin.Crypto.SignatureSpec- Haskoin.Keys.ExtendedSpec- Haskoin.Keys.MnemonicSpec- Haskoin.KeysSpec Haskoin.NetworkSpec Haskoin.ScriptSpec Haskoin.Transaction.PartialSpec+ Haskoin.Transaction.TaprootSpec Haskoin.TransactionSpec Haskoin.UtilSpec Paths_haskoin_core@@ -147,12 +165,16 @@ , aeson >=1.4.6.0 , array >=0.5.4.0 , base >=4.9 && <5- , base16-bytestring >=0.1.1.6+ , base16 >=1.0+ , base64+ , binary >=0.8.8+ , bytes >=0.17 , bytestring >=0.10.10.0- , cereal >=0.5.8.1+ , cereal >=0.5.8 , conduit >=1.3.1.2 , containers >=0.6.2.1- , cryptonite >=0.26+ , 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@@ -166,7 +188,7 @@ , network >=3.1.1.1 , safe >=0.3.18 , scientific >=0.3.6.2- , secp256k1-haskell >=0.4.0+ , secp256k1-haskell >=1.2.0 , split >=0.2.3.3 , string-conversions >=0.4.0.1 , text >=1.2.3.0
src/Haskoin.hs view
@@ -1,33 +1,31 @@-{-|-Module : Haskoin-Description : Bitcoin (BTC/BCH) Libraries for Haskell-Copyright : No rights reserved-License : MIT-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- ( module Address- , module Network- , module Constants- , module Block- , module Transaction- , module Script- , module Keys- , module Crypto- , module Util- ) where+ ( module Address,+ module Block,+ module Transaction,+ module Script,+ module Crypto,+ module Network,+ module Util,+ )+where -import Haskoin.Address as Address-import Haskoin.Block as Block-import Haskoin.Constants as Constants-import Haskoin.Crypto as Crypto-import Haskoin.Keys as Keys-import Haskoin.Network as Network-import Haskoin.Script as Script-import Haskoin.Transaction as Transaction-import 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
src/Haskoin/Address.hs view
@@ -1,221 +1,269 @@-{-# LANGUAGE DeriveAnyClass #-}-{-# LANGUAGE DeriveGeneric #-}-{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE DeriveAnyClass #-}+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE DuplicateRecordFields #-}+{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE ImportQualifiedPost #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE OverloadedRecordDot #-} {-# LANGUAGE OverloadedStrings #-}-{-|-Module : Haskoin.Address-Copyright : No rights reserved-License : MIT-Maintainer : jprupp@protonmail.ch-Stability : experimental-Portability : POSIX+{-# LANGUAGE NoFieldSelectors #-} -Base58, CashAddr, Bech32 address and WIF private key serialization support.--}+-- |+-- 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- , isWitnessPubKeyAddress- , isWitnessScriptAddress- , addrToText- , textToAddr- , bech32ToAddr- , cashToAddr- , base58ToAddr- , addrToJSON- , addrToEncoding- , addrFromJSON- , 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+ ( -- * 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-import Control.Arrow (second)-import Control.DeepSeq-import Control.Monad-import Data.Aeson as A-import Data.Aeson.Encoding 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 Data.Word (Word8)-import GHC.Generics (Generic)-import Haskoin.Address.Base58-import Haskoin.Address.Bech32-import Haskoin.Address.CashAddr-import Haskoin.Constants-import Haskoin.Crypto-import Haskoin.Keys.Common-import Haskoin.Script-import Haskoin.Util+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- { 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- }- -- | other witness address- | WitnessAddress- { getAddrVersion :: !Word8- , getAddrData :: !ByteString- }- deriving (Eq, Ord, Generic, Show, Read, Serialize, Hashable, NFData)+ = -- | 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+isPubKeyAddress _ = False -- | 'Address' pays to a script hash. isScriptAddress :: Address -> Bool isScriptAddress ScriptAddress {} = True-isScriptAddress _ = False+isScriptAddress _ = False -- | 'Address' pays to a witness public key hash. Only valid for SegWit -- networks. isWitnessPubKeyAddress :: Address -> Bool isWitnessPubKeyAddress WitnessPubKeyAddress {} = True-isWitnessPubKeyAddress _ = False+isWitnessPubKeyAddress _ = False isWitnessScriptAddress :: Address -> Bool isWitnessScriptAddress WitnessScriptAddress {} = True-isWitnessScriptAddress _ = False+isWitnessScriptAddress _ = False isWitnessAddress :: Address -> Bool isWitnessAddress WitnessAddress {} = True-isWitnessAddress _ = False--addrToJSON :: Network -> Address -> Value-addrToJSON net a = toJSON (addrToText net a)--addrToEncoding :: Network -> Address -> Encoding-addrToEncoding net = maybe null_ text . addrToText net+isWitnessAddress _ = False --- | JSON parsing for Bitcoin addresses. Works with 'Base58', 'CashAddr' and--- 'Bech32'.-addrFromJSON :: Network -> Value -> Parser Address-addrFromJSON net =+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+ 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 {getAddrHash160 = h}- | isNothing (getCashAddrPrefix net) =- Just . encodeBase58Check . runPut $ base58put net a- | otherwise = cashAddrEncode net 0 (S.encode h)-addrToText net a@ScriptAddress {getAddrHash160 = h}- | isNothing (getCashAddrPrefix net) =- Just . encodeBase58Check . runPut $ base58put net a- | otherwise =- cashAddrEncode net 1 (S.encode h)-addrToText net WitnessPubKeyAddress {getAddrHash160 = h} = do- hrp <- getBech32Prefix net- segwitEncode hrp 0 (B.unpack (S.encode h))-addrToText net WitnessScriptAddress {getAddrHash256 = h} = do- hrp <- getBech32Prefix net- segwitEncode hrp 0 (B.unpack (S.encode h))-addrToText net WitnessAddress {getAddrVersion = v, getAddrData = d} = do- hrp <- getBech32Prefix net- segwitEncode hrp v (B.unpack d)+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 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 (S.decode bs)- 1 -> ScriptAddress <$> eitherToMaybe (S.decode bs)- _ -> Nothing+ (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 <- getBech32Prefix net- (ver, bs) <- second B.pack <$> segwitDecode hrp txt- case ver of- 0 -> case B.length bs of- 20 -> WitnessPubKeyAddress <$> eitherToMaybe (S.decode bs)- 32 -> WitnessScriptAddress <$> eitherToMaybe (S.decode bs)- _ -> Nothing- _ -> Just $ WitnessAddress ver bs+ 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 . runGet (base58get net) =<< decodeBase58Check txt+ eitherToMaybe . runGetS (base58get net) =<< decodeBase58Check txt -base58get :: Network -> Get Address+base58get :: (MonadGet m) => Network -> m Address base58get net = do- pfx <- getWord8- addr <- S.get- f pfx addr+ pfx <- getWord8+ addr <- deserialize+ 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"+ | x == net.addrPrefix = return $ PubKeyAddress a+ | x == net.scriptPrefix = return $ ScriptAddress a+ | otherwise = fail "Does not recognize address prefix" -base58put :: Network -> Putter Address+base58put :: (MonadPut m) => Network -> Address -> m () base58put net (PubKeyAddress h) = do- putWord8 (getAddrPrefix net)- put h+ putWord8 net.addrPrefix+ serialize h base58put net (ScriptAddress h) = do- putWord8 (getScriptPrefix net)- put h+ 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 :: PubKeyI -> Address-pubKeyAddr = PubKeyAddress . addressHash . S.encode+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@@ -223,14 +271,19 @@ -- | Obtain a SegWit pay-to-witness-public-key-hash (P2WPKH) address from a -- public key.-pubKeyWitnessAddr :: PubKeyI -> Address-pubKeyWitnessAddr = WitnessPubKeyAddress . addressHash . S.encode+pubKeyWitnessAddr :: Ctx -> PublicKey -> Address+pubKeyWitnessAddr ctx =+ WitnessPubKeyAddress . addressHash . marshal ctx -- | Obtain a backwards-compatible SegWit P2SH-P2WPKH address from a public key.-pubKeyCompatWitnessAddr :: PubKeyI -> Address-pubKeyCompatWitnessAddr =- p2shAddr .- addressHash . encodeOutputBS . PayWitnessPKHash . addressHash . S.encode+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'.@@ -246,64 +299,68 @@ p2wshAddr = WitnessScriptAddress -- | Compute a standard pay-to-script-hash (P2SH) address for an output script.-payToScriptAddress :: ScriptOutput -> Address-payToScriptAddress = p2shAddr . addressHash . encodeOutputBS+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 :: ScriptOutput -> Address-payToWitnessScriptAddress = p2wshAddr . sha256 . encodeOutputBS+payToWitnessScriptAddress :: Ctx -> ScriptOutput -> Address+payToWitnessScriptAddress ctx = p2wshAddr . sha256 . marshal ctx -- | Compute a backwards-compatible SegWit P2SH-P2WSH address.-payToNestedScriptAddress :: ScriptOutput -> Address-payToNestedScriptAddress =- p2shAddr . addressHash . encodeOutputBS . toP2WSH . encodeOutput+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+ \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 :: Address -> Script-addressToScript = encodeOutput . addressToOutput+addressToScript :: Ctx -> Address -> Script+addressToScript ctx = encodeOutput ctx . addressToOutput -- | Encode address as output script in 'ByteString' form.-addressToScriptBS :: Address -> ByteString-addressToScriptBS = S.encode . addressToScript+addressToScriptBS :: Ctx -> Address -> ByteString+addressToScriptBS ctx = runPutS . serialize . addressToScript ctx -- | 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+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 :: ByteString -> Either String Address-scriptToAddressBS =- maybeToEither "Could not decode address" . outputAddress <=< decodeOutputBS+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 :: ScriptOutput -> Maybe Address-outputAddress =- \case- PayPKHash h -> Just $ PubKeyAddress h- PayScriptHash h -> Just $ ScriptAddress h- PayPK k -> Just $ pubKeyAddr k- PayWitnessPKHash h -> Just $ WitnessPubKeyAddress h- PayWitnessScriptHash h -> Just $ WitnessScriptAddress h- PayWitness v d -> Just $ WitnessAddress v d- _ -> Nothing+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 :: ScriptInput -> Maybe Address-inputAddress =- \case- (RegularInput (SpendPKHash _ key)) -> Just $ pubKeyAddr key- (ScriptHashInput _ rdm) -> Just $ payToScriptAddress rdm- _ -> Nothing+inputAddress :: Ctx -> ScriptInput -> Maybe Address+inputAddress ctx =+ \case+ (RegularInput (SpendPKHash _ key)) -> Just $ pubKeyAddr ctx key+ (ScriptHashInput _ rdm) -> Just $ payToScriptAddress ctx rdm+ _ -> Nothing
src/Haskoin/Address/Base58.hs view
@@ -1,36 +1,44 @@+{-# LANGUAGE ImportQualifiedPost #-} {-# LANGUAGE OverloadedStrings #-}-{-|-Module : Haskoin.Address.Base58-Copyright : No rights reserved-License : MIT-Maintainer : jprupp@protonmail.ch-Stability : experimental-Portability : POSIX+{-# LANGUAGE NoFieldSelectors #-} -Support for legacy 'Base58' addresses. Superseded by Bech32 for Bitcoin SegWit-(BTC) and CashAddr for Bitcoin Cash (BCH).--}+-- |+-- 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+ ( -- * 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 Haskoin.Crypto.Hash-import Haskoin.Util-import Numeric (readInt, showIntAtBase)+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@@ -39,65 +47,79 @@ 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 -> Char-b58 = C.index b58Data+b58 :: Int -> Word8+b58 = (b58Array !) -- | Convert a 'Base58' character into the number it represents.-b58' :: Char -> Maybe Int-b58' = flip C.elemIndex b58Data+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 b58 i ""+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+ case go of+ Just (r, []) -> Just r+ _ -> Nothing where- p = isJust . b58'- f = fromMaybe e . b58'+ 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"+ 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+ l <> 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+ (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 =- BS.append prefix <$> r+ B.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+ (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 $ BS.append bs $ encode $ checkSum32 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) = BS.splitAt (BS.length rs - 4) rs- guard $ chk == encode (checkSum32 res)- return res+ rs <- decodeBase58 bs+ let (res, chk) = B.splitAt (B.length rs - 4) rs+ guard $ chk == (runPutS . serialize . checkSum32) res+ return res
src/Haskoin/Address/Bech32.hs view
@@ -1,46 +1,68 @@+{-# LANGUAGE ImportQualifiedPost #-}+{-# LANGUAGE MultiWayIf #-} {-# LANGUAGE OverloadedStrings #-}-{-|-Module : Haskoin.Address.Base58-Copyright : No rights reserved-License : MIT-Maintainer : jprupp@protonmail.ch-Stability : experimental-Portability : POSIX+{-# LANGUAGE NoFieldSelectors #-} -Support for Bitcoin SegWit (BTC) Bech32 addresses. This module is a modified-version of Marko Bencun's reference implementation.--}+-- |+-- 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- , Data- , bech32Encode- , bech32Decode- , toBase32- , toBase256- , segwitEncode- , segwitDecode- , Word5(..)- , word5- , fromWord5- ) where+ ( -- * 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 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)+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 @@ -50,28 +72,28 @@ -- | Data part of 'Bech32' address. type Data = [Word8] -(.>>.), (.<<.) :: Bits a => a -> Int -> a+(.>>.), (.<<.) :: (Bits a) => a -> Int -> a (.>>.) = unsafeShiftR (.<<.) = unsafeShiftL -- | Five-bit word for Bech32.-newtype Word5 =- UnsafeWord5 Word8- deriving (Eq, Ord)+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+ 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 :: (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 :: (Num a) => Word5 -> a fromWord5 (UnsafeWord5 x) = fromIntegral x {-# INLINE fromWord5 #-} {-# SPECIALIZE INLINE fromWord5 :: Word5 -> Word8 #-}@@ -79,13 +101,13 @@ -- | 'Bech32' character map as array of five-bit integers to character. charset :: Array Word5 Char charset =- listArray (UnsafeWord5 0, UnsafeWord5 31) "qpzry9x8gf2tvdw0s3jn54khce6mua7l"+ 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+ | inRange (bounds inv) upperC = inv ! upperC+ | otherwise = Nothing where upperC = toUpper c inv = listArray ('0', 'Z') (repeat Nothing) // map swap (assocs charset)@@ -96,7 +118,7 @@ bech32Polymod values = foldl' go 1 values .&. 0x3fffffff where go chk value =- foldl' xor chk' [g | (g, i) <- zip generator [25 ..], testBit chk i]+ 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@@ -105,22 +127,32 @@ -- words. bech32HRPExpand :: HRP -> [Word5] bech32HRPExpand hrp =- map (UnsafeWord5 . (.>>. 5)) hrpBytes ++- [UnsafeWord5 0] ++ map word5 hrpBytes+ 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]]+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- polymod =- bech32Polymod (values ++ map UnsafeWord5 [0, 0, 0, 0, 0, 0]) `xor` 1+ w5 = values ++ map UnsafeWord5 [0, 0, 0, 0, 0, 0]+ polymod = bech32Polymod w5 `xor` bech32Const enc --- | 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+-- | 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@@ -129,101 +161,105 @@ -- | 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+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+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 -> 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- guard $ bech32VerifyChecksum hrp' dat'- return (hrp', take (T.length dat - 6) dat')+ 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 _ 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+ 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 :: (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)+ 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]- ]+ [ (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+ 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+ 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+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- (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)+ (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 witver witprog- bech32Encode hrp $ UnsafeWord5 witver : toBase32 witprog+ guard $ segwitCheck enc witver witprog+ bech32Encode enc hrp $ UnsafeWord5 witver : toBase32 witprog+ where+ enc = if witver == 0 then Bech32 else Bech32m
src/Haskoin/Address/CashAddr.hs view
@@ -1,41 +1,55 @@+{-# LANGUAGE DuplicateRecordFields #-}+{-# LANGUAGE ImportQualifiedPost #-}+{-# LANGUAGE OverloadedRecordDot #-} {-# LANGUAGE OverloadedStrings #-}-{-|-Module : Haskoin.Address.CashAddr-Copyright : No rights reserved-License : MIT-Maintainer : jprupp@protonmail.ch-Stability : experimental-Portability : POSIX+{-# LANGUAGE NoFieldSelectors #-} -Support for Bitcoin Cash (BCH) CashAddr format.--}+-- |+-- 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+ ( -- * 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 Haskoin.Constants-import Haskoin.Util+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@@ -48,8 +62,8 @@ -- | 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.+-- | 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.@@ -64,32 +78,32 @@ -- 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)+ 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 <- getCashAddrPrefix net- cash32encodeType pfx cv bs+ 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)+ 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' @@ -97,64 +111,72 @@ -- '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)+ 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)+ 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' = E.encodeUtf8 text+ 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) (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+ 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+ 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+ 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)+ guard (vb .&. 0x80 == 0)+ return (ver, len) where ver = vb `shiftR` 3 len = ls !! fromIntegral (vb .&. 0x07)@@ -164,35 +186,35 @@ -- 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)+ 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]]+ 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)+ 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]+ [0x98f2bc8e61, 0x79b76d99e2, 0xf33e5fb3c4, 0xae2eabe2a8, 0x1e4f43e470] inner c0 c (b, g)- | c0 `testBit` b = c `xor` g- | otherwise = c+ | c0 `testBit` b = c `xor` g+ | otherwise = c -- | Validate that polymod 'ByteString' (eight bytes) is equal to zero. verifyCash32Polymod :: ByteString -> Bool
src/Haskoin/Block.hs view
@@ -1,19 +1,21 @@-{-|-Module : Haskoin.Block-Copyright : No rights reserved-License : MIT-Maintainer : jprupp@protonmail.ch-Stability : experimental-Portability : POSIX+{-# LANGUAGE DuplicateRecordFields #-} -Most functions relating to blocks are exported by this module.--}+-- |+-- 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+ ( 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+import Haskoin.Block.Common+import Haskoin.Block.Headers+import Haskoin.Block.Merkle
src/Haskoin/Block/Common.hs view
@@ -1,60 +1,64 @@-{-# LANGUAGE DeriveAnyClass #-}-{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE DeriveAnyClass #-}+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE DuplicateRecordFields #-}+{-# LANGUAGE ImportQualifiedPost #-} {-# LANGUAGE OverloadedStrings #-}-{-|-Module : Haskoin.Block.Common-Copyright : No rights reserved-License : MIT-Maintainer : jprupp@protonmail.ch-Stability : experimental-Portability : POSIX+{-# LANGUAGE NoFieldSelectors #-} -Common data types and functions to handle blocks from the block chain.--}+-- |+-- 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+ ( -- * Blocks+ 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 (..), object, toJSON,- withObject, withText, (.:), (.=))-import Data.Aeson.Encoding (pairs, unsafeToEncoding)-import Data.Bits (shiftL, shiftR, (.&.), (.|.))-import qualified Data.ByteString as B-import Data.ByteString.Builder (char7)-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 Haskoin.Crypto.Hash-import Haskoin.Network.Common-import Haskoin.Transaction.Common-import Haskoin.Util-import qualified Text.Read as R+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@@ -64,71 +68,92 @@ -- | Block header and transactions. data Block = Block- { blockHeader :: !BlockHeader- , blockTxns :: ![Tx]- }- deriving (Eq, Show, Read, Generic, Hashable, NFData)+ { 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 = 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+ 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 $ "header" .= h <> "transactions" .= t+ 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"+ parseJSON =+ withObject "Block" $ \o ->+ Block <$> o .: "header" <*> o .: "transactions" -- | Block header hash. To be serialized reversed for display purposes.-newtype BlockHash = BlockHash- { getBlockHash :: Hash256- } deriving (Eq, Ord, Generic, Hashable, Serialize, NFData)+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+ showsPrec _ = shows . blockHashToHex instance Read BlockHash where- readPrec = do- R.String str <- R.lexP- maybe R.pfail return $ hexToBlockHash $ cs str+ 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+ 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+ parseJSON =+ withText "BlockHash" $+ maybe mzero return . hexToBlockHash instance ToJSON BlockHash where- toJSON = String . blockHashToHex- toEncoding h =- unsafeToEncoding $- char7 '"' <> hexBuilder (B.reverse (encode h)) <> char7 '"'+ 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 (encode h))+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 (decode bs)- return $ BlockHash h+ 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@@ -137,81 +162,97 @@ -- 'Block'. Variations in the coinbase will result in different merkle roots in -- the 'BlockHeader'. data BlockHeader = BlockHeader- { blockVersion :: !Word32 -- 4 bytes+ { version :: !Word32, -- 4 bytes+ -- | hash of the previous block (parent)- , prevBlock :: !BlockHash -- 32 bytes+ prev :: !BlockHash, -- 32 bytes+ -- | root of the merkle tree of transactions- , merkleRoot :: !Hash256 -- 32 bytes+ merkle :: !Hash256, -- 32 bytes+ -- | unix timestamp- , blockTimestamp :: !Timestamp -- 4 bytes+ timestamp :: !Timestamp, -- 4 bytes+ -- | difficulty target- , blockBits :: !Word32 -- 4 bytes+ bits :: !Word32, -- 4 bytes+ -- | random nonce- , bhNonce :: !Word32 -- 4 bytes- }- deriving (Eq, Ord, Show, Read, Generic, Hashable, NFData)- -- 80 bytes+ 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 (encode m)- , "timestamp" .= t- , "bits" .= b- , "nonce" .= n- ]- toEncoding (BlockHeader v p m t b n) =- pairs- ( "version" .= v- <> "prevblock" .= p- <> "merkleroot" .= encodeHex (encode m)- <> "timestamp" .= t- <> "bits" .= b- <> "nonce" .= n- )+ 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 . decode =<<) . decodeHex+ 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- 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+ put = serialize+ get = deserialize -- | Compute hash of 'BlockHeader'. headerHash :: BlockHeader -> BlockHash-headerHash = BlockHash . doubleSHA256 . encode+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.@@ -228,66 +269,96 @@ -- block hash, at at the tip of the chain, or after 500 entries, whichever comes -- earlier. data GetBlocks = GetBlocks- { getBlocksVersion :: !Word32+ { version :: !Word32, -- | block locator object- , getBlocksLocator :: !BlockLocator+ locator :: !BlockLocator, -- | hash of the last desired block- , getBlocksHashStop :: !BlockHash- }- deriving (Eq, Show, Read, 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+ stop :: !BlockHash+ }+ deriving (Eq, Show, Read, Generic, NFData) -putGetBlockMsg :: Word32 -> BlockLocator -> BlockHash -> Put-putGetBlockMsg v xs h = do+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 put- put h+ 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- { getHeadersVersion :: !Word32+ { version :: !Word32, -- | block locator object- , getHeadersBL :: !BlockLocator+ locator :: !BlockLocator, -- | hash of the last desired block header- , getHeadersHashStop :: !BlockHash- }- deriving (Eq, Show, Read, Generic, NFData)+ 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- get = GetHeaders <$> getWord32le <*> (repList =<< get) <*> get- where- repList (VarInt c) = replicateM (fromIntegral c) get- put (GetHeaders v xs h) = putGetBlockMsg v xs h+ 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- headersList :: [BlockHeaderCount]- } deriving (Eq, Show, Read, Generic, NFData)+ { -- | 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- 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+ 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@@ -299,7 +370,10 @@ -- \[ -- N = -1^{sign} \times mantissa \times 256^{exponent-3} -- \]-decodeCompact :: Word32 -> (Integer, Bool) -- ^ true means overflow+decodeCompact ::+ Word32 ->+ -- | true means overflow+ (Integer, Bool) decodeCompact nCompact = (if neg then res * (-1) else res, over) where nSize :: Int@@ -307,15 +381,20 @@ nWord' :: Word32 nWord' = nCompact .&. 0x007fffff nWord :: Word32- nWord | nSize <= 3 = nWord' `shiftR` (8 * (3 - nSize))- | otherwise = nWord'+ nWord+ | nSize <= 3 = nWord' `shiftR` (8 * (3 - nSize))+ | otherwise = nWord' res :: Integer- res | nSize <= 3 = fromIntegral nWord- | otherwise = fromIntegral nWord `shiftL` (8 * (nSize - 3))+ 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)+ 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.@@ -325,22 +404,24 @@ i' = abs i neg = i < 0 nSize' :: Int- nSize' = let f 0 = 0- f n = 1 + f (n `shiftR` 8)- in f i'+ 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)))+ | 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''' .&. 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'+ nCompact+ | neg && (nCompact' .&. 0x007fffff /= 0) = nCompact' .|. 0x00800000+ | otherwise = nCompact' low64 :: Integer low64 = 0xffffffffffffffff
src/Haskoin/Block/Headers.hs view
@@ -1,902 +1,974 @@-{-# LANGUAGE DeriveAnyClass #-}-{-# LANGUAGE DeriveGeneric #-}-{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE LambdaCase #-}-{-# LANGUAGE RecordWildCards #-}-{-|-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.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 Haskoin.Block.Common-import Haskoin.Constants-import Haskoin.Crypto-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- { nodeHeader :: !BlockHeader- , nodeHeight :: !BlockHeight- -- | accumulated work so far- , nodeWork :: !BlockWork- -- | skip magic block hash- , nodeSkip :: !BlockHash- }- deriving (Show, Read, Generic, Hashable, NFData)--instance Serialize BlockNode where- get = do- nodeHeader <- S.get- nodeHeight <- getWord32le- nodeWork <- S.get- if nodeHeight == 0- then do- let nodeSkip = headerHash nodeHeader- return BlockNode {..}- else do- nodeSkip <- S.get- return BlockNode {..}- put bn = do- put $ nodeHeader bn- putWord32le $ nodeHeight bn- put $ nodeWork bn- case nodeHeight bn of- 0 -> return ()- _ -> 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 BlockNode {nodeHeight = 0} = True-isGenesis _ = False---- | Build the genesis 'BlockNode' for the supplied 'Network'.-genesisNode :: Network -> BlockNode-genesisNode net =- BlockNode- { nodeHeader = getGenesisHeader net- , nodeHeight = 0- , nodeWork = headerWork (getGenesisHeader net)- , nodeSkip = headerHash (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 n BlockNode{..}- | nodeHeight == 0 = return $ reverse acc- | otherwise = 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 _ bn@BlockNode {nodeHeight = 0} = return bn-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---- | 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 (nodeHeight par > nodeHeight anchor)- return $ nextAsertWorkRequired net anchor- daa = do- daa_height <- getDaaBlockHeight net- guard (nodeHeight par + 1 >= daa_height)- return $ nextDaaWorkRequired net- eda = do- eda_height <- getEdaBlockHeight net- guard (nodeHeight par + 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- | 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- 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- height = nodeHeight par- e1 = error "Cannot get ancestor at parent - 144 height"- minDifficulty =- blockTimestamp bh >- blockTimestamp (nodeHeader par) + getTargetSpacing net * 2--mtp :: BlockHeaders m => BlockNode -> m Timestamp-mtp bn- | nodeHeight bn == 0 = return 0- | otherwise = do- pars <- getParents 11 bn- return $ medianTime (map (blockTimestamp . nodeHeader) 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 = nodeHeight b - nodeHeight a <= 1- is_between a' b' = a' /= GT && b' /= LT- choose_one a b- | top = return b- | otherwise = return a--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 (nodeHeight a) (nodeHeight b)--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 getAsertActivationTime net 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 (prevBlock (nodeHeader anchor))- let anchor_parent_time = toInteger $ blockTimestamp $ nodeHeader anchor_parent- time_diff = current_time - anchor_parent_time- return $ computeAsertBits halflife anchor_bits time_diff height_diff- where- halflife = getAsertHalfLife net- anchor_height = toInteger $ nodeHeight anchor- anchor_bits = blockBits $ nodeHeader anchor- current_height = toInteger (nodeHeight par) + 1- height_diff = current_height - anchor_height- current_time = toInteger $ blockTimestamp bh- 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 = (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+{-# 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
src/Haskoin/Block/Merkle.hs view
@@ -1,165 +1,196 @@ {-# LANGUAGE DeriveAnyClass #-}-{-# LANGUAGE DeriveGeneric #-}-{-|-Module : Haskoin.Block.Merkle-Copyright : No rights reserved-License : MIT-Maintainer : jprupp@protonmail.ch-Stability : experimental-Portability : POSIX+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE DuplicateRecordFields #-}+{-# LANGUAGE OverloadedRecordDot #-}+{-# LANGUAGE NoFieldSelectors #-} -Function to deal with Merkle trees inside blocks.--}+-- |+-- 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+ ( -- * 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.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 Haskoin.Block.Common-import Haskoin.Constants-import Haskoin.Crypto.Hash-import Haskoin.Network.Common-import Haskoin.Transaction.Common+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+type MerkleRoot = Hash256 -- | Bits that are used to rebuild partial merkle tree transaction hash list.-type FlagBits = [Bool]+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)+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 Serialize MerkleBlock where+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) - 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)+ 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 - 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+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) ]- ]+ [ 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+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 :: Int -- ^ number of transactions (leaf nodes)- -> Int -- ^ height at which we want to compute the width- -> Int -- ^ width of the Merkle tree+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 :: [TxHash] -- ^ transaction hashes (leaf nodes)- -> MerkleRoot -- ^ root of the Merkle tree+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 $ encode a `BS.append` encode b+hash2 a b = doubleSHA256 $ runPutS (serialize a) <> runPutS (serialize 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 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 = getTxHash $ txs !! pos- | otherwise = hash2 left right+ | 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+ 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+ -- | 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)+ 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)+ | 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@@ -167,60 +198,79 @@ 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 = ([], [])+ | (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 ::+ 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)+ | 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+ | 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 ::+ 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+ | ntx == 0 =+ Left "extractMatches: number of transactions can not be 0"- | ntx > getMaxBlockSize net `div` 60 = Left+ | ntx > net.maxBlockSize `div` 60 =+ Left "extractMatches: number of transactions excessively high"- | length hashes > ntx = Left+ | length hashes > ntx =+ Left "extractMatches: More hashes provided than the number of transactions"- | length flags < length hashes = Left+ | length flags < length hashes =+ Left "extractMatches: At least one bit per node and one bit per hash"- | isNothing resM = Left+ | isNothing resM =+ Left "extractMatches: traverseAndExtract failed"- | (nBitsUsed+7) `div` 8 /= (length flags+7) `div` 8 = Left+ | (nBitsUsed + 7) `div` 8 /= (length flags + 7) `div` 8 =+ Left "extractMatches: All bits were not consumed"- | nHashUsed /= length hashes = Left $+ | nHashUsed /= length hashes =+ Left $ "extractMatches: All hashes were not consumed: " ++ show nHashUsed- | otherwise = return (merkRoot, matches)+ | otherwise = return (merkRoot, matches) where resM = traverseAndExtract (calcTreeHeight ntx) 0 ntx flags hashes (merkRoot, matches, nBitsUsed, nHashUsed) = fromMaybe e resM@@ -237,18 +287,15 @@ -- | 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])+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+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
− src/Haskoin/Constants.hs
@@ -1,562 +0,0 @@-{-# LANGUAGE CPP #-}-{-# LANGUAGE DeriveAnyClass #-}-{-# LANGUAGE DeriveGeneric #-}-{-# LANGUAGE OverloadedStrings #-}-{-|-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.Constants- ( -- * 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.Word (Word32, Word64, Word8)-import GHC.Generics (Generic)-import Haskoin.Block.Common-import Text.Read---- | Version of Haskoin Core package.-versionString :: IsString a => a-#ifdef CURRENT_PACKAGE_VERSION-versionString = CURRENT_PACKAGE_VERSION-#else-versionString = "Unavailable"-#endif---- | 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)- -- | asert3-2d algorithm activation time- -- TODO: Replace with block height after fork- , getAsertActivationTime :: !(Maybe Word32)- -- | asert3-2d algorithm halflife (not used for non-BCH networks)- , getAsertHalfLife :: !Integer- -- | 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- , "dnsseed.emzy.de" -- Stephan Oeste- , "seed.bitcoin.wiz.biz" -- Jason Maurice- ]- , getBip44Coin = 0- , getSigHashForkId = Nothing- , getEdaBlockHeight = Nothing- , getDaaBlockHeight = Nothing- , getAsertActivationTime = Nothing- , getAsertHalfLife = 0- , 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- , getAsertActivationTime = Nothing- , getAsertHalfLife = 0- , 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- , getAsertActivationTime = Nothing- , getAsertHalfLife = 0- , 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-bch.bitcoinforks.org"- , "btccash-seeder.bitcoinunlimited.info"- , "seed.bchd.cash"- , "seed.bch.loping.net"- , "dnsseed.electroncash.de"- ]- , getBip44Coin = 145- , getSigHashForkId = Just 0- , getEdaBlockHeight = Just 478559- , getDaaBlockHeight = Just 404031- , getAsertActivationTime = Just 1605441600- , getAsertHalfLife = 60 * 60 * 10- , 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-bch.bitcoinforks.org"- , "testnet-seed.bchd.cash"- , "seed.tbch.loping.net"- ]- , getBip44Coin = 1- , getSigHashForkId = Just 0- , getEdaBlockHeight = Just 1155876- , getDaaBlockHeight = Just 1188697- , getAsertActivationTime = Just 1605441600- , getAsertHalfLife = 60 * 60- , 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- , getAsertActivationTime = Just 1605441600- , getAsertHalfLife = 2 * 24 * 60 * 60- , 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]
src/Haskoin/Crypto.hs view
@@ -1,19 +1,23 @@-{-|-Module : Haskoin.Crypto-Copyright : No rights reserved-License : MIT-Maintainer : jprupp@protonmail.ch-Stability : experimental-Portability : POSIX+{-# LANGUAGE DuplicateRecordFields #-} -Hashing functions and ECDSA signatures.--}+-- |+-- 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 Hash- , module Signature- , module Secp256k1- ) where+ ( module Secp256k1,+ module Hash,+ module Keys,+ module Signature,+ )+where -import Crypto.Secp256k1 as Secp256k1-import Haskoin.Crypto.Hash as Hash-import Haskoin.Crypto.Signature as Signature+import Crypto.Secp256k1 as Secp256k1+import Haskoin.Crypto.Hash as Hash+import Haskoin.Crypto.Keys as Keys+import Haskoin.Crypto.Signature as Signature
src/Haskoin/Crypto/Hash.hs view
@@ -1,202 +1,267 @@-{-# LANGUAGE DeriveAnyClass #-}-{-# LANGUAGE DeriveGeneric #-}-{-|-Module : Haskoin.Crypto.Hash-Copyright : No rights reserved-License : MIT-Maintainer : jprupp@protonmail.ch-Stability : experimental-Portability : POSIX+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE DerivingStrategies #-}+{-# LANGUAGE DuplicateRecordFields #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE ImportQualifiedPost #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE OverloadedRecordDot #-}+{-# LANGUAGE TypeApplications #-}+{-# LANGUAGE NoFieldSelectors #-} -Hashing functions and corresponding data types. Uses functions from the-cryptonite library.--}+-- |+-- 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(getHash512)- , Hash256(getHash256)- , Hash160(getHash160)- , CheckSum32(getCheckSum32)- , sha512- , sha256- , ripemd160- , sha1- , doubleSHA256- , addressHash- , checkSum32- , hmac512- , hmac256- , split512- , join512- ) where+ ( -- * 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 (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 Haskoin.Util-import Text.Read as R+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- { getCheckSum32 :: Word32- } deriving (Eq, Ord, Serialize, Show, Read, Hashable, Generic, NFData)+ { 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 { getHash512 :: ShortByteString }- deriving (Eq, Ord, Hashable, Generic, NFData)+newtype Hash512 = Hash512 {get :: ShortByteString}+ deriving (Eq, Ord, Generic)+ deriving newtype (Hashable, NFData) -- | Type for 256-bit hashes.-newtype Hash256 = Hash256 { getHash256 :: ShortByteString }- deriving (Eq, Ord, Hashable, Generic, NFData)+newtype Hash256 = Hash256 {get :: ShortByteString}+ deriving (Eq, Ord, Generic)+ deriving newtype (Hashable, NFData) -- | Type for 160-bit hashes.-newtype Hash160 = Hash160 { getHash160 :: ShortByteString }- deriving (Eq, Ord, Hashable, Generic, NFData)+newtype Hash160 = Hash160 {get :: ShortByteString}+ deriving (Eq, Ord, Generic)+ deriving newtype (Hashable, NFData) instance Show Hash512 where- showsPrec _ = shows . encodeHex . BSS.fromShort . getHash512+ showsPrec _ = shows . encodeHex . fromShort . (.get) instance Read Hash512 where- readPrec = do- R.String str <- lexP- maybe pfail return $ Hash512 . BSS.toShort <$> decodeHex (cs str)+ readPrec = do+ R.String str <- lexP+ maybe pfail (return . Hash512 . toShort) (decodeHex (cs str)) instance Show Hash256 where- showsPrec _ = shows . encodeHex . BSS.fromShort . getHash256+ showsPrec _ = shows . encodeHex . fromShort . (.get) instance Read Hash256 where- readPrec = do- R.String str <- lexP- maybe pfail return $ Hash256 . BSS.toShort <$> decodeHex (cs str)+ readPrec = do+ R.String str <- lexP+ maybe pfail (return . Hash256 . toShort) (decodeHex (cs str)) instance Show Hash160 where- showsPrec _ = shows . encodeHex . BSS.fromShort . getHash160+ showsPrec _ = shows . encodeHex . fromShort . (.get) instance Read Hash160 where- readPrec = do- R.String str <- lexP- maybe pfail return $ Hash160 . BSS.toShort <$> decodeHex (cs str)+ 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 BS.length bs of- 64 -> Hash512 (BSS.toShort bs)- _ -> e- where- e = error "Could not decode hash from hex string"+ 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- get = Hash512 <$> Get.getShortByteString 64- put = Put.putShortByteString . getHash512+ 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 BS.length bs of- 32 -> Hash256 (BSS.toShort bs)- _ -> e- where- e = error "Could not decode hash from hex string"+ 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- get = Hash256 <$> Get.getShortByteString 32- put = Put.putShortByteString . getHash256+ 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 BS.length bs of- 20 -> Hash160 (BSS.toShort bs)- _ -> e- where- e = error "Could not decode hash from hex string"+ 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- get = Hash160 <$> Get.getShortByteString 20- put = Put.putShortByteString . getHash160+ put = serialize+ get = deserialize +instance Binary Hash160 where+ put = serialize+ get = deserialize+ -- | Calculate SHA512 hash.-sha512 :: ByteArrayAccess b => b -> Hash512-sha512 = Hash512 . BSS.toShort . BA.convert . hashWith SHA512+sha512 :: (ByteArrayAccess b) => b -> Hash512+sha512 = Hash512 . toShort . convert . hashWith SHA512 -- | Calculate SHA256 hash.-sha256 :: ByteArrayAccess b => b -> Hash256-sha256 = Hash256 . BSS.toShort . BA.convert . hashWith SHA256+sha256 :: (ByteArrayAccess b) => b -> Hash256+sha256 = Hash256 . toShort . convert . hashWith SHA256 -- | Calculate RIPEMD160 hash.-ripemd160 :: ByteArrayAccess b => b -> Hash160-ripemd160 = Hash160 . BSS.toShort . BA.convert . hashWith RIPEMD160+ripemd160 :: (ByteArrayAccess b) => b -> Hash160+ripemd160 = Hash160 . toShort . convert . hashWith RIPEMD160 -- | Claculate SHA1 hash.-sha1 :: ByteArrayAccess b => b -> Hash160-sha1 = Hash160 . BSS.toShort . BA.convert . hashWith SHA1+sha1 :: (ByteArrayAccess b) => b -> Hash160+sha1 = Hash160 . toShort . convert . hashWith SHA1 -- | Compute two rounds of SHA-256.-doubleSHA256 :: ByteArrayAccess b => b -> Hash256+doubleSHA256 :: (ByteArrayAccess b) => b -> Hash256 doubleSHA256 =- Hash256 . BSS.toShort . BA.convert . hashWith SHA256 . hashWith SHA256+ Hash256 . toShort . convert . hashWith SHA256 . hashWith SHA256 -- | Compute SHA-256 followed by RIPMED-160.-addressHash :: ByteArrayAccess b => b -> Hash160+addressHash :: (ByteArrayAccess b) => b -> Hash160 addressHash =- Hash160 . BSS.toShort . BA.convert . hashWith RIPEMD160 . hashWith SHA256+ 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")- . decode- . BS.take 4- . BA.convert- . hashWith SHA256- . hashWith SHA256+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 $ BSS.toShort $ BA.convert (hmac key msg :: HMAC SHA512)+ 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 $ BSS.toShort $ BA.convert (hmac key msg :: HMAC SHA256)+ Hash256 $ toShort $ 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))+ (Hash256 (toShort a), Hash256 (toShort b)) where- (a, b) = BS.splitAt 32 . BSS.fromShort $ getHash512 h+ (a, b) = B.splitAt 32 $ fromShort h.get -- | 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)+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
+ src/Haskoin/Crypto/Keys.hs view
@@ -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
+ src/Haskoin/Crypto/Keys/Common.hs view
@@ -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
+ src/Haskoin/Crypto/Keys/Extended.hs view
@@ -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
+ src/Haskoin/Crypto/Keys/Extended/Internal.hs view
@@ -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
+ src/Haskoin/Crypto/Keys/Mnemonic.hs view
@@ -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"+ ]
src/Haskoin/Crypto/Signature.hs view
@@ -1,88 +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+-- 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- putSig- , getSig- , signHash- , verifyHashSig- , isCanonicalHalfOrder- , decodeStrictSig- , exportSig- ) where+ ( -- * Signatures+ 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.Maybe (fromMaybe, isNothing)-import Data.Serialize as S-import Haskoin.Crypto.Hash-import Numeric (showHex)+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 . encode+ 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 :: SecKey -> Hash256 -> Sig-signHash k = signMsg k . hashToMsg+signHash :: Ctx -> SecKey -> Hash256 -> Sig+signHash ctx k = signMsg ctx k . hashToMsg -- | Verify an ECDSA signature for a 256-bit hash.-verifyHashSig :: Hash256 -> Sig -> PubKey -> Bool-verifyHashSig h s p = verifySig p norm (hashToMsg h)+verifyHashSig :: Ctx -> Hash256 -> Sig -> PubKey -> Bool+verifyHashSig ctx h s p = verifySig ctx p norm (hashToMsg h) where- norm = fromMaybe s (normalizeSig s)+ norm = fromMaybe s (normalizeSig ctx s) --- | 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+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 bs of- Just s -> return s- Nothing -> fail "Invalid signature"+ case decodeStrictSig ctx 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+ 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 :: Sig -> Bool-isCanonicalHalfOrder = isNothing . normalizeSig+isCanonicalHalfOrder :: Ctx -> Sig -> Bool+isCanonicalHalfOrder ctx = isNothing . normalizeSig ctx -- | Decode signature strictly.-decodeStrictSig :: ByteString -> Maybe Sig-decodeStrictSig bs = do- g <- importSig 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 g- let zero = BS.replicate 32 0- guard $ BS.take 32 (getCompactSig compact) /= zero- guard $ BS.take 32 (BS.drop 32 (getCompactSig compact)) /= zero- guard $ isCanonicalHalfOrder g- return g+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
− src/Haskoin/Keys.hs
@@ -1,20 +0,0 @@-{-|-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.Keys- ( module Haskoin.Keys.Common- , module Haskoin.Keys.Extended- , module Haskoin.Keys.Mnemonic- ) where--import Haskoin.Keys.Common-import Haskoin.Keys.Extended-import Haskoin.Keys.Mnemonic
− src/Haskoin/Keys/Common.hs
@@ -1,154 +0,0 @@-{-# LANGUAGE DeriveAnyClass #-}-{-# LANGUAGE DeriveGeneric #-}-{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE OverloadedStrings #-}-{-# 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.Keys.Common- ( -- * Public & Private Keys- PubKeyI(..)- , SecKeyI(..)- , exportPubKey- , importPubKey- , wrapPubKey- , derivePubKeyI- , wrapSecKey- , fromMiniKey- , tweakPubKey- , tweakSecKey- , getSecKey- , secKey- -- ** Private Key Wallet Import Format (WIF)- , fromWif- , toWif- ) where--import Control.Applicative ((<|>))-import Control.DeepSeq-import Control.Monad (guard, mzero, (<=<))-import Crypto.Secp256k1-import Data.Aeson (FromJSON, ToJSON (..), Value (String),- parseJSON, withText)-import Data.Aeson.Encoding (unsafeToEncoding)-import Data.ByteString (ByteString)-import qualified Data.ByteString as BS-import Data.ByteString.Builder (char7)-import Data.Hashable-import Data.Maybe (fromMaybe)-import Data.Serialize (Serialize, decode, encode, get, put)-import Data.Serialize.Get (getByteString)-import Data.Serialize.Put (putByteString)-import Data.String (IsString, fromString)-import Data.String.Conversions (cs)-import GHC.Generics (Generic)-import Haskoin.Address.Base58-import Haskoin.Constants-import Haskoin.Crypto.Hash-import 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- toEncoding s = unsafeToEncoding $ char7 '"' <> hexBuilder (encode s) <> char7 '"'--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---- | Tweak a private key.-tweakSecKey :: SecKey -> Hash256 -> Maybe SecKey-tweakSecKey key h = tweakAddSecKey key =<< tweak (encode h)---- | 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---- | 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 (BS.head bs == getSecretPrefix net)- 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 -> SecKeyI -> Base58-toWif net (SecKeyI k c) =- encodeBase58Check . BS.cons (getSecretPrefix net) $- if c- then getSecKey k `BS.snoc` 0x01- else getSecKey k
− src/Haskoin/Keys/Extended.hs
@@ -1,940 +0,0 @@-{-# LANGUAGE DeriveAnyClass #-}-{-# LANGUAGE DeriveGeneric #-}-{-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE GADTs #-}-{-# LANGUAGE OverloadedStrings #-}-{-|-Module : Haskoin.Keys.Extended-Copyright : No rights reserved-License : MIT-Maintainer : jprupp@protonmail.ch-Stability : experimental-Portability : POSIX--BIP-32 extended keys.--}-module 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- , xPubToEncoding- , xPubFromJSON- , xPrvExport- , xPrvToJSON- , xPrvToEncoding- , 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.Encoding (Encoding, text)-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.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 Haskoin.Address-import Haskoin.Constants-import Haskoin.Crypto.Hash-import Haskoin.Keys.Common-import Haskoin.Script-import 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)--instance Serialize XPrvKey where- put k = do- putWord8 $ xPrvDepth k- putWord32be $ xPrvParent k- putWord32be $ xPrvIndex k- put $ xPrvChain k- putPadPrvKey $ xPrvKey k- get =- XPrvKey <$> getWord8- <*> getWord32be- <*> getWord32be- <*> S.get- <*> getPadPrvKey--xPrvToJSON :: Network -> XPrvKey -> Value-xPrvToJSON net = A.String . xPrvExport net--xPrvToEncoding :: Network -> XPrvKey -> Encoding-xPrvToEncoding net = text . xPrvExport 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---- | 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)--instance Serialize XPubKey where- put k = do- putWord8 $ xPubDepth k- putWord32be $ xPubParent k- putWord32be $ xPubIndex k- put $ xPubChain k- put $ wrapPubKey True (xPubKey k)- get =- XPubKey <$> getWord8- <*> getWord32be- <*> getWord32be- <*> S.get- <*> (pubKeyPoint <$> S.get)---- | 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--xPubToEncoding :: Network -> XPubKey -> Encoding-xPubToEncoding net = text . xPubExport net---- | 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"- S.get---- | Serialize an extended private key.-putXPrvKey :: Network -> Putter XPrvKey-putXPrvKey net k = do- putWord32be $ getExtSecretPrefix net- put 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"- S.get---- | Serialize an extended public key.-putXPubKey :: Network -> Putter XPubKey-putXPubKey net k = do- putWord32be $ getExtPubKeyPrefix net- put 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- 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 Serialize DerivPath where- get = listToPath <$> S.get- put = put . pathToList--instance Serialize HardPath where- get = maybe mzero return . toHard . listToPath =<< S.get- put = put . pathToList--instance Serialize SoftPath where- get = maybe mzero return . toSoft . 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- toEncoding = text . 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- 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 { 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"- S.get---- | Serialize HDW-specific private key.-putPadPrvKey :: Putter SecKey-putPadPrvKey p = putWord8 0x00 >> S.put p--bsPadPrvKey :: SecKey -> ByteString-bsPadPrvKey = runPut . putPadPrvKey
− src/Haskoin/Keys/Mnemonic.hs
@@ -1,516 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}-{-|-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.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 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"- ]
src/Haskoin/Network.hs view
@@ -1,21 +1,27 @@-{-|-Module : Haskoin.Network-Copyright : No rights reserved-License : MIT-Maintainer : jprupp@protonmail.ch-Stability : experimental-Portability : POSIX+{-# LANGUAGE DuplicateRecordFields #-} -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+-- 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 Common- , module Message- , module Bloom- ) where+ ( 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.Message as Message+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
src/Haskoin/Network/Bloom.hs view
@@ -1,53 +1,63 @@ {-# LANGUAGE DeriveAnyClass #-}-{-# LANGUAGE DeriveGeneric #-}-{-|-Module : Haskoin.Network.Bloom-Copyright : No rights reserved-License : MIT-Maintainer : jprupp@protonmail.ch-Stability : experimental-Portability : POSIX+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE DerivingStrategies #-}+{-# LANGUAGE DuplicateRecordFields #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE ImportQualifiedPost #-}+{-# LANGUAGE OverloadedRecordDot #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE NoFieldSelectors #-} -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+-- 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+ ( -- * 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 Haskoin.Network.Common-import Haskoin.Script.Standard-import Haskoin.Transaction.Common+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@@ -68,25 +78,35 @@ -- | 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)+ = -- | 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 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"+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" - put f = putWord8 $ case f of- BloomUpdateNone -> 0- BloomUpdateAll -> 1- BloomUpdateP2PubKeyOnly -> 2+ 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@@ -94,170 +114,231 @@ -- 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)+ { -- | 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 Serialize BloomFilter where+instance Serial BloomFilter where+ deserialize =+ BloomFilter+ <$> (S.fromList <$> (readDat =<< deserialize))+ <*> getWord32le+ <*> getWord32le+ <*> deserialize+ where+ readDat (VarInt len) = replicateM (fromIntegral len) getWord8 - get = BloomFilter <$> (S.fromList <$> (readDat =<< get))- <*> getWord32le <*> getWord32le- <*> get- 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 - put (BloomFilter dat hashFuncs tweak flags) = do- putVarInt $ S.length dat- forM_ (F.toList dat) putWord8- putWord32le hashFuncs- putWord32le tweak- put 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 { filterLoadBloomFilter :: BloomFilter }- deriving (Eq, Show, Read, Generic, NFData)+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- get = FilterLoad <$> get- put (FilterLoad f) = put f+ 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 { getFilterData :: ByteString }- deriving (Eq, Show, Read, Generic, NFData)+newtype FilterAdd = FilterAdd {get :: ByteString}+ deriving (Eq, Show, Read, Generic)+ deriving newtype (NFData) -instance Serialize FilterAdd where- get = do- (VarInt len) <- get- dat <- getByteString $ fromIntegral len- return $ FilterAdd dat+instance Serial FilterAdd where+ deserialize = do+ (VarInt len) <- deserialize+ dat <- getByteString $ fromIntegral len+ return $ FilterAdd dat - put (FilterAdd bs) = do- putVarInt $ BS.length bs- putByteString bs+ 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 :: Int -- ^ number of elements- -> Double -- ^ false positive rate- -> Word32 -- ^ random nonce (tweak) for the hash function- -> BloomFlags -- ^ bloom filter flags- -> BloomFilter -- ^ bloom filter+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+ 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+ a = -1 / ln2Squared * fromIntegral numElem * log fpRate -- Maximum size in bits- b = fromIntegral $ maxBloomSize * 8- numHashF = truncate $ min c (fromIntegral maxHashFuncs)+ b = fromIntegral $ maxBloomSize * 8+ numHashF = truncate $ min c (fromIntegral maxHashFuncs) -- Suggested number of hash functions- c = fromIntegral bloomSize * 8 / fromIntegral numElem * ln2+ 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)+bloomHash b hashNum bs =+ murmur3 seed bs `mod` (fromIntegral (S.length (b.array)) * 8) where- seed = hashNum * 0xfba4c795 + bloomTweak bfilter+ seed = hashNum * 0xfba4c795 + b.tweak -- | 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 }+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 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+ 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 :: 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+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 = 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+ 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 :: 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- | not (null matchOuts) = Just $ foldl' addRelevant bfilter matchOuts- | otherwise = Nothing- -- TxHash if we end up inserting an outpoint+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 (decodeOutputBS . scriptOutput) $ txOut 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+ -- Check if any txOuts were contained in the bloom filter matchFilter =- filter (\(_, op) -> bloomContains bfilter $ encodeScriptOut op)+ filter (\(_, op) -> any (bloomContains b) (encodeScriptOut op)) matchOuts = matchFilter idxOutputScripts addRelevant :: BloomFilter -> (Word32, ScriptOutput) -> BloomFilter addRelevant bf (id', scriptOut) =- case (bloomFlags bfilter, scriptType)- -- We filtered out BloomUpdateNone so we insert any PayPk or PayMulSig- of- (_, True) -> bloomInsert bf outpoint- (BloomUpdateAll, _) -> bloomInsert bf outpoint- _ -> error "Error Updating Bloom Filter with relevant outpoint"+ 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 = encode $ OutPoint {outPointHash = h, outPointIndex = id'}+ 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 (PayMulSig outputMuSig _) = encode outputMuSig- encodeScriptOut (PayWitnessScriptHash scriptHash) = encode scriptHash- encodeScriptOut (DataCarrier getOutputDat) = encode getOutputDat- encodeScriptOut outputHash = (encode . getOutputHash) outputHash+ -- 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 bfilter = all (== 0x00) $ F.toList $ bloomData bfilter+isBloomEmpty b = all (== 0x00) $ F.toList b.array -- | Returns True if the filter is full (all bytes set to 0xff) isBloomFull :: BloomFilter -> Bool-isBloomFull bfilter = all (== 0xff) $ F.toList $ bloomData bfilter+isBloomFull b = all (== 0xff) $ F.toList b.array -- | 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+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
src/Haskoin/Network/Common.hs view
@@ -1,585 +1,753 @@-{-# LANGUAGE DeriveAnyClass #-}-{-# LANGUAGE DeriveGeneric #-}-{-# LANGUAGE OverloadedStrings #-}-{-|-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.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 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 $+{-# 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'.
+ src/Haskoin/Network/Constants.hs view
@@ -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]
+ src/Haskoin/Network/Data.hs view
@@ -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)
src/Haskoin/Network/Message.hs view
@@ -1,69 +1,84 @@ {-# LANGUAGE DeriveAnyClass #-}-{-# LANGUAGE DeriveGeneric #-}-{-|-Module : Haskoin.Network.Message-Copyright : No rights reserved-License : MIT-Maintainer : jprupp@protonmail.ch-Stability : experimental-Portability : POSIX+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE DuplicateRecordFields #-}+{-# LANGUAGE ImportQualifiedPost #-}+{-# LANGUAGE OverloadedRecordDot #-}+{-# LANGUAGE NoFieldSelectors #-} -Peer-to-peer network message serialization.--}+-- |+-- 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+ ( -- * Network Message+ Message (..),+ MessageHeader (..),+ msgType,+ putMessage,+ getMessage,+ )+where -import Control.DeepSeq-import Control.Monad (unless)-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 Haskoin.Block.Common-import Haskoin.Block.Merkle-import Haskoin.Constants-import Haskoin.Crypto.Hash-import Haskoin.Network.Bloom-import Haskoin.Network.Common-import Haskoin.Transaction.Common+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- headMagic :: !Word32- -- | message type- , headCmd :: !MessageCommand- -- | length of payload- , headPayloadSize :: !Word32- -- | checksum of payload- , headChecksum :: !CheckSum32- } deriving (Eq, Show, Generic, NFData)+ { -- | 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 Serialize MessageHeader where+instance Serial MessageHeader where+ deserialize =+ MessageHeader+ <$> getWord32be+ <*> deserialize+ <*> getWord32le+ <*> deserialize - get = MessageHeader <$> getWord32be- <*> get- <*> getWord32le- <*> get+ serialize (MessageHeader m c l chk) = do+ putWord32be m+ serialize c+ putWord32le l+ serialize chk - put (MessageHeader m c l chk) = do- putWord32be m- put c- putWord32le l- put 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@@ -71,131 +86,139 @@ -- 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)+ = 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 (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+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 :: (MonadGet m) => Network -> m 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"+ (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 :: Network -> Putter Message+putMessage :: (MonadPut m) => Network -> Message -> m () 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+ 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
src/Haskoin/Script.hs view
@@ -1,21 +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+-- 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+ ( 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+import Haskoin.Script.Common as Common+import Haskoin.Script.SigHash as SigHash+import Haskoin.Script.Standard as Standard
src/Haskoin/Script/Common.hs view
@@ -1,578 +1,598 @@-{-# LANGUAGE DeriveAnyClass #-}-{-# LANGUAGE DeriveGeneric #-}-{-# LANGUAGE OverloadedStrings #-}-{-|-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.ByteString (ByteString)-import qualified Data.ByteString as B-import Data.Hashable-import Data.Serialize as S-import Data.Word (Word8)-import GHC.Generics (Generic)---- | 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_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 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_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-- 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_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----- | 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+{-# 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
src/Haskoin/Script/SigHash.hs view
@@ -1,90 +1,105 @@-{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE DeriveAnyClass #-}+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE DerivingStrategies #-}+{-# LANGUAGE DuplicateRecordFields #-}+{-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-}-{-# LANGUAGE OverloadedStrings #-}-{-|-Module : Haskoin.Script.SigHash-Copyright : No rights reserved-License : MIT-Maintainer : jprupp@protonmail.ch-Stability : experimental-Portability : POSIX+{-# LANGUAGE ImportQualifiedPost #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE OverloadedRecordDot #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE NoFieldSelectors #-} -Transaction signatures and related functions.--}+-- |+-- 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- , hasAnyoneCanPayFlag- , hasForkIdFlag- , setAnyoneCanPayFlag- , setForkIdFlag- , isSigHashAll- , isSigHashNone- , isSigHashSingle- , isSigHashUnknown- , sigHashAddForkId- , sigHashGetForkId- , sigHashAddNetworkId- , txSigHash- , txSigHashForkId- , TxSignature(..)- , encodeTxSig- , decodeTxSig- ) where+ ( -- * 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 qualified Data.Aeson as J-import Data.Bits-import qualified Data.ByteString as BS-import Data.Hashable-import Data.Maybe-import Data.Scientific-import Data.Serialize-import Data.Word-import GHC.Generics (Generic)-import Haskoin.Constants-import Haskoin.Crypto.Hash-import Haskoin.Network.Common-import Haskoin.Crypto-import Haskoin.Script.Common-import Haskoin.Transaction.Common-import Haskoin.Util+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- = 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)+ = -- | 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"+ 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@@ -97,30 +112,19 @@ -- 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- )+newtype SigHash+ = SigHash Word32+ deriving (Eq, Ord, Enum, Show, Read, Generic)+ deriving newtype (Bits, Integral, Num, Real, Hashable, NFData) -instance J.FromJSON SigHash where- parseJSON =- J.withScientific "sighash" $- maybe mzero (return . SigHash) . toBoundedInteger+instance FromJSON SigHash where+ parseJSON =+ withScientific "sighash" $+ maybe mzero (return . SigHash) . toBoundedInteger -instance J.ToJSON SigHash where- toJSON = J.Number . fromIntegral- toEncoding (SigHash n) = J.toEncoding n+instance ToJSON SigHash where+ toJSON = Number . fromIntegral+ toEncoding (SigHash n) = toEncoding n -- | SIGHASH_NONE as a byte. sigHashNone :: SigHash@@ -147,16 +151,16 @@ setForkIdFlag = (.|. sigHashForkId) -- | Set SIGHASH_ANYONECANPAY flag.-setAnyoneCanPayFlag :: SigHash -> SigHash-setAnyoneCanPayFlag = (.|. sigHashAnyoneCanPay)+setAnyoneCanPay :: SigHash -> SigHash+setAnyoneCanPay = (.|. 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)+anyoneCanPay :: SigHash -> Bool+anyoneCanPay = (/= 0) . (.&. sigHashAnyoneCanPay) -- | Returns 'True' if the 'SigHash' has the value 'SIGHASH_ALL'. isSigHashAll :: SigHash -> Bool@@ -173,7 +177,7 @@ -- | Returns 'True' if the 'SigHash' has the value 'SIGHASH_UNKNOWN'. isSigHashUnknown :: SigHash -> Bool isSigHashUnknown =- (`notElem` [sigHashAll, sigHashNone, sigHashSingle]) . (.&. 0x1f)+ (`notElem` [sigHashAll, sigHashNone, sigHashSingle]) . (.&. 0x1f) -- | Add a fork id to a 'SigHash'. sigHashAddForkId :: SigHash -> Word32 -> SigHash@@ -182,106 +186,113 @@ -- | Add fork id of a particular network to a 'SigHash'. sigHashAddNetworkId :: Network -> SigHash -> SigHash sigHashAddNetworkId net =- (`sigHashAddForkId` fromMaybe 0 (getSigHashForkId 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- -> 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 ::+ 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 (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+ | 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) $ scriptOps out+ 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- | hasAnyoneCanPayFlag sh =- [ (txins !! i) { scriptInput = encode out } ]- | isSigHashAll sh || isSigHashUnknown sh = single- | otherwise = zipWith noSeq single [0 ..]+ | anyoneCanPay sh = [serialOut (txins !! i)]+ | 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 }+ 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]+ | 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+ 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- -> 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 ::+ 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 . 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+ 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 $ hasAnyoneCanPayFlag sh =- doubleSHA256 $ runPut $ mapM_ (put . prevOutput) $ txIn tx- | otherwise = zeros+ | not (anyoneCanPay sh) =+ doubleSHA256 . runPutS $ mapM_ (serialize . (.outpoint)) tx.inputs+ | otherwise = zeros hashSequence- | not (hasAnyoneCanPayFlag sh) &&- not (isSigHashSingle sh) && not (isSigHashNone sh) =- doubleSHA256 $ runPut $ mapM_ (putWord32le . txInSequence) $ txIn tx- | otherwise = zeros+ | not (anyoneCanPay sh || isSigHashSingle sh || isSigHashNone sh) =+ doubleSHA256 . runPutS $ mapM_ (putWord32le . (.sequence)) tx.inputs+ | 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+ | 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 = encode s- putVarInt $ BS.length encodedScript- putByteString encodedScript+ let encodedScript = runPutS $ serialize s+ putVarInt $ B.length encodedScript+ putByteString encodedScript zeros :: Hash256 zeros = "0000000000000000000000000000000000000000000000000000000000000000" @@ -289,31 +300,51 @@ -- 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)+ = TxSignature+ { sig :: !Sig,+ hash :: !SigHash+ }+ | TxSignatureEmpty+ deriving (Eq, Show, Read, Generic, NFData) -instance NFData TxSignature+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) --- | Serialize a 'TxSignature'.-encodeTxSig :: TxSignature -> BS.ByteString-encodeTxSig TxSignatureEmpty = error "Can not encode an empty signature"-encodeTxSig (TxSignature sig (SigHash n)) =- runPut $ putSig 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 --- | Deserialize a 'TxSignature'.-decodeTxSig :: Network -> BS.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"+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
src/Haskoin/Script/Standard.hs view
@@ -1,183 +1,205 @@-{-# LANGUAGE DeriveAnyClass #-}-{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE DeriveAnyClass #-}+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE DuplicateRecordFields #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE ImportQualifiedPost #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE OverloadedRecordDot #-} {-# LANGUAGE OverloadedStrings #-}-{-|-Module : Haskoin.Script.Standard-Copyright : No rights reserved-License : MIT-Maintainer : jprupp@protonmail.ch-Stability : experimental-Portability : POSIX+{-# LANGUAGE NoFieldSelectors #-} -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+-- 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- , isPayWitnessPKHash- , isPayWitnessScriptHash- , isDataCarrier- , encodeOutput- , encodeOutputBS- , decodeOutput- , decodeOutputBS- , toP2SH- , toP2WSH- , sortMulSig+ ( -- * Standard Script Outputs+ ScriptOutput (..),+ RedeemScript,+ isPayPK,+ isPayPKHash,+ isPayMulSig,+ isPayScriptHash,+ isPayWitness,+ isPayWitnessPKHash,+ isPayWitnessScriptHash,+ isDataCarrier,+ encodeOutput,+ decodeOutput,+ toP2SH,+ toP2WSH,+ sortMulSig,+ -- * Standard Script Inputs- , ScriptInput(..)- , SimpleInput(..)- , encodeInput- , encodeInputBS- , decodeInput- , decodeInputBS- , isSpendPK- , isSpendPKHash- , isSpendMulSig- , isScriptHashInput- ) where+ ScriptInput (..),+ SimpleInput (..),+ encodeInput,+ decodeInput,+ isSpendPK,+ isSpendPKHash,+ isSpendMulSig,+ isScriptHashInput,+ )+where -import Control.Applicative ((<|>))-import Control.DeepSeq-import Control.Monad (guard, liftM2, (<=<))-import qualified Data.Aeson as A-import qualified Data.Aeson.Encoding as A-import Data.ByteString (ByteString)-import qualified Data.ByteString as BS-import Data.Function (on)-import Data.Hashable-import Data.List (sortBy)-import Data.Maybe (fromJust, isJust)-import Data.Serialize as S-import Data.Word (Word8)-import GHC.Generics (Generic)-import Haskoin.Constants-import Haskoin.Crypto-import Haskoin.Keys.Common-import Haskoin.Script.Common-import Haskoin.Script.SigHash-import Haskoin.Util+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 { 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 }- -- | another pay to witness address- | PayWitness { getWitnessVersion :: !Word8- , getWitnessData :: !ByteString- }- -- | provably unspendable data carrier- | DataCarrier { getOutputData :: !ByteString }- deriving (Eq, Show, Read, Generic, Hashable, NFData)+ = -- | 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 A.FromJSON ScriptOutput where- parseJSON =- A.withText "scriptoutput" $ \t ->- either fail return $- maybeToEither "scriptoutput not hex" (decodeHex t) >>=- decodeOutputBS+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 -instance A.ToJSON ScriptOutput where- toJSON = A.String . encodeHex . encodeOutputBS- toEncoding = A.text . encodeHex . encodeOutputBS+ 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+isPayPK _ = False -- | Is script a pay-to-pub-key-hash output? isPayPKHash :: ScriptOutput -> Bool isPayPKHash (PayPKHash _) = True-isPayPKHash _ = False+isPayPKHash _ = False -- | Is script a pay-to-multi-sig output? isPayMulSig :: ScriptOutput -> Bool isPayMulSig (PayMulSig _ _) = True-isPayMulSig _ = False+isPayMulSig _ = False -- | Is script a pay-to-script-hash output? isPayScriptHash :: ScriptOutput -> Bool isPayScriptHash (PayScriptHash _) = True-isPayScriptHash _ = False+isPayScriptHash _ = False -- | Is script a pay-to-witness-pub-key-hash output? isPayWitnessPKHash :: ScriptOutput -> Bool isPayWitnessPKHash (PayWitnessPKHash _) = True-isPayWitnessPKHash _ = False+isPayWitnessPKHash _ = False -- | Is script a pay-to-witness-script-hash output? isPayWitnessScriptHash :: ScriptOutput -> Bool isPayWitnessScriptHash (PayWitnessScriptHash _) = True-isPayWitnessScriptHash _ = False+isPayWitnessScriptHash _ = False -- | Is script paying to a different type of witness address? isPayWitness :: ScriptOutput -> Bool isPayWitness (PayWitness _ _) = True-isPayWitness _ = False+isPayWitness _ = False -- | Is script a data carrier output? isDataCarrier :: ScriptOutput -> Bool isDataCarrier (DataCarrier _) = True-isDataCarrier _ = False+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]- | BS.length bs == 20 -> PayWitnessPKHash <$> S.decode bs- | BS.length bs == 32 -> PayWitnessScriptHash <$> S.decode bs- -- Other Witness- [ver, OP_PUSHDATA bs _]- | isJust (opWitnessVersion ver)- && BS.length bs >= 2- && BS.length bs <= 40 ->- Right $ PayWitness (fromJust (opWitnessVersion ver)) bs- -- Provably unspendable data carrier output- [OP_RETURN, OP_PUSHDATA bs _] -> Right $ DataCarrier bs- -- Pay to MultiSig Keys- _ -> matchPayMulSig s+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 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@@ -185,18 +207,19 @@ 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_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@@ -206,202 +229,207 @@ opWitnessVersion OP_16 = Just 16 opWitnessVersion _ = Nothing ---- | 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 ->+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 . 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 ]- (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]+ 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] --- | Similar to 'encodeOutput' but encodes to a ByteString-encodeOutputBS :: ScriptOutput -> ByteString-encodeOutputBS = S.encode . encodeOutput+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 . S.encode+toP2SH = PayScriptHash . addressHash . runPutS . serialize -- | Encode script as a pay-to-witness-script-hash script toP2WSH :: Script -> ScriptOutput-toP2WSH = PayWitnessScriptHash . sha256 . S.encode+toP2WSH = PayWitnessScriptHash . sha256 . runPutS . serialize -- | 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"+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 (:) (S.decode bs) (go xs)- go [] = return []- go _ = Left "matchPayMulSig: invalid multisig opcode"+ 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 :: 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"+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- { getInputSig :: !TxSignature- -- ^ transaction signature- }- | SpendPKHash- { getInputSig :: !TxSignature- -- ^ embedded signature- , getInputKey :: !PubKeyI- -- ^ public key- }- | SpendMulSig- { getInputMulSigKeys :: ![TxSignature]- -- ^ list of signatures- }- deriving (Eq, Show, Generic, NFData)+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+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+isSpendPKHash _ = False -- | Returns true if the input script is spending a multisig output. isSpendMulSig :: ScriptInput -> Bool isSpendMulSig (RegularInput (SpendMulSig _)) = True-isSpendMulSig _ = False+isSpendMulSig _ = False -- | Returns true if the input script is spending a pay-to-script-hash output. isScriptHashInput :: ScriptInput -> Bool isScriptHashInput (ScriptHashInput _ _) = True-isScriptHashInput _ = False+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)+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 -> Script -> Either String SimpleInput-decodeSimpleInput net (Script ops) =- maybeToEither errMsg $ matchPK ops <|> matchPKHash ops <|> matchMulSig ops+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+ matchPK _ = Nothing matchPKHash [op, OP_PUSHDATA pub _] =- SpendPKHash <$> f op <*> eitherToMaybe (decode pub)+ SpendPKHash <$> f op <*> eitherToMaybe (unmarshal ctx pub) matchPKHash _ = Nothing- matchMulSig (x:xs) = do- guard $ x == OP_0- SpendMulSig <$> mapM f xs+ matchMulSig (x : xs) = do+ guard $ x == OP_0+ SpendMulSig <$> mapM f xs matchMulSig _ = Nothing- f OP_0 = return TxSignatureEmpty+ f OP_0 = return TxSignatureEmpty f (OP_PUSHDATA "" OPCODE) = f OP_0- f (OP_PUSHDATA bs _) = eitherToMaybe $ decodeTxSig net bs- f _ = Nothing+ 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 -> Script -> Either String ScriptInput-decodeInput net s@(Script ops) =- maybeToEither errMsg $ matchSimpleInput <|> matchPayScriptHash+decodeInput :: Network -> Ctx -> Script -> Either String ScriptInput+decodeInput net ctx s@(Script ops) =+ maybeToEither errMsg $ matchSimpleInput <|> matchPayScriptHash where matchSimpleInput =- RegularInput <$> eitherToMaybe (decodeSimpleInput net s)+ RegularInput <$> eitherToMaybe (decodeSimpleInput net ctx 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+ 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" --- | Like 'decodeInput' but decodes directly from a serialized script--- 'ByteString'.-decodeInputBS :: Network -> ByteString -> Either String ScriptInput-decodeInputBS net = decodeInput net <=< decode+instance Marshal (Network, Ctx) ScriptInput where+ marshalGet (net, ctx) =+ deserialize >>= either fail return . decodeInput net ctx --- | 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]+ marshalPut (net, ctx) =+ serialize . encodeInput net ctx --- | Similar to 'encodeInput' but encodes directly to a serialized script--- 'ByteString'.-encodeInputBS :: ScriptInput -> ByteString-encodeInputBS = encode . encodeInput+-- | 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 :: SimpleInput -> Script-encodeSimpleInput s =- Script $+encodeSimpleInput :: Network -> Ctx -> SimpleInput -> Script+encodeSimpleInput net ctx s =+ Script $ case s of- SpendPK ts -> [f ts]- SpendPKHash ts p -> [f ts, opPushData $ encode p]- SpendMulSig xs -> OP_0 : map f xs+ 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 ts-+ f ts = opPushData $ encodeTxSig net ctx ts
src/Haskoin/Transaction.hs view
@@ -1,21 +1,27 @@-{-|-Module : Haskoin.Transaction-Copyright : No rights reserved-License : MIT-Maintainer : jprupp@protonmail.ch-Stability : experimental-Portability : POSIX+{-# LANGUAGE DuplicateRecordFields #-} -Transactions and related code.--}+-- |+-- 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 Partial- ) where+ ( 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.Partial as Partial-import Haskoin.Transaction.Segwit as Segwit+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
src/Haskoin/Transaction/Builder.hs view
@@ -1,115 +1,132 @@-{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE DuplicateRecordFields #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE ImportQualifiedPost #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE OverloadedRecordDot #-} {-# LANGUAGE OverloadedStrings #-}-{-|-Module : Haskoin.Transaction.Builder-Copyright : No rights reserved-License : MIT-Maintainer : jprupp@protonmail.ch-Stability : experimental-Portability : POSIX+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE NoFieldSelectors #-} -Code to simplify transaction creation, signing, fee calculation and coin-selection.--}+-- |+-- 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+ ( -- * Transaction Builder+ buildAddrTx,+ buildTx,+ buildInput,+ SigInput (..),+ signTx,+ signNestedWitnessTx,+ makeSignature,+ signInput,+ signNestedInput,+ verifyStdTx,+ mergeTxs,+ sigKeys,+ mergeTxInput,+ findSigInput,+ verifyStdInput, -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 Haskoin.Address-import Haskoin.Constants-import Haskoin.Crypto.Hash (Hash256, addressHash)-import Haskoin.Crypto.Signature-import Haskoin.Keys.Common-import Haskoin.Network.Common-import Haskoin.Script-import Haskoin.Transaction.Builder.Sign (SigInput (..), buildInput,- makeSignature, sigKeys)-import qualified Haskoin.Transaction.Builder.Sign as S-import Haskoin.Transaction.Common-import Haskoin.Transaction.Segwit (decodeWitnessInput, isSegwit,- viewWitnessProgram)-import Haskoin.Util+ -- * 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+ 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 ::+ (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 $+ 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 ::+ (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"+ | target > 0 =+ maybeToEither err+ <$> greedyAddSink target (guessTxFee fee nOut) continue+ | otherwise = return $ Left "chooseCoins: Target must be > 0" where err = "chooseCoins: No solution found" @@ -117,37 +134,49 @@ -- 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 ::+ (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+ 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 ::+ (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"+ | 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" @@ -157,89 +186,144 @@ -- 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 ::+ (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+ 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+ 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+ 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- -- If no solution was found, return Nothing- then Nothing- -- If we have a solution, return it- else Just (ps, pTot - goal (length ps))+ 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)+ 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)+ 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 ::+ -- | 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+ 8 + inpLen + inp + outLen + out where- inpLen = B.length $ encode $ VarInt $ fromIntegral $ length msi + pki- outLen = B.length $ encode $ VarInt $ fromIntegral $ pkout + msout+ 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)+ -- (20: hash160) + (5: opcodes) ++ -- (1: script len) + (8: Word64) out =- pkout * 34 +- -- (20: hash160) + (3: opcodes) +- -- (1: script len) + (8: Word64)+ 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 $ encode $ VarInt $ fromIntegral scp) + scp- -- OP_M + n*PubKey + OP_N + OP_CHECKMULTISIG+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 $ encode $ opPushData $ B.replicate (n * 34 + 3) 0+ fromIntegral $+ B.length $+ runPutS $+ serialize $+ opPushData $+ B.replicate (n * 34 + 3) 0 -- Redeem + m*sig + OP_0 scp = rdm + m * 73 + 1 @@ -247,24 +331,24 @@ -- | 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 ops rcps =- buildTx ops <$> mapM f rcps+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)+ 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 :: [OutPoint] -> [(ScriptOutput, Word64)] -> Tx-buildTx ops rcpts =- Tx 1 (toIn <$> ops) (toOut <$> rcpts) [] 0+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 $ encodeOutputBS o+ 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@@ -279,40 +363,56 @@ -- -- > 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)+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- -> 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+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 -> Tx -> Int -> SigInput -> SecKeyI -> Either String Tx-signInput net tx i si = S.signInput net tx i (si, False)+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 -> Tx -> Int -> SigInput -> SecKeyI -> Either String Tx-signNestedInput net tx i si = S.signInput net tx i (si, True)+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 = S.findInputIndex sigInputOP+findSigInput = Sign.findInputIndex (.outpoint) {- Merge multisig transactions -} @@ -320,161 +420,205 @@ -- 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+ 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 (txIn $ head txs) f) [0 ..]+ zipOp = zip (matchTemplate os (head txs).inputs f) [0 ..] outs =- map (first $ (\(o, v, _) -> (o, v)) . fromJust) $+ map (first $ (\(o, v, _) -> (o, v)) . fromJust) $ filter (isJust . fst) zipOp- f (_, _, o) txin = o == prevOutput txin+ f (_, _, o) txin = o == txin.outpoint emptyTxs = map (\tx -> foldl clearInput tx outs) txs- ins is i = updateIndex i is (\ti -> ti {scriptInput = B.empty})+ ins is i = updateIndex i is (\TxIn {..} -> TxIn {script = B.empty, ..}) clearInput tx (_, i) =- Tx (txVersion tx) (ins (txIn tx) i) (txOut tx) [] (txLockTime tx)+ 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- -> [Tx]- -> Tx- -> ((ScriptOutput, Word64), Int)- -> Either String Tx-mergeTxInput net txs tx ((so, val), i) = do- -- Ignore transactions with empty inputs- let ins = map (scriptInput . (!! i) . txIn) 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 <- 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)+ 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 (getRegularInput si) rdm- _ -> Left "Invalid output script type"+ 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 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"+ 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- (txSigHash net tx (encodeOutput out) val i sh)- x- (pubKeyPoint 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 -> Tx -> [(ScriptOutput, Word64, OutPoint)] -> Bool-verifyStdTx net tx xs =- not (null (txIn tx)) && all go (zip (matchTemplate xs (txIn tx) f) [0 ..])+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 == prevOutput txin- go (Just (so, val, _), i) = verifyStdInput net tx i so val- go _ = False+ 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 -> Tx -> Int -> ScriptOutput -> Word64 -> Bool-verifyStdInput net tx i so0 val- | isSegwit so0 =+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 =- fromRight False $- (verifyLegacyInput so0 <$> decodeInputBS net inp) <|>- (nestedScriptOutput >>= \so -> verifyNestedInput so0 so <$> wp so)+ | otherwise =+ or $+ rights+ [ verifyLegacyInput so0 <$> unmarshal (net, ctx) inp,+ nestedScriptOutput >>= \so -> verifyNestedInput so0 so <$> wp so+ ] where- inp = scriptInput $ txIn tx !! i- theTxSigHash so = S.makeSigHash net tx i so val+ inp = (tx.inputs !! i).script+ theTxSigHash so = Sign.makeSigHash net ctx tx i so val ws :: WitnessStack- ws | length (txWitness tx) > i = txWitness tx !! i- | otherwise = []+ ws+ | length tx.witness > i = tx.witness !! i+ | otherwise = [] wp :: ScriptOutput -> Either String (Maybe ScriptOutput, SimpleInput)- wp so = decodeWitnessInput net =<< viewWitnessProgram net so ws+ wp so = decodeWitnessInput net ctx =<< viewWitnessProgram net ctx so ws nestedScriptOutput :: Either String ScriptOutput- nestedScriptOutput = scriptOps <$> decode inp >>= \case- [OP_PUSHDATA bs _] -> decodeOutputBS bs- _ -> Left "nestedScriptOutput: not a nested output"+ 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 (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+ (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+ ScriptOutput -> (Maybe ScriptOutput, SimpleInput) -> Bool 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+ ( 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+ ScriptOutput -> ScriptOutput -> (Maybe RedeemScript, SimpleInput) -> Bool verifyNestedInput so so' x = case so of- PayScriptHash h -> payToScriptAddress so' == p2shAddr h && verifySegwitInput so' x- _ -> False+ PayScriptHash h -> payToScriptAddress ctx so' == p2shAddr h && verifySegwitInput so' x+ _ -> False -- | 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+ 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' :: (SigHash -> Hash256) -> [PubKey] -> [TxSignature] -> Int-countMulSig' _ [] _ = 0-countMulSig' _ _ [] = 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+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
src/Haskoin/Transaction/Builder/Sign.hs view
@@ -1,271 +1,322 @@-{-# LANGUAGE DeriveAnyClass #-}-{-# LANGUAGE DeriveGeneric #-}-{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE DeriveAnyClass #-}+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE DuplicateRecordFields #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE OverloadedRecordDot #-} {-# LANGUAGE OverloadedStrings #-}-{-|-Module : Haskoin.Transaction.Builder.Sign-Copyright : No rights reserved-License : MIT-Maintainer : jprupp@protonmail.ch-Stability : experimental-Portability : POSIX+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE NoFieldSelectors #-} -Types and logic for signing transactions.--}+-- |+-- 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+ ( SigInput (..),+ makeSignature,+ makeSigHash,+ signTx,+ findInputIndex,+ signInput,+ buildInput,+ sigKeys,+ )+where -import Control.DeepSeq (NFData)-import Control.Monad (foldM, when)-import Data.Aeson (FromJSON, ToJSON (..), object,- pairs, parseJSON, withObject, (.:),- (.:?), (.=))-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 Haskoin.Address (getAddrHash160, pubKeyAddr)-import Haskoin.Constants (Network)-import Haskoin.Crypto (Hash256, SecKey)-import Haskoin.Crypto.Signature (signHash, verifyHashSig)-import Haskoin.Keys.Common (PubKeyI (..), SecKeyI (..),- derivePubKeyI, wrapSecKey)-import Haskoin.Script-import Haskoin.Transaction.Common-import Haskoin.Transaction.Segwit-import Haskoin.Util (matchTemplate, updateIndex)+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- { sigInputScript :: !ScriptOutput -- ^ output script to spend+ { -- | output script to spend -- ^ output script value- , sigInputValue :: !Word64 -- ^ output script value+ script :: !ScriptOutput,+ -- | output script value -- ^ outpoint to spend- , sigInputOP :: !OutPoint -- ^ outpoint to spend+ value :: !Word64,+ -- | outpoint to spend -- ^ signature type- , sigInputSH :: !SigHash -- ^ signature type+ outpoint :: !OutPoint,+ -- | signature type -- ^ redeem script- , sigInputRedeem :: !(Maybe RedeemScript) -- ^ redeem script- }- deriving (Eq, Show, Read, Generic, Hashable, NFData)+ sighash :: !SigHash,+ -- | redeem script+ redeem :: !(Maybe RedeemScript)+ }+ deriving (Show, Read, Eq, Generic, 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 ]- toEncoding (SigInput so val op sh rdm) =- pairs $- "pkscript" .= so- <> "value" .= val- <> "outpoint" .= op- <> "sighash" .= sh- <> maybe mempty ("redeem" .=) rdm+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+ ] -instance FromJSON SigInput where- parseJSON =- withObject "SigInput" $ \o ->- SigInput <$> o .: "pkscript"- <*> o .: "value"- <*> o .: "outpoint"- <*> o .: "sighash"- <*> o .:? "redeem"+ 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- -> 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+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 = txIn otx+ ti = otx.inputs go tx (sigi@(SigInput so _ _ _ rdmM, _), i) = do- keys <- sigKeys so rdmM allKeys- foldM (\t k -> signInput net t i sigi k) tx keys+ 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- -> Tx- -> Int- -> (SigInput, Bool) -- ^ boolean flag: nest input- -> SecKeyI- -> Either String Tx-signInput net tx i (sigIn@(SigInput so val _ _ 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- }+ 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 x = x {scriptInput = encodeInputBS si}- g so' x = x {scriptInput = S.encode . opPushData $ encodeOutputBS so'}- txis = txIn tx+ 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 txis (g so')- | isSegwit so' = txIn tx- | otherwise = updateIndex i txis (f 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 :: Tx -> Int -> ScriptOutput -> ScriptInput -> Either String WitnessData-updatedWitnessData tx i so si- | isSegwit so = updateWitness . toWitnessStack =<< calcWitnessProgram so si- | otherwise = return $ txWitness tx+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 $ 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+ | 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 ::- (a -> OutPoint) -- ^ extract an outpoint- -> [a] -- ^ input list- -> [TxIn] -- ^ reference list of inputs- -> [(a, Int)]+ -- | 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..]+ mapMaybe g $ zip (matchTemplate as ti f) [0 ..] where- f s txin = getOutPoint s == prevOutput txin- g (Just s, i) = Just (s,i)+ 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 ::- 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"+ 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 = derivePubKeyI prv- ]+ [ (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) . getAddrHash160 . pubKeyAddr . snd+ 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- -> 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"+ 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 tx so i- return $ RegularInput $ SpendMulSig mSigs- _ -> Left "buildInput: Invalid output/redeem script combination"+ 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 (getRegularInput inp) rdm+ inp <- buildRegularInput rdm+ return $ ScriptHashInput inp.get rdm f (TxSignature x sh) p =- verifyHashSig (makeSigHash net tx i so val sh rdmM) x (pubKeyPoint 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 -> Tx -> ScriptOutput -> Int -> [TxSignature]-parseExistingSigs net tx so i = insSigs <> witSigs+parseExistingSigs :: Network -> Ctx -> Tx -> ScriptOutput -> Int -> [TxSignature]+parseExistingSigs net ctx 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+ 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 $ txWitness tx = []- | otherwise = rights $ decodeTxSig net <$> (txWitness tx !! i)+ | 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 -> Tx -> Int -> SigInput -> SecKeyI -> TxSignature-makeSignature net tx i (SigInput so val _ sh rdmM) key =- TxSignature (signHash (secKeyData key) m) sh+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 tx i so val sh rdmM+ 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- -> Tx- -> Int- -> ScriptOutput- -> Word64- -> SigHash- -> Maybe RedeemScript- -> Hash256-makeSigHash net tx i so val sh rdmM = h net tx (encodeOutput so') val i sh+ 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-+ PayWitnessPKHash h' -> PayPKHash h'+ _ -> fromMaybe so rdmM+ h+ | isSegwit so = txSigHashForkId+ | otherwise = txSigHash
src/Haskoin/Transaction/Common.hs view
@@ -1,344 +1,429 @@-{-# LANGUAGE DeriveAnyClass #-}-{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE DeriveAnyClass #-}+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE DerivingStrategies #-}+{-# LANGUAGE DuplicateRecordFields #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE ImportQualifiedPost #-}+{-# LANGUAGE OverloadedRecordDot #-} {-# LANGUAGE OverloadedStrings #-}-{-|-Module : Haskoin.Transaction.Common-Copyright : No rights reserved-License : MIT-Maintainer : jprupp@protonmail.ch-Stability : experimental-Portability : POSIX+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE NoFieldSelectors #-} -Code related to transactions parsing and serialization.--}+-- |+-- 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+ ( -- * 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, (<=<))-import Data.Aeson as A-import Data.Aeson.Encoding (unsafeToEncoding)-import Data.ByteString (ByteString)-import qualified Data.ByteString as B-import Data.ByteString.Builder (char7)-import Data.Hashable (Hashable)-import Data.Maybe (fromMaybe)-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 Haskoin.Crypto.Hash-import Haskoin.Network.Common-import Haskoin.Util-import Text.Read as R+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 { getTxHash :: Hash256 }- deriving (Eq, Ord, Generic, Hashable, Serialize, NFData)+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+ showsPrec _ = shows . txHashToHex instance Read TxHash where- readPrec = do- R.String str <- R.lexP- maybe R.pfail return $ hexToTxHash $ cs str+ 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+ 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+ parseJSON =+ withText "txid" $+ maybe mzero return . hexToTxHash instance ToJSON TxHash where- toJSON = A.String . txHashToHex- toEncoding h =- unsafeToEncoding $ char7 '"' <> hexBuilder (B.reverse (S.encode h)) <> char7 '"'+ toJSON = A.String . txHashToHex+ toEncoding = hexEncoding . BL.reverse . runPutL . serialize -- | Transaction hash excluding signatures. nosigTxHash :: Tx -> TxHash-nosigTxHash tx =- TxHash $ doubleSHA256 $ S.encode tx { txIn = map clearInput $ txIn tx }+nosigTxHash Tx {..} =+ TxHash . doubleSHA256 . runPutS $ serialize tx where- clearInput ti = ti { scriptInput = B.empty }+ 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 (S.encode h))+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 (S.decode bs)- return $ TxHash h+ 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- 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)+ { -- | 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 (S.encode tx {txWitness = []}))+txHash tx = TxHash . doubleSHA256 . runPutS $ serialize tx {witness = []} instance IsString Tx where- fromString =- fromMaybe e . (eitherToMaybe . S.decode <=< decodeHex) . cs- where- e = error "Could not read transaction from hex string"+ 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- get = parseWitnessTx <|> parseLegacyTx- put tx- | null (txWitness tx) = putLegacyTx tx- | otherwise = putWitnessTx tx+ put = serialize+ get = deserialize -putInOut :: Tx -> Put+putInOut :: (MonadPut m) => Tx -> m () putInOut tx = do- putVarInt $ length (txIn tx)- forM_ (txIn tx) put- putVarInt $ length (txOut tx)- forM_ (txOut tx) put+ putVarInt $ length tx.inputs+ mapM_ serialize tx.inputs+ putVarInt $ length tx.outputs+ mapM_ serialize tx.outputs -- | Non-SegWit transaction serializer.-putLegacyTx :: Tx -> Put+putLegacyTx :: (MonadPut m) => Tx -> m () putLegacyTx tx = do- putWord32le (txVersion tx)- putInOut tx- putWord32le (txLockTime tx)+ putWord32le tx.version+ putInOut tx+ putWord32le tx.locktime -- | Witness transaciton serializer.-putWitnessTx :: Tx -> Put+putWitnessTx :: (MonadPut m) => Tx -> m () putWitnessTx tx = do- putWord32le (txVersion tx)- putWord8 0x00- putWord8 0x01- putInOut tx- putWitnessData (txWitness tx)- putWord32le (txLockTime tx)+ 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 :: Get Tx+parseLegacyTx :: (MonadGet m) => m 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}+ version <- getWord32le+ inputs <- rl =<< deserialize+ outputs <- rl =<< deserialize+ when (length inputs == 0x00 && length outputs == 0x01) $+ fail "Witness transaction"+ locktime <- getWord32le+ return Tx {witness = [], ..} where- replicateList (VarInt c) = replicateM (fromIntegral c) S.get+ rl (VarInt c) = replicateM (fromIntegral c) deserialize -- | Witness transaction deserializer.-parseWitnessTx :: Get Tx+parseWitnessTx :: (MonadGet m) => m 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}+ 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) S.get+ replicateList (VarInt c) = replicateM (fromIntegral c) deserialize -- | Witness data deserializer. Requires count of inputs.-parseWitnessData :: Int -> Get WitnessData+parseWitnessData :: (MonadGet m) => Int -> m WitnessData parseWitnessData n = replicateM n parseWitnessStack where parseWitnessStack = do- VarInt i <- S.get- replicateM (fromIntegral i) parseWitnessStackItem+ VarInt i <- deserialize+ replicateM (fromIntegral i) parseWitnessStackItem parseWitnessStackItem = do- VarInt i <- S.get- getByteString $ fromIntegral i+ VarInt i <- deserialize+ getByteString $ fromIntegral i -- | Witness data serializer.-putWitnessData :: WitnessData -> Put+putWitnessData :: (MonadPut m) => WitnessData -> m () putWitnessData = mapM_ putWitnessStack where putWitnessStack ws = do- putVarInt $ length ws- mapM_ putWitnessStackItem ws+ putVarInt $ length ws+ mapM_ putWitnessStackItem ws putWitnessStackItem bs = do- putVarInt $ B.length bs- putByteString bs+ 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+ 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- ( "version" .= v- <> "inputs" .= i- <> "outputs" .= o- <> "witnessdata" .= fmap (fmap encodeHex) w- <> "locktime" .= l- )+ 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- 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)+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 Serialize TxIn where- get =- TxIn <$> S.get <*> (readBS =<< S.get) <*> getWord32le- where- readBS (VarInt len) = getByteString $ fromIntegral len+instance Serial TxIn where+ deserialize =+ TxIn <$> deserialize <*> (readBS =<< deserialize) <*> getWord32le+ where+ readBS (VarInt len) = getByteString $ fromIntegral len - put (TxIn o s q) = do- put o- putVarInt $ B.length s- putByteString s- putWord32le q+ 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"+ 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- ( "prevoutput" .= o- <> "inputscript" .= encodeHex s- <> "sequence" .= q- )+ 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- outValue :: !Word64- -- | pubkey script- , scriptOutput :: !ByteString- } deriving (Eq, Show, Read, Ord, Generic, Hashable, NFData)+data TxOut = TxOut+ { -- | value of output is satoshi+ value :: !Word64,+ -- | pubkey script+ script :: !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)+instance Serial TxOut where+ deserialize = do+ val <- getWord64le+ VarInt len <- deserialize+ TxOut val <$> getByteString (fromIntegral len) - put (TxOut o s) = do- putWord64le o- putVarInt $ B.length s- putByteString s+ 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 ->- TxOut <$> o .: "value"- <*> (maybe mzero return . decodeHex =<< o .: "outputscript")+ 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 ("value" .= o <> "outputscript" .= encodeHex s)+ 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- outPointHash :: !TxHash- -- | position of output in previous transaction- , outPointIndex :: !Word32- } deriving (Show, Read, Eq, Ord, Generic, Hashable, NFData)+ { -- | 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- get = do- (h,i) <- liftM2 (,) S.get getWord32le- return $ OutPoint h i- put (OutPoint h i) = put h >> putWord32le i+ put = serialize+ get = deserialize instance FromJSON OutPoint where- parseJSON =- withObject "OutPoint" $ \o ->- OutPoint <$> o .: "txid" <*> o .: "index"+ 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 ("txid" .= h <> "index" .= i)+ 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- { outPointHash =- "0000000000000000000000000000000000000000000000000000000000000000"- , outPointIndex = maxBound+ OutPoint+ { hash = "0000000000000000000000000000000000000000000000000000000000000000",+ index = maxBound }
src/Haskoin/Transaction/Genesis.hs view
@@ -1,37 +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+-- |+-- 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 Data.String (fromString)-import Haskoin.Script.Standard-import Haskoin.Transaction.Common-import Haskoin.Util+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 :: Tx-genesisTx =- Tx 1 [txin] [txout] [] locktime+genesisTx :: Ctx -> Tx+genesisTx ctx =+ Tx 1 [txin] [txout] [] locktime where txin = TxIn outpoint inputBS maxBound- txout = TxOut 5000000000 (encodeOutputBS output)+ txout = TxOut 5000000000 (marshal ctx output) locktime = 0 outpoint = OutPoint z maxBound- Just inputBS = decodeHex $ fromString $- "04ffff001d0104455468652054696d65732030332f4a616e2f323030392043686" ++- "16e63656c6c6f72206f6e206272696e6b206f66207365636f6e64206261696c6f" ++- "757420666f722062616e6b73"- output = PayPK $ fromString $- "04678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb" ++- "649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5f"+ Just inputBS =+ decodeHex+ "04ffff001d0104455468652054696d65732030332f4a616e2f323030392043686\+ \16e63656c6c6f72206f6e206272696e6b206f66207365636f6e64206261696c6f\+ \757420666f722062616e6b73"+ Just pubKeyBS =+ decodeHex+ "04678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb\+ \649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5f"+ Right pubKey =+ unmarshal ctx pubKeyBS+ output = PayPK pubKey z = "0000000000000000000000000000000000000000000000000000000000000000"
src/Haskoin/Transaction/Partial.hs view
@@ -1,501 +1,949 @@-{-# LANGUAGE DeriveGeneric #-}-{-# LANGUAGE GeneralizedNewtypeDeriving #-}-{-# LANGUAGE NamedFieldPuns #-}-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE ScopedTypeVariables #-}-{-|-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- 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 Haskoin.Address (Address (..), pubKeyAddr)-import Haskoin.Keys (Fingerprint, KeyIndex, PubKeyI)-import Haskoin.Network (VarInt (..), VarString (..),- putVarInt)-import Haskoin.Script (Script (..), ScriptOp (..),- ScriptOutput (..), SigHash,- decodeOutput, decodeOutputBS,- encodeOutputBS, isPayScriptHash,- opPushData, toP2SH, toP2WSH)-import Haskoin.Transaction.Common (Tx (..), TxOut, WitnessStack,- outPointIndex, prevOutput,- scriptInput, scriptOutput)-import 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 ())+{-# 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
src/Haskoin/Transaction/Segwit.hs view
@@ -1,145 +1,175 @@-{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE DuplicateRecordFields #-}+{-# LANGUAGE LambdaCase #-} {-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE TupleSections #-}-{-|-Module : Haskoin.Transaction.Segwit-Copyright : No rights reserved-License : MIT-Maintainer : jprupp@protonmail.ch-Stability : experimental-Portability : POSIX+{-# LANGUAGE TupleSections #-}+{-# LANGUAGE NoFieldSelectors #-} -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+-- 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+ ( -- * Segwit+ WitnessProgram (..),+ WitnessProgramPKH (..),+ WitnessProgramSH (..),+ isSegwit,+ viewWitnessProgram,+ decodeWitnessInput,+ calcWitnessProgram,+ simpleInputStack,+ toWitnessStack,+ )+where -import Data.ByteString (ByteString)-import qualified Data.Serialize as S-import Haskoin.Constants-import Haskoin.Keys.Common-import Haskoin.Script-import Haskoin.Transaction.Common+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+ 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)+ = P2WPKH WitnessProgramPKH+ | P2WSH WitnessProgramSH+ | EmptyWitnessProgram+ deriving (Eq) -- | 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+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- { witnessSignature :: !TxSignature- , witnessPubKey :: !PubKeyI- }- deriving (Eq, Show)+ { signature :: !TxSignature,+ key :: !PublicKey+ }+ deriving (Eq) -- | High-level representation of a P2WSH witness -- -- @since 0.11.0.0 data WitnessProgramSH = WitnessProgramSH- { witnessScriptHashStack :: ![ByteString]- , witnessScriptHashScript :: !Script- }- deriving (Eq, Show)+ { stack :: ![ByteString],+ script :: !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"+ 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- -> 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 _, [sigBS]) ->- SpendPK <$> decodeTxSig net sigBS- (PayPKHash _, [sigBS, keyBS]) ->- SpendPKHash <$> decodeTxSig net sigBS <*> S.decode keyBS- (PayMulSig _ _, "" : sigsBS) ->- SpendMulSig <$> traverse (decodeTxSig net) sigsBS- _ -> Left "decodeWitnessInput: Non-standard script output"- EmptyWitnessProgram -> Left "decodeWitnessInput: Empty witness program"+ 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 :: 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"+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 = return . P2WPKH . WitnessProgramPKH sig- p2wsh i o = return . P2WSH $ WitnessProgramSH (simpleInputStack i) (encodeOutput o)+ 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 :: SimpleInput -> [ByteString]-simpleInputStack = \case- SpendPK sig -> [f sig]- SpendPKHash sig k -> [f sig, S.encode k]- SpendMulSig sigs -> "" : fmap f sigs+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 sig+ f sig = encodeTxSig net ctx sig
+ src/Haskoin/Transaction/Taproot.hs view
@@ -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
src/Haskoin/Util.hs view
@@ -1,207 +1,17 @@-{-# LANGUAGE CPP #-}-{-# 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+-- Copyright : No rights reserved+-- License : MIT+-- Maintainer : jprupp@protonmail.ch+-- Stability : experimental+-- Portability : POSIX+--+-- Marshalling and helper functions. module Haskoin.Util- (- -- * ByteString Helpers- bsToInteger- , integerToBS- , hexBuilder- , 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.ByteString.Builder-import qualified Data.ByteString.Lazy as BL-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)--hexBuilder :: ByteString -> Builder-hexBuilder = byteStringHex---- | Encode as string of human-readable hex characters.-encodeHex :: ByteString -> Text-encodeHex = E.decodeUtf8 . BL.toStrict . toLazyByteString . byteStringHex---- | Decode string of human-readable hex characters.-decodeHex :: Text -> Maybe ByteString-# if MIN_VERSION_base16_bytestring(1,0,0)-decodeHex = eitherToMaybe . B16.decode . E.encodeUtf8-# else-decodeHex text =- let (x, b) = B16.decode (E.encodeUtf8 text)- in guard (b == BS.empty) >> return x-# endif---- | 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 }- }+ ( module Marshal,+ module Helpers,+ )+where --- | 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)+import Haskoin.Util.Helpers as Helpers+import Haskoin.Util.Marshal as Marshal
src/Haskoin/Util/Arbitrary.hs view
@@ -1,23 +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.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+ ( 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+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
src/Haskoin/Util/Arbitrary/Address.hs view
@@ -1,20 +1,21 @@ {-# LANGUAGE TupleSections #-}-{-|-Module : Haskoin.Test.Address-Copyright : No rights reserved-License : MIT-Maintainer : jprupp@protonmail.ch-Stability : experimental-Portability : POSIX--}++-- |+-- 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.Constants-import Haskoin.Util.Arbitrary.Crypto-import Haskoin.Util.Arbitrary.Util-import Test.QuickCheck+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@@ -23,20 +24,21 @@ -- | Arbitrary address including pay-to-witness arbitraryAddressAll :: Gen Address arbitraryAddressAll =- oneof [ arbitraryPubKeyAddress- , arbitraryScriptAddress- , arbitraryWitnessPubKeyAddress- , arbitraryWitnessScriptAddress- , arbitraryWitnessAddress- ]+ 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, bchRegTest]- then (net, ) <$> arbitraryAddress- else (net, ) <$> arbitraryAddressAll+ 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@@ -56,8 +58,8 @@ arbitraryWitnessAddress :: Gen Address arbitraryWitnessAddress = do- ver <- choose (0, 16)- len <- choose (2, 40)- ws <- vectorOf len arbitrary- let bs = B.pack ws- return $ WitnessAddress ver bs+ ver <- choose (1, 16)+ len <- choose (2, 40)+ ws <- vectorOf len arbitrary+ let bs = B.pack ws+ return $ WitnessAddress ver bs
src/Haskoin/Util/Arbitrary/Block.hs view
@@ -1,39 +1,40 @@-{-|-Module : Haskoin.Test.Block-Copyright : No rights reserved-License : MIT-Maintainer : jprupp@protonmail.ch-Stability : experimental-Portability : POSIX--}+-- |+-- 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.Constants-import Haskoin.Util.Arbitrary.Util-import Haskoin.Util.Arbitrary.Crypto-import Haskoin.Util.Arbitrary.Network-import Haskoin.Util.Arbitrary.Transaction-import Test.QuickCheck+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 -> Gen Block-arbitraryBlock net = do- h <- arbitraryBlockHeader- c <- choose (0,10)- txs <- vectorOf c (arbitraryTx net)- return $ Block h txs+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+ BlockHeader+ <$> arbitrary+ <*> arbitraryBlockHash+ <*> arbitraryHash256+ <*> arbitrary+ <*> arbitrary+ <*> arbitrary -- | Arbitrary block hash. arbitraryBlockHash :: Gen BlockHash@@ -42,48 +43,45 @@ -- | Arbitrary 'GetBlocks' object with at least one block hash. arbitraryGetBlocks :: Gen GetBlocks arbitraryGetBlocks =- GetBlocks <$> arbitrary- <*> listOf1 arbitraryBlockHash- <*> arbitraryBlockHash+ GetBlocks+ <$> arbitrary+ <*> listOf1 arbitraryBlockHash+ <*> arbitraryBlockHash -- | Arbitrary 'GetHeaders' object with at least one block header. arbitraryGetHeaders :: Gen GetHeaders arbitraryGetHeaders =- GetHeaders <$> arbitrary- <*> listOf1 arbitraryBlockHash- <*> arbitraryBlockHash+ GetHeaders+ <$> arbitrary+ <*> listOf1 arbitraryBlockHash+ <*> arbitraryBlockHash -- | Arbitrary 'Headers' object with at least one block header. arbitraryHeaders :: Gen Headers arbitraryHeaders =- Headers <$> listOf1 ((,) <$> arbitraryBlockHeader <*> arbitraryVarInt)+ 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+ 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 =- oneof- [ BlockNode- <$> arbitraryBlockHeader- <*> choose (0, maxBound)- <*> arbitrarySizedNatural- <*> arbitraryBlockHash- ]+ BlockNode+ <$> arbitraryBlockHeader+ <*> choose (0, maxBound)+ <*> arbitrarySizedNatural+ <*> arbitraryBlockHash -- | Arbitrary 'HeaderMemory' arbitraryHeaderMemory :: Gen HeaderMemory arbitraryHeaderMemory = do- ls <- listOf $ (,) <$> arbitrary <*> arbitraryBSS- HeaderMemory- <$> (return $ HashMap.fromList ls)- <*> arbitraryBlockNode-+ ls <- listOf $ (,) <$> arbitrary <*> arbitraryBSS+ HeaderMemory (HashMap.fromList ls) <$> arbitraryBlockNode
src/Haskoin/Util/Arbitrary/Crypto.hs view
@@ -1,33 +1,32 @@-{-|-Module : Haskoin.Test.Crypto-Copyright : No rights reserved-License : MIT-Maintainer : jprupp@protonmail.ch-Stability : experimental-Portability : POSIX--}+-- |+-- 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+import Haskoin.Crypto.Hash+import Haskoin.Util.Arbitrary.Util+import Test.QuickCheck -- | Arbitrary 160-bit hash. arbitraryHash160 :: Gen Hash160 arbitraryHash160 =- ripemd160 <$> arbitraryBSn 20+ ripemd160 <$> arbitraryBSn 20 -- | Arbitrary 256-bit hash. arbitraryHash256 :: Gen Hash256 arbitraryHash256 =- sha256 <$> arbitraryBSn 32+ sha256 <$> arbitraryBSn 32 -- | Arbitrary 512-bit hash. arbitraryHash512 :: Gen Hash512 arbitraryHash512 =- sha512 <$> arbitraryBSn 64+ sha512 <$> arbitraryBSn 64 -- | Arbitrary 32-bit checksum. arbitraryCheckSum32 :: Gen CheckSum32 arbitraryCheckSum32 =- checkSum32 <$> arbitraryBSn 4+ checkSum32 <$> arbitraryBSn 4
src/Haskoin/Util/Arbitrary/Keys.hs view
@@ -1,44 +1,59 @@-{-|-Module : Haskoin.Test.Keys-Copyright : No rights reserved-License : MIT-Maintainer : jprupp@protonmail.ch-Stability : experimental-Portability : POSIX--}+-- |+-- 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 Data.Bits (clearBit)-import Data.List (foldl')-import Data.Word (Word32)-import Haskoin.Crypto-import Haskoin.Keys.Common-import Haskoin.Keys.Extended-import Haskoin.Util.Arbitrary.Crypto-import Test.QuickCheck+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.-arbitrarySecKeyI :: Gen SecKeyI-arbitrarySecKeyI = wrapSecKey <$> arbitrary <*> arbitrary+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 :: Gen (SecKeyI, PubKeyI)-arbitraryKeyPair = do- k <- arbitrarySecKeyI- return (k, derivePubKeyI k)+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- <*> arbitrary- <*> arbitrary- <*> arbitraryHash256- <*> arbitrary+ 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.-arbitraryXPubKey :: Gen (XPrvKey, XPubKey)-arbitraryXPubKey = (\k -> (k, deriveXPubKey k)) <$> arbitraryXPrvKey+arbitraryXKeyPair :: Ctx -> Gen (XPrvKey, XPubKey)+arbitraryXKeyPair ctx = (\k -> (k, deriveXPubKey ctx k)) <$> arbitraryXPrvKey {- Custom derivations -} @@ -49,9 +64,10 @@ -- | Arbitrary BIP-32 path index. Can be hardened or not. arbitraryBip32PathIndex :: Gen Bip32PathIndex arbitraryBip32PathIndex =- oneof [ Bip32SoftIndex <$> genIndex- , Bip32HardIndex <$> genIndex- ]+ oneof+ [ Bip32SoftIndex <$> genIndex,+ Bip32HardIndex <$> genIndex+ ] -- | Arbitrary BIP-32 derivation path composed of only hardened derivations. arbitraryHardPath :: Gen HardPath@@ -69,17 +85,18 @@ -- 'ParsedEmpty' elements. arbitraryParsedPath :: Gen ParsedPath arbitraryParsedPath =- oneof [ ParsedPrv <$> arbitraryDerivPath- , ParsedPub <$> arbitraryDerivPath- , ParsedEmpty <$> arbitraryDerivPath- ]+ 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)+arbitrarySignature :: Ctx -> Gen (Hash256, SecKey, Sig)+arbitrarySignature ctx = do+ m <- arbitraryHash256+ key <- arbitrary+ let sig = signHash ctx key m+ return (m, key, sig)
src/Haskoin/Util/Arbitrary/Message.hs view
@@ -1,52 +1,53 @@-{-|-Module : Haskoin.Test.Message-Copyright : No rights reserved-License : MIT-Maintainer : jprupp@protonmail.ch-Stability : experimental-Portability : POSIX--}+-- |+-- 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.Constants-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+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+ 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- ]+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+ ]
src/Haskoin/Util/Arbitrary/Network.hs view
@@ -1,21 +1,20 @@-{-|-Module : Haskoin.Test.Network-Copyright : No rights reserved-License : MIT-Maintainer : jprupp@protonmail.ch-Stability : experimental-Portability : POSIX--}+-- |+-- 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+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@@ -28,19 +27,20 @@ -- | 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+ 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)@@ -61,15 +61,16 @@ -- | Arbitrary 'Version'. arbitraryVersion :: Gen Version arbitraryVersion =- Version <$> arbitrary- <*> arbitrary- <*> arbitrary- <*> arbitraryNetworkAddress- <*> arbitraryNetworkAddress- <*> arbitrary- <*> arbitraryVarString- <*> arbitrary- <*> arbitrary+ Version+ <$> arbitrary+ <*> arbitrary+ <*> arbitrary+ <*> arbitraryNetworkAddress+ <*> arbitraryNetworkAddress+ <*> arbitrary+ <*> arbitraryVarString+ <*> arbitrary+ <*> arbitrary -- | Arbitrary non-empty 'Addr'. arbitraryAddr1 :: Gen Addr@@ -83,27 +84,29 @@ -- | 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+ 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- ]+ elements+ [ RejectMalformed,+ RejectInvalid,+ RejectInvalid,+ RejectDuplicate,+ RejectNonStandard,+ RejectDust,+ RejectInsufficientFee,+ RejectCheckpoint+ ] -- | Arbitrary non-empty 'GetData'. arbitraryGetData :: Gen GetData@@ -124,27 +127,27 @@ -- | Arbitrary bloom filter flags. arbitraryBloomFlags :: Gen BloomFlags arbitraryBloomFlags =- elements- [ BloomUpdateNone- , BloomUpdateAll- , BloomUpdateP2PubKeyOnly- ]+ 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)+ 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+ (_, _, bf) <- arbitraryBloomFilter+ return $ FilterLoad bf -- | Arbitrary 'FilterAdd'. arbitraryFilterAdd :: Gen FilterAdd@@ -153,26 +156,26 @@ -- | 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)))- ]+ 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)))+ ]
src/Haskoin/Util/Arbitrary/Script.hs view
@@ -1,29 +1,33 @@+{-# LANGUAGE DuplicateRecordFields #-}+{-# LANGUAGE ImportQualifiedPost #-} {-# LANGUAGE LambdaCase #-}-{-|-Module : Haskoin.Test.Script-Copyright : No rights reserved-License : MIT-Maintainer : jprupp@protonmail.ch-Stability : experimental-Portability : POSIX--}+{-# 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 qualified Data.ByteString as B-import Data.Maybe-import Data.Word-import Haskoin.Address-import Haskoin.Constants-import Haskoin.Keys.Common-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+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@@ -32,144 +36,156 @@ -- | 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- ]+ 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- ]+ 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@@ -182,83 +198,86 @@ -- | 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+ 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- ]+ 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 -> 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)+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 (getSigHashForkId net) && hasForkIdFlag sh+ filterBad sh =+ not $+ isSigHashUnknown sh+ || isNothing net.sigHashForkId && hasForkIdFlag sh -- | Arbitrary transaction signature that could also be empty.-arbitraryTxSignatureEmpty :: Network -> Gen TxSignature-arbitraryTxSignatureEmpty net =- frequency [ (1, return TxSignatureEmpty)- , (10, lst3 <$> arbitraryTxSignature net)- ]+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)+ 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- , arbitraryWitOutput- ]+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 :: Gen ScriptOutput-arbitrarySimpleOutput =- oneof- [ arbitraryPKOutput- , arbitraryPKHashOutput- , arbitraryMSOutput- ]+arbitrarySimpleOutput :: Ctx -> Gen ScriptOutput+arbitrarySimpleOutput ctx =+ oneof+ [ arbitraryPKOutput ctx,+ arbitraryPKHashOutput,+ arbitraryMSOutput ctx+ ] -- | Arbitrary 'ScriptOutput' of type 'PayPK'-arbitraryPKOutput :: Gen ScriptOutput-arbitraryPKOutput = PayPK . snd <$> arbitraryKeyPair+arbitraryPKOutput :: Ctx -> Gen ScriptOutput+arbitraryPKOutput ctx = PayPK . snd <$> arbitraryKeyPair ctx -- | Arbitrary 'ScriptOutput' of type 'PayPKHash' arbitraryPKHashOutput :: Gen ScriptOutput@@ -274,128 +293,128 @@ arbitraryWitOutput :: Gen ScriptOutput arbitraryWitOutput = do- ver <- choose (0, 16)- len <- choose (2, 40)- ws <- vectorOf len arbitrary- let bs = B.pack ws- return $ PayWitness ver bs+ 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 :: Gen ScriptOutput-arbitraryMSOutput = do- (m, n) <- arbitraryMSParam- keys <- map snd <$> vectorOf n arbitraryKeyPair- return $ PayMulSig keys m+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 :: Gen ScriptOutput-arbitraryMSOutputC = do- (m, n) <- arbitraryMSParam- keys <-- map snd <$>- vectorOf n (arbitraryKeyPair `suchThat` (pubKeyCompressed . snd))- return $ PayMulSig keys m+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 . getAddrHash160 <$> arbitraryScriptAddress+arbitrarySHOutput = PayScriptHash . (.hash160) <$> 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- ]+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 -> Gen ScriptInput-arbitrarySimpleInput net =- oneof- [ arbitraryPKInput net- , arbitraryPKHashInput net- , arbitraryMSInput net- ]+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 -> Gen ScriptInput-arbitraryPKInput net = RegularInput . SpendPK <$> arbitraryTxSignatureEmpty net+arbitraryPKInput :: Network -> Ctx -> Gen ScriptInput+arbitraryPKInput net ctx = RegularInput . SpendPK <$> arbitraryTxSignatureEmpty net ctx -- | Arbitrary 'ScriptInput' of type 'SpendPK'.-arbitraryPKHashInput :: Network -> Gen ScriptInput-arbitraryPKHashInput net = do- sig <- arbitraryTxSignatureEmpty net- key <- snd <$> arbitraryKeyPair- return $ RegularInput $ SpendPKHash sig key+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 -> Gen ScriptInput-arbitraryPKHashInputFull net = do- sig <- lst3 <$> arbitraryTxSignature net- key <- snd <$> arbitraryKeyPair- return $ RegularInput $ SpendPKHash sig key+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 -> Gen ScriptInput-arbitraryPKHashInputFullC net = do- sig <- lst3 <$> arbitraryTxSignature net- key <- fmap snd $ arbitraryKeyPair `suchThat` (pubKeyCompressed . snd)- return $ RegularInput $ SpendPKHash sig key+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 -> Gen ScriptInput-arbitraryMSInput net = do- m <- fst <$> arbitraryMSParam- sigs <- vectorOf m (arbitraryTxSignatureEmpty net)- return $ RegularInput $ SpendMulSig sigs+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 -> Gen ScriptInput-arbitrarySHInput net = do- i <- arbitrarySimpleInput net- ScriptHashInput (getRegularInput i) <$> arbitrarySimpleOutput+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 -> Gen ScriptInput-arbitraryMulSigSHInput net =- arbitraryMSOutput >>= \case- rdm@(PayMulSig _ m) -> do- sigs <- vectorOf m (arbitraryTxSignatureEmpty net)- return $ ScriptHashInput (SpendMulSig sigs) rdm- _ -> undefined+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 -> Gen ScriptInput-arbitraryMulSigSHInputC net =- arbitraryMSOutputC >>= \case- rdm@(PayMulSig _ m) -> do- sigs <- vectorOf m (arbitraryTxSignatureEmpty net)- return $ ScriptHashInput (SpendMulSig sigs) rdm- _ -> undefined+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 -> Gen ScriptInput-arbitraryMulSigSHInputFull net =- arbitraryMSOutput >>= \case- rdm@(PayMulSig _ m) -> do- sigs <- map lst3 <$> vectorOf m (arbitraryTxSignature net)- return $ ScriptHashInput (SpendMulSig sigs) rdm- _ -> undefined+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 -> Gen ScriptInput-arbitraryMulSigSHInputFullC net =- arbitraryMSOutputC >>= \case- rdm@(PayMulSig _ m) -> do- sigs <- map lst3 <$> vectorOf m (arbitraryTxSignature net)- return $ ScriptHashInput (SpendMulSig sigs) rdm- _ -> undefined+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
src/Haskoin/Util/Arbitrary/Transaction.hs view
@@ -1,35 +1,41 @@-{-|-Module : Haskoin.Test.Transaction-Copyright : No rights reserved-License : MIT-Maintainer : jprupp@protonmail.ch-Stability : experimental-Portability : POSIX--}+{-# 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 qualified Data.ByteString as BS-import Data.Either (fromRight)-import Data.List (nub, nubBy, permutations)-import Data.Word (Word64)-import Haskoin.Address-import Haskoin.Constants-import Haskoin.Keys.Common-import Haskoin.Script-import Haskoin.Util.Arbitrary.Crypto-import Haskoin.Util.Arbitrary.Keys-import Haskoin.Util.Arbitrary.Script-import Haskoin.Util.Arbitrary.Util-import Haskoin.Transaction-import Test.QuickCheck+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)+newtype TestCoin = TestCoin {getTestCoin :: Word64}+ deriving (Eq, Show) instance Coin TestCoin where- coinValue = getTestCoin+ coinValue = getTestCoin -- | Arbitrary transaction hash (for non-existent transaction). arbitraryTxHash :: Gen TxHash@@ -37,234 +43,241 @@ -- | Arbitrary amount of Satoshi as 'Word64' (Between 1 and 21e14) arbitrarySatoshi :: Network -> Gen TestCoin-arbitrarySatoshi net = TestCoin <$> choose (1, getMaxSatoshi net)+arbitrarySatoshi net = TestCoin <$> choose (1, net.maxSatoshi) -- | Arbitrary 'OutPoint'. arbitraryOutPoint :: Gen OutPoint arbitraryOutPoint = OutPoint <$> arbitraryTxHash <*> arbitrary -- | Arbitrary 'TxOut'.-arbitraryTxOut :: Network -> Gen TxOut-arbitraryTxOut net =- TxOut <$> (getTestCoin <$> arbitrarySatoshi net)- <*> (encodeOutputBS <$> arbitraryScriptOutput net)+arbitraryTxOut :: Network -> Ctx -> Gen TxOut+arbitraryTxOut net ctx =+ TxOut+ <$> (getTestCoin <$> arbitrarySatoshi net)+ <*> (marshal ctx <$> arbitraryScriptOutput net ctx) -- | Arbitrary 'TxIn'.-arbitraryTxIn :: Network -> Gen TxIn-arbitraryTxIn net =- TxIn <$> arbitraryOutPoint- <*> (encodeInputBS <$> arbitraryScriptInput net)- <*> arbitrary+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 -> Gen Tx-arbitraryTx net = oneof [arbitraryLegacyTx net, arbitraryWitnessTx net]+arbitraryTx :: Network -> Ctx -> Gen Tx+arbitraryTx net ctx =+ oneof [arbitraryLegacyTx net ctx, arbitraryWitnessTx net ctx] -- | Arbitrary regular transaction.-arbitraryLegacyTx :: Network -> Gen Tx-arbitraryLegacyTx net = arbitraryWLTx net False+arbitraryLegacyTx :: Network -> Ctx -> Gen Tx+arbitraryLegacyTx net ctx = arbitraryWLTx net ctx False -- | Arbitrary witness transaction (witness data is fake).-arbitraryWitnessTx :: Network -> Gen Tx-arbitraryWitnessTx net = arbitraryWLTx net True+arbitraryWitnessTx :: Network -> Ctx -> Gen Tx+arbitraryWitnessTx net ctx = arbitraryWLTx net ctx True -- | Arbitrary witness or legacy transaction.-arbitraryWLTx :: Network -> Bool -> Gen Tx-arbitraryWLTx net wit = do- ni <- choose (1, 5)- no <- choose (1, 5)- 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+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 -> Gen Tx-arbitraryAddrOnlyTx net = do- ni <- choose (1, 5)- no <- choose (1, 5)- inps <- vectorOf ni (arbitraryAddrOnlyTxIn net)- outs <- vectorOf no (arbitraryAddrOnlyTxOut net)- Tx <$> arbitrary <*> pure inps <*> pure outs <*> pure [] <*> arbitrary+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 -> Gen Tx-arbitraryAddrOnlyTxFull net = do- ni <- choose (1, 5)- no <- choose (1, 5)- inps <- vectorOf ni (arbitraryAddrOnlyTxInFull net)- outs <- vectorOf no (arbitraryAddrOnlyTxOut net)- Tx <$> arbitrary <*> pure inps <*> pure outs <*> pure [] <*> arbitrary+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 -> Gen TxIn-arbitraryAddrOnlyTxIn net = do- inp <- oneof [arbitraryPKHashInput net, arbitraryMulSigSHInput net]- TxIn <$> arbitraryOutPoint <*> pure (encodeInputBS inp) <*> arbitrary+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 -> Gen TxIn-arbitraryAddrOnlyTxInFull net = do- inp <-- oneof [arbitraryPKHashInputFullC net, arbitraryMulSigSHInputFullC net]- TxIn <$> arbitraryOutPoint <*> pure (encodeInputBS inp) <*> arbitrary+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 -> Gen TxOut-arbitraryAddrOnlyTxOut net = do- v <- getTestCoin <$> arbitrarySatoshi net- out <- oneof [arbitraryPKHashOutput, arbitrarySHOutput]- return $ TxOut v $ encodeOutputBS out+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 -> Gen (SigInput, [SecKeyI])-arbitrarySigInput net =- oneof- [ wrapKey <$> arbitraryPKSigInput net- , wrapKey <$> arbitraryPKHashSigInput net- , arbitraryMSSigInput net- , arbitrarySHSigInput net- , wrapKey <$> arbitraryWPKHSigInput net- , arbitraryWSHSigInput net- ]+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 -> Gen (SigInput, SecKeyI)-arbitraryPKSigInput net = arbitraryAnyInput net False+arbitraryPKSigInput :: Network -> Ctx -> Gen (SigInput, PrivateKey)+arbitraryPKSigInput net ctx = arbitraryAnyInput net ctx False -- | Arbitrary 'SigInput' with a 'ScriptOutput' of type 'PayPKHash'.-arbitraryPKHashSigInput :: Network -> Gen (SigInput, SecKeyI)-arbitraryPKHashSigInput net = arbitraryAnyInput net True+arbitraryPKHashSigInput :: Network -> Ctx -> Gen (SigInput, PrivateKey)+arbitraryPKHashSigInput net ctx = arbitraryAnyInput net ctx 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)+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)+ 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)+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 -> 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)+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 -> 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)+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 -> 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)+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 -> 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)+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 -> 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+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 -> 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)+ 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 tx [sigi] [prv]+ 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- 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)- ]+ (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, SecKeyI) -> (SigInput, [SecKeyI])+wrapKey :: (SigInput, PrivateKey) -> (SigInput, [PrivateKey]) wrapKey (s, k) = (s, [k])
src/Haskoin/Util/Arbitrary/Util.hs view
@@ -1,55 +1,61 @@ {-# LANGUAGE ExistentialQuantification #-}-{-# LANGUAGE OverloadedStrings #-}-{-|-Module : Haskoin.Test.Util-Copyright : No rights reserved-License : MIT-Maintainer : jprupp@protonmail.ch-Stability : experimental-Portability : POSIX--}+{-# 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-, SerialBox(..)-, JsonBox(..)-, NetBox(..)-, ReadBox(..)-, testIdentity-, testSerial-, testRead-, testJson-, testNetJson-, arbitraryNetData-, genNetData-)+ ( 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 qualified Data.ByteString.Short as BSS-import qualified Data.Map.Strict as Map-import Data.Proxy-import qualified Data.Serialize as S-import Data.Time.Clock (UTCTime (..))-import Data.Time.Clock.POSIX (posixSecondsToUTCTime)-import qualified Data.Typeable as T-import Data.Word (Word32)-import Haskoin.Constants-import Test.Hspec (Spec, describe, shouldBe,- shouldSatisfy)-import Test.Hspec.QuickCheck (prop)-import Test.QuickCheck+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@@ -78,14 +84,16 @@ -- | 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+ 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)- ]+arbitraryMaybe g =+ frequency+ [ (1, return Nothing),+ (5, Just <$> g)+ ] -- | Generate an Network arbitraryNetwork :: Gen Network@@ -93,115 +101,154 @@ -- Helpers for creating Serial and JSON Identity tests -data SerialBox =- forall a. (Show a, Eq a, T.Typeable a, S.Serialize a) =>- SerialBox (Gen a)+instance Show Ctx where+ show _ = "Ctx" -data ReadBox =- forall a. (Read a, Show a, Eq a, T.Typeable a) =>- ReadBox (Gen a)+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 JsonBox+ = forall a.+ (Show a, Eq a, T.Typeable a, A.ToJSON a, A.FromJSON a) =>+ JsonBox (Gen a) -data NetBox =- forall a. (Show a, Eq a, T.Typeable a) =>- NetBox- ( Network -> a -> A.Value- , Network -> a -> A.Encoding- , Network -> A.Value -> A.Parser a- , Gen (Network, a))+data MarshalJsonBox+ = forall s a.+ (Show a, Show s, Eq a, T.Typeable a, MarshalJSON s a) =>+ MarshalJsonBox (Gen (s, a)) -testIdentity :: [SerialBox] -> [ReadBox] -> [JsonBox] -> [NetBox] -> Spec-testIdentity serialVals readVals jsonVals netVals = do- describe "Data.Serialize Encoding" $- forM_ serialVals $ \(SerialBox g) -> testSerial g- describe "Read/Show Encoding" $- forM_ readVals $ \(ReadBox g) -> testRead g- describe "Data.Aeson Encoding" $- forM_ jsonVals $ \(JsonBox g) -> testJson g- describe "Data.Aeson Encoding with Network" $- forM_ netVals $ \(NetBox (j,e,p,g)) -> testNetJson j e p g+data SerialBox+ = forall a.+ (Show a, Eq a, T.Typeable a, Serial a) =>+ SerialBox (Gen a) --- | Generate Data.Serialize identity tests+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, S.Serialize a) => Gen a -> Spec+ (Eq a, Show a, T.Typeable a, Serial a) => Gen a -> Spec testSerial gen =- prop ("Data.Serialize encoding/decoding identity for " <> name) $- forAll gen $ \x -> (S.decode . S.encode) x `shouldBe` Right x+ 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 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+-- | 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 a -> Proxy a+ 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+ (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)+ 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)+ (A.decode . A.encodingToLazyByteString . A.toEncoding) (toMap x)+ == Just (toMap x) --- | Generate Data.Aeson identity tests for type that need the @Network@-testNetJson ::- (Eq a, Show a, T.Typeable a)- => (Network -> a -> A.Value)- -> (Network -> a -> A.Encoding)- -> (Network -> A.Value -> A.Parser a)- -> Gen (Network, a)- -> Spec-testNetJson j e p g = do- prop ("Data.Aeson toJSON/fromJSON identity (with network) for " <> name) $- forAll g $ \(net, x) -> dec net (encVal net x) `shouldBe` Just x- prop ("Data.Aeson toEncoding/fromJSON identity (with network) for " <> name) $- forAll g $ \(net, x) -> dec net (encEnc net x) `shouldBe` Just 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- encVal net = A.encode . toMap . j net- encEnc net = A.encodingToLazyByteString . toMapE . e net- dec net = A.parseMaybe (p net) . fromMap <=< A.decode- name = show $ T.typeRep $ proxy j- proxy :: (Network -> a -> A.Value) -> Proxy a+ name = show $ T.typeRep $ proxy gen+ proxy :: Gen (s, a) -> Proxy a proxy = const Proxy--arbitraryNetData :: Arbitrary a => Gen (Network, a)-arbitraryNetData = do- net <- arbitraryNetwork- x <- arbitrary- return (net, x)--genNetData :: Gen a -> Gen (Network, a)-genNetData gen = do- net <- arbitraryNetwork- x <- gen- return (net, x)+ 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")+toMapE = A.pairs . A.pair "object" fromMap :: Map.Map String a -> a fromMap = (Map.! "object")-
+ src/Haskoin/Util/Helpers.hs view
@@ -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
+ src/Haskoin/Util/Marshal.hs view
@@ -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
test/Haskoin/Address/Bech32Spec.hs view
@@ -1,154 +1,244 @@+{-# LANGUAGE ImportQualifiedPost #-} {-# LANGUAGE OverloadedStrings #-}-module 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 Haskoin.Address-import Haskoin.Address.Bech32-import Haskoin.Util-import Test.Hspec-import Test.HUnit+module Haskoin.Address.Bech32Spec (spec) where -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" []+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 :: 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+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)+ 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)+ 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)+ 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+ ByteString.pack $ witver' : fromIntegral (length witprog) : witprog+ where+ witver' = if witver == 0 then 0 else witver + 0x50 -validChecksums :: [Text]+validChecksums :: [(Bech32Encoding, Text)] validChecksums =- [ "A12UEL5L"- , "an83characterlonghumanreadablepartthatcontainsthenumber1andtheexcludedcharactersbio1tt5tgs"- , "abcdef1qpzry9x8gf2tvdw0s3jn54khce6mua7lmqqqxw"- , "11qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqc8247j"- , "split1checkupstagehandshakeupstreamerranterredcaperred2y9e3w"- ]+ [ ( 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"- ]+ [ " 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")- ]+ [ ( "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"- ]+ [ "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 T.pack hrpTestPermutations+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]+ a <- ['t', 'T']+ b <- ['e', 'E']+ c <- ['s', 'S']+ d <- ['t', 'T']+ return [a, b, c, d]
test/Haskoin/Address/CashAddrSpec.hs view
@@ -1,145 +1,149 @@+{-# LANGUAGE ImportQualifiedPost #-} {-# LANGUAGE OverloadedStrings #-}+ module 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 Haskoin.Address-import Haskoin.Constants-import Haskoin.Util-import Test.Hspec-import Test.HUnit+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+ 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 (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+ 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@@ -148,132 +152,164 @@ -- 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")- ]+ [ ( 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"+ )+ ]
test/Haskoin/AddressSpec.hs view
@@ -1,189 +1,197 @@+{-# LANGUAGE ImportQualifiedPost #-} {-# LANGUAGE OverloadedStrings #-}-module Haskoin.AddressSpec (spec) where -import Data.ByteString (ByteString)-import qualified Data.ByteString as BS (append, empty, pack)-import Data.Maybe (fromJust, isJust)-import Data.Text (Text)-import qualified Data.Text as T-import Haskoin.Address-import Haskoin.Constants-import Haskoin.Keys-import Haskoin.Util-import Haskoin.Util.Arbitrary-import Test.Hspec-import Test.Hspec.QuickCheck-import Test.HUnit-import Test.QuickCheck--serialVals :: [SerialBox]-serialVals = [SerialBox arbitraryAddressAll]+module Haskoin.AddressSpec (spec) where -readVals :: [ReadBox]-readVals = [ReadBox arbitraryAddressAll]+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 -netVals :: [NetBox]-netVals =- [NetBox (addrToJSON, addrToEncoding, addrFromJSON, arbitraryNetAddress)]+identityTests :: IdentityTests+identityTests =+ def+ { readTests = [ReadBox arbitraryAddressAll],+ serialTests = [SerialBox arbitraryAddressAll],+ marshalJsonTests = [MarshalJsonBox arbitraryNetAddress]+ } spec :: Spec-spec = do- testIdentity serialVals readVals [] netVals- 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 (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 compatWitnessVectors+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)+ assertEqual "encodeBase58" e b58+ assertEqual "encodeBase58Check" chk b58Chk+ assertEqual "decodeBase58" (Just bs) (decodeBase58 b58)+ assertEqual "decodeBase58Check" (Just bs) (decodeBase58Check b58Chk) where- b58 = encodeBase58 bs+ 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]- , "1111cWB5HCBdLjAuqGGReWE3R3CguuwSjw6RHn39s2yuDRTS5N\- \sBgNiFpWgAnEx6VQi8csexkgYw3mdYrMHr8x9i7aEwP8kZ7vcc\- \XWqKDvGv3u1GxFKPuAkn8JCPPGDMf3vMMnbzm6Nh9zh1gcNsMv\- \H3ZNLmP5fSG6DGbbi2tuwMWPthr4boWwCxf7ewSgNQeacyozhK\- \DDQQ1qL5fQFUW52QKUZDZ5fw3KXNQJMcNTcaB723LchjeKun7M\- \uGW5qyCBZYzA1KjofN1gYBV3NqyhQJ3Ns746GNuf9N2pQPmHz4\- \xpnSrrfCvy6TVVz5d4PdrjeshsWQwpZsZGzvbdAdN8MKV5QsBDY"- , "111151KWPPBRzdWPr1ASeu172gVgLf1YfUp6VJyk6K9t4cLqYt\- \FHcMa2iX8S3NJEprUcW7W5LvaPRpz7UG7puBj5STE3nKhCGt5e\- \ckYq7mMn5nT7oTTic2BAX6zDdqrmGCnkszQkzkz8e5QLGDjf7K\- \eQgtEDm4UER6DMSdBjFQVa6cHrrJn9myVyyhUrsVnfUk2WmNFZ\- \vkWv3Tnvzo2cJ1xW62XDfUgYz1pd97eUGGPuXvDFfLsBVd1dfd\- \UhPwxW7pMPgdWHTmg5uqKGFF6vE4xXpAqZTbTxRZjCDdTn68c2\- \wrcxApm8hq3JX65Hix7VtcD13FF8b7BzBtwjXq1ze6NMjKgUcq\- \pJTN9vt")- ]+ [ (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+ 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")- ]+ [ ("", ""),+ ("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)+ 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)+ [ ("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)+ assertEqual+ "decodeBase58Check invalid match"+ resM+ (encodeHex <$> decodeBase58Check a) base58ChkInvalidVectors :: [(Text, Maybe Text)] base58ChkInvalidVectors =- [ ("3vQB7B6MrGQZaxCuFg4oh", Just "68656c6c6f20776f726c64")- , ("3vQB7B6MrGQZaxCuFg4oi", Nothing) - , ("3vQB7B6MrGQZaxCuFg4oh0IOl", Nothing)- , ("3vQB7B6MrGQZaxCuFg4oh\00IOl", Nothing)- ]+ [ ("3vQB7B6MrGQZaxCuFg4oh", Just "68656c6c6f20776f726c64"),+ ("3vQB7B6MrGQZaxCuFg4oi", Nothing),+ ("3vQB7B6MrGQZaxCuFg4oh0IOl", Nothing),+ ("3vQB7B6MrGQZaxCuFg4oh\00IOl", Nothing)+ ] -testCompatWitnessVector :: (Network, Text, Text) -> Assertion-testCompatWitnessVector (net, seckey, addr) = do- let seckeyM = fromWif net seckey- assertBool "decode seckey" (isJust seckeyM)- let pubkey = derivePubKeyI (fromJust seckeyM)- let addrM = addrToText btcTest (pubKeyCompatWitnessAddr pubkey)- assertBool "address can be encoded" (isJust addrM)- assertEqual "witness address matches" addr (fromJust addrM)+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")- ]+ [ ( btcTest,+ "cNUnpYpMsJXYCERYBciJnsWBpcYEFjdcbq6dxj4SskGhs7uHuJ7Q",+ "2N6PDTueBHvXzW61B4oe5SW1D3v2Z3Vpbvw"+ )+ ]
test/Haskoin/BlockSpec.hs view
@@ -1,57 +1,64 @@+{-# LANGUAGE DuplicateRecordFields #-}+{-# LANGUAGE OverloadedRecordDot #-} {-# LANGUAGE OverloadedStrings #-} module Haskoin.BlockSpec- ( spec- ) where--import Control.Monad.State.Strict-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.Constants-import Haskoin.Transaction-import Haskoin.Util.Arbitrary-import Test.Hspec-import Test.Hspec.QuickCheck-import Test.HUnit hiding (State)-import Test.QuickCheck-import Text.Printf (printf)---serialVals :: [SerialBox]-serialVals =- [ SerialBox (arbitraryBlock =<< arbitraryNetwork)- , SerialBox arbitraryBlockHash- , SerialBox arbitraryBlockHeader- , SerialBox arbitraryGetBlocks- , SerialBox arbitraryGetHeaders- , SerialBox arbitraryHeaders- , SerialBox arbitraryMerkleBlock- , SerialBox arbitraryBlockNode- ]+ ( spec,+ )+where -readVals :: [ReadBox]-readVals =- [ ReadBox (arbitraryBlock =<< arbitraryNetwork)- , ReadBox arbitraryBlockHash- , ReadBox arbitraryBlockHeader- , ReadBox arbitraryGetBlocks- , ReadBox arbitraryGetHeaders- , ReadBox arbitraryHeaders- , ReadBox arbitraryMerkleBlock- , ReadBox arbitraryBlockNode- ]+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) -jsonVals :: [JsonBox]-jsonVals =- [ JsonBox (arbitraryBlock =<< arbitraryNetwork)- , JsonBox arbitraryBlockHash- , JsonBox arbitraryBlockHeader- ]+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@@ -59,63 +66,66 @@ withChain :: Network -> State HeaderMemory a -> a withChain net f = evalState f (initialChain net) -chain :: BlockHeaders m => Network -> BlockHeader -> Int -> m ()+chain :: (BlockHeaders m) => Network -> BlockHeader -> Int -> m () chain net bh i = do- bnsE <- connectBlocks net myTime bhs- either error (const $ return ()) bnsE+ bnsE <- connectBlocks net myTime bhs+ either error (const $ return ()) bnsE where bhs = appendBlocks net 6 bh i spec :: Spec-spec = do- testIdentity serialVals readVals jsonVals []- describe "blockchain headers" $ do- it "gets best block on bchRegTest" $- let net = bchRegTest- bb =- withChain net $ do- chain net (getGenesisHeader net) 100- getBestBlockHeader- in nodeHeight bb `shouldBe` 100- it "builds a block locator on bchRegTest" $- let net = bchRegTest- 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 on bchRegTest" $- let net = bchRegTest- bb = withChain net $ splitChain net >> getBestBlockHeader- in nodeHeight bb `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)+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 -- ↓@@ -124,38 +134,40 @@ -- → → 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)+ 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 =- unless (nodeHeight bn == n) $+ e n bn@BlockNode {} =+ unless (bn.height == n) $ error $- "Node height " ++- show (nodeHeight bn) ++ " of first chunk should be " ++ show n+ "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+ 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 -} @@ -167,202 +179,208 @@ buildExtractTree :: Network -> [(TxHash, Bool)] -> Bool buildExtractTree net txs =- r == buildMerkleRoot (map fst txs) && m == map fst (filter snd 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") $+ 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)+ 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)+ 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)+ 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"- ]- )- ]+ -- 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 * 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)+ 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 @@ -370,35 +388,34 @@ 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+ (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 [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+ 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+ forM_ blocks $ \(AsertBlock _ h t g) ->+ computeAsertBits+ (2 * 24 * 60 * 60)+ anchor_bits+ (t - anchor_parent_time)+ (h - anchor_height)+ `shouldBe` g
test/Haskoin/Crypto/HashSpec.hs view
@@ -1,75 +1,82 @@+{-# LANGUAGE ImportQualifiedPost #-}+{-# LANGUAGE OverloadedRecordDot #-} {-# LANGUAGE OverloadedStrings #-}-module Haskoin.Crypto.HashSpec (spec) where+{-# LANGUAGE NoFieldSelectors #-} -import Data.Bits-import Data.ByteString (ByteString)-import Data.ByteString.Builder-import qualified Data.ByteString.Char8 as C-import qualified Data.ByteString.Lazy as BL-import qualified Data.ByteString.Short as BSS-import Data.Maybe (fromJust)-import Data.Serialize as S-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.Hspec-import Test.Hspec.QuickCheck-import Test.HUnit-import Test.QuickCheck+module Haskoin.Crypto.HashSpec (spec) where -serialVals :: [SerialBox]-serialVals =- [ SerialBox arbitraryBS- , SerialBox arbitraryBSS- , SerialBox arbitraryHash160- , SerialBox arbitraryHash256- , SerialBox arbitraryHash512- ]+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 -readVals :: [ReadBox]-readVals =- [ ReadBox arbitraryBS- , ReadBox arbitraryBSS- , ReadBox arbitraryHash160- , ReadBox arbitraryHash256- , ReadBox arbitraryHash512- ]+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 serialVals readVals [] []- 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 $ encode h) == h- prop "from string Hash256" $- forAll arbitraryHash256 $ \h ->- fromString (cs $ encodeHex $ encode h) == h- prop "from string Hash160" $- forAll arbitraryHash160 $ \h ->- fromString (cs $ encodeHex $ encode h) == h- describe "Test Vectors" $ do- it "Passes RIPEMD160 test vectors" $- mapM_ (testVector ripemd160 getHash160) ripemd160Vectors- it "Passes SHA1 test vectors" $- mapM_ (testVector sha1 getHash160) sha1Vectors- it "Passes SHA256 test vectors" $- mapM_ (testVector sha256 getHash256) sha256Vectors- it "Passes SHA512 test vectors" $- mapM_ (testVector sha512 getHash512) sha512Vectors- it "Passes HMAC_SHA256 test vectors" $- mapM_ (testHMACVector hmac256 getHash256) hmacSha256Vectors- it "Passes HMAC_SHA512 test vectors" $- mapM_ (testHMACVector hmac512 getHash512) hmacSha512Vectors+ 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)@@ -78,264 +85,317 @@ -- 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+ -- 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 -> BSS.ShortByteString)- -> (ByteString, Text)- -> Assertion+ (ByteString -> a) ->+ (a -> Short.ShortByteString) ->+ (ByteString, Text) ->+ Assertion testVector f1 f2 (i, res) =- assertEqual "Hash matches" res (encodeHex (BSS.fromShort $ f2 $ f1 i))+ assertEqual "Hash matches" res (encodeHex (Short.fromShort $ f2 $ f1 i)) testHMACVector ::- (ByteString -> ByteString -> a)- -> (a -> BSS.ShortByteString)- -> (Text, Text, Text)- -> Assertion+ (ByteString -> ByteString -> a) ->+ (a -> Short.ShortByteString) ->+ (Text, Text, Text) ->+ Assertion testHMACVector f1 f2 (k, m, res) =- assertEqual "Hash matches" res (encodeHex (BSS.fromShort $ f2 $ f1 bsK bsM))+ assertEqual "Hash matches" res (encodeHex (Short.fromShort $ f2 $ f1 bsK bsM)) where bsK = fromJust $ decodeHex k bsM = fromJust $ decodeHex m longTestString :: ByteString longTestString =- BL.toStrict $! toLazyByteString $! go [0 .. 199999]+ 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+ 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")- , (C.replicate 1000000 'a', "52783243c1697bdbe16d37f97f68f08325dc1528")- , (longTestString, "464243587bd146ea835cdf57bdae582f25ec45f1")- ]+ [ ("", "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")- , (C.replicate 1000000 'a', "34aa973cd4c4daa4f61eeb2bdbad27316534016f")- , (longTestString, "b7755760681cbfd971451668f32af5774f4656b5")- ]+ [ ("", "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")- , ( C.replicate 1000000 'a'- , "cdc76e5c9914fb9281a1c7e284d73e67f1809a48a497200e046d39ccc7112cd0")- , ( longTestString- , "a316d55510b49662420f49d145d42fb83f31ef8dc016aa4e32df049991a91e26")- ]+ [ ("", "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")- , ( C.replicate 1000000 'a'- , "e718483d0ce769644e2e42c7bc15b4638e1f98b13b2044285632a803afa973ebde0ff2\- \44877ea60a4cb0432ce577c31beb009c5c2c49aa2e4eadb217ad8cc09b")- , ( longTestString- , "40cac46c147e6131c5193dd5f34e9d8bb4951395f27b08c558c65ff4ba2de59437de8c\- \3ef5459d76a52cedc02dc499a3c9ed9dedbfb3281afd9653b8a112fafc")- ]+ [ ( "",+ "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")- ]+ [ ( "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")- ]+ [ ( "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"+ )+ ]
+ test/Haskoin/Crypto/Keys/ExtendedSpec.hs view
@@ -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"+ ]+ ]
+ test/Haskoin/Crypto/Keys/MnemonicSpec.hs view
@@ -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
+ test/Haskoin/Crypto/KeysSpec.hs view
@@ -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
test/Haskoin/Crypto/SignatureSpec.hs view
@@ -1,104 +1,126 @@+{-# LANGUAGE ImportQualifiedPost #-}+{-# LANGUAGE OverloadedRecordDot #-} {-# LANGUAGE OverloadedStrings #-}+ module Haskoin.Crypto.SignatureSpec (spec) where -import Control.Monad-import Data.Bits (testBit)-import Data.ByteString (ByteString)-import qualified Data.ByteString as BS-import Data.Map.Strict (Map)-import qualified Data.Map.Strict as Map-import Data.Maybe-import Data.Serialize as S-import Data.String.Conversions (cs)-import Data.Text (Text)-import Haskoin.Address-import Haskoin.Transaction-import Haskoin.Keys-import Haskoin.Crypto-import Haskoin.Script-import Haskoin.Constants-import Haskoin.Util-import Haskoin.Util.Arbitrary-import Haskoin.UtilSpec (readTestFile)-import Test.Hspec-import Test.Hspec.QuickCheck-import Test.HUnit-import Test.QuickCheck+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 = do- describe "Signature properties" $ do- prop "verify signature" $- forAll arbitrarySignature $ \(m, key', sig) ->- verifyHashSig m sig (derivePubKey key')- prop "s component less than half order" $- forAll arbitrarySignature $ isCanonicalHalfOrder . lst3- prop "encoded signature is canonical" $- forAll arbitrarySignature $ testIsCanonical . lst3- prop "decodeStrictSig . exportSig identity" $- forAll arbitrarySignature $- (\s -> decodeStrictSig (exportSig s) == Just s) . lst3- prop "importSig . exportSig identity" $- forAll arbitrarySignature $- (\s -> importSig (exportSig s) == Just s) . lst3- prop "getSig . putSig identity" $- forAll arbitrarySignature $- (\s -> runGet getSig (runPut $ putSig s) == Right s) . lst3- describe "Signature vectors" $- checkDistSig $ \file1 file2 -> do- vectors <- runIO (readTestFile file1 :: IO [(Text, Text, Text)])- vectorsDER <- runIO (readTestFile file2 :: IO [(Text, Text, Text)])- it "Passes the trezor rfc6979 test vectors" $- mapM_ (testRFC6979Vector . toVector) vectors- it "Passes the rfc6979 DER test vectors" $- mapM_ (testRFC6979DERVector . toVector) vectorsDER- describe "BIP143 signature vectors" $ do- 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+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 :: Sig -> Bool-testIsCanonical sig = not $+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)- )+ (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+ s = exportSig ctx sig len = fromIntegral $ BS.length s rlen = BS.index s 3 slen = BS.index s (fromIntegral rlen + 5)@@ -109,45 +131,46 @@ -- between implementations. We check the output of signMsg 1 0 data ValidImpl- = ImplCore- | ImplABC+ = ImplCore+ | ImplCash -implSig :: Text-implSig =- encodeHex $- exportSig $- signMsg+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"- , ImplABC)- ]+ Map.fromList+ [ ( "3045022100a0b37f8fba683cc68f6574cd43b39f0343a50008bf6ccea9d13231\+ \d9e7e2e1e4022011edc8d307254296264aebfc3dc76cd8b668373a072fd64665\+ \b50000e9fcce52",+ ImplCore+ ),+ ( "304402200581361d23e645be9e3efe63a9a2ac2e8dd0c70ba3ac8554c9befe06\+ \0ad0b36202207d8172f1e259395834793d81b17e986f1e6131e4734969d2f4ae\+ \3a9c8bc42965",+ ImplCash+ )+ ] -getImpl :: Maybe ValidImpl-getImpl = implSig `Map.lookup` validImplMap+getImpl :: Ctx -> Maybe ValidImpl+getImpl ctx = implSig ctx `Map.lookup` validImplMap rfc6979files :: ValidImpl -> (FilePath, FilePath) rfc6979files ImplCore = ("rfc6979core.json", "rfc6979DERcore.json")-rfc6979files ImplABC = ("rfc6979abc.json", "rfc6979DERabc.json")+rfc6979files ImplCash = ("rfc6979cash.json", "rfc6979DERcash.json") -checkDistSig :: (FilePath -> FilePath -> Spec) -> Spec-checkDistSig go =- case rfc6979files <$> getImpl of- Just (file1, file2) -> go file1 file2- _ ->- it "Passes rfc6979 test vectors" $- void $ assertFailure "Invalid rfc6979 signature"+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@@ -155,272 +178,272 @@ toVector :: (Text, Text, Text) -> (SecKey, ByteString, Text) toVector (prv, m, res) = (fromJust $ (secKey <=< decodeHex) prv, cs m, res) -testRFC6979Vector :: (SecKey, ByteString, Text) -> Assertion-testRFC6979Vector (prv, m, res) = do- assertEqual "RFC 6979 Vector" res (encodeHex $ encode $ exportCompactSig s)- assertBool "Signature is valid" $ verifyHashSig h s (derivePubKey prv)- assertBool "Signature is canonical" $ testIsCanonical s- assertBool "Signature is normalized" $ isCanonicalHalfOrder s+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 prv h+ 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 :: (SecKey, ByteString, Text) -> Assertion-testRFC6979DERVector (prv, m, res) = do- assertEqual "RFC 6979 DER Vector" res (encodeHex $ exportSig s)- assertBool "DER Signature is valid" $ verifyHashSig h s (derivePubKey prv)- assertBool "DER Signature is canonical" $ testIsCanonical s- assertBool "DER Signature is normalized" $ isCanonicalHalfOrder s+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 prv h+ s = signHash ctx prv h -- Reproduce the P2WPKH example from BIP 143-testBip143p2wpkh :: Assertion-testBip143p2wpkh =- case getImpl of- Just ImplCore ->- assertEqual "BIP143 Core p2wpkh" (Right signedTxCore) generatedSignedTx- Just ImplABC ->- assertEqual "BIP143 ABC p2wpkh" (Right signedTxABC) generatedSignedTx- Nothing -> assertFailure "Invalid secp256k1 library"+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"- signedTxABC =- "01000000000102fff7f7881a8099afa6940d42d1e7f6362bec38171ea3edf433\- \541db4e4ad969f000000004847304402200fbc9dad97500334e47c2dca50096a\- \2117c01952c2870102e320823d21c36229022007cb36c2b141d11c08ef81d948\- \f148332fc09fe8f6d226aaaf8ba6ae0d8a66ba01eeffffffef51e1b804cc89d1\- \82d279655c3aa89e815b1b309fe287d9b2b55d57b90ec68a0100000000ffffff\- \ff02202cb206000000001976a9148280b37df378db99f66f85c95a783a76ac7a\- \6d5988ac9093510d000000001976a9143bde42dbee7e4dbe6a21b2d50ce2f016\- \7faa815988ac0002473044022011cb891cee521eb1fc7aef681655a881288553\- \fc024cff9cee5007bae5e6b8c602200b89d60ee2f98aa9a645dad59cd680b4b6\- \25f343efcd3e7fb70852100ef601890121025476c2e83188368da1ff3e292e7a\- \cafcdb3566bb0ad253f62fc70f07aeee635711000000"+ "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"+ "0100000002fff7f7881a8099afa6940d42d1e7f6362bec38171ea3edf433541d\+ \b4e4ad969f0000000000eeffffffef51e1b804cc89d182d279655c3aa89e815b\+ \1b309fe287d9b2b55d57b90ec68a0100000000ffffffff02202cb20600000000\+ \1976a9148280b37df378db99f66f85c95a783a76ac7a6d5988ac9093510d0000\+ \00001976a9143bde42dbee7e4dbe6a21b2d50ce2f0167faa815988ac11000000" Just key0 =- secHexKey- "bbc27228ddcb9209d7fd6f36b02f7dfa6252af40bb2f1cbc7a557da8027ff866"- pubKey0 = toPubKey key0+ secHexKey+ "bbc27228ddcb9209d7fd6f36b02f7dfa6252af40bb2f1cbc7a557da8027ff866"+ pubKey0 = toPubKey ctx key0 Just key1 =- secHexKey- "619c335025c7f4012e556c2a58b2506e30b8511b53ade95ea316fd8c3286feb9"- [op0, op1] = prevOutput <$> txIn unsignedTx+ secHexKey+ "619c335025c7f4012e556c2a58b2506e30b8511b53ade95ea316fd8c3286feb9"+ [op0, op1] = (.outpoint) <$> unsignedTx.inputs sigIn0 = SigInput (PayPK pubKey0) 625000000 op0 sigHashAll Nothing- WitnessPubKeyAddress h = pubKeyWitnessAddr $ toPubKey key1+ WitnessPubKeyAddress h = pubKeyWitnessAddr ctx $ toPubKey ctx key1 sigIn1 = SigInput (PayWitnessPKHash h) 600000000 op1 sigHashAll Nothing- generatedSignedTx = signTx btc unsignedTx [sigIn0, sigIn1] [key0, key1]+ generatedSignedTx = signTx btc ctx unsignedTx [sigIn0, sigIn1] [key0, key1] -- Reproduce the P2SH-P2WPKH example from BIP 143-testBip143p2shp2wpkh :: Assertion-testBip143p2shp2wpkh =- case getImpl of- Just ImplCore ->- assertEqual "BIP143 Core p2sh-p2wpkh" (Right signedTxCore) generatedSignedTx- Just ImplABC ->- assertEqual "BIP143 ABC p2sh-p2wpkh" (Right signedTxABC) generatedSignedTx- Nothing -> assertFailure "Invalid secp256k1 library"+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"- signedTxABC =- "01000000000101db6b1b20aa0fd7b23880be2ecbd4a98130974cf4748fb66092\- \ac4d3ceb1a5477010000001716001479091972186c449eb1ded22b78e40d009b\- \df0089feffffff02b8b4eb0b000000001976a914a457b684d7f0d539a46a45bb\- \c043f35b59d0d96388ac0008af2f000000001976a914fd270b1ee6abcaea97fe\- \a7ad0402e8bd8ad6d77c88ac024730440220091c78fd1e21535f6ddc45515e4c\- \afca15cdf344765d72c1529fb82d3ada2d1802204a980d5e37d0b04f5e1185a0\- \f97295c383764e9a4b08d8bd1161b33c6719139a012103ad1d8e89212f0b92c7\- \4d23bb710c00662ad1470198ac48c43f7d6f93a2a2687392040000"+ "01000000000101db6b1b20aa0fd7b23880be2ecbd4a98130974cf4748fb66092\+ \ac4d3ceb1a5477010000001716001479091972186c449eb1ded22b78e40d009b\+ \df0089feffffff02b8b4eb0b000000001976a914a457b684d7f0d539a46a45bb\+ \c043f35b59d0d96388ac0008af2f000000001976a914fd270b1ee6abcaea97fe\+ \a7ad0402e8bd8ad6d77c88ac02473044022047ac8e878352d3ebbde1c94ce3a1\+ \0d057c24175747116f8288e5d794d12d482f0220217f36a485cae903c713331d\+ \877c1f64677e3622ad4010726870540656fe9dcb012103ad1d8e89212f0b92c7\+ \4d23bb710c00662ad1470198ac48c43f7d6f93a2a2687392040000"+ signedTxCash =+ "01000000000101db6b1b20aa0fd7b23880be2ecbd4a98130974cf4748fb66092\+ \ac4d3ceb1a5477010000001716001479091972186c449eb1ded22b78e40d009b\+ \df0089feffffff02b8b4eb0b000000001976a914a457b684d7f0d539a46a45bb\+ \c043f35b59d0d96388ac0008af2f000000001976a914fd270b1ee6abcaea97fe\+ \a7ad0402e8bd8ad6d77c88ac024730440220091c78fd1e21535f6ddc45515e4c\+ \afca15cdf344765d72c1529fb82d3ada2d1802204a980d5e37d0b04f5e1185a0\+ \f97295c383764e9a4b08d8bd1161b33c6719139a012103ad1d8e89212f0b92c7\+ \4d23bb710c00662ad1470198ac48c43f7d6f93a2a2687392040000" unsignedTx =- "0100000001db6b1b20aa0fd7b23880be2ecbd4a98130974cf4748fb66092ac4d\- \3ceb1a54770100000000feffffff02b8b4eb0b000000001976a914a457b684d7\- \f0d539a46a45bbc043f35b59d0d96388ac0008af2f000000001976a914fd270b\- \1ee6abcaea97fea7ad0402e8bd8ad6d77c88ac92040000"+ "0100000001db6b1b20aa0fd7b23880be2ecbd4a98130974cf4748fb66092ac4d\+ \3ceb1a54770100000000feffffff02b8b4eb0b000000001976a914a457b684d7\+ \f0d539a46a45bbc043f35b59d0d96388ac0008af2f000000001976a914fd270b\+ \1ee6abcaea97fea7ad0402e8bd8ad6d77c88ac92040000" Just key0 =- secHexKey- "eb696a065ef48a2192da5b28b694f87544b30fae8327c4510137a922f32c6dcf"- op0 = prevOutput . head $ txIn unsignedTx- WitnessPubKeyAddress h = pubKeyWitnessAddr $ toPubKey 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 unsignedTx [sigIn0] [key0]+ generatedSignedTx = signNestedWitnessTx btc ctx unsignedTx [sigIn0] [key0] -- P2WSH multisig example (tested against bitcoin-core 0.19.0.1)-testP2WSHMulsig :: Assertion-testP2WSHMulsig =- case getImpl of- Just ImplCore ->- assertEqual "Core p2wsh multisig" (Right signedTxCore) generatedSignedTx- Just ImplABC ->- assertEqual "ABC p2wsh multisig" (Right signedTxABC) generatedSignedTx- Nothing -> assertFailure "Invalid secp256k1 library"+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"- signedTxABC =- "01000000000101d2e34df5d7ee565208eddd231548916b9b0e99f4f5071f8961\- \34a448c5fb07bf0100000000ffffffff01f0b9f505000000001976a9143d5a35\- \2cab583b12fbcb26d1269b4a2c951a33ad88ac0400483045022100b79bf3714a\- \50f8f0e2f946034361ba4f6567b796d55910d89e98720d2e99f98c0220134879\- \518002df23e80a058475fa8b10bc4182bedfecd5f85e446a00f211ea53014830\- \45022100ce3c77480d664430a7544c1a962d1ae31151109a528a37e5bccc92ba\- \2e460ad10220317bc9a71d0c3471058d16d4c3b1ea99616208db6b9b9040fb81\- \0a7fa27f72b40169522102ba46d3bb8db74c77c6cf082db57fc0548058fcdea8\- \11549e186526e3d10caf6721038ac8aef2dd9cea5e7d66e2f6e23f177a6c21f6\- \9ea311fa0c85d81badb6b37ceb2103d96d2bfbbc040faaf93491d69e2bfe9695\- \e2d8e007a7f26db96c2ee42db15dc953ae00000000"+ "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 $ prevOutput <$> txIn unsignedTx+ "0100000001d2e34df5d7ee565208eddd231548916b9b0e99f4f5071f896134a4\+ \48c5fb07bf0100000000ffffffff01f0b9f505000000001976a9143d5a352cab\+ \583b12fbcb26d1269b4a2c951a33ad88ac00000000"+ op0 = (head unsignedTx.inputs).outpoint Just keys =- traverse- secHexKey- [ "3030303030303030303030303030303030303030303030303030303030303031"- , "3030303030303030303030303030303030303030303030303030303030303032"- , "3030303030303030303030303030303030303030303030303030303030303033"- ]- rdm = PayMulSig (toPubKey <$> keys) 2+ traverse+ secHexKey+ [ "3030303030303030303030303030303030303030303030303030303030303031",+ "3030303030303030303030303030303030303030303030303030303030303032",+ "3030303030303030303030303030303030303030303030303030303030303033"+ ]+ rdm = PayMulSig (toPubKey ctx <$> keys) 2 sigIn =- SigInput- (toP2WSH $ encodeOutput rdm)- 100000000- op0- sigHashAll- (Just rdm)- generatedSignedTx = signTx btc unsignedTx [sigIn] (take 2 keys)+ 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 :: Assertion-testBip143p2shp2wpkhMulsig =- case getImpl of- Just ImplCore ->- assertEqual- "BIP143 Core p2sh-p2wsh multisig"- (Right signedTxCore)- generatedSignedTx- Just ImplABC ->- assertEqual- "BIP143 Core p2sh-p2wsh multisig"- (Right signedTxABC)- generatedSignedTx- Nothing -> assertFailure "Invalid secp256k1 library"+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"- signedTxABC =- "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"+ "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 $ prevOutput <$> txIn unsignedTx+ "010000000136641869ca081e70f394c6948e8af409e18b619df2ed74aa106c1c\+ \a29787b96e0100000000ffffffff0200e9a435000000001976a914389ffce9cd\+ \9ae88dcc0631e88a821ffdbe9bfe2688acc0832f05000000001976a9147480a3\+ \3f950689af511e6e84c138dbbd3c3ee41588ac00000000"+ op0 = (head unsignedTx.inputs).outpoint rawKeys =- [ "730fff80e1413068a05b57d6a58261f07551163369787f349438ea38ca80fac6"- , "11fa3d25a17cbc22b29c44a484ba552b5a53149d106d3d853e22fdd05a2d8bb3"- , "77bf4141a87d55bdd7f3cd0bdccf6e9e642935fec45f2f30047be7b799120661"- , "14af36970f5025ea3e8b5542c0f8ebe7763e674838d08808896b63c3351ffe49"- , "fe9a95c19eef81dde2b95c1284ef39be497d128e2aa46916fb02d552485e0323"- , "428a7aee9f0c2af0cd19af3cf1c78149951ea528726989b2e83e4778d2c3f890"- ]+ [ "730fff80e1413068a05b57d6a58261f07551163369787f349438ea38ca80fac6",+ "11fa3d25a17cbc22b29c44a484ba552b5a53149d106d3d853e22fdd05a2d8bb3",+ "77bf4141a87d55bdd7f3cd0bdccf6e9e642935fec45f2f30047be7b799120661",+ "14af36970f5025ea3e8b5542c0f8ebe7763e674838d08808896b63c3351ffe49",+ "fe9a95c19eef81dde2b95c1284ef39be497d128e2aa46916fb02d552485e0323",+ "428a7aee9f0c2af0cd19af3cf1c78149951ea528726989b2e83e4778d2c3f890"+ ] Just keys = traverse secHexKey rawKeys- rdm = PayMulSig (toPubKey <$> keys) 6- sigIn sh = SigInput (toP2WSH $ encodeOutput rdm) 987654321 op0 sh (Just rdm)+ rdm = PayMulSig (toPubKey ctx <$> keys) 6+ sigIn sh = SigInput (toP2WSH $ encodeOutput ctx rdm) 987654321 op0 sh (Just rdm) sigHashesA = [sigHashAll, sigHashNone, sigHashSingle]- sigHashesB = setAnyoneCanPayFlag <$> sigHashesA+ sigHashesB = setAnyoneCanPay <$> sigHashesA sigIns = sigIn <$> (sigHashesA <> sigHashesB) generatedSignedTx = foldM addSig unsignedTx $ zip sigIns keys- addSig tx (sigIn', key') = signNestedWitnessTx btc tx [sigIn'] [key']+ addSig tx (sigIn', key') = signNestedWitnessTx btc ctx tx [sigIn'] [key'] secHexKey :: Text -> Maybe SecKey secHexKey = decodeHex >=> secKey -toPubKey :: SecKey -> PubKeyI-toPubKey = derivePubKeyI . wrapSecKey True+toPubKey :: Ctx -> SecKey -> PublicKey+toPubKey ctx = derivePublicKey ctx . wrapSecKey True
− test/Haskoin/Keys/ExtendedSpec.hs
@@ -1,578 +0,0 @@-{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE OverloadedStrings #-}-module Haskoin.Keys.ExtendedSpec (spec) where--import Control.Monad (forM_)-import Data.Aeson as A-import Data.Bits ((.&.))-import qualified Data.ByteString.Lazy.Char8 as B8-import Data.Either (isLeft)-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 Haskoin.Address-import Haskoin.Constants-import Haskoin.Keys-import Haskoin.Util-import Haskoin.Util.Arbitrary-import Haskoin.UtilSpec (customCerealID)-import Test.Hspec-import Test.Hspec.QuickCheck-import Test.HUnit (Assertion, assertBool, assertEqual)-import Test.QuickCheck hiding ((.&.))--serialVals :: [SerialBox]-serialVals =- [ SerialBox arbitraryDerivPath- , SerialBox arbitraryHardPath- , SerialBox arbitrarySoftPath- ]--readVals :: [ReadBox]-readVals =- [ ReadBox arbitraryDerivPath- , ReadBox arbitraryHardPath- , ReadBox arbitrarySoftPath- , ReadBox arbitraryXPrvKey- , ReadBox (snd <$> arbitraryXPubKey)- , ReadBox arbitraryParsedPath- , ReadBox arbitraryBip32PathIndex- ]--jsonVals :: [JsonBox]-jsonVals =- [ JsonBox arbitraryDerivPath- , JsonBox arbitraryHardPath- , JsonBox arbitrarySoftPath- , JsonBox arbitraryParsedPath- ]--netVals :: [NetBox]-netVals =- [ NetBox- ( xPrvToJSON- , xPrvToEncoding- , xPrvFromJSON- , genNetData arbitraryXPrvKey)- , NetBox- ( xPubToJSON- , xPubToEncoding- , xPubFromJSON- , genNetData (snd <$> arbitraryXPubKey))- ]--spec :: Spec-spec = do- testIdentity serialVals readVals jsonVals netVals- describe "Custom identity tests" $ do- prop "encodes and decodes extended private key" $- forAll arbitraryNetwork $ \net ->- forAll arbitraryXPrvKey $- customCerealID (getXPrvKey net) (putXPrvKey net)- prop "encodes and decodes extended public key" $- forAll arbitraryNetwork $ \net ->- forAll arbitraryXPubKey $- customCerealID (getXPubKey net) (putXPubKey net) . snd- describe "bip32 subkey derivation vector 1" $ vectorSpec m1 vector1- describe "bip32 subkey derivation vector 2" $ vectorSpec m2 vector2- describe "bip32 subkey derivation vector 3" $ vectorSpec m3 vector3- 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 "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- 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 $ \(_, k) ->- xPubImport net (xPubExport net k) == Just k--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--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---- BIP 0032 Test Vectors--- https://en.bitcoin.it/wiki/BIP_0032_TestVectors--bip44Addr :: DerivPath-bip44Addr = Deriv :| 44 :| 0 :| 0 :/ 0 :/ 0--vectorSpec :: TestKey -> [TestVector] -> Spec-vectorSpec mTxt vecTxt =- forM_ (parseVector mTxt vecTxt) $ \(d,m,v) ->- it ("chain " <> cs d) $ runVector m v--runVector :: XPrvKey -> TestVector -> Assertion-runVector m v = do- assertBool "xPrvID" $ encodeHex (S.encode $ xPrvID m) == v !! 0- assertBool "xPrvFP" $ encodeHex (S.encode $ xPrvFP m) == v !! 1- assertBool "xPrvAddr" $- addrToText btc (xPubAddr $ deriveXPubKey m) == Just (v !! 2)- assertBool "bip44Addr" $- addrToText btc (xPubAddr $ deriveXPubKey $ derivePath bip44Addr m) ==- Just (v !! 3)- assertBool "prvKey" $ encodeHex (getSecKey $ xPrvKey m) == v !! 4- assertBool "xPrvWIF" $ xPrvWif btc m == v !! 5- assertBool "pubKey" $- encodeHex (exportPubKey True $ xPubKey $ deriveXPubKey m) == v !! 6- assertBool "chain code" $ encodeHex (S.encode $ xPrvChain m) == v !! 7- assertBool "Hex PubKey" $- encodeHex (runPut $ putXPubKey btc $ deriveXPubKey m) == v !! 8- assertBool "Hex PrvKey" $ encodeHex (runPut (putXPrvKey btc m)) == v !! 9- assertBool "Base58 PubKey" $ xPubExport btc (deriveXPubKey m) == v !! 10- assertBool "Base58 PrvKey" $ xPrvExport btc m == v !! 11---- This function was used to generate addition data for the test vectors-genVector :: XPrvKey -> [(Text, Text)]-genVector m =- [ ("xPrvID", encodeHex (S.encode $ xPrvID m))- , ("xPrvFP", encodeHex (S.encode $ xPrvFP m))- , ("xPrvAddr", fromJust $ addrToText btc (xPubAddr $ deriveXPubKey m))- , ( "bip44Addr"- , fromJust $- addrToText btc (xPubAddr $ deriveXPubKey $ derivePath bip44Addr m))- , ("prvKey", encodeHex (getSecKey $ xPrvKey m))- , ("xPrvWIF", xPrvWif btc m)- , ("pubKey", encodeHex (exportPubKey True $ xPubKey $ deriveXPubKey m))- , ("chain code", encodeHex (S.encode $ xPrvChain m))- , ("Hex PubKey", encodeHex (runPut $ putXPubKey btc $ deriveXPubKey m))- , ("Hex PrvKey", encodeHex (runPut (putXPrvKey btc m)))- ]--parseVector :: TestKey -> [TestVector] -> [(Text, XPrvKey, TestVector)]-parseVector mTxt vs =- go <$> vs- where- mast = makeXPrvKey $ fromJust $ decodeHex mTxt- go (d:vec) =- let deriv = getParsedPath $ fromJust $ parsePath $ cs d- in (d, derivePath 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"- ]- ]
− test/Haskoin/Keys/MnemonicSpec.hs
@@ -1,490 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}-module Haskoin.Keys.MnemonicSpec (spec) where--import Control.Monad (zipWithM_)-import Data.Bits (shiftR, (.&.))-import qualified Data.ByteString as BS-import Data.Either (fromRight)-import Data.List (isPrefixOf)-import Data.Maybe (fromJust)-import Data.Serialize (Serialize, encode)-import Data.Text (Text)-import qualified Data.Text as T-import Data.Word (Word32, Word64)-import Haskoin.Keys-import Haskoin.Util-import Haskoin.Util.Arbitrary-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
− test/Haskoin/KeysSpec.hs
@@ -1,257 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}-module Haskoin.KeysSpec (spec) where--import Control.Lens-import Control.Monad-import Data.Aeson as A-import Data.Aeson.Lens-import qualified Data.ByteString as BS-import qualified Data.ByteString.Char8 as C-import Data.Maybe-import Data.Serialize as S-import Data.String (fromString)-import Data.String.Conversions (cs)-import Data.Text (Text)-import Haskoin.Address-import Haskoin.Constants-import Haskoin.Crypto-import Haskoin.Keys-import Haskoin.Script-import Haskoin.Util-import Haskoin.Util.Arbitrary-import Haskoin.UtilSpec (readTestFile)-import Test.Hspec-import Test.Hspec.QuickCheck-import Test.HUnit-import Test.QuickCheck--serialVals :: [SerialBox]-serialVals =- [ SerialBox (arbitrary :: Gen SecKey)- , SerialBox (snd <$> arbitraryKeyPair) -- PubKeyI- ]--readVals :: [ReadBox]-readVals =- [ ReadBox (arbitrary :: Gen SecKey)- , ReadBox arbitrarySecKeyI- , ReadBox (snd <$> arbitraryKeyPair) -- PubKeyI- ]--jsonVals :: [JsonBox]-jsonVals =- [ JsonBox (snd <$> arbitraryKeyPair) -- PubKeyI- ]--spec :: Spec-spec = do- testIdentity serialVals readVals jsonVals []- describe "PubKey properties" $ do- prop "Public key is canonical" $- forAll arbitraryKeyPair (isCanonicalPubKey . snd)- prop "Public key fromString identity" $- forAll arbitraryKeyPair $ \(_, k) ->- fromString (cs . encodeHex $ S.encode k) == k- describe "SecKey properties" $- prop "fromWif . toWif identity" $- forAll arbitraryNetwork $ \net ->- forAll arbitraryKeyPair $ \(pk, _) ->- fromWif net (toWif net pk) == Just pk- 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- it "Passes address matching tests" testMatchingAddress- it "Passes signature verification" testSigs- it "Passes deterministic signing tests" testDetSigning- 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 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 vectors---- 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--testMiniKey :: Assertion-testMiniKey =- assertEqual "fromMiniKey" (Just res) (go "S6c56bnXQiBjk9mqSYE7ykVQ7NzrRy")- where- go = fmap (encodeHex . S.encode . secKeyData) . fromMiniKey- res = "4c7a9640c72dc2099f23715d0c8a0d8a35f8906e3cab61dd3f78b67bf887c9ab"---- Test vectors from:--- https://github.com/bitcoin/bitcoin/blob/master/src/test/key_io_tests.cpp--testKeyIOValidVector :: (Text, Text, A.Value) -> Assertion-testKeyIOValidVector (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 . S.encode . secKeyData <$> prvKeyM- assertBool "Valid PrvKey" $ isJust prvKeyM- assertEqual "Valid compression" (Just isComp) (secKeyCompressed <$> prvKeyM)- assertEqual "WIF matches payload" (Just payload) prvKeyHexM- let prvAsPubM = (eitherToMaybe . decodeOutputBS <=< decodeHex) a- assertBool "PrvKey is invalid ScriptOutput" $ isNothing prvAsPubM- -- Test from SecKey to WIF- let secM = eitherToMaybe . S.decode =<< 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 . encodeOutputBS . addressToOutput <$> addrM- assertBool ("Valid Address " <> cs a) $ isJust addrM- assertEqual "Address matches payload" (Just payload) scriptM- let pubAsWifM = fromWif net a- pubAsSecM = eitherToMaybe . S.decode =<< decodeHex a :: Maybe SecKey- assertBool "Address is invalid Wif" $ isNothing pubAsWifM- assertBool "Address is invalid PrvKey" $ isNothing pubAsSecM- -- Test Script to Addr- let outM = eitherToMaybe . decodeOutputBS =<< decodeHex payload- resM = addrToText net =<< outputAddress =<< 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 :: [Text] -> Assertion-testKeyIOInvalidVector [a] = do- let wifMs = (`fromWif` a) <$> allNets- secKeyM = (eitherToMaybe . S.decode <=< decodeHex) a :: Maybe SecKey- scriptM = (eitherToMaybe . decodeOutputBS <=< decodeHex) a :: Maybe ScriptOutput- 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 $ secKeyCompressed sec1- assertBool "Key 2" $ not $ secKeyCompressed sec2- assertBool "Key 1C" $ secKeyCompressed sec1C- assertBool "Key 2C" $ secKeyCompressed sec2C--testKeyCompressed :: Assertion-testKeyCompressed = do- assertBool "Key 1" $ not $ pubKeyCompressed pub1- assertBool "Key 2" $ not $ pubKeyCompressed pub2- assertBool "Key 1C" $ pubKeyCompressed pub1C- assertBool "Key 2C" $ pubKeyCompressed pub2C--testMatchingAddress :: Assertion-testMatchingAddress = do- assertEqual "Key 1" (Just addr1) $ addrToText btc (pubKeyAddr pub1)- assertEqual "Key 2" (Just addr2) $ addrToText btc (pubKeyAddr pub2)- assertEqual "Key 1C" (Just addr1C) $ addrToText btc (pubKeyAddr pub1C)- assertEqual "Key 2C" (Just addr2C) $ addrToText btc (pubKeyAddr pub2C)--testSigs :: Assertion-testSigs = forM_ sigMsg $ testSignature . doubleSHA256--sigMsg :: [BS.ByteString]-sigMsg =- [ mconcat ["Very secret message ", C.pack (show (i :: Int)), ": 11"]- | i <- [0..15]- ]--testSignature :: Hash256 -> Assertion-testSignature 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)--testDetSigning :: Assertion-testDetSigning = do- let m = doubleSHA256 ("Very deterministic message" :: BS.ByteString)- assertEqual- "Det sig 1"- (signHash (secKeyData sec1) m)- (signHash (secKeyData sec1C) m)- assertEqual- "Det sig 2"- (signHash (secKeyData sec2) m)- (signHash (secKeyData sec2C) m)--strSecret1, strSecret2, strSecret1C, strSecret2C :: Text-strSecret1 = "5HxWvvfubhXpYYpS3tJkw6fq9jE9j18THftkZjHHfmFiWtmAbrj"-strSecret2 = "5KC4ejrDjv152FGwP386VD1i2NYc5KkfSMyv1nGy1VGDxGHqVY3"-strSecret1C = "Kwr371tjA9u2rFSMZjTNun2PXXP3WPZu2afRHTcta6KxEUdm1vEw"-strSecret2C = "L3Hq7a8FEQwJkW1M2GNKDW28546Vp5miewcCzSqUD9kCAXrJdS3g"--sec1, sec2, sec1C, sec2C :: SecKeyI-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 :: PubKeyI-pub1 = derivePubKeyI sec1-pub2 = derivePubKeyI sec2-pub1C = derivePubKeyI sec1C-pub2C = derivePubKeyI sec2C-
test/Haskoin/NetworkSpec.hs view
@@ -1,73 +1,82 @@+{-# LANGUAGE DuplicateRecordFields #-}+{-# LANGUAGE OverloadedRecordDot #-} {-# LANGUAGE OverloadedStrings #-}+ module Haskoin.NetworkSpec (spec) where -import Data.Maybe (fromJust)-import Data.Serialize as S-import Data.Text (Text)-import Data.Word (Word32)-import Haskoin.Address-import Haskoin.Constants-import Haskoin.Keys-import Haskoin.Network-import Haskoin.Transaction-import Haskoin.Util-import Haskoin.Util.Arbitrary-import Haskoin.UtilSpec (customCerealID)-import Test.Hspec-import Test.Hspec.QuickCheck-import Test.HUnit (Assertion, assertBool, assertEqual)-import Test.QuickCheck+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 -serialVals :: [SerialBox]-serialVals =- [ 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- ]+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 = do- testIdentity serialVals [] [] []- describe "Custom identity tests" $ do- prop "Data.Serialize Encoding for type Message" $- forAll arbitraryNetwork $ \net ->- forAll (arbitraryMessage net) $- 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- describe "relevant bloom filter update" $ do- it "Relevant Update" relevantOutputUpdated- it "Irrelevant Update" irrelevantOutputNotUpdated+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" $- S.encode f4 == bs+ 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@@ -85,86 +94,97 @@ bloomFilter2 :: Assertion bloomFilter2 = bloomFilter 2147483649 "03ce4299050000000100008001" -bloomFilter3 :: Assertion-bloomFilter3 =- assertBool "Bloom filter serialization is incorrect" $- S.encode f2 == bs+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 $ S.encode p- f2 = bloomInsert f1 $ S.encode $ getAddrHash160 $ pubKeyAddr p+ f1 = bloomInsert f0 $ marshal ctx p+ f2 = bloomInsert f1 $ runPutS $ serialize (pubKeyAddr ctx p).hash160 k = fromJust $ fromWif btc "5Kg1gnAjaLfKiwhhPpGS3QfRg2m6awQvaj98JCZBZQ5SuS2F15C"- p = derivePubKeyI k+ p = derivePublicKey ctx k bs = fromJust $ decodeHex "038fc16b080000000000000001" -relevantOutputUpdated :: Assertion-relevantOutputUpdated = assertBool "Bloom filter output updated" $+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 bf1 relevantTx- spendTxInput = encode .prevOutput <$> txIn spendRelevantTx+ 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 :: 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+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- { 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- }+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- { 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- }+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- { 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- }+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+ }
test/Haskoin/ScriptSpec.hs view
@@ -1,439 +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 qualified Data.ByteString as B-import Data.Either-import Data.List-import Data.Maybe-import Data.Serialize as S-import Data.String-import Data.String.Conversions (cs)-import Data.Text (Text)-import Data.Word-import Haskoin.Address-import Haskoin.Constants-import Haskoin.Keys-import Haskoin.Script-import Haskoin.Transaction-import Haskoin.Util-import Haskoin.Util.Arbitrary-import Haskoin.UtilSpec (readTestFile)-import Test.Hspec-import Test.Hspec.QuickCheck-import Test.HUnit as HUnit-import Test.QuickCheck-import Text.Read--serialVals :: [SerialBox]-serialVals =- [ SerialBox arbitraryScriptOp- , SerialBox arbitraryScript- ]+module Haskoin.ScriptSpec (spec) where -readVals :: [ReadBox]-readVals =- [ ReadBox arbitrarySigHash- , ReadBox arbitrarySigHashFlag- , ReadBox arbitraryScript- , ReadBox arbitraryPushDataType- , ReadBox arbitraryScriptOp- , ReadBox (arbitraryScriptOutput =<< arbitraryNetwork)- ]+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 -jsonVals :: [JsonBox]-jsonVals =- [ JsonBox $ arbitraryScriptOutput =<< arbitraryNetwork- , JsonBox arbitraryOutPoint- , JsonBox arbitrarySigHash- , JsonBox $ fst <$> (arbitrarySigInput =<< arbitraryNetwork)- ]+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 = do- testIdentity serialVals readVals jsonVals []- describe "btc scripts" $ props btc- describe "bch scripts" $ props bch- describe "multi signatures" $- zipWithM_ (curry mapMulSigVector) mulSigVectors [0 ..]- describe "signature decoding" $- zipWithM_ (curry (sigDecodeMap btc)) 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+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 -> Spec-props net = do- standardSpec net- strictSigSpec net- scriptSpec net- txSigHashForkIdSpec net- forkIdScriptSpec net- sigHashSpec net- txSigHashSpec net+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 -> Spec-standardSpec net = do- prop "has intToScriptOp . scriptOpToInt identity" $- forAll arbitraryIntScriptOp $ \i ->- intToScriptOp <$> scriptOpToInt i `shouldBe` Right i- prop "has decodeOutput . encodeOutput identity" $- forAll (arbitraryScriptOutput net) $ \so ->- decodeOutput (encodeOutput so) `shouldBe` Right so- prop "has decodeInput . encodeOutput identity" $- forAll (arbitraryScriptInput net) $ \si ->- decodeInput net (encodeInput si) `shouldBe` Right si- prop "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))+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 -> Spec-scriptSpec net =- when (getNetworkName net == "btc") $+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 =- "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)+ 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 -> Spec-forkIdScriptSpec net =- when (isJust (getSigHashForkId net)) $+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- 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+ 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+ Tx 1 [txI] [txO] [] 0 where- txO = TxOut {outValue = val, scriptOutput = scriptPubKey}+ txO = TxOut {value = val, script = scriptPubKey} txI =- TxIn- { prevOutput = nullOutPoint- , scriptInput = S.encode $ Script [OP_0, OP_0]- , txInSequence = maxBound+ 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+ Tx 1 [txI] [txO] [] 0 where- txO = TxOut {outValue = val, scriptOutput = B.empty}+ txO = TxOut {value = val, script = B.empty} txI =- TxIn- { prevOutput = OutPoint (txHash $ creditTx scriptPubKey val) 0- , scriptInput = scriptSig- , txInSequence = maxBound+ 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+ B.concat $ fromMaybe err $ mapM f $ words str where f = decodeHex . cs . dropHex . replaceToken- dropHex ('0':'x':xs) = xs- dropHex xs = xs+ 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+ Just opcode -> "0x" <> cs (encodeHex $ runPutS $ serialize (opcode :: ScriptOp))+ _ -> str -strictSigSpec :: Network -> Spec-strictSigSpec net =- when (getNetworkName net == "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 ->- decodeTxSig net sig `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 ->- decodeTxSig net sig `shouldSatisfy` isLeft+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 (getNetworkName net == "btc") $+ 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 . 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+ 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 (getNetworkName net == "btc") $+ 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 . 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+ 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 -> Spec-sigHashSpec net = 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- 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+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 $ txIn tx) ==>- if length (txIn tx) > length (txOut tx)- then res `shouldBe` one- else res `shouldNotBe` one+ 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 (txIn tx) - 1) (f sigHashSingle)+ res = txSigHash net tx s val (length tx.inputs - 1) (f sigHashSingle) one = "0100000000000000000000000000000000000000000000000000000000000000" f =- if acp- then setAnyoneCanPayFlag- else id+ if acp+ then setAnyoneCanPay+ else id {- Parse tests from bitcoin-qt repository -} -mapMulSigVector :: ((Text, Text), Int) -> Spec-mapMulSigVector (v, i) =- it name $ runMulSigVector v+mapMulSigVector :: Ctx -> ((Text, Text), Int) -> Spec+mapMulSigVector ctx (v, i) =+ it name $ runMulSigVector ctx v where name = "check multisig vector " <> show i -runMulSigVector :: (Text, Text) -> Assertion-runMulSigVector (a, ops) = assertBool "multisig vector" $ Just a == b+runMulSigVector :: Ctx -> (Text, Text) -> Assertion+runMulSigVector ctx (a, ops) = assertBool "multisig vector" $ Just a == b where s = do- s' <- decodeHex ops- eitherToMaybe $ S.decode s'+ s' <- decodeHex ops+ eitherToMaybe $ runGetS deserialize s' b = do- o <- s- d <- eitherToMaybe $ decodeOutput o- addrToText btc $ payToScriptAddress d+ o <- s+ d <- eitherToMaybe $ decodeOutput ctx o+ addrToText btc $ payToScriptAddress ctx d -sigDecodeMap :: Network -> (Text, Int) -> Spec-sigDecodeMap net (_, i) =- it ("check signature " ++ show i) func+sigDecodeMap :: Network -> Ctx -> (Text, Int) -> Spec+sigDecodeMap net ctx (_, i) =+ it ("check signature " ++ show i) func where- func = testSigDecode net $ scriptSigSignatures !! i+ func = testSigDecode net ctx $ 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+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"- )- ]+ [ ( "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"- ]+ -- 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 $ S.encode s)+ assertEqual "Encode script" res (encodeHex $ runPutS $ serialize s) where res =- "514104cc71eb30d653c0c3163990c47b976f3fb3f37cccdcbedb169a1dfef58b\- \bfbfaff7d8a473e7e2e6d317b87bafe8bde97e3cf8f065dec022b51d11fcdd0d\- \348ac4410461cbdcc5409fb4b4d42b51d33381354d80e550078cb532a34bfa2f\- \cfdeb7d76519aecc62770f5b0e4ef8551946d8a540911abe3e7854a26f39f58b\- \25c15342af52ae"+ "514104cc71eb30d653c0c3163990c47b976f3fb3f37cccdcbedb169a1dfef58b\+ \bfbfaff7d8a473e7e2e6d317b87bafe8bde97e3cf8f065dec022b51d11fcdd0d\+ \348ac4410461cbdcc5409fb4b4d42b51d33381354d80e550078cb532a34bfa2f\+ \cfdeb7d76519aecc62770f5b0e4ef8551946d8a540911abe3e7854a26f39f58b\+ \25c15342af52ae" s =- Script- [ OP_1- , opPushData $ d+ Script+ [ OP_1,+ opPushData $+ d "04cc71eb30d653c0c3163990c47b976f3fb3f37cccdcbedb169a1dfef5\ \8bbfbfaff7d8a473e7e2e6d317b87bafe8bde97e3cf8f065dec022b51d\- \11fcdd0d348ac4"- , opPushData $ d+ \11fcdd0d348ac4",+ opPushData $+ d "0461cbdcc5409fb4b4d42b51d33381354d80e550078cb532a34bfa2fcf\ \deb7d76519aecc62770f5b0e4ef8551946d8a540911abe3e7854a26f39\- \f58b25c15342af"- , OP_2- , OP_CHECKMULTISIG- ]+ \f58b25c15342af",+ OP_2,+ OP_CHECKMULTISIG+ ] d = fromJust . decodeHex-
test/Haskoin/Transaction/PartialSpec.hs view
@@ -1,317 +1,550 @@+{-# 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.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 Haskoin.Address-import Haskoin.Constants-import Haskoin.Crypto-import Haskoin.Keys-import Haskoin.Script-import Haskoin.Transaction-import Haskoin.Transaction.Partial-import Haskoin.Util-import Haskoin.Util.Arbitrary+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 = 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+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" $- 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+ (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 :: 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)+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 . inputRedeemScript $ inputs psbt !! 1- assertBool "p2wpkh" $ (isPayWitnessPKHash <$> decodeOutput rdmScript) == Right True+ let rdmScript = fromJust (psbt.inputs !! 1).inputRedeemScript+ assertBool "p2wpkh" $ (isPayWitnessPKHash <$> decodeOutput ctx 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+ 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) (outputs psbt)+ mapM_ (assertEqual "outputs are empty" emptyOutput) psbt.outputs -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)+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 :: 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)+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 rdmScript = fromJust $ inputRedeemScript secondInput- assertBool "p2wpkh" $ (isPayWitnessPKHash <$> decodeOutput rdmScript) == Right True+ let Just rdmScript = secondInput.inputRedeemScript+ assertBool "p2wpkh" $ (isPayWitnessPKHash <$> decodeOutput ctx rdmScript) == Right True - let scrptPubKey = witnessScriptPubKey secondInput- rdmScriptP2SH = toP2SH rdmScript- assertEqual "redeem script pubkey equal" rdmScriptP2SH scrptPubKey- assertEqual "expected redeem script" expectedOut rdmScriptP2SH+ 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` outputs psbt+ assertBool "all non-empty outputs" $ emptyOutput `notElem` psbt.outputs -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)+vec5Test :: Ctx -> Assertion+vec5Test ctx = do+ psbt <- decodeHexPSBTM ctx "Cannot parse validVec5" validVec5Hex+ assertEqual "Correctly decode PSBT" expectedPsbt psbt+ let (input : _) = psbt.inputs - let rdmScript = fromJust $ inputRedeemScript input- assertBool "p2wsh" $ (isPayWitnessScriptHash <$> decodeOutput rdmScript) == Right True+ let Just rdmScript = input.inputRedeemScript+ assertBool "p2wsh" $ (isPayWitnessScriptHash <$> decodeOutput ctx rdmScript) == Right True - let scrptPubKey = witnessScriptPubKey input- rdmScriptP2SH = toP2SH rdmScript- assertEqual "redeem script pubkey equal" rdmScriptP2SH scrptPubKey- assertEqual "expected redeem script" expectedOut2 rdmScriptP2SH+ 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")- . decodeOutputBS . fromJust $ decodeHex "a9146345200f68d189e1adc0df1c4d16ea8f14c0dbeb87"+ 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 :: Assertion-vec6Test = do- psbt <- decodeHexPSBTM "Cannot parse validVec6" validVec6Hex- assertEqual "1 input" 1 (length $ inputs psbt)- assertEqual "1 output" 1 (length $ outputs psbt)+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 = unsignedTransaction psbt- assertEqual "correct transaction" "75c5c9665a570569ad77dd1279e6fd4628a093c4dcbf8d41532614044c14c115" (txHash tx)+ let tx = psbt.unsignedTransaction+ assertEqual "correct transaction" "75c5c9665a570569ad77dd1279e6fd4628a093c4dcbf8d41532614044c14c115" (txHash tx) - assertEqual "correct unknowns" expectedUnknowns (inputUnknown . head $ inputs psbt)+ assertEqual "correct unknowns" expectedUnknowns (head psbt.inputs).inputUnknown where- expectedUnknowns = UnknownMap $ singleton- (Key 0x0f (fromJust $ decodeHex "010203040506070809"))- (fromJust $ decodeHex "0102030405060708090a0b0c0d0e0f")+ 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"+complexPsbtTest :: Ctx -> Assertion+complexPsbtTest ctx = do+ complex <- readTestFileParser (parseComplexJSON ctx) "complex_psbt.json" -witnessScriptPubKey :: Input -> ScriptOutput-witnessScriptPubKey = fromRight (error "could not decode witness utxo")- . decodeOutputBS . scriptOutput . fromJust . witnessUtxo+ let computedCombinedPsbt = mergeMany $ complexSignedPsbts complex+ expectedCombinedPsbt = stripRedundantUtxos $ complexCombinedPsbt complex+ assertEqual "combined psbt" computedCombinedPsbt (Just expectedCombinedPsbt) -decodeHexPSBT :: Text -> Either String PartiallySignedTransaction-decodeHexPSBT = S.decode . fromJust . decodeHex+ let computedCompletePsbt = complete ctx $ complexCombinedPsbt complex+ expectedCompletePsbt = complexCompletePsbt complex+ assertEqual "complete psbt" computedCompletePsbt expectedCompletePsbt -decodeHexPSBTM :: (Monad m, MonadFail m) => String -> Text -> m PartiallySignedTransaction-decodeHexPSBTM errMsg = either (fail . (errMsg <>) . (": " <>)) return . decodeHexPSBT+ 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 . S.decode . fromJust . decodeHex+ either (error "Could not decode script") encodeScript+ . runGetS deserialize+ . fromJust+ . decodeHex where encodeScript :: Script -> ByteString- encodeScript = S.encode+ encodeScript = runPutS . serialize -invalidVecTest :: Text -> Assertion-invalidVecTest = assertBool "invalid psbt" . isLeft . decodeHexPSBT+invalidVecTest :: Ctx -> Text -> Assertion+invalidVecTest ctx = assertBool "invalid psbt" . isLeft . decodeHexPSBT ctx -decodeVecTest :: Int -> Text -> Assertion-decodeVecTest i = assertBool (show i <> " decodes correctly") . isRight . decodeHexPSBT+decodeVecTest :: Ctx -> Int -> Text -> Assertion+decodeVecTest ctx i = assertBool (show i <> " decodes correctly") . isRight . decodeHexPSBT ctx -encodeVecTest :: PartiallySignedTransaction -> Text -> Assertion-encodeVecTest psbt hex = assertEqual "encodes correctly" (S.encode psbt) (fromJust $ decodeHex hex)+encodeVecTest :: Ctx -> PSBT -> Text -> Assertion+encodeVecTest ctx psbt hex =+ assertEqual+ "encodes correctly"+ ((runPut . putPSBT ctx) psbt)+ ((fromJust . decodeHex) hex) -trivialPSBT :: PartiallySignedTransaction-trivialPSBT = PartiallySignedTransaction- { unsignedTransaction = Tx { txVersion = 2, txIn = [], txOut = [], txWitness = [], txLockTime = 0 }- , globalUnknown = UnknownMap mempty- , inputs = []- , outputs = []+trivialPSBT :: PSBT+trivialPSBT =+ PSBT+ { unsignedTransaction = Tx {version = 2, inputs = [], outputs = [], witness = [], locktime = 0},+ globalUnknown = UnknownMap mempty,+ inputs = [],+ outputs = [] } trivialPSBTHex :: Text trivialPSBTHex = "70736274ff01000a0200000000000000000000" -nonEmptyTransactionPSBT :: PartiallySignedTransaction+nonEmptyTransactionPSBT :: PSBT nonEmptyTransactionPSBT = emptyPSBT testTx1 -verifyNonWitnessPSBT :: Network -> PartiallySignedTransaction -> Bool-verifyNonWitnessPSBT net psbt = verifyStdTx net (finalTransaction (complete psbt)) sigData+verifyNonWitnessPSBT :: Network -> Ctx -> PSBT -> Bool+verifyNonWitnessPSBT net ctx psbt =+ verifyStdTx net ctx (finalTransaction (complete ctx 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)+ 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 -> (SecKeyI, PubKeyI) -> PartiallySignedTransaction-unfinalizedPkhPSBT net (prvKey, pubKey) = (emptyPSBT currTx)- { inputs = [ emptyInput { nonWitnessUtxo = Just prevTx, partialSigs = singleton pubKey sig } ] }+unfinalizedPkhPSBT :: Network -> Ctx -> (PrivateKey, PublicKey) -> PSBT+unfinalizedPkhPSBT net ctx (prvKey, pubKey) =+ let PSBT {..} = emptyPSBT currTx+ in PSBT {inputs = [input], ..} 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+ 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 :: Gen ([(SecKeyI, PubKeyI)], Int)-arbitraryMultiSig = do- (m, n) <- arbitraryMSParam- keys <- vectorOf n arbitraryKeyPair- return (keys, m)+arbitraryMultiSig :: Ctx -> Gen ([(PrivateKey, PublicKey)], Int)+arbitraryMultiSig ctx = do+ (m, n) <- arbitraryMSParam+ keys <- vectorOf n (arbitraryKeyPair ctx)+ 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- } ] }+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 $ PayMulSig (map snd keys) m- prevOut = TxOut { outValue = 200000000, scriptOutput = encodeOutputBS (toP2SH prevOutScript) }- h = txSigHash net currTx prevOutScript (outValue prevOut) 0 sigHashAll+ 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, pubKey) = (pubKey, encodeTxSig $ TxSignature (signHash (secKeyData prvKey) h) sigHashAll)+ 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- { 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- }+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"- ]-+ [ "0200000001268171371edff285e937adeea4b37b78000c0566cbb3ad64641713ca42171bf6000000006a473044022070b2245123e6bf474d60c5b50c043d4c691a5d2435f09a34a7662a9dc251790a022001329ca9dacf280bdf30740ec0390422422c81cb45839457aeb76fc12edd95b3012102657d118d3357b8e0f4c2cd46db7b39f6d9c38d9a70abcb9b2de5dc8dbfe4ce31feffffff02d3dff505000000001976a914d0c59903c5bac2868760e90fd521a4665aa7652088ac00e1f5050000000017a9143545e6e33b832c47050f24d3eeb93c9c03948bc787b32e1300",+ "70736274ff0100750200000001268171371edff285e937adeea4b37b78000c0566cbb3ad64641713ca42171bf60000000000feffffff02d3dff505000000001976a914d0c59903c5bac2868760e90fd521a4665aa7652088ac00e1f5050000000017a9143545e6e33b832c47050f24d3eeb93c9c03948bc787b32e1300000100fda5010100000000010289a3c71eab4d20e0371bbba4cc698fa295c9463afa2e397f8533ccb62f9567e50100000017160014be18d152a9b012039daf3da7de4f53349eecb985ffffffff86f8aa43a71dff1448893a530a7237ef6b4608bbb2dd2d0171e63aec6a4890b40100000017160014fe3e9ef1a745e974d902c4355943abcb34bd5353ffffffff0200c2eb0b000000001976a91485cff1097fd9e008bb34af709c62197b38978a4888ac72fef84e2c00000017a914339725ba21efd62ac753a9bcd067d6c7a6a39d05870247304402202712be22e0270f394f568311dc7ca9a68970b8025fdd3b240229f07f8a5f3a240220018b38d7dcd314e734c9276bd6fb40f673325bc4baa144c800d2f2f02db2765c012103d2e15674941bad4a996372cb87e1856d3652606d98562fe39c5e9e7e413f210502483045022100d12b852d85dcd961d2f5f4ab660654df6eedcc794c0c33ce5cc309ffb5fce58d022067338a8e0e1725c197fb1a88af59f51e44e4255b20167c8684031c05d1f2592a01210223b72beef0965d10be0778efecd61fcac6f79a4ea169393380734464f84f2ab30000000000",+ "70736274ff0100fd0a010200000002ab0949a08c5af7c49b8212f417e2f15ab3f5c33dcf153821a8139f877a5b7be4000000006a47304402204759661797c01b036b25928948686218347d89864b719e1f7fcf57d1e511658702205309eabf56aa4d8891ffd111fdf1336f3a29da866d7f8486d75546ceedaf93190121035cdc61fc7ba971c0b501a646a2a83b102cb43881217ca682dc86e2d73fa88292feffffffab0949a08c5af7c49b8212f417e2f15ab3f5c33dcf153821a8139f877a5b7be40100000000feffffff02603bea0b000000001976a914768a40bbd740cbe81d988e71de2a4d5c71396b1d88ac8e240000000000001976a9146f4620b553fa095e721b9ee0efe9fa039cca459788ac00000000000001012000e1f5050000000017a9143545e6e33b832c47050f24d3eeb93c9c03948bc787010416001485d13537f2e265405a34dbafa9e3dda01fb82308000000",+ "70736274ff000100fda5010100000000010289a3c71eab4d20e0371bbba4cc698fa295c9463afa2e397f8533ccb62f9567e50100000017160014be18d152a9b012039daf3da7de4f53349eecb985ffffffff86f8aa43a71dff1448893a530a7237ef6b4608bbb2dd2d0171e63aec6a4890b40100000017160014fe3e9ef1a745e974d902c4355943abcb34bd5353ffffffff0200c2eb0b000000001976a91485cff1097fd9e008bb34af709c62197b38978a4888ac72fef84e2c00000017a914339725ba21efd62ac753a9bcd067d6c7a6a39d05870247304402202712be22e0270f394f568311dc7ca9a68970b8025fdd3b240229f07f8a5f3a240220018b38d7dcd314e734c9276bd6fb40f673325bc4baa144c800d2f2f02db2765c012103d2e15674941bad4a996372cb87e1856d3652606d98562fe39c5e9e7e413f210502483045022100d12b852d85dcd961d2f5f4ab660654df6eedcc794c0c33ce5cc309ffb5fce58d022067338a8e0e1725c197fb1a88af59f51e44e4255b20167c8684031c05d1f2592a01210223b72beef0965d10be0778efecd61fcac6f79a4ea169393380734464f84f2ab30000000000",+ "70736274ff0100750200000001268171371edff285e937adeea4b37b78000c0566cbb3ad64641713ca42171bf60000000000feffffff02d3dff505000000001976a914d0c59903c5bac2868760e90fd521a4665aa7652088ac00e1f5050000000017a9143545e6e33b832c47050f24d3eeb93c9c03948bc787b32e1300000100fda5010100000000010289a3c71eab4d20e0371bbba4cc698fa295c9463afa2e397f8533ccb62f9567e50100000017160014be18d152a9b012039daf3da7de4f53349eecb985ffffffff86f8aa43a71dff1448893a530a7237ef6b4608bbb2dd2d0171e63aec6a4890b40100000017160014fe3e9ef1a745e974d902c4355943abcb34bd5353ffffffff0200c2eb0b000000001976a91485cff1097fd9e008bb34af709c62197b38978a4888ac72fef84e2c00000017a914339725ba21efd62ac753a9bcd067d6c7a6a39d05870247304402202712be22e0270f394f568311dc7ca9a68970b8025fdd3b240229f07f8a5f3a240220018b38d7dcd314e734c9276bd6fb40f673325bc4baa144c800d2f2f02db2765c012103d2e15674941bad4a996372cb87e1856d3652606d98562fe39c5e9e7e413f210502483045022100d12b852d85dcd961d2f5f4ab660654df6eedcc794c0c33ce5cc309ffb5fce58d022067338a8e0e1725c197fb1a88af59f51e44e4255b20167c8684031c05d1f2592a01210223b72beef0965d10be0778efecd61fcac6f79a4ea169393380734464f84f2ab30000000001003f0200000001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000ffffffff010000000000000000036a010000000000000000",+ "70736274ff020001550200000001279a2323a5dfb51fc45f220fa58b0fc13e1e3342792a85d7e36cd6333b5cbc390000000000ffffffff01a05aea0b000000001976a914ffe9c0061097cc3b636f2cb0460fa4fc427d2b4588ac0000000000010120955eea0b0000000017a9146345200f68d189e1adc0df1c4d16ea8f14c0dbeb87220203b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd4646304302200424b58effaaa694e1559ea5c93bbfd4a89064224055cdf070b6771469442d07021f5c8eb0fea6516d60b8acb33ad64ede60e8785bfb3aa94b99bdf86151db9a9a010104220020771fd18ad459666dd49f3d564e3dbc42f4c84774e360ada16816a8ed488d5681010547522103b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd462103de55d1e1dac805e3f8a58c1fbf9b94c02f3dbaafe127fefca4995f26f82083bd52ae220603b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd4610b4a6ba67000000800000008004000080220603de55d1e1dac805e3f8a58c1fbf9b94c02f3dbaafe127fefca4995f26f82083bd10b4a6ba670000008000000080050000800000",+ "70736274ff0100550200000001279a2323a5dfb51fc45f220fa58b0fc13e1e3342792a85d7e36cd6333b5cbc390000000000ffffffff01a05aea0b000000001976a914ffe9c0061097cc3b636f2cb0460fa4fc427d2b4588ac000000000002010020955eea0b0000000017a9146345200f68d189e1adc0df1c4d16ea8f14c0dbeb87220203b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd4646304302200424b58effaaa694e1559ea5c93bbfd4a89064224055cdf070b6771469442d07021f5c8eb0fea6516d60b8acb33ad64ede60e8785bfb3aa94b99bdf86151db9a9a010104220020771fd18ad459666dd49f3d564e3dbc42f4c84774e360ada16816a8ed488d5681010547522103b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd462103de55d1e1dac805e3f8a58c1fbf9b94c02f3dbaafe127fefca4995f26f82083bd52ae220603b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd4610b4a6ba67000000800000008004000080220603de55d1e1dac805e3f8a58c1fbf9b94c02f3dbaafe127fefca4995f26f82083bd10b4a6ba670000008000000080050000800000",+ "70736274ff0100550200000001279a2323a5dfb51fc45f220fa58b0fc13e1e3342792a85d7e36cd6333b5cbc390000000000ffffffff01a05aea0b000000001976a914ffe9c0061097cc3b636f2cb0460fa4fc427d2b4588ac0000000000010120955eea0b0000000017a9146345200f68d189e1adc0df1c4d16ea8f14c0dbeb87210203b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd46304302200424b58effaaa694e1559ea5c93bbfd4a89064224055cdf070b6771469442d07021f5c8eb0fea6516d60b8acb33ad64ede60e8785bfb3aa94b99bdf86151db9a9a010104220020771fd18ad459666dd49f3d564e3dbc42f4c84774e360ada16816a8ed488d5681010547522103b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd462103de55d1e1dac805e3f8a58c1fbf9b94c02f3dbaafe127fefca4995f26f82083bd52ae220603b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd4610b4a6ba67000000800000008004000080220603de55d1e1dac805e3f8a58c1fbf9b94c02f3dbaafe127fefca4995f26f82083bd10b4a6ba670000008000000080050000800000",+ "70736274ff0100550200000001279a2323a5dfb51fc45f220fa58b0fc13e1e3342792a85d7e36cd6333b5cbc390000000000ffffffff01a05aea0b000000001976a914ffe9c0061097cc3b636f2cb0460fa4fc427d2b4588ac0000000000010120955eea0b0000000017a9146345200f68d189e1adc0df1c4d16ea8f14c0dbeb87220203b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd4646304302200424b58effaaa694e1559ea5c93bbfd4a89064224055cdf070b6771469442d07021f5c8eb0fea6516d60b8acb33ad64ede60e8785bfb3aa94b99bdf86151db9a9a01020400220020771fd18ad459666dd49f3d564e3dbc42f4c84774e360ada16816a8ed488d5681010547522103b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd462103de55d1e1dac805e3f8a58c1fbf9b94c02f3dbaafe127fefca4995f26f82083bd52ae220603b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd4610b4a6ba67000000800000008004000080220603de55d1e1dac805e3f8a58c1fbf9b94c02f3dbaafe127fefca4995f26f82083bd10b4a6ba670000008000000080050000800000",+ "70736274ff0100550200000001279a2323a5dfb51fc45f220fa58b0fc13e1e3342792a85d7e36cd6333b5cbc390000000000ffffffff01a05aea0b000000001976a914ffe9c0061097cc3b636f2cb0460fa4fc427d2b4588ac0000000000010120955eea0b0000000017a9146345200f68d189e1adc0df1c4d16ea8f14c0dbeb87220203b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd4646304302200424b58effaaa694e1559ea5c93bbfd4a89064224055cdf070b6771469442d07021f5c8eb0fea6516d60b8acb33ad64ede60e8785bfb3aa94b99bdf86151db9a9a010104220020771fd18ad459666dd49f3d564e3dbc42f4c84774e360ada16816a8ed488d568102050047522103b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd462103de55d1e1dac805e3f8a58c1fbf9b94c02f3dbaafe127fefca4995f26f82083bd52ae220603b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd4610b4a6ba67000000800000008004000080220603de55d1e1dac805e3f8a58c1fbf9b94c02f3dbaafe127fefca4995f26f82083bd10b4a6ba670000008000000080050000800000",+ "70736274ff0100550200000001279a2323a5dfb51fc45f220fa58b0fc13e1e3342792a85d7e36cd6333b5cbc390000000000ffffffff01a05aea0b000000001976a914ffe9c0061097cc3b636f2cb0460fa4fc427d2b4588ac0000000000010120955eea0b0000000017a9146345200f68d189e1adc0df1c4d16ea8f14c0dbeb87220203b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd4646304302200424b58effaaa694e1559ea5c93bbfd4a89064224055cdf070b6771469442d07021f5c8eb0fea6516d60b8acb33ad64ede60e8785bfb3aa94b99bdf86151db9a9a010104220020771fd18ad459666dd49f3d564e3dbc42f4c84774e360ada16816a8ed488d5681010547522103b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd462103de55d1e1dac805e3f8a58c1fbf9b94c02f3dbaafe127fefca4995f26f82083bd52ae210603b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd10b4a6ba67000000800000008004000080220603de55d1e1dac805e3f8a58c1fbf9b94c02f3dbaafe127fefca4995f26f82083bd10b4a6ba670000008000000080050000800000",+ "70736274ff01009a020000000258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd750000000000ffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d0100000000ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f0000000000020000bb0200000001aad73931018bd25f84ae400b68848be09db706eac2ac18298babee71ab656f8b0000000048473044022058f6fc7c6a33e1b31548d481c826c015bd30135aad42cd67790dab66d2ad243b02204a1ced2604c6735b6393e5b41691dd78b00f0c5942fb9f751856faa938157dba01feffffff0280f0fa020000000017a9140fb9463421696b82c833af241c78c17ddbde493487d0f20a270100000017a91429ca74f8a08f81999428185c97b5d852e4063f6187650000000107da00473044022074018ad4180097b873323c0015720b3684cc8123891048e7dbcd9b55ad679c99022073d369b740e3eb53dcefa33823c8070514ca55a7dd9544f157c167913261118c01483045022100f61038b308dc1da865a34852746f015772934208c6d24454393cd99bdf2217770220056e675a675a6d0a02b85b14e5e29074d8a25a9b5760bea2816f661910a006ea01475221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752ae0001012000c2eb0b0000000017a914b7f5faf40e3d40a5a459b1db3535f2b72fa921e8870107232200208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b20289030108da0400473044022062eb7a556107a7c73f45ac4ab5a1dddf6f7075fb1275969a7f383efff784bcb202200c05dbb7470dbf2f08557dd356c7325c1ed30913e996cd3840945db12228da5f01473044022065f45ba5998b59a27ffe1a7bed016af1f1f90d54b3aa8f7450aa5f56a25103bd02207f724703ad1edb96680b284b56d4ffcb88f7fb759eabbe08aa30f29b851383d20147522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae00220203a9a4c37f5996d3aa25dbac6b570af0650394492942460b354753ed9eeca5877110d90c6a4f000000800000008004000080002202027f6399757d2eff55a136ad02c684b1838b6556e5f1b6b34282a94b6b5005109610d90c6a4f00000080000000800500008000",+ "70736274ff01009a020000000258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd750000000000ffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d0100000000ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f00000000000100bb0200000001aad73931018bd25f84ae400b68848be09db706eac2ac18298babee71ab656f8b0000000048473044022058f6fc7c6a33e1b31548d481c826c015bd30135aad42cd67790dab66d2ad243b02204a1ced2604c6735b6393e5b41691dd78b00f0c5942fb9f751856faa938157dba01feffffff0280f0fa020000000017a9140fb9463421696b82c833af241c78c17ddbde493487d0f20a270100000017a91429ca74f8a08f81999428185c97b5d852e4063f618765000000020700da00473044022074018ad4180097b873323c0015720b3684cc8123891048e7dbcd9b55ad679c99022073d369b740e3eb53dcefa33823c8070514ca55a7dd9544f157c167913261118c01483045022100f61038b308dc1da865a34852746f015772934208c6d24454393cd99bdf2217770220056e675a675a6d0a02b85b14e5e29074d8a25a9b5760bea2816f661910a006ea01475221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752ae0001012000c2eb0b0000000017a914b7f5faf40e3d40a5a459b1db3535f2b72fa921e8870107232200208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b20289030108da0400473044022062eb7a556107a7c73f45ac4ab5a1dddf6f7075fb1275969a7f383efff784bcb202200c05dbb7470dbf2f08557dd356c7325c1ed30913e996cd3840945db12228da5f01473044022065f45ba5998b59a27ffe1a7bed016af1f1f90d54b3aa8f7450aa5f56a25103bd02207f724703ad1edb96680b284b56d4ffcb88f7fb759eabbe08aa30f29b851383d20147522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae00220203a9a4c37f5996d3aa25dbac6b570af0650394492942460b354753ed9eeca5877110d90c6a4f000000800000008004000080002202027f6399757d2eff55a136ad02c684b1838b6556e5f1b6b34282a94b6b5005109610d90c6a4f00000080000000800500008000",+ "70736274ff01009a020000000258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd750000000000ffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d0100000000ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f00000000000100bb0200000001aad73931018bd25f84ae400b68848be09db706eac2ac18298babee71ab656f8b0000000048473044022058f6fc7c6a33e1b31548d481c826c015bd30135aad42cd67790dab66d2ad243b02204a1ced2604c6735b6393e5b41691dd78b00f0c5942fb9f751856faa938157dba01feffffff0280f0fa020000000017a9140fb9463421696b82c833af241c78c17ddbde493487d0f20a270100000017a91429ca74f8a08f81999428185c97b5d852e4063f6187650000000107da00473044022074018ad4180097b873323c0015720b3684cc8123891048e7dbcd9b55ad679c99022073d369b740e3eb53dcefa33823c8070514ca55a7dd9544f157c167913261118c01483045022100f61038b308dc1da865a34852746f015772934208c6d24454393cd99bdf2217770220056e675a675a6d0a02b85b14e5e29074d8a25a9b5760bea2816f661910a006ea01475221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752ae0001012000c2eb0b0000000017a914b7f5faf40e3d40a5a459b1db3535f2b72fa921e8870107232200208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b2028903020800da0400473044022062eb7a556107a7c73f45ac4ab5a1dddf6f7075fb1275969a7f383efff784bcb202200c05dbb7470dbf2f08557dd356c7325c1ed30913e996cd3840945db12228da5f01473044022065f45ba5998b59a27ffe1a7bed016af1f1f90d54b3aa8f7450aa5f56a25103bd02207f724703ad1edb96680b284b56d4ffcb88f7fb759eabbe08aa30f29b851383d20147522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae00220203a9a4c37f5996d3aa25dbac6b570af0650394492942460b354753ed9eeca5877110d90c6a4f000000800000008004000080002202027f6399757d2eff55a136ad02c684b1838b6556e5f1b6b34282a94b6b5005109610d90c6a4f00000080000000800500008000",+ "70736274ff01009a020000000258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd750000000000ffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d0100000000ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f00000000000100bb0200000001aad73931018bd25f84ae400b68848be09db706eac2ac18298babee71ab656f8b0000000048473044022058f6fc7c6a33e1b31548d481c826c015bd30135aad42cd67790dab66d2ad243b02204a1ced2604c6735b6393e5b41691dd78b00f0c5942fb9f751856faa938157dba01feffffff0280f0fa020000000017a9140fb9463421696b82c833af241c78c17ddbde493487d0f20a270100000017a91429ca74f8a08f81999428185c97b5d852e4063f6187650000000107da00473044022074018ad4180097b873323c0015720b3684cc8123891048e7dbcd9b55ad679c99022073d369b740e3eb53dcefa33823c8070514ca55a7dd9544f157c167913261118c01483045022100f61038b308dc1da865a34852746f015772934208c6d24454393cd99bdf2217770220056e675a675a6d0a02b85b14e5e29074d8a25a9b5760bea2816f661910a006ea01475221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752ae0001012000c2eb0b0000000017a914b7f5faf40e3d40a5a459b1db3535f2b72fa921e8870107232200208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b20289030108da0400473044022062eb7a556107a7c73f45ac4ab5a1dddf6f7075fb1275969a7f383efff784bcb202200c05dbb7470dbf2f08557dd356c7325c1ed30913e996cd3840945db12228da5f01473044022065f45ba5998b59a27ffe1a7bed016af1f1f90d54b3aa8f7450aa5f56a25103bd02207f724703ad1edb96680b284b56d4ffcb88f7fb759eabbe08aa30f29b851383d20147522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae00210203a9a4c37f5996d3aa25dbac6b570af0650394492942460b354753ed9eeca58710d90c6a4f000000800000008004000080002202027f6399757d2eff55a136ad02c684b1838b6556e5f1b6b34282a94b6b5005109610d90c6a4f00000080000000800500008000",+ "70736274ff0100730200000001301ae986e516a1ec8ac5b4bc6573d32f83b465e23ad76167d68b38e730b4dbdb0000000000ffffffff02747b01000000000017a91403aa17ae882b5d0d54b25d63104e4ffece7b9ea2876043993b0000000017a914b921b1ba6f722e4bfa83b6557a3139986a42ec8387000000000001011f00ca9a3b00000000160014d2d94b64ae08587eefc8eeb187c601e939f9037c0203000100000000010016001462e9e982fff34dd8239610316b090cd2a3b747cb000100220020876bad832f1d168015ed41232a9ea65a1815d9ef13c0ef8759f64b5b2b278a65010125512103b7ce23a01c5b4bf00a642537cdfabb315b668332867478ef51309d2bd57f8a8751ae00",+ "70736274ff0100730200000001301ae986e516a1ec8ac5b4bc6573d32f83b465e23ad76167d68b38e730b4dbdb0000000000ffffffff02747b01000000000017a91403aa17ae882b5d0d54b25d63104e4ffece7b9ea2876043993b0000000017a914b921b1ba6f722e4bfa83b6557a3139986a42ec8387000000000001011f00ca9a3b00000000160014d2d94b64ae08587eefc8eeb187c601e939f9037c0002000016001462e9e982fff34dd8239610316b090cd2a3b747cb000100220020876bad832f1d168015ed41232a9ea65a1815d9ef13c0ef8759f64b5b2b278a65010125512103b7ce23a01c5b4bf00a642537cdfabb315b668332867478ef51309d2bd57f8a8751ae00",+ "70736274ff0100730200000001301ae986e516a1ec8ac5b4bc6573d32f83b465e23ad76167d68b38e730b4dbdb0000000000ffffffff02747b01000000000017a91403aa17ae882b5d0d54b25d63104e4ffece7b9ea2876043993b0000000017a914b921b1ba6f722e4bfa83b6557a3139986a42ec8387000000000001011f00ca9a3b00000000160014d2d94b64ae08587eefc8eeb187c601e939f9037c00010016001462e9e982fff34dd8239610316b090cd2a3b747cb000100220020876bad832f1d168015ed41232a9ea65a1815d9ef13c0ef8759f64b5b2b278a6521010025512103b7ce23a01c5b4bf00a642537cdfabb315b668332867478ef51309d2bd57f8a8751ae00"+ ] -validEncodeVec :: [(PartiallySignedTransaction, Text)]+validEncodeVec :: [(PSBT, 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- }+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- { 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- }+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 { outValue = 200000000, scriptOutput = hexScript "76a91485cff1097fd9e008bb34af709c62197b38978a4888ac" }- , TxOut { outValue = 190303501938, scriptOutput = hexScript "a914339725ba21efd62ac753a9bcd067d6c7a6a39d0587" }+testUtxo1 =+ testUtxo+ [ TxOut {value = 200000000, script = hexScript "76a91485cff1097fd9e008bb34af709c62197b38978a4888ac"},+ TxOut {value = 190303501938, script = hexScript "a914339725ba21efd62ac753a9bcd067d6c7a6a39d0587"} ] -validVec1 :: PartiallySignedTransaction-validVec1 = (emptyPSBT testTx1) { inputs = [ emptyInput { nonWitnessUtxo = Just testUtxo1 } ] }+validVec1 :: PSBT+validVec1 =+ let PSBT {..} = emptyPSBT testTx1+ in PSBT {inputs = [emptyInput {nonWitnessUtxo = Just testUtxo1}], ..} validVec :: [Text] validVec = [validVec1Hex, validVec2Hex, validVec3Hex, validVec4Hex, validVec5Hex, validVec6Hex]@@ -333,3 +566,50 @@ 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
+ test/Haskoin/Transaction/TaprootSpec.hs view
@@ -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
test/Haskoin/TransactionSpec.hs view
@@ -1,326 +1,347 @@+{-# LANGUAGE DuplicateRecordFields #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE ImportQualifiedPost #-}+{-# LANGUAGE OverloadedRecordDot #-} {-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards #-} module Haskoin.TransactionSpec (spec) where -import qualified Data.ByteString as B-import Data.Either-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 Haskoin.Address-import Haskoin.Constants-import Haskoin.Keys-import Haskoin.Script-import Haskoin.Transaction-import Haskoin.Util-import Haskoin.Util.Arbitrary-import Test.Hspec-import Test.Hspec.QuickCheck-import Test.HUnit-import Test.QuickCheck--serialVals :: [SerialBox]-serialVals =- [ SerialBox $ arbitraryTx =<< arbitraryNetwork- , SerialBox $ arbitraryWitnessTx =<< arbitraryNetwork- , SerialBox $ arbitraryLegacyTx =<< arbitraryNetwork- , SerialBox $ arbitraryTxIn =<< arbitraryNetwork- , SerialBox $ arbitraryTxOut =<< arbitraryNetwork- , SerialBox arbitraryOutPoint- ]--readVals :: [ReadBox]-readVals =- [ ReadBox arbitraryTxHash- , ReadBox $ arbitraryTx =<< arbitraryNetwork- , ReadBox $ arbitraryTxIn =<< arbitraryNetwork- , ReadBox $ arbitraryTxOut =<< arbitraryNetwork- , ReadBox arbitraryOutPoint- ]+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 -jsonVals :: [JsonBox]-jsonVals =- [ JsonBox arbitraryTxHash- , JsonBox $ arbitraryTx =<< arbitraryNetwork- , JsonBox $ arbitraryWitnessTx =<< arbitraryNetwork- , JsonBox $ arbitraryLegacyTx =<< arbitraryNetwork- , JsonBox $ arbitraryTxIn =<< arbitraryNetwork- , JsonBox $ arbitraryTxOut =<< arbitraryNetwork- , JsonBox arbitraryOutPoint- ]+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 = do- testIdentity serialVals readVals jsonVals []- 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- prop "guess transaction size" $- forAll arbitraryNetwork $ \net ->- forAll (arbitraryAddrOnlyTxFull net) (testGuessSize net)- 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) (testDetSignTx net)- prop "sign and validate (nested) transaction" $- forAll arbitraryNetwork $ \net ->- forAll (arbitrarySigningData net) (testDetSignNestedTx net)- prop "merge partially signed transactions" $- forAll arbitraryNetwork $ \net ->- property $ forAll (arbitraryPartialTxs net) (testMergeTx net)- describe "Transaction vectors" $ do- it "compute txid from tx" $ mapM_ testTxidVector txidVectors- it "build pkhash transaction (generated from bitcoind)" $- mapM_ testPKHashVector pkHashVectors+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)+ assertEqual "txid" (Just tid) (txHashToHex . txHash <$> txM) where- txM = eitherToMaybe . S.decode =<< decodeHex tx+ 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"- )- ]+ [ ( "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 :: ([(Text, Word32)], [(Text, Word64)], Text) -> Assertion-testPKHashVector (is, os, res) =- assertEqual "Build PKHash Tx" (Right res) (encodeHex . S.encode <$> txE)+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 (map f is) os+ 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")- ]+ [ ( [ ( "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 -> Address -> TestCoin -> Bool-testBuildAddrTx net a (TestCoin v)- | isPubKeyAddress a = Right (PayPKHash (getAddrHash160 a)) == out- | isScriptAddress a = Right (PayScriptHash (getAddrHash160 a)) == out- | otherwise = undefined+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- tx = buildAddrTx net [] [(fromJust (addrToText net a), v)]- out =- decodeOutputBS $- scriptOutput $- head $ txOut (fromRight (error "Could not build transaction") tx)+ 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 -> Tx -> Bool-testGuessSize net tx =- guess >= len && guess <= len + 2 * delta+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 $ S.encode tx- ins = map f $ txIn tx- f i =- fromRight (error "Could not decode input") $- decodeInputBS net $ scriptInput i+ 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- (fromRight (error "Could not decode transaction output") .- decodeOutputBS . scriptOutput) $- txOut tx+ 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 ==>+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+ 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+ 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+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+testDetSignTx :: Network -> Ctx -> (Tx, [SigInput], [PrivateKey]) -> Bool+testDetSignTx net ctx (tx, sigis, prv) =+ not verify1 && not verify2 && verify3 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+ 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 -> (Tx, [SigInput], [SecKeyI]) -> Bool-testDetSignNestedTx net (tx, sigis, prv) =- not (verifyStdTx net tx verData) &&- not (verifyStdTx net txSigP verData) &&- verifyStdTx net txSigC verData+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 =- fromRight (error "Could not decode transaction") $- signNestedWitnessTx net tx sigis (secKeyData <$> tail prv)+ either error id $+ signNestedWitnessTx net ctx tx sigis ((.key) <$> tail prv) txSigC =- fromRight (error "Could not decode transaction") $- signNestedWitnessTx net txSigP sigis [secKeyData (head prv)]+ either error id $+ signNestedWitnessTx net ctx txSigP sigis [(head prv).key] verData = handleSegwit <$> sigis handleSegwit (SigInput s v o _ _)- | isSegwit s = (toP2SH $ encodeOutput s, v, o)- | otherwise = (s, v, o)+ | isSegwit s = (toP2SH (encodeOutput ctx 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))- ]+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 txs outs+ mergeRes = mergeTxs net ctx txs outs mergedTx = fromRight (error "Could not merge") mergeRes- isValid = verifyStdTx net mergedTx outs+ isValid = verifyStdTx net ctx mergedTx outs enoughSigs = all (\(m, c) -> c >= m) sigMap- sigMap =- map (\((_, _, _, m, _), inp) -> (m, sigCnt inp)) $- zip os $ txIn mergedTx+ sigFun (_, _, _, m, _) inp = (m, sigCnt inp)+ sigMap = zipWith sigFun os mergedTx.inputs 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"+ case unmarshal (net, ctx) inp.script of+ Right (RegularInput (SpendMulSig sigs)) -> length sigs+ Right (ScriptHashInput (SpendMulSig sigs) _) -> length sigs+ _ -> error "Invalid input script type"
test/Haskoin/UtilSpec.hs view
@@ -1,58 +1,67 @@-module Haskoin.UtilSpec- ( spec- , customCerealID- , readTestFile- ) where+{-# LANGUAGE ImportQualifiedPost #-} -import Data.Aeson (FromJSON, ToJSON)-import qualified Data.Aeson as A-import Data.Aeson.Encoding (encodingToLazyByteString)-import Data.Aeson.Types (Parser, parseMaybe)-import qualified Data.ByteString as BS-import Data.Either (fromLeft, fromRight, isLeft, isRight)-import Data.Foldable (toList)-import Data.List (permutations)-import Data.Map.Strict (singleton)-import Data.Maybe-import qualified Data.Sequence as Seq-import Data.Serialize as S-import Haskoin.Util-import Haskoin.Util.Arbitrary-import Test.Hspec-import Test.Hspec.QuickCheck-import Test.QuickCheck+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- 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+ 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 :: BS.ByteString -> Bool+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)+ 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+ if null as+ then 0+ else i `mod` length as bs = permutations as !! idx testMatchTemplateLen :: [Int] -> [Int] -> Bool@@ -67,14 +76,3 @@ testMaybeToEither :: Maybe Int -> String -> Bool testMaybeToEither (Just v) str = maybeToEither str (Just v) == Right v testMaybeToEither m str = maybeToEither str m == Left str--{-- Test Utilities --}--customCerealID :: Eq a => Get a -> Putter a -> a -> Bool-customCerealID g p a = runGet g (runPut (p a)) == Right a--readTestFile :: A.FromJSON a => FilePath -> IO a-readTestFile fp = do- fileM <- A.decodeFileStrict $ "data/" <> fp- maybe (error $ "Could not read test file " <> fp) return fileM-