예제 #1
0
 def test_from_shares(self):
     shares = [
         "eraser senior decision roster beard treat identify grumpy salt index fake aviation theater cubic bike cause research dragon emphasis counter",
         "eraser senior ceramic snake clay various huge numb argue hesitate auction category timber browser greatest hanger petition script leaf pickup",
         "eraser senior ceramic shaft dynamic become junior wrist silver peasant force math alto coal amazing segment yelp velvet image paces",
         "eraser senior ceramic round column hawk trust auction smug shame alive greatest sheriff living perfect corner chest sled fumes adequate",
         "eraser senior decision smug corner ruin rescue cubic angel tackle skin skunk program roster trash rumor slush angel flea amazing",
     ]
     hd_priv = HDPrivateKey.from_shares(shares, passphrase=b"TREZOR")
     expected = HDPrivateKey.from_mnemonic(
         "label original trim census flock area area virus purchase hobby globe cart"
     )
     self.assertEqual(hd_priv.xprv(), expected.xprv())
     shares = [
         "wildlife deal ceramic round aluminum pitch goat racism employer miracle percent math decision episode dramatic editor lily prospect program scene rebuild display sympathy have single mustang junction relate often chemical society wits estate",
         "wildlife deal decision scared acne fatal snake paces obtain election dryer dominant romp tactics railroad marvel trust helpful flip peanut theory theater photo luck install entrance taxi step oven network dictate intimate listen",
         "wildlife deal ceramic scatter argue equip vampire together ruin reject literary rival distance aquatic agency teammate rebound false argue miracle stay again blessing peaceful unknown cover beard acid island language debris industry idle",
         "wildlife deal ceramic snake agree voter main lecture axis kitchen physics arcade velvet spine idea scroll promise platform firm sharp patrol divorce ancestor fantasy forbid goat ajar believe swimming cowboy symbolic plastic spelling",
         "wildlife deal decision shadow analysis adjust bulb skunk muscle mandate obesity total guitar coal gravity carve slim jacket ruin rebuild ancestor numerous hour mortgage require herd maiden public ceiling pecan pickup shadow club",
     ]
     hd_priv = HDPrivateKey.from_shares(shares, passphrase=b"TREZOR")
     expected = HDPrivateKey.from_mnemonic(
         "fatal click tennis boring short ask clever bus valve island join edit blue end obtain rate travel fragile ahead mimic maid receive employ learn"
     )
     self.assertEqual(hd_priv.xprv(), expected.xprv())
예제 #2
0
 def test_bip49(self):
     mnemonic = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"
     password = b""
     path = "m"
     hd_private_key = HDPrivateKey.from_mnemonic(
         mnemonic, password, path=path, network="testnet"
     )
     want = "tprv8ZgxMBicQKsPe5YMU9gHen4Ez3ApihUfykaqUorj9t6FDqy3nP6eoXiAo2ssvpAjoLroQxHqr3R5nE3a5dU3DHTjTgJDd7zrbniJr6nrCzd"
     self.assertEqual(hd_private_key.xprv(), want)
     hd_private_key = HDPrivateKey.from_mnemonic(
         mnemonic, password, path=path, network="signet"
     )
     want = "tprv8ZgxMBicQKsPe5YMU9gHen4Ez3ApihUfykaqUorj9t6FDqy3nP6eoXiAo2ssvpAjoLroQxHqr3R5nE3a5dU3DHTjTgJDd7zrbniJr6nrCzd"
     self.assertEqual(hd_private_key.xprv(), want)
     account0 = (
         hd_private_key.child((1 << 31) + 49).child((1 << 31) + 1).child(1 << 31)
     )
     want = "tprv8gRrNu65W2Msef2BdBSUgFdRTGzC8EwVXnV7UGS3faeXtuMVtGfEdidVeGbThs4ELEoayCAzZQ4uUji9DUiAs7erdVskqju7hrBcDvDsdbY"
     self.assertEqual(account0.xprv(), want)
     account0_pub = account0.pub
     account0_first_key = account0.child(0).child(0)
     pub_first_key = account0_pub.traverse("m/0/0")
     want = "cULrpoZGXiuC19Uhvykx7NugygA3k86b3hmdCeyvHYQZSxojGyXJ"
     self.assertEqual(account0_first_key.wif(), want)
     want = 0xC9BDB49CFBAEDCA21C4B1F3A7803C34636B1D7DC55A717132443FC3F4C5867E8
     self.assertEqual(account0_first_key.private_key.secret, want)
     want = bytes.fromhex(
         "03a1af804ac108a8a51782198c2d034b28bf90c8803f5a53f76276fa69a4eae77f"
     )
     self.assertEqual(account0_first_key.private_key.point.sec(), want)
     self.assertEqual(pub_first_key.address(), account0_first_key.address())
예제 #3
0
    def test_xpub_version(self):
        mnemonic = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"
        hd_obj = HDPrivateKey.from_mnemonic(mnemonic, network="testnet")
        tprv = "tprv8ZgxMBicQKsPe5YMU9gHen4Ez3ApihUfykaqUorj9t6FDqy3nP6eoXiAo2ssvpAjoLroQxHqr3R5nE3a5dU3DHTjTgJDd7zrbniJr6nrCzd"
        tpub = "tpubD6NzVbkrYhZ4XYa9MoLt4BiMZ4gkt2faZ4BcmKu2a9te4LDpQmvEz2L2yDERivHxFPnxXXhqDRkUNnQCpZggCyEZLBktV7VaSmwayqMJy1s"
        self.assertEqual(hd_obj.xprv(), tprv)
        self.assertEqual(hd_obj.xpub(), tpub)
        hd_obj = HDPrivateKey.from_mnemonic(mnemonic, network="signet")
        self.assertEqual(hd_obj.xprv(), tprv)
        self.assertEqual(hd_obj.xpub(), tpub)
        # parse this same HDPrivateKey from the tprv (assume we no longer have the mnemonic)
        recreated_obj = HDPrivateKey.parse(tprv)
        self.assertEqual(recreated_obj.xpub(), tpub)
        # Confirm the version bytes have passed through correctly to the HDPublicKey object:
        self.assertEqual(recreated_obj.pub.xpub(), tpub)

        hd_obj = HDPrivateKey.from_mnemonic(mnemonic, network="mainnet")
        xprv = "xprv9s21ZrQH143K3GJpoapnV8SFfukcVBSfeCficPSGfubmSFDxo1kuHnLisriDvSnRRuL2Qrg5ggqHKNVpxR86QEC8w35uxmGoggxtQTPvfUu"
        xpub = "xpub661MyMwAqRbcFkPHucMnrGNzDwb6teAX1RbKQmqtEF8kK3Z7LZ59qafCjB9eCRLiTVG3uxBxgKvRgbubRhqSKXnGGb1aoaqLrpMBDrVxga8"
        self.assertEqual(hd_obj.xprv(), xprv)
        self.assertEqual(hd_obj.xpub(), xpub)

        # parse this same HDPrivateKey from the xprv (assume we no longer have the mnemonic)
        recreated_obj = HDPrivateKey.parse(xprv)
        self.assertEqual(recreated_obj.xpub(), xpub)
        # Confirm the version bytes have passed through correctly to the HDPublicKey object:
        self.assertEqual(recreated_obj.pub.xpub(), xpub)
예제 #4
0
    def test_key_record(self):
        # Matched against seedpicker.net on 2021-06-04
        seed_phrase = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon art"

        # mainnet
        hdpriv_obj = HDPrivateKey.from_mnemonic(seed_phrase)
        want = "[5436d724/48h/0h/0h/2h]Zpub74fkz9VnCkwXHqheLbKfBEPKPfnFg3S1Ecn7Jx4HgBWwD1FS5VLMtFYNqLmFBVifGuXKz2nirv647gFYCinhBSdBqrrh5vq8ok8m2eaRAt7"
        self.assertEqual(
            hdpriv_obj.generate_p2wsh_key_record(use_slip132_version_byte=True), want
        )
        want = "[5436d724/48h/0h/0h/2h]xpub6E79FaRWLSJCAgA2jDHRvyrWKwT6aSmR685zptzyYPvmUd44omcxZ1NAzDtbdFBvEADjcVbV4NzTDwQeU6oiSV9KGiMSWhjANZjbfUHkm3Y"
        self.assertEqual(hdpriv_obj.generate_p2wsh_key_record(), want)
        # testnet
        hdpriv_obj = HDPrivateKey.from_mnemonic(seed_phrase, network="testnet")
        want = "[5436d724/48h/1h/0h/2h]Vpub5ncJ4gVToMcTWjG4shBZHeeCUXhX5r86W9cwggqw1m6aojbrHxr9yJFsoXaiXrBfAzV3TaVyxCB6EYUW21SVayfcAhiVc9XRJS1WL4Gh9td"
        self.assertEqual(
            hdpriv_obj.generate_p2wsh_key_record(use_slip132_version_byte=True), want
        )
        want = "[5436d724/48h/1h/0h/2h]tpubDFkN51vYF36W4Yfn3wGv5fpmRo3ok7vZZjc1gmRJjumq33L776e6GkP4HGdCVjDqYiBahXCrXQKja8aUZ2xovQNS8WkF46MdY7TLHJLYD7H"
        self.assertEqual(hdpriv_obj.generate_p2wsh_key_record(), want)
        # signet
        hdpriv_obj = HDPrivateKey.from_mnemonic(seed_phrase, network="signet")
        want = "[5436d724/48h/1h/0h/2h]Vpub5ncJ4gVToMcTWjG4shBZHeeCUXhX5r86W9cwggqw1m6aojbrHxr9yJFsoXaiXrBfAzV3TaVyxCB6EYUW21SVayfcAhiVc9XRJS1WL4Gh9td"
        self.assertEqual(
            hdpriv_obj.generate_p2wsh_key_record(use_slip132_version_byte=True), want
        )
        want = "[5436d724/48h/1h/0h/2h]tpubDFkN51vYF36W4Yfn3wGv5fpmRo3ok7vZZjc1gmRJjumq33L776e6GkP4HGdCVjDqYiBahXCrXQKja8aUZ2xovQNS8WkF46MdY7TLHJLYD7H"
        self.assertEqual(hdpriv_obj.generate_p2wsh_key_record(), want)
예제 #5
0
    def test_from_mnemonic_errors(self):
        with self.assertRaises(InvalidBIP39Length):
            # only 1 word!
            HDPrivateKey.from_mnemonic("hello")

        with self.assertRaises(InvalidChecksumWordsError):
            # 12 words (11 would work)
            mnemonic = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon"
            HDPrivateKey.from_mnemonic(mnemonic)
예제 #6
0
def _get_all_valid_checksum_words(first_words, first_match=True):
    # TODO: move to buidl library
    to_return = []
    for word in WORD_LIST:
        try:
            HDPrivateKey.from_mnemonic(first_words + " " + word)
            if first_match:
                return [word], ""
            to_return.append(word)
        except KeyError as e:
            # We have a word in first_words that is not in WORD_LIST
            return [], "Invalid BIP39 Word: {}".format(e.args[0])
        except ValueError:
            pass

    return to_return, ""
