text:
defmodule Bitcoinex.PSBTTest do
  use ExUnit.Case
  doctest Bitcoinex.PSBT

  alias Bitcoinex.PSBT
  alias Bitcoinex.PSBT.In
  alias Bitcoinex.PSBT.Out
  alias Bitcoinex.PSBT.Global

  @valid_psbts [
    %{
      psbt:
        "cHNidP8BAHUCAAAAASaBcTce3/KF6Tet7qSze3gADAVmy7OtZGQXE8pCFxv2AAAAAAD+////AtPf9QUAAAAAGXapFNDFmQPFusKGh2DpD9UhpGZap2UgiKwA4fUFAAAAABepFDVF5uM7gyxHBQ8k0+65PJwDlIvHh7MuEwAAAQD9pQEBAAAAAAECiaPHHqtNIOA3G7ukzGmPopXJRjr6Ljl/hTPMti+VZ+UBAAAAFxYAFL4Y0VKpsBIDna89p95PUzSe7LmF/////4b4qkOnHf8USIk6UwpyN+9rRgi7st0tAXHmOuxqSJC0AQAAABcWABT+Pp7xp0XpdNkCxDVZQ6vLNL1TU/////8CAMLrCwAAAAAZdqkUhc/xCX/Z4Ai7NK9wnGIZeziXikiIrHL++E4sAAAAF6kUM5cluiHv1irHU6m80GfWx6ajnQWHAkcwRAIgJxK+IuAnDzlPVoMR3HyppolwuAJf3TskAinwf4pfOiQCIAGLONfc0xTnNMkna9b7QPZzMlvEuqFEyADS8vAtsnZcASED0uFWdJQbrUqZY3LLh+GFbTZSYG2YVi/jnF6efkE/IQUCSDBFAiEA0SuFLYXc2WHS9fSrZgZU327tzHlMDDPOXMMJ/7X85Y0CIGczio4OFyXBl/saiK9Z9R5E5CVbIBZ8hoQDHAXR8lkqASECI7cr7vCWXRC+B3jv7NYfysb3mk6haTkzgHNEZPhPKrMAAAAAAAAA",
      expected_global: %Global{
        unsigned_tx: %Bitcoinex.Transaction{
          inputs: [
            %Bitcoinex.Transaction.In{
              prev_txid: "f61b1742ca13176464adb3cb66050c00787bb3a4eead37e985f2df1e37718126",
              prev_vout: 0,
              script_sig: "",
              sequence_no: 4_294_967_294
            }
          ],
          lock_time: 1_257_139,
          outputs: [
            %Bitcoinex.Transaction.Out{
              script_pub_key: "76a914d0c59903c5bac2868760e90fd521a4665aa7652088ac",
              value: 99_999_699
            },
            %Bitcoinex.Transaction.Out{
              script_pub_key: "a9143545e6e33b832c47050f24d3eeb93c9c03948bc787",
              value: 100_000_000
            }
          ],
          version: 2
        }
      },
      expected_in: [
        %In{
          non_witness_utxo: %Bitcoinex.Transaction{
            inputs: [
              %Bitcoinex.Transaction.In{
                prev_txid: "e567952fb6cc33857f392efa3a46c995a28f69cca4bb1b37e0204dab1ec7a389",
                prev_vout: 1,
                script_sig: "160014be18d152a9b012039daf3da7de4f53349eecb985",
                sequence_no: 4_294_967_295
              },
              %Bitcoinex.Transaction.In{
                prev_txid: "b490486aec3ae671012dddb2bb08466bef37720a533a894814ff1da743aaf886",
                prev_vout: 1,
                script_sig: "160014fe3e9ef1a745e974d902c4355943abcb34bd5353",
                sequence_no: 4_294_967_295
              }
            ],
            lock_time: 0,
            outputs: [
              %Bitcoinex.Transaction.Out{
                script_pub_key: "76a91485cff1097fd9e008bb34af709c62197b38978a4888ac",
                value: 200_000_000
              },
              %Bitcoinex.Transaction.Out{
                script_pub_key: "a914339725ba21efd62ac753a9bcd067d6c7a6a39d0587",
                value: 190_303_501_938
              }
            ],
            version: 1,
            witnesses: [
              %Bitcoinex.Transaction.Witness{
                txinwitness: [
                  "304402202712be22e0270f394f568311dc7ca9a68970b8025fdd3b240229f07f8a5f3a240220018b38d7dcd314e734c9276bd6fb40f673325bc4baa144c800d2f2f02db2765c01",
                  "03d2e15674941bad4a996372cb87e1856d3652606d98562fe39c5e9e7e413f2105"
                ]
              },
              %Bitcoinex.Transaction.Witness{
                txinwitness: [
                  "3045022100d12b852d85dcd961d2f5f4ab660654df6eedcc794c0c33ce5cc309ffb5fce58d022067338a8e0e1725c197fb1a88af59f51e44e4255b20167c8684031c05d1f2592a01",
                  "0223b72beef0965d10be0778efecd61fcac6f79a4ea169393380734464f84f2ab3"
                ]
              }
            ]
          }
        }
      ],
      expected_out: [
        %Bitcoinex.PSBT.Out{
          bip32_derivation: nil,
          proprietary: nil,
          redeem_script: nil,
          witness_script: nil
        },
        %Bitcoinex.PSBT.Out{
          bip32_derivation: nil,
          proprietary: nil,
          redeem_script: nil,
          witness_script: nil
        }
      ]
    },
    %{
      psbt:
        "cHNidP8BAKACAAAAAqsJSaCMWvfEm4IS9Bfi8Vqz9cM9zxU4IagTn4d6W3vkAAAAAAD+////qwlJoIxa98SbghL0F+LxWrP1wz3PFTghqBOfh3pbe+QBAAAAAP7///8CYDvqCwAAAAAZdqkUdopAu9dAy+gdmI5x3ipNXHE5ax2IrI4kAAAAAAAAGXapFG9GILVT+glechue4O/p+gOcykWXiKwAAAAAAAEHakcwRAIgR1lmF5fAGwNrJZKJSGhiGDR9iYZLcZ4ff89X0eURZYcCIFMJ6r9Wqk2Ikf/REf3xM286KdqGbX+EhtdVRs7tr5MZASEDXNxh/HupccC1AaZGoqg7ECy0OIEhfKaC3Ibi1z+ogpIAAQEgAOH1BQAAAAAXqRQ1RebjO4MsRwUPJNPuuTycA5SLx4cBBBYAFIXRNTfy4mVAWjTbr6nj3aAfuCMIAAAA",
      expected_global: %Global{
        unsigned_tx: %Bitcoinex.Transaction{
          inputs: [
            %Bitcoinex.Transaction.In{
              prev_txid: "e47b5b7a879f13a8213815cf3dc3f5b35af1e217f412829bc4f75a8ca04909ab",
              prev_vout: 0,
              script_sig: "",
              sequence_no: 4_294_967_294
            },
            %Bitcoinex.Transaction.In{
              prev_txid: "e47b5b7a879f13a8213815cf3dc3f5b35af1e217f412829bc4f75a8ca04909ab",
              prev_vout: 1,
              script_sig: "",
              sequence_no: 4_294_967_294
            }
          ],
          lock_time: 0,
          outputs: [
            %Bitcoinex.Transaction.Out{
              script_pub_key: "76a914768a40bbd740cbe81d988e71de2a4d5c71396b1d88ac",
              value: 199_900_000
            },
            %Bitcoinex.Transaction.Out{
              script_pub_key: "76a9146f4620b553fa095e721b9ee0efe9fa039cca459788ac",
              value: 9358
            }
          ],
          version: 2,
          witnesses: nil
        }
      },
      expected_in: [
        %In{
          bip32_derivation: nil,
          final_scriptsig:
            "47304402204759661797c01b036b25928948686218347d89864b719e1f7fcf57d1e511658702205309eabf56aa4d8891ffd111fdf1336f3a29da866d7f8486d75546ceedaf93190121035cdc61fc7ba971c0b501a646a2a83b102cb43881217ca682dc86e2d73fa88292",
          final_scriptwitness: nil,
          non_witness_utxo: nil,
          partial_sig: nil,
          por_commitment: nil,
          proprietary: nil,
          redeem_script: nil,
          sighash_type: nil,
          witness_script: nil,
          witness_utxo: nil
        },
        %In{
          bip32_derivation: nil,
          final_scriptsig: nil,
          final_scriptwitness: nil,
          non_witness_utxo: nil,
          partial_sig: nil,
          por_commitment: nil,
          proprietary: nil,
          redeem_script: "001485d13537f2e265405a34dbafa9e3dda01fb82308",
          sighash_type: nil,
          witness_script: nil,
          witness_utxo: %Bitcoinex.Transaction.Out{
            script_pub_key: "a9143545e6e33b832c47050f24d3eeb93c9c03948bc787",
            value: 100_000_000
          }
        }
      ],
      expected_out: [
        %Bitcoinex.PSBT.Out{
          bip32_derivation: nil,
          proprietary: nil,
          redeem_script: nil,
          witness_script: nil
        },
        %Bitcoinex.PSBT.Out{
          bip32_derivation: nil,
          proprietary: nil,
          redeem_script: nil,
          witness_script: nil
        }
      ]
    },
    %{
      psbt:
        "cHNidP8BAFICAAAAAZ38ZijCbFiZ/hvT3DOGZb/VXXraEPYiCXPfLTht7BJ2AQAAAAD/////AfA9zR0AAAAAFgAUezoAv9wU0neVwrdJAdCdpu8TNXkAAAAATwEENYfPAto/0AiAAAAAlwSLGtBEWx7IJ1UXcnyHtOTrwYogP/oPlMAVZr046QADUbdDiH7h1A3DKmBDck8tZFmztaTXPa7I+64EcvO8Q+IM2QxqT64AAIAAAACATwEENYfPAto/0AiAAAABuQRSQnE5zXjCz/JES+NTzVhgXj5RMoXlKLQH+uP2FzUD0wpel8itvFV9rCrZp+OcFyLrrGnmaLbyZnzB1nHIPKsM2QxqT64AAIABAACAAAEBKwBlzR0AAAAAIgAgLFSGEmxJeAeagU4TcV1l82RZ5NbMre0mbQUIZFuvpjIBBUdSIQKdoSzbWyNWkrkVNq/v5ckcOrlHPY5DtTODarRWKZyIcSEDNys0I07Xz5wf6l0F1EFVeSe+lUKxYusC4ass6AIkwAtSriIGAp2hLNtbI1aSuRU2r+/lyRw6uUc9jkO1M4NqtFYpnIhxENkMak+uAACAAAAAgAAAAAAiBgM3KzQjTtfPnB/qXQXUQVV5J76VQrFi6wLhqyzoAiTACxDZDGpPrgAAgAEAAIAAAAAAACICA57/H1R6HV+S36K6evaslxpL0DukpzSwMVaiVritOh75EO3kXMUAAACAAAAAgAEAAIAA",
      expected_global: %Global{
        proprietary: nil,
        unsigned_tx: %Bitcoinex.Transaction{
          inputs: [
            %Bitcoinex.Transaction.In{
              prev_txid: "7612ec6d382ddf730922f610da7a5dd5bf658633dcd31bfe99586cc22866fc9d",
              prev_vout: 1,
              script_sig: "",
              sequence_no: 4_294_967_295
            }
          ],
          lock_time: 0,
          outputs: [
            %Bitcoinex.Transaction.Out{
              script_pub_key: "00147b3a00bfdc14d27795c2b74901d09da6ef133579",
              value: 499_990_000
            }
          ],
          version: 2,
          witnesses: nil
        },
        version: nil,
        xpub: [
          %{
            derivation: [2_147_483_822, 2_147_483_648],
            master_pfp: 1_332_350_169,
            xpub:
              "tpubDBkJeJo2X94Yq3RVz65DoUgyLUkaDrkfyrn2VcgyCRSKCRonvKvCF2FpYDGJWDkdRHBajXJGpc63GnumUt63ySvqCu2XaTRGVTKMYGuFk9H"
          },
          %{
            derivation: [2_147_483_822, 2_147_483_649],
            master_pfp: 1_332_350_169,
            xpub:
              "tpubDBkJeJo2X94YsvtBEU1eKoibEWiNv51nW5iHhs6VZp59jsE6nen8KZMFyGHuGbCvqjRqirgeMcfpVBkttpUUT6brm4duzSGoZeTbhqCNUu6"
          }
        ]
      },
      expected_in: [
        %Bitcoinex.PSBT.In{
          bip32_derivation: [
            %{
              derivation: [2_147_483_822, 2_147_483_648, 0],
              public_key: "029da12cdb5b235692b91536afefe5c91c3ab9473d8e43b533836ab456299c8871",
              pfp: 1_332_350_169
            },
            %{
              derivation: [2_147_483_822, 2_147_483_649, 0],
              public_key: "03372b34234ed7cf9c1fea5d05d441557927be9542b162eb02e1ab2ce80224c00b",
              pfp: 1_332_350_169
            }
          ],
          final_scriptsig: nil,
          final_scriptwitness: nil,
          non_witness_utxo: nil,
          partial_sig: nil,
          por_commitment: nil,
          proprietary: nil,
          redeem_script: nil,
          sighash_type: nil,
          witness_script:
            "5221029da12cdb5b235692b91536afefe5c91c3ab9473d8e43b533836ab456299c88712103372b34234ed7cf9c1fea5d05d441557927be9542b162eb02e1ab2ce80224c00b52ae",
          witness_utxo: %Bitcoinex.Transaction.Out{
            script_pub_key:
              "00202c5486126c4978079a814e13715d65f36459e4d6ccaded266d0508645bafa632",
            value: 500_000_000
          }
        }
      ],
      expected_out: [
        %Out{
          bip32_derivation: [
            %{
              derivation: [2_147_483_648, 2_147_483_648, 2_147_483_649],
              public_key: "039eff1f547a1d5f92dfa2ba7af6ac971a4bd03ba4a734b03156a256b8ad3a1ef9",
              pfp: 3_311_199_469
            }
          ],
          proprietary: nil,
          redeem_script: nil,
          witness_script: nil
        }
      ]
    },
    %{
      psbt:
        "cHNidP8BAFUCAAAAASeaIyOl37UfxF8iD6WLD8E+HjNCeSqF1+Ns1jM7XLw5AAAAAAD/////AaBa6gsAAAAAGXapFP/pwAYQl8w7Y28ssEYPpPxCfStFiKwAAAAAAAEBIJVe6gsAAAAAF6kUY0UgD2jRieGtwN8cTRbqjxTA2+uHIgIDsTQcy6doO2r08SOM1ul+cWfVafrEfx5I1HVBhENVvUZGMEMCIAQktY7/qqaU4VWepck7v9SokGQiQFXN8HC2dxRpRC0HAh9cjrD+plFtYLisszrWTt5g6Hhb+zqpS5m9+GFR25qaAQEEIgAgdx/RitRZZm3Unz1WTj28QvTIR3TjYK2haBao7UiNVoEBBUdSIQOxNBzLp2g7avTxI4zW6X5xZ9Vp+sR/HkjUdUGEQ1W9RiED3lXR4drIBeP4pYwfv5uUwC89uq/hJ/78pJlfJvggg71SriIGA7E0HMunaDtq9PEjjNbpfnFn1Wn6xH8eSNR1QYRDVb1GELSmumcAAACAAAAAgAQAAIAiBgPeVdHh2sgF4/iljB+/m5TALz26r+En/vykmV8m+CCDvRC0prpnAAAAgAAAAIAFAACAAAA=",
      expected_global: %Global{
        proprietary: nil,
        unsigned_tx: %Bitcoinex.Transaction{
          inputs: [
            %Bitcoinex.Transaction.In{
              prev_txid: "39bc5c3b33d66ce3d7852a7942331e3ec10f8ba50f225fc41fb5dfa523239a27",
              prev_vout: 0,
              script_sig: "",
              sequence_no: 4_294_967_295
            }
          ],
          lock_time: 0,
          outputs: [
            %Bitcoinex.Transaction.Out{
              script_pub_key: "76a914ffe9c0061097cc3b636f2cb0460fa4fc427d2b4588ac",
              value: 199_908_000
            }
          ],
          version: 2,
          witnesses: nil
        },
        version: nil,
        xpub: nil
      },
      expected_in: [
        %Bitcoinex.PSBT.In{
          bip32_derivation: [
            %{
              derivation: [2_147_483_648, 2_147_483_648, 2_147_483_652],
              public_key: "03b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd46",
              pfp: 1_740_285_620
            },
            %{
              derivation: [2_147_483_648, 2_147_483_648, 2_147_483_653],
              public_key: "03de55d1e1dac805e3f8a58c1fbf9b94c02f3dbaafe127fefca4995f26f82083bd",
              pfp: 1_740_285_620
            }
          ],
          final_scriptsig: nil,
          final_scriptwitness: nil,
          non_witness_utxo: nil,
          partial_sig: %{
            public_key: "03b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd46",
            signature:
              "304302200424b58effaaa694e1559ea5c93bbfd4a89064224055cdf070b6771469442d07021f5c8eb0fea6516d60b8acb33ad64ede60e8785bfb3aa94b99bdf86151db9a9a01"
          },
          por_commitment: nil,
          proprietary: nil,
          redeem_script: "0020771fd18ad459666dd49f3d564e3dbc42f4c84774e360ada16816a8ed488d5681",
          sighash_type: nil,
          witness_script:
            "522103b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd462103de55d1e1dac805e3f8a58c1fbf9b94c02f3dbaafe127fefca4995f26f82083bd52ae",
          witness_utxo: %Bitcoinex.Transaction.Out{
            script_pub_key: "a9146345200f68d189e1adc0df1c4d16ea8f14c0dbeb87",
            value: 199_909_013
          }
        }
      ],
      expected_out: [
        %Bitcoinex.PSBT.Out{
          bip32_derivation: nil,
          proprietary: nil,
          redeem_script: nil,
          witness_script: nil
        }
      ]
    },
    %{
      # finalized psbt
      psbt:
        "cHNidP8BAKcBAAAAAjHC7gs4NF4rUOrlta+j+wB8UHTEuLn0XY6FDUcGybQMAAAAAAD+////NUUKTkDqBbL9oqrAIk9199/ZANXi/8XEgguqQY8iiewAAAAAAP7///8CdImYAAAAAAAiACCs+u6eefBEoqCFYVWhxscCwh/WJZ+286/E8zNH9gRhd4CWmAAAAAAAF6kUV3ZMSDpAgQZkllBPVNL5uRPlwOOHAAAAAAABASuAlpgAAAAAACIAIDH8Jza8S0T6nWkCcU5GqgwxJ2rGEgWFgDSGiJVFJ5W0AQj9/QAEAEgwRQIhALL4SZucnmwtsJ2BguTQkajOkbvRTRcIMF2B/c26pnZDAiAwNPAWsW3b3PxNXZouG43Z2HJ4WufvpjM0x+VlprgFUAFHMEQCIGV66oyrbw0b9HXA8EeGKrIi88YhTGuhpQKdDxX1VivPAiAcxSrameybDohX8yINx2t452PyyqP6qUiTUMNnoAv+twFpUiECZ3pcsDl1tPNTASW/gFEm/PlWLEnQJN5h32F5qmC2U6AhA1fyyfYB3ma7Vg6JKICdCsQFD7/IchNleJnjTaTGbCFgIQP8V/0ULlUTx5q8mJ6eJh6GaCHkHXDkTnmFbpZRGDsQVVOuAAEBK4CWmAAAAAAAIgAgi3WHXCAbeRTULI6EPlb3Z3+J153IX4zK5bHRsqnrSO4BCPwEAEcwRAIgelTwDK+TOYwP6luGb5htloRgijKLoLmNrjk9imXolaICIFQ9Rq0MrOGcrYHC6BZIyyz+tB0Lm8FhqnARl7R+TpyaAUcwRAIgfHNbxYLcTt1yWeADHyo5ye4jtApn+YTgFzK16IsOW0QCIDcOnv2QYaZlc0etz9kfIrkpoepeTndtvEREKROzqqlCAWlSIQIIPVGeoWYEHRGxyDhpzTqE0uBZIjBj5DDXgBX5QWwecCECL5C1pXxiQ5uiuhZASuHYEUq+gXmXqE+wxPnV590o+HAhA0odK6A98KAdcHcI5pcbNfwR1oq0PsofJzNfvSKkdqCMU64AAQFpUiECPhqS90SDpMEqGW1sAlOsWJz63Vlk/z5sY6711XcFHtQhAk0OObM6tXeCqY/Qan0GUzheUJ7jt03EVVnm22OR0xN4IQNsC65rywLkfIV8SA7R0jiIyK1qZrg6sRHLa5JCr7HHJVOuIgICPhqS90SDpMEqGW1sAlOsWJz63Vlk/z5sY6711XcFHtQgAAAAAAAAAIACAACAAgAAAAAAAAAAAAAAAQAAAA0AAAAiAgJNDjmzOrV3gqmP0Gp9BlM4XlCe47dNxFVZ5ttjkdMTeCAAAAAAAAAAgAIAAIACAAAAAAAAAAAAAAABAAAADQAAACICA2wLrmvLAuR8hXxIDtHSOIjIrWpmuDqxEctrkkKvscclIAAAAAAAAACAAgAAgAIAAAAAAAAAAAAAAAEAAAANAAAAAAA=",
      expected_global: %Global{
        proprietary: nil,
        unsigned_tx: %Bitcoinex.Transaction{
          inputs: [
            %Bitcoinex.Transaction.In{
              prev_txid: "0cb4c906470d858e5df4b9b8c474507c00fba3afb5e5ea502b5e34380beec231",
              prev_vout: 0,
              script_sig: "",
              sequence_no: 4_294_967_294
            },
            %Bitcoinex.Transaction.In{
              prev_txid: "ec89228f41aa0b82c4c5ffe2d500d9dff7754f22c0aaa2fdb205ea404e0a4535",
              prev_vout: 0,
              script_sig: "",
              sequence_no: 4_294_967_294
            }
          ],
          lock_time: 0,
          outputs: [
            %Bitcoinex.Transaction.Out{
              script_pub_key:
                "0020acfaee9e79f044a2a0856155a1c6c702c21fd6259fb6f3afc4f33347f6046177",
              value: 9_996_660
            },
            %Bitcoinex.Transaction.Out{
              script_pub_key: "a91457764c483a4081066496504f54d2f9b913e5c0e387",
              value: 10_000_000
            }
          ],
          version: 1,
          witnesses: nil
        },
        version: nil,
        xpub: nil
      },
      expected_in: [
        %Bitcoinex.PSBT.In{
          bip32_derivation: nil,
          final_scriptsig: nil,
          final_scriptwitness: %Bitcoinex.Transaction.Witness{
            txinwitness: [
              "",
              "3045022100b2f8499b9c9e6c2db09d8182e4d091a8ce91bbd14d1708305d81fdcdbaa6764302203034f016b16ddbdcfc4d5d9a2e1b8dd9d872785ae7efa63334c7e565a6b8055001",
              "30440220657aea8cab6f0d1bf475c0f047862ab222f3c6214c6ba1a5029d0f15f5562bcf02201cc52ada99ec9b0e8857f3220dc76b78e763f2caa3faa9489350c367a00bfeb701",
              "522102677a5cb03975b4f3530125bf805126fcf9562c49d024de61df6179aa60b653a0210357f2c9f601de66bb560e8928809d0ac4050fbfc87213657899e34da4c66c21602103fc57fd142e5513c79abc989e9e261e866821e41d70e44e79856e9651183b105553ae"
            ]
          },
          non_witness_utxo: nil,
          partial_sig: nil,
          por_commitment: nil,
          proprietary: nil,
          redeem_script: nil,
          sighash_type: nil,
          witness_script: nil,
          witness_utxo: %Bitcoinex.Transaction.Out{
            script_pub_key:
              "002031fc2736bc4b44fa9d6902714e46aa0c31276ac61205858034868895452795b4",
            value: 10_000_000
          }
        },
        %Bitcoinex.PSBT.In{
          bip32_derivation: nil,
          final_scriptsig: nil,
          final_scriptwitness: %Bitcoinex.Transaction.Witness{
            txinwitness: [
              "",
              "304402207a54f00caf93398c0fea5b866f986d9684608a328ba0b98dae393d8a65e895a20220543d46ad0cace19cad81c2e81648cb2cfeb41d0b9bc161aa701197b47e4e9c9a01",
              "304402207c735bc582dc4edd7259e0031f2a39c9ee23b40a67f984e01732b5e88b0e5b440220370e9efd9061a6657347adcfd91f22b929a1ea5e4e776dbc44442913b3aaa94201",
              "522102083d519ea166041d11b1c83869cd3a84d2e059223063e430d78015f9416c1e7021022f90b5a57c62439ba2ba16404ae1d8114abe817997a84fb0c4f9d5e7dd28f87021034a1d2ba03df0a01d707708e6971b35fc11d68ab43eca1f27335fbd22a476a08c53ae"
            ]
          },
          non_witness_utxo: nil,
          partial_sig: nil,
          por_commitment: nil,
          proprietary: nil,
          redeem_script: nil,
          sighash_type: nil,
          witness_script: nil,
          witness_utxo: %Bitcoinex.Transaction.Out{
            script_pub_key:
              "00208b75875c201b7914d42c8e843e56f7677f89d79dc85f8ccae5b1d1b2a9eb48ee",
            value: 10_000_000
          }
        }
      ],
      expected_out: [
        %Bitcoinex.PSBT.Out{
          bip32_derivation: [
            %{
              derivation: [2_147_483_648, 2_147_483_650, 2, 0, 0, 1, 13],
              public_key: "023e1a92f74483a4c12a196d6c0253ac589cfadd5964ff3e6c63aef5d577051ed4",
              pfp: 0
            },
            %{
              derivation: [2_147_483_648, 2_147_483_650, 2, 0, 0, 1, 13],
              public_key: "024d0e39b33ab57782a98fd06a7d0653385e509ee3b74dc45559e6db6391d31378",
              pfp: 0
            },
            %{
              derivation: [2_147_483_648, 2_147_483_650, 2, 0, 0, 1, 13],
              public_key: "036c0bae6bcb02e47c857c480ed1d23888c8ad6a66b83ab111cb6b9242afb1c725",
              pfp: 0
            }
          ],
          proprietary: nil,
          redeem_script: nil,
          witness_script:
            "5221023e1a92f74483a4c12a196d6c0253ac589cfadd5964ff3e6c63aef5d577051ed421024d0e39b33ab57782a98fd06a7d0653385e509ee3b74dc45559e6db6391d3137821036c0bae6bcb02e47c857c480ed1d23888c8ad6a66b83ab111cb6b9242afb1c72553ae"
        },
        %Bitcoinex.PSBT.Out{
          bip32_derivation: nil,
          proprietary: nil,
          redeem_script: nil,
          witness_script: nil
        }
      ]
    },
    %{
      psbt: "cHNidP8BAAoAAAAAAAAAAAAAAA==",
      expected_global: %Global{
        proprietary: nil,
        unsigned_tx: %Bitcoinex.Transaction{
          inputs: [],
          lock_time: 0,
          outputs: [],
          witnesses: nil,
          version: 0
        },
        version: nil,
        xpub: nil
      },
      expected_in: [],
      expected_out: []
    },
    %{
      psbt:
        "cHNidP8BAHUCAAAAASaBcTce3/KF6Tet7qSze3gADAVmy7OtZGQXE8pCFxv2AAAAAAD+////AtPf9QUAAAAAGXapFNDFmQPFusKGh2DpD9UhpGZap2UgiKwA4fUFAAAAABepFDVF5uM7gyxHBQ8k0+65PJwDlIvHh7MuEwAAAQD9pQEBAAAAAAECiaPHHqtNIOA3G7ukzGmPopXJRjr6Ljl/hTPMti+VZ+UBAAAAFxYAFL4Y0VKpsBIDna89p95PUzSe7LmF/////4b4qkOnHf8USIk6UwpyN+9rRgi7st0tAXHmOuxqSJC0AQAAABcWABT+Pp7xp0XpdNkCxDVZQ6vLNL1TU/////8CAMLrCwAAAAAZdqkUhc/xCX/Z4Ai7NK9wnGIZeziXikiIrHL++E4sAAAAF6kUM5cluiHv1irHU6m80GfWx6ajnQWHAkcwRAIgJxK+IuAnDzlPVoMR3HyppolwuAJf3TskAinwf4pfOiQCIAGLONfc0xTnNMkna9b7QPZzMlvEuqFEyADS8vAtsnZcASED0uFWdJQbrUqZY3LLh+GFbTZSYG2YVi/jnF6efkE/IQUCSDBFAiEA0SuFLYXc2WHS9fSrZgZU327tzHlMDDPOXMMJ/7X85Y0CIGczio4OFyXBl/saiK9Z9R5E5CVbIBZ8hoQDHAXR8lkqASECI7cr7vCWXRC+B3jv7NYfysb3mk6haTkzgHNEZPhPKrMAAAAAAQMEAQAAAAAAAA==",
      expected_global: %Global{
        proprietary: nil,
        unsigned_tx: %Bitcoinex.Transaction{
          inputs: [
            %Bitcoinex.Transaction.In{
              prev_txid: "f61b1742ca13176464adb3cb66050c00787bb3a4eead37e985f2df1e37718126",
              prev_vout: 0,
              script_sig: "",
              sequence_no: 4_294_967_294
            }
          ],
          lock_time: 1_257_139,
          outputs: [
            %Bitcoinex.Transaction.Out{
              script_pub_key: "76a914d0c59903c5bac2868760e90fd521a4665aa7652088ac",
              value: 99_999_699
            },
            %Bitcoinex.Transaction.Out{
              script_pub_key: "a9143545e6e33b832c47050f24d3eeb93c9c03948bc787",
              value: 100_000_000
            }
          ],
          witnesses: nil,
          version: 2
        },
        version: nil,
        xpub: nil
      },
      expected_in: [
        %Bitcoinex.PSBT.In{
          bip32_derivation: nil,
          final_scriptsig: nil,
          final_scriptwitness: nil,
          non_witness_utxo: %Bitcoinex.Transaction{
            inputs: [
              %Bitcoinex.Transaction.In{
                prev_txid: "e567952fb6cc33857f392efa3a46c995a28f69cca4bb1b37e0204dab1ec7a389",
                prev_vout: 1,
                script_sig: "160014be18d152a9b012039daf3da7de4f53349eecb985",
                sequence_no: 4_294_967_295
              },
              %Bitcoinex.Transaction.In{
                prev_txid: "b490486aec3ae671012dddb2bb08466bef37720a533a894814ff1da743aaf886",
                prev_vout: 1,
                script_sig: "160014fe3e9ef1a745e974d902c4355943abcb34bd5353",
                sequence_no: 4_294_967_295
              }
            ],
            lock_time: 0,
            outputs: [
              %Bitcoinex.Transaction.Out{
                script_pub_key: "76a91485cff1097fd9e008bb34af709c62197b38978a4888ac",
                value: 200_000_000
              },
              %Bitcoinex.Transaction.Out{
                script_pub_key: "a914339725ba21efd62ac753a9bcd067d6c7a6a39d0587",
                value: 190_303_501_938
              }
            ],
            version: 1,
            witnesses: [
              %Bitcoinex.Transaction.Witness{
                txinwitness: [
                  "304402202712be22e0270f394f568311dc7ca9a68970b8025fdd3b240229f07f8a5f3a240220018b38d7dcd314e734c9276bd6fb40f673325bc4baa144c800d2f2f02db2765c01",
                  "03d2e15674941bad4a996372cb87e1856d3652606d98562fe39c5e9e7e413f2105"
                ]
              },
              %Bitcoinex.Transaction.Witness{
                txinwitness: [
                  "3045022100d12b852d85dcd961d2f5f4ab660654df6eedcc794c0c33ce5cc309ffb5fce58d022067338a8e0e1725c197fb1a88af59f51e44e4255b20167c8684031c05d1f2592a01",
                  "0223b72beef0965d10be0778efecd61fcac6f79a4ea169393380734464f84f2ab3"
                ]
              }
            ]
          },
          partial_sig: nil,
          por_commitment: nil,
          proprietary: nil,
          redeem_script: nil,
          sighash_type: <<1, 0, 0, 0>>,
          witness_script: nil,
          witness_utxo: nil
        }
      ],
      expected_out: [
        %Bitcoinex.PSBT.Out{
          bip32_derivation: nil,
          proprietary: nil,
          redeem_script: nil,
          witness_script: nil
        },
        %Bitcoinex.PSBT.Out{
          bip32_derivation: nil,
          proprietary: nil,
          redeem_script: nil,
          witness_script: nil
        }
      ]
    }
  ]

  describe "decode/1" do
    test "valid psbts" do
      for valid_psbt <- @valid_psbts do
        case PSBT.decode(valid_psbt.psbt) do
          {:ok, psbt} ->
            assert valid_psbt.expected_global == psbt.global
            assert valid_psbt.expected_in == psbt.inputs
            assert valid_psbt.expected_out == psbt.outputs
            assert valid_psbt.psbt == PSBT.encode_b64(psbt)

          {:error, _} ->
            assert :error != :error
        end
      end
    end
  end

  describe "to_file/2 & from_file/1" do
    test "valid psbts" do
      filename = "./test/psbt-test.psbt"

      for valid_psbt <- @valid_psbts do
        case PSBT.decode(valid_psbt.psbt) do
          {:ok, psbt_in} ->
            PSBT.to_file(psbt_in, filename)
            {res, psbt_out} = PSBT.from_file(filename)
            assert res == :ok
            assert valid_psbt.expected_global == psbt_out.global
            assert valid_psbt.expected_in == psbt_out.inputs
            assert valid_psbt.expected_out == psbt_out.outputs
            assert valid_psbt.psbt == PSBT.encode_b64(psbt_out)

          {:error, _} ->
            assert false
        end
      end

      File.rm_rf(filename)
    end
  end
