haskoin-core-0.21.0: src/Haskoin/Transaction/Segwit.hs
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TupleSections #-}
{- |
Module : Haskoin.Transaction.Segwit
Copyright : No rights reserved
License : MIT
Maintainer : jprupp@protonmail.ch
Stability : experimental
Portability : POSIX
Types to represent segregated witness data and auxilliary functions to
manipulate it. See [BIP 141](https://github.com/bitcoin/bips/blob/master/bip-0141.mediawiki)
and [BIP 143](https://github.com/bitcoin/bips/blob/master/bip-0143.mediawiki) for
details.
-}
module Haskoin.Transaction.Segwit (
-- * Segwit
WitnessProgram (..),
WitnessProgramPKH (..),
WitnessProgramSH (..),
isSegwit,
viewWitnessProgram,
decodeWitnessInput,
calcWitnessProgram,
simpleInputStack,
toWitnessStack,
) where
import Data.ByteString (ByteString)
import Data.Bytes.Get
import Data.Bytes.Put
import Data.Bytes.Serial
import Haskoin.Data
import Haskoin.Keys.Common
import Haskoin.Script
import Haskoin.Transaction.Common
{- | Test if a 'ScriptOutput' is P2WPKH or P2WSH
@since 0.11.0.0
-}
isSegwit :: ScriptOutput -> Bool
isSegwit = \case
PayWitnessPKHash{} -> True
PayWitnessScriptHash{} -> True
_ -> False
{- | High level represenation of a (v0) witness program
@since 0.11.0.0
-}
data WitnessProgram
= P2WPKH WitnessProgramPKH
| P2WSH WitnessProgramSH
| EmptyWitnessProgram
deriving (Eq, Show)
{- | Encode a witness program
@since 0.11.0.0
-}
toWitnessStack :: WitnessProgram -> WitnessStack
toWitnessStack = \case
P2WPKH (WitnessProgramPKH sig key) -> [encodeTxSig sig, runPutS (serialize key)]
P2WSH (WitnessProgramSH stack scr) -> stack <> [runPutS (serialize scr)]
EmptyWitnessProgram -> mempty
{- | High level representation of a P2WPKH witness
@since 0.11.0.0
-}
data WitnessProgramPKH = WitnessProgramPKH
{ witnessSignature :: !TxSignature
, witnessPubKey :: !PubKeyI
}
deriving (Eq, Show)
{- | High-level representation of a P2WSH witness
@since 0.11.0.0
-}
data WitnessProgramSH = WitnessProgramSH
{ witnessScriptHashStack :: ![ByteString]
, witnessScriptHashScript :: !Script
}
deriving (Eq, Show)
{- | Calculate the witness program from the transaction data
@since 0.11.0.0
-}
viewWitnessProgram ::
Network -> ScriptOutput -> WitnessStack -> Either String WitnessProgram
viewWitnessProgram net so witness = case so of
PayWitnessPKHash _ | length witness == 2 -> do
sig <- decodeTxSig net $ head witness
pubkey <- runGetS deserialize $ witness !! 1
return . P2WPKH $ WitnessProgramPKH sig pubkey
PayWitnessScriptHash _ | not (null witness) -> do
redeemScript <- runGetS deserialize $ last witness
return . P2WSH $ WitnessProgramSH (init witness) redeemScript
_
| null witness -> return EmptyWitnessProgram
| otherwise -> Left "viewWitnessProgram: Invalid witness program"
{- | Analyze the witness, trying to match it with standard input structures
@since 0.11.0.0
-}
decodeWitnessInput ::
Network ->
WitnessProgram ->
Either String (Maybe ScriptOutput, SimpleInput)
decodeWitnessInput net = \case
P2WPKH (WitnessProgramPKH sig key) -> return (Nothing, SpendPKHash sig key)
P2WSH (WitnessProgramSH st scr) -> do
so <- decodeOutput scr
fmap (Just so,) $ case (so, st) of
(PayPK _, [sigBS]) ->
SpendPK <$> decodeTxSig net sigBS
(PayPKHash _, [sigBS, keyBS]) ->
SpendPKHash <$> decodeTxSig net sigBS <*> runGetS deserialize keyBS
(PayMulSig _ _, "" : sigsBS) ->
SpendMulSig <$> traverse (decodeTxSig net) sigsBS
_ -> Left "decodeWitnessInput: Non-standard script output"
EmptyWitnessProgram -> Left "decodeWitnessInput: Empty witness program"
{- | Create the witness program for a standard input
@since 0.11.0.0
-}
calcWitnessProgram :: ScriptOutput -> ScriptInput -> Either String WitnessProgram
calcWitnessProgram so si = case (so, si) of
(PayWitnessPKHash{}, RegularInput (SpendPKHash sig pk)) -> p2wpkh sig pk
(PayScriptHash{}, RegularInput (SpendPKHash sig pk)) -> p2wpkh sig pk
(PayWitnessScriptHash{}, ScriptHashInput i o) -> p2wsh i o
(PayScriptHash{}, ScriptHashInput i o) -> p2wsh i o
_ -> Left "calcWitnessProgram: Invalid segwit SigInput"
where
p2wpkh sig = return . P2WPKH . WitnessProgramPKH sig
p2wsh i o = return . P2WSH $ WitnessProgramSH (simpleInputStack i) (encodeOutput o)
{- | Create the witness stack required to spend a standard P2WSH input
@since 0.11.0.0
-}
simpleInputStack :: SimpleInput -> [ByteString]
simpleInputStack = \case
SpendPK sig -> [f sig]
SpendPKHash sig k -> [f sig, runPutS (serialize k)]
SpendMulSig sigs -> "" : fmap f sigs
where
f TxSignatureEmpty = ""
f sig = encodeTxSig sig