예제 #7
0
    def test_receive_1of2(self):
        # This test is not strictly neccesary, it just proves/shows how I generated the testnet address that received these coins
        # In the next test, I'll show to spend them

        # For full details, see test_create_p2sh_multisig in test_script.py

        # Insecure testing BIP39 mnemonics
        root_path = "m/45'/0/0/0"
        hdprivs = [
            HDPrivateKey.from_mnemonic(seed_word * 12, network="testnet")
            for seed_word in ("action ", "agent ")
        ]

        # Validate the 0th receive address for m/45'/0
        expected_spending_addr = "2ND4qfpdHyeXJboAUkKZqJsyiKyXvHRKhbi"
        pubkey_hexes = [
            hdpriv.traverse(root_path).pub.sec().hex() for hdpriv in hdprivs
        ]

        redeem_script_to_use = RedeemScript.create_p2sh_multisig(
            quorum_m=1,
            pubkey_hexes=pubkey_hexes,
            sort_keys=True,
        )
        self.assertEqual(expected_spending_addr,
                         redeem_script_to_use.address(network="testnet"))
예제 #8
0
 def test_child(self):
     seed = b"[email protected] Jimmy Song"
     tests = (
         (
             "testnet",
             "tb1qu6mnnk54hxfhy4aj58v0w6e7q8hghtv8wcdl7g",
             "tb1qscu8evdlqsucj7p84xwnrf63h4jsdr5yqga8zq",
         ),
         (
             "signet",
             "tb1qu6mnnk54hxfhy4aj58v0w6e7q8hghtv8wcdl7g",
             "tb1qscu8evdlqsucj7p84xwnrf63h4jsdr5yqga8zq",
         ),
         (
             "mainnet",
             "bc1qu6mnnk54hxfhy4aj58v0w6e7q8hghtv8y7kv9m",
             "bc1qscu8evdlqsucj7p84xwnrf63h4jsdr5y2wx5en",
         ),
     )
     for network, want1, want2 in tests:
         priv = HDPrivateKey.from_seed(seed, network=network)
         pub = priv.pub
         addr = priv.child(0).p2wpkh_address()
         self.assertEqual(addr, want1)
         addr = pub.child(0).p2wpkh_address()
         self.assertEqual(addr, want1)
         addr = priv.child(0x80000002).p2wpkh_address()
         self.assertEqual(addr, want2)
         with self.assertRaises(ValueError):
             pub.child(0x80000002)
예제 #9
0
 def test_parse(self):
     xpub = "xpub661MyMwAqRbcEZVB4dScxMAdx6d4nFc9nvyvH3v4gJL378CSRZiYmhRoP7mBy6gSPSCYk6SzXPTf3ND1cZAceL7SfJ1Z3GC8vBgp2epUt13"
     hd_pub = HDPublicKey.parse(xpub)
     self.assertEqual(hd_pub.xpub(), xpub)
     xprv = "xprv9s21ZrQH143K25QhxbucbDDuQ4naNntJRi4KUfWT7xo4EKsHt2QJDu7KXp1A3u7Bi1j8ph3EGsZ9Xvz9dGuVrtHHs7pXeTzjuxBrCmmhgC6"
     hd_priv = HDPrivateKey.parse(xprv)
     self.assertEqual(hd_priv.xprv(), xprv)
예제 #10
0
 def test_bad_depth(self):
     starting_path = "m/48h/0h/0h/2h"
     starting_xpub = (HDPrivateKey.from_mnemonic(
         "bacon " * 24).traverse(starting_path).xpub())
     with self.assertRaises(ValueError):
         blind_xpub(starting_xpub=starting_xpub,
                    starting_path="m/1",
                    secret_path="m/999")
예제 #11
0
    def test_blind_root_xpub(self):
        # All test vectors below compared manually to https://iancoleman.io/bip39/

        starting_path = "m"
        # The .traverse() here does nothing and is optional, just here for consistency/clarity:
        root_xpub = (HDPrivateKey.from_mnemonic(
            "bacon " * 24).traverse(starting_path).xpub())

        self.assertEqual(
            root_xpub,
            "xpub661MyMwAqRbcGMzZtBZrKWaDMAQKjd5bMSnHr8qBfCz5zMwZEuajnc8cxjsEUECAZDeDC7s4zbT3Z9KrrM9wJ3MaT6sH9eRYxLY1BNf45BF",
        )
        secret_path = "m/1/2/3/4/5/6/7/8/9"
        have = blind_xpub(starting_xpub=root_xpub,
                          starting_path="m",
                          secret_path=secret_path)
        want = {
            "blinded_full_path":
            secret_path,  # the same since we started with the root path
            "blinded_child_xpub":
            "xpub6QbmAk7amacgfQVY3aTAiqhM1okb4Gi3EFzBJQMEja1uU4gZvG9vwiExr2C1ryZqnyczTvvaNhw63UALmXGkFo7FHhajX8SNimgK3zu3tp5",
        }
        self.assertEqual(have, want)

        starting_path = "m/48h/0h/0h/2h"
        child_xpub = (HDPrivateKey.from_mnemonic(
            "bacon " * 24).traverse(starting_path).xpub())
        self.assertEqual(
            child_xpub,
            "xpub6EeqK2JLwngrHJEQ4X4iqrySZV9qU3TgwMgf6NStLZa37AfNiHTtTE9ji1F9YQDLArJMLy8sw3Q2samVj5VQQjaaUHr5z2Hz57NWHJCfh31",
        )
        secret_path = "m/920870093/318569592/821713943/1914815254/1398142787/9"  # randomly generated
        have = blind_xpub(
            starting_xpub=child_xpub,
            starting_path=starting_path,
            secret_path=secret_path,
        )
        want = {
            "blinded_full_path":
            "m/48h/0h/0h/2h/920870093/318569592/821713943/1914815254/1398142787/9",
            "blinded_child_xpub":
            "xpub6SDyub38LYeUd211VxRZCiVnU4fSr4ZwPssFGMxyyXjZ6EvrA4ZkZhp4f9My2qzqSJFHxkAv8ctaGipx7UM6sCzmso7wxiohHBhcBs7ipWz",
        }
        self.assertEqual(have, want)
예제 #12
0
    def test_vpub(self):
        # From https://seedpicker.net/calculator/last-word.html?network=testnet
        mnemonic = "zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo buddy"
        path = "m/48'/1'/0'/2'"

        self.assertEqual(
            "669dce62", HDPrivateKey.from_mnemonic(mnemonic).fingerprint().hex()
        )
        vpub = (
            HDPrivateKey.from_mnemonic(mnemonic)
            .traverse(path)
            .xpub(bytes.fromhex("02575483"))
        )
        want = "Vpub5mru9pEB9wFgRsCsYi4hcTvaqZ5p2xs2upUsN5Fig6nUrug2xkfPmbt2PfUS5QhDgCLctdkuQLmVnpN1j8a6RS9Mk53mbxi3Mx4HB6vCTWc"
        self.assertEqual(vpub, want)

        # Be sure that we can parse a vpub:
        hdpubkey_obj = HDPublicKey.parse(want)
        self.assertEqual(hdpubkey_obj.depth, 4)
예제 #13
0
    def test_secure_mnemonic_bits(self):
        tests = (
            # num_bits, num_words
            (128, 12),
            (160, 15),
            (192, 18),
            (224, 21),
            (256, 24),
        )

        for num_bits, num_words in tests:
            mnemonic = secure_mnemonic(num_bits=num_bits)
            self.assertEqual(num_words, len(mnemonic.split(" ")))
            # This is inherently non-deterministic, so we can't check the specific output
            HDPrivateKey.from_mnemonic(mnemonic, network="testnet")

        for invalid_num_bits in (-1, 1, 127, 129, 257, "notanint"):
            with self.assertRaises(ValueError):
                secure_mnemonic(num_bits=invalid_num_bits)
예제 #14
0
 def test_from_seed(self):
     seed = b"[email protected] Jimmy Song"
     tests = {
         "testnet": "tb1q7kn55vf3mmd40gyj46r245lw87dc6us5n50lrg",
         "signet": "tb1q7kn55vf3mmd40gyj46r245lw87dc6us5n50lrg",
         "mainnet": "bc1q7kn55vf3mmd40gyj46r245lw87dc6us5ej5vcm",
     }
     for network, want in tests.items():
         priv = HDPrivateKey.from_seed(seed, network=network)
         addr = priv.p2wpkh_address()
         self.assertEqual(addr, want)
예제 #15
0
    def test_secure_mnemonic_extra_entropy(self):
        tests = (
            # num_bits, num_words, extra_entropy
            (128, 12, 0),
            (160, 15, 1),
            (192, 18, 2**128),
            (224, 21, 2**256),
            (256, 24, 2**512),
        )

        for num_bits, num_words, extra_entropy in tests:
            mnemonic = secure_mnemonic(num_bits=num_bits, extra_entropy=extra_entropy)
            self.assertEqual(num_words, len(mnemonic.split(" ")))
            # This is inherently non-deterministic, so we can't check the specific output
            HDPrivateKey.from_mnemonic(mnemonic, network="testnet")

        with self.assertRaises(TypeError):
            secure_mnemonic(extra_entropy="not an int")
        with self.assertRaises(ValueError):
            secure_mnemonic(extra_entropy=-1)
예제 #16
0
 def test_p2wpkh_address(self):
     mnemonic = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"
     password = b""
     path = "m/84'/0'/0'"
     account = HDPrivateKey.from_mnemonic(
         mnemonic, password, path=path, network="mainnet"
     )
     want = "zprvAdG4iTXWBoARxkkzNpNh8r6Qag3irQB8PzEMkAFeTRXxHpbF9z4QgEvBRmfvqWvGp42t42nvgGpNgYSJA9iefm1yYNZKEm7z6qUWCroSQnE"
     self.assertEqual(account.xprv(version=bytes.fromhex("04b2430c")), want)
     want = "zpub6rFR7y4Q2AijBEqTUquhVz398htDFrtymD9xYYfG1m4wAcvPhXNfE3EfH1r1ADqtfSdVCToUG868RvUUkgDKf31mGDtKsAYz2oz2AGutZYs"
     self.assertEqual(account.xpub(version=bytes.fromhex("04b24746")), want)
     first_key = account.child(0).child(0)
     want = "bc1qcr8te4kr609gcawutmrza0j4xv80jy8z306fyu"
     self.assertEqual(first_key.p2wpkh_address(), want)
