From f90a760192fe39948f28b85f49ce4f7c4d158267 Mon Sep 17 00:00:00 2001 From: wukong1971 <87334822+wukong1971@users.noreply.github.com> Date: Thu, 26 Aug 2021 11:22:21 -0700 Subject: [PATCH] Create a new kind of watch only wallet: SegwitWatchonlyWallet --- .../jmbitcoin/secp256k1_deterministic.py | 5 ++- jmclient/jmclient/wallet.py | 31 +++++++++++---- jmclient/jmclient/wallet_utils.py | 38 +++++++++++++------ 3 files changed, 55 insertions(+), 19 deletions(-) diff --git a/jmbitcoin/jmbitcoin/secp256k1_deterministic.py b/jmbitcoin/jmbitcoin/secp256k1_deterministic.py index 6835afd3f..542e89102 100644 --- a/jmbitcoin/jmbitcoin/secp256k1_deterministic.py +++ b/jmbitcoin/jmbitcoin/secp256k1_deterministic.py @@ -8,12 +8,15 @@ # Below code ASSUMES binary inputs and compressed pubkeys MAINNET_PRIVATE = b'\x04\x88\xAD\xE4' MAINNET_PUBLIC = b'\x04\x88\xB2\x1E' +MAINNET_PUBLIC_P2SH_P2WPKH = b'\x04\x9D\x7C\xB2' +MAINNET_PUBLIC_P2WPKH = b'\x04\xB2\x47\x46' + TESTNET_PRIVATE = b'\x04\x35\x83\x94' TESTNET_PUBLIC = b'\x04\x35\x87\xCF' SIGNET_PRIVATE = b'\x04\x35\x83\x94' SIGNET_PUBLIC = b'\x04\x35\x87\xCF' PRIVATE = [MAINNET_PRIVATE, TESTNET_PRIVATE, SIGNET_PRIVATE] -PUBLIC = [MAINNET_PUBLIC, TESTNET_PUBLIC, SIGNET_PUBLIC] +PUBLIC = [MAINNET_PUBLIC, MAINNET_PUBLIC_P2SH_P2WPKH, MAINNET_PUBLIC_P2WPKH, TESTNET_PUBLIC, SIGNET_PUBLIC] privtopub = privkey_to_pubkey diff --git a/jmclient/jmclient/wallet.py b/jmclient/jmclient/wallet.py index 4c3597a0e..6c6c982e1 100644 --- a/jmclient/jmclient/wallet.py +++ b/jmclient/jmclient/wallet.py @@ -2581,14 +2581,10 @@ class SegwitLegacyWallet(ImportWalletMixin, BIP39WalletMixin, PSBTWalletMixin, S class SegwitWallet(ImportWalletMixin, BIP39WalletMixin, PSBTWalletMixin, SNICKERWalletMixin, BIP84Wallet): TYPE = TYPE_P2WPKH -class SegwitWalletFidelityBonds(FidelityBondMixin, SegwitWallet): - TYPE = TYPE_SEGWIT_WALLET_FIDELITY_BONDS - -class FidelityBondWatchonlyWallet(FidelityBondMixin, BIP84Wallet): - TYPE = TYPE_WATCHONLY_FIDELITY_BONDS - _ENGINE = ENGINES[TYPE_WATCHONLY_P2WPKH] - _TIMELOCK_ENGINE = ENGINES[TYPE_WATCHONLY_TIMELOCK_P2WSH] +class WatchonlyMixin(object): + # When watching an external wallet, we only watch account 0 + WATCH_ONLY_MIXDEPTH = 0 @classmethod def _verify_entropy(cls, ent): @@ -2598,6 +2594,26 @@ def _verify_entropy(cls, ent): def _derive_bip32_master_key(cls, master_entropy): return btc.bip32_deserialize(master_entropy.decode()) + +class SegwitWatchonlyWallet(WatchonlyMixin, BIP84Wallet): + TYPE = TYPE_WATCHONLY_P2WPKH + _ENGINE = ENGINES[TYPE_WATCHONLY_P2WPKH] + + def _get_key_ident(self): + return sha256(sha256( + self.get_bip32_pub_export(0, self.BIP32_EXT_ID).encode('ascii')).digest())\ + .digest()[:3] + + +class SegwitWalletFidelityBonds(FidelityBondMixin, SegwitWallet): + TYPE = TYPE_SEGWIT_WALLET_FIDELITY_BONDS + + +class FidelityBondWatchonlyWallet(FidelityBondMixin, WatchonlyMixin, BIP84Wallet): + TYPE = TYPE_WATCHONLY_FIDELITY_BONDS + _ENGINE = ENGINES[TYPE_WATCHONLY_P2WPKH] + _TIMELOCK_ENGINE = ENGINES[TYPE_WATCHONLY_TIMELOCK_P2WSH] + def _get_bip32_export_path(self, mixdepth=None, address_type=None): path = super()._get_bip32_export_path(mixdepth, address_type) return path @@ -2607,6 +2623,7 @@ def _get_bip32_export_path(self, mixdepth=None, address_type=None): LegacyWallet.TYPE: LegacyWallet, SegwitLegacyWallet.TYPE: SegwitLegacyWallet, SegwitWallet.TYPE: SegwitWallet, + SegwitWatchonlyWallet.TYPE: SegwitWatchonlyWallet, SegwitWalletFidelityBonds.TYPE: SegwitWalletFidelityBonds, FidelityBondWatchonlyWallet.TYPE: FidelityBondWatchonlyWallet } diff --git a/jmclient/jmclient/wallet_utils.py b/jmclient/jmclient/wallet_utils.py index 1f9ff705d..dc14f4282 100644 --- a/jmclient/jmclient/wallet_utils.py +++ b/jmclient/jmclient/wallet_utils.py @@ -14,6 +14,7 @@ VolatileStorage, StoragePasswordError, is_segwit_mode, SegwitLegacyWallet, LegacyWallet, SegwitWallet, FidelityBondMixin, FidelityBondWatchonlyWallet, is_native_segwit_mode, load_program_config, add_base_options, check_regtest) +from jmclient.wallet import SegwitWatchonlyWallet, WatchonlyMixin from jmclient.wallet_service import WalletService from jmbase.support import (get_password, jmprint, EXIT_FAILURE, EXIT_ARGERROR, utxo_to_utxostr, hextobin, bintohex, @@ -51,7 +52,8 @@ def get_wallettool_parser(): (gettimelockaddress) Obtain a timelocked address. Argument is locktime value as yyyy-mm. For example `2021-03`. (addtxoutproof) Add a tx out proof as metadata to a burner transaction. Specify path with -H and proof which is output of Bitcoin Core\'s RPC call gettxoutproof. -(createwatchonly) Create a watch-only fidelity bond wallet. +(createwatchonly) Create a watch-only wallet. +(createfbwatchonly) Create a watch-only fidelity bond wallet. (setlabel) Set the label associated with the given address. """ parser = OptionParser(usage='usage: %prog [options] [wallet file] [method] [args..]', @@ -235,7 +237,7 @@ def __init__(self, wallet_path_repr, account, address_type, branchentries=None, FidelityBondMixin.BIP32_BURN_ID] self.address_type = address_type if xpub: - assert xpub.startswith('xpub') or xpub.startswith('tpub') + assert xpub.startswith('xpub') or xpub.startswith('tpub') or xpub.startswith('ypub') or xpub.startswith('zpub') self.xpub = xpub if xpub else "" self.branchentries = branchentries @@ -1315,7 +1317,7 @@ def wallet_addtxoutproof(wallet_service, hdpath, txoutproof): new_merkle_branch, block_index) return "Done" -def wallet_createwatchonly(wallet_root_path, master_pub_key): +def wallet_createwatchonly(wallet_root_path, master_pub_key, is_fidelity_bond_wallet = False): wallet_name = cli_get_wallet_file_name(defaultname="watchonly.jmdat") if not wallet_name: @@ -1326,17 +1328,26 @@ def wallet_createwatchonly(wallet_root_path, master_pub_key): password = cli_get_wallet_passphrase_check() if not password: + jmprint("The passphrase can not be empty", "error") return "" - entropy = FidelityBondMixin.get_xpub_from_fidelity_bond_master_pub_key(master_pub_key) - if not entropy: - jmprint("Error with provided master pub key", "error") - return "" + if is_fidelity_bond_wallet: + entropy = FidelityBondMixin.get_xpub_from_fidelity_bond_master_pub_key(master_pub_key) + if not entropy: + jmprint("Error with provided master public key", "error") + return "" + else: + entropy = master_pub_key entropy = entropy.encode() - wallet = create_wallet(wallet_path, password, - max_mixdepth=FidelityBondMixin.FIDELITY_BOND_MIXDEPTH, - wallet_cls=FidelityBondWatchonlyWallet, entropy=entropy) + if is_fidelity_bond_wallet: + create_wallet(wallet_path, password, + max_mixdepth=FidelityBondMixin.FIDELITY_BOND_MIXDEPTH, + wallet_cls=FidelityBondWatchonlyWallet, entropy=entropy) + else: + create_wallet(wallet_path, password, + max_mixdepth=WatchonlyMixin.WATCH_ONLY_MIXDEPTH, + wallet_cls=SegwitWatchonlyWallet, entropy=entropy) return "Done" def get_configured_wallet_type(support_fidelity_bonds): @@ -1503,7 +1514,7 @@ def wallet_tool_main(wallet_root_path): check_regtest(blockchain_start=False) # full path to the wallets/ subdirectory in the user data area: wallet_root_path = os.path.join(jm_single().datadir, wallet_root_path) - noseed_methods = ['generate', 'recover', 'createwatchonly'] + noseed_methods = ['generate', 'recover', 'createwatchonly', 'createfbwatchonly'] methods = ['display', 'displayall', 'summary', 'showseed', 'importprivkey', 'history', 'showutxos', 'freeze', 'gettimelockaddress', 'addtxoutproof', 'changepass', 'setlabel'] @@ -1626,6 +1637,11 @@ def wallet_tool_main(wallet_root_path): + 'Core\'s RPC call gettxoutproof', "error") sys.exit(EXIT_ARGERROR) return wallet_addtxoutproof(wallet_service, options.hd_path, args[2]) + elif method == "createfbwatchonly": + if len(args) < 2: + jmprint("args: [master public key]", "error") + sys.exit(EXIT_ARGERROR) + return wallet_createwatchonly(wallet_root_path, args[1], is_fidelity_bond_wallet=True) elif method == "createwatchonly": if len(args) < 2: jmprint("args: [master public key]", "error")