bitcoin-script (empty) → 0.9.0
raw patch · 9 files changed
+675/−0 lines, 9 filesdep +basedep +base16-bytestringdep +binarysetup-changed
Dependencies added: base, base16-bytestring, binary, bitcoin-script, bytestring, hspec, hspec-expectations, text
Files
- LICENSE +22/−0
- README.md +10/−0
- Setup.hs +3/−0
- bitcoin-script.cabal +61/−0
- src/Bitcoin/Script.hs +17/−0
- src/Bitcoin/Script/Types.hs +528/−0
- test/Bitcoin/ScriptSpec.hs +25/−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,10 @@+haskell-bitcoin-script +================== + +[](https://travis-ci.org/solatis/haskell-bitcoin-script) +[](https://coveralls.io/r/solatis/haskell-bitcoin-script?branch=master) +[](http://en.wikipedia.org/wiki/MIT_License) +[](http://haskell.org) + +This library provides utilities for compiling, manipulation and decompiling of +Bitcoin scripts.
+ Setup.hs view
@@ -0,0 +1,3 @@+import Distribution.Simple + +main = defaultMain
+ bitcoin-script.cabal view
@@ -0,0 +1,61 @@+name: bitcoin-script +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://github.com/solatis/haskell-bitcoin-script +bug-reports: http://github.com/solatis/haskell-bitcoin-script/issues +stability: experimental +synopsis: Compilation, manipulation and decompilation of Bitcoin scripts +description: + Provides pure functions to compile, decompile and manipulate Bitcoin scripts. + This project relies heavily on the Haskoin project for the compiling and + decompiling of the script assembly, and continuously merges changes downstream. + + The advantage this library has over Haskoin is that it uses very few + dependencies and doesn't rely on external libraries such as LevelDB and snappy. + +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.Script + Bitcoin.Script.Types + + build-depends: base >= 4.3 && < 5 + , text + , bytestring + , base16-bytestring + , binary + +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.ScriptSpec + Spec + Main + + build-depends: base >= 4.3 && < 5 + , hspec + , hspec-expectations + + , bytestring + , bitcoin-script + +source-repository head + type: git + location: git://github.com/solatis/haskell-bitcoin-script.git + branch: master
+ src/Bitcoin/Script.hs view
@@ -0,0 +1,17 @@+module Bitcoin.Script where + +import qualified Data.Binary as B +import qualified Data.ByteString.Base16.Lazy as BS16L +import qualified Data.ByteString.Lazy as BSL + +import Bitcoin.Script.Types + +-- | Decodes a hex representation of a script into a 'Script' object. +decode :: BSL.ByteString -> Script +decode = + B.decode . fst . BS16L.decode + +-- | Encodes a 'Script' object into a hex representation +encode :: Script -> BSL.ByteString +encode = + BS16L.encode . B.encode
+ src/Bitcoin/Script/Types.hs view
@@ -0,0 +1,528 @@+module Bitcoin.Script.Types +( ScriptOp(..) +, Script(..) +, PushDataType(..) +, isPushOp +, opPushData +) where + +import Control.Monad (liftM2, unless, forM_) +import Control.Applicative ((<$>)) + +import Data.Word (Word8) +import Data.Binary (Binary, get, put) +import Data.Binary.Get + ( isEmpty + , getWord8 + , getWord16le + , getWord32le + , getByteString + ) +import Data.Binary.Put + ( putWord8 + , putWord16le + , putWord32le + , putByteString + ) +import qualified Data.ByteString as BS + ( ByteString + , length + ) + +-- | 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 the spending signatures in the input of a transaction +data Script = + Script { + -- | List of script operators defining this script + scriptOps :: ![ScriptOp] + } + deriving (Eq, Show, Read) + +instance Binary Script where + get = + Script <$> getScriptOps + where + getScriptOps = do + empty <- isEmpty + if empty + then return [] + else liftM2 (:) get getScriptOps + + put (Script ops) = forM_ ops put + +-- | Data type representing the type of an OP_PUSHDATA opcode. +data PushDataType + = + -- | The next opcode bytes is data to be pushed onto the stack + OPCODE + -- | The next byte contains the number of bytes to be pushed onto + -- the stack + | OPDATA1 + -- | The next two bytes contains the number of bytes to be pushed onto + -- the stack + | OPDATA2 + -- | The next four bytes contains the number of bytes to be pushed onto + -- the stack + | OPDATA4 + deriving (Show, Read, Eq) + +-- | Data type representing all of the operators allowed inside a 'Script'. +data ScriptOp + -- Pushing Data + = OP_PUSHDATA !BS.ByteString !PushDataType + | OP_0 + | OP_1NEGATE + | OP_RESERVED + | OP_1 | OP_2 | OP_3 | OP_4 + | OP_5 | OP_6 | OP_7 | OP_8 + | OP_9 | OP_10 | OP_11 | OP_12 + | OP_13 | OP_14 | OP_15 | OP_16 + + -- Flow control + | OP_NOP + | OP_VER -- reserved + | OP_IF + | OP_NOTIF + | OP_VERIF -- resreved + | OP_VERNOTIF -- reserved + | OP_ELSE + | OP_ENDIF + | OP_VERIFY + | OP_RETURN + + -- Stack operations + | OP_TOALTSTACK + | OP_FROMALTSTACK + | OP_IFDUP + | OP_DEPTH + | OP_DROP + | OP_DUP + | OP_NIP + | OP_OVER + | OP_PICK + | OP_ROLL + | OP_ROT + | OP_SWAP + | OP_TUCK + | OP_2DROP + | OP_2DUP + | OP_3DUP + | OP_2OVER + | OP_2ROT + | OP_2SWAP + + -- Splice + | OP_CAT + | OP_SUBSTR + | OP_LEFT + | OP_RIGHT + | OP_SIZE + + -- Bitwise logic + | OP_INVERT + | OP_AND + | OP_OR + | OP_XOR + | OP_EQUAL + | OP_EQUALVERIFY + | OP_RESERVED1 + | OP_RESERVED2 + + -- Arithmetic + | OP_1ADD + | OP_1SUB + | OP_2MUL + | OP_2DIV + | OP_NEGATE + | OP_ABS + | OP_NOT + | OP_0NOTEQUAL + | OP_ADD + | OP_SUB + | OP_MUL + | OP_DIV + | OP_MOD + | OP_LSHIFT + | OP_RSHIFT + | OP_BOOLAND + | OP_BOOLOR + | OP_NUMEQUAL + | OP_NUMEQUALVERIFY + | OP_NUMNOTEQUAL + | OP_LESSTHAN + | OP_GREATERTHAN + | OP_LESSTHANOREQUAL + | OP_GREATERTHANOREQUAL + | OP_MIN + | OP_MAX + | OP_WITHIN + + -- Crypto + | OP_RIPEMD160 + | OP_SHA1 + | OP_SHA256 + | OP_HASH160 + | OP_HASH256 + | OP_CODESEPARATOR + | OP_CHECKSIG + | OP_CHECKSIGVERIFY + | OP_CHECKMULTISIG + | OP_CHECKMULTISIGVERIFY + + -- Expansion + | OP_NOP1 | OP_NOP2 | OP_NOP3 | OP_NOP4 | OP_NOP5 + | OP_NOP6 | OP_NOP7 | OP_NOP8 | OP_NOP9 | OP_NOP10 + + + -- Other + | OP_PUBKEYHASH + | OP_PUBKEY + | OP_INVALIDOPCODE !Word8 + + deriving (Show, Read, Eq) + +instance Binary ScriptOp where + + get = go =<< (fromIntegral <$> getWord8) + where + go op + | op == 0x00 = return OP_0 + | op <= 0x4b = do + payload <- getByteString (fromIntegral op) + return $ OP_PUSHDATA payload OPCODE + | op == 0x4c = do + len <- getWord8 + payload <- getByteString (fromIntegral len) + return $ OP_PUSHDATA payload OPDATA1 + | op == 0x4d = do + len <- getWord16le + payload <- getByteString (fromIntegral len) + return $ OP_PUSHDATA payload OPDATA2 + | op == 0x4e = do + len <- getWord32le + payload <- getByteString (fromIntegral len) + return $ OP_PUSHDATA payload OPDATA4 + + | op == 0x4f = return OP_1NEGATE + | op == 0x50 = return OP_RESERVED + | op == 0x51 = return OP_1 + | op == 0x52 = return OP_2 + | op == 0x53 = return OP_3 + | op == 0x54 = return OP_4 + | op == 0x55 = return OP_5 + | op == 0x56 = return OP_6 + | op == 0x57 = return OP_7 + | op == 0x58 = return OP_8 + | op == 0x59 = return OP_9 + | op == 0x5a = return OP_10 + | op == 0x5b = return OP_11 + | op == 0x5c = return OP_12 + | op == 0x5d = return OP_13 + | op == 0x5e = return OP_14 + | op == 0x5f = return OP_15 + | op == 0x60 = return OP_16 + -- Flow control + | op == 0x61 = return OP_NOP + | op == 0x62 = return OP_VER -- reserved + | op == 0x63 = return OP_IF + | op == 0x64 = return OP_NOTIF + | op == 0x65 = return OP_VERIF -- reserved + | op == 0x66 = return OP_VERNOTIF -- reserved + | op == 0x67 = return OP_ELSE + | op == 0x68 = return OP_ENDIF + | op == 0x69 = return OP_VERIFY + | op == 0x6a = return OP_RETURN + + -- Stack + | op == 0x6b = return OP_TOALTSTACK + | op == 0x6c = return OP_FROMALTSTACK + | op == 0x6d = return OP_2DROP + | op == 0x6e = return OP_2DUP + | op == 0x6f = return OP_3DUP + | op == 0x70 = return OP_2OVER + | op == 0x71 = return OP_2ROT + | op == 0x72 = return OP_2SWAP + | op == 0x73 = return OP_IFDUP + | op == 0x74 = return OP_DEPTH + | op == 0x75 = return OP_DROP + | op == 0x76 = return OP_DUP + | op == 0x77 = return OP_NIP + | op == 0x78 = return OP_OVER + | op == 0x79 = return OP_PICK + | op == 0x7a = return OP_ROLL + | op == 0x7b = return OP_ROT + | op == 0x7c = return OP_SWAP + | op == 0x7d = return OP_TUCK + + -- Splice + | op == 0x7e = return OP_CAT + | op == 0x7f = return OP_SUBSTR + | op == 0x80 = return OP_LEFT + | op == 0x81 = return OP_RIGHT + | op == 0x82 = return OP_SIZE + + -- Bitwise logic + | op == 0x83 = return OP_INVERT + | op == 0x84 = return OP_AND + | op == 0x85 = return OP_OR + | op == 0x86 = return OP_XOR + | op == 0x87 = return OP_EQUAL + | op == 0x88 = return OP_EQUALVERIFY + | op == 0x89 = return OP_RESERVED1 + | op == 0x8a = return OP_RESERVED2 + + -- Arithmetic + | op == 0x8b = return OP_1ADD + | op == 0x8c = return OP_1SUB + | op == 0x8d = return OP_2MUL + | op == 0x8e = return OP_2DIV + | op == 0x8f = return OP_NEGATE + | op == 0x90 = return OP_ABS + | op == 0x91 = return OP_NOT + | op == 0x92 = return OP_0NOTEQUAL + | op == 0x93 = return OP_ADD + | op == 0x94 = return OP_SUB + | op == 0x95 = return OP_MUL + | op == 0x96 = return OP_DIV + | op == 0x97 = return OP_MOD + | op == 0x98 = return OP_LSHIFT + | op == 0x99 = return OP_RSHIFT + | op == 0x9a = return OP_BOOLAND + | op == 0x9b = return OP_BOOLOR + | op == 0x9c = return OP_NUMEQUAL + | op == 0x9d = return OP_NUMEQUALVERIFY + | op == 0x9e = return OP_NUMNOTEQUAL + | op == 0x9f = return OP_LESSTHAN + | op == 0xa0 = return OP_GREATERTHAN + | op == 0xa1 = return OP_LESSTHANOREQUAL + | op == 0xa2 = return OP_GREATERTHANOREQUAL + | op == 0xa3 = return OP_MIN + | op == 0xa4 = return OP_MAX + | op == 0xa5 = return OP_WITHIN + + -- Crypto + | op == 0xa6 = return OP_RIPEMD160 + | op == 0xa7 = return OP_SHA1 + | op == 0xa8 = return OP_SHA256 + | op == 0xa9 = return OP_HASH160 + | op == 0xaa = return OP_HASH256 + | op == 0xab = return OP_CODESEPARATOR + | op == 0xac = return OP_CHECKSIG + | op == 0xad = return OP_CHECKSIGVERIFY + | op == 0xae = return OP_CHECKMULTISIG + | op == 0xaf = return OP_CHECKMULTISIGVERIFY + + -- More NOPs + | op == 0xb0 = return OP_NOP1 + | op == 0xb1 = return OP_NOP2 + | op == 0xb2 = return OP_NOP3 + | op == 0xb3 = return OP_NOP4 + | op == 0xb4 = return OP_NOP5 + | op == 0xb5 = return OP_NOP6 + | op == 0xb6 = return OP_NOP7 + | op == 0xb7 = return OP_NOP8 + | op == 0xb8 = return OP_NOP9 + | op == 0xb9 = return OP_NOP10 + + -- Constants + | op == 0xfd = return OP_PUBKEYHASH + | op == 0xfe = return OP_PUBKEY + + | otherwise = return $ OP_INVALIDOPCODE op + + put op = case op of + + (OP_PUSHDATA payload optype)-> do + let len = BS.length payload + case optype of + OPCODE -> do + unless (len <= 0x4b) $ fail + "OP_PUSHDATA OPCODE: Payload size too big" + putWord8 $ fromIntegral len + OPDATA1 -> do + unless (len <= 0xff) $ fail + "OP_PUSHDATA OPDATA1: Payload size too big" + putWord8 0x4c + putWord8 $ fromIntegral len + OPDATA2 -> do + unless (len <= 0xffff) $ fail + "OP_PUSHDATA OPDATA2: Payload size too big" + putWord8 0x4d + putWord16le $ fromIntegral len + OPDATA4 -> do + unless (len <= 0x7fffffff) $ fail + "OP_PUSHDATA OPDATA4: Payload size too big" + putWord8 0x4e + putWord32le $ fromIntegral len + putByteString payload + + -- Constants + OP_0 -> putWord8 0x00 + OP_1NEGATE -> putWord8 0x4f + OP_RESERVED -> putWord8 0x50 + OP_1 -> putWord8 0x51 + OP_2 -> putWord8 0x52 + OP_3 -> putWord8 0x53 + OP_4 -> putWord8 0x54 + OP_5 -> putWord8 0x55 + OP_6 -> putWord8 0x56 + OP_7 -> putWord8 0x57 + OP_8 -> putWord8 0x58 + OP_9 -> putWord8 0x59 + OP_10 -> putWord8 0x5a + OP_11 -> putWord8 0x5b + OP_12 -> putWord8 0x5c + OP_13 -> putWord8 0x5d + OP_14 -> putWord8 0x5e + OP_15 -> putWord8 0x5f + OP_16 -> putWord8 0x60 + + -- Crypto Constants + OP_PUBKEY -> putWord8 0xfe + OP_PUBKEYHASH -> putWord8 0xfd + + -- Invalid Opcodes + (OP_INVALIDOPCODE x) -> putWord8 x + + -- Flow Control + OP_NOP -> putWord8 0x61 + OP_VER -> putWord8 0x62 + OP_IF -> putWord8 0x63 + OP_NOTIF -> putWord8 0x64 + OP_VERIF -> putWord8 0x65 + OP_VERNOTIF -> putWord8 0x66 + OP_ELSE -> putWord8 0x67 + OP_ENDIF -> putWord8 0x68 + OP_VERIFY -> putWord8 0x69 + OP_RETURN -> putWord8 0x6a + + -- Stack Operations + OP_TOALTSTACK -> putWord8 0x6b + OP_FROMALTSTACK -> putWord8 0x6c + OP_2DROP -> putWord8 0x6d + OP_2DUP -> putWord8 0x6e + OP_3DUP -> putWord8 0x6f + OP_2OVER -> putWord8 0x70 + OP_2ROT -> putWord8 0x71 + OP_2SWAP -> putWord8 0x72 + OP_IFDUP -> putWord8 0x73 + OP_DEPTH -> putWord8 0x74 + OP_DROP -> putWord8 0x75 + OP_DUP -> putWord8 0x76 + OP_NIP -> putWord8 0x77 + OP_OVER -> putWord8 0x78 + OP_PICK -> putWord8 0x79 + OP_ROLL -> putWord8 0x7a + OP_ROT -> putWord8 0x7b + OP_SWAP -> putWord8 0x7c + OP_TUCK -> putWord8 0x7d + + -- Splice + OP_CAT -> putWord8 0x7e + OP_SUBSTR -> putWord8 0x7f + OP_LEFT -> putWord8 0x80 + OP_RIGHT -> putWord8 0x81 + OP_SIZE -> putWord8 0x82 + + -- Bitwise Logic + OP_INVERT -> putWord8 0x83 + OP_AND -> putWord8 0x84 + OP_OR -> putWord8 0x85 + OP_XOR -> putWord8 0x86 + OP_EQUAL -> putWord8 0x87 + OP_EQUALVERIFY -> putWord8 0x88 + OP_RESERVED1 -> putWord8 0x89 + OP_RESERVED2 -> putWord8 0x8a + + -- Arithmetic + OP_1ADD -> putWord8 0x8b + OP_1SUB -> putWord8 0x8c + OP_2MUL -> putWord8 0x8d + OP_2DIV -> putWord8 0x8e + OP_NEGATE -> putWord8 0x8f + OP_ABS -> putWord8 0x90 + OP_NOT -> putWord8 0x91 + OP_0NOTEQUAL -> putWord8 0x92 + OP_ADD -> putWord8 0x93 + OP_SUB -> putWord8 0x94 + OP_MUL -> putWord8 0x95 + OP_DIV -> putWord8 0x96 + OP_MOD -> putWord8 0x97 + OP_LSHIFT -> putWord8 0x98 + OP_RSHIFT -> putWord8 0x99 + OP_BOOLAND -> putWord8 0x9a + OP_BOOLOR -> putWord8 0x9b + OP_NUMEQUAL -> putWord8 0x9c + OP_NUMEQUALVERIFY -> putWord8 0x9d + OP_NUMNOTEQUAL -> putWord8 0x9e + OP_LESSTHAN -> putWord8 0x9f + OP_GREATERTHAN -> putWord8 0xa0 + OP_LESSTHANOREQUAL -> putWord8 0xa1 + OP_GREATERTHANOREQUAL-> putWord8 0xa2 + OP_MIN -> putWord8 0xa3 + OP_MAX -> putWord8 0xa4 + OP_WITHIN -> putWord8 0xa5 + + -- Crypto + OP_RIPEMD160 -> putWord8 0xa6 + OP_SHA1 -> putWord8 0xa7 + OP_SHA256 -> putWord8 0xa8 + OP_HASH160 -> putWord8 0xa9 + OP_HASH256 -> putWord8 0xaa + OP_CODESEPARATOR -> putWord8 0xab + OP_CHECKSIG -> putWord8 0xac + OP_CHECKSIGVERIFY -> putWord8 0xad + OP_CHECKMULTISIG -> putWord8 0xae + OP_CHECKMULTISIGVERIFY -> putWord8 0xaf + + -- More NOPs + OP_NOP1 -> putWord8 0xb0 + OP_NOP2 -> putWord8 0xb1 + OP_NOP3 -> putWord8 0xb2 + OP_NOP4 -> putWord8 0xb3 + OP_NOP5 -> putWord8 0xb4 + OP_NOP6 -> putWord8 0xb5 + OP_NOP7 -> putWord8 0xb6 + OP_NOP8 -> putWord8 0xb7 + OP_NOP9 -> putWord8 0xb8 + OP_NOP10 -> putWord8 0xb9 + + +-- | Check whether opcode is only data. +isPushOp :: ScriptOp -> Bool +isPushOp op = case op of + OP_PUSHDATA _ _ -> True + OP_0 -> True + OP_1NEGATE -> True + OP_1 -> True + OP_2 -> True + OP_3 -> True + OP_4 -> True + OP_5 -> True + OP_6 -> True + OP_7 -> True + OP_8 -> True + OP_9 -> True + OP_10 -> True + OP_11 -> True + OP_12 -> True + OP_13 -> True + OP_14 -> True + OP_15 -> True + OP_16 -> True + _ -> False + +-- | Optimally encode data using one of the 4 types of data pushing opcodes +opPushData :: BS.ByteString -> ScriptOp +opPushData bs + | len <= 0x4b = OP_PUSHDATA bs OPCODE + | len <= 0xff = OP_PUSHDATA bs OPDATA1 + | len <= 0xffff = OP_PUSHDATA bs OPDATA2 + | len <= 0xffffffff = OP_PUSHDATA bs OPDATA4 + | otherwise = error "opPushData: payload size too big" + where + len = BS.length bs
+ test/Bitcoin/ScriptSpec.hs view
@@ -0,0 +1,25 @@+module Bitcoin.ScriptSpec where + +import Bitcoin.Script (decode, encode) +import Bitcoin.Script.Types + +import qualified Data.ByteString.Lazy.Char8 as BSL8 (pack) + +import Test.Hspec +import Test.Hspec.Expectations (expectationFailure) + +spec :: Spec +spec = do + describe "when decompiling a specific script" $ do + it "encoding a decoding a script results in the original hex" $ do + let hex = BSL8.pack "76a914975efcba1e058667594dc57146022ec46560a63c88ac" + + (encode . decode) hex `shouldBe` hex + + it "succesfully parses a transaction into a meaningful object" $ do + let hex = BSL8.pack "76a914975efcba1e058667594dc57146022ec46560a63c88ac" + decoded = decode hex + + case decoded of + (Script [OP_DUP, OP_HASH160, OP_PUSHDATA _ OPCODE, OP_EQUALVERIFY, OP_CHECKSIG]) -> 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 #-}