예제 #17
0
def _get_bip39_seed(network):
    readline.parse_and_bind("tab: complete")
    old_completer = readline.get_completer()
    completer = WordCompleter(wordlist=BIP39)
    readline.set_completer(completer.complete)

    bip39_prompt = blue_fg("Enter your full BIP39 seed phrase: ")
    while True:
        seed_phrase = input(bip39_prompt).strip()
        seed_phrase_num = len(seed_phrase.split())

        if seed_phrase_num not in (12, 15, 18, 21, 24):
            # Other length seed phrases also work but this is not documented as it's for advanced users
            print_red(
                f"You entered {seed_phrase_num} words. "
                "By default seed phrases should be 24 words long (advanced users may enter seed phrases that are 12, 15, 18 or 21 words long)."
            )
            continue

        for cnt, word in enumerate(seed_phrase.split()):
            if word not in BIP39:
                print_red(f"Word #{cnt+1} ({word}) is not a valid BIP39 word")
                continue
        try:
            HDPrivateKey.from_mnemonic(mnemonic=seed_phrase, network=network)

            password = _get_password()
            hd_priv = HDPrivateKey.from_mnemonic(mnemonic=seed_phrase,
                                                 network=network,
                                                 password=password.encode())
        except Exception as e:
            print_red(f"Invalid mnemonic: {e}")
            continue

        readline.set_completer(old_completer)
        return hd_priv
예제 #18
0
    def test_p2wsh_1of2_sorted_and_unsorted(self):
        TESTNET_DEFAULT_PATH = "m/48h/1h/0h/2h"
        # https://github.com/satoshilabs/slips/blob/master/slip-0132.md
        TESTNET_VERSION_BYTE = bytes.fromhex("043587cf")

        key_records = []
        for mnenmonic in ("invest ", "sell "):
            hd_priv_obj = HDPrivateKey.from_mnemonic(mnenmonic * 12)
            xfp_hex = hd_priv_obj.fingerprint().hex()
            xpub = hd_priv_obj.traverse(
                path=TESTNET_DEFAULT_PATH).xpub(TESTNET_VERSION_BYTE)
            key_records.append({
                "xfp": xfp_hex,
                "path": TESTNET_DEFAULT_PATH,
                "xpub_parent": xpub,
                "account_index": 0,
            })

        p2wsh_sorted_obj = P2WSHSortedMulti(quorum_m=1,
                                            key_records=key_records)
        expected = "wsh(sortedmulti(1,[aa917e75/48h/1h/0h/2h]tpubDEZRP2dRKoGRJnR9zn6EoLouYKbYyjFsxywgG7wMQwCDVkwNvoLhcX1rTQipYajmTAF82kJoKDiNCgD4wUPahACE7n1trMSm7QS8B3S1fdy/0/*,[2553c4b8/48h/1h/0h/2h]tpubDEiNuxUt4pKjKk7khdv9jfcS92R1WQD6Z3dwjyMFrYj2iMrYbk3xB5kjg6kL4P8SoWsQHpd378RCTrM7fsw4chnJKhE2kfbfc4BCPkVh6g9/0/*))#t0v98kwu"
        self.assertEqual(expected, str(p2wsh_sorted_obj))
        self.assertEqual(
            p2wsh_sorted_obj.get_address(1),
            "tb1q6y5dh62l40q9de8k53sjekqz2zcn9dfs55g5v8pjmd7ehg08gk0sms9q4l",
        )

        # Test unsorted

        # These sometimes produce the same address (should be 50% on average)
        addr_matches = [
            False, True, True, True, True, False, True, False, True, False
        ]
        for cnt, addr_match in enumerate(addr_matches):
            sorted_addr = p2wsh_sorted_obj.get_address(cnt, sort_keys=True)
            unsorted_addr = p2wsh_sorted_obj.get_address(cnt, sort_keys=False)
            if addr_match:
                self.assertEqual(sorted_addr, unsorted_addr)
            else:
                self.assertNotEqual(sorted_addr, unsorted_addr)

        # manually checked on 2021-06-08 that when imported to Caravan it generates the same addresses as buidl
        want = """{"name": "foo", "addressType": "P2WSH", "network": "testnet", "client": {"type": "public"}, "quorum": {"requiredSigners": 1, "totalSigners": 2}, "extendedPublicKeys": [{"bip32Path": "m/48'/1'/0'/2'", "xpub": "tpubDEZRP2dRKoGRJnR9zn6EoLouYKbYyjFsxywgG7wMQwCDVkwNvoLhcX1rTQipYajmTAF82kJoKDiNCgD4wUPahACE7n1trMSm7QS8B3S1fdy", "xfp": "aa917e75", "name": "alice"}, {"bip32Path": "m/48'/1'/0'/2'", "xpub": "tpubDEiNuxUt4pKjKk7khdv9jfcS92R1WQD6Z3dwjyMFrYj2iMrYbk3xB5kjg6kL4P8SoWsQHpd378RCTrM7fsw4chnJKhE2kfbfc4BCPkVh6g9", "xfp": "2553c4b8", "name": "bob"}], "startingAddressIndex": 0}"""
        self.assertEqual(
            p2wsh_sorted_obj.caravan_export(wallet_name="foo",
                                            key_record_names=["alice", "bob"]),
            want,
        )
예제 #19
0
 def test_p2tr_address(self):
     mnemonic = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"
     password = b""
     path = "m"
     hd_priv_key = HDPrivateKey.from_mnemonic(
         mnemonic, password, path=path, network="mainnet"
     )
     self.assertEqual(
         hd_priv_key.get_p2tr_change_address(),
         "bc1p3qkhfews2uk44qtvauqyr2ttdsw7svhkl9nkm9s9c3x4ax5h60wqwruhk7",
     )
     self.assertEqual(
         hd_priv_key.get_p2tr_receiving_address(),
         "bc1p5cyxnuxmeuwuvkwfem96lqzszd02n6xdcjrs20cac6yqjjwudpxqkedrcr",
     )
     self.assertEqual(
         hd_priv_key.get_p2tr_receiving_address(address_num=1),
         "bc1p4qhjn9zdvkux4e44uhx8tc55attvtyu358kutcqkudyccelu0was9fqzwh",
     )
예제 #20
0
 def test_zprv(self):
     mnemonic, priv = HDPrivateKey.generate(extra_entropy=1 << 128)
     for word in mnemonic.split():
         self.assertTrue(word in BIP39)
     zprv = priv.xprv(version=bytes.fromhex("04b2430c"))
     self.assertTrue(zprv.startswith("zprv"))
     zpub = priv.pub.xpub(version=bytes.fromhex("04b24746"))
     self.assertTrue(zpub.startswith("zpub"))
     derived = HDPrivateKey.parse(zprv)
     self.assertEqual(zprv, derived.xprv(bytes.fromhex("04b2430c")))
     mnemonic, priv = HDPrivateKey.generate(network="testnet")
     zprv = priv.xprv(bytes.fromhex("045f18bc"))
     self.assertTrue(zprv.startswith("vprv"))
     zpub = priv.pub.xpub(bytes.fromhex("045f1cf6"))
     self.assertTrue(zpub.startswith("vpub"))
     xpub = priv.pub.xpub(bytes.fromhex("043587cf"))
     self.assertTrue(xpub.startswith("tpub"))
     derived = HDPrivateKey.parse(zprv)
     self.assertEqual(zprv, derived.xprv(bytes.fromhex("045f18bc")))
     derived_pub = HDPublicKey.parse(zpub)
     self.assertEqual(zpub, derived_pub.xpub(bytes.fromhex("045f1cf6")))
     mnemonic, priv = HDPrivateKey.generate(network="signet")
     zprv = priv.xprv(bytes.fromhex("045f18bc"))
     self.assertTrue(zprv.startswith("vprv"))
     zpub = priv.pub.xpub(bytes.fromhex("045f1cf6"))
     self.assertTrue(zpub.startswith("vpub"))
     xpub = priv.pub.xpub(bytes.fromhex("043587cf"))
     self.assertTrue(xpub.startswith("tpub"))
     derived = HDPrivateKey.parse(zprv)
     self.assertEqual(zprv, derived.xprv(bytes.fromhex("045f18bc")))
     derived_pub = HDPublicKey.parse(zpub)
     self.assertEqual(zpub, derived_pub.xpub(bytes.fromhex("045f1cf6")))
     with self.assertRaises(ValueError):
         bad_zprv = encode_base58_checksum(b"\x00" * 78)
         HDPrivateKey.parse(bad_zprv)
     with self.assertRaises(ValueError):
         bad_zpub = encode_base58_checksum(b"\x00" * 78)
         HDPublicKey.parse(bad_zpub)
     with self.assertRaises(ValueError):
         derived_pub.child(1 << 31)
예제 #21
0
 def test_traverse(self):
     seed = b"[email protected] Jimmy Song"
     tests = (
         ("testnet", "tb1q423gz8cenqt6vfw987vlyxql0rh2jgh4sy0tue"),
         ("signet", "tb1q423gz8cenqt6vfw987vlyxql0rh2jgh4sy0tue"),
         ("mainnet", "bc1q423gz8cenqt6vfw987vlyxql0rh2jgh46z5c82"),
     )
     for network, want in tests:
         priv = HDPrivateKey.from_seed(seed, network=network)
         pub = priv.pub
         path = "m/1/2/3/4"
         self.assertEqual(
             priv.traverse(path).p2wpkh_address(),
             pub.traverse(path).p2wpkh_address(),
         )
         path = "m/0/1'/2/3'"
         self.assertEqual(
             priv.traverse(path).p2wpkh_address(),
             want,
         )
예제 #22
0
    def test_blind_m48_xpub(self):
        # All test vectors below compared manually to https://iancoleman.io/bip39/ and then converted with https://jlopp.github.io/xpub-converter/
        starting_path = "m/48h/0h/0h/2h"
        starting_xpub = (HDPrivateKey.from_mnemonic(
            "oil " * 12).traverse(starting_path).xpub())

        self.assertEqual(
            starting_xpub,
            "xpub6F8WgTkiV8iDPFG1Kv4sNrcBNMMgKK4cjfxjdZWvR3kChfbt3L2dJF7xmCHBMGMmxjyzwgjdFkh9UN3623YpsmqN1KwZGR45Y3ANLQQX87u",
        )
        secret_path = "m/920870093/318569592/821713943/1914815254/1398142787/9"  # randomly generated
        have = blind_xpub(
            starting_xpub=starting_xpub,
            starting_path=starting_path,
            secret_path=secret_path,
        )
        want = {
            "blinded_full_path":
            "m/48h/0h/0h/2h/920870093/318569592/821713943/1914815254/1398142787/9",
            "blinded_child_xpub":
            "xpub6RKvkus7fgUnP2trNVo2N1jaQeGPBHCV9m63Jaje8EW2Ry5KjYySb1tbPSi3E7Vh6ZzxRn15hUmBg5KmSaQvKjZmTvXKQnRPXcoJS9PXkiS",
        }
        self.assertEqual(have, want)
