Browse Source

Create tests for fidelity bond wallets

master
chris-belcher 6 years ago
parent
commit
c70183bdac
No known key found for this signature in database
GPG Key ID: EF734EA677F31129
  1. 5
      jmclient/jmclient/__init__.py
  2. 2
      jmclient/jmclient/wallet.py
  3. 14
      jmclient/jmclient/wallet_utils.py
  4. 10
      jmclient/test/commontest.py
  5. 12
      jmclient/test/test_tx_creation.py
  6. 161
      jmclient/test/test_wallet.py

5
jmclient/jmclient/__init__.py

@ -13,7 +13,8 @@ from .old_mnemonic import mn_decode, mn_encode
from .taker import Taker, P2EPTaker
from .wallet import (Mnemonic, estimate_tx_fee, WalletError, BaseWallet, ImportWalletMixin,
BIP39WalletMixin, BIP32Wallet, BIP49Wallet, LegacyWallet,
SegwitWallet, SegwitLegacyWallet, FidelityBondMixin, FidelityBondWatchonlyWallet,
SegwitWallet, SegwitLegacyWallet, FidelityBondMixin,
FidelityBondWatchonlyWallet, SegwitLegacyWalletFidelityBonds,
UTXOManager, WALLET_IMPLEMENTATIONS, compute_tx_locktime)
from .storage import (Argon2Hash, Storage, StorageError, RetryableStorageError,
StoragePasswordError, VolatileStorage)
@ -49,7 +50,7 @@ from .cli_options import (add_base_options, add_common_options,
from .wallet_utils import (
wallet_tool_main, wallet_generate_recover_bip39, open_wallet,
open_test_wallet_maybe, create_wallet, get_wallet_cls, get_wallet_path,
wallet_display, get_utxos_enabled_disabled)
wallet_display, get_utxos_enabled_disabled, wallet_gettimelockaddress)
from .wallet_service import WalletService
from .maker import Maker, P2EPMaker
from .yieldgenerator import YieldGenerator, YieldGeneratorBasic, ygmain

2
jmclient/jmclient/wallet.py

@ -1672,6 +1672,8 @@ class FidelityBondMixin(object):
"""
converts a time number to a unix timestamp
"""
if not 0 <= timenumber < cls.TIMENUMBERS_PER_PUBKEY:
raise ValueError()
year = cls.TIMELOCK_EPOCH_YEAR + (timenumber*cls.TIMENUMBER_UNIT) // cls.MONTHS_IN_YEAR
month = cls.TIMELOCK_EPOCH_MONTH + (timenumber*cls.TIMENUMBER_UNIT) % cls.MONTHS_IN_YEAR
return timegm(datetime(year, month, *cls.TIMELOCK_DAY_AND_SHORTER).timetuple())

14
jmclient/jmclient/wallet_utils.py

