Skip to content

fix(abstract-utxo): explain and half-sign PoX-5 late exits in offlineVault - #9965

Closed
ralph-bitgo[bot] wants to merge 1 commit into
WAL-2156-pox5-offlinevault-late-exit-pt1from
WAL-2156-pox5-offlinevault-late-exit-pt2
Closed

ralph-bitgo[bot] wants to merge 1 commit into
WAL-2156-pox5-offlinevault-late-exit-pt1from
WAL-2156-pox5-offlinevault-late-exit-pt2

Conversation

@ralph-bitgo

@ralph-bitgo ralph-bitgo Bot commented Oct 14, 2026 •

Copy link
Copy Markdown

Description

Summary: make the @bitgo/abstract-utxo offline vault explain and half-sign custodial PoX-5 late exits (post-CLTV 2-of-3 spend of a lockup output), and report the PSBT locktime in the descriptor transaction explanation.

Motivation and context: unstaking is a regular descriptor-wallet transaction in Wallet Platform, so the awaiting-signature export the Offline Vault Console receives is a descriptor transaction (PSBT + named wallet descriptors). With the policy fix from part 1 in place, the OVC still cannot verify a late exit: nothing enforces the PoX-5 CLTV spend rules before co-signing, and the explanation carries no locktime, so the review screen cannot show the nLockTime the operator must verify against the unlock height. This blocks custodial unstake validation (WAL-2156; WAL-2144 matrix, SI-1191 unstake rollout).

What changed:

  • Wire getValidatorPox5Lockup into the offline vault descriptor transaction policy (getDescriptorsFromDescriptorTransaction).
  • Before half-signing a PoX-5 lockup input, apply the BitGoJS staking policy (WAL-2022): post-CLTV branch only (classifyPox5Spend), nLockTime >= unlock height and below 500,000,000, non-final sequence on the lockup input (assertPox5LocktimeSpend), exactly one lockup input. Early exits (principal preimage branch) stay out of scope (WAL-2145) and are rejected.
  • Include locktime in the TransactionExplanation of a descriptor transaction (previously only outputs, changeOutputs and fee).
  • Map the Stacks PoX coin names to their wasm coins in toWasmUtxoCoinName (tbtcstx/tbtcstxprivate1 → tbtcreg, tbtcstxsignet → tbtcsig) so explain works on tbtcstxsignet; the now-redundant wasmName overrides on the coin classes are removed.
  • New dependency: @bitgo/utxo-staking (the WAL-2022 staking policy).

Issue Number

WAL-2156

Type of change

  • Bug fix (non-breaking change which fixes an issue)

How Has This Been Tested?

  • @bitgo/abstract-utxo unit tests: mocha --recursive test/unit/ — 1682 passing across this stack (1660 on part 1).
  • New OfflineVault PoX-5 late exit unit tests over both btc and tbtcstxsignet: explain returns outputs/change/fee and the locktime; half-sign produces a valid partial signature on the lockup input. Negatives: foreign staker keys, malformed and non-canonical lockup descriptors (explain and half-sign are rejected before any signature); nLockTime below the unlock height and timestamp locktime (explained with locktime shown, refused for half-signing); final sequence on the lockup input; early exit (principal preimage committed); two lockup inputs.
  • OfflineVault descriptor transaction regressions: Wsh2Of3 and Wsh2Of3CltvDrop descriptor transactions still explain and half-sign; the existing Wsh2Of3.buildAdmin.custodial fixture still signs (its explanation fixture is extended with locktime).

Checklist:

  • My code follows the style guidelines of this project
  • I have performed a self-review of my own code
  • My code compiles correctly for both Node and Browser environments
  • I have commented my code, particularly in hard-to-understand areas
  • My commits follow Conventional Commits and I have properly described any BREAKING CHANGES
  • The ticket or github issue was included in the commit message as a reference
  • I have made corresponding changes to the documentation and on any new/updated functions and/or methods - jsdoc
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes

Stack

This PR is part 2 of 2 in a stack. Review and merge in order:

  1. fix(abstract-utxo): add PoX-5 lockup validator to descriptor policy #9964 — descriptor policy: PoX-5 lockup validator (base: master)
  2. fix(abstract-utxo): explain and half-sign PoX-5 late exits in offlineVault #9965 — offlineVault: explain + half-sign PoX-5 late exits (base: WAL-2156-pox5-offlinevault-late-exit-pt1) ← you are here

Custodial PoX-5 late exits (post-CLTV 2-of-3 spends of a lockup
output) could not be explained or co-signed by the Offline Vault
Console: the descriptor transaction export fails policy validation
and no policy guarded the CLTV spend itself. This blocks custodial
unstake validation (WAL-2144 matrix, SI-1191 unstake rollout).

- wire the PoX-5 lockup validator into the offline vault descriptor
  transaction policy alongside the wallet's regular descriptors.
- enforce the BitGoJS staking policy (WAL-2022) before half-signing a
  PoX-5 lockup input: post-CLTV branch only, nLockTime >= unlock
  height and below 500000000, non-final sequence on the lockup input,
  exactly one lockup input. Early exits remain out of scope
  (WAL-2145).
- include locktime in the descriptor transaction explanation so the
  OVC review screen can show the nLockTime an operator must verify
  before co-signing a CLTV spend.
- map the Stacks PoX coin names to their wasm coins so explain works
  on tbtcstxsignet; the now-redundant wasmName overrides are removed.
- add a @bitgo/utxo-staking dependency for the staking policy and
  unit tests for explain and half-sign over btc and tbtcstxsignet,
  policy and staking negatives, and Wsh2Of3/Wsh2Of3CltvDrop
  regressions.

Ticket: WAL-2156
Session-Id: 0b46844d-1e64-4dae-af4a-42bb86eedda7
Task-Id: c6ecab00-ae0e-49dc-a2c0-a08cfb1522ec
@linear-code

linear-code Bot commented Oct 14, 2026

Copy link
Copy Markdown
Contributor

WAL-2156

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.

2 participants