Skip to content

Commit

Permalink
Create a new kind of watch only wallet: SegwitWatchonlyWallet
Browse files Browse the repository at this point in the history
  • Loading branch information
BitcoinWukong authored and Wukong committed May 11, 2022
1 parent 7254149 commit 0a43381
Show file tree
Hide file tree
Showing 4 changed files with 101 additions and 21 deletions.
5 changes: 4 additions & 1 deletion jmbitcoin/jmbitcoin/secp256k1_deterministic.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
31 changes: 30 additions & 1 deletion jmclient/jmclient/cryptoengine.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
# make existing wallets unsable.
TYPE_P2PKH, TYPE_P2SH_P2WPKH, TYPE_P2WPKH, TYPE_P2SH_M_N, TYPE_TIMELOCK_P2WSH, \
TYPE_SEGWIT_WALLET_FIDELITY_BONDS, TYPE_WATCHONLY_FIDELITY_BONDS, \
TYPE_WATCHONLY_TIMELOCK_P2WSH, TYPE_WATCHONLY_P2WPKH, TYPE_P2WSH = range(10)
TYPE_WATCHONLY_TIMELOCK_P2WSH, TYPE_WATCHONLY_P2SH_P2WPKH, TYPE_WATCHONLY_P2WPKH, TYPE_P2WSH = range(11)
NET_MAINNET, NET_TESTNET, NET_SIGNET = range(3)
NET_MAP = {'mainnet': NET_MAINNET, 'testnet': NET_TESTNET,
'signet': NET_SIGNET}
Expand Down Expand Up @@ -412,6 +412,34 @@ def sign_transaction(cls, tx, index, privkey, amount,
hashcode=btc.SIGHASH_ALL, **kwargs):
raise RuntimeError("Cannot spend from watch-only wallets")

class BTC_Watchonly_P2SH_P2WPKH(BTC_P2SH_P2WPKH):

@classmethod
def derive_bip32_privkey(cls, master_key, path):
return BTC_Watchonly_Timelocked_P2WSH.derive_bip32_privkey(master_key, path)

@classmethod
def privkey_to_wif(cls, privkey_locktime):
return BTC_Watchonly_Timelocked_P2WSH.privkey_to_wif(privkey_locktime)

@staticmethod
def privkey_to_pubkey(privkey):
#in watchonly wallets there are no privkeys, so functions
# like _get_key_from_path() actually return pubkeys and
# this function is a noop
return privkey

@classmethod
def derive_bip32_pub_export(cls, master_key, path):
return super(BTC_Watchonly_P2SH_P2WPKH, cls).derive_bip32_pub_export(
master_key, BTC_Watchonly_Timelocked_P2WSH.get_watchonly_path(path))

@classmethod
def sign_transaction(cls, tx, index, privkey, amount,
hashcode=btc.SIGHASH_ALL, **kwargs):
raise RuntimeError("Cannot spend from watch-only wallets")


class BTC_Watchonly_P2WPKH(BTC_P2WPKH):

@classmethod
Expand Down Expand Up @@ -445,6 +473,7 @@ def sign_transaction(cls, tx, index, privkey, amount,
TYPE_P2WPKH: BTC_P2WPKH,
TYPE_TIMELOCK_P2WSH: BTC_Timelocked_P2WSH,
TYPE_WATCHONLY_TIMELOCK_P2WSH: BTC_Watchonly_Timelocked_P2WSH,
TYPE_WATCHONLY_P2SH_P2WPKH: BTC_Watchonly_P2SH_P2WPKH,
TYPE_WATCHONLY_P2WPKH: BTC_Watchonly_P2WPKH,
TYPE_SEGWIT_WALLET_FIDELITY_BONDS: BTC_P2WPKH
}
43 changes: 35 additions & 8 deletions jmclient/jmclient/wallet.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@
select, NotEnoughFundsException
from .cryptoengine import TYPE_P2PKH, TYPE_P2SH_P2WPKH, TYPE_P2WSH,\
TYPE_P2WPKH, TYPE_TIMELOCK_P2WSH, TYPE_SEGWIT_WALLET_FIDELITY_BONDS,\
TYPE_WATCHONLY_FIDELITY_BONDS, TYPE_WATCHONLY_TIMELOCK_P2WSH, TYPE_WATCHONLY_P2WPKH,\
TYPE_WATCHONLY_FIDELITY_BONDS, TYPE_WATCHONLY_TIMELOCK_P2WSH,\
TYPE_WATCHONLY_P2SH_P2WPKH, TYPE_WATCHONLY_P2WPKH,\
ENGINES, detect_script_type, EngineError
from .support import get_random_bytes
from . import mn_encode, mn_decode
Expand Down Expand Up @@ -2591,14 +2592,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):
Expand All @@ -2608,6 +2605,34 @@ def _verify_entropy(cls, ent):
def _derive_bip32_master_key(cls, master_entropy):
return btc.bip32_deserialize(master_entropy.decode())

class SegwitLegacyWatchonlyWallet(WatchonlyMixin, BIP49Wallet):
TYPE = TYPE_WATCHONLY_P2SH_P2WPKH
_ENGINE = ENGINES[TYPE_WATCHONLY_P2SH_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 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
Expand All @@ -2617,6 +2642,8 @@ def _get_bip32_export_path(self, mixdepth=None, address_type=None):
LegacyWallet.TYPE: LegacyWallet,
SegwitLegacyWallet.TYPE: SegwitLegacyWallet,
SegwitWallet.TYPE: SegwitWallet,
SegwitLegacyWatchonlyWallet.TYPE: SegwitLegacyWatchonlyWallet,
SegwitWatchonlyWallet.TYPE: SegwitWatchonlyWallet,
SegwitWalletFidelityBonds.TYPE: SegwitWalletFidelityBonds,
FidelityBondWatchonlyWallet.TYPE: FidelityBondWatchonlyWallet
}
43 changes: 32 additions & 11 deletions jmclient/jmclient/wallet_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 SegwitLegacyWatchonlyWallet, SegwitWatchonlyWallet, WatchonlyMixin
from jmclient.wallet_service import WalletService
from jmbase.support import (get_password, jmprint, EXIT_FAILURE,
EXIT_ARGERROR, utxo_to_utxostr, hextobin, bintohex,
Expand Down Expand Up @@ -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..]',
Expand Down Expand Up @@ -237,7 +239,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

Expand Down Expand Up @@ -1338,7 +1340,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:
Expand All @@ -1349,17 +1351,31 @@ 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:
if master_pub_key.startswith('ypub'):
wallet_cls = SegwitLegacyWatchonlyWallet
else:
wallet_cls = SegwitWatchonlyWallet

create_wallet(wallet_path, password,
max_mixdepth=WatchonlyMixin.WATCH_ONLY_MIXDEPTH,
wallet_cls=wallet_cls, entropy=entropy)
return "Done"

def get_configured_wallet_type(support_fidelity_bonds):
Expand Down Expand Up @@ -1526,7 +1542,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']
Expand Down Expand Up @@ -1649,6 +1665,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")
Expand Down

0 comments on commit 0a43381

Please sign in to comment.