end


text:
defmodule Bitcoinex.Network do
  @enforce_keys [
    :name,
    :hrp_segwit_prefix,
    :p2pkh_version_decimal_prefix,
    :p2sh_version_decimal_prefix
  ]
  defstruct [
    :name,
    :hrp_segwit_prefix,
    :p2pkh_version_decimal_prefix,
    :p2sh_version_decimal_prefix
  ]

  @type t() :: %__MODULE__{
          name: atom,
          hrp_segwit_prefix: String.t(),
          p2pkh_version_decimal_prefix: integer(),
          p2sh_version_decimal_prefix: integer()
        }

  @type network_name :: :mainnet | :testnet | :regtest

  def supported_networks() do
    [
      mainnet(),
      testnet(),
      regtest()
    ]
  end

  def mainnet do
    %__MODULE__{
      name: :mainnet,
      hrp_segwit_prefix: "bc",
      p2pkh_version_decimal_prefix: 0,
      p2sh_version_decimal_prefix: 5
    }
  end

  def testnet do
    %__MODULE__{
      name: :testnet,
      hrp_segwit_prefix: "tb",
      p2pkh_version_decimal_prefix: 111,
      p2sh_version_decimal_prefix: 196
    }
  end

  def regtest do
    %__MODULE__{
      name: :regtest,
      hrp_segwit_prefix: "bcrt",
      p2pkh_version_decimal_prefix: 111,
      p2sh_version_decimal_prefix: 196
    }
  end
