Skip to content

feat(wasm-utxo): implement MuSig2 with BitGo-specific p2tr variant#6

Merged
OttoAllmendinger merged 7 commits into
masterfrom
BTC-2652.add-bitgo-musig2
Oct 22, 2025
Merged

feat(wasm-utxo): implement MuSig2 with BitGo-specific p2tr variant#6
OttoAllmendinger merged 7 commits into
masterfrom
BTC-2652.add-bitgo-musig2

Conversation

@OttoAllmendinger

@OttoAllmendinger OttoAllmendinger commented Oct 21, 2025

Copy link
Copy Markdown
Contributor

This PR adds part of the MuSig2 multi-signature protocol implementation with support
for BitGo's legacy p2tr aggregation method.

This enables the generation of the legacy p2tr and p2trMusig2 script types in subsequent steps.

The implementation includes:

  • BIP-327 reference implementation for MuSig2
  • BitGo-specific variant of MuSig2 key aggregation using x-only pubkeys
  • Support for both standard and BitGo's legacy p2tr behavior
  • Comprehensive test cases to verify functionality

Additional improvements:

  • Re-export bitcoin from miniscript for easier transition
  • Add development dependencies (serde, serde_json, hex) for better testing
  • Update .vscode in gitignore

Note that this does not include any signing code yet.

Issue: BTC-2652

OttoAllmendinger and others added 3 commits October 21, 2025 15:13
BTC-2652

Co-authored-by: llm-git <llm-git@ttll.de>
Added serde, serde_json, and hex crates to dev-dependencies to enhance
test capabilities in the wasm-utxo package.

Co-authored-by: llm-git <llm-git@ttll.de>

Issue: BTC-2652
Re-export bitcoin from miniscript to support transition to a more
general bitcoin package. This provides easier access to the bitcoin
module.

Issue: BTC-2652

Co-authored-by: llm-git <llm-git@ttll.de>
@OttoAllmendinger OttoAllmendinger force-pushed the BTC-2652.add-bitgo-musig2 branch from eb1bfa5 to 4f8dbde Compare October 21, 2025 13:15
OttoAllmendinger and others added 3 commits October 21, 2025 15:32
Add the BIP-327 reference implementation for MuSig2 multi-signature protocol
including Python code and test vectors.

Issue: BTC-2652

1: https://github.com/bitcoin/bips/blob/ab9d5b8/bip-0327/reference.py

Co-authored-by: llm-git <llm-git@ttll.de>
This adds a modified version of BIP-0327 reference implementation to
support BitGo's legacy p2tr aggregation method. The implementation adds:

1. Support for x-only pubkeys in key aggregation
2. Legacy p2tr aggregation function that converts to x-only format before
   sorting
3. Enhanced sign and verify functions to handle both pubkey formats
4. Comprehensive test cases to verify both standard MuSig2 (p2trMusig2)
   and legacy p2tr behavior

Issue: BTC-2652

Co-authored-by: llm-git <llm-git@ttll.de>
Add implementation of BitGo's non-standard variant of MuSig2 key aggregation
that uses x-only (32-byte) pubkeys in the hash computation. This differs from
the standard BIP327 implementation and is required for compatibility with
existing BitGo wallets.

BTC-2652

Co-authored-by: llm-git <llm-git@ttll.de>
@OttoAllmendinger OttoAllmendinger force-pushed the BTC-2652.add-bitgo-musig2 branch from 4f8dbde to 663d0d9 Compare October 21, 2025 13:32
@OttoAllmendinger OttoAllmendinger marked this pull request as ready for review October 21, 2025 13:40
@OttoAllmendinger OttoAllmendinger requested a review from a team as a code owner October 21, 2025 13:40
ppongbitgo
ppongbitgo previously approved these changes Oct 22, 2025
To satisfy the config of the `infra/` repo mostly. It's not a great name.

