Decred test_008_gettxout

This commit is contained in:
tecnovert
2024-05-07 00:20:22 +02:00
parent 74c7072926
commit d527ec4974
13 changed files with 652 additions and 189 deletions

View File

@@ -30,7 +30,7 @@ from typing import Optional
from sqlalchemy.orm import sessionmaker, scoped_session
from sqlalchemy.orm.session import close_all_sessions
from .interface import Curves
from .interface.base import Curves
from .interface.part import PARTInterface, PARTInterfaceAnon, PARTInterfaceBlind
from . import __version__
@@ -2334,9 +2334,7 @@ class BasicSwap(BaseApp):
msg_buf.proof_signature = proof_sig
if len(proof_utxos) > 0:
msg_buf.proof_utxos = bytes()
for utxo in proof_utxos:
msg_buf.proof_utxos += utxo[0] + utxo[1].to_bytes(2, 'big')
msg_buf.proof_utxos = ci_to.encodeProofUtxos(proof_utxos)
contract_count = self.getNewContractId()
msg_buf.pkhash_buyer = getKeyID(self.getContractPubkey(dt.datetime.fromtimestamp(now).date(), contract_count))
@@ -3356,10 +3354,11 @@ class BasicSwap(BaseApp):
return None
ci = self.ci(coin_type)
if coin_type in (Coins.NAV, ):
if coin_type in (Coins.NAV, Coins.DCR):
wif_prefix = chainparams[coin_type][self.chain]['key_prefix']
prevout = ci.find_prevout_info(txn, txn_script)
else:
# TODO: Sign in bsx for all coins
wif_prefix = chainparams[Coins.PART][self.chain]['key_prefix']
txjs = self.callcoinrpc(Coins.PART, 'decoderawtransaction', [txn])
if ci.using_segwit():
@@ -3414,7 +3413,7 @@ class BasicSwap(BaseApp):
options = {}
if self.coin_clients[coin_type]['use_segwit']:
options['force_segwit'] = True
if coin_type in (Coins.NAV, ):
if coin_type in (Coins.NAV, Coins.DCR):
refund_sig = ci.getTxSignature(refund_txn, prevout, privkey)
else:
refund_sig = self.callcoinrpc(Coins.PART, 'createsignaturewithkey', [refund_txn, prevout, privkey, 'ALL', options])
@@ -3432,7 +3431,7 @@ class BasicSwap(BaseApp):
script += format(OpCodes.OP_PUSHDATA1, '02x') + format(len(txn_script), '02x') + txn_script.hex()
refund_txn = ci.setTxScriptSig(bytes.fromhex(refund_txn), 0, bytes.fromhex(script)).hex()
if coin_type in (Coins.NAV, ):
if coin_type in (Coins.NAV, Coins.DCR):
# Only checks signature
ro = ci.verifyRawTransaction(refund_txn, [prevout])
else:

View File

