2020-10-31 20:08:30 +00:00
#!/usr/bin/env python
# -*- coding: utf-8 -*-
2024-01-24 21:12:18 +00:00
# Copyright (c) 2020-2024 tecnovert
2020-10-31 20:08:30 +00:00
# Distributed under the MIT software license, see the accompanying
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
2022-01-01 21:30:32 +00:00
import json
2021-01-19 13:10:42 +00:00
import base64
2020-10-31 20:08:30 +00:00
import hashlib
import logging
2022-01-01 21:30:32 +00:00
import traceback
2020-10-31 20:08:30 +00:00
from io import BytesIO
2022-12-05 15:04:23 +00:00
2020-11-15 17:02:46 +00:00
from basicswap . contrib . test_framework import segwit_addr
2023-05-11 21:45:06 +00:00
from basicswap . interface import (
Curves )
2022-08-08 22:10:37 +00:00
from basicswap . util import (
2021-11-01 13:52:40 +00:00
ensure ,
make_int ,
2022-03-23 22:00:35 +00:00
b2h , i2b , b2i , i2h )
2022-08-08 22:10:37 +00:00
from basicswap . util . ecc import (
2022-03-23 22:00:35 +00:00
ep ,
pointToCPK , CPKToPoint ,
getSecretInt )
2022-08-08 22:10:37 +00:00
from basicswap . util . script import (
2022-03-23 22:00:35 +00:00
decodeScriptNum ,
getCompactSizeLen ,
SerialiseNumCompact ,
getWitnessElementLen ,
)
2022-08-08 22:10:37 +00:00
from basicswap . util . address import (
2022-03-23 22:00:35 +00:00
toWIF ,
2021-10-20 17:47:49 +00:00
b58encode ,
2021-12-18 23:45:17 +00:00
decodeWif ,
2021-11-01 13:52:40 +00:00
decodeAddress ,
2021-11-05 08:55:18 +00:00
pubkeyToAddress ,
2022-03-23 22:00:35 +00:00
)
2020-11-14 22:13:11 +00:00
from coincurve . keys import (
2020-12-10 22:43:36 +00:00
PrivateKey ,
2020-11-14 22:13:11 +00:00
PublicKey )
from coincurve . dleag import (
verify_secp256k1_point )
2020-11-15 17:02:46 +00:00
from coincurve . ecdsaotves import (
ecdsaotves_enc_sign ,
ecdsaotves_enc_verify ,
ecdsaotves_dec_sig ,
ecdsaotves_rec_enc_key )
2020-10-31 20:08:30 +00:00
2022-08-08 22:10:37 +00:00
from basicswap . contrib . test_framework . messages import (
2020-10-31 20:08:30 +00:00
COIN ,
COutPoint ,
CTransaction ,
CTxIn ,
CTxInWitness ,
CTxOut ,
2022-12-08 01:22:18 +00:00
uint256_from_str )
2020-10-31 20:08:30 +00:00
2022-08-08 22:10:37 +00:00
from basicswap . contrib . test_framework . script import (
2021-11-01 13:52:40 +00:00
CScript , CScriptOp ,
2020-10-31 20:08:30 +00:00
OP_IF , OP_ELSE , OP_ENDIF ,
2021-11-01 13:52:40 +00:00
OP_0 , OP_2 ,
2020-10-31 20:08:30 +00:00
OP_CHECKSIG ,
OP_CHECKMULTISIG ,
OP_CHECKSEQUENCEVERIFY ,
OP_DROP ,
2022-12-05 15:04:23 +00:00
OP_HASH160 , OP_EQUAL ,
2020-10-31 20:08:30 +00:00
SIGHASH_ALL ,
SegwitV0SignatureHash ,
hash160 )
2022-08-08 22:10:37 +00:00
from basicswap . basicswap_util import (
2021-12-15 13:41:43 +00:00
TxLockTypes )
2021-02-03 14:01:27 +00:00
2022-08-08 22:10:37 +00:00
from basicswap . chainparams import CoinInterface , Coins
from basicswap . rpc import make_rpc_func , openrpc
2020-10-31 20:08:30 +00:00
2021-02-03 14:01:27 +00:00
SEQUENCE_LOCKTIME_GRANULARITY = 9 # 512 seconds
SEQUENCE_LOCKTIME_TYPE_FLAG = ( 1 << 22 )
SEQUENCE_LOCKTIME_MASK = 0x0000ffff
2021-11-01 13:52:40 +00:00
def ensure_op ( v , err_string = ' Bad opcode ' ) :
ensure ( v , err_string )
2022-12-08 01:22:18 +00:00
def findOutput ( tx , script_pk : bytes ) :
2020-10-31 20:08:30 +00:00
for i in range ( len ( tx . vout ) ) :
if tx . vout [ i ] . scriptPubKey == script_pk :
return i
return None
2022-12-10 23:26:42 +00:00
def find_vout_for_address_from_txobj ( tx_obj , addr : str ) - > int :
2021-11-04 21:49:52 +00:00
"""
Locate the vout index of the given transaction sending to the
given address . Raises runtime error exception if not found .
"""
for i in range ( len ( tx_obj [ " vout " ] ) ) :
2022-03-27 11:05:53 +00:00
scriptPubKey = tx_obj [ " vout " ] [ i ] [ " scriptPubKey " ]
if " addresses " in scriptPubKey :
if any ( [ addr == a for a in scriptPubKey [ " addresses " ] ] ) :
return i
elif " address " in scriptPubKey :
if addr == scriptPubKey [ " address " ] :
return i
2021-11-05 08:55:18 +00:00
raise RuntimeError ( " Vout not found for address: txid= {} , addr= {} " . format ( tx_obj [ ' txid ' ] , addr ) )
2021-11-04 21:49:52 +00:00
2020-10-31 20:08:30 +00:00
class BTCInterface ( CoinInterface ) :
2023-05-11 21:45:06 +00:00
@staticmethod
def curve_type ( ) :
return Curves . secp256k1
2020-11-15 17:02:46 +00:00
@staticmethod
def coin_type ( ) :
return Coins . BTC
2020-11-28 23:04:26 +00:00
@staticmethod
def COIN ( ) :
return COIN
2020-10-31 20:08:30 +00:00
@staticmethod
2021-02-13 22:54:01 +00:00
def exp ( ) - > int :
2020-10-31 20:08:30 +00:00
return 8
@staticmethod
2021-02-13 22:54:01 +00:00
def nbk ( ) - > int :
2020-10-31 20:08:30 +00:00
return 32
@staticmethod
2021-02-13 22:54:01 +00:00
def nbK ( ) - > int : # No. of bytes requires to encode a public key
2020-10-31 20:08:30 +00:00
return 33
@staticmethod
2021-02-13 22:54:01 +00:00
def witnessScaleFactor ( ) - > int :
2020-10-31 20:08:30 +00:00
return 4
@staticmethod
2021-02-13 22:54:01 +00:00
def txVersion ( ) - > int :
2020-10-31 20:08:30 +00:00
return 2
@staticmethod
2022-12-08 01:22:18 +00:00
def getTxOutputValue ( tx ) - > int :
2020-10-31 20:08:30 +00:00
rv = 0
for output in tx . vout :
rv + = output . nValue
return rv
2020-11-27 17:52:26 +00:00
@staticmethod
2021-02-13 22:54:01 +00:00
def compareFeeRates ( a , b ) - > bool :
2020-10-31 20:08:30 +00:00
return abs ( a - b ) < 20
2021-01-02 14:59:34 +00:00
@staticmethod
2023-07-18 23:19:04 +00:00
def xmr_swap_a_lock_spend_tx_vsize ( ) - > int :
2021-01-02 14:59:34 +00:00
return 147
2023-07-18 23:19:04 +00:00
@staticmethod
def xmr_swap_b_lock_spend_tx_vsize ( ) - > int :
return 110
2021-01-29 23:45:24 +00:00
@staticmethod
def txoType ( ) :
return CTxOut
2021-02-03 14:01:27 +00:00
@staticmethod
2022-12-08 01:22:18 +00:00
def getExpectedSequence ( lockType : int , lockVal : int ) - > int :
2022-07-31 18:01:49 +00:00
assert ( lockVal > = 1 ) , ' Bad lockVal '
2021-12-15 13:41:43 +00:00
if lockType == TxLockTypes . SEQUENCE_LOCK_BLOCKS :
2021-02-03 14:01:27 +00:00
return lockVal
2021-12-15 13:41:43 +00:00
if lockType == TxLockTypes . SEQUENCE_LOCK_TIME :
2021-02-03 14:01:27 +00:00
secondsLocked = lockVal
# Ensure the locked time is never less than lockVal
if secondsLocked % ( 1 << SEQUENCE_LOCKTIME_GRANULARITY ) != 0 :
secondsLocked + = ( 1 << SEQUENCE_LOCKTIME_GRANULARITY )
secondsLocked >> = SEQUENCE_LOCKTIME_GRANULARITY
return secondsLocked | SEQUENCE_LOCKTIME_TYPE_FLAG
raise ValueError ( ' Unknown lock type ' )
@staticmethod
2022-12-08 01:22:18 +00:00
def decodeSequence ( lock_value : int ) - > int :
2021-02-03 14:01:27 +00:00
# Return the raw value
if lock_value & SEQUENCE_LOCKTIME_TYPE_FLAG :
return ( lock_value & SEQUENCE_LOCKTIME_MASK ) << SEQUENCE_LOCKTIME_GRANULARITY
return lock_value & SEQUENCE_LOCKTIME_MASK
2022-12-08 01:22:18 +00:00
@staticmethod
def depth_spendable ( ) - > int :
return 0
2021-01-29 23:45:24 +00:00
def __init__ ( self , coin_settings , network , swap_client = None ) :
2021-02-15 23:20:24 +00:00
super ( ) . __init__ ( network )
2022-01-01 21:30:32 +00:00
self . _rpc_host = coin_settings . get ( ' rpchost ' , ' 127.0.0.1 ' )
self . _rpcport = coin_settings [ ' rpcport ' ]
self . _rpcauth = coin_settings [ ' rpcauth ' ]
2023-12-29 13:36:00 +00:00
self . rpc = make_rpc_func ( self . _rpcport , self . _rpcauth , host = self . _rpc_host )
self . _rpc_wallet = ' wallet.dat '
self . rpc_wallet = make_rpc_func ( self . _rpcport , self . _rpcauth , host = self . _rpc_host , wallet = self . _rpc_wallet )
2020-11-21 13:16:27 +00:00
self . blocks_confirmed = coin_settings [ ' blocks_confirmed ' ]
2021-01-18 22:52:05 +00:00
self . setConfTarget ( coin_settings [ ' conf_target ' ] )
2022-01-24 21:32:48 +00:00
self . _use_segwit = coin_settings [ ' use_segwit ' ]
2022-08-10 22:02:36 +00:00
self . _connection_type = coin_settings [ ' connection_type ' ]
2021-01-29 23:45:24 +00:00
self . _sc = swap_client
2021-02-03 14:01:27 +00:00
self . _log = self . _sc . log if self . _sc and self . _sc . log else logging
2022-11-16 22:36:13 +00:00
self . _expect_seedid_hex = None
2021-01-18 22:52:05 +00:00
2023-12-29 13:36:00 +00:00
def checkWallets ( self ) - > int :
wallets = self . rpc ( ' listwallets ' )
# Wallet name is "" for some LTC and PART installs on older cores
if self . _rpc_wallet not in wallets and len ( wallets ) > 0 :
self . _log . debug ( ' Changing {} wallet name. ' . format ( self . ticker ( ) ) )
for wallet_name in wallets :
# Skip over other expected wallets
if wallet_name in ( ' mweb ' , ) :
continue
self . _rpc_wallet = wallet_name
self . _log . info ( ' Switched {} wallet name to {} . ' . format ( self . ticker ( ) , self . _rpc_wallet ) )
self . rpc_wallet = make_rpc_func ( self . _rpcport , self . _rpcauth , host = self . _rpc_host , wallet = self . _rpc_wallet )
break
return len ( wallets )
2022-12-08 01:22:18 +00:00
def using_segwit ( self ) - > bool :
# Using btc native segwit
2022-08-10 22:02:36 +00:00
return self . _use_segwit
2023-08-29 20:06:16 +00:00
def use_p2shp2wsh ( self ) - > bool :
# p2sh-p2wsh
return False
2022-08-10 22:02:36 +00:00
def get_connection_type ( self ) :
return self . _connection_type
2022-01-01 21:30:32 +00:00
def open_rpc ( self , wallet = None ) :
return openrpc ( self . _rpcport , self . _rpcauth , wallet = wallet , host = self . _rpc_host )
def json_request ( self , rpc_conn , method , params ) :
try :
v = rpc_conn . json_request ( method , params )
r = json . loads ( v . decode ( ' utf-8 ' ) )
except Exception as ex :
traceback . print_exc ( )
raise ValueError ( ' RPC Server Error ' + str ( ex ) )
if ' error ' in r and r [ ' error ' ] is not None :
raise ValueError ( ' RPC error ' + str ( r [ ' error ' ] ) )
return r [ ' result ' ]
def close_rpc ( self , rpc_conn ) :
rpc_conn . close ( )
2022-12-08 01:22:18 +00:00
def setConfTarget ( self , new_conf_target : int ) - > None :
2022-04-10 22:11:51 +00:00
ensure ( new_conf_target > = 1 and new_conf_target < 33 , ' Invalid conf_target value ' )
2021-01-18 22:52:05 +00:00
self . _conf_target = new_conf_target
2020-10-31 20:08:30 +00:00
2022-12-08 01:22:18 +00:00
def testDaemonRPC ( self , with_wallet = True ) - > None :
2023-12-29 13:36:00 +00:00
self . rpc_wallet ( ' getwalletinfo ' if with_wallet else ' getblockchaininfo ' )
2020-11-07 11:08:07 +00:00
def getDaemonVersion ( self ) :
2023-12-29 13:36:00 +00:00
return self . rpc ( ' getnetworkinfo ' ) [ ' version ' ]
2020-11-07 11:08:07 +00:00
def getBlockchainInfo ( self ) :
2023-12-29 13:36:00 +00:00
return self . rpc ( ' getblockchaininfo ' )
2020-11-07 11:08:07 +00:00
2022-12-08 01:22:18 +00:00
def getChainHeight ( self ) - > int :
2023-12-29 13:36:00 +00:00
return self . rpc ( ' getblockcount ' )
2020-12-06 17:34:56 +00:00
def getMempoolTx ( self , txid ) :
2023-12-29 13:36:00 +00:00
return self . rpc ( ' getrawtransaction ' , [ txid . hex ( ) ] )
2020-12-06 17:34:56 +00:00
2021-01-31 12:26:32 +00:00
def getBlockHeaderFromHeight ( self , height ) :
2023-12-29 13:36:00 +00:00
block_hash = self . rpc ( ' getblockhash ' , [ height ] )
return self . rpc ( ' getblockheader ' , [ block_hash ] )
2021-01-31 12:26:32 +00:00
2021-02-03 14:01:27 +00:00
def getBlockHeader ( self , block_hash ) :
2023-12-29 13:36:00 +00:00
return self . rpc ( ' getblockheader ' , [ block_hash ] )
2021-02-03 14:01:27 +00:00
2022-12-05 15:04:23 +00:00
def getBlockHeaderAt ( self , time : int , block_after = False ) :
2023-12-29 13:36:00 +00:00
blockchaininfo = self . rpc ( ' getblockchaininfo ' )
last_block_header = self . rpc ( ' getblockheader ' , [ blockchaininfo [ ' bestblockhash ' ] ] )
2022-10-11 05:55:35 +00:00
max_tries = 5000
for i in range ( max_tries ) :
2023-12-29 13:36:00 +00:00
prev_block_header = self . rpc ( ' getblock ' , [ last_block_header [ ' previousblockhash ' ] ] )
2022-10-11 05:55:35 +00:00
if prev_block_header [ ' time ' ] < = time :
return last_block_header if block_after else prev_block_header
last_block_header = prev_block_header
raise ValueError ( f ' Block header not found at time: { time } ' )
2023-02-17 23:47:44 +00:00
def initialiseWallet ( self , key_bytes : bytes ) - > None :
2021-12-18 23:45:17 +00:00
key_wif = self . encodeKey ( key_bytes )
2023-12-29 13:36:00 +00:00
self . rpc_wallet ( ' sethdseed ' , [ True , key_wif ] )
2020-12-03 23:46:01 +00:00
2020-11-07 11:08:07 +00:00
def getWalletInfo ( self ) :
2023-12-29 13:36:00 +00:00
rv = self . rpc_wallet ( ' getwalletinfo ' )
2022-11-11 23:51:30 +00:00
rv [ ' encrypted ' ] = ' unlocked_until ' in rv
rv [ ' locked ' ] = rv . get ( ' unlocked_until ' , 1 ) < = 0
2024-01-28 18:16:30 +00:00
rv [ ' locked_utxos ' ] = len ( self . rpc_wallet ( ' listlockunspent ' ) )
2022-11-11 23:51:30 +00:00
return rv
2020-11-07 11:08:07 +00:00
2022-12-08 01:22:18 +00:00
def walletRestoreHeight ( self ) - > int :
2021-02-11 12:57:54 +00:00
return self . _restore_height
2022-12-08 01:22:18 +00:00
def getWalletRestoreHeight ( self ) - > int :
2023-12-29 13:36:00 +00:00
start_time = self . rpc_wallet ( ' getwalletinfo ' ) [ ' keypoololdest ' ]
2021-02-11 12:57:54 +00:00
2023-08-29 20:06:16 +00:00
blockchaininfo = self . getBlockchainInfo ( )
2021-02-11 12:57:54 +00:00
best_block = blockchaininfo [ ' bestblockhash ' ]
chain_synced = round ( blockchaininfo [ ' verificationprogress ' ] , 3 )
if chain_synced < 1.0 :
2021-11-01 13:52:40 +00:00
raise ValueError ( ' {} chain isn \' t synced. ' . format ( self . coin_name ( ) ) )
2021-02-11 12:57:54 +00:00
2022-01-01 21:30:32 +00:00
self . _log . debug ( ' Finding block at time: {} ' . format ( start_time ) )
rpc_conn = self . open_rpc ( )
try :
block_hash = best_block
while True :
block_header = self . json_request ( rpc_conn , ' getblockheader ' , [ block_hash ] )
if block_header [ ' time ' ] < start_time :
return block_header [ ' height ' ]
block_hash = block_header [ ' previousblockhash ' ]
finally :
self . close_rpc ( rpc_conn )
2022-12-08 01:22:18 +00:00
raise ValueError ( ' {} wallet restore height not found. ' . format ( self . coin_name ( ) ) )
2021-02-11 12:57:54 +00:00
2022-12-05 15:04:23 +00:00
def getWalletSeedID ( self ) - > str :
2023-12-29 13:36:00 +00:00
wi = self . rpc_wallet ( ' getwalletinfo ' )
2022-12-12 22:12:28 +00:00
return ' Not found ' if ' hdseedid ' not in wi else wi [ ' hdseedid ' ]
2020-12-04 17:06:50 +00:00
2022-12-05 15:04:23 +00:00
def checkExpectedSeed ( self , expect_seedid ) - > bool :
2022-11-16 22:36:13 +00:00
self . _expect_seedid_hex = expect_seedid
2022-10-21 11:00:28 +00:00
return expect_seedid == self . getWalletSeedID ( )
2022-12-05 15:04:23 +00:00
def getNewAddress ( self , use_segwit : bool , label : str = ' swap_receive ' ) - > str :
2022-01-24 21:32:48 +00:00
args = [ label ]
2020-11-07 11:08:07 +00:00
if use_segwit :
args . append ( ' bech32 ' )
2023-12-29 13:36:00 +00:00
return self . rpc_wallet ( ' getnewaddress ' , args )
2020-11-07 11:08:07 +00:00
2023-07-05 21:35:25 +00:00
def isValidAddress ( self , address : str ) - > bool :
try :
2023-12-29 13:36:00 +00:00
rv = self . rpc_wallet ( ' validateaddress ' , [ address ] )
2023-07-05 21:35:25 +00:00
if rv [ ' isvalid ' ] is True :
return True
except Exception as ex :
self . _log . debug ( ' validateaddress failed: {} ' . format ( address ) )
return False
def isValidAddressHash ( self , address_hash : bytes ) - > bool :
hash_len = len ( address_hash )
if hash_len == 20 :
return True
def isValidPubkey ( self , pubkey : bytes ) - > bool :
try :
self . verifyPubkey ( pubkey )
return True
except Exception :
return False
2022-12-08 01:22:18 +00:00
def isAddressMine ( self , address : str , or_watch_only : bool = False ) - > bool :
2023-12-29 13:36:00 +00:00
addr_info = self . rpc_wallet ( ' getaddressinfo ' , [ address ] )
2022-12-08 01:22:18 +00:00
if not or_watch_only :
return addr_info [ ' ismine ' ]
return addr_info [ ' ismine ' ] or addr_info [ ' iswatchonly ' ]
2022-11-16 22:36:13 +00:00
2022-12-05 15:04:23 +00:00
def checkAddressMine ( self , address : str ) - > None :
2023-12-29 13:36:00 +00:00
addr_info = self . rpc_wallet ( ' getaddressinfo ' , [ address ] )
2022-11-16 22:36:13 +00:00
ensure ( addr_info [ ' ismine ' ] , ' ismine is false ' )
2023-02-19 19:52:22 +00:00
if self . sc . _restrict_unknown_seed_wallets :
ensure ( addr_info [ ' hdseedid ' ] == self . _expect_seedid_hex , ' unexpected seedid ' )
2022-11-16 22:36:13 +00:00
2023-12-01 17:16:28 +00:00
def get_fee_rate ( self , conf_target : int = 2 ) - > ( float , str ) :
2024-02-01 09:28:21 +00:00
chain_client_settings = self . _sc . getChainClientSettings ( self . coin_type ( ) ) # basicswap.json
2023-12-01 17:16:28 +00:00
override_feerate = chain_client_settings . get ( ' override_feerate ' , None )
if override_feerate :
self . _log . debug ( ' Fee rate override used for %s : %f ' , self . coin_name ( ) , override_feerate )
return override_feerate , ' override_feerate '
min_relay_fee = chain_client_settings . get ( ' min_relay_fee ' , None )
2023-12-01 19:30:23 +00:00
2023-12-01 17:16:28 +00:00
def try_get_fee_rate ( self , conf_target ) :
2020-11-27 22:20:35 +00:00
try :
2024-02-01 09:28:21 +00:00
fee_rate : float = self . rpc_wallet ( ' estimatesmartfee ' , [ conf_target ] ) [ ' feerate ' ]
assert ( fee_rate > 0.0 ) , ' Negative feerate '
2023-12-01 17:16:28 +00:00
return fee_rate , ' estimatesmartfee '
2020-11-27 22:20:35 +00:00
except Exception :
2023-12-01 17:16:28 +00:00
try :
2024-02-01 09:28:21 +00:00
fee_rate : float = self . rpc_wallet ( ' getwalletinfo ' ) [ ' paytxfee ' ]
2023-12-01 17:16:28 +00:00
assert ( fee_rate > 0.0 ) , ' Non positive feerate '
return fee_rate , ' paytxfee '
except Exception :
2024-02-01 09:28:21 +00:00
fee_rate : float = self . rpc ( ' getnetworkinfo ' ) [ ' relayfee ' ]
return fee_rate , ' relayfee '
2023-12-01 17:16:28 +00:00
fee_rate , rate_src = try_get_fee_rate ( self , conf_target )
if min_relay_fee and min_relay_fee > fee_rate :
2024-02-01 09:28:21 +00:00
self . _log . warning ( ' Feerate {} ( {} ) is below min relay fee {} for {} ' . format ( self . format_amount ( fee_rate , True , 1 ) , rate_src , self . format_amount ( min_relay_fee , True , 1 ) , self . coin_name ( ) ) )
2023-12-01 17:16:28 +00:00
return min_relay_fee , ' min_relay_fee '
return fee_rate , rate_src
2020-11-27 22:20:35 +00:00
2022-12-08 01:22:18 +00:00
def isSegwitAddress ( self , address : str ) - > bool :
2021-11-27 15:58:58 +00:00
return address . startswith ( self . chainparams_network ( ) [ ' hrp ' ] + ' 1 ' )
2022-12-08 01:22:18 +00:00
def decodeAddress ( self , address : str ) - > bytes :
2021-11-01 13:52:40 +00:00
bech32_prefix = self . chainparams_network ( ) [ ' hrp ' ]
2022-11-07 20:31:10 +00:00
if len ( bech32_prefix ) > 0 and address . startswith ( bech32_prefix + ' 1 ' ) :
2020-11-29 23:05:30 +00:00
return bytes ( segwit_addr . decode ( bech32_prefix , address ) [ 1 ] )
2020-11-15 17:02:46 +00:00
return decodeAddress ( address ) [ 1 : ]
2022-12-08 01:22:18 +00:00
def pubkey_to_segwit_address ( self , pk : bytes ) - > str :
2021-11-01 13:52:40 +00:00
bech32_prefix = self . chainparams_network ( ) [ ' hrp ' ]
2021-09-04 23:18:34 +00:00
version = 0
pkh = hash160 ( pk )
2021-09-05 15:36:27 +00:00
return segwit_addr . encode ( bech32_prefix , version , pkh )
2021-09-04 23:18:34 +00:00
2022-12-08 01:22:18 +00:00
def pkh_to_address ( self , pkh : bytes ) - > str :
2021-11-01 13:52:40 +00:00
# pkh is hash160(pk)
2022-07-31 18:01:49 +00:00
assert ( len ( pkh ) == 20 )
2021-11-01 13:52:40 +00:00
prefix = self . chainparams_network ( ) [ ' pubkey_address ' ]
data = bytes ( ( prefix , ) ) + pkh
2021-10-20 17:47:49 +00:00
checksum = hashlib . sha256 ( hashlib . sha256 ( data ) . digest ( ) ) . digest ( )
return b58encode ( data + checksum [ 0 : 4 ] )
2022-12-08 01:22:18 +00:00
def sh_to_address ( self , sh : bytes ) - > str :
2022-11-07 20:31:10 +00:00
assert ( len ( sh ) == 20 )
prefix = self . chainparams_network ( ) [ ' script_address ' ]
data = bytes ( ( prefix , ) ) + sh
checksum = hashlib . sha256 ( hashlib . sha256 ( data ) . digest ( ) ) . digest ( )
return b58encode ( data + checksum [ 0 : 4 ] )
2022-12-08 01:22:18 +00:00
def encode_p2wsh ( self , script : bytes ) - > str :
2021-11-01 13:52:40 +00:00
bech32_prefix = self . chainparams_network ( ) [ ' hrp ' ]
version = 0
program = script [ 2 : ] # strip version and length
return segwit_addr . encode ( bech32_prefix , version , program )
2022-12-08 01:22:18 +00:00
def encodeScriptDest ( self , script : bytes ) - > str :
2022-11-07 20:31:10 +00:00
return self . encode_p2wsh ( script )
2022-12-08 01:22:18 +00:00
def encode_p2sh ( self , script : bytes ) - > str :
2021-11-05 08:55:18 +00:00
return pubkeyToAddress ( self . chainparams_network ( ) [ ' script_address ' ] , script )
2022-12-08 01:22:18 +00:00
def pubkey_to_address ( self , pk : bytes ) - > str :
2022-07-31 18:01:49 +00:00
assert ( len ( pk ) == 33 )
2021-11-01 13:52:40 +00:00
return self . pkh_to_address ( hash160 ( pk ) )
2023-07-14 07:31:05 +00:00
def getNewSecretKey ( self ) - > bytes :
return i2b ( getSecretInt ( ) )
2020-10-31 20:08:30 +00:00
2020-11-14 22:13:11 +00:00
def getPubkey ( self , privkey ) :
return PublicKey . from_secret ( privkey ) . format ( )
2023-02-17 23:47:44 +00:00
def getAddressHashFromKey ( self , key : bytes ) - > bytes :
2020-12-04 17:06:50 +00:00
pk = self . getPubkey ( key )
return hash160 ( pk )
2023-02-17 23:47:44 +00:00
def getSeedHash ( self , seed ) - > bytes :
2022-11-08 14:43:28 +00:00
return self . getAddressHashFromKey ( seed ) [ : : - 1 ]
2022-12-08 01:22:18 +00:00
def verifyKey ( self , k : bytes ) - > bool :
2020-11-14 22:13:11 +00:00
i = b2i ( k )
2022-07-31 18:01:49 +00:00
return ( i < ep . o and i > 0 )
2020-11-14 22:13:11 +00:00
2022-12-08 01:22:18 +00:00
def verifyPubkey ( self , pubkey_bytes : bytes ) - > bool :
2020-11-14 22:13:11 +00:00
return verify_secp256k1_point ( pubkey_bytes )
2022-12-08 01:22:18 +00:00
def encodeKey ( self , key_bytes : bytes ) - > str :
2021-12-18 23:45:17 +00:00
wif_prefix = self . chainparams_network ( ) [ ' key_prefix ' ]
return toWIF ( wif_prefix , key_bytes )
2022-12-08 01:22:18 +00:00
def encodePubkey ( self , pk : bytes ) - > bytes :
2020-10-31 20:08:30 +00:00
return pointToCPK ( pk )
2022-12-08 01:22:18 +00:00
def encodeSegwitAddress ( self , key_hash : bytes ) - > str :
2022-11-07 20:31:10 +00:00
return segwit_addr . encode ( self . chainparams_network ( ) [ ' hrp ' ] , 0 , key_hash )
2022-12-08 01:22:18 +00:00
def decodeSegwitAddress ( self , addr : str ) - > bytes :
2022-11-07 20:31:10 +00:00
return bytes ( segwit_addr . decode ( self . chainparams_network ( ) [ ' hrp ' ] , addr ) [ 1 ] )
2020-10-31 20:08:30 +00:00
def decodePubkey ( self , pke ) :
return CPKToPoint ( pke )
2023-08-29 20:06:16 +00:00
def decodeKey ( self , k : str ) - > bytes :
2021-12-18 23:45:17 +00:00
return decodeWif ( k )
2020-10-31 20:08:30 +00:00
def sumKeys ( self , ka , kb ) :
2021-02-11 12:57:54 +00:00
# TODO: Add to coincurve
return i2b ( ( b2i ( ka ) + b2i ( kb ) ) % ep . o )
2020-10-31 20:08:30 +00:00
def sumPubkeys ( self , Ka , Kb ) :
2021-02-11 12:57:54 +00:00
return PublicKey . combine_keys ( [ PublicKey ( Ka ) , PublicKey ( Kb ) ] ) . format ( )
2020-10-31 20:08:30 +00:00
2022-12-08 01:22:18 +00:00
def getScriptForPubkeyHash ( self , pkh : bytes ) - > CScript :
2023-08-29 20:06:16 +00:00
# p2wpkh
2020-12-08 22:05:28 +00:00
return CScript ( [ OP_0 , pkh ] )
2023-08-29 20:06:16 +00:00
def loadTx ( self , tx_bytes : bytes ) - > CTransaction :
# Load tx from bytes to internal representation
tx = CTransaction ( )
tx . deserialize ( BytesIO ( tx_bytes ) )
return tx
2022-12-08 01:22:18 +00:00
def extractScriptLockScriptValues ( self , script_bytes : bytes ) :
2020-10-31 20:08:30 +00:00
script_len = len ( script_bytes )
2021-11-01 13:52:40 +00:00
ensure ( script_len == 71 , ' Bad script length ' )
2020-12-10 22:43:36 +00:00
o = 0
2021-11-01 13:52:40 +00:00
ensure_op ( script_bytes [ o ] == OP_2 )
ensure_op ( script_bytes [ o + 1 ] == 33 )
2020-12-10 22:43:36 +00:00
o + = 2
pk1 = script_bytes [ o : o + 33 ]
2020-10-31 20:08:30 +00:00
o + = 33
2021-11-01 13:52:40 +00:00
ensure_op ( script_bytes [ o ] == 33 )
2020-10-31 20:08:30 +00:00
o + = 1
2020-12-10 22:43:36 +00:00
pk2 = script_bytes [ o : o + 33 ]
2020-10-31 20:08:30 +00:00
o + = 33
2021-11-01 13:52:40 +00:00
ensure_op ( script_bytes [ o ] == OP_2 )
ensure_op ( script_bytes [ o + 1 ] == OP_CHECKMULTISIG )
2020-10-31 20:08:30 +00:00
2020-12-10 22:43:36 +00:00
return pk1 , pk2
2020-11-14 22:13:11 +00:00
2022-12-08 01:22:18 +00:00
def createSCLockTx ( self , value : int , script : bytearray , vkbv : bytes = None ) - > bytes :
2020-10-31 20:08:30 +00:00
tx = CTransaction ( )
tx . nVersion = self . txVersion ( )
2021-01-29 23:45:24 +00:00
tx . vout . append ( self . txoType ( ) ( value , self . getScriptDest ( script ) ) )
2022-12-05 22:45:35 +00:00
return tx . serialize ( )
2020-10-31 20:08:30 +00:00
2022-11-07 20:31:10 +00:00
def fundSCLockTx ( self , tx_bytes , feerate , vkbv = None ) :
2021-11-01 13:52:40 +00:00
return self . fundTx ( tx_bytes , feerate )
2023-07-14 07:31:05 +00:00
def extractScriptLockRefundScriptValues ( self , script_bytes : bytes ) :
2020-10-31 20:08:30 +00:00
script_len = len ( script_bytes )
2021-11-01 13:52:40 +00:00
ensure ( script_len > 73 , ' Bad script length ' )
ensure_op ( script_bytes [ 0 ] == OP_IF )
ensure_op ( script_bytes [ 1 ] == OP_2 )
ensure_op ( script_bytes [ 2 ] == 33 )
2020-10-31 20:08:30 +00:00
pk1 = script_bytes [ 3 : 3 + 33 ]
2021-11-01 13:52:40 +00:00
ensure_op ( script_bytes [ 36 ] == 33 )
2020-10-31 20:08:30 +00:00
pk2 = script_bytes [ 37 : 37 + 33 ]
2021-11-01 13:52:40 +00:00
ensure_op ( script_bytes [ 70 ] == OP_2 )
ensure_op ( script_bytes [ 71 ] == OP_CHECKMULTISIG )
ensure_op ( script_bytes [ 72 ] == OP_ELSE )
2020-10-31 20:08:30 +00:00
o = 73
csv_val , nb = decodeScriptNum ( script_bytes , o )
o + = nb
2021-11-01 13:52:40 +00:00
ensure ( script_len == o + 5 + 33 , ' Bad script length ' ) # Fails if script too long
ensure_op ( script_bytes [ o ] == OP_CHECKSEQUENCEVERIFY )
2020-10-31 20:08:30 +00:00
o + = 1
2021-11-01 13:52:40 +00:00
ensure_op ( script_bytes [ o ] == OP_DROP )
2020-10-31 20:08:30 +00:00
o + = 1
2021-11-01 13:52:40 +00:00
ensure_op ( script_bytes [ o ] == 33 )
2020-10-31 20:08:30 +00:00
o + = 1
pk3 = script_bytes [ o : o + 33 ]
o + = 33
2021-11-01 13:52:40 +00:00
ensure_op ( script_bytes [ o ] == OP_CHECKSIG )
2020-10-31 20:08:30 +00:00
o + = 1
2021-11-01 13:52:40 +00:00
ensure_op ( script_bytes [ o ] == OP_ENDIF )
2020-10-31 20:08:30 +00:00
return pk1 , pk2 , csv_val , pk3
2023-07-14 07:31:05 +00:00
def genScriptLockRefundTxScript ( self , Kal , Kaf , csv_val ) - > CScript :
2020-11-14 22:13:11 +00:00
2020-12-10 22:43:36 +00:00
Kal_enc = Kal if len ( Kal ) == 33 else self . encodePubkey ( Kal )
2020-11-14 22:13:11 +00:00
Kaf_enc = Kaf if len ( Kaf ) == 33 else self . encodePubkey ( Kaf )
2020-10-31 20:08:30 +00:00
return CScript ( [
CScriptOp ( OP_IF ) ,
2020-12-10 22:43:36 +00:00
2 , Kal_enc , Kaf_enc , 2 , CScriptOp ( OP_CHECKMULTISIG ) ,
2020-10-31 20:08:30 +00:00
CScriptOp ( OP_ELSE ) ,
csv_val , CScriptOp ( OP_CHECKSEQUENCEVERIFY ) , CScriptOp ( OP_DROP ) ,
2020-11-14 22:13:11 +00:00
Kaf_enc , CScriptOp ( OP_CHECKSIG ) ,
2020-10-31 20:08:30 +00:00
CScriptOp ( OP_ENDIF ) ] )
2022-11-07 20:31:10 +00:00
def createSCLockRefundTx ( self , tx_lock_bytes , script_lock , Kal , Kaf , lock1_value , csv_val , tx_fee_rate , vkbv = None ) :
2020-11-14 22:13:11 +00:00
tx_lock = CTransaction ( )
2023-08-29 20:06:16 +00:00
tx_lock = self . loadTx ( tx_lock_bytes )
2020-10-31 20:08:30 +00:00
2022-11-07 20:31:10 +00:00
output_script = self . getScriptDest ( script_lock )
2020-10-31 20:08:30 +00:00
locked_n = findOutput ( tx_lock , output_script )
2021-11-01 13:52:40 +00:00
ensure ( locked_n is not None , ' Output not found in tx ' )
2020-10-31 20:08:30 +00:00
locked_coin = tx_lock . vout [ locked_n ] . nValue
tx_lock . rehash ( )
2021-11-01 13:52:40 +00:00
tx_lock_id_int = tx_lock . sha256
2020-10-31 20:08:30 +00:00
2020-12-10 22:43:36 +00:00
refund_script = self . genScriptLockRefundTxScript ( Kal , Kaf , csv_val )
2020-10-31 20:08:30 +00:00
tx = CTransaction ( )
tx . nVersion = self . txVersion ( )
2022-11-07 20:31:10 +00:00
tx . vin . append ( CTxIn ( COutPoint ( tx_lock_id_int , locked_n ) ,
nSequence = lock1_value ,
scriptSig = self . getScriptScriptSig ( script_lock ) ) )
tx . vout . append ( self . txoType ( ) ( locked_coin , self . getScriptDest ( refund_script ) ) )
2020-10-31 20:08:30 +00:00
2021-11-01 13:52:40 +00:00
dummy_witness_stack = self . getScriptLockTxDummyWitness ( script_lock )
witness_bytes = self . getWitnessStackSerialisedLength ( dummy_witness_stack )
2020-10-31 20:08:30 +00:00
vsize = self . getTxVSize ( tx , add_witness_bytes = witness_bytes )
2023-07-14 07:31:05 +00:00
pay_fee = round ( tx_fee_rate * vsize / 1000 )
2020-10-31 20:08:30 +00:00
tx . vout [ 0 ] . nValue = locked_coin - pay_fee
tx . rehash ( )
2022-11-07 20:31:10 +00:00
self . _log . info ( ' createSCLockRefundTx %s : \n fee_rate, vsize, fee: %ld , %ld , %ld . ' ,
2021-01-29 23:45:24 +00:00
i2h ( tx . sha256 ) , tx_fee_rate , vsize , pay_fee )
2020-10-31 20:08:30 +00:00
2020-11-14 22:13:11 +00:00
return tx . serialize ( ) , refund_script , tx . vout [ 0 ] . nValue
2020-10-31 20:08:30 +00:00
2022-11-07 20:31:10 +00:00
def createSCLockRefundSpendTx ( self , tx_lock_refund_bytes , script_lock_refund , pkh_refund_to , tx_fee_rate , vkbv = None ) :
2020-10-31 20:08:30 +00:00
# Returns the coinA locked coin to the leader
# The follower will sign the multisig path with a signature encumbered by the leader's coinB spend pubkey
2020-11-27 17:52:26 +00:00
# If the leader publishes the decrypted signature the leader's coinB spend privatekey will be revealed to the follower
2020-10-31 20:08:30 +00:00
2020-11-14 22:13:11 +00:00
tx_lock_refund = self . loadTx ( tx_lock_refund_bytes )
2022-11-07 20:31:10 +00:00
output_script = self . getScriptDest ( script_lock_refund )
2020-10-31 20:08:30 +00:00
locked_n = findOutput ( tx_lock_refund , output_script )
2021-11-01 13:52:40 +00:00
ensure ( locked_n is not None , ' Output not found in tx ' )
2020-10-31 20:08:30 +00:00
locked_coin = tx_lock_refund . vout [ locked_n ] . nValue
tx_lock_refund . rehash ( )
tx_lock_refund_hash_int = tx_lock_refund . sha256
tx = CTransaction ( )
tx . nVersion = self . txVersion ( )
2022-11-07 20:31:10 +00:00
tx . vin . append ( CTxIn ( COutPoint ( tx_lock_refund_hash_int , locked_n ) ,
nSequence = 0 ,
scriptSig = self . getScriptScriptSig ( script_lock_refund ) ) )
2020-10-31 20:08:30 +00:00
2021-01-29 23:45:24 +00:00
tx . vout . append ( self . txoType ( ) ( locked_coin , self . getScriptForPubkeyHash ( pkh_refund_to ) ) )
2020-10-31 20:08:30 +00:00
2021-11-01 13:52:40 +00:00
dummy_witness_stack = self . getScriptLockRefundSpendTxDummyWitness ( script_lock_refund )
witness_bytes = self . getWitnessStackSerialisedLength ( dummy_witness_stack )
2020-10-31 20:08:30 +00:00
vsize = self . getTxVSize ( tx , add_witness_bytes = witness_bytes )
2023-07-14 07:31:05 +00:00
pay_fee = round ( tx_fee_rate * vsize / 1000 )
2020-10-31 20:08:30 +00:00
tx . vout [ 0 ] . nValue = locked_coin - pay_fee
tx . rehash ( )
2022-11-07 20:31:10 +00:00
self . _log . info ( ' createSCLockRefundSpendTx %s : \n fee_rate, vsize, fee: %ld , %ld , %ld . ' ,
2021-01-29 23:45:24 +00:00
i2h ( tx . sha256 ) , tx_fee_rate , vsize , pay_fee )
2020-10-31 20:08:30 +00:00
2020-11-14 22:13:11 +00:00
return tx . serialize ( )
2020-10-31 20:08:30 +00:00
2022-11-07 20:31:10 +00:00
def createSCLockRefundSpendToFTx ( self , tx_lock_refund_bytes , script_lock_refund , pkh_dest , tx_fee_rate , vkbv = None ) :
2021-11-01 13:52:40 +00:00
# lock refund swipe tx
2020-10-31 20:08:30 +00:00
# Sends the coinA locked coin to the follower
2020-11-27 17:52:26 +00:00
tx_lock_refund = self . loadTx ( tx_lock_refund_bytes )
2022-11-07 20:31:10 +00:00
output_script = self . getScriptDest ( script_lock_refund )
2020-10-31 20:08:30 +00:00
locked_n = findOutput ( tx_lock_refund , output_script )
2021-11-01 13:52:40 +00:00
ensure ( locked_n is not None , ' Output not found in tx ' )
2020-10-31 20:08:30 +00:00
locked_coin = tx_lock_refund . vout [ locked_n ] . nValue
A , B , lock2_value , C = self . extractScriptLockRefundScriptValues ( script_lock_refund )
tx_lock_refund . rehash ( )
tx_lock_refund_hash_int = tx_lock_refund . sha256
tx = CTransaction ( )
tx . nVersion = self . txVersion ( )
2022-11-07 20:31:10 +00:00
tx . vin . append ( CTxIn ( COutPoint ( tx_lock_refund_hash_int , locked_n ) ,
nSequence = lock2_value ,
scriptSig = self . getScriptScriptSig ( script_lock_refund ) ) )
2020-10-31 20:08:30 +00:00
2021-01-29 23:45:24 +00:00
tx . vout . append ( self . txoType ( ) ( locked_coin , self . getScriptForPubkeyHash ( pkh_dest ) ) )
2020-10-31 20:08:30 +00:00
2021-11-01 13:52:40 +00:00
dummy_witness_stack = self . getScriptLockRefundSwipeTxDummyWitness ( script_lock_refund )
witness_bytes = self . getWitnessStackSerialisedLength ( dummy_witness_stack )
2020-10-31 20:08:30 +00:00
vsize = self . getTxVSize ( tx , add_witness_bytes = witness_bytes )
2023-07-14 07:31:05 +00:00
pay_fee = round ( tx_fee_rate * vsize / 1000 )
2020-10-31 20:08:30 +00:00
tx . vout [ 0 ] . nValue = locked_coin - pay_fee
tx . rehash ( )
2022-11-07 20:31:10 +00:00
self . _log . info ( ' createSCLockRefundSpendToFTx %s : \n fee_rate, vsize, fee: %ld , %ld , %ld . ' ,
2021-01-29 23:45:24 +00:00
i2h ( tx . sha256 ) , tx_fee_rate , vsize , pay_fee )
2020-10-31 20:08:30 +00:00
2020-11-14 22:13:11 +00:00
return tx . serialize ( )
2020-10-31 20:08:30 +00:00
2023-07-14 07:31:05 +00:00
def createSCLockSpendTx ( self , tx_lock_bytes , script_lock , pkh_dest , tx_fee_rate , vkbv = None , fee_info = { } ) :
2020-11-15 17:02:46 +00:00
tx_lock = self . loadTx ( tx_lock_bytes )
2022-11-07 20:31:10 +00:00
output_script = self . getScriptDest ( script_lock )
2020-10-31 20:08:30 +00:00
locked_n = findOutput ( tx_lock , output_script )
2021-11-01 13:52:40 +00:00
ensure ( locked_n is not None , ' Output not found in tx ' )
2020-10-31 20:08:30 +00:00
locked_coin = tx_lock . vout [ locked_n ] . nValue
tx_lock . rehash ( )
2021-11-01 13:52:40 +00:00
tx_lock_id_int = tx_lock . sha256
2020-10-31 20:08:30 +00:00
tx = CTransaction ( )
tx . nVersion = self . txVersion ( )
2022-11-07 20:31:10 +00:00
tx . vin . append ( CTxIn ( COutPoint ( tx_lock_id_int , locked_n ) ,
scriptSig = self . getScriptScriptSig ( script_lock ) ) )
2020-10-31 20:08:30 +00:00
2021-01-29 23:45:24 +00:00
tx . vout . append ( self . txoType ( ) ( locked_coin , self . getScriptForPubkeyHash ( pkh_dest ) ) )
2020-10-31 20:08:30 +00:00
2021-11-01 13:52:40 +00:00
dummy_witness_stack = self . getScriptLockTxDummyWitness ( script_lock )
witness_bytes = self . getWitnessStackSerialisedLength ( dummy_witness_stack )
2020-10-31 20:08:30 +00:00
vsize = self . getTxVSize ( tx , add_witness_bytes = witness_bytes )
2023-07-14 07:31:05 +00:00
pay_fee = round ( tx_fee_rate * vsize / 1000 )
2020-10-31 20:08:30 +00:00
tx . vout [ 0 ] . nValue = locked_coin - pay_fee
2023-07-14 07:31:05 +00:00
fee_info [ ' fee_paid ' ] = pay_fee
fee_info [ ' rate_used ' ] = tx_fee_rate
fee_info [ ' witness_bytes ' ] = witness_bytes
fee_info [ ' vsize ' ] = vsize
2020-10-31 20:08:30 +00:00
tx . rehash ( )
2022-11-07 20:31:10 +00:00
self . _log . info ( ' createSCLockSpendTx %s : \n fee_rate, vsize, fee: %ld , %ld , %ld . ' ,
2021-01-29 23:45:24 +00:00
i2h ( tx . sha256 ) , tx_fee_rate , vsize , pay_fee )
2020-10-31 20:08:30 +00:00
2020-11-14 22:13:11 +00:00
return tx . serialize ( )
2020-10-31 20:08:30 +00:00
2022-11-07 20:31:10 +00:00
def verifySCLockTx ( self , tx_bytes , script_out ,
swap_value ,
Kal , Kaf ,
feerate ,
check_lock_tx_inputs , vkbv = None ) :
2020-10-31 20:08:30 +00:00
# Verify:
#
# Not necessary to check the lock txn is mineable, as protocol will wait for it to confirm
# However by checking early we can avoid wasting time processing unmineable txns
# Check fee is reasonable
2020-11-15 17:02:46 +00:00
tx = self . loadTx ( tx_bytes )
2021-11-01 13:52:40 +00:00
txid = self . getTxid ( tx )
self . _log . info ( ' Verifying lock tx: {} . ' . format ( b2h ( txid ) ) )
2020-10-31 20:08:30 +00:00
2021-11-01 13:52:40 +00:00
ensure ( tx . nVersion == self . txVersion ( ) , ' Bad version ' )
ensure ( tx . nLockTime == 0 , ' Bad nLockTime ' ) # TODO match txns created by cores
2020-10-31 20:08:30 +00:00
2022-11-07 20:31:10 +00:00
script_pk = self . getScriptDest ( script_out )
2020-10-31 20:08:30 +00:00
locked_n = findOutput ( tx , script_pk )
2021-11-01 13:52:40 +00:00
ensure ( locked_n is not None , ' Output not found in tx ' )
2020-10-31 20:08:30 +00:00
locked_coin = tx . vout [ locked_n ] . nValue
2021-11-01 13:52:40 +00:00
# Check value
ensure ( locked_coin == swap_value , ' Bad locked value ' )
2020-10-31 20:08:30 +00:00
2021-11-01 13:52:40 +00:00
# Check script
2020-12-10 22:43:36 +00:00
A , B = self . extractScriptLockScriptValues ( script_out )
2021-11-01 13:52:40 +00:00
ensure ( A == Kal , ' Bad script pubkey ' )
ensure ( B == Kaf , ' Bad script pubkey ' )
2020-10-31 20:08:30 +00:00
if check_lock_tx_inputs :
2021-11-01 13:52:40 +00:00
# TODO: Check that inputs are unspent
# Verify fee rate
2020-10-31 20:08:30 +00:00
inputs_value = 0
add_bytes = 0
add_witness_bytes = getCompactSizeLen ( len ( tx . vin ) )
for pi in tx . vin :
2023-12-29 13:36:00 +00:00
ptx = self . rpc ( ' getrawtransaction ' , [ i2h ( pi . prevout . hash ) , True ] )
2020-10-31 20:08:30 +00:00
prevout = ptx [ ' vout ' ] [ pi . prevout . n ]
inputs_value + = make_int ( prevout [ ' value ' ] )
prevout_type = prevout [ ' scriptPubKey ' ] [ ' type ' ]
if prevout_type == ' witness_v0_keyhash ' :
add_witness_bytes + = 107 # sig 72, pk 33 and 2 size bytes
add_witness_bytes + = getCompactSizeLen ( 107 )
else :
# Assume P2PKH, TODO more types
add_bytes + = 107 # OP_PUSH72 <ecdsa_signature> OP_PUSH33 <public_key>
outputs_value = 0
for txo in tx . vout :
outputs_value + = txo . nValue
fee_paid = inputs_value - outputs_value
2022-07-31 18:01:49 +00:00
assert ( fee_paid > 0 )
2020-10-31 20:08:30 +00:00
vsize = self . getTxVSize ( tx , add_bytes , add_witness_bytes )
2021-11-01 13:52:40 +00:00
fee_rate_paid = fee_paid * 1000 / / vsize
2020-10-31 20:08:30 +00:00
2021-01-29 23:45:24 +00:00
self . _log . info ( ' tx amount, vsize, feerate: %ld , %ld , %ld ' , locked_coin , vsize , fee_rate_paid )
2020-10-31 20:08:30 +00:00
if not self . compareFeeRates ( fee_rate_paid , feerate ) :
2021-01-29 23:45:24 +00:00
self . _log . warning ( ' feerate paid doesn \' t match expected: %ld , %ld ' , fee_rate_paid , feerate )
2020-10-31 20:08:30 +00:00
# TODO: Display warning to user
2021-11-01 13:52:40 +00:00
return txid , locked_n
2020-10-31 20:08:30 +00:00
2022-11-07 20:31:10 +00:00
def verifySCLockRefundTx ( self , tx_bytes , lock_tx_bytes , script_out ,
prevout_id , prevout_n , prevout_seq , prevout_script ,
Kal , Kaf , csv_val_expect , swap_value , feerate , vkbv = None ) :
2020-10-31 20:08:30 +00:00
# Verify:
# Must have only one input with correct prevout and sequence
# Must have only one output to the p2wsh of the lock refund script
# Output value must be locked_coin - lock tx fee
2020-11-15 17:02:46 +00:00
tx = self . loadTx ( tx_bytes )
2021-11-01 13:52:40 +00:00
txid = self . getTxid ( tx )
self . _log . info ( ' Verifying lock refund tx: {} . ' . format ( b2h ( txid ) ) )
2020-10-31 20:08:30 +00:00
2021-11-01 13:52:40 +00:00
ensure ( tx . nVersion == self . txVersion ( ) , ' Bad version ' )
ensure ( tx . nLockTime == 0 , ' nLockTime not 0 ' )
ensure ( len ( tx . vin ) == 1 , ' tx doesn \' t have one input ' )
2020-10-31 20:08:30 +00:00
2021-11-01 13:52:40 +00:00
ensure ( tx . vin [ 0 ] . nSequence == prevout_seq , ' Bad input nSequence ' )
2022-11-07 20:31:10 +00:00
ensure ( tx . vin [ 0 ] . scriptSig == self . getScriptScriptSig ( prevout_script ) , ' Input scriptsig mismatch ' )
2021-11-01 13:52:40 +00:00
ensure ( tx . vin [ 0 ] . prevout . hash == b2i ( prevout_id ) and tx . vin [ 0 ] . prevout . n == prevout_n , ' Input prevout mismatch ' )
2020-10-31 20:08:30 +00:00
2021-11-01 13:52:40 +00:00
ensure ( len ( tx . vout ) == 1 , ' tx doesn \' t have one output ' )
2020-10-31 20:08:30 +00:00
2022-11-07 20:31:10 +00:00
script_pk = self . getScriptDest ( script_out )
2020-10-31 20:08:30 +00:00
locked_n = findOutput ( tx , script_pk )
2021-11-01 13:52:40 +00:00
ensure ( locked_n is not None , ' Output not found in tx ' )
2020-10-31 20:08:30 +00:00
locked_coin = tx . vout [ locked_n ] . nValue
# Check script and values
A , B , csv_val , C = self . extractScriptLockRefundScriptValues ( script_out )
2021-11-01 13:52:40 +00:00
ensure ( A == Kal , ' Bad script pubkey ' )
ensure ( B == Kaf , ' Bad script pubkey ' )
ensure ( csv_val == csv_val_expect , ' Bad script csv value ' )
ensure ( C == Kaf , ' Bad script pubkey ' )
2020-10-31 20:08:30 +00:00
fee_paid = swap_value - locked_coin
2022-07-31 18:01:49 +00:00
assert ( fee_paid > 0 )
2020-10-31 20:08:30 +00:00
2021-11-01 13:52:40 +00:00
dummy_witness_stack = self . getScriptLockTxDummyWitness ( prevout_script )
witness_bytes = self . getWitnessStackSerialisedLength ( dummy_witness_stack )
2020-10-31 20:08:30 +00:00
vsize = self . getTxVSize ( tx , add_witness_bytes = witness_bytes )
2021-11-01 13:52:40 +00:00
fee_rate_paid = fee_paid * 1000 / / vsize
2020-10-31 20:08:30 +00:00
2021-01-29 23:45:24 +00:00
self . _log . info ( ' tx amount, vsize, feerate: %ld , %ld , %ld ' , locked_coin , vsize , fee_rate_paid )
2020-10-31 20:08:30 +00:00
if not self . compareFeeRates ( fee_rate_paid , feerate ) :
2021-02-03 14:01:27 +00:00
raise ValueError ( ' Bad fee rate, expected: {} ' . format ( feerate ) )
2020-10-31 20:08:30 +00:00
2021-11-01 13:52:40 +00:00
return txid , locked_coin , locked_n
2020-10-31 20:08:30 +00:00
2022-11-07 20:31:10 +00:00
def verifySCLockRefundSpendTx ( self , tx_bytes , lock_refund_tx_bytes ,
lock_refund_tx_id , prevout_script ,
Kal ,
prevout_n , prevout_value , feerate , vkbv = None ) :
2020-10-31 20:08:30 +00:00
# Verify:
# Must have only one input with correct prevout (n is always 0) and sequence
# Must have only one output sending lock refund tx value - fee to leader's address, TODO: follower shouldn't need to verify destination addr
2020-11-15 17:02:46 +00:00
tx = self . loadTx ( tx_bytes )
2021-11-01 13:52:40 +00:00
txid = self . getTxid ( tx )
self . _log . info ( ' Verifying lock refund spend tx: {} . ' . format ( b2h ( txid ) ) )
2020-10-31 20:08:30 +00:00
2021-11-01 13:52:40 +00:00
ensure ( tx . nVersion == self . txVersion ( ) , ' Bad version ' )
ensure ( tx . nLockTime == 0 , ' nLockTime not 0 ' )
ensure ( len ( tx . vin ) == 1 , ' tx doesn \' t have one input ' )
2020-10-31 20:08:30 +00:00
2021-11-01 13:52:40 +00:00
ensure ( tx . vin [ 0 ] . nSequence == 0 , ' Bad input nSequence ' )
2022-11-07 20:31:10 +00:00
ensure ( tx . vin [ 0 ] . scriptSig == self . getScriptScriptSig ( prevout_script ) , ' Input scriptsig mismatch ' )
2021-11-01 13:52:40 +00:00
ensure ( tx . vin [ 0 ] . prevout . hash == b2i ( lock_refund_tx_id ) and tx . vin [ 0 ] . prevout . n == 0 , ' Input prevout mismatch ' )
2020-10-31 20:08:30 +00:00
2021-11-01 13:52:40 +00:00
ensure ( len ( tx . vout ) == 1 , ' tx doesn \' t have one output ' )
2020-10-31 20:08:30 +00:00
2020-11-27 17:52:26 +00:00
# Destination doesn't matter to the follower
2020-11-30 14:29:40 +00:00
'''
p2wpkh = CScript ( [ OP_0 , hash160 ( Kal ) ] )
locked_n = findOutput ( tx , p2wpkh )
2021-11-01 13:52:40 +00:00
ensure ( locked_n is not None , ' Output not found in lock refund spend tx ' )
2020-11-30 14:29:40 +00:00
'''
2020-11-27 17:52:26 +00:00
tx_value = tx . vout [ 0 ] . nValue
2020-10-31 20:08:30 +00:00
fee_paid = prevout_value - tx_value
2022-07-31 18:01:49 +00:00
assert ( fee_paid > 0 )
2020-10-31 20:08:30 +00:00
2021-11-01 13:52:40 +00:00
dummy_witness_stack = self . getScriptLockRefundSpendTxDummyWitness ( prevout_script )
witness_bytes = self . getWitnessStackSerialisedLength ( dummy_witness_stack )
2020-10-31 20:08:30 +00:00
vsize = self . getTxVSize ( tx , add_witness_bytes = witness_bytes )
2021-11-01 13:52:40 +00:00
fee_rate_paid = fee_paid * 1000 / / vsize
2020-10-31 20:08:30 +00:00
2021-01-29 23:45:24 +00:00
self . _log . info ( ' tx amount, vsize, feerate: %ld , %ld , %ld ' , tx_value , vsize , fee_rate_paid )
2020-10-31 20:08:30 +00:00
if not self . compareFeeRates ( fee_rate_paid , feerate ) :
2021-02-03 14:01:27 +00:00
raise ValueError ( ' Bad fee rate, expected: {} ' . format ( feerate ) )
2020-10-31 20:08:30 +00:00
return True
2022-11-07 20:31:10 +00:00
def verifySCLockSpendTx ( self , tx_bytes ,
lock_tx_bytes , lock_tx_script ,
a_pkhash_f , feerate , vkbv = None ) :
2020-10-31 20:08:30 +00:00
# Verify:
# Must have only one input with correct prevout (n is always 0) and sequence
# Must have only one output with destination and amount
2020-11-21 13:16:27 +00:00
tx = self . loadTx ( tx_bytes )
2021-11-01 13:52:40 +00:00
txid = self . getTxid ( tx )
self . _log . info ( ' Verifying lock spend tx: {} . ' . format ( b2h ( txid ) ) )
2020-10-31 20:08:30 +00:00
2021-11-01 13:52:40 +00:00
ensure ( tx . nVersion == self . txVersion ( ) , ' Bad version ' )
ensure ( tx . nLockTime == 0 , ' nLockTime not 0 ' )
ensure ( len ( tx . vin ) == 1 , ' tx doesn \' t have one input ' )
2020-10-31 20:08:30 +00:00
2020-11-21 13:16:27 +00:00
lock_tx = self . loadTx ( lock_tx_bytes )
2021-11-01 13:52:40 +00:00
lock_tx_id = self . getTxid ( lock_tx )
2020-10-31 20:08:30 +00:00
2022-11-07 20:31:10 +00:00
output_script = self . getScriptDest ( lock_tx_script )
2020-10-31 20:08:30 +00:00
locked_n = findOutput ( lock_tx , output_script )
2021-11-01 13:52:40 +00:00
ensure ( locked_n is not None , ' Output not found in tx ' )
2020-10-31 20:08:30 +00:00
locked_coin = lock_tx . vout [ locked_n ] . nValue
2021-11-01 13:52:40 +00:00
ensure ( tx . vin [ 0 ] . nSequence == 0 , ' Bad input nSequence ' )
2022-11-07 20:31:10 +00:00
ensure ( tx . vin [ 0 ] . scriptSig == self . getScriptScriptSig ( lock_tx_script ) , ' Input scriptsig mismatch ' )
2021-11-01 13:52:40 +00:00
ensure ( tx . vin [ 0 ] . prevout . hash == b2i ( lock_tx_id ) and tx . vin [ 0 ] . prevout . n == locked_n , ' Input prevout mismatch ' )
2020-10-31 20:08:30 +00:00
2021-11-01 13:52:40 +00:00
ensure ( len ( tx . vout ) == 1 , ' tx doesn \' t have one output ' )
2020-12-10 10:07:26 +00:00
p2wpkh = self . getScriptForPubkeyHash ( a_pkhash_f )
2021-11-01 13:52:40 +00:00
ensure ( tx . vout [ 0 ] . scriptPubKey == p2wpkh , ' Bad output destination ' )
2020-10-31 20:08:30 +00:00
2021-11-01 13:52:40 +00:00
# The value of the lock tx output should already be verified, if the fee is as expected the difference will be the correct amount
2020-10-31 20:08:30 +00:00
fee_paid = locked_coin - tx . vout [ 0 ] . nValue
2022-07-31 18:01:49 +00:00
assert ( fee_paid > 0 )
2020-10-31 20:08:30 +00:00
2021-11-01 13:52:40 +00:00
dummy_witness_stack = self . getScriptLockTxDummyWitness ( lock_tx_script )
witness_bytes = self . getWitnessStackSerialisedLength ( dummy_witness_stack )
2020-10-31 20:08:30 +00:00
vsize = self . getTxVSize ( tx , add_witness_bytes = witness_bytes )
2021-11-01 13:52:40 +00:00
fee_rate_paid = fee_paid * 1000 / / vsize
2020-10-31 20:08:30 +00:00
2021-01-29 23:45:24 +00:00
self . _log . info ( ' tx amount, vsize, feerate: %ld , %ld , %ld ' , tx . vout [ 0 ] . nValue , vsize , fee_rate_paid )
2020-10-31 20:08:30 +00:00
if not self . compareFeeRates ( fee_rate_paid , feerate ) :
2021-02-03 14:01:27 +00:00
raise ValueError ( ' Bad fee rate, expected: {} ' . format ( feerate ) )
2020-10-31 20:08:30 +00:00
return True
2021-11-01 13:52:40 +00:00
def signTx ( self , key_bytes , tx_bytes , input_n , prevout_script , prevout_value ) :
2020-11-14 22:13:11 +00:00
tx = self . loadTx ( tx_bytes )
2021-11-01 13:52:40 +00:00
sig_hash = SegwitV0SignatureHash ( prevout_script , tx , input_n , SIGHASH_ALL , prevout_value )
2020-10-31 20:08:30 +00:00
2020-12-11 07:11:35 +00:00
eck = PrivateKey ( key_bytes )
return eck . sign ( sig_hash , hasher = None ) + bytes ( ( SIGHASH_ALL , ) )
2020-10-31 20:08:30 +00:00
2021-11-01 13:52:40 +00:00
def signTxOtVES ( self , key_sign , pubkey_encrypt , tx_bytes , input_n , prevout_script , prevout_value ) :
2020-11-15 17:02:46 +00:00
tx = self . loadTx ( tx_bytes )
2021-11-01 13:52:40 +00:00
sig_hash = SegwitV0SignatureHash ( prevout_script , tx , input_n , SIGHASH_ALL , prevout_value )
2020-10-31 20:08:30 +00:00
2020-11-15 17:02:46 +00:00
return ecdsaotves_enc_sign ( key_sign , pubkey_encrypt , sig_hash )
2021-11-01 13:52:40 +00:00
def verifyTxOtVES ( self , tx_bytes , ct , Ks , Ke , input_n , prevout_script , prevout_value ) :
2020-11-15 17:02:46 +00:00
tx = self . loadTx ( tx_bytes )
2021-11-01 13:52:40 +00:00
sig_hash = SegwitV0SignatureHash ( prevout_script , tx , input_n , SIGHASH_ALL , prevout_value )
2020-11-27 17:52:26 +00:00
return ecdsaotves_enc_verify ( Ks , Ke , sig_hash , ct )
2020-10-31 20:08:30 +00:00
def decryptOtVES ( self , k , esig ) :
2020-12-11 07:11:35 +00:00
return ecdsaotves_dec_sig ( k , esig ) + bytes ( ( SIGHASH_ALL , ) )
2020-10-31 20:08:30 +00:00
2023-08-29 20:06:16 +00:00
def verifyTxSig ( self , tx_bytes : bytes , sig : bytes , K : bytes , input_n : int , prevout_script : bytes , prevout_value : int ) - > bool :
2020-11-14 22:13:11 +00:00
tx = self . loadTx ( tx_bytes )
2021-11-01 13:52:40 +00:00
sig_hash = SegwitV0SignatureHash ( prevout_script , tx , input_n , SIGHASH_ALL , prevout_value )
2020-10-31 20:08:30 +00:00
2020-12-11 07:11:35 +00:00
pubkey = PublicKey ( K )
return pubkey . verify ( sig [ : - 1 ] , sig_hash , hasher = None ) # Pop the hashtype byte
2020-10-31 20:08:30 +00:00
2020-11-27 17:52:26 +00:00
def verifySig ( self , pubkey , signed_hash , sig ) :
2020-12-11 07:11:35 +00:00
pubkey = PublicKey ( pubkey )
return pubkey . verify ( sig , signed_hash , hasher = None )
2020-11-27 17:52:26 +00:00
2020-10-31 20:08:30 +00:00
def fundTx ( self , tx , feerate ) :
2021-11-01 13:52:40 +00:00
feerate_str = self . format_amount ( feerate )
2020-11-15 21:31:59 +00:00
# TODO: unlock unspents if bid cancelled
options = {
' lockUnspents ' : True ,
' feeRate ' : feerate_str ,
}
2023-12-29 13:36:00 +00:00
rv = self . rpc_wallet ( ' fundrawtransaction ' , [ tx . hex ( ) , options ] )
2020-11-14 22:13:11 +00:00
return bytes . fromhex ( rv [ ' hex ' ] )
2020-10-31 20:08:30 +00:00
2021-11-05 22:34:25 +00:00
def listInputs ( self , tx_bytes ) :
tx = self . loadTx ( tx_bytes )
2023-12-29 13:36:00 +00:00
all_locked = self . rpc_wallet ( ' listlockunspent ' )
2021-11-05 22:34:25 +00:00
inputs = [ ]
for pi in tx . vin :
txid_hex = i2h ( pi . prevout . hash )
islocked = any ( [ txid_hex == a [ ' txid ' ] and pi . prevout . n == a [ ' vout ' ] for a in all_locked ] )
inputs . append ( { ' txid ' : txid_hex , ' vout ' : pi . prevout . n , ' islocked ' : islocked } )
return inputs
2021-01-09 13:00:25 +00:00
def unlockInputs ( self , tx_bytes ) :
tx = self . loadTx ( tx_bytes )
inputs = [ ]
for pi in tx . vin :
inputs . append ( { ' txid ' : i2h ( pi . prevout . hash ) , ' vout ' : pi . prevout . n } )
2024-01-31 22:58:14 +00:00
self . rpc_wallet ( ' lockunspent ' , [ True , inputs ] )
2021-01-09 13:00:25 +00:00
2022-12-08 01:22:18 +00:00
def signTxWithWallet ( self , tx : bytes ) - > bytes :
2023-12-29 13:36:00 +00:00
rv = self . rpc_wallet ( ' signrawtransactionwithwallet ' , [ tx . hex ( ) ] )
2020-11-14 22:13:11 +00:00
return bytes . fromhex ( rv [ ' hex ' ] )
2020-10-31 20:08:30 +00:00
2022-12-08 01:22:18 +00:00
def signTxWithKey ( self , tx : bytes , key : bytes ) - > bytes :
key_wif = self . encodeKey ( key )
2023-12-29 13:36:00 +00:00
rv = self . rpc ( ' signrawtransactionwithkey ' , [ tx . hex ( ) , [ key_wif , ] ] )
2022-12-08 01:22:18 +00:00
return bytes . fromhex ( rv [ ' hex ' ] )
def publishTx ( self , tx : bytes ) :
2023-12-29 13:36:00 +00:00
return self . rpc ( ' sendrawtransaction ' , [ tx . hex ( ) ] )
2020-10-31 20:08:30 +00:00
2022-12-08 01:22:18 +00:00
def encodeTx ( self , tx ) - > bytes :
2020-10-31 20:08:30 +00:00
return tx . serialize ( )
2022-12-08 01:22:18 +00:00
def getTxid ( self , tx ) - > bytes :
2021-11-01 13:52:40 +00:00
if isinstance ( tx , str ) :
tx = bytes . fromhex ( tx )
2020-11-15 17:02:46 +00:00
if isinstance ( tx , bytes ) :
tx = self . loadTx ( tx )
2020-10-31 20:08:30 +00:00
tx . rehash ( )
return i2b ( tx . sha256 )
2020-11-29 23:05:30 +00:00
def getTxOutputPos ( self , tx , script ) :
if isinstance ( tx , bytes ) :
tx = self . loadTx ( tx )
2022-11-07 20:31:10 +00:00
script_pk = self . getScriptDest ( script )
2020-11-29 23:05:30 +00:00
return findOutput ( tx , script_pk )
2022-12-08 01:22:18 +00:00
def getPubkeyHash ( self , K : bytes ) - > bytes :
return hash160 ( K )
2020-10-31 20:08:30 +00:00
def getScriptDest ( self , script ) :
return CScript ( [ OP_0 , hashlib . sha256 ( script ) . digest ( ) ] )
2022-12-08 01:22:18 +00:00
def getScriptScriptSig ( self , script : bytes ) - > bytes :
2022-11-07 20:31:10 +00:00
return bytes ( )
2023-08-29 20:06:16 +00:00
def getP2SHP2WSHDest ( self , script ) :
script_hash = hashlib . sha256 ( script ) . digest ( )
assert len ( script_hash ) == 32
p2wsh_hash = hash160 ( CScript ( [ OP_0 , script_hash ] ) )
assert len ( p2wsh_hash ) == 20
return CScript ( [ OP_HASH160 , p2wsh_hash , OP_EQUAL ] )
def getP2SHP2WSHScriptSig ( self , script ) :
script_hash = hashlib . sha256 ( script ) . digest ( )
assert len ( script_hash ) == 32
return CScript ( [ CScript ( [ OP_0 , script_hash , ] ) , ] )
2022-12-08 01:22:18 +00:00
def getPkDest ( self , K : bytes ) - > bytearray :
2020-12-08 22:05:28 +00:00
return self . getScriptForPubkeyHash ( self . getPubkeyHash ( K ) )
2020-10-31 20:08:30 +00:00
def scanTxOutset ( self , dest ) :
2023-12-29 13:36:00 +00:00
return self . rpc ( ' scantxoutset ' , [ ' start ' , [ ' raw( {} ) ' . format ( dest . hex ( ) ) ] ] )
2020-10-31 20:08:30 +00:00
2023-07-18 23:19:04 +00:00
def getTransaction ( self , txid : bytes ) :
2020-10-31 20:08:30 +00:00
try :
2023-12-29 13:36:00 +00:00
return bytes . fromhex ( self . rpc ( ' getrawtransaction ' , [ txid . hex ( ) ] ) )
2020-10-31 20:08:30 +00:00
except Exception as ex :
# TODO: filter errors
return None
2023-07-18 23:19:04 +00:00
def getWalletTransaction ( self , txid : bytes ) :
2020-11-29 23:05:30 +00:00
try :
2023-12-29 13:36:00 +00:00
return bytes . fromhex ( self . rpc ( ' gettransaction ' , [ txid . hex ( ) ] ) )
2020-11-29 23:05:30 +00:00
except Exception as ex :
# TODO: filter errors
return None
2023-03-23 12:15:47 +00:00
def setTxSignature ( self , tx_bytes : bytes , stack ) - > bytes :
2020-11-21 13:16:27 +00:00
tx = self . loadTx ( tx_bytes )
2020-10-31 20:08:30 +00:00
tx . wit . vtxinwit . clear ( )
tx . wit . vtxinwit . append ( CTxInWitness ( ) )
tx . wit . vtxinwit [ 0 ] . scriptWitness . stack = stack
2020-11-21 13:16:27 +00:00
return tx . serialize ( )
2020-10-31 20:08:30 +00:00
2023-03-23 12:15:47 +00:00
def setTxScriptSig ( self , tx_bytes : bytes , input_no : int , script_sig : bytes ) - > bytes :
tx = self . loadTx ( tx_bytes )
tx . vin [ 0 ] . scriptSig = script_sig
return tx . serialize ( )
2022-12-05 15:04:23 +00:00
def stripTxSignature ( self , tx_bytes ) - > bytes :
2021-11-01 13:52:40 +00:00
tx = self . loadTx ( tx_bytes )
tx . wit . vtxinwit . clear ( )
return tx . serialize ( )
2022-12-05 15:04:23 +00:00
def extractLeaderSig ( self , tx_bytes ) - > bytes :
2020-11-21 13:16:27 +00:00
tx = self . loadTx ( tx_bytes )
2020-10-31 20:08:30 +00:00
return tx . wit . vtxinwit [ 0 ] . scriptWitness . stack [ 1 ]
2022-12-05 15:04:23 +00:00
def extractFollowerSig ( self , tx_bytes ) - > bytes :
2020-11-21 13:16:27 +00:00
tx = self . loadTx ( tx_bytes )
2020-10-31 20:08:30 +00:00
return tx . wit . vtxinwit [ 0 ] . scriptWitness . stack [ 2 ]
2022-12-20 20:19:01 +00:00
def createBLockTx ( self , Kbs , output_amount , vkbv = None ) - > bytes :
2020-10-31 20:08:30 +00:00
tx = CTransaction ( )
tx . nVersion = self . txVersion ( )
2022-12-08 01:22:18 +00:00
p2wpkh_script_pk = self . getPkDest ( Kbs )
tx . vout . append ( self . txoType ( ) ( output_amount , p2wpkh_script_pk ) )
2020-11-14 22:13:11 +00:00
return tx . serialize ( )
2020-10-31 20:08:30 +00:00
2021-09-04 23:18:34 +00:00
def encodeSharedAddress ( self , Kbv , Kbs ) :
return self . pubkey_to_segwit_address ( Kbs )
2022-12-20 20:19:01 +00:00
def publishBLockTx ( self , kbv , Kbs , output_amount , feerate , delay_for : int = 10 , unlock_time : int = 0 ) - > bytes :
2020-10-31 20:08:30 +00:00
b_lock_tx = self . createBLockTx ( Kbs , output_amount )
b_lock_tx = self . fundTx ( b_lock_tx , feerate )
2021-11-01 13:52:40 +00:00
b_lock_tx_id = self . getTxid ( b_lock_tx )
2020-10-31 20:08:30 +00:00
b_lock_tx = self . signTxWithWallet ( b_lock_tx )
2022-12-08 01:22:18 +00:00
return bytes . fromhex ( self . publishTx ( b_lock_tx ) )
2020-10-31 20:08:30 +00:00
def recoverEncKey ( self , esig , sig , K ) :
2020-11-21 13:16:27 +00:00
return ecdsaotves_rec_enc_key ( K , esig , sig [ : - 1 ] ) # Strip sighash type
2020-10-31 20:08:30 +00:00
2022-12-08 01:22:18 +00:00
def getTxVSize ( self , tx , add_bytes : int = 0 , add_witness_bytes : int = 0 ) - > int :
2020-10-31 20:08:30 +00:00
wsf = self . witnessScaleFactor ( )
len_full = len ( tx . serialize_with_witness ( ) ) + add_bytes + add_witness_bytes
len_nwit = len ( tx . serialize_without_witness ( ) ) + add_bytes
weight = len_nwit * ( wsf - 1 ) + len_full
return ( weight + wsf - 1 ) / / wsf
2021-10-23 14:00:32 +00:00
def findTxB ( self , kbv , Kbs , cb_swap_value , cb_block_confirmed , restore_height , bid_sender ) :
2022-12-08 01:22:18 +00:00
dest_address = self . pubkey_to_segwit_address ( Kbs ) if self . using_segwit ( ) else self . pubkey_to_address ( Kbs )
return self . getLockTxHeight ( None , dest_address , cb_swap_value , restore_height )
'''
2020-10-31 20:08:30 +00:00
raw_dest = self . getPkDest ( Kbs )
rv = self . scanTxOutset ( raw_dest )
for utxo in rv [ ' unspents ' ] :
if ' height ' in utxo and utxo [ ' height ' ] > 0 and rv [ ' height ' ] - utxo [ ' height ' ] > cb_block_confirmed :
2021-01-02 21:04:29 +00:00
if self . make_int ( utxo [ ' amount ' ] ) != cb_swap_value :
2021-01-29 23:45:24 +00:00
self . _log . warning ( ' Found output to lock tx pubkey of incorrect value: %s ' , str ( utxo [ ' amount ' ] ) )
2020-10-31 20:08:30 +00:00
else :
2020-11-21 13:16:27 +00:00
return { ' txid ' : utxo [ ' txid ' ] , ' vout ' : utxo [ ' vout ' ] , ' amount ' : utxo [ ' amount ' ] , ' height ' : utxo [ ' height ' ] }
return None
2022-12-08 01:22:18 +00:00
'''
2020-10-31 20:08:30 +00:00
2022-12-10 23:26:42 +00:00
def getBLockSpendTxFee ( self , tx , fee_rate : int ) - > int :
witness_bytes = 109
vsize = self . getTxVSize ( tx , add_witness_bytes = witness_bytes )
2023-07-14 07:31:05 +00:00
pay_fee = round ( fee_rate * vsize / 1000 )
2022-12-10 23:26:42 +00:00
self . _log . info ( f ' BLockSpendTx fee_rate, vsize, fee: { fee_rate } , { vsize } , { pay_fee } . ' )
return pay_fee
2022-12-08 01:22:18 +00:00
def spendBLockTx ( self , chain_b_lock_txid : bytes , address_to : str , kbv : bytes , kbs : bytes , cb_swap_value : int , b_fee : int , restore_height : int ) - > bytes :
2022-12-10 23:26:42 +00:00
self . _log . info ( ' spendBLockTx %s : \n ' , chain_b_lock_txid . hex ( ) )
2023-12-29 13:36:00 +00:00
wtx = self . rpc_wallet ( ' gettransaction ' , [ chain_b_lock_txid . hex ( ) , ] )
2022-12-08 01:22:18 +00:00
lock_tx = self . loadTx ( bytes . fromhex ( wtx [ ' hex ' ] ) )
2020-10-31 20:08:30 +00:00
2022-12-08 01:22:18 +00:00
Kbs = self . getPubkey ( kbs )
script_pk = self . getPkDest ( Kbs )
locked_n = findOutput ( lock_tx , script_pk )
ensure ( locked_n is not None , ' Output not found in tx ' )
pkh_to = self . decodeAddress ( address_to )
2020-10-31 20:08:30 +00:00
2022-12-08 01:22:18 +00:00
tx = CTransaction ( )
tx . nVersion = self . txVersion ( )
2020-10-31 20:08:30 +00:00
2022-12-08 01:22:18 +00:00
script_lock = self . getScriptForPubkeyHash ( Kbs )
chain_b_lock_txid_int = uint256_from_str ( chain_b_lock_txid [ : : - 1 ] )
2020-10-31 20:08:30 +00:00
2022-12-08 01:22:18 +00:00
tx . vin . append ( CTxIn ( COutPoint ( chain_b_lock_txid_int , locked_n ) ,
nSequence = 0 ,
scriptSig = self . getScriptScriptSig ( script_lock ) ) )
tx . vout . append ( self . txoType ( ) ( cb_swap_value , self . getScriptForPubkeyHash ( pkh_to ) ) )
2022-12-10 23:26:42 +00:00
pay_fee = self . getBLockSpendTxFee ( tx , b_fee )
2022-12-08 01:22:18 +00:00
tx . vout [ 0 ] . nValue = cb_swap_value - pay_fee
2021-11-01 13:52:40 +00:00
2022-12-08 01:22:18 +00:00
b_lock_spend_tx = tx . serialize ( )
b_lock_spend_tx = self . signTxWithKey ( b_lock_spend_tx , kbs )
return bytes . fromhex ( self . publishTx ( b_lock_spend_tx ) )
def importWatchOnlyAddress ( self , address : str , label : str ) :
2023-12-29 13:36:00 +00:00
self . rpc_wallet ( ' importaddress ' , [ address , label , False ] )
2022-11-07 20:31:10 +00:00
2022-12-08 01:22:18 +00:00
def isWatchOnlyAddress ( self , address : str ) :
2023-12-29 13:36:00 +00:00
addr_info = self . rpc_wallet ( ' getaddressinfo ' , [ address ] )
2022-11-07 20:31:10 +00:00
return addr_info [ ' iswatchonly ' ]
def getSCLockScriptAddress ( self , lock_script ) :
lock_tx_dest = self . getScriptDest ( lock_script )
return self . encodeScriptDest ( lock_tx_dest )
2022-12-08 01:22:18 +00:00
def getLockTxHeight ( self , txid , dest_address , bid_amount , rescan_from , find_index : bool = False ) :
2021-11-04 21:49:52 +00:00
# Add watchonly address and rescan if required
2022-11-07 20:31:10 +00:00
2022-12-08 01:22:18 +00:00
if not self . isAddressMine ( dest_address , or_watch_only = True ) :
2022-11-07 20:31:10 +00:00
self . importWatchOnlyAddress ( dest_address , ' bid ' )
2021-11-04 21:49:52 +00:00
self . _log . info ( ' Imported watch-only addr: {} ' . format ( dest_address ) )
2022-07-04 20:29:49 +00:00
self . _log . info ( ' Rescanning {} chain from height: {} ' . format ( self . coin_name ( ) , rescan_from ) )
2023-12-29 13:36:00 +00:00
self . rpc_wallet ( ' rescanblockchain ' , [ rescan_from ] )
2021-11-04 21:49:52 +00:00
return_txid = True if txid is None else False
if txid is None :
2023-12-29 13:36:00 +00:00
txns = self . rpc_wallet ( ' listunspent ' , [ 0 , 9999999 , [ dest_address , ] ] )
2021-11-04 21:49:52 +00:00
for tx in txns :
if self . make_int ( tx [ ' amount ' ] ) == bid_amount :
txid = bytes . fromhex ( tx [ ' txid ' ] )
break
if txid is None :
return None
2021-11-01 13:52:40 +00:00
try :
2023-12-29 13:36:00 +00:00
tx = self . rpc_wallet ( ' gettransaction ' , [ txid . hex ( ) ] )
2021-11-01 13:52:40 +00:00
block_height = 0
if ' blockhash ' in tx :
2023-12-29 13:36:00 +00:00
block_header = self . rpc ( ' getblockheader ' , [ tx [ ' blockhash ' ] ] )
2021-11-01 13:52:40 +00:00
block_height = block_header [ ' height ' ]
rv = {
' depth ' : 0 if ' confirmations ' not in tx else tx [ ' confirmations ' ] ,
' height ' : block_height }
2021-11-04 21:49:52 +00:00
2021-11-01 13:52:40 +00:00
except Exception as e :
2021-12-31 19:34:00 +00:00
self . _log . debug ( ' getLockTxHeight gettransaction failed: %s , %s ' , txid . hex ( ) , str ( e ) )
2021-11-04 21:49:52 +00:00
return None
if find_index :
2023-12-29 13:36:00 +00:00
tx_obj = self . rpc ( ' decoderawtransaction ' , [ tx [ ' hex ' ] ] )
2021-11-04 21:49:52 +00:00
rv [ ' index ' ] = find_vout_for_address_from_txobj ( tx_obj , dest_address )
if return_txid :
rv [ ' txid ' ] = txid . hex ( )
2021-11-01 13:52:40 +00:00
return rv
2020-10-31 20:08:30 +00:00
2021-11-01 13:52:40 +00:00
def getOutput ( self , txid , dest_script , expect_value , xmr_swap = None ) :
2020-11-21 13:16:27 +00:00
# TODO: Use getrawtransaction if txindex is active
2023-12-29 13:36:00 +00:00
utxos = self . rpc ( ' scantxoutset ' , [ ' start ' , [ ' raw( {} ) ' . format ( dest_script . hex ( ) ) ] ] )
2021-02-14 12:19:30 +00:00
if ' height ' in utxos : # chain_height not returned by v18 codebase
chain_height = utxos [ ' height ' ]
else :
chain_height = self . getChainHeight ( )
2020-11-21 13:16:27 +00:00
rv = [ ]
for utxo in utxos [ ' unspents ' ] :
if txid and txid . hex ( ) != utxo [ ' txid ' ] :
continue
2021-01-02 21:04:29 +00:00
if expect_value != self . make_int ( utxo [ ' amount ' ] ) :
2020-11-21 13:16:27 +00:00
continue
rv . append ( {
2020-12-11 12:08:32 +00:00
' depth ' : 0 if ' height ' not in utxo else ( chain_height - utxo [ ' height ' ] ) + 1 ,
2020-12-10 14:37:26 +00:00
' height ' : 0 if ' height ' not in utxo else utxo [ ' height ' ] ,
2021-01-02 21:04:29 +00:00
' amount ' : self . make_int ( utxo [ ' amount ' ] ) ,
2020-11-21 13:16:27 +00:00
' txid ' : utxo [ ' txid ' ] ,
' vout ' : utxo [ ' vout ' ] } )
2020-12-10 14:37:26 +00:00
return rv , chain_height
2020-11-21 13:16:27 +00:00
2020-12-05 11:22:22 +00:00
def withdrawCoin ( self , value , addr_to , subfee ) :
params = [ addr_to , value , ' ' , ' ' , subfee , True , self . _conf_target ]
2023-12-29 13:36:00 +00:00
return self . rpc_wallet ( ' sendtoaddress ' , params )
2020-12-05 11:22:22 +00:00
2020-12-10 22:43:36 +00:00
def signCompact ( self , k , message ) :
message_hash = hashlib . sha256 ( bytes ( message , ' utf-8 ' ) ) . digest ( )
privkey = PrivateKey ( k )
return privkey . sign_recoverable ( message_hash , hasher = None ) [ : 64 ]
2023-05-11 21:45:06 +00:00
def signRecoverable ( self , k , message ) :
message_hash = hashlib . sha256 ( bytes ( message , ' utf-8 ' ) ) . digest ( )
privkey = PrivateKey ( k )
return privkey . sign_recoverable ( message_hash , hasher = None )
2022-11-07 20:31:10 +00:00
def verifyCompactSig ( self , K , message , sig ) :
2020-12-10 22:43:36 +00:00
message_hash = hashlib . sha256 ( bytes ( message , ' utf-8 ' ) ) . digest ( )
pubkey = PublicKey ( K )
rv = pubkey . verify_compact ( sig , message_hash , hasher = None )
2022-07-31 18:01:49 +00:00
assert ( rv is True )
2020-12-10 22:43:36 +00:00
2023-05-11 21:45:06 +00:00
def verifySigAndRecover ( self , sig , message ) :
message_hash = hashlib . sha256 ( bytes ( message , ' utf-8 ' ) ) . digest ( )
pubkey = PublicKey . from_signature_and_message ( sig , message_hash , hasher = None )
return pubkey . format ( )
2022-12-05 15:04:23 +00:00
def verifyMessage ( self , address : str , message : str , signature : str , message_magic : str = None ) - > bool :
2021-01-19 13:10:42 +00:00
if message_magic is None :
2022-11-07 20:31:10 +00:00
message_magic = self . chainparams ( ) [ ' message_magic ' ]
2021-01-19 13:10:42 +00:00
message_bytes = SerialiseNumCompact ( len ( message_magic ) ) + bytes ( message_magic , ' utf-8 ' ) + SerialiseNumCompact ( len ( message ) ) + bytes ( message , ' utf-8 ' )
message_hash = hashlib . sha256 ( hashlib . sha256 ( message_bytes ) . digest ( ) ) . digest ( )
signature_bytes = base64 . b64decode ( signature )
rec_id = ( signature_bytes [ 0 ] - 27 ) & 3
signature_bytes = signature_bytes [ 1 : ] + bytes ( ( rec_id , ) )
try :
pubkey = PublicKey . from_signature_and_message ( signature_bytes , message_hash , hasher = None )
except Exception as e :
2021-01-29 23:45:24 +00:00
self . _log . info ( ' verifyMessage failed: ' + str ( e ) )
2021-01-19 13:10:42 +00:00
return False
address_hash = self . decodeAddress ( address )
pubkey_hash = hash160 ( pubkey . format ( ) )
return True if address_hash == pubkey_hash else False
2022-12-11 18:31:43 +00:00
def showLockTransfers ( self , kbv , Kbs , restore_height ) :
2021-11-01 13:52:40 +00:00
raise ValueError ( ' Unimplemented ' )
def getLockTxSwapOutputValue ( self , bid , xmr_swap ) :
return bid . amount
def getLockRefundTxSwapOutputValue ( self , bid , xmr_swap ) :
return xmr_swap . a_swap_refund_value
def getLockRefundTxSwapOutput ( self , xmr_swap ) :
# Only one prevout exists
return 0
2023-07-14 07:31:05 +00:00
def getScriptLockTxDummyWitness ( self , script : bytes ) :
2021-11-01 13:52:40 +00:00
return [
2023-07-14 07:31:05 +00:00
b ' ' ,
bytes ( 72 ) ,
bytes ( 72 ) ,
bytes ( len ( script ) )
2021-11-01 13:52:40 +00:00
]
2023-07-14 07:31:05 +00:00
def getScriptLockRefundSpendTxDummyWitness ( self , script : bytes ) :
2021-11-01 13:52:40 +00:00
return [
2023-07-14 07:31:05 +00:00
b ' ' ,
bytes ( 72 ) ,
bytes ( 72 ) ,
bytes ( ( 1 , ) ) ,
bytes ( len ( script ) )
2021-11-01 13:52:40 +00:00
]
2023-07-14 07:31:05 +00:00
def getScriptLockRefundSwipeTxDummyWitness ( self , script : bytes ) :
2021-11-01 13:52:40 +00:00
return [
2023-07-14 07:31:05 +00:00
bytes ( 72 ) ,
b ' ' ,
bytes ( len ( script ) )
2021-11-01 13:52:40 +00:00
]
def getWitnessStackSerialisedLength ( self , witness_stack ) :
length = getCompactSizeLen ( len ( witness_stack ) )
for e in witness_stack :
2023-07-14 07:31:05 +00:00
length + = getWitnessElementLen ( len ( e ) )
2021-11-01 13:52:40 +00:00
# See core SerializeTransaction
2023-07-14 07:31:05 +00:00
length + = 1 # vinDummy
2021-11-01 13:52:40 +00:00
length + = 1 # flags
return length
2021-09-05 15:36:27 +00:00
2022-12-05 15:04:23 +00:00
def describeTx ( self , tx_hex : str ) :
2023-12-29 13:36:00 +00:00
return self . rpc ( ' decoderawtransaction ' , [ tx_hex ] )
2021-11-02 15:48:33 +00:00
2023-02-14 21:34:01 +00:00
def getSpendableBalance ( self ) - > int :
2023-12-29 13:36:00 +00:00
return self . make_int ( self . rpc_wallet ( ' getbalances ' ) [ ' mine ' ] [ ' trusted ' ] )
2022-01-01 20:55:39 +00:00
2022-12-05 15:04:23 +00:00
def createUTXO ( self , value_sats : int ) :
2022-01-24 21:32:48 +00:00
# Create a new address and send value_sats to it
spendable_balance = self . getSpendableBalance ( )
if spendable_balance < value_sats :
raise ValueError ( ' Balance too low ' )
address = self . getNewAddress ( self . _use_segwit , ' create_utxo ' )
return self . withdrawCoin ( self . format_amount ( value_sats ) , address , False ) , address
2022-12-05 15:04:23 +00:00
def createRawFundedTransaction ( self , addr_to : str , amount : int , sub_fee : bool = False , lock_unspents : bool = True ) - > str :
2023-12-29 13:36:00 +00:00
txn = self . rpc ( ' createrawtransaction ' , [ [ ] , { addr_to : self . format_amount ( amount ) } ] )
2022-08-16 18:52:43 +00:00
options = {
2022-12-05 15:04:23 +00:00
' lockUnspents ' : lock_unspents ,
2022-08-16 18:52:43 +00:00
' conf_target ' : self . _conf_target ,
}
2022-12-05 15:04:23 +00:00
if sub_fee :
options [ ' subtractFeeFromOutputs ' ] = [ 0 , ]
2023-12-29 13:36:00 +00:00
return self . rpc_wallet ( ' fundrawtransaction ' , [ txn , options ] ) [ ' hex ' ]
2022-12-05 15:04:23 +00:00
def createRawSignedTransaction ( self , addr_to , amount ) - > str :
txn_funded = self . createRawFundedTransaction ( addr_to , amount )
2023-12-29 13:36:00 +00:00
return self . rpc_wallet ( ' signrawtransactionwithwallet ' , [ txn_funded ] ) [ ' hex ' ]
2022-08-16 18:52:43 +00:00
2022-12-05 15:04:23 +00:00
def getBlockWithTxns ( self , block_hash : str ) :
2023-12-29 13:36:00 +00:00
return self . rpc ( ' getblock ' , [ block_hash , 2 ] )
2022-08-10 22:02:36 +00:00
2022-11-07 20:31:10 +00:00
def getUnspentsByAddr ( self ) :
unspent_addr = dict ( )
2023-12-29 13:36:00 +00:00
unspent = self . rpc_wallet ( ' listunspent ' )
2022-11-07 20:31:10 +00:00
for u in unspent :
if u [ ' spendable ' ] is not True :
continue
2024-01-24 21:12:18 +00:00
if ' address ' not in u :
continue
if ' desc ' in u :
desc = u [ ' desc ' ]
if self . using_segwit :
if self . use_p2shp2wsh ( ) :
if not desc . startswith ( ' sh(wpkh ' ) :
continue
else :
if not desc . startswith ( ' wpkh ' ) :
continue
else :
if not desc . startswith ( ' pkh ' ) :
continue
2022-11-07 20:31:10 +00:00
unspent_addr [ u [ ' address ' ] ] = unspent_addr . get ( u [ ' address ' ] , 0 ) + self . make_int ( u [ ' amount ' ] , r = 1 )
return unspent_addr
2022-12-05 15:04:23 +00:00
def getUTXOBalance ( self , address : str ) :
2023-12-29 13:36:00 +00:00
num_blocks = self . rpc ( ' getblockcount ' )
2022-11-07 20:31:10 +00:00
sum_unspent = 0
self . _log . debug ( ' [rm] scantxoutset start ' ) # scantxoutset is slow
2023-12-29 13:36:00 +00:00
ro = self . rpc ( ' scantxoutset ' , [ ' start ' , [ ' addr( {} ) ' . format ( address ) ] ] ) # TODO: Use combo(address) where possible
2022-11-07 20:31:10 +00:00
self . _log . debug ( ' [rm] scantxoutset end ' )
for o in ro [ ' unspents ' ] :
sum_unspent + = self . make_int ( o [ ' amount ' ] )
return sum_unspent
def getProofOfFunds ( self , amount_for , extra_commit_bytes ) :
# TODO: Lock unspent and use same output/s to fund bid
unspent_addr = self . getUnspentsByAddr ( )
sign_for_addr = None
for addr , value in unspent_addr . items ( ) :
if value > = amount_for :
sign_for_addr = addr
break
ensure ( sign_for_addr is not None , ' Could not find address with enough funds for proof ' )
self . _log . debug ( ' sign_for_addr %s ' , sign_for_addr )
2022-11-12 20:17:49 +00:00
2022-12-08 01:22:18 +00:00
if self . using_segwit ( ) : # TODO: Use isSegwitAddress when scantxoutset can use combo
2022-11-07 20:31:10 +00:00
# 'Address does not refer to key' for non p2pkh
pkh = self . decodeAddress ( sign_for_addr )
sign_for_addr = self . pkh_to_address ( pkh )
self . _log . debug ( ' sign_for_addr converted %s ' , sign_for_addr )
2023-12-29 13:36:00 +00:00
signature = self . rpc_wallet ( ' signmessage ' , [ sign_for_addr , sign_for_addr + ' _swap_proof_ ' + extra_commit_bytes . hex ( ) ] )
2022-11-07 20:31:10 +00:00
2023-11-24 19:44:48 +00:00
prove_utxos = [ ] # TODO: Send specific utxos
return ( sign_for_addr , signature , prove_utxos )
def decodeProofUtxos ( self , msg_utxos ) :
proof_utxos = [ ]
if len ( msg_utxos ) > 0 :
num_utxos = len ( msg_utxos ) / / 34
p : int = 0
for i in range ( num_utxos ) :
2023-11-30 16:16:24 +00:00
proof_utxos . append ( ( msg_utxos [ p : p + 32 ] , int . from_bytes ( msg_utxos [ p + 32 : p + 34 ] , ' big ' ) ) )
2023-11-24 19:44:48 +00:00
p + = 34
return proof_utxos
def verifyProofOfFunds ( self , address , signature , utxos , extra_commit_bytes ) :
2022-11-07 20:31:10 +00:00
passed = self . verifyMessage ( address , address + ' _swap_proof_ ' + extra_commit_bytes . hex ( ) , signature )
ensure ( passed is True , ' Proof of funds signature invalid ' )
2022-12-08 01:22:18 +00:00
if self . using_segwit ( ) :
2022-11-07 20:31:10 +00:00
address = self . encodeSegwitAddress ( decodeAddress ( address ) [ 1 : ] )
return self . getUTXOBalance ( address )
2022-12-05 15:04:23 +00:00
def isWalletEncrypted ( self ) - > bool :
2023-12-29 13:36:00 +00:00
wallet_info = self . rpc_wallet ( ' getwalletinfo ' )
2022-11-11 23:51:30 +00:00
return ' unlocked_until ' in wallet_info
2022-12-05 15:04:23 +00:00
def isWalletLocked ( self ) - > bool :
2023-12-29 13:36:00 +00:00
wallet_info = self . rpc_wallet ( ' getwalletinfo ' )
2022-11-11 23:51:30 +00:00
if ' unlocked_until ' in wallet_info and wallet_info [ ' unlocked_until ' ] < = 0 :
return True
return False
2022-11-18 21:31:52 +00:00
def isWalletEncryptedLocked ( self ) :
2023-12-29 13:36:00 +00:00
wallet_info = self . rpc_wallet ( ' getwalletinfo ' )
2022-11-18 21:31:52 +00:00
encrypted = ' unlocked_until ' in wallet_info
locked = encrypted and wallet_info [ ' unlocked_until ' ] < = 0
return encrypted , locked
2022-12-05 15:04:23 +00:00
def changeWalletPassword ( self , old_password : str , new_password : str ) :
2022-11-12 20:17:49 +00:00
self . _log . info ( ' changeWalletPassword - {} ' . format ( self . ticker ( ) ) )
2022-11-11 23:51:30 +00:00
if old_password == ' ' :
2022-11-17 22:58:14 +00:00
if self . isWalletEncrypted ( ) :
raise ValueError ( ' Old password must be set ' )
2023-12-29 13:36:00 +00:00
return self . rpc_wallet ( ' encryptwallet ' , [ new_password ] )
self . rpc_wallet ( ' walletpassphrasechange ' , [ old_password , new_password ] )
2022-11-11 23:51:30 +00:00
2022-12-05 15:04:23 +00:00
def unlockWallet ( self , password : str ) :
2022-11-11 23:51:30 +00:00
if password == ' ' :
return
2022-11-12 20:17:49 +00:00
self . _log . info ( ' unlockWallet - {} ' . format ( self . ticker ( ) ) )
2022-12-12 22:12:28 +00:00
if self . coin_type ( ) == Coins . BTC :
# Recreate wallet if none found
# Required when encrypting an existing btc wallet, workaround is to delete the btc wallet and recreate
2023-12-29 13:36:00 +00:00
wallets = self . rpc ( ' listwallets ' )
2022-12-12 22:12:28 +00:00
if len ( wallets ) < 1 :
self . _log . info ( ' Creating wallet.dat for {} . ' . format ( self . coin_name ( ) ) )
# wallet_name, disable_private_keys, blank, passphrase, avoid_reuse, descriptors
2023-12-29 13:36:00 +00:00
self . rpc ( ' createwallet ' , [ ' wallet.dat ' , False , True , ' ' , False , False ] )
self . rpc_wallet ( ' encryptwallet ' , [ password ] )
2022-12-12 22:12:28 +00:00
2022-11-11 23:51:30 +00:00
# Max timeout value, ~3 years
2023-12-29 13:36:00 +00:00
self . rpc_wallet ( ' walletpassphrase ' , [ password , 100000000 ] )
2022-12-12 22:12:28 +00:00
self . _sc . checkWalletSeed ( self . coin_type ( ) )
2022-11-11 23:51:30 +00:00
def lockWallet ( self ) :
2022-11-12 20:17:49 +00:00
self . _log . info ( ' lockWallet - {} ' . format ( self . ticker ( ) ) )
2023-12-29 13:36:00 +00:00
self . rpc_wallet ( ' walletlock ' )
2022-11-11 23:51:30 +00:00
2022-12-05 15:04:23 +00:00
def get_p2sh_script_pubkey ( self , script : bytearray ) - > bytearray :
script_hash = hash160 ( script )
assert len ( script_hash ) == 20
return CScript ( [ OP_HASH160 , script_hash , OP_EQUAL ] )
def get_p2wsh_script_pubkey ( self , script : bytearray ) - > bytearray :
return CScript ( [ OP_0 , hashlib . sha256 ( script ) . digest ( ) ] )
2022-12-08 01:22:18 +00:00
def findTxnByHash ( self , txid_hex : str ) :
# Only works for wallet txns
try :
2023-12-29 13:36:00 +00:00
rv = self . rpc_wallet ( ' gettransaction ' , [ txid_hex ] )
2022-12-08 01:22:18 +00:00
except Exception as ex :
self . _log . debug ( ' findTxnByHash getrawtransaction failed: {} ' . format ( txid_hex ) )
return None
if ' confirmations ' in rv and rv [ ' confirmations ' ] > = self . blocks_confirmed :
return { ' txid ' : txid_hex , ' amount ' : 0 , ' height ' : rv [ ' blockheight ' ] }
return None
2023-03-23 12:15:47 +00:00
def createRedeemTxn ( self , prevout , output_addr : str , output_value : int ) - > str :
tx = CTransaction ( )
tx . nVersion = self . txVersion ( )
prev_txid = uint256_from_str ( bytes . fromhex ( prevout [ ' txid ' ] ) [ : : - 1 ] )
tx . vin . append ( CTxIn ( COutPoint ( prev_txid , prevout [ ' vout ' ] ) ) )
pkh = self . decodeAddress ( output_addr )
script = self . getScriptForPubkeyHash ( pkh )
tx . vout . append ( self . txoType ( ) ( output_value , script ) )
tx . rehash ( )
return tx . serialize ( ) . hex ( )
def createRefundTxn ( self , prevout , output_addr : str , output_value : int , locktime : int , sequence : int ) - > str :
tx = CTransaction ( )
tx . nVersion = self . txVersion ( )
tx . nLockTime = locktime
prev_txid = uint256_from_str ( bytes . fromhex ( prevout [ ' txid ' ] ) [ : : - 1 ] )
tx . vin . append ( CTxIn ( COutPoint ( prev_txid , prevout [ ' vout ' ] ) , nSequence = sequence , ) )
pkh = self . decodeAddress ( output_addr )
script = self . getScriptForPubkeyHash ( pkh )
tx . vout . append ( self . txoType ( ) ( output_value , script ) )
tx . rehash ( )
return tx . serialize ( ) . hex ( )
2023-07-18 23:19:04 +00:00
def ensureFunds ( self , amount : int ) - > None :
2023-07-05 21:35:25 +00:00
if self . getSpendableBalance ( ) < amount :
raise ValueError ( ' Balance too low ' )
2023-08-29 20:06:16 +00:00
def getHTLCSpendTxVSize ( self , redeem : bool = True ) - > int :
tx_vsize = 5 # Add a few bytes, sequence in script takes variable amount of bytes
if self . using_segwit ( ) :
tx_vsize + = 143 if redeem else 134
else :
tx_vsize + = 323 if redeem else 287
return tx_vsize
2020-11-21 13:16:27 +00:00
2020-10-31 20:08:30 +00:00
def testBTCInterface ( ) :
2022-11-07 20:31:10 +00:00
print ( ' TODO: testBTCInterface ' )
2020-10-31 20:08:30 +00:00
if __name__ == " __main__ " :
testBTCInterface ( )