예제 #23
0
 def test_blind_m48_vpub(self):
     # All test vectors below compared manually to https://iancoleman.io/bip39/ and then converted with https://jlopp.github.io/xpub-converter/
     starting_path = "m/48h/1h/0h/2h"
     # Vpub version bytes
     starting_vpub = (HDPrivateKey.from_mnemonic(
         "oil " * 12).traverse(starting_path).xpub(
             bytes.fromhex("02575483")))
     self.assertEqual(
         starting_vpub,
         "Vpub5mvQbnmqKfpPjWfAZEw5Xjdr6UjnjyZEirzrhNMSuKjL8Qfd3nqLBkrBrVXNeMgKCjPXbyLnSCn6qcD8fHQCkNnNLnkpQtY3sh4MHmywvbe",
     )
     secret_path = "m/920870093/318569592/821713943/1914815254/1398142787/9"  # randomly generated
     have = blind_xpub(
         starting_xpub=starting_vpub,
         starting_path=starting_path,
         secret_path=secret_path,
     )
     want = {
         "blinded_full_path":
         "m/48h/1h/0h/2h/920870093/318569592/821713943/1914815254/1398142787/9",
         "blinded_child_xpub":
         "Vpub5xWzaq6Ya7K9Y2UeFyL8SfHhydnp5FZDskm9mkAfUppkXVJDKrnLxU2Ezd55k8RSzSf4YETJj982NJQGCSzKJxUa6oQmbe1HWTRavCRzzxj",
     }
     self.assertEqual(have, want)
예제 #24
0
 def test_prv_pub(self):
     tests = [
         {
             "seed": bytes.fromhex("000102030405060708090a0b0c0d0e0f"),
             "paths": [
                 [
                     "m",
                     "xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8",
                     "xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi",
                 ],
                 [
                     "m/0'",
                     "xpub68Gmy5EdvgibQVfPdqkBBCHxA5htiqg55crXYuXoQRKfDBFA1WEjWgP6LHhwBZeNK1VTsfTFUHCdrfp1bgwQ9xv5ski8PX9rL2dZXvgGDnw",
                     "xprv9uHRZZhk6KAJC1avXpDAp4MDc3sQKNxDiPvvkX8Br5ngLNv1TxvUxt4cV1rGL5hj6KCesnDYUhd7oWgT11eZG7XnxHrnYeSvkzY7d2bhkJ7",
                 ],
                 [
                     "m/0'/1",
                     "xpub6ASuArnXKPbfEwhqN6e3mwBcDTgzisQN1wXN9BJcM47sSikHjJf3UFHKkNAWbWMiGj7Wf5uMash7SyYq527Hqck2AxYysAA7xmALppuCkwQ",
                     "xprv9wTYmMFdV23N2TdNG573QoEsfRrWKQgWeibmLntzniatZvR9BmLnvSxqu53Kw1UmYPxLgboyZQaXwTCg8MSY3H2EU4pWcQDnRnrVA1xe8fs",
                 ],
                 [
                     "m/0'/1/2'",
                     "xpub6D4BDPcP2GT577Vvch3R8wDkScZWzQzMMUm3PWbmWvVJrZwQY4VUNgqFJPMM3No2dFDFGTsxxpG5uJh7n7epu4trkrX7x7DogT5Uv6fcLW5",
                     "xprv9z4pot5VBttmtdRTWfWQmoH1taj2axGVzFqSb8C9xaxKymcFzXBDptWmT7FwuEzG3ryjH4ktypQSAewRiNMjANTtpgP4mLTj34bhnZX7UiM",
                 ],
                 [
                     "m/0'/1/2'/2",
                     "xpub6FHa3pjLCk84BayeJxFW2SP4XRrFd1JYnxeLeU8EqN3vDfZmbqBqaGJAyiLjTAwm6ZLRQUMv1ZACTj37sR62cfN7fe5JnJ7dh8zL4fiyLHV",
                     "xprvA2JDeKCSNNZky6uBCviVfJSKyQ1mDYahRjijr5idH2WwLsEd4Hsb2Tyh8RfQMuPh7f7RtyzTtdrbdqqsunu5Mm3wDvUAKRHSC34sJ7in334",
                 ],
                 [
                     "m/0'/1/2'/2/1000000000",
                     "xpub6H1LXWLaKsWFhvm6RVpEL9P4KfRZSW7abD2ttkWP3SSQvnyA8FSVqNTEcYFgJS2UaFcxupHiYkro49S8yGasTvXEYBVPamhGW6cFJodrTHy",
                     "xprvA41z7zogVVwxVSgdKUHDy1SKmdb533PjDz7J6N6mV6uS3ze1ai8FHa8kmHScGpWmj4WggLyQjgPie1rFSruoUihUZREPSL39UNdE3BBDu76",
                 ],
             ],
         },
         {
             "seed": bytes.fromhex(
                 "fffcf9f6f3f0edeae7e4e1dedbd8d5d2cfccc9c6c3c0bdbab7b4b1aeaba8a5a29f9c999693908d8a8784817e7b7875726f6c696663605d5a5754514e4b484542"
             ),
             "paths": [
                 [
                     "m",
                     "xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB",
                     "xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U",
                 ],
                 [
                     "m/0",
                     "xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH",
                     "xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt",
                 ],
                 [
                     "m/0/2147483647'",
                     "xpub6ASAVgeehLbnwdqV6UKMHVzgqAG8Gr6riv3Fxxpj8ksbH9ebxaEyBLZ85ySDhKiLDBrQSARLq1uNRts8RuJiHjaDMBU4Zn9h8LZNnBC5y4a",
                     "xprv9wSp6B7kry3Vj9m1zSnLvN3xH8RdsPP1Mh7fAaR7aRLcQMKTR2vidYEeEg2mUCTAwCd6vnxVrcjfy2kRgVsFawNzmjuHc2YmYRmagcEPdU9",
                 ],
                 [
                     "m/0/2147483647'/1",
                     "xpub6DF8uhdarytz3FWdA8TvFSvvAh8dP3283MY7p2V4SeE2wyWmG5mg5EwVvmdMVCQcoNJxGoWaU9DCWh89LojfZ537wTfunKau47EL2dhHKon",
                     "xprv9zFnWC6h2cLgpmSA46vutJzBcfJ8yaJGg8cX1e5StJh45BBciYTRXSd25UEPVuesF9yog62tGAQtHjXajPPdbRCHuWS6T8XA2ECKADdw4Ef",
                 ],
                 [
                     "m/0/2147483647'/1/2147483646'",
                     "xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL",
                     "xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc",
                 ],
                 [
                     "m/0/2147483647'/1/2147483646'/2",
                     "xpub6FnCn6nSzZAw5Tw7cgR9bi15UV96gLZhjDstkXXxvCLsUXBGXPdSnLFbdpq8p9HmGsApME5hQTZ3emM2rnY5agb9rXpVGyy3bdW6EEgAtqt",
                     "xprvA2nrNbFZABcdryreWet9Ea4LvTJcGsqrMzxHx98MMrotbir7yrKCEXw7nadnHM8Dq38EGfSh6dqA9QWTyefMLEcBYJUuekgW4BYPJcr9E7j",
                 ],
             ],
         },
         {
             "seed": bytes.fromhex(
                 "4b381541583be4423346c643850da4b320e46a87ae3d2a4e6da11eba819cd4acba45d239319ac14f863b8d5ab5a0d0c64d2e8a1e7d1457df2e5a3c51c73235be"
             ),
             "paths": [
                 [
                     "m",
                     "xpub661MyMwAqRbcEZVB4dScxMAdx6d4nFc9nvyvH3v4gJL378CSRZiYmhRoP7mBy6gSPSCYk6SzXPTf3ND1cZAceL7SfJ1Z3GC8vBgp2epUt13",
                     "xprv9s21ZrQH143K25QhxbucbDDuQ4naNntJRi4KUfWT7xo4EKsHt2QJDu7KXp1A3u7Bi1j8ph3EGsZ9Xvz9dGuVrtHHs7pXeTzjuxBrCmmhgC6",
                 ],
                 [
                     "m/0'",
                     "xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y",
                     "xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L",
                 ],
             ],
         },
     ]
     for test in tests:
         seed = test["seed"]
         for path, xpub, xprv in test["paths"]:
             # test from seed
             private_key = HDPrivateKey.from_seed(seed).traverse(path)
             public_key = HDPublicKey.parse(xpub)
             self.assertEqual(private_key.xprv(), xprv)
             self.assertEqual(private_key.xpub(), public_key.xpub())
             self.assertEqual(private_key.address(), public_key.address())
