Backwards-incompatible modernisation of package

- Shorten all field names ignoring 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 bytestring builders for all JSON toEncoding implementations.
- Use ormolu for formatting.
- Simplify module organisation.
- Strongly break backwards compatibility.
This commit is contained in:
JP Rupp 2023-07-28 19:48:43 +01:00
parent 64ceb860e1
commit 8fb472f60d
No known key found for this signature in database
GPG Key ID: 93391726EAFA0C5D
83 changed files with 15974 additions and 15777 deletions

View File

@ -1,289 +1,420 @@
# 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.21.2
## [1.0.0]
### 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
## [0.21.1] - 2021-12-13
### Changed
- Make Base58 faster.
## 0.21.0
## [0.21.0] - 2022-11-23
### Added
- BCH Testnet4 support.
### Changed
- Use a newtype for Fingerprint, which uses an 8 digit hex string for various
instances. This fixes inconsistent (de)serialization across the package.
- 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
## [0.20.5] - 2021-09-13
### Added
- Support Bech32m address format for Taproot.
## 0.20.4
## [0.20.4] - 2021-06-08
### Fixed
- Add missing case for witness version.
## 0.20.3
## [0.20.3] - 2021-05-17
### Fixed
- Allow unknown inv types.
## 0.20.2
## [0.20.2] - 2021-05-17
### Fixed
- Allow unknown messages of zero length.
## 0.20.1
## [0.20.1] - 2021-05-14
### Fixed
- Correct case where binary search returned the wrong element.
## 0.20.0
## [0.20.0] - 2021-02-22
### Chaged
- Use bytes instead of binary or cereal.
## 0.19.0
## [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
## [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
### Changed
- Use the C-preprocessor to handle versions of `base16-bytestring` including 1.0
(with a breaking API change)
## [0.17.1] - 2020-11-02
### Changed
- Use the C-preprocessor to handle versions of `base16-bytestring`
## [0.17.0] - 2020-10-21
## 0.17.0
### 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
### Added
- Add reward computation to block functions.
- Add PSBT [BIP-174](https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki) types and functions
## [0.8.4] - 2018-12-05
## 0.8.3
### Added
- Add reward computation to block functions.
- Add PSBT BIP-174 types and functions.
## [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.
@ -299,6 +430,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
- 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`.
@ -309,6 +441,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
- 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.

View File

@ -17,4 +17,5 @@ Haskoin Core is a library of Bitcoin and Bitcoin Cash functions written in Haske
## Contributing
Please use `ormolu` (or `fourmolu`) to format code prior to submission. See `scripts/pre-commit.sh` for an example pre-commit hook.
All code is formatted with [Ormolu](https://github.com/tweag/ormolu).
Convenience formatting script available at [scripts/format](scripts/format)

View File

@ -1,11 +1,11 @@
cabal-version: 1.12
-- This file has been generated from package.yaml by hpack version 0.35.1.
-- This file has been generated from package.yaml by hpack version 0.35.2.
--
-- see: https://github.com/sol/hpack
name: haskoin-core
version: 0.22.0
version: 1.0.0
synopsis: Bitcoin & Bitcoin Cash library for Haskell
description: Please see the README on GitHub at <https://github.com/haskoin/haskoin-core#readme>
category: Bitcoin, Finance, Network
@ -25,9 +25,9 @@ extra-source-files:
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
@ -51,18 +51,18 @@ library
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.Data
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
@ -87,8 +87,10 @@ library
Haskoin.Util.Arbitrary.Script
Haskoin.Util.Arbitrary.Transaction
Haskoin.Util.Arbitrary.Util
Haskoin.Util.Helpers
Haskoin.Util.Marshal
other-modules:
Haskoin.Keys.Extended.Internal
Haskoin.Crypto.Keys.Extended.Internal
hs-source-dirs:
src
build-depends:
@ -114,7 +116,7 @@ library
, network >=3.1.1.1
, safe >=0.3.18
, scientific >=0.3.6.2
, secp256k1-haskell >=0.7.0
, secp256k1-haskell >=1.0.0
, split >=0.2.3.3
, string-conversions >=0.4.0.1
, text >=1.2.3.0
@ -133,10 +135,10 @@ test-suite spec
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
@ -174,7 +176,7 @@ test-suite spec
, network >=3.1.1.1
, safe >=0.3.18
, scientific >=0.3.6.2
, secp256k1-haskell >=0.7.0
, secp256k1-haskell >=1.0.0
, split >=0.2.3.3
, string-conversions >=0.4.0.1
, text >=1.2.3.0

6
hie.yaml Normal file
View File

@ -0,0 +1,6 @@
cradle:
stack:
- path: "./src"
component: haskoin-core:lib
- path: "./test"
component: haskoin-core:test:spec

View File

@ -1,5 +1,5 @@
name: haskoin-core
version: 0.22.0
version: 1.0.0
synopsis: Bitcoin & Bitcoin Cash library for Haskell
description: Please see the README on GitHub at <https://github.com/haskoin/haskoin-core#readme>
category: Bitcoin, Finance, Network
@ -41,7 +41,7 @@ dependencies:
- split >= 0.2.3.3
- safe >= 0.3.18
- scientific >= 0.3.6.2
- secp256k1-haskell >= 0.7.0
- secp256k1-haskell >= 1.0.0
- string-conversions >= 0.4.0.1
- text >= 1.2.3.0
- time >= 1.9.3
@ -51,7 +51,7 @@ dependencies:
library:
source-dirs: src
other-modules:
Haskoin.Keys.Extended.Internal
Haskoin.Crypto.Keys.Extended.Internal
when:
- condition: false
other-modules: Paths_haskoin_core

View File

@ -1,4 +1,4 @@
#!/usr/bin/env bash
find src -type f -name "*.hs" | xargs fourmolu -i
find test -type f -name "*.hs" | xargs fourmolu -i
find src -type f -name "*.hs" | xargs ormolu -i
find test -type f -name "*.hs" | xargs ormolu -i

View File

@ -1,34 +1,28 @@
{- |
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 Data,
module Constants,
module Address,
-- |
-- 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 Block,
module Transaction,
module Script,
module Keys,
module Crypto,
module Network,
module Util,
) where
)
where
import Haskoin.Address as Address
import Haskoin.Block as Block
import Haskoin.Constants as Constants
import Haskoin.Crypto as Crypto
import Haskoin.Data as Data
import Haskoin.Keys as Keys
import Haskoin.Network as Network
import Haskoin.Script as Script
import Haskoin.Transaction as Transaction

View File

@ -1,22 +1,26 @@
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE ImportQualifiedPost #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedRecordDot #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE NoFieldSelectors #-}
{- |
Module : Haskoin.Address
Copyright : No rights reserved
License : MIT
Maintainer : jprupp@protonmail.ch
Stability : experimental
Portability : POSIX
Base58, CashAddr, Bech32 address and WIF private key serialization support.
-}
module Haskoin.Address (
-- * Addresses
-- |
-- 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,
@ -28,9 +32,6 @@ module Haskoin.Address (
bech32ToAddr,
cashToAddr,
base58ToAddr,
addrToJSON,
addrToEncoding,
addrFromJSON,
pubKeyAddr,
pubKeyWitnessAddr,
pubKeyCompatWitnessAddr,
@ -51,35 +52,38 @@ module Haskoin.Address (
module Haskoin.Address.Base58,
module Haskoin.Address.Bech32,
module Haskoin.Address.CashAddr,
) where
)
where
import Control.Applicative
import Control.Applicative (Alternative ((<|>)))
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 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 qualified Data.ByteString as B
import Data.Bytes.Get
import Data.Bytes.Put
import Data.Bytes.Serial
import Data.Hashable
import Data.Maybe
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 qualified Data.Text as T
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
import Haskoin.Data
import Haskoin.Keys.Common
import Haskoin.Script
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.
@ -87,27 +91,27 @@ data Address
= -- | pay to public key hash (regular)
PubKeyAddress
{ -- | RIPEMD160 hash of public key's SHA256 hash
getAddrHash160 :: !Hash160
hash160 :: !Hash160
}
| -- | pay to script hash
ScriptAddress
{ -- | RIPEMD160 hash of script's SHA256 hash
getAddrHash160 :: !Hash160
hash160 :: !Hash160
}
| -- | pay to witness public key hash
WitnessPubKeyAddress
{ -- | RIPEMD160 hash of public key's SHA256 hash
getAddrHash160 :: !Hash160
hash160 :: !Hash160
}
| -- | pay to witness script hash
WitnessScriptAddress
{ -- | HASH256 hash of script
getAddrHash256 :: !Hash256
hash256 :: !Hash256
}
| -- | other witness address
WitnessAddress
{ getAddrVersion :: !Word8
, getAddrData :: !ByteString
{ version :: !Word8,
bytes :: !ByteString
}
deriving
(Eq, Ord, Generic, Show, Read, Hashable, NFData)
@ -138,7 +142,8 @@ instance Serial Address where
0x02 -> WitnessPubKeyAddress <$> deserialize
0x03 -> WitnessScriptAddress <$> deserialize
0x04 ->
WitnessAddress <$> getWord8
WitnessAddress
<$> getWord8
<*> (getByteString . fromIntegral =<< getWord64be)
b ->
fail . T.unpack $
@ -163,9 +168,8 @@ isScriptAddress :: Address -> Bool
isScriptAddress ScriptAddress {} = True
isScriptAddress _ = False
{- | 'Address' pays to a witness public key hash. Only valid for SegWit
networks.
-}
-- | 'Address' pays to a witness public key hash. Only valid for SegWit
-- networks.
isWitnessPubKeyAddress :: Address -> Bool
isWitnessPubKeyAddress WitnessPubKeyAddress {} = True
isWitnessPubKeyAddress _ = False
@ -178,43 +182,35 @@ 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
{- | 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
{- | Convert address to human-readable string. Uses 'Base58', 'Bech32', or
'CashAddr' depending on network.
-}
-- | 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) =
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{getAddrHash160 = h}
| isNothing (getCashAddrPrefix net) =
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{getAddrHash160 = h} = do
hrp <- getBech32Prefix net
addrToText net WitnessPubKeyAddress {hash160 = h} = do
hrp <- net.bech32Prefix
segwitEncode hrp 0 (B.unpack (runPutS $ serialize h))
addrToText net WitnessScriptAddress{getAddrHash256 = h} = do
hrp <- getBech32Prefix net
addrToText net WitnessScriptAddress {hash256 = h} = do
hrp <- net.bech32Prefix
segwitEncode hrp 0 (B.unpack (runPutS $ serialize h))
addrToText net WitnessAddress{getAddrVersion = v, getAddrData = d} = do
hrp <- getBech32Prefix net
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.
@ -232,7 +228,7 @@ cashToAddr net txt = do
bech32ToAddr :: Network -> Text -> Maybe Address
bech32ToAddr net txt = do
hrp <- getBech32Prefix net
hrp <- net.bech32Prefix
(ver, bs) <- second B.pack <$> segwitDecode hrp txt
case ver of
0 -> case B.length bs of
@ -245,54 +241,52 @@ base58ToAddr :: Network -> Text -> Maybe Address
base58ToAddr net txt =
eitherToMaybe . runGetS (base58get net) =<< decodeBase58Check txt
base58get :: MonadGet m => Network -> m Address
base58get :: (MonadGet m) => Network -> m Address
base58get net = do
pfx <- getWord8
addr <- deserialize
f pfx addr
where
f x a
| x == getAddrPrefix net = return $ PubKeyAddress a
| x == getScriptPrefix net = return $ ScriptAddress a
| x == net.addrPrefix = return $ PubKeyAddress a
| x == net.scriptPrefix = return $ ScriptAddress a
| otherwise = fail "Does not recognize address prefix"
base58put :: MonadPut m => Network -> Address -> m ()
base58put :: (MonadPut m) => Network -> Address -> m ()
base58put net (PubKeyAddress h) = do
putWord8 (getAddrPrefix net)
putWord8 net.addrPrefix
serialize h
base58put net (ScriptAddress h) = do
putWord8 (getScriptPrefix net)
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 . runPutS . serialize
pubKeyAddr :: Ctx -> PublicKey -> Address
pubKeyAddr ctx = PubKeyAddress . addressHash . marshal ctx
-- | Obtain a standard pay-to-public-key-hash (P2PKH) address from a 'Hash160'.
p2pkhAddr :: Hash160 -> Address
p2pkhAddr = PubKeyAddress
{- | Obtain a SegWit pay-to-witness-public-key-hash (P2WPKH) address from a
public key.
-}
pubKeyWitnessAddr :: PubKeyI -> Address
pubKeyWitnessAddr = WitnessPubKeyAddress . addressHash . runPutS . serialize
-- | Obtain a SegWit pay-to-witness-public-key-hash (P2WPKH) address from a
-- public key.
pubKeyWitnessAddr :: Ctx -> PublicKey -> Address
pubKeyWitnessAddr ctx =
WitnessPubKeyAddress . addressHash . marshal ctx
-- | Obtain a backwards-compatible SegWit P2SH-P2WPKH address from a public key.
pubKeyCompatWitnessAddr :: PubKeyI -> Address
pubKeyCompatWitnessAddr =
pubKeyCompatWitnessAddr :: Ctx -> PublicKey -> Address
pubKeyCompatWitnessAddr ctx =
p2shAddr
. addressHash
. encodeOutputBS
. marshal ctx
. PayWitnessPKHash
. addressHash
. runPutS
. serialize
. marshal ctx
{- | Obtain a SegWit pay-to-witness-public-key-hash (P2WPKH) address from a
'Hash160'.
-}
-- | Obtain a SegWit pay-to-witness-public-key-hash (P2WPKH) address from a
-- 'Hash160'.
p2wpkhAddr :: Hash160 -> Address
p2wpkhAddr = WitnessPubKeyAddress
@ -305,23 +299,21 @@ p2wshAddr :: Hash256 -> Address
p2wshAddr = WitnessScriptAddress
-- | Compute a standard pay-to-script-hash (P2SH) address for an output script.
payToScriptAddress :: ScriptOutput -> Address
payToScriptAddress = p2shAddr . addressHash . encodeOutputBS
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
-- | Compute a SegWit pay-to-witness-script-hash (P2WSH) address for an output
-- script.
payToWitnessScriptAddress :: Ctx -> ScriptOutput -> Address
payToWitnessScriptAddress ctx = p2wshAddr . sha256 . marshal ctx
-- | Compute a backwards-compatible SegWit P2SH-P2WSH address.
payToNestedScriptAddress :: 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.
-}
-- | 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
@ -332,39 +324,43 @@ addressToOutput =
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 = runPutS . serialize . 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 =
outputAddress :: Ctx -> ScriptOutput -> Maybe Address
outputAddress ctx =
\case
PayPKHash h -> Just $ PubKeyAddress h
PayScriptHash h -> Just $ ScriptAddress h
PayPK k -> Just $ pubKeyAddr k
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 =
inputAddress :: Ctx -> ScriptInput -> Maybe Address
inputAddress ctx =
\case
(RegularInput (SpendPKHash _ key)) -> Just $ pubKeyAddr key
(ScriptHashInput _ rdm) -> Just $ payToScriptAddress rdm
(RegularInput (SpendPKHash _ key)) -> Just $ pubKeyAddr ctx key
(ScriptHashInput _ rdm) -> Just $ payToScriptAddress ctx rdm
_ -> Nothing

View File

@ -1,41 +1,43 @@
{-# LANGUAGE ImportQualifiedPost #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE NoFieldSelectors #-}
{- |
Module : Haskoin.Address.Base58
Copyright : No rights reserved
License : MIT
Maintainer : jprupp@protonmail.ch
Stability : experimental
Portability : POSIX
Support for legacy 'Base58' addresses. Superseded by Bech32 for Bitcoin SegWit
(BTC) and CashAddr for Bitcoin Cash (BCH).
-}
module Haskoin.Address.Base58 (
-- * Base58
-- |
-- 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
)
where
import Control.Monad
import Data.Array
import Control.Monad (guard)
import Data.Array (Array, assocs, listArray, (!), (//))
import Data.ByteString (ByteString)
import qualified Data.ByteString as BS
import qualified Data.ByteString.Char8 as C
import Data.Bytes.Get
import Data.Bytes.Put
import Data.Bytes.Serial
import Data.Char
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 qualified Data.Text as T
import Data.Word
import Haskoin.Crypto.Hash
import Haskoin.Util
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.
@ -46,16 +48,19 @@ b58Data :: ByteString
b58Data = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
b58Array :: Array Int Word8
b58Array = listArray (0, 57) (BS.unpack b58Data)
b58Array = listArray (0, 57) (B.unpack b58Data)
b58InvArray :: Array Word8 (Maybe Int)
b58InvArray = listArray (minBound, maxBound) (repeat Nothing) // map swap (assocs b58Array)
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.
-}
-- | Convert a number less than or equal to provided integer into a 'Base58'
-- character.
b58 :: Int -> Word8
b58 = (b58Array !)
@ -63,9 +68,8 @@ b58 = (b58Array !)
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.
-}
-- | Encode an arbitrary-length 'Integer' into a 'Base58' string. Leading zeroes
-- will not be part of the resulting string.
encodeBase58I :: Integer -> Base58
encodeBase58I i = cs $ showIntAtBase 58 (chr . fromIntegral . b58) i ""
@ -81,44 +85,41 @@ decodeBase58I s =
go = listToMaybe $ readInt 58 p f (cs s)
e = error "Could not decode base58"
{- | Encode an arbitrary 'ByteString' into a its 'Base58' representation,
preserving leading zeroes.
-}
-- | Encode an arbitrary 'ByteString' into a its 'Base58' representation,
-- preserving leading zeroes.
encodeBase58 :: ByteString -> Base58
encodeBase58 bs =
l <> r
where
(z, b) = BS.span (== 0) bs
l = cs $ BS.replicate (BS.length z) (b58 0) -- preserve leading 0's
(z, b) = B.span (== 0) bs
l = cs $ B.replicate (B.length z) (b58 0) -- preserve leading 0's
r
| BS.null b = T.empty
| 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 (== b58 0) (cs t)
prefix = BS.replicate (BS.length z) 0 -- preserve leading 1's
(z, b) = B.span (== b58 0) (cs t)
prefix = B.replicate (B.length z) 0 -- preserve leading 1's
r
| BS.null b = Just BS.empty
| 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'.
-}
-- | 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 $ runPutS $ serialize $ 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.
-}
-- | 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 == runPutS (serialize (checkSum32 res))
let (res, chk) = B.splitAt (B.length rs - 4) rs
guard $ chk == (runPutS . serialize . checkSum32) res
return res

View File

@ -1,19 +1,20 @@
{-# LANGUAGE ImportQualifiedPost #-}
{-# LANGUAGE MultiWayIf #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE NoFieldSelectors #-}
{- |
Module : Haskoin.Address.Base58
Copyright : No rights reserved
License : MIT
Maintainer : jprupp@protonmail.ch
Stability : experimental
Portability : POSIX
Support for Bitcoin SegWit (BTC) Bech32 addresses. This module is a modified
version of Marko Bencun's reference implementation.
-}
module Haskoin.Address.Bech32 (
-- * Bech32
-- |
-- Module : Haskoin.Address.Base58
-- Copyright : No rights reserved
-- License : MIT
-- Maintainer : jprupp@protonmail.ch
-- Stability : experimental
-- Portability : POSIX
--
-- Support for Bitcoin SegWit (BTC) Bech32 addresses. This module is a modified
-- version of Marko Bencun's reference implementation.
module Haskoin.Address.Bech32
( -- * Bech32
HRP,
Bech32,
Bech32Encoding (..),
@ -28,19 +29,20 @@ module Haskoin.Address.Bech32 (
Word5 (..),
word5,
fromWord5,
) where
)
where
import Control.Monad (guard)
import Data.Array (
Array,
import Data.Array
( Array,
assocs,
bounds,
listArray,
(!),
(//),
)
import Data.Bits (
Bits,
import Data.Bits
( Bits,
testBit,
unsafeShiftL,
unsafeShiftR,
@ -48,14 +50,14 @@ import Data.Bits (
(.&.),
(.|.),
)
import qualified Data.ByteString as B
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 qualified Data.Text as T
import qualified Data.Text.Encoding as E
import Data.Text qualified as T
import Data.Text.Encoding qualified as E
import Data.Word (Word8)
data Bech32Encoding = Bech32 | Bech32m
@ -70,7 +72,7 @@ type HRP = Text
-- | Data part of 'Bech32' address.
type Data = [Word8]
(.>>.), (.<<.) :: Bits a => a -> Int -> a
(.>>.), (.<<.) :: (Bits a) => a -> Int -> a
(.>>.) = unsafeShiftR
(.<<.) = unsafeShiftL
@ -85,13 +87,13 @@ instance Ix Word5 where
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 #-}
@ -121,9 +123,8 @@ bech32Polymod values = foldl' go 1 values .&. 0x3fffffff
generator = [0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3]
chk' = chk .<<. 5 `xor` fromWord5 value
{- | Convert human-readable part of 'Bech32' string into a list of five-bit
words.
-}
-- | Convert human-readable part of 'Bech32' string into a list of five-bit
-- words.
bech32HRPExpand :: HRP -> [Word5]
bech32HRPExpand hrp =
map (UnsafeWord5 . (.>>. 5)) hrpBytes
@ -157,10 +158,9 @@ bech32VerifyChecksum hrp dat =
maxBech32Length :: Int
maxBech32Length = 90
{- | Encode string of five-bit words into 'Bech32' using a provided
human-readable part. Can fail if 'HRP' is invalid or result would be longer
than 90 characters.
-}
-- | Encode string of five-bit words into 'Bech32' using a provided
-- human-readable part. Can fail if 'HRP' is invalid or result would be longer
-- than 90 characters.
bech32Encode :: Bech32Encoding -> HRP -> [Word5] -> Maybe Bech32
bech32Encode enc hrp dat = do
guard $ checkHRP hrp
@ -176,9 +176,8 @@ 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.
-}
-- | Decode human-readable 'Bech32' string into a human-readable part and a
-- string of five-bit words.
bech32Decode :: Bech32 -> Maybe (Bech32Encoding, HRP, [Word5])
bech32Decode bech32 = do
guard $ T.length bech32 <= maxBech32Length
@ -206,12 +205,11 @@ noPadding frombits bits padValue result = do
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]
-- | Big endian conversion of a bytestring from base \(2^{frombits}\) to base
-- \(2^{tobits}\). {frombits} and {twobits} must be positive and
-- \(2^{frombits}\) and \(2^{tobits}\) must be smaller than the size of Word.
-- Every value in 'dat' must be strictly smaller than \(2^{frombits}\).
convertBits :: (Functor f) => [Word] -> Int -> Int -> Pad f -> f [Word]
convertBits dat frombits tobits pad = concat . reverse <$> go dat 0 0 []
where
go [] acc bits result =
@ -257,9 +255,8 @@ segwitDecode hrp addr = do
guard $ segwitCheck enc witver decoded
return (witver, decoded)
{- | Encode 'Data' as a SegWit 'Bech32' address. Needs human-readable part and
witness program version.
-}
-- | Encode 'Data' as a SegWit 'Bech32' address. Needs human-readable part and
-- witness program version.
segwitEncode :: HRP -> Word8 -> Data -> Maybe Text
segwitEncode hrp witver witprog = do
guard $ segwitCheck enc witver witprog

View File

@ -1,17 +1,20 @@
{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE ImportQualifiedPost #-}
{-# LANGUAGE OverloadedRecordDot #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE NoFieldSelectors #-}
{- |
Module : Haskoin.Address.CashAddr
Copyright : No rights reserved
License : MIT
Maintainer : jprupp@protonmail.ch
Stability : experimental
Portability : POSIX
Support for Bitcoin Cash (BCH) CashAddr format.
-}
module Haskoin.Address.CashAddr (
-- * CashAddr
-- |
-- 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,
@ -22,26 +25,35 @@ module Haskoin.Address.CashAddr (
cash32encodeType,
cash32decode,
cash32encode,
) where
)
where
import Control.Monad
import Control.Monad (guard)
import Data.Bits
( Bits
( shiftL,
shiftR,
testBit,
xor,
(.&.),
(.|.)
),
)
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.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 qualified Data.Text as T
import qualified Data.Text.Encoding as E
import Data.Word
import Haskoin.Data
import Haskoin.Util
import Data.Text qualified as T
import Data.Text.Encoding (decodeUtf8, encodeUtf8)
import Data.Word (Word64, Word8)
import Haskoin.Network.Data (Network (cashAddrPrefix))
import Haskoin.Util.Helpers (convertBits)
{- | 'CashAddr' prefix, usually shown before the colon in addresses, but sometimes
omitted. It is used in the checksum calculation to avoid parsing an address
from the wrong network.
-}
-- | 'CashAddr' prefix, usually shown before the colon in addresses, but sometimes
-- omitted. It is used in the checksum calculation to avoid parsing an address
-- from the wrong network.
type CashPrefix = Text
-- | 'CashAddr' version, until new address schemes appear it will be zero.
@ -50,9 +62,8 @@ type CashVersion = Word8
-- | High level 'CashAddr' human-reabale string, with explicit or implicit prefix.
type CashAddr = Text
{- | Low level 'Cash32' is the human-readable low-level encoding used by 'CashAddr'. It
need not encode a valid address but any binary data.
-}
-- | 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.
@ -63,29 +74,26 @@ charset = "qpzry9x8gf2tvdw0s3jn54khce6mua7l"
base32char :: Char -> Maybe Word8
base32char = fmap fromIntegral . (`elemIndex` charset)
{- | High-Level: decode 'CashAddr' string if it is valid for the
provided 'Network'. Prefix may be omitted from the string.
-}
-- | High-Level: decode 'CashAddr' string if it is valid for the
-- provided 'Network'. Prefix may be omitted from the string.
cashAddrDecode :: Network -> CashAddr -> Maybe (CashVersion, ByteString)
cashAddrDecode net ca = do
epfx <- getCashAddrPrefix net
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.
-}
-- | 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
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.
-}
-- | 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')
@ -99,9 +107,8 @@ cash32decodeType ca' = do
where
ca = T.toLower ca'
{- | Mid-Level: encode 'CashAddr' string containing arbitrary prefix and
'CashVersion'. Length must be among those allowed by the standard.
-}
-- | Mid-Level: encode 'CashAddr' string containing arbitrary prefix and
-- 'CashVersion'. Length must be among those allowed by the standard.
cash32encodeType :: CashPrefix -> CashVersion -> ByteString -> Maybe Cash32
cash32encodeType pfx cv bs = do
let len = B.length bs
@ -109,9 +116,8 @@ cash32encodeType pfx cv bs = do
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.
-}
-- | 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'
@ -128,26 +134,24 @@ cash32decode text = do
bb = B.take (B.length b32 - 8) b32
guard (verifyCash32Polymod cs)
let out = toBase256 bb
return (E.decodeUtf8 pfx, out)
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.
-}
-- | 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)
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 <> ":" <> E.decodeUtf8 c32
in pfx <> ":" <> decodeUtf8 c32
{- | Convert base of 'ByteString' from eight bits per byte to five bits per
byte, adding padding as necessary.
-}
-- | Convert base of 'ByteString' from eight bits per byte to five bits per
-- byte, adding padding as necessary.
toBase32 :: ByteString -> ByteString
toBase32 =
B.pack
@ -157,9 +161,8 @@ toBase32 =
. 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.
-}
-- | 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
@ -179,9 +182,8 @@ decodeVersionByte vb = do
len = ls !! fromIntegral (vb .&. 0x07)
ls = [20, 24, 28, 32, 40, 48, 56, 64]
{- | Encode 'CashVersion' and length into version byte. Fail if version is
larger than five bits, or length incorrect, since that is invalid.
-}
-- | Encode 'CashVersion' and length into version byte. Fail if version is
-- larger than five bits, or length incorrect, since that is invalid.
encodeVersionByte :: CashVersion -> Int -> Maybe Word8
encodeVersionByte ver len = do
guard (ver == ver .&. 0x0f)

View File

@ -1,18 +1,18 @@
{- |
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
-- 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
)
where
import Haskoin.Block.Common
import Haskoin.Block.Headers

View File

@ -1,19 +1,21 @@
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE ImportQualifiedPost #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE NoFieldSelectors #-}
{- |
Module : Haskoin.Block.Common
Copyright : No rights reserved
License : MIT
Maintainer : jprupp@protonmail.ch
Stability : experimental
Portability : POSIX
Common data types and functions to handle blocks from the block chain.
-}
module Haskoin.Block.Common (
-- * Blocks
-- |
-- 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,
@ -29,53 +31,34 @@ module Haskoin.Block.Common (
Headers (..),
decodeCompact,
encodeCompact,
) where
)
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 Control.Monad
import Data.Aeson
import Data.Aeson.Encoding
import Data.Binary (Binary (..))
import Data.Bits (shiftL, shiftR, (.&.), (.|.))
import qualified Data.ByteString as B
import Data.Bits
import Data.ByteString qualified as B
import Data.ByteString.Builder (char7)
import qualified Data.ByteString.Lazy as BL
import Data.Bytes.Get (
MonadGet,
getWord32le,
runGetL,
runGetS,
)
import Data.Bytes.Put (
MonadPut,
putWord32le,
runPutL,
runPutS,
)
import Data.Bytes.Serial (Serial (..))
import Data.Hashable (Hashable)
import Data.Maybe (fromMaybe)
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 (IsString, fromString)
import Data.String.Conversions (cs)
import Data.String
import Data.String.Conversions
import Data.Text (Text)
import Data.Word (Word32)
import GHC.Generics (Generic)
import Data.Word
import GHC.Generics
import Haskoin.Crypto.Hash
import Haskoin.Network.Common
import Haskoin.Transaction.Common
import Haskoin.Util
import qualified Text.Read as R
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
@ -85,8 +68,8 @@ type Timestamp = Word32
-- | Block header and transactions.
data Block = Block
{ blockHeader :: !BlockHeader
, blockTxns :: ![Tx]
{ header :: !BlockHeader,
txs :: ![Tx]
}
deriving (Eq, Show, Read, Generic, Hashable, NFData)
@ -110,8 +93,14 @@ instance Binary Block where
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 =
@ -119,9 +108,7 @@ instance FromJSON Block where
Block <$> o .: "header" <*> o .: "transactions"
-- | Block header hash. To be serialized reversed for display purposes.
newtype BlockHash = BlockHash
{ getBlockHash :: Hash256
}
newtype BlockHash = BlockHash {get :: Hash256}
deriving (Eq, Ord, Generic, Hashable, Serial, NFData)
instance Serialize BlockHash where
@ -142,8 +129,9 @@ instance Read BlockHash where
instance IsString BlockHash where
fromString s =
let e = error "Could not read block hash from hex string"
in fromMaybe e $ hexToBlockHash $ cs s
fromMaybe (error "Could not read block hash from hex string") $
hexToBlockHash $
cs s
instance FromJSON BlockHash where
parseJSON =
@ -152,46 +140,44 @@ instance FromJSON BlockHash where
instance ToJSON BlockHash where
toJSON = String . blockHashToHex
toEncoding h =
unsafeToEncoding $
char7 '"'
<> hexBuilder (BL.reverse (runPutL (serialize h)))
<> char7 '"'
toEncoding = hexEncoding . L.reverse . runPutL . serialize
{- | Block hashes are reversed with respect to the in-memory byte order in a
block hash when displayed.
-}
-- | Block hashes are reversed with respect to the in-memory byte order in a
-- block hash when displayed.
blockHashToHex :: BlockHash -> Text
blockHashToHex (BlockHash h) = encodeHex (B.reverse (runPutS (serialize h)))
{- | Convert a human-readable hex block hash into a 'BlockHash'. Bytes are
reversed as normal.
-}
-- | Convert a human-readable hex block hash into a 'BlockHash'. Bytes are
-- reversed as normal.
hexToBlockHash :: Text -> Maybe BlockHash
hexToBlockHash hex = do
bs <- B.reverse <$> decodeHex hex
h <- eitherToMaybe (runGetS deserialize bs)
return $ BlockHash h
{- | Data type recording information of a 'Block'. The hash of a block is
defined as the hash of this data structure, serialized. The block mining
process involves finding a partial hash collision by varying the nonce in the
'BlockHeader' and/or additional entropy in the coinbase 'Transaction' of this
'Block'. Variations in the coinbase will result in different merkle roots in
the 'BlockHeader'.
-}
-- | Data type recording information of a 'Block'. The hash of a block is
-- defined as the hash of this data structure, serialized. The block mining
-- process involves finding a partial hash collision by varying the nonce in the
-- 'BlockHeader' and/or additional entropy in the coinbase 'Transaction' of this
-- 'Block'. Variations in the coinbase will result in different merkle roots in
-- the 'BlockHeader'.
data BlockHeader = BlockHeader
{ blockVersion :: !Word32 -- 4 bytes
, -- | hash of the previous block (parent)
prevBlock :: !BlockHash -- 32 bytes
, -- | root of the merkle tree of transactions
merkleRoot :: !Hash256 -- 32 bytes
, -- | unix timestamp
blockTimestamp :: !Timestamp -- 4 bytes
, -- | difficulty target
blockBits :: !Word32 -- 4 bytes
, -- | random nonce
bhNonce :: !Word32 -- 4 bytes
{ version :: !Word32, -- 4 bytes
-- | hash of the previous block (parent)
prev :: !BlockHash, -- 32 bytes
-- | root of the merkle tree of transactions
merkle :: !Hash256, -- 32 bytes
-- | unix timestamp
timestamp :: !Timestamp, -- 4 bytes
-- | difficulty target
bits :: !Word32, -- 4 bytes
-- | random nonce
nonce :: !Word32 -- 4 bytes
}
deriving (Eq, Ord, Show, Read, Generic, Hashable, NFData)
@ -200,27 +186,29 @@ data BlockHeader = BlockHeader
instance ToJSON BlockHeader where
toJSON (BlockHeader v p m t b n) =
object
[ "version" .= v
, "prevblock" .= p
, "merkleroot" .= encodeHex (runPutS (serialize m))
, "timestamp" .= t
, "bits" .= b
, "nonce" .= n
[ "version" .= v,
"prevblock" .= p,
"merkleroot" .= encodeHex (runPutS $ serialize m),
"timestamp" .= t,
"bits" .= b,
"nonce" .= n
]
toEncoding (BlockHeader v p m t b n) =
pairs
( "version" .= v
<> "prevblock" .= p
<> "merkleroot" .= encodeHex (runPutS (serialize m))
<> "timestamp" .= t
<> "bits" .= b
<> "nonce" .= 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"
BlockHeader
<$> o .: "version"
<*> o .: "prevblock"
<*> (f =<< o .: "merkleroot")
<*> o .: "timestamp"
@ -239,12 +227,12 @@ instance Serial BlockHeader where
n <- getWord32le
return
BlockHeader
{ blockVersion = v
, prevBlock = p
, merkleRoot = m
, blockTimestamp = t
, blockBits = b
, bhNonce = n
{ version = v,
prev = p,
merkle = m,
timestamp = t,
bits = b,
nonce = n
}
serialize (BlockHeader v p m bt bb n) = do
putWord32le v
@ -266,28 +254,26 @@ instance Serialize BlockHeader where
headerHash :: BlockHeader -> BlockHash
headerHash = BlockHash . doubleSHA256 . runPutS . serialize
{- | A block locator is a set of block headers, denser towards the best block
and sparser towards the genesis block. It starts at the highest block known.
It is used by a node to synchronize against the network. When the locator is
provided to a peer, it will send back block hashes starting from the first
block in the locator that it recognizes.
-}
-- | A block locator is a set of block headers, denser towards the best block
-- and sparser towards the genesis block. It starts at the highest block known.
-- It is used by a node to synchronize against the network. When the locator is
-- provided to a peer, it will send back block hashes starting from the first
-- block in the locator that it recognizes.
type BlockLocator = [BlockHash]
{- | Data type representing a getblocks message request. It is used in the
bitcoin protocol to retrieve blocks from a peer by providing it a
'BlockLocator' object. The response to a 'GetBlocks' message is an 'Inv'
message containing a list of block hashes that the peer believes this node is
missing. The number of block hashes in that inv message will end at the stop
block hash, at at the tip of the chain, or after 500 entries, whichever comes
earlier.
-}
-- | Data type representing a getblocks message request. It is used in the
-- bitcoin protocol to retrieve blocks from a peer by providing it a
-- 'BlockLocator' object. The response to a 'GetBlocks' message is an 'Inv'
-- message containing a list of block hashes that the peer believes this node is
-- missing. The number of block hashes in that inv message will end at the stop
-- block hash, at at the tip of the chain, or after 500 entries, whichever comes
-- earlier.
data GetBlocks = GetBlocks
{ getBlocksVersion :: !Word32
, -- | block locator object
getBlocksLocator :: !BlockLocator
, -- | hash of the last desired block
getBlocksHashStop :: !BlockHash
{ version :: !Word32,
-- | block locator object
locator :: !BlockLocator,
-- | hash of the last desired block
stop :: !BlockHash
}
deriving (Eq, Show, Read, Generic, NFData)
@ -299,31 +285,27 @@ instance Serial GetBlocks where
<*> deserialize
where
repList (VarInt c) = replicateM (fromIntegral c) deserialize
serialize (GetBlocks v xs h) = putGetBlockMsg v xs h
instance Serialize GetBlocks where
put = serialize
get = deserialize
putGetBlockMsg :: MonadPut m => Word32 -> BlockLocator -> BlockHash -> m ()
putGetBlockMsg v xs h = do
serialize (GetBlocks v xs h) = do
putWord32le v
putVarInt $ length xs
forM_ xs serialize
serialize h
{- | Similar to the 'GetBlocks' message type but for retrieving block headers
only. The response to a 'GetHeaders' request is a 'Headers' message
containing a list of block headers. A maximum of 2000 block headers can be
returned. 'GetHeaders' is used by simplified payment verification (SPV)
clients to exclude block contents when synchronizing the block chain.
-}
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
, -- | block locator object
getHeadersBL :: !BlockLocator
, -- | hash of the last desired block header
getHeadersHashStop :: !BlockHash
{ version :: !Word32,
-- | block locator object
locator :: !BlockLocator,
-- | hash of the last desired block header
stop :: !BlockHash
}
deriving (Eq, Show, Read, Generic, NFData)
@ -335,7 +317,11 @@ instance Serial GetHeaders where
<*> deserialize
where
repList (VarInt c) = replicateM (fromIntegral c) deserialize
serialize (GetHeaders v xs h) = putGetBlockMsg v xs h
serialize (GetHeaders v xs h) = do
putWord32le v
putVarInt $ length xs
forM_ xs serialize
serialize h
instance Serialize GetHeaders where
put = serialize
@ -348,12 +334,11 @@ instance Binary GetHeaders where
-- | '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.
-}
-- | 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]
list :: [BlockHeaderCount]
}
deriving (Eq, Show, Read, Generic, NFData)
@ -374,18 +359,17 @@ instance Binary Headers where
put = serialize
get = deserialize
{- | Decode the compact number used in the difficulty target of a block.
The compact format is a representation of a whole number \(N\) using an
unsigned 32-bit number similar to a floating point format. The most
significant 8 bits are the unsigned exponent of base 256. This exponent can
be thought of as the number of bytes of \(N\). The lower 23 bits are the
mantissa. Bit number 24 represents the sign of \(N\).
\[
N = -1^{sign} \times mantissa \times 256^{exponent-3}
\]
-}
-- | Decode the compact number used in the difficulty target of a block.
--
-- The compact format is a representation of a whole number \(N\) using an
-- unsigned 32-bit number similar to a floating point format. The most
-- significant 8 bits are the unsigned exponent of base 256. This exponent can
-- be thought of as the number of bytes of \(N\). The lower 23 bits are the
-- mantissa. Bit number 24 represents the sign of \(N\).
--
-- \[
-- N = -1^{sign} \times mantissa \times 256^{exponent-3}
-- \]
decodeCompact ::
Word32 ->
-- | true means overflow
@ -412,9 +396,8 @@ decodeCompact nCompact = (if neg then res * (-1) else res, over)
|| nWord > 0xffff && nSize > 32
)
{- | Encode an 'Integer' to the compact number format used in the difficulty
target of a block.
-}
-- | Encode an 'Integer' to the compact number format used in the difficulty
-- target of a block.
encodeCompact :: Integer -> Word32
encodeCompact i = nCompact
where

File diff suppressed because it is too large Load Diff

View File

@ -1,18 +1,20 @@
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE OverloadedRecordDot #-}
{-# LANGUAGE NoFieldSelectors #-}
{- |
Module : Haskoin.Block.Merkle
Copyright : No rights reserved
License : MIT
Maintainer : jprupp@protonmail.ch
Stability : experimental
Portability : POSIX
Function to deal with Merkle trees inside blocks.
-}
module Haskoin.Block.Merkle (
-- * Merkle Blocks
-- |
-- 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,
@ -32,7 +34,8 @@ module Haskoin.Block.Merkle (
extractMatches,
splitIn,
boolsToWord8,
) where
)
where
import Control.DeepSeq
import Control.Monad (forM_, replicateM, when)
@ -50,8 +53,8 @@ import Data.Word (Word32, Word8)
import GHC.Generics
import Haskoin.Block.Common
import Haskoin.Crypto.Hash
import Haskoin.Data
import Haskoin.Network.Common
import Haskoin.Network.Data
import Haskoin.Transaction.Common
-- | Hash of the block's Merkle root.
@ -63,18 +66,17 @@ 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.
-}
-- | 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
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)
@ -126,9 +128,8 @@ calcTreeHeight ntx
| 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.
-}
-- | Computes the width of a Merkle tree at a specific height. The transactions
-- are at height 0.
calcTreeWidth ::
-- | number of transactions (leaf nodes)
Int ->
@ -162,7 +163,7 @@ calcHash ::
Hash256
calcHash height pos txs
| height < 0 || pos < 0 = error "calcHash: Invalid parameters"
| height == 0 = getTxHash $ txs !! pos
| height == 0 = (txs !! pos).get
| otherwise = hash2 left right
where
left = calcHash (height - 1) (pos * 2) txs
@ -171,11 +172,10 @@ calcHash height pos txs
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.
-}
-- | Build a partial Merkle tree. Provide a list of tuples with all transaction
-- hashes in the block, and whether the transaction is to be included in the
-- partial tree. Returns a flag bits structure and the computed partial Merkle
-- tree.
buildPartialMerkle ::
-- | transaction hash and whether to include
[(TxHash, Bool)] ->
@ -183,9 +183,8 @@ buildPartialMerkle ::
(FlagBits, PartialMerkleTree)
buildPartialMerkle hs = traverseAndBuild (calcTreeHeight $ length hs) 0 hs
{- | Helper function to build partial Merkle tree. Used by 'buildPartialMerkle'
above.
-}
-- | Helper function to build partial Merkle tree. Used by 'buildPartialMerkle'
-- above.
traverseAndBuild ::
Int -> Int -> [(TxHash, Bool)] -> (FlagBits, PartialMerkleTree)
traverseAndBuild height pos txs
@ -238,10 +237,9 @@ traverseAndExtract height pos ntx flags 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'.
-}
-- | 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 ->
@ -254,7 +252,7 @@ extractMatches net flags hashes ntx
| ntx == 0 =
Left
"extractMatches: number of transactions can not be 0"
| ntx > getMaxBlockSize net `div` 60 =
| ntx > net.maxBlockSize `div` 60 =
Left
"extractMatches: number of transactions excessively high"
| length hashes > ntx =
@ -278,9 +276,8 @@ extractMatches net flags hashes ntx
(merkRoot, matches, nBitsUsed, nHashUsed) = fromMaybe e resM
e = error "extractMatches: unexpected error extracting a Maybe value"
{- | Helper function to split a list in chunks 'Int' length. Last chunk may be
smaller.
-}
-- | Helper function to split a list in chunks 'Int' length. Last chunk may be
-- smaller.
splitIn :: Int -> [a] -> [[a]]
splitIn _ [] = []
splitIn c xs = xs1 : splitIn c xs2
@ -294,14 +291,10 @@ 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"
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.

View File

@ -1,615 +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 (
Network (..),
-- * 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.Data
import Haskoin.Network.Common
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) . getNetworkName) allNets
-- | Bitcoin SegWit network. Symbol: BTC.
btc :: Network
btc =
Network
{ getNetworkName = "btc"
, getAddrPrefix = 0
, getScriptPrefix = 5
, getSecretPrefix = 128
, getExtPubKeyPrefix = 0x0488b21e
, getExtSecretPrefix = 0x0488ade4
, getNetworkMagic = 0xf9beb4d9
, getGenesisHeader =
BlockHeader
0x01
"0000000000000000000000000000000000000000000000000000000000000000"
(buildMerkleRoot [txHash genesisTx])
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"
, getAddrPrefix = 111
, getScriptPrefix = 196
, getSecretPrefix = 239
, getExtPubKeyPrefix = 0x043587cf
, getExtSecretPrefix = 0x04358394
, getNetworkMagic = 0x0b110907
, getGenesisHeader =
BlockHeader
0x01
"0000000000000000000000000000000000000000000000000000000000000000"
(buildMerkleRoot [txHash genesisTx])
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"
, getAddrPrefix = 111
, getScriptPrefix = 196
, getSecretPrefix = 239
, getExtPubKeyPrefix = 0x043587cf
, getExtSecretPrefix = 0x04358394
, getNetworkMagic = 0xfabfb5da
, getGenesisHeader =
BlockHeader
-- 0f9188f13cb7b2c71f2a335e3a4fc328bf5beb436012afca590b1a11466e2206
0x01
"0000000000000000000000000000000000000000000000000000000000000000"
(buildMerkleRoot [txHash genesisTx])
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"
, getAddrPrefix = 0
, getScriptPrefix = 5
, getSecretPrefix = 128
, getExtPubKeyPrefix = 0x0488b21e
, getExtSecretPrefix = 0x0488ade4
, getNetworkMagic = 0xe3e1f3e8
, getGenesisHeader =
BlockHeader
0x01
"0000000000000000000000000000000000000000000000000000000000000000"
(buildMerkleRoot [txHash genesisTx])
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.
bchTest4 :: Network
bchTest4 =
Network
{ getNetworkName = "bchtest4"
, getAddrPrefix = 111
, getScriptPrefix = 196
, getSecretPrefix = 239
, getExtPubKeyPrefix = 0x043587cf
, getExtSecretPrefix = 0x04358394
, getNetworkMagic = 0xe2b7daaf
, getGenesisHeader =
BlockHeader
0x01
"0000000000000000000000000000000000000000000000000000000000000000"
(buildMerkleRoot [txHash genesisTx])
1597811185
0x1d00ffff
114152193
, -- Hash 000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943
getMaxBlockSize = 2000000
, getMaxSatoshi = 2100000000000000
, getHaskoinUserAgent = "/haskoin-bch-test4:" <> versionString <> "/"
, getDefaultPort = 28333
, getAllowMinDifficultyBlocks = True
, getPowNoRetargetting = False
, getPowLimit =
0x00000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffff
, getBip34Block =
( 2
, "00000000b0c65b1e03baace7d5c093db0d6aac224df01484985ffd5e86a1a20c"
)
, getBip65Height = 3
, getBip66Height = 4
, getTargetTimespan = 14 * 24 * 60 * 60
, getTargetSpacing = 10 * 60
, getCheckpoints =
[
( 5000
, "000000009f092d074574a216faec682040a853c4f079c33dfd2c3ef1fd8108c4"
)
, -- Axion activation
( 16845
, "00000000fb325b8f34fe80c96a5f708a08699a68bbab82dba4474d86bd743077"
)
,
( 38000
, "000000000015197537e59f339e3b1bbf81a66f691bd3d7aa08560fc7bf5113fb"
)
,
( 54700
, "00000000009af4379d87f17d0f172ee4769b48839a5a3a3e81d69da4322518b8"
)
]
, getSeeds =
[ "testnet4-seed-bch.bitcoinforks.org"
, "testnet4-seed-bch.toom.im"
, "seed.tbch4.loping.net"
, "testnet4-seed.flowee.cash"
]
, getBip44Coin = 1
, getSigHashForkId = Just 0
, getEdaBlockHeight = Just 7
, getDaaBlockHeight = Just 3000
, getAsertActivationTime = Just 1605441600
, getAsertHalfLife = 60 * 60
, getSegWit = False
, getCashAddrPrefix = Just "bchtest"
, getBech32Prefix = Nothing
, getReplaceByFee = False
, getHalvingInterval = 210000
}
-- | Testnet for Bitcoin Cash network.
bchTest :: Network
bchTest =
Network
{ getNetworkName = "bchtest"
, getAddrPrefix = 111
, getScriptPrefix = 196
, getSecretPrefix = 239
, getExtPubKeyPrefix = 0x043587cf
, getExtSecretPrefix = 0x04358394
, getNetworkMagic = 0xf4e5f3f4
, getGenesisHeader =
BlockHeader
0x01
"0000000000000000000000000000000000000000000000000000000000000000"
(buildMerkleRoot [txHash genesisTx])
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"
, getAddrPrefix = 111
, getScriptPrefix = 196
, getSecretPrefix = 239
, getExtPubKeyPrefix = 0x043587cf
, getExtSecretPrefix = 0x04358394
, getNetworkMagic = 0xdab5bffa
, getGenesisHeader =
BlockHeader
-- 0f9188f13cb7b2c71f2a335e3a4fc328bf5beb436012afca590b1a11466e2206
0x01
"0000000000000000000000000000000000000000000000000000000000000000"
(buildMerkleRoot [txHash genesisTx])
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, bchTest4, bchTest, btcRegTest, bchRegTest]

View File

@ -1,19 +1,21 @@
{- |
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 : Haskoin.Crypto
-- Copyright : No rights reserved
-- License : MIT
-- Maintainer : jprupp@protonmail.ch
-- Stability : experimental
-- Portability : POSIX
--
-- Hashing functions and ECDSA signatures.
module Haskoin.Crypto
( module Secp256k1,
module Hash,
module Keys,
module Signature,
module Secp256k1,
) where
)
where
import Crypto.Secp256k1 as Secp256k1
import Haskoin.Crypto.Hash as Hash
import Haskoin.Crypto.Keys as Keys
import Haskoin.Crypto.Signature as Signature

View File

@ -1,24 +1,29 @@
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE ImportQualifiedPost #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedRecordDot #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE NoFieldSelectors #-}
{- |
Module : Haskoin.Crypto.Hash
Copyright : No rights reserved
License : MIT
Maintainer : jprupp@protonmail.ch
Stability : experimental
Portability : POSIX
Hashing functions and corresponding data types. Uses functions from the
cryptonite library.
-}
module Haskoin.Crypto.Hash (
-- * Hashes
Hash512 (getHash512),
Hash256 (getHash256),
Hash160 (getHash160),
CheckSum32 (getCheckSum32),
-- |
-- Module : Haskoin.Crypto.Hash
-- Copyright : No rights reserved
-- License : MIT
-- Maintainer : jprupp@protonmail.ch
-- Stability : experimental
-- Portability : POSIX
--
-- Hashing functions and corresponding data types. Uses functions from the
-- cryptonite library.
module Haskoin.Crypto.Hash
( -- * Hashes
Hash512 (get),
Hash256 (get),
Hash160 (get),
CheckSum32 (get),
sha512,
sha256,
ripemd160,
@ -31,45 +36,43 @@ module Haskoin.Crypto.Hash (
split512,
join512,
initTaggedHash,
) where
)
where
import Control.DeepSeq
import Crypto.Hash (
Context,
RIPEMD160 (..),
SHA1 (..),
SHA256 (..),
SHA512 (..),
hashInit,
hashUpdates,
hashWith,
)
import Crypto.Hash
import Crypto.MAC.HMAC (HMAC, hmac)
import Data.Binary (Binary (..))
import Data.ByteArray (ByteArrayAccess)
import qualified Data.ByteArray as BA
import Data.ByteArray (ByteArrayAccess, convert)
import Data.ByteString (ByteString)
import qualified Data.ByteString as BS
import Data.ByteString.Short (ShortByteString)
import qualified Data.ByteString.Short as BSS
import qualified Data.Bytes.Get as Get
import qualified Data.Bytes.Put as Put
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
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
{ get :: Word32
}
deriving (Eq, Ord, Serial, Show, Read, Hashable, Generic, NFData)
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
@ -80,55 +83,58 @@ instance Binary CheckSum32 where
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)
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)
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)
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)
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 . BSS.toShort <$> Get.getByteString 64
serialize = Put.putByteString . BSS.fromShort . getHash512
deserialize = Hash512 . toShort <$> getByteString 64
serialize = putByteString . fromShort . (.get)
instance Serialize Hash512 where
put = serialize
@ -143,15 +149,15 @@ instance IsString Hash256 where
case decodeHex $ cs str of
Nothing -> e
Just bs ->
case BS.length bs of
32 -> Hash256 (BSS.toShort 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 . BSS.toShort <$> Get.getByteString 32
serialize = Put.putByteString . BSS.fromShort . getHash256
deserialize = Hash256 . toShort <$> getByteString 32
serialize = putByteString . fromShort . (.get)
instance Serialize Hash256 where
put = serialize
@ -166,15 +172,15 @@ instance IsString Hash160 where
case decodeHex $ cs str of
Nothing -> e
Just bs ->
case BS.length bs of
20 -> Hash160 (BSS.toShort 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 . BSS.toShort <$> Get.getByteString 20
serialize = Put.putByteString . BSS.fromShort . getHash160
deserialize = Hash160 . toShort <$> getByteString 20
serialize = putByteString . fromShort . (.get)
instance Serialize Hash160 where
put = serialize
@ -185,40 +191,40 @@ instance Binary Hash160 where
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 :: (ByteArrayAccess b) => b -> CheckSum32
checkSum32 =
fromRight (error "Could not decode bytes as CheckSum32")
. Get.runGetS deserialize
. BS.take 4
. BA.convert
. runGetS deserialize
. B.take 4
. convert
. hashWith SHA256
. hashWith SHA256
@ -227,31 +233,29 @@ checkSum32 =
-- | 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
-}
-- | Initialize tagged hash specified in BIP340
--
-- @since 0.21.0
initTaggedHash ::
-- | Hash tag
ByteString ->

View File

@ -0,0 +1,20 @@
-- |
-- 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

View File

@ -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

File diff suppressed because it is too large Load Diff

View File

@ -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

File diff suppressed because it is too large Load Diff

View File

@ -1,38 +1,46 @@
{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE ImportQualifiedPost #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedRecordDot #-}
{-# LANGUAGE OverloadedStrings #-}
{- |
Module : Haskoin.Crypto.Signature
Copyright : No rights reserved
License : MIT
Maintainer : jprupp@protonmail.ch
Stability : experimental
Portability : POSIX
ECDSA signatures using secp256k1 curve. Uses functions from upstream secp256k1
library.
-}
module Haskoin.Crypto.Signature (
-- * Signatures
putSig,
getSig,
-- |
-- Module : Haskoin.Crypto.Signature
-- Copyright : No rights reserved
-- License : MIT
-- Maintainer : jprupp@protonmail.ch
-- Stability : experimental
-- Portability : POSIX
--
-- ECDSA signatures using secp256k1 curve. Uses functions from upstream secp256k1
-- library.
module Haskoin.Crypto.Signature
( -- * Signatures
signHash,
verifyHashSig,
isCanonicalHalfOrder,
decodeStrictSig,
exportSig,
) where
)
where
import Control.Monad (guard, unless, when)
import Crypto.Secp256k1
import Data.Aeson
import Data.Aeson.Encoding
import Data.Binary (Binary (..))
import Data.ByteString (ByteString)
import qualified Data.ByteString as BS
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.
@ -43,20 +51,18 @@ hashToMsg =
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 :: MonadGet m => m Sig
getSig = do
l <-
lookAhead $ do
instance Marshal Ctx Sig where
marshalGet ctx = do
l <- lookAhead $ do
t <- getWord8
-- 0x30 is DER sequence type
unless (t == 0x30) $
@ -67,27 +73,34 @@ getSig = do
when (l >= 0x80) $ fail "Multi-octect length not supported"
return $ fromIntegral l
bs <- getByteString $ l + 2
case decodeStrictSig bs of
case decodeStrictSig ctx bs of
Just s -> return s
Nothing -> fail "Invalid signature"
-- | Serialize an ECDSA signature for Bitcoin use.
putSig :: MonadPut m => Sig -> m ()
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
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 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
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

View File

@ -1,93 +0,0 @@
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveGeneric #-}
module Haskoin.Data (
Network (..),
) 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.Serialize (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
-- | Network definition.
data Network = Network
{ -- | lowercase alphanumeric and dashes
getNetworkName :: !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, Show, Read, Generic, NFData)

View File

@ -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

View File

@ -1,190 +0,0 @@
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE LambdaCase #-}
{-# 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.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.Binary (Binary (..))
import Data.ByteString (ByteString)
import qualified Data.ByteString 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.Data
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 . runGetS deserialize <=< decodeHex $ cs str
where
e = error "Could not decode public key"
instance ToJSON PubKeyI where
toJSON = String . encodeHex . runPutS . serialize
toEncoding s =
unsafeToEncoding $
char7 '"'
<> hexBuilder (runPutL (serialize s))
<> char7 '"'
instance FromJSON PubKeyI where
parseJSON =
withText "PubKeyI" $
maybe mzero return . ((eitherToMaybe . runGetS deserialize) <=< decodeHex)
instance Serial PubKeyI where
deserialize =
s >>= \case
True -> c
False -> u
where
s =
lookAhead $
getWord8 >>= \case
0x02 -> return True
0x03 -> return True
0x04 -> return False
_ -> fail "Not a public key"
c = do
bs <- getByteString 33
maybe (fail "Could not decode public key") return $
PubKeyI <$> importPubKey bs <*> pure True
u = do
bs <- getByteString 65
maybe (fail "Could not decode public key") return $
PubKeyI <$> importPubKey bs <*> pure False
serialize pk = putByteString $ exportPubKey (pubKeyCompressed pk) (pubKeyPoint pk)
instance Serialize PubKeyI where
put = serialize
get = deserialize
instance Binary PubKeyI where
put = serialize
get = deserialize
-- | 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 (runPutS (serialize 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 (runPutS (serialize h))
-- | Decode Casascius mini private keys (22 or 30 characters).
fromMiniKey :: ByteString -> Maybe SecKeyI
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 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

File diff suppressed because it is too large Load Diff

View File

@ -1,83 +0,0 @@
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
module Haskoin.Keys.Extended.Internal (
Fingerprint (..),
fingerprintToText,
textToFingerprint,
) where
import Control.DeepSeq (NFData)
import Control.Monad ((>=>))
import Data.Aeson (
FromJSON,
ToJSON,
parseJSON,
toJSON,
withText,
)
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 qualified Data.Serialize as S
import Data.String (IsString (..))
import Data.Text (Text)
import qualified Data.Text as Text
import Data.Typeable (Typeable)
import Data.Word (Word32)
import GHC.Generics (Generic)
import Haskoin.Util (decodeHex, encodeHex)
import Text.Read (readEither, readPrec)
-- | Fingerprint of parent
newtype Fingerprint = Fingerprint {unFingerprint :: 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 . unFingerprint
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

File diff suppressed because it is too large Load Diff

View File

@ -1,21 +1,25 @@
{- |
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 : Haskoin.Network
-- Copyright : No rights reserved
-- License : MIT
-- Maintainer : jprupp@protonmail.ch
-- Stability : experimental
-- Portability : POSIX
--
-- This module provides basic types used for the Bitcoin networking protocol
-- together with 'Data.Serialize' instances for efficiently serializing and
-- de-serializing them.
module Haskoin.Network
( module Data,
module Constants,
module Common,
module Message,
module Bloom,
) where
)
where
import Haskoin.Network.Bloom as Bloom
import Haskoin.Network.Common as Common
import Haskoin.Network.Constants as Constants
import Haskoin.Network.Data as Data
import Haskoin.Network.Message as Message

View File

@ -1,21 +1,27 @@
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE ImportQualifiedPost #-}
{-# LANGUAGE OverloadedRecordDot #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE NoFieldSelectors #-}
{- |
Module : Haskoin.Network.Bloom
Copyright : No rights reserved
License : MIT
Maintainer : jprupp@protonmail.ch
Stability : experimental
Portability : POSIX
Bloom filters are used to reduce data transfer when synchronizing thin cients.
When bloom filters are used a client will obtain filtered blocks that only
contain transactions that pass the bloom filter. Transactions announced via inv
messages also pass the filter.
-}
module Haskoin.Network.Bloom (
-- * Bloom Filters
-- |
-- 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 (..),
@ -28,27 +34,30 @@ module Haskoin.Network.Bloom (
isBloomFull,
acceptsFilters,
bloomRelevantUpdate,
) where
)
where
import Control.DeepSeq
import Control.Monad (forM_, replicateM)
import Crypto.Secp256k1 (Ctx)
import Data.Binary (Binary (..))
import Data.Bits
import Data.ByteString (ByteString)
import qualified Data.ByteString as BS
import Data.ByteString qualified as BS
import Data.Bytes.Get
import Data.Bytes.Put
import Data.Bytes.Serial
import qualified Data.Foldable as F
import Data.Foldable qualified as F
import Data.Hash.Murmur (murmur3)
import Data.List (foldl')
import qualified Data.Sequence as S
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
@ -66,9 +75,8 @@ ln2 = 0.6931471805599453094172321214581765680755001343602552
bitMask :: [Word8]
bitMask = [0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80]
{- | The bloom flags are used to tell the remote peer how to auto-update
the provided bloom filter.
-}
-- | The bloom flags are used to tell the remote peer how to auto-update
-- the provided bloom filter.
data BloomFlags
= -- | never update
BloomUpdateNone
@ -99,22 +107,21 @@ instance Serialize BloomFlags where
get = deserialize
put = serialize
{- | A bloom filter is a probabilistic data structure that SPV clients send to
other peers to filter the set of transactions received from them. Bloom
filters can have false positives but not false negatives. Some transactions
that pass the filter may not be relevant to the receiving peer. By
controlling the false positive rate, SPV nodes can trade off bandwidth
versus privacy.
-}
-- | A bloom filter is a probabilistic data structure that SPV clients send to
-- other peers to filter the set of transactions received from them. Bloom
-- filters can have false positives but not false negatives. Some transactions
-- that pass the filter may not be relevant to the receiving peer. By
-- controlling the false positive rate, SPV nodes can trade off bandwidth
-- versus privacy.
data BloomFilter = BloomFilter
{ -- | bloom filter data
bloomData :: !(S.Seq Word8)
, -- | number of hash functions for this filter
bloomHashFuncs :: !Word32
, -- | hash function random nonce
bloomTweak :: !Word32
, -- | bloom filter auto-update flags
bloomFlags :: !BloomFlags
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)
@ -128,10 +135,10 @@ instance Serial BloomFilter where
where
readDat (VarInt len) = replicateM (fromIntegral len) getWord8
serialize (BloomFilter dat hashFuncs tweak flags) = do
putVarInt $ S.length dat
forM_ (F.toList dat) putWord8
putWord32le hashFuncs
serialize BloomFilter {..} = do
putVarInt $ S.length array
mapM_ putWord8 (F.toList array)
putWord32le functions
putWord32le tweak
serialize flags
@ -144,8 +151,9 @@ instance Serialize BloomFilter where
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
@ -159,11 +167,11 @@ instance Serialize FilterLoad where
put = serialize
get = deserialize
{- | Add the given data element to the connections current filter without
requiring a completely new one to be set.
-}
newtype FilterAdd = FilterAdd {getFilterData :: ByteString}
deriving (Eq, Show, Read, Generic, NFData)
-- | Add the given data element to the connections current filter without
-- requiring a completely new one to be set.
newtype FilterAdd = FilterAdd {get :: ByteString}
deriving (Eq, Show, Read, Generic)
deriving newtype (NFData)
instance Serial FilterAdd where
deserialize = do
@ -183,9 +191,8 @@ 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.
-}
-- | Build a bloom filter that will provide the given false positive rate when
-- the given number of elements have been inserted.
bloomCreate ::
-- | number of elements
Int ->
@ -211,14 +218,13 @@ bloomCreate numElem fpRate =
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.
-}
-- | Insert arbitrary data into a bloom filter. Returns the new bloom filter
-- containing the new data.
bloomInsert ::
-- | Original bloom filter
BloomFilter ->
@ -226,21 +232,20 @@ bloomInsert ::
ByteString ->
-- | Bloom filter containing the new data
BloomFilter
bloomInsert bfilter bs
| isBloomFull bfilter = bfilter
| otherwise = bfilter{bloomData = newData}
bloomInsert b bs
| isBloomFull b = b
| otherwise = b {array = dat}
where
idxs = map (\i -> bloomHash bfilter i bs) [0 .. bloomHashFuncs bfilter - 1]
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
newData = foldl upd (bloomData bfilter) idxs
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.
-}
-- | Tests if some arbitrary data matches the filter. This can be either because
-- the data was inserted into the filter or because it is a false positive.
bloomContains ::
-- | Bloom filter
BloomFilter ->
@ -248,70 +253,83 @@ bloomContains ::
ByteString ->
-- | Returns True if the data matches the filter
Bool
bloomContains bfilter bs
| isBloomFull bfilter = True
| isBloomEmpty bfilter = False
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]
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
.&. (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).
-}
-- | Checks if any of the outputs of a tx is in the current bloom filter.
-- If it is, add the txid and vout as an outpoint (i.e. so that
-- a future tx that spends the output won't be missed).
bloomRelevantUpdate ::
Ctx ->
-- | Bloom filter
BloomFilter ->
-- | Tx that may (or may not) have relevant outputs
Tx ->
-- | Returns an updated bloom filter adding relevant output
Maybe BloomFilter
bloomRelevantUpdate bfilter tx
| isBloomFull bfilter || isBloomEmpty bfilter = Nothing
| bloomFlags bfilter == BloomUpdateNone = Nothing
| not (null matchOuts) = Just $ foldl' addRelevant bfilter matchOuts
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
decodedOutputScripts = traverse (unmarshal ctx . (.script)) tx.outputs
err = error "Error Decoding output script"
idxOutputScripts = either (const err) (zip [0 ..]) decodedOutputScripts
-- Check if any txOuts were contained in the bloom filter
matchFilter =
filter (\(_, op) -> 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) of
case (b.flags, scriptType) of
-- We filtered out BloomUpdateNone so we insert any PayPk or PayMulSig
(_, True) -> bloomInsert bf outpoint
(BloomUpdateAll, _) -> bloomInsert bf outpoint
_ -> error "Error Updating Bloom Filter with relevant outpoint"
where
outpoint = runPutS $ serialize $ OutPoint{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 _) = runPutS $ serialize outputMuSig
encodeScriptOut (PayWitnessScriptHash scriptHash) = runPutS $ serialize scriptHash
encodeScriptOut (DataCarrier getOutputDat) = runPutS $ serialize getOutputDat
encodeScriptOut outputHash = (runPutS . serialize . getOutputHash) outputHash
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 ::
@ -319,9 +337,8 @@ isBloomValid ::
BloomFilter ->
-- | True if the given filter is valid
Bool
isBloomValid bfilter =
S.length (bloomData bfilter) <= maxBloomSize
&& bloomHashFuncs bfilter <= maxHashFuncs
isBloomValid BloomFilter {..} =
S.length array <= maxBloomSize && functions <= maxHashFuncs
-- | Does the peer with these version services accept bloom filters?
acceptsFilters :: Word64 -> Bool

View File

@ -1,19 +1,23 @@
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE NoFieldSelectors #-}
{-# LANGUAGE ImportQualifiedPost #-}
{- |
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
-- |
-- 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 (..),
@ -44,14 +48,15 @@ module Haskoin.Network.Common (
commandToString,
stringToCommand,
putVarInt,
) where
)
where
import Control.DeepSeq
import Control.Monad (forM_, liftM2, replicateM, unless)
import Data.Binary (Binary (..))
import Data.Bits (shiftL)
import Data.ByteString (ByteString)
import qualified Data.ByteString as B
import Data.ByteString qualified as B
import Data.ByteString.Char8 as C (replicate)
import Data.Bytes.Get
import Data.Bytes.Put
@ -68,14 +73,14 @@ 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.
-}
-- | 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]
list :: [NetworkAddressTime]
}
deriving (Eq, Show, Generic, NFData)
deriving (Eq, Show, Generic)
deriving newtype (NFData)
instance Serial Addr where
deserialize = Addr <$> (repList =<< deserialize)
@ -95,15 +100,14 @@ 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 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
payload :: !VarString,
-- | ECDSA signature of the payload
signature :: !VarString
}
deriving (Eq, Show, Read, Generic, NFData)
@ -119,19 +123,19 @@ 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.
-}
-- | 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]
list :: [InvVector]
}
deriving (Eq, Show, Generic, NFData)
deriving (Eq, Show, Generic)
deriving newtype (NFData)
instance Serial GetData where
deserialize = GetData <$> (repList =<< deserialize)
@ -150,15 +154,15 @@ 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.
-}
-- | '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]
list :: [InvVector]
}
deriving (Eq, Show, Generic, NFData)
deriving (Eq, Show, Generic)
deriving newtype (NFData)
instance Serial Inv where
deserialize = Inv <$> (repList =<< deserialize)
@ -177,9 +181,8 @@ 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 type identifying the type of an inventory vector. SegWit types are
-- only used in 'GetData' messages, not 'Inv'.
data InvType
= -- | error
InvError
@ -233,14 +236,13 @@ 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.
-}
-- | 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
invType :: !InvType,
-- | 256-bit hash of object
invHash :: !Hash256
}
deriving (Eq, Show, Generic, NFData)
@ -259,7 +261,8 @@ instance Serialize InvVector where
newtype HostAddress
= HostAddress ByteString
deriving (Eq, Show, Ord, Generic, NFData)
deriving (Eq, Show, Ord, Generic)
deriving newtype (NFData)
instance Serial HostAddress where
serialize (HostAddress bs) = putByteString bs
@ -273,15 +276,14 @@ 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 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
services :: !Word64,
-- | address and port information
address :: !HostAddress
}
deriving (Eq, Show, Generic, NFData)
@ -294,7 +296,7 @@ hostToSockAddr (HostAddress bs) =
sockToHostAddress :: SockAddr -> HostAddress
sockToHostAddress = HostAddress . runPutS . putSockAddr
putSockAddr :: MonadPut m => SockAddr -> m ()
putSockAddr :: (MonadPut m) => SockAddr -> m ()
putSockAddr (SockAddrInet6 p _ (a, b, c, d) _) = do
putWord32be a
putWord32be b
@ -309,7 +311,7 @@ putSockAddr (SockAddrInet p a) = do
putWord16be (fromIntegral p)
putSockAddr _ = error "Invalid address type"
getSockAddr :: MonadGet m => m SockAddr
getSockAddr :: (MonadGet m) => m SockAddr
getSockAddr = do
a <- getWord32be
b <- getWord32be
@ -336,16 +338,16 @@ 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.
-}
-- | 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]
list :: [InvVector]
}
deriving (Eq, Show, Generic, NFData)
deriving (Eq, Show, Generic)
deriving newtype (NFData)
instance Serial NotFound where
deserialize = NotFound <$> (repList =<< deserialize)
@ -364,22 +366,23 @@ instance Serialize NotFound where
get = deserialize
put = serialize
{- | A 'Ping' message is sent to bitcoin peers to check if a connection is still
open.
-}
-- | 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
nonce :: Word64
}
deriving (Eq, Show, Read, Generic, NFData)
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'
pongNonce :: Word64
nonce :: Word64
}
deriving (Eq, Show, Read, Generic, NFData)
deriving (Eq, Show, Read, Generic)
deriving newtype (NFData)
instance Serial Ping where
deserialize = Ping <$> getWord64le
@ -408,13 +411,13 @@ instance Serialize Pong where
-- | 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
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)
@ -444,8 +447,8 @@ instance Serial RejectCode where
_ ->
fail $
unwords
[ "Reject get: Invalid code"
, show code
[ "Reject get: Invalid code",
show code
]
serialize code = putWord8 $ case code of
@ -498,11 +501,11 @@ 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 {getVarInt :: Word64}
deriving (Eq, Show, Read, Generic, NFData)
-- | 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)
@ -537,8 +540,9 @@ putVarInt :: (MonadPut m, Integral a) => a -> m ()
putVarInt = serialize . VarInt . fromIntegral
-- | Data type for serialization of variable-length strings.
newtype VarString = VarString {getVarString :: ByteString}
deriving (Eq, Show, Read, Generic, NFData)
newtype VarString = VarString {get :: ByteString}
deriving (Eq, Show, Read, Generic)
deriving newtype (NFData)
instance Serial VarString where
deserialize = VarString <$> (readBS =<< deserialize)
@ -557,35 +561,35 @@ 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.
-}
-- | 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)
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
Version
<$> getWord32le
<*> getWord64le
<*> getWord64le
<*> deserialize
@ -618,22 +622,21 @@ instance Serialize Version where
get = deserialize
-- | 0x00 is 'False', anything else is 'True'.
getBool :: MonadGet m => m Bool
getBool :: (MonadGet m) => m Bool
getBool = go =<< getWord8
where
go 0 = return False
go _ = return True
putBool :: MonadPut m => Bool -> m ()
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.
-}
-- | 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

View File

@ -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]

View File

@ -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)

View File

@ -1,30 +1,34 @@
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE ImportQualifiedPost #-}
{-# LANGUAGE OverloadedRecordDot #-}
{-# LANGUAGE NoFieldSelectors #-}
{- |
Module : Haskoin.Network.Message
Copyright : No rights reserved
License : MIT
Maintainer : jprupp@protonmail.ch
Stability : experimental
Portability : POSIX
Peer-to-peer network message serialization.
-}
module Haskoin.Network.Message (
-- * Network Message
-- |
-- 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
)
where
import Control.DeepSeq
import Control.Monad (unless)
import Data.Binary (Binary (..))
import Data.ByteString (ByteString)
import qualified Data.ByteString as BS
import Data.ByteString qualified as B
import Data.Bytes.Get
import Data.Bytes.Put
import Data.Bytes.Serial
@ -34,23 +38,22 @@ import GHC.Generics (Generic)
import Haskoin.Block.Common
import Haskoin.Block.Merkle
import Haskoin.Crypto.Hash
import Haskoin.Data
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 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
magic :: !Word32,
-- | message type
cmd :: !MessageCommand,
-- | length of payload
size :: !Word32,
-- | checksum of payload
checksum :: !CheckSum32
}
deriving (Eq, Show, Generic, NFData)
@ -76,13 +79,12 @@ instance Serialize MessageHeader where
put = serialize
get = deserialize
{- | The 'Message' type is used to identify all the valid messages that can be
sent between bitcoin peers. Only values of type 'Message' will be accepted
by other bitcoin peers as bitcoin protocol messages need to be correctly
serialized with message headers. Serializing a 'Message' value will
include the 'MessageHeader' with the correct checksum value automatically.
No need to add the 'MessageHeader' separately.
-}
-- | The 'Message' type is used to identify all the valid messages that can be
-- sent between bitcoin peers. Only values of type 'Message' will be accepted
-- by other bitcoin peers as bitcoin protocol messages need to be correctly
-- serialized with message headers. Serializing a 'Message' value will
-- include the 'MessageHeader' with the correct checksum value automatically.
-- No need to add the 'MessageHeader' separately.
data Message
= MVersion !Version
| MVerAck
@ -136,12 +138,12 @@ msgType MGetAddr = MCGetAddr
msgType (MOther c _) = MCOther c
-- | Deserializer for network messages.
getMessage :: MonadGet m => Network -> m Message
getMessage :: (MonadGet m) => Network -> m Message
getMessage net = do
(MessageHeader mgc cmd len chk) <- deserialize
bs <- lookAhead $ getByteString $ fromIntegral len
unless
(mgc == getNetworkMagic net)
(mgc == net.magic)
(fail $ "get: Invalid network magic bytes: " ++ show mgc)
unless
(checkSum32 bs == chk)
@ -170,7 +172,8 @@ getMessage net = do
MCOther c -> MOther c <$> getByteString (fromIntegral len)
_ ->
fail $
"get: command " ++ show cmd
"get: command "
++ show cmd
++ " should not carry a payload"
either fail return (runGetS f bs)
else case cmd of
@ -179,19 +182,20 @@ getMessage net = do
MCFilterClear -> return MFilterClear
MCMempool -> return MMempool
MCSendHeaders -> return MSendHeaders
MCOther c -> return (MOther c BS.empty)
MCOther c -> return (MOther c B.empty)
_ ->
fail $
"get: command " ++ show cmd
"get: command "
++ show cmd
++ " is expected to carry a payload"
-- | Serializer for network messages.
putMessage :: MonadPut m => Network -> Message -> m ()
putMessage :: (MonadPut m) => Network -> Message -> m ()
putMessage net msg = do
let (cmd, payload) =
case msg of
MVersion m -> (MCVersion, runPutS $ serialize m)
MVerAck -> (MCVerAck, BS.empty)
MVerAck -> (MCVerAck, B.empty)
MAddr m -> (MCAddr, runPutS $ serialize m)
MInv m -> (MCInv, runPutS $ serialize m)
MGetData m -> (MCGetData, runPutS $ serialize m)
@ -202,19 +206,19 @@ putMessage net msg = do
MBlock m -> (MCBlock, runPutS $ serialize m)
MMerkleBlock m -> (MCMerkleBlock, runPutS $ serialize m)
MHeaders m -> (MCHeaders, runPutS $ serialize m)
MGetAddr -> (MCGetAddr, BS.empty)
MGetAddr -> (MCGetAddr, B.empty)
MFilterLoad m -> (MCFilterLoad, runPutS $ serialize m)
MFilterAdd m -> (MCFilterAdd, runPutS $ serialize m)
MFilterClear -> (MCFilterClear, BS.empty)
MFilterClear -> (MCFilterClear, B.empty)
MPing m -> (MCPing, runPutS $ serialize m)
MPong m -> (MCPong, runPutS $ serialize m)
MAlert m -> (MCAlert, runPutS $ serialize m)
MMempool -> (MCMempool, BS.empty)
MMempool -> (MCMempool, B.empty)
MReject m -> (MCReject, runPutS $ serialize m)
MSendHeaders -> (MCSendHeaders, BS.empty)
MSendHeaders -> (MCSendHeaders, B.empty)
MOther c p -> (MCOther c, p)
chk = checkSum32 payload
len = fromIntegral $ BS.length payload
header = MessageHeader (getNetworkMagic net) cmd len chk
len = fromIntegral $ B.length payload
header = MessageHeader net.magic cmd len chk
serialize header
putByteString payload

View File

@ -1,20 +1,20 @@
{- |
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 : 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
)
where
import Haskoin.Script.Common as Common
import Haskoin.Script.SigHash as SigHash

View File

@ -1,19 +1,22 @@
{-# 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
-- |
-- 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 (..),
@ -21,13 +24,17 @@ module Haskoin.Script.Common (
opPushData,
intToScriptOp,
scriptOpToInt,
) where
)
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
@ -36,22 +43,33 @@ 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.
-}
-- | 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]
ops :: [ScriptOp]
}
deriving (Eq, Show, Read, Generic, Hashable, NFData)
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 =
@ -570,9 +588,8 @@ intToScriptOp i
$ 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'
-}
-- | 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

View File

@ -1,27 +1,35 @@
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE ImportQualifiedPost #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedRecordDot #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE NoFieldSelectors #-}
{- |
Module : Haskoin.Script.SigHash
Copyright : No rights reserved
License : MIT
Maintainer : jprupp@protonmail.ch
Stability : experimental
Portability : POSIX
Transaction signatures and related functions.
-}
module Haskoin.Script.SigHash (
-- * Script Signatures
-- |
-- 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,
anyoneCanPay,
hasForkIdFlag,
setAnyoneCanPayFlag,
setAnyoneCanPay,
setForkIdFlag,
isSigHashAll,
isSigHashNone,
@ -33,15 +41,19 @@ module Haskoin.Script.SigHash (
txSigHash,
txSigHashForkId,
TxSignature (..),
encodeTxSig,
decodeTxSig,
) where
encodeTxSig,
)
where
import Control.DeepSeq
import Control.Monad
import qualified Data.Aeson as J
import Crypto.Secp256k1
import Data.Aeson
import Data.Bits
import qualified Data.ByteString as BS
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
@ -50,10 +62,10 @@ import Data.Maybe
import Data.Scientific
import Data.Word
import GHC.Generics (Generic)
import Haskoin.Crypto
import Haskoin.Crypto.Hash
import Haskoin.Data
import Haskoin.Crypto.Signature
import Haskoin.Network.Common
import Haskoin.Network.Data
import Haskoin.Script.Common
import Haskoin.Transaction.Common
import Haskoin.Util
@ -89,43 +101,30 @@ instance Enum SigHashFlag where
toEnum 0x80 = SIGHASH_ANYONECANPAY
toEnum _ = error "Not a valid sighash flag"
{- | Data type representing the different ways a transaction can be signed.
When producing a signature, a hash of the transaction is used as the message
to be signed. The 'SigHash' parameter controls which parts of the
transaction are used or ignored to produce the transaction hash. The idea is
that if some part of a transaction is not used to produce the transaction
hash, then you can change that part of the transaction after producing a
signature without invalidating that signature.
If the 'SIGHASH_ANYONECANPAY' flag is set (true), then only the current input
is signed. Otherwise, all of the inputs of a transaction are signed. The
default value for 'SIGHASH_ANYONECANPAY' is unset (false).
-}
-- | Data type representing the different ways a transaction can be signed.
-- When producing a signature, a hash of the transaction is used as the message
-- to be signed. The 'SigHash' parameter controls which parts of the
-- transaction are used or ignored to produce the transaction hash. The idea is
-- that if some part of a transaction is not used to produce the transaction
-- hash, then you can change that part of the transaction after producing a
-- signature without invalidating that signature.
--
-- If the 'SIGHASH_ANYONECANPAY' flag is set (true), then only the current input
-- is signed. Otherwise, all of the inputs of a transaction are signed. The
-- default value for 'SIGHASH_ANYONECANPAY' is unset (false).
newtype SigHash
= SigHash Word32
deriving
( Eq
, Ord
, Bits
, Enum
, Integral
, Num
, Real
, Show
, Read
, Generic
, Hashable
, NFData
)
deriving (Eq, Ord, Enum, Show, Read, Generic)
deriving newtype (Bits, Integral, Num, Real, Hashable, NFData)
instance J.FromJSON SigHash where
instance FromJSON SigHash where
parseJSON =
J.withScientific "sighash" $
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
@ -152,16 +151,16 @@ setForkIdFlag :: SigHash -> SigHash
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
@ -187,7 +186,7 @@ sigHashAddForkId sh w = (fromIntegral w `shiftL` 8) .|. (sh .&. 0x000000ff)
-- | Add fork id of a particular network to a 'SigHash'.
sigHashAddNetworkId :: Network -> SigHash -> SigHash
sigHashAddNetworkId net =
(`sigHashAddForkId` fromMaybe 0 (getSigHashForkId net))
(`sigHashAddForkId` fromMaybe 0 net.sigHashForkId)
-- | Get fork id from 'SigHash'.
sigHashGetForkId :: SigHash -> Word32
@ -209,38 +208,33 @@ txSigHash ::
-- | hash to be signed
Hash256
txSigHash net tx out v i sh
| hasForkIdFlag sh && isJust (getSigHashForkId net) =
| hasForkIdFlag sh && isJust net.sigHashForkId =
txSigHashForkId net tx out v i sh
| otherwise = do
let newIn = buildInputs (txIn tx) fout i sh
let newIn = buildInputs tx.inputs 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 $
runPutS $ 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 = runPutS $ serialize out}]
| 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 = runPutS $ serialize 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]
@ -250,11 +244,10 @@ buildOutputs txos i sh
| 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.
-}
-- | Compute the hash that will be used for signing a transaction. This
-- function is used when the 'SIGHASH_FORKID' flag is set.
txSigHashForkId ::
Network ->
-- | transaction to sign
@ -271,70 +264,87 @@ txSigHashForkId ::
Hash256
txSigHashForkId net tx out v i sh =
doubleSHA256 . runPutS $ do
putWord32le $ txVersion tx
putWord32le tx.version
serialize hashPrevouts
serialize hashSequence
serialize $ prevOutput $ txIn tx !! i
serialize (tx.inputs !! i).outpoint
putScript out
putWord64le v
putWord32le $ txInSequence $ txIn tx !! i
putWord32le (tx.inputs !! i).sequence
serialize hashOutputs
putWord32le $ txLockTime tx
putWord32le tx.locktime
putWord32le $ fromIntegral $ sigHashAddNetworkId net sh
where
hashPrevouts
| not $ hasAnyoneCanPayFlag sh =
doubleSHA256 $ runPutS $ mapM_ (serialize . prevOutput) $ txIn tx
| not (anyoneCanPay sh) =
doubleSHA256 . runPutS $ mapM_ (serialize . (.outpoint)) tx.inputs
| otherwise = zeros
hashSequence
| not (hasAnyoneCanPayFlag sh)
&& not (isSigHashSingle sh)
&& not (isSigHashNone sh) =
doubleSHA256 $ runPutS $ mapM_ (putWord32le . txInSequence) $ txIn tx
| not (anyoneCanPay sh || isSigHashSingle sh || isSigHashNone sh) =
doubleSHA256 . runPutS $ mapM_ (putWord32le . (.sequence)) tx.inputs
| otherwise = zeros
hashOutputs
| not (isSigHashSingle sh) && not (isSigHashNone sh) =
doubleSHA256 $ runPutS $ mapM_ serialize $ txOut tx
| isSigHashSingle sh && i < length (txOut tx) =
doubleSHA256 $ runPutS $ serialize $ txOut tx !! i
| not (isSigHashSingle sh || isSigHashNone sh) =
doubleSHA256 . runPutS $ mapM_ serialize tx.outputs
| isSigHashSingle sh && i < length tx.outputs =
doubleSHA256 . runPutS $ serialize $ tx.outputs !! i
| otherwise = zeros
putScript s = do
let encodedScript = runPutS $ serialize s
putVarInt $ BS.length encodedScript
putVarInt $ B.length encodedScript
putByteString encodedScript
zeros :: Hash256
zeros = "0000000000000000000000000000000000000000000000000000000000000000"
{- | Data type representing a signature together with a 'SigHash'. The 'SigHash'
is serialized as one byte at the end of an ECDSA 'Sig'. All signatures in
transaction inputs are of type 'TxSignature'.
-}
-- | Data type representing a signature together with a 'SigHash'. The 'SigHash'
-- is serialized as one byte at the end of an ECDSA 'Sig'. All signatures in
-- transaction inputs are of type 'TxSignature'.
data TxSignature
= TxSignature
{ txSignature :: !Sig
, txSignatureSigHash :: !SigHash
{ sig :: !Sig,
hash :: !SigHash
}
| TxSignatureEmpty
deriving (Eq, Show, Generic)
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)) =
runPutS $ putSig sig >> putWord8 (fromIntegral n)
-- | 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
marshalGet (net, ctx) =
bool decode empty =<< isEmpty
where
empty = return TxSignatureEmpty
decode = do
sig <- marshalGet ctx
sh <- fromIntegral <$> getWord8
when (isSigHashUnknown sh) $
Left "Non-canonical signature: unknown hashtype byte"
when (isNothing (getSigHashForkId net) && hasForkIdFlag sh) $
Left "Non-canonical signature: invalid network for forkId"
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
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

View File

@ -1,20 +1,26 @@
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE ImportQualifiedPost #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedRecordDot #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE NoFieldSelectors #-}
{- |
Module : Haskoin.Script.Standard
Copyright : No rights reserved
License : MIT
Maintainer : jprupp@protonmail.ch
Stability : experimental
Portability : POSIX
Standard scripts like pay-to-public-key, pay-to-public-key-hash,
pay-to-script-hash, pay-to-multisig and corresponding SegWit variants.
-}
module Haskoin.Script.Standard (
-- * Standard Script Outputs
-- |
-- 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,
@ -26,9 +32,7 @@ module Haskoin.Script.Standard (
isPayWitnessScriptHash,
isDataCarrier,
encodeOutput,
encodeOutputBS,
decodeOutput,
decodeOutputBS,
toP2SH,
toP2WSH,
sortMulSig,
@ -37,22 +41,23 @@ module Haskoin.Script.Standard (
ScriptInput (..),
SimpleInput (..),
encodeInput,
encodeInputBS,
decodeInput,
decodeInputBS,
isSpendPK,
isSpendPKHash,
isSpendMulSig,
isScriptHashInput,
) where
)
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 Crypto.Secp256k1
import Data.Aeson (ToJSON (..), Value (..), withText)
import Data.Aeson.Encoding (Encoding, text)
import Data.Aeson.Types (Parser)
import Data.ByteString (ByteString)
import qualified Data.ByteString as BS
import Data.ByteString qualified as B
import Data.Bytes.Get
import Data.Bytes.Put
import Data.Bytes.Serial
@ -62,52 +67,51 @@ import Data.List (sortBy)
import Data.Maybe (fromJust, isJust)
import Data.Word (Word8)
import GHC.Generics (Generic)
import Haskoin.Crypto
import Haskoin.Data
import Haskoin.Keys.Common
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 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}
PayPK {key :: !PublicKey}
| -- | pay to public key hash
PayPKHash {getOutputHash :: !Hash160}
PayPKHash {hash160 :: !Hash160}
| -- | multisig
PayMulSig
{ getOutputMulSigKeys :: ![PubKeyI]
, getOutputMulSigRequired :: !Int
{ keys :: ![PublicKey],
required :: !Int
}
| -- | pay to a script hash
PayScriptHash {getOutputHash :: !Hash160}
PayScriptHash {hash160 :: !Hash160}
| -- | pay to witness public key hash
PayWitnessPKHash {getOutputHash :: !Hash160}
PayWitnessPKHash {hash160 :: !Hash160}
| -- | pay to witness script hash
PayWitnessScriptHash {getScriptHash :: !Hash256}
PayWitnessScriptHash {hash256 :: !Hash256}
| -- | another pay to witness address
PayWitness
{ getWitnessVersion :: !Word8
, getWitnessData :: !ByteString
{ version :: !Word8,
bytes :: !ByteString
}
| -- | provably unspendable data carrier
DataCarrier {getOutputData :: !ByteString}
deriving (Eq, Show, Read, Generic, Hashable, NFData)
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
@ -149,13 +153,13 @@ isDataCarrier :: ScriptOutput -> Bool
isDataCarrier (DataCarrier _) = True
isDataCarrier _ = False
{- | Tries to decode a 'ScriptOutput' from a 'Script'. This can fail if the
script is not recognized as any of the standard output types.
-}
decodeOutput :: Script -> Either String ScriptOutput
decodeOutput s = case scriptOps s of
-- | Tries to decode a 'ScriptOutput' from a 'Script'. This can fail if the
-- script is not recognized as any of the standard output types.
decodeOutput :: Ctx -> Script -> Either String ScriptOutput
decodeOutput ctx s = case s.ops of
-- Pay to PubKey
[OP_PUSHDATA bs _, OP_CHECKSIG] -> PayPK <$> runGetS deserialize bs
[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
@ -164,20 +168,24 @@ decodeOutput s = case scriptOps s of
PayScriptHash <$> runGetS deserialize bs
-- Pay to Witness
[OP_0, OP_PUSHDATA bs OPCODE]
| BS.length bs == 20 -> PayWitnessPKHash <$> runGetS deserialize bs
| BS.length bs == 32 -> PayWitnessScriptHash <$> runGetS deserialize bs
| BS.length bs /= 20 && BS.length bs /= 32 ->
Left "Version 0 segwit program must be 20 or 32 bytes long"
| 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 _]
| isJust (opWitnessVersion ver)
&& BS.length bs >= 2
&& BS.length bs <= 40 ->
Right $ PayWitness (fromJust (opWitnessVersion ver)) 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
_ -> matchPayMulSig s
_ -> matchPayMulSig ctx s <|> Left "decodeOutput: Non-standard output"
witnessVersionOp :: Word8 -> Maybe ScriptOp
witnessVersionOp 0 = Just OP_0
@ -219,29 +227,25 @@ opWitnessVersion OP_15 = Just 15
opWitnessVersion OP_16 = Just 16
opWitnessVersion _ = Nothing
-- | Similar to 'decodeOutput' but decodes from a 'ByteString'.
decodeOutputBS :: ByteString -> Either String ScriptOutput
decodeOutputBS = decodeOutput <=< runGetS deserialize
-- | Computes a 'Script' from a standard 'ScriptOutput'.
encodeOutput :: ScriptOutput -> Script
encodeOutput s = Script $ case s of
encodeOutput :: Ctx -> ScriptOutput -> Script
encodeOutput ctx s = Script $ case s of
-- Pay to PubKey
(PayPK k) -> [opPushData $ runPutS $ serialize k, OP_CHECKSIG]
(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
[ 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 . runPutS . serialize) ps
keys = map (opPushData . marshal ctx) ps
in opM : keys ++ [opN, OP_CHECKMULTISIG]
| otherwise -> error "encodeOutput: PayMulSig r must be <= than pkeys"
-- Pay to Script Hash Address
@ -255,15 +259,19 @@ encodeOutput s = Script $ case s of
(PayWitness v h) ->
[ case witnessVersionOp v of
Nothing -> error "encodeOutput: invalid witness version"
Just c -> c
, opPushData h
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 = runPutS . serialize . 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
@ -274,8 +282,8 @@ toP2WSH :: Script -> ScriptOutput
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
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
@ -283,50 +291,52 @@ matchPayMulSig (Script ops) = case splitAt (length ops - 2) ops of
else Left "matchPayMulSig: Invalid M or N parameters"
_ -> Left "matchPayMulSig: script did not match output template"
where
go (OP_PUSHDATA bs _ : xs) = liftM2 (:) (runGetS deserialize 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` (runPutS . serialize)) keys) r
-- | Sort the public keys of a multisig output in ascending order by comparing
-- their compressed serialized representations. Refer to BIP-67.
sortMulSig :: Ctx -> ScriptOutput -> ScriptOutput
sortMulSig ctx out = case out of
PayMulSig keys r ->
PayMulSig
(sortBy (compare `on` marshal ctx) keys)
r
_ -> error "Can only call orderMulSig on PayMulSig scripts"
{- | Data type describing standard transaction input scripts. Input scripts
provide the signing data required to unlock the coins of the output they are
trying to spend, except in pay-to-witness-public-key-hash and
pay-to-script-hash transactions.
-}
-- | Data type describing standard transaction input scripts. Input scripts
-- provide the signing data required to unlock the coins of the output they are
-- trying to spend, except in pay-to-witness-public-key-hash and
-- pay-to-script-hash transactions.
data SimpleInput
= SpendPK
{ -- | transaction signature
getInputSig :: !TxSignature
signature :: !TxSignature
}
| SpendPKHash
{ -- | embedded signature
getInputSig :: !TxSignature
, -- | public key
getInputKey :: !PubKeyI
signature :: !TxSignature,
-- | public key
key :: !PublicKey
}
| SpendMulSig
{ -- | list of signatures
getInputMulSigKeys :: ![TxSignature]
signatures :: ![TxSignature]
}
deriving (Eq, Show, Generic, NFData)
deriving (Eq, Show, Read, Generic, NFData)
{- | Returns true if the input script is spending from a pay-to-public-key
output.
-}
-- | Returns true if the input script is spending from a pay-to-public-key
-- output.
isSpendPK :: ScriptInput -> Bool
isSpendPK (RegularInput (SpendPK _)) = True
isSpendPK _ = False
{- | Returns true if the input script is spending from a pay-to-public-key-hash
output.
-}
-- | 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
@ -341,34 +351,33 @@ isScriptHashInput :: ScriptInput -> Bool
isScriptHashInput (ScriptHashInput _ _) = True
isScriptHashInput _ = False
{- | A redeem script is the output script serialized into the spending input
script. It must be included in inputs that spend pay-to-script-hash outputs.
-}
-- | A redeem script is the output script serialized into the spending input
-- script. It must be included in inputs that spend pay-to-script-hash outputs.
type RedeemScript = ScriptOutput
-- | Standard input script high-level representation.
data ScriptInput
= RegularInput
{ -- | get wrapped simple input
getRegularInput :: !SimpleInput
get :: !SimpleInput
}
| ScriptHashInput
{ -- | get simple input associated with redeem script
getScriptHashInput :: !SimpleInput
, -- | redeem script
getScriptHashRedeem :: !RedeemScript
get :: !SimpleInput,
-- | redeem script
redeem :: !RedeemScript
}
deriving (Eq, Show, Generic, NFData)
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) =
decodeSimpleInput :: Network -> Ctx -> Script -> Either String SimpleInput
decodeSimpleInput net ctx (Script ops) =
maybeToEither errMsg $ matchPK ops <|> matchPKHash ops <|> matchMulSig ops
where
matchPK [op] = SpendPK <$> f op
matchPK _ = Nothing
matchPKHash [op, OP_PUSHDATA pub _] =
SpendPKHash <$> f op <*> eitherToMaybe (runGetS deserialize pub)
SpendPKHash <$> f op <*> eitherToMaybe (unmarshal ctx pub)
matchPKHash _ = Nothing
matchMulSig (x : xs) = do
guard $ x == OP_0
@ -376,56 +385,49 @@ decodeSimpleInput net (Script ops) =
matchMulSig _ = Nothing
f OP_0 = return TxSignatureEmpty
f (OP_PUSHDATA "" OPCODE) = f OP_0
f (OP_PUSHDATA bs _) = eitherToMaybe $ decodeTxSig net bs
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) =
-- | Heuristic to decode a 'ScriptInput' from a 'Script'. This function fails if
-- the script can not be parsed as a standard script input.
decodeInput :: Network -> Ctx -> Script -> Either String ScriptInput
decodeInput net ctx s@(Script ops) =
maybeToEither errMsg $ matchSimpleInput <|> matchPayScriptHash
where
matchSimpleInput =
RegularInput <$> eitherToMaybe (decodeSimpleInput net s)
RegularInput <$> eitherToMaybe (decodeSimpleInput net ctx s)
matchPayScriptHash =
case splitAt (length (scriptOps s) - 1) ops of
case splitAt (length s.ops - 1) ops of
(is, [OP_PUSHDATA bs _]) -> do
rdm <- eitherToMaybe $ decodeOutputBS bs
inp <- eitherToMaybe $ decodeSimpleInput net $ Script is
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 <=< runGetS deserialize
instance Marshal (Network, Ctx) ScriptInput where
marshalGet (net, ctx) =
deserialize >>= either fail return . decodeInput net ctx
marshalPut (net, ctx) =
serialize . encodeInput net ctx
-- | Encode a standard input into a script.
encodeInput :: ScriptInput -> Script
encodeInput s = case s of
RegularInput ri -> encodeSimpleInput ri
encodeInput :: Network -> Ctx -> ScriptInput -> Script
encodeInput net ctx s = case s of
RegularInput ri -> encodeSimpleInput net ctx ri
ScriptHashInput i o ->
Script $
scriptOps (encodeSimpleInput i) ++ [opPushData $ encodeOutputBS o]
{- | Similar to 'encodeInput' but encodes directly to a serialized script
'ByteString'.
-}
encodeInputBS :: ScriptInput -> ByteString
encodeInputBS = runPutS . serialize . encodeInput
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 =
encodeSimpleInput :: Network -> Ctx -> SimpleInput -> Script
encodeSimpleInput net ctx s =
Script $
case s of
SpendPK ts -> [f ts]
SpendPKHash ts p -> [f ts, opPushData $ runPutS $ serialize p]
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

View File

@ -1,21 +1,21 @@
{- |
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 : Haskoin.Transaction
-- Copyright : No rights reserved
-- License : MIT
-- Maintainer : jprupp@protonmail.ch
-- Stability : experimental
-- Portability : POSIX
--
-- Transactions and related code.
module Haskoin.Transaction
( module Common,
module Builder,
module Segwit,
module Taproot,
module Partial,
module Genesis,
) where
)
where
import Haskoin.Transaction.Builder as Builder
import Haskoin.Transaction.Common as Common

View File

@ -1,20 +1,24 @@
{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE ImportQualifiedPost #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedRecordDot #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE NoFieldSelectors #-}
{- |
Module : Haskoin.Transaction.Builder
Copyright : No rights reserved
License : MIT
Maintainer : jprupp@protonmail.ch
Stability : experimental
Portability : POSIX
Code to simplify transaction creation, signing, fee calculation and coin
selection.
-}
module Haskoin.Transaction.Builder (
-- * Transaction Builder
-- |
-- 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,
@ -43,24 +47,19 @@ module Haskoin.Transaction.Builder (
guessMSTxFee,
guessTxSize,
guessMSSize,
) where
)
where
import Control.Applicative ((<|>))
import Control.Arrow (first)
import Control.Monad (foldM, unless)
import Control.Monad.Identity (runIdentity)
import Crypto.Secp256k1
import qualified Data.ByteString as B
import Data.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 (ConduitT, Void, await, runConduit, (.|))
import Data.Conduit.List (sourceList)
import Data.Either (fromRight)
import Data.List (nub)
@ -70,38 +69,27 @@ 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.Data
import Haskoin.Keys.Common
import Haskoin.Network.Common
import Haskoin.Network.Data
import Haskoin.Script
import Haskoin.Transaction.Builder.Sign (
SigInput (..),
buildInput,
makeSignature,
sigKeys,
)
import qualified Haskoin.Transaction.Builder.Sign as S
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 (
decodeWitnessInput,
isSegwit,
viewWitnessProgram,
)
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.
-}
-- | Any type can be used as a Coin if it can provide a value in Satoshi.
-- The value is used in coin selection algorithms.
class Coin c where
coinValue :: c -> Word64
{- | Coin selection algorithm for normal (non-multisig) transactions. This
function returns the selected coins together with the amount of change to
send back to yourself, taking the fee into account.
-}
-- | 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 =>
(Coin c) =>
-- | value to send
Word64 ->
-- | fee per byte
@ -118,11 +106,10 @@ chooseCoins target fee nOut continue coins =
runIdentity . runConduit $
sourceList coins .| chooseCoinsSink target fee nOut continue
{- | Coin selection algorithm for normal (non-multisig) transactions. This
function returns the selected coins together with the amount of change to
send back to yourself, taking the fee into account. This version uses a Sink
for conduit-based coin selection.
-}
-- | Coin selection algorithm for normal (non-multisig) transactions. This
-- function returns the selected coins together with the amount of change to
-- send back to yourself, taking the fee into account. This version uses a Sink
-- for conduit-based coin selection.
chooseCoinsSink ::
(Monad m, Coin c) =>
-- | value to send
@ -143,13 +130,12 @@ chooseCoinsSink target fee nOut continue
where
err = "chooseCoins: No solution found"
{- | Coin selection algorithm for multisig transactions. This function returns
the selected coins together with the amount of change to send back to
yourself, taking the fee into account. This function assumes all the coins
are script hash outputs that send funds to a multisignature address.
-}
-- | Coin selection algorithm for multisig transactions. This function returns
-- the selected coins together with the amount of change to send back to
-- yourself, taking the fee into account. This function assumes all the coins
-- are script hash outputs that send funds to a multisignature address.
chooseMSCoins ::
Coin c =>
(Coin c) =>
-- | value to send
Word64 ->
-- | fee per byte
@ -167,12 +153,11 @@ chooseMSCoins target fee ms nOut continue coins =
runIdentity . runConduit $
sourceList coins .| chooseMSCoinsSink target fee ms nOut continue
{- | Coin selection algorithm for multisig transactions. This function returns
the selected coins together with the amount of change to send back to
yourself, taking the fee into account. This function assumes all the coins
are script hash outputs that send funds to a multisignature address. This
version uses a Sink if you need conduit-based coin selection.
-}
-- | Coin selection algorithm for multisig transactions. This function returns
-- the selected coins together with the amount of change to send back to
-- yourself, taking the fee into account. This function assumes all the coins
-- are script hash outputs that send funds to a multisignature address. This
-- version uses a Sink if you need conduit-based coin selection.
chooseMSCoinsSink ::
(Monad m, Coin c) =>
-- | value to send
@ -195,13 +180,12 @@ chooseMSCoinsSink target fee ms nOut continue
where
err = "chooseMSCoins: No solution found"
{- | Select coins greedily by starting from an empty solution. If the 'continue'
flag is set, the algorithm will try to find a better solution in the stream
after a solution is found. If the next solution found is not strictly better
than the previously found solution, the algorithm stops and returns the
previous solution. If the continue flag is not set, the algorithm will return
the first solution it finds in the stream.
-}
-- | Select coins greedily by starting from an empty solution. If the 'continue'
-- flag is set, the algorithm will try to find a better solution in the stream
-- after a solution is found. If the next solution found is not strictly better
-- than the previously found solution, the algorithm stops and returns the
-- previous solution. If the continue flag is not set, the algorithm will return
-- the first solution it finds in the stream.
greedyAddSink ::
(Monad m, Coin c) =>
-- | value to send
@ -263,9 +247,8 @@ guessMSTxFee :: Word64 -> (Int, Int) -> Int -> Int -> Word64
guessMSTxFee byteFee ms nOut nIn =
byteFee * fromIntegral (guessTxSize 0 (replicate nIn ms) nOut 0)
{- | Computes an upper bound on the size of a transaction based on some known
properties of the transaction.
-}
-- | Computes an upper bound on the size of a transaction based on some known
-- properties of the transaction.
guessTxSize ::
-- | number of regular transaction inputs
Int ->
@ -300,6 +283,26 @@ guessTxSize pki msi pkout msout =
out =
pkout * 34
+
-- (20: hash160) + (3: opcodes) +
-- (20: hash160) + (3: opcodes) +
-- (20: hash160) + (3: opcodes) +
-- (20: hash160) + (3: opcodes) +
-- (1: script len) + (8: Word64)
-- (1: script len) + (8: Word64)
-- (1: script len) + (8: Word64)
-- (1: script len) + (8: Word64)
-- (20: hash160) + (3: opcodes) +
-- (20: hash160) + (3: opcodes) +
-- (1: script len) + (8: Word64)
-- (1: script len) + (8: Word64)
-- (20: hash160) + (3: opcodes) +
-- (20: hash160) + (3: opcodes) +
-- (1: script len) + (8: Word64)
-- (1: script len) + (8: Word64)
-- (20: hash160) + (3: opcodes) +
-- (1: script len) + (8: Word64)
msout * 32
@ -316,18 +319,21 @@ guessMSSize (m, n) =
rdm =
fromIntegral $
B.length $ runPutS $ serialize $ opPushData $ B.replicate (n * 34 + 3) 0
B.length $
runPutS $
serialize $
opPushData $
B.replicate (n * 34 + 3) 0
-- Redeem + m*sig + OP_0
scp = rdm + m * 73 + 1
{- Build a new Tx -}
{- | Build a transaction by providing a list of outpoints as inputs
and a list of recipient addresses and amounts as outputs.
-}
buildAddrTx :: Network -> [OutPoint] -> [(Text, Word64)] -> Either String Tx
buildAddrTx net ops rcps =
buildTx ops <$> mapM f rcps
-- | Build a transaction by providing a list of outpoints as inputs
-- and a list of recipient addresses and amounts as outputs.
buildAddrTx :: Network -> Ctx -> [OutPoint] -> [(Text, Word64)] -> Either String Tx
buildAddrTx net ctx ops rcps =
buildTx ctx ops <$> mapM f rcps
where
f (aTxt, v) =
maybeToEither ("buildAddrTx: Invalid address " <> cs aTxt) $ do
@ -335,32 +341,31 @@ buildAddrTx net ops rcps =
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 =
-- | Build a transaction by providing a list of outpoints as inputs
-- and a list of 'ScriptOutput' and amounts as outputs.
buildTx :: Ctx -> [OutPoint] -> [(ScriptOutput, Word64)] -> Tx
buildTx ctx ops rcpts =
Tx 1 (toIn <$> ops) (toOut <$> rcpts) [] 0
where
toIn op = TxIn op B.empty maxBound
toOut (o, v) = TxOut v $ 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
in RFC-6979.
Example: P2SH-P2WKH
> sigIn = SigInput (PayWitnessPKHash h) 100000 op sigHashAll Nothing
> signedTx = signTx btc unsignedTx [sigIn] [key]
Example: P2SH-P2WSH multisig
> sigIn = SigInput (PayWitnessScriptHash h) 100000 op sigHashAll (Just $ PayMulSig [p1,p2,p3] 2)
> signedTx = signTx btc unsignedTx [sigIn] [k1,k3]
-}
-- | Sign a transaction by providing the 'SigInput' signing parameters and a
-- list of private keys. The signature is computed deterministically as defined
-- in RFC-6979.
--
-- Example: P2SH-P2WKH
--
-- > sigIn = SigInput (PayWitnessPKHash h) 100000 op sigHashAll Nothing
-- > signedTx = signTx btc unsignedTx [sigIn] [key]
--
-- Example: P2SH-P2WSH multisig
--
-- > sigIn = SigInput (PayWitnessScriptHash h) 100000 op sigHashAll (Just $ PayMulSig [p1,p2,p3] 2)
-- > signedTx = signTx btc unsignedTx [sigIn] [k1,k3]
signTx ::
Network ->
Ctx ->
-- | transaction to sign
Tx ->
-- | signing parameters
@ -369,15 +374,15 @@ signTx ::
[SecKey] ->
-- | signed transaction
Either String Tx
signTx net tx si = S.signTx net tx $ notNested <$> si
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'.
-}
-- | This function differs from 'signTx' by assuming all segwit inputs are
-- P2SH-nested. Use the same signing parameters for segwit inputs as in 'signTx'.
signNestedWitnessTx ::
Network ->
Ctx ->
-- | transaction to sign
Tx ->
-- | signing parameters
@ -386,176 +391,215 @@ signNestedWitnessTx ::
[SecKey] ->
-- | signed transaction
Either String Tx
signNestedWitnessTx net tx si = S.signTx net tx $ nested <$> si
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.
-}
-- | 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 -}
{- | Merge partially-signed multisig transactions. This function does not
support segwit and P2SH-segwit inputs. Use PSBTs to merge transactions with
segwit inputs.
-}
-- | Merge partially-signed multisig transactions. This function does not
-- support segwit and P2SH-segwit inputs. Use PSBTs to merge transactions with
-- segwit inputs.
mergeTxs ::
Network -> [Tx] -> [(ScriptOutput, Word64, OutPoint)] -> Either String Tx
mergeTxs net txs os
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 txs) (head emptyTxs) outs
| 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) $
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.
-}
-- | Merge input from partially-signed multisig transactions. This function
-- does not support segwit and P2SH-segwit inputs.
mergeTxInput ::
Network ->
Ctx ->
[Tx] ->
Tx ->
((ScriptOutput, Word64), Int) ->
Either String Tx
mergeTxInput net txs tx ((so, val), i) = do
mergeTxInput net ctx txs tx ((so, val), i) = do
-- Ignore transactions with empty inputs
let ins = map (scriptInput . (!! i) . txIn) txs
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 <- 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)
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
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
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)
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)
ctx
(txSigHash net tx (encodeOutput ctx out) val i sh)
x
(pubKeyPoint p)
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
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
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)
(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
| 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 <$> runGetS deserialize inp >>= \case
[OP_PUSHDATA bs _] -> decodeOutputBS bs
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)
verifyHashSig ctx (theTxSigHash so sh Nothing) sig pub.point
(PayPKHash h, RegularInput (SpendPKHash (TxSignature sig sh) pub)) ->
pubKeyAddr pub == p2pkhAddr h
&& verifyHashSig (theTxSigHash so sh Nothing) sig (pubKeyPoint pub)
pubKeyAddr ctx pub == p2pkhAddr h
&& verifyHashSig ctx (theTxSigHash so sh Nothing) sig pub.point
(PayMulSig pubs r, RegularInput (SpendMulSig sigs)) ->
countMulSig net tx out val i (pubKeyPoint <$> pubs) sigs == r
countMulSig net ctx tx out val i ((.point) <$> pubs) sigs == r
(PayScriptHash h, ScriptHashInput si' rdm) ->
payToScriptAddress rdm == p2shAddr h && verifyLegacyInput rdm (RegularInput si')
payToScriptAddress ctx rdm == p2shAddr h && verifyLegacyInput rdm (RegularInput si')
_ -> False
where
out = encodeOutput so
out = encodeOutput ctx so
verifySegwitInput ::
ScriptOutput -> (Maybe ScriptOutput, SimpleInput) -> Bool
verifySegwitInput so (rdm, si) = case (so, rdm, si) of
(PayWitnessPKHash h, Nothing, SpendPKHash (TxSignature sig sh) pub) ->
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 (runPutS (serialize 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
( PayWitnessPKHash h,
Nothing,
SpendPKHash (TxSignature sig sh) pub
) ->
let keytest = pubKeyWitnessAddr ctx pub == p2wpkhAddr h
sighash = theTxSigHash so sh Nothing
pkpoint = pub.point
verify = verifyHashSig ctx sighash sig pkpoint
in keytest && verify
( PayWitnessScriptHash h,
Just rdm'@(PayPK pub),
SpendPK (TxSignature sig sh)
) ->
let keytest = payToWitnessScriptAddress ctx rdm' == p2wshAddr h
sighash = theTxSigHash so sh $ Just rdm'
pkpoint = pub.point
verify = verifyHashSig ctx sighash sig pkpoint
in keytest && verify
( PayWitnessScriptHash h,
Just rdm'@(PayPKHash kh),
SpendPKHash (TxSignature sig sh) pub
) ->
let keytest = payToWitnessScriptAddress ctx rdm' == p2wshAddr h
addrtest = addressHash (marshal ctx pub) == kh
pkpoint = pub.point
sighash = theTxSigHash so sh $ Just rdm'
verify = verifyHashSig ctx sighash sig pkpoint
in keytest && addrtest && verify
( PayWitnessScriptHash h,
Just rdm'@(PayMulSig pubs r),
SpendMulSig sigs
) ->
let keytest = payToWitnessScriptAddress ctx rdm' == p2wshAddr h
pkpoints = (.point) <$> pubs
hashfun sh = theTxSigHash so sh $ Just rdm'
verify = countMulSig' ctx hashfun pkpoints sigs == r
in keytest && verify
_ -> False
verifyNestedInput ::
ScriptOutput -> ScriptOutput -> (Maybe RedeemScript, SimpleInput) -> Bool
verifyNestedInput so so' x = case so of
PayScriptHash h -> payToScriptAddress so' == p2shAddr h && verifySegwitInput so' x
PayScriptHash h -> payToScriptAddress ctx so' == p2shAddr h && verifySegwitInput so' x
_ -> False
-- | Count the number of valid signatures for a multi-signature transaction.
countMulSig ::
Network ->
Ctx ->
Tx ->
Script ->
Word64 ->
@ -563,15 +607,16 @@ countMulSig ::
[PubKey] ->
[TxSignature] ->
Int
countMulSig net tx out val i =
countMulSig' h
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

View File

@ -1,20 +1,25 @@
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedRecordDot #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE NoFieldSelectors #-}
{- |
Module : Haskoin.Transaction.Builder.Sign
Copyright : No rights reserved
License : MIT
Maintainer : jprupp@protonmail.ch
Stability : experimental
Portability : POSIX
Types and logic for signing transactions.
-}
module Haskoin.Transaction.Builder.Sign (
SigInput (..),
-- |
-- 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,
@ -22,105 +27,92 @@ module Haskoin.Transaction.Builder.Sign (
signInput,
buildInput,
sigKeys,
) where
import Control.DeepSeq (NFData)
import Control.Monad (foldM, when)
import Data.Aeson (
FromJSON,
ToJSON (..),
object,
pairs,
parseJSON,
withObject,
(.:),
(.:?),
(.=),
)
where
import Control.DeepSeq
import Control.Monad
import Crypto.Secp256k1
import Data.Aeson
import Data.Aeson.Encoding
import Data.Bytes.Get
import Data.Bytes.Put
import Data.Bytes.Serial
import Data.Either (rights)
import Data.Hashable (Hashable)
import Data.List (find, nub)
import Data.Maybe (
catMaybes,
fromMaybe,
mapMaybe,
maybeToList,
)
import Data.Word (Word64)
import GHC.Generics (Generic)
import Haskoin.Address (getAddrHash160, pubKeyAddr)
import Haskoin.Crypto (Hash256, SecKey)
import Haskoin.Crypto.Signature (signHash, verifyHashSig)
import Haskoin.Data (Network)
import Haskoin.Keys.Common (
PubKeyI (..),
SecKeyI (..),
derivePubKeyI,
wrapSecKey,
)
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 (matchTemplate, updateIndex)
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 type used to specify the signing parameters of a transaction input.
-- To sign an input, the previous output script, outpoint and sighash are
-- required. When signing a pay to script hash output, an additional redeem
-- script is required.
data SigInput = SigInput
{ -- | output script to spend
-- ^ output script value
sigInputScript :: !ScriptOutput
, -- | output script value
script :: !ScriptOutput,
-- | output script value
-- ^ outpoint to spend
sigInputValue :: !Word64
, -- | outpoint to spend
value :: !Word64,
-- | outpoint to spend
-- ^ signature type
sigInputOP :: !OutPoint
, -- | signature type
outpoint :: !OutPoint,
-- | signature type
-- ^ redeem script
sigInputSH :: !SigHash
, -- | redeem script
sigInputRedeem :: !(Maybe RedeemScript)
sighash :: !SigHash,
-- | redeem script
redeem :: !(Maybe RedeemScript)
}
deriving (Eq, Show, Read, Generic, Hashable, NFData)
deriving (Show, Read, Eq, Generic, NFData)
instance ToJSON SigInput where
toJSON (SigInput so val op sh rdm) =
instance MarshalJSON Ctx SigInput where
marshalValue ctx (SigInput s v o h r) =
object $
[ "pkscript" .= so
, "value" .= val
, "outpoint" .= op
, "sighash" .= sh
[ "pkscript" .= marshalValue ctx s,
"value" .= v,
"outpoint" .= o,
"sighash" .= h
]
++ [ "redeem" .= marshalValue ctx r
| r <- maybeToList r
]
++ ["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 FromJSON SigInput where
parseJSON =
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 <$> o .: "pkscript"
SigInput
<$> (unmarshalValue ctx =<< o .: "pkscript")
<*> o .: "value"
<*> o .: "outpoint"
<*> o .: "sighash"
<*> o .:? "redeem"
<*> (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.
-}
-- | Sign a transaction by providing the 'SigInput' signing parameters and a
-- list of private keys. The signature is computed deterministically as defined
-- in RFC-6979.
signTx ::
Network ->
Ctx ->
-- | transaction to sign
Tx ->
-- | signing parameters, with nesting flag
@ -129,59 +121,63 @@ signTx ::
[SecKey] ->
-- | signed transaction
Either String Tx
signTx net otx sigis allKeys
signTx net ctx otx sigis allKeys
| null ti = Left "signTx: Transaction has no inputs"
| otherwise = foldM go otx $ findInputIndex (sigInputOP . fst) sigis ti
| 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.
-}
-- | Sign a single input in a transaction deterministically (RFC-6979). The
-- nesting flag only affects the behavior of segwit inputs.
signInput ::
Network ->
Ctx ->
Tx ->
Int ->
-- | boolean flag: nest input
(SigInput, Bool) ->
SecKeyI ->
PrivateKey ->
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
}
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 = runPutS . serialize . 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
-- | Add the witness data of the transaction given segwit parameters for an input.
--
-- @since 0.11.0.0
updatedWitnessData ::
Network ->
Ctx ->
Tx ->
Int ->
ScriptOutput ->
ScriptInput ->
Either String WitnessData
updatedWitnessData net ctx tx i so si
| isSegwit so =
updateWitness . toWitnessStack net ctx =<< calcWitnessProgram net ctx so si
| otherwise =
return tx.witness
where
updateWitness w
| null $ 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 ::
@ -195,45 +191,45 @@ findInputIndex ::
findInputIndex getOutPoint as ti =
mapMaybe g $ zip (matchTemplate as ti f) [0 ..]
where
f s txin = getOutPoint s == prevOutput txin
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'.
-}
-- | Find from the list of provided private keys which one is required to sign
-- the 'ScriptOutput'.
sigKeys ::
Ctx ->
ScriptOutput ->
Maybe RedeemScript ->
[SecKey] ->
Either String [SecKeyI]
sigKeys so rdmM keys =
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 rdm Nothing keys
(PayScriptHash _, Just rdm) -> sigKeys ctx rdm Nothing keys
(PayWitnessPKHash h, _) -> return $ keyByHash h
(PayWitnessScriptHash _, Just rdm) -> sigKeys rdm Nothing keys
(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
| 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.
-}
-- | Construct an input for a transaction given a signature, public key and data
-- about the previous output.
buildInput ::
Network ->
Ctx ->
-- | transaction where input will be added
Tx ->
-- | input index where signature will go
@ -245,15 +241,19 @@ buildInput ::
-- | redeem script if pay-to-script-hash
Maybe RedeemScript ->
TxSignature ->
PubKeyI ->
PublicKey ->
Either String ScriptInput
buildInput net tx i so val rdmM sig pub = do
when (i >= length (txIn tx)) $ Left "buildInput: Invalid input index"
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
(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
@ -261,47 +261,50 @@ buildInput net tx i so val rdmM sig pub = do
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
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
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
-- | Apply heuristics to extract the signatures for a particular input that are
-- embedded in the transaction.
--
-- @since 0.11.0.0
parseExistingSigs :: Network -> Ctx -> Tx -> ScriptOutput -> Int -> [TxSignature]
parseExistingSigs net ctx tx so i = insSigs <> witSigs
where
insSigs = case decodeInputBS net scp of
insSigs = case unmarshal (net, ctx) scp of
Right (ScriptHashInput (SpendMulSig xs) _) -> xs
Right (RegularInput (SpendMulSig xs)) -> xs
_ -> []
scp = scriptInput $ txIn tx !! i
scp = (tx.inputs !! i).script
witSigs
| not $ isSegwit so = []
| null $ txWitness tx = []
| otherwise = rights $ decodeTxSig net <$> (txWitness tx !! i)
| 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
-}
-- | A function which selects the digest algorithm and parameters as appropriate
--
-- @since 0.11.0.0
makeSigHash ::
Network ->
Ctx ->
Tx ->
Int ->
ScriptOutput ->
@ -309,7 +312,7 @@ makeSigHash ::
SigHash ->
Maybe RedeemScript ->
Hash256
makeSigHash net tx i so val sh rdmM = h net tx (encodeOutput so') val i sh
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'

View File

@ -1,19 +1,25 @@
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE ImportQualifiedPost #-}
{-# LANGUAGE OverloadedRecordDot #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE NoFieldSelectors #-}
{- |
Module : Haskoin.Transaction.Common
Copyright : No rights reserved
License : MIT
Maintainer : jprupp@protonmail.ch
Stability : experimental
Portability : POSIX
Code related to transactions parsing and serialization.
-}
module Haskoin.Transaction.Common (
-- * Transactions
-- |
-- 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 (..),
@ -27,27 +33,20 @@ module Haskoin.Transaction.Common (
txHashToHex,
nosigTxHash,
nullOutPoint,
) where
)
where
import Control.Applicative ((<|>))
import Control.DeepSeq
import Control.Monad (
forM_,
guard,
liftM2,
mzero,
replicateM,
unless,
when,
(<=<),
)
import Control.Monad (forM_, guard, liftM2, mzero, replicateM, unless, when, (<=<))
import Data.Aeson as A
import Data.Aeson.Encoding (unsafeToEncoding)
import Data.Aeson.Encoding qualified as E
import Data.Binary (Binary (..))
import Data.Bool (bool)
import Data.ByteString (ByteString)
import qualified Data.ByteString as B
import Data.ByteString qualified as B
import Data.ByteString.Builder (char7)
import qualified Data.ByteString.Lazy as BL
import Data.ByteString.Lazy qualified as BL
import Data.Bytes.Get
import Data.Bytes.Put
import Data.Bytes.Serial
@ -61,12 +60,17 @@ import Data.Word (Word32, Word64)
import GHC.Generics (Generic)
import Haskoin.Crypto.Hash
import Haskoin.Network.Common
import Haskoin.Util
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, Serial, 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
@ -96,25 +100,20 @@ instance FromJSON TxHash where
instance ToJSON TxHash where
toJSON = A.String . txHashToHex
toEncoding h =
unsafeToEncoding $
char7 '"'
<> hexBuilder (BL.reverse (runPutL (serialize h)))
<> char7 '"'
toEncoding = hexEncoding . BL.reverse . runPutL . serialize
-- | Transaction hash excluding signatures.
nosigTxHash :: Tx -> TxHash
nosigTxHash tx =
TxHash $
doubleSHA256 $
runPutS $
serialize 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 (runPutS (serialize h)))
txHashToHex (TxHash h) =
encodeHex . B.reverse . runPutS $ serialize h
-- | Convert transaction hash from hex, reversing bytes.
hexToTxHash :: Text -> Maybe TxHash
@ -135,21 +134,21 @@ 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
version :: !Word32,
-- | list of transaction inputs
inputs :: ![TxIn],
-- | list of transaction outputs
outputs :: ![TxOut],
-- | witness data for the transaction
witness :: !WitnessData,
-- | earliest mining height or time
locktime :: !Word32
}
deriving (Show, Read, Eq, Ord, Generic, Hashable, NFData)
-- | Compute transaction hash.
txHash :: Tx -> TxHash
txHash tx = TxHash . doubleSHA256 . runPutS $ serialize tx{txWitness = []}
txHash tx = TxHash . doubleSHA256 . runPutS $ serialize tx {witness = []}
instance IsString Tx where
fromString =
@ -158,10 +157,9 @@ instance IsString Tx where
e = error "Could not read transaction from hex string"
instance Serial Tx where
deserialize =
isWitnessTx >>= \w -> if w then parseWitnessTx else parseLegacyTx
deserialize = isWitnessTx >>= bool parseLegacyTx parseWitnessTx
serialize tx
| null (txWitness tx) = putLegacyTx tx
| null tx.witness = putLegacyTx tx
| otherwise = putWitnessTx tx
instance Binary Tx where
@ -172,31 +170,31 @@ instance Serialize Tx where
put = serialize
get = deserialize
putInOut :: MonadPut m => Tx -> m ()
putInOut :: (MonadPut m) => Tx -> m ()
putInOut tx = do
putVarInt $ length (txIn tx)
forM_ (txIn tx) serialize
putVarInt $ length (txOut tx)
forM_ (txOut tx) serialize
putVarInt $ length tx.inputs
mapM_ serialize tx.inputs
putVarInt $ length tx.outputs
mapM_ serialize tx.outputs
-- | Non-SegWit transaction serializer.
putLegacyTx :: MonadPut m => Tx -> m ()
putLegacyTx :: (MonadPut m) => Tx -> m ()
putLegacyTx tx = do
putWord32le (txVersion tx)
putWord32le tx.version
putInOut tx
putWord32le (txLockTime tx)
putWord32le tx.locktime
-- | Witness transaciton serializer.
putWitnessTx :: MonadPut m => Tx -> m ()
putWitnessTx :: (MonadPut m) => Tx -> m ()
putWitnessTx tx = do
putWord32le (txVersion tx)
putWord32le tx.version
putWord8 0x00
putWord8 0x01
putInOut tx
putWitnessData (txWitness tx)
putWord32le (txLockTime tx)
putWitnessData tx.witness
putWord32le tx.locktime
isWitnessTx :: MonadGet m => m Bool
isWitnessTx :: (MonadGet m) => m Bool
isWitnessTx = lookAhead $ do
_ <- getWord32le
m <- getWord8
@ -204,42 +202,35 @@ isWitnessTx = lookAhead $ do
return (m == 0x00 && f == 0x01)
-- | Non-SegWit transaction deseralizer.
parseLegacyTx :: MonadGet m => m Tx
parseLegacyTx :: (MonadGet m) => m Tx
parseLegacyTx = do
v <- getWord32le
is <- replicateList =<< deserialize
os <- replicateList =<< deserialize
when (length is == 0x00 && length os == 0x01) $ fail "Witness transaction"
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) deserialize
rl (VarInt c) = replicateM (fromIntegral c) deserialize
-- | Witness transaction deserializer.
parseWitnessTx :: MonadGet m => m Tx
parseWitnessTx :: (MonadGet m) => m Tx
parseWitnessTx = do
v <- getWord32le
version <- getWord32le
m <- getWord8
f <- getWord8
unless (m == 0x00 && f == 0x01) $ fail "Not a witness transaction"
is <- replicateList =<< deserialize
os <- replicateList =<< deserialize
w <- parseWitnessData $ length is
l <- getWord32le
return
Tx{txVersion = v, txIn = is, txOut = os, txWitness = w, txLockTime = l}
inputs <- replicateList =<< deserialize
outputs <- replicateList =<< deserialize
witness <- parseWitnessData $ length inputs
locktime <- getWord32le
return Tx {..}
where
replicateList (VarInt c) = replicateM (fromIntegral c) deserialize
-- | Witness data deserializer. Requires count of inputs.
parseWitnessData :: MonadGet m => Int -> m WitnessData
parseWitnessData :: (MonadGet m) => Int -> m WitnessData
parseWitnessData n = replicateM n parseWitnessStack
where
parseWitnessStack = do
@ -250,7 +241,7 @@ parseWitnessData n = replicateM n parseWitnessStack
getByteString $ fromIntegral i
-- | Witness data serializer.
putWitnessData :: MonadPut m => WitnessData -> m ()
putWitnessData :: (MonadPut m) => WitnessData -> m ()
putWitnessData = mapM_ putWitnessStack
where
putWitnessStack ws = do
@ -262,7 +253,8 @@ putWitnessData = mapM_ putWitnessStack
instance FromJSON Tx where
parseJSON = withObject "Tx" $ \o ->
Tx <$> o .: "version"
Tx
<$> o .: "version"
<*> o .: "inputs"
<*> o .: "outputs"
<*> (mapM (mapM f) =<< o .: "witnessdata")
@ -273,29 +265,32 @@ instance FromJSON Tx where
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
[ "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
)
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
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)
@ -322,30 +317,32 @@ instance Serialize TxIn where
instance FromJSON TxIn where
parseJSON =
withObject "TxIn" $ \o ->
TxIn <$> o .: "prevoutput"
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
[ "prevoutput" .= o,
"inputscript" .= encodeHex s,
"sequence" .= q
]
toEncoding (TxIn o s q) =
pairs
( "prevoutput" .= o
<> "inputscript" .= encodeHex s
<> "sequence" .= 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
value :: !Word64,
-- | pubkey script
script :: !ByteString
}
deriving (Eq, Show, Read, Ord, Generic, Hashable, NFData)
@ -370,22 +367,28 @@ instance Serialize TxOut where
instance FromJSON TxOut where
parseJSON =
withObject "TxOut" $ \o ->
TxOut <$> o .: "value"
<*> (maybe mzero return . decodeHex =<< o .: "outputscript")
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)
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
hash :: !TxHash,
-- | position of output in previous transaction
index :: !Word32
}
deriving (Show, Read, Eq, Ord, Generic, Hashable, NFData)
@ -410,13 +413,17 @@ instance FromJSON OutPoint where
instance ToJSON OutPoint where
toJSON (OutPoint h i) = object ["txid" .= h, "index" .= i]
toEncoding (OutPoint h i) = pairs ("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
{ hash = "0000000000000000000000000000000000000000000000000000000000000000",
index = maxBound
}

View File

@ -1,42 +1,42 @@
{-# LANGUAGE OverloadedStrings #-}
{- |
Module : Haskoin.Transaction.Genesis
Copyright : No rights reserved
License : UNLICENSE
Maintainer : jprupp@protonmail.ch
Stability : experimental
Portability : POSIX
-- |
-- 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
Code related to transactions parsing and serialization.
-}
module Haskoin.Transaction.Genesis (
genesisTx,
) where
import Data.String (fromString)
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 =
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"
decodeHex
"04ffff001d0104455468652054696d65732030332f4a616e2f323030392043686\
\16e63656c6c6f72206f6e206272696e6b206f66207365636f6e64206261696c6f\
\757420666f722062616e6b73"
Just pubKeyBS =
decodeHex
"04678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb\
\649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5f"
Right pubKey =
unmarshal ctx pubKeyBS
output = PayPK pubKey
z = "0000000000000000000000000000000000000000000000000000000000000000"

File diff suppressed because it is too large Load Diff

View File

@ -1,22 +1,23 @@
{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE NoFieldSelectors #-}
{- |
Module : Haskoin.Transaction.Segwit
Copyright : No rights reserved
License : MIT
Maintainer : jprupp@protonmail.ch
Stability : experimental
Portability : POSIX
Types to represent segregated witness data and auxilliary functions to
manipulate it. See [BIP 141](https://github.com/bitcoin/bips/blob/master/bip-0141.mediawiki)
and [BIP 143](https://github.com/bitcoin/bips/blob/master/bip-0143.mediawiki) for
details.
-}
module Haskoin.Transaction.Segwit (
-- * Segwit
-- |
-- 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 (..),
@ -26,77 +27,83 @@ module Haskoin.Transaction.Segwit (
calcWitnessProgram,
simpleInputStack,
toWitnessStack,
) where
)
where
import Crypto.Secp256k1
import Data.ByteString (ByteString)
import Data.Bytes.Get
import Data.Bytes.Put
import Data.Bytes.Serial
import Haskoin.Data
import Haskoin.Keys.Common
import Haskoin.Script
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
-}
-- | Test if a 'ScriptOutput' is P2WPKH or P2WSH
--
-- @since 0.11.0.0
isSegwit :: ScriptOutput -> Bool
isSegwit = \case
PayWitnessPKHash {} -> True
PayWitnessScriptHash {} -> True
_ -> False
{- | High level represenation of a (v0) witness program
@since 0.11.0.0
-}
-- | High level represenation of a (v0) witness program
--
-- @since 0.11.0.0
data WitnessProgram
= P2WPKH WitnessProgramPKH
| P2WSH WitnessProgramSH
| EmptyWitnessProgram
deriving (Eq, Show)
deriving (Eq)
{- | Encode a witness program
-- | Encode a witness program
--
-- @since 0.11.0.0
toWitnessStack :: Network -> Ctx -> WitnessProgram -> WitnessStack
toWitnessStack net ctx = \case
P2WPKH (WitnessProgramPKH sig key) ->
[encodeTxSig net ctx sig, marshal ctx key]
P2WSH (WitnessProgramSH stack scr) ->
stack <> [runPutS (serialize scr)]
EmptyWitnessProgram ->
mempty
@since 0.11.0.0
-}
toWitnessStack :: WitnessProgram -> WitnessStack
toWitnessStack = \case
P2WPKH (WitnessProgramPKH sig key) -> [encodeTxSig sig, runPutS (serialize key)]
P2WSH (WitnessProgramSH stack scr) -> stack <> [runPutS (serialize scr)]
EmptyWitnessProgram -> mempty
{- | High level representation of a P2WPKH witness
@since 0.11.0.0
-}
-- | High level representation of a P2WPKH witness
--
-- @since 0.11.0.0
data WitnessProgramPKH = WitnessProgramPKH
{ witnessSignature :: !TxSignature
, witnessPubKey :: !PubKeyI
{ signature :: !TxSignature,
key :: !PublicKey
}
deriving (Eq, Show)
deriving (Eq)
{- | High-level representation of a P2WSH witness
@since 0.11.0.0
-}
-- | High-level representation of a P2WSH witness
--
-- @since 0.11.0.0
data WitnessProgramSH = WitnessProgramSH
{ witnessScriptHashStack :: ![ByteString]
, witnessScriptHashScript :: !Script
{ stack :: ![ByteString],
script :: !Script
}
deriving (Eq, Show)
{- | Calculate the witness program from the transaction data
@since 0.11.0.0
-}
-- | 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
Network ->
Ctx ->
ScriptOutput ->
WitnessStack ->
Either String WitnessProgram
viewWitnessProgram net ctx so witness = case so of
PayWitnessPKHash _ | length witness == 2 -> do
sig <- decodeTxSig net $ head witness
pubkey <- runGetS deserialize $ witness !! 1
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
@ -105,52 +112,64 @@ viewWitnessProgram net so witness = case so of
| 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
-}
-- | Analyze the witness, trying to match it with standard input structures
--
-- @since 0.11.0.0
decodeWitnessInput ::
Network ->
Ctx ->
WitnessProgram ->
Either String (Maybe ScriptOutput, SimpleInput)
decodeWitnessInput net = \case
decodeWitnessInput net ctx = \case
P2WPKH (WitnessProgramPKH sig key) -> return (Nothing, SpendPKHash sig key)
P2WSH (WitnessProgramSH st scr) -> do
so <- decodeOutput scr
so <- decodeOutput ctx scr
fmap (Just so,) $ case (so, st) of
(PayPK _, [sigBS]) ->
SpendPK <$> decodeTxSig net sigBS
SpendPK <$> decodeTxSig net ctx sigBS
(PayPKHash _, [sigBS, keyBS]) ->
SpendPKHash <$> decodeTxSig net sigBS <*> runGetS deserialize keyBS
SpendPKHash
<$> decodeTxSig net ctx sigBS
<*> unmarshal ctx keyBS
(PayMulSig _ _, "" : sigsBS) ->
SpendMulSig <$> traverse (decodeTxSig net) 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
-- | Create the witness program for a standard input
--
-- @since 0.11.0.0
calcWitnessProgram ::
Network ->
Ctx ->
ScriptOutput ->
ScriptInput ->
Either String WitnessProgram
calcWitnessProgram net ctx so si = case (so, si) of
(PayWitnessPKHash {}, RegularInput (SpendPKHash sig pk)) ->
Right $ p2wpkh sig pk
(PayScriptHash {}, RegularInput (SpendPKHash sig pk)) ->
Right $ p2wpkh sig pk
(PayWitnessScriptHash {}, ScriptHashInput i o) ->
Right $ p2wsh i o
(PayScriptHash {}, ScriptHashInput i o) ->
Right $ p2wsh i o
_ -> Left "calcWitnessProgram: Invalid segwit SigInput"
where
p2wpkh sig = 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
-- | Create the witness stack required to spend a standard P2WSH input
--
-- @since 0.11.0.0
simpleInputStack :: Network -> Ctx -> SimpleInput -> [ByteString]
simpleInputStack net ctx = \case
SpendPK sig -> [f sig]
SpendPKHash sig k -> [f sig, runPutS (serialize k)]
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

View File

@ -1,21 +1,26 @@
{-# 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 (..),
-- |
-- 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,
@ -28,104 +33,106 @@ module Haskoin.Transaction.Taproot (
viewTaprootWitness,
encodeTaprootWitness,
verifyScriptPathData,
) where
)
where
import Control.Applicative (many)
import Control.Monad ((<=<))
import Crypto.Hash (
Digest,
import Crypto.Hash
( Digest,
SHA256,
digestFromByteString,
hashFinalize,
hashUpdate,
hashUpdates,
)
import Data.Aeson (FromJSON (parseJSON), ToJSON (toJSON), withText)
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 qualified Data.ByteArray as BA
import Data.ByteArray qualified as BA
import Data.ByteString (ByteString)
import qualified Data.ByteString as BS
import Data.Bytes.Get (getBytes, runGetS)
import Data.Bytes.Put (putByteString, runPutS)
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 (PubKey, initTaggedHash, tweak, tweakAddPubKey)
import Haskoin.Keys.Common (PubKeyI (PubKeyI), pubKeyPoint)
import Haskoin.Script.Common (Script)
import Haskoin.Script.Standard (ScriptOutput (PayWitness))
import Haskoin.Transaction.Common (WitnessStack)
import Haskoin.Util (decodeHex, eitherToMaybe, encodeHex)
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 {xOnlyPubKey :: PubKey}
deriving (Show)
-- | 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
k1 == k2 = runPutS (serialize k1) == runPutS (serialize k2)
XOnlyPubKey k1 == XOnlyPubKey k2 = f k1 == f k2
where
f = BS.take 32 . (.get)
instance Serial XOnlyPubKey where
serialize (XOnlyPubKey pk) =
instance Marshal Ctx XOnlyPubKey where
marshalPut ctx (XOnlyPubKey pk) =
putByteString
. BS.drop 1
. runPutS
. serialize
$ PubKeyI pk True
deserialize =
either fail (pure . XOnlyPubKey . pubKeyPoint)
. runGetS deserialize
. marshal ctx
$ PublicKey pk True
marshalGet ctx =
either fail (pure . XOnlyPubKey . (\PublicKey {point} -> point))
. unmarshal ctx
. BS.cons 0x02
=<< getBytes 32
instance Serialize XOnlyPubKey where
put = serialize
get = deserialize
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
instance Binary XOnlyPubKey where
put = serialize
get = deserialize
marshalValue ctx =
String . encodeHex . marshal ctx
-- | Hex encoding
instance FromJSON XOnlyPubKey where
parseJSON =
withText "XOnlyPubKey" $
either fail pure
. (runGetS deserialize <=< maybe (Left "Unable to decode hex") Right . decodeHex)
-- | Hex encoding
instance ToJSON XOnlyPubKey where
toJSON = toJSON . encodeHex . runPutS . serialize
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
-}
-- | 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
-}
-- | 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
@ -138,10 +145,9 @@ getMerkleProofs = getProofs mempty
updateProof proofInit branchCommitment (v, s, proofTail) =
(v, s, reverse $ proofInit <> (branchCommitment : proofTail))
{- | Calculate the root hash for this tree.
@since 0.21.0
-}
-- | Calculate the root hash for this tree.
--
-- @since 0.21.0
mastCommitment :: MAST -> Digest SHA256
mastCommitment = \case
MASTBranch leftBranch rightBranch ->
@ -154,8 +160,8 @@ hashBranch hashA hashB =
hashFinalize $
hashUpdates
(initTaggedHash "TapBranch")
[ min hashA hashB
, max hashA hashB
[ min hashA hashB,
max hashA hashB
]
leafHash :: TapLeafVersion -> Script -> Digest SHA256
@ -170,141 +176,140 @@ leafHash leafVersion leafScript =
where
scriptBytes = runPutS $ serialize leafScript
{- | Representation of a full taproot output.
@since 0.21.0
-}
-- | Representation of a full taproot output.
--
-- @since 0.21.0
data TaprootOutput = TaprootOutput
{ taprootInternalKey :: PubKey
, taprootMAST :: Maybe MAST
{ internalKey :: PubKey,
mast :: Maybe MAST
}
deriving (Show)
-- | @since 0.21.0
taprootOutputKey :: TaprootOutput -> PubKey
taprootOutputKey TaprootOutput{taprootInternalKey, taprootMAST} =
fromMaybe keyFail $ tweak commitment >>= tweakAddPubKey taprootInternalKey
taprootOutputKey :: Ctx -> TaprootOutput -> PubKey
taprootOutputKey ctx TaprootOutput {..} =
fromMaybe keyFail $
tweak commitment >>= tweakAddPubKey ctx internalKey
where
commitment = taprootCommitment taprootInternalKey $ mastCommitment <$> taprootMAST
commitment =
taprootCommitment ctx internalKey $
mastCommitment <$> mast
keyFail = error "haskoin-core taprootOutputKey: key derivation failed"
taprootCommitment :: PubKey -> Maybe (Digest SHA256) -> ByteString
taprootCommitment internalKey merkleRoot =
BA.convert . hashFinalize
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 . serialize $ XOnlyPubKey internalKey
keyBytes = runPutS . marshalPut ctx $ XOnlyPubKey internalKey
{- | Generate the output script for a taproot output
-- | 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
@since 0.21.0
-}
taprootScriptOutput :: TaprootOutput -> ScriptOutput
taprootScriptOutput = PayWitness 0x01 . runPutS . serialize . XOnlyPubKey . taprootOutputKey
{- | Comprehension of taproot witness data
@since 0.21.0
-}
-- | Comprehension of taproot witness data
--
-- @since 0.21.0
data TaprootWitness
= -- | Signature
KeyPathSpend ByteString
| ScriptPathSpend ScriptPathData
deriving (Eq, Show)
deriving (Eq)
-- | @since 0.21.0
data ScriptPathData = ScriptPathData
{ scriptPathAnnex :: Maybe ByteString
, scriptPathStack :: [ByteString]
, scriptPathScript :: Script
, scriptPathExternalIsOdd :: Bool
, -- | This value is masked by 0xFE
scriptPathLeafVersion :: Word8
, scriptPathInternalKey :: PubKey
, scriptPathControl :: [ByteString]
{ annex :: Maybe ByteString,
stack :: [ByteString],
script :: Script,
extIsOdd :: Bool,
-- | This value is masked by 0xFE
leafVersion :: Word8,
internalKey :: PubKey,
control :: [ByteString]
}
deriving (Eq, Show)
deriving (Eq)
{- | Try to interpret a 'WitnessStack' as taproot witness data.
@since 0.21.0
-}
viewTaprootWitness :: WitnessStack -> Maybe TaprootWitness
viewTaprootWitness witnessStack = case reverse witnessStack of
-- | 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 scriptPathAnnex = \case
scriptBytes : controlBytes : scriptPathStack -> do
scriptPathScript <- eitherToMaybe $ runGetS deserialize scriptBytes
(v, scriptPathInternalKey, scriptPathControl) <- deconstructControl controlBytes
pure . ScriptPathSpend $
ScriptPathData
{ scriptPathAnnex
, scriptPathStack
, scriptPathScript
, scriptPathExternalIsOdd = odd v
, scriptPathLeafVersion = v .&. 0xFE
, scriptPathInternalKey
, scriptPathControl
}
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
k <- xOnlyPubKey <$> deserialize
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 :: TaprootWitness -> WitnessStack
encodeTaprootWitness = \case
-- | 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 ->
scriptPathStack scriptPathData
<> [ runPutS . serialize $ scriptPathScript scriptPathData
, mconcat
[ BS.pack [scriptPathLeafVersion scriptPathData .|. parity scriptPathData]
, runPutS . serialize . XOnlyPubKey $ scriptPathInternalKey scriptPathData
, mconcat $ scriptPathControl scriptPathData
]
, fromMaybe mempty $ scriptPathAnnex scriptPathData
]
ScriptPathSpend scriptPathData -> wit scriptPathData
where
parity = bool 0 1 . scriptPathExternalIsOdd
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
-}
-- | Verify that the script path spend is valid, except for script execution.
--
-- @since 0.21.0
verifyScriptPathData ::
Ctx ->
-- | Output key
PubKey ->
ScriptPathData ->
Bool
verifyScriptPathData outputKey scriptPathData = fromMaybe False $ do
tweak commitment >>= fmap onComputedKey . tweakAddPubKey (scriptPathInternalKey scriptPathData)
verifyScriptPathData ctx outkey spd = fromMaybe False $ do
tweak commitment
>>= fmap onComputedKey
. tweakAddPubKey ctx spd.internalKey
where
onComputedKey computedKey =
XOnlyPubKey outputKey == XOnlyPubKey computedKey
&& expectedParity == keyParity computedKey
commitment = taprootCommitment (scriptPathInternalKey scriptPathData) (Just merkleRoot)
XOnlyPubKey outkey == XOnlyPubKey computedKey
&& expectedParity == keyParity ctx computedKey
commitment =
taprootCommitment ctx spd.internalKey (Just merkleRoot)
merkleRoot =
foldl' hashBranch theLeafHash
. mapMaybe (digestFromByteString @SHA256)
$ scriptPathControl scriptPathData
theLeafHash = (leafHash <$> (.&. 0xFE) . scriptPathLeafVersion <*> scriptPathScript) scriptPathData
expectedParity = bool 0 1 $ scriptPathExternalIsOdd scriptPathData
foldl' hashBranch theLeafHash $
mapMaybe (digestFromByteString @SHA256) spd.control
theLeafHash =
(leafHash <$> (.&. 0xFE) . (.leafVersion) <*> (.script))
spd
expectedParity = bool 0 1 spd.extIsOdd
keyParity :: PubKey -> Word8
keyParity key = case BS.unpack . runPutS . serialize $ PubKeyI key True of
keyParity :: Ctx -> PubKey -> Word8
keyParity ctx key =
case BS.unpack . marshal ctx $ PublicKey key True of
0x02 : _ -> 0x00
_ -> 0x01

View File

@ -1,8 +1,3 @@
{-# LANGUAGE CPP #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE ImportQualifiedPost #-}
-- |
-- Module : Haskoin.Util
-- Copyright : No rights reserved
@ -11,376 +6,12 @@
-- Stability : experimental
-- Portability : POSIX
--
-- This module defines various utility functions used across the library.
-- Marshalling and helper functions.
module Haskoin.Util
( -- * ByteString Helpers
bsToInteger,
integerToBS,
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,
( module Marshal,
module Helpers,
)
where
import Control.Monad
import Control.Monad.Except (ExceptT (..), liftEither)
import Data.Aeson.Types
( Options (..),
SumEncoding (..),
defaultOptions,
defaultTaggedObject,
)
import Data.Base16.Types (assertBase16, extractBase16)
import Data.Bits
import Data.ByteString (ByteString)
import Data.ByteString qualified as BS
import Data.ByteString.Base16 qualified as B16
import Data.ByteString.Builder
import Data.ByteString.Lazy qualified as BL
import Data.ByteString.Lazy.Base16 qualified as BL16
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.Text (Text)
import Data.Text.Encoding qualified as E
import Data.Text.Lazy qualified as TL
import Data.Text.Lazy.Encoding qualified as EL
import Data.Word
-- 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 :: BL.ByteString -> Builder
hexBuilder = lazyByteStringHex
encodeHex :: ByteString -> Text
encodeHex = extractBase16 . B16.encodeBase16
-- | Encode as string of human-readable hex characters.
encodeHexLazy :: BL.ByteString -> TL.Text
encodeHexLazy = extractBase16 . BL16.encodeBase16
decodeHex :: Text -> Maybe ByteString
decodeHex t =
if B16.isBase16 u8
then Just . B16.decodeBase16 $ assertBase16 u8
else Nothing
where
u8 = E.encodeUtf8 t
-- | Decode string of human-readable hex characters.
decodeHexLazy :: TL.Text -> Maybe BL.ByteString
decodeHexLazy t =
if BL16.isBase16 u8
then Just . BL16.decodeBase16 $ assertBase16 u8
else Nothing
where
u8 = EL.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 = 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 ::
-- | 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 (BS.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
import Haskoin.Util.Helpers as Helpers
import Haskoin.Util.Marshal as Marshal

View File

@ -1,16 +1,16 @@
{- |
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 : Haskoin.Test
-- Copyright : No rights reserved
-- License : MIT
-- Maintainer : jprupp@protonmail.ch
-- Stability : experimental
-- Portability : POSIX
--
-- Arbitrary instances for testing.
module Haskoin.Util.Arbitrary
( module X,
)
where
import Haskoin.Util.Arbitrary.Address as X
import Haskoin.Util.Arbitrary.Block as X

View File

@ -1,19 +1,18 @@
{-# 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.Data
import Haskoin.Network.Constants
import Haskoin.Network.Data
import Haskoin.Util.Arbitrary.Crypto
import Haskoin.Util.Arbitrary.Util
import Test.QuickCheck
@ -26,11 +25,11 @@ arbitraryAddress = oneof [arbitraryPubKeyAddress, arbitraryScriptAddress]
arbitraryAddressAll :: Gen Address
arbitraryAddressAll =
oneof
[ arbitraryPubKeyAddress
, arbitraryScriptAddress
, arbitraryWitnessPubKeyAddress
, arbitraryWitnessScriptAddress
, arbitraryWitnessAddress
[ arbitraryPubKeyAddress,
arbitraryScriptAddress,
arbitraryWitnessPubKeyAddress,
arbitraryWitnessScriptAddress,
arbitraryWitnessAddress
]
-- | Arbitrary valid combination of (Network, Address)

View File

@ -1,16 +1,16 @@
{- |
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.Data
import Haskoin.Crypto (Ctx)
import Haskoin.Network.Data
import Haskoin.Util.Arbitrary.Crypto
import Haskoin.Util.Arbitrary.Network
import Haskoin.Util.Arbitrary.Transaction
@ -18,17 +18,18 @@ import Haskoin.Util.Arbitrary.Util
import Test.QuickCheck
-- | Block full or arbitrary transactions.
arbitraryBlock :: Network -> Gen Block
arbitraryBlock net = do
arbitraryBlock :: Network -> Ctx -> Gen Block
arbitraryBlock net ctx = do
h <- arbitraryBlockHeader
c <- choose (0, 10)
txs <- vectorOf c (arbitraryTx net)
txs <- vectorOf c (arbitraryTx net ctx)
return $ Block h txs
-- | Block header with random hash.
arbitraryBlockHeader :: Gen BlockHeader
arbitraryBlockHeader =
BlockHeader <$> arbitrary
BlockHeader
<$> arbitrary
<*> arbitraryBlockHash
<*> arbitraryHash256
<*> arbitrary
@ -42,14 +43,16 @@ arbitraryBlockHash = BlockHash <$> arbitraryHash256
-- | Arbitrary 'GetBlocks' object with at least one block hash.
arbitraryGetBlocks :: Gen GetBlocks
arbitraryGetBlocks =
GetBlocks <$> arbitrary
GetBlocks
<$> arbitrary
<*> listOf1 arbitraryBlockHash
<*> arbitraryBlockHash
-- | Arbitrary 'GetHeaders' object with at least one block header.
arbitraryGetHeaders :: Gen GetHeaders
arbitraryGetHeaders =
GetHeaders <$> arbitrary
GetHeaders
<$> arbitrary
<*> listOf1 arbitraryBlockHash
<*> arbitraryBlockHash
@ -71,13 +74,11 @@ arbitraryMerkleBlock = do
-- | Arbitrary 'BlockNode'
arbitraryBlockNode :: Gen BlockNode
arbitraryBlockNode =
oneof
[ BlockNode
BlockNode
<$> arbitraryBlockHeader
<*> choose (0, maxBound)
<*> arbitrarySizedNatural
<*> arbitraryBlockHash
]
-- | Arbitrary 'HeaderMemory'
arbitraryHeaderMemory :: Gen HeaderMemory

View File

@ -1,11 +1,10 @@
{- |
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

View File

@ -1,33 +1,34 @@
{- |
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 Crypto.Secp256k1
import Data.Bits (clearBit)
import Data.Coerce (coerce)
import Data.List (foldl')
import Data.Word (Word32)
import Haskoin.Crypto
import Haskoin.Keys.Common
import Haskoin.Keys.Extended
import Haskoin.Keys.Extended.Internal (Fingerprint (..))
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 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
@ -35,15 +36,16 @@ arbitraryFingerprint = Fingerprint <$> arbitrary
-- | Arbitrary extended private key.
arbitraryXPrvKey :: Gen XPrvKey
arbitraryXPrvKey =
XPrvKey <$> arbitrary
XPrvKey
<$> arbitrary
<*> arbitraryFingerprint
<*> arbitrary
<*> arbitraryHash256
<*> arbitrary
-- | Arbitrary extended public key with its corresponding private key.
arbitraryXPubKey :: Gen (XPrvKey, XPubKey)
arbitraryXPubKey = (\k -> (k, deriveXPubKey k)) <$> arbitraryXPrvKey
arbitraryXPubKey :: Ctx -> Gen (XPrvKey, XPubKey)
arbitraryXPubKey ctx = (\k -> (k, deriveXPubKey ctx k)) <$> arbitraryXPrvKey
{- Custom derivations -}
@ -55,8 +57,8 @@ genIndex = (`clearBit` 31) <$> arbitrary
arbitraryBip32PathIndex :: Gen Bip32PathIndex
arbitraryBip32PathIndex =
oneof
[ Bip32SoftIndex <$> genIndex
, Bip32HardIndex <$> genIndex
[ Bip32SoftIndex <$> genIndex,
Bip32HardIndex <$> genIndex
]
-- | Arbitrary BIP-32 derivation path composed of only hardened derivations.
@ -71,24 +73,22 @@ arbitrarySoftPath = foldl' (:/) Deriv <$> listOf genIndex
arbitraryDerivPath :: Gen DerivPath
arbitraryDerivPath = concatBip32Segments <$> listOf arbitraryBip32PathIndex
{- | Arbitrary parsed derivation path. Can contain 'ParsedPrv', 'ParsedPub' or
'ParsedEmpty' elements.
-}
-- | Arbitrary parsed derivation path. Can contain 'ParsedPrv', 'ParsedPub' or
-- 'ParsedEmpty' elements.
arbitraryParsedPath :: Gen ParsedPath
arbitraryParsedPath =
oneof
[ ParsedPrv <$> arbitraryDerivPath
, ParsedPub <$> arbitraryDerivPath
, ParsedEmpty <$> arbitraryDerivPath
[ 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
-- | Arbitrary message hash, private key, nonce and corresponding signature. The
-- signature is generated with a random message, random private key and a random
-- nonce.
arbitrarySignature :: Ctx -> Gen (Hash256, SecKey, Sig)
arbitrarySignature ctx = do
m <- arbitraryHash256
key <- arbitrary
let sig = signHash key m
let sig = signHash ctx key m
return (m, key, sig)

View File

@ -1,14 +1,14 @@
{- |
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.Data
import Haskoin.Crypto (Ctx)
import Haskoin.Network.Data
import Haskoin.Network.Message
import Haskoin.Util.Arbitrary.Block
import Haskoin.Util.Arbitrary.Crypto
@ -19,34 +19,35 @@ import Test.QuickCheck
-- | Arbitrary 'MessageHeader'.
arbitraryMessageHeader :: Gen MessageHeader
arbitraryMessageHeader =
MessageHeader <$> arbitrary
MessageHeader
<$> arbitrary
<*> arbitraryMessageCommand
<*> arbitrary
<*> arbitraryCheckSum32
-- | Arbitrary 'Message'.
arbitraryMessage :: Network -> Gen Message
arbitraryMessage net =
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
, 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
[ 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
]

View File

@ -1,11 +1,10 @@
{- |
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)
@ -37,8 +36,8 @@ arbitraryNetworkAddress = do
b <- arbitrary
c <- arbitrary
d <- arbitrary
return $ SockAddrInet6 (fromIntegral p) 0 (a, b, c, d) 0
, return $ SockAddrInet (fromIntegral (p :: Word16)) a
return $ SockAddrInet6 (fromIntegral p) 0 (a, b, c, d) 0,
return $ SockAddrInet (fromIntegral (p :: Word16)) a
]
let n = sockToHostAddress d
return $ NetworkAddress s n
@ -62,7 +61,8 @@ arbitraryInv1 = Inv <$> listOf1 arbitraryInvVector
-- | Arbitrary 'Version'.
arbitraryVersion :: Gen Version
arbitraryVersion =
Version <$> arbitrary
Version
<$> arbitrary
<*> arbitrary
<*> arbitrary
<*> arbitraryNetworkAddress
@ -76,9 +76,8 @@ arbitraryVersion =
arbitraryAddr1 :: Gen Addr
arbitraryAddr1 = Addr <$> listOf1 arbitraryNetworkAddressTime
{- | Arbitrary 'Alert' with random payload and signature. Signature is not
valid.
-}
-- | Arbitrary 'Alert' with random payload and signature. Signature is not
-- valid.
arbitraryAlert :: Gen Alert
arbitraryAlert = Alert <$> arbitraryVarString <*> arbitraryVarString
@ -90,8 +89,8 @@ arbitraryReject = do
s <- arbitraryVarString
d <-
oneof
[ return BS.empty
, BS.pack <$> vectorOf 32 arbitrary
[ return BS.empty,
BS.pack <$> vectorOf 32 arbitrary
]
return $ Reject m c s d
@ -99,14 +98,14 @@ arbitraryReject = do
arbitraryRejectCode :: Gen RejectCode
arbitraryRejectCode =
elements
[ RejectMalformed
, RejectInvalid
, RejectInvalid
, RejectDuplicate
, RejectNonStandard
, RejectDust
, RejectInsufficientFee
, RejectCheckpoint
[ RejectMalformed,
RejectInvalid,
RejectInvalid,
RejectDuplicate,
RejectNonStandard,
RejectDust,
RejectInsufficientFee,
RejectCheckpoint
]
-- | Arbitrary non-empty 'GetData'.
@ -129,14 +128,13 @@ arbitraryPong = Pong <$> arbitrary
arbitraryBloomFlags :: Gen BloomFlags
arbitraryBloomFlags =
elements
[ BloomUpdateNone
, BloomUpdateAll
, BloomUpdateP2PubKeyOnly
[ BloomUpdateNone,
BloomUpdateAll,
BloomUpdateP2PubKeyOnly
]
{- | Arbitrary bloom filter with its corresponding number of elements
and false positive rate.
-}
-- | Arbitrary bloom filter with its corresponding number of elements
-- and false positive rate.
arbitraryBloomFilter :: Gen (Int, Double, BloomFilter)
arbitraryBloomFilter = do
n <- choose (0, 100000)
@ -160,24 +158,24 @@ 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)))
[ 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)))
]

View File

@ -1,23 +1,25 @@
{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE ImportQualifiedPost #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedRecordDot #-}
{- |
Module : Haskoin.Test.Script
Copyright : No rights reserved
License : MIT
Maintainer : jprupp@protonmail.ch
Stability : experimental
Portability : POSIX
-}
-- |
-- Module : Haskoin.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.ByteString qualified as B
import Data.Maybe
import Data.Word
import Haskoin.Address
import Haskoin.Constants
import Haskoin.Data
import Haskoin.Keys.Common
import Haskoin.Crypto.Keys.Common
import Haskoin.Network.Constants
import Haskoin.Network.Data
import Haskoin.Script
import Haskoin.Transaction.Common
import Haskoin.Util
@ -36,153 +38,153 @@ 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
[ 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
[ 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'.
@ -199,29 +201,28 @@ arbitraryValidSigHash net = do
sh <- elements [sigHashAll, sigHashNone, sigHashSingle]
f1 <-
elements $
if isJust (getSigHashForkId net)
if isJust net.sigHashForkId
then [id, setForkIdFlag]
else [id]
f2 <- elements [id, setAnyoneCanPayFlag]
f2 <- elements [id, setAnyoneCanPay]
return $ f1 $ f2 sh
arbitrarySigHashFlag :: Gen SigHashFlag
arbitrarySigHashFlag =
elements
[ SIGHASH_ALL
, SIGHASH_NONE
, SIGHASH_SINGLE
, SIGHASH_FORKID
, SIGHASH_ANYONECANPAY
[ 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
-- | Arbitrary message hash, private key and corresponding 'TxSignature'. The
-- signature is generated deterministically using a random message and a random
-- private key.
arbitraryTxSignature :: Network -> Ctx -> Gen (TxHash, SecKey, TxSignature)
arbitraryTxSignature net ctx = do
(m, key, sig) <- arbitrarySignature ctx
sh <- (fromIntegral <$> (arbitrary :: Gen Word8)) `suchThat` filterBad
let txsig = TxSignature sig sh
return (TxHash m, key, txsig)
@ -229,14 +230,14 @@ arbitraryTxSignature net = do
filterBad sh =
not $
isSigHashUnknown sh
|| isNothing (getSigHashForkId net) && hasForkIdFlag sh
|| isNothing net.sigHashForkId && hasForkIdFlag sh
-- | Arbitrary transaction signature that could also be empty.
arbitraryTxSignatureEmpty :: Network -> Gen TxSignature
arbitraryTxSignatureEmpty net =
arbitraryTxSignatureEmpty :: Network -> Ctx -> Gen TxSignature
arbitraryTxSignatureEmpty net ctx =
frequency
[ (1, return TxSignatureEmpty)
, (10, lst3 <$> arbitraryTxSignature net)
[ (1, return TxSignatureEmpty),
(10, lst3 <$> arbitraryTxSignature net ctx)
]
-- | Arbitrary m of n parameters.
@ -247,37 +248,36 @@ arbitraryMSParam = do
return (m, n)
-- | Arbitrary 'ScriptOutput' (Can by any valid type).
arbitraryScriptOutput :: Network -> Gen ScriptOutput
arbitraryScriptOutput net =
arbitraryScriptOutput :: Network -> Ctx -> Gen ScriptOutput
arbitraryScriptOutput net ctx =
oneof $
[ arbitraryPKOutput
, arbitraryPKHashOutput
, arbitraryMSOutput
, arbitrarySHOutput
, arbitraryDCOutput
[ arbitraryPKOutput ctx,
arbitraryPKHashOutput,
arbitraryMSOutput ctx,
arbitrarySHOutput,
arbitraryDCOutput
]
++ if getSegWit net
++ if net.segWit
then
[ arbitraryWPKHashOutput
, arbitraryWSHOutput
, arbitraryWitOutput
[ arbitraryWPKHashOutput,
arbitraryWSHOutput,
arbitraryWitOutput
]
else []
{- | Arbitrary 'ScriptOutput' of type 'PayPK', 'PayPKHash' or 'PayMS'
(Not 'PayScriptHash', 'DataCarrier', or SegWit)
-}
arbitrarySimpleOutput :: Gen ScriptOutput
arbitrarySimpleOutput =
-- | Arbitrary 'ScriptOutput' of type 'PayPK', 'PayPKHash' or 'PayMS'
-- (Not 'PayScriptHash', 'DataCarrier', or SegWit)
arbitrarySimpleOutput :: Ctx -> Gen ScriptOutput
arbitrarySimpleOutput ctx =
oneof
[ arbitraryPKOutput
, arbitraryPKHashOutput
, arbitraryMSOutput
[ 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
@ -300,124 +300,121 @@ arbitraryWitOutput = do
return $ PayWitness ver bs
-- | Arbitrary 'ScriptOutput' of type 'PayMS'.
arbitraryMSOutput :: Gen ScriptOutput
arbitraryMSOutput = do
arbitraryMSOutput :: Ctx -> Gen ScriptOutput
arbitraryMSOutput ctx = do
(m, n) <- arbitraryMSParam
keys <- map snd <$> vectorOf n arbitraryKeyPair
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
arbitraryMSOutputC :: Ctx -> Gen ScriptOutput
arbitraryMSOutputC ctx = do
(m, n) <- arbitraryMSParam
keys <-
map snd
<$> vectorOf n (arbitraryKeyPair `suchThat` (pubKeyCompressed . 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 =
arbitraryScriptInput :: Network -> Ctx -> Gen ScriptInput
arbitraryScriptInput net ctx =
oneof
[ arbitraryPKInput net
, arbitraryPKHashInput net
, arbitraryMSInput net
, arbitrarySHInput net
[ 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 =
-- | Arbitrary 'ScriptInput' of type 'SpendPK', 'SpendPKHash' or 'SpendMulSig'
-- (not 'ScriptHashInput')
arbitrarySimpleInput :: Network -> Ctx -> Gen ScriptInput
arbitrarySimpleInput net ctx =
oneof
[ arbitraryPKInput net
, arbitraryPKHashInput net
, arbitraryMSInput net
[ 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
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
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)
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
arbitraryMSInput :: Network -> Ctx -> Gen ScriptInput
arbitraryMSInput net ctx = do
m <- fst <$> arbitraryMSParam
sigs <- vectorOf m (arbitraryTxSignatureEmpty net)
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
-- | Arbitrary 'ScriptInput' of type 'ScriptHashInput' containing a
-- 'RedeemScript' of type 'PayMulSig' and an input of type 'SpendMulSig'.
arbitraryMulSigSHInput :: Network -> Ctx -> Gen ScriptInput
arbitraryMulSigSHInput net ctx =
arbitraryMSOutput ctx >>= \case
rdm@(PayMulSig _ m) -> do
sigs <- vectorOf m (arbitraryTxSignatureEmpty net)
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
-- | Arbitrary 'ScriptInput' of type 'ScriptHashInput' containing a
-- 'RedeemScript' of type 'PayMulSig' and an input of type 'SpendMulSig'.
arbitraryMulSigSHInputC :: Network -> Ctx -> Gen ScriptInput
arbitraryMulSigSHInputC net ctx =
arbitraryMSOutputC ctx >>= \case
rdm@(PayMulSig _ m) -> do
sigs <- vectorOf m (arbitraryTxSignatureEmpty net)
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
arbitraryMulSigSHInputFull :: Network -> Ctx -> Gen ScriptInput
arbitraryMulSigSHInputFull net ctx =
arbitraryMSOutput ctx >>= \case
rdm@(PayMulSig _ m) -> do
sigs <- map lst3 <$> vectorOf m (arbitraryTxSignature net)
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
arbitraryMulSigSHInputFullC :: Network -> Ctx -> Gen ScriptInput
arbitraryMulSigSHInputFullC net ctx =
arbitraryMSOutputC ctx >>= \case
rdm@(PayMulSig _ m) -> do
sigs <- map lst3 <$> vectorOf m (arbitraryTxSignature net)
sigs <- map lst3 <$> vectorOf m (arbitraryTxSignature net ctx)
return $ ScriptHashInput (SpendMulSig sigs) rdm
_ -> undefined

View File

@ -1,24 +1,29 @@
{- |
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.ByteString qualified as BS
import Data.Either (fromRight)
import Data.List (nub, nubBy, permutations)
import Data.Word (Word64)
import Haskoin.Address
import Haskoin.Constants
import Haskoin.Data
import Haskoin.Keys.Common
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
@ -38,122 +43,122 @@ arbitraryTxHash = TxHash <$> arbitraryHash256
-- | 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)
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
arbitraryWLTx :: Network -> Ctx -> Bool -> Gen Tx
arbitraryWLTx net ctx 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
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
-- | Arbitrary transaction containing only inputs of type 'SpendPKHash',
-- 'SpendScriptHash' (multisig) and outputs of type 'PayPKHash' and 'PaySH'.
-- Only compressed public keys are used.
arbitraryAddrOnlyTx :: Network -> Ctx -> Gen Tx
arbitraryAddrOnlyTx net ctx = do
ni <- choose (1, 5)
no <- choose (1, 5)
inps <- vectorOf ni (arbitraryAddrOnlyTxIn net)
outs <- vectorOf no (arbitraryAddrOnlyTxOut net)
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
arbitraryAddrOnlyTxFull :: Network -> Ctx -> Gen Tx
arbitraryAddrOnlyTxFull net ctx = do
ni <- choose (1, 5)
no <- choose (1, 5)
inps <- vectorOf ni (arbitraryAddrOnlyTxInFull net)
outs <- vectorOf no (arbitraryAddrOnlyTxOut net)
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
-- | Arbitrary TxIn that can only be of type 'SpendPKHash' or 'SpendScriptHash'
-- (multisig). Only compressed public keys are used.
arbitraryAddrOnlyTxIn :: Network -> Ctx -> Gen TxIn
arbitraryAddrOnlyTxIn net ctx = do
inp <- oneof [arbitraryPKHashInput net ctx, arbitraryMulSigSHInput net ctx]
TxIn <$> arbitraryOutPoint <*> pure (marshal (net, ctx) inp) <*> arbitrary
-- | like 'arbitraryAddrOnlyTxIn' with no empty signatures.
arbitraryAddrOnlyTxInFull :: Network -> Gen TxIn
arbitraryAddrOnlyTxInFull net = do
arbitraryAddrOnlyTxInFull :: Network -> Ctx -> Gen TxIn
arbitraryAddrOnlyTxInFull net ctx = do
inp <-
oneof [arbitraryPKHashInputFullC net, arbitraryMulSigSHInputFullC net]
TxIn <$> arbitraryOutPoint <*> pure (encodeInputBS inp) <*> arbitrary
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
arbitraryAddrOnlyTxOut :: Network -> Ctx -> Gen TxOut
arbitraryAddrOnlyTxOut net ctx = do
v <- getTestCoin <$> arbitrarySatoshi net
out <- oneof [arbitraryPKHashOutput, arbitrarySHOutput]
return $ TxOut v $ encodeOutputBS out
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 =
-- | Arbitrary 'SigInput' with the corresponding private keys used
-- to generate the 'ScriptOutput' or 'RedeemScript'.
arbitrarySigInput :: Network -> Ctx -> Gen (SigInput, [PrivateKey])
arbitrarySigInput net ctx =
oneof
[ wrapKey <$> arbitraryPKSigInput net
, wrapKey <$> arbitraryPKHashSigInput net
, arbitraryMSSigInput net
, arbitrarySHSigInput net
, wrapKey <$> arbitraryWPKHSigInput net
, arbitraryWSHSigInput net
[ 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
arbitraryAnyInput :: Network -> Ctx -> Bool -> Gen (SigInput, PrivateKey)
arbitraryAnyInput net ctx pkh = do
(k, p) <- arbitraryKeyPair ctx
let out
| pkh = PayPKHash $ getAddrHash160 $ pubKeyAddr p
| pkh = PayPKHash (pubKeyAddr ctx p).hash160
| otherwise = PayPK p
(val, op, sh) <- arbitraryInputStuff net
return (SigInput out val op sh Nothing, k)
@ -167,60 +172,58 @@ arbitraryInputStuff net = do
return (val, op, sh)
-- | Arbitrary 'SigInput' with a 'ScriptOutput' of type 'PayMulSig'.
arbitraryMSSigInput :: Network -> Gen (SigInput, [SecKeyI])
arbitraryMSSigInput net = do
arbitraryMSSigInput :: Network -> Ctx -> Gen (SigInput, [PrivateKey])
arbitraryMSSigInput net ctx = do
(m, n) <- arbitraryMSParam
ks <- vectorOf n arbitraryKeyPair
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
-- | Arbitrary 'SigInput' with 'ScriptOutput' of type 'PaySH' and a
-- 'RedeemScript'.
arbitrarySHSigInput :: Network -> Ctx -> Gen (SigInput, [PrivateKey])
arbitrarySHSigInput net ctx = do
(SigInput rdm val op sh _, ks) <-
oneof
[ wrapKey <$> arbitraryPKSigInput net
, wrapKey <$> arbitraryPKHashSigInput net
, arbitraryMSSigInput net
[ wrapKey <$> arbitraryPKSigInput net ctx,
wrapKey <$> arbitraryPKHashSigInput net ctx,
arbitraryMSSigInput net ctx
]
let out = PayScriptHash $ getAddrHash160 $ payToScriptAddress rdm
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
arbitraryWPKHSigInput :: Network -> Ctx -> Gen (SigInput, PrivateKey)
arbitraryWPKHSigInput net ctx = do
(k, p) <- arbitraryKeyPair ctx
(val, op, sh) <- arbitraryInputStuff net
let out = PayWitnessPKHash . getAddrHash160 $ pubKeyAddr p
let out = PayWitnessPKHash (pubKeyAddr ctx p).hash160
return (SigInput out val op sh Nothing, k)
arbitraryWSHSigInput :: Network -> Gen (SigInput, [SecKeyI])
arbitraryWSHSigInput net = do
arbitraryWSHSigInput :: Network -> Ctx -> Gen (SigInput, [PrivateKey])
arbitraryWSHSigInput net ctx = do
(SigInput rdm val op sh _, ks) <-
oneof
[ wrapKey <$> arbitraryPKSigInput net
, wrapKey <$> arbitraryPKHashSigInput net
, arbitraryMSSigInput net
[ wrapKey <$> arbitraryPKSigInput net ctx,
wrapKey <$> arbitraryPKHashSigInput net ctx,
arbitraryMSSigInput net ctx
]
let out = PayWitnessScriptHash . getAddrHash256 $ payToWitnessScriptAddress rdm
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
-- | Arbitrary 'Tx' (empty 'TxIn'), 'SigInputs' and private keys that can be
-- passed to 'signTx' or 'detSignTx' to fully sign the 'Tx'.
arbitrarySigningData :: Network -> Ctx -> Gen (Tx, [SigInput], [PrivateKey])
arbitrarySigningData net ctx = do
v <- arbitrary
ni <- choose (1, 5)
no <- choose (1, 5)
sigis <- vectorOf ni (arbitrarySigInput net)
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)
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
@ -228,12 +231,12 @@ arbitrarySigningData net = do
return (tx, map fst uSigis, keys)
-- | Arbitrary transaction with empty inputs.
arbitraryEmptyTx :: Network -> Gen Tx
arbitraryEmptyTx net = do
arbitraryEmptyTx :: Network -> Ctx -> Gen Tx
arbitraryEmptyTx net ctx = do
v <- arbitrary
no <- choose (1, 5)
ni <- choose (1, 5)
outs <- vectorOf no (arbitraryTxOut net)
outs <- vectorOf no (arbitraryTxOut net ctx)
ops <- vectorOf ni arbitraryOutPoint
t <- arbitrary
s <- arbitrary
@ -241,13 +244,13 @@ arbitraryEmptyTx net = do
-- | Arbitrary partially-signed transactions.
arbitraryPartialTxs ::
Network -> Gen ([Tx], [(ScriptOutput, Word64, OutPoint, Int, Int)])
arbitraryPartialTxs net = do
tx <- arbitraryEmptyTx net
Network -> Ctx -> Gen ([Tx], [(ScriptOutput, Word64, OutPoint, Int, Int)])
arbitraryPartialTxs net ctx = do
tx <- arbitraryEmptyTx net ctx
res <-
forM (map prevOutput $ txIn tx) $ \op -> do
forM (map (.outpoint) tx.inputs) $ \op -> do
(so, val, rdmM, prvs, m, n) <- arbitraryData
txs <- mapM (singleSig so val rdmM tx op . secKeyData) prvs
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
@ -255,27 +258,26 @@ arbitraryPartialTxs net = do
sh <- arbitraryValidSigHash net
let sigi = SigInput so val op sh rdmM
return . fromRight (error "Could not decode transaction") $
signTx net tx [sigi] [prv]
signTx net ctx tx [sigi] [prv]
arbitraryData = do
(m, n) <- arbitraryMSParam
val <- getTestCoin <$> arbitrarySatoshi net
nPrv <- choose (m, n)
keys <- vectorOf n arbitraryKeyPair
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 $ getAddrHash160 $ payToScriptAddress so
, val
, Just so
, prvKeys
, m
, n
[ (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])

View File

@ -1,16 +1,15 @@
{-# LANGUAGE ExistentialQuantification #-}
{-# 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,
-- |
-- 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,
@ -30,15 +29,16 @@ module Haskoin.Util.Arbitrary.Util (
testNetJson,
arbitraryNetData,
genNetData,
) where
)
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 Data.ByteString.Lazy (fromStrict, toStrict)
import qualified Data.ByteString.Short as BSS
import Data.Bytes.Get
import Data.Bytes.Put
import Data.Bytes.Serial
@ -48,8 +48,8 @@ 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 Haskoin.Data
import Haskoin.Network.Constants
import Haskoin.Network.Data
import Test.Hspec (Spec, describe, shouldBe, shouldSatisfy)
import Test.Hspec.QuickCheck (prop)
import Test.QuickCheck
@ -88,8 +88,8 @@ arbitraryUTCTime = do
arbitraryMaybe :: Gen a -> Gen (Maybe a)
arbitraryMaybe g =
frequency
[ (1, return Nothing)
, (5, Just <$> g)
[ (1, return Nothing),
(5, Just <$> g)
]
-- | Generate an Network
@ -117,22 +117,26 @@ 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)
( Network -> a -> A.Value,
Network -> a -> A.Encoding,
Network -> A.Value -> A.Parser a,
Gen (Network, a)
)
testIdentity :: [SerialBox] -> [ReadBox] -> [JsonBox] -> [NetBox] -> Spec
testIdentity serialVals readVals jsonVals netVals = do
describe "Binary Encoding" $
forM_ serialVals $ \(SerialBox g) -> testSerial g
forM_ serialVals $
\(SerialBox g) -> testSerial g
describe "Read/Show Encoding" $
forM_ readVals $ \(ReadBox g) -> testRead g
forM_ readVals $
\(ReadBox g) -> testRead g
describe "Data.Aeson Encoding" $
forM_ jsonVals $ \(JsonBox g) -> testJson g
forM_ jsonVals $
\(JsonBox g) -> testJson g
describe "Data.Aeson Encoding with Network" $
forM_ netVals $ \(NetBox (j, e, p, g)) -> testNetJson j e p g
forM_ netVals $
\(NetBox (j, e, p, g)) -> testNetJson j e p g
-- | Generate binary identity tests
testSerial ::
@ -154,7 +158,8 @@ 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
forAll gen $
\x -> (read . show) x `shouldBe` x
where
name = show $ T.typeRep $ proxy gen
proxy :: Gen a -> Proxy a
@ -187,9 +192,11 @@ testNetJson ::
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
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
forAll g $
\(net, x) -> dec net (encEnc net x) `shouldBe` Just x
where
encVal net = A.encode . toMap . j net
encEnc net = A.encodingToLazyByteString . toMapE . e net
@ -198,7 +205,7 @@ testNetJson j e p g = do
proxy :: (Network -> a -> A.Value) -> Proxy a
proxy = const Proxy
arbitraryNetData :: Arbitrary a => Gen (Network, a)
arbitraryNetData :: (Arbitrary a) => Gen (Network, a)
arbitraryNetData = do
net <- arbitraryNetwork
x <- arbitrary

426
src/Haskoin/Util/Helpers.hs Normal file
View File

@ -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

View File

@ -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

View File

@ -1,8 +1,8 @@
resolver: lts-21.0
resolver: lts-21.4
nix:
packages:
- secp256k1
- pkg-config
extra-deps:
- base16-1.0@sha256:9b72a280a7af75a5026fa25a1b8ae18ec10200a070947723f1fd61dc8d407862,2472
- secp256k1-haskell-0.7.0@sha256:1585601c67d7c62c698402ffe8462de216a499608521a8136d0aa15f0a03a23f,2140
- secp256k1-haskell-1.0.0@sha256:42e1dc0ddba74b752bddf7d55c19aa10b24ff6f51889a53bc07c2ff2107aca16,2082

View File

@ -12,15 +12,15 @@ packages:
original:
hackage: base16-1.0@sha256:9b72a280a7af75a5026fa25a1b8ae18ec10200a070947723f1fd61dc8d407862,2472
- completed:
hackage: secp256k1-haskell-0.7.0@sha256:1585601c67d7c62c698402ffe8462de216a499608521a8136d0aa15f0a03a23f,2140
hackage: secp256k1-haskell-1.0.0@sha256:42e1dc0ddba74b752bddf7d55c19aa10b24ff6f51889a53bc07c2ff2107aca16,2082
pantry-tree:
sha256: a7726275193ac4ef14c9d97378222d3ca494524c48354edf69214513def7d48d
size: 599
sha256: 7846a02f6292cb0179cdf7252b3832f74b3109079e45248c931791f951355702
size: 600
original:
hackage: secp256k1-haskell-0.7.0@sha256:1585601c67d7c62c698402ffe8462de216a499608521a8136d0aa15f0a03a23f,2140
hackage: secp256k1-haskell-1.0.0@sha256:42e1dc0ddba74b752bddf7d55c19aa10b24ff6f51889a53bc07c2ff2107aca16,2082
snapshots:
- completed:
sha256: 1867d84255dff8c87373f5dd03e5a5cb1c10a99587e26c8793e750c54e83ffdc
size: 639139
url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/21/0.yaml
original: lts-21.0
sha256: caa77fdbc5b9f698262b21ee78030133272ec53116ad6ddbefdc4c321f668e0c
size: 640014
url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/21/4.yaml
original: lts-21.4

View File

@ -1,18 +1,17 @@
{-# LANGUAGE ImportQualifiedPost #-}
{-# LANGUAGE OverloadedStrings #-}
module Haskoin.Address.Bech32Spec (
spec,
) where
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.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 qualified Data.Text as T
import Data.Text qualified as Text
import Data.Word (Word8)
import Haskoin.Address
import Haskoin.Address.Bech32
@ -27,32 +26,42 @@ spec = do
it "should be invalid" $
forM_ invalidChecksums testInvalidChecksum
it "should be case-insensitive" $
all (== Just "test12hrzfj") $
map (flip (bech32Encode Bech32) []) hrpCaseVariants
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") $
map (flip (bech32Encode Bech32) []) hrpCaseVariants
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 []
assert $
isNothing $
segwitEncode "bc" 17 []
it "should not encode invalid length for version 0" $
assert $ isNothing $ segwitEncode "bc" 0 (replicate 30 1)
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)
assert $
isJust $
segwitEncode "bc" 1 (replicate 30 1)
it "should not encode another long data string" $
assert $ isNothing $ segwitEncode "bc" 1 (replicate 41 1)
assert $
isNothing $
segwitEncode "bc" 1 (replicate 41 1)
it "should not encode empty human readable part" $
assert $ isNothing $ bech32Encode Bech32 "" []
assert $
isNothing $
bech32Encode Bech32 "" []
it "should not decode empty human-readable part" $
assert $ isNothing $ bech32Decode "10a06t8"
assert $
isNothing $
bech32Decode "10a06t8"
it "human-readable part should be case-insensitive" $
bech32Encode Bech32 "HRP" [] `shouldBe` bech32Encode Bech32 "hrp" []
@ -62,14 +71,14 @@ testValidChecksum enc checksum = case bech32Decode checksum of
Just (enc', resultHRP, resultData) -> do
assertEqual (show checksum ++ " encoding incorrect") enc enc'
-- test that a corrupted checksum fails decoding.
let (hrp, rest) = T.breakOnEnd "1" checksum
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 $ T.toLower checksum
expectedChecksum = Just $ Text.toLower checksum
assertEqual
(show checksum ++ " re-encode")
expectedChecksum
@ -81,11 +90,11 @@ testInvalidChecksum checksum =
testValidAddress :: (Text, Text) -> Assertion
testValidAddress (address, hexscript) = do
let address' = T.toLower address
hrp = T.take 2 address'
let address' = Text.toLower address
hrp = Text.take 2 address'
case segwitDecode hrp address of
Nothing ->
assertFailure (T.unpack address <> ": decode failed")
assertFailure (Text.unpack address <> ": decode failed")
Just (witver, witprog) -> do
assertEqual
(show address)
@ -103,151 +112,128 @@ testInvalidAddress address = do
segwitScriptPubkey :: Word8 -> [Word8] -> ByteString
segwitScriptPubkey witver witprog =
B.pack $ witver' : fromIntegral (length witprog) : witprog
ByteString.pack $ witver' : fromIntegral (length witprog) : witprog
where
witver' = if witver == 0 then 0 else witver + 0x50
validChecksums :: [(Bech32Encoding, Text)]
validChecksums =
[
( Bech32
, "A12UEL5L"
)
,
( Bech32
, "an83characterlonghumanreadablepartthatcontainsthenumber1andtheexcludedcharactersbio1tt5tgs"
)
,
( Bech32
, "abcdef1qpzry9x8gf2tvdw0s3jn54khce6mua7lmqqqxw"
)
,
( Bech32
, "11qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqc8247j"
)
,
( Bech32
, "split1checkupstagehandshakeupstreamerranterredcaperred2y9e3w"
)
,
( Bech32m
, "A1LQFN3A"
)
,
( Bech32m
, "a1lqfn3a"
)
,
( Bech32m
, "an83characterlonghumanreadablepartthatcontainsthetheexcludedcharactersbioandnumber11sg7hg6"
)
,
( Bech32m
, "abcdef1l7aum6echk45nj3s0wdvt2fg8x9yrzpqzd3ryx"
)
,
( Bech32m
, "11llllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllludsr8"
)
,
( Bech32m
, "split1checkupstagehandshakeupstreamerranterredcaperredlc445v"
)
,
( Bech32m
, "?1v759aa"
[ ( 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"
)
,
( "tb1qqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvsesrxh6hy"
, "0020000000c4a5cad46221b2a187905e5266362b99d5e91c6ce24d165dab93e86433"
)
,
( "BC1QW508D6QEJXTDG4Y5R3ZARVARY0C5XW7KV8F3T4"
, "0014751e76e8199196d454941c45d1b3a323f1433bd6"
)
,
( "tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sl5k7"
, "00201863143c14c5166804bd19203356da136c985678cd4d27a1b8c6329604903262"
)
,
( "bc1pw508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7kt5nd6y"
, "5128751e76e8199196d454941c45d1b3a323f1433bd6751e76e8199196d454941c45d1b3a323f1433bd6"
)
,
( "BC1SW50QGDZ25J"
, "6002751e"
)
,
( "bc1zw508d6qejxtdg4y5r3zarvaryvaxxpcs"
, "5210751e76e8199196d454941c45d1b3a323"
)
,
( "tb1qqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvsesrxh6hy"
, "0020000000c4a5cad46221b2a187905e5266362b99d5e91c6ce24d165dab93e86433"
)
,
( "tb1pqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvsesf3hn0c"
, "5120000000c4a5cad46221b2a187905e5266362b99d5e91c6ce24d165dab93e86433"
)
,
( "bc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vqzk5jj0"
, "512079be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"
[ ( "BC1QW508D6QEJXTDG4Y5R3ZARVARY0C5XW7KV8F3T4",
"0014751e76e8199196d454941c45d1b3a323f1433bd6"
),
( "tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sl5k7",
"00201863143c14c5166804bd19203356da136c985678cd4d27a1b8c6329604903262"
),
( "tb1qqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvsesrxh6hy",
"0020000000c4a5cad46221b2a187905e5266362b99d5e91c6ce24d165dab93e86433"
),
( "BC1QW508D6QEJXTDG4Y5R3ZARVARY0C5XW7KV8F3T4",
"0014751e76e8199196d454941c45d1b3a323f1433bd6"
),
( "tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sl5k7",
"00201863143c14c5166804bd19203356da136c985678cd4d27a1b8c6329604903262"
),
( "bc1pw508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7kt5nd6y",
"5128751e76e8199196d454941c45d1b3a323f1433bd6751e76e8199196d454941c45d1b3a323f1433bd6"
),
( "BC1SW50QGDZ25J",
"6002751e"
),
( "bc1zw508d6qejxtdg4y5r3zarvaryvaxxpcs",
"5210751e76e8199196d454941c45d1b3a323"
),
( "tb1qqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvsesrxh6hy",
"0020000000c4a5cad46221b2a187905e5266362b99d5e91c6ce24d165dab93e86433"
),
( "tb1pqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvsesf3hn0c",
"5120000000c4a5cad46221b2a187905e5266362b99d5e91c6ce24d165dab93e86433"
),
( "bc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vqzk5jj0",
"512079be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"
)
]
invalidAddresses :: [Text]
invalidAddresses =
[ "tc1qw508d6qejxtdg4y5r3zarvary0c5xw7kg3g4ty"
, "bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t5"
, "BC13W508D6QEJXTDG4Y5R3ZARVARY0C5XW7KN40WF2"
, "bc1rw5uspcuh"
, "bc10w508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7kw5rljs90"
, "BC1QR508D6QEJXTDG4Y5R3ZARVARYV98GJ9P"
, "tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sL5k7"
, "bc1zw508d6qejxtdg4y5r3zarvaryvqyzf3du"
, "tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3pjxtptv"
, "bc1gmk9yu"
, "tc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vq5zuyut"
, "bc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vqh2y7hd"
, "tb1z0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vqglt7rf"
, "BC1S0XLXVLHEMJA6C4DQV22UAPCTQUPFHLXM9H8Z3K2E72Q4K9HCZ7VQ54WELL"
, "bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kemeawh"
, "tb1q0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vq24jc47"
, "bc1p38j9r5y49hruaue7wxjce0updqjuyyx0kh56v8s25huc6995vvpql3jow4"
, "BC130XLXVLHEMJA6C4DQV22UAPCTQUPFHLXM9H8Z3K2E72Q4K9HCZ7VQ7ZWS8R"
, "bc1pw5dgrnzv"
, "bc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7v8n0nx0muaewav253zgeav"
, "tb1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vq47Zagq"
, "bc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7v07qwwzcrf"
, "tb1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vpggkg4j"
[ "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 = map Text.pack hrpTestPermutations
hrpTestPermutations :: [String]
hrpTestPermutations = do

View File

@ -1,14 +1,15 @@
{-# LANGUAGE ImportQualifiedPost #-}
{-# LANGUAGE OverloadedStrings #-}
module Haskoin.Address.CashAddrSpec (spec) where
import Control.Monad
import qualified Data.ByteString.Char8 as C
import Data.ByteString.Char8 qualified as Char8
import Data.Maybe
import Data.String.Conversions
import Data.Text (Text)
import Haskoin.Address
import Haskoin.Constants
import Haskoin.Network.Constants
import Haskoin.Util
import Test.HUnit
import Test.Hspec
@ -28,8 +29,8 @@ spec = do
"bitcoincash:qpzry9x8gf2tvdw0s3jn54khce6mua7lcw20ayyn"
mpb
`shouldBe` Just
( "bitcoincash"
, "\NULD2\DC4\199BT\182\&5\207\132e:V\215\198u\190w\223"
( "bitcoincash",
"\NULD2\DC4\199BT\182\&5\207\132e:V\215\198u\190w\223"
)
it "bchtest:testnetaddress4d6njnut" $ do
let mpb = cash32decode "bchtest:testnetaddress4d6njnut"
@ -40,8 +41,8 @@ spec = do
"bchreg:555555555555555555555555555555555555555555555udxmlmrz"
mpb
`shouldBe` Just
( "bchreg"
, "\165)JR\148\165)JR\148\165)JR\148\165)JR\148\165)JR\148\165)J"
( "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
@ -136,212 +137,179 @@ testCashAddr (len, typ, addr, hex) = do
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)
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 (C.length pay)
assertEqual "Length doesn't match" len (Char8.length pay)
assertEqual "Version doesn't match" typ ver
assertEqual "Payload doesn't match" bs pay
where
Just bs = decodeHex hex
Just (_, ver, pay) = cash32decodeType addr
{- | All vectors starting with @pref@ had the wrong version in the spec
document.
-}
-- | All vectors starting with @pref@ had the wrong version in the spec
-- document.
vectors :: [(Int, CashVersion, Text, Text)]
vectors =
[
( 20
, 0
, "bitcoincash:qr6m7j9njldwwzlg9v7v53unlr4jkmx6eylep8ekg2"
, "F5BF48B397DAE70BE82B3CCA4793F8EB2B6CDAC9"
)
,
( 20
, 1
, "bchtest:pr6m7j9njldwwzlg9v7v53unlr4jkmx6eyvwc0uz5t"
, "F5BF48B397DAE70BE82B3CCA4793F8EB2B6CDAC9"
)
,
( 20
, 1
, "pref:pr6m7j9njldwwzlg9v7v53unlr4jkmx6ey65nvtks5"
, "F5BF48B397DAE70BE82B3CCA4793F8EB2B6CDAC9"
)
,
( 20
, 15
, "prefix:0r6m7j9njldwwzlg9v7v53unlr4jkmx6ey3qnjwsrf"
, "F5BF48B397DAE70BE82B3CCA4793F8EB2B6CDAC9"
)
,
( 24
, 0
, "bitcoincash:q9adhakpwzztepkpwp5z0dq62m6u5v5xtyj7j3h2ws4mr9g0"
, "7ADBF6C17084BC86C1706827B41A56F5CA32865925E946EA"
)
,
( 24
, 1
, "bchtest:p9adhakpwzztepkpwp5z0dq62m6u5v5xtyj7j3h2u94tsynr"
, "7ADBF6C17084BC86C1706827B41A56F5CA32865925E946EA"
)
,
( 24
, 1
, "pref:p9adhakpwzztepkpwp5z0dq62m6u5v5xtyj7j3h2khlwwk5v"
, "7ADBF6C17084BC86C1706827B41A56F5CA32865925E946EA"
)
,
( 24
, 15
, "prefix:09adhakpwzztepkpwp5z0dq62m6u5v5xtyj7j3h2p29kc2lp"
, "7ADBF6C17084BC86C1706827B41A56F5CA32865925E946EA"
)
,
( 28
, 0
, "bitcoincash:qgagf7w02x4wnz3mkwnchut2vxphjzccwxgjvvjmlsxqwkcw59jxxuz"
, "3A84F9CF51AAE98A3BB3A78BF16A6183790B18719126325BFC0C075B"
)
,
( 28
, 1
, "bchtest:pgagf7w02x4wnz3mkwnchut2vxphjzccwxgjvvjmlsxqwkcvs7md7wt"
, "3A84F9CF51AAE98A3BB3A78BF16A6183790B18719126325BFC0C075B"
)
,
( 28
, 1
, "pref:pgagf7w02x4wnz3mkwnchut2vxphjzccwxgjvvjmlsxqwkcrsr6gzkn"
, "3A84F9CF51AAE98A3BB3A78BF16A6183790B18719126325BFC0C075B"
)
,
( 28
, 15
, "prefix:0gagf7w02x4wnz3mkwnchut2vxphjzccwxgjvvjmlsxqwkc5djw8s9g"
, "3A84F9CF51AAE98A3BB3A78BF16A6183790B18719126325BFC0C075B"
)
,
( 32
, 0
, "bitcoincash:qvch8mmxy0rtfrlarg7ucrxxfzds5pamg73h7370aa87d80gyhqxq5nlegake"
, "3173EF6623C6B48FFD1A3DCC0CC6489B0A07BB47A37F47CFEF4FE69DE825C060"
)
,
( 32
, 1
, "bchtest:pvch8mmxy0rtfrlarg7ucrxxfzds5pamg73h7370aa87d80gyhqxq7fqng6m6"
, "3173EF6623C6B48FFD1A3DCC0CC6489B0A07BB47A37F47CFEF4FE69DE825C060"
)
,
( 32
, 1
, "pref:pvch8mmxy0rtfrlarg7ucrxxfzds5pamg73h7370aa87d80gyhqxq4k9m7qf9"
, "3173EF6623C6B48FFD1A3DCC0CC6489B0A07BB47A37F47CFEF4FE69DE825C060"
)
,
( 32
, 15
, "prefix:0vch8mmxy0rtfrlarg7ucrxxfzds5pamg73h7370aa87d80gyhqxqsh6jgp6w"
, "3173EF6623C6B48FFD1A3DCC0CC6489B0A07BB47A37F47CFEF4FE69DE825C060"
)
,
( 40
, 0
, "bitcoincash:qnq8zwpj8cq05n7pytfmskuk9r4gzzel8qtsvwz79zdskftrzxtar994cgutavfklv39gr3uvz"
, "C07138323E00FA4FC122D3B85B9628EA810B3F381706385E289B0B25631197D194B5C238BEB136FB"
)
,
( 40
, 1
, "bchtest:pnq8zwpj8cq05n7pytfmskuk9r4gzzel8qtsvwz79zdskftrzxtar994cgutavfklvmgm6ynej"
, "C07138323E00FA4FC122D3B85B9628EA810B3F381706385E289B0B25631197D194B5C238BEB136FB"
)
,
( 40
, 1
, "pref:pnq8zwpj8cq05n7pytfmskuk9r4gzzel8qtsvwz79zdskftrzxtar994cgutavfklv0vx5z0w3"
, "C07138323E00FA4FC122D3B85B9628EA810B3F381706385E289B0B25631197D194B5C238BEB136FB"
)
,
( 40
, 15
, "prefix:0nq8zwpj8cq05n7pytfmskuk9r4gzzel8qtsvwz79zdskftrzxtar994cgutavfklvwsvctzqy"
, "C07138323E00FA4FC122D3B85B9628EA810B3F381706385E289B0B25631197D194B5C238BEB136FB"
)
,
( 48
, 0
, "bitcoincash:qh3krj5607v3qlqh5c3wq3lrw3wnuxw0sp8dv0zugrrt5a3kj6ucysfz8kxwv2k53krr7n933jfsunqex2w82sl"
, "E361CA9A7F99107C17A622E047E3745D3E19CF804ED63C5C40C6BA763696B98241223D8CE62AD48D863F4CB18C930E4C"
)
,
( 48
, 1
, "bchtest:ph3krj5607v3qlqh5c3wq3lrw3wnuxw0sp8dv0zugrrt5a3kj6ucysfz8kxwv2k53krr7n933jfsunqnzf7mt6x"
, "E361CA9A7F99107C17A622E047E3745D3E19CF804ED63C5C40C6BA763696B98241223D8CE62AD48D863F4CB18C930E4C"
)
,
( 48
, 1
, "pref:ph3krj5607v3qlqh5c3wq3lrw3wnuxw0sp8dv0zugrrt5a3kj6ucysfz8kxwv2k53krr7n933jfsunqjntdfcwg"
, "E361CA9A7F99107C17A622E047E3745D3E19CF804ED63C5C40C6BA763696B98241223D8CE62AD48D863F4CB18C930E4C"
)
,
( 48
, 15
, "prefix:0h3krj5607v3qlqh5c3wq3lrw3wnuxw0sp8dv0zugrrt5a3kj6ucysfz8kxwv2k53krr7n933jfsunqakcssnmn"
, "E361CA9A7F99107C17A622E047E3745D3E19CF804ED63C5C40C6BA763696B98241223D8CE62AD48D863F4CB18C930E4C"
)
,
( 56
, 0
, "bitcoincash:qmvl5lzvdm6km38lgga64ek5jhdl7e3aqd9895wu04fvhlnare5937w4ywkq57juxsrhvw8ym5d8qx7sz7zz0zvcypqscw8jd03f"
, "D9FA7C4C6EF56DC4FF423BAAE6D495DBFF663D034A72D1DC7D52CBFE7D1E6858F9D523AC0A7A5C34077638E4DD1A701BD017842789982041"
)
,
( 56
, 1
, "bchtest:pmvl5lzvdm6km38lgga64ek5jhdl7e3aqd9895wu04fvhlnare5937w4ywkq57juxsrhvw8ym5d8qx7sz7zz0zvcypqs6kgdsg2g"
, "D9FA7C4C6EF56DC4FF423BAAE6D495DBFF663D034A72D1DC7D52CBFE7D1E6858F9D523AC0A7A5C34077638E4DD1A701BD017842789982041"
)
,
( 56
, 1
, "pref:pmvl5lzvdm6km38lgga64ek5jhdl7e3aqd9895wu04fvhlnare5937w4ywkq57juxsrhvw8ym5d8qx7sz7zz0zvcypqsammyqffl"
, "D9FA7C4C6EF56DC4FF423BAAE6D495DBFF663D034A72D1DC7D52CBFE7D1E6858F9D523AC0A7A5C34077638E4DD1A701BD017842789982041"
)
,
( 56
, 15
, "prefix:0mvl5lzvdm6km38lgga64ek5jhdl7e3aqd9895wu04fvhlnare5937w4ywkq57juxsrhvw8ym5d8qx7sz7zz0zvcypqsgjrqpnw8"
, "D9FA7C4C6EF56DC4FF423BAAE6D495DBFF663D034A72D1DC7D52CBFE7D1E6858F9D523AC0A7A5C34077638E4DD1A701BD017842789982041"
)
,
( 64
, 0
, "bitcoincash:qlg0x333p4238k0qrc5ej7rzfw5g8e4a4r6vvzyrcy8j3s5k0en7calvclhw46hudk5flttj6ydvjc0pv3nchp52amk97tqa5zygg96mtky5sv5w"
, "D0F346310D5513D9E01E299978624BA883E6BDA8F4C60883C10F28C2967E67EC77ECC7EEEAEAFC6DA89FAD72D11AC961E164678B868AEEEC5F2C1DA08884175B"
)
,
( 64
, 1
, "bchtest:plg0x333p4238k0qrc5ej7rzfw5g8e4a4r6vvzyrcy8j3s5k0en7calvclhw46hudk5flttj6ydvjc0pv3nchp52amk97tqa5zygg96mc773cwez"
, "D0F346310D5513D9E01E299978624BA883E6BDA8F4C60883C10F28C2967E67EC77ECC7EEEAEAFC6DA89FAD72D11AC961E164678B868AEEEC5F2C1DA08884175B"
)
,
( 64
, 1
, "pref:plg0x333p4238k0qrc5ej7rzfw5g8e4a4r6vvzyrcy8j3s5k0en7calvclhw46hudk5flttj6ydvjc0pv3nchp52amk97tqa5zygg96mg7pj3lh8"
, "D0F346310D5513D9E01E299978624BA883E6BDA8F4C60883C10F28C2967E67EC77ECC7EEEAEAFC6DA89FAD72D11AC961E164678B868AEEEC5F2C1DA08884175B"
)
,
( 64
, 15
, "prefix:0lg0x333p4238k0qrc5ej7rzfw5g8e4a4r6vvzyrcy8j3s5k0en7calvclhw46hudk5flttj6ydvjc0pv3nchp52amk97tqa5zygg96ms92w6845"
, "D0F346310D5513D9E01E299978624BA883E6BDA8F4C60883C10F28C2967E67EC77ECC7EEEAEAFC6DA89FAD72D11AC961E164678B868AEEEC5F2C1DA08884175B"
[ ( 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"
)
]

View File

@ -1,16 +1,17 @@
{-# LANGUAGE ImportQualifiedPost #-}
{-# LANGUAGE OverloadedStrings #-}
module Haskoin.AddressSpec (spec) where
import Data.ByteString (ByteString)
import qualified Data.ByteString as BS (append, empty, pack)
import Data.ByteString qualified as B
import Data.Maybe (fromJust, isJust)
import Data.Text (Text)
import qualified Data.Text as T
import Data.Text qualified as T
import Haskoin.Address
import Haskoin.Constants
import Haskoin.Data
import Haskoin.Keys
import Haskoin.Crypto
import Haskoin.Network.Constants
import Haskoin.Network.Data
import Haskoin.Util
import Haskoin.Util.Arbitrary
import Test.HUnit
@ -26,10 +27,10 @@ readVals = [ReadBox arbitraryAddressAll]
netVals :: [NetBox]
netVals =
[NetBox (addrToJSON, addrToEncoding, addrFromJSON, arbitraryNetAddress)]
[NetBox (marshalValue, marshalEncoding, unmarshalValue, arbitraryNetAddress)]
spec :: Spec
spec = do
spec = prepareContext $ \ctx -> do
testIdentity serialVals readVals [] netVals
describe "Address properties" $ do
prop "encodes and decodes base58 bytestring" $
@ -43,7 +44,7 @@ spec = do
(textToAddr net =<< addrToText net a) == Just a
prop "outputAddress . addressToOutput identity" $
forAll arbitraryAddress $ \a ->
outputAddress (addressToOutput a) == Just a
outputAddress ctx (addressToOutput a) == Just a
describe "Address vectors" $ do
it "Passes Base58 vectors 1" $
mapM_ testVector vectors
@ -54,7 +55,7 @@ spec = do
it "Passes Base58Check invalid decoding vectors" $
mapM_ testBase58ChkInvalidVector base58ChkInvalidVectors
it "Passes addresses witness p2sh(pwpkh) vectors" $
mapM_ testCompatWitnessVector compatWitnessVectors
mapM_ (testCompatWitnessVector ctx) compatWitnessVectors
testVector :: (ByteString, Text, Text) -> Assertion
testVector (bs, e, chk) = do
@ -68,21 +69,20 @@ testVector (bs, e, chk) = do
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\
[ (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\
\xpnSrrfCvy6TVVz5d4PdrjeshsWQwpZsZGzvbdAdN8MKV5QsBDY",
"111151KWPPBRzdWPr1ASeu172gVgLf1YfUp6VJyk6K9t4cLqYt\
\FHcMa2iX8S3NJEprUcW7W5LvaPRpz7UG7puBj5STE3nKhCGt5e\
\ckYq7mMn5nT7oTTic2BAX6zDdqrmGCnkszQkzkz8e5QLGDjf7K\
\eQgtEDm4UER6DMSdBjFQVa6cHrrJn9myVyyhUrsVnfUk2WmNFZ\
@ -107,30 +107,26 @@ testBase58Vector (a, b) = do
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")
,
[ ("", ""),
("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"
)
,
\da93ff480d6dd43dc62a641155a5",
"123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
),
( "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c\
\1d1e1f202122232425262728292a2b2c2d2e2f30313233343536373839\
\3a3b3c3d3e3f404142434445464748494a4b4c4d4e4f50515253545556\
@ -139,8 +135,8 @@ base58Vectors =
\9192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacad\
\aeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9ca\
\cbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7\
\e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff"
, "1cWB5HCBdLjAuqGGReWE3R3CguuwSjw6RHn39s2yuDRTS5NsBgNiFpWgAn\
\e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
"1cWB5HCBdLjAuqGGReWE3R3CguuwSjw6RHn39s2yuDRTS5NsBgNiFpWgAn\
\Ex6VQi8csexkgYw3mdYrMHr8x9i7aEwP8kZ7vccXWqKDvGv3u1GxFKPuAk\
\n8JCPPGDMf3vMMnbzm6Nh9zh1gcNsMvH3ZNLmP5fSG6DGbbi2tuwMWPthr\
\4boWwCxf7ewSgNQeacyozhKDDQQ1qL5fQFUW52QKUZDZ5fw3KXNQJMcNTc\
@ -158,12 +154,12 @@ testBase58InvalidVector (a, resM) =
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")
@ -178,26 +174,25 @@ testBase58ChkInvalidVector (a, resM) =
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
testCompatWitnessVector :: Ctx -> (Network, Text, Text) -> Assertion
testCompatWitnessVector ctx (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)
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"
)
]

View File

@ -1,8 +1,11 @@
{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE OverloadedRecordDot #-}
{-# LANGUAGE OverloadedStrings #-}
module Haskoin.BlockSpec (
spec,
) where
module Haskoin.BlockSpec
( spec,
)
where
import Control.Monad.State.Strict
import Data.Either (fromRight)
@ -12,9 +15,11 @@ import Data.String.Conversions (cs)
import Data.Text (Text)
import Data.Word (Word32)
import Haskoin.Block
import Haskoin.Constants
import Haskoin.Data
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
@ -22,35 +27,35 @@ import Test.Hspec.QuickCheck
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
serialVals :: Ctx -> [SerialBox]
serialVals ctx =
[ SerialBox (flip arbitraryBlock ctx =<< arbitraryNetwork),
SerialBox arbitraryBlockHash,
SerialBox arbitraryBlockHeader,
SerialBox arbitraryGetBlocks,
SerialBox arbitraryGetHeaders,
SerialBox arbitraryHeaders,
SerialBox arbitraryMerkleBlock,
SerialBox arbitraryBlockNode
]
readVals :: [ReadBox]
readVals =
[ ReadBox (arbitraryBlock =<< arbitraryNetwork)
, ReadBox arbitraryBlockHash
, ReadBox arbitraryBlockHeader
, ReadBox arbitraryGetBlocks
, ReadBox arbitraryGetHeaders
, ReadBox arbitraryHeaders
, ReadBox arbitraryMerkleBlock
, ReadBox arbitraryBlockNode
readVals :: Ctx -> [ReadBox]
readVals ctx =
[ ReadBox (flip arbitraryBlock ctx =<< arbitraryNetwork),
ReadBox arbitraryBlockHash,
ReadBox arbitraryBlockHeader,
ReadBox arbitraryGetBlocks,
ReadBox arbitraryGetHeaders,
ReadBox arbitraryHeaders,
ReadBox arbitraryMerkleBlock,
ReadBox arbitraryBlockNode
]
jsonVals :: [JsonBox]
jsonVals =
[ JsonBox (arbitraryBlock =<< arbitraryNetwork)
, JsonBox arbitraryBlockHash
, JsonBox arbitraryBlockHeader
jsonVals :: Ctx -> [JsonBox]
jsonVals ctx =
[ JsonBox (flip arbitraryBlock ctx =<< arbitraryNetwork),
JsonBox arbitraryBlockHash,
JsonBox arbitraryBlockHeader
]
myTime :: Timestamp
@ -59,7 +64,7 @@ myTime = 1499083075
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
@ -67,29 +72,29 @@ chain net bh i = do
bhs = appendBlocks net 6 bh i
spec :: Spec
spec = do
testIdentity serialVals readVals jsonVals []
spec = prepareContext $ \ctx -> do
testIdentity (serialVals ctx) (readVals ctx) (jsonVals ctx) []
describe "blockchain headers" $ do
it "gets best block on bchRegTest" $
let net = bchRegTest
bb =
withChain net $ do
chain net (getGenesisHeader net) 100
chain net net.genesisHeader 100
getBestBlockHeader
in nodeHeight bb `shouldBe` 100
in bb.height `shouldBe` 100
it "builds a block locator on bchRegTest" $
let net = bchRegTest
loc =
withChain net $ do
chain net (getGenesisHeader net) 100
chain net net.genesisHeader 100
bb <- getBestBlockHeader
blockLocatorNodes bb
heights = map nodeHeight loc
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 nodeHeight bb `shouldBe` 4035
in bb.height `shouldBe` 4035
describe "block hash" $ do
prop "encodes and decodes block hash" $
forAll arbitraryBlockHash $ \h ->
@ -127,32 +132,32 @@ spec = do
-- → → 2185
splitChain :: Network -> State HeaderMemory ()
splitChain net = do
start <- go 1 (getGenesisHeader net) 2015
start <- go 1 net.genesisHeader 2015
e 2015 (head start)
tail1 <- go 2 (nodeHeader $ head start) 2016
tail1 <- go 2 (head start).header 2016
e 4031 (head tail1)
tail2 <- go 3 (nodeHeader $ head start) 20
tail2 <- go 3 (head start).header 20
e 2035 (head tail2)
tail3 <- go 4 (nodeHeader $ head tail2) 2000
tail3 <- go 4 (head tail2).header 2000
e 4035 (head tail3)
tail4 <- go 5 (nodeHeader $ head tail2) 150
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 (nodeHeight sp1)
++ show sp1.height
sp2 <- splitPoint (head tail4) (head tail3)
unless (sp2 == head tail2) $
error $
"Split point wrong between blocks 2185 and 4035: "
++ show (nodeHeight sp2)
++ 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)
++ show bn.height
++ " of first chunk should be "
++ show n
go seed start n = do
@ -255,8 +260,8 @@ testCompactBitcoinCore = do
(encodeCompact . fst $ decodeCompact 0x05009234)
assertEqual
"vector 8 (decode)"
( 0x1234560000000000000000000000000000000000000000000000000000000000
, False
( 0x1234560000000000000000000000000000000000000000000000000000000000,
False
)
(decodeCompact 0x20123456)
assertEqual
@ -271,100 +276,94 @@ testCompactBitcoinCore = do
runMerkleVector :: (Text, [Text]) -> Assertion
runMerkleVector (r, hs) =
assertBool "merkle vector" $
buildMerkleRoot (map f hs) == getTxHash (f r)
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"
[ ( "fb6698ac95b754256c5e71b4fbe07638cb6ca83ee67f44e181b91727f09f4b1f",
[ "dd96fdcfaec994bf583af650ff6022980ee0ba1686d84d0a3a2d24eabf34bc52",
"1bc216f786a564378710ae589916fc8e092ddfb9f24fe6c47b733550d476d5d9",
"a1db0b0194426064b067899ff2d975fb277fd52dbb1a38370800c76dd6503d41",
"d69f7fb0e668fbd437d1bf5211cc34d7eb8746f50cfddf705fe10bc2f8f7035f",
"5b4057cd80be7df5ed2ac42b776897ed3c26e3a01e4072075b8129c587094ef6",
"ed6dabcfba0ef43c50d89a8a0e4b236b1bc6585d4c3bbf49728b55f44312d6bc",
"056aaa9a3c635909c794e9b0acc7dccb0456c59a84c6b08417335bee4515e3d3",
"05bae5f1d1c874171692e1fc06f664e63eb143d3f096601ef938e4a9012eee66",
"b5e48e94e3f2fba197b3f591e01f47e185d7834d669529d44078e41c671aab0f",
"3b56aeadfc0c5484fd507bc89f13f2e5f61c42e0a4ae9062eda9a9aeef7db6a4",
"2affa187e1ebb94a2a86578b9f64951e854ff3d346fef259acfb6d0f5212e0d3"
]
)
, -- Block 00000000000007cc4b6f07bfed72bccc1ed8dd031a93969a4c22211f784457d4
),
-- Block 00000000000007cc4b6f07bfed72bccc1ed8dd031a93969a4c22211f784457d4
( "886fea311d2dc64c315519f2d647e43998d780d2170f77e53dc0d85bf2ee680c"
,
[ "c9c9e5211512629fd111cc071d745b8c79bf486b4ea95489eb5de08b5d786b8e"
, "20beb0ee30dfd323ade790ce9a46ae7a174f9ea44ce22a17c4d4eb23b7016f51"
, "d4cb7dd741e78a8f57e12f6c8ddb0361ff2a5bf9365bd7d7df761060847daf9a"
, "ddbfa6fdd29d4b47aeaadf82a4bf0a93d58cd7d8401fabf860a1ae8eeb51f42e"
, "9d82bafe44abee248b968c86f165051c8413482c232659795335c52922dab471"
, "86035372d31b53efd848cea7231aa9738c209aff64d3c59b1619341afb5b6ba3"
, "11e7a7393d9658813dfaebc04fa6d4b73bac8d641bffa7067da879523d43d030"
, "2f676b9aa5bc0ebf3395032c84c466e40cac29f80434cd1138e31c2d0fcc5c13"
, "37567d559fbfae07fda9a90de0ce30b202128bc8ebdfef5ad2b53e865a3478c2"
, "0b8e6c1200c454361e94e261738429e9c9b8dcffd85ec8511bbf5dc7e2e0ada8"
( "886fea311d2dc64c315519f2d647e43998d780d2170f77e53dc0d85bf2ee680c",
[ "c9c9e5211512629fd111cc071d745b8c79bf486b4ea95489eb5de08b5d786b8e",
"20beb0ee30dfd323ade790ce9a46ae7a174f9ea44ce22a17c4d4eb23b7016f51",
"d4cb7dd741e78a8f57e12f6c8ddb0361ff2a5bf9365bd7d7df761060847daf9a",
"ddbfa6fdd29d4b47aeaadf82a4bf0a93d58cd7d8401fabf860a1ae8eeb51f42e",
"9d82bafe44abee248b968c86f165051c8413482c232659795335c52922dab471",
"86035372d31b53efd848cea7231aa9738c209aff64d3c59b1619341afb5b6ba3",
"11e7a7393d9658813dfaebc04fa6d4b73bac8d641bffa7067da879523d43d030",
"2f676b9aa5bc0ebf3395032c84c466e40cac29f80434cd1138e31c2d0fcc5c13",
"37567d559fbfae07fda9a90de0ce30b202128bc8ebdfef5ad2b53e865a3478c2",
"0b8e6c1200c454361e94e261738429e9c9b8dcffd85ec8511bbf5dc7e2e0ada8"
]
)
, -- Block 00000000839a8e6886ab5951d76f411475428afc90947ee320161bbf18eb6048
),
-- Block 00000000839a8e6886ab5951d76f411475428afc90947ee320161bbf18eb6048
( "0e3e2357e806b6cdb1f70b54c3a3a17b6714ee1f0e68bebb44a74b1efd512098"
, ["0e3e2357e806b6cdb1f70b54c3a3a17b6714ee1f0e68bebb44a74b1efd512098"]
)
, -- Block 000000000004d160ac1f7b775d7c1823345aeadd5fcb29ca2ad2403bb7babd4c
( "0e3e2357e806b6cdb1f70b54c3a3a17b6714ee1f0e68bebb44a74b1efd512098",
["0e3e2357e806b6cdb1f70b54c3a3a17b6714ee1f0e68bebb44a74b1efd512098"]
),
-- Block 000000000004d160ac1f7b775d7c1823345aeadd5fcb29ca2ad2403bb7babd4c
( "aae018650f513fc42d55b2210ec3ceeeb194fb1261d37989de07451fc0cbac5c"
,
[ "a4454f22831acd7904a9902c5070a3ee4bf4c2b13bc6b2dc66735dd3c4414028"
, "45297f334278885108dd38a0b689ed95a4373dd3f7e4413e6aebdc2654fb771b"
( "aae018650f513fc42d55b2210ec3ceeeb194fb1261d37989de07451fc0cbac5c",
[ "a4454f22831acd7904a9902c5070a3ee4bf4c2b13bc6b2dc66735dd3c4414028",
"45297f334278885108dd38a0b689ed95a4373dd3f7e4413e6aebdc2654fb771b"
]
)
, -- Block 000000000001d1b13a7e86ddb20da178f20d6da5cd037a29c2a15b8b84cc774e
),
-- Block 000000000001d1b13a7e86ddb20da178f20d6da5cd037a29c2a15b8b84cc774e
( "ca3580505feb87544760ac14a5859659e23be05f765bbed9f86a3c9aad1a5d0c"
,
[ "60702384c6e9d34ff03c2b3e726bdc649befe603216815bd0a2974921d0d9549"
, "11f40f58941d2a81a1616a3b84b7dd8b9d07e68750827de488c11a18f54220bb"
, "d78e82527aa8cf16e375010bc666362c0258d3c0da1885a1871121706da8b633"
( "ca3580505feb87544760ac14a5859659e23be05f765bbed9f86a3c9aad1a5d0c",
[ "60702384c6e9d34ff03c2b3e726bdc649befe603216815bd0a2974921d0d9549",
"11f40f58941d2a81a1616a3b84b7dd8b9d07e68750827de488c11a18f54220bb",
"d78e82527aa8cf16e375010bc666362c0258d3c0da1885a1871121706da8b633"
]
)
, -- Block 0000000000000630a4e2266a31776e952a19b7c99a6387917d9de9032f608021
),
-- 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"
( "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"
]
)
]
@ -373,7 +372,7 @@ testSubsidy :: Network -> Assertion
testSubsidy net = go (2 * 50 * 100 * 1000 * 1000) 0
where
go previous_subsidy halvings = do
let height = halvings * getHalvingInterval net
let height = halvings * net.halvingInterval
subsidy = computeSubsidy net height
if halvings >= 64
then subsidy `shouldBe` 0

View File

@ -1,13 +1,16 @@
{-# LANGUAGE ImportQualifiedPost #-}
{-# LANGUAGE OverloadedRecordDot #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE NoFieldSelectors #-}
module Haskoin.Crypto.HashSpec (spec) where
import Data.Bits
import Data.ByteString (ByteString)
import Data.ByteString.Builder
import qualified Data.ByteString.Char8 as C
import qualified Data.ByteString.Lazy as BL
import qualified Data.ByteString.Short as BSS
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
@ -27,19 +30,19 @@ import Test.QuickCheck
serialVals :: [SerialBox]
serialVals =
[ SerialBox arbitraryBS
, SerialBox arbitraryHash160
, SerialBox arbitraryHash256
, SerialBox arbitraryHash512
[ SerialBox arbitraryBS,
SerialBox arbitraryHash160,
SerialBox arbitraryHash256,
SerialBox arbitraryHash512
]
readVals :: [ReadBox]
readVals =
[ ReadBox arbitraryBS
, ReadBox arbitraryBSS
, ReadBox arbitraryHash160
, ReadBox arbitraryHash256
, ReadBox arbitraryHash512
[ ReadBox arbitraryBS,
ReadBox arbitraryBSS,
ReadBox arbitraryHash160,
ReadBox arbitraryHash256,
ReadBox arbitraryHash512
]
spec :: Spec
@ -48,7 +51,8 @@ spec =
testIdentity serialVals readVals [] []
describe "Property Tests" $ do
prop "join512( split512(h) ) == h" $
forAll arbitraryHash256 $ forAll arbitraryHash256 . joinSplit512
forAll arbitraryHash256 $
forAll arbitraryHash256 . joinSplit512
prop "decodeCompact . encodeCompact i == i" decEncCompact
prop "from string Hash512" $
forAll arbitraryHash512 $ \h ->
@ -61,17 +65,17 @@ spec =
fromString (cs $ encodeHex $ runPutS $ serialize h) == h
describe "Test Vectors" $ do
it "Passes RIPEMD160 test vectors" $
mapM_ (testVector ripemd160 getHash160) ripemd160Vectors
mapM_ (testVector ripemd160 (.get)) ripemd160Vectors
it "Passes SHA1 test vectors" $
mapM_ (testVector sha1 getHash160) sha1Vectors
mapM_ (testVector sha1 (.get)) sha1Vectors
it "Passes SHA256 test vectors" $
mapM_ (testVector sha256 getHash256) sha256Vectors
mapM_ (testVector sha256 (.get)) sha256Vectors
it "Passes SHA512 test vectors" $
mapM_ (testVector sha512 getHash512) sha512Vectors
mapM_ (testVector sha512 (.get)) sha512Vectors
it "Passes HMAC_SHA256 test vectors" $
mapM_ (testHMACVector hmac256 getHash256) hmacSha256Vectors
mapM_ (testHMACVector hmac256 (.get)) hmacSha256Vectors
it "Passes HMAC_SHA512 test vectors" $
mapM_ (testHMACVector hmac512 getHash512) hmacSha512Vectors
mapM_ (testHMACVector hmac512 (.get)) hmacSha512Vectors
joinSplit512 :: Hash256 -> Hash256 -> Bool
joinSplit512 a b = split512 (join512 (a, b)) == (a, b)
@ -92,26 +96,26 @@ decEncCompact i
testVector ::
(ByteString -> a) ->
(a -> BSS.ShortByteString) ->
(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) ->
(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
@ -125,157 +129,128 @@ longTestString =
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\
[ ( "",
"cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d1\
\3c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e"
)
,
( "abc"
, "ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a219299\
),
( "abc",
"ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a219299\
\2a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f"
)
,
( "message digest"
, "107dbf389d9e9f71a3a95f6c055b9251bc5268c2be16d6c13492ea45b0199f3309e164\
),
( "message digest",
"107dbf389d9e9f71a3a95f6c055b9251bc5268c2be16d6c13492ea45b0199f3309e164\
\55ab1e96118e8a905d5597b72038ddb372a89826046de66687bb420e7c"
)
,
( "secure hash algorithm"
, "7746d91f3de30c68cec0dd693120a7e8b04d8073cb699bdce1a3f64127bca7a3d5db50\
),
( "secure hash algorithm",
"7746d91f3de30c68cec0dd693120a7e8b04d8073cb699bdce1a3f64127bca7a3d5db50\
\2e814bb63c063a7a5043b2df87c61133395f4ad1edca7fcf4b30c3236e"
)
,
( "SHA512 is considered to be safe"
, "099e6468d889e1c79092a89ae925a9499b5408e01b66cb5b0a3bd0dfa51a99646b4a39\
),
( "SHA512 is considered to be safe",
"099e6468d889e1c79092a89ae925a9499b5408e01b66cb5b0a3bd0dfa51a99646b4a39\
\01caab1318189f74cd8cf2e941829012f2449df52067d3dd5b978456c2"
)
,
( "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"
, "204a8fc6dda82f0a0ced7beb8e08a41657c16ef468b228a8279be331a703c33596fd15\
),
( "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq",
"204a8fc6dda82f0a0ced7beb8e08a41657c16ef468b228a8279be331a703c33596fd15\
\c13b1b07f9aa1d3bea57789ca031ad85c7a71dd70354ec631238ca3445"
)
,
( "For this sample, this 63-byte string will be used as input data"
, "b3de4afbc516d2478fe9b518d063bda6c8dd65fc38402dd81d1eb7364e72fb6e6663cf\
),
( "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\
),
( "This is exactly 64 bytes long, not counting the terminating byte",
"70aefeaa0e7ac4f8fe17532d7185a289bee3b428d950c14fa8b713ca09814a387d2458\
\70e007a80ad97c369d193e41701aa07f3221d15f0e65a1ff970cedf030"
)
,
),
( "abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmn\
\opjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu"
, "8e959b75dae313da8cf4f72814fc143f8f7779c6eb9f7fa17299aeadb6889018501d28\
\opjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu",
"8e959b75dae313da8cf4f72814fc143f8f7779c6eb9f7fa17299aeadb6889018501d28\
\9e4900f7e4331b99dec4b5433ac7d329eeb6dd26545e96e55b874be909"
)
,
( C.replicate 1000000 'a'
, "e718483d0ce769644e2e42c7bc15b4638e1f98b13b2044285632a803afa973ebde0ff2\
),
( Char8.replicate 1000000 'a',
"e718483d0ce769644e2e42c7bc15b4638e1f98b13b2044285632a803afa973ebde0ff2\
\44877ea60a4cb0432ce577c31beb009c5c2c49aa2e4eadb217ad8cc09b"
)
,
( longTestString
, "40cac46c147e6131c5193dd5f34e9d8bb4951395f27b08c558c65ff4ba2de59437de8c\
),
( longTestString,
"40cac46c147e6131c5193dd5f34e9d8bb4951395f27b08c558c65ff4ba2de59437de8c\
\3ef5459d76a52cedc02dc499a3c9ed9dedbfb3281afd9653b8a112fafc"
)
]
@ -283,155 +258,143 @@ sha512Vectors =
-- 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"
)
,
[ ( "0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b",
"4869205468657265",
"b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7"
),
( "4a656665",
"7768617420646f2079612077616e7420666f72206e6f7468696e673f",
"5bdcc146bf60754e6a042426089575c75a003f089d2739839dec58b964ec3843"
),
( "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd\
\dddddddddddddddddddddddddddddd",
"773ea91e36800e46854db8ebd09181a72959098b3ef8c122d9635514ced565fe"
),
( "0102030405060708090a0b0c0d0e0f10111213141516171819",
"cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd\
\cdcdcdcdcdcdcdcdcdcdcdcdcdcdcd",
"82558a389a443c0ea4cc819899f2083a85f0faa3e578f8077a2e3ff46729665b"
),
( "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
\aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
\aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
\aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
, "54657374205573696e67204c6172676572205468616e20426c6f636b2d53697a65204b\
\6579202d2048617368204b6579204669727374"
, "60e431591ee0b67f0d8a26aacbf5b77f8e0bc6213728c5140546040f0ee37f54"
)
,
\aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"54657374205573696e67204c6172676572205468616e20426c6f636b2d53697a65204b\
\6579202d2048617368204b6579204669727374",
"60e431591ee0b67f0d8a26aacbf5b77f8e0bc6213728c5140546040f0ee37f54"
),
( "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
\aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
\aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
\aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
, "5468697320697320612074657374207573696e672061206c6172676572207468616e20\
\aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"5468697320697320612074657374207573696e672061206c6172676572207468616e20\
\626c6f636b2d73697a65206b657920616e642061206c6172676572207468616e20626c\
\6f636b2d73697a6520646174612e20546865206b6579206e6565647320746f20626520\
\686173686564206265666f7265206265696e6720757365642062792074686520484d41\
\4320616c676f726974686d2e"
, "9b09ffa71b942fcb27635fbcd5b0e944bfdc63644f0713938a7f51535c3a35e2"
)
, -- Test case with key length 63 bytes.
\4320616c676f726974686d2e",
"9b09ffa71b942fcb27635fbcd5b0e944bfdc63644f0713938a7f51535c3a35e2"
),
-- Test case with key length 63 bytes.
( "4a6566654a6566654a6566654a6566654a6566654a6566654a6566654a6566654a6566\
\654a6566654a6566654a6566654a6566654a6566654a6566654a6566"
, "7768617420646f2079612077616e7420666f72206e6f7468696e673f"
, "9de4b546756c83516720a4ad7fe7bdbeac4298c6fdd82b15f895a6d10b0769a6"
)
, -- Test case with key length 64 bytes.
\654a6566654a6566654a6566654a6566654a6566654a6566654a6566",
"7768617420646f2079612077616e7420666f72206e6f7468696e673f",
"9de4b546756c83516720a4ad7fe7bdbeac4298c6fdd82b15f895a6d10b0769a6"
),
-- Test case with key length 64 bytes.
( "4a6566654a6566654a6566654a6566654a6566654a6566654a6566654a6566654a6566\
\654a6566654a6566654a6566654a6566654a6566654a6566654a656665"
, "7768617420646f2079612077616e7420666f72206e6f7468696e673f"
, "528c609a4c9254c274585334946b7c2661bad8f1fc406b20f6892478d19163dd"
)
, -- Test case with key length 65 bytes.
\654a6566654a6566654a6566654a6566654a6566654a6566654a656665",
"7768617420646f2079612077616e7420666f72206e6f7468696e673f",
"528c609a4c9254c274585334946b7c2661bad8f1fc406b20f6892478d19163dd"
),
-- Test case with key length 65 bytes.
( "4a6566654a6566654a6566654a6566654a6566654a6566654a6566654a6566654a6566\
\654a6566654a6566654a6566654a6566654a6566654a6566654a6566654a"
, "7768617420646f2079612077616e7420666f72206e6f7468696e673f"
, "d06af337f359a2330deffb8e3cbe4b5b7aa8ca1f208528cdbd245d5dc63c4483"
\654a6566654a6566654a6566654a6566654a6566654a6566654a6566654a",
"7768617420646f2079612077616e7420666f72206e6f7468696e673f",
"d06af337f359a2330deffb8e3cbe4b5b7aa8ca1f208528cdbd245d5dc63c4483"
)
]
-- test cases 1, 2, 3, 4, 6 and 7 of RFC 4231
hmacSha512Vectors :: [(Text, Text, Text)]
hmacSha512Vectors =
[
( "0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b"
, "4869205468657265"
, "87aa7cdea5ef619d4ff0b4241a1d6cb02379f4e2ce4ec2787ad0b30545e17cde\
[ ( "0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b",
"4869205468657265",
"87aa7cdea5ef619d4ff0b4241a1d6cb02379f4e2ce4ec2787ad0b30545e17cde\
\daa833b7d6b8a702038b274eaea3f4e4be9d914eeb61f1702e696c203a126854"
)
,
( "4a656665"
, "7768617420646f2079612077616e7420666f72206e6f7468696e673f"
, "164b7a7bfcf819e2e395fbe73b56e0a387bd64222e831fd610270cd7ea250554\
),
( "4a656665",
"7768617420646f2079612077616e7420666f72206e6f7468696e673f",
"164b7a7bfcf819e2e395fbe73b56e0a387bd64222e831fd610270cd7ea250554\
\9758bf75c05a994a6d034f65f8f0e6fdcaeab1a34d4a6b4b636e070a38bce737"
)
,
( "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
, "dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd\
\dddddddddddddddddddddddddddddddddddd"
, "fa73b0089d56a284efb0f0756c890be9b1b5dbdd8ee81a3655f83e33b2279d39\
),
( "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd\
\dddddddddddddddddddddddddddddddddddd",
"fa73b0089d56a284efb0f0756c890be9b1b5dbdd8ee81a3655f83e33b2279d39\
\bf3e848279a722c806b485a47e67c807b946a337bee8942674278859e13292fb"
)
,
( "0102030405060708090a0b0c0d0e0f10111213141516171819"
, "cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd\
\cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd"
, "b0ba465637458c6990e5a8c5f61d4af7e576d97ff94b872de76f8050361ee3db\
),
( "0102030405060708090a0b0c0d0e0f10111213141516171819",
"cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd\
\cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd",
"b0ba465637458c6990e5a8c5f61d4af7e576d97ff94b872de76f8050361ee3db\
\a91ca5c11aa25eb4d679275cc5788063a5f19741120c4f2de2adebeb10a298dd"
)
,
),
( "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
\aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
\aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
\aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
\aaaaaa"
, "54657374205573696e67204c6172676572205468616e20426c6f636b2d53697a\
\65204b6579202d2048617368204b6579204669727374"
, "80b24263c7c1a3ebb71493c1dd7be8b49b46d1f41b4aeec1121b013783f8f352\
\aaaaaa",
"54657374205573696e67204c6172676572205468616e20426c6f636b2d53697a\
\65204b6579202d2048617368204b6579204669727374",
"80b24263c7c1a3ebb71493c1dd7be8b49b46d1f41b4aeec1121b013783f8f352\
\6b56d037e05f2598bd0fd2215d6a1e5295e64f73f63f0aec8b915a985d786598"
)
,
),
( "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
\aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
\aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
\aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
\aaaaaa"
, "5468697320697320612074657374207573696e672061206c6172676572207468\
\aaaaaa",
"5468697320697320612074657374207573696e672061206c6172676572207468\
\616e20626c6f636b2d73697a65206b657920616e642061206c61726765722074\
\68616e20626c6f636b2d73697a6520646174612e20546865206b6579206e6565\
\647320746f20626520686173686564206265666f7265206265696e6720757365\
\642062792074686520484d414320616c676f726974686d2e"
, "e37b6a775dc87dbaa4dfa9f96e5e3ffddebd71f8867289865df5a32d20cdc944\
\642062792074686520484d414320616c676f726974686d2e",
"e37b6a775dc87dbaa4dfa9f96e5e3ffddebd71f8867289865df5a32d20cdc944\
\b6022cac3c4982b10d5eeb55c3e4de15134676fb6de0446065c97440fa8c6a58"
)
, -- Test case with key length 127 bytes.
),
-- Test case with key length 127 bytes.
( "4a6566654a6566654a6566654a6566654a6566654a6566654a6566654a656665\
\4a6566654a6566654a6566654a6566654a6566654a6566654a6566654a656665\
\4a6566654a6566654a6566654a6566654a6566654a6566654a6566654a656665\
\4a6566654a6566654a6566654a6566654a6566654a6566654a6566654a6566"
, "7768617420646f2079612077616e7420666f72206e6f7468696e673f"
, "267424dfb8eeb999f3e5ec39a4fe9fd14c923e6187e0897063e5c9e02b2e624a\
\4a6566654a6566654a6566654a6566654a6566654a6566654a6566654a6566",
"7768617420646f2079612077616e7420666f72206e6f7468696e673f",
"267424dfb8eeb999f3e5ec39a4fe9fd14c923e6187e0897063e5c9e02b2e624a\
\c04413e762977df71a9fb5d562b37f89dfdfb930fce2ed1fa783bbc2a203d80e"
)
, -- Test case with key length 128 bytes.
),
-- Test case with key length 128 bytes.
( "4a6566654a6566654a6566654a6566654a6566654a6566654a6566654a656665\
\4a6566654a6566654a6566654a6566654a6566654a6566654a6566654a656665\
\4a6566654a6566654a6566654a6566654a6566654a6566654a6566654a656665\
\4a6566654a6566654a6566654a6566654a6566654a6566654a6566654a656665"
, "7768617420646f2079612077616e7420666f72206e6f7468696e673f"
, "43aaac07bb1dd97c82c04df921f83b16a68d76815cd1a30d3455ad43a3d80484\
\4a6566654a6566654a6566654a6566654a6566654a6566654a6566654a656665",
"7768617420646f2079612077616e7420666f72206e6f7468696e673f",
"43aaac07bb1dd97c82c04df921f83b16a68d76815cd1a30d3455ad43a3d80484\
\2bb35462be42cc2e4b5902de4d204c1c66d93b47d1383e3e13a3788687d61258"
)
, -- Test case with key length 129 bytes.
),
-- Test case with key length 129 bytes.
( "4a6566654a6566654a6566654a6566654a6566654a6566654a6566654a656665\
\4a6566654a6566654a6566654a6566654a6566654a6566654a6566654a656665\
\4a6566654a6566654a6566654a6566654a6566654a6566654a6566654a656665\
\4a6566654a6566654a6566654a6566654a6566654a6566654a6566654a656665\
\4a"
, "7768617420646f2079612077616e7420666f72206e6f7468696e673f"
, "0b273325191cfc1b4b71d5075c8fcad67696309d292b1dad2cd23983a35feb8e\
\4a",
"7768617420646f2079612077616e7420666f72206e6f7468696e673f",
"0b273325191cfc1b4b71d5075c8fcad67696309d292b1dad2cd23983a35feb8e\
\fb29795e79f2ef27f68cb1e16d76178c307a67beaad9456fac5fdffeadb16e2c"
)
]

View File

@ -0,0 +1,608 @@
{-# 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.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 ((.&.))
serialVals :: [SerialBox]
serialVals =
[ SerialBox arbitraryDerivPath,
SerialBox arbitraryHardPath,
SerialBox arbitrarySoftPath
]
readVals :: Ctx -> [ReadBox]
readVals ctx =
[ ReadBox arbitraryDerivPath,
ReadBox arbitraryHardPath,
ReadBox arbitrarySoftPath,
ReadBox arbitraryXPrvKey,
ReadBox (snd <$> arbitraryXPubKey ctx),
ReadBox arbitraryParsedPath,
ReadBox arbitraryBip32PathIndex
]
jsonVals :: [JsonBox]
jsonVals =
[ JsonBox arbitraryDerivPath,
JsonBox arbitraryHardPath,
JsonBox arbitrarySoftPath,
JsonBox arbitraryParsedPath
]
netVals :: Ctx -> [NetBox]
netVals ctx =
[ NetBox
( marshalValue,
marshalEncoding,
unmarshalValue,
genNetData arbitraryXPrvKey
),
NetBox
( marshalValue . (,ctx),
marshalEncoding . (,ctx),
unmarshalValue . (,ctx),
genNetData (snd <$> arbitraryXPubKey ctx)
)
]
spec :: Spec
spec = prepareContext $ \ctx -> do
testIdentity serialVals (readVals ctx) jsonVals (netVals 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)) . snd
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"
]
]

View File

@ -0,0 +1,500 @@
{-# LANGUAGE ImportQualifiedPost #-}
{-# LANGUAGE OverloadedStrings #-}
module Haskoin.Crypto.Keys.MnemonicSpec (spec) where
import Control.Monad (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.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
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

View File

@ -0,0 +1,261 @@
{-# 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.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
spec :: Spec
spec = prepareContext $ \ctx -> do
describe "Key pair property checks" $ do
testNetJson
marshalValue
marshalEncoding
unmarshalValue
$ (,)
<$> arbitraryNetwork
<*> fmap fst (arbitraryKeyPair 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

View File

@ -1,3 +1,5 @@
{-# LANGUAGE ImportQualifiedPost #-}
{-# LANGUAGE OverloadedRecordDot #-}
{-# LANGUAGE OverloadedStrings #-}
module Haskoin.Crypto.SignatureSpec (spec) where
@ -5,64 +7,70 @@ 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.ByteString qualified as BS
import Data.Map.Strict (Map)
import qualified Data.Map.Strict as 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.Constants
import Haskoin.Crypto
import Haskoin.Keys
import Haskoin.Network.Constants
import Haskoin.Script
import Haskoin.Transaction
import Haskoin.Util
import Haskoin.Util.Arbitrary
import Haskoin.UtilSpec (readTestFile)
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')
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 $ isCanonicalHalfOrder . lst3
forAll (arbitrarySignature ctx) $
isCanonicalHalfOrder ctx . lst3
prop "encoded signature is canonical" $
forAll arbitrarySignature $ testIsCanonical . lst3
forAll (arbitrarySignature ctx) $
testIsCanonical ctx . lst3
prop "decodeStrictSig . exportSig identity" $
forAll arbitrarySignature $
(\s -> decodeStrictSig (exportSig s) == Just s) . lst3
forAll (arbitrarySignature ctx) $
(\s -> decodeStrictSig ctx (exportSig ctx s) == Just s) . lst3
prop "importSig . exportSig identity" $
forAll arbitrarySignature $
(\s -> importSig (exportSig s) == Just s) . lst3
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 $
(\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
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
it "agrees with BIP143 p2sh-p2wpkh example" testBip143p2shp2wpkh
it "builds a p2wsh multisig transaction" testP2WSHMulsig
it "agrees with BIP143 p2sh-p2wsh multisig example" testBip143p2shp2wpkhMulsig
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 =
testIsCanonical :: Ctx -> Sig -> Bool
testIsCanonical ctx sig =
not $
-- Non-canonical signature: too short
(len < 8)
@ -112,7 +120,7 @@ testIsCanonical sig =
&& 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)
@ -124,13 +132,14 @@ testIsCanonical sig =
data ValidImpl
= ImplCore
| ImplABC
| ImplCash
implSig :: Text
implSig =
implSig :: Ctx -> Text
implSig ctx =
encodeHex $
exportSig $
exportSig ctx $
signMsg
ctx
"0000000000000000000000000000000000000000000000000000000000000001"
"0000000000000000000000000000000000000000000000000000000000000000"
@ -138,34 +147,30 @@ implSig =
validImplMap :: Map Text ValidImpl
validImplMap =
Map.fromList
[
( "3045022100a0b37f8fba683cc68f6574cd43b39f0343a50008bf6ccea9d13231\
[ ( "3045022100a0b37f8fba683cc68f6574cd43b39f0343a50008bf6ccea9d13231\
\d9e7e2e1e4022011edc8d307254296264aebfc3dc76cd8b668373a072fd64665\
\b50000e9fcce52"
, ImplCore
)
,
\b50000e9fcce52",
ImplCore
),
( "304402200581361d23e645be9e3efe63a9a2ac2e8dd0c70ba3ac8554c9befe06\
\0ad0b36202207d8172f1e259395834793d81b17e986f1e6131e4734969d2f4ae\
\3a9c8bc42965"
, ImplABC
\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
checkDistSig :: Ctx -> (FilePath -> FilePath -> Assertion) -> Assertion
checkDistSig ctx go =
case rfc6979files <$> getImpl ctx of
Just (file1, file2) -> go file1 file2
_ ->
it "Passes rfc6979 test vectors" $
void $ assertFailure "Invalid rfc6979 signature"
_ -> assertFailure "invalid RFC6979 signature"
{- Trezor RFC 6979 Test Vectors -}
-- github.com/trezor/python-ecdsa/blob/master/ecdsa/test_pyecdsa.py
@ -173,37 +178,37 @@ checkDistSig go =
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
testBip143p2wpkh :: Ctx -> Assertion
testBip143p2wpkh ctx =
case getImpl ctx of
Just ImplCore ->
assertEqual "BIP143 Core p2wpkh" (Right signedTxCore) generatedSignedTx
Just ImplABC ->
assertEqual "BIP143 ABC p2wpkh" (Right signedTxABC) generatedSignedTx
Just ImplCash ->
assertEqual "BIP143 ABC p2wpkh" (Right signedTxCash) generatedSignedTx
Nothing -> assertFailure "Invalid secp256k1 library"
where
signedTxCore =
@ -218,7 +223,7 @@ testBip143p2wpkh =
\a8a0d5447a12fb1366d7f01cc44a0220573a954c4518331561406f90300e8f33\
\58f51928d43c212a8caed02de67eebee0121025476c2e83188368da1ff3e292e\
\7acafcdb3566bb0ad253f62fc70f07aeee635711000000"
signedTxABC =
signedTxCash =
"01000000000102fff7f7881a8099afa6940d42d1e7f6362bec38171ea3edf433\
\541db4e4ad969f000000004847304402200fbc9dad97500334e47c2dca50096a\
\2117c01952c2870102e320823d21c36229022007cb36c2b141d11c08ef81d948\
@ -239,24 +244,24 @@ testBip143p2wpkh =
Just key0 =
secHexKey
"bbc27228ddcb9209d7fd6f36b02f7dfa6252af40bb2f1cbc7a557da8027ff866"
pubKey0 = toPubKey key0
pubKey0 = toPubKey ctx key0
Just key1 =
secHexKey
"619c335025c7f4012e556c2a58b2506e30b8511b53ade95ea316fd8c3286feb9"
[op0, op1] = prevOutput <$> txIn unsignedTx
[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
testBip143p2shp2wpkh :: Ctx -> Assertion
testBip143p2shp2wpkh ctx =
case getImpl ctx of
Just ImplCore ->
assertEqual "BIP143 Core p2sh-p2wpkh" (Right signedTxCore) generatedSignedTx
Just ImplABC ->
assertEqual "BIP143 ABC p2sh-p2wpkh" (Right signedTxABC) generatedSignedTx
Just ImplCash ->
assertEqual "BIP143 Cash p2sh-p2wpkh" (Right signedTxCash) generatedSignedTx
Nothing -> assertFailure "Invalid secp256k1 library"
where
signedTxCore =
@ -268,7 +273,7 @@ testBip143p2shp2wpkh =
\0d057c24175747116f8288e5d794d12d482f0220217f36a485cae903c713331d\
\877c1f64677e3622ad4010726870540656fe9dcb012103ad1d8e89212f0b92c7\
\4d23bb710c00662ad1470198ac48c43f7d6f93a2a2687392040000"
signedTxABC =
signedTxCash =
"01000000000101db6b1b20aa0fd7b23880be2ecbd4a98130974cf4748fb66092\
\ac4d3ceb1a5477010000001716001479091972186c449eb1ded22b78e40d009b\
\df0089feffffff02b8b4eb0b000000001976a914a457b684d7f0d539a46a45bb\
@ -285,19 +290,19 @@ testBip143p2shp2wpkh =
Just key0 =
secHexKey
"eb696a065ef48a2192da5b28b694f87544b30fae8327c4510137a922f32c6dcf"
op0 = prevOutput . head $ txIn unsignedTx
WitnessPubKeyAddress h = pubKeyWitnessAddr $ toPubKey key0
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
testP2WSHMulsig :: Ctx -> Assertion
testP2WSHMulsig ctx =
case getImpl ctx of
Just ImplCore ->
assertEqual "Core p2wsh multisig" (Right signedTxCore) generatedSignedTx
Just ImplABC ->
assertEqual "ABC p2wsh multisig" (Right signedTxABC) generatedSignedTx
Just ImplCash ->
assertEqual "Cash p2wsh multisig" (Right signedTxCash) generatedSignedTx
Nothing -> assertFailure "Invalid secp256k1 library"
where
signedTxCore =
@ -312,7 +317,7 @@ testP2WSHMulsig =
\549e186526e3d10caf6721038ac8aef2dd9cea5e7d66e2f6e23f177a6c21f69e\
\a311fa0c85d81badb6b37ceb2103d96d2bfbbc040faaf93491d69e2bfe9695e2\
\d8e007a7f26db96c2ee42db15dc953ae00000000"
signedTxABC =
signedTxCash =
"01000000000101d2e34df5d7ee565208eddd231548916b9b0e99f4f5071f8961\
\34a448c5fb07bf0100000000ffffffff01f0b9f505000000001976a9143d5a35\
\2cab583b12fbcb26d1269b4a2c951a33ad88ac0400483045022100b79bf3714a\
@ -328,37 +333,37 @@ testP2WSHMulsig =
"0100000001d2e34df5d7ee565208eddd231548916b9b0e99f4f5071f896134a4\
\48c5fb07bf0100000000ffffffff01f0b9f505000000001976a9143d5a352cab\
\583b12fbcb26d1269b4a2c951a33ad88ac00000000"
op0 = head $ prevOutput <$> txIn unsignedTx
op0 = (head unsignedTx.inputs).outpoint
Just keys =
traverse
secHexKey
[ "3030303030303030303030303030303030303030303030303030303030303031"
, "3030303030303030303030303030303030303030303030303030303030303032"
, "3030303030303030303030303030303030303030303030303030303030303033"
[ "3030303030303030303030303030303030303030303030303030303030303031",
"3030303030303030303030303030303030303030303030303030303030303032",
"3030303030303030303030303030303030303030303030303030303030303033"
]
rdm = PayMulSig (toPubKey <$> keys) 2
rdm = PayMulSig (toPubKey ctx <$> keys) 2
sigIn =
SigInput
(toP2WSH $ encodeOutput rdm)
(toP2WSH $ encodeOutput ctx rdm)
100000000
op0
sigHashAll
(Just rdm)
generatedSignedTx = signTx btc unsignedTx [sigIn] (take 2 keys)
generatedSignedTx = signTx btc ctx unsignedTx [sigIn] (take 2 keys)
-- Reproduce the P2SH-P2WSH multisig example from BIP 143
testBip143p2shp2wpkhMulsig :: Assertion
testBip143p2shp2wpkhMulsig =
case getImpl of
testBip143p2shp2wpkhMulsig :: Ctx -> Assertion
testBip143p2shp2wpkhMulsig ctx =
case getImpl ctx of
Just ImplCore ->
assertEqual
"BIP143 Core p2sh-p2wsh multisig"
(Right signedTxCore)
generatedSignedTx
Just ImplABC ->
Just ImplCash ->
assertEqual
"BIP143 Core p2sh-p2wsh multisig"
(Right signedTxABC)
(Right signedTxCash)
generatedSignedTx
Nothing -> assertFailure "Invalid secp256k1 library"
where
@ -388,7 +393,7 @@ testBip143p2shp2wpkhMulsig =
\21cfdf1ed1376e58c5d1f47de74683123987e967a8f42103a6d48b1131e94ba0\
\4d9737d61acdaa1322008af9602b3b14862c07a1789aac162102d8b661b0b330\
\2ee2f162b09e07a55ad5dfbe673a9f01d9f0c19617681024306b56ae00000000"
signedTxABC =
signedTxCash =
"0100000000010136641869ca081e70f394c6948e8af409e18b619df2ed74aa10\
\6c1ca29787b96e0100000023220020a16b5755f7f6f96dbd65f5f0d6ab9418b8\
\9af4b1f14a1bb8a09062c35f0dcb54ffffffff0200e9a435000000001976a914\
@ -419,26 +424,26 @@ testBip143p2shp2wpkhMulsig =
\a29787b96e0100000000ffffffff0200e9a435000000001976a914389ffce9cd\
\9ae88dcc0631e88a821ffdbe9bfe2688acc0832f05000000001976a9147480a3\
\3f950689af511e6e84c138dbbd3c3ee41588ac00000000"
op0 = head $ prevOutput <$> txIn unsignedTx
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

View File

@ -1,621 +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.Bytes.Get
import Data.Bytes.Put
import Data.Bytes.Serial
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.Constants
import Haskoin.Keys
import Haskoin.Util
import Haskoin.Util.Arbitrary
import Haskoin.UtilSpec (customCerealID)
import Test.HUnit (Assertion, assertBool, assertEqual)
import Test.Hspec
import Test.Hspec.QuickCheck
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 (runPutS . serialize $ xPrvID m) == v !! 0
assertBool "xPrvFP" $ encodeHex (runPutS . serialize $ 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 (runPutS . serialize $ xPrvChain m) == v !! 7
assertBool "Hex PubKey" $
encodeHex (runPutS $ putXPubKey btc $ deriveXPubKey m) == v !! 8
assertBool "Hex PrvKey" $ encodeHex (runPutS (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 (runPutS . serialize $ xPrvID m))
, ("xPrvFP", encodeHex (runPutS . serialize $ 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 (runPutS . serialize $ xPrvChain m))
, ("Hex PubKey", encodeHex (runPutS $ putXPubKey btc $ deriveXPubKey m))
, ("Hex PrvKey", encodeHex (runPutS (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"
]
]

View File

@ -1,498 +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.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
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

View File

@ -1,266 +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.Bytes.Get
import Data.Bytes.Put
import Data.Bytes.Serial
import Data.Maybe
import qualified 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.HUnit
import Test.Hspec
import Test.Hspec.QuickCheck
import Test.QuickCheck
serialVals :: [SerialBox]
serialVals =
[ 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 $ runPutS $ serialize 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 = runPutS $ serialize p
testMiniKey :: Assertion
testMiniKey =
assertEqual "fromMiniKey" (Just res) (go "S6c56bnXQiBjk9mqSYE7ykVQ7NzrRy")
where
go = fmap (encodeHex . runPutS . S.put . 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 . runPutS . S.put . 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 . runGetS S.get =<< 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 . runGetS S.get
=<< 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 . runGetS S.get <=< 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

View File

@ -1,3 +1,5 @@
{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE OverloadedRecordDot #-}
{-# LANGUAGE OverloadedStrings #-}
module Haskoin.NetworkSpec (spec) where
@ -9,13 +11,12 @@ import Data.Maybe (fromJust)
import Data.Text (Text)
import Data.Word (Word32)
import Haskoin.Address
import Haskoin.Constants
import Haskoin.Keys
import Haskoin.Crypto
import Haskoin.Network
import Haskoin.Network.Constants
import Haskoin.Transaction
import Haskoin.Util
import Haskoin.Util.Arbitrary
import Haskoin.UtilSpec (customCerealID)
import Test.HUnit (Assertion, assertBool, assertEqual)
import Test.Hspec
import Test.Hspec.QuickCheck
@ -23,50 +24,51 @@ 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
[ 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
spec = prepareContext $ \ctx -> do
testIdentity serialVals [] [] []
describe "Custom identity tests" $ do
prop "Data.Serialize Encoding for type Message" $
forAll arbitraryNetwork $ \net ->
forAll (arbitraryMessage 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
it "bloom filter vector 3" $ bloomFilter3 ctx
describe "relevant bloom filter update" $ do
it "Relevant Update" relevantOutputUpdated
it "Irrelevant Update" irrelevantOutputNotUpdated
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
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" $
@ -88,97 +90,97 @@ bloomFilter1 = bloomFilter 0 "03614e9b050000000000000001"
bloomFilter2 :: Assertion
bloomFilter2 = bloomFilter 2147483649 "03ce4299050000000100008001"
bloomFilter3 :: Assertion
bloomFilter3 =
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 $ runPutS $ serialize p
f2 = bloomInsert f1 $ runPutS $ serialize $ 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 =
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 = runPutS . serialize . prevOutput <$> txIn spendRelevantTx
bf2 = fromJust $ bloomRelevantUpdate ctx bf1 relevantTx
spendTxInput = runPutS . serialize . (.outpoint) <$> spendRelevantTx.inputs
irrelevantOutputNotUpdated :: Assertion
irrelevantOutputNotUpdated = assertEqual "Bloom filter not updated" Nothing bf2
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 bf1 unrelatedTx
bf2 = bloomRelevantUpdate ctx bf1 unrelatedTx
-- Random transaction (57dc904f32ad4daab7b321dd469e8791ad09df784cdd273a73985150a4f225e9)
relevantTx :: Tx
relevantTx =
Tx
{ txVersion = 1
, txIn =
{ version = 1,
inputs =
[ TxIn
{ prevOutput = OutPoint "35fe9017b7e3af592920b56fa06ac02faf0c52cdb19dcb416129ac71c95d060e" 1
, scriptInput = fromJust $ decodeHex "473044022032fc8eef299b7e94b9a986a6aa2dcb9733ab804bef80df995e443b9c1f8c604202203335df7a2e2b4789451cdb4b2b05a786a81c51519eb6a567fd6fe8cd7b2d33fe014104272502dc63a512dad1473cb82a71be9baf4f4303abd1ff6028fc8a78e1f3aec1218907119dec14f07354850758ff0948e88a904fa411c4df7d5444414ec64ad6"
, txInSequence = 4294967295
{ outpoint = OutPoint "35fe9017b7e3af592920b56fa06ac02faf0c52cdb19dcb416129ac71c95d060e" 1,
script = fromJust $ decodeHex "473044022032fc8eef299b7e94b9a986a6aa2dcb9733ab804bef80df995e443b9c1f8c604202203335df7a2e2b4789451cdb4b2b05a786a81c51519eb6a567fd6fe8cd7b2d33fe014104272502dc63a512dad1473cb82a71be9baf4f4303abd1ff6028fc8a78e1f3aec1218907119dec14f07354850758ff0948e88a904fa411c4df7d5444414ec64ad6",
sequence = 4294967295
}
]
, txOut =
[ TxOut{outValue = 100000000, scriptOutput = fromJust $ decodeHex "76a91403f47604ea2736334151081e13265b4fe38e6fa888ac"}
, TxOut{outValue = 107980000, scriptOutput = fromJust $ decodeHex "76a91481cc186a2f4a69f633ed4bf10ef4a78be13effdd88ac"}
]
, txWitness = []
, txLockTime = 0
],
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 =
{ version = 1,
inputs =
[ TxIn
{ prevOutput = OutPoint "57dc904f32ad4daab7b321dd469e8791ad09df784cdd273a73985150a4f225e9" 0
, scriptInput = fromJust $ decodeHex "483045022100ecc334821e4e94cc2fdc841d5ad147d5bb942b993ba81460cc446e0410afa811022015fcbc542b734dbb61a05ec06012095096de5839c50808fe56f2b315e877c20d012103fb64e5792fa586172339b776b7017d3d529358cb73be6406a1fc994228d14f88"
, txInSequence = 4294967295
{ outpoint = OutPoint "57dc904f32ad4daab7b321dd469e8791ad09df784cdd273a73985150a4f225e9" 0,
script = fromJust $ decodeHex "483045022100ecc334821e4e94cc2fdc841d5ad147d5bb942b993ba81460cc446e0410afa811022015fcbc542b734dbb61a05ec06012095096de5839c50808fe56f2b315e877c20d012103fb64e5792fa586172339b776b7017d3d529358cb73be6406a1fc994228d14f88",
sequence = 4294967295
},
TxIn
{ outpoint = OutPoint "cfee6a8d6e68e8fd16df6fff010afffcd19d7e075aa7b707dd1bae6adc420042" 0,
script = fromJust $ decodeHex "47304402200e6bb95fa606f254d17089d83c4ceeb19c5d1699b4faddcd4f1f1568286e6b650220087fb8439f31e1b30e47710d095422405f601d6151f2f93e125e1a08a6e29ad4012103b49252e8fc6d5b49c8d14ee71fab45591df4a126a6c453c724f3d356e38f0cee",
sequence = 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
],
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 =
{ version = 1,
inputs =
[ TxIn
{ prevOutput = OutPoint "3ec3a71431c68e5d978a5fb4a0a1081d8bee8384d8aa4c06b1fbaf9413e2214f" 20
, scriptInput = fromJust $ decodeHex "483045022100ec9c202c9d3140b973aca9d7f21a82138aa4cfa43fddc5419098ac5e26a6f152022010848fd688f290ae010fb5cb493410caa03145fc12445900ec1ad2bde33aecd9012102c7445e72d723f99a0064526c28269d07f47c8fd81531a94a8d3bf5ebd5e23ef1"
, txInSequence = 4294967295
{ outpoint = OutPoint "3ec3a71431c68e5d978a5fb4a0a1081d8bee8384d8aa4c06b1fbaf9413e2214f" 20,
script = fromJust $ decodeHex "483045022100ec9c202c9d3140b973aca9d7f21a82138aa4cfa43fddc5419098ac5e26a6f152022010848fd688f290ae010fb5cb493410caa03145fc12445900ec1ad2bde33aecd9012102c7445e72d723f99a0064526c28269d07f47c8fd81531a94a8d3bf5ebd5e23ef1",
sequence = 4294967295
}
]
, txOut =
[ TxOut{outValue = 12600000, scriptOutput = fromJust $ decodeHex "76a9148fef3b7051de8cc44e966159e7ea37f4520187e888ac"}
]
, txWitness = []
, txLockTime = 0
],
outputs =
[ TxOut {value = 12600000, script = fromJust $ decodeHex "76a9148fef3b7051de8cc44e966159e7ea37f4520187e888ac"}
],
witness = [],
locktime = 0
}

View File

@ -1,11 +1,15 @@
{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE ImportQualifiedPost #-}
{-# LANGUAGE OverloadedRecordDot #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TupleSections #-}
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.ByteString qualified as B
import Data.Bytes.Get
import Data.Bytes.Put
import Data.Bytes.Serial
@ -17,14 +21,13 @@ import Data.String.Conversions (cs)
import Data.Text (Text)
import Data.Word
import Haskoin.Address
import Haskoin.Constants
import Haskoin.Data
import Haskoin.Keys
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 Haskoin.UtilSpec (readTestFile)
import Test.HUnit as HUnit
import Test.Hspec
import Test.Hspec.QuickCheck
@ -33,148 +36,145 @@ import Text.Read
serialVals :: [SerialBox]
serialVals =
[ SerialBox arbitraryScriptOp
, SerialBox arbitraryScript
[ SerialBox arbitraryScriptOp,
SerialBox arbitraryScript
]
readVals :: [ReadBox]
readVals =
[ ReadBox arbitrarySigHash
, ReadBox arbitrarySigHashFlag
, ReadBox arbitraryScript
, ReadBox arbitraryPushDataType
, ReadBox arbitraryScriptOp
, ReadBox (arbitraryScriptOutput =<< arbitraryNetwork)
readVals :: Ctx -> [ReadBox]
readVals ctx =
[ ReadBox arbitrarySigHash,
ReadBox arbitrarySigHashFlag,
ReadBox arbitraryScript,
ReadBox arbitraryPushDataType,
ReadBox arbitraryScriptOp,
ReadBox ((`arbitraryScriptOutput` ctx) =<< arbitraryNetwork)
]
jsonVals :: [JsonBox]
jsonVals =
[ JsonBox $ arbitraryScriptOutput =<< arbitraryNetwork
, JsonBox arbitraryOutPoint
, JsonBox arbitrarySigHash
, JsonBox $ fst <$> (arbitrarySigInput =<< arbitraryNetwork)
jsonVals :: Ctx -> [JsonBox]
jsonVals ctx =
[ JsonBox $
fmap (marshalValue ctx) $
arbitraryNetwork >>= flip arbitraryScriptOutput ctx,
JsonBox arbitraryOutPoint,
JsonBox arbitrarySigHash,
JsonBox $
fmap (marshalValue ctx . fst) $
arbitraryNetwork >>= flip arbitrarySigInput ctx
]
netVals :: Ctx -> [NetBox]
netVals ctx =
[ NetBox
( marshalValue . (,ctx),
marshalEncoding . (,ctx),
unmarshalValue . (,ctx),
do
net <- arbitraryNetwork
(_, _, txsig) <- arbitraryTxSignature net ctx
return (net, txsig)
)
]
spec :: Spec
spec = do
testIdentity serialVals readVals jsonVals []
describe "btc scripts" $ props btc
describe "bch scripts" $ props bch
spec = prepareContext $ \ctx -> do
testIdentity serialVals (readVals ctx) (jsonVals ctx) (netVals ctx)
describe "btc scripts" $ props btc ctx
describe "bch scripts" $ props bch ctx
describe "multi signatures" $
zipWithM_ (curry mapMulSigVector) mulSigVectors [0 ..]
zipWithM_ (curry (mapMulSigVector ctx)) mulSigVectors [0 ..]
describe "signature decoding" $
zipWithM_ (curry (sigDecodeMap btc)) scriptSigSignatures [0 ..]
zipWithM_ (curry (sigDecodeMap btc ctx)) scriptSigSignatures [0 ..]
describe "SigHashFlag fromEnum/toEnum" $
prop "fromEnum/toEnum" $
forAll arbitrarySigHashFlag $ \f -> toEnum (fromEnum f) `shouldBe` f
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
props :: Network -> Ctx -> Spec
props net ctx = do
standardSpec net ctx
strictSigSpec net ctx
scriptSpec net ctx
txSigHashForkIdSpec net
forkIdScriptSpec net
sigHashSpec net
forkIdScriptSpec net ctx
sigHashSpec net ctx
txSigHashSpec net
standardSpec :: Network -> Spec
standardSpec net = do
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) $ \so ->
decodeOutput (encodeOutput so) `shouldBe` Right so
forAll (arbitraryScriptOutput net ctx) $ \so ->
decodeOutput ctx (encodeOutput ctx so) `shouldBe` Right so
prop "has decodeInput . encodeOutput identity" $
forAll (arbitraryScriptInput net) $ \si ->
decodeInput net (encodeInput si) `shouldBe` Right si
forAll (arbitraryScriptInput net ctx) $ \si ->
(decodeInput net ctx . encodeInput net ctx) si `shouldBe` Right si
prop "can sort multisig scripts" $
forAll arbitraryMSOutput $ \out ->
map
(runPutS . serialize)
(getOutputMulSigKeys (sortMulSig out))
`shouldSatisfy` \xs -> xs == sort xs
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 (Script [OP_0])
decodeInput net ctx (Script [OP_0])
`shouldBe` Right (RegularInput (SpendPK TxSignatureEmpty))
decodeInput net (Script [opPushData ""])
decodeInput net ctx (Script [opPushData ""])
`shouldBe` Right (RegularInput (SpendPK TxSignatureEmpty))
let pk =
derivePubKeyI $
wrapSecKey True $ fromJust $ secKey $ B.replicate 32 1
decodeInput net (Script [OP_0, opPushData $ runPutS $ serialize pk])
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 (Script [OP_0, OP_0])
decodeInput net ctx (Script [OP_0, OP_0])
`shouldBe` Right (RegularInput (SpendMulSig [TxSignatureEmpty]))
decodeInput net (Script [OP_0, OP_0, OP_0, OP_0])
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
)
]
[(String, String, String, String, String)]
vectorsB =
mapMaybe (A.decode . A.encode) xs ::
[ ( [Word64]
, String
, String
, String
, String
, String
)
]
[([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
any
(`isInfixOf` flags)
["DERSIG", "STRICTENC", "NULLDUMMY"]
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)
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
[ ( [Word64],
String,
String,
String,
String,
String
)
]
length vectors `shouldBe` 3
@ -182,42 +182,36 @@ forkIdScriptSpec net =
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
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" -> ver `shouldBe` True
_ -> ver `shouldBe` False
"OK" -> valid `shouldBe` True
_ -> valid `shouldBe` False
creditTx :: ByteString -> Word64 -> Tx
creditTx scriptPubKey val =
Tx 1 [txI] [txO] [] 0
where
txO = TxOut{outValue = val, scriptOutput = scriptPubKey}
txO = TxOut {value = val, script = scriptPubKey}
txI =
TxIn
{ prevOutput = nullOutPoint
, scriptInput = runPutS $ serialize $ Script [OP_0, OP_0]
, txInSequence = maxBound
{ outpoint = nullOutPoint,
script = runPutS $ serialize $ Script [OP_0, OP_0],
sequence = maxBound
}
spendTx :: ByteString -> Word64 -> ByteString -> Tx
spendTx scriptPubKey val scriptSig =
Tx 1 [txI] [txO] [] 0
where
txO = TxOut{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
{ outpoint = OutPoint (txHash $ creditTx scriptPubKey val) 0,
script = scriptSig,
sequence = maxBound
}
parseScript :: String -> ByteString
@ -234,34 +228,37 @@ replaceToken str = case readMaybe $ "OP_" <> str of
Just opcode -> "0x" <> cs (encodeHex $ runPutS $ serialize (opcode :: ScriptOp))
_ -> str
strictSigSpec :: Network -> Spec
strictSigSpec net =
when (getNetworkName net == "btc") $ do
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 ->
decodeTxSig net sig `shouldSatisfy` isRight
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 ->
decodeTxSig net sig `shouldSatisfy` isLeft
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
[ ( String,
String,
Int,
Integer,
String
)
]
length vectors `shouldBe` 500
@ -278,17 +275,17 @@ txSigHashSpec net =
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
[ ( String,
String,
Int,
Word64,
Integer,
String
)
]
length vectors `shouldBe` 13
@ -301,13 +298,14 @@ txSigHashForkIdSpec net =
res = eitherToMaybe . runGetS deserialize =<< decodeHex (cs resStr)
Just (txSigHashForkId net tx s val i sh) `shouldBe` res
sigHashSpec :: Network -> Spec
sigHashSpec net = do
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 :: SigHash)
`shouldBe` "SigHash "
<> show (0xabac3344 :: Word32)
it "can add a forkid" $ do
0x00 `sigHashAddForkId` 0x00 `shouldBe` 0x00
@ -327,13 +325,13 @@ sigHashSpec net = do
sigHashNone `shouldBe` 0x02
sigHashSingle `shouldBe` 0x03
setForkIdFlag sigHashAll `shouldBe` 0x41
setAnyoneCanPayFlag sigHashAll `shouldBe` 0x81
setAnyoneCanPayFlag (setForkIdFlag sigHashAll) `shouldBe` 0xc1
setAnyoneCanPay sigHashAll `shouldBe` 0x81
setAnyoneCanPay (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
anyoneCanPay sigHashAll `shouldBe` False
anyoneCanPay (setAnyoneCanPay sigHashAll) `shouldBe` True
isSigHashAll sigHashNone `shouldBe` False
isSigHashAll sigHashAll `shouldBe` True
isSigHashNone sigHashSingle `shouldBe` False
@ -347,68 +345,69 @@ sigHashSpec net = do
isSigHashUnknown 0x04 `shouldBe` True
it "can decodeTxSig . encode a TxSignature" $
property $
forAll (arbitraryTxSignature net) $ \(_, _, ts) ->
decodeTxSig net (encodeTxSig ts) `shouldBe` Right ts
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) $ forAll arbitraryScript . testSigHashOne net
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)
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
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 $ runGetS deserialize s'
b = do
o <- s
d <- eitherToMaybe $ decodeOutput o
addrToText btc $ payToScriptAddress d
d <- eitherToMaybe $ decodeOutput ctx o
addrToText btc $ payToScriptAddress ctx d
sigDecodeMap :: Network -> (Text, Int) -> Spec
sigDecodeMap net (_, i) =
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 =
testSigDecode :: Network -> Ctx -> Text -> Assertion
testSigDecode net ctx str =
let bs = fromJust $ decodeHex str
eitherSig = decodeTxSig net bs
eitherSig = decodeTxSig net ctx bs
in assertBool
( unwords
[ "Decode failed:"
, fromLeft (error "Decode did not fail") eitherSig
[ "Decode failed:",
fromLeft (error "Decode did not fail") eitherSig
]
)
$ isRight eitherSig
mulSigVectors :: [(Text, Text)]
mulSigVectors =
[
( "3QJmV3qfvL9SuYo34YihAf3sRCW3qSinyC"
, "52410491bba2510912a5bd37da1fb5b1673010e43d2c6d812c514e91bfa9f2eb\
[ ( "3QJmV3qfvL9SuYo34YihAf3sRCW3qSinyC",
"52410491bba2510912a5bd37da1fb5b1673010e43d2c6d812c514e91bfa9f2eb\
\129e1c183329db55bd868e209aac2fbc02cb33d98fe74bf23f0c235d6126b1d8\
\334f864104865c40293a680cb9c020e7b1e106d8c1916d3cef99aa431a56d253\
\e69256dac09ef122b1a986818a7cb624532f062c1d1f8722084861c5c3291ccf\
@ -446,18 +445,18 @@ encodeScriptVector =
\25c15342af52ae"
s =
Script
[ OP_1
, opPushData $
[ OP_1,
opPushData $
d
"04cc71eb30d653c0c3163990c47b976f3fb3f37cccdcbedb169a1dfef5\
\8bbfbfaff7d8a473e7e2e6d317b87bafe8bde97e3cf8f065dec022b51d\
\11fcdd0d348ac4"
, opPushData $
\11fcdd0d348ac4",
opPushData $
d
"0461cbdcc5409fb4b4d42b51d33381354d80e550078cb532a34bfa2fcf\
\deb7d76519aecc62770f5b0e4ef8551946d8a540911abe3e7854a26f39\
\f58b25c15342af"
, OP_2
, OP_CHECKMULTISIG
\f58b25c15342af",
OP_2,
OP_CHECKMULTISIG
]
d = fromJust . decodeHex

View File

@ -1,244 +1,259 @@
{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE ImportQualifiedPost #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE OverloadedRecordDot #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
module Haskoin.Transaction.PartialSpec (spec) where
import Control.Monad ((<=<))
import Data.Aeson (FromJSON, parseJSON, withObject, (.:))
import Data.Bifunctor (first)
import Data.ByteString (ByteString)
import Data.ByteString.Base64 (decodeBase64)
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 as S
import Data.Serialize
import Data.Text (Text)
import Test.HUnit (Assertion, assertBool, assertEqual)
import Test.Hspec
import Test.QuickCheck
import Control.Monad ((<=<))
import Data.Aeson (FromJSON, parseJSON, withObject, (.:))
import Data.Bifunctor (first)
import Data.ByteString.Base64 (decodeBase64)
import qualified Data.Text as Text
import Data.Text qualified as Text
import Data.Text.Encoding (encodeUtf8)
import Haskoin.Address
import Haskoin.Constants
import Haskoin.Crypto
import Haskoin.Data
import Haskoin.Keys
import Haskoin.Network.Constants
import Haskoin.Network.Data
import Haskoin.Script
import Haskoin.Transaction
import Haskoin.Util
import Haskoin.Util.Arbitrary
import Haskoin.UtilSpec (readTestFile)
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" $
(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 $ verifyNonWitnessPSBT btc . unfinalizedPkhPSBT btc
forAll (arbitraryKeyPair ctx) $
verifyNonWitnessPSBT btc ctx . unfinalizedPkhPSBT btc ctx
it "signed and finalized multisig PSBTs verify" $
property $
forAll arbitraryMultiSig $ verifyNonWitnessPSBT btc . unfinalizedMsPSBT btc
it "encodes and decodes psbt with final witness script" $
(fmap (encodeHex . S.encode) . decodeHexPSBT) validVec7Hex == Right validVec7Hex
it "handles complex psbts correctly" complexPsbtTest
it "calculates keys properly" psbtSignerTest
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
let scrptPubKey = witnessScriptPubKey ctx (psbt.inputs !! 1)
rdmScriptP2SH = toP2SH rdmScript
assertEqual "redeem script pubkey equal" rdmScriptP2SH scrptPubKey
assertEqual "expected redeem script" expectedOut rdmScriptP2SH
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
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")
. decodeOutputBS
. scriptOutput
$ txOut utx !! fromIntegral prevVOut
. unmarshal ctx
. (.script)
$ utx.outputs !! fromIntegral prevVOut
assertBool "p2pkh" $ isPayPKHash prevOutputKey
assertEqual "sighash type" sigHashAll (fromJust $ sigHashType firstInput)
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
let scrptPubKey = witnessScriptPubKey ctx secondInput
rdmScriptP2SH = toP2SH rdmScript
assertEqual "redeem script pubkey equal" rdmScriptP2SH scrptPubKey
assertEqual "expected redeem script" expectedOut rdmScriptP2SH
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
vec5Test :: Ctx -> Assertion
vec5Test ctx = do
psbt <- decodeHexPSBTM ctx "Cannot parse validVec5" validVec5Hex
assertEqual "Correctly decode PSBT" expectedPsbt psbt
let input = head $ inputs 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
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
. unmarshal ctx
. fromJust
$ decodeHex "a9146345200f68d189e1adc0df1c4d16ea8f14c0dbeb87"
-- From the bitcoind decodepsbt rpc call
expectedPsbt =
PartiallySignedTransaction
PSBT
{ unsignedTransaction =
Tx
{ txVersion = 2
, txIn =
{ version = 2,
inputs =
[ TxIn
{ prevOutput =
{ outpoint =
OutPoint
{ outPointHash = "39bc5c3b33d66ce3d7852a7942331e3ec10f8ba50f225fc41fb5dfa523239a27"
, outPointIndex = 0
{ hash = "39bc5c3b33d66ce3d7852a7942331e3ec10f8ba50f225fc41fb5dfa523239a27",
index = 0
},
script = "",
sequence = 4294967295
}
, scriptInput = ""
, txInSequence = 4294967295
}
]
, txOut =
],
outputs =
[ TxOut
{ outValue = 199908000
, scriptOutput = (fromJust . decodeHex) "76a914ffe9c0061097cc3b636f2cb0460fa4fc427d2b4588ac"
{ value = 199908000,
script = (fromJust . decodeHex) "76a914ffe9c0061097cc3b636f2cb0460fa4fc427d2b4588ac"
}
]
, txWitness = mempty
, txLockTime = 0
}
, globalUnknown = mempty
, inputs =
],
witness = mempty,
locktime = 0
},
globalUnknown = mempty,
inputs =
[ Input
{ nonWitnessUtxo = Nothing
, witnessUtxo =
{ nonWitnessUtxo = Nothing,
witnessUtxo =
Just
( TxOut
{ outValue = 199909013
, scriptOutput = (fromJust . decodeHex) "a9146345200f68d189e1adc0df1c4d16ea8f14c0dbeb87"
{ value = 199909013,
script = (fromJust . decodeHex) "a9146345200f68d189e1adc0df1c4d16ea8f14c0dbeb87"
}
)
, partialSigs =
),
partialSigs =
fromList
[
( PubKeyI
{ pubKeyPoint = "03b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd46"
, pubKeyCompressed = True
}
, (fromJust . decodeHex) "304302200424b58effaaa694e1559ea5c93bbfd4a89064224055cdf070b6771469442d07021f5c8eb0fea6516d60b8acb33ad64ede60e8785bfb3aa94b99bdf86151db9a9a01"
[ ( PublicKey
{ point =
fromJust $
importPubKey ctx
=<< decodeHex
"03b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd46",
compress = True
},
(fromJust . decodeHex) "304302200424b58effaaa694e1559ea5c93bbfd4a89064224055cdf070b6771469442d07021f5c8eb0fea6516d60b8acb33ad64ede60e8785bfb3aa94b99bdf86151db9a9a01"
)
]
, sigHashType = Nothing
, inputRedeemScript =
],
sigHashType = Nothing,
inputRedeemScript =
Just
. fromRight (error "vec5Test: Could not decode redeem script")
. decode
. fromJust
$ decodeHex "0020771fd18ad459666dd49f3d564e3dbc42f4c84774e360ada16816a8ed488d5681"
, inputWitnessScript =
$ decodeHex "0020771fd18ad459666dd49f3d564e3dbc42f4c84774e360ada16816a8ed488d5681",
inputWitnessScript =
Just
. fromRight (error "vec5Test: Could not decode witness script")
. decode
. fromJust
$ decodeHex "522103b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd462103de55d1e1dac805e3f8a58c1fbf9b94c02f3dbaafe127fefca4995f26f82083bd52ae"
, inputHDKeypaths =
$ decodeHex "522103b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd462103de55d1e1dac805e3f8a58c1fbf9b94c02f3dbaafe127fefca4995f26f82083bd52ae",
inputHDKeypaths =
fromList
[
( PubKeyI
{ pubKeyPoint = "03b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd46"
, pubKeyCompressed = True
}
, ("b4a6ba67", [hardIndex 0, hardIndex 0, hardIndex 4])
[ ( 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])
)
,
( PubKeyI
{ pubKeyPoint = "03de55d1e1dac805e3f8a58c1fbf9b94c02f3dbaafe127fefca4995f26f82083bd"
, pubKeyCompressed = True
],
finalScriptSig = Nothing,
finalScriptWitness = Nothing,
inputUnknown = mempty
}
, ("b4a6ba67", [hardIndex 0, hardIndex 0, hardIndex 5])
)
]
, finalScriptSig = Nothing
, finalScriptWitness = Nothing
, inputUnknown = mempty
}
]
, outputs =
],
outputs =
[ Output
{ outputRedeemScript = Nothing
, outputWitnessScript = Nothing
, outputHDKeypaths = mempty
, outputUnknown = mempty
{ 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
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 $
@ -246,28 +261,28 @@ vec6Test = do
(Key 0x0f (fromJust $ decodeHex "010203040506070809"))
(fromJust $ decodeHex "0102030405060708090a0b0c0d0e0f")
complexPsbtTest :: Assertion
complexPsbtTest = do
complexPsbtData <- readTestFile "complex_psbt.json"
complexPsbtTest :: Ctx -> Assertion
complexPsbtTest ctx = do
complex <- readTestFileParser (parseComplexJSON ctx) "complex_psbt.json"
let computedCombinedPsbt = mergeMany $ complexSignedPsbts complexPsbtData
expectedCombinedPsbt = stripRedundantUtxos $ complexCombinedPsbt complexPsbtData
let computedCombinedPsbt = mergeMany $ complexSignedPsbts complex
expectedCombinedPsbt = stripRedundantUtxos $ complexCombinedPsbt complex
assertEqual "combined psbt" computedCombinedPsbt (Just expectedCombinedPsbt)
let computedCompletePsbt = complete $ complexCombinedPsbt complexPsbtData
expectedCompletePsbt = complexCompletePsbt complexPsbtData
let computedCompletePsbt = complete ctx $ complexCombinedPsbt complex
expectedCompletePsbt = complexCompletePsbt complex
assertEqual "complete psbt" computedCompletePsbt expectedCompletePsbt
let computedFinalTx = finalTransaction $ complexCompletePsbt complexPsbtData
assertEqual "final tx" computedFinalTx (complexFinalTx complexPsbtData)
let computedFinalTx = finalTransaction $ complexCompletePsbt complex
assertEqual "final tx" computedFinalTx (complexFinalTx complex)
where
stripRedundantUtxos psbt = psbt{inputs = stripRedundantUtxo <$> inputs psbt}
stripRedundantUtxos PSBT {..} = PSBT {inputs = stripRedundantUtxo <$> inputs, ..}
stripRedundantUtxo input
| Just{} <- witnessUtxo input = input{nonWitnessUtxo = Nothing}
| Just {} <- input.witnessUtxo = input {nonWitnessUtxo = Nothing}
| otherwise = input
psbtSignerTest :: Assertion
psbtSignerTest = do
psbtSignerTest :: Ctx -> Assertion
psbtSignerTest ctx = do
assertEqual "recover explicit secret key" (Just theSecKey) (getSignerKey signer thePubKey Nothing)
assertEqual
"recover key for origin path"
@ -278,46 +293,46 @@ psbtSignerTest = do
(Just directPathSecKey)
(getSignerKey signer directPathPubKey (Just (keyFP, directPath)))
where
signer = secKeySigner theSecKey <> xPrvSigner xprv (Just origin)
signer = secKeySigner ctx theSecKey <> xPrvSigner ctx xprv (Just origin)
Just theSecKey = secKey "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
thePubKey = PubKeyI{pubKeyPoint = derivePubKey theSecKey, pubKeyCompressed = True}
thePubKey = PublicKey {point = derivePubKey ctx theSecKey, compress = True}
rootXPrv = makeXPrvKey "psbtSignerTest"
rootFP = xPubFP $ deriveXPubKey rootXPrv
xprv = derivePath keyPath rootXPrv
keyFP = xPubFP $ deriveXPubKey xprv
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 = xPrvKey $ derivePath originKeyPath rootXPrv
originPathPubKey = PubKeyI{pubKeyPoint = derivePubKey originPathSecKey, pubKeyCompressed = True}
originPathSecKey = (derivePath ctx originKeyPath rootXPrv).key
originPathPubKey = PublicKey {point = derivePubKey ctx originPathSecKey, compress = True}
directPath = Deriv :/ 1
directPathSecKey = xPrvKey $ derivePath directPath xprv
directPathPubKey = PubKeyI{pubKeyPoint = derivePubKey directPathSecKey, pubKeyCompressed = True}
directPathSecKey = (derivePath ctx directPath xprv).key
directPathPubKey = PublicKey {point = derivePubKey ctx directPathSecKey, compress = True}
expectedOut :: ScriptOutput
expectedOut =
expectedOut :: Ctx -> ScriptOutput
expectedOut ctx =
fromRight (error "could not decode expected output")
. decodeOutputBS
. unmarshal ctx
. fromJust
$ decodeHex "a9143545e6e33b832c47050f24d3eeb93c9c03948bc787"
witnessScriptPubKey :: Input -> ScriptOutput
witnessScriptPubKey =
witnessScriptPubKey :: Ctx -> Input -> ScriptOutput
witnessScriptPubKey ctx =
fromRight (error "could not decode witness utxo")
. decodeOutputBS
. scriptOutput
. unmarshal ctx
. (.script)
. fromJust
. witnessUtxo
. (.witnessUtxo)
decodeHexPSBT :: Text -> Either String PartiallySignedTransaction
decodeHexPSBT = S.decode . fromJust . decodeHex
decodeHexPSBT :: Ctx -> Text -> Either String PSBT
decodeHexPSBT ctx = runGet (getPSBT ctx) . fromJust . decodeHex
decodeHexPSBTM :: (Monad m, MonadFail m) => String -> Text -> m PartiallySignedTransaction
decodeHexPSBTM errMsg = either (fail . (errMsg <>) . (": " <>)) return . decodeHexPSBT
decodeHexPSBTM :: (Monad m, MonadFail m) => Ctx -> String -> Text -> m PSBT
decodeHexPSBTM ctx errMsg = either (fail . (errMsg <>) . (": " <>)) return . decodeHexPSBT ctx
hexScript :: Text -> ByteString
hexScript =
@ -329,185 +344,205 @@ hexScript =
encodeScript :: Script -> ByteString
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 :: PSBT
trivialPSBT =
PartiallySignedTransaction
{ unsignedTransaction = Tx{txVersion = 2, txIn = [], txOut = [], txWitness = [], txLockTime = 0}
, globalUnknown = UnknownMap mempty
, inputs = []
, outputs = []
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) =
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, prevOutput txInput))
(txOut . fromJust $ nonWitnessUtxo input)
(\(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)
input =
emptyInput
{ nonWitnessUtxo = Just prevTx,
partialSigs = singleton pubKey sig
}
currTx =
unfinalizedTx (txHash prevTx)
prevTx =
testUtxo [prevOut]
prevOutScript =
addressToScript ctx (pubKeyAddr ctx pubKey)
prevOut =
TxOut
{ outValue = 200000000
, scriptOutput = runPutS (serialize prevOutScript)
{ value = 200000000,
script = runPutS (serialize prevOutScript)
}
h = txSigHash net currTx prevOutScript (outValue prevOut) 0 sigHashAll
sig = encodeTxSig $ TxSignature (signHash (secKeyData prvKey) h) sigHashAll
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
arbitraryMultiSig :: Ctx -> Gen ([(PrivateKey, PublicKey)], Int)
arbitraryMultiSig ctx = do
(m, n) <- arbitraryMSParam
keys <- vectorOf n arbitraryKeyPair
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 =
{ version = 2,
inputs =
[ TxIn
{ prevOutput = OutPoint prevHash 0
, scriptInput = ""
, txInSequence = 4294967294
{ outpoint = OutPoint prevHash 0,
script = "",
sequence = 4294967294
}
]
, txOut =
[ TxOut{outValue = 99999699, scriptOutput = hexScript "76a914d0c59903c5bac2868760e90fd521a4665aa7652088ac"}
, TxOut{outValue = 100000000, scriptOutput = hexScript "a9143545e6e33b832c47050f24d3eeb93c9c03948bc787"}
]
, txWitness = []
, txLockTime = 1257139
],
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 =
{ version = 2,
inputs =
[ TxIn
{ prevOutput = OutPoint "f61b1742ca13176464adb3cb66050c00787bb3a4eead37e985f2df1e37718126" 0
, scriptInput = ""
, txInSequence = 4294967294
{ outpoint = OutPoint "f61b1742ca13176464adb3cb66050c00787bb3a4eead37e985f2df1e37718126" 0,
script = "",
sequence = 4294967294
}
]
, txOut =
[ TxOut{outValue = 99999699, scriptOutput = hexScript "76a914d0c59903c5bac2868760e90fd521a4665aa7652088ac"}
, TxOut{outValue = 100000000, scriptOutput = hexScript "a9143545e6e33b832c47050f24d3eeb93c9c03948bc787"}
]
, txWitness = []
, txLockTime = 1257139
],
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 =
{ version = 1,
inputs =
[ TxIn
{ prevOutput = OutPoint "e567952fb6cc33857f392efa3a46c995a28f69cca4bb1b37e0204dab1ec7a389" 1
, scriptInput = hexScript "160014be18d152a9b012039daf3da7de4f53349eecb985"
, txInSequence = 4294967295
}
, TxIn
{ prevOutput = OutPoint "b490486aec3ae671012dddb2bb08466bef37720a533a894814ff1da743aaf886" 1
, scriptInput = hexScript "160014fe3e9ef1a745e974d902c4355943abcb34bd5353"
, txInSequence = 4294967295
{ 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"
]
, txOut = prevOuts
, txWitness =
[
[ fromJust $ decodeHex "304402202712be22e0270f394f568311dc7ca9a68970b8025fdd3b240229f07f8a5f3a240220018b38d7dcd314e734c9276bd6fb40f673325bc4baa144c800d2f2f02db2765c01"
, fromJust $ decodeHex "03d2e15674941bad4a996372cb87e1856d3652606d98562fe39c5e9e7e413f2105"
]
,
[ fromJust $ decodeHex "3045022100d12b852d85dcd961d2f5f4ab660654df6eedcc794c0c33ce5cc309ffb5fce58d022067338a8e0e1725c197fb1a88af59f51e44e4255b20167c8684031c05d1f2592a01"
, fromJust $ decodeHex "0223b72beef0965d10be0778efecd61fcac6f79a4ea169393380734464f84f2ab3"
]
]
, txLockTime = 0
],
locktime = 0
}
testUtxo1 :: Tx
testUtxo1 =
testUtxo
[ TxOut{outValue = 200000000, scriptOutput = hexScript "76a91485cff1097fd9e008bb34af709c62197b38978a4888ac"}
, TxOut{outValue = 190303501938, scriptOutput = hexScript "a914339725ba21efd62ac753a9bcd067d6c7a6a39d0587"}
[ 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]
@ -534,35 +569,43 @@ validVec6Hex = "70736274ff01003f0200000001ffffffffffffffffffffffffffffffffffffff
validVec7Hex :: Text
validVec7Hex = "70736274ff0100520200000001815dd29e16fd2f567a040ce24f5337fb9cfd0c05bacd8890714a33edc7cbbc920000000000ffffffff0192f1052a01000000160014ef9ade26f63015d57f4ecdb268d1a9b8d6cd8872000000000001008402000000010000000000000000000000000000000000000000000000000000000000000000ffffffff03510101ffffffff0200f2052a010000001600145f4ffa19dbbe464561c50fc4d8d8ac0b41009dd20000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90000000001011f00f2052a010000001600145f4ffa19dbbe464561c50fc4d8d8ac0b41009dd201086b02473044022026a9f7afdb0128970bb3577e536ec3d3dc10c1e82650d11c9da1df9003b31d0202202258b11f962f12e0897c642cd6f38a0181db17197f3693a530c9431eb44dde7d0121033dc786e9628bb6c41c08fceb9b37458ad7a95e7e6b04e0bde45b6879398c3ac100220203a6affb58dda998a4ffdce652feb91038fdfc78c748ae687372e11292af8d312d101c4c5bfc00000080000000800100008000"
data ComplexPsbtData = ComplexPsbtData
{ complexSignedPsbts :: [PartiallySignedTransaction]
, complexCombinedPsbt :: PartiallySignedTransaction
, complexCompletePsbt :: PartiallySignedTransaction
, complexFinalTx :: Tx
data ComplexPSBT = ComplexPSBT
{ complexSignedPsbts :: [PSBT],
complexCombinedPsbt :: PSBT,
complexCompletePsbt :: PSBT,
complexFinalTx :: Tx
}
deriving (Eq, Show)
instance FromJSON ComplexPsbtData where
parseJSON = withObject "ComplexPsbtData" $ \obj -> do
ComplexPsbtData
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 "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 . (S.decode <=< maybe (Left "hex") Right . decodeHex)
parsePsbt = either fail pure . (S.decode <=< first Text.unpack . decodeBase64) . encodeUtf8
psbtField fieldName obj = obj .: fieldName >>= parsePsbt
parseTx =
either fail pure
. ( decode
<=< maybe (Left "hex") Right
. decodeHex
)
parsePsbt =
either fail pure
. (runGet (getPSBT ctx) <=< first Text.unpack . decodeBase64)
. encodeUtf8
psbtField fieldName obj =
obj .: fieldName >>= parsePsbt

View File

@ -1,60 +1,48 @@
{-# 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 (FromJSON (parseJSON), withObject, (.:), (.:?))
import Data.Aeson.Types (Parser)
import qualified Data.ByteArray as BA
import Data.Aeson
import Data.Aeson.Types
import Data.ByteArray qualified as BA
import Data.ByteString (ByteString)
import qualified Data.ByteString as BS
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 (
MAST (..),
PubKey,
PubKeyI (PubKeyI),
ScriptOutput,
ScriptPathData (..),
TaprootOutput (TaprootOutput),
TaprootWitness (ScriptPathSpend),
XOnlyPubKey (..),
addrToText,
btc,
decodeHex,
encodeTaprootWitness,
getMerkleProofs,
mastCommitment,
outputAddress,
taprootInternalKey,
taprootMAST,
taprootOutputKey,
taprootScriptOutput,
verifyScriptPathData,
)
import Haskoin.UtilSpec (readTestFile)
import Haskoin
import Test.HUnit (assertBool, (@?=))
import Test.Hspec (Spec, describe, it, runIO)
spec :: Spec
spec = do
TestVector{testScriptPubKey} <- runIO $ readTestFile "bip341.json"
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 testScriptPubKey
it "should build the correct script output" $ mapM_ testScriptOutput testScriptPubKey
it "should calculate the correct control blocks" $ mapM_ testControlBlocks testScriptPubKey
it "should arrive at the correct address" $ mapM_ testAddress testScriptPubKey
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 $ (taprootMAST . tspkGiven) testData
mapM_ checkMASTDetails $ ((.mast) . tspkGiven) testData
where
checkMASTDetails theMAST = do
-- Leaf hashes
@ -67,62 +55,64 @@ testHashes testData =
leaf@MASTLeaf {} -> [BA.convert $ mastCommitment leaf]
MASTCommitment {} -> mempty -- The test vectors have complete trees
testOutputKey :: TestScriptPubKey -> IO ()
testOutputKey testData = do
XOnlyPubKey (taprootOutputKey theOutput) @?= theOutputKey
testOutputKey :: Ctx -> TestScriptPubKey -> IO ()
testOutputKey ctx testData = do
XOnlyPubKey (taprootOutputKey ctx theOutput) @?= theOutputKey
where
theOutput = tspkGiven testData
theOutputKey = XOnlyPubKey . spkiTweakedPubKey $ tspkIntermediary testData
testScriptOutput :: TestScriptPubKey -> IO ()
testScriptOutput testData =
taprootScriptOutput (tspkGiven testData) @?= (spkeScriptPubKey . tspkExpected) testData
testScriptOutput :: Ctx -> TestScriptPubKey -> IO ()
testScriptOutput ctx testData =
taprootScriptOutput ctx (tspkGiven testData) @?= (spkeScriptPubKey . tspkExpected) testData
testControlBlocks :: TestScriptPubKey -> IO ()
testControlBlocks testData = do
testControlBlocks :: Ctx -> TestScriptPubKey -> IO ()
testControlBlocks ctx testData = do
mapM_ onExamples exampleControlBlocks
mapM_ checkVerification scriptPathSpends
where
theOutput = tspkGiven testData
theOutputKey = taprootOutputKey theOutput
theOutputKey = taprootOutputKey ctx theOutput
exampleControlBlocks = spkeControlBlocks $ tspkExpected testData
calculatedControlBlocks =
(!! 1) . encodeTaprootWitness . ScriptPathSpend <$> scriptPathSpends
(!! 1) . encodeTaprootWitness ctx . ScriptPathSpend <$> scriptPathSpends
scriptPathSpends =
fmap mkScriptPathSpend
. maybe mempty getMerkleProofs
$ taprootMAST theOutput
mkScriptPathSpend (scriptPathLeafVersion, scriptPathScript, proof) =
mkScriptPathSpend <$> maybe mempty getMerkleProofs theOutput.mast
mkScriptPathSpend (leafVersion, script, proof) =
ScriptPathData
{ scriptPathAnnex = Nothing
, scriptPathStack = mempty
, scriptPathScript
, scriptPathExternalIsOdd = odd $ keyParity theOutputKey
, scriptPathLeafVersion
, scriptPathInternalKey = taprootInternalKey theOutput
, scriptPathControl = BA.convert <$> proof
{ 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 theOutputKey
checkVerification = assertBool "Script verifies" . verifyScriptPathData ctx theOutputKey
keyParity :: PubKey -> Word8
keyParity key = case BS.unpack . runPutS . serialize $ PubKeyI key True of
keyParity :: Ctx -> PubKey -> Word8
keyParity ctx key =
case BS.unpack . marshal ctx $ PublicKey key True of
0x02 : _ -> 0x00
_ -> 0x01
testAddress :: TestScriptPubKey -> IO ()
testAddress testData = computedAddress @?= (Just . spkeAddress . tspkExpected) testData
testAddress :: Ctx -> TestScriptPubKey -> IO ()
testAddress ctx testData =
computedAddress @?= (Just . spkeAddress . tspkExpected) testData
where
computedAddress = (addrToText btc <=< outputAddress) . taprootScriptOutput $ tspkGiven testData
computedAddress =
(addrToText btc <=< outputAddress ctx)
. taprootScriptOutput ctx
$ tspkGiven testData
newtype SpkGiven = SpkGiven {unSpkGiven :: TaprootOutput}
instance FromJSON SpkGiven where
parseJSON = withObject "SpkGiven" $ \obj ->
fmap SpkGiven $
TaprootOutput
<$> (xOnlyPubKey <$> obj .: "internalPubkey")
<*> (obj .:? "scriptTree" >>= traverse parseScriptTree)
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
@ -139,51 +129,53 @@ instance FromJSON SpkGiven where
hexScript = either fail pure . runGetS deserialize <=< jsonHex
data SpkIntermediary = SpkIntermediary
{ spkiLeafHashes :: Maybe [ByteString]
, spkiMerkleRoot :: Maybe ByteString
, spkiTweakedPubKey :: PubKey
{ spkiLeafHashes :: Maybe [ByteString],
spkiMerkleRoot :: Maybe ByteString,
spkiTweakedPubKey :: PubKey
}
instance FromJSON SpkIntermediary where
parseJSON = withObject "SpkIntermediary" $ \obj ->
spkIntermediaryParseJSON :: Ctx -> Value -> Parser SpkIntermediary
spkIntermediaryParseJSON ctx = withObject "SpkIntermediary" $ \obj ->
SpkIntermediary
<$> (obj .:? "leafHashes" >>= (traverse . traverse) jsonHex)
<*> (obj .: "merkleRoot" >>= traverse jsonHex)
<*> (xOnlyPubKey <$> obj .: "tweakedPubkey")
<*> fmap
(\(XOnlyPubKey k) -> k)
(unmarshalValue ctx =<< obj .: "tweakedPubkey")
data SpkExpected = SpkExpected
{ spkeScriptPubKey :: ScriptOutput
, spkeControlBlocks :: Maybe [ByteString]
, spkeAddress :: Text
{ spkeScriptPubKey :: ScriptOutput,
spkeControlBlocks :: Maybe [ByteString],
spkeAddress :: Text
}
instance FromJSON SpkExpected where
parseJSON = withObject "SpkExpected" $ \obj ->
spkExpectedParseJSON :: Ctx -> Value -> Parser SpkExpected
spkExpectedParseJSON ctx = withObject "SpkExpected" $ \obj ->
SpkExpected
<$> obj .: "scriptPubKey"
<*> (obj .:? "scriptPathControlBlocks" >>= (traverse . traverse) jsonHex)
<$> (unmarshalValue ctx =<< obj .: "scriptPubKey")
<*> ((traverse . traverse) jsonHex =<< obj .:? "scriptPathControlBlocks")
<*> obj .: "bip350Address"
data TestScriptPubKey = TestScriptPubKey
{ tspkGiven :: TaprootOutput
, tspkIntermediary :: SpkIntermediary
, tspkExpected :: SpkExpected
{ tspkGiven :: TaprootOutput,
tspkIntermediary :: SpkIntermediary,
tspkExpected :: SpkExpected
}
instance FromJSON TestScriptPubKey where
parseJSON = withObject "TestScriptPubKey" $ \obj ->
TestScriptPubKey
<$> (unSpkGiven <$> obj .: "given")
<*> obj .: "intermediary"
<*> obj .: "expected"
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]
}
instance FromJSON TestVector where
parseJSON = withObject "TestVector" $ \obj ->
TestVector <$> obj .: "scriptPubKey"
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

View File

@ -1,8 +1,14 @@
{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE ImportQualifiedPost #-}
{-# LANGUAGE OverloadedRecordDot #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
module Haskoin.TransactionSpec (spec) where
import qualified Data.ByteString as B
import Control.Monad (unless)
import Data.ByteString qualified as B
import Data.Bytes.Get
import Data.Bytes.Put
import Data.Bytes.Serial
@ -13,9 +19,9 @@ import Data.String.Conversions
import Data.Text (Text)
import Data.Word (Word32, Word64)
import Haskoin.Address
import Haskoin.Constants
import Haskoin.Data
import Haskoin.Keys
import Haskoin.Crypto
import Haskoin.Network.Constants
import Haskoin.Network.Data
import Haskoin.Script
import Haskoin.Transaction
import Haskoin.Util
@ -25,51 +31,53 @@ import Test.Hspec
import Test.Hspec.QuickCheck
import Test.QuickCheck
serialVals :: [SerialBox]
serialVals =
[ SerialBox $ arbitraryTx =<< arbitraryNetwork
, SerialBox $ arbitraryWitnessTx =<< arbitraryNetwork
, SerialBox $ arbitraryLegacyTx =<< arbitraryNetwork
, SerialBox $ arbitraryTxIn =<< arbitraryNetwork
, SerialBox $ arbitraryTxOut =<< arbitraryNetwork
, SerialBox arbitraryOutPoint
serialVals :: Ctx -> [SerialBox]
serialVals ctx =
[ 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
]
readVals :: [ReadBox]
readVals =
[ ReadBox arbitraryTxHash
, ReadBox $ arbitraryTx =<< arbitraryNetwork
, ReadBox $ arbitraryTxIn =<< arbitraryNetwork
, ReadBox $ arbitraryTxOut =<< arbitraryNetwork
, ReadBox arbitraryOutPoint
readVals :: Ctx -> [ReadBox]
readVals ctx =
[ ReadBox arbitraryTxHash,
ReadBox $ flip arbitraryTx ctx =<< arbitraryNetwork,
ReadBox $ flip arbitraryTxIn ctx =<< arbitraryNetwork,
ReadBox $ flip arbitraryTxOut ctx =<< arbitraryNetwork,
ReadBox arbitraryOutPoint
]
jsonVals :: [JsonBox]
jsonVals =
[ JsonBox arbitraryTxHash
, JsonBox $ arbitraryTx =<< arbitraryNetwork
, JsonBox $ arbitraryWitnessTx =<< arbitraryNetwork
, JsonBox $ arbitraryLegacyTx =<< arbitraryNetwork
, JsonBox $ arbitraryTxIn =<< arbitraryNetwork
, JsonBox $ arbitraryTxOut =<< arbitraryNetwork
, JsonBox arbitraryOutPoint
jsonVals :: Ctx -> [JsonBox]
jsonVals ctx =
[ 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
]
spec :: Spec
spec = do
testIdentity serialVals readVals jsonVals []
spec = prepareContext $ \ctx -> do
testIdentity (serialVals ctx) (readVals ctx) (jsonVals ctx) []
describe "Transaction properties" $ do
prop "decode and encode txid" $
forAll arbitraryTxHash $ \h -> hexToTxHash (txHashToHex h) == Just h
forAll arbitraryTxHash $
\h -> hexToTxHash (txHashToHex h) == Just h
prop "from string transaction id" $
forAll arbitraryTxHash $ \h -> fromString (cs $ txHashToHex h) == h
forAll arbitraryTxHash $
\h -> fromString (cs $ txHashToHex h) == h
prop "building address tx" $
forAll arbitraryNetwork $ \net ->
forAll arbitraryAddress $
forAll (arbitrarySatoshi net) . testBuildAddrTx net
forAll (arbitrarySatoshi net) . testBuildAddrTx net ctx
prop "guess transaction size" $
forAll arbitraryNetwork $ \net ->
forAll (arbitraryAddrOnlyTxFull net) (testGuessSize net)
forAll (arbitraryAddrOnlyTxFull net ctx) (testGuessSize net ctx)
prop "choose coins" $
forAll arbitraryNetwork $ \net ->
forAll (listOf (arbitrarySatoshi net)) testChooseCoins
@ -79,17 +87,17 @@ spec = do
forAll (listOf (arbitrarySatoshi net)) . testChooseMSCoins
prop "sign and validate transaction" $
forAll arbitraryNetwork $ \net ->
forAll (arbitrarySigningData net) (testDetSignTx net)
forAll (arbitrarySigningData net ctx) (testDetSignTx net ctx)
prop "sign and validate (nested) transaction" $
forAll arbitraryNetwork $ \net ->
forAll (arbitrarySigningData net) (testDetSignNestedTx net)
forAll (arbitrarySigningData net ctx) (testDetSignNestedTx net ctx)
prop "merge partially signed transactions" $
forAll arbitraryNetwork $ \net ->
property $ forAll (arbitraryPartialTxs net) (testMergeTx 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 pkHashVectors
mapM_ (testPKHashVector ctx) pkHashVectors
-- Txid Vectors
@ -101,17 +109,15 @@ testTxidVector (tid, tx) =
txidVectors :: [(Text, Text)]
txidVectors =
[
( "23b397edccd3740a74adb603c9756370fafcde9bcc4483eb271ecad09a94dd63"
, "0100000001b14bdcbc3e01bdaad36cc08e81e69c82e1060bc14e518db2b49aa4\
[ ( "23b397edccd3740a74adb603c9756370fafcde9bcc4483eb271ecad09a94dd63",
"0100000001b14bdcbc3e01bdaad36cc08e81e69c82e1060bc14e518db2b49aa4\
\3ad90ba26000000000490047304402203f16c6f40162ab686621ef3000b04e75\
\418a0c0cb2d8aebeac894ae360ac1e780220ddc15ecdfc3507ac48e1681a33eb\
\60996631bf6bf5bc0a0682c4db743ce7ca2b01ffffffff0140420f0000000000\
\1976a914660d4ef3a743e3e696ad990364e555c271ad504b88ac00000000"
)
,
( "c99c49da4c38af669dea436d3e73780dfdb6c1ecf9958baa52960e8baee30e73"
, "01000000010276b76b07f4935c70acf54fbf1f438a4c397a9fb7e633873c4dd3\
),
( "c99c49da4c38af669dea436d3e73780dfdb6c1ecf9958baa52960e8baee30e73",
"01000000010276b76b07f4935c70acf54fbf1f438a4c397a9fb7e633873c4dd3\
\bc062b6b40000000008c493046022100d23459d03ed7e9511a47d13292d3430a\
\04627de6235b6e51a40f9cd386f2abe3022100e7d25b080f0bb8d8d5f878bba7\
\d54ad2fda650ea8d158a33ee3cbd11768191fd004104b0e2c879e4daf7b9ab68\
@ -119,10 +125,9 @@ txidVectors =
\2effc514b76279476550ba3663fdcaff94c38420e9d5000000000100093d0000\
\0000001976a9149a7b0f3b80c6baaeedce0a0842553800f832ba1f88ac000000\
\00"
)
,
( "f7fdd091fa6d8f5e7a8c2458f5c38faffff2d3f1406b6e4fe2c99dcc0d2d1cbb"
, "01000000023d6cf972d4dff9c519eff407ea800361dd0a121de1da8b6f4138a2\
),
( "f7fdd091fa6d8f5e7a8c2458f5c38faffff2d3f1406b6e4fe2c99dcc0d2d1cbb",
"01000000023d6cf972d4dff9c519eff407ea800361dd0a121de1da8b6f4138a2\
\f25de864b4000000008a4730440220ffda47bfc776bcd269da4832626ac332ad\
\fca6dd835e8ecd83cd1ebe7d709b0e022049cffa1cdc102a0b56e0e04913606c\
\70af702a1149dc3b305ab9439288fee090014104266abb36d66eb4218a6dd31f\
@ -135,10 +140,9 @@ txidVectors =
\0a50f919273e613f895b855fb7465ccbc8919ad1bd4a306c783f22cd32273276\
\94c4fa4c1c439affffffff01f0da5200000000001976a914857ccd42dded6df3\
\2949d4646dfa10a92458cfaa88ac00000000"
)
,
( "afd9c17f8913577ec3509520bd6e5d63e9c0fd2a5f70c787993b097ba6ca9fae"
, "010000000370ac0a1ae588aaf284c308d67ca92c69a39e2db81337e563bf40c5\
),
( "afd9c17f8913577ec3509520bd6e5d63e9c0fd2a5f70c787993b097ba6ca9fae",
"010000000370ac0a1ae588aaf284c308d67ca92c69a39e2db81337e563bf40c5\
\9da0a5cf63000000006a4730440220360d20baff382059040ba9be98947fd678\
\fb08aab2bb0c172efa996fd8ece9b702201b4fb0de67f015c90e7ac8a193aeab\
\486a1f587e0f54d0fb9552ef7f5ce6caec032103579ca2e6d107522f012cd00b\
@ -161,74 +165,60 @@ txidVectors =
-- Build address transactions vectors generated from bitcoin-core raw tx API
testPKHashVector :: ([(Text, Word32)], [(Text, Word64)], Text) -> Assertion
testPKHashVector (is, os, res) =
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
[ ( [ ( "eb29eba154166f6541ebcc9cbdf5088756e026af051f123bcfb526df594549db",
14
)
]
, [("14LsRquZfURNFrzpcLVGdaHTfAPjjwiSPb", 90000000)]
, "0100000001db494559df26b5cf3b121f05af26e0568708f5bd9ccceb41656f1654\
],
[("14LsRquZfURNFrzpcLVGdaHTfAPjjwiSPb", 90000000)],
"0100000001db494559df26b5cf3b121f05af26e0568708f5bd9ccceb41656f1654\
\a1eb29eb0e00000000ffffffff01804a5d05000000001976a91424aa604689cc58\
\2292b97668bedd91dd5bf9374c88ac00000000"
),
( [ ( "eb29eba154166f6541ebcc9cbdf5088756e026af051f123bcfb526df594549db",
0
),
( "0001000000000000000000000000000000000000000000000000000000000000",
2147483647
)
,
(
[
( "eb29eba154166f6541ebcc9cbdf5088756e026af051f123bcfb526df594549db"
, 0
)
,
( "0001000000000000000000000000000000000000000000000000000000000000"
, 2147483647
)
]
,
[ ("14LsRquZfURNFrzpcLVGdaHTfAPjjwiSPb", 1)
, ("19VCgS642vzEA1sdByoSn6GsWBwraV8D4n", 2100000000000000)
]
, "0100000002db494559df26b5cf3b121f05af26e0568708f5bd9ccceb41656f1654\
],
[ ("14LsRquZfURNFrzpcLVGdaHTfAPjjwiSPb", 1),
("19VCgS642vzEA1sdByoSn6GsWBwraV8D4n", 2100000000000000)
],
"0100000002db494559df26b5cf3b121f05af26e0568708f5bd9ccceb41656f1654\
\a1eb29eb0000000000ffffffff0000000000000000000000000000000000000000\
\000000000000000000000100ffffff7f00ffffffff0201000000000000001976a9\
\1424aa604689cc582292b97668bedd91dd5bf9374c88ac0040075af07507001976\
\a9145d16672f53981ff21c5f42b40d1954993cbca54f88ac00000000"
),
( [ ( "eb29eba154166f6541ebcc9cbdf5088756e026af051f123bcfb526df594549db",
0
),
( "0001000000000000000000000000000000000000000000000000000000000000",
2147483647
)
,
(
[
( "eb29eba154166f6541ebcc9cbdf5088756e026af051f123bcfb526df594549db"
, 0
)
,
( "0001000000000000000000000000000000000000000000000000000000000000"
, 2147483647
)
]
, []
, "0100000002db494559df26b5cf3b121f05af26e0568708f5bd9ccceb41656f1654a\
],
[],
"0100000002db494559df26b5cf3b121f05af26e0568708f5bd9ccceb41656f1654a\
\1eb29eb0000000000ffffffff000000000000000000000000000000000000000000\
\0000000000000000000100ffffff7f00ffffffff0000000000"
)
,
( []
,
[ ("14LsRquZfURNFrzpcLVGdaHTfAPjjwiSPb", 1)
, ("19VCgS642vzEA1sdByoSn6GsWBwraV8D4n", 2100000000000000)
]
, "01000000000201000000000000001976a91424aa604689cc582292b97668bedd91d\
),
( [],
[ ("14LsRquZfURNFrzpcLVGdaHTfAPjjwiSPb", 1),
("19VCgS642vzEA1sdByoSn6GsWBwraV8D4n", 2100000000000000)
],
"01000000000201000000000000001976a91424aa604689cc582292b97668bedd91d\
\d5bf9374c88ac0040075af07507001976a9145d16672f53981ff21c5f42b40d1954\
\993cbca54f88ac00000000"
)
@ -236,49 +226,39 @@ pkHashVectors =
-- 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
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 =
testGuessSize :: Network -> Ctx -> Tx -> Bool
testGuessSize net ctx tx =
guess >= len && guess <= len + 2 * delta
where
delta = pki + sum (map fst msi)
guess = guessTxSize pki msi pkout msout
len = B.length $ runPutS $ serialize tx
ins = map f $ txIn tx
f i =
fromRight (error "Could not decode input") $
decodeInputBS net $ scriptInput i
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
==> case chooseCoins target byteFee nOut True coins of
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)
@ -297,8 +277,8 @@ testChooseMSCoins ::
Int ->
Property
testChooseMSCoins (m, n) coins target byteFee nOut =
nOut >= 0
==> case chooseMSCoins target byteFee (m, n) nOut True coins of
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)
@ -311,59 +291,57 @@ testChooseMSCoins (m, n) coins target byteFee nOut =
{- 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)
| 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) =
testMergeTx :: Network -> Ctx -> ([Tx], [(ScriptOutput, Word64, OutPoint, Int, Int)]) -> Bool
testMergeTx net ctx (txs, os) =
and
[ isRight mergeRes
, length (txIn mergedTx) == length os
, if enoughSigs
[ isRight mergeRes,
length mergedTx.inputs == length os,
if enoughSigs
then isValid
else not isValid
, -- Signature count == min (length txs) (sum required signatures)
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
case unmarshal (net, ctx) inp.script of
Right (RegularInput (SpendMulSig sigs)) -> length sigs
Right (ScriptHashInput (SpendMulSig sigs) _) -> length sigs
_ -> error "Invalid input script type"

View File

@ -1,26 +1,25 @@
module Haskoin.UtilSpec (
spec,
customCerealID,
readTestFile,
) where
{-# LANGUAGE ImportQualifiedPost #-}
import Data.Aeson (FromJSON, ToJSON)
import qualified Data.Aeson as A
module Haskoin.UtilSpec (spec) where
import Data.Aeson
import Data.Aeson.Encoding (encodingToLazyByteString)
import Data.Aeson.Types (Parser, parseMaybe)
import qualified Data.ByteString as BS
import Data.Aeson.Types (Parser, parse)
import Data.ByteString (ByteString)
import Data.ByteString qualified as B
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.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
import Test.QuickCheck (forAll)
spec :: Spec
spec =
@ -38,7 +37,7 @@ spec =
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
@ -67,14 +66,3 @@ testEitherToMaybe e = isNothing (eitherToMaybe e)
testMaybeToEither :: Maybe Int -> String -> Bool
testMaybeToEither (Just v) str = maybeToEither str (Just v) == Right v
testMaybeToEither m str = maybeToEither str m == Left str
{-- 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 =
A.eitherDecodeFileStrict ("data/" <> fp) >>= either (error . message) return
where
message aesonErr = "Could not read test file " <> fp <> ": " <> aesonErr