end

repo_name: RiverFinancial/bitcoinex

repo_language: Elixir

mime_type: text/plain


text:
defmodule Bitcoinex.Secp256k1.ExtendedKeyTest do
  use ExUnit.Case
  doctest Bitcoinex.ExtendedKey

  alias Bitcoinex.ExtendedKey
  alias Bitcoinex.Secp256k1.Ecdsa

  @min_hardened_child_num 0x80000000
  # 2^32
  @max_hardened_child_num 0x100000000

  @invalid_xkeys [
    # changed prefix
    "zpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8",
    # invalid char
    "xpubi61MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8",
    # invalid prefix
    "apub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8",
    # invalid len
    "xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet",
    # invalid len
    "xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1E",
    # invalid len (with valid checksum)
    "Deb7pNXSbX7qSvc2eMjkNYTrggh4pBgYa2QMFjEjj6hUy1iHp71d1gf3ue7Ni7X8Pkhcy13aakoEYSMS1DFiMaXB4qYhN33dh3oqD9n1YWSxVk"
  ]

  @bip32_test_case_1 %{
    # test vectors from bip32: https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki#test-vectors
    seed: "000102030405060708090a0b0c0d0e0f",
    xpub_m:
      "xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8",
    xpub_m_obj: %Bitcoinex.ExtendedKey{
      chaincode:
        <<135, 61, 255, 129, 192, 47, 82, 86, 35, 253, 31, 229, 22, 126, 172, 58, 85, 160, 73,
          222, 61, 49, 75, 180, 46, 226, 39, 255, 237, 55, 213, 8>>,
      checksum: <<171, 71, 59, 33>>,
      child_num: <<0, 0, 0, 0>>,
      depth: <<0>>,
      key:
        <<3, 57, 163, 96, 19, 48, 21, 151, 218, 239, 65, 251, 229, 147, 160, 44, 197, 19, 208,
          181, 85, 39, 236, 45, 241, 5, 14, 46, 143, 244, 156, 133, 194>>,
      parent_fingerprint: <<0, 0, 0, 0>>,
      prefix: <<4, 136, 178, 30>>
    },
    xprv_m:
      "xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi",
    xprv_m_obj: %Bitcoinex.ExtendedKey{
      chaincode:
        <<135, 61, 255, 129, 192, 47, 82, 86, 35, 253, 31, 229, 22, 126, 172, 58, 85, 160, 73,
          222, 61, 49, 75, 180, 46, 226, 39, 255, 237, 55, 213, 8>>,
      checksum: <<231, 126, 157, 113>>,
      child_num: <<0, 0, 0, 0>>,
      depth: <<0>>,
      key:
        <<0, 232, 243, 46, 114, 61, 236, 244, 5, 26, 239, 172, 142, 44, 147, 201, 197, 178, 20,
          49, 56, 23, 205, 176, 26, 20, 148, 185, 23, 200, 67, 107, 53>>,
      parent_fingerprint: <<0, 0, 0, 0>>,
      prefix: <<4, 136, 173, 228>>
    },
    xpub_m_0h:
      "xpub68Gmy5EdvgibQVfPdqkBBCHxA5htiqg55crXYuXoQRKfDBFA1WEjWgP6LHhwBZeNK1VTsfTFUHCdrfp1bgwQ9xv5ski8PX9rL2dZXvgGDnw",
    xprv_m_0h:
      "xprv9uHRZZhk6KAJC1avXpDAp4MDc3sQKNxDiPvvkX8Br5ngLNv1TxvUxt4cV1rGL5hj6KCesnDYUhd7oWgT11eZG7XnxHrnYeSvkzY7d2bhkJ7",
    xpub_m_0h_1:
      "xpub6ASuArnXKPbfEwhqN6e3mwBcDTgzisQN1wXN9BJcM47sSikHjJf3UFHKkNAWbWMiGj7Wf5uMash7SyYq527Hqck2AxYysAA7xmALppuCkwQ",
    xprv_m_0h_1:
      "xprv9wTYmMFdV23N2TdNG573QoEsfRrWKQgWeibmLntzniatZvR9BmLnvSxqu53Kw1UmYPxLgboyZQaXwTCg8MSY3H2EU4pWcQDnRnrVA1xe8fs",
    xpub_m_0h_1_2h:
      "xpub6D4BDPcP2GT577Vvch3R8wDkScZWzQzMMUm3PWbmWvVJrZwQY4VUNgqFJPMM3No2dFDFGTsxxpG5uJh7n7epu4trkrX7x7DogT5Uv6fcLW5",
    xprv_m_0h_1_2h:
      "xprv9z4pot5VBttmtdRTWfWQmoH1taj2axGVzFqSb8C9xaxKymcFzXBDptWmT7FwuEzG3ryjH4ktypQSAewRiNMjANTtpgP4mLTj34bhnZX7UiM",
    xpub_m_0h_1_2h_2:
      "xpub6FHa3pjLCk84BayeJxFW2SP4XRrFd1JYnxeLeU8EqN3vDfZmbqBqaGJAyiLjTAwm6ZLRQUMv1ZACTj37sR62cfN7fe5JnJ7dh8zL4fiyLHV",
    xprv_m_0h_1_2h_2:
      "xprvA2JDeKCSNNZky6uBCviVfJSKyQ1mDYahRjijr5idH2WwLsEd4Hsb2Tyh8RfQMuPh7f7RtyzTtdrbdqqsunu5Mm3wDvUAKRHSC34sJ7in334",
    xpub_m_0h_1_2h_2_1000000000:
      "xpub6H1LXWLaKsWFhvm6RVpEL9P4KfRZSW7abD2ttkWP3SSQvnyA8FSVqNTEcYFgJS2UaFcxupHiYkro49S8yGasTvXEYBVPamhGW6cFJodrTHy",
    xprv_m_0h_1_2h_2_1000000000:
      "xprvA41z7zogVVwxVSgdKUHDy1SKmdb533PjDz7J6N6mV6uS3ze1ai8FHa8kmHScGpWmj4WggLyQjgPie1rFSruoUihUZREPSL39UNdE3BBDu76"
  }

  @bip32_test_case_2 %{
    seed:
      "fffcf9f6f3f0edeae7e4e1dedbd8d5d2cfccc9c6c3c0bdbab7b4b1aeaba8a5a29f9c999693908d8a8784817e7b7875726f6c696663605d5a5754514e4b484542",
    xpub_m:
      "xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB",
    xprv_m:
      "xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U",
    xpub_m_0:
      "xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH",
    xprv_m_0:
      "xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt",
    xpub_m_0_2147483647h:
      "xpub6ASAVgeehLbnwdqV6UKMHVzgqAG8Gr6riv3Fxxpj8ksbH9ebxaEyBLZ85ySDhKiLDBrQSARLq1uNRts8RuJiHjaDMBU4Zn9h8LZNnBC5y4a",
    xprv_m_0_2147483647h:
      "xprv9wSp6B7kry3Vj9m1zSnLvN3xH8RdsPP1Mh7fAaR7aRLcQMKTR2vidYEeEg2mUCTAwCd6vnxVrcjfy2kRgVsFawNzmjuHc2YmYRmagcEPdU9",
    xpub_m_0_2147483647h_1:
      "xpub6DF8uhdarytz3FWdA8TvFSvvAh8dP3283MY7p2V4SeE2wyWmG5mg5EwVvmdMVCQcoNJxGoWaU9DCWh89LojfZ537wTfunKau47EL2dhHKon",
    xprv_m_0_2147483647h_1:
      "xprv9zFnWC6h2cLgpmSA46vutJzBcfJ8yaJGg8cX1e5StJh45BBciYTRXSd25UEPVuesF9yog62tGAQtHjXajPPdbRCHuWS6T8XA2ECKADdw4Ef",
    xpub_m_0_2147483647h_1_2147483646h:
      "xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL",
    xprv_m_0_2147483647h_1_2147483646h:
      "xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc",
    xpub_m_0_2147483647h_1_2147483646h_2:
      "xpub6FnCn6nSzZAw5Tw7cgR9bi15UV96gLZhjDstkXXxvCLsUXBGXPdSnLFbdpq8p9HmGsApME5hQTZ3emM2rnY5agb9rXpVGyy3bdW6EEgAtqt",
    xprv_m_0_2147483647h_1_2147483646h_2:
      "xprvA2nrNbFZABcdryreWet9Ea4LvTJcGsqrMzxHx98MMrotbir7yrKCEXw7nadnHM8Dq38EGfSh6dqA9QWTyefMLEcBYJUuekgW4BYPJcr9E7j"
  }

  @bip32_test_case_3 %{
    # Test for retention of leading Zeros
    seed:
      "4b381541583be4423346c643850da4b320e46a87ae3d2a4e6da11eba819cd4acba45d239319ac14f863b8d5ab5a0d0c64d2e8a1e7d1457df2e5a3c51c73235be",
    xpub_m:
      "xpub661MyMwAqRbcEZVB4dScxMAdx6d4nFc9nvyvH3v4gJL378CSRZiYmhRoP7mBy6gSPSCYk6SzXPTf3ND1cZAceL7SfJ1Z3GC8vBgp2epUt13",
    xprv_m:
      "xprv9s21ZrQH143K25QhxbucbDDuQ4naNntJRi4KUfWT7xo4EKsHt2QJDu7KXp1A3u7Bi1j8ph3EGsZ9Xvz9dGuVrtHHs7pXeTzjuxBrCmmhgC6",
    xpub_m_0h:
      "xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y",
    xprv_m_0h:
      "xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L"
  }

  @derivation_paths_to_strings [
    %{
      str: "84/0/0/2/1/",
      deriv: %ExtendedKey.DerivationPath{child_nums: [84, 0, 0, 2, 1]}
    },
    %{
      str: "84'/0'/",
      deriv: %ExtendedKey.DerivationPath{
        child_nums: [84 + @min_hardened_child_num, 0 + @min_hardened_child_num]
      }
    },
    %{
      str: "84'/0'/",
      deriv: %ExtendedKey.DerivationPath{
        child_nums: [84 + @min_hardened_child_num, 0 + @min_hardened_child_num]
      }
    },
    %{
      str: "84'/0'/1/2/2147483647/",
      deriv: %ExtendedKey.DerivationPath{
        child_nums: [
          84 + @min_hardened_child_num,
          0 + @min_hardened_child_num,
          1,
          2,
          2_147_483_647
        ]
      }
    }
  ]

  @strings_to_derivation_paths [
    %{
      str: "84/0/0/2/1/",
      deriv: %ExtendedKey.DerivationPath{child_nums: [84, 0, 0, 2, 1]}
    },
    %{
      str: "m/84'/0'/0'/2/1",
      deriv: %ExtendedKey.DerivationPath{
        child_nums: [
          84 + @min_hardened_child_num,
          0 + @min_hardened_child_num,
          2_147_483_648,
          2,
          1
        ]
      }
    },
    %{
      str: "m/84'/0'/",
      deriv: %ExtendedKey.DerivationPath{
        child_nums: [84 + @min_hardened_child_num, 0 + @min_hardened_child_num]
      }
    },
    %{
      str: "m/84'/0'",
      deriv: %ExtendedKey.DerivationPath{
        child_nums: [84 + @min_hardened_child_num, 0 + @min_hardened_child_num]
      }
    },
    %{
      str: "84'/0'",
      deriv: %ExtendedKey.DerivationPath{
        child_nums: [84 + @min_hardened_child_num, 0 + @min_hardened_child_num]
      }
    },
    %{
      str: "84'/0'/",
      deriv: %ExtendedKey.DerivationPath{
        child_nums: [84 + @min_hardened_child_num, 0 + @min_hardened_child_num]
      }
    },
    %{
      str: "84'/0'/1/2/2147483647",
      deriv: %ExtendedKey.DerivationPath{
        child_nums: [
          84 + @min_hardened_child_num,
          0 + @min_hardened_child_num,
          1,
          2,
          2_147_483_647
        ]
      }
    },
    %{
      str: "m/84h/0h/0h/2/1",
      deriv: %ExtendedKey.DerivationPath{
        child_nums: [
          84 + @min_hardened_child_num,
          0 + @min_hardened_child_num,
          2_147_483_648,
          2,
          1
        ]
      }
    },
    %{
      str: "m/84h/0h/",
      deriv: %ExtendedKey.DerivationPath{
        child_nums: [84 + @min_hardened_child_num, 0 + @min_hardened_child_num]
      }
    },
    %{
      str: "84h/0h",
      deriv: %ExtendedKey.DerivationPath{
        child_nums: [84 + @min_hardened_child_num, 0 + @min_hardened_child_num]
      }
    },
    %{
      str: "84h/0h/",
      deriv: %ExtendedKey.DerivationPath{
        child_nums: [84 + @min_hardened_child_num, 0 + @min_hardened_child_num]
      }
    }
  ]

  @error_derivation_path_strings [
    "a/82/1",
    "m/85//1/",
    "h/5/",
    "0/0/h",
    "h",
    "'",
    "0/h/0",
    "m/1/1/#{@max_hardened_child_num}",
    "m/-1/0/1"
  ]

  # Extended Key Testing

  describe "parse_extended_key/1" do
    test "successfully parse extended xprv" do
      t = @bip32_test_case_1
      # priv
      assert ExtendedKey.parse_extended_key(t.xprv_m) == {:ok, t.xprv_m_obj}
      assert ExtendedKey.display_extended_key(t.xprv_m_obj) == t.xprv_m
      # pub
      assert ExtendedKey.parse_extended_key(t.xpub_m) == {:ok, t.xpub_m_obj}
      assert ExtendedKey.display_extended_key(t.xpub_m_obj) == t.xpub_m
    end
  end

  describe "to_extended_public_key/1" do
    test "successfully turn xprv into xpub" do
      t = @bip32_test_case_1
      assert ExtendedKey.to_extended_public_key(t.xprv_m_obj) == {:ok, t.xpub_m_obj}
    end
  end

  describe "BIP32 tests" do
    # Test 1
    test "BIP32 tests 1: successfully convert xprv to xpub." do
      t = @bip32_test_case_1

      {:ok, xprv} = ExtendedKey.parse_extended_key(t.xprv_m)
      {:ok, xpub} = ExtendedKey.parse_extended_key(t.xpub_m)
      assert ExtendedKey.to_extended_public_key(xprv) == {:ok, xpub}

      {:ok, xprv} = ExtendedKey.parse_extended_key(t.xprv_m_0h)
      {:ok, xpub} = ExtendedKey.parse_extended_key(t.xpub_m_0h)
      assert ExtendedKey.to_extended_public_key(xprv) == {:ok, xpub}

      {:ok, xprv} = ExtendedKey.parse_extended_key(t.xprv_m_0h_1)
      {:ok, xpub} = ExtendedKey.parse_extended_key(t.xpub_m_0h_1)
      assert ExtendedKey.to_extended_public_key(xprv) == {:ok, xpub}

      {:ok, xprv} = ExtendedKey.parse_extended_key(t.xprv_m_0h_1_2h)
      {:ok, xpub} = ExtendedKey.parse_extended_key(t.xpub_m_0h_1_2h)
      assert ExtendedKey.to_extended_public_key(xprv) == {:ok, xpub}

      {:ok, xprv} = ExtendedKey.parse_extended_key(t.xprv_m_0h_1_2h_2)
      {:ok, xpub} = ExtendedKey.parse_extended_key(t.xpub_m_0h_1_2h_2)
      assert ExtendedKey.to_extended_public_key(xprv) == {:ok, xpub}

      {:ok, xprv} = ExtendedKey.parse_extended_key(t.xprv_m_0h_1_2h_2_1000000000)
      {:ok, xpub} = ExtendedKey.parse_extended_key(t.xpub_m_0h_1_2h_2_1000000000)
      assert ExtendedKey.to_extended_public_key(xprv) == {:ok, xpub}
    end

    test "BIP32 tests 1: derive prv keys in sequence" do
      t = @bip32_test_case_1
      # derive prv child from prv parent_fingerprint
      {:ok, m_xprv} = ExtendedKey.parse_extended_key(t.xprv_m)
      {:ok, m_0h_xprv} = ExtendedKey.derive_private_child(m_xprv, @min_hardened_child_num)

      assert ExtendedKey.parse_extended_key(t.xprv_m_0h) == {:ok, m_0h_xprv}

      # derive child m/0'/1
      {:ok, m_0h_1_xprv} = ExtendedKey.derive_private_child(m_0h_xprv, 1)
      assert ExtendedKey.parse_extended_key(t.xprv_m_0h_1) == {:ok, m_0h_1_xprv}
    end

    test "BIP32 tests 1: derive pub keys from master prv key" do
      t = @bip32_test_case_1

      {:ok, xprv} = ExtendedKey.parse_extended_key(t.xprv_m)
      {:ok, m_0h_xpub} = ExtendedKey.derive_public_child(xprv, @min_hardened_child_num)

      assert ExtendedKey.parse_extended_key(t.xpub_m_0h) == {:ok, m_0h_xpub}

      {:ok, xprv} = ExtendedKey.parse_extended_key(t.xprv_m)
      {:ok, xprv} = ExtendedKey.derive_private_child(xprv, @min_hardened_child_num)
      {:ok, xprv} = ExtendedKey.derive_private_child(xprv, 1)
      {:ok, m_0h_1_2h_xpub} = ExtendedKey.derive_public_child(xprv, @min_hardened_child_num + 2)

      assert ExtendedKey.parse_extended_key(t.xpub_m_0h_1_2h) == {:ok, m_0h_1_2h_xpub}
    end

    test "BIP32 tests 1: derive m/0'/1/2'/2/1000000000 from master key" do
      t = @bip32_test_case_1

      {:ok, xprv} = ExtendedKey.parse_extended_key(t.xprv_m)
      {:ok, xprv} = ExtendedKey.derive_private_child(xprv, @min_hardened_child_num)
      {:ok, xprv} = ExtendedKey.derive_private_child(xprv, 1)
      {:ok, xprv} = ExtendedKey.derive_private_child(xprv, @min_hardened_child_num + 2)
      {:ok, xprv} = ExtendedKey.derive_private_child(xprv, 2)
      {:ok, m_0h_1_2h_2_1000000000_xprv} = ExtendedKey.derive_private_child(xprv, 1_000_000_000)

      assert ExtendedKey.parse_extended_key(t.xprv_m_0h_1_2h_2_1000000000) ==
               {:ok, m_0h_1_2h_2_1000000000_xprv}
    end

    test "BIP32 tests 1: derive pub child from pub parent_fingerprint" do
      t = @bip32_test_case_1

      {:ok, xpub} = ExtendedKey.parse_extended_key(t.xpub_m_0h)
      {:ok, m_0h_1_xpub} = ExtendedKey.derive_public_child(xpub, 1)

      assert ExtendedKey.parse_extended_key(t.xpub_m_0h_1) == {:ok, m_0h_1_xpub}

      {:ok, xpub_m_0h_1_2h} = ExtendedKey.parse_extended_key(t.xpub_m_0h_1_2h)
      {:ok, m_0h_1_2h_2_xpub} = ExtendedKey.derive_public_child(xpub_m_0h_1_2h, 2)

      assert ExtendedKey.parse_extended_key(t.xpub_m_0h_1_2h_2) == {:ok, m_0h_1_2h_2_xpub}

      {:ok, m_0h_1_2h_2_1000000000_xpub} =
        m_0h_1_2h_2_xpub
        |> ExtendedKey.derive_public_child(1_000_000_000)

      assert ExtendedKey.parse_extended_key(t.xpub_m_0h_1_2h_2_1000000000) ==
               {:ok, m_0h_1_2h_2_1000000000_xpub}
    end

    test "BIP32 tests 1: to_public_key works for both xprv and xpubs" do
      t = @bip32_test_case_1

      {:ok, xprv} = ExtendedKey.parse_extended_key(t.xprv_m)
      {:ok, xpub} = ExtendedKey.parse_extended_key(t.xpub_m)

      pub = ExtendedKey.to_public_key(xpub)
      # test that to_public_key works for xprv and xpub keys
      assert ExtendedKey.to_public_key(xprv) == pub
    end

    test "BIP32 tests 1: seed to master prv key" do
      t = @bip32_test_case_1

      seed = t.seed |> Base.decode16!(case: :lower)
      {:ok, xprv} = ExtendedKey.parse_extended_key(t.xprv_m)

      assert ExtendedKey.seed_to_master_private_key(seed) == {:ok, xprv}
    end

    test "BIP32 tests 1: derive key from seed and deriv path" do
      t = @bip32_test_case_1

      seed = t.seed |> Base.decode16!(case: :lower)
      {:ok, xprv} = t.xprv_m |> ExtendedKey.parse_extended_key()
      deriv = %ExtendedKey.DerivationPath{child_nums: []}

      assert ExtendedKey.derive_extended_key(seed, deriv) == {:ok, xprv}

      # derive m/0'/1
      {:ok, xprv} = t.xprv_m_0h_1 |> ExtendedKey.parse_extended_key()
      deriv = %ExtendedKey.DerivationPath{child_nums: [@min_hardened_child_num, 1]}

      assert ExtendedKey.derive_extended_key(seed, deriv) == {:ok, xprv}

      # derive xprv_m_0h_1_2h_2_1000000000
      {:ok, xprv_m_0h_1_2h_2_1000000000} =
        t.xprv_m_0h_1_2h_2_1000000000 |> ExtendedKey.parse_extended_key()

      deriv = %ExtendedKey.DerivationPath{
        child_nums: [@min_hardened_child_num, 1, @min_hardened_child_num + 2, 2, 1_000_000_000]
      }

      assert ExtendedKey.derive_extended_key(seed, deriv) == {:ok, xprv_m_0h_1_2h_2_1000000000}
    end

    # Test 2

    test "BIP32 tests 2: successfully convert xprv to xpub." do
      t = @bip32_test_case_2

      {:ok, xprv} = ExtendedKey.parse_extended_key(t.xprv_m)
      {:ok, xpub} = ExtendedKey.parse_extended_key(t.xpub_m)
      assert ExtendedKey.to_extended_public_key(xprv) == {:ok, xpub}

      {:ok, xprv} = ExtendedKey.parse_extended_key(t.xprv_m_0)
      {:ok, xpub} = ExtendedKey.parse_extended_key(t.xpub_m_0)
      assert ExtendedKey.to_extended_public_key(xprv) == {:ok, xpub}

      {:ok, xprv} = ExtendedKey.parse_extended_key(t.xprv_m_0_2147483647h)
      {:ok, xpub} = ExtendedKey.parse_extended_key(t.xpub_m_0_2147483647h)
      assert ExtendedKey.to_extended_public_key(xprv) == {:ok, xpub}

      {:ok, xprv} = ExtendedKey.parse_extended_key(t.xprv_m_0_2147483647h_1)
      {:ok, xpub} = ExtendedKey.parse_extended_key(t.xpub_m_0_2147483647h_1)
      assert ExtendedKey.to_extended_public_key(xprv) == {:ok, xpub}

      {:ok, xprv} = ExtendedKey.parse_extended_key(t.xprv_m_0_2147483647h_1_2147483646h)
      {:ok, xpub} = ExtendedKey.parse_extended_key(t.xpub_m_0_2147483647h_1_2147483646h)
      assert ExtendedKey.to_extended_public_key(xprv) == {:ok, xpub}

      {:ok, xprv} = ExtendedKey.parse_extended_key(t.xprv_m_0_2147483647h_1_2147483646h_2)
      {:ok, xpub} = ExtendedKey.parse_extended_key(t.xpub_m_0_2147483647h_1_2147483646h_2)
      assert ExtendedKey.to_extended_public_key(xprv) == {:ok, xpub}
    end

    test "BIP32 tests 2: derive prv keys in sequence" do
      t = @bip32_test_case_2
      # derive prv child from prv parent_fingerprint
      {:ok, xprv} = ExtendedKey.parse_extended_key(t.xprv_m)
      {:ok, m_0_xprv} = ExtendedKey.derive_private_child(xprv, 0)

      assert ExtendedKey.parse_extended_key(t.xprv_m_0) == {:ok, m_0_xprv}

      # derive child m/0/2147483647h
      {:ok, m_0_2147483647h_xprv} =
        ExtendedKey.derive_private_child(m_0_xprv, 2_147_483_647 + @min_hardened_child_num)

      assert ExtendedKey.parse_extended_key(t.xprv_m_0_2147483647h) == {:ok, m_0_2147483647h_xprv}
    end

    test "BIP32 tests 2: derive pub keys from master prv key" do
      t = @bip32_test_case_2

      {:ok, xprv} = ExtendedKey.parse_extended_key(t.xprv_m)
      {:ok, m_0_xpub} = ExtendedKey.derive_public_child(xprv, 0)

      assert ExtendedKey.parse_extended_key(t.xpub_m_0) == {:ok, m_0_xpub}

      {:ok, xprv} = ExtendedKey.parse_extended_key(t.xprv_m)
      {:ok, xprv_temp} = ExtendedKey.derive_private_child(xprv, 0)

      {:ok, xprv_temp} =
        ExtendedKey.derive_private_child(xprv_temp, 2_147_483_647 + @min_hardened_child_num)

      {:ok, m_0_2147483647h_1_xpub} = ExtendedKey.derive_public_child(xprv_temp, 1)

      assert ExtendedKey.parse_extended_key(t.xpub_m_0_2147483647h_1) ==
               {:ok, m_0_2147483647h_1_xpub}
    end

    test "BIP32 tests 2: derive child pub keys from prv and pubkey" do
      t = @bip32_test_case_2

      {:ok, xprv} = ExtendedKey.parse_extended_key(t.xprv_m)
      {:ok, xpub1} = ExtendedKey.derive_public_child(xprv, 0)

      {:ok, xpub} = ExtendedKey.parse_extended_key(t.xpub_m)
      {:ok, xpub2} = ExtendedKey.derive_public_child(xpub, 0)

      assert xpub1 == xpub2

      {:ok, xprv} = ExtendedKey.parse_extended_key(t.xprv_m_0_2147483647h)
      {:ok, xpub1} = ExtendedKey.derive_public_child(xprv, 1)

      {:ok, xpub} = ExtendedKey.parse_extended_key(t.xpub_m_0_2147483647h)
      {:ok, xpub2} = ExtendedKey.derive_public_child(xpub, 1)

      assert xpub1 == xpub2
    end

    test "BIP32 tests 2: seed to master prv key" do
      t = @bip32_test_case_2

      seed = t.seed |> Base.decode16!(case: :lower)
      {:ok, xprv} = t.xprv_m |> ExtendedKey.parse_extended_key()

      assert ExtendedKey.seed_to_master_private_key(seed) == {:ok, xprv}
    end

    test "BIP32 tests 2: derive key from seed and deriv path" do
      t = @bip32_test_case_2

      seed = t.seed |> Base.decode16!(case: :lower)
      {:ok, xprv} = t.xprv_m |> ExtendedKey.parse_extended_key()
      deriv = %ExtendedKey.DerivationPath{child_nums: []}

      assert ExtendedKey.derive_extended_key(seed, deriv) == {:ok, xprv}

      {:ok, xprv} = t.xprv_m_0_2147483647h |> ExtendedKey.parse_extended_key()

      deriv = %ExtendedKey.DerivationPath{
        child_nums: [0, 2_147_483_647 + @min_hardened_child_num]
      }

      assert ExtendedKey.derive_extended_key(seed, deriv) == {:ok, xprv}

      {:ok, xprv_m_0_2147483647h_1_2147483646h_2} =
        t.xprv_m_0_2147483647h_1_2147483646h_2 |> ExtendedKey.parse_extended_key()

      deriv = %ExtendedKey.DerivationPath{
        child_nums: [
          0,
          2_147_483_647 + @min_hardened_child_num,
          1,
          2_147_483_646 + @min_hardened_child_num,
          2
        ]
      }

      assert ExtendedKey.derive_extended_key(seed, deriv) ==
               {:ok, xprv_m_0_2147483647h_1_2147483646h_2}
    end

    # Test 3

    test "BIP32 tests 3: derive public key from private key" do
      t = @bip32_test_case_3

      {:ok, xprv} = ExtendedKey.parse_extended_key(t.xprv_m)
      {:ok, xpub} = ExtendedKey.parse_extended_key(t.xpub_m)
      assert ExtendedKey.to_extended_public_key(xprv) == {:ok, xpub}
      # check that to_extended_public_key is identity for xpub
      assert ExtendedKey.to_extended_public_key(xpub) == xpub

      {:ok, xprv} = ExtendedKey.parse_extended_key(t.xprv_m_0h)
      {:ok, xpub} = ExtendedKey.parse_extended_key(t.xpub_m_0h)
      assert ExtendedKey.to_extended_public_key(xprv) == {:ok, xpub}
      assert ExtendedKey.to_extended_public_key(xpub) == xpub
    end

    test "BIP32 tests 3: derive prv child from parent" do
      t = @bip32_test_case_3

      {:ok, xprv} = ExtendedKey.parse_extended_key(t.xprv_m)
      {:ok, xprv_m_0h} = ExtendedKey.derive_private_child(xprv, @min_hardened_child_num)

      assert ExtendedKey.parse_extended_key(t.xprv_m_0h) == {:ok, xprv_m_0h}
    end

    test "BIP32 tests 3: derive pub child from prv parent" do
      t = @bip32_test_case_3

      {:ok, xprv} = ExtendedKey.parse_extended_key(t.xprv_m)
      {:ok, xpub_m_0h} = ExtendedKey.derive_public_child(xprv, @min_hardened_child_num)

      assert ExtendedKey.display_extended_key(xpub_m_0h) == t.xpub_m_0h
    end

    test "BIP32 tests 3: derive master prv key from seed" do
      t = @bip32_test_case_3

      {:ok, xprv} = ExtendedKey.parse_extended_key(t.xprv_m)

      {:ok, s_xprv} =
        t.seed
        |> Base.decode16!(case: :lower)
        |> ExtendedKey.seed_to_master_private_key()

      assert xprv == s_xprv
    end

    test "BIP32 tests 3: derive child prv key from seed" do
      t = @bip32_test_case_3

      {:ok, xprv_m_0h} = ExtendedKey.parse_extended_key(t.xprv_m_0h)
      deriv = %ExtendedKey.DerivationPath{child_nums: [@min_hardened_child_num]}

      {:ok, s_xprv_m_0h} =
        t.seed
        |> Base.decode16!(case: :lower)
        |> ExtendedKey.derive_extended_key(deriv)

      assert xprv_m_0h == s_xprv_m_0h
    end
  end

  describe "Invalid Key testing" do
    test "invalid key testing" do
      for t <- @invalid_xkeys do
        {err, _} = ExtendedKey.parse_extended_key(t)
        assert err == :error
      end
    end
  end

  describe "Derive Private and public key, sign message, verify signature" do
    test "derive prv and public key, sign msg, verify" do
      t = @bip32_test_case_1

      {:ok, xprv} = ExtendedKey.parse_extended_key(t.xprv_m)
      {:ok, xpub} = ExtendedKey.parse_extended_key(t.xpub_m)

      {:ok, prv} = ExtendedKey.to_private_key(xprv)
      {:ok, pub} = ExtendedKey.to_public_key(xpub)

      msg = "eat out from 5 pounds"
      z = :binary.decode_unsigned(Bitcoinex.Utils.double_sha256(msg))
      sig = Ecdsa.sign(prv, z)
      assert Ecdsa.verify_signature(pub, z, sig)
    end
  end

  describe "Fail on attempt to derive hardened child from pubkey" do
    test "fail to derive hardened child from pubkey parent" do
      t = @bip32_test_case_3

      {:ok, xpub} = ExtendedKey.parse_extended_key(t.xpub_m)
      {err, _msg} = ExtendedKey.derive_child_key(xpub, @min_hardened_child_num)
      assert :error == err
    end

    test "fail to derive hardened child from pubkey parent with deriv path" do
      t = @bip32_test_case_3

      deriv = %ExtendedKey.DerivationPath{
        child_nums: [@min_hardened_child_num, 1]
      }

      {:ok, xpub} = ExtendedKey.parse_extended_key(t.xpub_m)
      {err, _msg} = ExtendedKey.derive_extended_key(xpub, deriv)
      assert :error == err
    end
  end

  # Derivation Path Testing

  describe "derive_extended_key/2 using BIP 32 test cases" do
    test "test use of deriv path" do
      t = @bip32_test_case_1

      deriv = %ExtendedKey.DerivationPath{
        child_nums: [@min_hardened_child_num, 1, @min_hardened_child_num + 2, 2]
      }

      {:ok, child_key} =
        t.xprv_m_obj
        |> ExtendedKey.derive_extended_key(deriv)

      assert ExtendedKey.display_extended_key(child_key) == t.xprv_m_0h_1_2h_2
    end

    test "successfully derive xpub child key with derivation path" do
      t = @bip32_test_case_2

      deriv = %ExtendedKey.DerivationPath{
        child_nums: [
          0,
          @min_hardened_child_num + 2_147_483_647,
          1,
          @min_hardened_child_num + 2_147_483_646
        ]
      }

      {:ok, xprv_t1} = ExtendedKey.parse_extended_key(t.xprv_m)
      {:ok, xprv_t2} = ExtendedKey.derive_extended_key(xprv_t1, deriv)
      {:ok, child_key} = ExtendedKey.to_extended_public_key(xprv_t2)

      assert ExtendedKey.display_extended_key(child_key) == t.xpub_m_0_2147483647h_1_2147483646h
    end

    test "test use of deriv path bip32 test 2" do
      t = @bip32_test_case_2

      deriv = %ExtendedKey.DerivationPath{
        child_nums: [
          0,
          2_147_483_647 + @min_hardened_child_num,
          1,
          2_147_483_646 + @min_hardened_child_num,
          2
        ]
      }

      {:ok, m} = ExtendedKey.parse_extended_key(t.xprv_m)

      {:ok, child_key} = ExtendedKey.derive_extended_key(m, deriv)

      assert ExtendedKey.parse_extended_key(t.xprv_m_0_2147483647h_1_2147483646h_2) ==
               {:ok, child_key}
    end
  end

  describe "Derivation Path parse/ser testing" do
    test "from_string/1" do
      for t <- @strings_to_derivation_paths do
        assert ExtendedKey.DerivationPath.from_string(t.str) == {:ok, t.deriv}
      end
    end

    test "to_string/1" do
      for t <- @derivation_paths_to_strings do
        assert ExtendedKey.DerivationPath.to_string(t.deriv) == {:ok, t.str}
      end
    end

    test "Raise exceptions on invalid derivation paths" do
      for t <- @error_derivation_path_strings do
        {res, _} = ExtendedKey.DerivationPath.from_string(t)
        assert :error == res
      end
    end
  end