@@ -4,14 +4,9 @@
# Distributed under the MIT software license, see the accompanying
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
import threading
from enum import IntEnum
from .util import (
COIN,
make_int,
format_amount,
TemporaryError,
)
XMR_COIN = 10 ** 12
@@ -180,11 +175,11 @@ chainparams = {
'max_amount': 100000 * COIN,
'name': 'testnet3',
},
'regtest': {
'regtest': { # simnet
'rpcport': 18656,
'pubkey_address': 0x0e00,
'script_address': 0x0ddb,
'key_prefix': 0x22fe,
'pubkey_address': 0x0e91,
'script_address': 0x0e6c,
'key_prefix': 0x2307,
'bip44': 1,
'min_amount': 1000,
'max_amount': 100000 * COIN,
@@ -422,89 +417,3 @@ def getCoinIdFromTicker(ticker):
return ticker_map[ticker.lower()]
except Exception:
raise ValueError('Unknown coin')
class CoinInterface:
def __init__(self, network):
self.setDefaults()
self._network = network
self._mx_wallet = threading.Lock()
def setDefaults(self):
self._unknown_wallet_seed = True
self._restore_height = None
def make_int(self, amount_in: int, r: int = 0) -> int:
return make_int(amount_in, self.exp(), r=r)
def format_amount(self, amount_in, conv_int=False, r=0):
amount_int = make_int(amount_in, self.exp(), r=r) if conv_int else amount_in
return format_amount(amount_int, self.exp())
def coin_name(self) -> str:
coin_chainparams = chainparams[self.coin_type()]
if coin_chainparams.get('use_ticker_as_name', False):
return coin_chainparams['ticker']
return coin_chainparams['name'].capitalize()
def ticker(self) -> str:
ticker = chainparams[self.coin_type()]['ticker']
if self._network == 'testnet':
ticker = 't' + ticker
elif self._network == 'regtest':
ticker = 'rt' + ticker
return ticker
def getExchangeTicker(self, exchange_name: str) -> str:
return chainparams[self.coin_type()]['ticker']
def getExchangeName(self, exchange_name: str) -> str:
return chainparams[self.coin_type()]['name']
def ticker_mainnet(self) -> str:
ticker = chainparams[self.coin_type()]['ticker']
return ticker
def min_amount(self) -> int:
return chainparams[self.coin_type()][self._network]['min_amount']
def max_amount(self) -> int:
return chainparams[self.coin_type()][self._network]['max_amount']
def setWalletSeedWarning(self, value: bool) -> None:
self._unknown_wallet_seed = value
def setWalletRestoreHeight(self, value: int) -> None:
self._restore_height = value
def knownWalletSeed(self) -> bool:
return not self._unknown_wallet_seed
def chainparams(self):
return chainparams[self.coin_type()]
def chainparams_network(self):
return chainparams[self.coin_type()][self._network]
def has_segwit(self) -> bool:
return chainparams[self.coin_type()].get('has_segwit', True)
def is_transient_error(self, ex) -> bool:
if isinstance(ex, TemporaryError):
return True
str_error: str = str(ex).lower()
if 'not enough unlocked money' in str_error:
return True
if 'no unlocked balance' in str_error:
return True
if 'transaction was rejected by daemon' in str_error:
return True
if 'invalid unlocked_balance' in str_error:
return True
if 'daemon is busy' in str_error:
return True
if 'timed out' in str_error:
return True
if 'request-sent' in str_error:
return True
return False

View File

@@ -1,13 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Copyright (c) 2023 tecnovert
# Distributed under the MIT software license, see the accompanying
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
from enum import IntEnum
class Curves(IntEnum):
secp256k1 = 1
ed25519 = 2

148
basicswap/interface/base.py Normal file
View File

@@ -0,0 +1,148 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Copyright (c) 2024 tecnovert
# Distributed under the MIT software license, see the accompanying
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
import threading
from enum import IntEnum
from basicswap.chainparams import (
chainparams,
)
from basicswap.util import (
ensure,
i2b, b2i,
make_int,
format_amount,
TemporaryError,
)
from basicswap.util.ecc import (
ep,
getSecretInt,
)
from coincurve.dleag import (
verify_secp256k1_point
)
from coincurve.keys import (
PublicKey,
)
class Curves(IntEnum):
secp256k1 = 1
ed25519 = 2
class CoinInterface:
def __init__(self, network):
self.setDefaults()
self._network = network
self._mx_wallet = threading.Lock()
def setDefaults(self):
self._unknown_wallet_seed = True
self._restore_height = None
def make_int(self, amount_in: int, r: int = 0) -> int:
return make_int(amount_in, self.exp(), r=r)
def format_amount(self, amount_in, conv_int=False, r=0):
amount_int = make_int(amount_in, self.exp(), r=r) if conv_int else amount_in
return format_amount(amount_int, self.exp())
def coin_name(self) -> str:
coin_chainparams = chainparams[self.coin_type()]
if coin_chainparams.get('use_ticker_as_name', False):
return coin_chainparams['ticker']
return coin_chainparams['name'].capitalize()
def ticker(self) -> str:
ticker = chainparams[self.coin_type()]['ticker']
if self._network == 'testnet':
ticker = 't' + ticker
elif self._network == 'regtest':
ticker = 'rt' + ticker
return ticker
def getExchangeTicker(self, exchange_name: str) -> str:
return chainparams[self.coin_type()]['ticker']
def getExchangeName(self, exchange_name: str) -> str:
return chainparams[self.coin_type()]['name']
def ticker_mainnet(self) -> str:
ticker = chainparams[self.coin_type()]['ticker']
return ticker
def min_amount(self) -> int:
return chainparams[self.coin_type()][self._network]['min_amount']
def max_amount(self) -> int:
return chainparams[self.coin_type()][self._network]['max_amount']
def setWalletSeedWarning(self, value: bool) -> None:
self._unknown_wallet_seed = value
def setWalletRestoreHeight(self, value: int) -> None:
self._restore_height = value
def knownWalletSeed(self) -> bool:
return not self._unknown_wallet_seed
def chainparams(self):
return chainparams[self.coin_type()]
def chainparams_network(self):
return chainparams[self.coin_type()][self._network]
def has_segwit(self) -> bool:
return chainparams[self.coin_type()].get('has_segwit', True)
def is_transient_error(self, ex) -> bool:
if isinstance(ex, TemporaryError):
return True
str_error: str = str(ex).lower()
if 'not enough unlocked money' in str_error:
return True
if 'no unlocked balance' in str_error:
return True
if 'transaction was rejected by daemon' in str_error:
return True
if 'invalid unlocked_balance' in str_error:
return True
if 'daemon is busy' in str_error:
return True
if 'timed out' in str_error:
return True
if 'request-sent' in str_error:
return True
return False
def setConfTarget(self, new_conf_target: int) -> None:
ensure(new_conf_target >= 1 and new_conf_target < 33, 'Invalid conf_target value')
self._conf_target = new_conf_target
def walletRestoreHeight(self) -> int:
return self._restore_height
class Secp256k1Interface(CoinInterface):
@staticmethod
def curve_type():
return Curves.secp256k1
def getNewSecretKey(self) -> bytes:
return i2b(getSecretInt())
def getPubkey(self, privkey):
return PublicKey.from_secret(privkey).format()
def verifyKey(self, k: bytes) -> bool:
i = b2i(k)
return (i < ep.o and i > 0)
def verifyPubkey(self, pubkey_bytes: bytes) -> bool:
return verify_secp256k1_point(pubkey_bytes)

View File

@@ -5,24 +5,32 @@
# Distributed under the MIT software license, see the accompanying
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
import json
import base64
import hashlib
import json
import logging
import traceback
from io import BytesIO
from basicswap.contrib.test_framework import segwit_addr
from basicswap.interface import (
Curves)
from basicswap.basicswap_util import (
getVoutByAddress,
getVoutByScriptPubKey,
)
from basicswap.contrib.test_framework import (
segwit_addr,
)
from basicswap.interface.base import (
Secp256k1Interface,
)
from basicswap.util import (
ensure,
b2h, i2b, b2i, i2h)
b2h, i2b, b2i, i2h,
)
from basicswap.util.ecc import (
ep,
pointToCPK, CPKToPoint,
getSecretInt)
)
from basicswap.util.script import (
decodeScriptNum,
getCompactSizeLen,
@@ -42,14 +50,14 @@ from basicswap.util.crypto import (
)
from coincurve.keys import (
PrivateKey,
PublicKey)
from coincurve.dleag import (
verify_secp256k1_point)
PublicKey,
)
from coincurve.ecdsaotves import (
ecdsaotves_enc_sign,
ecdsaotves_enc_verify,
ecdsaotves_dec_sig,
ecdsaotves_rec_enc_key)
ecdsaotves_rec_enc_key
)
from basicswap.contrib.test_framework.messages import (
COIN,
@@ -70,12 +78,13 @@ from basicswap.contrib.test_framework.script import (
OP_DROP,
OP_HASH160, OP_EQUAL,
SIGHASH_ALL,
SegwitV0SignatureHash)
SegwitV0SignatureHash,
)
from basicswap.basicswap_util import (
TxLockTypes)
TxLockTypes
)
from basicswap.chainparams import CoinInterface, Coins
from basicswap.chainparams import Coins
from basicswap.rpc import make_rpc_func, openrpc
@@ -111,25 +120,6 @@ def find_vout_for_address_from_txobj(tx_obj, addr: str) -> int:
raise RuntimeError("Vout not found for address: txid={}, addr={}".format(tx_obj['txid'], addr))
class Secp256k1Interface(CoinInterface):
@staticmethod
def curve_type():
return Curves.secp256k1
def getNewSecretKey(self) -> bytes:
return i2b(getSecretInt())
def getPubkey(self, privkey):
return PublicKey.from_secret(privkey).format()
def verifyKey(self, k: bytes) -> bool:
i = b2i(k)
return (i < ep.o and i > 0)
def verifyPubkey(self, pubkey_bytes: bytes) -> bool:
return verify_secp256k1_point(pubkey_bytes)
class BTCInterface(Secp256k1Interface):
@staticmethod
@@ -185,7 +175,7 @@ class BTCInterface(Secp256k1Interface):
@staticmethod
def getExpectedSequence(lockType: int, lockVal: int) -> int:
assert (lockVal >= 1), 'Bad lockVal'
ensure(lockVal >= 1, 'Bad lockVal')
if lockType == TxLockTypes.SEQUENCE_LOCK_BLOCKS:
return lockVal
if lockType == TxLockTypes.SEQUENCE_LOCK_TIME:
@@ -271,10 +261,6 @@ class BTCInterface(Secp256k1Interface):
def close_rpc(self, rpc_conn):
rpc_conn.close()
def setConfTarget(self, new_conf_target: int) -> None:
ensure(new_conf_target >= 1 and new_conf_target < 33, 'Invalid conf_target value')
self._conf_target = new_conf_target
def testDaemonRPC(self, with_wallet=True) -> None:
self.rpc_wallet('getwalletinfo' if with_wallet else 'getblockchaininfo')
@@ -321,9 +307,6 @@ class BTCInterface(Secp256k1Interface):
rv['locked_utxos'] = len(self.rpc_wallet('listlockunspent'))
return rv
def walletRestoreHeight(self) -> int:
return self._restore_height
def getWalletRestoreHeight(self) -> int:
start_time = self.rpc_wallet('getwalletinfo')['keypoololdest']
@@ -1249,7 +1232,7 @@ class BTCInterface(Secp256k1Interface):
'vout': utxo['vout']})
return rv, chain_height
def withdrawCoin(self, value, addr_to, subfee):
def withdrawCoin(self, value: float, addr_to: str, subfee: bool):
params = [addr_to, value, '', '', subfee, True, self._conf_target]
return self.rpc_wallet('sendtoaddress', params)
@@ -1435,6 +1418,12 @@ class BTCInterface(Secp256k1Interface):
prove_utxos = [] # TODO: Send specific utxos
return (sign_for_addr, signature, prove_utxos)
def encodeProofUtxos(self, proof_utxos):
packed_utxos = bytes()
for utxo in proof_utxos:
packed_utxos += utxo[0] + utxo[1].to_bytes(2, 'big')
return packed_utxos
def decodeProofUtxos(self, msg_utxos):
proof_utxos = []
if len(msg_utxos) > 0:
@@ -1555,6 +1544,24 @@ class BTCInterface(Secp256k1Interface):
tx_vsize += 323 if redeem else 287
return tx_vsize
def find_prevout_info(self, txn_hex: str, txn_script: bytes):
txjs = self.rpc('decoderawtransaction', [txn_hex])
if self.using_segwit():
p2wsh = self.getScriptDest(txn_script)
n = getVoutByScriptPubKey(txjs, p2wsh.hex())
else:
addr_to = self.encode_p2sh(txn_script)
n = getVoutByAddress(txjs, addr_to)
return {
'txid': txjs['txid'],
'vout': n,
'scriptPubKey': txjs['vout'][n]['scriptPubKey']['hex'],
'redeemScript': txn_script.hex(),
'amount': txjs['vout'][n]['value']
}
def testBTCInterface():
print('TODO: testBTCInterface')

