Browse Source

Add support for spending timelocked UTXOs

The cryptoengine class BTC_Timelocked_P2WSH now implements
sign_transaction() which can be used to spend timelocked UTXOs.

FidelityBondMixin.is_timelocked_path() is now used outside the class
so its leading underscore has been removed.
master
chris-belcher 6 years ago
parent
commit
a0a0d28f4e
No known key found for this signature in database
GPG Key ID: EF734EA677F31129
  1. 24
      jmclient/jmclient/cryptoengine.py
  2. 18
      jmclient/jmclient/taker_utils.py
  3. 8
      jmclient/jmclient/wallet.py

24
jmclient/jmclient/cryptoengine.py

@ -332,14 +332,19 @@ class BTC_Timelocked_P2WSH(BTCEngine):
return btc.bin_to_b58check(priv, cls.WIF_PREFIX)
@classmethod
def sign_transaction(cls, tx, index, privkey, amount,
def sign_transaction(cls, tx, index, privkey_locktime, amount,
hashcode=btc.SIGHASH_ALL, **kwargs):
raise Exception("not implemented yet")
assert amount is not None
@classmethod
def sign_transaction(cls, tx, index, privkey, amount,
hashcode=btc.SIGHASH_ALL, **kwargs):
raise RuntimeError("Cannot spend from watch-only wallets")
privkey, locktime = privkey_locktime
privkey = hexlify(privkey).decode()
pubkey = btc.privkey_to_pubkey(privkey)
pubkey = unhexlify(pubkey)
redeem_script = cls.pubkey_to_script_code((pubkey, locktime))
tx = btc.serialize(tx)
sig = btc.get_p2sh_signature(tx, index, redeem_script, privkey,
amount)
return btc.apply_freeze_signature(tx, index, redeem_script, sig)
class BTC_Watchonly_Timelocked_P2WSH(BTC_Timelocked_P2WSH):
@ -368,7 +373,7 @@ class BTC_Watchonly_Timelocked_P2WSH(BTC_Timelocked_P2WSH):
@classmethod
def sign_transaction(cls, tx, index, privkey, amount,
hashcode=btc.SIGHASH_ALL, **kwargs):
raise Exception("not implemented yet")
raise RuntimeError("Cannot spend from watch-only wallets")
class BTC_Watchonly_P2SH_P2WPKH(BTC_P2SH_P2WPKH):
@ -392,6 +397,11 @@ class BTC_Watchonly_P2SH_P2WPKH(BTC_P2SH_P2WPKH):
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")
ENGINES = {
TYPE_P2PKH: BTC_P2PKH,
TYPE_P2SH_P2WPKH: BTC_P2SH_P2WPKH,

18
jmclient/jmclient/taker_utils.py

@ -117,12 +117,28 @@ def direct_send(wallet_service, amount, mixdepth, destination, answeryes=False,
change_addr = wallet_service.get_internal_addr(mixdepth)
outs.append({"value": changeval, "address": change_addr})
#compute transaction locktime, has special case for spending timelocked coins
tx_locktime = compute_tx_locktime()
if mixdepth == FidelityBondMixin.FIDELITY_BOND_MIXDEPTH and \
isinstance(wallet_service.wallet, FidelityBondMixin):
for outpoint, utxo in utxos.items():
path = wallet_service.script_to_path(
wallet_service.addr_to_script(utxo["address"]))
if not FidelityBondMixin.is_timelocked_path(path):
continue
path_locktime = path[-1]
tx_locktime = max(tx_locktime, path_locktime+1)
#compute_tx_locktime() gives a locktime in terms of block height
#timelocked addresses use unix time instead
#OP_CHECKLOCKTIMEVERIFY can only compare like with like, so we
#must use unix time as the transaction locktime
#Now ready to construct transaction
log.info("Using a fee of : " + amount_to_str(fee_est) + ".")
if amount != 0:
log.info("Using a change value of: " + amount_to_str(changeval) + ".")
txsigned = sign_tx(wallet_service, make_shuffled_tx(
list(utxos.keys()), outs, False, 2, compute_tx_locktime()), utxos)
list(utxos.keys()), outs, False, 2, tx_locktime), utxos)
log.info("Got signed transaction:\n")
log.info(pformat(txsigned))
tx = serialize(txsigned)

8
jmclient/jmclient/wallet.py

@ -1694,7 +1694,7 @@ class FidelityBondMixin(object):
return timenumber
@classmethod
def _is_timelocked_path(cls, path):
def is_timelocked_path(cls, path):
return len(path) > 4 and path[4] == cls.BIP32_TIMELOCK_ID
def get_xpub_from_fidelity_bond_master_pub_key(cls, mpk):
@ -1737,7 +1737,7 @@ class FidelityBondMixin(object):
return (cls.BIP32_EXT_ID, cls.BIP32_INT_ID, cls.BIP32_TIMELOCK_ID, cls.BIP32_BURN_ID)
def _get_priv_from_path(self, path):
if self._is_timelocked_path(path):
if self.is_timelocked_path(path):
key_path = path[:-1]
locktime = path[-1]
engine = self._TIMELOCK_ENGINE
@ -1766,7 +1766,7 @@ class FidelityBondMixin(object):
refering to the pubkey plus the timelock value which together are needed to create the address
"""
def get_path_repr(self, path):
if self._is_timelocked_path(path) and len(path) == 7:
if self.is_timelocked_path(path) and len(path) == 7:
return super(FidelityBondMixin, self).get_path_repr(path[:-1]) +\
":" + str(path[-1])
else:
@ -1785,7 +1785,7 @@ class FidelityBondMixin(object):
))
def get_details(self, path):
if self._is_timelocked_path(path):
if self.is_timelocked_path(path):
return self._get_mixdepth_from_path(path), path[-3], path[-2]
else:
return super(FidelityBondMixin, self).get_details(path)

Loading…
Cancel
Save