BIP460: CISA for Taproot key path spends #36122

pull fjahr wants to merge 22 commits into bitcoin:master from fjahr:bip460 changing 111 files +11245 −219
  1. fjahr commented at 7:51 PM on August 29, 2026: contributor

    Reference implementation of BIP460 Cross-Input Signature Aggregation for Taproot key path spends. Implements witness v2 outputs whose key path spends can be aggregated with BIP458 half-aggregation or BIP459 full-aggregation, or opt out with a plain BIP341 signature.

    Draft to accompany the BIPs, not intended for merge:

    • BIP458: bitcoin/bips#2205
    • BIP459: bitcoin/bips#2210
    • BIP460: bitcoin/bips#2212

    Further notes:

    • Depends on the unmerged secp256k1 modules from bitcoin-core/secp256k1#1566 (halfagg) and bitcoin-core/secp256k1#1754 (fullagg). The updated subtree here adds both.
    • Activation is regtest-only and there are no parameters set for other chains
    • BIP460 test vectors are vendored in src/test/data/ and run in src/test/cisa_tests.cpp. Additional functional tests cover relay and block validation.
  2. DrahtBot commented at 7:51 PM on August 29, 2026: contributor

    <!--e57a25ab6845829454e8d69fc972939a-->

    The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.

    <!--006a51241073e994b41acfe9ec718e94-->

    Code Coverage & Benchmarks

    For details see: https://corecheck.dev/bitcoin/bitcoin/pulls/36122.

    <!--021abf342d371248e50ceaed478a90ca-->

    Reviews

    See the guideline and AI policy for information on the review process. A summary of reviews will appear here.

    <!--174a7506f384e20aa4161008e828411d-->

    Conflicts

    Reviewers, this pull request conflicts with the following ones:

    • #36382 (rpc: flag invalid taproot leaf control blocks in decodepsbt by fametrano)
    • #36224 (test: Add test coverage for PartiallySignedTransaction::Merge() by nebula-21)
    • #36167 ([RFC] Enable -Wunused by fanquake)
    • #36091 (test: Add debug output to common tested types by rustaceanrob)
    • #36039 (psbt: classify missing Taproot script-path signatures as signer by btcpavao)
    • #35793 (Implement BIP 54 (Consensus Cleanup) without mainnet activation by darosior)
    • #35747 (wallet: Fix FillPSBT failing to sign owned inputs when UTXOs disagree by nervana21)
    • #35662 (script: prevent reinitializing sighash data for another transaction by l0rinc)
    • #35569 (Encapsulation for CTransaction by purpleKarrot)
    • #35444 (wallet: make descriptor SPKM mutex non-recursive by w0xlt)
    • #35370 (rpc: add key-origin modes to PSBT processing RPCs by junbyjun1238)
    • #34520 (refactor: Add [[nodiscard]] to functions returning bool+mutable ref by maflcko)
    • #32857 (wallet: allow skipping script paths by Sjors)
    • #32575 (consensus: Remove special treatment for single threaded script checking by fjahr)
    • #30342 (kernel, logging: Deliver each context's log output to its own logging connection by ryanofsky)
    • #29843 (policy: Allow non-standard scripts with -acceptnonstdtxn=1 (test nets only) by ajtowns)
    • #29491 ([EXPERIMENTAL] Schnorr batch verification for blocks by fjahr)
    • #29247 (CAT in Tapscript (BIP-347) by arminsabouri)
    • #28690 (build: Introduce internal kernel library by sedited)

    If you consider this pull request important, please also help to review the conflicting pull requests. Ideally, start with the one that should be merged first.

    <!--5faf32d7da4f0f540f40219e4f7537a3-->

  3. DrahtBot added the label CI failed on Aug 29, 2026
  4. fjahr force-pushed on Sep 7, 2026
  5. fjahr force-pushed on Sep 7, 2026
  6. fjahr commented at 7:54 PM on September 7, 2026: contributor

    The latest commits, prefixed with draft-bip: go further than BIP460 itself. They implement two companion BIPs necessary for adoption that have not been reviewed and I don't think they are worth sharing on the mailing list yet either:

    They are included so that wallets and protocols, like payjoin or coinjoin for example, can build proofs of concept on top of this branch and exercise CISA end-to-end through the their already common descriptor, wallet and PSBT RPCs flows. Expect these commits to change as these drafts evolve. Review of the consensus and policy commits does not depend on them and I would prefer if review is focused on those unless you are building a PoC and depend on these later changes.

  7. fjahr force-pushed on Sep 7, 2026
  8. DrahtBot added the label Needs rebase on Sep 7, 2026
  9. DrahtBot removed the label CI failed on Sep 7, 2026
  10. fjahr force-pushed on Sep 8, 2026
  11. fjahr force-pushed on Sep 8, 2026
  12. DrahtBot added the label CI failed on Sep 8, 2026
  13. DrahtBot removed the label Needs rebase on Sep 8, 2026
  14. DrahtBot removed the label CI failed on Sep 8, 2026
  15. DrahtBot added the label Needs rebase on Sep 9, 2026
  16. BarneyChambers commented at 11:39 AM on September 15, 2026: none

    Hey Fabian, Hope you don't mind if I add a review here. I built this locally and ran feature_cisa.py and wallet_cisa.py from commit 224e15f. Both passed, and the consensus/policy test coverage looks great. I did hit three wallet/PSBT issues while reviewing the changes.

  17. in src/script/sign.cpp:824 in 224e15fd89 outdated
     818 | @@ -717,7 +819,10 @@ static bool SignStep(const SigningProvider& provider, const BaseSignatureCreator
     819 |          return false;
     820 |  
     821 |      case TxoutType::WITNESS_V1_TAPROOT:
     822 | -        return SignTaproot(provider, creator, WitnessV1Taproot(XOnlyPubKey{vSolutions[0]}), sigdata, ret);
     823 | +        return SignTaproot(provider, creator, XOnlyPubKey{vSolutions[0]}, /*cisa=*/false, sigdata, ret);
     824 | +
     825 | +    case TxoutType::WITNESS_V2_CISA:
    


    BarneyChambers commented at 11:42 AM on September 15, 2026:

    For a marked v2 keypath, VerifyScript returning success after ParseCISAWitness seems correct for a per-input structure check. ProduceSignature and DataFromTransaction then treat that success as complete, so a dummy member looks fully signed.

    I made a 2-input v2 spend and put 32 zero bytes on both witnesses (half-agg members, no final). signrawtransactionwithwallet came back complete=true with no errors. testmempoolaccept then failed with mempool-script-verify-flag-failed (Invalid CISA aggregation group structure). So CISACheck is doing its job. Anyone who only looks at VerifyScript / complete will think the input is done.

    Would it be reasonable to run VerifyCISATransaction on the full tx before setting complete when the parse yields a marker?


    fjahr commented at 11:04 AM on September 21, 2026:

    The per-input success is intentional so existing aggregation data is not overwritten by a following opted-out signature, but nothing ran the tx-level check afterwards. SignTransaction now runs VerifyCISATransaction once all inputs are processed and reports the error on the aggregated inputs, so complete should now be false for your example. I also added a test for this in wallet_cisa.py.

  18. in src/psbt.cpp:482 in 224e15fd89 outdated
     477 |      if (sequence == std::nullopt && input.sequence != std::nullopt) sequence = input.sequence;
     478 |      if (time_locktime == std::nullopt && input.time_locktime != std::nullopt) time_locktime = input.time_locktime;
     479 |      if (height_locktime == std::nullopt && input.height_locktime != std::nullopt) height_locktime = input.height_locktime;
     480 |  }
     481 |  
     482 | +bool PSBTInput::CISACompatible(const PSBTInput& input) const
    


    BarneyChambers commented at 11:46 AM on September 15, 2026:

    You already reject conflicting full-agg nonces and partial sigs. wallet_cisa.py covers that (PSBTs not compatible). Half-agg signatures do not get checked the same way. Merge keeps the first m_cisa_halfagg_sig it sees.

    I took two otherwise identical half-agg PSBTs and flipped one bit in input 0’s sig. combinepsbt([A, B]) succeeded and kept A. Swapping the order kept B.

    We can fix this issue if you treat m_cisa_halfagg_sig like the full-agg partial sig (both present and unequal means incompatible). I only mention it because the full-agg side already works


    fjahr commented at 11:04 AM on September 21, 2026:

    This is intended, halfagg has no signing session so two different signatures for the same input can both be valid and either one aggregates fine. It matches the behavior of BIP 174/371 for PSBT_IN_PARTIAL_SIG and PSBT_IN_TAP_KEY_SIG, which also keep the first. The flipped bit is an invalid signature rather than a conflict, and the Finalizer verifies every signature before aggregating, so it fails with complete=false instead of producing an invalid tx. I added an explicit test here.

  19. in src/psbt.cpp:844 in 224e15fd89 outdated
     840 | @@ -793,6 +841,77 @@ void RemoveUnnecessaryTransactions(PartiallySignedTransaction& psbtx)
     841 |      }
     842 |  }
     843 |  
     844 | +void FinalizeCISAInputs(PartiallySignedTransaction& psbt, const PrecomputedTransactionData& txdata)
    


    BarneyChambers commented at 11:50 AM on September 15, 2026:

    FinalizeCISAInputs skips any input that is already signed. This skip on already-signed inputs can leave you with two separate groups of the same mode. Consensus then fails the tx with SCRIPT_ERR_CISA_GROUP_INVALID, because once it has seen a group’s final it will not accept another member.

    The problem is basically when input 0 already has a final, and you then put input 1 into the same mode with a signature. The loop walks past input 0 and builds a brand new 1-input group on input 1.

    Working example: I signed a 2-input half-agg PSBT without finalizing, cleared input 1’s mode and sig, and ran finalizepsbt. That put a 65-byte final on input 0 and reported complete=false. I put input 1’s mode and sig back and finalized again. That time it reported complete=true, both witnesses were 65 bytes, and testmempoolaccept failed with a Invalid CISA aggregation group structure error.


    fjahr commented at 11:04 AM on September 21, 2026:

    Right, good catch. The finalizer should treat a group as closed once any input of that mode has a final witness and I have changed FinalizeCISAInputs to do that, so the remaining input stays unfinalized instead of forming a second group. I also updated the psbt bip draft with a small sentence to clarify this.

  20. Squashed 'src/secp256k1/' changes from a7f264373e5..cb3e42dd43d
    cb3e42dd43d Merge bitcoin-core/secp256k1#1566: BIP458: Schnorr (Incremental) Half Aggregation
    1af6f99027a fullagg: Add ctime tests
    45a859a8b1b fullagg: Add benchmarks
    613aeb691f5 fullagg: Add docs
    59e68180a6e fullagg: Add to build system
    e7435ba849f fullagg: Add to CI
    1e4959f8d0e fullagg: Add example
    d5276a22663 fullagg: Add include file
    d1bf3236f5b fullagg: Add module
    33619725026 halfagg: Add docs
    a206a10d8f5 halfagg: Add example
    13b4e8c78e3 halfagg: Add benchmarks
    345d9ff4caa Experimental: Add BIP 458 half-aggregation for Schnorr signatures
    46db787112b Merge bitcoin-core/secp256k1#1918: refactor: split `ge_parse` into explicit variants (compressed, uncompressed, uncompressed+hybrid)
    de02108c51c tests: cover compressed pubkey parsing/serialization in `_ec_pubkey_parse` test
    d01de271444 Merge bitcoin-core/secp256k1#1935: tests: generate scalars_near_split_bounds instead of hardcoding
    31b1b300e81 refactor: split `ge_parse` helper into explicit variants
    014825f0a30 tests: generate scalars_near_split_bounds instead of hardcoding
    99ae231231f Merge bitcoin-core/secp256k1#1893: test: cover schnorrsig_sign_custom in constant-time tests
    38255a943f0 test: cover custom Schnorr nonce callback in ctime tests
    2a3780d73ff test: cover schnorrsig_sign_custom in constant-time tests
    
    git-subtree-dir: src/secp256k1
    git-subtree-split: cb3e42dd43d77f70b9198f443682c284ff27eee1
    45e7e41c8b
  21. Merge commit '45e7e41c8b6728428f1bda6e173f049596a328cd' into bip460-rebased 2d90723809
  22. build: Enable secp halfagg and fullagg modules e4ecf50c52
  23. consensus: Add CISA deployment and script flag 7cda56d3a2
  24. script: Add witness v2 keypath signature message cbf0c3796d
  25. script: Add per-input witness v2 validation a505efc065
  26. script: Add transaction-level CISA verification 3d38ac632d
  27. validation: Add CISA check to the check queue 89ce95d910
  28. policy: Make witness v2 transactions standard a6bcd2d5f1
  29. test: Add CISA unit tests with BIP460 test vectors d24c191aa4
  30. test: Add CISA functional test 58481a2cf1
  31. draft-bip: descriptor: Add cisa() output script descriptor 02d87e699b
  32. fjahr force-pushed on Sep 21, 2026
  33. draft-bip: test: Add cisa() descriptor tests 94a70f205d
  34. draft-bip: cisa: Add half and full aggregation signing helpers 0a6bb78156
  35. draft-bip: psbt: Add CISA input fields e41865fd25
  36. draft-bip: sign: Sign witness v2 inputs by aggregation mode 2a5114ea4d
  37. draft-bip: psbt: Finalize CISA aggregation groups 188b8491b2
  38. draft-bip: rpc: Add cisa_mode option to PSBT signing RPCs a1e01eeaad
  39. draft-bip: test: Add CISA PSBT tests 14678b7312
  40. draft-bip: cisa: Decouple full aggregation nonces from the signed message f3f0218c04
  41. draft-bip: wallet: Add reservecisanonce for pre-shared full aggregation nonces 4e93d1a784
  42. draft-bip: test: Test full aggregation with reserved nonces 07addd3748
  43. fjahr force-pushed on Sep 21, 2026
  44. DrahtBot added the label CI failed on Sep 21, 2026
  45. DrahtBot commented at 11:25 AM on September 21, 2026: contributor

    <!--85328a0da195eb286784d51f73fa0af9-->

    🚧 At least one of the CI tasks failed. <sub>Task 32 bit ARM: https://github.com/bitcoin/bitcoin/actions/runs/35590888385/job/106304773630</sub> <sub>LLM reason (✨ experimental): CI failed during compilation because cisa_tests.cpp triggered -Werror=array-bounds (array subscript 64 out of bounds in std::vector/construction).</sub>

    <details><summary>Hints</summary>

    Try to run the tests locally, according to the documentation. However, a CI failure may still happen due to a number of reasons, for example:

    • Possibly due to a silent merge conflict (the changes in this pull request being incompatible with the current code in the target branch). If so, make sure to rebase on the latest commit of the target branch.

    • A sanitizer issue, which can only be found by compiling with the sanitizer and running the affected test.

    • An intermittent issue.

    Leave a comment here, if you need help tracking down a confusing failure.

    </details>

  46. DrahtBot removed the label Needs rebase on Sep 21, 2026

github-metadata-mirror

This is a metadata mirror of the GitHub repository bitcoin/bitcoin. This site is not affiliated with GitHub. Content is generated from a GitHub metadata backup.
generated: 2026-10-04 22:51 UTC

This site is hosted by @0xB10C
More mirrored repositories can be found on mirror.b10c.me