haskoin-core-0.4.1: Network/Haskoin/Script/Parser.hs
module Network.Haskoin.Script.Parser
( ScriptOutput(..)
, ScriptInput(..)
, SimpleInput(..)
, RedeemScript
, scriptAddr
, outputAddress
, inputAddress
, encodeInput
, encodeInputBS
, decodeInput
, decodeInputBS
, encodeOutput
, encodeOutputBS
, decodeOutput
, decodeOutputBS
, sortMulSig
, intToScriptOp
, scriptOpToInt
, isPayPK
, isPayPKHash
, isPayMulSig
, isPayScriptHash
, isSpendPK
, isSpendPKHash
, isSpendMulSig
, isScriptHashInput
, isDataCarrier
) where
import Control.Applicative ((<|>))
import Control.DeepSeq (NFData, rnf)
import Control.Monad (guard, liftM2, (<=<))
import Data.Aeson (FromJSON, ToJSON,
Value (String), parseJSON,
toJSON, withText)
import Data.ByteString (ByteString)
import qualified Data.ByteString as BS (head, singleton)
import Data.Foldable (foldrM)
import Data.List (sortBy)
import Data.Serialize (encode, decode)
import Data.String.Conversions (cs)
import Network.Haskoin.Crypto.Base58
import Network.Haskoin.Crypto.Hash
import Network.Haskoin.Crypto.Keys
import Network.Haskoin.Script.SigHash
import Network.Haskoin.Script.Types
import Network.Haskoin.Util
-- | Data type describing standard transaction output scripts. Output scripts
-- provide the conditions that must be fulfilled for someone to spend the
-- output coins.
data ScriptOutput =
-- | Pay to a public key.
PayPK { getOutputPubKey :: !PubKey }
-- | Pay to a public key hash.
| PayPKHash { getOutputAddress :: !Address }
-- | Pay to multiple public keys.
| PayMulSig { getOutputMulSigKeys :: ![PubKey]
, getOutputMulSigRequired :: !Int
}
-- | Pay to a script hash.
| PayScriptHash { getOutputAddress :: !Address }
-- | Provably unspendable data carrier.
| DataCarrier { getOutputData :: !ByteString }
deriving (Eq, Show, Read)
instance FromJSON ScriptOutput where
parseJSON = withText "scriptoutput" $ \t -> either fail return $
maybeToEither "scriptoutput not hex" (decodeHex $ cs t) >>=
decodeOutputBS
instance ToJSON ScriptOutput where
toJSON = String . cs . encodeHex . encodeOutputBS
instance NFData ScriptOutput where
rnf (PayPK k) = rnf k
rnf (PayPKHash a) = rnf a
rnf (PayMulSig k r) = rnf k `seq` rnf r
rnf (PayScriptHash a) = rnf a
rnf (DataCarrier a) = rnf a
-- | Returns True if the script is a pay to public key output.
isPayPK :: ScriptOutput -> Bool
isPayPK (PayPK _) = True
isPayPK _ = False
-- | Returns True if the script is a pay to public key hash output.
isPayPKHash :: ScriptOutput -> Bool
isPayPKHash (PayPKHash _) = True
isPayPKHash _ = False
-- | Returns True if the script is a pay to multiple public keys output.
isPayMulSig :: ScriptOutput -> Bool
isPayMulSig (PayMulSig _ _) = True
isPayMulSig _ = False
-- | Returns true if the script is a pay to script hash output.
isPayScriptHash :: ScriptOutput -> Bool
isPayScriptHash (PayScriptHash _) = True
isPayScriptHash _ = False
-- | Returns True if the script is an OP_RETURN "datacarrier" output
isDataCarrier :: ScriptOutput -> Bool
isDataCarrier (DataCarrier _) = True
isDataCarrier _ = False
-- | Computes a script address from a script output. This address can be used
-- in a pay to script hash output.
scriptAddr :: ScriptOutput -> Address
scriptAddr = ScriptAddress . hash160 . getHash256 . hash256 . encodeOutputBS
-- | Sorts the public keys of a multisignature output in ascending order by
-- comparing their serialized representations. This feature allows for easier
-- multisignature account management as participants in a multisignature wallet
-- will blindly agree on an ordering of the public keys without having to
-- communicate.
sortMulSig :: ScriptOutput -> ScriptOutput
sortMulSig out = case out of
PayMulSig keys r -> PayMulSig (sortBy f keys) r
_ -> error "Can only call orderMulSig on PayMulSig scripts"
where
f a b = encode a `compare` encode b
-- | Computes a 'Script' from a 'ScriptOutput'. The 'Script' is a list of
-- 'ScriptOp' can can be used to build a 'Tx'.
encodeOutput :: ScriptOutput -> Script
encodeOutput s = Script $ case s of
-- Pay to PubKey
(PayPK k) -> [opPushData $ encode k, OP_CHECKSIG]
-- Pay to PubKey Hash Address
(PayPKHash a) -> case a of
(PubKeyAddress h) -> [ OP_DUP, OP_HASH160, opPushData $ encode h
, OP_EQUALVERIFY, OP_CHECKSIG
]
(ScriptAddress _) ->
error "encodeOutput: ScriptAddress is invalid in PayPKHash"
-- Pay to MultiSig Keys
(PayMulSig ps r)
| r <= length ps ->
let opM = intToScriptOp r
opN = intToScriptOp $ length ps
keys = map (opPushData . encode) ps
in opM : keys ++ [opN, OP_CHECKMULTISIG]
| otherwise -> error "encodeOutput: PayMulSig r must be <= than pkeys"
-- Pay to Script Hash Address
(PayScriptHash a) -> case a of
(ScriptAddress h) -> [ OP_HASH160
, opPushData $ encode h, OP_EQUAL
]
(PubKeyAddress _) ->
error "encodeOutput: PubKeyAddress is invalid in PayScriptHash"
-- Provably unspendable output
(DataCarrier d) -> [OP_RETURN, opPushData d]
-- | Similar to 'encodeOutput' but encodes to a ByteString
encodeOutputBS :: ScriptOutput -> ByteString
encodeOutputBS = encode . encodeOutput
-- | Tries to decode a 'ScriptOutput' from a 'Script'. This can fail if the
-- script is not recognized as any of the standard output types.
decodeOutput :: Script -> Either String ScriptOutput
decodeOutput s = case scriptOps s of
-- Pay to PubKey
[OP_PUSHDATA bs _, OP_CHECKSIG] -> PayPK <$> decode bs
-- Pay to PubKey Hash
[OP_DUP, OP_HASH160, OP_PUSHDATA bs _, OP_EQUALVERIFY, OP_CHECKSIG] ->
(PayPKHash . PubKeyAddress) <$> decode bs
-- Pay to Script Hash
[OP_HASH160, OP_PUSHDATA bs _, OP_EQUAL] ->
(PayScriptHash . ScriptAddress) <$> decode bs
-- Provably unspendable data carrier output
[OP_RETURN, OP_PUSHDATA bs _] -> Right $ DataCarrier bs
-- Pay to MultiSig Keys
_ -> matchPayMulSig s
-- | Similar to 'decodeOutput' but decodes from a ByteString
decodeOutputBS :: ByteString -> Either String ScriptOutput
decodeOutputBS = decodeOutput <=< decode
-- Match [ OP_N, PubKey1, ..., PubKeyM, OP_M, OP_CHECKMULTISIG ]
matchPayMulSig :: Script -> Either String ScriptOutput
matchPayMulSig (Script ops) = case splitAt (length ops - 2) ops of
(m:xs,[n,OP_CHECKMULTISIG]) -> do
(intM,intN) <- liftM2 (,) (scriptOpToInt m) (scriptOpToInt n)
if intM <= intN && length xs == intN
then liftM2 PayMulSig (go xs) (return intM)
else Left "matchPayMulSig: Invalid M or N parameters"
_ -> Left "matchPayMulSig: script did not match output template"
where
go (OP_PUSHDATA bs _:xs) = liftM2 (:) (decode bs) (go xs)
go [] = return []
go _ = Left "matchPayMulSig: invalid multisig opcode"
-- | Transforms integers [1 .. 16] to 'ScriptOp' [OP_1 .. OP_16]
intToScriptOp :: Int -> ScriptOp
intToScriptOp i
| i `elem` [1..16] = either (const err) id op
| otherwise = err
where
op = decode $ BS.singleton $ fromIntegral $ i + 0x50
err = error $ "intToScriptOp: Invalid integer " ++ (show i)
-- | Decode 'ScriptOp' [OP_1 .. OP_16] to integers [1 .. 16]. This functions
-- fails for other values of 'ScriptOp'
scriptOpToInt :: ScriptOp -> Either String Int
scriptOpToInt s
| res `elem` [1..16] = return res
| otherwise = Left $ "scriptOpToInt: invalid opcode " ++ (show s)
where
res = (fromIntegral $ BS.head $ encode s) - 0x50
-- | Get the address of a `ScriptOutput`
outputAddress :: ScriptOutput -> Either String Address
outputAddress s = case s of
PayPKHash a -> return a
PayScriptHash a -> return a
PayPK k -> return $ pubKeyAddr k
_ -> Left "outputAddress: bad output script type"
-- | Get the address of a `ScriptInput`
inputAddress :: ScriptInput -> Either String Address
inputAddress s = case s of
RegularInput (SpendPKHash _ key) -> return $ pubKeyAddr key
ScriptHashInput _ rdm -> return $ scriptAddr rdm
_ -> Left "inputAddress: bad input script type"
-- | Data type describing standard transaction input scripts. Input scripts
-- provide the signing data required to unlock the coins of the output they are
-- trying to spend.
data SimpleInput
-- | Spend the coins of a PayPK output.
= SpendPK { getInputSig :: !TxSignature }
-- | Spend the coins of a PayPKHash output.
| SpendPKHash { getInputSig :: !TxSignature
, getInputKey :: !PubKey
}
-- | Spend the coins of a PayMulSig output.
| SpendMulSig { getInputMulSigKeys :: ![TxSignature] }
deriving (Eq, Show, Read)
instance NFData SimpleInput where
rnf (SpendPK i) = rnf i
rnf (SpendPKHash i k) = rnf i `seq` rnf k
rnf (SpendMulSig k) = rnf k
-- | Returns True if the input script is spending a public key.
isSpendPK :: ScriptInput -> Bool
isSpendPK (RegularInput (SpendPK _)) = True
isSpendPK _ = False
-- | Returns True if the input script is spending a public key hash.
isSpendPKHash :: ScriptInput -> Bool
isSpendPKHash (RegularInput (SpendPKHash _ _)) = True
isSpendPKHash _ = False
-- | Returns True if the input script is spending a multisignature output.
isSpendMulSig :: ScriptInput -> Bool
isSpendMulSig (RegularInput (SpendMulSig _)) = True
isSpendMulSig _ = False
isScriptHashInput :: ScriptInput -> Bool
isScriptHashInput (ScriptHashInput _ _) = True
isScriptHashInput _ = False
type RedeemScript = ScriptOutput
data ScriptInput
= RegularInput { getRegularInput :: SimpleInput }
| ScriptHashInput { getScriptHashInput :: SimpleInput
, getScriptHashRedeem :: RedeemScript
}
deriving (Eq, Show, Read)
instance NFData ScriptInput where
rnf (RegularInput i) = rnf i
rnf (ScriptHashInput i o) = rnf i `seq` rnf o
-- | Computes a 'Script' from a 'SimpleInput'. The 'Script' is a list of
-- 'ScriptOp' that can be used to build a 'Tx'.
encodeSimpleInput :: SimpleInput -> Script
encodeSimpleInput s = Script $ case s of
SpendPK ts -> [ opPushData $ encodeSig ts ]
SpendPKHash ts p -> [ opPushData $ encodeSig ts
, opPushData $ encode p
]
SpendMulSig ts -> OP_0 : map (opPushData . encodeSig) ts
decodeSimpleInput :: Script -> Either String SimpleInput
decodeSimpleInput (Script ops) = maybeToEither errMsg $
matchPK ops <|> matchPKHash ops <|> matchMulSig ops
where
matchPK [OP_PUSHDATA bs _] = SpendPK <$> eitherToMaybe (decodeSig bs)
matchPK _ = Nothing
matchPKHash [OP_PUSHDATA sig _, OP_PUSHDATA pub _] =
liftM2 SpendPKHash (eitherToMaybe $ decodeSig sig) (decodeToMaybe pub)
matchPKHash _ = Nothing
matchMulSig (x:xs) = do
guard $ isPushOp x
SpendMulSig <$> foldrM f [] xs
matchMulSig _ = Nothing
f (OP_PUSHDATA bs _) acc =
liftM2 (:) (eitherToMaybe $ decodeSig bs) (Just acc)
f _ _ = Nothing
errMsg = "decodeInput: Could not decode script input"
encodeInput :: ScriptInput -> Script
encodeInput s = case s of
RegularInput ri -> encodeSimpleInput ri
ScriptHashInput i o -> Script $
(scriptOps $ encodeSimpleInput i) ++ [opPushData $ encodeOutputBS o]
-- | Similar to 'encodeInput' but encodes to a ByteString
encodeInputBS :: ScriptInput -> ByteString
encodeInputBS = encode . encodeInput
-- | Decodes a 'ScriptInput' from a 'Script'. This function fails if the
-- script can not be parsed as a standard script input.
decodeInput :: Script -> Either String ScriptInput
decodeInput s@(Script ops) = maybeToEither errMsg $
matchSimpleInput <|> matchPayScriptHash
where
matchSimpleInput = RegularInput <$> (eitherToMaybe $ decodeSimpleInput s)
matchPayScriptHash = case splitAt (length (scriptOps s) - 1) ops of
(is, [OP_PUSHDATA bs _]) -> do
rdm <- eitherToMaybe $ decodeOutputBS bs
inp <- eitherToMaybe $ decodeSimpleInput $ Script is
return $ ScriptHashInput inp rdm
_ -> Nothing
errMsg = "decodeInput: Could not decode script input"
-- | Similar to 'decodeInput' but decodes from a ByteString
decodeInputBS :: ByteString -> Either String ScriptInput
decodeInputBS = decodeInput <=< decode