예제 #25
0
    def test_sweep_1of2_p2sh(self):

        # This test will produce a validly signed TX for the 1-of-2 p2sh using either key, which will result in a different TX ID
        # d8172be9981a4f57e6e4ebe0f4785f5f2035aee40ffbb2d6f1200810a879d490 is the one that was broadcast to the testnet blockchain:
        # https://blockstream.info/testnet/tx/d8172be9981a4f57e6e4ebe0f4785f5f2035aee40ffbb2d6f1200810a879d490

        kwargs = {
            "public_key_records": [
                # action x12
                [
                    "e0c595c5",
                    "tpubDBnspiLZfrq1V7j1iuMxGiPsuHyy6e4QBnADwRrbH89AcnsUEMfWiAYXmSbMuNFsrMdnbQRDGGSM1AFGL6zUWNVSmwRavoJzdQBbZKLgLgd",
                    "m/45'/0",
                ],
                # agent x12
                [
                    "838f3ff9",
                    "tpubDAKJicb9Tkw34PFLEBUcbnH99twN3augmg7oYHHx9Aa9iodXmA4wtGEJr8h2XjJYqn2j1v5qHLjpWEe8aPihmC6jmsgomsuc9Zeh4ZushNk",
                    "m/45'/0",
                ],
            ],
            "input_dicts": [
                {
                    "quorum_m": 1,
                    "path_dict": {
                        # xfp: root_path
                        "e0c595c5": "m/45'/0/0/0",
                        "838f3ff9": "m/45'/0/0/0",
                    },
                    "prev_tx_dict": {
                        "hex":
                        "02000000000101380bff9db676d159ad34849079c77e0d5c1df9c841b6a6640cba9bfc15077eea0100000000feffffff02008312000000000017a914d96bb9c5888f473dbd077d77009fb49ba2fda24287611c92a00100000017a9148722f07fbcf0fc506ea4ba9daa811d11396bbcfd870247304402202fe3c2f18e1486407bf0baabd2b3376102f0844a754d8e2fb8de71b39b3f76c702200c1fe8f7f9ef5165929ed51bf754edd7dd3e591921979cf5b891c841a1fd19d80121037c8fe1fa1ae4dfff522c532917c73c4884469e3b6a284e9a039ec612dca78eefd29c1e00",
                        "hash_hex":
                        "4412d2a7664d01bb784a0a359e9aacf160ee436067c6a42dca355da4817ca7da",
                        "output_idx": 0,
                        "output_sats": 1213184,
                    },
                },
            ],
            "output_dicts": [
                {
                    "sats": 999500,
                    "address": "mkHS9ne12qx9pS9VojpwU5xtRd4T7X7ZUt",
                },
            ],
            "fee_sats":
            213684,
        }

        expected_unsigned_psbt_b64 = "cHNidP8BAFUBAAAAAdqnfIGkXTXKLaTGZ2BD7mDxrJqeNQpKeLsBTWan0hJEAAAAAAD/////AUxADwAAAAAAGXapFDRKD0jKFQ7CuQOBdmC5tosTpnAmiKwAAAAATwEENYfPAheO+c4AAAAALjD5unTcevzcKaWlkg/+xoU+FD5bJ+iltDa2WE1bTngCfSOd3kkScA1e4OGM3MJ/Oqg+nxEHlwuV7YBCuoT745YMg48/+S0AAIAAAAAATwEENYfPAuBIVlUAAAAAHnpWPWgBnfWu2tzv9Ujws27ps0hOBHMQbZlpQ6c5m4MDN4/ffnc3ejfVlqkEgG7tlPX4aTS92pfU5LAvGDQQKHoM4MWVxS0AAIAAAAAAAAEA4AIAAAAAAQE4C/+dtnbRWa00hJB5x34NXB35yEG2pmQMupv8FQd+6gEAAAAA/v///wIAgxIAAAAAABepFNlrucWIj0c9vQd9dwCftJui/aJCh2EckqABAAAAF6kUhyLwf7zw/FBupLqdqoEdETlrvP2HAkcwRAIgL+PC8Y4UhkB78Lqr0rM3YQLwhEp1TY4vuN5xs5s/dscCIAwf6Pf571Flkp7VG/dU7dfdPlkZIZec9biRyEGh/RnYASEDfI/h+hrk3/9SLFMpF8c8SIRGnjtqKE6aA57GEtynju/SnB4AAQRHUSECE59xZ/F9qU0k32/ihozbXtq9DP+Dq0h8lC6i8HVw2fAhAjy3HGmZkHaMAY3xejZsTrdL3hafKeiEJhwQyXPsJq06Uq4iBgITn3Fn8X2pTSTfb+KGjNte2r0M/4OrSHyULqLwdXDZ8BSDjz/5LQAAgAAAAAAAAAAAAAAAACIGAjy3HGmZkHaMAY3xejZsTrdL3hafKeiEJhwQyXPsJq06FODFlcUtAACAAAAAAAAAAAAAAAAAAAA="
        tests = (
            # (seed_word repeated x12, signed_tx_hash_hex),
            (
                # this is the one we broadcasted
                "action ",
                "d8172be9981a4f57e6e4ebe0f4785f5f2035aee40ffbb2d6f1200810a879d490",
            ),
            (
                "agent ",
                "c38dcee54f40193d668c8911eeba7ec20f570fdbdb31fbe4351f66d7005c7bfb",
            ),
        )

        # Now we prove we can sign this with either key
        for seed_word, signed_tx_hash_hex in tests:
            psbt_obj = create_multisig_psbt(**kwargs, script_type="p2sh")
            self.assertEqual(len(psbt_obj.hd_pubs), 2)
            self.assertEqual(psbt_obj.serialize_base64(),
                             expected_unsigned_psbt_b64)

            hdpriv = HDPrivateKey.from_mnemonic(seed_word * 12,
                                                network="testnet")

            root_path_to_use = None
            for cnt, psbt_in in enumerate(psbt_obj.psbt_ins):

                self.assertEqual(psbt_in.redeem_script.get_quorum(), (1, 2))

                # For this TX there is only one psbt_in (1 input)
                for child_pubkey in psbt_in.redeem_script.signing_pubkeys():
                    named_pubkey = psbt_in.named_pubs[child_pubkey]
                    if (named_pubkey.root_fingerprint.hex() ==
                            hdpriv.fingerprint().hex()):
                        root_path_to_use = named_pubkey.root_path

                # In this example, the path is the same regardless of which key we sign with:
                self.assertEqual(root_path_to_use, "m/45'/0/0/0")

            private_keys = [hdpriv.traverse(root_path_to_use).private_key]

            self.assertTrue(
                psbt_obj.sign_with_private_keys(private_keys=private_keys))

            psbt_obj.finalize()

            self.assertEqual(psbt_obj.final_tx().hash().hex(),
                             signed_tx_hash_hex)

        # add in smeonwhat random test to confirm this bug is fixed
        # https://github.com/buidl-bitcoin/buidl-python/pull/129
        # this should NOT raise an exception
        kwargs["output_dicts"][0][
            "address"] = "tb1q9hj5j7mh9f7t6cwdvz34nj6pyzva5ftj2ecarcdqph5wc3n49hyqchh3cg"
        create_multisig_psbt(**kwargs, script_type="p2sh")
예제 #26
0
    def test_full_wallet_functionality(self):
        """
        Create a wallet, fund it, and spend it (validating on all devices first).
        Do this both with and without change.
        """

        child_path = "m/0/0"
        base_path = "m/45'/0"
        xpubs = [
            # Verified against electrum 2021-10
            "tpubDAp5uHrhyCRnoQdMmF9xFU78bev55TKux39Viwn8VCnWywjTCBB3TFSA1i5i4scj8ZkD8RR37EZMPyqBwk4wD4VNpxyowgk2rQ7i8AdVauN",
            "tpubDBxHJMmnouRSes7kweA5wUp2Jm5F1xEPCG3fjYRnBSiQM7hjsp9gp8Wsag16A8yrpMgJkPX8iddXmwuxKaUnAWfdcGcuXbiATYeGXCKkEXK",
            "tpubDANTttKyukGYwXvhTmdCa6p1nHmsbFef2yRXb9b2JTvLGoekYk6ZG9oHXGHYJjTn5g3DijvQrZ4rH6EosYqpVngKiUx2jvWdPmq6ZSDr3ZE",
        ]

        xfps = ["9a6a2580", "8c7e2500", "0586b1ce"]

        pubkey_records = []
        for cnt, seed_word in enumerate(("bacon", "flag", "gas")):
            seed_phrase = f"{seed_word} " * 24
            hd_obj = HDPrivateKey.from_mnemonic(seed_phrase, network="testnet")

            # takes the form xfp, child_xpub, base_path
            pubkey_record = [
                hd_obj.fingerprint().hex(),
                hd_obj.traverse(base_path).xpub(),
                base_path,
            ]
            pubkey_records.append(pubkey_record)

            self.assertEqual(pubkey_record, [xfps[cnt], xpubs[cnt], base_path])

        pubkey_hexes = [
            HDPublicKey.parse(tpub).traverse(child_path).sec().hex()
            for tpub in xpubs
        ]

        rs_obj = RedeemScript.create_p2sh_multisig(quorum_m=2,
                                                   pubkey_hexes=pubkey_hexes,
                                                   sort_keys=True)

        deposit_address = rs_obj.address(network="testnet")
        # We funded this testnet address in 2021-10:
        self.assertEqual(deposit_address,
                         "2MzLzwPgo6ZTyPzkguNWKfFHgCXdZSJu9tL")

        input_dicts = [{
            "quorum_m": 2,
            "path_dict": {
                # xfp: root_path
                "9a6a2580": "m/45'/0/0/0",
                "8c7e2500": "m/45'/0/0/0",
                "0586b1ce": "m/45'/0/0/0",
            },
            "prev_tx_dict": {
                # This was the funding transaction send to 2MzLzwPgo6ZTyPzkguNWKfFHgCXdZSJu9tL
                "hex":
                "02000000000101045448b2faafc53a7586bd6a746a598d9a1cfc3dd548d8f8afd12c11c53c16740000000000feffffff02809a4b000000000017a914372becc78d20f8acb94059fa8d1e3219b67c1d9387a08601000000000017a9144de07b9788bc64143292f9610443a7f81cc5fc308702473044022059f2036e5b6da03e592863664082f957472dbb89330895d23ac66f94e3819f63022050d31d401cd86fe797c31ba7fb2a2cf9e12356bba8388897f4f522bc4999224f0121029c71554e111bb41463ad288b4808effef8136755da80d42aa2bcea12ed8a99bbba0e2000",
                "hash_hex":
                "838504ad934d97915d9ab58b0ece6900ed123abf3a51936563ba9d67b0e41fa4",
                "output_idx": 1,
                "output_sats": 100_000,
            },
        }]
예제 #27
0
    def test_create_p2sh_multisig(self):
        BASE_PATH = "m/45h/0"

        # Insecure testing BIP39 mnemonics
        hdpriv_root_1 = HDPrivateKey.from_mnemonic("action " * 12, network="testnet")
        hdpriv_root_2 = HDPrivateKey.from_mnemonic("agent " * 12, network="testnet")

        child_xpriv_1 = hdpriv_root_1.traverse(BASE_PATH)
        child_xpriv_2 = hdpriv_root_2.traverse(BASE_PATH)

        # xpubs from electrum:
        self.assertEqual(
            child_xpriv_1.xpub(),
            "tpubDBnspiLZfrq1V7j1iuMxGiPsuHyy6e4QBnADwRrbH89AcnsUEMfWiAYXmSbMuNFsrMdnbQRDGGSM1AFGL6zUWNVSmwRavoJzdQBbZKLgLgd",
        )
        self.assertEqual(
            child_xpriv_2.xpub(),
            "tpubDAKJicb9Tkw34PFLEBUcbnH99twN3augmg7oYHHx9Aa9iodXmA4wtGEJr8h2XjJYqn2j1v5qHLjpWEe8aPihmC6jmsgomsuc9Zeh4ZushNk",
        )

        # addresses from electrum
        expected_receive_addrs = [
            "2ND4qfpdHyeXJboAUkKZqJsyiKyXvHRKhbi",
            "2N1T1HAC9TnNvhEDG4oDEuKNnmdsXs2HNwq",
            "2N7fdTu5JkQihTpo2mZ3QYudrfU2xMdgh3M",
        ]
        expected_change_addrs = [
            "2MzQhXqN93igSKGW9CMvkpZ9TYowWgiNEF8",
            "2Msk2ckm2Ee4kJnzQuyQtpYDpMZrXf5XtKD",
            "2N5wXpJBKtAKSCiAZLwdh2sPwt5k2HBGtGC",
        ]

        # validate receive addrs match electrum
        for cnt, expected_receive_addr in enumerate(expected_receive_addrs):
            redeem_script = RedeemScript.create_p2sh_multisig(
                quorum_m=1,
                pubkey_hexes=[
                    child_xpriv_1.traverse(f"m/0/{cnt}").pub.sec().hex(),
                    child_xpriv_2.traverse(f"m/0/{cnt}").pub.sec().hex(),
                ],
                # Electrum sorts child pubkeys lexicographically:
                sort_keys=True,
            )
            derived_addr = redeem_script.address(network="testnet")
            self.assertEqual(derived_addr, expected_receive_addr)

        # validate change addrs match electrum
        for cnt, expected_change_addr in enumerate(expected_change_addrs):
            redeem_script = RedeemScript.create_p2sh_multisig(
                quorum_m=1,
                pubkey_hexes=[
                    child_xpriv_1.traverse(f"m/1/{cnt}").pub.sec().hex(),
                    child_xpriv_2.traverse(f"m/1/{cnt}").pub.sec().hex(),
                ],
                # Electrum sorts lexicographically:
                sort_keys=True,
            )
            derived_addr = redeem_script.address(network="testnet")
            self.assertEqual(derived_addr, expected_change_addr)

        # For recovery only
        misordered_pubkeys_addr = "2NFzScjC9jaMbo5ST4M1WVeeWgSaVT7xS1W"
        pubkey_hexes = [
            child_xpriv_1.traverse("m/0/0").pub.sec().hex(),
            child_xpriv_2.traverse("m/0/0").pub.sec().hex(),
        ]
        misordered_redeem_script = RedeemScript.create_p2sh_multisig(
            quorum_m=1,
            # Note that the order here is intentional explicit and we're NOT sorting lexicographically (reverse=True)
            pubkey_hexes=sorted(pubkey_hexes, reverse=True),
            sort_keys=False,
            expected_addr=misordered_pubkeys_addr,
            expected_addr_network="testnet",
        )
        self.assertEqual(
            misordered_redeem_script.address("testnet"), misordered_pubkeys_addr
        )

        with self.assertRaises(ValueError):
            fake_addr = "tb1qw508d6qejxtdg4y5r3zarvary0c5xw7kxpjzsx"
            RedeemScript.create_p2sh_multisig(
                quorum_m=1,
                pubkey_hexes=pubkey_hexes,
                sort_keys=False,
                expected_addr=fake_addr,
                expected_addr_network="testnet",
            )
