Ejemplo n.º 1
0
def test_wallet_change_addresses(bitcoin_regtest, devices_filled_data_folder, device_manager):
    wm = WalletManager(devices_filled_data_folder, bitcoin_regtest.get_cli(), "regtest", device_manager)
    # A wallet-creation needs a device
    device = device_manager.get_by_alias('specter')
    key = Key.from_json({
        "derivation": "m/48h/1h/0h/2h",
        "original": "Vpub5n9kKePTPPGtw3RddeJWJe29epEyBBcoHbbPi5HhpoG2kTVsSCUzsad33RJUt3LktEUUPPofcZczuudnwR7ZgkAkT6N2K2Z7wdyjYrVAkXM",
        "fingerprint": "08686ac6",
        "type": "wsh",
        "xpub": "tpubDFHpKypXq4kwUrqLotPs6fCic5bFqTRGMBaTi9s5YwwGymE8FLGwB2kDXALxqvNwFxB1dLWYBmmeFVjmUSdt2AsaQuPmkyPLBKRZW8BGCiL"
    })
    wallet = wm.create_wallet('a_second_test_wallet', 1, 'wpkh', [key], [device])

    address = wallet.address
    change_address = wallet.change_address
    assert wallet.addresses == [address]
    assert wallet.change_addresses == [change_address]
    assert wallet.active_addresses == [address]
    assert wallet.labels == ['Address #0']

    wallet.cli.generatetoaddress(20, change_address)
    random_address = "mruae2834buqxk77oaVpephnA5ZAxNNJ1r"
    wallet.cli.generatetoaddress(110, random_address)
    wallet.getdata()

    # new change address should be genrated automatically after receiving
    # assert wallet.change_addresses == [change_address, wallet.change_address]
    # This will not work here since Bitcoin Core doesn't count mining rewards in `getreceivedbyaddress`
    # See: https://github.com/bitcoin/bitcoin/issues/14654

    assert wallet.active_addresses == [address, change_address]
    # labels should return only active addresses
    assert wallet.labels == ['Address #0', 'Change #0']
def test_wallet_change_addresses(
    bitcoin_regtest, devices_filled_data_folder, device_manager
):
    wm = WalletManager(
        200100,
        devices_filled_data_folder,
        bitcoin_regtest.get_rpc(),
        "regtest",
        device_manager,
        allow_threading=False,
    )
    # A wallet-creation needs a device
    device = device_manager.get_by_alias("specter")
    key = Key.from_json(
        {
            "derivation": "m/48h/1h/0h/2h",
            "original": "Vpub5n9kKePTPPGtw3RddeJWJe29epEyBBcoHbbPi5HhpoG2kTVsSCUzsad33RJUt3LktEUUPPofcZczuudnwR7ZgkAkT6N2K2Z7wdyjYrVAkXM",
            "fingerprint": "08686ac6",
            "type": "wsh",
            "xpub": "tpubDFHpKypXq4kwUrqLotPs6fCic5bFqTRGMBaTi9s5YwwGymE8FLGwB2kDXALxqvNwFxB1dLWYBmmeFVjmUSdt2AsaQuPmkyPLBKRZW8BGCiL",
        }
    )
    wallet = wm.create_wallet("a_second_test_wallet", 1, "wpkh", [key], [device])

    address = wallet.address
    change_address = wallet.change_address
    assert wallet.addresses == [address]
    assert wallet.change_addresses == [change_address]

    wallet.rpc.generatetoaddress(20, change_address)
    random_address = "mruae2834buqxk77oaVpephnA5ZAxNNJ1r"
    wallet.rpc.generatetoaddress(110, random_address)
    wallet.getdata()
