From ad5b2a5ee36b98cf339ac2394ee7f66ed1c84482 Mon Sep 17 00:00:00 2001 From: Wukong Date: Thu, 12 Aug 2021 15:29:38 -0700 Subject: [PATCH] Create a new kind of watch only wallet: SegwitWatchonlyWallet --- .../jmbitcoin/secp256k1_deterministic.py | 5 ++- jmclient/jmclient/cryptoengine.py | 5 +++ jmclient/jmclient/wallet.py | 28 ++++++++++---- jmclient/jmclient/wallet_utils.py | 37 +++++++++++++------ 4 files changed, 55 insertions(+), 20 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/cryptoengine.py b/jmclient/jmclient/cryptoengine.py index f5d2a2a1a..d7828790f 100644 --- a/jmclient/jmclient/cryptoengine.py +++ b/jmclient/jmclient/cryptoengine.py @@ -429,6 +429,11 @@ def derive_bip32_pub_export(cls, master_key, path): return super(BTC_Watchonly_P2WPKH, cls).derive_bip32_pub_export( master_key, BTC_Watchonly_Timelocked_P2WSH.get_watchonly_path(path)) + @classmethod + def derive_bip32_priv_export(cls, master_key, path): + #return pub key instead of priv key in watchonly wallet + return BTC_Watchonly_P2WPKH.derive_bip32_pub_export(master_key, path) + @classmethod def sign_transaction(cls, tx, index, privkey, amount, hashcode=btc.SIGHASH_ALL, **kwargs): diff --git a/jmclient/jmclient/wallet.py b/jmclient/jmclient/wallet.py index 09889862c..7173f7ba1 100644 --- a/jmclient/jmclient/wallet.py +++ b/jmclient/jmclient/wallet.py @@ -24,7 +24,7 @@ from .blockchaininterface import INF_HEIGHT from .support import select_gradual, select_greedy, select_greediest, \ select, NotEnoughFundsException -from .cryptoengine import TYPE_P2PKH, TYPE_P2SH_P2WPKH,\ +from .cryptoengine import BTC_Watchonly_P2WPKH, TYPE_P2PKH, TYPE_P2SH_P2WPKH,\ TYPE_P2WPKH, TYPE_TIMELOCK_P2WSH, TYPE_SEGWIT_WALLET_FIDELITY_BONDS,\ TYPE_WATCHONLY_FIDELITY_BONDS, TYPE_WATCHONLY_TIMELOCK_P2WSH, TYPE_WATCHONLY_P2WPKH,\ ENGINES @@ -2485,14 +2485,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): @@ -2502,6 +2498,21 @@ 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] + + +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 @@ -2511,6 +2522,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 d175c76a4..701c9693b 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. """ parser = OptionParser(usage='usage: %prog [options] [wallet file] [method] [args..]', description=description, formatter=IndentedHelpFormatterWithNL()) @@ -214,7 +216,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 @@ -1268,7 +1270,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: @@ -1281,15 +1283,23 @@ def wallet_createwatchonly(wallet_root_path, master_pub_key): if not password: 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 pub 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): @@ -1453,7 +1463,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'] @@ -1576,6 +1586,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")