예제 #28
0
    def process_psbt(self, sign_tx=True):
        # Clear any previous submission in case of errors
        self.psbtDecodedLabel.setText("")
        self.psbtDecodedROEdit.clear()
        self.psbtDecodedROEdit.setHidden(True)

        self.psbtSignedLabel.setText("")
        self.psbtSignedROEdit.clear()
        self.psbtSignedROEdit.setHidden(True)

        self.qrButton.setHidden(True)
        self.qrButton.setText("")
        # TODO: why setText and not hide?

        if self.infernetwork_button.isChecked():
            PARSE_WITH_TESTNET = None
        elif self.mainnet_button.isChecked():
            PARSE_WITH_TESTNET = False
        elif self.testnet_button.isChecked():
            PARSE_WITH_TESTNET = True
        else:
            # This shouldn't be possible
            raise Exception(
                "Invalid Network Selection: No Radio Button Chosen")

        psbt_str = _clean_submisission(self.psbtEdit.toPlainText())

        if not psbt_str:
            return _msgbox_err(
                main_text="No PSBT Supplied",
                informative_text="Enter a PSBT to decode and/or sign.",
            )

        try:
            psbt_obj = PSBT.parse_base64(b64=psbt_str,
                                         testnet=PARSE_WITH_TESTNET)
        except Exception as e:
            if type(e) is ValueError and str(e) == "Mainnet/Testnet mixing":
                # TODO: less hackey way to catch this error?
                return _msgbox_err(
                    main_text="PSBT Network Error",
                    informative_text=
                    "The network you selected doesn't match the PSBT.",
                    detailed_text=str(e),
                )
            else:
                return _msgbox_err(
                    main_text="PSBT Parse Error",
                    informative_text="Are you sure that's a valid PSBT?",
                    detailed_text=str(e),
                )

        # Parse TX
        self.TX_FEE_SATS = psbt_obj.tx_obj.fee()
        self.IS_TESTNET = psbt_obj.tx_obj.testnet

        # Validate multisig transaction
        # TODO: abstract some of this into buidl library?
        # Below is confusing because we perform both validation and coordinate signing.

        # This tool only supports a TX with the following constraints:
        #   We sign ALL inputs and they have the same multisig wallet (quorum + pubkeys)
        #   There can only be 1 output (sweep transaction) or 2 outputs (spend + change).
        #   If there is change, we validate it has the same multisig wallet as the inputs we sign.

        # Gather TX info and validate
        inputs_desc = []
        for cnt, psbt_in in enumerate(psbt_obj.psbt_ins):
            psbt_in.validate()  # redundant but explicit

            if type(psbt_in.witness_script) != WitnessScript:
                return _msgbox_err(
                    main_text="Input #{cnt} does not contain a witness script",
                    informative_text=
                    "This tool can only sign p2wsh transactions.",
                )

            # Determine quroum_m (and that it hasn't changed between inputs)
            try:
                quorum_m = OP_CODE_NAMES[
                    psbt_in.witness_script.commands[0]].split("OP_")[1]
            except Exception:
                return _msgbox_err(
                    main_text="Non-p2wsh Input",
                    informative_text=
                    f"Witness script for input #{cnt} is not p2wsh",
                    detailed_text=f"PSBT Input:\n {psbt_in}",
                )

            # for calculating msig fingerprint
            root_xfp_hexes = []
            for _, details in psbt_in.named_pubs.items():
                root_xfp_hexes.append(details.root_fingerprint.hex())

            input_desc = {
                "quorum":
                f"{quorum_m}-of-{len(root_xfp_hexes)}",
                "root_xfp_hexes":
                root_xfp_hexes,
                "prev_txhash":
                psbt_in.tx_in.prev_tx.hex(),
                "prev_idx":
                psbt_in.tx_in.prev_index,
                "n_sequence":
                psbt_in.tx_in.sequence,
                "sats":
                psbt_in.tx_in.value(),
                # TODO: would be possible for transaction to be p2sh-wrapped p2wsh (can we tell?)
                "addr":
                psbt_in.witness_script.address(testnet=self.IS_TESTNET),
                # "p2sh_addr": psbt_in.witness_script.p2sh_address(testnet=self.IS_TESTNET),
                "witness_script":
                str(psbt_in.witness_script),
                "msig_digest":
                _calculate_msig_digest(quorum_m=quorum_m,
                                       root_xfp_hexes=root_xfp_hexes),
            }
            inputs_desc.append(input_desc)

        if not all(x["msig_digest"] == inputs_desc[0]["msig_digest"]
                   for x in inputs_desc):
            return _msgbox_err(
                main_text="Inputs Contain Conflicting Wallet Quorums",
                informative_text=
                "This transaction is not inherently bad, but transactions of this type are only possible for experts. Please construct 1 or more transactions with one input instead.",
                detailed_text=f"For developers: {inputs_desc}",
            )

        TOTAL_INPUT_SATS = sum([x["sats"] for x in inputs_desc])

        # This too only supports TXs with 1-2 outputs (sweep TX OR spend+change TX):
        if len(psbt_obj.psbt_outs) > 2:
            return _msgbox_err(
                main_text="Too Many Outputs",
                informative_text=
                f"Multiwallet does not support batching, and your transaction has {len(psbt_obj.psbt_outs)} outputs.",
                detailed_text=
                "Please construct a transaction with <= 2 outputs.",
            )

        spend_addr, output_spend_sats = "", 0
        outputs_desc = []
        for cnt, psbt_out in enumerate(psbt_obj.psbt_outs):
            psbt_out.validate()  # redundant but explicit

            output_desc = {
                "sats":
                psbt_out.tx_out.amount,
                "addr_type":
                psbt_out.tx_out.script_pubkey.__class__.__name__.rstrip(
                    "ScriptPubKey"),
            }

            if psbt_out.witness_script:
                output_desc["addr"] = psbt_out.witness_script.address(
                    testnet=self.IS_TESTNET)
            else:
                output_desc["addr"] = psbt_out.tx_out.script_pubkey.address(
                    testnet=self.IS_TESTNET)

            if psbt_out.named_pubs:
                # Validate below that this is correct and abort otherwise
                output_desc["is_change"] = True

                root_xfp_hexes = []  # for calculating msig fingerprint
                for _, details in psbt_out.named_pubs.items():
                    root_xfp_hexes.append(details.root_fingerprint.hex())

                # Determine quroum_m (and that it hasn't changed between inputs)
                try:
                    quorum_m = OP_CODE_NAMES[
                        psbt_out.witness_script.commands[0]].split("OP_")[1]
                except Exception:
                    return _msgbox_err(
                        main_text="Non-p2wsh Change Output",
                        informative_text=
                        "This transaction may be trying to trick you into sending change to a third party.",
                        detailed_text=
                        f"Witness script for output #{cnt} is not p2wsh: {psbt_out}",
                    )

                output_msig_digest = _calculate_msig_digest(
                    quorum_m=quorum_m, root_xfp_hexes=root_xfp_hexes)
                if output_msig_digest != inputs_desc[0]["msig_digest"]:
                    return _msgbox_err(
                        main_text="Invalid Change Detected",
                        informative_text=
                        f"Output #{cnt} is claiming to be change but has different multisig wallet(s)! Do a sweep transaction (1-output) if you want this wallet to cosign.",
                        detailed_text=f"For developers: {outputs_desc}",
                    )
            else:
                output_desc["is_change"] = False
                spend_addr = output_desc["addr"]
                output_spend_sats = output_desc["sats"]

            outputs_desc.append(output_desc)

        # Sanity check
        if len(outputs_desc) != len(psbt_obj.psbt_outs):
            return _msgbox_err(
                main_text="PSBT Parse Error",
                informative_text=
                f"{len(outputs_desc)} outputs in TX summary doesn't match {len(psbt_obj.psbt_outs)} outputs in PSBT.",
            )

        # Confirm if 2 outputs we only have 1 change and 1 spend (can't be 2 changes or 2 spends)
        if len(outputs_desc) == 2:
            if all(x["is_change"] == outputs_desc[0]["is_change"]
                   for x in outputs_desc):
                return _msgbox_err(
                    main_text="Change-Only Transaction with 2 Outputs",
                    informative_text=
                    "Transactions with 2 outputs that are BOTH change are not allowed, as only experts can properly validate them. Please construct a transaction with fewer outputs.",
                    detailed_text=f"For developers: {outputs_desc}",
                )

        TX_SUMMARY = " ".join([
            inputs_desc[0]["quorum"],
            "PSBT sends",
            _format_satoshis(output_spend_sats, in_btc=self.UNITS == "btc"),
            "to",
            spend_addr,
            "with a fee of",
            _format_satoshis(self.TX_FEE_SATS, in_btc=self.UNITS == "btc"),
            f"({round(self.TX_FEE_SATS / TOTAL_INPUT_SATS * 100, 2)}% of spend)",
        ])
        self.psbtDecodedLabel.setText(
            f"<b>Decoded Transaction Summary</b> - {'Testnet' if self.IS_TESTNET else 'Mainnet'}"
        )
        self.psbtDecodedROEdit.setHidden(False)
        self.psbtDecodedROEdit.appendPlainText(TX_SUMMARY)

        # TODO: surface this to user somehow
        to_print = []
        to_print.append("DETAILED VIEW")
        to_print.append(f"TXID: {psbt_obj.tx_obj.id()}")
        to_print.append(
            f"Network: {'Testnet' if psbt_obj.tx_obj.testnet else 'Mainnet'}")
        to_print.append("-" * 80)
        to_print.append(f"{len(inputs_desc)} Input(s):")
        for cnt, input_desc in enumerate(inputs_desc):
            to_print.append(f"  input #{cnt}")
            for k in input_desc:
                to_print.append(f"    {k}: {input_desc[k]}")
        to_print.append("-" * 80)
        to_print.append(f"{len(outputs_desc)} Output(s):")
        for cnt, output_desc in enumerate(outputs_desc):
            to_print.append(f"  output #{cnt}")
            for k in output_desc:
                to_print.append(f"    {k}: {output_desc[k]}")
        print("\n".join(to_print))

        seed_phrase = _clean_submisission(self.fullSeedEdit.toPlainText())

        if not sign_tx:
            return

        if not seed_phrase:
            return _msgbox_err(
                main_text="No Seed Phrase Supplied",
                informative_text="Cannot sign transaction without seed phrase",
            )

        seed_phrase_num = len(seed_phrase.split())
        if seed_phrase_num not in (12, 15, 18, 21, 24):
            return _msgbox_err(
                main_text="Enter 24 word seed-phrase",
                informative_text=f"You entered {seed_phrase_num} words",
            )

        try:
            hd_priv = HDPrivateKey.from_mnemonic(seed_phrase,
                                                 testnet=self.IS_TESTNET)
        except Exception as e:
            return _msgbox_err(
                main_text="Invalid BIP39 Seed Phrase",
                informative_text="Transaction NOT signed",
                detailed_text=str(e),
            )

        # Derive list of child private keys we'll use to sign the TX
        root_paths = set()
        for cnt, psbt_in in enumerate(psbt_obj.psbt_ins):
            # Redundant safety check:
            bad_txhash = inputs_desc[cnt][
                "prev_txhash"] != psbt_in.tx_in.prev_tx.hex()
            bad_idx = inputs_desc[cnt]["prev_idx"] != psbt_in.tx_in.prev_index
            if bad_txhash or bad_idx:
                return _msgbox_err(
                    main_text="PSBT Parse Error",
                    informative_text="Transaction NOT signed",
                    detailed_text=
                    f"For developers: Input #{cnt} prev_txhash or prev_idx mismatch: \n{PSBT.serialize_base64()}",
                )

            for _, details in psbt_in.named_pubs.items():
                if details.root_fingerprint.hex() == hd_priv.fingerprint().hex(
                ):
                    root_paths.add(details.root_path)

        if not root_paths:
            return _msgbox_err(
                main_text="Wrong Seed",
                informative_text=
                "Seed supplied does not correspond to transaction input(s). Does it belong to another wallet?",
            )

        private_keys = [
            hd_priv.traverse(root_path).private_key for root_path in root_paths
        ]

        try:
            if psbt_obj.sign_with_private_keys(private_keys) is True:
                self.psbtSignedLabel.setText("<b>Signed PSBT to Broadcast</b>")
                self.psbtSignedROEdit.setHidden(False)
                self.psbtSignedROEdit.appendPlainText(
                    psbt_obj.serialize_base64())

                self.qrButton.setHidden(False)
                self.qrButton.setText("QR")
                self.qrButton.setIcon(create_qr_icon())
            else:
                return _msgbox_err(
                    main_text="Transaction Not Signed",
                    informative_text="Couldn't find private key to sign with",
                    detailed_text=
                    "This should've been checked earlier and should not be possible!",
                )
        except Exception as e:
            return _msgbox_err(
                main_text="Transaction Not Signed",
                informative_text="There was an error during signing.",
                detailed_text=f"For developers: {e}",
            )
