Skip to content

Commit

Permalink
Use RPC deriveaddresses to generate addresses
Browse files Browse the repository at this point in the history
Output Descriptors and the RPC call deriveaddresses can generate
addresses much faster than the previously-used pure python routines.

This functionality is only fully supported in Bitcoin Core 0.20.0 so
the code checks for that version.
  • Loading branch information
chris-belcher committed May 5, 2020
1 parent db78fb9 commit 3e52ed9
Show file tree
Hide file tree
Showing 4 changed files with 154 additions and 162 deletions.
6 changes: 3 additions & 3 deletions electrumpersonalserver/bitcoin/deterministic.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ def raw_bip32_ckd(rawtuple, i):

if i >= 2**31:
if vbytes in PUBLIC:
raise Exception("Can't do private derivation on public key!")
raise ValueError("Can't do private derivation on public key!")
I = hmac.new(chaincode, b'\x00' + priv[:32] + encode(i, 256, 4),
hashlib.sha512).digest()
else:
Expand Down Expand Up @@ -70,7 +70,7 @@ def bip32_serialize(rawtuple):
def bip32_deserialize(data):
dbin = changebase(data, 58, 256)
if bin_dbl_sha256(dbin[:-4])[:4] != dbin[-4:]:
raise Exception("Invalid checksum")
raise ValueError("Invalid checksum")
vbytes = dbin[0:4]
depth = from_byte_to_int(dbin[4])
fingerprint = dbin[5:9]
Expand Down Expand Up @@ -118,7 +118,7 @@ def raw_crack_bip32_privkey(parent_pub, priv):
i = int(i)

if i >= 2**31:
raise Exception("Can't crack private derivation!")
raise ValueError("Can't crack private derivation!")

I = hmac.new(pchaincode, pkey + encode(i, 256, 4), hashlib.sha512).digest()

Expand Down
57 changes: 38 additions & 19 deletions electrumpersonalserver/server/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -174,34 +174,39 @@ def get_scriptpubkeys_to_monitor(rpc, config):

deterministic_wallets = []
for key in config.options("master-public-keys"):
wal = deterministicwallet.parse_electrum_master_public_key(
config.get("master-public-keys", key),
int(config.get("bitcoin-rpc", "gap_limit")))
mpk = config.get("master-public-keys", key)
gaplimit = int(config.get("bitcoin-rpc", "gap_limit"))
chain = rpc.call("getblockchaininfo", [])["chain"]
try:
wal = deterministicwallet.parse_electrum_master_public_key(mpk,
gaplimit, rpc, chain)
except ValueError:
raise ValueError("Bad master public key format. Get it from " +
"Electrum menu `Wallet` -> `Information`")
deterministic_wallets.append(wal)

#check whether these deterministic wallets have already been imported
import_needed = False
wallets_imported = 0
spks_to_import = []
addresses_to_import = []
TEST_ADDR_COUNT = 3
logger.info("Displaying first " + str(TEST_ADDR_COUNT) + " addresses of " +
"each master public key:")
for config_mpk_key, wal in zip(config.options("master-public-keys"),
deterministic_wallets):
first_spks = wal.get_scriptpubkeys(change=0, from_index=0,
first_addrs, first_spk = wal.get_addresses(change=0, from_index=0,
count=TEST_ADDR_COUNT)
first_addrs = [hashes.script_to_address(s, rpc) for s in first_spks]
logger.info("\n" + config_mpk_key + " =>\n\t" + "\n\t".join(
first_addrs))
last_spk = wal.get_scriptpubkeys(0, int(config.get("bitcoin-rpc",
"initial_import_count")) - 1, 1)
last_addr = [hashes.script_to_address(last_spk[0], rpc)]
last_addr, last_spk = wal.get_addresses(change=0, from_index=int(
config.get("bitcoin-rpc", "initial_import_count")) - 1, count=1)
if not set(first_addrs + last_addr).issubset(imported_addresses):
import_needed = True
wallets_imported += 1
for change in [0, 1]:
spks_to_import.extend(wal.get_scriptpubkeys(change, 0,
int(config.get("bitcoin-rpc", "initial_import_count"))))
addrs, spks = wal.get_addresses(change, 0,
int(config.get("bitcoin-rpc", "initial_import_count")))
addresses_to_import.extend(addrs)
logger.info("Obtaining bitcoin addresses to monitor . . .")
#check whether watch-only addresses have been imported
watch_only_addresses = []
Expand All @@ -223,8 +228,6 @@ def get_scriptpubkeys_to_monitor(rpc, config):

#if addresses need to be imported then return them
if import_needed:
addresses_to_import = [hashes.script_to_address(spk, rpc)
for spk in spks_to_import]
#TODO minus imported_addresses
logger.info("Importing " + str(wallets_imported) + " wallets and " +
str(len(watch_only_addresses_to_import)) + " watch-only " +
Expand All @@ -242,15 +245,15 @@ def get_scriptpubkeys_to_monitor(rpc, config):
spks_to_monitor = []
for wal in deterministic_wallets:
for change in [0, 1]:
spks_to_monitor.extend(wal.get_scriptpubkeys(change, 0,
int(config.get("bitcoin-rpc", "initial_import_count"))))
addrs, spks = wal.get_addresses(change, 0,
int(config.get("bitcoin-rpc", "initial_import_count")))
spks_to_monitor.extend(spks)
#loop until one address found that isnt imported
while True:
spk = wal.get_new_scriptpubkeys(change, count=1)[0]
spks_to_monitor.append(spk)
if hashes.script_to_address(spk, rpc) not in imported_addresses:
addrs, spks = wal.get_new_addresses(change, count=1)
if addrs[0] not in imported_addresses:
break
spks_to_monitor.pop()
spks_to_monitor.append(spks[0])
wal.rewind_one(change)

spks_to_monitor.extend([hashes.address_to_script(addr, rpc)
Expand Down Expand Up @@ -389,6 +392,22 @@ def main():
logger.error("Wallet related RPC call failed, possibly the " +
"bitcoin node was compiled with the disable wallet flag")
return

test_keydata = (
"2 tpubD6NzVbkrYhZ4YVMVzC7wZeRfz3bhqcHvV8M3UiULCfzFtLtp5nwvi6LnBQegrkx" +
"YGPkSzXUEvcPEHcKdda8W1YShVBkhFBGkLxjSQ1Nx3cJ tpubD6NzVbkrYhZ4WjgNYq2nF" +
"TbiSLW2SZAzs4g5JHLqwQ3AmR3tCWpqsZJJEoZuP5HAEBNxgYQhtWMezszoaeTCg6FWGQB" +
"T74sszGaxaf64o5s")
chain = rpc.call("getblockchaininfo", [])["chain"]
try:
gaplimit = 5
deterministicwallet.parse_electrum_master_public_key(test_keydata,
gaplimit, rpc, chain)
except ValueError as e:
logger.error(repr(e))
logger.error("Descriptor related RPC call failed. Bitcoin Core 0.20.0"
+ " or higher required. Exiting..")
return
if opts.rescan:
rescan_script(logger, rpc, opts.rescan_date)
return
Expand Down
Loading

0 comments on commit 3e52ed9

Please sign in to comment.