packages feed

bitcoin-address-0.1: lib/Bitcoin/Address/Script.hs

-- | This module exports standard Bitcoin 'S.Script' constructions.
module Bitcoin.Address.Script
  ( -- * Scripts
    p2pkh
  , p2sh
  , segWit
  , multiSig
  ) where

import Bitcoin.Keys (Pub, pubCompressed)
import Control.Monad
import qualified Data.Bitcoin.Script as S

import Bitcoin.Address.Hash
import Bitcoin.Address.Internal (op0to16)
import qualified Bitcoin.Address.SegWit as SW

--------------------------------------------------------------------------------

-- | Standard 'Bitcoin.Address.P2PKH' script.
--
-- [pubh]: 'PubHash160'.
--
-- Script:
--
-- @
-- 'S.OP_DUP'
-- 'S.OP_HASH160'
-- 'S.OP_PUSHDATA' pubh 'S.OPCODE'
-- 'S.OP_EQUALVERIFY'
-- 'S.OP_CHECKSIG'
-- @
p2pkh :: PubHash160 -> S.Script
p2pkh pkh = S.Script
  [ S.OP_DUP
  , S.OP_HASH160
  , S.OP_PUSHDATA (unPubHash160 pkh) S.OPCODE
  , S.OP_EQUALVERIFY
  , S.OP_CHECKSIG
  ]

-- | Standard 'Bitcoin.Address.P2SH' script.
--
-- [sh]: 'ScriptHash160'.
--
-- Script:
--
-- @
-- 'S.OP_HASH160'
-- 'S.OP_PUSHDATA' sh 'S.OPCODE'
-- 'S.OP_EQUAL'
-- @
p2sh :: ScriptHash160 -> S.Script
p2sh sh = S.Script
  [ S.OP_HASH160
  , S.OP_PUSHDATA (unScriptHash160 sh) S.OPCODE
  , S.OP_EQUAL
  ]

-- | Standard SegWit 'Bitcoin.Address.Program' script.
--
-- [ver]: SegWit 'SW.versionOp'
--
-- [prog]: SegWit 'SW.programData'
--
-- Script:
--
-- @
-- ver
-- 'S.OP_PUSHDATA' prog 'S.OPCODE'
-- @
segWit :: SW.Program -> S.Script
segWit swp = S.Script
  [ SW.versionOp (SW.programVersion swp)
  , S.OP_PUSHDATA (SW.programData swp ) S.OPCODE
  ]

-- | Standard “m-of-n” multi-signature script.
--
-- [m]: Number of required signatures in range ['S.OP_1' … 'S.OP_16']
--
-- [n]: Number of 'Pub'lic keys given in range ['S.OP_1' … 'S.OP_16']
--
-- [pubs]: Compressed SEC representation the given 'Pub'lic keys, each one
-- (@pub@) encoded as @'S.OP_PUSHDATA' ('pubCompressed' pub) 'S.OPCODE'@.
--
-- Script:
--
-- @
-- m
-- pubs
-- n
-- 'S.OP_CHECKMULTISIG'
-- @
multiSig
  :: [Pub] -- ^ Public keys. Total number in range [1 … 16]
  -> Int   -- ^ Required number of signatures in range [1, 16].
  -> Maybe S.Script -- ^ 'Nothing' if any of the inputs is invalid.
multiSig pks req = do
  let len = length pks
  guard (req >= 1 && req <= 16 && req <= len)
  opLen <- op0to16 len
  opReq <- op0to16 req
  pure $ S.Script $ mconcat
    [ [ opReq ]
    , do pk <- pks
         pure $ S.OP_PUSHDATA (pubCompressed pk) S.OPCODE
    , [ opLen, S.OP_CHECKMULTISIG ] ]