test(abstract-utxo): Pearl transaction flow and live-node cross-validation - #9367
Draft
manojkumar138 wants to merge 2 commits into
Draft
test(abstract-utxo): Pearl transaction flow and live-node cross-validation#9367manojkumar138 wants to merge 2 commits into
manojkumar138 wants to merge 2 commits into
Conversation
Cover the full offline fixed-script lifecycle for Pearl: address
generation, PSBT build, sign, verify, finalize, extract, serialize
round-trip and parse. 27 cases.
Pearl has no @bitgo/utxo-lib network, so none of the utxo-lib based test
helpers apply - AcidTest, testutil.constructPsbt and the shared utxoCoins
fixtures all resolve a network. The PSBT is built directly with
fixedScriptWallet.BitGoPsbt instead.
All three taptree leaves are exercised. Note the canonical signer order
is user+bitgo, user+backup and backup+bitgo: wasm-utxo rejects the
reversed forms with "Could not find control block for leaf script", so
the third leaf is backup+bitgo rather than bitgo+backup as the TDD writes
it.
verifySignature rather than the return of sign() is used to assert
signing succeeded. sign() reports the inputs it touched, and it returns
the input index even for a key that is not part of the chosen leaf, so it
cannot distinguish a usable signature from a discarded one.
Two scope limits, both asserted rather than left implicit:
- Broadcasting needs a live pearld node or Pearl in the BitGo test
environment, neither of which exists yet (CECHO-1802). The flow is
verified as far as a finalized transaction with a real txid.
- p2trMusig2 key-path signing needs MuSig2 nonce rounds and is a later
phase per the TDD. Those chains are asserted to be buildable and to
fail finalization without nonces.
TICKET: CECHO-1806
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Contributor
manojkumar138
marked this pull request as draft
July 28, 2026 07:04
Add sandboxVectors.ts, checking our library against output from a real pearld 1.0.2 regtest node (BitGo/coins-sandbox#898) rather than against itself. transactionFlow.ts proves the PSBT lifecycle is internally consistent but cannot prove a node would accept the bytes, since its inputs reference txids that never existed. Genuinely externally verified: - deserializing the node's raw coinbase transaction and computing the node's own txid, 272e5b8a...d295, plus a byte-exact round-trip - encoding the node's real p2tr scriptPubKeys to addresses: the bech32m data part matches the node exactly (HRP and checksum differ by construction, since the node ran regtest and wasm-utxo has no Pearl regtest CoinName), and decoding restores the node's scriptPubKey - the witness shape the node accepted: 4 stack items, 64-byte BIP-340 signatures with no sighash byte, leaf version 0xc0 Control block size is 1 + 32 + 32*depth, so it reveals each leaf's position. The measured depths confirm the taptree the TDD specifies: user+bitgo shallow at depth 1, user+backup and backup+bitgo at depth 2. The node's 65-byte sample was a user+bitgo spend, which is why it saw depth 1. KNOWN DIVERGENCE, asserted rather than left implicit: the sandbox spends used a NUMS internal key and 70-byte leaf scripts. BitGo's p2tr chain derives an internal key from the wallet keys and produces 68-byte leaves. This is BitGo's house construction rather than anything Pearl-specific - the same input built for btc yields a byte-identical witness - but it means the sandbox's on-chain-verified addresses and signatures describe a different script than the SDK generates, so they cannot be compared. Three tests pin the divergence so it cannot drift unnoticed. Consequence worth flagging: BitGo's actual Pearl taptree has not yet been accepted by a pearld node. Closing that needs a regtest broadcast of an SDK-built transaction. TICKET: CECHO-1806 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Test coverage for the Pearl (Duplex) fixed-script lifecycle — 42 tests across two files:
transactionFlow.tssandboxVectors.tsThe split is deliberate. The first file builds transactions whose inputs reference txids that never existed, so it can only prove our libraries agree with themselves. The second checks against output captured from a live
pearld1.0.2 regtest node during sandboxing (coins-sandbox#898).Scope — what is and isn't covered
sha256txids, not real UTXOs)Single-input/single-output only; no dust boundaries, multi-recipient, or RBF.
What is genuinely node-verified
Transaction deserialization. Given the node's raw coinbase hex, our library computes the node's own txid
272e5b8a…d295and round-trips byte-exactly. The node published both the bytes and the txid, so agreement is external.Address encoding. For the node's real p2tr scriptPubKeys, our bech32m data part matches the node exactly — e.g.
efxxurpnufae39uq0gcquq3msh27rhdau2rjh38mf9ndlhflk9js. Only HRP and checksum differ, as they must: the node ran regtest (rprl) and wasm-utxo has no Pearl regtestCoinName. Decoding restores the node's scriptPubKey exactly.Witness shape. 4 stack items, 64-byte BIP-340 signatures with no sighash byte, leaf version
0xc0— the shape the node accepted.Taptree depth. Control block size is
1 + 32 + 32*depth, so it reveals each leaf's position. The measured depths confirm the TDD's taptree:user+bitgoshallow at depth 1,user+backupandbackup+bitgoat depth 2. The node's 65-byte sample was auser+bitgospend, which is why it saw depth 1.The sandbox spends set the BIP-341 NUMS point as the internal key and produced 70-byte leaf scripts. BitGo's
p2trchain instead derives an internal key from the wallet keys and produces 68-byte leaves.This is BitGo's house construction, not a Pearl bug — building the same input for
btcyields a byte-identical witness, and the derived internal key is identical across both coins. But it has two consequences worth attention:Three tests pin this so it cannot drift unnoticed, including one that fails if BitGo ever moves to a NUMS internal key (at which point the sandbox vectors become comparable).
Worth noting the TDD lists "NUMS internal key" as a chain parameter, which is misleading — it was a property of the sandbox's hand-rolled script. Anything else derived from that section may inherit the same assumption.
Three assumptions the tests corrected
The TDD's third taptree leaf is written backwards. It says
[bitgo+backup]; wasm-utxo only acceptsbackup+bitgoand rejects the reverse withCould not find control block for leaf script.sign()'s return value proves nothing. It returns the input index even for a key outside the chosen leaf.verifySignatureis the source of truth; finalization enforces the leaf.One test was vacuous until mutation testing caught it. "Rejects non-taproot chains" passed even with chain 30 substituted in, because chain 30 also throws without a
signPath— a different error. It now supplies asignPathand matches/Unsupported script type/.Mutation testing
I mutated each suite rather than trusting a green run. All caught:
user+backupVerification
abstract-utxo: 1594 passing (1552 → +42), zero failures. Prettier and eslint clean.Next step to close the remaining gap
A regtest broadcast of an SDK-built transaction — that is the only thing that proves BitGo's Pearl taptree is consensus-valid. Blocked by the TDD's own risks: no public
pearldDocker image, and ZK-PoW CGo crashes on Apple Silicon.Related
TICKET: CECHO-1806
🤖 Generated with Claude Code