Repository navigation
fix(abstract-utxo): check the descriptor policy spend rules when a descriptor wallet signs - #9991
Open
OttoAllmendinger wants to merge 1 commit into
Open
OttoAllmendinger wants to merge 1 commit into
OttoAllmendinger wants to merge 1 commit into
Conversation
Contributor
OttoAllmendinger
force-pushed
the
otto/SI-1971-pox5-hot-descriptor-spend-policy
branch
from
October 16, 2026 13:00
6466f0d to
2ecba1b
Compare
1 task done
OttoAllmendinger
force-pushed
the
otto/SI-1971-pox5-hot-descriptor-spend-policy
branch
from
October 16, 2026 13:17
2ecba1b to
6466f0d
Compare
OttoAllmendinger
added this pull request to stack #9993
October 16, 2026 13:18
Contributor
|
|
OttoAllmendinger
force-pushed
the
otto/SI-1971-pox5-hot-descriptor-spend-policy
branch
from
October 16, 2026 13:23
6466f0d to
67d999b
Compare
1 task done
Base automatically changed from
otto/SI-1971-pox5-offlinevault-early-exit
to
master
October 19, 2026 08:16
…scriptor wallet signs Hot descriptor wallets sign through coin.signTransaction, which only checked the descriptor policy and that each input has a known descriptor. Nothing verified how a PoX-5 lockup was spent before the SDK user key signed it, for late and early exits alike. Give DescriptorValidationPolicy an optional validateSpend(psbt, descriptorMap) that throws for spends of the descriptors it covers that must not be signed, and forward it through the policy combinators. The PoX-5 lockup validator implements it with the PoX-5 spend policy the offline vault applies before half-signing: exactly one lockup input; for a late exit nLockTime at or above the unlock height and a non-final sequence; for an early exit nLockTime 0 and the principal preimage the descriptor commits to. signTransaction makes one generic call and does not know about PoX-5. Outside of prod the policy still accepts any descriptor, but spends are checked in every environment. Move the policy from offlineVault/descriptor to descriptor/pox5SpendPolicy so the descriptor policy can use it. Refs: SI-1971
OttoAllmendinger
force-pushed
the
otto/SI-1971-pox5-hot-descriptor-spend-policy
branch
from
October 19, 2026 08:17
67d999b to
9098c43
Compare
OttoAllmendinger
marked this pull request as ready for review
October 19, 2026 08:17
This branch has not been deployed
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.
Description
Follow-up to #9987 (stacked on it; this PR targets its branch and should be retargeted to
masteronce #9987 merges).#9987 makes the offline vault PoX-5 spend policy branch-aware, but it only covers the offline vault. A hot descriptor wallet signs through
coin.signTransaction(transaction/signTransaction.ts), which only validated the descriptor policy and that each input matches a known descriptor. Nothing checked nLockTime, sequence, the principal preimage or the single-lockup-input rule before the SDK user key signed a PoX-5 exit, for late and early exits alike.The check hangs off the descriptor policy instead of being called from the generic sign path:
DescriptorValidationPolicygets an optionalvalidateSpend(psbt, descriptorMap): it throws when a spend of a descriptor the policy covers must not be signed, and does nothing for inputs it does not recognize. The policy combinators (every,some,eachOneOf) forward it to the policies they are made of.assertPox5SpendPolicy: exactly one lockup input; late exit: nLockTime at or above the unlock height and below 500,000,000, non-final sequence; early exit: nLockTime0and a singlePSBT_IN_SHA256record matching the descriptorstakerCommitment.signTransactionmakes one generic call,policy.validateSpend?.(psbt, descriptorMap), and does not know about PoX-5. PoX-5 is named only where it already was (the lockup validators invalidatePolicy.ts).getPolicyForEnvstill accepts any descriptor, but it keeps the prod policy'svalidateSpend, so spends are checked in every environment.PSBTs without a PoX-5 lockup input are unaffected. To let the descriptor policy use it,
offlineVault/descriptor/pox5.tsmoves todescriptor/pox5SpendPolicy.ts(no behavior change; the offline vault imports it from there). The offline vault still callsassertPox5SpendPolicydirectly; moving it onto the policy hook is left for a follow-up.Issue Number
Refs: SI-1971
Type of change
Behavior change to be aware of
A hot descriptor wallet now refuses to sign a PoX-5 lockup spend that violates the policy, where it signed it before. Wallet Platform builds late exits with nLockTime at the unlock height and a non-final sequence, and early exits with nLockTime 0 and the preimage, so correctly built exits are unaffected. This has not been run against the live matrix.
How Has This Been Tested?
modules/abstract-utxo:test/unit/transaction/descriptor/signTransactionPox5.tsthroughcoin.signTransactionwith a descriptor wallet: late exit signs at the unlock height and is rejected below it or with a final sequence; early exit signs with nLockTime 0 and keeps the preimage, and is rejected with a non-zero nLockTime or a preimage the descriptor does not commit to. The policy ofprod,adminProd,testandmockall check the spend. Without thevalidateSpendcall the rejection tests fail.npx mocha --recursive test/unit/transaction test/unit/signTransaction.ts test/unit/offlineVault: 446 passing.Early exit on the hot path is still blocked at the HSM, which rejects early-exit-key partial signatures (SI-2002), and that is not addressed here.