View File

@@ -5,10 +5,20 @@
# Distributed under the MIT software license, see the accompanying
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
import base64
import hashlib
import logging
import random
from basicswap.basicswap_util import (
getVoutByScriptPubKey,
TxLockTypes
)
from basicswap.chainparams import Coins
from basicswap.interface.btc import Secp256k1Interface
from basicswap.util import (
ensure,
)
from basicswap.util.address import (
b58decode,
b58encode,
@@ -18,6 +28,9 @@ from basicswap.util.crypto import (
hash160,
ripemd160,
)
from basicswap.util.script import (
SerialiseNumCompact,
)
from basicswap.util.extkey import ExtKeyPair
from basicswap.util.integer import encode_varint
from basicswap.interface.dcr.rpc import make_rpc_func
@@ -25,10 +38,15 @@ from .messages import CTransaction, CTxOut, SigHashType, TxSerializeType
from .script import push_script_data, OP_HASH160, OP_EQUAL, OP_DUP, OP_EQUALVERIFY, OP_CHECKSIG
from coincurve.keys import (
PrivateKey
PrivateKey,
PublicKey,
)
SEQUENCE_LOCKTIME_GRANULARITY = 9 # 512 seconds
SEQUENCE_LOCKTIME_TYPE_FLAG = (1 << 22)
SEQUENCE_LOCKTIME_MASK = 0x0000f
SigHashSerializePrefix: int = 1
SigHashSerializeWitness: int = 3
@@ -136,6 +154,20 @@ class DCRInterface(Secp256k1Interface):
def txoType():
return CTxOut
@staticmethod
def getExpectedSequence(lockType: int, lockVal: int) -> int:
ensure(lockVal >= 1, 'Bad lockVal')
if lockType == TxLockTypes.SEQUENCE_LOCK_BLOCKS:
return lockVal
if lockType == TxLockTypes.SEQUENCE_LOCK_TIME:
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')
def __init__(self, coin_settings, network, swap_client=None):
super().__init__(network)
self._rpc_host = coin_settings.get('rpchost', '127.0.0.1')
@@ -148,6 +180,8 @@ class DCRInterface(Secp256k1Interface):
self.rpc_wallet = make_rpc_func(coin_settings['walletrpcport'], self._rpcauth, host=self._rpc_host)
else:
self.rpc_wallet = None
self.blocks_confirmed = coin_settings['blocks_confirmed']
self.setConfTarget(coin_settings['conf_target'])
self._use_segwit = coin_settings['use_segwit']
@@ -161,6 +195,13 @@ class DCRInterface(Secp256k1Interface):
checksum = blake256(blake256(data))
return b58encode(data + checksum[0:4])
def sh_to_address(self, sh: bytes) -> str:
assert (len(sh) == 20)
prefix = self.chainparams_network()['script_address']
data = prefix.to_bytes(2, 'big') + sh
checksum = blake256(blake256(data))
return b58encode(data + checksum[0:4])
def decode_address(self, address: str) -> bytes:
addr_data = b58decode(address)
if addr_data is None:
@@ -198,7 +239,18 @@ class DCRInterface(Secp256k1Interface):
return self._use_segwit
def getWalletInfo(self):
rv = {}
rv = self.rpc_wallet('getinfo')
wi = self.rpc_wallet('walletinfo')
balances = self.rpc_wallet('getbalance')
default_account_bal = balances['balances'][0] # 0 always default?
rv['balance'] = default_account_bal['spendable']
rv['unconfirmed_balance'] = default_account_bal['unconfirmed']
rv['immature_balance'] = default_account_bal['immaturecoinbaserewards'] + default_account_bal['immaturestakegeneration']
rv['encrypted'] = True
rv['locked'] = True if wi['unlocked'] is False else False
return rv
def getSeedHash(self, seed: bytes) -> bytes:
@@ -251,6 +303,13 @@ class DCRInterface(Secp256k1Interface):
tx = self.loadTx(tx_bytes)
return tx.serialize(TxSerializeType.NoWitness)
def getTxSignature(self, tx_hex: str, prevout_data, key_wif: str) -> str:
sig_type, key = self.decodeKey(key_wif)
redeem_script = bytes.fromhex(prevout_data['redeemScript'])
sig = self.signTx(key, bytes.fromhex(tx_hex), 0, redeem_script, self.make_int(prevout_data['amount']))
return sig.hex()
def getScriptDest(self, script: bytes) -> bytes:
# P2SH
script_hash = self.pkh(script)
@@ -258,8 +317,205 @@ class DCRInterface(Secp256k1Interface):
return OP_HASH160.to_bytes(1) + len(script_hash).to_bytes(1) + script_hash + OP_EQUAL.to_bytes(1)
def encodeScriptDest(self, script_dest: bytes) -> str:
script_hash = script_dest[2:-1] # Extract hash from script
return self.sh_to_address(script_hash)
def getPubkeyHashDest(self, pkh: bytes) -> bytes:
# P2PKH
assert len(pkh) == 20
return OP_DUP.to_bytes(1) + OP_HASH160.to_bytes(1) + len(pkh).to_bytes(1) + pkh + OP_EQUALVERIFY.to_bytes(1) + OP_CHECKSIG.to_bytes(1)
def get_fee_rate(self, conf_target: int = 2) -> (float, str):
chain_client_settings = self._sc.getChainClientSettings(self.coin_type()) # basicswap.json
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)
def try_get_fee_rate(self, conf_target):
# TODO: How to estimate required fee?
try:
fee_rate: float = self.rpc_wallet('walletinfo')['txfee']
assert (fee_rate > 0.0), 'Non positive feerate'
return fee_rate, 'paytxfee'
except Exception:
fee_rate: float = self.rpc('getnetworkinfo')['relayfee']
return fee_rate, 'relayfee'
fee_rate, rate_src = try_get_fee_rate(self, conf_target)
if min_relay_fee and min_relay_fee > fee_rate:
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()))
return min_relay_fee, 'min_relay_fee'
return fee_rate, rate_src
def getNewAddress(self, use_segwit: bool = True, label: str = 'swap_receive') -> str:
return self.rpc_wallet('getnewaddress')
def getProofOfFunds(self, amount_for, extra_commit_bytes):
# TODO: Lock unspent and use same output/s to fund bid
unspents_by_addr = dict()
unspents = self.rpc_wallet('listunspent')
if unspents is None:
unspents = []
for u in unspents:
if u['spendable'] is not True:
continue
if u['address'] not in unspents_by_addr:
unspents_by_addr[u['address']] = {'total': 0, 'utxos': []}
utxo_amount: int = self.make_int(u['amount'], r=1)
unspents_by_addr[u['address']]['total'] += utxo_amount
unspents_by_addr[u['address']]['utxos'].append((utxo_amount, u['txid'], u['vout'], u['tree']))
max_utxos: int = 4
viable_addrs = []
for addr, data in unspents_by_addr.items():
if data['total'] >= amount_for:
# Sort from largest to smallest amount
sorted_utxos = sorted(data['utxos'], key=lambda x: x[0])
# Max outputs required to reach amount_for
utxos_req: int = 0
sum_value: int = 0
for utxo in sorted_utxos:
sum_value += utxo[0]
utxos_req += 1
if sum_value >= amount_for:
break
if utxos_req <= max_utxos:
viable_addrs.append(addr)
continue
ensure(len(viable_addrs) > 0, 'Could not find address with enough funds for proof')
sign_for_addr: str = random.choice(viable_addrs)
self._log.debug('sign_for_addr %s', sign_for_addr)
prove_utxos = []
sorted_utxos = sorted(unspents_by_addr[sign_for_addr]['utxos'], key=lambda x: x[0])
hasher = hashlib.sha256()
sum_value: int = 0
for utxo in sorted_utxos:
sum_value += utxo[0]
outpoint = (bytes.fromhex(utxo[1]), utxo[2], utxo[3])
prove_utxos.append(outpoint)
hasher.update(outpoint[0])
hasher.update(outpoint[1].to_bytes(2, 'big'))
hasher.update(outpoint[2].to_bytes(1))
if sum_value >= amount_for:
break
utxos_hash = hasher.digest()
signature = self.rpc_wallet('signmessage', [sign_for_addr, sign_for_addr + '_swap_proof_' + utxos_hash.hex() + extra_commit_bytes.hex()])
return (sign_for_addr, signature, prove_utxos)
def withdrawCoin(self, value: float, addr_to: str, subfee: bool = False) -> str:
if subfee:
raise ValueError('TODO')
params = [addr_to, value]
return self.rpc_wallet('sendtoaddress', params)
def isAddressMine(self, address: str, or_watch_only: bool = False) -> bool:
addr_info = self.rpc('validateaddress', [address])
return addr_info.get('ismine', False)
def encodeProofUtxos(self, proof_utxos):
packed_utxos = bytes()
for utxo in proof_utxos:
packed_utxos += utxo[0] + utxo[1].to_bytes(2, 'big') + utxo[2].to_bytes(1)
return packed_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):
proof_utxos.append((msg_utxos[p: p + 32], int.from_bytes(msg_utxos[p + 32: p + 34], 'big'), msg_utxos[p + 34]))
p += 35
return proof_utxos
def verifyProofOfFunds(self, address: str, signature: bytes, utxos, extra_commit_bytes: bytes):
hasher = hashlib.sha256()
sum_value: int = 0
for outpoint in utxos:
hasher.update(outpoint[0])
hasher.update(outpoint[1].to_bytes(2, 'big'))
hasher.update(outpoint[2].to_bytes(1))
utxos_hash = hasher.digest()
passed = self.verifyMessage(address, address + '_swap_proof_' + utxos_hash.hex() + extra_commit_bytes.hex(), signature)
ensure(passed is True, 'Proof of funds signature invalid')
sum_value: int = 0
for outpoint in utxos:
txout = self.rpc('gettxout', [outpoint[0].hex(), outpoint[1], outpoint[2]])
sum_value += self.make_int(txout['value'])
return sum_value
def verifyMessage(self, address: str, message: str, signature: str, message_magic: str = None) -> bool:
if message_magic is None:
message_magic = self.chainparams()['message_magic']
message_bytes = SerialiseNumCompact(len(message_magic)) + bytes(message_magic, 'utf-8') + SerialiseNumCompact(len(message)) + bytes(message, 'utf-8')
message_hash = blake256(message_bytes)
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:
self._log.info('verifyMessage failed: ' + str(e))
return False
address_hash = self.decode_address(address)[2:]
pubkey_hash = ripemd160(blake256(pubkey.format()))
return True if address_hash == pubkey_hash else False
def signTxWithWallet(self, tx) -> bytes:
return bytes.fromhex(self.rpc('signrawtransaction', [tx.hex()])['hex'])
def createRawFundedTransaction(self, addr_to: str, amount: int, sub_fee: bool = False, lock_unspents: bool = True) -> str:
# amount can't be a string, else: Failed to parse request: parameter #2 'amounts' must be type float64 (got string)
float_amount = float(self.format_amount(amount))
txn = self.rpc('createrawtransaction', [[], {addr_to: float_amount}])
fee_rate, fee_src = self.get_fee_rate(self._conf_target)
self._log.debug(f'Fee rate: {fee_rate}, source: {fee_src}, block target: {self._conf_target}')
options = {
'lockUnspents': lock_unspents,
'feeRate': fee_rate,
}
if sub_fee:
options['subtractFeeFromOutputs'] = [0,]
return self.rpc_wallet('fundrawtransaction', [txn, 'default', options])['hex']
def createRawSignedTransaction(self, addr_to, amount) -> str:
txn_funded = self.createRawFundedTransaction(addr_to, amount)
return self.rpc_wallet('signrawtransaction', [txn_funded])['hex']
def getLockTxHeight(self, txid, dest_address, bid_amount, rescan_from, find_index: bool = False):
self._log.debug('TODO: getLockTxHeight')
return None
def find_prevout_info(self, txn_hex: str, txn_script: bytes):
txjs = self.rpc('decoderawtransaction', [txn_hex])
n = getVoutByScriptPubKey(txjs, self.getScriptDest(txn_script).hex())
return {
'txid': txjs['txid'],
'vout': n,
'scriptPubKey': txjs['vout'][n]['scriptPubKey']['hex'],
'redeemScript': txn_script.hex(),
'amount': txjs['vout'][n]['value']
}