Ejemplo n.º 3
0
def test_wallet_createpsbt(bitcoin_regtest, devices_filled_data_folder, device_manager):
    wm = WalletManager(devices_filled_data_folder, bitcoin_regtest.get_cli(), "regtest", device_manager)
    # A wallet-creation needs a device
    device = device_manager.get_by_alias('specter')
    key = Key.from_json({
        "derivation": "m/48h/1h/0h/2h",
        "original": "Vpub5n9kKePTPPGtw3RddeJWJe29epEyBBcoHbbPi5HhpoG2kTVsSCUzsad33RJUt3LktEUUPPofcZczuudnwR7ZgkAkT6N2K2Z7wdyjYrVAkXM",
        "fingerprint": "08686ac6",
        "type": "wsh",
        "xpub": "tpubDFHpKypXq4kwUrqLotPs6fCic5bFqTRGMBaTi9s5YwwGymE8FLGwB2kDXALxqvNwFxB1dLWYBmmeFVjmUSdt2AsaQuPmkyPLBKRZW8BGCiL"
    })
    wallet = wm.create_wallet('a_second_test_wallet', 1, 'wpkh', [key], [device])
    # Let's fund the wallet with ... let's say 40 blocks a 50 coins each --> 200 coins
    address = wallet.getnewaddress()
    assert address == 'bcrt1qtnrv2jpygx2ef3zqfjhqplnycxak2m6ljnhq6z'
    wallet.cli.generatetoaddress(20, address)
    # in two addresses
    address = wallet.getnewaddress()
    wallet.cli.generatetoaddress(20, address)
    # newly minted coins need 100 blocks to get spendable
    # let's mine another 100 blocks to get these coins spendable
    random_address = "mruae2834buqxk77oaVpephnA5ZAxNNJ1r"
    wallet.cli.generatetoaddress(110, random_address)
    # update the wallet data
    wallet.get_balance()
    # Now we have loads of potential inputs
    # Let's spend 500 coins
    assert wallet.fullbalance >= 250
    # From this print-statement, let's grab some txids which we'll use for coinselect
    unspents = wallet.cli.listunspent(0)
    # Lets take 3 more or less random txs from the unspents:
    selected_coins = [
        "{},{},{}".format(unspents[5]['txid'], unspents[5]['vout'], unspents[5]['amount']), 
        "{},{},{}".format(unspents[9]['txid'], unspents[9]['vout'], unspents[9]['amount']),
        "{},{},{}".format(unspents[12]['txid'], unspents[12]['vout'], unspents[12]['amount'])
    ]
    selected_coins_amount_sum = unspents[5]['amount'] + unspents[9]['amount'] + unspents[12]['amount']
    number_of_coins_to_spend = selected_coins_amount_sum - 0.1 # Let's spend almost all of them 
    psbt = wallet.createpsbt([random_address], [number_of_coins_to_spend], True, 10, selected_coins=selected_coins)
    assert len(psbt['tx']['vin']) == 3
    psbt_txs = [ tx['txid'] for tx in psbt['tx']['vin'] ]
    for coin in selected_coins:
        assert coin.split(",")[0] in psbt_txs

    # Now let's spend more coins than we have selected. This should result in an exception:
    try:
        psbt = wallet.createpsbt([random_address], [number_of_coins_to_spend +1], True, 10, selected_coins=selected_coins)
        assert False, "should throw an exception!"
    except SpecterError as e:
        pass

    assert wallet.locked_amount == selected_coins_amount_sum
    assert len(wallet.cli.listlockunspent()) == 3
    assert wallet.full_available_balance == wallet.fullbalance - selected_coins_amount_sum

    wallet.delete_pending_psbt(psbt['tx']['txid'])
    assert wallet.locked_amount == 0
    assert len(wallet.cli.listlockunspent()) == 0
    assert wallet.full_available_balance == wallet.fullbalance
def test_wallet_labeling(bitcoin_regtest, devices_filled_data_folder, device_manager):
    wm = WalletManager(
        200100,
        devices_filled_data_folder,
        bitcoin_regtest.get_rpc(),
        "regtest",
        device_manager,
        allow_threading=False,
    )
    # A wallet-creation needs a device
    device = device_manager.get_by_alias("specter")
    key = Key.from_json(
        {
            "derivation": "m/48h/1h/0h/2h",
            "original": "Vpub5n9kKePTPPGtw3RddeJWJe29epEyBBcoHbbPi5HhpoG2kTVsSCUzsad33RJUt3LktEUUPPofcZczuudnwR7ZgkAkT6N2K2Z7wdyjYrVAkXM",
            "fingerprint": "08686ac6",
            "type": "wsh",
            "xpub": "tpubDFHpKypXq4kwUrqLotPs6fCic5bFqTRGMBaTi9s5YwwGymE8FLGwB2kDXALxqvNwFxB1dLWYBmmeFVjmUSdt2AsaQuPmkyPLBKRZW8BGCiL",
        }
    )
    wallet = wm.create_wallet("a_second_test_wallet", 1, "wpkh", [key], [device])

    address = wallet.address
    assert wallet.getlabel(address) == "Address #0"
    wallet.setlabel(address, "Random label")
    assert wallet.getlabel(address) == "Random label"

    wallet.rpc.generatetoaddress(20, address)

    random_address = "mruae2834buqxk77oaVpephnA5ZAxNNJ1r"
    wallet.rpc.generatetoaddress(100, random_address)

    # update utxo
    wallet.getdata()
    # update balance
    wallet.get_balance()

    address_balance = wallet.fullbalance
    assert len(wallet.full_utxo) == 20

    new_address = wallet.getnewaddress()
    wallet.setlabel(new_address, "")
    wallet.rpc.generatetoaddress(20, new_address)

    random_address = "mruae2834buqxk77oaVpephnA5ZAxNNJ1r"
    wallet.rpc.generatetoaddress(100, random_address)

    wallet.getdata()
    wallet.get_balance()

    assert len(wallet.full_utxo) == 40

    wallet.setlabel(new_address, "")
    third_address = wallet.getnewaddress()

    wallet.getdata()
    assert sorted(wallet.addresses) == sorted([address, new_address, third_address])
Ejemplo n.º 5
0
 def _check_duplicate_keys(cls, keys):
     """raise a SpecterError when a xpub in the passed KeyList is listed twice. Should prevent MultisigWallets where
     xpubs are used twice.
     """
     # normalizing xpubs in order to ignore slip132 differences
     xpubs = [Key.parse_xpub(key.original).xpub for key in keys]
     for xpub in xpubs:
         if xpubs.count(xpub) > 1:
             raise SpecterError(_(f"xpub {xpub} seem to be used at least twice!"))
