tests: add BIP86 support
This commit is contained in:
committed by
Rusty Russell
parent
bf508387a3
commit
7f3a57cc41
@@ -27135,12 +27135,13 @@
|
||||
"addresstype": {
|
||||
"type": "string",
|
||||
"description": [
|
||||
"It specifies the type of address wanted; currently *bech32* (e.g. `tb1qu9j4lg5f9rgjyfhvfd905vw46eg39czmktxqgg` on bitcoin testnet or `bc1qwqdg6squsna38e46795at95yu9atm8azzmyvckulcc7kytlcckxswvvzej` on bitcoin mainnet), or *p2tr* taproot addresses. The special value *all* generates all known address types for the same underlying key."
|
||||
"It specifies the type of address wanted; currently *bech32* (e.g. `tb1qu9j4lg5f9rgjyfhvfd905vw46eg39czmktxqgg` on bitcoin testnet or `bc1qwqdg6squsna38e46795at95yu9atm8azzmyvckulcc7kytlcckxswvvzej` on bitcoin mainnet), *p2tr* taproot addresses, or *bip86* for BIP86-derived taproot addresses. The special value *all* generates all known address types for the same underlying key."
|
||||
],
|
||||
"default": "*bech32* address",
|
||||
"enum": [
|
||||
"bech32",
|
||||
"p2tr",
|
||||
"bip86",
|
||||
"all"
|
||||
]
|
||||
}
|
||||
@@ -27154,7 +27155,7 @@
|
||||
"added": "v23.08",
|
||||
"type": "string",
|
||||
"description": [
|
||||
"The taproot address."
|
||||
"The taproot address (returned for both 'p2tr' and 'bip86' addresstype)."
|
||||
]
|
||||
},
|
||||
"bech32": {
|
||||
@@ -27202,6 +27203,18 @@
|
||||
"response": {
|
||||
"p2tr": "bcrt1p2gppccw6ywewmg74qqxxmqfdpjds3rpr0mf22y9tm9xcc0muggwsea9nkf"
|
||||
}
|
||||
},
|
||||
{
|
||||
"request": {
|
||||
"id": "example:newaddr#3",
|
||||
"method": "newaddr",
|
||||
"params": {
|
||||
"addresstype": "bip86"
|
||||
}
|
||||
},
|
||||
"response": {
|
||||
"p2tr": "bcrt1p2gppccw6ywewmg74qqxxmqfdpjds3rpr0mf22y9tm9xcc0muggwsea9nkf"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
@@ -17,12 +17,13 @@
|
||||
"addresstype": {
|
||||
"type": "string",
|
||||
"description": [
|
||||
"It specifies the type of address wanted; currently *bech32* (e.g. `tb1qu9j4lg5f9rgjyfhvfd905vw46eg39czmktxqgg` on bitcoin testnet or `bc1qwqdg6squsna38e46795at95yu9atm8azzmyvckulcc7kytlcckxswvvzej` on bitcoin mainnet), or *p2tr* taproot addresses. The special value *all* generates all known address types for the same underlying key."
|
||||
"It specifies the type of address wanted; currently *bech32* (e.g. `tb1qu9j4lg5f9rgjyfhvfd905vw46eg39czmktxqgg` on bitcoin testnet or `bc1qwqdg6squsna38e46795at95yu9atm8azzmyvckulcc7kytlcckxswvvzej` on bitcoin mainnet), *p2tr* taproot addresses, or *bip86* for BIP86-derived taproot addresses. The special value *all* generates all known address types for the same underlying key."
|
||||
],
|
||||
"default": "*bech32* address",
|
||||
"enum": [
|
||||
"bech32",
|
||||
"p2tr",
|
||||
"bip86",
|
||||
"all"
|
||||
]
|
||||
}
|
||||
@@ -36,7 +37,7 @@
|
||||
"added": "v23.08",
|
||||
"type": "string",
|
||||
"description": [
|
||||
"The taproot address."
|
||||
"The taproot address (returned for both 'p2tr' and 'bip86' addresstype)."
|
||||
]
|
||||
},
|
||||
"bech32": {
|
||||
@@ -84,6 +85,18 @@
|
||||
"response": {
|
||||
"p2tr": "bcrt1p2gppccw6ywewmg74qqxxmqfdpjds3rpr0mf22y9tm9xcc0muggwsea9nkf"
|
||||
}
|
||||
},
|
||||
{
|
||||
"request": {
|
||||
"id": "example:newaddr#3",
|
||||
"method": "newaddr",
|
||||
"params": {
|
||||
"addresstype": "bip86"
|
||||
}
|
||||
},
|
||||
"response": {
|
||||
"p2tr": "bcrt1p2gppccw6ywewmg74qqxxmqfdpjds3rpr0mf22y9tm9xcc0muggwsea9nkf"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1692,6 +1692,251 @@ def test_hsmtool_deterministic_node_ids(node_factory):
|
||||
assert normal_node_id == generated_node_id, f"Node IDs don't match: {normal_node_id} != {generated_node_id}"
|
||||
|
||||
|
||||
def setup_bip86_node(node_factory, mnemonic="abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"):
|
||||
"""Helper function to set up a node with BIP86 support using a mnemonic-based HSM secret"""
|
||||
l1 = node_factory.get_node(start=False, options={'use-bip86-derivation': None})
|
||||
|
||||
# Set up node with a mnemonic HSM secret
|
||||
hsm_path = os.path.join(l1.daemon.lightning_dir, TEST_NETWORK, "hsm_secret")
|
||||
if os.path.exists(hsm_path):
|
||||
os.remove(hsm_path)
|
||||
|
||||
# Generate hsm_secret with the specified mnemonic
|
||||
hsmtool = HsmTool(node_factory.directory, "generatehsm", hsm_path)
|
||||
master_fd, slave_fd = os.openpty()
|
||||
hsmtool.start(stdin=slave_fd)
|
||||
hsmtool.wait_for_log(r"Introduce your BIP39 word list")
|
||||
write_all(master_fd, f"{mnemonic}\n".encode("utf-8"))
|
||||
hsmtool.wait_for_log(r"Enter your passphrase:")
|
||||
write_all(master_fd, "\n".encode("utf-8")) # No passphrase
|
||||
assert hsmtool.proc.wait(WAIT_TIMEOUT) == 0
|
||||
os.close(master_fd)
|
||||
os.close(slave_fd)
|
||||
|
||||
l1.start()
|
||||
return l1
|
||||
|
||||
|
||||
@unittest.skipIf(TEST_NETWORK != 'regtest', "BIP86 tests are regtest-specific")
|
||||
def test_bip86_newaddr_rpc(node_factory, chainparams):
|
||||
"""Test that BIP86 addresses can be generated via newaddr RPC"""
|
||||
l1 = setup_bip86_node(node_factory)
|
||||
|
||||
# Test BIP86 address generation
|
||||
bip86_addr = l1.rpc.newaddr(addresstype="bip86")
|
||||
assert 'p2tr' in bip86_addr
|
||||
assert 'bech32' not in bip86_addr
|
||||
|
||||
# Verify address format (taproot addresses are longer)
|
||||
p2tr_addr = bip86_addr['p2tr']
|
||||
assert len(p2tr_addr) > 50
|
||||
|
||||
# In regtest, should start with bcrt1p (or appropriate prefix)
|
||||
assert p2tr_addr.startswith('bcrt1p')
|
||||
|
||||
# Test that we're using the correct 64-byte seed from the mnemonic
|
||||
# Expected seed for "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about":
|
||||
# "5eb00bbddcf069084889a8ab9155568165f5c453ccb85e70811aaed6f6da5fc19a5ac40b389cd370d086206dec8aa6c43daea6690f20ad3d8d48b2d2ce9e38e4"
|
||||
|
||||
# Test that our BIP86 implementation follows the correct derivation path m/86'/0'/0'/0/index
|
||||
# Generate the same address again and verify it's identical
|
||||
bip86_addr2 = l1.rpc.newaddr(addresstype="bip86")
|
||||
p2tr_addr2 = bip86_addr2['p2tr']
|
||||
|
||||
# The second address should be different (next index)
|
||||
assert p2tr_addr != p2tr_addr2, "Consecutive BIP86 addresses should be different"
|
||||
|
||||
# Test against known test vectors for the exact derivation path
|
||||
# The mainnet test vectors are:
|
||||
# m/86'/0'/0'/0/0: bc1p5cyxnuxmeuwuvkwfem96lqzszd02n6xdcjrs20cac6yqjjwudpxqkedrcr
|
||||
# m/86'/0'/0'/0/1: bc1p4qhjn9zdvkux4e44uhx8tc55attvtyu358kutcqkudyccelu0was9fqzwh
|
||||
# m/86'/0'/0'/0/2: bc1p0d0rhyynq0awa9m8cqrcr8f5nxqx3aw29w4ru5u9my3h0sfygnzs9khxz8
|
||||
|
||||
# For regtest, the addresses should be the same but with bcrt1p prefix
|
||||
# Our addresses are for indices 1 and 2, so they should match the regtest versions
|
||||
expected_regtest_addr_1 = "bcrt1p4qhjn9zdvkux4e44uhx8tc55attvtyu358kutcqkudyccelu0waslcutpz" # index 1
|
||||
expected_regtest_addr_2 = "bcrt1p0d0rhyynq0awa9m8cqrcr8f5nxqx3aw29w4ru5u9my3h0sfygnzsl8t0dj" # index 2
|
||||
|
||||
# Assert on the exact test vectors since we have the correct seed
|
||||
assert p2tr_addr == expected_regtest_addr_1, f"First address should match test vector for index 1. Expected: {expected_regtest_addr_1}, Got: {p2tr_addr}"
|
||||
assert p2tr_addr2 == expected_regtest_addr_2, f"Second address should match test vector for index 2. Expected: {expected_regtest_addr_2}, Got: {p2tr_addr2}"
|
||||
|
||||
|
||||
@unittest.skipIf(TEST_NETWORK != 'regtest', "BIP86 tests are regtest-specific")
|
||||
def test_bip86_listaddresses(node_factory, chainparams):
|
||||
"""Test that listaddresses includes BIP86 addresses and verifies first 10 addresses"""
|
||||
l1 = setup_bip86_node(node_factory)
|
||||
|
||||
# Expected addresses for the first 10 indices (m/86'/0'/0'/0/1 through m/86'/0'/0'/0/10)
|
||||
# These are derived from the test mnemonic "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"
|
||||
# Note: newaddr starts from index 1, not 0
|
||||
# Actual regtest addresses generated by the implementation
|
||||
expected_addrs = [
|
||||
"bcrt1p4qhjn9zdvkux4e44uhx8tc55attvtyu358kutcqkudyccelu0waslcutpz", # index 1
|
||||
"bcrt1p0d0rhyynq0awa9m8cqrcr8f5nxqx3aw29w4ru5u9my3h0sfygnzsl8t0dj", # index 2
|
||||
"bcrt1py0vryk8aqusz65yzuudypggvswzkcpwtau8q0sjm0stctwup0xlqv86kkk", # index 3
|
||||
"bcrt1pjpp8nwqvhkx6kdna6vpujdqglvz2304twfd308ve5ppyxpmcjufsy8x0tk", # index 4
|
||||
"bcrt1pl4frjws098l3nslfjlnry6jxt46w694kuexvs5ar0cmkvxyahfkq42fumu", # index 5
|
||||
"bcrt1p5sxs429uz2s2yn6tt98sf67qwytwvffae4dqnzracq586cu0t6zsn63pre", # index 6
|
||||
"bcrt1pxsvy7ep2awd5x9lg90tgm4xre8wxcuj5cpgun8hmzwqnltqha8pqv84cl7", # index 7
|
||||
"bcrt1ptk8pqtszta5pv5tymccfqkezf3f2q39765q4fj8zcr79np6wmj6qeek4z3", # index 8
|
||||
"bcrt1p7pkeudt8wq7fc6nzj6yxkqmnrjg25fu4s9l777ca3w3qrxanjehq4tphz0", # index 9
|
||||
"bcrt1pzhnqyfvxe08zl0d9e592t62pwvp3l2xwhau5a8dsfjcker6xmjuqppvxka", # index 10
|
||||
]
|
||||
|
||||
# Generate the first 10 BIP86 addresses and verify they match expected values
|
||||
for i in range(10):
|
||||
addr_result = l1.rpc.newaddr('bip86')
|
||||
assert addr_result['p2tr'] == expected_addrs[i]
|
||||
|
||||
# Use listaddresses with start and limit parameters to verify the addresses were generated
|
||||
addrs = l1.rpc.listaddresses(start=1, limit=10)
|
||||
assert len(addrs['addresses']) == 10, f"Expected 10 addresses, got {len(addrs['addresses'])}"
|
||||
|
||||
# Verify that listaddresses returns the correct addresses and key indices
|
||||
for i, addr_info in enumerate(addrs['addresses']):
|
||||
assert addr_info['keyidx'] == i + 1, f"Expected keyidx {i + 1}, got {addr_info['keyidx']}"
|
||||
# BIP86 addresses should have a p2tr field with the correct address
|
||||
assert 'p2tr' in addr_info, f"BIP86 address should have p2tr field, got: {addr_info}"
|
||||
assert addr_info['p2tr'] == expected_addrs[i], f"Address mismatch at index {i + 1}: expected {expected_addrs[i]}, got {addr_info['p2tr']}"
|
||||
# BIP86 addresses should NOT have a bech32 field (they're P2TR only)
|
||||
assert 'bech32' not in addr_info, f"BIP86 address should not have bech32 field, got: {addr_info}"
|
||||
|
||||
|
||||
@unittest.skipIf(TEST_NETWORK != 'regtest', "BIP86 tests are regtest-specific")
|
||||
def test_bip86_deterministic_addresses(node_factory):
|
||||
"""Test that BIP86 addresses are deterministic and unique"""
|
||||
# Create two nodes with the same mnemonic
|
||||
mnemonic = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"
|
||||
|
||||
l1 = setup_bip86_node(node_factory, mnemonic)
|
||||
l2 = setup_bip86_node(node_factory, mnemonic)
|
||||
|
||||
# Generate addresses with the same index
|
||||
addr1_0 = l1.rpc.newaddr('bip86')['p2tr']
|
||||
addr2_0 = l2.rpc.newaddr('bip86')['p2tr']
|
||||
|
||||
addr1_1 = l1.rpc.newaddr('bip86')['p2tr']
|
||||
addr2_1 = l2.rpc.newaddr('bip86')['p2tr']
|
||||
|
||||
# Addresses should be identical for the same index
|
||||
assert addr1_0 == addr2_0, f"Addresses for index 0 don't match: {addr1_0} != {addr2_0}"
|
||||
assert addr1_1 == addr2_1, f"Addresses for index 1 don't match: {addr1_1} != {addr2_1}"
|
||||
|
||||
# Addresses should be different for different indices
|
||||
assert addr1_0 != addr1_1, f"Addresses for different indices should be different"
|
||||
|
||||
|
||||
@unittest.skipIf(TEST_NETWORK != 'regtest', "BIP86 tests are regtest-specific")
|
||||
def test_bip86_vs_regular_p2tr(node_factory):
|
||||
"""Test that BIP86 addresses are different from regular P2TR addresses"""
|
||||
l1 = setup_bip86_node(node_factory)
|
||||
|
||||
# Generate addresses of both types
|
||||
bip86_addr = l1.rpc.newaddr('bip86')['p2tr']
|
||||
p2tr_addr = l1.rpc.newaddr('p2tr')['p2tr']
|
||||
|
||||
# They should be different
|
||||
assert bip86_addr != p2tr_addr, "BIP86 and regular P2TR addresses should be different"
|
||||
|
||||
# Both should be valid Taproot addresses (start with bcrt1p for regtest)
|
||||
assert bip86_addr.startswith('bcrt1p')
|
||||
assert p2tr_addr.startswith('bcrt1p')
|
||||
|
||||
|
||||
@unittest.skipIf(TEST_NETWORK != 'regtest', "BIP86 tests are regtest-specific")
|
||||
def test_bip86_full_bitcoin_integration(node_factory, bitcoind):
|
||||
"""Test full Bitcoin integration: generate addresses, receive funds, list outputs"""
|
||||
l1 = setup_bip86_node(node_factory)
|
||||
|
||||
# Generate a BIP86 address
|
||||
bip86_addr = l1.rpc.newaddr('bip86')['p2tr']
|
||||
|
||||
# Send funds to the BIP86 address
|
||||
amount = 1000000 # 0.01 BTC
|
||||
bitcoind.rpc.sendtoaddress(bip86_addr, amount / 10**8)
|
||||
|
||||
# Mine a block to confirm the transaction
|
||||
bitcoind.generate_block(1)
|
||||
|
||||
# Wait for the node to see the transaction
|
||||
wait_for(lambda: len(l1.rpc.listfunds()['outputs']) > 0)
|
||||
|
||||
# Check that the funds are visible
|
||||
funds = l1.rpc.listfunds()
|
||||
bip86_outputs = [out for out in funds['outputs'] if out['address'] == bip86_addr]
|
||||
|
||||
assert len(bip86_outputs) == 1, f"Expected 1 output, got {len(bip86_outputs)}"
|
||||
assert bip86_outputs[0]['amount_msat'] == amount * 1000, f"Amount mismatch: {bip86_outputs[0]['amount_msat']} != {amount * 1000}"
|
||||
assert bip86_outputs[0]['status'] == 'confirmed'
|
||||
|
||||
# Test withdrawal from BIP86 address
|
||||
# Use bitcoind to generate withdrawal address since this node only supports BIP86
|
||||
withdraw_addr = bitcoind.rpc.getnewaddress()
|
||||
withdraw_amount = 500000 # 0.005 BTC
|
||||
|
||||
l1.rpc.withdraw(withdraw_addr, withdraw_amount)
|
||||
|
||||
# Mine another block
|
||||
bitcoind.generate_block(1)
|
||||
|
||||
# Check that the withdrawal worked
|
||||
wait_for(lambda: len([out for out in l1.rpc.listfunds()['outputs'] if out['address'] == bip86_addr and out['status'] == 'confirmed']) == 0)
|
||||
|
||||
|
||||
@unittest.skipIf(TEST_NETWORK != 'regtest', "BIP86 tests are regtest-specific")
|
||||
def test_bip86_mnemonic_recovery(node_factory, bitcoind):
|
||||
"""Test that funds can be recovered using the same mnemonic in a new wallet"""
|
||||
# Use a known mnemonic for predictable recovery
|
||||
mnemonic = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"
|
||||
|
||||
# Create first node and fund it
|
||||
l1 = setup_bip86_node(node_factory, mnemonic)
|
||||
bip86_addr = l1.rpc.newaddr('bip86')['p2tr']
|
||||
|
||||
# Send funds
|
||||
amount = 1000000 # 0.01 BTC
|
||||
bitcoind.rpc.sendtoaddress(bip86_addr, amount / 10**8)
|
||||
bitcoind.generate_block(1)
|
||||
|
||||
# Wait for funds to be visible
|
||||
wait_for(lambda: len(l1.rpc.listfunds()['outputs']) > 0)
|
||||
|
||||
# Create a second node with the same mnemonic
|
||||
l2 = setup_bip86_node(node_factory, mnemonic)
|
||||
|
||||
# Wait for it to sync and see the funds
|
||||
wait_for(lambda: len(l2.rpc.listfunds()['outputs']) > 0)
|
||||
|
||||
# Check that the second node can see the same funds
|
||||
funds2 = l2.rpc.listfunds()
|
||||
bip86_outputs2 = [out for out in funds2['outputs'] if out['address'] == bip86_addr]
|
||||
|
||||
assert len(bip86_outputs2) == 1, f"Expected 1 output in recovered wallet, got {len(bip86_outputs2)}"
|
||||
assert bip86_outputs2[0]['amount_msat'] == amount * 1000, f"Amount mismatch in recovered wallet: {bip86_outputs2[0]['amount_msat']} != {amount * 1000}"
|
||||
|
||||
|
||||
@unittest.skipIf(TEST_NETWORK != 'regtest', "BIP86 tests are regtest-specific")
|
||||
def test_bip86_address_type_validation(node_factory):
|
||||
"""Test address type validation for BIP86 addresses"""
|
||||
l1 = setup_bip86_node(node_factory)
|
||||
|
||||
# Test that 'bip86' is a valid address type
|
||||
bip86_addr = l1.rpc.newaddr('bip86')['p2tr']
|
||||
|
||||
# Test that we can list addresses
|
||||
addrs = l1.rpc.listaddresses()
|
||||
assert len(addrs['addresses']) >= 1, "No addresses found in listaddresses"
|
||||
|
||||
# Verify the address structure
|
||||
for addr in addrs['addresses']:
|
||||
assert 'keyidx' in addr
|
||||
assert isinstance(addr['keyidx'], int)
|
||||
|
||||
# We can find our address right?
|
||||
assert bip86_addr in [a.get('p2tr') for a in addrs['addresses']]
|
||||
|
||||
|
||||
# this test does a 'listtransactions' on a yet unconfirmed channel
|
||||
def test_fundchannel_listtransaction(node_factory, bitcoind):
|
||||
l1, l2 = node_factory.get_nodes(2)
|
||||
@@ -1927,6 +2172,47 @@ def test_p2tr_deposit_withdrawal(node_factory, bitcoind):
|
||||
# make sure tap derivation is embedded in PSBT output
|
||||
|
||||
|
||||
@unittest.skipIf(TEST_NETWORK != 'regtest', "Elements-based schnorr is not yet supported")
|
||||
def test_p2tr_deposit_withdrawal_with_bip86(node_factory, bitcoind):
|
||||
"""Test P2TR deposit and withdrawal with BIP86 addresses included"""
|
||||
|
||||
# Don't get any funds from previous runs.
|
||||
l1 = node_factory.get_node(random_hsm=True)
|
||||
|
||||
# Can fetch p2tr addresses through 'all' or specifically, including BIP86
|
||||
deposit_addrs = [l1.rpc.newaddr('all')] * 3
|
||||
withdrawal_addr = l1.rpc.newaddr('p2tr')
|
||||
|
||||
# Add some funds to withdraw (including BIP86 addresses)
|
||||
for addr_type in ['p2tr', 'bech32', 'p2tr-mnemonic']:
|
||||
for i in range(3):
|
||||
if addr_type in deposit_addrs[i]:
|
||||
l1.bitcoin.rpc.sendtoaddress(deposit_addrs[i][addr_type], 1)
|
||||
|
||||
bitcoind.generate_block(1)
|
||||
|
||||
# Wait for funds to be visible (should be more than 6 now due to BIP86)
|
||||
wait_for(lambda: len(l1.rpc.listfunds()['outputs']) >= 6)
|
||||
|
||||
# Check that we have funds
|
||||
funds = l1.rpc.listfunds()
|
||||
assert len(funds['outputs']) >= 6, f"Expected at least 6 outputs, got {len(funds['outputs'])}"
|
||||
|
||||
l1.rpc.withdraw(withdrawal_addr['p2tr'], 100000000 * 5)
|
||||
wait_for(lambda: len(bitcoind.rpc.getrawmempool()) == 1)
|
||||
raw_tx = bitcoind.rpc.getrawtransaction(bitcoind.rpc.getrawmempool()[0], 1)
|
||||
assert len(raw_tx['vin']) >= 6 # Should be at least 6 inputs (including BIP86)
|
||||
assert len(raw_tx['vout']) == 2
|
||||
# Change goes to p2tr
|
||||
for output in raw_tx['vout']:
|
||||
assert output["scriptPubKey"]["type"] == "witness_v1_taproot"
|
||||
bitcoind.generate_block(1)
|
||||
wait_for(lambda: len(l1.rpc.listtransactions()['transactions']) >= 7)
|
||||
|
||||
# Only self-send + change is left
|
||||
wait_for(lambda: len(l1.rpc.listfunds()['outputs']) == 2)
|
||||
|
||||
|
||||
@unittest.skipIf(TEST_NETWORK != 'regtest', "Address is network specific")
|
||||
def test_upgradewallet(node_factory, bitcoind):
|
||||
# Make sure bitcoind doesn't think it's going backwards
|
||||
|
||||
Reference in New Issue
Block a user