packages feed

haskoin-core-0.12.0: src/Network/Haskoin/Transaction/Builder/Sign.hs

{-# LANGUAGE DeriveAnyClass    #-}
{-# LANGUAGE DeriveGeneric     #-}
{-# LANGUAGE LambdaCase        #-}
{-# LANGUAGE OverloadedStrings #-}
{-|
Module      : Network.Haskoin.Transaction.Builder.Sign
Copyright   : No rights reserved
License     : UNLICENSE
Maintainer  : jprupp@protonmail.ch
Stability   : experimental
Portability : POSIX

Types and logic for signing transactions.
-}
module Network.Haskoin.Transaction.Builder.Sign
    ( SigInput (..)
    , makeSignature
    , makeSigHash
    , signTx
    , findInputIndex
    , signInput
    , buildInput
    , sigKeys
    ) where

import           Control.DeepSeq                    (NFData)
import           Control.Monad                      (foldM, mzero, when)
import           Data.Aeson                         (FromJSON, ToJSON,
                                                     Value (Object), object,
                                                     parseJSON, toJSON, (.:),
                                                     (.:?), (.=))
import           Data.Either                        (rights)
import           Data.Hashable                      (Hashable)
import           Data.List                          (find, nub)
import           Data.Maybe                         (catMaybes, fromMaybe,
                                                     mapMaybe, maybeToList)
import qualified Data.Serialize                     as S
import           Data.Word                          (Word64)
import           GHC.Generics                       (Generic)

import           Network.Haskoin.Address            (getAddrHash160, pubKeyAddr)
import           Network.Haskoin.Constants          (Network)
import           Network.Haskoin.Crypto             (Hash256, SecKey)
import           Network.Haskoin.Crypto.Signature   (signHash, verifyHashSig)
import           Network.Haskoin.Keys.Common        (PubKeyI (..), SecKeyI (..),
                                                     derivePubKeyI, wrapSecKey)
import           Network.Haskoin.Script.Common      (ScriptOutput (..),
                                                     encodeOutput,
                                                     encodeOutputBS, opPushData)
import           Network.Haskoin.Script.SigHash     (SigHash, TxSignature (..),
                                                     decodeTxSig, txSigHash,
                                                     txSigHashForkId)
import           Network.Haskoin.Script.Standard    (RedeemScript,
                                                     ScriptInput (..),
                                                     SimpleInput (..),
                                                     decodeInputBS,
                                                     encodeInputBS)
import           Network.Haskoin.Transaction.Common (OutPoint, Tx (..),
                                                     TxIn (..), WitnessData)
import           Network.Haskoin.Transaction.Segwit (WitnessProgram (..),
                                                     calcWitnessProgram,
                                                     isSegwit, toWitnessStack)
import           Network.Haskoin.Util               (matchTemplate, updateIndex)

-- | Data type used to specify the signing parameters of a transaction input.
-- To sign an input, the previous output script, outpoint and sighash are
-- required. When signing a pay to script hash output, an additional redeem
-- script is required.
data SigInput = SigInput
    { sigInputScript :: !ScriptOutput         -- ^ output script to spend
    , sigInputValue  :: !Word64               -- ^ output script value
    , sigInputOP     :: !OutPoint             -- ^ outpoint to spend
    , sigInputSH     :: !SigHash              -- ^ signature type
    , sigInputRedeem :: !(Maybe RedeemScript) -- ^ redeem script
    } deriving (Eq, Show, Read, Generic, Hashable, NFData)

instance ToJSON SigInput where
    toJSON (SigInput so val op sh rdm) = object $
        [ "pkscript" .= so
        , "value"    .= val
        , "outpoint" .= op
        , "sighash"  .= sh
        ] ++ [ "redeem" .= r | r <- maybeToList rdm ]

instance FromJSON SigInput where
    parseJSON (Object o) = do
        so  <- o .: "pkscript"
        val <- o .: "value"
        op  <- o .: "outpoint"
        sh  <- o .: "sighash"
        rdm <- o .:? "redeem"
        return $ SigInput so val op sh rdm
    parseJSON _ = mzero

-- | Sign a transaction by providing the 'SigInput' signing parameters and a
-- list of private keys. The signature is computed deterministically as defined
-- in RFC-6979.
signTx :: Network
       -> Tx                 -- ^ transaction to sign
       -> [(SigInput, Bool)] -- ^ signing parameters, with nesting flag
       -> [SecKey]           -- ^ private keys to sign with
       -> Either String Tx   -- ^ signed transaction
signTx net otx sigis allKeys
    | null ti   = Left "signTx: Transaction has no inputs"
    | otherwise = foldM go otx $ findInputIndex (sigInputOP . fst) sigis ti
  where
    ti = txIn otx
    go tx (sigi@(SigInput so _ _ _ rdmM, _), i) = do
        keys <- sigKeys so rdmM allKeys
        foldM (\t k -> signInput net t i sigi k) tx keys

-- | Sign a single input in a transaction deterministically (RFC-6979).  The
-- nesting flag only affects the behavior of segwit inputs.
signInput ::
       Network
    -> Tx
    -> Int
    -> (SigInput, Bool) -- ^ boolean flag: nest input
    -> SecKeyI
    -> Either String Tx
signInput net tx i (sigIn@(SigInput so val _ sh rdmM), nest) key = do
    let sig = makeSignature net tx i sigIn key
    si <- buildInput net tx i so val rdmM sig $ derivePubKeyI key
    w  <- updatedWitnessData tx i so si
    return tx { txIn      = nextTxIn so si
              , txWitness = w
              }
  where
    f si x = x {scriptInput = encodeInputBS si}
    g so x = x {scriptInput = S.encode . opPushData $ encodeOutputBS so}
    txis = txIn tx
    nextTxIn so si
        | isSegwit so && nest = updateIndex i txis (g so)
        | isSegwit so         = txIn tx
        | otherwise           = updateIndex i txis (f si)

-- | Add the witness data of the transaction given segwit parameters for an input.
--
-- @since 0.11.0.0
updatedWitnessData :: Tx -> Int -> ScriptOutput -> ScriptInput -> Either String WitnessData
updatedWitnessData tx i so si
    | isSegwit so = updateWitness . toWitnessStack =<< calcWitnessProgram so si
    | otherwise   = return $ txWitness tx
  where
    updateWitness w
        | null $ txWitness tx        = return $ updateIndex i defaultStack (const w)
        | length (txWitness tx) /= n = Left "Invalid number of witness stacks"
        | otherwise                  = return $ updateIndex i (txWitness tx) (const w)
    defaultStack = replicate n $ toWitnessStack EmptyWitnessProgram
    n = length $ txIn tx

-- | Associate an input index to each value in a list
findInputIndex ::
       (a -> OutPoint) -- ^ extract an outpoint
    -> [a]             -- ^ input list
    -> [TxIn]          -- ^ reference list of inputs
    -> [(a, Int)]
findInputIndex getOutPoint as ti =
    mapMaybe g $ zip (matchTemplate as ti f) [0..]
  where
    f s txin = getOutPoint s == prevOutput txin
    g (Just s, i)  = Just (s,i)
    g (Nothing, _) = Nothing

-- | Find from the list of provided private keys which one is required to sign
-- the 'ScriptOutput'.
sigKeys ::
       ScriptOutput
    -> Maybe RedeemScript
    -> [SecKey]
    -> Either String [SecKeyI]
sigKeys so rdmM keys =
    case (so, rdmM) of
        (PayPK p, Nothing) ->
            return . map fst . maybeToList $ find ((== p) . snd) zipKeys
        (PayPKHash h, Nothing) -> return $ keyByHash h
        (PayMulSig ps r, Nothing) ->
            return $ map fst $ take r $ filter ((`elem` ps) . snd) zipKeys
        (PayScriptHash _, Just rdm) -> sigKeys rdm Nothing keys
        (PayWitnessPKHash h, _) -> return $ keyByHash h
        (PayWitnessScriptHash _, Just rdm) -> sigKeys rdm Nothing keys
        _ -> Left "sigKeys: Could not decode output script"
  where
    zipKeys =
        [ (prv, pub)
        | k <- keys
        , t <- [True, False]
        , let prv = wrapSecKey t k
        , let pub = derivePubKeyI prv
        ]
    keyByHash h = fmap fst . maybeToList . findKey h $ zipKeys
    findKey h   = find $ (== h) . getAddrHash160 . pubKeyAddr . snd

-- | Construct an input for a transaction given a signature, public key and data
-- about the previous output.
buildInput ::
       Network
    -> Tx                 -- ^ transaction where input will be added
    -> Int                -- ^ input index where signature will go
    -> ScriptOutput       -- ^ output script being spent
    -> Word64             -- ^ amount of previous output
    -> Maybe RedeemScript -- ^ redeem script if pay-to-script-hash
    -> TxSignature
    -> PubKeyI
    -> Either String ScriptInput
buildInput net tx i so val rdmM sig pub = do
    when (i >= length (txIn tx)) $ Left "buildInput: Invalid input index"
    case (so, rdmM) of
        (PayScriptHash _, Just rdm)        -> buildScriptHashInput rdm
        (PayWitnessScriptHash _, Just rdm) -> buildScriptHashInput rdm
        (PayWitnessPKHash _, Nothing)      -> return . RegularInput $ SpendPKHash sig pub
        (_, Nothing)                       -> buildRegularInput so
        _ -> Left "buildInput: Invalid output/redeem script combination"
  where
    buildRegularInput = \case
        PayPK _ -> return $ RegularInput $ SpendPK sig
        PayPKHash _ -> return $ RegularInput $ SpendPKHash sig pub
        PayMulSig msPubs r -> do
            let mSigs   = take r $ catMaybes $ matchTemplate allSigs msPubs f
                allSigs = nub $ sig : parseExistingSigs net tx so i
            return $ RegularInput $ SpendMulSig mSigs
        _ -> Left "buildInput: Invalid output/redeem script combination"
    buildScriptHashInput rdm = do
        inp <- buildRegularInput rdm
        return $ ScriptHashInput (getRegularInput inp) rdm
    f (TxSignature x sh) p =
        verifyHashSig (makeSigHash net tx i so val sh rdmM) x (pubKeyPoint p)
    f TxSignatureEmpty _ = False

-- | Apply heuristics to extract the signatures for a particular input that are
-- embedded in the transaction.
--
-- @since 0.11.0.0
parseExistingSigs :: Network -> Tx -> ScriptOutput -> Int -> [TxSignature]
parseExistingSigs net tx so i = insSigs <> witSigs
  where
    insSigs = case decodeInputBS net scp of
            Right (ScriptHashInput (SpendMulSig xs) _) -> xs
            Right (RegularInput (SpendMulSig xs))      -> xs
            _                                          -> []
    scp = scriptInput $ txIn tx !! i
    witSigs
        | not $ isSegwit so   = []
        | null $ txWitness tx = []
        | otherwise           = rights $ decodeTxSig net <$> (txWitness tx !! i)

-- | Produce a structured representation of a deterministic (RFC-6979) signature over an input.
makeSignature :: Network -> Tx -> Int -> SigInput -> SecKeyI -> TxSignature
makeSignature net tx i (SigInput so val _ sh rdmM) key = TxSignature (signHash (secKeyData key) m) sh
  where
    m = makeSigHash net tx i so val sh rdmM

-- | A function which selects the digest algorithm and parameters as appropriate
--
-- @since 0.11.0.0
makeSigHash ::
       Network
    -> Tx
    -> Int
    -> ScriptOutput
    -> Word64
    -> SigHash
    -> Maybe RedeemScript
    -> Hash256
makeSigHash net tx i so val sh rdmM = h net tx (encodeOutput so') val i sh
  where
    so' = case so of
        PayWitnessPKHash h -> PayPKHash h
        _                  -> fromMaybe so rdmM
    h | isSegwit so = txSigHashForkId
      | otherwise   = txSigHash