{-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE OverloadedStrings #-} {-| Module : Network.Haskoin.Address Copyright : No rights reserved License : UNLICENSE Maintainer : xenog@protonmail.com Stability : experimental Portability : POSIX Base58, CashAddr, Bech32 address and WIF private key serialization support. -} module Network.Haskoin.Address ( Address , getAddrHash160 , getAddrHash256 , getAddrNet , isPubKeyAddress , isScriptAddress , isWitnessPubKeyAddress , isWitnessScriptAddress , addrToString , stringToAddr , addrFromJSON , pubKeyAddr , pubKeyWitnessAddr , p2pkhAddr , p2wpkhAddr , p2shAddr , p2wshAddr -- * Private Key Wallet Import Format (WIF) , fromWif , toWif ) where import Control.Applicative import Control.DeepSeq import Control.Monad import Data.Aeson as A import Data.Aeson.Types import qualified Data.ByteString as B import Data.Function import Data.Hashable import Data.List import Data.Maybe import Data.Serialize as S import Data.String.Conversions import Data.Text (Text) import GHC.Generics as G (Generic) import Network.Haskoin.Address.Base58 import Network.Haskoin.Address.Bech32 import Network.Haskoin.Address.CashAddr import Network.Haskoin.Constants import Network.Haskoin.Crypto import Network.Haskoin.Keys.Common import Network.Haskoin.Util import Text.Read as R -- | Address format for Bitcoin and Bitcoin Cash. data Address -- | pay to public key hash (regular) = PubKeyAddress { getAddrHash160 :: !Hash160 -- ^ RIPEMD160 hash of public key's SHA256 hash , getAddrNet :: !Network -- ^ address network } -- | pay to script hash | ScriptAddress { getAddrHash160 :: !Hash160 -- ^ RIPEMD160 hash of script's SHA256 hash , getAddrNet :: !Network -- ^ address network } -- | pay to witness public key hash | WitnessPubKeyAddress { getAddrHash160 :: !Hash160 -- ^ RIPEMD160 hash of public key's SHA256 hash , getAddrNet :: !Network -- ^ address network } -- | pay to witness script hash | WitnessScriptAddress { getAddrHash256 :: !Hash256 -- ^ HASH256 hash of script , getAddrNet :: !Network -- ^ address network } deriving (Eq, G.Generic) instance Hashable Address where hashWithSalt i (PubKeyAddress h _) = i `hashWithSalt` h hashWithSalt i (ScriptAddress h _) = i `hashWithSalt` h hashWithSalt i (WitnessPubKeyAddress h _) = i `hashWithSalt` h hashWithSalt i (WitnessScriptAddress h _) = i `hashWithSalt` h hash (PubKeyAddress h _) = hash h hash (ScriptAddress h _) = hash h hash (WitnessPubKeyAddress h _) = hash h hash (WitnessScriptAddress h _) = hash h instance Ord Address where compare = compare `on` f where f (PubKeyAddress h _) = S.encode h f (ScriptAddress h _) = S.encode h f (WitnessPubKeyAddress h _) = S.encode h f (WitnessScriptAddress h _) = S.encode h instance NFData Address -- | 'Address' pays to a public key hash. isPubKeyAddress :: Address -> Bool isPubKeyAddress PubKeyAddress {} = True isPubKeyAddress _ = False -- | 'Address' pays to a script hash. isScriptAddress :: Address -> Bool isScriptAddress ScriptAddress {} = True isScriptAddress _ = False -- | 'Address' pays to a witness public key hash. Only valid for SegWit -- networks. isWitnessPubKeyAddress :: Address -> Bool isWitnessPubKeyAddress WitnessPubKeyAddress {} = True isWitnessPubKeyAddress _ = False -- | 'Address' pays to a witness script hash. Only valid for SegWit networks. isWitnessScriptAddress :: Address -> Bool isWitnessScriptAddress WitnessScriptAddress {} = True isWitnessScriptAddress _ = False -- | Deserializer for binary 'Base58' addresses. base58get :: Network -> Get Address base58get net = do pfx <- getWord8 addr <- S.get f pfx addr where f x a | x == getAddrPrefix net = return (PubKeyAddress a net) | x == getScriptPrefix net = return (ScriptAddress a net) | otherwise = fail "Does not recognize address prefix" -- | Binary serializer for 'Base58' addresses. base58put :: Putter Address base58put (PubKeyAddress h net) = do putWord8 (getAddrPrefix net) put h base58put (ScriptAddress h net) = do putWord8 (getScriptPrefix net) put h base58put _ = error "Cannot serialize this address as Base58" instance Show Address where showsPrec _ a = shows (addrToString a) instance Read Address where readPrec = do R.String str <- lexP let bs = cs str maybe pfail return $ foldl' (\a n -> a <|> stringToAddr n bs) Nothing allNets instance ToJSON Address where toJSON = A.String . addrToString -- | JSON parsing for Bitcoin addresses. Works with 'Base58', 'CashAddr' and -- 'Bech32'. addrFromJSON :: Network -> Value -> Parser Address addrFromJSON net = withText "address" $ \t -> case stringToAddr net t of Nothing -> fail "could not decode address" Just x -> return x -- | Convert address to human-readable string. Uses 'Base58', 'Bech32', or -- 'CashAddr' depending on network. addrToString :: Address -> Text addrToString a@PubKeyAddress {getAddrHash160 = h, getAddrNet = net} | isNothing (getCashAddrPrefix net) = encodeBase58Check $ runPut $ base58put a | otherwise = fromMaybe (error "Colud not encode a CashAddr") $ cashAddrEncode net 0 (S.encode h) addrToString a@ScriptAddress {getAddrHash160 = h, getAddrNet = net} | isNothing (getCashAddrPrefix net) = encodeBase58Check $ runPut $ base58put a | otherwise = fromMaybe (error "Could not encode a CashAddr") $ cashAddrEncode net 1 (S.encode h) addrToString WitnessPubKeyAddress {getAddrHash160 = h, getAddrNet = net} = let mt = do hrp <- getBech32Prefix net segwitEncode hrp 0 (B.unpack (S.encode h)) in fromMaybe (error "Could not encode a Bech32 address") mt addrToString WitnessScriptAddress {getAddrHash256 = h, getAddrNet = net} = let mt = do hrp <- getBech32Prefix net segwitEncode hrp 0 (B.unpack (S.encode h)) in fromMaybe (error "Could not encode a Bech32 address") mt -- | Parse 'Base58', 'Bech32' or 'CashAddr' address, depending on network. stringToAddr :: Network -> Text -> Maybe Address stringToAddr net bs = cash <|> segwit <|> b58 where b58 = eitherToMaybe . runGet (base58get net) =<< decodeBase58Check bs cash = cashAddrDecode net bs >>= \(ver, bs') -> case ver of 0 -> do h <- eitherToMaybe (S.decode bs') return $ PubKeyAddress h net 1 -> do h <- eitherToMaybe (S.decode bs') return $ ScriptAddress h net _ -> Nothing segwit = do hrp <- getBech32Prefix net (ver, bs') <- segwitDecode hrp bs guard (ver == 0) let bs'' = B.pack bs' case B.length bs'' of 20 -> do h <- eitherToMaybe (S.decode bs'') return $ WitnessPubKeyAddress h net 32 -> do h <- eitherToMaybe (S.decode bs'') return $ WitnessScriptAddress h net _ -> Nothing -- | Obtain a P2PKH address from a public key. pubKeyAddr :: Network -> PubKeyI -> Address pubKeyAddr net k = PubKeyAddress (addressHash (S.encode k)) net -- | Obtain a P2PKH address from a 'Hash160'. p2pkhAddr :: Network -> Hash160 -> Address p2pkhAddr net h = PubKeyAddress h net -- | Obtain a P2WPKH address from a public key. Only on SegWit networks. pubKeyWitnessAddr :: Network -> PubKeyI -> Maybe Address pubKeyWitnessAddr net k | getSegWit net = Just $ WitnessPubKeyAddress (addressHash (S.encode k)) net | otherwise = Nothing -- | Obtain a P2WPKH address from a 'Hash160'. p2wpkhAddr :: Network -> Hash160 -> Maybe Address p2wpkhAddr net h | getSegWit net = Just $ WitnessPubKeyAddress h net | otherwise = Nothing -- | Obtain a P2SH address from a 'Hash160'. p2shAddr :: Network -> Hash160 -> Address p2shAddr net h = ScriptAddress h net -- | Obtain a P2WSH address from a 'Hash256' p2wshAddr :: Network -> Hash256 -> Maybe Address p2wshAddr net h | getSegWit net = Just $ WitnessScriptAddress h net | otherwise = Nothing -- | Decode private key from WIF (wallet import format) string. fromWif :: Network -> Base58 -> Maybe SecKeyI fromWif net wif = do bs <- decodeBase58Check wif -- Check that this is a private key guard (B.head bs == getSecretPrefix net) case B.length bs of -- Uncompressed format 33 -> wrapSecKey False <$> secKey (B.tail bs) -- Compressed format 34 -> do guard $ B.last bs == 0x01 wrapSecKey True <$> secKey (B.tail $ B.init bs) -- Bad length _ -> Nothing -- | Encode private key into a WIF string. toWif :: Network -> SecKeyI -> Base58 toWif net (SecKeyI k c) = encodeBase58Check . B.cons (getSecretPrefix net) $ if c then getSecKey k `B.snoc` 0x01 else getSecKey k