Skip to content

Commit 3e25fd9

Browse files
committed
tests: add BIP86 support
1 parent 5f431bd commit 3e25fd9

File tree

4 files changed

+317
-4
lines changed

4 files changed

+317
-4
lines changed

contrib/msggen/msggen/schema.json

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25994,12 +25994,13 @@
2599425994
"addresstype": {
2599525995
"type": "string",
2599625996
"description": [
25997-
"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."
25997+
"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."
2599825998
],
2599925999
"default": "*bech32* address",
2600026000
"enum": [
2600126001
"bech32",
2600226002
"p2tr",
26003+
"bip86",
2600326004
"all"
2600426005
]
2600526006
}
@@ -26013,7 +26014,7 @@
2601326014
"added": "v23.08",
2601426015
"type": "string",
2601526016
"description": [
26016-
"The taproot address."
26017+
"The taproot address (returned for both 'p2tr' and 'bip86' addresstype)."
2601726018
]
2601826019
},
2601926020
"bech32": {
@@ -26061,6 +26062,18 @@
2606126062
"response": {
2606226063
"p2tr": "bcrt1p2gppccw6ywewmg74qqxxmqfdpjds3rpr0mf22y9tm9xcc0muggwsea9nkf"
2606326064
}
26065+
},
26066+
{
26067+
"request": {
26068+
"id": "example:newaddr#3",
26069+
"method": "newaddr",
26070+
"params": {
26071+
"addresstype": "bip86"
26072+
}
26073+
},
26074+
"response": {
26075+
"p2tr": "bcrt1p2gppccw6ywewmg74qqxxmqfdpjds3rpr0mf22y9tm9xcc0muggwsea9nkf"
26076+
}
2606426077
}
2606526078
]
2606626079
},

doc/schemas/newaddr.json

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,13 @@
1717
"addresstype": {
1818
"type": "string",
1919
"description": [
20-
"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."
20+
"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."
2121
],
2222
"default": "*bech32* address",
2323
"enum": [
2424
"bech32",
2525
"p2tr",
26+
"bip86",
2627
"all"
2728
]
2829
}
@@ -36,7 +37,7 @@
3637
"added": "v23.08",
3738
"type": "string",
3839
"description": [
39-
"The taproot address."
40+
"The taproot address (returned for both 'p2tr' and 'bip86' addresstype)."
4041
]
4142
},
4243
"bech32": {
@@ -84,6 +85,18 @@
8485
"response": {
8586
"p2tr": "bcrt1p2gppccw6ywewmg74qqxxmqfdpjds3rpr0mf22y9tm9xcc0muggwsea9nkf"
8687
}
88+
},
89+
{
90+
"request": {
91+
"id": "example:newaddr#3",
92+
"method": "newaddr",
93+
"params": {
94+
"addresstype": "bip86"
95+
}
96+
},
97+
"response": {
98+
"p2tr": "bcrt1p2gppccw6ywewmg74qqxxmqfdpjds3rpr0mf22y9tm9xcc0muggwsea9nkf"
99+
}
87100
}
88101
]
89102
}

tests/test_wallet.py

Lines changed: 280 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1696,6 +1696,245 @@ def test_hsmtool_deterministic_node_ids(node_factory):
16961696
# Verify both node IDs are identical
16971697
assert normal_node_id == generated_node_id, f"Node IDs don't match: {normal_node_id} != {generated_node_id}"
16981698

