bitcoin-scripting-0.1.0: src/Language/Bitcoin/Script/Descriptors/Parser.hs
{-# LANGUAGE OverloadedStrings #-}
module Language.Bitcoin.Script.Descriptors.Parser (
parseDescriptor,
descriptorParser,
parseKeyDescriptor,
keyDescriptorParser,
) where
import Control.Applicative (optional, (<|>))
import Data.Attoparsec.Text (Parser)
import qualified Data.Attoparsec.Text as A
import Data.Bool (bool)
import qualified Data.ByteString as BS
import Data.Maybe (isJust)
import Data.Text (Text, pack)
import Haskoin.Address (textToAddr)
import Haskoin.Constants (Network)
import Haskoin.Keys (
DerivPath,
DerivPathI (..),
fromWif,
importPubKey,
wrapPubKey,
xPubImport,
)
import Language.Bitcoin.Script.Descriptors.Syntax
import Language.Bitcoin.Utils (
alphanum,
application,
argList,
brackets,
comma,
hex,
maybeFail,
)
parseDescriptor :: Network -> Text -> Either String ScriptDescriptor
parseDescriptor net = A.parseOnly $ descriptorParser net
descriptorParser :: Network -> Parser ScriptDescriptor
descriptorParser net =
shP <|> wshP <|> pkP <|> pkhP <|> wpkhP <|> comboP <|> rawP <|> addrP
<|> multiP
<|> sortedMultiP
where
dp = descriptorParser net
kp = keyDescriptorParser net
shP = Sh <$> application "sh" dp
wshP = Wsh <$> application "wsh" dp
pkP = Pk <$> application "pk" kp
pkhP = Pkh <$> application "pkh" kp
wpkhP = Wpkh <$> application "wpkh" kp
comboP = Combo <$> application "combo" kp
rawP = Raw <$> application "raw" hex
addrP =
application "addr" (A.manyTill A.anyChar $ A.char ')')
>>= maybeFail "descriptorParser: unable to parse address" Addr . textToAddr net . pack
multiP = application "multi" $ Multi <$> A.decimal <*> comma keyList
sortedMultiP = application "sortedmulti" $ SortedMulti <$> A.decimal <*> comma keyList
keyList = argList kp
parseKeyDescriptor :: Network -> Text -> Either String KeyDescriptor
parseKeyDescriptor net = A.parseOnly $ keyDescriptorParser net
keyDescriptorParser :: Network -> Parser KeyDescriptor
keyDescriptorParser net = KeyDescriptor <$> originP <*> keyP
where
originP = optional . brackets $ Origin <$> A.hexadecimal <*> pathP
keyP = pubP <|> wifP <|> XPub <$> xpubP <*> pathP <*> famP
pubP = do
bs <- hex
maybeFail "Unable to parse pubkey" (toPubKey bs) $ importPubKey bs
toPubKey bs = Pubkey . wrapPubKey (isCompressed bs)
isCompressed bs = BS.length bs == 33
wifP = A.many1' alphanum >>= maybeFail "Unable to parse WIF secret key" SecretKey . fromWif net . pack
xpubP = A.many1' alphanum >>= maybeFail "Unable to parse xpub" id . xPubImport net . pack
famP = (HardKeys <$ A.string "/*'") <|> (SoftKeys <$ A.string "/*") <|> pure Single
pathP :: Parser DerivPath
pathP = go Deriv
where
go d = maybe (return d) go =<< optional (componentP d)
componentP d = do
_ <- A.char '/'
n <- A.decimal
isHard <- isJust <$> optional (A.char '\'' <|> A.char 'h')
return $ bool (d :/) (d :|) isHard n