Ejemplo n.º 6
0
def test_write_device(app):
    a_key = Key(
        "Vpub5n9kKePTPPGtw3RddeJWJe29epEyBBcoHbbPi5HhpoG2kTVsSCUzsad33RJUt3LktEUUPPofcZczuudnwR7ZgkAkT6N2K2Z7wdyjYrVAkXM",
        "08686ac6",
        "m/48h/1h/0h/2h",
        "wsh",
        "tpubDFHpKypXq4kwUrqLotPs6fCic5bFqTRGMBaTi9s5YwwGymE8FLGwB2kDXALxqvNwFxB1dLWYBmmeFVjmUSdt2AsaQuPmkyPLBKRZW8BGCiL",
    )
    # the DeviceManager doesn't care so much about the content of a key
    # so this is a minimal valid "key":
    another_key = Key.from_json(
        {
            "original": "tpubDDZ5jjGT5RvrAyjoLZfdCfv1PAPmicnhNctwZGKiCMF1Zy5hCGMqppxwYZzWgvPqk7LucMMHo7rkB6Dyj5ZLd2W62FAEP3U6pV4jD5gb9ma"
        }
    )
    app.specter.device_manager.add_device("some_name", "the_type", [a_key, another_key])
    write_device(
        app.specter.device_manager.get_by_alias("some_name"),
        "/tmp/delete_me_test_file.json",
    )
    os.remove("/tmp/delete_me_test_file.json")
Ejemplo n.º 7
0
    def parse_signers(self, devices, cosigners_types):
        keys = []
        cosigners = []
        unknown_cosigners = []
        unknown_cosigners_types = []

        if self.multisig_N == None:
            self.multisig_N = 1
            self.multisig_M = 1
            self.origin_fingerprint = [self.origin_fingerprint]
            self.origin_path = [self.origin_path]
            self.base_key = [self.base_key]
        for i in range(self.multisig_N):
            cosigner_found = False
            for device in devices:
                cosigner = devices[device]
                if self.origin_fingerprint[i] is None:
                    self.origin_fingerprint[i] = ""
                if self.origin_path[i] is None:
                    self.origin_path[i] = self.origin_fingerprint[i]
                for key in cosigner.keys:
                    if key.fingerprint + key.derivation.replace(
                        "m", ""
                    ) == self.origin_fingerprint[i] + self.origin_path[i].replace(
                        "'", "h"
                    ):
                        keys.append(key)
                        cosigners.append(cosigner)
                        cosigner_found = True
                        break
                if cosigner_found:
                    break
            if not cosigner_found:
                desc_key = Key.parse_xpub(
                    "[{}{}]{}".format(
                        self.origin_fingerprint[i],
                        self.origin_path[i],
                        self.base_key[i],
                    )
                )
                if len(cosigners_types) > i:
                    unknown_cosigners.append((desc_key, cosigners_types[i]["label"]))
                else:
                    unknown_cosigners.append((desc_key, None))
                if len(unknown_cosigners) > len(cosigners_types):
                    unknown_cosigners_types.append("other")
                else:
                    unknown_cosigners_types.append(cosigners_types[i]["type"])

        return (keys, cosigners, unknown_cosigners, unknown_cosigners_types)
Ejemplo n.º 8
0
    def parse_signers(self, devices, cosigners_types):
        keys = []
        cosigners = []
        unknown_cosigners = []
        unknown_cosigners_types = []

        for i, descriptor_key in enumerate(self.descriptor.keys):
            # remove derivation from the key for comparison
            account_key = DescriptorKey.from_string(str(descriptor_key))
            account_key.allowed_derivation = None
            # Specter Key class
            desc_key = Key.parse_xpub(str(account_key))
            cosigner_found = False
            for cosigner in devices.values():
                for key in cosigner.keys:
                    # check key matches
                    if key.to_string(slip132=False) == desc_key.to_string(
                        slip132=False
                    ):
                        keys.append(key)
                        cosigners.append(cosigner)
                        cosigner_found = True
                        break
                if cosigner_found:
                    break
            if not cosigner_found:
                if len(cosigners_types) > i:
                    unknown_cosigners.append((desc_key, cosigners_types[i]["label"]))
                else:
                    unknown_cosigners.append((desc_key, None))
                if len(unknown_cosigners) > len(cosigners_types):
                    unknown_cosigners_types.append("other")
                else:
                    unknown_cosigners_types.append(cosigners_types[i]["type"])

        return (keys, cosigners, unknown_cosigners, unknown_cosigners_types)