1699+
def setup_bip86_node(node_factory, mnemonic="abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"):
1700+
"""Helper function to set up a node with BIP86 support using a mnemonic-based HSM secret"""
1701+
l1 = node_factory.get_node(start=False, options={'use-bip86-derivation': None})
1702+
1703+
# Set up node with a mnemonic HSM secret
1704+
hsm_path = os.path.join(l1.daemon.lightning_dir, TEST_NETWORK, "hsm_secret")
1705+
if os.path.exists(hsm_path):
1706+
os.remove(hsm_path)
1707+
1708+
# Generate hsm_secret with the specified mnemonic
1709+
hsmtool = HsmTool(node_factory.directory, "generatehsm", hsm_path)
1710+
master_fd, slave_fd = os.openpty()
1711+
hsmtool.start(stdin=slave_fd)
1712+
hsmtool.wait_for_log(r"Introduce your BIP39 word list")
1713+
write_all(master_fd, f"{mnemonic}\n".encode("utf-8"))
1714+
hsmtool.wait_for_log(r"Enter your passphrase:")
1715+
write_all(master_fd, "\n".encode("utf-8")) # No passphrase
1716+
assert hsmtool.proc.wait(WAIT_TIMEOUT) == 0
1717+
os.close(master_fd)
1718+
os.close(slave_fd)
1719+
1720+
l1.start()
1721+
return l1
1722+
1723+
1724+
@unittest.skipIf(TEST_NETWORK != 'regtest', "BIP86 tests are regtest-specific")
1725+
def test_bip86_newaddr_rpc(node_factory, chainparams):
1726+
"""Test that BIP86 addresses can be generated via newaddr RPC"""
1727+
l1 = setup_bip86_node(node_factory)
1728+
1729+
# Test BIP86 address generation
1730+
bip86_addr = l1.rpc.newaddr(addresstype="bip86")
1731+
assert 'p2tr' in bip86_addr
1732+
assert 'bech32' not in bip86_addr
1733+
1734+
# Verify address format (taproot addresses are longer)
1735+
p2tr_addr = bip86_addr['p2tr']
1736+
assert len(p2tr_addr) > 50
1737+
1738+
# In regtest, should start with bcrt1p (or appropriate prefix)
1739+
assert p2tr_addr.startswith('bcrt1p')
1740+
1741+
# Test that we're using the correct 64-byte seed from the mnemonic
1742+
# Expected seed for "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"
1743+
expected_seed_hex = "5eb00bbddcf069084889a8ab9155568165f5c453ccb85e70811aaed6f6da5fc19a5ac40b389cd370d086206dec8aa6c43daea6690f20ad3d8d48b2d2ce9e38e4"
1744+
1745+
# Test that our BIP86 implementation follows the correct derivation path m/86'/0'/0'/0/index
1746+
# Generate the same address again and verify it's identical
1747+
bip86_addr2 = l1.rpc.newaddr(addresstype="bip86")
1748+
p2tr_addr2 = bip86_addr2['p2tr']
1749+
1750+
# The second address should be different (next index)
1751+
assert p2tr_addr != p2tr_addr2, "Consecutive BIP86 addresses should be different"
1752+
1753+
# Test against known test vectors for the exact derivation path
1754+
# The mainnet test vectors are:
1755+
# m/86'/0'/0'/0/0: bc1p5cyxnuxmeuwuvkwfem96lqzszd02n6xdcjrs20cac6yqjjwudpxqkedrcr
1756+
# m/86'/0'/0'/0/1: bc1p4qhjn9zdvkux4e44uhx8tc55attvtyu358kutcqkudyccelu0was9fqzwh
1757+
# m/86'/0'/0'/0/2: bc1p0d0rhyynq0awa9m8cqrcr8f5nxqx3aw29w4ru5u9my3h0sfygnzs9khxz8
1758+
1759+
# For regtest, the addresses should be the same but with bcrt1p prefix
1760+
# Our addresses are for indices 1 and 2, so they should match the regtest versions
1761+
expected_regtest_addr_1 = "bcrt1p4qhjn9zdvkux4e44uhx8tc55attvtyu358kutcqkudyccelu0waslcutpz" # index 1
1762+
expected_regtest_addr_2 = "bcrt1p0d0rhyynq0awa9m8cqrcr8f5nxqx3aw29w4ru5u9my3h0sfygnzsl8t0dj" # index 2
1763+
1764+
# Assert on the exact test vectors since we have the correct seed
1765+
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}"
1766+
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}"
1767+
1768+
@unittest.skipIf(TEST_NETWORK != 'regtest', "BIP86 tests are regtest-specific")
1769+
def test_bip86_listaddresses(node_factory, chainparams):
1770+
"""Test that listaddresses includes BIP86 addresses and verifies first 10 addresses"""
1771+
l1 = setup_bip86_node(node_factory)
1772+
1773+
# Expected addresses for the first 10 indices (m/86'/0'/0'/0/1 through m/86'/0'/0'/0/10)
1774+
# These are derived from the test mnemonic "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"
1775+
# Note: newaddr starts from index 1, not 0
1776+
# Actual regtest addresses generated by the implementation
1777+
expected_addrs = [
1778+
"bcrt1p4qhjn9zdvkux4e44uhx8tc55attvtyu358kutcqkudyccelu0waslcutpz", # index 1
1779+
"bcrt1p0d0rhyynq0awa9m8cqrcr8f5nxqx3aw29w4ru5u9my3h0sfygnzsl8t0dj", # index 2
1780+
"bcrt1py0vryk8aqusz65yzuudypggvswzkcpwtau8q0sjm0stctwup0xlqv86kkk", # index 3
1781+
"bcrt1pjpp8nwqvhkx6kdna6vpujdqglvz2304twfd308ve5ppyxpmcjufsy8x0tk", # index 4
1782+
"bcrt1pl4frjws098l3nslfjlnry6jxt46w694kuexvs5ar0cmkvxyahfkq42fumu", # index 5
1783+
"bcrt1p5sxs429uz2s2yn6tt98sf67qwytwvffae4dqnzracq586cu0t6zsn63pre", # index 6
1784+
"bcrt1pxsvy7ep2awd5x9lg90tgm4xre8wxcuj5cpgun8hmzwqnltqha8pqv84cl7", # index 7
1785+
"bcrt1ptk8pqtszta5pv5tymccfqkezf3f2q39765q4fj8zcr79np6wmj6qeek4z3", # index 8
1786+
"bcrt1p7pkeudt8wq7fc6nzj6yxkqmnrjg25fu4s9l777ca3w3qrxanjehq4tphz0", # index 9
1787+
"bcrt1pzhnqyfvxe08zl0d9e592t62pwvp3l2xwhau5a8dsfjcker6xmjuqppvxka", # index 10
1788+
]
1789+
1790+
# Generate the first 10 BIP86 addresses and verify they match expected values
1791+
for i in range(10):
1792+
addr_result = l1.rpc.newaddr('bip86')
1793+
assert addr_result['p2tr'] == expected_addrs[i]
1794+
1795+
# Use listaddresses with start and limit parameters to verify the addresses were generated
1796+
addrs = l1.rpc.listaddresses(start=1, limit=10)
1797+
assert len(addrs['addresses']) == 10, f"Expected 10 addresses, got {len(addrs['addresses'])}"
1798+
1799+
# Verify that listaddresses returns the correct addresses and key indices
1800+
for i, addr_info in enumerate(addrs['addresses']):
1801+
assert addr_info['keyidx'] == i + 1, f"Expected keyidx {i+1}, got {addr_info['keyidx']}"
1802+
# BIP86 addresses should have a p2tr field with the correct address
1803+
assert 'p2tr' in addr_info, f"BIP86 address should have p2tr field, got: {addr_info}"
1804+
assert addr_info['p2tr'] == expected_addrs[i], f"Address mismatch at index {i+1}: expected {expected_addrs[i]}, got {addr_info['p2tr']}"
1805+
# BIP86 addresses should NOT have a bech32 field (they're P2TR only)
1806+
assert 'bech32' not in addr_info, f"BIP86 address should not have bech32 field, got: {addr_info}"
1807+
1808+
1809+
@unittest.skipIf(TEST_NETWORK != 'regtest', "BIP86 tests are regtest-specific")
1810+
def test_bip86_deterministic_addresses(node_factory):
1811+
"""Test that BIP86 addresses are deterministic and unique"""
1812+
# Create two nodes with the same mnemonic
1813+
mnemonic = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"
1814+
1815+
l1 = setup_bip86_node(node_factory, mnemonic)
1816+
l2 = setup_bip86_node(node_factory, mnemonic)
1817+
1818+
# Generate addresses with the same index
1819+
addr1_0 = l1.rpc.newaddr('bip86')['p2tr']
1820+
addr2_0 = l2.rpc.newaddr('bip86')['p2tr']
1821+
1822+
addr1_1 = l1.rpc.newaddr('bip86')['p2tr']
1823+
addr2_1 = l2.rpc.newaddr('bip86')['p2tr']
1824+
1825+
# Addresses should be identical for the same index
1826+
assert addr1_0 == addr2_0, f"Addresses for index 0 don't match: {addr1_0} != {addr2_0}"
1827+
assert addr1_1 == addr2_1, f"Addresses for index 1 don't match: {addr1_1} != {addr2_1}"
1828+
1829+
# Addresses should be different for different indices
1830+
assert addr1_0 != addr1_1, f"Addresses for different indices should be different"
1831+
1832+
1833+
@unittest.skipIf(TEST_NETWORK != 'regtest', "BIP86 tests are regtest-specific")
1834+
def test_bip86_vs_regular_p2tr(node_factory):
1835+
"""Test that BIP86 addresses are different from regular P2TR addresses"""
1836+
l1 = setup_bip86_node(node_factory)
1837+
1838+
# Generate addresses of both types
1839+
bip86_addr = l1.rpc.newaddr('bip86')['p2tr']
1840+
p2tr_addr = l1.rpc.newaddr('p2tr')['p2tr']
1841+
1842+
# They should be different
1843+
assert bip86_addr != p2tr_addr, "BIP86 and regular P2TR addresses should be different"
1844+
1845+
# Both should be valid Taproot addresses (start with bcrt1p for regtest)
1846+
assert bip86_addr.startswith('bcrt1p')
1847+
assert p2tr_addr.startswith('bcrt1p')
1848+
1849+
1850+
@unittest.skipIf(TEST_NETWORK != 'regtest', "BIP86 tests are regtest-specific")
1851+
def test_bip86_full_bitcoin_integration(node_factory, bitcoind):
1852+
"""Test full Bitcoin integration: generate addresses, receive funds, list outputs"""
1853+
l1 = setup_bip86_node(node_factory)
1854+
1855+
# Generate a BIP86 address
1856+
bip86_addr = l1.rpc.newaddr('bip86')['p2tr']
1857+
1858+
# Send funds to the BIP86 address
1859+
amount = 1000000 # 0.01 BTC
1860+
txid = bitcoind.rpc.sendtoaddress(bip86_addr, amount / 10**8)
1861+
1862+
# Mine a block to confirm the transaction
1863+
bitcoind.generate_block(1)
1864+
1865+
# Wait for the node to see the transaction
1866+
wait_for(lambda: len(l1.rpc.listfunds()['outputs']) > 0)
1867+
1868+
# Check that the funds are visible
1869+
funds = l1.rpc.listfunds()
1870+
bip86_outputs = [out for out in funds['outputs'] if out['address'] == bip86_addr]
1871+
1872+
assert len(bip86_outputs) == 1, f"Expected 1 output, got {len(bip86_outputs)}"
1873+
assert bip86_outputs[0]['amount_msat'] == amount * 1000, f"Amount mismatch: {bip86_outputs[0]['amount_msat']} != {amount * 1000}"
1874+
assert bip86_outputs[0]['status'] == 'confirmed'
1875+
1876+
# Test withdrawal from BIP86 address
1877+
# Use bitcoind to generate withdrawal address since this node only supports BIP86
1878+
withdraw_addr = bitcoind.rpc.getnewaddress()
1879+
withdraw_amount = 500000 # 0.005 BTC
1880+
1881+
l1.rpc.withdraw(withdraw_addr, withdraw_amount)
1882+
1883+
# Mine another block
1884+
bitcoind.generate_block(1)
1885+
1886+
# Check that the withdrawal worked
1887+
wait_for(lambda: len([out for out in l1.rpc.listfunds()['outputs'] if out['address'] == bip86_addr and out['status'] == 'confirmed']) == 0)
1888+
1889+
1890+
@unittest.skipIf(TEST_NETWORK != 'regtest', "BIP86 tests are regtest-specific")
1891+
def test_bip86_mnemonic_recovery(node_factory, bitcoind):
1892+
"""Test that funds can be recovered using the same mnemonic in a new wallet"""
1893+
# Use a known mnemonic for predictable recovery
1894+
mnemonic = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"
1895+
1896+
# Create first node and fund it
1897+
l1 = setup_bip86_node(node_factory, mnemonic)
1898+
bip86_addr = l1.rpc.newaddr('bip86')['p2tr']
1899+
1900+
# Send funds
1901+
amount = 1000000 # 0.01 BTC
1902+
txid = bitcoind.rpc.sendtoaddress(bip86_addr, amount / 10**8)
1903+
bitcoind.generate_block(1)
1904+
1905+
# Wait for funds to be visible
1906+
wait_for(lambda: len(l1.rpc.listfunds()['outputs']) > 0)
1907+
1908+
# Create a second node with the same mnemonic
1909+
l2 = setup_bip86_node(node_factory, mnemonic)
1910+
1911+
# Wait for it to sync and see the funds
1912+
wait_for(lambda: len(l2.rpc.listfunds()['outputs']) > 0)
1913+
1914+
# Check that the second node can see the same funds
1915+
funds2 = l2.rpc.listfunds()
1916+
bip86_outputs2 = [out for out in funds2['outputs'] if out['address'] == bip86_addr]
1917+
1918+
assert len(bip86_outputs2) == 1, f"Expected 1 output in recovered wallet, got {len(bip86_outputs2)}"
1919+
assert bip86_outputs2[0]['amount_msat'] == amount * 1000, f"Amount mismatch in recovered wallet: {bip86_outputs2[0]['amount_msat']} != {amount * 1000}"
1920+
1921+
1922+
@unittest.skipIf(TEST_NETWORK != 'regtest', "BIP86 tests are regtest-specific")
1923+
def test_bip86_address_type_validation(node_factory):
1924+
"""Test address type validation for BIP86 addresses"""
1925+
l1 = setup_bip86_node(node_factory)
1926+
1927+
# Test that 'bip86' is a valid address type
1928+
bip86_addr = l1.rpc.newaddr('bip86')['p2tr']
1929+
1930+
# Test that we can list addresses
1931+
addrs = l1.rpc.listaddresses()
1932+
assert len(addrs['addresses']) >= 1, "No addresses found in listaddresses"
1933+
1934+
# Verify the address structure
1935+
for addr in addrs['addresses']:
1936+
assert 'keyidx' in addr
1937+
assert isinstance(addr['keyidx'], int)
16991938

