コード例 #1
0
    def check_if_seed_valid(self, seed_phrase) -> bool:
        try:
            bip39.mnemonic_to_seed((" ".join(seed_phrase)).strip())
        except ValueError:
            return False

        return True
コード例 #2
0
 def add_hot_wallet_keys(
     self,
     mnemonic,
     passphrase,
     paths,
     file_password,
     wallet_manager,
     testnet,
     keys_range=[0, 1000],
     keys_purposes=[],
 ):
     super().add_hot_wallet_keys(
         mnemonic,
         passphrase,
         paths,
         file_password,
         wallet_manager,
         testnet,
         keys_range=keys_range,
         keys_purposes=keys_purposes,
     )
     rpc = wallet_manager.rpc.wallet(
         os.path.join(wallet_manager.rpc_path + "_hotstorage", self.alias)
     )
     seed = bip39.mnemonic_to_seed(mnemonic, passphrase)
     master_blinding_key = master_blinding_from_seed(seed)
     rpc.importmasterblindingkey(master_blinding_key.secret.hex())
     self.set_blinding_key(master_blinding_key.wif())
コード例 #3
0
    def set_seed_phrase(self, seed_phrase, passphrase):
        # requires a valid seed phrase or error will be thrown
        self.seed_phrase = seed_phrase
        self.seed = bip39.mnemonic_to_seed(
            (" ".join(self.seed_phrase)).strip(), passphrase)
        self.root = bip32.HDKey.from_seed(
            self.seed, version=NETWORKS[self.current_network]["xprv"])
        self.fingerprint = self.root.child(0).fingerprint
        self.bip48_xprv = self.root.derive(self.hardened_derivation)
        self.bip48_xpub = self.bip48_xprv.to_public()

        self.tx = None
        self.inp_amount = None
        self.fee = None
        self.spend = None
        self.destinationaddress = None
        self.dest_addr_cnt = None
        self.change = None
        self.controller = None
        self.buttons = None
        self.ins = None
        self.outs = None

        self.camera_loop_timer = None
        self.camera_data = None
        self.is_camera_data = False

        self.qr_total_frames = 0
        self.qr_cur_frame_count = 0
        self.qr_data = []
        self.frame_display = []
        self.percentage_complete = 0

        self.scan_started_ind = 0
        self.scan_display_working = 0
コード例 #4
0
ファイル: hdkey.py プロジェクト: jimmysong/embit
def main():
    # generate 16 bytes of entropy and
    # convert to a mnemonic phrase (12 words)
    entropy = bytes([random.getrandbits(8) for i in range(16)])
    mnemonic = bip39.mnemonic_from_bytes(entropy)
    # or just define hardcoded:
    mnemonic = "alien visual jealous source coral memory embark certain radar capable clip edit"
    print(mnemonic)
    # convert to seed, empty password
    seed = bip39.mnemonic_to_seed(mnemonic)
    # convert to the root key
    # you can define the version - x/y/zprv for desired network
    root = bip32.HDKey.from_seed(seed, version=NETWORKS["test"]["xprv"])
    print(root.to_base58())

    print("\nBIP-44 - legacy")
    # derive account according to bip44
    bip44_xprv = root.derive("m/44h/1h/0h")
    print(bip44_xprv.to_base58())
    # corresponding master public key:
    bip44_xpub = bip44_xprv.to_public()
    print(bip44_xpub.to_base58())
    # first 5 receiving addresses
    for i in range(5):
        # .key member is a public key for HD public keys
        #            and a private key for HD private keys
        pub = bip44_xpub.derive("m/0/%d" % i).key
        sc = script.p2pkh(pub)
        print(sc.address(NETWORKS["test"]))

    print("\nBIP-84 - native segwit")
    # derive account according to bip84
    bip84_xprv = root.derive("m/84h/1h/0h")
    # you can also change version of the key to get zpub (vpub on testnet)
    bip84_xprv.version = NETWORKS["test"]["zprv"]
    print(bip84_xprv.to_base58())
    # corresponding master public key:
    bip84_xpub = bip84_xprv.to_public()
    print(bip84_xpub.to_base58())
    # first 5 receiving addresses
    for i in range(5):
        pub = bip84_xpub.derive("m/0/%d" % i).key
        sc = script.p2wpkh(pub)
        print(sc.address(NETWORKS["test"]))

    print("\nBIP-49 - nested segwit")
    # derive account according to bip49
    bip49_xprv = root.derive("m/49h/1h/0h")
    # you can also change version of the key to get ypub (upub on testnet)
    bip49_xprv.version = NETWORKS["test"]["yprv"]
    print(bip49_xprv.to_base58())
    # corresponding master public key:
    bip49_xpub = bip49_xprv.to_public()
    print(bip49_xpub.to_base58())
    # first 5 receiving addresses
    for i in range(5):
        pub = bip49_xpub.derive("m/0/%d" % i).key
        # use p2sh(p2wpkh(pubkey)) to get nested segwit scriptpubkey
        sc = script.p2sh(script.p2wpkh(pub))
        print(sc.address(NETWORKS["test"]))
コード例 #5
0
 def _generate_seed(self) -> bool:
     self.seed = None
     try:
         self.seed = bip39.mnemonic_to_seed(self.mnemonic,
                                            password=self.passphrase,
                                            wordlist=self._wordlist)
         return True
     except Exception as e:
         return False
コード例 #6
0
 def test_bip39(self):
     for [seed, exp_mnemonic, hex_seed, xprv] in VECTORS:
         act_mnemonic = mnemonic_from_bytes(unhexlify(seed))
         act_xkey = HDKey.from_seed(
             mnemonic_to_seed(act_mnemonic, password="******"))
         self.assertEqual(act_mnemonic, exp_mnemonic)
         self.assertTrue(mnemonic_is_valid(act_mnemonic))
         self.assertEqual(
             hexlify(mnemonic_to_bytes(act_mnemonic)).decode(), seed)
         self.assertEqual(act_xkey.to_base58(), xprv)
コード例 #7
0
    def test_alternate_wordlist(self):
        # Spanish mnemonics validated via https://iancoleman.io/bip39/#spanish
        mnemonics = [
            ("título paso humano cañón enfado ropero hueco cromo blusa turno fideo glaciar verano baba gordo fila trance íntimo rotar gustar sombra revés laguna jardín",
             "bb0c5656117fd52d995dafca2d692974e74cb7c713c35871a0915d7bda6122694b2b67664113b198d2c1dd828195587c7dec8d6179f93d2157d6a11d8d0a949d"
             ),
            ("natural tóxico choque regreso norte tarta uña prisión bulto ángulo fervor nariz",
             "30affe746f3a81816739c2dacc3de426084482b729c7b592cee0ff2bdf73315943a5da8d8da4afd767f905d5ded4b0ab3a948d7eff9834fca5e8691a186fee20"
             ),
        ]
        from .data.bip39_es import WORDLIST as ES_WORDLIST

        for mnemonic, expected_seed in mnemonics:
            self.assertTrue(mnemonic_is_valid(mnemonic, wordlist=ES_WORDLIST))

            self.assertEqual(
                hexlify(mnemonic_to_seed(mnemonic,
                                         wordlist=ES_WORDLIST)).decode(),
                expected_seed)

            self.assertEqual(
                mnemonic_from_bytes(mnemonic_to_bytes(mnemonic,
                                                      wordlist=ES_WORDLIST),
                                    wordlist=ES_WORDLIST), mnemonic)
コード例 #8
0
def main():
    # get root key from the mnemonic
    mnemonic = "alien visual jealous source coral memory embark certain radar capable clip edit"
    seed = bip39.mnemonic_to_seed(mnemonic)
    root = bip32.HDKey.from_seed(seed, version=NETWORKS["test"]["xprv"])

    # get bip84-xpub to import to Bitcoin Core:
    # we will use the form [fingerprint/derivation]xpub
    # to import to Bitcoin Core with descriptors

    # first let's get the root fingerprint
    # we can get it from any child of the root key
    fingerprint = root.child(0).fingerprint
    hardened_derivation = "m/84h/1h/0h"
    # derive account according to bip84
    bip84_xprv = root.derive(hardened_derivation)
    # corresponding master public key:
    bip84_xpub = bip84_xprv.to_public()
    print("[%s%s]%s" % (hexlify(fingerprint).decode('utf-8'),
                        hardened_derivation[1:], bip84_xpub.to_base58()))

    # parse psbt transaction
    b64_psbt = "cHNidP8BAHICAAAAAY3LB6teEH6qJHluFYG3AQe8n0HDUcUSEuw2WIJ1ECDUAAAAAAD/////AoDDyQEAAAAAF6kU882+nVMDKGj4rKzjDB6NjyJqSBCHaPMhCgAAAAAWABQUbW8/trQg4d3PKL8WLi2kUa1BqAAAAAAAAQEfAMLrCwAAAAAWABTR6Cr4flM2A0LMGjGiaZ+fhod37SIGAhHf737H1jCUjkJ1K5DqFkaY0keihxeWBQpm1kDtVZyxGLMX7IZUAACAAQAAgAAAAIAAAAAAAAAAAAAAIgIDPtTTi27VFw59jdmWDV8b1YciQzhYGO7m8zB9CvD0brcYsxfshlQAAIABAACAAAAAgAEAAAAAAAAAAA=="
    # first convert it to binary
    raw = a2b_base64(b64_psbt)
    # then parse
    tx = psbt.PSBT.parse(raw)

    # print how much we are spending and where
    total_in = 0
    for inp in tx.inputs:
        total_in += inp.witness_utxo.value
    print("Inputs:", total_in, "satoshi")
    change_out = 0  # value that goes back to us
    send_outputs = []
    for i, out in enumerate(tx.outputs):
        # check if it is a change or not:
        change = False
        # should be one or zero for single-key addresses
        for pub in out.bip32_derivations:
            # check if it is our key
            if out.bip32_derivations[pub].fingerprint == fingerprint:
                hdkey = root.derive(out.bip32_derivations[pub].derivation)
                mypub = hdkey.key.get_public_key()
                if mypub != pub:
                    raise ValueError("Derivation path doesn't look right")
                # now check if provided scriptpubkey matches
                sc = script.p2wpkh(mypub)
                if sc == tx.tx.vout[i].script_pubkey:
                    change = True
                    continue
        if change:
            change_out += tx.tx.vout[i].value
        else:
            send_outputs.append(tx.tx.vout[i])
    print("Spending", total_in - change_out, "satoshi")
    fee = total_in - change_out
    for out in send_outputs:
        fee -= out.value
        print(out.value, "to", out.script_pubkey.address(NETWORKS["test"]))
    print("Fee:", fee, "satoshi")

    # sign the transaction
    tx.sign_with(root)
    raw = tx.serialize()
    # convert to base64
    b64_psbt = b2a_base64(raw)
    # somehow b2a ends with \n...
    if b64_psbt[-1:] == b"\n":
        b64_psbt = b64_psbt[:-1]
    # print
    print("\nSigned transaction:")
    print(b64_psbt.decode('utf-8'))
コード例 #9
0
    def add_hot_wallet_keys(
        self,
        mnemonic,
        passphrase,
        paths,
        file_password,
        wallet_manager,
        testnet,
        keys_range=[0, 1000],
        keys_purposes=[],
    ):
        if type(keys_range[0]) == str:
            keys_range[0] = int(keys_range[0])
        if type(keys_range[1]) == str:
            keys_range[1] = int(keys_range[1])
        seed = bip39.mnemonic_to_seed(mnemonic, passphrase)
        root = bip32.HDKey.from_seed(seed)
        network = networks.NETWORKS["test" if testnet else "main"]
        root.version = network["xprv"]
        xprv = root.to_base58()
        # Load the wallet if not loaded
        self._load_wallet(wallet_manager)
        rpc = wallet_manager.rpc.wallet(
            os.path.join(wallet_manager.rpc_path + "_hotstorage", self.alias))
        if file_password:
            rpc.walletpassphrase(file_password, 60)
        rpc.importmulti(
            [{
                "desc":
                AddChecksum("sh(wpkh({}{}/0/*))".format(
                    xprv,
                    path.rstrip("/").replace("m", ""))),
                "range":
                keys_range,
                "timestamp":
                "now",
            } for path in paths] + [{
                "desc":
                AddChecksum("sh(wpkh({}{}/1/*))".format(
                    xprv,
                    path.rstrip("/").replace("m", ""))),
                "range":
                keys_range,
                "timestamp":
                "now",
            } for path in paths],
            {"rescan": False},
        )

        xpubs = [root.derive(path).to_public().to_base58() for path in paths]
        # root fingerprint is fingerprint field of the first child
        master_fpr = root.child(0).fingerprint.hex()
        keys = []
        for i in range(len(paths)):
            try:
                path = paths[i]
                xpub = xpubs[i]
                # detect slip132 version for xpubs
                slip132_prefix = bip32.detect_version(path,
                                                      default="xpub",
                                                      network=network)
                xpub = "[{}{}]{}\n".format(
                    master_fpr,
                    path.replace("m", ""),
                    convert_xpub_prefix(xpub, slip132_prefix),
                )
                keys.append(
                    Key.parse_xpub(
                        xpub,
                        keys_purposes[i] if len(keys_purposes) > i else ""))
            except Exception:
                # TODO: This should never occur, but just in case,
                # we must make sure to catch it properly so it
                # doesn't crash the app no matter what.
                raise Exception("Failed to parse this xpub:\n" +
                                "\n".join(xpub))
        self.add_keys(keys)
コード例 #10
0
def main():
    # mnemonic we use
    mnemonic = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"
    seed = bip39.mnemonic_to_seed(mnemonic)
    root = bip32.HDKey.from_seed(seed)

    # A few transactions
    b64psbts = [
        # p2wsh, 2 inputs
        "cHNidP8BALICAAAAAq1DhxRK+mUH4T6uUNob8bUaZ7MP+44MW4+Y9bOxpjhZAAAAAAD9////aWclWQ+45HKrI07r878E2UrAupT2paT4QurbmtNjYNQBAAAAAP3///8CQEIPAAAAAAAiACCpkDPDhmIzPlkJrjw9A71xjbIUWf3VUB7ooFJhTVm04tjSIQEAAAAAIgAgjQKFDauIXsV5u23LBdYgOwX1FwGGrLiQfWzBtFKZ7dIAAAAATwEENYfPBD5i336AAAACQStJhNVJul7vHKbo83VdmuAW2m0WaXLKDlFANn7dUNoCNbhLMdw4Knz7Q7o6exdL6UFhQegW9nJb0SUStbLEpawUAgjLdzAAAIABAACAAAAAgAIAAIBPAQQ1h88EnbHQAIAAAAI/2Nc7x7iMpJNapTe/OJTV4oifqzQcYY9KV2+PGRjCdQJoww1WnSNqfcxXGyux0q1PqfmzUqgJNqKJCpmqI9t47BQmu4PEMAAAgAEAAIAAAACAAgAAgE8BBDWHzwS6wUg5gAAAAh1Pvr3ZZ+GvcUwJl9OPz2cLXOnTAcBEC7zDtqIOt3IcA1aOofNgUZFu0baQw54SqOcGA7KAvTDOXygfKRilU2OqFHPF2gowAACAAQAAgAAAAIACAACAAAEBK4CWmAAAAAAAIgAgiYAxcG7dnrEiZ4VHFVHOo18XCalvhZYuMqBr9n7HESQBBWlSIQJOjQgMfX26XEf+trHIEk3rYkEX5Y2NfrFKQARPcd2X8iEDBWHUgq25PfHvE+hlcBryJG7wo2y8jKUSPY7sd85OOMchA2iVcuKLD+2p1pgcAjfZ5d7b/sFt5xQ/aAoC7V0Vn3WHU64iBgJOjQgMfX26XEf+trHIEk3rYkEX5Y2NfrFKQARPcd2X8hwmu4PEMAAAgAEAAIAAAACAAgAAgAAAAAABAAAAIgYDBWHUgq25PfHvE+hlcBryJG7wo2y8jKUSPY7sd85OOMccAgjLdzAAAIABAACAAAAAgAIAAIAAAAAAAQAAACIGA2iVcuKLD+2p1pgcAjfZ5d7b/sFt5xQ/aAoC7V0Vn3WHHHPF2gowAACAAQAAgAAAAIACAACAAAAAAAEAAAAAAQErgJaYAAAAAAAiACAzd60wM9EFnPHSNbsSJfyipL8myVLVP2/vwzotVUSNxQEFaVIhAiKCMRLlzIhLkRbLIUIMx5KYJM0v6LcjW/mS6K7eFGwiIQKDzUflU23LeecRgzDo5IBCEvaWGfHW7JkNxzXvuc7FdCEDC5DtLoa61/Kk/pdpu0F9e6nKoRJIB9v7Ni377rZefgFTriIGAiKCMRLlzIhLkRbLIUIMx5KYJM0v6LcjW/mS6K7eFGwiHAIIy3cwAACAAQAAgAAAAIACAACAAAAAAAAAAAAiBgKDzUflU23LeecRgzDo5IBCEvaWGfHW7JkNxzXvuc7FdBwmu4PEMAAAgAEAAIAAAACAAgAAgAAAAAAAAAAAIgYDC5DtLoa61/Kk/pdpu0F9e6nKoRJIB9v7Ni377rZefgEcc8XaCjAAAIABAACAAAAAgAIAAIAAAAAAAAAAAAABAWlSIQKtIdmtKKuZrH7f2R4iIU8RWVOrCdHVWBCS+0e9pZJy/iEDoH074LrWPIA10hyXtBCJDT06GdLkA6+z/PxoJqomPHYhA6GoQ/otQdk71nUpYZFfbkSKdBkkSj4CuPTPYrzGp6JrU64iAgKtIdmtKKuZrH7f2R4iIU8RWVOrCdHVWBCS+0e9pZJy/hwCCMt3MAAAgAEAAIAAAACAAgAAgAEAAAAAAAAAIgIDoH074LrWPIA10hyXtBCJDT06GdLkA6+z/PxoJqomPHYcc8XaCjAAAIABAACAAAAAgAIAAIABAAAAAAAAACICA6GoQ/otQdk71nUpYZFfbkSKdBkkSj4CuPTPYrzGp6JrHCa7g8QwAACAAQAAgAAAAIACAACAAQAAAAAAAAAAAA==",
        # p2sh-p2wsh
        "cHNidP8BAHICAAAAAR30J629i3Y/R8woRpLQ9JUa31rKxyM+Ny4NEsme48GWAAAAAAD9////Atw5XQUAAAAAF6kUdSESczdYagEyToVUSXyT8VTNz+OHgJaYAAAAAAAWABTmav7/w4OOcfCiewfjsA7eaujhYAAAAABPAQQ1h88EPmLffoAAAAHdXEj2dn8EYJ+rRdXEYu5laq6lJI5Mp+3t63ckwty05QKrJBNPewhwQaGPYRif6+XaxozFXvTXn7pU24H6fRy1FxQCCMt3MAAAgAEAAIAAAACAAQAAgE8BBDWHzwSdsdAAgAAAAeeOv56oeaaFTrNonMKDHk1C8brbWGFvdlecVue+v0/RAn/g4yI3oYsyen7OOcT7caYl4Mn7nQbyonHcusUR+GhzFCa7g8QwAACAAQAAgAAAAIABAACATwEENYfPBLrBSDmAAAABpzrb4oeEh2NNy/w/fr3osfyZTx7AaGDPAcP+LqeR3bYC5ioqmXPuazp69HwimlvecLylm9BLuyl/VpPXqiVrl20Uc8XaCjAAAIABAACAAAAAgAEAAIAAAQEgAOH1BQAAAAAXqRSv3gkn8731qcPbSDu4TJOlJJZ/PocBBCIAIOeiFBX5x0vX6CacrAUVovrs1DDCcKJS5qptFS3sjpDpAQVpUiECZ+pFYkOTVjB+eG+vQFA3MNjZWiA6DjRcs1Wl36A/zgMhA2Ygckuwjah29wiVRgA6wFx51+6ayrzeCIQ2eE4zfxPtIQOnUlBn22cn2CPCZkMSI6cDaZK2SlLV20rT6pqMoQCJsFOuIgYCZ+pFYkOTVjB+eG+vQFA3MNjZWiA6DjRcs1Wl36A/zgMcc8XaCjAAAIABAACAAAAAgAEAAIAAAAAAAAAAACIGA2Ygckuwjah29wiVRgA6wFx51+6ayrzeCIQ2eE4zfxPtHCa7g8QwAACAAQAAgAAAAIABAACAAAAAAAAAAAAiBgOnUlBn22cn2CPCZkMSI6cDaZK2SlLV20rT6pqMoQCJsBwCCMt3MAAAgAEAAIAAAACAAQAAgAAAAAAAAAAAAAEAIgAgZBwTq05RkpqKv6FV6LQjuM07Qv0/bYfWVc9NUQOFvwQBAWlSIQI32jVSdTgeu7+YZKrWfgOZ2J/LV36c5rBoApTzhrNlDCEC+hEqm3XmRt862AFFeyJ7p1m8A+V7czj6OajUNgCfg4EhA/MUVfxGh4k3Po3LB8CmMRsvIHcGNO0elUgETaITZA3UU64iAgI32jVSdTgeu7+YZKrWfgOZ2J/LV36c5rBoApTzhrNlDBwCCMt3MAAAgAEAAIAAAACAAQAAgAEAAAAAAAAAIgIC+hEqm3XmRt862AFFeyJ7p1m8A+V7czj6OajUNgCfg4EcJruDxDAAAIABAACAAAAAgAEAAIABAAAAAAAAACICA/MUVfxGh4k3Po3LB8CmMRsvIHcGNO0elUgETaITZA3UHHPF2gowAACAAQAAgAAAAIABAACAAQAAAAAAAAAAAA==",
        # p2wpkh
        "cHNidP8BAHECAAAAAc88WMMpgq4gUIjZvUnrmwKs3009rnalFsazBrFd46FOAAAAAAD9////Anw/XQUAAAAAFgAULzSqHPAKU7BVopGgOn1F8KaYi1KAlpgAAAAAABYAFOZq/v/Dg45x8KJ7B+OwDt5q6OFgAAAAAAABAR8A4fUFAAAAABYAFNDEo+8J6Ze26Z45flGP4+QaEYyhIgYC56slN7XUnpcDCargbp5J82zhyf671E7I4NHMoLT5wxkYc8XaClQAAIABAACAAAAAgAAAAAAAAAAAACICA11J7M1U0AmeQ2did8em1GJdYR2oil30m/lReneRp3elGHPF2gpUAACAAQAAgAAAAIABAAAAAAAAAAAA",
        # p2sh-p2wpkh
        "cHNidP8BAHICAAAAAXbva/K90EDzwdg6zLl0OfGrsaVWrR0PUpaB/6foypSKAQAAAAD9////Apw9XQUAAAAAF6kUJR3RFFeiWcO6R+XMo3F/5CFOApiHgJaYAAAAAAAWABTmav7/w4OOcfCiewfjsA7eaujhYAAAAAAAAQEgAOH1BQAAAAAXqRQzbKoT4IuWCAoytdgY1ZtKs7NnQocBBBYAFDiXH3OTD2wUHZd6xP1KcnyFSTWzIgYDoa+ASsEIqKUXghmMLQNLKL+QyIA/WlP3Ynb6aaTq538Yc8XaCjEAAIABAACAAAAAgAAAAAAAAAAAAAEAFgAUcL6x4EpQCUDp86uqZuGkmsVbjzUiAgKi/ImWxSYiSLXa78Wk0M3NAMEwR9DLEwKBNupjDYdahxhzxdoKMQAAgAEAAIAAAACAAQAAAAAAAAAAAA==",
    ]
    for i, b64psbt in enumerate(b64psbts):
        print("\nTransaction #%d" % (i + 1))
        raw = a2b_base64(b64psbt)
        tx = psbt.PSBT.parse(raw)

        # Check inputs of the transaction and check that they use the same script type
        # For multisig parsed policy will look like this:
        # { script_type: p2wsh, cosigners: [xpubs strings], m: 2, n: 3}
        policy = None
        inp_amount = 0
        for inp in tx.inputs:
            inp_amount += inp.witness_utxo.value
            # get policy of the input
            inp_policy = get_policy(inp, inp.witness_utxo.script_pubkey,
                                    tx.xpubs)
            # if policy is None - assign current
            if policy is None:
                policy = inp_policy
            # otherwise check that everything in the policy is the same
            else:
                # check policy is the same
                if policy != inp_policy:
                    raise RuntimeError("Mixed inputs in the transaction")

        wallet = "Native Segwit "
        if "p2sh-" in policy["type"]:
            wallet = "Nested Segwit "
        if "m" in policy:
            wallet += "Multisig (%d of %d)" % (policy["m"], policy["n"])
        else:
            wallet += "Single sig"
        print("Spending from: %s" % wallet)
        print("Input amount: %d sat" % inp_amount)
        # now go through outputs and check if they are change
        spending = 0
        change = 0
        for i, out in enumerate(tx.outputs):
            out_policy = get_policy(out, tx.tx.vout[i].script_pubkey, tx.xpubs)
            is_change = False
            # if policy is the same - probably change
            if out_policy == policy:
                # double-check that it's change
                # we already checked in get_cosigners and parse_multisig
                # that pubkeys are generated from cosigners,
                # and witness script is corresponding multisig
                # so we only need to check that scriptpubkey is generated from
                # witness script

                # empty script by default
                sc = script.Script(b"")
                # multisig, we know witness script
                if policy["type"] == "p2wsh":
                    sc = script.p2wsh(out.witness_script)
                elif policy["type"] == "p2sh-p2wsh":
                    sc = script.p2sh(script.p2wsh(out.witness_script))
                # single-sig
                elif "pkh" in policy["type"]:
                    if len(out.bip32_derivations.values()) > 0:
                        der = list(
                            out.bip32_derivations.values())[0].derivation
                        my_pubkey = root.derive(der)
                    if policy["type"] == "p2wpkh":
                        sc = script.p2wpkh(my_pubkey)
                    elif policy["type"] == "p2sh-p2wpkh":
                        sc = script.p2sh(script.p2wpkh(my_pubkey))
                if sc.data == tx.tx.vout[i].script_pubkey.data:
                    is_change = True
            if is_change:
                change += tx.tx.vout[i].value
                print("Change %d sats" % tx.tx.vout[i].value)
            else:
                spending += tx.tx.vout[i].value
                print("Spending %d sats to %s" %
                      (tx.tx.vout[i].value,
                       tx.tx.vout[i].script_pubkey.address()))

        fee = inp_amount - change - spending
        print("Fee: %d sats" % fee)
