Skip to content

Commit 5869f53

Browse files
OttoAllmendingerllm-git
andcommitted
feat(utxo-staking): extract key details from parsed staking descriptors
Extract key information from descriptor nodes including staker key, finality provider keys, covenant keys, and timelock values. This allows for better programmatic access to descriptor components. Issue: BTC-2319 Co-authored-by: llm-git <[email protected]>
1 parent 8f82377 commit 5869f53

File tree

1 file changed

+47
-3
lines changed

1 file changed

+47
-3
lines changed

modules/utxo-staking/src/babylon/parseDescriptor.ts

Lines changed: 47 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,31 @@ import { PatternMatcher, Pattern } from '@bitgo/utxo-core/descriptor';
33

44
import { getUnspendableKey } from './descriptor';
55

6-
type ParsedStakingDescriptor = {
6+
export type ParsedStakingDescriptor = {
7+
stakerKey: Buffer;
8+
finalityProviderKeys: Buffer[];
9+
covenantKeys: Buffer[];
10+
covenantThreshold: number;
11+
stakingTimeLock: number;
712
slashingMiniscriptNode: ast.MiniscriptNode;
813
unbondingMiniscriptNode: ast.MiniscriptNode;
914
timelockMiniscriptNode: ast.MiniscriptNode;
1015
};
1116

17+
function parseMulti(multi: unknown): [number, string[]] {
18+
if (!Array.isArray(multi) || multi.length < 1) {
19+
throw new Error('Invalid multi structure: not an array or empty');
20+
}
21+
const [threshold, ...keys] = multi;
22+
if (typeof threshold !== 'number') {
23+
throw new Error('Invalid multi structure: threshold is not a number');
24+
}
25+
if (!keys.every((k) => typeof k === 'string')) {
26+
throw new Error('Invalid multi structure: not all keys are strings');
27+
}
28+
return [threshold, keys];
29+
}
30+
1231
/**
1332
* @return parsed staking descriptor components or null if the descriptor does not match the expected staking pattern.
1433
*/
@@ -59,7 +78,7 @@ export function parseStakingDescriptor(descriptor: Descriptor | ast.DescriptorNo
5978

6079
// Verify unbonding timelock node shape: and_v([pk, older])
6180
const timelockPattern: Pattern = {
62-
and_v: [{ 'v:pk': { $var: 'stakerKey3' } }, { older: { $var: 'unbondingTimeLockValue' } }],
81+
and_v: [{ 'v:pk': { $var: 'stakerKey3' } }, { older: { $var: 'stakingTimeLock' } }],
6382
};
6483

6584
const timelockMatch = matcher.match(timelockNode, timelockPattern);
@@ -76,11 +95,36 @@ export function parseStakingDescriptor(descriptor: Descriptor | ast.DescriptorNo
7695
}
7796

7897
// Verify timelock value is a number
79-
if (typeof timelockMatch.unbondingTimeLockValue !== 'number') {
98+
if (typeof timelockMatch.stakingTimeLock !== 'number') {
8099
throw new Error('Unbonding timelock value must be a number');
81100
}
82101

102+
const stakerKey = Buffer.from(slashingMatch.stakerKey1 as string, 'hex');
103+
const stakingTimeLock = timelockMatch.stakingTimeLock as number;
104+
105+
const [covenantThreshold, covenantKeyStrings] = parseMulti(slashingMatch.covenantMulti);
106+
const covenantKeys = covenantKeyStrings.map((k) => Buffer.from(k, 'hex'));
107+
108+
let finalityProviderKeys: Buffer[];
109+
const fpKeyOrMulti = slashingMatch.finalityProviderKeyOrMulti as ast.MiniscriptNode;
110+
if ('v:pk' in fpKeyOrMulti) {
111+
finalityProviderKeys = [Buffer.from(fpKeyOrMulti['v:pk'], 'hex')];
112+
} else if ('v:multi_a' in fpKeyOrMulti) {
113+
const [threshold, keyStrings] = parseMulti(fpKeyOrMulti['v:multi_a']);
114+
if (threshold !== 1) {
115+
throw new Error('Finality provider multi threshold must be 1');
116+
}
117+
finalityProviderKeys = keyStrings.map((k) => Buffer.from(k, 'hex'));
118+
} else {
119+
throw new Error('Invalid finality provider key structure');
120+
}
121+
83122
return {
123+
stakerKey,
124+
finalityProviderKeys,
125+
covenantKeys,
126+
covenantThreshold,
127+
stakingTimeLock,
84128
slashingMiniscriptNode: slashingNode,
85129
unbondingMiniscriptNode: unbondingNode,
86130
timelockMiniscriptNode: timelockNode,

0 commit comments

Comments
 (0)