Skip to content

Commit

Permalink
codex32: add functional test for seed import
Browse files Browse the repository at this point in the history
Github-Pull: bitcoin#27351
Rebased-From: 38ddc11
  • Loading branch information
apoelstra authored and luke-jr committed Jul 27, 2023
1 parent 42c87a8 commit 34c15b4
Show file tree
Hide file tree
Showing 2 changed files with 270 additions and 0 deletions.
1 change: 1 addition & 0 deletions test/functional/test_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,7 @@
'mempool_expiry.py',
'wallet_import_with_label.py --legacy-wallet',
'wallet_importdescriptors.py --descriptors',
'wallet_importseed.py --descriptors',
'wallet_upgradewallet.py --legacy-wallet',
'wallet_crosschain.py',
'mining_basic.py',
Expand Down
269 changes: 269 additions & 0 deletions test/functional/wallet_importseed.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,269 @@
#!/usr/bin/env python3
# Copyright (c) 2013 The Bitcoin Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test the 'seeds' argument to the importdescriptors RPC
Test importingi seeds by using the BIP 93 test vectors to verify that imported
seeds are compatible with descriptors containing the corresponding xpubs, that
the wallet is able to recognize and send funds, and that the wallet can derive
addresses, when given only seeds as private data."""

import time

from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
assert_raises_rpc_error,
)


class ImportDescriptorsTest(BitcoinTestFramework):
def add_options(self, parser):
self.add_wallet_options(parser, legacy=False)

def set_test_params(self):
self.num_nodes = 1
self.setup_clean_chain = True
self.wallet_names = []

def skip_test_if_missing_module(self):
self.skip_if_no_wallet()

def run_test(self):
test_start = int(time.time())

# Spend/receive tests
self.nodes[0].createwallet(wallet_name='w0', descriptors=True)
self.nodes[0].createwallet(wallet_name='w1', descriptors=True, blank=True)
w0 = self.nodes[0].get_wallet_rpc('w0')
w1 = self.nodes[0].get_wallet_rpc('w1')

self.generatetoaddress(self.nodes[0], 2, w0.getnewaddress())
self.generate(self.nodes[0], 100)

# Test 1: send coins to wallet, check they are not received, then import
# the descriptor and make sure they are recognized. Send them
# back and repeat. Uses single codex32 seed.
#
# xpub converted from BIP 93 test vector 1 xpriv using rust-bitcoin
xpub = "tpubD6NzVbkrYhZ4YAqhvsGTCD5axU32P9MH7ySPr38icriLyJc4KcCvwVzE3rsi" \
"XaAHBC8QtYWhiBGdc6aZRmroQShGcWygQfErbvLULfJSi8j"
descriptors = [
f"wsh(pk({xpub}/55/*))",
f"tr({xpub}/1/2/3/4/5/*)",
f"pkh({xpub}/*)",
f"wpkh({xpub}/*)",
f"rawtr({xpub}/1/2/3/*)",
]
assert_raises_rpc_error(-4, "This wallet has no available keys", w1.getnewaddress)
for descriptor in descriptors:
descriptor_chk = w0.getdescriptorinfo(descriptor)["descriptor"]
addr = w0.deriveaddresses(descriptor_chk, range=[0, 20])[0]

assert w0.getbalance() > 99 # sloppy balance checks, to account for fees
w0.sendtoaddress(addr, 95)
self.generate(self.nodes[0], 1)
assert w0.getbalance() < 5

w1.importdescriptors(
[{"desc": descriptor_chk, "timestamp": test_start, "range": 0, "active": True}],
[["ms10testsxxxxxxxxxxxxxxxxxxxxxxxxxx4nzvca9cmczlw"]],
)

assert w1.getbalance() > 94
w1.sendtoaddress(w0.getnewaddress(), 95, "", "", True)
self.generate(self.nodes[0], 1)
assert w0.getbalance() > 99
w1.getnewaddress() # no failure now

# Test 2: deriveaddresses on hardened keys fails before import, succeeds after.
# Uses single codex32 seed in 2 shares.
#
# xpub converted from BIP 93 test vector 2 xpriv using rust-bitcoin
self.nodes[0].createwallet(wallet_name='w2', descriptors=True, blank=True)
w2 = self.nodes[0].get_wallet_rpc('w2')

xpub = "tpubD6NzVbkrYhZ4Wf289qp46iFM6zACTdXTqqrA3pKUV8bF8SNBcYS8xvVPZg43" \
"6YhSuCqTKLfnDkmwi9TE6fa5cvxm3NHRCBbgJoC6YgsQBFY"
descriptor = f"tr([fab6868a/1h/2]{xpub}/1h/2/*h)"
descriptor_chk = w2.getdescriptorinfo(descriptor)["descriptor"]
assert_raises_rpc_error(
-4,
"This wallet has no available keys",
w2.getnewaddress,
address_type="bech32m",
)

# Try importing descriptor with wrong seed
err = w2.importdescriptors(
[{"desc": descriptor_chk, "timestamp": test_start, "active": True, "range": [0, 20]}],
[["ms10testsxxxxxxxxxxxxxxxxxxxxxxxxxx4nzvca9cmczlw"]],
)
assert "Cannot expand descriptor." in err[0]["error"]["message"]
assert_raises_rpc_error(
-4,
"This wallet has no available keys",
w2.getnewaddress,
address_type="bech32m",
)

# Try various failure cases
assert_raises_rpc_error(
-5,
"single share must be the S share",
w2.importdescriptors,
[{"desc": descriptor_chk, "timestamp": test_start, "active": True, "range": [0, 20]}],
[["MS12NAMEA320ZYXWVUTSRQPNMLKJHGFEDCAXRPP870HKKQRM"]],
)

assert_raises_rpc_error(
-5,
"two input shares had the same index",
w2.importdescriptors,
[{"desc": descriptor_chk, "timestamp": test_start, "active": True, "range": [0, 20]}],
[[
"MS12NAMEA320ZYXWVUTSRQPNMLKJHGFEDCAXRPP870HKKQRM",
"MS12NAMEA320ZYXWVUTSRQPNMLKJHGFEDCAXRPP870HKKQRM",
]],
)

assert_raises_rpc_error(
-5,
"input shares had inconsistent seed IDs",
w2.importdescriptors,
[{"desc": descriptor_chk, "timestamp": test_start, "active": True, "range": [0, 20]}],
[[
"MS12NAMEA320ZYXWVUTSRQPNMLKJHGFEDCAXRPP870HKKQRM",
"ms13cashcacdefghjklmnpqrstuvwxyz023949xq35my48dr",
]],
)

# Do it correctly
w2.importdescriptors(
[{"desc": descriptor_chk, "timestamp": test_start, "active": True, "range": [0, 20]}],
[[
"MS12NAMEA320ZYXWVUTSRQPNMLKJHGFEDCAXRPP870HKKQRM",
"MS12NAMECACDEFGHJKLMNPQRSTUVWXYZ023FTR2GDZMPY6PN",
]],
)
# getnewaddress no longer fails. Annoyingl, deriveaddresses will
w2.getnewaddress(address_type="bech32m")
assert_raises_rpc_error(
-5,
"Cannot derive script without private keys",
w2.deriveaddresses,
descriptor_chk,
0,
)
# Do it again, to see if nothing breaks
w2.importdescriptors(
[{"desc": descriptor_chk, "timestamp": test_start, "active": True, "range": [0, 20]}],
[[
"MS12NAMEA320ZYXWVUTSRQPNMLKJHGFEDCAXRPP870HKKQRM",
"MS12NAMECACDEFGHJKLMNPQRSTUVWXYZ023FTR2GDZMPY6PN",
]],
)

# Test 3: multiple seeds, multiple descriptors
#
# xpubs converted from BIP 93 test vector 3, 4 and 5 xprivs using rust-bitcoin

self.nodes[0].createwallet(wallet_name='w3', descriptors=True, blank=True)
w3 = self.nodes[0].get_wallet_rpc('w3')
xpub1 = "tpubD6NzVbkrYhZ4WNNA2qNKYbaxKR3TYtP2n5bNSj6JKzYsVUPxahe2vWJKwiX2" \
"wfoTJyERQNJ8YnmJvprMHygyaXziTdyFVsSGNmfQtDCCSJ3" # vector 3
xpub2 = "tpubD6NzVbkrYhZ4Y9KL2R346X9ZwcN16c37vjXuZEhDV2LaMt84zqVbKVbVAw1z" \
"nMksNtdKnSRZQXyBL9qJaNnq9BkjtRBdsQbxkTbSGZGrcG6" # vector 4
xpub3 = "tpubD6NzVbkrYhZ4Ykomd4u92cmRCkhZtctLkKU3vCVi7DKBAopRDWVpq6wEGoq7" \
"xYbCQQjEGM8KkqxvQDoLa3sdfpzTBv1yodq4FKwrCdxweHE" # vector 5

descriptor1 = f"rawtr({xpub1}/1/2h/*)"
descriptor1_chk = w3.getdescriptorinfo(descriptor1)["descriptor"]
descriptor2 = f"wpkh({xpub2}/1h/2/*)"
descriptor2_chk = w3.getdescriptorinfo(descriptor2)["descriptor"]
descriptor3 = f"pkh({xpub3}/1h/2/3/4/5/6/7/8/9/10/*)"
descriptor3_chk = w3.getdescriptorinfo(descriptor3)["descriptor"]

assert_raises_rpc_error(
-4,
"This wallet has no available keys",
w3.getnewaddress,
address_type="bech32m",
)
assert_raises_rpc_error(
-4,
"This wallet has no available keys",
w3.getnewaddress,
address_type="bech32",
)
assert_raises_rpc_error(
-4,
"This wallet has no available keys",
w3.getnewaddress,
address_type="legacy",
)

# First try without enough input shares.
assert_raises_rpc_error(
-5,
"did not have enough input shares",
w3.importdescriptors,
[
{"desc": descriptor1_chk, "timestamp": test_start, "active": True, "range": 10},
{"desc": descriptor2_chk, "timestamp": test_start, "active": True, "range": 15},
],
[[
"ms13casheekgpemxzshcrmqhaydlp6yhms3ws7320xyxsar9",
"ms13cashf8jh6sdrkpyrsp5ut94pj8ktehhw2hfvyrj48704",
], [
"ms10leetsllhdmn9m42vcsamx24zrxgs3qrl7ahwvhw4fnzrhve25gvezzyqqtum9pgv99ycma",
]],
)
# Wallet still doesn't work, even the descriptor whose seed was correctly specified
assert_raises_rpc_error(
-4,
"This wallet has no available keys",
w3.getnewaddress,
address_type="bech32",
)

# Do it properly
w3.importdescriptors(
[
{"desc": descriptor1_chk, "timestamp": test_start, "active": True, "range": 10},
{"desc": descriptor2_chk, "timestamp": test_start, "active": True, "range": 15},
{"desc": descriptor3_chk, "timestamp": test_start, "active": True, "range": 15},
],
[[
"ms13cashd0wsedstcdcts64cd7wvy4m90lm28w4ffupqs7rm",
"ms13casheekgpemxzshcrmqhaydlp6yhms3ws7320xyxsar9",
"ms13cashf8jh6sdrkpyrsp5ut94pj8ktehhw2hfvyrj48704",
], [
"ms10leetsllhdmn9m42vcsamx24zrxgs3qrl7ahwvhw4fnzrhve25gvezzyqqtum9pgv99ycma",
]],
)
# All good now for the two descriptors that had seeds
w3.getnewaddress(address_type="bech32")
w3.getnewaddress(address_type="bech32m")
# but the one without a seed still doesn't work
assert_raises_rpc_error(
-12,
"No legacy addresses available",
w3.getnewaddress,
address_type="legacy",
)

# Ok, try to import the legacy one separately.
w3.importdescriptors(
[{"desc": descriptor3_chk, "timestamp": test_start, "active": True, "range": 15}],
[["MS100C8VSM32ZXFGUHPCHTLUPZRY9X8GF2TVDW0S3JN54KHCE6MUA7LQPZYGSFJD" # concat string
"6AN074RXVCEMLH8WU3TK925ACDEFGHJKLMNPQRSTUVWXY06FHPV80UNDVARHRAK"]],
)
# And all is well!
w3.getnewaddress(address_type="bech32")
w3.getnewaddress(address_type="bech32m")
w3.getnewaddress(address_type="legacy")


if __name__ == '__main__':
ImportDescriptorsTest().main()

0 comments on commit 34c15b4

Please sign in to comment.