diff --git a/bitcoin.cabal b/bitcoin.cabal index e3766ae2..f8832384 100644 --- a/bitcoin.cabal +++ b/bitcoin.cabal @@ -96,10 +96,8 @@ library , array >=0.5.4.0 , base >=4.9 && <5 , base16 >=0.3.0.1 - , binary >=0.8.8 , bytes >=0.17 , bytestring >=0.10.10.0 - , cereal >=0.5.8 , containers >=0.6.2.1 , cryptonite >=0.26 , deepseq >=1.4.4.0 @@ -149,11 +147,9 @@ test-suite spec , base >=4.9 && <5 , base16 >=0.3.0.1 , base64 ==0.4.* - , binary >=0.8.8 , bitcoin , bytes >=0.17 , bytestring >=0.10.10.0 - , cereal >=0.5.8 , containers >=0.6.2.1 , cryptonite >=0.26 , deepseq >=1.4.4.0 diff --git a/package.yaml b/package.yaml index 612ce464..b3e88210 100644 --- a/package.yaml +++ b/package.yaml @@ -22,10 +22,10 @@ dependencies: - array >= 0.5.4.0 - base >=4.9 && <5 - base16 >= 0.3.0.1 - - binary >= 0.8.8 + # - binary >= 0.8.8 - bytes >= 0.17 - bytestring >= 0.10.10.0 - - cereal >= 0.5.8 + # - cereal >= 0.5.8 - containers >= 0.6.2.1 - cryptonite >= 0.26 - deepseq >= 1.4.4.0 diff --git a/src/Haskoin/Address.hs b/src/Haskoin/Address.hs index 5742758f..d910570f 100644 --- a/src/Haskoin/Address.hs +++ b/src/Haskoin/Address.hs @@ -51,7 +51,6 @@ import Control.Applicative import Control.Arrow (second) import Control.DeepSeq import Control.Monad -import Data.Binary (Binary (..)) import Data.ByteString (ByteString) import qualified Data.ByteString as B import Data.Bytes.Get @@ -59,7 +58,6 @@ import Data.Bytes.Put import Data.Bytes.Serial import Data.Hashable import Data.Maybe -import Data.Serialize (Serialize (..)) import Data.Text (Text) import qualified Data.Text as T import Data.Word (Word8) diff --git a/src/Haskoin/Constants.hs b/src/Haskoin/Constants.hs index 43fe39b9..4626dff5 100644 --- a/src/Haskoin/Constants.hs +++ b/src/Haskoin/Constants.hs @@ -22,14 +22,12 @@ module Haskoin.Constants ( ) 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) diff --git a/src/Haskoin/Crypto/Hash.hs b/src/Haskoin/Crypto/Hash.hs index 41102173..2a2d08d7 100644 --- a/src/Haskoin/Crypto/Hash.hs +++ b/src/Haskoin/Crypto/Hash.hs @@ -2,17 +2,16 @@ {-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE TypeApplications #-} -{- | -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 +--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), @@ -45,7 +44,6 @@ import Crypto.Hash ( hashWith, ) import Crypto.MAC.HMAC (HMAC, hmac) -import Data.Binary (Binary (..)) import Data.ByteArray (ByteArrayAccess) import qualified Data.ByteArray as BA import Data.ByteString (ByteString) @@ -57,7 +55,6 @@ import qualified Data.Bytes.Put as Put import Data.Bytes.Serial (Serial (..)) import Data.Either (fromRight) import Data.Hashable (Hashable) -import Data.Serialize (Serialize (..)) import Data.String (IsString, fromString) import Data.String.Conversions (cs) import Data.Word (Word32) @@ -65,56 +62,59 @@ import GHC.Generics (Generic) import Haskoin.Util import Text.Read as R + -- | 'Word32' wrapped for type-safe 32-bit checksums. newtype CheckSum32 = CheckSum32 { getCheckSum32 :: Word32 } deriving (Eq, Ord, Serial, Show, Read, Hashable, Generic, NFData) -instance Serialize CheckSum32 where - put = serialize - get = deserialize - -instance Binary CheckSum32 where - put = serialize - get = deserialize -- | Type for 512-bit hashes. newtype Hash512 = Hash512 {getHash512 :: ShortByteString} deriving (Eq, Ord, Hashable, Generic, NFData) + -- | Type for 256-bit hashes. newtype Hash256 = Hash256 {getHash256 :: ShortByteString} deriving (Eq, Ord, Hashable, Generic, NFData) + -- | Type for 160-bit hashes. newtype Hash160 = Hash160 {getHash160 :: ShortByteString} deriving (Eq, Ord, Hashable, Generic, NFData) + instance Show Hash512 where showsPrec _ = shows . encodeHex . BSS.fromShort . getHash512 + instance Read Hash512 where readPrec = do R.String str <- lexP maybe pfail return $ Hash512 . BSS.toShort <$> decodeHex (cs str) + instance Show Hash256 where showsPrec _ = shows . encodeHex . BSS.fromShort . getHash256 + instance Read Hash256 where readPrec = do R.String str <- lexP maybe pfail return $ Hash256 . BSS.toShort <$> decodeHex (cs str) + instance Show Hash160 where showsPrec _ = shows . encodeHex . BSS.fromShort . getHash160 + instance Read Hash160 where readPrec = do R.String str <- lexP maybe pfail return $ Hash160 . BSS.toShort <$> decodeHex (cs str) + instance IsString Hash512 where fromString str = case decodeHex $ cs str of @@ -123,20 +123,14 @@ instance IsString Hash512 where case BS.length bs of 64 -> Hash512 (BSS.toShort bs) _ -> e - where - e = error "Could not decode hash from hex string" + 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 -instance Serialize Hash512 where - put = serialize - get = deserialize - -instance Binary Hash512 where - put = serialize - get = deserialize instance IsString Hash256 where fromString str = @@ -146,20 +140,14 @@ instance IsString Hash256 where case BS.length bs of 32 -> Hash256 (BSS.toShort bs) _ -> e - where - e = error "Could not decode hash from hex string" + 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 -instance Serialize Hash256 where - put = serialize - get = deserialize - -instance Binary Hash256 where - put = serialize - get = deserialize instance IsString Hash160 where fromString str = @@ -169,47 +157,47 @@ instance IsString Hash160 where case BS.length bs of 20 -> Hash160 (BSS.toShort bs) _ -> e - where - e = error "Could not decode hash from hex string" + 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 -instance Serialize Hash160 where - put = serialize - get = deserialize - -instance Binary Hash160 where - put = serialize - get = deserialize -- | Calculate SHA512 hash. sha512 :: ByteArrayAccess b => b -> Hash512 sha512 = Hash512 . BSS.toShort . BA.convert . hashWith SHA512 + -- | Calculate SHA256 hash. sha256 :: ByteArrayAccess b => b -> Hash256 sha256 = Hash256 . BSS.toShort . BA.convert . hashWith SHA256 + -- | Calculate RIPEMD160 hash. ripemd160 :: ByteArrayAccess b => b -> Hash160 ripemd160 = Hash160 . BSS.toShort . BA.convert . hashWith RIPEMD160 + -- | Claculate SHA1 hash. sha1 :: ByteArrayAccess b => b -> Hash160 sha1 = Hash160 . BSS.toShort . BA.convert . hashWith SHA1 + -- | Compute two rounds of SHA-256. doubleSHA256 :: ByteArrayAccess b => b -> Hash256 doubleSHA256 = Hash256 . BSS.toShort . BA.convert . hashWith SHA256 . hashWith SHA256 + -- | Compute SHA-256 followed by RIPMED-160. addressHash :: ByteArrayAccess b => b -> Hash160 addressHash = Hash160 . BSS.toShort . BA.convert . hashWith RIPEMD160 . hashWith SHA256 + {- CheckSum -} -- | Computes a 32 bit checksum. @@ -222,6 +210,7 @@ checkSum32 = . hashWith SHA256 . hashWith SHA256 + {- HMAC -} -- | Computes HMAC over SHA-512. @@ -229,17 +218,20 @@ hmac512 :: ByteString -> ByteString -> Hash512 hmac512 key msg = Hash512 $ BSS.toShort $ BA.convert (hmac key msg :: HMAC SHA512) + -- | Computes HMAC over SHA-256. hmac256 :: (ByteArrayAccess k, ByteArrayAccess m) => k -> m -> Hash256 hmac256 key msg = Hash256 $ BSS.toShort $ BA.convert (hmac key msg :: HMAC SHA256) + -- | Split a 'Hash512' into a pair of 'Hash256'. split512 :: Hash512 -> (Hash256, Hash256) split512 h = (Hash256 (BSS.toShort a), Hash256 (BSS.toShort b)) - where - (a, b) = BS.splitAt 32 . BSS.fromShort $ getHash512 h + where + (a, b) = BS.splitAt 32 . BSS.fromShort $ getHash512 h + -- | Join a pair of 'Hash256' into a 'Hash512'. join512 :: (Hash256, Hash256) -> Hash512 @@ -248,10 +240,10 @@ join512 (a, b) = . BSS.toShort $ BSS.fromShort (getHash256 a) `BS.append` BSS.fromShort (getHash256 b) -{- | Initialize tagged hash specified in BIP340 -@since 0.21.0 --} +-- | Initialize tagged hash specified in BIP340 +-- +--@since 0.21.0 initTaggedHash :: -- | Hash tag ByteString -> @@ -259,5 +251,5 @@ initTaggedHash :: initTaggedHash tag = (`hashUpdates` [hashedTag, hashedTag]) $ hashInit @SHA256 - where - hashedTag = hashWith SHA256 tag + where + hashedTag = hashWith SHA256 tag diff --git a/src/Haskoin/Crypto/Signature.hs b/src/Haskoin/Crypto/Signature.hs index 31e24047..1bd34f93 100644 --- a/src/Haskoin/Crypto/Signature.hs +++ b/src/Haskoin/Crypto/Signature.hs @@ -1,16 +1,15 @@ {-# LANGUAGE OverloadedStrings #-} -{- | -Module : Haskoin.Crypto.Signature -Copyright : No rights reserved -License : MIT -Maintainer : jprupp@protonmail.ch -Stability : experimental -Portability : POSIX - -ECDSA signatures using secp256k1 curve. Uses functions from upstream secp256k1 -library. --} +-- | +--Module : Haskoin.Crypto.Signature +--Copyright : No rights reserved +--License : MIT +--Maintainer : jprupp@protonmail.ch +--Stability : experimental +--Portability : POSIX +-- +--ECDSA signatures using secp256k1 curve. Uses functions from upstream secp256k1 +--library. module Haskoin.Crypto.Signature ( -- * Signatures putSig, @@ -24,33 +23,35 @@ module Haskoin.Crypto.Signature ( import Control.Monad (guard, unless, when) import Crypto.Secp256k1 -import Data.Binary (Binary (..)) import Data.ByteString (ByteString) import qualified Data.ByteString as BS import Data.Bytes.Get import Data.Bytes.Put import Data.Bytes.Serial import Data.Maybe (fromMaybe, isNothing) -import Data.Serialize (Serialize (..)) import Haskoin.Crypto.Hash import Numeric (showHex) + -- | Convert 256-bit hash into a 'Msg' for signing or verification. hashToMsg :: Hash256 -> Msg hashToMsg = fromMaybe e . msg . runPutS . serialize - where - e = error "Could not convert 32-byte hash to secp256k1 message" + where + e = error "Could not convert 32-byte hash to secp256k1 message" + -- | Sign a 256-bit hash using secp256k1 elliptic curve. signHash :: SecKey -> Hash256 -> Sig signHash k = signMsg k . hashToMsg + -- | Verify an ECDSA signature for a 256-bit hash. verifyHashSig :: Hash256 -> Sig -> PubKey -> Bool verifyHashSig h s p = verifySig p norm (hashToMsg h) - where - norm = fromMaybe s (normalizeSig s) + where + norm = fromMaybe s (normalizeSig s) + -- | Deserialize an ECDSA signature as commonly encoded in Bitcoin. getSig :: MonadGet m => m Sig @@ -71,14 +72,17 @@ getSig = do 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 + -- | Is canonical half order. isCanonicalHalfOrder :: Sig -> Bool isCanonicalHalfOrder = isNothing . normalizeSig + -- | Decode signature strictly. decodeStrictSig :: ByteString -> Maybe Sig decodeStrictSig bs = do diff --git a/src/Haskoin/Data.hs b/src/Haskoin/Data.hs index 769c49c7..b2220420 100644 --- a/src/Haskoin/Data.hs +++ b/src/Haskoin/Data.hs @@ -6,13 +6,11 @@ module Haskoin.Data ( ) 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) diff --git a/src/Haskoin/Keys/Common.hs b/src/Haskoin/Keys/Common.hs index 1be5aea9..57de5b5b 100644 --- a/src/Haskoin/Keys/Common.hs +++ b/src/Haskoin/Keys/Common.hs @@ -38,7 +38,6 @@ module Haskoin.Keys.Common ( import Control.DeepSeq import Control.Monad (guard, mzero, (<=<)) import Crypto.Secp256k1 -import Data.Binary (Binary (..)) import Data.ByteString (ByteString) import qualified Data.ByteString as BS import Data.ByteString.Builder (char7) @@ -47,7 +46,6 @@ 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) diff --git a/src/Haskoin/Keys/Extended.hs b/src/Haskoin/Keys/Extended.hs index 864a53d4..7e94b5ba 100644 --- a/src/Haskoin/Keys/Extended.hs +++ b/src/Haskoin/Keys/Extended.hs @@ -103,7 +103,6 @@ import Control.DeepSeq import Control.Exception (Exception, throw) import Control.Monad (guard, mzero, unless, (<=<)) import Crypto.Secp256k1 -import Data.Binary (Binary (get, put)) import Data.Bits (clearBit, setBit, testBit) import Data.ByteString (ByteString) import qualified Data.ByteString as B @@ -115,8 +114,6 @@ import Data.Hashable import Data.List (foldl') import Data.List.Split (splitOn) import Data.Maybe (fromMaybe) -import Data.Serialize (Serialize (..)) -import qualified Data.Serialize as S import Data.String (IsString, fromString) import Data.String.Conversions (cs) import qualified Data.Text as Text diff --git a/src/Haskoin/Keys/Extended/Internal.hs b/src/Haskoin/Keys/Extended/Internal.hs index 1b2e78ab..6e4293e1 100644 --- a/src/Haskoin/Keys/Extended/Internal.hs +++ b/src/Haskoin/Keys/Extended/Internal.hs @@ -9,15 +9,12 @@ module Haskoin.Keys.Extended.Internal ( import Control.DeepSeq (NFData) import Control.Monad ((>=>)) -import Data.Binary (Binary (..)) -import Data.Bytes.Get (getWord32be) -import Data.Bytes.Put (putWord32be) +import Data.Bytes.Get (getWord32be, runGetS) +import Data.Bytes.Put (putWord32be, runPutS) 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 @@ -34,28 +31,28 @@ newtype Fingerprint = Fingerprint {unFingerprint :: Word32} fingerprintToText :: Fingerprint -> Text -fingerprintToText = encodeHex . S.encode +fingerprintToText = encodeHex . runPutS . serialize textToFingerprint :: Text -> Either String Fingerprint -textToFingerprint = maybe (Left "Fingerprint: invalid hex") Right . decodeHex >=> S.decode +textToFingerprint = maybe (Left "Fingerprint: invalid hex") Right . decodeHex >=> runGetS deserialize instance Show Fingerprint where - show = show . Text.unpack . encodeHex . S.encode + show = show . Text.unpack . encodeHex . runPutS . serialize instance Read Fingerprint where readPrec = readPrec >>= maybe (fail "Fingerprint: invalid hex") pure . decodeHex - >>= either (fail . ("Fingerprint: " <>)) pure . S.decode + >>= either (fail . ("Fingerprint: " <>)) pure . runGetS deserialize instance IsString Fingerprint where fromString = fromRight decodeError - . S.decode + . runGetS deserialize . fromMaybe hexError . decodeHex . Text.pack @@ -67,13 +64,3 @@ instance IsString Fingerprint where 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 diff --git a/src/Haskoin/Network/Bloom.hs b/src/Haskoin/Network/Bloom.hs index 047add34..d082eacb 100644 --- a/src/Haskoin/Network/Bloom.hs +++ b/src/Haskoin/Network/Bloom.hs @@ -1,19 +1,18 @@ {-# LANGUAGE DeriveAnyClass #-} {-# LANGUAGE DeriveGeneric #-} -{- | -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 +--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 (..), @@ -32,7 +31,6 @@ module Haskoin.Network.Bloom ( import Control.DeepSeq import Control.Monad (forM_, replicateM) -import Data.Binary (Binary (..)) import Data.Bits import Data.ByteString (ByteString) import qualified Data.ByteString as BS @@ -43,32 +41,36 @@ import qualified Data.Foldable as F import Data.Hash.Murmur (murmur3) import Data.List (foldl') import qualified Data.Sequence as S -import Data.Serialize (Serialize (..)) import Data.Word import GHC.Generics (Generic) import Haskoin.Network.Common import Haskoin.Script.Standard import Haskoin.Transaction.Common + -- | 20,000 items with fp rate < 0.1% or 10,000 items and <0.0001% maxBloomSize :: Int maxBloomSize = 36000 + maxHashFuncs :: Word32 maxHashFuncs = 50 + ln2Squared :: Double ln2Squared = 0.4804530139182014246671025263266649717305529515945455 + ln2 :: Double ln2 = 0.6931471805599453094172321214581765680755001343602552 + bitMask :: [Word8] bitMask = [0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80] -{- | The bloom flags are used to tell the remote peer how to auto-update - the provided bloom filter. --} + +-- | The bloom flags are used to tell the remote peer how to auto-update +-- the provided bloom filter. data BloomFlags = -- | never update BloomUpdateNone @@ -78,34 +80,38 @@ data BloomFlags BloomUpdateP2PubKeyOnly deriving (Eq, Show, Read, Generic, NFData) + instance Serial BloomFlags where deserialize = go =<< getWord8 - where - go 0 = return BloomUpdateNone - go 1 = return BloomUpdateAll - go 2 = return BloomUpdateP2PubKeyOnly - go _ = fail "BloomFlags get: Invalid bloom flag" + where + go 0 = return BloomUpdateNone + go 1 = return BloomUpdateAll + go 2 = return BloomUpdateP2PubKeyOnly + go _ = fail "BloomFlags get: Invalid bloom flag" + serialize f = putWord8 $ case f of BloomUpdateNone -> 0 BloomUpdateAll -> 1 BloomUpdateP2PubKeyOnly -> 2 + instance Binary BloomFlags where get = deserialize put = serialize + instance Serialize BloomFlags where get = deserialize put = serialize -{- | A bloom filter is a probabilistic data structure that SPV clients send to - other peers to filter the set of transactions received from them. Bloom - filters can have false positives but not false negatives. Some transactions - that pass the filter may not be relevant to the receiving peer. By - controlling the false positive rate, SPV nodes can trade off bandwidth - versus privacy. --} + +-- | 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) @@ -118,6 +124,7 @@ data BloomFilter = BloomFilter } deriving (Eq, Show, Read, Generic, NFData) + instance Serial BloomFilter where deserialize = BloomFilter @@ -125,8 +132,9 @@ instance Serial BloomFilter where <*> getWord32le <*> getWord32le <*> deserialize - where - readDat (VarInt len) = replicateM (fromIntegral len) getWord8 + where + readDat (VarInt len) = replicateM (fromIntegral len) getWord8 + serialize (BloomFilter dat hashFuncs tweak flags) = do putVarInt $ S.length dat @@ -135,57 +143,67 @@ instance Serial BloomFilter where putWord32le tweak serialize flags + instance Binary BloomFilter where put = serialize get = deserialize + instance Serialize BloomFilter where put = serialize get = deserialize + -- | Set a new bloom filter on the peer connection. newtype FilterLoad = FilterLoad {filterLoadBloomFilter :: BloomFilter} deriving (Eq, Show, Read, Generic, NFData) + instance Serial FilterLoad where deserialize = FilterLoad <$> deserialize serialize (FilterLoad f) = serialize f + instance Binary FilterLoad where put = serialize get = deserialize + instance Serialize FilterLoad where put = serialize get = deserialize -{- | Add the given data element to the connections current filter without - requiring a completely new one to be set. --} + +-- | Add the given data element to the connections current filter without +-- requiring a completely new one to be set. newtype FilterAdd = FilterAdd {getFilterData :: ByteString} deriving (Eq, Show, Read, Generic, NFData) + instance Serial FilterAdd where deserialize = do (VarInt len) <- deserialize dat <- getByteString $ fromIntegral len return $ FilterAdd dat + serialize (FilterAdd bs) = do putVarInt $ BS.length bs putByteString bs + instance Binary FilterAdd where put = serialize get = deserialize + instance Serialize FilterAdd where put = serialize get = deserialize -{- | Build a bloom filter that will provide the given false positive rate when - the given number of elements have been inserted. --} + +-- | 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 -> @@ -199,26 +217,27 @@ bloomCreate :: BloomFilter bloomCreate numElem fpRate = BloomFilter (S.replicate bloomSize 0) numHashF - where - -- Bloom filter size in bytes - bloomSize = truncate $ min a b / 8 - -- Suggested size in bits - a = -1 / ln2Squared * fromIntegral numElem * log fpRate - -- Maximum size in bits - b = fromIntegral $ maxBloomSize * 8 - numHashF = truncate $ min c (fromIntegral maxHashFuncs) - -- Suggested number of hash functions - c = fromIntegral bloomSize * 8 / fromIntegral numElem * ln2 + where + -- Bloom filter size in bytes + bloomSize = truncate $ min a b / 8 + -- Suggested size in bits + a = -1 / ln2Squared * fromIntegral numElem * log fpRate + -- Maximum size in bits + b = fromIntegral $ maxBloomSize * 8 + numHashF = truncate $ min c (fromIntegral maxHashFuncs) + -- Suggested number of hash functions + c = fromIntegral bloomSize * 8 / fromIntegral numElem * ln2 + bloomHash :: BloomFilter -> Word32 -> ByteString -> Word32 bloomHash bfilter hashNum bs = murmur3 seed bs `mod` (fromIntegral (S.length (bloomData bfilter)) * 8) - where - seed = hashNum * 0xfba4c795 + bloomTweak bfilter + where + seed = hashNum * 0xfba4c795 + bloomTweak bfilter + -{- | 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 -> @@ -229,18 +248,18 @@ bloomInsert :: bloomInsert bfilter bs | isBloomFull bfilter = bfilter | otherwise = bfilter{bloomData = newData} - where - idxs = map (\i -> bloomHash bfilter i bs) [0 .. bloomHashFuncs bfilter - 1] - upd s i = - S.adjust - (.|. bitMask !! fromIntegral (7 .&. i)) - (fromIntegral $ i `shiftR` 3) - s - newData = foldl upd (bloomData bfilter) idxs - -{- | Tests if some arbitrary data matches the filter. This can be either because - the data was inserted into the filter or because it is a false positive. --} + where + idxs = map (\i -> bloomHash bfilter i bs) [0 .. bloomHashFuncs bfilter - 1] + upd s i = + S.adjust + (.|. bitMask !! fromIntegral (7 .&. i)) + (fromIntegral $ i `shiftR` 3) + s + newData = foldl upd (bloomData bfilter) idxs + + +-- | Tests if some arbitrary data matches the filter. This can be either because +-- the data was inserted into the filter or because it is a false positive. bloomContains :: -- | Bloom filter BloomFilter -> @@ -252,17 +271,17 @@ bloomContains bfilter bs | isBloomFull bfilter = True | isBloomEmpty bfilter = False | otherwise = all isSet idxs - where - s = bloomData bfilter - idxs = map (\i -> bloomHash bfilter i bs) [0 .. bloomHashFuncs bfilter - 1] - isSet i = - S.index s (fromIntegral $ i `shiftR` 3) - .&. (bitMask !! fromIntegral (7 .&. i)) /= 0 - -{- | Checks if any of the outputs of a tx is in the current bloom filter. - If it is, add the txid and vout as an outpoint (i.e. so that - a future tx that spends the output won't be missed). --} + where + s = bloomData bfilter + idxs = map (\i -> bloomHash bfilter i bs) [0 .. bloomHashFuncs bfilter - 1] + isSet i = + S.index s (fromIntegral $ i `shiftR` 3) + .&. (bitMask !! fromIntegral (7 .&. i)) /= 0 + + +-- | Checks if any of the outputs of a tx is in the current bloom filter. +-- If it is, add the txid and vout as an outpoint (i.e. so that +-- a future tx that spends the output won't be missed). bloomRelevantUpdate :: -- | Bloom filter BloomFilter -> @@ -275,44 +294,47 @@ bloomRelevantUpdate bfilter tx | bloomFlags bfilter == BloomUpdateNone = Nothing | not (null matchOuts) = Just $ foldl' addRelevant bfilter matchOuts | otherwise = Nothing - where - -- TxHash if we end up inserting an outpoint - - h = txHash tx - -- Decode the scriptOutpus and add vOuts in case we make them outpoints - decodedOutputScripts = traverse (decodeOutputBS . scriptOutput) $ txOut tx - err = error "Error Decoding output script" - idxOutputScripts = either (const err) (zip [0 ..]) decodedOutputScripts - -- Check if any txOuts were contained in the bloom filter - matchFilter = - filter (\(_, op) -> bloomContains bfilter $ encodeScriptOut op) - matchOuts = matchFilter idxOutputScripts - addRelevant :: BloomFilter -> (Word32, ScriptOutput) -> BloomFilter - addRelevant bf (id', scriptOut) = - case (bloomFlags bfilter, 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'} - 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 + where + -- TxHash if we end up inserting an outpoint + + h = txHash tx + -- Decode the scriptOutpus and add vOuts in case we make them outpoints + decodedOutputScripts = traverse (decodeOutputBS . scriptOutput) $ txOut tx + err = error "Error Decoding output script" + idxOutputScripts = either (const err) (zip [0 ..]) decodedOutputScripts + -- Check if any txOuts were contained in the bloom filter + matchFilter = + filter (\(_, op) -> bloomContains bfilter $ encodeScriptOut op) + matchOuts = matchFilter idxOutputScripts + addRelevant :: BloomFilter -> (Word32, ScriptOutput) -> BloomFilter + addRelevant bf (id', scriptOut) = + case (bloomFlags bfilter, 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'} + 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 + -- | Returns True if the filter is empty (all bytes set to 0x00) isBloomEmpty :: BloomFilter -> Bool isBloomEmpty bfilter = all (== 0x00) $ F.toList $ bloomData bfilter + -- | Returns True if the filter is full (all bytes set to 0xff) isBloomFull :: BloomFilter -> Bool isBloomFull bfilter = all (== 0xff) $ F.toList $ bloomData bfilter + -- | Tests if a given bloom filter is valid. isBloomValid :: -- | Bloom filter to test @@ -323,6 +345,7 @@ isBloomValid bfilter = S.length (bloomData bfilter) <= maxBloomSize && bloomHashFuncs bfilter <= maxHashFuncs + -- | Does the peer with these version services accept bloom filters? acceptsFilters :: Word64 -> Bool acceptsFilters srv = srv .&. (1 `shiftL` 2) /= 0 diff --git a/src/Haskoin/Network/Common.hs b/src/Haskoin/Network/Common.hs index f1d942bc..038fce51 100644 --- a/src/Haskoin/Network/Common.hs +++ b/src/Haskoin/Network/Common.hs @@ -2,16 +2,15 @@ {-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE OverloadedStrings #-} -{- | -Module : Haskoin.Network.Common -Copyright : No rights reserved -License : MIT -Maintainer : jprupp@protonmail.ch -Stability : experimental -Portability : POSIX - -Common functions and data types related to peer-to-peer network. --} +-- | +--Module : Haskoin.Network.Common +--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 (..), @@ -48,7 +47,6 @@ module Haskoin.Network.Common ( 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 @@ -56,7 +54,6 @@ import Data.ByteString.Char8 as C (replicate) import Data.Bytes.Get import Data.Bytes.Put import Data.Bytes.Serial -import Data.Serialize (Serialize (..)) import Data.String import Data.String.Conversions (cs) import Data.Word (Word32, Word64) @@ -65,40 +62,35 @@ import Haskoin.Crypto.Hash import Network.Socket (SockAddr (..)) import Text.Read as R + -- | Network address with a timestamp. type NetworkAddressTime = (Word32, NetworkAddress) -{- | Provides information about known nodes in the bitcoin network. An 'Addr' - type is sent inside a 'Message' as a response to a 'GetAddr' message. --} + +-- | Provides information about known nodes in the bitcoin network. An 'Addr' +-- type is sent inside a 'Message' as a response to a 'GetAddr' message. newtype Addr = Addr { -- List of addresses of other nodes on the network with timestamps. addrList :: [NetworkAddressTime] } deriving (Eq, Show, Generic, NFData) + instance Serial Addr where deserialize = Addr <$> (repList =<< deserialize) - where - repList (VarInt c) = replicateM (fromIntegral c) action - action = liftM2 (,) getWord32le deserialize + where + repList (VarInt c) = replicateM (fromIntegral c) action + action = liftM2 (,) getWord32le deserialize + serialize (Addr xs) = do putVarInt $ length xs forM_ xs $ \(a, b) -> putWord32le a >> serialize b -instance Binary Addr where - get = deserialize - put = serialize - -instance Serialize Addr where - get = deserialize - put = serialize -{- | Data type describing signed messages that can be sent between bitcoin - nodes to display important notifications to end users about the health of - the network. --} +-- | Data 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 @@ -107,79 +99,60 @@ data Alert = Alert } deriving (Eq, Show, Read, Generic, NFData) + instance Serial Alert where deserialize = Alert <$> deserialize <*> deserialize serialize (Alert p s) = serialize p >> serialize s -instance Binary Alert where - put = serialize - get = deserialize - -instance Serialize Alert where - put = serialize - get = deserialize - -{- | The 'GetData' type is used to retrieve information on a specific object - ('Block' or 'Tx') identified by the objects hash. The payload of a 'GetData' - request is a list of 'InvVector' which represent all the hashes of objects - that a node wants. The response to a 'GetBlock' message will be either a - 'Block' or a 'Tx' message depending on the type of the object referenced by - the hash. Usually, 'GetData' messages are sent after a node receives an 'Inv' - message that contains unknown object hashes. --} + +-- | The 'GetData' type is used to retrieve information on a specific object +-- ('Block' or 'Tx') identified by the objects hash. The payload of a 'GetData' +-- request is a list of 'InvVector' which represent all the hashes of objects +-- that a node wants. The response to a 'GetBlock' message will be either a +-- 'Block' or a 'Tx' message depending on the type of the object referenced by +-- the hash. Usually, 'GetData' messages are sent after a node receives an 'Inv' +-- message that contains unknown object hashes. newtype GetData = GetData { -- | list of object hashes getDataList :: [InvVector] } deriving (Eq, Show, Generic, NFData) + instance Serial GetData where deserialize = GetData <$> (repList =<< deserialize) - where - repList (VarInt c) = replicateM (fromIntegral c) deserialize + where + repList (VarInt c) = replicateM (fromIntegral c) deserialize + serialize (GetData xs) = do putVarInt $ length xs forM_ xs serialize -instance Binary GetData where - get = deserialize - put = serialize -instance Serialize GetData where - get = deserialize - put = serialize - -{- | 'Inv' messages are used by nodes to advertise their knowledge of new - objects by publishing a list of hashes to a peer. 'Inv' messages can be sent - unsolicited or in response to a 'GetBlocks' message. --} +-- | 'Inv' messages are used by nodes to advertise their knowledge of new +-- objects by publishing a list of hashes to a peer. 'Inv' messages can be sent +-- unsolicited or in response to a 'GetBlocks' message. newtype Inv = Inv { -- | inventory invList :: [InvVector] } deriving (Eq, Show, Generic, NFData) + instance Serial Inv where deserialize = Inv <$> (repList =<< deserialize) - where - repList (VarInt c) = replicateM (fromIntegral c) deserialize + where + repList (VarInt c) = replicateM (fromIntegral c) deserialize + serialize (Inv xs) = do putVarInt $ length xs forM_ xs serialize -instance Binary Inv where - get = deserialize - put = serialize - -instance Serialize Inv where - get = deserialize - put = serialize -{- | Data type identifying the type of an inventory vector. SegWit types are - only used in 'GetData' messages, not 'Inv'. --} +-- | Data type identifying the type of an inventory vector. SegWit types are +-- only used in 'GetData' messages, not 'Inv'. data InvType = -- | error InvError @@ -199,20 +172,21 @@ data InvType InvType Word32 deriving (Eq, Show, Read, Generic, NFData) + instance Serial InvType where deserialize = go =<< getWord32le - where - go x = - case x of - 0 -> return InvError - 1 -> return InvTx - 2 -> return InvBlock - 3 -> return InvMerkleBlock - _ - | x == 1 `shiftL` 30 + 1 -> return InvWitnessTx - | x == 1 `shiftL` 30 + 2 -> return InvWitnessBlock - | x == 1 `shiftL` 30 + 3 -> return InvWitnessMerkleBlock - | otherwise -> return (InvType x) + where + go x = + case x of + 0 -> return InvError + 1 -> return InvTx + 2 -> return InvBlock + 3 -> return InvMerkleBlock + _ + | x == 1 `shiftL` 30 + 1 -> return InvWitnessTx + | x == 1 `shiftL` 30 + 2 -> return InvWitnessBlock + | x == 1 `shiftL` 30 + 3 -> return InvWitnessMerkleBlock + | otherwise -> return (InvType x) serialize x = putWord32le $ case x of @@ -225,18 +199,10 @@ instance Serial InvType where InvWitnessMerkleBlock -> 1 `shiftL` 30 + 3 InvType w -> w -instance Binary InvType where - get = deserialize - put = serialize -instance Serialize InvType where - get = deserialize - put = serialize - -{- | Invectory vectors represent hashes identifying objects such as a 'Block' or - a 'Tx'. They notify other peers about new data or data they have otherwise - requested. --} +-- | 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 @@ -245,38 +211,25 @@ data InvVector = InvVector } deriving (Eq, Show, Generic, NFData) + instance Serial InvVector where deserialize = InvVector <$> deserialize <*> deserialize serialize (InvVector t h) = serialize t >> serialize h -instance Binary InvVector where - get = deserialize - put = serialize - -instance Serialize InvVector where - get = deserialize - put = serialize newtype HostAddress = HostAddress ByteString deriving (Eq, Show, Ord, Generic, NFData) + instance Serial HostAddress where serialize (HostAddress bs) = putByteString bs deserialize = HostAddress <$> getByteString 18 -instance Binary HostAddress where - get = deserialize - put = serialize - -instance Serialize HostAddress where - get = deserialize - put = serialize -{- | Data type describing a bitcoin network address. Addresses are stored in - IPv6 format. IPv4 addresses are mapped to IPv6 using IPv4 mapped IPv6 - addresses: . --} +-- | Data type describing a bitcoin network address. Addresses are stored in +-- IPv6 format. IPv4 addresses are mapped to IPv6 using IPv4 mapped IPv6 +-- addresses: . data NetworkAddress = NetworkAddress { -- | bitmask of services available for this address naServices :: !Word64 @@ -285,15 +238,18 @@ data NetworkAddress = NetworkAddress } deriving (Eq, Show, Generic, NFData) + hostToSockAddr :: HostAddress -> SockAddr hostToSockAddr (HostAddress bs) = case runGetS getSockAddr bs of Left e -> error e Right x -> x + sockToHostAddress :: SockAddr -> HostAddress sockToHostAddress = HostAddress . runPutS . putSockAddr + putSockAddr :: MonadPut m => SockAddr -> m () putSockAddr (SockAddrInet6 p _ (a, b, c, d) _) = do putWord32be a @@ -309,6 +265,7 @@ putSockAddr (SockAddrInet p a) = do putWord16be (fromIntegral p) putSockAddr _ = error "Invalid address type" + getSockAddr :: MonadGet m => m SockAddr getSockAddr = do a <- getWord32be @@ -324,49 +281,36 @@ getSockAddr = do p <- getWord16be return $ SockAddrInet6 (fromIntegral p) 0 (a, b, c, d) 0 + instance Serial NetworkAddress where deserialize = NetworkAddress <$> getWord64le <*> deserialize serialize (NetworkAddress s a) = putWord64le s >> serialize a -instance Binary NetworkAddress where - get = deserialize - put = serialize - -instance Serialize NetworkAddress where - get = deserialize - put = serialize -{- | A 'NotFound' message is returned as a response to a 'GetData' message - whe one of the requested objects could not be retrieved. This could happen, - for example, if a tranasaction was requested and was not available in the - memory pool of the receiving node. --} +-- | A 'NotFound' message is returned as a response to a 'GetData' message +-- whe one of the requested objects could not be retrieved. This could happen, +-- for example, if a tranasaction was requested and was not available in the +-- memory pool of the receiving node. newtype NotFound = NotFound { -- | Inventory vectors related to this request notFoundList :: [InvVector] } deriving (Eq, Show, Generic, NFData) + instance Serial NotFound where deserialize = NotFound <$> (repList =<< deserialize) - where - repList (VarInt c) = replicateM (fromIntegral c) deserialize + where + repList (VarInt c) = replicateM (fromIntegral c) deserialize + serialize (NotFound xs) = do putVarInt $ length xs forM_ xs serialize -instance Binary NotFound where - get = deserialize - put = serialize -instance Serialize NotFound where - get = deserialize - put = serialize - -{- | A 'Ping' message is sent to bitcoin peers to check if a connection is still - open. --} +-- | 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. @@ -374,6 +318,7 @@ newtype Ping = Ping } deriving (Eq, Show, Read, Generic, NFData) + -- | A Pong message is sent as a response to a ping message. newtype Pong = Pong { -- | nonce from corresponding 'Ping' @@ -381,29 +326,16 @@ newtype Pong = Pong } deriving (Eq, Show, Read, Generic, NFData) + instance Serial Ping where deserialize = Ping <$> getWord64le serialize (Ping n) = putWord64le n + instance Serial Pong where deserialize = Pong <$> getWord64le serialize (Pong n) = putWord64le n -instance Binary Ping where - get = deserialize - put = serialize - -instance Binary Pong where - get = deserialize - put = serialize - -instance Serialize Ping where - get = deserialize - put = serialize - -instance Serialize Pong where - get = deserialize - put = serialize -- | The 'Reject' message is sent when messages are rejected by a peer. data Reject = Reject @@ -418,6 +350,7 @@ data Reject = Reject } deriving (Eq, Show, Read, Generic, NFData) + -- | Rejection code associated to the 'Reject' message. data RejectCode = RejectMalformed @@ -430,6 +363,7 @@ data RejectCode | RejectCheckpoint deriving (Eq, Show, Read, Generic, NFData) + instance Serial RejectCode where deserialize = getWord8 >>= \code -> case code of @@ -448,6 +382,7 @@ instance Serial RejectCode where , show code ] + serialize code = putWord8 $ case code of RejectMalformed -> 0x01 RejectInvalid -> 0x10 @@ -458,19 +393,13 @@ instance Serial RejectCode where RejectInsufficientFee -> 0x42 RejectCheckpoint -> 0x43 -instance Binary RejectCode where - put = serialize - get = deserialize - -instance Serialize RejectCode where - put = serialize - get = deserialize -- | Convenience function to build a 'Reject' message. reject :: MessageCommand -> RejectCode -> ByteString -> Reject reject cmd code reason = Reject cmd code (VarString reason) B.empty + instance Serial Reject where deserialize = deserialize >>= \(VarString bs) -> @@ -478,39 +407,33 @@ instance Serial Reject where <$> deserialize <*> deserialize <*> maybeData - where - maybeData = - isEmpty >>= \done -> - if done - then return B.empty - else getByteString 32 + where + maybeData = + isEmpty >>= \done -> + if done + then return B.empty + else getByteString 32 serialize (Reject cmd code reason dat) = do serialize $ VarString $ commandToString cmd serialize code serialize reason unless (B.null dat) $ putByteString dat -instance Binary Reject where - put = serialize - get = deserialize - -instance Serialize Reject where - put = serialize - get = deserialize -{- | Data type representing a variable-length integer. The 'VarInt' type - usually precedes an array or a string that can vary in length. --} +-- | Data type representing a variable-length integer. The 'VarInt' type +-- usually precedes an array or a string that can vary in length. newtype VarInt = VarInt {getVarInt :: Word64} deriving (Eq, Show, Read, Generic, NFData) + instance Serial VarInt where deserialize = VarInt <$> (getWord8 >>= go) - where - go 0xff = getWord64le - go 0xfe = fromIntegral <$> getWord32le - go 0xfd = fromIntegral <$> getWord16le - go x = return $ fromIntegral x + where + go 0xff = getWord64le + go 0xfe = fromIntegral <$> getWord32le + go 0xfd = fromIntegral <$> getWord16le + go x = return $ fromIntegral x + serialize (VarInt x) | x < 0xfd = @@ -525,42 +448,30 @@ instance Serial VarInt where putWord8 0xff putWord64le x -instance Binary VarInt where - put = serialize - get = deserialize - -instance Serialize VarInt where - put = serialize - get = deserialize putVarInt :: (MonadPut m, Integral a) => a -> m () putVarInt = serialize . VarInt . fromIntegral + -- | Data type for serialization of variable-length strings. newtype VarString = VarString {getVarString :: ByteString} deriving (Eq, Show, Read, Generic, NFData) + instance Serial VarString where deserialize = VarString <$> (readBS =<< deserialize) - where - readBS (VarInt len) = getByteString (fromIntegral len) + where + readBS (VarInt len) = getByteString (fromIntegral len) + serialize (VarString bs) = do putVarInt $ B.length bs putByteString bs -instance Binary VarString where - put = serialize - get = deserialize -instance Serialize VarString where - put = serialize - get = deserialize - -{- | When a bitcoin node creates an outgoing connection to another node, - the first message it will send is a 'Version' message. The other node - will similarly respond with it's own 'Version' message. --} +-- | 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 @@ -583,6 +494,7 @@ data Version = Version } deriving (Eq, Show, Generic, NFData) + instance Serial Version where deserialize = Version <$> getWord32le @@ -594,9 +506,10 @@ instance Serial Version where <*> deserialize <*> getWord32le <*> (go =<< isEmpty) - where - go True = return True - go False = getBool + where + go True = return True + go False = getBool + serialize (Version v s t ar as n ua sh r) = do putWord32le v @@ -609,31 +522,25 @@ instance Serial Version where putWord32le sh putBool r -instance Binary Version where - put = serialize - get = deserialize - -instance Serialize Version where - put = serialize - get = deserialize -- | 0x00 is 'False', anything else is 'True'. getBool :: MonadGet m => m Bool getBool = go =<< getWord8 - where - go 0 = return False - go _ = return True + where + go 0 = return False + go _ = return True + putBool :: MonadPut m => Bool -> m () putBool True = putWord8 1 putBool False = putWord8 0 -{- | A 'MessageCommand' is included in a 'MessageHeader' in order to identify - the type of message present in the payload. This allows the message - de-serialization code to know how to decode a particular message payload. - Every valid 'Message' constructor has a corresponding 'MessageCommand' - constructor. --} + +-- | 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 @@ -660,33 +567,30 @@ data MessageCommand | MCOther ByteString deriving (Eq, Generic, NFData) + instance Show MessageCommand where showsPrec _ = shows . commandToString + instance Read MessageCommand where readPrec = do String str <- lexP return (stringToCommand (cs str)) + instance Serial MessageCommand where deserialize = go <$> getByteString 12 - where - go bs = - let str = unpackCommand bs - in stringToCommand str + where + go bs = + let str = unpackCommand bs + in stringToCommand str serialize mc = putByteString $ packCommand $ commandToString mc -instance Binary MessageCommand where - put = serialize - get = deserialize - -instance Serialize MessageCommand where - put = serialize - get = deserialize instance IsString MessageCommand where fromString str = stringToCommand (cs str) + -- | Read a 'MessageCommand' from its string representation. stringToCommand :: ByteString -> MessageCommand stringToCommand str = case str of @@ -714,6 +618,7 @@ stringToCommand str = case str of "sendheaders" -> MCSendHeaders _ -> MCOther str + -- | Convert a 'MessageCommand' to its string representation. commandToString :: MessageCommand -> ByteString commandToString mc = case mc of @@ -741,36 +646,44 @@ commandToString mc = case mc of MCSendHeaders -> "sendheaders" MCOther c -> c + -- | Pack a string 'MessageCommand' so that it is exactly 12-bytes long. packCommand :: ByteString -> ByteString packCommand s = B.take 12 $ s `mappend` C.replicate 12 '\NUL' + -- | Undo packing done by 'packCommand'. unpackCommand :: ByteString -> ByteString unpackCommand = B.takeWhile (/= 0) + -- | Node offers no services. nodeNone :: Word64 nodeNone = 0 + -- | Services indicate node is a full node that can serve full blocks. nodeNetwork :: Word64 nodeNetwork = 1 + -- | Services indicate node allows to request 'UTXO' set. nodeGetUTXO :: Word64 nodeGetUTXO = 1 `shiftL` 1 + -- | Services indicate node accepts bloom filters. nodeBloom :: Word64 nodeBloom = 1 `shiftL` 2 + -- | Services indicate SegWit-capable node. nodeWitness :: Word64 nodeWitness = 1 `shiftL` 3 + -- | Services indicate Xtreme Thinblocks compatibility. nodeXThin :: Word64 nodeXThin = 1 `shiftL` 4 diff --git a/src/Haskoin/Network/Message.hs b/src/Haskoin/Network/Message.hs index ba26b84c..9ea81e8f 100644 --- a/src/Haskoin/Network/Message.hs +++ b/src/Haskoin/Network/Message.hs @@ -1,16 +1,15 @@ {-# LANGUAGE DeriveAnyClass #-} {-# LANGUAGE DeriveGeneric #-} -{- | -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 +--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 (..), @@ -22,13 +21,11 @@ module Haskoin.Network.Message ( import Control.DeepSeq import Control.Monad (unless) -import Data.Binary (Binary (..)) import Data.ByteString (ByteString) import qualified Data.ByteString as BS import Data.Bytes.Get import Data.Bytes.Put import Data.Bytes.Serial -import Data.Serialize (Serialize (..)) import Data.Word (Word32) import GHC.Generics (Generic) import Haskoin.Block.Common @@ -39,9 +36,9 @@ import Haskoin.Network.Bloom import Haskoin.Network.Common 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 @@ -54,6 +51,7 @@ data MessageHeader = MessageHeader } deriving (Eq, Show, Generic, NFData) + instance Serial MessageHeader where deserialize = MessageHeader @@ -62,27 +60,30 @@ instance Serial MessageHeader where <*> getWord32le <*> deserialize + serialize (MessageHeader m c l chk) = do putWord32be m serialize c putWord32le l serialize chk + instance Binary MessageHeader where put = serialize get = deserialize + instance Serialize MessageHeader where put = serialize get = deserialize -{- | The 'Message' type is used to identify all the valid messages that can be - sent between bitcoin peers. Only values of type 'Message' will be accepted - by other bitcoin peers as bitcoin protocol messages need to be correctly - serialized with message headers. Serializing a 'Message' value will - include the 'MessageHeader' with the correct checksum value automatically. - No need to add the 'MessageHeader' separately. --} + +-- | 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 @@ -109,6 +110,7 @@ data Message | MOther !ByteString !ByteString deriving (Eq, Show, Generic, NFData) + -- | Get 'MessageCommand' assocated with a message. msgType :: Message -> MessageCommand msgType (MVersion _) = MCVersion @@ -135,6 +137,7 @@ msgType MSendHeaders = MCSendHeaders msgType MGetAddr = MCGetAddr msgType (MOther c _) = MCOther c + -- | Deserializer for network messages. getMessage :: MonadGet m => Network -> m Message getMessage net = do @@ -185,6 +188,7 @@ getMessage net = do "get: command " ++ show cmd ++ " is expected to carry a payload" + -- | Serializer for network messages. putMessage :: MonadPut m => Network -> Message -> m () putMessage net msg = do diff --git a/src/Haskoin/Script/Common.hs b/src/Haskoin/Script/Common.hs index 9e3b0cb5..cd0f8624 100644 --- a/src/Haskoin/Script/Common.hs +++ b/src/Haskoin/Script/Common.hs @@ -2,16 +2,15 @@ {-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE OverloadedStrings #-} -{- | -Module : Haskoin.Script.Common -Copyright : No rights reserved -License : MIT -Maintainer : jprupp@protonmail.ch -Stability : experimental -Portability : POSIX - -Common script-related functions and data types. --} +-- | +--Module : Haskoin.Script.Common +--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 (..), @@ -25,7 +24,6 @@ module Haskoin.Script.Common ( import Control.DeepSeq import Control.Monad -import Data.Binary (Binary (..)) import Data.ByteString (ByteString) import qualified Data.ByteString as B import Data.Bytes.Get @@ -33,45 +31,39 @@ import Data.Bytes.Put import Data.Bytes.Serial import Data.Either (fromRight) import Data.Hashable -import Data.Serialize (Serialize (..)) import Data.Word (Word8) import GHC.Generics (Generic) -{- | 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] } deriving (Eq, Show, Read, Generic, Hashable, NFData) + instance Serial Script where deserialize = Script <$> getScriptOps - where - getScriptOps = do - empty <- isEmpty - if empty - then return [] - else (:) <$> deserialize <*> getScriptOps + where + getScriptOps = do + empty <- isEmpty + if empty + then return [] + else (:) <$> deserialize <*> getScriptOps - serialize (Script ops) = forM_ ops serialize -instance Binary Script where - put = serialize - get = deserialize + serialize (Script ops) = forM_ ops serialize -instance Serialize Script where - put = serialize - get = deserialize -- | Data type representing the type of an OP_PUSHDATA opcode. data PushDataType @@ -85,6 +77,7 @@ data PushDataType OPDATA4 deriving (Show, Read, Eq, Generic, Hashable, NFData) + -- | Data type representing an operator allowed inside a 'Script'. data ScriptOp = -- Pushing Data @@ -217,149 +210,151 @@ data ScriptOp | OP_INVALIDOPCODE !Word8 deriving (Show, Read, Eq, Generic, Hashable, NFData) + instance Serial ScriptOp where deserialize = go . fromIntegral =<< getWord8 - where - go op - | op == 0x00 = return OP_0 - | op <= 0x4b = do - payload <- getByteString (fromIntegral op) - return $ OP_PUSHDATA payload OPCODE - | op == 0x4c = do - len <- getWord8 - payload <- getByteString (fromIntegral len) - return $ OP_PUSHDATA payload OPDATA1 - | op == 0x4d = do - len <- getWord16le - payload <- getByteString (fromIntegral len) - return $ OP_PUSHDATA payload OPDATA2 - | op == 0x4e = do - len <- getWord32le - payload <- getByteString (fromIntegral len) - return $ OP_PUSHDATA payload OPDATA4 - | op == 0x4f = return OP_1NEGATE - | op == 0x50 = return OP_RESERVED - | op == 0x51 = return OP_1 - | op == 0x52 = return OP_2 - | op == 0x53 = return OP_3 - | op == 0x54 = return OP_4 - | op == 0x55 = return OP_5 - | op == 0x56 = return OP_6 - | op == 0x57 = return OP_7 - | op == 0x58 = return OP_8 - | op == 0x59 = return OP_9 - | op == 0x5a = return OP_10 - | op == 0x5b = return OP_11 - | op == 0x5c = return OP_12 - | op == 0x5d = return OP_13 - | op == 0x5e = return OP_14 - | op == 0x5f = return OP_15 - | op == 0x60 = return OP_16 - -- Flow control - | op == 0x61 = return OP_NOP - | op == 0x62 = return OP_VER -- reserved - | op == 0x63 = return OP_IF - | op == 0x64 = return OP_NOTIF - | op == 0x65 = return OP_VERIF -- reserved - | op == 0x66 = return OP_VERNOTIF -- reserved - | op == 0x67 = return OP_ELSE - | op == 0x68 = return OP_ENDIF - | op == 0x69 = return OP_VERIFY - | op == 0x6a = return OP_RETURN - -- Stack - | op == 0x6b = return OP_TOALTSTACK - | op == 0x6c = return OP_FROMALTSTACK - | op == 0x6d = return OP_2DROP - | op == 0x6e = return OP_2DUP - | op == 0x6f = return OP_3DUP - | op == 0x70 = return OP_2OVER - | op == 0x71 = return OP_2ROT - | op == 0x72 = return OP_2SWAP - | op == 0x73 = return OP_IFDUP - | op == 0x74 = return OP_DEPTH - | op == 0x75 = return OP_DROP - | op == 0x76 = return OP_DUP - | op == 0x77 = return OP_NIP - | op == 0x78 = return OP_OVER - | op == 0x79 = return OP_PICK - | op == 0x7a = return OP_ROLL - | op == 0x7b = return OP_ROT - | op == 0x7c = return OP_SWAP - | op == 0x7d = return OP_TUCK - -- Splice - | op == 0x7e = return OP_CAT - | op == 0x7f = return OP_SUBSTR - | op == 0x80 = return OP_LEFT - | op == 0x81 = return OP_RIGHT - | op == 0x82 = return OP_SIZE - -- Bitwise logic - | op == 0x83 = return OP_INVERT - | op == 0x84 = return OP_AND - | op == 0x85 = return OP_OR - | op == 0x86 = return OP_XOR - | op == 0x87 = return OP_EQUAL - | op == 0x88 = return OP_EQUALVERIFY - | op == 0x89 = return OP_RESERVED1 - | op == 0x8a = return OP_RESERVED2 - -- Arithmetic - | op == 0x8b = return OP_1ADD - | op == 0x8c = return OP_1SUB - | op == 0x8d = return OP_2MUL - | op == 0x8e = return OP_2DIV - | op == 0x8f = return OP_NEGATE - | op == 0x90 = return OP_ABS - | op == 0x91 = return OP_NOT - | op == 0x92 = return OP_0NOTEQUAL - | op == 0x93 = return OP_ADD - | op == 0x94 = return OP_SUB - | op == 0x95 = return OP_MUL - | op == 0x96 = return OP_DIV - | op == 0x97 = return OP_MOD - | op == 0x98 = return OP_LSHIFT - | op == 0x99 = return OP_RSHIFT - | op == 0x9a = return OP_BOOLAND - | op == 0x9b = return OP_BOOLOR - | op == 0x9c = return OP_NUMEQUAL - | op == 0x9d = return OP_NUMEQUALVERIFY - | op == 0x9e = return OP_NUMNOTEQUAL - | op == 0x9f = return OP_LESSTHAN - | op == 0xa0 = return OP_GREATERTHAN - | op == 0xa1 = return OP_LESSTHANOREQUAL - | op == 0xa2 = return OP_GREATERTHANOREQUAL - | op == 0xa3 = return OP_MIN - | op == 0xa4 = return OP_MAX - | op == 0xa5 = return OP_WITHIN - -- Crypto - | op == 0xa6 = return OP_RIPEMD160 - | op == 0xa7 = return OP_SHA1 - | op == 0xa8 = return OP_SHA256 - | op == 0xa9 = return OP_HASH160 - | op == 0xaa = return OP_HASH256 - | op == 0xab = return OP_CODESEPARATOR - | op == 0xac = return OP_CHECKSIG - | op == 0xad = return OP_CHECKSIGVERIFY - | op == 0xae = return OP_CHECKMULTISIG - | op == 0xaf = return OP_CHECKMULTISIGVERIFY - -- More NOPs - | op == 0xb0 = return OP_NOP1 - | op == 0xb1 = return OP_CHECKLOCKTIMEVERIFY - | op == 0xb2 = return OP_CHECKSEQUENCEVERIFY - | op == 0xb3 = return OP_NOP4 - | op == 0xb4 = return OP_NOP5 - | op == 0xb5 = return OP_NOP6 - | op == 0xb6 = return OP_NOP7 - | op == 0xb7 = return OP_NOP8 - | op == 0xb8 = return OP_NOP9 - | op == 0xb9 = return OP_NOP10 - -- Bitcoin Cash Nov 2018 hard fork - | op == 0xba = return OP_CHECKDATASIG - | op == 0xbb = return OP_CHECKDATASIGVERIFY - -- Bitcoin Cash May 2020 hard fork - | op == 0xbc = return OP_REVERSEBYTES - -- Constants - | op == 0xfd = return OP_PUBKEYHASH - | op == 0xfe = return OP_PUBKEY - | otherwise = return $ OP_INVALIDOPCODE op + where + go op + | op == 0x00 = return OP_0 + | op <= 0x4b = do + payload <- getByteString (fromIntegral op) + return $ OP_PUSHDATA payload OPCODE + | op == 0x4c = do + len <- getWord8 + payload <- getByteString (fromIntegral len) + return $ OP_PUSHDATA payload OPDATA1 + | op == 0x4d = do + len <- getWord16le + payload <- getByteString (fromIntegral len) + return $ OP_PUSHDATA payload OPDATA2 + | op == 0x4e = do + len <- getWord32le + payload <- getByteString (fromIntegral len) + return $ OP_PUSHDATA payload OPDATA4 + | op == 0x4f = return OP_1NEGATE + | op == 0x50 = return OP_RESERVED + | op == 0x51 = return OP_1 + | op == 0x52 = return OP_2 + | op == 0x53 = return OP_3 + | op == 0x54 = return OP_4 + | op == 0x55 = return OP_5 + | op == 0x56 = return OP_6 + | op == 0x57 = return OP_7 + | op == 0x58 = return OP_8 + | op == 0x59 = return OP_9 + | op == 0x5a = return OP_10 + | op == 0x5b = return OP_11 + | op == 0x5c = return OP_12 + | op == 0x5d = return OP_13 + | op == 0x5e = return OP_14 + | op == 0x5f = return OP_15 + | op == 0x60 = return OP_16 + -- Flow control + | op == 0x61 = return OP_NOP + | op == 0x62 = return OP_VER -- reserved + | op == 0x63 = return OP_IF + | op == 0x64 = return OP_NOTIF + | op == 0x65 = return OP_VERIF -- reserved + | op == 0x66 = return OP_VERNOTIF -- reserved + | op == 0x67 = return OP_ELSE + | op == 0x68 = return OP_ENDIF + | op == 0x69 = return OP_VERIFY + | op == 0x6a = return OP_RETURN + -- Stack + | op == 0x6b = return OP_TOALTSTACK + | op == 0x6c = return OP_FROMALTSTACK + | op == 0x6d = return OP_2DROP + | op == 0x6e = return OP_2DUP + | op == 0x6f = return OP_3DUP + | op == 0x70 = return OP_2OVER + | op == 0x71 = return OP_2ROT + | op == 0x72 = return OP_2SWAP + | op == 0x73 = return OP_IFDUP + | op == 0x74 = return OP_DEPTH + | op == 0x75 = return OP_DROP + | op == 0x76 = return OP_DUP + | op == 0x77 = return OP_NIP + | op == 0x78 = return OP_OVER + | op == 0x79 = return OP_PICK + | op == 0x7a = return OP_ROLL + | op == 0x7b = return OP_ROT + | op == 0x7c = return OP_SWAP + | op == 0x7d = return OP_TUCK + -- Splice + | op == 0x7e = return OP_CAT + | op == 0x7f = return OP_SUBSTR + | op == 0x80 = return OP_LEFT + | op == 0x81 = return OP_RIGHT + | op == 0x82 = return OP_SIZE + -- Bitwise logic + | op == 0x83 = return OP_INVERT + | op == 0x84 = return OP_AND + | op == 0x85 = return OP_OR + | op == 0x86 = return OP_XOR + | op == 0x87 = return OP_EQUAL + | op == 0x88 = return OP_EQUALVERIFY + | op == 0x89 = return OP_RESERVED1 + | op == 0x8a = return OP_RESERVED2 + -- Arithmetic + | op == 0x8b = return OP_1ADD + | op == 0x8c = return OP_1SUB + | op == 0x8d = return OP_2MUL + | op == 0x8e = return OP_2DIV + | op == 0x8f = return OP_NEGATE + | op == 0x90 = return OP_ABS + | op == 0x91 = return OP_NOT + | op == 0x92 = return OP_0NOTEQUAL + | op == 0x93 = return OP_ADD + | op == 0x94 = return OP_SUB + | op == 0x95 = return OP_MUL + | op == 0x96 = return OP_DIV + | op == 0x97 = return OP_MOD + | op == 0x98 = return OP_LSHIFT + | op == 0x99 = return OP_RSHIFT + | op == 0x9a = return OP_BOOLAND + | op == 0x9b = return OP_BOOLOR + | op == 0x9c = return OP_NUMEQUAL + | op == 0x9d = return OP_NUMEQUALVERIFY + | op == 0x9e = return OP_NUMNOTEQUAL + | op == 0x9f = return OP_LESSTHAN + | op == 0xa0 = return OP_GREATERTHAN + | op == 0xa1 = return OP_LESSTHANOREQUAL + | op == 0xa2 = return OP_GREATERTHANOREQUAL + | op == 0xa3 = return OP_MIN + | op == 0xa4 = return OP_MAX + | op == 0xa5 = return OP_WITHIN + -- Crypto + | op == 0xa6 = return OP_RIPEMD160 + | op == 0xa7 = return OP_SHA1 + | op == 0xa8 = return OP_SHA256 + | op == 0xa9 = return OP_HASH160 + | op == 0xaa = return OP_HASH256 + | op == 0xab = return OP_CODESEPARATOR + | op == 0xac = return OP_CHECKSIG + | op == 0xad = return OP_CHECKSIGVERIFY + | op == 0xae = return OP_CHECKMULTISIG + | op == 0xaf = return OP_CHECKMULTISIGVERIFY + -- More NOPs + | op == 0xb0 = return OP_NOP1 + | op == 0xb1 = return OP_CHECKLOCKTIMEVERIFY + | op == 0xb2 = return OP_CHECKSEQUENCEVERIFY + | op == 0xb3 = return OP_NOP4 + | op == 0xb4 = return OP_NOP5 + | op == 0xb5 = return OP_NOP6 + | op == 0xb6 = return OP_NOP7 + | op == 0xb7 = return OP_NOP8 + | op == 0xb8 = return OP_NOP9 + | op == 0xb9 = return OP_NOP10 + -- Bitcoin Cash Nov 2018 hard fork + | op == 0xba = return OP_CHECKDATASIG + | op == 0xbb = return OP_CHECKDATASIGVERIFY + -- Bitcoin Cash May 2020 hard fork + | op == 0xbc = return OP_REVERSEBYTES + -- Constants + | op == 0xfd = return OP_PUBKEYHASH + | op == 0xfe = return OP_PUBKEY + | otherwise = return $ OP_INVALIDOPCODE op + serialize op = case op of (OP_PUSHDATA payload optype) -> do @@ -513,13 +508,6 @@ instance Serial ScriptOp where -- Bitcoin Cash May 2020 hard fork OP_REVERSEBYTES -> putWord8 0xbc -instance Binary ScriptOp where - put = serialize - get = deserialize - -instance Serialize ScriptOp where - put = serialize - get = deserialize -- | Check whether opcode is only data. isPushOp :: ScriptOp -> Bool @@ -545,6 +533,7 @@ isPushOp op = case op of OP_16 -> True _ -> False + -- | Optimally encode data using one of the 4 types of data pushing opcodes. opPushData :: ByteString -> ScriptOp opPushData bs @@ -553,29 +542,30 @@ opPushData bs | len <= 0xffff = OP_PUSHDATA bs OPDATA2 | len <= 0xffffffff = OP_PUSHDATA bs OPDATA4 | otherwise = error "opPushData: payload size too big" - where - len = B.length bs + where + len = B.length bs + -- | Transforms integers @[1 .. 16]@ to 'ScriptOp' @[OP_1 .. OP_16]@. intToScriptOp :: Int -> ScriptOp intToScriptOp i | i `elem` [1 .. 16] = op | otherwise = err - where - op = - fromRight err - . runGetS deserialize - . B.singleton - . fromIntegral - $ i + 0x50 - err = error $ "intToScriptOp: Invalid integer " ++ show i + where + op = + fromRight err + . runGetS deserialize + . B.singleton + . fromIntegral + $ i + 0x50 + err = error $ "intToScriptOp: Invalid integer " ++ show i + -{- | Decode 'ScriptOp' @[OP_1 .. OP_16]@ to integers @[1 .. 16]@. This functions - fails for other values of 'ScriptOp' --} +-- | Decode 'ScriptOp' @[OP_1 .. OP_16]@ to integers @[1 .. 16]@. This functions +-- fails for other values of 'ScriptOp' scriptOpToInt :: ScriptOp -> Either String Int scriptOpToInt s | res `elem` [1 .. 16] = return res | otherwise = Left $ "scriptOpToInt: invalid opcode " ++ show s - where - res = fromIntegral (B.head $ runPutS $ serialize s) - 0x50 + where + res = fromIntegral (B.head $ runPutS $ serialize s) - 0x50