haskoin-core-0.21.0: src/Haskoin/Transaction/Partial.hs
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}
{- |
Module : Haskoin.Transaction.Partial
Copyright : No rights reserved
License : MIT
Maintainer : matt@bitnomial.com
Stability : experimental
Portability : POSIX
Code related to PSBT parsing and serialization.
-}
module Haskoin.Transaction.Partial (
-- * Partially-Signed Transactions
PartiallySignedTransaction (..),
Input (..),
Output (..),
UnknownMap (..),
Key (..),
merge,
mergeMany,
mergeInput,
mergeOutput,
complete,
finalTransaction,
emptyPSBT,
emptyInput,
emptyOutput,
-- ** Signing
PsbtSigner,
getSignerKey,
secKeySigner,
xPrvSigner,
signPSBT,
) where
import Control.Applicative ((<|>))
import Control.DeepSeq
import Control.Monad (foldM, guard, replicateM, void)
import Data.ByteString (ByteString)
import qualified Data.ByteString as B
import Data.Bytes.Get (runGetS)
import Data.Bytes.Put (runPutS)
import Data.Bytes.Serial (Serial (..))
import Data.Either (fromRight)
import Data.HashMap.Strict (HashMap)
import qualified Data.HashMap.Strict as HM
import qualified Data.HashMap.Strict as HashMap
import Data.Hashable (Hashable)
import Data.List (foldl')
import Data.Maybe (fromMaybe, isJust)
import Data.Serialize (Get, Put, Serialize)
import qualified Data.Serialize as S
import GHC.Generics (Generic)
import GHC.Word (Word32, Word8)
import Haskoin.Address (Address (..), pubKeyAddr)
import Haskoin.Crypto (SecKey, derivePubKey)
import Haskoin.Data (Network)
import Haskoin.Keys (
DerivPath,
DerivPathI (Deriv),
Fingerprint,
KeyIndex,
PubKeyI,
SecKeyI (SecKeyI),
XPrvKey,
derivePath,
deriveXPubKey,
listToPath,
pathToList,
pubKeyCompressed,
pubKeyPoint,
xPrvKey,
xPubFP,
)
import Haskoin.Network (
VarInt (..),
VarString (..),
putVarInt,
)
import Haskoin.Script (
Script (..),
ScriptOp (..),
ScriptOutput (..),
SigHash,
decodeOutput,
decodeOutputBS,
encodeOutputBS,
encodeTxSig,
isPayScriptHash,
opPushData,
sigHashAll,
toP2SH,
toP2WSH,
)
import Haskoin.Transaction.Builder (SigInput (..), makeSignature)
import Haskoin.Transaction.Common (
Tx (..),
TxOut,
WitnessStack,
outPointIndex,
outValue,
prevOutput,
scriptInput,
scriptOutput,
)
import Haskoin.Transaction.Segwit (isSegwit)
import Haskoin.Util (eitherToMaybe)
{- | PSBT data type as specified in
[BIP-174](https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki).
This contains an unsigned transaction, inputs and outputs, and unspecified
extra data. There is one input per input in the unsigned transaction, and one
output per output in the unsigned transaction. The inputs and outputs in the
'PartiallySignedTransaction' line up by index with the inputs and outputs in
the unsigned transaction.
-}
data PartiallySignedTransaction = PartiallySignedTransaction
{ unsignedTransaction :: Tx
, globalUnknown :: UnknownMap
, inputs :: [Input]
, outputs :: [Output]
}
deriving (Show, Eq, Generic)
instance NFData PartiallySignedTransaction
{- | Inputs contain all of the data needed to sign a transaction and all of the
resulting signature data after signing.
-}
data Input = Input
{ nonWitnessUtxo :: Maybe Tx
, witnessUtxo :: Maybe TxOut
, partialSigs :: HashMap PubKeyI ByteString
, sigHashType :: Maybe SigHash
, inputRedeemScript :: Maybe Script
, inputWitnessScript :: Maybe Script
, inputHDKeypaths :: HashMap PubKeyI (Fingerprint, [KeyIndex])
, finalScriptSig :: Maybe Script
, finalScriptWitness :: Maybe WitnessStack
, inputUnknown :: UnknownMap
}
deriving (Show, Eq, Generic)
instance NFData Input
-- | Outputs can contain information needed to spend the output at a later date.
data Output = Output
{ outputRedeemScript :: Maybe Script
, outputWitnessScript :: Maybe Script
, outputHDKeypaths :: HashMap PubKeyI (Fingerprint, [KeyIndex])
, outputUnknown :: UnknownMap
}
deriving (Show, Eq, Generic)
instance NFData Output
{- | A map of raw PSBT keys to byte strings for extra data. The 'keyType' field
cannot overlap with any of the reserved 'keyType' fields specified in the
PSBT specification.
-}
newtype UnknownMap = UnknownMap {unknownMap :: HashMap Key ByteString}
deriving (Show, Eq, Semigroup, Monoid, Generic)
instance NFData UnknownMap
-- | Raw keys for the map type used in PSBTs.
data Key = Key
{ keyType :: Word8
, key :: ByteString
}
deriving (Show, Eq, Generic)
instance NFData Key
instance Hashable Key
{- | Take two 'PartiallySignedTransaction's and merge them. The
'unsignedTransaction' field in both must be the same.
-}
merge ::
PartiallySignedTransaction ->
PartiallySignedTransaction ->
Maybe PartiallySignedTransaction
merge psbt1 psbt2
| unsignedTransaction psbt1 == unsignedTransaction psbt2 =
Just $
psbt1
{ globalUnknown = globalUnknown psbt1 <> globalUnknown psbt2
, inputs = zipWith mergeInput (inputs psbt1) (inputs psbt2)
, outputs = zipWith mergeOutput (outputs psbt1) (outputs psbt2)
}
merge _ _ = Nothing
{- | A version of 'merge' for a collection of PSBTs.
@since 0.21.0
-}
mergeMany :: [PartiallySignedTransaction] -> Maybe PartiallySignedTransaction
mergeMany (psbt : psbts) = foldM merge psbt psbts
mergeMany _ = Nothing
mergeInput :: Input -> Input -> Input
mergeInput a b =
Input
{ nonWitnessUtxo =
if isJust witUtx
then Nothing
else nonWitnessUtxo a <|> nonWitnessUtxo b
, witnessUtxo =
witUtx
, sigHashType =
sigHashType a <|> sigHashType b
, partialSigs =
partialSigs a <> partialSigs b
, inputHDKeypaths =
inputHDKeypaths a <> inputHDKeypaths b
, inputUnknown =
inputUnknown a <> inputUnknown b
, inputRedeemScript =
inputRedeemScript a <|> inputRedeemScript b
, inputWitnessScript =
inputWitnessScript a <|> inputWitnessScript b
, finalScriptSig =
finalScriptSig a <|> finalScriptSig b
, finalScriptWitness =
finalScriptWitness a <|> finalScriptWitness b
}
where
witUtx = witnessUtxo a <|> witnessUtxo b
mergeOutput :: Output -> Output -> Output
mergeOutput a b =
Output
{ outputRedeemScript =
outputRedeemScript a <|> outputRedeemScript b
, outputWitnessScript =
outputWitnessScript a <|> outputWitnessScript b
, outputHDKeypaths =
outputHDKeypaths a <> outputHDKeypaths b
, outputUnknown =
outputUnknown a <> outputUnknown b
}
{- | A abstraction which covers varying key configurations. Use the 'Semigroup'
instance to create signers for sets of keys: `signerA <> signerB` can sign
anything for which `signerA` or `signerB` could sign.
@since 0.21@
-}
newtype PsbtSigner = PsbtSigner
{ unPsbtSigner ::
PubKeyI ->
Maybe (Fingerprint, DerivPath) ->
Maybe SecKey
}
instance Semigroup PsbtSigner where
PsbtSigner signer1 <> PsbtSigner signer2 =
PsbtSigner $ \pubKey origin ->
signer1 pubKey origin <|> signer2 pubKey origin
instance Monoid PsbtSigner where
mempty = PsbtSigner $ \_ _ -> Nothing
{- | Fetch the secret key for the given 'PubKeyI' if possible.
@since 0.21@
-}
getSignerKey :: PsbtSigner -> PubKeyI -> Maybe (Fingerprint, DerivPath) -> Maybe SecKey
getSignerKey = unPsbtSigner
{- | This signer can sign for one key.
@since 0.21@
-}
secKeySigner :: SecKey -> PsbtSigner
secKeySigner theSecKey = PsbtSigner signer
where
signer requiredKey _
| pubKeyPoint requiredKey == derivePubKey theSecKey = Just theSecKey
| otherwise = Nothing
{- | This signer can sign with any child key, provided that derivation information is present.
@since 0.21@
-}
xPrvSigner ::
XPrvKey ->
-- | Origin data, if the input key is explicitly a child key
Maybe (Fingerprint, DerivPath) ->
PsbtSigner
xPrvSigner xprv origin = PsbtSigner signer
where
signer pubKey (Just hdData)
| result@(Just theSecKey) <- maybe noOrigin onOrigin origin hdData
, pubKeyPoint pubKey == derivePubKey theSecKey =
result
signer _ _ = Nothing
noOrigin (fp, path)
| thisFP == fp = Just $ deriveSecKey path
| otherwise = Nothing
onOrigin (originFP, originPath) (fp, path)
| thisFP == fp = Just $ deriveSecKey path
| originFP == fp =
deriveSecKey <$> adjustPath (pathToList originPath) (pathToList path)
| otherwise = Nothing
deriveSecKey path = xPrvKey $ derivePath path xprv
thisFP = xPubFP $ deriveXPubKey xprv
-- The origin path should be a prefix of the target path if we match the
-- origin fingerprint. We need to remove this prefix.
adjustPath :: [KeyIndex] -> [KeyIndex] -> Maybe DerivPath
adjustPath (originIx : originTail) (thisIx : thisTail)
| originIx == thisIx = adjustPath originTail thisTail
| otherwise = Nothing
adjustPath [] thePath = Just $ listToPath thePath
adjustPath _ _ = Nothing
{- | Update a PSBT with signatures when possible. This function uses
'inputHDKeypaths' in order to calculate secret keys.
@since 0.21@
-}
signPSBT ::
Network ->
PsbtSigner ->
PartiallySignedTransaction ->
PartiallySignedTransaction
signPSBT net signer psbt =
psbt
{ inputs = addSigsForInput net signer tx <$> zip [0 :: Int ..] (inputs psbt)
}
where
tx = unsignedTransaction psbt
addSigsForInput :: Network -> PsbtSigner -> Tx -> (Int, Input) -> Input
addSigsForInput net signer tx (ix, input) =
maybe input (onPrevTxOut net signer tx ix input) $
Left <$> nonWitnessUtxo input <|> Right <$> witnessUtxo input
onPrevTxOut ::
Network ->
PsbtSigner ->
Tx ->
Int ->
Input ->
Either Tx TxOut ->
Input
onPrevTxOut net signer tx ix input prevTxData =
input
{ partialSigs = newSigs <> partialSigs input
}
where
newSigs = HM.mapWithKey sigForInput sigKeys
sigForInput thePubKey theSecKey =
encodeTxSig . makeSignature net tx ix theSigInput $
SecKeyI theSecKey (pubKeyCompressed thePubKey)
theSigInput =
SigInput
{ -- Must be the segwit input script for segwit spends (even nested)
sigInputScript = fromMaybe theInputScript segwitInput
, sigInputValue = outValue prevTxOut
, sigInputOP = thePrevOutPoint
, sigInputSH = fromMaybe sigHashAll $ sigHashType input
, -- Must be the witness script for segwit spends (even nested)
sigInputRedeem = theWitnessScript <|> theRedeemScript
}
prevTxOut = either ((!! (fromIntegral . outPointIndex) thePrevOutPoint) . txOut) id prevTxData
thePrevOutPoint = prevOutput $ txIn tx !! ix
segwitInput = justWhen isSegwit theInputScript <|> (justWhen isSegwit =<< theRedeemScript)
theInputScript = fromRight inputScriptErr $ (decodeOutputBS . scriptOutput) prevTxOut
inputScriptErr = error "addSigsForInput: Unable to decode input script"
theRedeemScript = case decodeOutput <$> inputRedeemScript input of
Just (Right script) -> Just script
Just Left{} -> error "addSigsForInput: Unable to decode redeem script"
_ -> Nothing
theWitnessScript = case decodeOutput <$> inputWitnessScript input of
Just (Right script) -> Just script
Just Left{} -> error "addSigsForInput: Unable to decode witness script"
_ -> Nothing
sigKeys = HM.mapMaybeWithKey getSignerKey $ inputHDKeypaths input
getSignerKey pubKey (fp, ixs) = unPsbtSigner signer pubKey $ Just (fp, listToPath ixs)
-- | Take partial signatures from all of the 'Input's and finalize the signature.
complete ::
PartiallySignedTransaction ->
PartiallySignedTransaction
complete psbt =
psbt
{ inputs =
map
(completeInput . analyzeInputs)
(indexed $ inputs psbt)
}
where
analyzeInputs (i, input) =
(,)
(outputScript =<< witnessUtxo input <|> nonWitScript)
input
where
nonWitScript = getPrevOut i =<< nonWitnessUtxo input
getPrevOut i tx =
(txOut tx !!?)
. fromIntegral
. outPointIndex
. prevOutput
=<< txIn (unsignedTransaction psbt) !!? i
xs !!? i = lookup i $ indexed xs
outputScript = eitherToMaybe . decodeOutputBS . scriptOutput
completeInput (Nothing, input) = input
completeInput (Just script, input) = pruneInputFields $ completeSig input script
-- If we have final scripts, we can get rid of data for signing following
-- the Bitcoin Core implementation.
pruneInputFields input
| isJust (finalScriptSig input) || isJust (finalScriptWitness input) =
input
{ partialSigs = mempty
, inputHDKeypaths = mempty
, inputRedeemScript = Nothing
, inputWitnessScript = Nothing
, sigHashType = Nothing
}
| otherwise = input
indexed :: [a] -> [(Word32, a)]
indexed = zip [0 ..]
completeSig :: Input -> ScriptOutput -> Input
completeSig input (PayPK k) =
input
{ finalScriptSig =
eitherToMaybe . runGetS deserialize
=<< HashMap.lookup k (partialSigs input)
}
completeSig input (PayPKHash h)
| [(k, sig)] <- HashMap.toList (partialSigs input)
, PubKeyAddress h == pubKeyAddr k =
input
{ finalScriptSig =
Just $
Script
[ opPushData sig
, opPushData (runPutS (serialize k))
]
}
completeSig input (PayMulSig pubKeys m)
| length sigs >= m =
input{finalScriptSig = Just finalSig}
where
sigs = collectSigs m pubKeys input
finalSig = Script $ OP_0 : map opPushData sigs
completeSig input (PayScriptHash h)
| Just rdmScript <- inputRedeemScript input
, PayScriptHash h == toP2SH rdmScript
, Right decodedScript <- decodeOutput rdmScript
, not (isPayScriptHash decodedScript) =
pushScript rdmScript $ completeSig input decodedScript
where
pushScript rdmScript updatedInput =
updatedInput
{ finalScriptSig =
Just $
fromMaybe (Script mempty) (finalScriptSig updatedInput)
`scriptAppend` serializedRedeemScript rdmScript
}
scriptAppend (Script script1) (Script script2) = Script $ script1 <> script2
completeSig input (PayWitnessPKHash h)
| [(k, sig)] <- HashMap.toList (partialSigs input)
, PubKeyAddress h == pubKeyAddr k =
input{finalScriptWitness = Just [sig, runPutS $ serialize k]}
completeSig input (PayWitnessScriptHash h)
| Just witScript <- inputWitnessScript input
, PayWitnessScriptHash h == toP2WSH witScript
, Right decodedScript <- decodeOutput witScript =
completeWitnessSig input decodedScript
completeSig input _ = input
serializedRedeemScript :: Script -> Script
serializedRedeemScript = Script . pure . opPushData . runPutS . serialize
completeWitnessSig :: Input -> ScriptOutput -> Input
completeWitnessSig input script@(PayMulSig pubKeys m)
| length sigs >= m =
input{finalScriptWitness = Just finalWit}
where
sigs = collectSigs m pubKeys input
finalWit = mempty : sigs <> [encodeOutputBS script]
completeWitnessSig input _ = input
collectSigs :: Int -> [PubKeyI] -> Input -> [ByteString]
collectSigs m pubKeys input =
take m . reverse $ foldl' lookupKey [] pubKeys
where
lookupKey sigs key =
maybe sigs (: sigs) $
HashMap.lookup key (partialSigs input)
{- | Take a finalized 'PartiallySignedTransaction' and produce the signed final
transaction. You may need to call 'complete' on the
'PartiallySignedTransaction' before producing the final transaction.
-}
finalTransaction :: PartiallySignedTransaction -> Tx
finalTransaction psbt =
setInputs
. foldl' finalizeInput ([], [])
$ zip (txIn tx) (inputs psbt)
where
tx = unsignedTransaction psbt
hasWitness =
any
(isJust . finalScriptWitness)
(inputs psbt)
setInputs (ins, witData) =
tx
{ txIn = reverse ins
, txWitness = if hasWitness then reverse witData else []
}
finalizeInput (ins, witData) (txInput, psbtInput) =
( txInput{scriptInput = maybe mempty (runPutS . serialize) $ finalScriptSig psbtInput} : ins
, fromMaybe [] (finalScriptWitness psbtInput) : witData
)
{- | Take an unsigned transaction and produce an empty
'PartiallySignedTransaction'
-}
emptyPSBT :: Tx -> PartiallySignedTransaction
emptyPSBT tx =
PartiallySignedTransaction
{ unsignedTransaction = tx
, globalUnknown = mempty
, inputs = replicate (length (txIn tx)) emptyInput
, outputs = replicate (length (txOut tx)) emptyOutput
}
emptyInput :: Input
emptyInput =
Input
Nothing
Nothing
HashMap.empty
Nothing
Nothing
Nothing
HashMap.empty
Nothing
Nothing
(UnknownMap HashMap.empty)
emptyOutput :: Output
emptyOutput = Output Nothing Nothing HashMap.empty (UnknownMap HashMap.empty)
instance Serialize PartiallySignedTransaction where
get = do
magic <- S.getBytes 4
guard $ magic == "psbt"
headerSep <- S.getWord8
guard $ headerSep == 0xff
keySize <- S.getWord8
guard $ keySize == 1
globalUnsignedTxType <- S.getWord8
guard $ globalUnsignedTxType == 0x00
unsignedTransaction <- getSizedBytes deserialize
guard $ all (B.null . scriptInput) (txIn unsignedTransaction)
guard $ null (txWitness unsignedTransaction)
globalUnknown <- S.get
globalEnd <- S.getWord8
guard $ globalEnd == 0x00
inputs <-
replicateM
(length (txIn unsignedTransaction))
S.get
outputs <-
replicateM
(length (txOut unsignedTransaction))
S.get
return
PartiallySignedTransaction
{ unsignedTransaction
, globalUnknown
, inputs
, outputs
}
put
PartiallySignedTransaction
{ unsignedTransaction
, globalUnknown
, inputs
, outputs
} = do
S.putByteString "psbt"
S.putWord8 0xff -- Header separator
S.putWord8 0x01 -- Key size
S.putWord8 0x00 -- Unsigned Transaction type
putSizedBytes $ serialize unsignedTransaction
S.put globalUnknown
S.putWord8 0x00 -- Global end
mapM_ S.put inputs
mapM_ S.put outputs
instance Serialize Key where
get = do
VarInt keySize <- deserialize
guard $ keySize > 0
t <- S.getWord8
k <- S.getBytes (fromIntegral keySize - 1)
return (Key t k)
put (Key t k) = do
putVarInt $ 1 + B.length k
S.putWord8 t
S.putByteString k
instance Serialize UnknownMap where
get = go HashMap.empty
where
getItem m = do
k <- S.get
VarString v <- deserialize
go $ HashMap.insert k v m
go m = do
isEnd <- S.lookAhead S.getWord8
if isEnd == 0x00
then return (UnknownMap m)
else getItem m
put (UnknownMap m) =
void $
HashMap.traverseWithKey
(\k v -> S.put k >> serialize (VarString v))
m
instance Serialize Input where
get =
getMap getInputItem setInputUnknown emptyInput
where
setInputUnknown f input =
input
{ inputUnknown =
UnknownMap (f (unknownMap (inputUnknown input)))
}
put
Input
{ nonWitnessUtxo
, witnessUtxo
, partialSigs
, sigHashType
, inputRedeemScript
, inputWitnessScript
, inputHDKeypaths
, finalScriptSig
, finalScriptWitness
, inputUnknown
} = do
whenJust
(putKeyValue InNonWitnessUtxo . serialize)
nonWitnessUtxo
whenJust
(putKeyValue InWitnessUtxo . serialize)
witnessUtxo
putPartialSig partialSigs
whenJust
putSigHash
sigHashType
whenJust
(putKeyValue InRedeemScript . serialize)
inputRedeemScript
whenJust
(putKeyValue InWitnessScript . serialize)
inputWitnessScript
putHDPath InBIP32Derivation inputHDKeypaths
whenJust
(putKeyValue InFinalScriptSig . serialize)
finalScriptSig
whenJust
(putKeyValue InFinalScriptWitness . putFinalScriptWitness)
finalScriptWitness
S.put inputUnknown
S.putWord8 0x00
where
putPartialSig =
putPubKeyMap serialize InPartialSig . fmap VarString
putSigHash sigHash = do
putKey InSigHashType
S.putWord8 0x04
S.putWord32le (fromIntegral sigHash)
putFinalScriptWitness witnessStack = do
S.put $ (VarInt . fromIntegral . length) witnessStack
mapM_ (serialize . VarString) witnessStack
instance Serialize Output where
get = getMap getOutputItem setOutputUnknown emptyOutput
where
setOutputUnknown f output =
output
{ outputUnknown =
UnknownMap (f (unknownMap (outputUnknown output)))
}
put
Output
{ outputRedeemScript
, outputWitnessScript
, outputHDKeypaths
, outputUnknown
} = do
whenJust
(putKeyValue OutRedeemScript . serialize)
outputRedeemScript
whenJust
(putKeyValue OutWitnessScript . serialize)
outputWitnessScript
putHDPath
OutBIP32Derivation
outputHDKeypaths
S.put outputUnknown
S.putWord8 0x00
putSizedBytes :: Put -> Put
putSizedBytes f = do
putVarInt (B.length bs)
S.putByteString bs
where
bs = S.runPut f
getSizedBytes :: Get a -> Get a
getSizedBytes =
S.getNested
(fromIntegral . getVarInt <$> deserialize)
putKeyValue :: Enum t => t -> Put -> Put
putKeyValue t v = do
putKey t
putSizedBytes v
putKey :: Enum t => t -> Put
putKey t = do
putVarInt (1 :: Word8)
S.putWord8 (enumWord8 t)
getMap ::
(Bounded t, Enum t) =>
(Int -> a -> t -> Get a) ->
((HashMap Key ByteString -> HashMap Key ByteString) -> a -> a) ->
a ->
Get a
getMap getMapItem setUnknown = go
where
getItem keySize m (Right t) =
getMapItem (fromIntegral keySize - 1) m t >>= go
getItem keySize m (Left t) = do
k <- S.getBytes (fromIntegral keySize - 1)
VarString v <- deserialize
go $ setUnknown (HashMap.insert (Key t k) v) m
go m = do
keySize <- getVarInt <$> deserialize
if keySize == 0
then return m
else getItem keySize m . word8Enum =<< S.getWord8
data InputType
= InNonWitnessUtxo
| InWitnessUtxo
| InPartialSig
| InSigHashType
| InRedeemScript
| InWitnessScript
| InBIP32Derivation
| InFinalScriptSig
| InFinalScriptWitness
deriving (Show, Eq, Enum, Bounded, Generic)
instance NFData InputType
data OutputType
= OutRedeemScript
| OutWitnessScript
| OutBIP32Derivation
deriving (Show, Eq, Enum, Bounded, Generic)
instance NFData OutputType
getInputItem :: Int -> Input -> InputType -> Get Input
getInputItem 0 input@Input{nonWitnessUtxo = Nothing} InNonWitnessUtxo = do
utxo <- getSizedBytes deserialize
return input{nonWitnessUtxo = Just utxo}
getInputItem 0 input@Input{witnessUtxo = Nothing} InWitnessUtxo = do
utxo <- getSizedBytes deserialize
return input{witnessUtxo = Just utxo}
getInputItem keySize input InPartialSig = do
(k, v) <- getPartialSig
return
input
{ partialSigs = HashMap.insert k v (partialSigs input)
}
where
getPartialSig =
(,)
<$> S.isolate keySize deserialize
<*> (getVarString <$> deserialize)
getInputItem 0 input@Input{sigHashType = Nothing} InSigHashType = do
VarInt size <- deserialize
guard $ size == 0x04
sigHash <- fromIntegral <$> S.getWord32le
return $ input{sigHashType = Just sigHash}
getInputItem 0 input@Input{inputRedeemScript = Nothing} InRedeemScript = do
script <- getSizedBytes deserialize
return $ input{inputRedeemScript = Just script}
getInputItem 0 input@Input{inputWitnessScript = Nothing} InWitnessScript = do
script <- getSizedBytes deserialize
return $ input{inputWitnessScript = Just script}
getInputItem keySize input InBIP32Derivation = do
(k, v) <- getHDPath keySize
return
input
{ inputHDKeypaths = HashMap.insert k v (inputHDKeypaths input)
}
getInputItem 0 input@Input{finalScriptSig = Nothing} InFinalScriptSig = do
script <- getSizedBytes deserialize
return $ input{finalScriptSig = Just script}
getInputItem 0 input@Input{finalScriptWitness = Nothing} InFinalScriptWitness = do
scripts <- map getVarString <$> getVarIntList
return $ input{finalScriptWitness = Just scripts}
where
getVarIntList = getSizedBytes $ do
VarInt n <- deserialize -- Item count
replicateM (fromIntegral n) deserialize
getInputItem keySize input inputType =
fail $
"Incorrect key size for input item or item already existed: "
<> show (keySize, input, inputType)
getOutputItem :: Int -> Output -> OutputType -> Get Output
getOutputItem 0 output@Output{outputRedeemScript = Nothing} OutRedeemScript = do
script <- getSizedBytes deserialize
return $ output{outputRedeemScript = Just script}
getOutputItem 0 output@Output{outputWitnessScript = Nothing} OutWitnessScript = do
script <- getSizedBytes deserialize
return $ output{outputWitnessScript = Just script}
getOutputItem keySize output OutBIP32Derivation = do
(k, v) <- getHDPath keySize
return $ output{outputHDKeypaths = HashMap.insert k v (outputHDKeypaths output)}
getOutputItem keySize output outputType =
fail $
"Incorrect key size for output item or item already existed: "
<> show (keySize, output, outputType)
getHDPath :: Int -> Get (PubKeyI, (Fingerprint, [KeyIndex]))
getHDPath keySize =
(,)
<$> S.isolate keySize deserialize
<*> (unPSBTHDPath <$> S.get)
putHDPath :: Enum t => t -> HashMap PubKeyI (Fingerprint, [KeyIndex]) -> Put
putHDPath t = putPubKeyMap S.put t . fmap PSBTHDPath
newtype PSBTHDPath = PSBTHDPath {unPSBTHDPath :: (Fingerprint, [KeyIndex])}
deriving (Show, Eq, Generic)
instance NFData PSBTHDPath
instance Serialize PSBTHDPath where
get = do
VarInt valueSize <- deserialize
guard $ valueSize `mod` 4 == 0
let numIndices = (fromIntegral valueSize - 4) `div` 4
PSBTHDPath
<$> S.isolate
(fromIntegral valueSize)
((,) <$> S.get <*> getKeyIndexList numIndices)
where
getKeyIndexList n = replicateM n S.getWord32le
put (PSBTHDPath (fp, kis)) = do
putVarInt (B.length bs)
S.putByteString bs
where
bs = S.runPut $ S.put fp >> mapM_ S.putWord32le kis
putPubKeyMap :: Enum t => (a -> Put) -> t -> HashMap PubKeyI a -> Put
putPubKeyMap f t =
void . HashMap.traverseWithKey putItem
where
putItem k v = do
S.put $ Key (enumWord8 t) (runPutS (serialize k))
f v
enumWord8 :: Enum a => a -> Word8
enumWord8 = fromIntegral . fromEnum
word8Enum :: forall a. (Bounded a, Enum a) => Word8 -> Either Word8 a
word8Enum n | n <= enumWord8 (maxBound :: a) = Right . toEnum $ fromIntegral n
word8Enum n = Left n
whenJust :: Monad m => (a -> m ()) -> Maybe a -> m ()
whenJust = maybe (return ())
justWhen :: (a -> Bool) -> a -> Maybe a
justWhen test x = if test x then Just x else Nothing