end


text:
defmodule Bitcoinex.AddressTest do
  use ExUnit.Case
  doctest Bitcoinex.Address

  alias Bitcoinex.Address

  describe "is_valid?/1" do
    setup do
      valid_mainnet_p2pkh_addresses = [
        "12KYrjTdVGjFMtaxERSk3gphreJ5US8aUP",
        "12QeMLzSrB8XH8FvEzPMVoRxVAzTr5XM2y",
        "17VZNX1SN5NtKa8UQFxwQbFeFc3iqRYhem",
        "1oNLrsHnBcR6dpaBpwz3LSwutbUNkNSjs"
      ]

      valid_testnet_p2pkh_addresses = [
        "mzBc4XEFSdzCDcTxAgf6EZXgsZWpztRhef",
        "mipcBbFg9gMiCh81Kj8tqqdgoZub1ZJRfn"
      ]

      valid_mainnet_p2sh_addresses = [
        "3NJZLcZEEYBpxYEUGewU4knsQRn1WM5Fkt"
      ]

      valid_testnet_p2sh_addresses = [
        "2MzQwSSnBHWHqSAqtTVQ6v47XtaisrJa1Vc"
      ]

      valid_mainnet_segwit_addresses = [
        "BC1QW508D6QEJXTDG4Y5R3ZARVARY0C5XW7KV8F3T4"
      ]

      valid_testnet_segwit_addresses = [
        "tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sl5k7",
        "tb1qqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvsesrxh6hy"
      ]

      valid_regtest_segwit_addresses = [
        "bcrt1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3qzf4jry",
        "bcrt1qqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvseswlauz7"
      ]

      valid_mainnet_p2wpkh_addresses = [
        "bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4"
      ]

      valid_testnet_p2wpkh_addresses = [
        "tb1qw508d6qejxtdg4y5r3zarvary0c5xw7kxpjzsx"
      ]

      valid_mainnet_p2wsh_addresses = [
        "bc1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3qccfmv3"
      ]

      valid_testnet_p2wsh_addresses = [
        "tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sl5k7"
      ]

      valid_mainnet_p2tr_addresses = [
        "bc1pqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqsyjer9e",
        "bc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vqzk5jj0"
      ]

      valid_testnet_p2tr_addresses = [
        "tb1pqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvsesf3hn0c"
      ]

      valid_mainnet_addresses =
        valid_mainnet_p2pkh_addresses ++
          valid_mainnet_p2sh_addresses ++
          valid_mainnet_segwit_addresses ++
          valid_mainnet_p2wpkh_addresses ++
          valid_mainnet_p2wsh_addresses ++
          valid_mainnet_p2tr_addresses

      valid_testnet_addresses =
        valid_testnet_p2pkh_addresses ++
          valid_testnet_p2sh_addresses ++
          valid_testnet_segwit_addresses ++
          valid_testnet_p2wpkh_addresses ++
          valid_testnet_p2wsh_addresses ++
          valid_testnet_p2tr_addresses

      valid_regtest_addresses =
        valid_testnet_p2pkh_addresses ++
          valid_testnet_p2sh_addresses ++ valid_regtest_segwit_addresses

      invalid_addresses = [
        # witness v1 address using bech32 (not bech32m) encoding
        "bc1pw508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7k7grplx",
        "BC1SW50QA3JX3S",
        "bc1zw508d6qejxtdg4y5r3zarvaryvg6kdaj",
        "",
        "rrRmhfXzGBKbV4YHtbpxfA1ftEcry8AJaX",
        "LSxNsEQekEpXMS4B7tUYstMEdMyH321ZQ1",
        "rrRmhfXzGBKbV4YHtbpxfA1ftEcry8AJaX",
        "tc1qw508d6qejxtdg4y5r3zarvary0c5xw7kg3g4ty",
        "bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t5",
        "BC13W508D6QEJXTDG4Y5R3ZARVARY0C5XW7KN40WF2",
        "bc1rw5uspcuh",
        "bc10w508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7kw5rljs90",
        "BC1QR508D6QEJXTDG4Y5R3ZARVARYV98GJ9P",
        "tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sL5k7",
        "bc1zw508d6qejxtdg4y5r3zarvaryvqyzf3du",
        "tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3pjxtptv",
        "bc1gmk9yu",
        # p2tr addresses
        "bc1pqyqszqgpqyqszqgpqyqszqppgpqyqszqgpqyqszqgpqyqszqgpqyqsyjer9e",
        "bc1p0xlxvlhemja6c4dqv22uapctqupfpphlxm9h8z3k2e72q4k9hcz7vqzk5jj0",
        "bc1pqyqszqgpqyqszqgpqyqszgpqyqszqgpqyqszqgpqyqszqgpqyqsyjer9e",
        "bc1p0xlxvlhemja6c4dqv22uapctquphlxm9h8z3k2e72q4k9hcz7vqzk5jj0",
        "bc1pqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqsyjer9f",
        "bc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vqzk5jj1"
      ]

      {:ok,
       valid_mainnet_addresses: valid_mainnet_addresses,
       valid_testnet_addresses: valid_testnet_addresses,
       valid_regtest_addresses: valid_regtest_addresses,
       valid_mainnet_p2pkh_addresses: valid_mainnet_p2pkh_addresses,
       valid_testnet_p2pkh_addresses: valid_testnet_p2pkh_addresses,
       valid_mainnet_p2sh_addresses: valid_mainnet_p2sh_addresses,
       valid_testnet_p2sh_addresses: valid_testnet_p2sh_addresses,
       valid_mainnet_segwit_addresses: valid_mainnet_segwit_addresses,
       valid_testnet_segwit_addresses: valid_testnet_segwit_addresses,
       valid_regtest_segwit_addresses: valid_regtest_segwit_addresses,
       valid_mainnet_p2wpkh_addresses: valid_mainnet_p2wpkh_addresses,
       valid_testnet_p2wpkh_addresses: valid_testnet_p2wpkh_addresses,
       valid_mainnet_p2wsh_addresses: valid_mainnet_p2wsh_addresses,
       valid_testnet_p2wsh_addresses: valid_testnet_p2wsh_addresses,
       valid_mainnet_p2tr_addresses: valid_mainnet_p2tr_addresses,
       valid_testnet_p2tr_addresses: valid_testnet_p2tr_addresses,
       invalid_addresses: invalid_addresses}
    end

    test "return true when the address is valid address either p2sh, p2pkh, pwsh, p2wpkh, p2tr",
         %{
           valid_mainnet_p2pkh_addresses: valid_mainnet_p2pkh_addresses,
           valid_mainnet_p2sh_addresses: valid_mainnet_p2sh_addresses,
           valid_mainnet_segwit_addresses: valid_mainnet_segwit_addresses,
           valid_mainnet_p2tr_addresses: valid_mainnet_p2tr_addresses
         } do
      all_valid_addresses =
        valid_mainnet_p2sh_addresses ++
          valid_mainnet_p2pkh_addresses ++
          valid_mainnet_segwit_addresses ++ valid_mainnet_p2tr_addresses

      for valid_address <- all_valid_addresses do
        assert Address.is_valid?(valid_address, :mainnet)
      end
    end

    test "return false when the address is valid address either p2sh, p2pkh, pwsh, p2wpkh but not in correct network",
         %{
           valid_testnet_segwit_addresses: valid_testnet_segwit_addresses,
           valid_testnet_p2pkh_addresses: valid_testnet_p2pkh_addresses,
           valid_testnet_p2sh_addresses: valid_testnet_p2sh_addresses,
           valid_regtest_segwit_addresses: valid_regtest_segwit_addresses,
           valid_testnet_p2tr_addresses: valid_testnet_p2tr_addresses
         } do
      all_valid_testnet_addresses =
        valid_testnet_segwit_addresses ++
          valid_testnet_p2pkh_addresses ++
          valid_testnet_p2sh_addresses ++
          valid_regtest_segwit_addresses ++
          valid_testnet_p2tr_addresses

      for valid_testnet_address <- all_valid_testnet_addresses do
        refute Address.is_valid?(valid_testnet_address, :mainnet)
      end
    end

    test "return false when the address is not valid address either p2sh, p2pkh, pwsh, p2wpkh, p2tr",
         %{
           invalid_addresses: invalid_addresses
         } do
      all_invalid_addresses = invalid_addresses

      for invalid_address <- all_invalid_addresses do
        for %{name: network_name} <- Bitcoinex.Network.supported_networks() do
          for address_type <- Bitcoinex.Address.supported_address_types() do
            refute Address.is_valid?(invalid_address, network_name, address_type)
          end
        end
      end
    end

    test "check that the address decodes to the correct address type", %{
      valid_mainnet_p2pkh_addresses: valid_mainnet_p2pkh_addresses,
      valid_testnet_p2pkh_addresses: valid_testnet_p2pkh_addresses,
      valid_mainnet_p2sh_addresses: valid_mainnet_p2sh_addresses,
      valid_testnet_p2sh_addresses: valid_testnet_p2sh_addresses,
      valid_mainnet_p2wpkh_addresses: valid_mainnet_p2wpkh_addresses,
      valid_testnet_p2wpkh_addresses: valid_testnet_p2wpkh_addresses,
      valid_mainnet_p2wsh_addresses: valid_mainnet_p2wsh_addresses,
      valid_testnet_p2wsh_addresses: valid_testnet_p2wsh_addresses,
      valid_mainnet_p2tr_addresses: valid_mainnet_p2tr_addresses,
      valid_testnet_p2tr_addresses: valid_testnet_p2tr_addresses
    } do
      for mainnet_p2pkh <- valid_mainnet_p2pkh_addresses do
        assert Address.decode_type(mainnet_p2pkh, :mainnet) == {:ok, :p2pkh}
      end

      for testnet_p2pkh <- valid_testnet_p2pkh_addresses do
        assert Address.decode_type(testnet_p2pkh, :testnet) == {:ok, :p2pkh}
      end

      for mainnet_p2sh <- valid_mainnet_p2sh_addresses do
        assert Address.decode_type(mainnet_p2sh, :mainnet) == {:ok, :p2sh}
      end

      for testnet_p2sh <- valid_testnet_p2sh_addresses do
        assert Address.decode_type(testnet_p2sh, :testnet) == {:ok, :p2sh}
      end

      for mainnet_p2wpkh <- valid_mainnet_p2wpkh_addresses do
        assert Address.decode_type(mainnet_p2wpkh, :mainnet) == {:ok, :p2wpkh}
      end

      for testnet_p2wpkh <- valid_testnet_p2wpkh_addresses do
        assert Address.decode_type(testnet_p2wpkh, :testnet) == {:ok, :p2wpkh}
      end

      for mainnet_p2wsh <- valid_mainnet_p2wsh_addresses do
        assert Address.decode_type(mainnet_p2wsh, :mainnet) == {:ok, :p2wsh}
      end

      for testnet_p2wsh <- valid_testnet_p2wsh_addresses do
        assert Address.decode_type(testnet_p2wsh, :testnet) == {:ok, :p2wsh}
      end

      for mainnet_p2tr <- valid_mainnet_p2tr_addresses do
        assert Address.decode_type(mainnet_p2tr, :mainnet) == {:ok, :p2tr}
      end

      for testnet_p2tr <- valid_testnet_p2tr_addresses do
        assert Address.decode_type(testnet_p2tr, :testnet) == {:ok, :p2tr}
      end
    end
  end

  describe "encode/3" do
    test "return true for encoding p2pkh" do
      pubkey_hash = Base.decode16!("6dcd022b3c5e6439238eb333ec1d6ddd1973b5ba", case: :lower)
      assert "1B1aF9aUzxqgEviiCSe9u339hpUWLVWfxu" == Address.encode(pubkey_hash, :mainnet, :p2pkh)
    end

    test "return true for encoding p2sh" do
      script_hash = Base.decode16!("6d77fa9de297e9c536c6b23cfda1a8450bb5f765", case: :lower)
      assert "3BfqJjn7H2jsbKd2NVHGP4sQWQ2bQWBRLv" == Address.encode(script_hash, :mainnet, :p2sh)
    end
  end