@ -1135,24 +1135,24 @@ def wallet_freezeutxo(wallet, md, display_callback=None, info_callback=None):
def wallet_gettimelockaddress(wallet_service, locktime_string):
if not isinstance(wallet_service.wallet, FidelityBondMixin):
def wallet_gettimelockaddress(wallet, locktime_string):
if not isinstance(wallet, FidelityBondMixin):
jmprint("Error: not a fidelity bond wallet", "error")
return ""
m = FidelityBondMixin.FIDELITY_BOND_MIXDEPTH
address_type = FidelityBondMixin.BIP32_TIMELOCK_ID
index = wallet_service.get_next_unused_index(m, address_type)
index = wallet.get_next_unused_index(m, address_type)
lock_datetime = datetime.strptime(locktime_string, "%Y-%m")
timenumber = FidelityBondMixin.timestamp_to_time_number(timegm(
lock_datetime.timetuple()))
path = wallet_service.get_path(m, address_type, index, timenumber)
jmprint("path = " + wallet_service.get_path_repr(path), "info")
path = wallet.get_path(m, address_type, index, timenumber)
jmprint("path = " + wallet.get_path_repr(path), "info")
jmprint("Coins sent to this address will be not be spendable until "
+ lock_datetime.strftime("%B %Y") + ". Full date: "
+ str(lock_datetime))
addr = wallet_service.get_address_from_path(path)
addr = wallet.get_address_from_path(path)
return addr
def wallet_addtxoutproof(wallet_service, hdpath, txoutproof):
@ -1457,7 +1457,7 @@ def wallet_tool_main(wallet_root_path):
if len(args) < 3:
jmprint('Must have locktime value yyyy-mm. For example 2021-03', "error")
sys.exit(EXIT_ARGERROR)
return wallet_gettimelockaddress(wallet_service, args[2])
return wallet_gettimelockaddress(wallet_service.wallet, args[2])
elif method == "addtxoutproof":
if len(args) < 3:
jmprint('Must have txout proof, which is the output of Bitcoin '

10
jmclient/test/commontest.py

@ -209,3 +209,13 @@ def interact(process, inputs, expected):
for i, inp in enumerate(inputs):
process.expect(expected[i])
process.sendline(inp)
def ensure_bip65_activated():
#on regtest bip65 activates on height 1351
#https://github.com/bitcoin/bitcoin/blob/1d1f8bbf57118e01904448108a104e20f50d2544/src/chainparams.cpp#L262
BIP65Height = 1351
current_height = jm_single().bc_interface.rpc("getblockchaininfo", [])["blocks"]
until_bip65_activation = BIP65Height - current_height + 1
if until_bip65_activation > 0:
jm_single().bc_interface.tick_forward_chain(until_bip65_activation)

12
jmclient/test/test_tx_creation.py

@ -5,9 +5,8 @@ network to check validity.'''
import time
import binascii
import struct
from commontest import make_wallets, make_sign_and_push
from binascii import unhexlify
>>>>>>> c158543... only allow pubkey in bytes for mk_freeze_script()
from commontest import make_wallets, make_sign_and_push, ensure_bip65_activated
import jmbitcoin as bitcoin
import pytest
@ -280,15 +279,6 @@ def test_spend_p2wsh(setup_tx_creation):
txid = jm_single().bc_interface.pushtx(tx)
assert txid
def ensure_bip65_activated():
#on regtest bip65 activates on height 1351
#https://github.com/bitcoin/bitcoin/blob/1d1f8bbf57118e01904448108a104e20f50d2544/src/chainparams.cpp#L262
BIP65Height = 1351
current_height = jm_single().bc_interface.rpc("getblockchaininfo", [])["blocks"]
until_bip65_activation = BIP65Height - current_height + 1
if until_bip65_activation > 0:
jm_single().bc_interface.tick_forward_chain(until_bip65_activation)
def test_spend_freeze_script(setup_tx_creation):
ensure_bip65_activated()

161
jmclient/test/test_wallet.py

@ -6,12 +6,13 @@ from binascii import hexlify, unhexlify
import pytest
import jmbitcoin as btc
from commontest import binarize_tx
from commontest import binarize_tx, ensure_bip65_activated
from jmbase import get_log
from jmclient import load_test_config, jm_single, \
SegwitLegacyWallet,BIP32Wallet, BIP49Wallet, LegacyWallet,\
VolatileStorage, get_network, cryptoengine, WalletError,\
SegwitWallet, WalletService
SegwitWallet, WalletService, SegwitLegacyWalletFidelityBonds,\
FidelityBondMixin, FidelityBondWatchonlyWallet, wallet_gettimelockaddress
from test_blockchaininterface import sync_test_wallet
testdir = os.path.dirname(os.path.realpath(__file__))
@ -239,6 +240,72 @@ def test_bip32_addresses_p2sh_p2wpkh(setup_wallet, mixdepth, internal, index, ad
assert wif == wallet.get_wif(mixdepth, internal, index)
assert address == wallet.get_addr(mixdepth, internal, index)
@pytest.mark.parametrize('index,timenumber,address,wif', [
[0, 0, 'bcrt1qndcqwedwa4lu77ryqpvp738d6p034a2fv8mufw3pw5smfcn39sgqpesn76', 'cST4g5R3mKp44K4J8PRVyys4XJu6EFavZyssq67PJKCnbhjdEdBY'],
[0, 50, 'bcrt1q73zhrfcu0ttkk4er9esrmvnpl6wpzhny5aly97jj9nw52agf8ncqjv8rda', 'cST4g5R3mKp44K4J8PRVyys4XJu6EFavZyssq67PJKCnbhjdEdBY'],
[5, 0, 'bcrt1qz5208jdm6399ja309ra28d0a34qlt0859u77uxc94v5mgk7auhtssau4pw', 'cRnUaBYTmyZURPe72YCrtvgxpBMvLKPZaCoXvKuWRPMryeJeAZx2'],
[9, 1, 'bcrt1qa7pd6qnadpmlm29vtvqnykalc34tr33eclaz7eeqal59n4gwr28qwnka2r', 'cQCxEPCWMwXVB16zCikDBTXMUccx6ioHQipPhYEp1euihkJUafyD']
])
def test_bip32_timelocked_addresses(setup_wallet, index, timenumber, address, wif):
jm_single().config.set('BLOCKCHAIN', 'network', 'testnet')
entropy = unhexlify('2e0339ba89b4a1272cdf78b27ee62669ee01992a59e836e2807051be128ca817')
storage = VolatileStorage()
SegwitLegacyWalletFidelityBonds.initialize(
storage, get_network(), entropy=entropy, max_mixdepth=1)
wallet = SegwitLegacyWalletFidelityBonds(storage)
mixdepth = FidelityBondMixin.FIDELITY_BOND_MIXDEPTH
address_type = FidelityBondMixin.BIP32_TIMELOCK_ID
#wallet needs to know about the script beforehand
wallet.get_script_and_update_map(mixdepth, address_type, index, timenumber)
assert address == wallet.get_addr(mixdepth, address_type, index, timenumber)
assert wif == wallet.get_wif_path(wallet.get_path(mixdepth, address_type, index, timenumber))
@pytest.mark.parametrize('timenumber,locktime_string', [
[0, "2020-01"],
[20, "2021-09"],
[100, "2028-05"],
[150, "2032-07"],
[350, "2049-03"]
])
def test_gettimelockaddress_method(setup_wallet, timenumber, locktime_string):
storage = VolatileStorage()
SegwitLegacyWalletFidelityBonds.initialize(storage, get_network())
wallet = SegwitLegacyWalletFidelityBonds(storage)
m = FidelityBondMixin.FIDELITY_BOND_MIXDEPTH
address_type = FidelityBondMixin.BIP32_TIMELOCK_ID
index = wallet.get_next_unused_index(m, address_type)
script = wallet.get_script_and_update_map(m, address_type, index,
timenumber)
addr = wallet.script_to_addr(script)
addr_from_method = wallet_gettimelockaddress(wallet, locktime_string)
assert addr == addr_from_method
@pytest.mark.parametrize('index,wif', [
[0, 'cMg9eH3fW2JDSyggvXucjmECRwiheCMDo2Qik8y1keeYaxynzrYa'],
[9, 'cURA1Qgxhd7QnhhwxCnCHD4pZddVrJdu2BkTdzNaTp9owRSkUvPy'],
[50, 'cRTaHZ1eezb8s6xsT2V7EAevYToQMi7cxQD9vgFZzaJZDfhMhf3c']
])
def test_bip32_burn_keys(setup_wallet, index, wif):
jm_single().config.set('BLOCKCHAIN', 'network', 'testnet')
entropy = unhexlify('2e0339ba89b4a1272cdf78b27ee62669ee01992a59e836e2807051be128ca817')
storage = VolatileStorage()
SegwitLegacyWalletFidelityBonds.initialize(
storage, get_network(), entropy=entropy, max_mixdepth=1)
wallet = SegwitLegacyWalletFidelityBonds(storage)
mixdepth = FidelityBondMixin.FIDELITY_BOND_MIXDEPTH
address_type = FidelityBondMixin.BIP32_BURN_ID
#advance index_cache enough
wallet.set_next_index(mixdepth, address_type, index, force=True)
assert wif == wallet.get_wif_path(wallet.get_path(mixdepth, address_type, index))
def test_import_key(setup_wallet):
jm_single().config.set('BLOCKCHAIN', 'network', 'testnet')
@ -331,6 +398,29 @@ def test_signing_simple(setup_wallet, wallet_cls, type_check):
txout = jm_single().bc_interface.pushtx(btc.serialize(tx))
assert txout
def test_timelocked_output_signing(setup_wallet):
jm_single().config.set('BLOCKCHAIN', 'network', 'testnet')
ensure_bip65_activated()
storage = VolatileStorage()
SegwitLegacyWalletFidelityBonds.initialize(storage, get_network())
wallet = SegwitLegacyWalletFidelityBonds(storage)
index = 0
timenumber = 0
script = wallet.get_script_and_update_map(
FidelityBondMixin.FIDELITY_BOND_MIXDEPTH,
FidelityBondMixin.BIP32_TIMELOCK_ID, index, timenumber)
utxo = fund_wallet_addr(wallet, wallet.script_to_addr(script))
timestamp = wallet._time_number_to_timestamp(timenumber)
tx = btc.deserialize(btc.mktx(['{}:{}'.format(
hexlify(utxo[0]).decode('ascii'), utxo[1])],
[btc.p2sh_scriptaddr(b"\x00",magicbyte=196) + ':' + str(10**8 - 9000)],
locktime=timestamp+1))
tx = wallet.sign_tx(tx, {0: (script, 10**8)})
txout = jm_single().bc_interface.pushtx(btc.serialize(tx))
assert txout
def test_get_bbm(setup_wallet):
jm_single().config.set('BLOCKCHAIN', 'network', 'testnet')
amount = 10**8
@ -537,6 +627,37 @@ def test_path_repr_imported(setup_wallet):
assert path_new == path
@pytest.mark.parametrize('timenumber,timestamp', [
[0, 1577836800],
[50, 1709251200],
[300, 2366841600],
[400, None], #too far in the future
[-1, None] #before epoch
])
def test_timenumber_to_timestamp(setup_wallet, timenumber, timestamp):
try:
implied_timestamp = FidelityBondMixin._time_number_to_timestamp(
timenumber)
assert implied_timestamp == timestamp
except ValueError:
#None means the timenumber is intentionally invalid
assert timestamp == None
@pytest.mark.parametrize('timestamp,timenumber', [
[1577836800, 0],
[1709251200, 50],
[2366841600, 300],
[1577836801, None], #not exactly midnight on first of month
[2629670400, None], #too far in future
[1575158400, None] #before epoch
])
def test_timestamp_to_timenumber(setup_wallet, timestamp, timenumber):
try:
implied_timenumber = FidelityBondMixin.timestamp_to_time_number(
timestamp)
assert implied_timenumber == timenumber
except ValueError:
assert timenumber == None
def test_wrong_wallet_cls(setup_wallet):
storage = VolatileStorage()
@ -658,6 +779,42 @@ def test_wallet_mixdepth_decrease(setup_wallet):
# because we explicitly ask for a specific mixdepth
assert utxo in new_wallet.select_utxos_(max_mixdepth, 10**7)
def test_watchonly_wallet(setup_wallet):
jm_single().config.set('BLOCKCHAIN', 'network', 'testnet')
storage = VolatileStorage()
SegwitLegacyWalletFidelityBonds.initialize(storage, get_network())
wallet = SegwitLegacyWalletFidelityBonds(storage)
paths = [
"m/49'/1'/0'/0/0",
"m/49'/1'/0'/1/0",
"m/49'/1'/0'/2/0:1577836800",
"m/49'/1'/0'/2/0:2314051200"
]
burn_path = "m/49'/1'/0'/3/0"
scripts = [wallet.get_script_from_path(wallet.path_repr_to_path(path))
for path in paths]
privkey, engine = wallet._get_priv_from_path(wallet.path_repr_to_path(burn_path))
burn_pubkey = engine.privkey_to_pubkey(privkey)
master_pub_key = wallet.get_bip32_pub_export(
FidelityBondMixin.FIDELITY_BOND_MIXDEPTH)
watchonly_storage = VolatileStorage()
entropy = FidelityBondMixin.get_xpub_from_fidelity_bond_master_pub_key(
master_pub_key).encode()
FidelityBondWatchonlyWallet.initialize(watchonly_storage, get_network(),
entropy=entropy)
watchonly_wallet = FidelityBondWatchonlyWallet(watchonly_storage)
watchonly_scripts = [watchonly_wallet.get_script_from_path(
watchonly_wallet.path_repr_to_path(path)) for path in paths]
privkey, engine = wallet._get_priv_from_path(wallet.path_repr_to_path(burn_path))
watchonly_burn_pubkey = engine.privkey_to_pubkey(privkey)
for script, watchonly_script in zip(scripts, watchonly_scripts):
assert script == watchonly_script
assert burn_pubkey == watchonly_burn_pubkey
@pytest.fixture(scope='module')
def setup_wallet():

Loading…
Cancel
Save