예제 #29
0
 def test_get_address(self):
     seedphrase = b"[email protected] Jimmy Song"
     mainnet_priv = HDPrivateKey.from_seed(seedphrase)
     testnet_priv = HDPrivateKey.from_seed(seedphrase, network="testnet")
     signet_priv = HDPrivateKey.from_seed(seedphrase, network="signet")
     tests = [
         [
             mainnet_priv.get_p2pkh_receiving_address,
             0,
             1,
             "13pS51XfGTVhxbtrGKVSvwf36r96tLUu1K",
         ],
         [
             testnet_priv.get_p2pkh_change_address,
             1,
             0,
             "n4EiCRsEEPaJ73HWA6zYEaHwo45BrP5MHb",
         ],
         [
             signet_priv.get_p2pkh_change_address,
             1,
             0,
             "n4EiCRsEEPaJ73HWA6zYEaHwo45BrP5MHb",
         ],
         [
             testnet_priv.get_p2sh_p2wpkh_receiving_address,
             0,
             2,
             "2NGKoo11UopXBWLC7qqj9BjgH9F3gvLdapz",
         ],
         [
             signet_priv.get_p2sh_p2wpkh_receiving_address,
             0,
             2,
             "2NGKoo11UopXBWLC7qqj9BjgH9F3gvLdapz",
         ],
         [
             mainnet_priv.get_p2sh_p2wpkh_change_address,
             0,
             0,
             "38hYFPLMTykhURpCQTxkdDcpQKyieiYiU7",
         ],
         [
             mainnet_priv.get_p2wpkh_receiving_address,
             2,
             0,
             "bc1qzeln78k9sghatd3uwnks8jek46qe23dw99zu9j",
         ],
         [
             testnet_priv.get_p2wpkh_change_address,
             1,
             1,
             "tb1qecjwdw5uwwdfezzntec7m4kc8zkyjcamlz7dv9",
         ],
         [
             signet_priv.get_p2wpkh_change_address,
             1,
             1,
             "tb1qecjwdw5uwwdfezzntec7m4kc8zkyjcamlz7dv9",
         ],
     ]
     for function, account, address, want in tests:
         got = function(account, address)
         self.assertEqual(got, want)
