From d67a5d65d09149edad37388d936df3069bacec03 Mon Sep 17 00:00:00 2001 From: Sarah GLINER Date: Mon, 6 Nov 2023 14:48:26 +0100 Subject: [PATCH] tests: add bitcoin --- test/python/apps/bitcoin.py | 40 +++++++++++++++++++- test/python/requirements.txt | 2 + test/python/test_bitcoin.py | 73 ++++++++++++++++++++++++++++++++++++ 3 files changed, 114 insertions(+), 1 deletion(-) create mode 100644 test/python/test_bitcoin.py diff --git a/test/python/apps/bitcoin.py b/test/python/apps/bitcoin.py index f53dc58e..32cbef94 100644 --- a/test/python/apps/bitcoin.py +++ b/test/python/apps/bitcoin.py @@ -1,6 +1,44 @@ +import sys +from enum import IntEnum +from pathlib import Path from ragger.utils import create_currency_config from ragger.bip import BtcDerivationPathFormat, bitcoin_pack_derivation_path +from ragger.backend.interface import BackendInterface, RAPDU +from ragger.backend import RaisePolicy + +sys.path.append(f"{Path(__file__).parent.resolve()}/bitcoin_client") +from txmaker import createPsbt +from ledger_bitcoin import Client, WalletPolicy, MultisigWallet, AddressType, PartialSignature, segwit_addr +from ledger_bitcoin import Client, Chain, createClient +from ledger_bitcoin.bip380.descriptors import Descriptor BTC_CONF = create_currency_config("BTC", "Bitcoin") -BTC_PACKED_DERIVATION_PATH = bitcoin_pack_derivation_path(BtcDerivationPathFormat.BECH32, "m/44'/0'/0'/0/0") +BTC_PACKED_DERIVATION_PATH = bitcoin_pack_derivation_path(BtcDerivationPathFormat.BECH32, "m/84'/0'/0'/0/0") + +CHAIN = Chain.MAIN + +class BitcoinErrors(IntEnum): + SW_SWAP_CHECKING_FAIL = 0x6b00 + +class BitcoinClient: + def __init__(self, backend: BackendInterface): + if not isinstance(backend, BackendInterface): + raise TypeError("backend must be an instance of BackendInterface") + self._backend = backend + self._backend.raise_policy = RaisePolicy.RAISE_CUSTOM + self._backend.whitelisted_status = [0x9000, 0xE000] + self.client = createClient(backend, chain=CHAIN, debug=True) + + def send_simple_sign_tx(self, in_wallet: WalletPolicy, fees: int, destination: WalletPolicy, send_amount: int) -> RAPDU: + in_amounts = [send_amount + fees] + out_amounts = [send_amount] + psbt = createPsbt(in_wallet, in_amounts, out_amounts, [False], [destination]) + self.client.sign_psbt(psbt, in_wallet, None) + + def get_address_from_wallet(wallet: WalletPolicy): + desc = Descriptor.from_str(wallet.get_descriptor(False)) + desc.derive(0) + spk = desc.script_pubkey + hrp = "bc" if CHAIN == Chain.MAIN else "tb" + return segwit_addr.encode(hrp, 0, spk[2:]) diff --git a/test/python/requirements.txt b/test/python/requirements.txt index ba5b2c5a..431daeab 100644 --- a/test/python/requirements.txt +++ b/test/python/requirements.txt @@ -4,3 +4,5 @@ stellar_sdk base58 xrpl-py scalecodec +bip32 +embit diff --git a/test/python/test_bitcoin.py b/test/python/test_bitcoin.py new file mode 100644 index 00000000..956c129a --- /dev/null +++ b/test/python/test_bitcoin.py @@ -0,0 +1,73 @@ +import pytest + +from .apps.exchange_test_runner import ExchangeTestRunner, ALL_TESTS_EXCEPT_MEMO +from .apps.bitcoin import BitcoinClient, BitcoinErrors +from ledger_bitcoin import WalletPolicy + +in_wallet = WalletPolicy( + "", + "wpkh(@0/**)", + [ + "[f5acc2fd/84'/0'/0']xpub6DUYn4moKgHkK2d7bXX3mHTPb6XQwRVFRMdZ6ZwLS5u3nonGVpJiFeZiQkHutwdFqxKP75jex8gvVm7ed4euYeDtMnoiF1Cz1z4CeBJYWin" + ], +) + +out_wallet = WalletPolicy( + "", + "wpkh(@0/**)", + [ + "xpub6CatWdiZiodmYVtWLtEQsAg1H9ooS1bmsJUBwQ83FE1Fyk386FWcyicJgEZv3quZSJKA5dh5Lo2PbubMGxCfZtRthV6ST2qquL9w3HSzcUn" + ], +) + +out_wallet_2 = WalletPolicy( + "", + "wpkh(@0/**)", + [ + "xpub6D7atwj3ewAGT347tUzTNzfTGos1rCFVX4v8gViXiM2R1QHvox1LhEf6NtCeNsCwpppFUoQuS6mHUwfTveA5tEEwn2LqZHfVBEz5qvYmYhf" + ], +) + +# ExchangeTestRunner implementation for Bitcoin +class BitcoinTests(ExchangeTestRunner): + + currency_ticker = "BTC" + valid_destination_1 = BitcoinClient.get_address_from_wallet(out_wallet) + valid_destination_memo_1 = "" + valid_destination_2 = BitcoinClient.get_address_from_wallet(out_wallet_2) + valid_destination_memo_2 = "0" + valid_refund = BitcoinClient.get_address_from_wallet(in_wallet) + valid_refund_memo = "" + valid_send_amount_1 = 20900000 + valid_send_amount_2 = 446739662 + valid_fees_1 = 100000 + valid_fees_2 = 10078 + fake_refund = "abcdabcd" + fake_refund_memo = "" + fake_payout = "abcdabcd" + fake_payout_memo = "" + signature_refusal_error_code = BitcoinErrors.SW_SWAP_CHECKING_FAIL + + def perform_final_tx(self, destination, send_amount, fees, memo): + if destination == BitcoinClient.get_address_from_wallet(out_wallet): + BitcoinClient(self.backend).send_simple_sign_tx(in_wallet=in_wallet, + fees=fees, + destination=out_wallet, + send_amount=send_amount) + + elif destination == BitcoinClient.get_address_from_wallet(out_wallet_2): + BitcoinClient(self.backend).send_simple_sign_tx(in_wallet=in_wallet, + fees=fees, + destination=out_wallet_2, + send_amount=send_amount) + + # TODO : assert signature validity + + +# Use a class to reuse the same Speculos instance +class TestsBitcoin: + + @pytest.mark.parametrize('test_to_run', ALL_TESTS_EXCEPT_MEMO) + def test_bitcoin(self, backend, exchange_navigation_helper, test_to_run): + BitcoinTests(backend, exchange_navigation_helper).run_test(test_to_run) +