end


text:
defmodule Bitcoinex.Base58Test do
  use ExUnit.Case
  use ExUnitProperties
  doctest Bitcoinex.Base58

  alias Bitcoinex.Base58

  # From
  @base58_encode_decode [
    ["", ""],
    ["61", "2g"],
    ["626262", "a3gV"],
    ["636363", "aPEr"],
    ["73696d706c792061206c6f6e6720737472696e67", "2cFupjhnEsSn59qHXstmK2ffpLv2"],
    ["00eb15231dfceb60925886b67d065299925915aeb172c06647", "1NS17iag9jJgTHD1VXjvLCEnZuQ3rJDE9L"],
    ["516b6fcd0f", "ABnLTmg"],
    ["bf4f89001e670274dd", "3SEo3LWLoPntC"],
    ["572e4794", "3EFU7m"],
    ["ecac89cad93923c02321", "EJDM8drfXA6uyA"],
    ["10c8511e", "Rt5zm"],
    ["00000000000000000000", "1111111111"],
    [
      "000111d38e5fc9071ffcd20b4a763cc9ae4f252bb4e48fd66a835e252ada93ff480d6dd43dc62a641155a5",
      "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
    ],
    [
      "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
      "1cWB5HCBdLjAuqGGReWE3R3CguuwSjw6RHn39s2yuDRTS5NsBgNiFpWgAnEx6VQi8csexkgYw3mdYrMHr8x9i7aEwP8kZ7vccXWqKDvGv3u1GxFKPuAkn8JCPPGDMf3vMMnbzm6Nh9zh1gcNsMvH3ZNLmP5fSG6DGbbi2tuwMWPthr4boWwCxf7ewSgNQeacyozhKDDQQ1qL5fQFUW52QKUZDZ5fw3KXNQJMcNTcaB723LchjeKun7MuGW5qyCBZYzA1KjofN1gYBV3NqyhQJ3Ns746GNuf9N2pQPmHz4xpnSrrfCvy6TVVz5d4PdrjeshsWQwpZsZGzvbdAdN8MKV5QsBDY"
    ]
  ]

  # From https://github.com/bitcoinjs/bs58check/blob/master/test/fixtures.json
  @valid_base58_strings [
    ["1AGNa15ZQXAZUgFiqJ2i7Z2DPU2J6hW62i", "0065a16059864a2fdbc7c99a4723a8395bc6f188eb"],
    ["3CMNFxN1oHBc4R1EpboAL5yzHGgE611Xou", "0574f209f6ea907e2ea48f74fae05782ae8a665257"],
    ["mo9ncXisMeAoXwqcV5EWuyncbmCcQN4rVs", "6f53c0307d6851aa0ce7825ba883c6bd9ad242b486"],
    ["2N2JD6wb56AfK4tfmM6PwdVmoYk2dCKf4Br", "c46349a418fc4578d10a372b54b45c280cc8c4382f"],
    [
      "5Kd3NBUAdUnhyzenEwVLy9pBKxSwXvE9FMPyR4UKZvpe6E3AgLr",
      "80eddbdc1168f1daeadbd3e44c1e3f8f5a284c2029f78ad26af98583a499de5b19"
    ],
    [
      "Kz6UJmQACJmLtaQj5A3JAge4kVTNQ8gbvXuwbmCj7bsaabudb3RD",
      "8055c9bccb9ed68446d1b75273bbce89d7fe013a8acd1625514420fb2aca1a21c401"
    ],
    [
      "9213qJab2HNEpMpYNBa7wHGFKKbkDn24jpANDs2huN3yi4J11ko",
      "ef36cb93b9ab1bdabf7fb9f2c04f1b9cc879933530ae7842398eef5a63a56800c2"
    ],
    [
      "cTpB4YiyKiBcPxnefsDpbnDxFDffjqJob8wGCEDXxgQ7zQoMXJdH",
      "efb9f4892c9e8282028fea1d2667c4dc5213564d41fc5783896a0d843fc15089f301"
    ],
    ["1Ax4gZtb7gAit2TivwejZHYtNNLT18PUXJ", "006d23156cbbdcc82a5a47eee4c2c7c583c18b6bf4"],
    ["3QjYXhTkvuj8qPaXHTTWb5wjXhdsLAAWVy", "05fcc5460dd6e2487c7d75b1963625da0e8f4c5975"],
    ["n3ZddxzLvAY9o7184TB4c6FJasAybsw4HZ", "6ff1d470f9b02370fdec2e6b708b08ac431bf7a5f7"],
    ["2NBFNJTktNa7GZusGbDbGKRZTxdK9VVez3n", "c4c579342c2c4c9220205e2cdc285617040c924a0a"],
    [
      "5K494XZwps2bGyeL71pWid4noiSNA2cfCibrvRWqcHSptoFn7rc",
      "80a326b95ebae30164217d7a7f57d72ab2b54e3be64928a19da0210b9568d4015e"
    ],
    [
      "L1RrrnXkcKut5DEMwtDthjwRcTTwED36thyL1DebVrKuwvohjMNi",
      "807d998b45c219a1e38e99e7cbd312ef67f77a455a9b50c730c27f02c6f730dfb401"
    ],
    [
      "93DVKyFYwSN6wEo3E2fCrFPUp17FtrtNi2Lf7n4G3garFb16CRj",
      "efd6bca256b5abc5602ec2e1c121a08b0da2556587430bcf7e1898af2224885203"
    ],
    [
      "cTDVKtMGVYWTHCb1AFjmVbEbWjvKpKqKgMaR3QJxToMSQAhmCeTN",
      "efa81ca4e8f90181ec4b61b6a7eb998af17b2cb04de8a03b504b9e34c4c61db7d901"
    ],
    ["1C5bSj1iEGUgSTbziymG7Cn18ENQuT36vv", "007987ccaa53d02c8873487ef919677cd3db7a6912"],
    ["3AnNxabYGoTxYiTEZwFEnerUoeFXK2Zoks", "0563bcc565f9e68ee0189dd5cc67f1b0e5f02f45cb"],
    ["n3LnJXCqbPjghuVs8ph9CYsAe4Sh4j97wk", "6fef66444b5b17f14e8fae6e7e19b045a78c54fd79"],
    ["2NB72XtkjpnATMggui83aEtPawyyKvnbX2o", "c4c3e55fceceaa4391ed2a9677f4a4d34eacd021a0"],
    [
      "5KaBW9vNtWNhc3ZEDyNCiXLPdVPHCikRxSBWwV9NrpLLa4LsXi9",
      "80e75d936d56377f432f404aabb406601f892fd49da90eb6ac558a733c93b47252"
    ],
    [
      "L1axzbSyynNYA8mCAhzxkipKkfHtAXYF4YQnhSKcLV8YXA874fgT",
      "808248bd0375f2f75d7e274ae544fb920f51784480866b102384190b1addfbaa5c01"
    ],
    [
      "927CnUkUbasYtDwYwVn2j8GdTuACNnKkjZ1rpZd2yBB1CLcnXpo",
      "ef44c4f6a096eac5238291a94cc24c01e3b19b8d8cef72874a079e00a242237a52"
    ],
    [
      "cUcfCMRjiQf85YMzzQEk9d1s5A4K7xL5SmBCLrezqXFuTVefyhY7",
      "efd1de707020a9059d6d3abaf85e17967c6555151143db13dbb06db78df0f15c6901"
    ],
    ["1Gqk4Tv79P91Cc1STQtU3s1W6277M2CVWu", "00adc1cc2081a27206fae25792f28bbc55b831549d"],
    ["33vt8ViH5jsr115AGkW6cEmEz9MpvJSwDk", "05188f91a931947eddd7432d6e614387e32b244709"],
    ["mhaMcBxNh5cqXm4aTQ6EcVbKtfL6LGyK2H", "6f1694f5bc1a7295b600f40018a618a6ea48eeb498"],
    ["2MxgPqX1iThW3oZVk9KoFcE5M4JpiETssVN", "c43b9b3fd7a50d4f08d1a5b0f62f644fa7115ae2f3"],
    [
      "5HtH6GdcwCJA4ggWEL1B3jzBBUB8HPiBi9SBc5h9i4Wk4PSeApR",
      "80091035445ef105fa1bb125eccfb1882f3fe69592265956ade751fd095033d8d0"
    ],
    [
      "L2xSYmMeVo3Zek3ZTsv9xUrXVAmrWxJ8Ua4cw8pkfbQhcEFhkXT8",
      "80ab2b4bcdfc91d34dee0ae2a8c6b6668dadaeb3a88b9859743156f462325187af01"
    ],
    [
      "92xFEve1Z9N8Z641KQQS7ByCSb8kGjsDzw6fAmjHN1LZGKQXyMq",
      "efb4204389cef18bbe2b353623cbf93e8678fbc92a475b664ae98ed594e6cf0856"
    ],
    [
      "cVM65tdYu1YK37tNoAyGoJTR13VBYFva1vg9FLuPAsJijGvG6NEA",
      "efe7b230133f1b5489843260236b06edca25f66adb1be455fbd38d4010d48faeef01"
    ],
    ["1JwMWBVLtiqtscbaRHai4pqHokhFCbtoB4", "00c4c1b72491ede1eedaca00618407ee0b772cad0d"],
    ["3QCzvfL4ZRvmJFiWWBVwxfdaNBT8EtxB5y", "05f6fe69bcb548a829cce4c57bf6fff8af3a5981f9"],
    ["mizXiucXRCsEriQCHUkCqef9ph9qtPbZZ6", "6f261f83568a098a8638844bd7aeca039d5f2352c0"],
    ["2NEWDzHWwY5ZZp8CQWbB7ouNMLqCia6YRda", "c4e930e1834a4d234702773951d627cce82fbb5d2e"],
    [
      "5KQmDryMNDcisTzRp3zEq9e4awRmJrEVU1j5vFRTKpRNYPqYrMg",
      "80d1fab7ab7385ad26872237f1eb9789aa25cc986bacc695e07ac571d6cdac8bc0"
    ],
    [
      "L39Fy7AC2Hhj95gh3Yb2AU5YHh1mQSAHgpNixvm27poizcJyLtUi",
      "80b0bbede33ef254e8376aceb1510253fc3550efd0fcf84dcd0c9998b288f166b301"
    ],
    [
      "91cTVUcgydqyZLgaANpf1fvL55FH53QMm4BsnCADVNYuWuqdVys",
      "ef037f4192c630f399d9271e26c575269b1d15be553ea1a7217f0cb8513cef41cb"
    ],
    [
      "cQspfSzsgLeiJGB2u8vrAiWpCU4MxUT6JseWo2SjXy4Qbzn2fwDw",
      "ef6251e205e8ad508bab5596bee086ef16cd4b239e0cc0c5d7c4e6035441e7d5de01"
    ],
    ["19dcawoKcZdQz365WpXWMhX6QCUpR9SY4r", "005eadaf9bb7121f0f192561a5a62f5e5f54210292"],
    ["37Sp6Rv3y4kVd1nQ1JV5pfqXccHNyZm1x3", "053f210e7277c899c3a155cc1c90f4106cbddeec6e"],
    ["myoqcgYiehufrsnnkqdqbp69dddVDMopJu", "6fc8a3c2a09a298592c3e180f02487cd91ba3400b5"],
    ["2N7FuwuUuoTBrDFdrAZ9KxBmtqMLxce9i1C", "c499b31df7c9068d1481b596578ddbb4d3bd90baeb"],
    [
      "5KL6zEaMtPRXZKo1bbMq7JDjjo1bJuQcsgL33je3oY8uSJCR5b4",
      "80c7666842503db6dc6ea061f092cfb9c388448629a6fe868d068c42a488b478ae"
    ],
    [
      "KwV9KAfwbwt51veZWNscRTeZs9CKpojyu1MsPnaKTF5kz69H1UN2",
      "8007f0803fc5399e773555ab1e8939907e9badacc17ca129e67a2f5f2ff84351dd01"
    ],
    [
      "93N87D6uxSBzwXvpokpzg8FFmfQPmvX4xHoWQe3pLdYpbiwT5YV",
      "efea577acfb5d1d14d3b7b195c321566f12f87d2b77ea3a53f68df7ebf8604a801"
    ],
    [
      "cMxXusSihaX58wpJ3tNuuUcZEQGt6DKJ1wEpxys88FFaQCYjku9h",
      "ef0b3b34f0958d8a268193a9814da92c3e8b58b4a4378a542863e34ac289cd830c01"
    ],
    ["13p1ijLwsnrcuyqcTvJXkq2ASdXqcnEBLE", "001ed467017f043e91ed4c44b4e8dd674db211c4e6"],
    ["3ALJH9Y951VCGcVZYAdpA3KchoP9McEj1G", "055ece0cadddc415b1980f001785947120acdb36fc"]
  ]

  @invalid_base58_strings [
    ["Z9inZq4e2HGQRZQezDjFMmqgUE8NwMRok", "Invalid checksum"],
    ["3HK7MezAm6qEZQUMPRf8jX7wDv6zig6Ky8", "Invalid checksum"],
    ["3AW8j12DUk8mgA7kkfZ1BrrzCVFuH1LsXS", "Invalid checksum"]
    # ["#####", "Non-base58 character"] # TODO: handle gracefully
  ]

  describe "decode_base!/1" do
    test "decode_base! properly decodes base58 encoded strings" do
      for pair <- @base58_encode_decode do
        [base16_str, base58_str] = pair
        base16_bin = Base.decode16!(base16_str, case: :lower)
        assert base16_bin == Base58.decode_base!(base58_str)
      end
    end
  end

  describe "encode_base!/1" do
    test "properly encodes to base58" do
      for pair <- @base58_encode_decode do
        [base16_str, base58_str] = pair
        base16_bin = Base.decode16!(base16_str, case: :lower)
        assert base58_str == Base58.encode_base(base16_bin)
      end
    end
  end

  describe "encode/1" do
    test "properly encodes Base58" do
      for pair <- @valid_base58_strings do
        [base58_str, base16_str] = pair

        base16_bin = Base.decode16!(base16_str, case: :lower)
        assert base58_str == Base58.encode(base16_bin)

        # double check
        {:ok, _decoded} = Base58.decode(base58_str)
      end
    end
  end

  describe "decode/1" do
    test "properly decodes Base58" do
      for pair <- @valid_base58_strings do
        [base58_str, base16_str] = pair
        base16_bin = Base.decode16!(base16_str, case: :lower)
        {:ok, decoded} = Base58.decode(base58_str)
        assert base16_bin == decoded
      end
    end

    test "catches invalid checksums" do
      for pair <- @invalid_base58_strings do
        [base58_str, _base16_str] = pair
        assert {:error, :invalid_checksum} = Base58.decode(base58_str)
      end
    end
  end
