{-# LANGUAGE StrictData #-} -- | Bitcoin address generation and rendering. Parsing is comming soon. -- -- Most of what you'll normally need for safely dealing with Bitcoin addresses -- is exported from this module. module Bitcoin.Address ( -- * Network settings btc , btcTestnet -- * Addresses , Address(..) , renderAddress , addressScript -- ** P2PKH , p2pkh -- ** P2SH , p2sh , p2sh_multiSig , p2sh_p2wpkh , p2sh_p2wsh , p2sh_p2wsh_multiSig -- ** SegWit P2WPKH , p2wpkh -- ** SegWit P2WSH , p2wsh , p2wsh_multiSig ) where import Bitcoin.Keys (Pub) import Bitcoin.Hash (check32) import qualified Data.Bitcoin.Script as S import qualified Data.ByteString as B import qualified Data.ByteString.Char8 as B8 import qualified Data.ByteString.Base58 as B58 import Bitcoin.Address.Hash import qualified Bitcoin.Address.Script as XS import qualified Bitcoin.Address.SegWit as SW import Bitcoin.Address.Settings -------------------------------------------------------------------------------- -- | A Bitcoin compatible address. -- -- These constructors are safe to use. However, you may find the similarly -- named helper functions in "Bitcoin.Address" more practical. data Address = P2PKH PrefixP2PKH PubHash160 -- ^ A “pay to public-key hash” address. See 'p2pkh'. | P2SH PrefixP2SH ScriptHash160 -- ^ A “pay to script hash” address. See 'p2sh', 'p2sh_multiSig', -- 'p2sh_p2wpkh', 'p2sh_p2wsh_multiSig'. | SegWit PrefixSegWit SW.Program -- ^ A native SegWit address. See 'p2wpkh', 'p2wsh', 'p2wsh_multiSig'. deriving (Eq, Ord) -- | As 'renderAddress'. instance Show Address where show = B8.unpack . renderAddress -------------------------------------------------------------------------------- -- P2PKH -- | 'P2PKH' 'Address' for a 'Pub'lic key. -- -- This is the address format associated with BIP-0032 -- derivation path @m\/44'\/0'@ p2pkh :: Settings -> Pub -> Address p2pkh sett = P2PKH (settings_prefixP2PKH sett) . pubHash160 -------------------------------------------------------------------------------- -- P2SH -- | 'P2SH' 'Address' for a 'S.Script'. -- -- See 'p2sh_p2wpkh', 'p2sh_p2wpkh' or 'p2sh_p2wsh_multiSig' for some of -- the typical constructions using 'p2sh'. It's unlikely that you'll need to use -- 'p2sh' directly unless you are deploying a non-standard 'S.Script'. p2sh :: Settings -> S.Script -> Address p2sh sett = P2SH (settings_prefixP2SH sett) . scriptHash160 -- | 'P2SH' “P2WPKH-in-'P2SH'” 'Address' for a 'Pub'lic key. -- -- This is the address format associated with BIP-0032 -- derivation path @m\/49'\/0'@ p2sh_p2wpkh :: Settings -> Pub -> Address p2sh_p2wpkh sett = p2sh sett . XS.segWit . SW.p2wpkh . pubHash160 -- | 'P2SH' 'Address' for a standard m-of-n 'XS.multiSig' 'S.Script'. p2sh_multiSig :: Settings -> [Pub] -- ^ Public keys. Total number in range [1 … 16] -> Int -- ^ Required number of signatures in range [1 … 16] -> Maybe Address -- ^ 'Nothing' if any of the inputs is invalid. p2sh_multiSig sett pks req = p2sh sett <$> XS.multiSig pks req -- | 'P2SH' “P2WSH-in-'P2SH'” 'Address' for a 'S.Script'. -- -- __WARNING__ SegWit does not support uncompressed SEC 'Pub'lic addresses -- (i.e., 'Bitcoin.Keys.pubUncompressed' and 'pubUncompressedHash160'), so be -- sure to only mention compressed SEC 'Pub'lic addresses in your 'S.Script's -- (i.e., 'Bitcoin.Keys.pubCompressed' and 'pubHash160'). p2sh_p2wsh :: Settings -> S.Script -> Address p2sh_p2wsh sett = p2sh sett . XS.segWit . SW.p2wsh . scriptSHA256 -- | 'P2SH' “P2WSH-in-'P2SH'” 'Address' for a standard m-of-n 'XS.multiSig' -- 'S.Script'. p2sh_p2wsh_multiSig :: Settings -> [Pub] -- ^ Public keys. Total number in range [1 … 16] -> Int -- ^ Required number of signatures in range [1 … 16] -> Maybe Address -- ^ 'Nothing' if any of the inputs is invalid. p2sh_p2wsh_multiSig sett pks req = p2sh_p2wsh sett <$> XS.multiSig pks req -------------------------------------------------------------------------------- -- SegWit -- | 'SegWit' “P2WPKH” 'Address' for a 'Pub'lic key. -- -- This is the address format associated with BIP-0032 -- derivation path @m\/84'\/0'@ p2wpkh :: Settings -> Pub -> Address p2wpkh sett = SegWit (settings_prefixSegWit sett) . SW.p2wpkh . pubHash160 -- | 'SegWit' “P2WSH” 'Address' for a 'S.Script'. -- -- Please see 'p2wsh_multiSig' for some of the typical constructions using -- 'p2wsh'. It's unlikely that you'll need to use 'p2wsh' directly unless you -- are deploying a non-standard 'S.Script'. -- -- __WARNING__ SegWit does not support uncompressed SEC 'Pub'lic addresses -- (i.e., 'Bitcoin.Keys.pubUncompressed' and 'pubUncompressedHash160'), so be -- sure to only mention compressed SEC 'Pub'lic addresses in your 'S.Script's -- (i.e., 'Bitcoin.Keys.pubCompressed' and 'pubHash160'). p2wsh :: Settings -> S.Script -> Address p2wsh sett = SegWit (settings_prefixSegWit sett) . SW.p2wsh . scriptSHA256 -- | 'SegWit' “P2WSH” 'Address' for a standard m-of-n 'XS.multiSig' 'S.Script'. p2wsh_multiSig :: Settings -> [Pub] -- ^ Public keys. Total number in range [1 … 16] -> Int -- ^ Required number of signatures in range [1 … 16] -> Maybe Address -- ^ 'Nothing' if any of the inputs is invalid. p2wsh_multiSig sett pks req = p2wsh sett <$> XS.multiSig pks req -------------------------------------------------------------------------------- -- | Obtain the 'S.Script' associated with a particular 'Address'. -- -- This will be one of 'XS.p2pkh', 'XS.p2sh' or 'XS.segWit' from -- the "Bitcoin.Address.Script" module. addressScript :: Address -> S.Script addressScript = \case P2PKH _ pkh -> XS.p2pkh pkh P2SH _ sh -> XS.p2sh sh SegWit _ swp -> XS.segWit swp -------------------------------------------------------------------------------- {- fromBase58Check :: B.ByteString -> Maybe B.ByteString fromBase58Check a = do b <- B58.decodeBase58 B58.bitcoinAlphabet a guard (B.length b >= 4) let (x, ch) = B.splitAt (B.length b - 4) b guard (check32 x == ch) pure x -} -------------------------------------------------------------------------------- -- | Render and 'Address' to its human readable form. renderAddress :: Address -> B.ByteString renderAddress = \case P2PKH pre pkh -> toBase58Check $ B.cons (unPrefixP2PKH pre) (unPubHash160 pkh) P2SH pre sh -> toBase58Check $ B.cons (unPrefixP2SH pre) (unScriptHash160 sh) SegWit pre swp -> SW.renderProgram pre swp -------------------------------------------------------------------------------- toBase58Check :: B.ByteString -> B.ByteString {-# INLINE toBase58Check #-} toBase58Check a = B58.encodeBase58 B58.bitcoinAlphabet (a <> check32 a) -------------------------------------------------------------------------------- -- | BTC /mainnet/ network settings. -- -- * 'P2PKH' addresses, when rendered, start with __1__. -- -- * 'P2SH' addresses, when rendered, start with __3__. -- -- * 'SegWit' addresses, when rendered, start with __bc1__. btc :: Settings btc = Settings btc_prefixP2PKH btc_prefixP2SH btc_prefixSegWit btc_prefixP2PKH :: PrefixP2PKH btc_prefixP2PKH = PrefixP2PKH 0x00 btc_prefixP2SH :: PrefixP2SH btc_prefixP2SH = PrefixP2SH 0x05 btc_prefixSegWit :: PrefixSegWit Just btc_prefixSegWit = prefixSegWit "bc" -------------------------------------------------------------------------------- -- | BTC /testnet/ network settings. -- -- * 'P2PKH' addresses, when rendered, start with __m__ or __n__. -- -- * 'P2SH' addresses, when rendered, start with __2__. -- -- * 'SegWit' addresses, when rendered, start with __tb1__. btcTestnet :: Settings btcTestnet = Settings btcTestnet_prefixP2PKH btcTestnet_prefixP2SH btcTestnet_prefixSegWit btcTestnet_prefixP2PKH :: PrefixP2PKH btcTestnet_prefixP2PKH = PrefixP2PKH 0x6f btcTestnet_prefixP2SH :: PrefixP2SH btcTestnet_prefixP2SH = PrefixP2SH 0xc4 btcTestnet_prefixSegWit :: PrefixSegWit Just btcTestnet_prefixSegWit = prefixSegWit "tb"