From a27a3885f34dc9df8f324d0a056bdeec1b31fb87 Mon Sep 17 00:00:00 2001 From: Stepan Snigirev Date: Wed, 7 Apr 2021 19:27:26 +0200 Subject: [PATCH] add descriptor support --- lnbits/extensions/watchonly/crud.py | 91 ++++++++++++++----- .../watchonly/templates/watchonly/index.html | 2 +- lnbits/extensions/watchonly/views_api.py | 5 +- requirements.txt | 2 +- 4 files changed, 72 insertions(+), 28 deletions(-) diff --git a/lnbits/extensions/watchonly/crud.py b/lnbits/extensions/watchonly/crud.py index 3de06b171..9255c952b 100644 --- a/lnbits/extensions/watchonly/crud.py +++ b/lnbits/extensions/watchonly/crud.py @@ -6,27 +6,76 @@ from .models import Wallets, Addresses, Mempool from lnbits.helpers import urlsafe_short_hash -from embit import bip32 -from embit import ec +from embit.descriptor import Descriptor, Key +from embit.descriptor.arguments import AllowedDerivation from embit.networks import NETWORKS -from embit import base58 -from embit.util import hashlib -import io -from embit.util import secp256k1 -from embit import hashes -from binascii import hexlify -from quart import jsonify -from embit import script -from embit import ec -from embit.networks import NETWORKS -from binascii import unhexlify, hexlify, a2b_base64, b2a_base64 import httpx ##########################WALLETS#################### +def detect_network(k): + version = k.key.version + for network_name in NETWORKS: + net = NETWORKS[network_name] + # not found in this network + if version in [net["xpub"], net["ypub"], net["zpub"], net["Zpub"], net["Ypub"]]: + return net + +def parse_key(masterpub: str): + """Parses masterpub or descriptor and returns a tuple: (Descriptor, network) + To create addresses use descriptor.derive(num).address(network=network) + """ + network = None + # probably a single key + if "(" not in masterpub: + k = Key.from_string(masterpub) + if not k.is_extended: + raise ValueError("The key is not a master public key") + if k.is_private: + raise ValueError("Private keys are not allowed") + # check depth + if k.key.depth != 3: + raise ValueError("Non-standard depth. Only bip44, bip49 and bip84 are supported with bare xpubs. For custom derivation paths use descriptors.") + # if allowed derivation is not provided use default /{0,1}/* + if k.allowed_derivation is None: + k.allowed_derivation = AllowedDerivation.default() + # get version bytes + version = k.key.version + for network_name in NETWORKS: + net = NETWORKS[network_name] + # not found in this network + if version in [net["xpub"], net["ypub"], net["zpub"]]: + network = net + if version == net["xpub"]: + desc = Descriptor.from_string("pkh(%s)" % str(k)) + elif version == net["ypub"]: + desc = Descriptor.from_string("sh(wpkh(%s))" % str(k)) + elif version == net["zpub"]: + desc = Descriptor.from_string("wpkh(%s)" % str(k)) + break + # we didn't find correct version + if network is None: + raise ValueError("Unknown master public key version") + else: + desc = Descriptor.from_string(masterpub) + if not desc.is_wildcard: + raise ValueError("Descriptor should have wildcards") + for k in desc.keys: + if k.is_extended: + net = detect_network(k) + if net is None: + raise ValueError(f"Unknown version: {k}") + if network is not None and network != net: + raise ValueError("Keys from different networks") + network = net + return desc, network + + async def create_watch_wallet(*, user: str, masterpub: str, title: str) -> Wallets: + # check the masterpub is fine, it will raise an exception if not + parse_key(masterpub) wallet_id = urlsafe_short_hash() await db.execute( """ @@ -40,7 +89,8 @@ async def create_watch_wallet(*, user: str, masterpub: str, title: str) -> Walle ) VALUES (?, ?, ?, ?, ?, ?) """, - (wallet_id, user, masterpub, title, 0, 0), + # address_no is -1 so fresh address on empty wallet can get address with index 0 + (wallet_id, user, masterpub, title, -1, 0), ) # weallet_id = db.cursor.lastrowid @@ -75,17 +125,8 @@ async def get_derive_address(wallet_id: str, num: int): wallet = await get_watch_wallet(wallet_id) key = wallet[2] - k = bip32.HDKey.from_base58(key) - child = k.derive([0, num]) - - if key[0:4] == "xpub": - address = script.p2pkh(child).address() - elif key[0:4] == "zpub": - address = script.p2wpkh(child).address() - elif key[0:4] == "ypub": - address = script.p2sh(script.p2wpkh(child)).address() - - return address + desc, network = parse_key(key) + return desc.derive(num).address(network=network) async def get_fresh_address(wallet_id: str) -> Addresses: diff --git a/lnbits/extensions/watchonly/templates/watchonly/index.html b/lnbits/extensions/watchonly/templates/watchonly/index.html index 33ae3e6e1..780707d0b 100644 --- a/lnbits/extensions/watchonly/templates/watchonly/index.html +++ b/lnbits/extensions/watchonly/templates/watchonly/index.html @@ -106,7 +106,7 @@ + label="Account Extended Public Key; xpub, ypub, zpub; Bitcoin Descriptor">