17001939

17011940
# this test does a 'listtransactions' on a yet unconfirmed channel
@@ -1933,6 +2172,47 @@ def test_p2tr_deposit_withdrawal(node_factory, bitcoind):
19332172
# make sure tap derivation is embedded in PSBT output
19342173

19352174

2175+
@unittest.skipIf(TEST_NETWORK != 'regtest', "Elements-based schnorr is not yet supported")
2176+
def test_p2tr_deposit_withdrawal_with_bip86(node_factory, bitcoind):
2177+
"""Test P2TR deposit and withdrawal with BIP86 addresses included"""
2178+
2179+
# Don't get any funds from previous runs.
2180+
l1 = node_factory.get_node(random_hsm=True)
2181+
2182+
# Can fetch p2tr addresses through 'all' or specifically, including BIP86
2183+
deposit_addrs = [l1.rpc.newaddr('all')] * 3
2184+
withdrawal_addr = l1.rpc.newaddr('p2tr')
2185+
2186+
# Add some funds to withdraw (including BIP86 addresses)
2187+
for addr_type in ['p2tr', 'bech32', 'p2tr-mnemonic']:
2188+
for i in range(3):
2189+
if addr_type in deposit_addrs[i]:
2190+
l1.bitcoin.rpc.sendtoaddress(deposit_addrs[i][addr_type], 1)
2191+
2192+
bitcoind.generate_block(1)
2193+
2194+
# Wait for funds to be visible (should be more than 6 now due to BIP86)
2195+
wait_for(lambda: len(l1.rpc.listfunds()['outputs']) >= 6)
2196+
2197+
# Check that we have funds
2198+
funds = l1.rpc.listfunds()
2199+
assert len(funds['outputs']) >= 6, f"Expected at least 6 outputs, got {len(funds['outputs'])}"
2200+
2201+
l1.rpc.withdraw(withdrawal_addr['p2tr'], 100000000 * 5)
2202+
wait_for(lambda: len(bitcoind.rpc.getrawmempool()) == 1)
2203+
raw_tx = bitcoind.rpc.getrawtransaction(bitcoind.rpc.getrawmempool()[0], 1)
2204+
assert len(raw_tx['vin']) >= 6 # Should be at least 6 inputs (including BIP86)
2205+
assert len(raw_tx['vout']) == 2
2206+
# Change goes to p2tr
2207+
for output in raw_tx['vout']:
2208+
assert output["scriptPubKey"]["type"] == "witness_v1_taproot"
2209+
bitcoind.generate_block(1)
2210+
wait_for(lambda: len(l1.rpc.listtransactions()['transactions']) >= 7)
2211+
2212+
# Only self-send + change is left
2213+
wait_for(lambda: len(l1.rpc.listfunds()['outputs']) == 2)
2214+
2215+
19362216
@unittest.skipIf(TEST_NETWORK != 'regtest', "Address is network specific")
19372217
def test_upgradewallet(node_factory, bitcoind):
19382218
# Make sure bitcoind doesn't think it's going backwards

0 commit comments

Comments
 (0)