View File

@@ -42,5 +42,4 @@ def push_script_data(data_array: bytearray, data: bytes) -> None:
else:
data_array += bytes((OP_PUSHDATA4,)) + len_data.to_bytes(4, 'little')
print('[rm] data_array', (data_array + data).hex())
data_array += data

View File

@@ -5,8 +5,8 @@
# Distributed under the MIT software license, see the accompanying
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
import random
import hashlib
import random
from .btc import BTCInterface, find_vout_for_address_from_txobj
from basicswap.util import (

View File

@@ -24,8 +24,9 @@ from coincurve.dleag import (
verify_ed25519_point,
)
from basicswap.interface import (
Curves)
from basicswap.interface.base import (
Curves,
)
from basicswap.util import (
i2b, b2i, b2h,
dumpj,
@@ -36,7 +37,8 @@ from basicswap.util.network import (
from basicswap.rpc_xmr import (
make_xmr_rpc_func,
make_xmr_rpc2_func)
from basicswap.chainparams import XMR_COIN, CoinInterface, Coins
from basicswap.chainparams import XMR_COIN, Coins
from basicswap.interface.base import CoinInterface
class XMRInterface(CoinInterface):
@@ -239,9 +241,6 @@ class XMRInterface(CoinInterface):
rv['locked'] = False
return rv
def walletRestoreHeight(self):
return self._restore_height
def getMainWalletAddress(self) -> str:
with self._mx_wallet:
self.openWallet(self._wallet_filename)

View File

@@ -9,7 +9,7 @@ from sqlalchemy.orm import scoped_session
from basicswap.util import (
ensure,
)
from basicswap.interface import Curves
from basicswap.interface.base import Curves
from basicswap.chainparams import (
Coins,
)