Co-authored-by: llm-git <llm-git@ttll.de>
@OttoAllmendinger OttoAllmendinger merged commit 3f3646e into master Oct 22, 2025
1 check passed
@OttoAllmendinger OttoAllmendinger deleted the BTC-2652.add-bitgo-musig2 branch October 22, 2025 20:37
OttoAllmendinger added a commit that referenced this pull request Jun 29, 2026
Add a pure-JS prevTx-inclusion policy to @bitgo/wasm-utxo so all
callers share one source of truth for whether a PSBT input needs
non_witness_utxo (full prevTx) or can be signed from witness_utxo-only.

requiresPrevTx(coinName, txFormat, chain) returns false when
witness_utxo-only is sufficient:
- psbt-lite never includes non_witness_utxo;
- segwit/taproot inputs (non-p2sh chains) always use witness_utxo;
- value-committing coins commit the input amount into the sighash, so
  prevTx is pointless for signing p2sh inputs even when txFormat is
  "psbt":
  * Zcash (zec/tzec): ZIP-243 transparent sighash commits the amount.
    Including prevTx also crashes wasm-utxo, whose consensus::deserialize
    rejects Zcash overwintered transactions.
  * BCH family (bch/bcha/eCash, bsv, btg + testnets): replay-protected
    BIP-143 sighash (SIGHASH_FORKID, the default for the whole family)
    commits the 8-byte value as preimage item #6.

isValueCommittingCoin is backed by a mainnet Set {zec, bch, bcha, bsv,
btg} normalized via getMainnet (covers testnets). isZcashCoin is kept
(selects ZcashBitGoPsbt). isNonSegwitChain checks chain === 0 || 1
literally so the module does not trigger WASM initialization — the
predicate is cheap to evaluate without loading wasm-utxo.

Exported both namespaced (fixedScriptWallet.*) and as top-level named
exports (alongside CoinName/getMainnet).

Refs: T1-3654
OttoAllmendinger added a commit that referenced this pull request Jun 29, 2026
Add a pure-JS prevTx-inclusion policy to @bitgo/wasm-utxo so all
callers share one source of truth for whether a PSBT input needs
non_witness_utxo (full prevTx) or can be signed from witness_utxo-only.

requiresPrevTx(coinName, txFormat, chain) returns false when
witness_utxo-only is sufficient:
- psbt-lite never includes non_witness_utxo;
- segwit/taproot inputs (non-p2sh chains) always use witness_utxo;
- value-committing coins commit the input amount into the sighash, so
  prevTx is pointless for signing p2sh inputs even when txFormat is
  "psbt":
  * Zcash (zec/tzec): ZIP-243 transparent sighash commits the amount.
    Including prevTx also crashes wasm-utxo, whose consensus::deserialize
    rejects Zcash overwintered transactions.
  * BCH family (bch/bcha/eCash, bsv, btg + testnets): replay-protected
    BIP-143 sighash (SIGHASH_FORKID, the default for the whole family)
    commits the 8-byte value as preimage item #6.

isValueCommittingCoin is backed by a mainnet Set {zec, bch, bcha, bsv,
btg} normalized via getMainnet (covers testnets). isZcashCoin is kept
(selects ZcashBitGoPsbt). isNonSegwitChain checks chain === 0 || 1
literally so the module does not trigger WASM initialization — the
predicate is cheap to evaluate without loading wasm-utxo.

Exported both namespaced (fixedScriptWallet.*) and as top-level named
exports (alongside CoinName/getMainnet).

Refs: T1-3654
OttoAllmendinger added a commit that referenced this pull request Jun 29, 2026
Add a pure-JS prevTx-inclusion policy to @bitgo/wasm-utxo so all
callers share one source of truth for whether a p2sh PSBT input needs
non_witness_utxo (full prevTx) or can be signed from witness_utxo-only.

requiresPrevTxForP2sh(coinName) answers only the coin-level question
for an input the caller has already determined is p2sh (via
isNonSegwitChain) and whose tx format includes prevTx (e.g. "psbt",
not "psbt-lite"). It returns false for value-committing coins whose
sighash commits the input amount, making prevTx cryptographically
pointless for signing p2sh inputs:

