fix: implement BIP341 key tweaking for P2TR transaction signing
This commit fixes P2TR (Pay-to-Taproot) transaction signing by properly implementing BIP341 key path spending. Key changes: - Add SignSchnorrTaproot() method to CKey for BIP341 tweaked signing - Implement ComputeTapTweak() and CreatePayToTaprootPubKey() in XOnlyPubKey - Add GetTaprootInternalKey() to SigningProvider interface for internal key lookup - Store taproot internal key mappings in LegacyScriptPubKeyMan - Fix FindTaprootPubKey() to use internal key mapping with fallback - Use empty scriptCode for Taproot key-path spending (per BIP341 spec) - Update HaveTaprootKey() to verify tweaked keys correctly Technical details: - Internal keys are tweaked using secp256k1_keypair_xonly_tweak_add - Parity handling is automatic via secp256k1 library - Empty scriptCode ensures correct sighash for key-path spending - Internal key to output key mapping stored for efficient lookup Testing: - P2TR address creation, funding, and spending work end-to-end - Multi-hop P2TR transactions tested successfully - All functional tests pass (feature_taproot.py, wallet_*, rpc_*) Fixes: non-mandatory-script-verify-flag error on P2TR spending
This commit is contained in:
66
test/functional/feature_taproot.py
Executable file
66
test/functional/feature_taproot.py
Executable file
@@ -0,0 +1,66 @@
|
||||
#!/usr/bin/env python3
|
||||
from test_framework.test_framework import PalladiumTestFramework
|
||||
from test_framework.util import assert_equal
|
||||
|
||||
class TaprootReproTest(PalladiumTestFramework):
|
||||
def set_test_params(self):
|
||||
self.num_nodes = 1
|
||||
self.setup_clean_chain = True
|
||||
self.extra_args = [[]] # standard args
|
||||
|
||||
def skip_test_if_missing_module(self):
|
||||
self.skip_if_no_wallet()
|
||||
|
||||
def run_test(self):
|
||||
node = self.nodes[0]
|
||||
|
||||
self.log.info("Create wallet")
|
||||
node.createwallet("tr_test")
|
||||
wallet = node.get_wallet_rpc("tr_test")
|
||||
|
||||
self.log.info("Generate blocks to get coins")
|
||||
mining_addr = wallet.getnewaddress()
|
||||
node.generatetoaddress(125, mining_addr)
|
||||
|
||||
self.log.info(f"Wallet Info: {wallet.getwalletinfo()}")
|
||||
self.log.info(f"Unspent: {wallet.listunspent()}")
|
||||
self.log.info(f"Address Info: {wallet.getaddressinfo(mining_addr)}")
|
||||
|
||||
balance_start = wallet.getbalance()
|
||||
self.log.info(f"Balance: {balance_start}")
|
||||
|
||||
self.log.info("Get new P2TR address (bech32m)")
|
||||
tr_addr = wallet.getnewaddress("", "bech32m")
|
||||
self.log.info(f"P2TR Address: {tr_addr}")
|
||||
|
||||
self.log.info("Send funds TO P2TR address")
|
||||
txid_to = wallet.sendtoaddress(tr_addr, 1.0)
|
||||
self.log.info(f"Sent to P2TR, txid: {txid_to}")
|
||||
|
||||
node.generatetoaddress(1, mining_addr)
|
||||
|
||||
# Check that the wallet sees the funds
|
||||
unspent = wallet.listunspent(0, 999999, [tr_addr])
|
||||
assert_equal(len(unspent), 1)
|
||||
assert_equal(unspent[0]['amount'], 1.0)
|
||||
self.log.info("Funds confirmed in P2TR address")
|
||||
|
||||
self.log.info("Attempt to spend FROM P2TR address")
|
||||
dest_addr = wallet.getnewaddress("", "bech32")
|
||||
|
||||
try:
|
||||
txid_from = wallet.sendtoaddress(dest_addr, 0.5)
|
||||
self.log.info(f"Spent from P2TR, txid: {txid_from}")
|
||||
|
||||
node.generatetoaddress(1, mining_addr)
|
||||
|
||||
# Verify transaction is confirmed
|
||||
tx = wallet.gettransaction(txid_from)
|
||||
assert_equal(tx['confirmations'], 1)
|
||||
self.log.info("P2TR spend confirmed success!")
|
||||
except Exception as e:
|
||||
self.log.error(f"Failed to spend from P2TR: {e}")
|
||||
raise
|
||||
|
||||
if __name__ == '__main__':
|
||||
TaprootReproTest().main()
|
||||
0
test/functional/rpc_createmultisig.py
Normal file → Executable file
0
test/functional/rpc_createmultisig.py
Normal file → Executable file
0
test/functional/rpc_psbt.py
Normal file → Executable file
0
test/functional/rpc_psbt.py
Normal file → Executable file
0
test/functional/test_runner.py
Normal file → Executable file
0
test/functional/test_runner.py
Normal file → Executable file
0
test/functional/wallet_address_types.py
Normal file → Executable file
0
test/functional/wallet_address_types.py
Normal file → Executable file
0
test/functional/wallet_basic.py
Normal file → Executable file
0
test/functional/wallet_basic.py
Normal file → Executable file
Reference in New Issue
Block a user