packages feed

haskoin-core-1.6.0: test/Haskoin/AddressSpec.hs

{-# LANGUAGE ImportQualifiedPost #-}
{-# LANGUAGE OverloadedRecordDot #-}
{-# LANGUAGE OverloadedStrings #-}

module Haskoin.AddressSpec (spec) where

import Control.DeepSeq (force)
import Control.Exception (evaluate)
import Control.Monad
import Data.Aeson (Value (String))
import Data.ByteString (ByteString)
import Data.ByteString qualified as B
import Data.Bytes.Get (runGetS)
import Data.Bytes.Put (runPutS)
import Data.Bytes.Serial (deserialize, serialize)
import Data.Default (def)
import Data.Either (isLeft)
import Data.Maybe (fromJust, isJust)
import Data.Text (Text)
import Data.Text qualified as T
import Haskoin.Address
import Haskoin.Crypto
import Haskoin.Network.Constants
import Haskoin.Network.Data
import Haskoin.Script
import Haskoin.Util
import Haskoin.Util.Arbitrary
import System.Timeout (timeout)
import Text.Read (readMaybe)
import Test.HUnit
import Test.Hspec
import Test.Hspec.QuickCheck
import Test.QuickCheck

identityTests :: Network -> IdentityTests
identityTests net =
  def
    { readTests = [ReadBox (arbitraryAddress net)],
      serialTests = [SerialBox (arbitraryAddress net)],
      marshalJsonTests = [MarshalJsonBox arbitraryNetAddress]
    }

spec :: Spec
spec = prepareContext $ \ctx -> do
  forM_ allNets $ \net -> testIdentity $ identityTests net
  describe "Address properties" $ do
    prop "encodes and decodes base58 bytestring" $
      forAll arbitraryBS $ \bs ->
        decodeBase58 (encodeBase58 bs) `shouldBe` Just bs
    prop "encodes and decodes base58 bytestring with checksum" $
      forAll arbitraryBS $ \bs ->
        decodeBase58Check (encodeBase58Check bs) `shouldBe` Just bs
    prop "textToAddr . addrToText identity" $
      forAll arbitraryNetAddress $ \(net, a) ->
        (textToAddr net =<< addrToText net a) `shouldBe` Just a
    prop "an address of a network is the address of its own output script on that network" $
      -- A generic CashAddr is left out. A token-aware one pays the script of
      -- its plain form, and the others have no script. Both have a test below.
      forAll arbitraryNetAddress $ \(net, a) ->
        not (isCashAddress a) ==>
          (scriptToAddressBS net ctx =<< maybeToEither "no script" (addressToScriptBS ctx a))
            `shouldBe` Right a
    prop "an address that outputAddress gives always has a text form on that network" $
      -- The output is of a kind of any network, not only of the one asked
      forAll ((,) <$> arbitraryNetwork <*> (arbitraryNetwork >>= \other -> arbitraryScriptOutput other ctx)) $ \(net, out) ->
        case outputAddress net ctx out of
          Nothing -> property True
          Just a -> property (isJust (addrToText net a))
  describe "Address vectors" $ do
    it "Passes Base58 vectors 1" $
      mapM_ testVector vectors
    it "Passes Base58 vectors 2" $
      mapM_ testBase58Vector base58Vectors
    it "Passes Base58 invalid decoding vectors" $
      mapM_ testBase58InvalidVector base58InvalidVectors
    it "Passes Base58Check invalid decoding vectors" $
      mapM_ testBase58ChkInvalidVector base58ChkInvalidVectors
    it "Passes addresses witness p2sh(pwpkh) vectors" $
      mapM_ (testCompatWitnessVector ctx) compatWitnessVectors
  describe "Base58" $ do
    it "decodes digits by their position in the alphabet" $
      -- 2 is digit 1 and 1 is digit 0, so 1 × 58 + 0 = 58
      decodeBase58 "21" `shouldBe` Just (B.pack [58])
    it "rejects a character outside the alphabet" $
      decodeBase58 "0" `shouldBe` Nothing
    it "rejects a non-ASCII character whose code point ends in a digit byte" $
      -- U+0131 is 0x131. Its low byte 0x31 is the digit 1.
      decodeBase58 "2\x0131" `shouldBe` Nothing
    it "rejects a checked string shorter than a checksum" $
      decodeBase58Check "1" `shouldBe` Nothing
  describe "Smart constructors" $ do
    it "witnessAddress builds a P2WPKH address from version 0 and 20 bytes" $
      witnessAddress 0 hash20Bytes `shouldBe` Just (WitnessPubKeyAddress hash20)
    it "witnessAddress builds a P2WSH address from version 0 and 32 bytes" $
      witnessAddress 0 hash32Bytes `shouldBe` Just (WitnessScriptAddress hash32)
    it "witnessAddress rejects version 0 with another program length" $
      witnessAddress 0 (B.replicate 21 1) `shouldBe` Nothing
    it "witnessAddress keeps version 1 with 32 bytes as a Taproot address" $ do
      let Just a = witnessAddress 1 hash32Bytes
      isWitnessAddress a `shouldBe` True
      addressVersion a `shouldBe` 1
      addressBytes a `shouldBe` hash32Bytes
    it "witnessAddress rejects a version above 16" $
      witnessAddress 17 hash32Bytes `shouldBe` Nothing
    it "witnessAddress rejects a program shorter than 2 bytes" $
      witnessAddress 1 (B.pack [1]) `shouldBe` Nothing
    it "witnessAddress rejects a program longer than 40 bytes" $
      witnessAddress 1 (B.replicate 41 1) `shouldBe` Nothing
    it "cashAddress builds a P2PKH address from type 0 and 20 bytes" $
      cashAddress 0 hash20Bytes `shouldBe` Just (PubKeyAddress hash20)
    it "cashAddress builds a P2SH address from type 1 and 20 bytes" $
      cashAddress 1 hash20Bytes `shouldBe` Just (ScriptAddress hash20)
    it "cashAddress builds a P2SH32 address from type 1 and 32 bytes" $
      cashAddress 1 hash32Bytes `shouldBe` Just (Script32Address hash32)
    it "cashAddress keeps a token-aware address as a generic CashAddr" $ do
      let Just a = cashAddress 2 hash20Bytes
      isCashAddress a `shouldBe` True
      addressVersion a `shouldBe` 2
      addressBytes a `shouldBe` hash20Bytes
    it "cashAddress rejects a type above 15" $
      cashAddress 16 hash20Bytes `shouldBe` Nothing
    it "cashAddress rejects a hash length the standard does not allow" $
      cashAddress 0 (B.replicate 21 1) `shouldBe` Nothing
  describe "Address predicates" $
    it "each predicate is true only for its own kind of address" $
      forM_ predicateTable $ \(name, a, kind) ->
        assertEqual name [k == kind | k <- [0 .. 6]] (map ($ a) predicates)
  describe "Address accessors" $ do
    it "addressHash160 gives the hash of every address paying to a 160-bit hash" $ do
      addressHash160 p2pkh `shouldBe` Just hash20
      addressHash160 p2sh `shouldBe` Just hash20
      addressHash160 p2wpkh `shouldBe` Just hash20
      addressHash160 tokenP2pkh `shouldBe` Just hash20
      addressHash160 tokenP2sh `shouldBe` Just hash20
    it "addressHash160 gives nothing for other kinds" $ do
      addressHash160 p2wsh `shouldBe` Nothing
      addressHash160 p2tr `shouldBe` Nothing
      addressHash160 cash15 `shouldBe` Nothing
    it "addressHash256 gives the hash of every address paying to a 256-bit hash" $ do
      addressHash256 p2wsh `shouldBe` Just hash32
      addressHash256 p2sh32 `shouldBe` Just hash32
      addressHash256 tokenP2sh32 `shouldBe` Just hash32
    it "addressHash256 gives nothing for other kinds" $ do
      addressHash256 p2pkh `shouldBe` Nothing
      addressHash256 p2tr `shouldBe` Nothing
    it "addressVersion is the witness version of SegWit kinds" $ do
      addressVersion p2wpkh `shouldBe` 0
      addressVersion p2wsh `shouldBe` 0
      addressVersion p2tr `shouldBe` 1
      addressVersion witness16 `shouldBe` 16
    it "addressVersion is the CashAddr type of other kinds" $ do
      addressVersion p2pkh `shouldBe` 0
      addressVersion p2sh `shouldBe` 1
      addressVersion p2sh32 `shouldBe` 1
      addressVersion tokenP2pkh `shouldBe` 2
      addressVersion tokenP2sh `shouldBe` 3
      addressVersion cash15 `shouldBe` 15
    it "addressBytes is the raw hash or program without any prefix" $ do
      addressBytes p2pkh `shouldBe` hash20Bytes
      addressBytes p2sh `shouldBe` hash20Bytes
      addressBytes p2wpkh `shouldBe` hash20Bytes
      addressBytes p2wsh `shouldBe` hash32Bytes
      addressBytes p2sh32 `shouldBe` hash32Bytes
      addressBytes p2tr `shouldBe` hash32Bytes
      addressBytes witness16 `shouldBe` B.pack [1 .. 40]
      addressBytes tokenP2pkh `shouldBe` hash20Bytes
      addressBytes cash15 `shouldBe` B.pack [1 .. 24]
  describe "addrToText" $ do
    it "encodes every address kind on every network" $
      forM_ textTable $ \(net, a, txt) ->
        assertEqual (net.name <> " " <> T.unpack txt) (Just txt) (addrToText net a)
    it "uses the testnet prefixes on every test and regression network" $ do
      addrToText btcTest4 p2pkh `shouldBe` Just "mrLC19Je2BuWQDkWSTriGYPyQJXKkkBmCx"
      addrToText btcRegTest p2sh `shouldBe` Just "2N44ThNe8NXHyv4bsX8AoVCXquBRW94Ls7W"
      addrToText btcTest4 p2wpkh `shouldBe` Just "tb1qw6syq5aa5z5ghkj3w7ux59wrk204txrn0swsqg"
      addrToText btcRegTest p2wpkh `shouldBe` Just "bcrt1qw6syq5aa5z5ghkj3w7ux59wrk204txrndehahp"
      addrToText bchTest4 p2pkh `shouldBe` Just "bchtest:qpm2qsznhks23z7629mms6s4cwef74vcwvqcw003ap"
      addrToText bchRegTest p2pkh `shouldBe` Just "bchreg:qpm2qsznhks23z7629mms6s4cwef74vcwv6ycwvz78"
    it "has no text for a SegWit address on a Bitcoin Cash network" $ do
      addrToText bch p2wpkh `shouldBe` Nothing
      addrToText bch p2wsh `shouldBe` Nothing
      addrToText bch p2tr `shouldBe` Nothing
    it "has no text for P2SH32 or a CashAddr kind on a Bitcoin network" $ do
      addrToText btc p2sh32 `shouldBe` Nothing
      addrToText btc tokenP2pkh `shouldBe` Nothing
      addrToText btc cash15 `shouldBe` Nothing
  describe "addrToBase58, addrToBech32 and addrToCash" $ do
    it "addrToBase58 gives the legacy form of a Bitcoin Cash address" $ do
      addrToBase58 bch p2pkh `shouldBe` Just "1BpEi6DfDAUFd7GtittLSdBeYJvcoaVggu"
      addrToBase58 bch p2sh `shouldBe` Just "3CWFddi6m4ndiGyKqzYvsFYagqDLPVMTzC"
      addrToBase58 bchTest p2sh `shouldBe` Just "2N44ThNe8NXHyv4bsX8AoVCXquBRW94Ls7W"
    it "addrToBase58 has no form for other kinds" $ do
      addrToBase58 btc p2wpkh `shouldBe` Nothing
      addrToBase58 bch p2sh32 `shouldBe` Nothing
      addrToBase58 bch tokenP2pkh `shouldBe` Nothing
    it "addrToBech32 encodes SegWit kinds on SegWit networks only" $ do
      addrToBech32 btc p2wpkh `shouldBe` Just "bc1qw6syq5aa5z5ghkj3w7ux59wrk204txrn9k4rmm"
      addrToBech32 btc p2pkh `shouldBe` Nothing
      addrToBech32 bch p2wpkh `shouldBe` Nothing
    it "addrToCash encodes on Bitcoin Cash networks only" $ do
      addrToCash bch p2pkh `shouldBe` Just "bitcoincash:qpm2qsznhks23z7629mms6s4cwef74vcwvy22gdx6a"
      addrToCash btc p2pkh `shouldBe` Nothing
      addrToCash bch p2wpkh `shouldBe` Nothing
  describe "textToAddr" $ do
    it "decodes every form that addrToText encodes" $
      forM_ textTable $ \(net, a, txt) ->
        assertEqual (net.name <> " " <> T.unpack txt) (Just a) (textToAddr net txt)
    it "accepts a CashAddr without prefix" $
      textToAddr bch "qpm2qsznhks23z7629mms6s4cwef74vcwvy22gdx6a" `shouldBe` Just p2pkh
    it "accepts a CashAddr in all uppercase" $
      textToAddr bch "BITCOINCASH:QPM2QSZNHKS23Z7629MMS6S4CWEF74VCWVY22GDX6A" `shouldBe` Just p2pkh
    it "accepts a Bech32 address in all uppercase" $
      textToAddr btc "BC1QW508D6QEJXTDG4Y5R3ZARVARY0C5XW7KV8F3T4"
        `shouldBe` Just (WitnessPubKeyAddress "751e76e8199196d454941c45d1b3a323f1433bd6")
    it "accepts a legacy Base58 address on a Bitcoin Cash network" $
      textToAddr bch "1BpEi6DfDAUFd7GtittLSdBeYJvcoaVggu" `shouldBe` Just p2pkh
    it "rejects a CashAddr with a mixed case prefix" $
      textToAddr bch "BitcoinCash:qpm2qsznhks23z7629mms6s4cwef74vcwvy22gdx6a" `shouldBe` Nothing
    it "rejects a CashAddr with mixed case data" $
      textToAddr bch "bitcoincash:qpm2qsznhks23z7629mms6s4cwef74vcwvy22gdX6A" `shouldBe` Nothing
    it "rejects a Bech32 address with mixed case" $
      textToAddr btcTest "tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sL5k7" `shouldBe` Nothing
    it "rejects a CashAddr whose padding bits are not zero" $
      textToAddr bch "bitcoincash:qpm2qsznhks23z7629mms6s4cwef74vcw0c0g40rx7" `shouldBe` Nothing
    it "rejects a CashAddr with an extra zero group" $
      textToAddr bch "bitcoincash:qpm2qsznhks23z7629mms6s4cwef74vcwvq5xhgekz9" `shouldBe` Nothing
    it "rejects a Bech32 address whose padding bits are not zero" $
      textToAddr btc "bc1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf308468nf" `shouldBe` Nothing
    it "rejects a Bech32 address with an extra zero group" $
      textToAddr btc "bc1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3qqnuhcm0" `shouldBe` Nothing
    it "rejects a Base58 address with a non-ASCII look-alike character" $
      -- U+0141 in place of A
      textToAddr btc "1BpEi6DfD\x0141UFd7GtittLSdBeYJvcoaVggu" `shouldBe` Nothing
    it "rejects a Bech32 address with a non-ASCII look-alike character" $
      -- U+017F in place of s
      textToAddr btcTest "tb1qrp33g0q5c5tx\x017Fp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sl5k7" `shouldBe` Nothing
    it "rejects a CashAddr with a non-ASCII look-alike character" $
      -- U+212A in place of K
      textToAddr bch "BITCOINCASH:QPM2QSZNH\x212AS23Z7629MMS6S4CWEF74VCWVY22GDX6A" `shouldBe` Nothing
    it "rejects an address of another network" $
      forM_ otherNetworkTable $ \(net, txt) ->
        assertEqual (net.name <> " " <> T.unpack txt) Nothing (textToAddr net txt)
    it "rejects a Base58 address with a bad checksum" $
      textToAddr btc "1BpEi6DfDAUFd7GtittLSdBeYJvcoaVggv" `shouldBe` Nothing
    it "rejects a Base58 payload that is not 21 bytes" $ do
      -- version byte, the hash and one extra byte
      textToAddr btc "1pjVWN2segoHYNh9Hv1ap6hzhC7eqEpz8oy" `shouldBe` Nothing
      -- version byte and 19 bytes of hash
      textToAddr btc "13T7H7AfZfPwWvz51tBGjWVhDz5TJEMr9" `shouldBe` Nothing
    it "rejects a Base58 version byte that is neither P2PKH nor P2SH" $
      -- version byte 1 and the hash
      textToAddr btc "b9qhCWwvLw8SYQykKDevkTSApBZZqXzeV" `shouldBe` Nothing
    it "rejects surrounding whitespace" $
      textToAddr btc " 1BpEi6DfDAUFd7GtittLSdBeYJvcoaVggu\n" `shouldBe` Nothing
    it "reads a CashAddr kind that has no output script, which is not payable" $ do
      -- type 4 with the 20-byte hash 01 … 01: Bitcoin Cash Node gives it no
      -- destination. Encoded with a Python transcription of its encoder.
      let unknownKind = textToAddr bch "bitcoincash:yqqszqgpqyqszqgpqyqszqgpqyqszqgpqyds8d3dsw"
      (isCashAddress <$> unknownKind) `shouldBe` Just True
      (addressToOutput =<< unknownKind) `shouldBe` Nothing
    it "reads the longest CashAddr, with a 64-byte hash" $
      -- 11 characters of prefix, the colon, and 112 of data and checksum
      (addressBytes <$> textToAddr bch longestCashAddr) `shouldBe` Just (B.pack [0 .. 63])
    it "refuses a string longer than any CashAddr without decoding it" $ do
      -- One character more than the longest address: 125
      textToAddr bch (longestCashAddr <> "q") `shouldBe` Nothing
      timeout 2000000 (evaluate (textToAddr bch ("bitcoincash:" <> T.replicate 20000000 "q")))
        `shouldReturn` Just Nothing
    it "rejects a long string of Base58 characters without decoding it" $
      timeout 2000000 (evaluate (textToAddr btc (T.replicate 500000 "z")))
        `shouldReturn` Just Nothing
  describe "Addresses from public keys" $ do
    it "pubKeyAddr hashes a compressed key" $
      addrToText btc (pubKeyAddr ctx (compressedKey ctx))
        `shouldBe` Just "1BgGZ9tcN4rm9KBzDn7KprQz87SZ26SAMH"
    it "pubKeyAddr hashes an uncompressed key in its uncompressed form" $
      addrToText btc (pubKeyAddr ctx (uncompressedKey ctx))
        `shouldBe` Just "1EHNa6Q4Jz2uvNExL497mE43ikXhwF6kZm"
    it "pubKeyWitnessAddr gives the P2WPKH address of a compressed key" $
      (addrToText btc =<< pubKeyWitnessAddr ctx (compressedKey ctx))
        `shouldBe` Just "bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4"
    it "pubKeyWitnessAddr refuses an uncompressed key" $
      pubKeyWitnessAddr ctx (uncompressedKey ctx) `shouldBe` Nothing
    it "pubKeyCompatWitnessAddr gives the P2SH-P2WPKH address of a compressed key" $
      (addrToText btc =<< pubKeyCompatWitnessAddr ctx (compressedKey ctx))
        `shouldBe` Just "3JvL6Ymt8MVWiCNHC7oWU6nLeHNJKLZGLN"
    it "pubKeyCompatWitnessAddr refuses an uncompressed key" $
      pubKeyCompatWitnessAddr ctx (uncompressedKey ctx) `shouldBe` Nothing
    it "p2pkhAddr, p2shAddr, p2wpkhAddr and p2wshAddr wrap a hash" $ do
      addrToText btc (p2pkhAddr hash20) `shouldBe` Just "1BpEi6DfDAUFd7GtittLSdBeYJvcoaVggu"
      addrToText btc (p2shAddr hash20) `shouldBe` Just "3CWFddi6m4ndiGyKqzYvsFYagqDLPVMTzC"
      addrToText btc (p2wpkhAddr hash20) `shouldBe` Just "bc1qw6syq5aa5z5ghkj3w7ux59wrk204txrn9k4rmm"
      addrToText btc (p2wshAddr hash32) `shouldBe` Just "bc1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3qccfmv3"
  describe "Addresses from scripts" $ do
    it "payToScriptAddress hashes the output script with HASH160" $
      (addrToText btc <$> payToScriptAddress ctx (multisig ctx))
        `shouldBe` Right (Just "38fEX6RbBBMmpu3nbbuULku1xyrrzqqqnE")
    it "payToWitnessScriptAddress hashes the output script with SHA256" $
      (addrToText btc <$> payToWitnessScriptAddress ctx (multisig ctx))
        `shouldBe` Right (Just "bc1qd6e6c86xp56gw8pty8suuqhscpttea2c5m2fggzjkxzk5nl9faks5pehxu")
    it "payToNestedScriptAddress wraps the P2WSH script in P2SH" $
      (addrToText btc <$> payToNestedScriptAddress ctx (multisig ctx))
        `shouldBe` Right (Just "3JSuZ22mPR2aD5bf77KsGi36V5ar3p87LX")
    it "p2shP2wpkhAddr is the P2SH address of the witness program of a key hash" $
      -- The P2SH-P2WPKH example of BIP 143: redeem script 0014 79091972…,
      -- script hash 4733f37cf4db86fbc2efed2500b4f4e49f312023.
      addrToText btc (p2shP2wpkhAddr "79091972186c449eb1ded22b78e40d009bdf0089")
        `shouldBe` Just "38BW8nqpHSWpkf5sXrQd2xYwvnPJwP59ic"
  describe "Script size limits of script hash addresses" $ do
    -- The expected addresses were computed with Python (hashlib, embit,
    -- base58) from the script bytes, not with this library.
    it "gives the P2SH address of a 1-of-15 multisig of compressed keys" $
      -- 3 + 15 × 34 = 513 bytes, which fits the 520 bytes of a push
      (addrToText btc <$> payToScriptAddress ctx (multisigOf ctx 15))
        `shouldBe` Right (Just "32vPTRtH3XPKgBxBtEoiQdGzVEZydYKduQ")
    it "has no P2SH address for a 1-of-16 multisig of compressed keys" $
      -- 3 + 16 × 34 = 547 bytes: no input can push that redeem script
      payToScriptAddress ctx (multisigOf ctx 16)
        `shouldBe` Left "The redeem script is 547 bytes long, and a pay-to-script-hash redeem script can have at most 520"
    it "has no P2SH address for a 1-of-20 multisig of compressed keys" $
      -- 3 + 20 × 34 + 1 = 684 bytes: the count 20 takes a one-byte push
      payToScriptAddress ctx (multisigOf ctx 20)
        `shouldBe` Left "The redeem script is 684 bytes long, and a pay-to-script-hash redeem script can have at most 520"
    it "gives the P2SH address of a redeem script of exactly 520 bytes" $
      -- OP_RETURN, the three bytes of OP_PUSHDATA2 and its length, 516 bytes
      (addrToText btc <$> payToScriptAddress ctx (DataCarrier [B.replicate 516 0xab]))
        `shouldBe` Right (Just "35xAkh7KgcziTpgBWPAoXGzGDgLujKCnLL")
    it "has no P2SH address for a redeem script of 521 bytes" $
      payToScriptAddress ctx (DataCarrier [B.replicate 517 0xab])
        `shouldBe` Left "The redeem script is 521 bytes long, and a pay-to-script-hash redeem script can have at most 520"
    it "gives the P2WSH and the P2SH-P2WSH address of a 1-of-20 multisig" $ do
      (addrToText btc <$> payToWitnessScriptAddress ctx (multisigOf ctx 20))
        `shouldBe` Right (Just "bc1q5yfkferfc5w4t3chd0ghw6ume447rey75v8df4z6sjrhnegv50ysg5v7a5")
      (addrToText btc <$> payToNestedScriptAddress ctx (multisigOf ctx 20))
        `shouldBe` Right (Just "3KHeFzKKQySyJSVTdqkkxxaMGRUCNdPfay")
    it "gives the P2WSH and the P2SH-P2WSH address of a witness script of exactly 10,000 bytes" $ do
      -- OP_RETURN, the three bytes of OP_PUSHDATA2 and its length, 9996 bytes
      (addrToText btc <$> payToWitnessScriptAddress ctx (DataCarrier [B.replicate 9996 0xab]))
        `shouldBe` Right (Just "bc1q5w9futq5wnymeh5wfrak2388u0cmup9nhkwxsq56rraaxvfy22pqrzp44n")
      (addrToText btc <$> payToNestedScriptAddress ctx (DataCarrier [B.replicate 9996 0xab]))
        `shouldBe` Right (Just "34v1ZTik6F9PmGBTcXnmbHF1NfTjufPgvv")
    it "has no P2WSH and no P2SH-P2WSH address for a witness script of 10,001 bytes" $ do
      payToWitnessScriptAddress ctx (DataCarrier [B.replicate 9997 0xab])
        `shouldBe` Left "The witness script is 10001 bytes long, and a pay-to-witness-script-hash witness script can have at most 10000"
      payToNestedScriptAddress ctx (DataCarrier [B.replicate 9997 0xab])
        `shouldBe` Left "The witness script is 10001 bytes long, and a pay-to-witness-script-hash witness script can have at most 10000"
    it "gives an error value, not an exception, for a multisig that no script can express" $ do
      payToScriptAddress ctx (PayMulSig [compressedKey ctx, secondKey ctx] 3)
        `shouldBe` Left "encodeOutput: multisig requires more signatures than it has keys"
      payToScriptAddress ctx (PayMulSig [compressedKey ctx] 0)
        `shouldBe` Left "encodeOutput: multisig must require at least one signature"
      payToWitnessScriptAddress ctx (PayMulSig [] 1)
        `shouldBe` Left "encodeOutput: multisig requires more signatures than it has keys"
      payToNestedScriptAddress ctx (PayMulSig (replicate 21 (compressedKey ctx)) 1)
        `shouldBe` Left "encodeOutput: multisig has more than 20 keys"
  describe "addressToOutput, addressToScript and addressToScriptBS" $ do
    it "builds the locking script of every address kind" $
      forM_ scriptTable $ \(_, a, hex) ->
        assertEqual (T.unpack hex) (Just hex) (encodeHex <$> addressToScriptBS ctx a)
    it "pays a token-aware address and its plain form with the same script" $ do
      let script = Just "76a91476a04053bda0a88bda5177b86a15c3b29f55987388ac"
      (encodeHex <$> (addressToScriptBS ctx =<< textToAddr bch "bitcoincash:zpm2qsznhks23z7629mms6s4cwef74vcwvrqekrq9w"))
        `shouldBe` script
      (encodeHex <$> (addressToScriptBS ctx =<< textToAddr bch "bitcoincash:qpm2qsznhks23z7629mms6s4cwef74vcwvy22gdx6a"))
        `shouldBe` script
    it "the script of a token-aware address has the plain address, which is another value" $ do
      -- An index keyed by address must look a token-aware address up by its
      -- script, or by its plain form
      (scriptToAddressBS bch ctx =<< maybeToEither "no script" (addressToScriptBS ctx tokenP2pkh))
        `shouldBe` Right p2pkh
      tokenP2pkh `shouldNotBe` p2pkh
    it "pays a token-aware P2PKH address with the plain P2PKH script" $
      (encodeHex <$> addressToScriptBS ctx tokenP2pkh)
        `shouldBe` Just "76a91476a04053bda0a88bda5177b86a15c3b29f55987388ac"
    it "pays a token-aware P2SH address with the plain P2SH script" $
      (encodeHex <$> addressToScriptBS ctx tokenP2sh)
        `shouldBe` Just "a91476a04053bda0a88bda5177b86a15c3b29f55987387"
    it "pays a token-aware P2SH32 address with the plain P2SH32 script" $
      (encodeHex <$> addressToScriptBS ctx tokenP2sh32)
        `shouldBe` Just "aa201863143c14c5166804bd19203356da136c985678cd4d27a1b8c632960490326287"
    it "has no script for a CashAddr type with no known script" $ do
      addressToOutput cash15 `shouldBe` Nothing
      addressToOutput (fromJust (cashAddress 0 hash32Bytes)) `shouldBe` Nothing
      addressToOutput (fromJust (cashAddress 2 hash32Bytes)) `shouldBe` Nothing
    it "addressToOutput gives the matching ScriptOutput" $ do
      addressToOutput p2pkh `shouldBe` Just (PayPKHash hash20)
      addressToOutput p2wsh `shouldBe` Just (PayWitnessScriptHash hash32)
      addressToOutput p2tr `shouldBe` Just (PayWitness 1 hash32Bytes)
      addressToOutput tokenP2sh32 `shouldBe` Just (PayScript32Hash hash32)
    it "addressToScript gives the script AST" $
      ((.ops) <$> addressToScript ctx p2sh)
        `shouldBe` Just [OP_HASH160, OP_PUSHDATA hash20Bytes OPCODE, OP_EQUAL]
  describe "outputAddress, scriptToAddress and scriptToAddressBS" $ do
    it "recovers the address of every standard locking script on a network that has its kind" $
      forM_ scriptTable $ \(net, a, hex) ->
        assertEqual (T.unpack hex) (Right a) (scriptToAddressBS net ctx (fromJust (decodeHex hex)))
    it "scriptToAddress works on the script AST" $
      (scriptToAddress btc ctx =<< runGetS deserialize (fromJust (decodeHex "76a91476a04053bda0a88bda5177b86a15c3b29f55987388ac")))
        `shouldBe` Right p2pkh
    it "has no address for a pay-to-public-key script" $
      scriptToAddressBS btc ctx (fromJust (decodeHex p2pkScript))
        `shouldBe` Left "Could not decode address"
    it "has no address for a bare multisig script" $
      scriptToAddressBS btc ctx (fromJust (decodeHex multisigScript))
        `shouldBe` Left "Could not decode address"
    it "has no address for a data carrier script" $
      scriptToAddressBS btc ctx (fromJust (decodeHex "6a04deadbeef"))
        `shouldBe` Left "Could not decode address"
    it "fails on a script that is not standard" $
      scriptToAddressBS btc ctx (fromJust (decodeHex "51")) `shouldSatisfy` isLeft
    it "outputAddress has no address for a pay-to-public-key output" $
      outputAddress btc ctx (PayPK (compressedKey ctx)) `shouldBe` Nothing
    it "a script of the 256-bit script hash shape has no address on Bitcoin" $ do
      -- Output 0 of mainnet transaction a4bfa8ab…bc31b, in block 211997.
      -- Bitcoin Core reports it as non-standard, with no address.
      let puzzle = fromJust (decodeHex "aa206fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d619000000000087")
      forM_ [btc, btcTest, btcTest4, btcRegTest] $ \net ->
        assertEqual net.name (Left "Could not decode address") (scriptToAddressBS net ctx puzzle)
      -- On Bitcoin Cash the same bytes are pay-to-script-hash with a 32-byte hash
      (addrToText bch =<< eitherToMaybe (scriptToAddressBS bch ctx puzzle))
        `shouldBe` Just "bitcoincash:pdh79rq2kmcmxukp563ydtnr7a8ex85rvhs45zyudrtpjqqqqqqqqgss5hn0j"
    it "a witness program has no address on Bitcoin Cash" $
      forM_ [bch, bchTest, bchTest4, bchRegTest] $ \net -> do
        -- version 0 with 20 bytes, version 0 with 32 bytes, and Taproot
        assertEqual net.name (Left "Could not decode address") $
          scriptToAddressBS net ctx (fromJust (decodeHex "001476a04053bda0a88bda5177b86a15c3b29f559873"))
        assertEqual net.name (Left "Could not decode address") $
          scriptToAddressBS net ctx (fromJust (decodeHex "00201863143c14c5166804bd19203356da136c985678cd4d27a1b8c6329604903262"))
        assertEqual net.name (Left "Could not decode address") $
          scriptToAddressBS net ctx (fromJust (decodeHex "51201863143c14c5166804bd19203356da136c985678cd4d27a1b8c6329604903262"))
    it "outputAddress gives an output kind an address only where the network has that kind" $ do
      outputAddress btc ctx (PayScript32Hash hash32) `shouldBe` Nothing
      outputAddress bch ctx (PayScript32Hash hash32) `shouldBe` Just p2sh32
      outputAddress bch ctx (PayWitnessPKHash hash20) `shouldBe` Nothing
      outputAddress bch ctx (PayWitnessScriptHash hash32) `shouldBe` Nothing
      outputAddress bch ctx (PayWitness 1 hash32Bytes) `shouldBe` Nothing
      outputAddress btc ctx (PayWitnessPKHash hash20) `shouldBe` Just p2wpkh
      outputAddress btc ctx (PayWitness 1 hash32Bytes) `shouldBe` Just p2tr
    it "the address of the mainnet 256-bit hash lock script is not written as JSON, because it has none" $
      -- This is the value an indexer encodes for that output
      marshalValue btc <$> eitherToMaybe (scriptToAddressBS btc ctx (fromJust (decodeHex "aa206fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d619000000000087")))
        `shouldBe` Nothing
  describe "inputAddress" $ do
    it "gives the P2PKH address of the key in a public key hash input" $
      (addrToText btc =<< inputAddress ctx (RegularInput (SpendPKHash TxSignatureEmpty (compressedKey ctx))))
        `shouldBe` Just "1BgGZ9tcN4rm9KBzDn7KprQz87SZ26SAMH"
    it "gives the P2SH address of the redeem script in a script hash input" $
      (addrToText btc =<< inputAddress ctx (ScriptHashInput (SpendMulSig [TxSignatureEmpty]) (multisig ctx)))
        `shouldBe` Just "38fEX6RbBBMmpu3nbbuULku1xyrrzqqqnE"
    it "gives the hash of the redeem script bytes of a decoded P2SH input" $ do
      -- OP_0, an empty signature, and the push of the 37-byte redeem script
      -- 51 21 <key of private key 1> 51 ae. Its HASH160 is
      -- 83eebb7d79aa1d388e3b0ac65b98ac580c4da01a (Python, hashlib).
      let script = fromJust (decodeHex "00002551210279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f8179851ae")
      (addrToText btc =<< inputAddress ctx =<< eitherToMaybe (decodeInput btc ctx =<< runGetS deserialize script))
        `shouldBe` Just "3DicS6C8JZm59RsrgXr56iVHzYdQngiehV"
    it "gives no address for a P2SH input whose redeem script pushes its key with OP_PUSHDATA1" $ do
      -- The redeem script 51 4c 21 <key> 51 ae hashes to
      -- 6b5837f2dea4723d69dc0911013387692b970cfa, the address
      -- 3BUbubs2pJzko1Xg5KBVDpoRa7V3kRAsVP. Written back in the form of this
      -- library it would hash to 3DicS6C8…, an address that held no coin.
      let script = fromJust (decodeHex "000026514c210279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f8179851ae")
      (inputAddress ctx =<< eitherToMaybe (decodeInput btc ctx =<< runGetS deserialize script))
        `shouldBe` Nothing
    it "has no address for a redeem script that is too long to be pushed" $
      inputAddress ctx (ScriptHashInput (SpendMulSig [TxSignatureEmpty]) (multisigOf ctx 16))
        `shouldBe` Nothing
    it "gives the P2SH address of the wrapped program in a nested witness input" $
      -- The P2SH-P2WPKH example of BIP 143: redeem script 0014 79091972…,
      -- script hash 4733f37cf4db86fbc2efed2500b4f4e49f312023.
      (addrToText btc =<< inputAddress ctx (NestedWitnessInput (PayWitnessPKHash "79091972186c449eb1ded22b78e40d009bdf0089")))
        `shouldBe` Just "38BW8nqpHSWpkf5sXrQd2xYwvnPJwP59ic"
    it "has no address for a pay-to-public-key input" $
      inputAddress ctx (RegularInput (SpendPK TxSignatureEmpty)) `shouldBe` Nothing
    it "has no address for a bare multisig input" $
      inputAddress ctx (RegularInput (SpendMulSig [TxSignatureEmpty])) `shouldBe` Nothing
  describe "Binary serialization" $ do
    it "writes a P2PKH address as a type byte and the hash" $
      encodeHex (runPutS (serialize p2pkh))
        `shouldBe` "0076a04053bda0a88bda5177b86a15c3b29f559873"
    it "writes a Taproot address as a type byte, the version and the sized program" $
      encodeHex (runPutS (serialize p2tr))
        `shouldBe` "0401" <> "0000000000000020" <> "1863143c14c5166804bd19203356da136c985678cd4d27a1b8c6329604903262"
    it "writes a token-aware address as a type byte, the CashAddr type and the sized hash" $
      encodeHex (runPutS (serialize tokenP2pkh))
        `shouldBe` "0602" <> "0000000000000014" <> "76a04053bda0a88bda5177b86a15c3b29f559873"
    it "rejects an unknown type byte" $
      deserializeAddress "0776a04053bda0a88bda5177b86a15c3b29f559873" `shouldSatisfy` isLeft
    it "rejects a witness version above 16" $
      deserializeAddress ("0411" <> "0000000000000002" <> "0102") `shouldSatisfy` isLeft
    it "rejects a witness program longer than 40 bytes" $
      deserializeAddress ("0401" <> "0000000000000029" <> T.replicate 41 "01") `shouldSatisfy` isLeft
    it "rejects a declared length above the longest payload before reading the payload" $ do
      -- 41 for a witness program, whose longest is 40
      deserializeAddress ("0401" <> "0000000000000029" <> T.replicate 41 "01")
        `shouldBe` Left "Failed reading: Address payload too long\nEmpty call stack\n"
      -- 65 for a CashAddr hash, whose longest is 64
      deserializeAddress ("0600" <> "0000000000000041" <> T.replicate 65 "01")
        `shouldBe` Left "Failed reading: Address payload too long\nEmpty call stack\n"
    it "rejects a sized payload whose length does not fit in memory" $
      deserializeAddress ("0401" <> "ffffffffffffffff" <> "0102") `shouldSatisfy` isLeft
    it "reads a version 0 witness program as the P2WPKH constructor" $
      deserializeAddress ("0400" <> "0000000000000014" <> "76a04053bda0a88bda5177b86a15c3b29f559873")
        `shouldBe` Right (WitnessPubKeyAddress hash20)
    it "reads a CashAddr of type 0 with 20 bytes as the P2PKH constructor" $
      deserializeAddress ("0600" <> "0000000000000014" <> "76a04053bda0a88bda5177b86a15c3b29f559873")
        `shouldBe` Right (PubKeyAddress hash20)
    it "rejects a CashAddr with a hash size the standard does not allow" $
      deserializeAddress ("0602" <> "0000000000000015" <> T.replicate 21 "01") `shouldSatisfy` isLeft
  describe "JSON" $ do
    it "encodes an address as its text form" $ do
      marshalValue btc p2pkh `shouldBe` String "1BpEi6DfDAUFd7GtittLSdBeYJvcoaVggu"
      marshalJSON btc p2pkh `shouldBe` "\"1BpEi6DfDAUFd7GtittLSdBeYJvcoaVggu\""
      marshalJSON bch tokenP2pkh `shouldBe` "\"bitcoincash:zpm2qsznhks23z7629mms6s4cwef74vcwvrqekrq9w\""
    it "decodes the text form" $
      unmarshalJSON btc "\"1BpEi6DfDAUFd7GtittLSdBeYJvcoaVggu\"" `shouldBe` Just p2pkh
    it "rejects text that is not an address" $ do
      unmarshalJSON btc "\"hello\"" `shouldBe` (Nothing :: Maybe Address)
      unmarshalJSON btc "null" `shouldBe` (Nothing :: Maybe Address)
    it "fails loudly instead of writing null when the address has no text form" $ do
      evaluate (force (marshalValue bch p2wpkh)) `shouldThrow` anyErrorCall
      evaluate (force (marshalJSON btc p2sh32)) `shouldThrow` anyErrorCall
  describe "Read" $ do
    it "reads what Show writes for the generic kinds" $ do
      read (show p2tr) `shouldBe` p2tr
      read (show tokenP2pkh) `shouldBe` tokenP2pkh
    it "does not read a witness version above 16" $
      (readMaybe "OtherWitnessAddress {version = 99, bytes = \"ab\"}" :: Maybe Address)
        `shouldBe` Nothing
    it "does not read a witness program shorter than two bytes" $
      (readMaybe "OtherWitnessAddress {version = 1, bytes = \"\"}" :: Maybe Address)
        `shouldBe` Nothing
    it "does not read a CashAddr type above 15" $
      (readMaybe "OtherCashAddress {version = 200, bytes = \"abcdefghijklmnopqrst\"}" :: Maybe Address)
        `shouldBe` Nothing
    it "does not read a CashAddr hash of a length the standard does not allow" $
      (readMaybe "OtherCashAddress {version = 2, bytes = \"abc\"}" :: Maybe Address)
        `shouldBe` Nothing
    it "does not read a generic kind that has a dedicated constructor" $ do
      -- version 0 with 20 bytes is a WitnessPubKeyAddress
      (readMaybe "OtherWitnessAddress {version = 0, bytes = \"abcdefghijklmnopqrst\"}" :: Maybe Address)
        `shouldBe` Nothing
      -- CashAddr type 0 with 20 bytes is a PubKeyAddress
      (readMaybe "OtherCashAddress {version = 0, bytes = \"abcdefghijklmnopqrst\"}" :: Maybe Address)
        `shouldBe` Nothing

{- Fixtures -}

-- | HASH160 behind the CashAddr translation vectors.
hash20 :: Hash160
hash20 = "76a04053bda0a88bda5177b86a15c3b29f559873"

hash20Bytes :: ByteString
hash20Bytes = fromJust $ decodeHex "76a04053bda0a88bda5177b86a15c3b29f559873"

-- | SHA256 behind the BIP-173 P2WSH vector.
hash32 :: Hash256
hash32 = "1863143c14c5166804bd19203356da136c985678cd4d27a1b8c6329604903262"

hash32Bytes :: ByteString
hash32Bytes = fromJust $ decodeHex "1863143c14c5166804bd19203356da136c985678cd4d27a1b8c6329604903262"

p2pkh, p2sh, p2wpkh, p2wsh, p2sh32, p2tr, witness16 :: Address
p2pkh = pubKeyAddress hash20
p2sh = scriptAddress hash20
p2wpkh = witnessPubKeyAddress hash20
p2wsh = witnessScriptAddress hash32
p2sh32 = script32Address hash32
p2tr = fromJust $ witnessAddress 1 hash32Bytes
witness16 = fromJust $ witnessAddress 16 (B.pack [1 .. 40])

tokenP2pkh, tokenP2sh, tokenP2sh32, cash15 :: Address
tokenP2pkh = fromJust $ cashAddress 2 hash20Bytes
tokenP2sh = fromJust $ cashAddress 3 hash20Bytes
tokenP2sh32 = fromJust $ cashAddress 3 hash32Bytes
cash15 = fromJust $ cashAddress 15 (B.pack [1 .. 24])

predicates :: [Address -> Bool]
predicates =
  [ isPubKeyAddress,
    isScriptAddress,
    isWitnessPubKeyAddress,
    isWitnessScriptAddress,
    isWitnessAddress,
    isScript32Address,
    isCashAddress
  ]

-- | Each address with the index of the only predicate that holds for it.
predicateTable :: [(String, Address, Int)]
predicateTable =
  [ ("P2PKH", p2pkh, 0),
    ("P2SH", p2sh, 1),
    ("P2WPKH", p2wpkh, 2),
    ("P2WSH", p2wsh, 3),
    ("P2TR", p2tr, 4),
    ("P2SH32", p2sh32, 5),
    ("token-aware P2PKH", tokenP2pkh, 6)
  ]

-- | Text form of every address kind on the main networks.
textTable :: [(Network, Address, Text)]
textTable =
  [ (btc, p2pkh, "1BpEi6DfDAUFd7GtittLSdBeYJvcoaVggu"),
    (btc, p2sh, "3CWFddi6m4ndiGyKqzYvsFYagqDLPVMTzC"),
    (btc, p2wpkh, "bc1qw6syq5aa5z5ghkj3w7ux59wrk204txrn9k4rmm"),
    (btc, p2wsh, "bc1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3qccfmv3"),
    (btc, p2tr, "bc1prp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3qj0fj5d"),
    (btc, witness16, "bc1sqypqxpq9qcrsszg2pvxq6rs0zqg3yyc5z5tpwxqergd3c8g7ruszzg3rysjjvfeg7dt0md"),
    (btcTest, p2pkh, "mrLC19Je2BuWQDkWSTriGYPyQJXKkkBmCx"),
    (btcTest, p2sh, "2N44ThNe8NXHyv4bsX8AoVCXquBRW94Ls7W"),
    (btcTest, p2wpkh, "tb1qw6syq5aa5z5ghkj3w7ux59wrk204txrn0swsqg"),
    (btcTest, p2wsh, "tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sl5k7"),
    (btcTest, p2tr, "tb1prp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q98lawz"),
    (bch, p2pkh, "bitcoincash:qpm2qsznhks23z7629mms6s4cwef74vcwvy22gdx6a"),
    (bch, p2sh, "bitcoincash:ppm2qsznhks23z7629mms6s4cwef74vcwvn0h829pq"),
    (bch, p2sh32, "bitcoincash:pvvxx9puznz3v6qyh5vjqv6kmgfkexzk0rx56faphrrr99syjqexyrusee847"),
    (bch, tokenP2pkh, "bitcoincash:zpm2qsznhks23z7629mms6s4cwef74vcwvrqekrq9w"),
    (bch, tokenP2sh, "bitcoincash:rpm2qsznhks23z7629mms6s4cwef74vcwv59yeyr7n"),
    (bch, tokenP2sh32, "bitcoincash:rvvxx9puznz3v6qyh5vjqv6kmgfkexzk0rx56faphrrr99syjqexy30vcqxv4"),
    (bch, cash15, "bitcoincash:0yqsyqcyq5rqwzqfpg9scrgwpugpzysnzs23v9cc7akt68km"),
    (bchTest, p2pkh, "bchtest:qpm2qsznhks23z7629mms6s4cwef74vcwvqcw003ap"),
    (bchTest, p2sh, "bchtest:ppm2qsznhks23z7629mms6s4cwef74vcwvhanqgjxu")
  ]

-- | Valid addresses of one network that another network must reject.
otherNetworkTable :: [(Network, Text)]
otherNetworkTable =
  [ (btc, "mrLC19Je2BuWQDkWSTriGYPyQJXKkkBmCx"),
    (btc, "tb1qw6syq5aa5z5ghkj3w7ux59wrk204txrn0swsqg"),
    (btc, "bitcoincash:qpm2qsznhks23z7629mms6s4cwef74vcwvy22gdx6a"),
    (btc, "qpm2qsznhks23z7629mms6s4cwef74vcwvy22gdx6a"),
    (btcTest, "1BpEi6DfDAUFd7GtittLSdBeYJvcoaVggu"),
    (btcTest, "bc1qw6syq5aa5z5ghkj3w7ux59wrk204txrn9k4rmm"),
    (btcRegTest, "tb1qw6syq5aa5z5ghkj3w7ux59wrk204txrn0swsqg"),
    (bch, "bc1qw6syq5aa5z5ghkj3w7ux59wrk204txrn9k4rmm"),
    (bch, "bchtest:qpm2qsznhks23z7629mms6s4cwef74vcwvqcw003ap"),
    (bch, "qpm2qsznhks23z7629mms6s4cwef74vcwvqcw003ap"),
    (bchTest, "bitcoincash:qpm2qsznhks23z7629mms6s4cwef74vcwvy22gdx6a")
  ]

-- | Locking script of every address kind that has one, with a network that
-- has that kind of output.
scriptTable :: [(Network, Address, Text)]
scriptTable =
  [ (btc, p2pkh, "76a91476a04053bda0a88bda5177b86a15c3b29f55987388ac"),
    (bch, p2pkh, "76a91476a04053bda0a88bda5177b86a15c3b29f55987388ac"),
    (btc, p2sh, "a91476a04053bda0a88bda5177b86a15c3b29f55987387"),
    (bch, p2sh, "a91476a04053bda0a88bda5177b86a15c3b29f55987387"),
    (bch, p2sh32, "aa201863143c14c5166804bd19203356da136c985678cd4d27a1b8c632960490326287"),
    (btc, p2wpkh, "001476a04053bda0a88bda5177b86a15c3b29f559873"),
    (btc, p2wsh, "00201863143c14c5166804bd19203356da136c985678cd4d27a1b8c6329604903262"),
    (btc, p2tr, "51201863143c14c5166804bd19203356da136c985678cd4d27a1b8c6329604903262"),
    (btc, witness16, "6028" <> "0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728")
  ]

-- | The longest CashAddr: type 0 with the 64-byte hash 00 01 … 3f. Encoded
-- with a Python transcription of the Bitcoin Cash Node encoder.
longestCashAddr :: Text
longestCashAddr = "bitcoincash:quqqzqsrqszsvpcgpy9qkrqdpc83qygjzv2p29shrqv35xcur50p7gppyg3jgffxyu5zj23t9skjutesxyerxdp4xcmnswf68v7r603lr3frdfp5"

-- | A 1-of-n multisig of the compressed public key of private key 1.
multisigOf :: Ctx -> Int -> ScriptOutput
multisigOf ctx n = PayMulSig (replicate n (compressedKey ctx)) 1

-- | Public key of private key 1, in both forms.
compressedKey, uncompressedKey :: Ctx -> PublicKey
compressedKey ctx =
  derivePublicKey ctx . fromJust $
    fromWif btc "KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU73sVHnoWn"
uncompressedKey ctx =
  derivePublicKey ctx . fromJust $
    fromWif btc "5HpHagT65TZzG1PH3CSu63k8DbpvD8s5ip4nEB3kEsreAnchuDf"

-- | Public key of private key 2.
secondKey :: Ctx -> PublicKey
secondKey ctx =
  either error id . unmarshal ctx . fromJust $
    decodeHex "02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5"

-- | 1-of-2 multisig of the keys of private keys 1 and 2.
multisig :: Ctx -> ScriptOutput
multisig ctx = PayMulSig [compressedKey ctx, secondKey ctx] 1

multisigScript :: Text
multisigScript =
  "5121"
    <> "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"
    <> "21"
    <> "02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5"
    <> "52ae"

-- | Pay to the public key of private key 1.
p2pkScript :: Text
p2pkScript = "210279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798ac"

deserializeAddress :: Text -> Either String Address
deserializeAddress = runGetS deserialize . fromJust . decodeHex

{- Vectors -}

testVector :: (ByteString, Text, Text) -> Assertion
testVector (bs, e, chk) = do
  assertEqual "encodeBase58" e b58
  assertEqual "encodeBase58Check" chk b58Chk
  assertEqual "decodeBase58" (Just bs) (decodeBase58 b58)
  assertEqual "decodeBase58Check" (Just bs) (decodeBase58Check b58Chk)
  where
    b58 = encodeBase58 bs
    b58Chk = encodeBase58Check bs

vectors :: [(ByteString, Text, Text)]
vectors =
  [ (B.empty, "", "3QJmnh"),
    (B.pack [0], "1", "1Wh4bh"),
    (B.pack [0, 0, 0, 0], "1111", "11114bdQda"),
    (B.pack [0, 0, 1, 0, 0], "11LUw", "113CUwsFVuo"),
    (B.pack [255], "5Q", "VrZDWwe"),
    ( B.pack [0, 0, 0, 0] `B.append` B.pack [1 .. 255],
      "1111cWB5HCBdLjAuqGGReWE3R3CguuwSjw6RHn39s2yuDRTS5N\
      \sBgNiFpWgAnEx6VQi8csexkgYw3mdYrMHr8x9i7aEwP8kZ7vcc\
      \XWqKDvGv3u1GxFKPuAkn8JCPPGDMf3vMMnbzm6Nh9zh1gcNsMv\
      \H3ZNLmP5fSG6DGbbi2tuwMWPthr4boWwCxf7ewSgNQeacyozhK\
      \DDQQ1qL5fQFUW52QKUZDZ5fw3KXNQJMcNTcaB723LchjeKun7M\
      \uGW5qyCBZYzA1KjofN1gYBV3NqyhQJ3Ns746GNuf9N2pQPmHz4\
      \xpnSrrfCvy6TVVz5d4PdrjeshsWQwpZsZGzvbdAdN8MKV5QsBDY",
      "111151KWPPBRzdWPr1ASeu172gVgLf1YfUp6VJyk6K9t4cLqYt\
      \FHcMa2iX8S3NJEprUcW7W5LvaPRpz7UG7puBj5STE3nKhCGt5e\
      \ckYq7mMn5nT7oTTic2BAX6zDdqrmGCnkszQkzkz8e5QLGDjf7K\
      \eQgtEDm4UER6DMSdBjFQVa6cHrrJn9myVyyhUrsVnfUk2WmNFZ\
      \vkWv3Tnvzo2cJ1xW62XDfUgYz1pd97eUGGPuXvDFfLsBVd1dfd\
      \UhPwxW7pMPgdWHTmg5uqKGFF6vE4xXpAqZTbTxRZjCDdTn68c2\
      \wrcxApm8hq3JX65Hix7VtcD13FF8b7BzBtwjXq1ze6NMjKgUcq\
      \pJTN9vt"
    )
  ]

-- Test vectors from:
-- https://github.com/bitcoin/bitcoin/blob/master/src/test/data/base58_encode_decode.json

testBase58Vector :: (Text, Text) -> Assertion
testBase58Vector (a, b) = do
  assertEqual "encodeBase58 match" b (encodeBase58 bsA)
  assertEqual "decodeBase58 match" a (encodeHex bsB)
  assertEqual "bytestring match" bsA bsB
  where
    bsA = fromJust $ decodeHex a
    bsB = fromJust $ decodeBase58 b

base58Vectors :: [(Text, Text)]
base58Vectors =
  [ ("", ""),
    ("61", "2g"),
    ("626262", "a3gV"),
    ("636363", "aPEr"),
    ( "73696d706c792061206c6f6e6720737472696e67",
      "2cFupjhnEsSn59qHXstmK2ffpLv2"
    ),
    ( "00eb15231dfceb60925886b67d065299925915aeb172c06647",
      "1NS17iag9jJgTHD1VXjvLCEnZuQ3rJDE9L"
    ),
    ("516b6fcd0f", "ABnLTmg"),
    ("bf4f89001e670274dd", "3SEo3LWLoPntC"),
    ("572e4794", "3EFU7m"),
    ("ecac89cad93923c02321", "EJDM8drfXA6uyA"),
    ("10c8511e", "Rt5zm"),
    ("00000000000000000000", "1111111111"),
    ( "000111d38e5fc9071ffcd20b4a763cc9ae4f252bb4e48fd66a835e252a\
      \da93ff480d6dd43dc62a641155a5",
      "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
    ),
    ( "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c\
      \1d1e1f202122232425262728292a2b2c2d2e2f30313233343536373839\
      \3a3b3c3d3e3f404142434445464748494a4b4c4d4e4f50515253545556\
      \5758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f70717273\
      \7475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f90\
      \9192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacad\
      \aeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9ca\
      \cbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7\
      \e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
      "1cWB5HCBdLjAuqGGReWE3R3CguuwSjw6RHn39s2yuDRTS5NsBgNiFpWgAn\
      \Ex6VQi8csexkgYw3mdYrMHr8x9i7aEwP8kZ7vccXWqKDvGv3u1GxFKPuAk\
      \n8JCPPGDMf3vMMnbzm6Nh9zh1gcNsMvH3ZNLmP5fSG6DGbbi2tuwMWPthr\
      \4boWwCxf7ewSgNQeacyozhKDDQQ1qL5fQFUW52QKUZDZ5fw3KXNQJMcNTc\
      \aB723LchjeKun7MuGW5qyCBZYzA1KjofN1gYBV3NqyhQJ3Ns746GNuf9N2\
      \pQPmHz4xpnSrrfCvy6TVVz5d4PdrjeshsWQwpZsZGzvbdAdN8MKV5QsBDY"
    )
  ]

-- Test vectors from:
-- https://github.com/bitcoin/bitcoin/blob/master/src/test/base58_tests.cpp

testBase58InvalidVector :: (Text, Maybe Text) -> Assertion
testBase58InvalidVector (a, resM) =
  assertEqual "decodeBase58 invalid match" resM (encodeHex <$> decodeBase58 a)

base58InvalidVectors :: [(Text, Maybe Text)]
base58InvalidVectors =
  [ ("invalid", Nothing),
    ("\0invalid", Nothing),
    ("good", Just "768320"),
    ("bad0IOl", Nothing),
    ("goodbad0IOl", Nothing),
    ("good\0bad0IOl", Nothing)
    --  Haskoin does not remove white spaces before decoding base58 strings
    -- , (" \t\n\v\f\r skip \r\f\v\n\t a", Nothing)
    -- , (" \t\n\v\f\r skip \r\f\v\n\t ", Just "971a55")
  ]

testBase58ChkInvalidVector :: (Text, Maybe Text) -> Assertion
testBase58ChkInvalidVector (a, resM) =
  assertEqual
    "decodeBase58Check invalid match"
    resM
    (encodeHex <$> decodeBase58Check a)

base58ChkInvalidVectors :: [(Text, Maybe Text)]
base58ChkInvalidVectors =
  [ ("3vQB7B6MrGQZaxCuFg4oh", Just "68656c6c6f20776f726c64"),
    ("3vQB7B6MrGQZaxCuFg4oi", Nothing),
    ("3vQB7B6MrGQZaxCuFg4oh0IOl", Nothing),
    ("3vQB7B6MrGQZaxCuFg4oh\00IOl", Nothing)
  ]

testCompatWitnessVector :: Ctx -> (Network, Text, Text) -> Assertion
testCompatWitnessVector ctx (net, seckey, addr) = do
  let seckeyM = fromWif net seckey
  assertBool "decode seckey" (isJust seckeyM)
  let pubkey = derivePublicKey ctx (fromJust seckeyM)
  let addrM = addrToText btcTest =<< pubKeyCompatWitnessAddr ctx pubkey
  assertBool "address can be encoded" (isJust addrM)
  assertEqual "witness address matches" addr (fromJust addrM)

compatWitnessVectors :: [(Network, Text, Text)]
compatWitnessVectors =
  [ ( btcTest,
      "cNUnpYpMsJXYCERYBciJnsWBpcYEFjdcbq6dxj4SskGhs7uHuJ7Q",
      "2N6PDTueBHvXzW61B4oe5SW1D3v2Z3Vpbvw"
    )
  ]