def test_wallet_labeling(bitcoin_regtest, devices_filled_data_folder,
                         device_manager):
    wm = WalletManager(devices_filled_data_folder, bitcoin_regtest.get_rpc(),
                       "regtest", device_manager)
    # A wallet-creation needs a device
    device = device_manager.get_by_alias('specter')
    key = Key.from_json({
        "derivation":
        "m/48h/1h/0h/2h",
        "original":
        "Vpub5n9kKePTPPGtw3RddeJWJe29epEyBBcoHbbPi5HhpoG2kTVsSCUzsad33RJUt3LktEUUPPofcZczuudnwR7ZgkAkT6N2K2Z7wdyjYrVAkXM",
        "fingerprint":
        "08686ac6",
        "type":
        "wsh",
        "xpub":
        "tpubDFHpKypXq4kwUrqLotPs6fCic5bFqTRGMBaTi9s5YwwGymE8FLGwB2kDXALxqvNwFxB1dLWYBmmeFVjmUSdt2AsaQuPmkyPLBKRZW8BGCiL"
    })
    wallet = wm.create_wallet('a_second_test_wallet', 1, 'wpkh', [key],
                              [device])

    address = wallet.address
    assert wallet.getlabel(address) == 'Address #0'
    wallet.setlabel(address, 'Random label')
    assert wallet.getlabel(address) == 'Random label'

    wallet.rpc.generatetoaddress(20, address)

    random_address = "mruae2834buqxk77oaVpephnA5ZAxNNJ1r"
    wallet.rpc.generatetoaddress(100, random_address)

    # update utxo
    wallet.getdata()
    # update balance
    wallet.get_balance()

    address_balance = wallet.fullbalance
    assert len(wallet.utxo) == 20
    assert wallet.is_current_address_used
    assert wallet.balance_on_address(address) == address_balance
    assert wallet.balance_on_label('Random label') == address_balance
    assert wallet.addresses_on_label('Random label') == [address]
    assert wallet.utxo_addresses == [address]
    assert wallet.utxo_labels == ['Random label']
    assert wallet.utxo_addresses == [address]

    new_address = wallet.getnewaddress()
    wallet.setlabel(new_address, '')
    wallet.rpc.generatetoaddress(20, new_address)

    random_address = "mruae2834buqxk77oaVpephnA5ZAxNNJ1r"
    wallet.rpc.generatetoaddress(100, random_address)

    wallet.getdata()
    wallet.get_balance()

    assert len(wallet.utxo) == 40
    assert wallet.is_current_address_used
    assert wallet.utxo_on_address(address) == 20
    assert wallet.balance_on_address(
        new_address) == wallet.fullbalance - address_balance
    assert sorted(wallet.utxo_addresses) == sorted([address, new_address])
    assert sorted(wallet.utxo_labels) == sorted(['Random label', new_address])
    assert sorted(wallet.utxo_addresses) == sorted([address, new_address])
    assert wallet.get_address_name(new_address, -1) == new_address
    assert wallet.get_address_name(new_address, 5) == 'Address #5'
    assert wallet.get_address_name(address, 5) == 'Random label'

    wallet.setlabel(new_address, '')
    third_address = wallet.getnewaddress()

    wallet.getdata()
    assert sorted(wallet.labels) == sorted(
        ['Random label', new_address, 'Address #2'])
    assert sorted(wallet.utxo_labels) == sorted(['Random label', new_address])
    assert sorted(wallet.addresses) == sorted(
        [address, new_address, third_address])
    assert sorted(wallet.utxo_addresses) == sorted([address, new_address])

    wallet.setlabel(third_address, 'Random label')
    wallet.getdata()
    assert sorted(wallet.addresses_on_label('Random label')) == sorted(
        [address, third_address])
def test_DeviceManager(empty_data_folder):
    # A DeviceManager manages devices, specifically the persistence
    # of them via json-files in an empty data folder
    dm = DeviceManager(data_folder=empty_data_folder)
    # initialization will load from the folder but it's empty at first
    assert len(dm.devices) == 0
    # a device has a name, a type and a list of keys
    a_key = Key(
        'Vpub5n9kKePTPPGtw3RddeJWJe29epEyBBcoHbbPi5HhpoG2kTVsSCUzsad33RJUt3LktEUUPPofcZczuudnwR7ZgkAkT6N2K2Z7wdyjYrVAkXM',
        '08686ac6', 'm/48h/1h/0h/2h', 'wsh',
        'tpubDFHpKypXq4kwUrqLotPs6fCic5bFqTRGMBaTi9s5YwwGymE8FLGwB2kDXALxqvNwFxB1dLWYBmmeFVjmUSdt2AsaQuPmkyPLBKRZW8BGCiL'
    )
    # the DeviceManager doesn't care so much about the content of a key
    # so this is a minimal valid "key":
    another_key = Key.from_json({'original': 'blub'})
    dm.add_device("some_name", "the_type", [a_key, another_key])
    # A json file was generated for the new device:
    assert os.path.isfile(dm.devices['some_name'].fullpath)
    # You can access the new device either by its name of with `get_by_alias` by its alias
    assert dm.get_by_alias('some_name').name == 'some_name'
    assert dm.get_by_alias('some_name').device_type == 'the_type'
    assert dm.get_by_alias('some_name').keys[0].fingerprint == '08686ac6'
    # Now it has a length of 1
    assert len(dm.devices) == 1
    # and is iterable
    assert [the_type.device_type
            for the_type in dm.devices.values()] == ['the_type']
    # The DeviceManager will return Device-Types (subclass of dict)
    assert type(dm.devices['some_name']) == Device

    # The DeviceManager also has a `devices_names` property, returning a sorted list of the names of all devices
    assert dm.devices_names == ['some_name']
    dm.add_device("another_name", "the_type", [a_key, another_key])
    assert dm.devices_names == ['another_name', 'some_name']

    # You can also remove a device - which will delete its json and remove it from the manager
    another_device_fullpath = dm.devices['another_name'].fullpath
    assert os.path.isfile(another_device_fullpath)
    dm.remove_device(dm.devices['another_name'])
    assert not os.path.isfile(another_device_fullpath)
    assert len(dm.devices) == 1
    assert dm.devices_names == ['some_name']

    # A device is mainly a Domain-Object which assumes an underlying
    # json-file which can be found in the "fullpath"-key
    # It derives from a dict
    # It needs a DeviceManager to be injected and can't reasonable
    # be created on your own.
    # It has 5 dict keys: `fullpath`, `alias`, `name`, `type`, `keys`
    some_device = dm.devices['some_name']
    assert some_device.fullpath == empty_data_folder + '/some_name.json'
    assert some_device.alias == 'some_name'
    assert some_device.name == 'some_name'
    assert some_device.device_type == 'the_type'
    assert len(some_device.keys) == 2
    assert some_device.keys[0] == a_key
    assert some_device.keys[1] == another_key

    # Keys can be added and removed. It will instantly update the underlying json
    # Adding keys can be done by passing an array of keys object to the `add_keys` method of a device
    # A key dict must contain an `original` property
    third_key = Key.from_json({'original': 'third_key'})
    some_device.add_keys([third_key])
    assert len(some_device.keys) == 3
    assert some_device.keys[0] == a_key
    assert some_device.keys[1] == another_key
    assert some_device.keys[2] == third_key

    # adding an existing key will do nothing
    some_device.add_keys([third_key])
    assert len(some_device.keys) == 3
    assert some_device.keys[0] == a_key
    assert some_device.keys[1] == another_key
    assert some_device.keys[2] == third_key

    # Removing a key can be done by passing the `original` property of the key to remove to the `remove_key` method of a device
    some_device.remove_key(third_key)
    assert len(some_device.keys) == 2
    assert some_device.keys[0] == a_key
    assert some_device.keys[1] == another_key

    # removing a none existing key will do nothing
    some_device.remove_key(third_key)
    assert len(some_device.keys) == 2
    assert some_device.keys[0] == a_key
    assert some_device.keys[1] == another_key
Ejemplo n.º 11
0
def test_fingerprint(ghost_machine_xpub_44):
    key = Key.parse_xpub(ghost_machine_xpub_44)
    assert key.fingerprint == "81f802e3"
Ejemplo n.º 12
0
def test_derivation(ghost_machine_zpub):
    key = Key.parse_xpub(f"[81f802e3/84'/0'/3]{ghost_machine_zpub}")
    assert key.derivation == "m/84h/0h/3"