- Zcash (zec/tzec): ZIP-243 transparent sighash commits the amount.
  Including prevTx also crashes wasm-utxo, whose consensus::deserialize
  rejects Zcash overwintered transactions.
- BCH family (bch/bcha/eCash, bsv, btg + testnets): replay-protected
  BIP-143 sighash (SIGHASH_FORKID, the default for the whole family)
  commits the 8-byte value as preimage item #6.

isValueCommittingCoin is backed by a mainnet Set {zec, bch, bcha, bsv,
btg} normalized via getMainnet (covers testnets). isNonSegwitChain
checks chain === 0 || 1 literally so the module does not trigger WASM
initialization — the predicates are cheap to evaluate without loading
wasm-utxo.

Exported both namespaced (fixedScriptWallet.*) and as top-level named
exports (alongside CoinName/getMainnet).

Refs: T1-3654
OttoAllmendinger added a commit that referenced this pull request Jun 29, 2026
Add a pure-JS prevTx-inclusion predicate to @bitgo/wasm-utxo so all
callers share one source of truth for whether a p2sh PSBT input needs
non_witness_utxo (full prevTx) or can be signed from witness_utxo-only.

requiresPrevTxForP2sh(coinName) answers only the coin-level question
for an input the caller has already determined is p2sh (non-segwit) and
whose tx format includes prevTx (e.g. "psbt", not "psbt-lite"). It
returns false for value-committing coins whose sighash commits the
input amount, making prevTx cryptographically pointless for signing
p2sh inputs:

- Zcash (zec/tzec): ZIP-243 transparent sighash commits the amount.
  Including prevTx also crashes wasm-utxo, whose consensus::deserialize
  rejects Zcash overwintered transactions.
- BCH family (bch/bcha/eCash, bsv, btg + testnets): replay-protected
  BIP-143 sighash (SIGHASH_FORKID, the default for the whole family)
  commits the 8-byte value as preimage item #6.

The value-committing mainnet set {zec, bch, bcha, bsv, btg} is
normalized via getMainnet (covers testnets). The module is pure JS — no
WASM initialization — so the predicate is cheap to evaluate without
loading wasm-utxo.

Exported both namespaced (fixedScriptWallet.requiresPrevTxForP2sh) and
as a top-level named export (alongside CoinName/getMainnet).

Refs: T1-3654
OttoAllmendinger added a commit that referenced this pull request Jun 30, 2026
Add a pure-JS prevTx-inclusion predicate to @bitgo/wasm-utxo so all
callers share one source of truth for whether a p2sh PSBT input needs
non_witness_utxo (full prevTx) or can be signed from witness_utxo-only.

requiresPrevTxForP2sh(coinName) answers only the coin-level question
for an input the caller has already determined is p2sh (non-segwit) and
whose tx format includes prevTx (e.g. "psbt", not "psbt-lite"). It
returns false for value-committing coins whose sighash commits the
input amount, making prevTx cryptographically pointless for signing
p2sh inputs:

- Zcash (zec/tzec): ZIP-243 transparent sighash commits the amount.
  Including prevTx also crashes wasm-utxo, whose consensus::deserialize
  rejects Zcash overwintered transactions.
- BCH family (bch/bcha/eCash, bsv, btg + testnets): replay-protected
  BIP-143 sighash (SIGHASH_FORKID, the default for the whole family)
  commits the 8-byte value as preimage item #6.

The value-committing mainnets {zec, bch, bcha, bsv, btg} are matched
via a switch on getMainnet(coinName), so testnets are covered. The
module is pure JS — no WASM initialization — so the predicate is cheap
to evaluate without loading wasm-utxo.

Exported both namespaced (fixedScriptWallet.requiresPrevTxForP2sh) and
as a top-level named export (alongside CoinName/getMainnet).

Refs: T1-3654
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants