diff --git a/modules/utxo-staking/src/pox5/recovery.ts b/modules/utxo-staking/src/pox5/recovery.ts index 7f19547ac6..bed1957431 100644 --- a/modules/utxo-staking/src/pox5/recovery.ts +++ b/modules/utxo-staking/src/pox5/recovery.ts @@ -58,6 +58,17 @@ export function assertPox5EarlyExitSpend(psbt: Psbt, input: pox5.Pox5InputMatch) getMatchedTransactionInput(psbt, input); } +export type Pox5SpendBranch = 'locktime' | 'early-exit'; + +/** Classify a PoX-5 spend from native principal-preimage metadata. */ +export function classifyPox5Spend(psbt: Psbt, input: pox5.Pox5InputMatch): Pox5SpendBranch { + getMatchedTransactionInput(psbt, input); + const hasPrincipalPreimage = psbt + .getInputKeyValues(input.inputIndex) + .some((record) => record.type === 'known' && record.key === 'PSBT_IN_SHA256'); + return hasPrincipalPreimage ? 'early-exit' : 'locktime'; +} + /** Add validated principal-preimage metadata for an early-exit spend. */ export function preparePox5EarlyExit( psbt: Psbt, diff --git a/modules/utxo-staking/test/unit/pox5/recovery.ts b/modules/utxo-staking/test/unit/pox5/recovery.ts index 5e4cf9ec58..4570da2786 100644 --- a/modules/utxo-staking/test/unit/pox5/recovery.ts +++ b/modules/utxo-staking/test/unit/pox5/recovery.ts @@ -8,6 +8,7 @@ import { getKey, getKeyTriple } from '@bitgo/wasm-utxo/testutils'; import { assertPox5EarlyExitSpend, assertPox5LocktimeSpend, + classifyPox5Spend, POX5_MAX_UNLOCK_HEIGHT, preparePox5EarlyExit, } from '../../../src/pox5'; @@ -60,6 +61,15 @@ describe('PoX-5 spend policy', function () { assert.doesNotThrow(() => assertPox5EarlyExitSpend(timestampLocktime.psbt, timestampLocktime.match)); }); + it('classifies the spend from native principal-preimage metadata', function () { + const locktimeSpend = createPox5RecoveryPsbt(UNLOCK_HEIGHT); + assert.equal(classifyPox5Spend(locktimeSpend.psbt, locktimeSpend.match), 'locktime'); + + const earlyExitSpend = createPox5RecoveryPsbt(0); + preparePox5EarlyExit(earlyExitSpend.psbt, 0, earlyExitSpend.match, earlyExitSpend.principalPreimage); + assert.equal(classifyPox5Spend(earlyExitSpend.psbt, earlyExitSpend.match), 'early-exit'); + }); + it('enforces the block-height and unlock-height boundaries', function () { const atHeight = createPox5RecoveryPsbt(UNLOCK_HEIGHT); const aboveHeight = createPox5RecoveryPsbt(UNLOCK_HEIGHT + 1);