Ejemplo n.º 13
0
def test_WalletManager_check_duplicate_keys(empty_data_folder):
    wm = WalletManager(
        200100,
        empty_data_folder,
        None,
        "regtest",
        None,
        allow_threading=False,
    )
    key1 = Key(
        "[f3e6eaff/84h/0h/0h]xpub6C5cCQfycZrPJnNg6cDdUU5efJrab8thRQDBxSSB4gP2J3xGdWu8cqiLvPZkejtuaY9LursCn6Es9PqHgLhBktW8217BomGDVBAJjUms8iG",
        "f3e6eaff",
        "84h/0h/0h",
        "",
        None,
        "xpub6C5cCQfycZrPJnNg6cDdUU5efJrab8thRQDBxSSB4gP2J3xGdWu8cqiLvPZkejtuaY9LursCn6Es9PqHgLhBktW8217BomGDVBAJjUms8iG",
    )
    key2 = Key(
        "[1ef4e492/49h/0h/0h]xpub6CRWp2zfwRYsVTuT2p96hKE2UT4vjq9gwvW732KWQjwoG7v6NCXyaTdz7NE5yDxsd72rAGK7qrjF4YVrfZervsJBjsXxvTL98Yhc7poBk7K",
        "1ef4e492",
        "m/49h/0h/0h",
        "sh-wpkh",
        None,
        "xpub6CRWp2zfwRYsVTuT2p96hKE2UT4vjq9gwvW732KWQjwoG7v6NCXyaTdz7NE5yDxsd72rAGK7qrjF4YVrfZervsJBjsXxvTL98Yhc7poBk7K",
    )
    key3 = Key(
        "[1ef4e492/49h/0h/0h]zpub6qk8ok1ouvwM1NkumKnsteGf1F9UUNshFdFdXEDwph8nQFaj8qEFry2cxoUveZCkPpNxQp4KhQwxuy4R7jXDMMsKkgW2yauC2dHbWYnr2Ee",
        "1ef4e492",
        "m/49h/0h/0h",
        "sh-wpkh",
        None,
        "zpub6qk8ok1ouvwM1NkumKnsteGf1F9UUNshFdFdXEDwph8nQFaj8qEFry2cxoUveZCkPpNxQp4KhQwxuy4R7jXDMMsKkgW2yauC2dHbWYnr2Ee",
    )

    key4 = Key(
        "[6ea15da6/49h/0h/0h]xpub6BtcNhqbaFaoC3oEfKky3Sm22pF48U2jmAf78cB3wdAkkGyAgmsVrgyt1ooSt3bHWgzsdUQh2pTJ867yTeUAMmFDKNSBp8J7WPmp7Df7zjv",
        "6ea15da6",
        "m/49h/0h/0h",
        "sh-wpkh",
        None,
        "xpub6BtcNhqbaFaoC3oEfKky3Sm22pF48U2jmAf78cB3wdAkkGyAgmsVrgyt1ooSt3bHWgzsdUQh2pTJ867yTeUAMmFDKNSBp8J7WPmp7Df7zjv",
    )

    key5 = Key(
        "[6ea15da6/49h/0h/0h]xpub6BtcNhqbaFaoG3xcuncx9xzL3X38FuWXdcdvsdG5Q99Cb4EgeVYPEYaVpX28he6472gEsCokg8v9oMVRTrZNe5LHtGSPcC5ofehYkhD1Kxy",
        "6ea15da6",
        "m/49h/0h/1h",
        "sh-wpkh",
        None,  # slightly different ypub than key4
        "xpub6BtcNhqbaFaoG3xcuncx9xzL3X38FuWXdcdvsdG5Q99Cb4EgeVYPEYaVpX28he6472gEsCokg8v9oMVRTrZNe5LHtGSPcC5ofehYkhD1Kxy",
    )

    # Case 1: Identical keys
    keys = [key1, key1]
    with pytest.raises(SpecterError):
        wm._check_duplicate_keys(keys)
    # Case 2: different keys
    # key2 and 3 are different as they don't have the same xpub. See #1500 for discussion
    keys = [key1, key2, key3]  # key2 xpub is the same than key3 zpub
    with pytest.raises(SpecterError):
        wm._check_duplicate_keys(keys)

    keys = [key4, key5]
    wm._check_duplicate_keys(keys)
Ejemplo n.º 14
0
def test_key_type(ghost_machine_ypub):
    key = Key.parse_xpub(ghost_machine_ypub)
    assert key.key_type == "sh-wpkh"
Ejemplo n.º 15
0
def test_wallet_labeling(bitcoin_regtest, devices_filled_data_folder,
                         device_manager):
    wm = WalletManager(
        200100,
        devices_filled_data_folder,
        bitcoin_regtest.get_rpc(),
        "regtest",
        device_manager,
        allow_threading=False,
    )
    # A wallet-creation needs a device
    device = device_manager.get_by_alias("specter")
    key = Key.from_json({
        "derivation":
        "m/48h/1h/0h/2h",
        "original":
        "Vpub5n9kKePTPPGtw3RddeJWJe29epEyBBcoHbbPi5HhpoG2kTVsSCUzsad33RJUt3LktEUUPPofcZczuudnwR7ZgkAkT6N2K2Z7wdyjYrVAkXM",
        "fingerprint":
        "08686ac6",
        "type":
        "wsh",
        "xpub":
        "tpubDFHpKypXq4kwUrqLotPs6fCic5bFqTRGMBaTi9s5YwwGymE8FLGwB2kDXALxqvNwFxB1dLWYBmmeFVjmUSdt2AsaQuPmkyPLBKRZW8BGCiL",
    })
    wallet = wm.create_wallet("a_second_test_wallet", 1, "wpkh", [key],
                              [device])

    address = wallet.address
    assert wallet.getlabel(address) == "Address #0"
    wallet.setlabel(address, "Random label")
    assert wallet.getlabel(address) == "Random label"

    wallet.rpc.generatetoaddress(20, address)

    random_address = "mruae2834buqxk77oaVpephnA5ZAxNNJ1r"
    wallet.rpc.generatetoaddress(100, random_address)

    # update utxo
    wallet.getdata()
    # update balance
    wallet.update_balance()

    address_balance = wallet.fullbalance
    assert len(wallet.full_utxo) == 20

    print(wallet.full_utxo[4])
    # Something like:
    # { 'txid': 'fab823558781745179916b4bfdfd65b382bfc0e70e85188f1b9538604202f537',
    #   'vout': 0, 'address': 'bcrt1qmlrraffw0evkjy2yrxmt263ksgfgv2gqhcddrt',
    #   'label': 'Random label', 'scriptPubKey': '0014dfc63ea52e7e5969114419b6b56a368212862900',
    #   'amount': 50.0, 'confirmations': 101, 'spendable': False, 'solvable': True,
    #   'desc': "wpkh([08686ac6/48'/1'/0'/2'/0/0]02fa445808af849209038f422a22e335754fa07a2ece42fc483660606dcda3e0e9)#8q60z40m",
    #   'safe': True, 'time': 1637091575, 'category': 'generate', 'locked': False
    # }

    new_address = wallet.getnewaddress()
    wallet.setlabel(new_address, "")
    wallet.rpc.generatetoaddress(20, new_address)

    random_address = "mruae2834buqxk77oaVpephnA5ZAxNNJ1r"
    wallet.rpc.generatetoaddress(100, random_address)

    wallet.getdata()
    wallet.update_balance()

    assert len(wallet.full_utxo) == 40

    wallet.setlabel(new_address, "")
    third_address = wallet.getnewaddress()

    wallet.getdata()
    assert sorted(wallet.addresses) == sorted(
        [address, new_address, third_address])
