bitcoin-tx (empty) → 0.9.0
raw patch · 9 files changed
+343/−0 lines, 9 filesdep +basedep +base16-bytestringdep +binarysetup-changed
Dependencies added: base, base16-bytestring, binary, bitcoin-script, bitcoin-tx, bytestring, hspec
Files
- LICENSE +22/−0
- README.md +11/−0
- Setup.hs +3/−0
- bitcoin-tx.cabal +60/−0
- src/Bitcoin/Transaction.hs +19/−0
- src/Bitcoin/Transaction/Types.hs +196/−0
- test/Bitcoin/TransactionSpec.hs +23/−0
- test/Main.hs +8/−0
- test/Spec.hs +1/−0
+ LICENSE view
@@ -0,0 +1,22 @@+The MIT License (MIT) + +Copyright (c) 2015 Leon Mergen + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +
+ README.md view
@@ -0,0 +1,11 @@+haskell-bitcoin-tx +================== + +[](https://travis-ci.org/solatis/haskell-bitcoin-tx) +[](https://coveralls.io/r/solatis/haskell-bitcoin-tx?branch=master) +[](http://en.wikipedia.org/wiki/MIT_License) +[](http://haskell.org) + +This library provides the same functionality as the bitcoin-tx command line +utility, which was introduced in Bitcoin Core v0.10. These functions are +pure and require no communication with a bitcoin daemon.
+ Setup.hs view
@@ -0,0 +1,3 @@+import Distribution.Simple + +main = defaultMain
+ bitcoin-tx.cabal view
@@ -0,0 +1,60 @@+name: bitcoin-tx +category: Network, Finance +version: 0.9.0 +license: MIT +license-file: LICENSE +copyright: (c) 2015 Leon Mergen +author: Leon Mergen +maintainer: leon@solatis.com +homepage: http://www.leonmergen.com/opensource.html +bug-reports: http://github.com/solatis/haskell-bitcoin-tx/issues +stability: experimental +synopsis: Utility functions for manipulating bitcoin transactions +description: + This library provides the same functionality as the bitcoin-tx command line + utility, which was introduced in Bitcoin Core v0.10. These functions are + pure and require no communication with a bitcoin daemon. + +build-type: Simple +data-files: LICENSE, README.md +cabal-version: >= 1.10 +tested-with: GHC == 7.6, GHC == 7.8, GHC == 7.10 + +library + hs-source-dirs: src + ghc-options: -Wall -ferror-spans + default-language: Haskell2010 + + exposed-modules: Bitcoin.Transaction + Bitcoin.Transaction.Types + + build-depends: base >= 4.3 && < 5 + , bytestring + , base16-bytestring + , binary + + , bitcoin-script + +test-suite test-suite + type: exitcode-stdio-1.0 + ghc-options: -Wall -ferror-spans -threaded -auto-all -caf-all -fno-warn-type-defaults + default-language: Haskell2010 + hs-source-dirs: test + main-is: Main.hs + + other-modules: Bitcoin.TransactionSpec + Spec + Main + + build-depends: base >= 4.3 && < 5 + , hspec + + , bytestring + + , bitcoin-tx + , bitcoin-script + +source-repository head + type: git + location: git://github.com/solatis/haskell-bitcoin-tx.git + branch: master
+ src/Bitcoin/Transaction.hs view
@@ -0,0 +1,19 @@+module Bitcoin.Transaction where + +import qualified Data.ByteString.Lazy as BSL +import qualified Data.ByteString.Base16.Lazy as BS16L + +import qualified Data.Binary as B ( decode + , encode ) + +import Bitcoin.Transaction.Types + +-- | Decodes a hex representation of a transaction into a 'Transaction' object. +decode :: BSL.ByteString -> Transaction +decode = + B.decode . fst . BS16L.decode + +-- | Encodes a 'Transaction' object into a hex representation. +encode :: Transaction -> BSL.ByteString +encode = + BS16L.encode . B.encode
+ src/Bitcoin/Transaction/Types.hs view
@@ -0,0 +1,196 @@+{-# LANGUAGE FlexibleInstances #-} + +module Bitcoin.Transaction.Types where + +import Control.Applicative ((<$>),(<*>)) +import Control.Monad (liftM2, replicateM, forM_) + +import Data.Word ( Word32 + , Word64 ) + +import qualified Data.ByteString as BS +import qualified Data.ByteString.Lazy as BSL + +import Data.Bits (shiftL, shiftR) +import Data.Binary ( Binary, get, put, encode, decode ) + +import Data.Binary.Get ( getByteString + , getWord8 + , getWord16le + , getWord32le + , getWord64le + , getWord64be ) + +import Data.Binary.Put ( putByteString + , putWord8 + , putWord16le + , putWord32le + , putWord64le + , putWord64be ) + +import qualified Bitcoin.Script as Btc ( Script (..) ) + +-- | 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) + +instance Binary VarInt where + + get = VarInt <$> ( getWord8 >>= go ) + where + go 0xff = getWord64le + go 0xfe = fromIntegral <$> getWord32le + go 0xfd = fromIntegral <$> getWord16le + go x = fromIntegral <$> return x + + put (VarInt x) + | x < 0xfd = + putWord8 $ fromIntegral x + | x <= 0xffff = do + putWord8 0xfd + putWord16le $ fromIntegral x + | x <= 0xffffffff = do + putWord8 0xfe + putWord32le $ fromIntegral x + | otherwise = do + putWord8 0xff + putWord64le x + +data TxnOutputType = TxnPubKey -- ^ JSON of "pubkey" received. + | TxnPubKeyHash -- ^ JSON of "pubkeyhash" received. + | TxnScriptHash -- ^ JSON of "scripthash" received. + | TxnMultisig -- ^ JSON of "multisig" received. + deriving ( Show, Read, Ord, Eq ) + + +data TransactionHash = TransactionHash Integer + deriving ( Show, Read, Eq ) + +instance Binary TransactionHash where + get = do + a <- fromIntegral <$> getWord64be + b <- fromIntegral <$> getWord64be + c <- fromIntegral <$> getWord64be + d <- fromIntegral <$> getWord64be + + return $ TransactionHash ((a `shiftL` 192) + (b `shiftL` 128) + (c `shiftL` 64) + d) + + put (TransactionHash i) = do + putWord64be $ fromIntegral (i `shiftR` 192) + putWord64be $ fromIntegral (i `shiftR` 128) + putWord64be $ fromIntegral (i `shiftR` 64) + putWord64be $ fromIntegral i + +-- | The OutPoint is used inside a transaction input to reference the previous +-- transaction output that it is spending. +data OutPoint = OutPoint { + -- | The hash of the referenced transaction. + outPointHash :: TransactionHash, + + -- | The position of the specific output in the transaction. + -- The first output position is 0. + outPointIndex :: !Word32 + } deriving (Read, Show, Eq) + +instance Binary OutPoint where + get = do + (h,i) <- liftM2 (,) get getWord32le + return $ OutPoint h i + + put (OutPoint h i) = put h >> putWord32le i + +-- | Data type representing a transaction input. +data TransactionIn = TransactionIn { + -- | Reference the previous transaction output (hash + position) + prevOutput :: OutPoint, + + -- | Script providing the requirements of the previous transaction + -- output to spend those coins. + scriptInput :: Btc.Script, + + -- | Transaction version as defined by the sender of the + -- transaction. The intended use is for replacing transactions with + -- new information before the transaction is included in a block. + txInSequence :: Word32 + + } deriving (Eq, Show, Read) + +instance Binary TransactionIn where + get = do + o <- get + (VarInt len) <- get + scriptBs <- getByteString (fromIntegral len) + s <- getWord32le + + let i = decode $ BSL.fromStrict scriptBs + + return $ TransactionIn o i s + + put (TransactionIn o i s) = do + let scriptBs = BSL.toStrict $ encode i + + put o + put $ VarInt $ fromIntegral $ BS.length scriptBs + putByteString scriptBs + putWord32le s + + +-- | Data type representing a transaction output. +data TransactionOut = TransactionOut { + -- | Transaction output value. + outValue :: Word64, + + -- | Script specifying the conditions to spend this output. + scriptOutput :: Btc.Script + + } deriving (Eq, Show, Read) + +instance Binary TransactionOut where + get = do + val <- getWord64le + (VarInt len) <- get + + scriptBs <- getByteString (fromIntegral len) + let s = decode $ BSL.fromStrict scriptBs + + return $ TransactionOut val s + + put (TransactionOut o s) = do + let scriptBs = BSL.toStrict $ encode s + + putWord64le o + put $ VarInt $ fromIntegral $ BS.length scriptBs + putByteString scriptBs + +-- | Data type representing a bitcoin transaction +data Transaction = Transaction { + -- | Transaction data format version + txVersion :: Word32, + + -- | List of transaction inputs + txIn :: [TransactionIn], + + -- | List of transaction outputs + txOut :: [TransactionOut], + + -- | The block number of timestamp at which this transaction is locked + txLockTime :: Word32 + + } deriving (Eq, Show, Read) + +instance Binary Transaction where + get = Transaction <$> getWord32le + <*> (replicateList =<< get) + <*> (replicateList =<< get) + <*> getWord32le + where + replicateList (VarInt c) = replicateM (fromIntegral c) get + + put (Transaction v is os l) = do + putWord32le v + put $ VarInt $ fromIntegral $ length is + forM_ is put + put $ VarInt $ fromIntegral $ length os + forM_ os put + putWord32le l
+ test/Bitcoin/TransactionSpec.hs view
@@ -0,0 +1,23 @@+module Bitcoin.TransactionSpec where + +import Bitcoin.Script (Script (..)) +import Bitcoin.Transaction (decode, encode) +import Bitcoin.Transaction.Types +import qualified Data.ByteString.Lazy.Char8 as BSL8 (pack) + +import Test.Hspec + +spec :: Spec +spec = do + describe "when parsing a specific transaction" $ do + let hex = BSL8.pack "0100000002f327e86da3e66bd20e1129b1fb36d07056f0b9a117199e759396526b8f3a20780000000049483045022100fce442ec52aa2792efc27fd3ad0eaf7fa69f097fdcefab017ea56d1799b10b2102207a6ae3eb61e11ffaba0453f173d1792f1b7bb8e7422ea945101d68535c4b474801fffffffff0ede03d75050f20801d50358829ae02c058e8677d2cc74df51f738285013c26000000006b483045022100b77f935ff366a6f3c2fdeb83589c790265d43b3d2cf5e5f0047da56c36de75f40220707ceda75d8dcf2ccaebc506f7293c3dcb910554560763d7659fb202f8ec324b012102240d7d3c7aad57b68aa0178f4c56f997d1bfab2ded3c2f9427686017c603a6d6ffffffff02f028d6dc010000001976a914ffb035781c3c69e076d48b60c3d38592e7ce06a788ac00ca9a3b000000001976a914fa5139067622fd7e1e722a05c17c2bb7d5fd6df088ac00000000" + + it "encoding a decoding a transaction results in the original hex" $ + (encode . decode) hex `shouldBe` hex + + it "succesfully parses a transaction into a meaningful object" $ do + let decoded = decode hex + + case decoded of + (Transaction 1 [(TransactionIn _ _ 4294967295), (TransactionIn _ _ 4294967295)] [(TransactionOut 7999990000 (Script _)), (TransactionOut 1000000000 (Script _))] 0) -> return () + _ -> expectationFailure ("Result does not match expected: " ++ show decoded)
+ test/Main.hs view
@@ -0,0 +1,8 @@+module Main where + +import Test.Hspec.Runner +import qualified Spec + +main :: IO () +main = + hspecWith defaultConfig Spec.spec
+ test/Spec.hs view
@@ -0,0 +1,1 @@+{-# OPTIONS_GHC -F -pgmF hspec-discover -optF --module-name=Spec #-}