예제 #30
0
 def test_from_mnemonic(self):
     tests = [
         [
             "00000000000000000000000000000000",
             "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about",
             "c55257c360c07c72029aebc1b53c05ed0362ada38ead3e3e9efa3708e53495531f09a6987599d18264c1e1c92f2cf141630c7a3c4ab7c81b2f001698e7463b04",
             "xprv9s21ZrQH143K3h3fDYiay8mocZ3afhfULfb5GX8kCBdno77K4HiA15Tg23wpbeF1pLfs1c5SPmYHrEpTuuRhxMwvKDwqdKiGJS9XFKzUsAF",
         ],
         [
             "7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f",
             "legal winner thank year wave sausage worth useful legal winner thank yellow",
             "2e8905819b8723fe2c1d161860e5ee1830318dbf49a83bd451cfb8440c28bd6fa457fe1296106559a3c80937a1c1069be3a3a5bd381ee6260e8d9739fce1f607",
             "xprv9s21ZrQH143K2gA81bYFHqU68xz1cX2APaSq5tt6MFSLeXnCKV1RVUJt9FWNTbrrryem4ZckN8k4Ls1H6nwdvDTvnV7zEXs2HgPezuVccsq",
         ],
         [
             "80808080808080808080808080808080",
             "letter advice cage absurd amount doctor acoustic avoid letter advice cage above",
             "d71de856f81a8acc65e6fc851a38d4d7ec216fd0796d0a6827a3ad6ed5511a30fa280f12eb2e47ed2ac03b5c462a0358d18d69fe4f985ec81778c1b370b652a8",
             "xprv9s21ZrQH143K2shfP28KM3nr5Ap1SXjz8gc2rAqqMEynmjt6o1qboCDpxckqXavCwdnYds6yBHZGKHv7ef2eTXy461PXUjBFQg6PrwY4Gzq",
         ],
         [
             "ffffffffffffffffffffffffffffffff",
             "zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo wrong",
             "ac27495480225222079d7be181583751e86f571027b0497b5b5d11218e0a8a13332572917f0f8e5a589620c6f15b11c61dee327651a14c34e18231052e48c069",
             "xprv9s21ZrQH143K2V4oox4M8Zmhi2Fjx5XK4Lf7GKRvPSgydU3mjZuKGCTg7UPiBUD7ydVPvSLtg9hjp7MQTYsW67rZHAXeccqYqrsx8LcXnyd",
         ],
         [
             "000000000000000000000000000000000000000000000000",
             "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon agent",
             "035895f2f481b1b0f01fcf8c289c794660b289981a78f8106447707fdd9666ca06da5a9a565181599b79f53b844d8a71dd9f439c52a3d7b3e8a79c906ac845fa",
             "xprv9s21ZrQH143K3mEDrypcZ2usWqFgzKB6jBBx9B6GfC7fu26X6hPRzVjzkqkPvDqp6g5eypdk6cyhGnBngbjeHTe4LsuLG1cCmKJka5SMkmU",
         ],
         [
             "7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f",
             "legal winner thank year wave sausage worth useful legal winner thank year wave sausage worth useful legal will",
             "f2b94508732bcbacbcc020faefecfc89feafa6649a5491b8c952cede496c214a0c7b3c392d168748f2d4a612bada0753b52a1c7ac53c1e93abd5c6320b9e95dd",
             "xprv9s21ZrQH143K3Lv9MZLj16np5GzLe7tDKQfVusBni7toqJGcnKRtHSxUwbKUyUWiwpK55g1DUSsw76TF1T93VT4gz4wt5RM23pkaQLnvBh7",
         ],
         [
             "808080808080808080808080808080808080808080808080",
             "letter advice cage absurd amount doctor acoustic avoid letter advice cage absurd amount doctor acoustic avoid letter always",
             "107d7c02a5aa6f38c58083ff74f04c607c2d2c0ecc55501dadd72d025b751bc27fe913ffb796f841c49b1d33b610cf0e91d3aa239027f5e99fe4ce9e5088cd65",
             "xprv9s21ZrQH143K3VPCbxbUtpkh9pRG371UCLDz3BjceqP1jz7XZsQ5EnNkYAEkfeZp62cDNj13ZTEVG1TEro9sZ9grfRmcYWLBhCocViKEJae",
         ],
         [
             "ffffffffffffffffffffffffffffffffffffffffffffffff",
             "zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo when",
             "0cd6e5d827bb62eb8fc1e262254223817fd068a74b5b449cc2f667c3f1f985a76379b43348d952e2265b4cd129090758b3e3c2c49103b5051aac2eaeb890a528",
             "xprv9s21ZrQH143K36Ao5jHRVhFGDbLP6FCx8BEEmpru77ef3bmA928BxsqvVM27WnvvyfWywiFN8K6yToqMaGYfzS6Db1EHAXT5TuyCLBXUfdm",
         ],
         [
             "0000000000000000000000000000000000000000000000000000000000000000",
             "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon art",
             "bda85446c68413707090a52022edd26a1c9462295029f2e60cd7c4f2bbd3097170af7a4d73245cafa9c3cca8d561a7c3de6f5d4a10be8ed2a5e608d68f92fcc8",
             "xprv9s21ZrQH143K32qBagUJAMU2LsHg3ka7jqMcV98Y7gVeVyNStwYS3U7yVVoDZ4btbRNf4h6ibWpY22iRmXq35qgLs79f312g2kj5539ebPM",
         ],
         [
             "7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f",
             "legal winner thank year wave sausage worth useful legal winner thank year wave sausage worth useful legal winner thank year wave sausage worth title",
             "bc09fca1804f7e69da93c2f2028eb238c227f2e9dda30cd63699232578480a4021b146ad717fbb7e451ce9eb835f43620bf5c514db0f8add49f5d121449d3e87",
             "xprv9s21ZrQH143K3Y1sd2XVu9wtqxJRvybCfAetjUrMMco6r3v9qZTBeXiBZkS8JxWbcGJZyio8TrZtm6pkbzG8SYt1sxwNLh3Wx7to5pgiVFU",
         ],
         [
             "8080808080808080808080808080808080808080808080808080808080808080",
             "letter advice cage absurd amount doctor acoustic avoid letter advice cage absurd amount doctor acoustic avoid letter advice cage absurd amount doctor acoustic bless",
             "c0c519bd0e91a2ed54357d9d1ebef6f5af218a153624cf4f2da911a0ed8f7a09e2ef61af0aca007096df430022f7a2b6fb91661a9589097069720d015e4e982f",
             "xprv9s21ZrQH143K3CSnQNYC3MqAAqHwxeTLhDbhF43A4ss4ciWNmCY9zQGvAKUSqVUf2vPHBTSE1rB2pg4avopqSiLVzXEU8KziNnVPauTqLRo",
         ],
         [
             "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
             "zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo vote",
             "dd48c104698c30cfe2b6142103248622fb7bb0ff692eebb00089b32d22484e1613912f0a5b694407be899ffd31ed3992c456cdf60f5d4564b8ba3f05a69890ad",
             "xprv9s21ZrQH143K2WFF16X85T2QCpndrGwx6GueB72Zf3AHwHJaknRXNF37ZmDrtHrrLSHvbuRejXcnYxoZKvRquTPyp2JiNG3XcjQyzSEgqCB",
         ],
         [
             "9e885d952ad362caeb4efe34a8e91bd2",
             "ozone drill grab fiber curtain grace pudding thank cruise elder eight picnic",
             "274ddc525802f7c828d8ef7ddbcdc5304e87ac3535913611fbbfa986d0c9e5476c91689f9c8a54fd55bd38606aa6a8595ad213d4c9c9f9aca3fb217069a41028",
             "xprv9s21ZrQH143K2oZ9stBYpoaZ2ktHj7jLz7iMqpgg1En8kKFTXJHsjxry1JbKH19YrDTicVwKPehFKTbmaxgVEc5TpHdS1aYhB2s9aFJBeJH",
         ],
         [
             "6610b25967cdcca9d59875f5cb50b0ea75433311869e930b",
             "gravity machine north sort system female filter attitude volume fold club stay feature office ecology stable narrow fog",
             "628c3827a8823298ee685db84f55caa34b5cc195a778e52d45f59bcf75aba68e4d7590e101dc414bc1bbd5737666fbbef35d1f1903953b66624f910feef245ac",
             "xprv9s21ZrQH143K3uT8eQowUjsxrmsA9YUuQQK1RLqFufzybxD6DH6gPY7NjJ5G3EPHjsWDrs9iivSbmvjc9DQJbJGatfa9pv4MZ3wjr8qWPAK",
         ],
         [
             "68a79eaca2324873eacc50cb9c6eca8cc68ea5d936f98787c60c7ebc74e6ce7c",
             "hamster diagram private dutch cause delay private meat slide toddler razor book happy fancy gospel tennis maple dilemma loan word shrug inflict delay length",
             "64c87cde7e12ecf6704ab95bb1408bef047c22db4cc7491c4271d170a1b213d20b385bc1588d9c7b38f1b39d415665b8a9030c9ec653d75e65f847d8fc1fc440",
             "xprv9s21ZrQH143K2XTAhys3pMNcGn261Fi5Ta2Pw8PwaVPhg3D8DWkzWQwjTJfskj8ofb81i9NP2cUNKxwjueJHHMQAnxtivTA75uUFqPFeWzk",
         ],
         [
             "c0ba5a8e914111210f2bd131f3d5e08d",
             "scheme spot photo card baby mountain device kick cradle pact join borrow",
             "ea725895aaae8d4c1cf682c1bfd2d358d52ed9f0f0591131b559e2724bb234fca05aa9c02c57407e04ee9dc3b454aa63fbff483a8b11de949624b9f1831a9612",
             "xprv9s21ZrQH143K3FperxDp8vFsFycKCRcJGAFmcV7umQmcnMZaLtZRt13QJDsoS5F6oYT6BB4sS6zmTmyQAEkJKxJ7yByDNtRe5asP2jFGhT6",
         ],
         [
             "6d9be1ee6ebd27a258115aad99b7317b9c8d28b6d76431c3",
             "horn tenant knee talent sponsor spell gate clip pulse soap slush warm silver nephew swap uncle crack brave",
             "fd579828af3da1d32544ce4db5c73d53fc8acc4ddb1e3b251a31179cdb71e853c56d2fcb11aed39898ce6c34b10b5382772db8796e52837b54468aeb312cfc3d",
             "xprv9s21ZrQH143K3R1SfVZZLtVbXEB9ryVxmVtVMsMwmEyEvgXN6Q84LKkLRmf4ST6QrLeBm3jQsb9gx1uo23TS7vo3vAkZGZz71uuLCcywUkt",
         ],
         [
             "9f6a2878b2520799a44ef18bc7df394e7061a224d2c33cd015b157d746869863",
             "panda eyebrow bullet gorilla call smoke muffin taste mesh discover soft ostrich alcohol speed nation flash devote level hobby quick inner drive ghost inside",
             "72be8e052fc4919d2adf28d5306b5474b0069df35b02303de8c1729c9538dbb6fc2d731d5f832193cd9fb6aeecbc469594a70e3dd50811b5067f3b88b28c3e8d",
             "xprv9s21ZrQH143K2WNnKmssvZYM96VAr47iHUQUTUyUXH3sAGNjhJANddnhw3i3y3pBbRAVk5M5qUGFr4rHbEWwXgX4qrvrceifCYQJbbFDems",
         ],
         [
             "23db8160a31d3e0dca3688ed941adbf3",
             "cat swing flag economy stadium alone churn speed unique patch report train",
             "deb5f45449e615feff5640f2e49f933ff51895de3b4381832b3139941c57b59205a42480c52175b6efcffaa58a2503887c1e8b363a707256bdd2b587b46541f5",
             "xprv9s21ZrQH143K4G28omGMogEoYgDQuigBo8AFHAGDaJdqQ99QKMQ5J6fYTMfANTJy6xBmhvsNZ1CJzRZ64PWbnTFUn6CDV2FxoMDLXdk95DQ",
         ],
         [
             "8197a4a47f0425faeaa69deebc05ca29c0a5b5cc76ceacc0",
             "light rule cinnamon wrap drastic word pride squirrel upgrade then income fatal apart sustain crack supply proud access",
             "4cbdff1ca2db800fd61cae72a57475fdc6bab03e441fd63f96dabd1f183ef5b782925f00105f318309a7e9c3ea6967c7801e46c8a58082674c860a37b93eda02",
             "xprv9s21ZrQH143K3wtsvY8L2aZyxkiWULZH4vyQE5XkHTXkmx8gHo6RUEfH3Jyr6NwkJhvano7Xb2o6UqFKWHVo5scE31SGDCAUsgVhiUuUDyh",
         ],
         [
             "066dca1a2bb7e8a1db2832148ce9933eea0f3ac9548d793112d9a95c9407efad",
             "all hour make first leader extend hole alien behind guard gospel lava path output census museum junior mass reopen famous sing advance salt reform",
             "26e975ec644423f4a4c4f4215ef09b4bd7ef924e85d1d17c4cf3f136c2863cf6df0a475045652c57eb5fb41513ca2a2d67722b77e954b4b3fc11f7590449191d",
             "xprv9s21ZrQH143K3rEfqSM4QZRVmiMuSWY9wugscmaCjYja3SbUD3KPEB1a7QXJoajyR2T1SiXU7rFVRXMV9XdYVSZe7JoUXdP4SRHTxsT1nzm",
         ],
         [
             "f30f8c1da665478f49b001d94c5fc452",
             "vessel ladder alter error federal sibling chat ability sun glass valve picture",
             "2aaa9242daafcee6aa9d7269f17d4efe271e1b9a529178d7dc139cd18747090bf9d60295d0ce74309a78852a9caadf0af48aae1c6253839624076224374bc63f",
             "xprv9s21ZrQH143K2QWV9Wn8Vvs6jbqfF1YbTCdURQW9dLFKDovpKaKrqS3SEWsXCu6ZNky9PSAENg6c9AQYHcg4PjopRGGKmdD313ZHszymnps",
         ],
         [
             "c10ec20dc3cd9f652c7fac2f1230f7a3c828389a14392f05",
             "scissors invite lock maple supreme raw rapid void congress muscle digital elegant little brisk hair mango congress clump",
             "7b4a10be9d98e6cba265566db7f136718e1398c71cb581e1b2f464cac1ceedf4f3e274dc270003c670ad8d02c4558b2f8e39edea2775c9e232c7cb798b069e88",
             "xprv9s21ZrQH143K4aERa2bq7559eMCCEs2QmmqVjUuzfy5eAeDX4mqZffkYwpzGQRE2YEEeLVRoH4CSHxianrFaVnMN2RYaPUZJhJx8S5j6puX",
         ],
         [
             "f585c11aec520db57dd353c69554b21a89b20fb0650966fa0a9d6f74fd989d8f",
             "void come effort suffer camp survey warrior heavy shoot primary clutch crush open amazing screen patrol group space point ten exist slush involve unfold",
             "01f5bced59dec48e362f2c45b5de68b9fd6c92c6634f44d6d40aab69056506f0e35524a518034ddc1192e1dacd32c1ed3eaa3c3b131c88ed8e7e54c49a5d0998",
             "xprv9s21ZrQH143K39rnQJknpH1WEPFJrzmAqqasiDcVrNuk926oizzJDDQkdiTvNPr2FYDYzWgiMiC63YmfPAa2oPyNB23r2g7d1yiK6WpqaQS",
         ],
     ]
     for entropy, mnemonic, seed, xprv in tests:
         private_key = HDPrivateKey.from_mnemonic(mnemonic, b"TREZOR")
         self.assertEqual(private_key.xprv(), xprv)