def test_DeviceManager(empty_data_folder):
    # A DeviceManager manages devices, specifically the persistence
    # of them via json-files in an empty data folder
    dm = DeviceManager(data_folder=empty_data_folder)
    # initialization will load from the folder but it's empty at first
    assert len(dm.devices) == 0
    # a device has a name, a type and a list of keys
    a_key = Key(
        "Vpub5n9kKePTPPGtw3RddeJWJe29epEyBBcoHbbPi5HhpoG2kTVsSCUzsad33RJUt3LktEUUPPofcZczuudnwR7ZgkAkT6N2K2Z7wdyjYrVAkXM",
        "08686ac6",
        "m/48h/1h/0h/2h",
        "wsh",
        "",
        "tpubDFHpKypXq4kwUrqLotPs6fCic5bFqTRGMBaTi9s5YwwGymE8FLGwB2kDXALxqvNwFxB1dLWYBmmeFVjmUSdt2AsaQuPmkyPLBKRZW8BGCiL",
    )
    # the DeviceManager doesn't care so much about the content of a key
    # so this is a minimal valid "key":
    another_key = Key.from_json(
        {
            "original": "tpubDDZ5jjGT5RvrAyjoLZfdCfv1PAPmicnhNctwZGKiCMF1Zy5hCGMqppxwYZzWgvPqk7LucMMHo7rkB6Dyj5ZLd2W62FAEP3U6pV4jD5gb9ma"
        }
    )
    dm.add_device("some_name", "the_type", [a_key, another_key])
    # A json file was generated for the new device:
    assert os.path.isfile(dm.devices["some_name"].fullpath)
    # You can access the new device either by its name of with `get_by_alias` by its alias
    assert dm.get_by_alias("some_name").name == "some_name"
    # unknown device is replaced by 'other'
    assert dm.get_by_alias("some_name").device_type == "other"
    assert dm.get_by_alias("some_name").keys[0].fingerprint == "08686ac6"
    # Now it has a length of 1
    assert len(dm.devices) == 1
    # and is iterable
    assert [the_type.device_type for the_type in dm.devices.values()] == ["other"]
    # The DeviceManager will return Device-Types (subclass of dict)
    # any unknown type is replaced by GenericDevice
    assert type(dm.devices["some_name"]) == GenericDevice

    # The DeviceManager also has a `devices_names` property, returning a sorted list of the names of all devices
    assert dm.devices_names == ["some_name"]
    dm.add_device("another_name", "the_type", [a_key, another_key])
    assert dm.devices_names == ["another_name", "some_name"]

    # You can also remove a device - which will delete its json and remove it from the manager
    another_device_fullpath = dm.devices["another_name"].fullpath
    assert os.path.isfile(another_device_fullpath)
    dm.remove_device(dm.devices["another_name"])
    assert not os.path.isfile(another_device_fullpath)
    assert len(dm.devices) == 1
    assert dm.devices_names == ["some_name"]

    # A device is mainly a Domain-Object which assumes an underlying
    # json-file which can be found in the "fullpath"-key
    # It derives from a dict
    # It needs a DeviceManager to be injected and can't reasonable
    # be created on your own.
    # It has 5 dict keys: `fullpath`, `alias`, `name`, `type`, `keys`
    some_device = dm.devices["some_name"]
    assert some_device.fullpath == empty_data_folder + "/some_name.json"
    assert some_device.alias == "some_name"
    assert some_device.name == "some_name"
    assert some_device.device_type == "other"
    assert len(some_device.keys) == 2
    assert some_device.keys[0] == a_key
    assert some_device.keys[1] == another_key

    # Keys can be added and removed. It will instantly update the underlying json
    # Adding keys can be done by passing an array of keys object to the `add_keys` method of a device
    # A key dict must contain an `original` property
    third_key = Key.from_json(
        {
            "original": "tpubDEmTg3b5aPNFnkHXx481F3h9dPSVJiyvqV24dBMXWncoRRu6VJzPDeEtQ4H7EnRtLbn2aPkxhTn8odWXsXkSRDdmAvCCrPmfjfPSVswfDhg"
        }
    )
    some_device.add_keys([third_key])
    assert len(some_device.keys) == 3
    assert some_device.keys[0] == a_key
    assert some_device.keys[1] == another_key
    assert some_device.keys[2] == third_key

    # adding an existing key will do nothing
    some_device.add_keys([third_key])
    assert len(some_device.keys) == 3
    assert some_device.keys[0] == a_key
    assert some_device.keys[1] == another_key
    assert some_device.keys[2] == third_key

    # Removing a key can be done by passing the `original` property of the key to remove to the `remove_key` method of a device
    some_device.remove_key(third_key)
    assert len(some_device.keys) == 2
    assert some_device.keys[0] == a_key
    assert some_device.keys[1] == another_key

    # removing a none existing key will do nothing
    some_device.remove_key(third_key)
    assert len(some_device.keys) == 2
    assert some_device.keys[0] == a_key
    assert some_device.keys[1] == another_key
