haskoin-core-1.2.2: src/Haskoin/Transaction/Builder/Sign.hs
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedRecordDot #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE NoFieldSelectors #-}
-- |
-- Module : Haskoin.Transaction.Builder.Sign
-- Copyright : No rights reserved
-- License : MIT
-- Maintainer : jprupp@protonmail.ch
-- Stability : experimental
-- Portability : POSIX
--
-- Types and logic for signing transactions.
module Haskoin.Transaction.Builder.Sign
( SigInput (..),
makeSignature,
makeSigHash,
signTx,
findInputIndex,
signInput,
buildInput,
sigKeys,
)
where
import Control.DeepSeq
import Control.Monad
import Crypto.Secp256k1
import Data.Aeson
import Data.Aeson.Encoding
import Data.Bytes.Get
import Data.Bytes.Put
import Data.Bytes.Serial
import Data.Either
import Data.Hashable
import Data.List
import Data.Maybe
import Data.Word
import GHC.Generics
import Haskoin.Address
import Haskoin.Crypto.Hash
import Haskoin.Crypto.Keys.Common
import Haskoin.Crypto.Signature
import Haskoin.Network.Data
import Haskoin.Script
import Haskoin.Transaction.Common
import Haskoin.Transaction.Segwit
import Haskoin.Util
-- | 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
{ -- | output script to spend
-- ^ output script value
script :: !ScriptOutput,
-- | output script value
-- ^ outpoint to spend
value :: !Word64,
-- | outpoint to spend
-- ^ signature type
outpoint :: !OutPoint,
-- | signature type
-- ^ redeem script
sighash :: !SigHash,
-- | redeem script
redeem :: !(Maybe RedeemScript)
}
deriving (Show, Read, Eq, Generic, NFData)
instance MarshalJSON Ctx SigInput where
marshalValue ctx (SigInput s v o h r) =
object $
[ "pkscript" .= marshalValue ctx s,
"value" .= v,
"outpoint" .= o,
"sighash" .= h
]
++ [ "redeem" .= marshalValue ctx r
| r <- maybeToList r
]
marshalEncoding ctx (SigInput s v o h r) =
pairs $
mconcat
[ "pkscript" `pair` marshalEncoding ctx s,
"value" `pair` word64 v,
"outpoint" `pair` toEncoding o,
"sighash" `pair` toEncoding h,
maybe mempty (pair "redeem" . marshalEncoding ctx) r
]
unmarshalValue ctx =
withObject "SigInput" $ \o ->
SigInput
<$> (unmarshalValue ctx =<< o .: "pkscript")
<*> o .: "value"
<*> o .: "outpoint"
<*> o .: "sighash"
<*> (mapM (unmarshalValue ctx) =<< o .:? "redeem")
-- | 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 ->
Ctx ->
-- | transaction to sign
Tx ->
-- | signing parameters, with nesting flag
[(SigInput, Bool)] ->
-- | private keys to sign with
[SecKey] ->
-- | signed transaction
Either String Tx
signTx net ctx otx sigis allKeys
| null ti = Left "signTx: Transaction has no inputs"
| otherwise = foldM go otx $ findInputIndex ((.outpoint) . fst) sigis ti
where
ti = otx.inputs
go tx (sigi@(SigInput so _ _ _ rdmM, _), i) = do
keys <- sigKeys ctx so rdmM allKeys
foldM (\t k -> signInput net ctx 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 ->
Ctx ->
Tx ->
Int ->
-- | boolean flag: nest input
(SigInput, Bool) ->
PrivateKey ->
Either String Tx
signInput net ctx tx i (sigIn@(SigInput so val _ _ rdmM), nest) key = do
let sig = makeSignature net ctx tx i sigIn key
si <- buildInput net ctx tx i so val rdmM sig $ derivePublicKey ctx key
w <- updatedWitnessData net ctx tx i so si
return tx {inputs = nextTxIn so si, witness = w}
where
f si TxIn {..} = TxIn {script = marshal (net, ctx) si, ..}
g so' TxIn {..} = TxIn {script = pkScript so', ..}
pkScript so' = runPutS . serialize . opPushData $ marshal ctx so'
nextTxIn so' si
| isSegwit so' && nest = updateIndex i tx.inputs (g so')
| isSegwit so' = tx.inputs
| otherwise = updateIndex i tx.inputs (f si)
-- | Add the witness data of the transaction given segwit parameters for an input.
--
-- @since 0.11.0.0
updatedWitnessData ::
Network ->
Ctx ->
Tx ->
Int ->
ScriptOutput ->
ScriptInput ->
Either String WitnessData
updatedWitnessData net ctx tx i so si
| isSegwit so =
updateWitness . toWitnessStack net ctx =<< calcWitnessProgram net ctx so si
| otherwise =
return tx.witness
where
updateWitness w
| null tx.witness = return $ updateIndex i defaultStack (const w)
| length tx.witness /= n = Left "Invalid number of witness stacks"
| otherwise = return $ updateIndex i tx.witness (const w)
defaultStack = replicate n $ toWitnessStack net ctx EmptyWitnessProgram
n = length tx.inputs
-- | Associate an input index to each value in a list
findInputIndex ::
-- | extract an outpoint
(a -> OutPoint) ->
-- | input list
[a] ->
-- | reference list of inputs
[TxIn] ->
[(a, Int)]
findInputIndex getOutPoint as ti =
mapMaybe g $ zip (matchTemplate as ti f) [0 ..]
where
f s txin = getOutPoint s == txin.outpoint
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 ::
Ctx ->
ScriptOutput ->
Maybe RedeemScript ->
[SecKey] ->
Either String [PrivateKey]
sigKeys ctx 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 ctx rdm Nothing keys
(PayWitnessPKHash h, _) -> return $ keyByHash h
(PayWitnessScriptHash _, Just rdm) -> sigKeys ctx 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 = derivePublicKey ctx prv
]
keyByHash h = fmap fst . maybeToList . findKey h $ zipKeys
findKey h = find $ (== h) . (.hash160) . pubKeyAddr ctx . snd
-- | Construct an input for a transaction given a signature, public key and data
-- about the previous output.
buildInput ::
Network ->
Ctx ->
-- | transaction where input will be added
Tx ->
-- | input index where signature will go
Int ->
-- | output script being spent
ScriptOutput ->
-- | amount of previous output
Word64 ->
-- | redeem script if pay-to-script-hash
Maybe RedeemScript ->
TxSignature ->
PublicKey ->
Either String ScriptInput
buildInput net ctx tx i so val rdmM sig pub = do
when (i >= length tx.inputs) $ 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 ctx tx so i
return $ RegularInput $ SpendMulSig mSigs
_ -> Left "buildInput: Invalid output/redeem script combination"
buildScriptHashInput rdm = do
inp <- buildRegularInput rdm
return $ ScriptHashInput inp.get rdm
f (TxSignature x sh) p =
verifyHashSig
ctx
(makeSigHash net ctx tx i so val sh rdmM)
x
p.point
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 -> Ctx -> Tx -> ScriptOutput -> Int -> [TxSignature]
parseExistingSigs net ctx tx so i = insSigs <> witSigs
where
insSigs = case unmarshal (net, ctx) scp of
Right (ScriptHashInput (SpendMulSig xs) _) -> xs
Right (RegularInput (SpendMulSig xs)) -> xs
_ -> []
scp = (tx.inputs !! i).script
witSigs
| not $ isSegwit so = []
| null tx.witness = []
| otherwise = rights $ decodeTxSig net ctx <$> (tx.witness !! i)
-- | Produce a structured representation of a deterministic (RFC-6979) signature over an input.
makeSignature :: Network -> Ctx -> Tx -> Int -> SigInput -> PrivateKey -> TxSignature
makeSignature net ctx tx i (SigInput so val _ sh rdmM) key =
TxSignature (signHash ctx key.key m) sh
where
m = makeSigHash net ctx tx i so val sh rdmM
-- | A function which selects the digest algorithm and parameters as appropriate
--
-- @since 0.11.0.0
makeSigHash ::
Network ->
Ctx ->
Tx ->
Int ->
ScriptOutput ->
Word64 ->
SigHash ->
Maybe RedeemScript ->
Hash256
makeSigHash net ctx tx i so val sh rdmM = h net tx (encodeOutput ctx so') val i sh
where
so' = case so of
PayWitnessPKHash h' -> PayPKHash h'
_ -> fromMaybe so rdmM
h
| isSegwit so = txSigHashForkId
| otherwise = txSigHash