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) return btc.bin_to_b58check(priv, cls.WIF_PREFIX)
@classmethod @classmethod
def sign_transaction(cls, tx, index, privkey, amount, def sign_transaction(cls, tx, index, privkey_locktime, amount,
hashcode=btc.SIGHASH_ALL, **kwargs): hashcode=btc.SIGHASH_ALL, **kwargs):
raise Exception("not implemented yet") assert amount is not None
@classmethod privkey, locktime = privkey_locktime
def sign_transaction(cls, tx, index, privkey, amount, privkey = hexlify(privkey).decode()
hashcode=btc.SIGHASH_ALL, **kwargs): pubkey = btc.privkey_to_pubkey(privkey)
raise RuntimeError("Cannot spend from watch-only wallets") 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): class BTC_Watchonly_Timelocked_P2WSH(BTC_Timelocked_P2WSH):
@ -368,7 +373,7 @@ class BTC_Watchonly_Timelocked_P2WSH(BTC_Timelocked_P2WSH):
@classmethod @classmethod
def sign_transaction(cls, tx, index, privkey, amount, def sign_transaction(cls, tx, index, privkey, amount,
hashcode=btc.SIGHASH_ALL, **kwargs): 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): 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( return super(BTC_Watchonly_P2SH_P2WPKH, cls).derive_bip32_pub_export(
master_key, BTC_Watchonly_Timelocked_P2WSH.get_watchonly_path(path)) 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 = { ENGINES = {
TYPE_P2PKH: BTC_P2PKH, TYPE_P2PKH: BTC_P2PKH,
TYPE_P2SH_P2WPKH: BTC_P2SH_P2WPKH, 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) change_addr = wallet_service.get_internal_addr(mixdepth)
outs.append({"value": changeval, "address": change_addr}) 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 #Now ready to construct transaction
log.info("Using a fee of : " + amount_to_str(fee_est) + ".") log.info("Using a fee of : " + amount_to_str(fee_est) + ".")
if amount != 0: if amount != 0:
log.info("Using a change value of: " + amount_to_str(changeval) + ".") log.info("Using a change value of: " + amount_to_str(changeval) + ".")
txsigned = sign_tx(wallet_service, make_shuffled_tx( 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("Got signed transaction:\n")
log.info(pformat(txsigned)) log.info(pformat(txsigned))
tx = serialize(txsigned) tx = serialize(txsigned)

8
jmclient/jmclient/wallet.py

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

Loading…
Cancel
Save