end


text:
defmodule Bitcoinex.Bech32Test do
  use ExUnit.Case
  doctest Bitcoinex.Bech32

  alias Bitcoinex.Bech32

  # Bech32
  @valid_bech32 [
    "A12UEL5L",
    "an83characterlonghumanreadablepartthatcontainsthenumber1andtheexcludedcharactersbio1tt5tgs",
    "abcdef1qpzry9x8gf2tvdw0s3jn54khce6mua7lmqqqxw",
    "11qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqc8247j",
    "split1checkupstagehandshakeupstreamerranterredcaperred2y9e3w"
  ]

  @invalid_bech32_hrp_char_out_of_range [
    <<0x20, "1nwldj5">>,
    <<0x7F, "1axkwrx">>,
    <<0x90::utf8, "1eym55h">>
  ]

  @invalid_bech32_max_length_exceeded [
    "an84characterslonghumanreadablepartthatcontainsthenumber1andtheexcludedcharactersbio1569pvx"
  ]

  @invalid_bech32_no_separator_character [
    "pzry9x0s0muk"
  ]

  @invalid_bech32_empty_hrp [
    "1pzry9x0s0muk",
    "10a06t8",
    "1qzzfhee"
  ]

  @invalid_bech32_checksum [
    "A12UEL5A"
  ]

  @invalid_bech32_invalid_data_character [
    "x1b4n0q5v"
  ]

  @invalid_bech32_too_short_checksum [
    "li1dgmt3"
  ]

  @invalid_bech32_invalid_character_in_checksum [
    <<"de1lg7wt", 0xFF::utf8>>
  ]

  # Bech32m
  @valid_bech32m [
    "A1LQFN3A",
    "a1lqfn3a",
    "an83characterlonghumanreadablepartthatcontainsthetheexcludedcharactersbioandnumber11sg7hg6",
    "abcdef1l7aum6echk45nj3s0wdvt2fg8x9yrzpqzd3ryx",
    "11llllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllludsr8",
    "split1checkupstagehandshakeupstreamerranterredcaperredlc445v",
    "?1v759aa"
  ]

  @invalid_bech32m_hrp_char_out_of_range [
    <<0x20, "1xj0phk">>,
    <<0x7F, "1g6xzxy">>,
    <<0x90::utf8, "1vctc34">>
  ]

  @invalid_bech32m_max_length_exceeded [
    "an84characterslonghumanreadablepartthatcontainsthetheexcludedcharactersbioandnumber11d6pts4"
  ]

  @invalid_bech32m_no_separator_character [
    "qyrz8wqd2c9m"
  ]

  @invalid_bech32m_empty_hrp [
    "1qyrz8wqd2c9m",
    "16plkw9",
    "1p2gdwpf"
  ]

  @invalid_bech32m_checksum [
    "M1VUXWEZ"
  ]

  @invalid_bech32m_invalid_data_character [
    "y1b0jsk6g",
    "lt1igcx5c0"
  ]

  @invalid_bech32m_too_short_checksum [
    "in1muywd"
  ]

  @invalid_bech32m_invalid_character_in_checksum [
    "mm1crxm3i",
    "au1s5cgom"
  ]

  describe "decode/1 for bech32" do
    test "successfully decode with valid bech32" do
      for bech <- @valid_bech32 do
        assert {:ok, {:bech32, hrp, data}} = Bech32.decode(bech)
        assert hrp != nil

        # encode after decode should be the same(after downcase) as before
        {:ok, new_bech} = Bech32.encode(hrp, data, :bech32)
        assert new_bech == String.downcase(bech)
      end
    end

    test "fail to decode with invalid bech32 out of ranges" do
      for bech <- @invalid_bech32_hrp_char_out_of_range do
        assert {:error, msg} = Bech32.decode(bech)
        assert msg == :hrp_char_out_opf_range
      end
    end

    test "fail to decode with invalid bech32 overall max length exceeded" do
      for bech <- @invalid_bech32_max_length_exceeded do
        assert {:error, msg} = Bech32.decode(bech)
        assert msg == :overall_max_length_exceeded
      end
    end

    test "fail to decode with invalid bech32 no separator character" do
      for bech <- @invalid_bech32_no_separator_character do
        assert {:error, msg} = Bech32.decode(bech)
        assert msg == :no_separator_character
      end
    end

    test "fail to decode with invalid bech32 empty hrp" do
      for bech <- @invalid_bech32_empty_hrp do
        assert {:error, msg} = Bech32.decode(bech)
        assert msg == :empty_hrp
      end
    end

    test "fail to decode with invalid data character" do
      for bech <- @invalid_bech32_invalid_data_character do
        assert {:error, msg} = Bech32.decode(bech)
        assert msg == :contain_invalid_data_char
      end
    end

    test "fail to decode with too short checksum" do
      for bech <- @invalid_bech32_too_short_checksum do
        assert {:error, msg} = Bech32.decode(bech)
        assert msg == :too_short_checksum
      end
    end

    test "fail to decode with invalid character in checksum" do
      for bech <- @invalid_bech32_invalid_character_in_checksum do
        assert {:error, msg} = Bech32.decode(bech)
        assert msg == :contain_invalid_data_char
      end
    end

    test "fail to decode with invalid checksum" do
      for bech <- @invalid_bech32_checksum do
        assert {:error, msg} = Bech32.decode(bech)
        assert msg == :invalid_checksum
      end
    end
  end

  describe "decode/1 for bech32m" do
    test "successfully decode with valid bech32" do
      for bech <- @valid_bech32m do
        assert {:ok, {:bech32m, hrp, data}} = Bech32.decode(bech)
        assert hrp != nil

        # encode after decode should be the same(after downcase) as before
        {:ok, new_bech} = Bech32.encode(hrp, data, :bech32m)
        assert new_bech == String.downcase(bech)
      end
    end

    test "fail to decode with invalid bech32m out of ranges" do
      for bech <- @invalid_bech32m_hrp_char_out_of_range do
        assert {:error, msg} = Bech32.decode(bech)
        assert msg == :hrp_char_out_opf_range
      end
    end

    test "fail to decode with invalid bech32m overall max length exceeded" do
      for bech <- @invalid_bech32m_max_length_exceeded do
        assert {:error, msg} = Bech32.decode(bech)
        assert msg == :overall_max_length_exceeded
      end
    end

    test "fail to decode with invalid bech32m no separator character" do
      for bech <- @invalid_bech32m_no_separator_character do
        assert {:error, msg} = Bech32.decode(bech)
        assert msg == :no_separator_character
      end
    end

    test "fail to decode with invalid bech32m empty hrp" do
      for bech <- @invalid_bech32m_empty_hrp do
        assert {:error, msg} = Bech32.decode(bech)
        assert msg == :empty_hrp
      end
    end

    test "fail to decode with invalid data character" do
      for bech <- @invalid_bech32m_invalid_data_character do
        assert {:error, msg} = Bech32.decode(bech)
        assert msg == :contain_invalid_data_char
      end
    end

    test "fail to decode with too short checksum" do
      for bech <- @invalid_bech32m_too_short_checksum do
        assert {:error, msg} = Bech32.decode(bech)
        assert msg == :too_short_checksum
      end
    end

    test "fail to decode with invalid character in checksum" do
      for bech <- @invalid_bech32m_invalid_character_in_checksum do
        assert {:error, msg} = Bech32.decode(bech)
        assert msg == :contain_invalid_data_char
      end
    end

    test "fail to decode with invalid checksum" do
      for bech <- @invalid_bech32m_checksum do
        assert {:error, msg} = Bech32.decode(bech)
        assert msg == :invalid_checksum
      end
    end
  end

  describe "encode/2 for bech32" do
    test "successfully encode with valid hrp and empty data" do
      assert {:ok, _bech} = Bech32.encode("bc", [], :bech32)
    end

    test "successfully encode with valid hrp and non empty valid data" do
      assert {:ok, _bech} = Bech32.encode("bc", [1, 2], :bech32)
    end

    test "successfully encode with invalid string data" do
      assert {:ok, _bech} = Bech32.encode("bc", "qpzry9x8gf2tvdw0s3jn54khce6mua7l", :bech32)
    end

    test "fail to encode with valid hrp and non empty valid string data" do
      assert {:error, _bech} = Bech32.encode("bc", "qpzry9x8gf2tvdw0s3jn54khce6mua7lo", :bech32)
    end

    test "fail to encode with overall encoded length is over 90" do
      data = for _ <- 1..82, do: 1
      assert {:error, :overall_max_length_exceeded} = Bech32.encode("bc", data, :bech32)
    end

    test "fail to encode with hrp contain invalid char(out of 1 to 83 US-ASCII)" do
      data = [1]
      assert {:error, :hrp_char_out_opf_range} = Bech32.encode(" ", data, :bech32)
      assert {:error, :hrp_char_out_opf_range} = Bech32.encode("\ł", data, :bech32)
      assert {:error, :hrp_char_out_opf_range} = Bech32.encode("中文", data, :bech32)
    end
  end

  describe "encode/2 for bech32m" do
    test "successfully encode with valid hrp and empty data" do
      assert {:ok, _bech} = Bech32.encode("bc", [], :bech32m)
    end

    test "successfully encode with valid hrp and non empty valid data" do
      assert {:ok, _bech} = Bech32.encode("bc", [1, 2], :bech32m)
    end

    test "successfully encode with invalid string data" do
      assert {:ok, _bech} = Bech32.encode("bc", "qpzry9x8gf2tvdw0s3jn54khce6mua7l", :bech32m)
    end

    test "fail to encode with valid hrp and non empty valid string data" do
      assert {:error, _bech} = Bech32.encode("bc", "qpzry9x8gf2tvdw0s3jn54khce6mua7lo", :bech32m)
    end

    test "fail to encode with overall encoded length is over 90" do
      data = for _ <- 1..82, do: 1
      assert {:error, :overall_max_length_exceeded} = Bech32.encode("bc", data, :bech32m)
    end

    test "fail to encode with hrp contain invalid char(out of 1 to 83 US-ASCII)" do
      data = [1]
      assert {:error, :hrp_char_out_opf_range} = Bech32.encode(" ", data, :bech32m)
      assert {:error, :hrp_char_out_opf_range} = Bech32.encode("\ł", data, :bech32m)
      assert {:error, :hrp_char_out_opf_range} = Bech32.encode("中文", data, :bech32m)
    end
  end
end


text: ExUnit.start()