packages feed

haskoin-core-1.2.2: src/Haskoin/Transaction/Segwit.hs

{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE NoFieldSelectors #-}

-- |
-- 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 Crypto.Secp256k1
import Data.ByteString (ByteString)
import Data.Bytes.Get (runGetS)
import Data.Bytes.Put (runPutS)
import Data.Bytes.Serial (Serial (deserialize, serialize))
import Haskoin.Crypto.Keys.Common
import Haskoin.Network.Data
import Haskoin.Script.Common
import Haskoin.Script.SigHash
import Haskoin.Script.Standard
import Haskoin.Transaction.Common
import Haskoin.Util.Marshal

-- | 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)

-- | Encode a witness program
--
-- @since 0.11.0.0
toWitnessStack :: Network -> Ctx -> WitnessProgram -> WitnessStack
toWitnessStack net ctx = \case
  P2WPKH (WitnessProgramPKH sig key) ->
    [encodeTxSig net ctx sig, marshal ctx 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
  { signature :: !TxSignature,
    key :: !PublicKey
  }
  deriving (Eq)

-- | High-level representation of a P2WSH witness
--
-- @since 0.11.0.0
data WitnessProgramSH = WitnessProgramSH
  { stack :: ![ByteString],
    script :: !Script
  }
  deriving (Eq, Show)

-- | Calculate the witness program from the transaction data
--
-- @since 0.11.0.0
viewWitnessProgram ::
  Network ->
  Ctx ->
  ScriptOutput ->
  WitnessStack ->
  Either String WitnessProgram
viewWitnessProgram net ctx so witness = case so of
  PayWitnessPKHash _ | length witness == 2 -> do
    sig <- decodeTxSig net ctx (head witness)
    pubkey <- unmarshal ctx $ 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 ->
  Ctx ->
  WitnessProgram ->
  Either String (Maybe ScriptOutput, SimpleInput)
decodeWitnessInput net ctx = \case
  P2WPKH (WitnessProgramPKH sig key) -> return (Nothing, SpendPKHash sig key)
  P2WSH (WitnessProgramSH st scr) -> do
    so <- decodeOutput ctx scr
    fmap (Just so,) $ case (so, st) of
      (PayPK _, [sigBS]) ->
        SpendPK <$> decodeTxSig net ctx sigBS
      (PayPKHash _, [sigBS, keyBS]) ->
        SpendPKHash
          <$> decodeTxSig net ctx sigBS
          <*> unmarshal ctx keyBS
      (PayMulSig _ _, "" : sigsBS) ->
        SpendMulSig
          <$> traverse (decodeTxSig net ctx) 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 ::
  Network ->
  Ctx ->
  ScriptOutput ->
  ScriptInput ->
  Either String WitnessProgram
calcWitnessProgram net ctx so si = case (so, si) of
  (PayWitnessPKHash {}, RegularInput (SpendPKHash sig pk)) ->
    Right $ p2wpkh sig pk
  (PayScriptHash {}, RegularInput (SpendPKHash sig pk)) ->
    Right $ p2wpkh sig pk
  (PayWitnessScriptHash {}, ScriptHashInput i o) ->
    Right $ p2wsh i o
  (PayScriptHash {}, ScriptHashInput i o) ->
    Right $ p2wsh i o
  _ -> Left "calcWitnessProgram: Invalid segwit SigInput"
  where
    p2wpkh sig =
      P2WPKH . WitnessProgramPKH sig
    p2wsh i =
      P2WSH . WitnessProgramSH (simpleInputStack net ctx i) . encodeOutput ctx

-- | Create the witness stack required to spend a standard P2WSH input
--
-- @since 0.11.0.0
simpleInputStack :: Network -> Ctx -> SimpleInput -> [ByteString]
simpleInputStack net ctx = \case
  SpendPK sig -> [f sig]
  SpendPKHash sig k -> [f sig, marshal ctx k]
  SpendMulSig sigs -> "" : fmap f sigs
  where
    f TxSignatureEmpty = ""
    f sig = encodeTxSig net ctx sig