コード例 #11
0
from unittest import TestCase
from embit.psbtview import PSBTView
from embit.psbt import PSBT, InputScope
from embit import bip32, bip39
from binascii import a2b_base64, b2a_base64
from io import BytesIO

ROOT = bip32.HDKey.from_seed(
    bip39.mnemonic_to_seed(
        "toy fault beef holiday later unit boring merge shield detail scrap negative"
    ))
# tprv8ZgxMBicQKsPeDhmZay7WoN2W9gkmZNv4bkPRgCsaqKAnafo2YkpmJFQUAv34PTdYciNteTu8A1tvDUBsusThseGfiPkdFAniazFzxRd8xv

PSBTS = [
    # native segwit single key, 3 inputs 4 outputs, version is None
    "cHNidP8BAP0NAQIAAAADzs+bdp5MDPJOynM/GVqxv0TxNfN65lYTcGjA8cD2NQABAAAAAP3////1a2FpOvW/FfVn5ct5op0HWQPcF4A4CiD1PuhzM0hvrQAAAAAA/f///2MhMb9J3pwquctpXFlr8LsbwD92XoSKkuwvoNt5prEyAQAAAAD9////BJ5YYgIAAAAAFgAUVE9NQrxzbpzaj8DvSA4+Vfb3S8+Aw8kBAAAAACIAILUTol87mgHQEKeZJcuNUJjXFfUupwaSGuS7QTdBQ1TW5pWYAAAAAAAWABSyijJ33ycsST0veZC7xCcjMLvGnoCWmAAAAAAAFgAUfwP8SJ6OrkWYgi79Rmtboh4vb6AAAAAAAAEAcQIAAAABuN9N7UQtW4/kL7p6GZ+7mJBDtdrC/DLvjimTOlG61vwAAAAAAP7///8CZnl3IwEAAAAWABTW7Yti5UnKO3zm7e0HWxgroI9k8YCWmAAAAAAAFgAUCCFopyBBzsmGQ+okmceR7NCUMRkAAAAAAQEfgJaYAAAAAAAWABQIIWinIEHOyYZD6iSZx5Hs0JQxGSIGAqmBsz9+uSoKzzc3UJKj73Z/nJbAJO8qGivBThNWuQtcGCYUvcRUAACAAQAAgAAAAIAAAAAAAgAAAAABAH0CAAAAAWMhMb9J3pwquctpXFlr8LsbwD92XoSKkuwvoNt5prEyAAAAAAD9////AoCWmAAAAAAAFgAUmt3MvhOm8evmqSbslu0VMw4ajTRcLDEBAAAAACIAIAzATi9Gij9I+WDMngJmvUJUfdBpvUc5SHXeB2kErkrHAAAAAAEBH4CWmAAAAAAAFgAUmt3MvhOm8evmqSbslu0VMw4ajTQiBgOBjOkF2dUPQVfZDgvAq1AJnIyXnxIndmAv9CatqeHP9hgmFL3EVAAAgAEAAIAAAACAAAAAAAEAAAAAAQB9AgAAAAG4303tRC1bj+QvunoZn7uYkEO12sL8Mu+OKZM6UbrW/AEAAAAA/f///wKAw8kBAAAAACIAIFwqEzbsUSf+/PC7SPfqGuy75tAydrgFkmv8DAEW6eNi5hwsBAAAAAAWABT9hflT6IkrLKaHS+x3zqee7ExP/AAAAAABAR/mHCwEAAAAABYAFP2F+VPoiSsspodL7HfOp57sTE/8IgYC420o3I+pkBOSVZyER/NTtZTLtP1iIKPdZK99ilI9UDoYJhS9xFQAAIABAACAAAAAgAEAAAAAAAAAAAAAIgICfM8IJ5ATUtpeuQOLpQbXkqvhs5mriKEpWLHod5vWAo4YJhS9xFQAAIABAACAAAAAgAEAAAACAAAAACICAujQqWO9vOEDwyUZqbeseGADN99ME/YWtJJTjW9/ag25GCYUvcRUAACAAQAAgAAAAIAAAAAAAwAAAAA=",
    # same transaction, version = 2
    "cHNidP8BAgQCAAAAAQMEAAAAAAEEAQMBBQEEAfsEAgAAAAABAHECAAAAAbjfTe1ELVuP5C+6ehmfu5iQQ7Xawvwy744pkzpRutb8AAAAAAD+////AmZ5dyMBAAAAFgAU1u2LYuVJyjt85u3tB1sYK6CPZPGAlpgAAAAAABYAFAghaKcgQc7JhkPqJJnHkezQlDEZAAAAAAEBH4CWmAAAAAAAFgAUCCFopyBBzsmGQ+okmceR7NCUMRkiBgKpgbM/frkqCs83N1CSo+92f5yWwCTvKhorwU4TVrkLXBgmFL3EVAAAgAEAAIAAAACAAAAAAAIAAAABDiDOz5t2nkwM8k7Kcz8ZWrG/RPE183rmVhNwaMDxwPY1AAEPBAEAAAABEAT9////AAEAfQIAAAABYyExv0nenCq5y2lcWWvwuxvAP3ZehIqS7C+g23mmsTIAAAAAAP3///8CgJaYAAAAAAAWABSa3cy+E6bx6+apJuyW7RUzDhqNNFwsMQEAAAAAIgAgDMBOL0aKP0j5YMyeAma9QlR90Gm9RzlIdd4HaQSuSscAAAAAAQEfgJaYAAAAAAAWABSa3cy+E6bx6+apJuyW7RUzDhqNNCIGA4GM6QXZ1Q9BV9kOC8CrUAmcjJefEid2YC/0Jq2p4c/2GCYUvcRUAACAAQAAgAAAAIAAAAAAAQAAAAEOIPVrYWk69b8V9Wfly3minQdZA9wXgDgKIPU+6HMzSG+tAQ8EAAAAAAEQBP3///8AAQB9AgAAAAG4303tRC1bj+QvunoZn7uYkEO12sL8Mu+OKZM6UbrW/AEAAAAA/f///wKAw8kBAAAAACIAIFwqEzbsUSf+/PC7SPfqGuy75tAydrgFkmv8DAEW6eNi5hwsBAAAAAAWABT9hflT6IkrLKaHS+x3zqee7ExP/AAAAAABAR/mHCwEAAAAABYAFP2F+VPoiSsspodL7HfOp57sTE/8IgYC420o3I+pkBOSVZyER/NTtZTLtP1iIKPdZK99ilI9UDoYJhS9xFQAAIABAACAAAAAgAEAAAAAAAAAAQ4gYyExv0nenCq5y2lcWWvwuxvAP3ZehIqS7C+g23mmsTIBDwQBAAAAARAE/f///wABAwieWGICAAAAAAEEFgAUVE9NQrxzbpzaj8DvSA4+Vfb3S88AAQMIgMPJAQAAAAABBCIAILUTol87mgHQEKeZJcuNUJjXFfUupwaSGuS7QTdBQ1TWACICAnzPCCeQE1LaXrkDi6UG15Kr4bOZq4ihKVix6Heb1gKOGCYUvcRUAACAAQAAgAAAAIABAAAAAgAAAAEDCOaVmAAAAAAAAQQWABSyijJ33ycsST0veZC7xCcjMLvGngAiAgLo0KljvbzhA8MlGam3rHhgAzffTBP2FrSSU41vf2oNuRgmFL3EVAAAgAEAAIAAAACAAAAAAAMAAAABAwiAlpgAAAAAAAEEFgAUfwP8SJ6OrkWYgi79Rmtboh4vb6AA",
    # nested segwit, 3 inp 3 outs
    "cHNidP8BAOMCAAAAAxTKkAuekd781k0dtVGHQB6rfsnXbOoRI8nCZaODOTuKAQAAAAD9////GzrJPRcrKOpTM/UuJtU9BHoPOauuwOwTbze8A98e9jkAAAAAAP3///8zUYet5YsLr1JURBtw6y1J+aOIFUYppSUF98NMQ0SGqAEAAAAA/f///wPA4eQAAAAAABYAFDi4Qvb/any4sD/j++An4QeQyZ8ZABu3AAAAAAAWABSZhIQZ8nU642HEO2Ad58HNlLuNX0LFLQAAAAAAF6kUmyPIidm0uYu4R4cd4G/ePLv/KL6HAAAAAAABAHICAAAAAc7Pm3aeTAzyTspzPxlasb9E8TXzeuZWE3BowPHA9jUAAAAAAAD+////Alji3iIBAAAAFgAUhtwMeBRjJ59uRi16BF//sCuKaQ+AlpgAAAAAABepFF/+tUQewdyL8GiqX8J+muPiFo88hwAAAAABASCAlpgAAAAAABepFF/+tUQewdyL8GiqX8J+muPiFo88hwEEFgAUlGEc5+39qmo7NT7GrA6unMA28sciBgObyQ41qMrOxju4XxzMINIB1ZGui3QjZEVfIZ7WGJnS2xgmFL3EMQAAgAEAAIAAAACAAAAAAAAAAAAAAQByAgAAAAGhyWgevahbYnWS3rmRhpZign4sGaq4mOdM4D8UKs2q1AAAAAAA/v///wKAlpgAAAAAABepFPAAkmx/dRJAKf+Lw49LAzeIh975h/JabSkBAAAAFgAUfWxZ1njvA0Yr7QVYEme4CDHh7qMAAAAAAQEggJaYAAAAAAAXqRTwAJJsf3USQCn/i8OPSwM3iIfe+YcBBBYAFM6hs2GlOxZSDUae67LI3aiHb3G/IgYCtyN18tSmyoFClQl6Fa87TtEnrMZH6NJoRooWChmuHd0YJhS9xDEAAIABAACAAAAAgAAAAAABAAAAAAEAcgIAAAABV2MIaeVqMWANJDD2CUpsLwzV+Kbyg5cpQYob80WZZSMAAAAAAP7///8C8lptKQEAAAAWABTXvwQx1pNX7/TEGt2BYOIovqhL4ICWmAAAAAAAF6kU9x5iJ+lOE1CyDvzfZ+6pFjHKnviHAAAAAAEBIICWmAAAAAAAF6kU9x5iJ+lOE1CyDvzfZ+6pFjHKnviHAQQWABQ5QbIFFdKLyxFSNm1hZ8S/28cjGSIGAz0meCzo95/qUlBrj9hp+T8hPjVPMIsYK7uTj4iq+Wl6GCYUvcQxAACAAQAAgAAAAIAAAAAAAgAAAAAAAAEAFgAUvQVfGjGkvg8XROgHZhlqhzm/ZOkiAgJXWxh/KFZ6O5bcwrw6M8BwIWtYoHYbrU7JqNOgbTtwxBgmFL3EMQAAgAEAAIAAAACAAQAAAAAAAAAA",
    # same psbt, version = 2
    "cHNidP8BAgQCAAAAAQMEAAAAAAEEAQMBBQEDAfsEAgAAAAABAHICAAAAAc7Pm3aeTAzyTspzPxlasb9E8TXzeuZWE3BowPHA9jUAAAAAAAD+////Alji3iIBAAAAFgAUhtwMeBRjJ59uRi16BF//sCuKaQ+AlpgAAAAAABepFF/+tUQewdyL8GiqX8J+muPiFo88hwAAAAABASCAlpgAAAAAABepFF/+tUQewdyL8GiqX8J+muPiFo88hwEEFgAUlGEc5+39qmo7NT7GrA6unMA28sciBgObyQ41qMrOxju4XxzMINIB1ZGui3QjZEVfIZ7WGJnS2xgmFL3EMQAAgAEAAIAAAACAAAAAAAAAAAABDiAUypALnpHe/NZNHbVRh0Aeq37J12zqESPJwmWjgzk7igEPBAEAAAABEAT9////AAEAcgIAAAABocloHr2oW2J1kt65kYaWYoJ+LBmquJjnTOA/FCrNqtQAAAAAAP7///8CgJaYAAAAAAAXqRTwAJJsf3USQCn/i8OPSwM3iIfe+YfyWm0pAQAAABYAFH1sWdZ47wNGK+0FWBJnuAgx4e6jAAAAAAEBIICWmAAAAAAAF6kU8ACSbH91EkAp/4vDj0sDN4iH3vmHAQQWABTOobNhpTsWUg1GnuuyyN2oh29xvyIGArcjdfLUpsqBQpUJehWvO07RJ6zGR+jSaEaKFgoZrh3dGCYUvcQxAACAAQAAgAAAAIAAAAAAAQAAAAEOIBs6yT0XKyjqUzP1LibVPQR6DzmrrsDsE283vAPfHvY5AQ8EAAAAAAEQBP3///8AAQByAgAAAAFXYwhp5WoxYA0kMPYJSmwvDNX4pvKDlylBihvzRZllIwAAAAAA/v///wLyWm0pAQAAABYAFNe/BDHWk1fv9MQa3YFg4ii+qEvggJaYAAAAAAAXqRT3HmIn6U4TULIO/N9n7qkWMcqe+IcAAAAAAQEggJaYAAAAAAAXqRT3HmIn6U4TULIO/N9n7qkWMcqe+IcBBBYAFDlBsgUV0ovLEVI2bWFnxL/bxyMZIgYDPSZ4LOj3n+pSUGuP2Gn5PyE+NU8wixgru5OPiKr5aXoYJhS9xDEAAIABAACAAAAAgAAAAAACAAAAAQ4gM1GHreWLC69SVEQbcOstSfmjiBVGKaUlBffDTENEhqgBDwQBAAAAARAE/f///wABAwjA4eQAAAAAAAEEFgAUOLhC9v9qfLiwP+P74CfhB5DJnxkAAQMIABu3AAAAAAABBBYAFJmEhBnydTrjYcQ7YB3nwc2Uu41fAAEAFgAUvQVfGjGkvg8XROgHZhlqhzm/ZOkiAgJXWxh/KFZ6O5bcwrw6M8BwIWtYoHYbrU7JqNOgbTtwxBgmFL3EMQAAgAEAAIAAAACAAQAAAAAAAAABAwhCxS0AAAAAAAEEF6kUmyPIidm0uYu4R4cd4G/ePLv/KL6HAA==",
    # legacy
    "cHNidP8BAMMCAAAAA/8CS6+D7pDNQd/uwrrbG8k78U9QFdzV559ZhQ1EVYGEAAAAAAD+////70WX911pnoWryXdRJlFlGJyuuh5NKwXkaz76h/meNogAAAAAAP7///+O9uy981BmAPPPKXis8gBJNmx+Q0cVhuVe2RaQJdI1BwAAAAAA/v///wJAeH0BAAAAABYAFPSXFN4nngE2GMROpImI5XRxRkCXP0lMAAAAAAAWABTpOJ/mb2jdP898bWAmZuD7Njn/awAAAAAAAQB0AgAAAAGCo1OTQLigQvL2iP3OK/kA4+1Mwv2Y4ErI0Zl4E5j3SQAAAAAA/v///wKAlpgAAAAAABl2qRQhggw2j7lZq5FRG/W8Wxtq9pwcbYis/eXoSQAAAAAWABT0gC7h3PBRXI9jFN868mhubmC+2gAAAAAiBgNoW2ak4dJxu2tMB1xKJ8i0IZ1upTKHilgX2NDjWlH58gwmFL3EAAAAAAEAAAAAAQB0AgAAAAGnjGh1Fuk1Y5dz4QE9r+3oEUfuAiK9MGm3tjnPkXvShgAAAAAA/v///wKAlpgAAAAAABl2qRSz5j68l/pmSGDdCy8X1hM5LcUaxYisFOboSQAAAAAWABTh9XHu7fEdxJ2bqU/DGbrfvU42UAAAAAAiBgNYvS1u+b8fq7DcmL63qHttHinxbZLp9rhbOnmyBUCeTwwmFL3EAAAAAAIAAAAAAQB0AgAAAAFgB75i+OT1O7SqGBsTmTajlxpDsnLc/iwxwfbvr6jLVwAAAAAA/v///wKAlpgAAAAAABl2qRQBEqxvaLOlc1lLiadG5yQ5bilaRYisxf7yQwAAAAAWABTCShfxLkJyJ3/wjVLDRtChyK5v1QAAAAAiBgMmekh8kmFBB8c2g+PQLCgSuginKK+UJuxobtdRZ4FAiAwmFL3EAAAAAAAAAAAAACICAvjQsARdPkCfLoeR/ysMrMfEKmvRZ7guQexDh5x6Tpj7EGMusisAAACAAQAAgAEAAIAA",
    # same, psbt version 2
    "cHNidP8BAgQCAAAAAQMEAAAAAAEEAQMBBQECAfsEAgAAAAABAHQCAAAAAYKjU5NAuKBC8vaI/c4r+QDj7UzC/ZjgSsjRmXgTmPdJAAAAAAD+////AoCWmAAAAAAAGXapFCGCDDaPuVmrkVEb9bxbG2r2nBxtiKz95ehJAAAAABYAFPSALuHc8FFcj2MU3zryaG5uYL7aAAAAACIGA2hbZqTh0nG7a0wHXEonyLQhnW6lMoeKWBfY0ONaUfnyDCYUvcQAAAAAAQAAAAEOIP8CS6+D7pDNQd/uwrrbG8k78U9QFdzV559ZhQ1EVYGEAQ8EAAAAAAEQBP7///8AAQB0AgAAAAGnjGh1Fuk1Y5dz4QE9r+3oEUfuAiK9MGm3tjnPkXvShgAAAAAA/v///wKAlpgAAAAAABl2qRSz5j68l/pmSGDdCy8X1hM5LcUaxYisFOboSQAAAAAWABTh9XHu7fEdxJ2bqU/DGbrfvU42UAAAAAAiBgNYvS1u+b8fq7DcmL63qHttHinxbZLp9rhbOnmyBUCeTwwmFL3EAAAAAAIAAAABDiDvRZf3XWmehavJd1EmUWUYnK66Hk0rBeRrPvqH+Z42iAEPBAAAAAABEAT+////AAEAdAIAAAABYAe+Yvjk9Tu0qhgbE5k2o5caQ7Jy3P4sMcH276+oy1cAAAAAAP7///8CgJaYAAAAAAAZdqkUARKsb2izpXNZS4mnRuckOW4pWkWIrMX+8kMAAAAAFgAUwkoX8S5Ccid/8I1Sw0bQociub9UAAAAAIgYDJnpIfJJhQQfHNoPj0CwoEroIpyivlCbsaG7XUWeBQIgMJhS9xAAAAAAAAAAAAQ4gjvbsvfNQZgDzzyl4rPIASTZsfkNHFYblXtkWkCXSNQcBDwQAAAAAARAE/v///wABAwhAeH0BAAAAAAEEFgAU9JcU3ieeATYYxE6kiYjldHFGQJcAIgIC+NCwBF0+QJ8uh5H/Kwysx8Qqa9FnuC5B7EOHnHpOmPsQYy6yKwAAAIABAACAAQAAgAEDCD9JTAAAAAAAAQQWABTpOJ/mb2jdP898bWAmZuD7Njn/awA=",
    # 1-of-2 multisig
    "cHNidP8BAMMCAAAAA+9Fl/ddaZ6Fq8l3USZRZRicrroeTSsF5Gs++of5njaIAAAAAAD+/////wJLr4PukM1B3+7CutsbyTvxT1AV3NXnn1mFDURVgYQAAAAAAP7///+O9uy981BmAPPPKXis8gBJNmx+Q0cVhuVe2RaQJdI1BwAAAAAA/v///wI/SUwAAAAAABYAFCcLXGMkxtEHdt09w5nGGub5dJ8JQHh9AQAAAAAWABT0lxTeJ54BNhjETqSJiOV0cUZAlwAAAAAAAQB0AgAAAAGnjGh1Fuk1Y5dz4QE9r+3oEUfuAiK9MGm3tjnPkXvShgAAAAAA/v///wKAlpgAAAAAABl2qRSz5j68l/pmSGDdCy8X1hM5LcUaxYisFOboSQAAAAAWABTh9XHu7fEdxJ2bqU/DGbrfvU42UAAAAAAiBgNYvS1u+b8fq7DcmL63qHttHinxbZLp9rhbOnmyBUCeTwwmFL3EAAAAAAIAAAAAAQB0AgAAAAGCo1OTQLigQvL2iP3OK/kA4+1Mwv2Y4ErI0Zl4E5j3SQAAAAAA/v///wKAlpgAAAAAABl2qRQhggw2j7lZq5FRG/W8Wxtq9pwcbYis/eXoSQAAAAAWABT0gC7h3PBRXI9jFN868mhubmC+2gAAAAAiBgNoW2ak4dJxu2tMB1xKJ8i0IZ1upTKHilgX2NDjWlH58gwmFL3EAAAAAAEAAAAAAQB0AgAAAAFgB75i+OT1O7SqGBsTmTajlxpDsnLc/iwxwfbvr6jLVwAAAAAA/v///wKAlpgAAAAAABl2qRQBEqxvaLOlc1lLiadG5yQ5bilaRYisxf7yQwAAAAAWABTCShfxLkJyJ3/wjVLDRtChyK5v1QAAAAAiBgMmekh8kmFBB8c2g+PQLCgSuginKK+UJuxobtdRZ4FAiAwmFL3EAAAAAAAAAAAAIgICv+hNVXqScnRDcCJsJcq35scU0SsSp08WNnVklPIs/DkQYy6yKwAAAIABAACAAwAAgAAA",
    # same, version 2
    "cHNidP8BAgQCAAAAAQMEAAAAAAEEAQMBBQECAfsEAgAAAAABAHQCAAAAAaeMaHUW6TVjl3PhAT2v7egRR+4CIr0wabe2Oc+Re9KGAAAAAAD+////AoCWmAAAAAAAGXapFLPmPryX+mZIYN0LLxfWEzktxRrFiKwU5uhJAAAAABYAFOH1ce7t8R3EnZupT8MZut+9TjZQAAAAACIGA1i9LW75vx+rsNyYvreoe20eKfFtkun2uFs6ebIFQJ5PDCYUvcQAAAAAAgAAAAEOIO9Fl/ddaZ6Fq8l3USZRZRicrroeTSsF5Gs++of5njaIAQ8EAAAAAAEQBP7///8AAQB0AgAAAAGCo1OTQLigQvL2iP3OK/kA4+1Mwv2Y4ErI0Zl4E5j3SQAAAAAA/v///wKAlpgAAAAAABl2qRQhggw2j7lZq5FRG/W8Wxtq9pwcbYis/eXoSQAAAAAWABT0gC7h3PBRXI9jFN868mhubmC+2gAAAAAiBgNoW2ak4dJxu2tMB1xKJ8i0IZ1upTKHilgX2NDjWlH58gwmFL3EAAAAAAEAAAABDiD/Akuvg+6QzUHf7sK62xvJO/FPUBXc1eefWYUNRFWBhAEPBAAAAAABEAT+////AAEAdAIAAAABYAe+Yvjk9Tu0qhgbE5k2o5caQ7Jy3P4sMcH276+oy1cAAAAAAP7///8CgJaYAAAAAAAZdqkUARKsb2izpXNZS4mnRuckOW4pWkWIrMX+8kMAAAAAFgAUwkoX8S5Ccid/8I1Sw0bQociub9UAAAAAIgYDJnpIfJJhQQfHNoPj0CwoEroIpyivlCbsaG7XUWeBQIgMJhS9xAAAAAAAAAAAAQ4gjvbsvfNQZgDzzyl4rPIASTZsfkNHFYblXtkWkCXSNQcBDwQAAAAAARAE/v///wAiAgK/6E1VepJydENwImwlyrfmxxTRKxKnTxY2dWSU8iz8ORBjLrIrAAAAgAEAAIADAACAAQMIP0lMAAAAAAABBBYAFCcLXGMkxtEHdt09w5nGGub5dJ8JAAEDCEB4fQEAAAAAAQQWABT0lxTeJ54BNhjETqSJiOV0cUZAlwA=",
]