Ejemplo n.º 17
0
def test_purpose(ghost_machine_ypub):
    key = Key.parse_xpub(ghost_machine_ypub)
    assert key.purpose == "Single (Nested)"
Ejemplo n.º 18
0
def test_xpub(ghost_machine_ypub, ghost_machine_xpub_49):
    key = Key.parse_xpub(ghost_machine_ypub)
    assert key.xpub == ghost_machine_xpub_49
Ejemplo n.º 19
0
def test_wallet_createpsbt(docker, request, devices_filled_data_folder,
                           device_manager):
    # Instantiate a fresh bitcoind instance to isolate this test.
    bitcoind_controller = instantiate_bitcoind_controller(docker,
                                                          request,
                                                          rpcport=18978)
    try:
        wm = WalletManager(
            200100,
            devices_filled_data_folder,
            bitcoind_controller.rpcconn.get_rpc(),
            "regtest",
            device_manager,
            allow_threading=False,
        )
        # A wallet-creation needs a device
        device = device_manager.get_by_alias("specter")
        key = Key.from_json({
            "derivation":
            "m/48h/1h/0h/2h",
            "original":
            "Vpub5n9kKePTPPGtw3RddeJWJe29epEyBBcoHbbPi5HhpoG2kTVsSCUzsad33RJUt3LktEUUPPofcZczuudnwR7ZgkAkT6N2K2Z7wdyjYrVAkXM",
            "fingerprint":
            "08686ac6",
            "type":
            "wsh",
            "xpub":
            "tpubDFHpKypXq4kwUrqLotPs6fCic5bFqTRGMBaTi9s5YwwGymE8FLGwB2kDXALxqvNwFxB1dLWYBmmeFVjmUSdt2AsaQuPmkyPLBKRZW8BGCiL",
        })
        wallet = wm.create_wallet("a_second_test_wallet", 1, "wpkh", [key],
                                  [device])
        # Let's fund the wallet with ... let's say 40 blocks a 50 coins each --> 200 coins
        address = wallet.getnewaddress()
        assert address == "bcrt1qtnrv2jpygx2ef3zqfjhqplnycxak2m6ljnhq6z"
        wallet.rpc.generatetoaddress(20, address)
        # in two addresses
        address = wallet.getnewaddress()
        wallet.rpc.generatetoaddress(20, address)
        # newly minted coins need 100 blocks to get spendable
        # let's mine another 100 blocks to get these coins spendable
        random_address = "mruae2834buqxk77oaVpephnA5ZAxNNJ1r"
        wallet.rpc.generatetoaddress(110, random_address)
        # update the wallet data
        wallet.update_balance()
        # Now we have loads of potential inputs
        # Let's spend 500 coins
        assert wallet.fullbalance >= 250
        # From this print-statement, let's grab some txids which we'll use for coinselect
        unspents = wallet.rpc.listunspent(0)
        # Lets take 3 more or less random txs from the unspents:
        selected_coins = [{
            "txid": u["txid"],
            "vout": u["vout"]
        } for u in [unspents[5], unspents[9], unspents[12]]]
        selected_coins_amount_sum = (unspents[5]["amount"] +
                                     unspents[9]["amount"] +
                                     unspents[12]["amount"])
        number_of_coins_to_spend = (selected_coins_amount_sum - 0.1
                                    )  # Let's spend almost all of them
        psbt = wallet.createpsbt(
            [random_address],
            [number_of_coins_to_spend],
            True,
            0,
            10,
            selected_coins=selected_coins,
        )
        assert len(psbt["tx"]["vin"]) == 3
        psbt_txs = [tx["txid"] for tx in psbt["tx"]["vin"]]
        for coin in selected_coins:
            assert coin["txid"] in psbt_txs

        # Now let's spend more coins than we have selected. This should result in an exception:
        try:
            psbt = wallet.createpsbt(
                [random_address],
                [number_of_coins_to_spend + 1],
                True,
                0,
                10,
                selected_coins=selected_coins,
            )
            assert False, "should throw an exception!"
        except SpecterError as e:
            pass

        assert wallet.locked_amount == selected_coins_amount_sum
        assert len(wallet.rpc.listlockunspent()) == 3
        assert (wallet.full_available_balance == wallet.fullbalance -
                selected_coins_amount_sum)

        wallet.delete_pending_psbt(psbt["tx"]["txid"])
        assert wallet.locked_amount == 0
        assert len(wallet.rpc.listlockunspent()) == 0
        assert wallet.full_available_balance == wallet.fullbalance
    finally:
        # cleanup
        bitcoind_controller.stop_bitcoind()