Skip to content

Commit e892cf4

Browse files
committed
Merge branch 'master' into rel/latest-WP-6380-resolve-conflict-2
# Conflicts: # modules/utxo-staking/package.json
2 parents 988f57b + 8361510 commit e892cf4

File tree

68 files changed

+7387
-640
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

68 files changed

+7387
-640
lines changed

CODEOWNERS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@
6060
/modules/sdk-coin-baby/ @BitGo/ethalt-team
6161
/modules/sdk-coin-bera/ @BitGo/ethalt-team
6262
/modules/sdk-coin-bsc/ @BitGo/ethalt-team
63+
/module/sdk-coin-canton/ @BitGo/ethalt-team
6364
/modules/sdk-coin-coredao/ @BitGo/ethalt-team
6465
/modules/sdk-coin-cosmos/ @BitGo/ethalt-team
6566
/modules/sdk-coin-cronos/ @BitGo/ethalt-team

modules/abstract-utxo/src/abstractUtxoCoin.ts

Lines changed: 0 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -920,35 +920,6 @@ export abstract class AbstractUtxoCoin extends BaseCoin {
920920
return isReplayProtectionUnspent<TNumber>(unspent, this.network);
921921
}
922922

923-
/**
924-
* @deprecated - use utxolib.bitgo.getDefaultSigHash(network) instead
925-
* @returns {number}
926-
*/
927-
get defaultSigHashType(): number {
928-
return utxolib.bitgo.getDefaultSigHash(this.network);
929-
}
930-
931-
/**
932-
* @deprecated - use utxolib.bitcoin.verifySignature() instead
933-
*/
934-
verifySignature(
935-
transaction: any,
936-
inputIndex: number,
937-
amount: number,
938-
verificationSettings: {
939-
signatureIndex?: number;
940-
publicKey?: string;
941-
} = {}
942-
): boolean {
943-
if (transaction.network !== this.network) {
944-
throw new Error(`network mismatch`);
945-
}
946-
return utxolib.bitgo.verifySignature(transaction, inputIndex, amount, {
947-
signatureIndex: verificationSettings.signatureIndex,
948-
publicKey: verificationSettings.publicKey ? Buffer.from(verificationSettings.publicKey, 'hex') : undefined,
949-
});
950-
}
951-
952923
/**
953924
* Decompose a raw psbt/transaction into useful information, such as the total amounts,
954925
* change amounts, and transaction outputs.

modules/babylonlabs-io-btc-staking-ts/.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -207,4 +207,4 @@ $RECYCLE.BIN/
207207
*.swp
208208
*.swo
209209

210-
build/
210+
build/

modules/babylonlabs-io-btc-staking-ts/jest.setup.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
const { initBTCCurve } = require("./src");
22

33
const originalTest = global.test;
4-
const NUM_ITERATIONS = 3;
5-
;
4+
const NUM_ITERATIONS = parseInt(process.env.TEST_REPEAT_TIMES) || 1;
65

76
initBTCCurve();
87

modules/babylonlabs-io-btc-staking-ts/package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@bitgo/babylonlabs-io-btc-staking-ts",
3-
"version": "2.4.1",
3+
"version": "3.0.0",
44
"description": "Library exposing methods for the creation and consumption of Bitcoin transactions pertaining to Babylon's Bitcoin Staking protocol.",
55
"module": "dist/index.js",
66
"main": "dist/index.cjs",
@@ -27,7 +27,7 @@
2727
"btc-staking"
2828
],
2929
"engines": {
30-
"node": ">=20 < 23"
30+
"node": ">=18 < 23"
3131
},
3232
"author": "Babylon Labs Ltd.",
3333
"license": "SEE LICENSE IN LICENSE",
@@ -37,7 +37,7 @@
3737
"nanoevents": "^9.1.0"
3838
},
3939
"dependencies": {
40-
"@babylonlabs-io/babylon-proto-ts": "1.0.0",
40+
"@babylonlabs-io/babylon-proto-ts": "1.7.2",
4141
"@bitcoin-js/tiny-secp256k1-asmjs": "2.2.3",
4242
"@cosmjs/encoding": "^0.33.0",
4343
"bip174": "=2.1.1",

modules/babylonlabs-io-btc-staking-ts/src/constants/fee.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,12 @@
22
export const DEFAULT_INPUT_SIZE = 180;
33
// Estimated size of a P2WPKH input in bytes
44
export const P2WPKH_INPUT_SIZE = 68;
5-
// Estimated size of a P2TR input in bytes
5+
// Estimated size of a P2TR input in bytes. 42vb inputs + 16vb witness
66
export const P2TR_INPUT_SIZE = 58;
7+
// Estimated size of a P2TR input in bytes for staking expansion transactions.
8+
// This value accounts for the witness size including covenant signatures
9+
// and is calibrated for a typical covenant quorum of 6 signatures.
10+
export const P2TR_STAKING_EXPANSION_INPUT_SIZE = 268;
711
// Estimated size of a transaction buffer in bytes
812
export const TX_BUFFER_SIZE_OVERHEAD = 11;
913
// Buffer for estimation accuracy when fee rate <= 2 sat/byte
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
export const BABYLON_REGISTRY_TYPE_URLS = {
22
MsgCreateBTCDelegation: "/babylon.btcstaking.v1.MsgCreateBTCDelegation",
3+
MsgBtcStakeExpand: "/babylon.btcstaking.v1.MsgBtcStakeExpand",
34
};
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
/**
2+
* Staking module address for the Babylon Genesis chain.
3+
* This address is derived deterministically from the module name and is the same across all environments.
4+
*/
5+
export const STAKING_MODULE_ADDRESS = "bbn13837feaxn8t0zvwcjwhw7lhpgdcx4s36eqteah";

modules/babylonlabs-io-btc-staking-ts/src/staking/index.ts

Lines changed: 173 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,11 @@ import {
1313
deriveStakingOutputInfo,
1414
findMatchingTxOutputIndex,
1515
toBuffers,
16-
validateParams,
17-
validateStakingTimelock,
18-
validateStakingTxInputData,
1916
} from "../utils/staking";
20-
import { stakingPsbt, unbondingPsbt } from "./psbt";
17+
import { stakingExpansionPsbt, stakingPsbt, unbondingPsbt } from "./psbt";
2118
import { StakingScriptData, StakingScripts } from "./stakingScript";
2219
import {
20+
stakingExpansionTransaction,
2321
slashEarlyUnbondedTransaction,
2422
slashTimelockUnbondedTransaction,
2523
stakingTransaction,
@@ -28,6 +26,7 @@ import {
2826
withdrawSlashingTransaction,
2927
withdrawTimelockUnbondedTransaction,
3028
} from "./transactions";
29+
import { validateParams, validateStakingExpansionCovenantQuorum, validateStakingTimelock, validateStakingTxInputData } from "../utils/staking/validation";
3130
export * from "./stakingScript";
3231

3332
export interface StakerInfo {
@@ -171,6 +170,106 @@ export class Staking {
171170
}
172171
}
173172

173+
/**
174+
* Creates a staking expansion transaction that extends an existing BTC stake
175+
* to new finality providers or renews the timelock.
176+
*
177+
* This method implements RFC 037 BTC Stake Expansion,
178+
* allowing existing active BTC staking transactions
179+
* to extend their delegation to new finality providers without going through
180+
* the full unbonding process.
181+
*
182+
* The expansion transaction:
183+
* 1. Spends the previous staking transaction output as the first input
184+
* 2. Uses funding UTXO as additional input to cover transaction fees or
185+
* to increase the staking amount
186+
* 3. Creates a new staking output with expanded finality provider coverage or
187+
* renews the timelock
188+
* 4. Has an output returning the remaining funds as change (if any) to the
189+
* staker BTC address
190+
*
191+
* @param {number} stakingAmountSat - The total staking amount in satoshis
192+
* (The amount had to be equal to the previous staking amount for now, this
193+
* lib does not yet support increasing the staking amount at this stage)
194+
* @param {UTXO[]} inputUTXOs - Available UTXOs to use for funding the
195+
* expansion transaction fees. Only one will be selected for the expansion
196+
* @param {number} feeRate - Fee rate in satoshis per byte for the
197+
* expansion transaction
198+
* @param {StakingParams} paramsForPreviousStakingTx - Staking parameters
199+
* used in the previous staking transaction
200+
* @param {Object} previousStakingTxInfo - Necessary information to spend the
201+
* previous staking transaction.
202+
* @returns {TransactionResult & { fundingUTXO: UTXO }} - An object containing
203+
* the unsigned expansion transaction and calculated fee, and the funding UTXO
204+
* @throws {StakingError} - If the transaction cannot be built or validation
205+
* fails
206+
*/
207+
public createStakingExpansionTransaction(
208+
stakingAmountSat: number,
209+
inputUTXOs: UTXO[],
210+
feeRate: number,
211+
paramsForPreviousStakingTx: StakingParams,
212+
previousStakingTxInfo: {
213+
stakingTx: Transaction,
214+
stakingInput: {
215+
finalityProviderPksNoCoordHex: string[],
216+
stakingTimelock: number,
217+
},
218+
},
219+
): TransactionResult & {
220+
fundingUTXO: UTXO;
221+
} {
222+
validateStakingTxInputData(
223+
stakingAmountSat,
224+
this.stakingTimelock,
225+
this.params,
226+
inputUTXOs,
227+
feeRate,
228+
);
229+
validateStakingExpansionCovenantQuorum(
230+
paramsForPreviousStakingTx,
231+
this.params,
232+
);
233+
234+
// Create a Staking instance for the previous staking transaction
235+
// This allows us to build the scripts needed to spend the previous
236+
// staking output
237+
const previousStaking = new Staking(
238+
this.network,
239+
this.stakerInfo,
240+
paramsForPreviousStakingTx,
241+
previousStakingTxInfo.stakingInput.finalityProviderPksNoCoordHex,
242+
previousStakingTxInfo.stakingInput.stakingTimelock,
243+
);
244+
245+
// Build the expansion transaction using the stakingExpansionTransaction
246+
// utility function.
247+
// This creates a transaction that spends the previous staking output and
248+
// creates new staking outputs
249+
const {
250+
transaction: stakingExpansionTx,
251+
fee: stakingExpansionTxFee,
252+
fundingUTXO,
253+
} = stakingExpansionTransaction(
254+
this.network,
255+
this.buildScripts(),
256+
stakingAmountSat,
257+
this.stakerInfo.address,
258+
feeRate,
259+
inputUTXOs,
260+
{
261+
stakingTx: previousStakingTxInfo.stakingTx,
262+
scripts: previousStaking.buildScripts(),
263+
},
264+
)
265+
266+
return {
267+
transaction: stakingExpansionTx,
268+
fee: stakingExpansionTxFee,
269+
fundingUTXO,
270+
};
271+
}
272+
174273
/**
175274
* Create a staking psbt based on the existing staking transaction.
176275
*
@@ -200,6 +299,76 @@ export class Staking {
200299
);
201300
}
202301

302+
/**
303+
* Convert a staking expansion transaction to a PSBT.
304+
*
305+
* @param {Transaction} stakingExpansionTx - The staking expansion
306+
* transaction to convert
307+
* @param {UTXO[]} inputUTXOs - Available UTXOs for the
308+
* funding input (second input)
309+
* @param {StakingParams} paramsForPreviousStakingTx - Staking parameters
310+
* used for the previous staking transaction
311+
* @param {Object} previousStakingTxInfo - Information about the previous
312+
* staking transaction
313+
* @returns {Psbt} The PSBT for the staking expansion transaction
314+
* @throws {Error} If the previous staking output cannot be found or
315+
* validation fails
316+
*/
317+
public toStakingExpansionPsbt(
318+
stakingExpansionTx: Transaction,
319+
inputUTXOs: UTXO[],
320+
paramsForPreviousStakingTx: StakingParams,
321+
previousStakingTxInfo: {
322+
stakingTx: Transaction,
323+
stakingInput: {
324+
finalityProviderPksNoCoordHex: string[],
325+
stakingTimelock: number,
326+
},
327+
},
328+
): Psbt {
329+
// Reconstruct the previous staking instance to access its scripts and
330+
// parameters. This is necessary because we need to identify which output
331+
// in the previous staking transaction is the staking output (it could be
332+
// at any output index)
333+
const previousStaking = new Staking(
334+
this.network,
335+
this.stakerInfo,
336+
paramsForPreviousStakingTx,
337+
previousStakingTxInfo.stakingInput.finalityProviderPksNoCoordHex,
338+
previousStakingTxInfo.stakingInput.stakingTimelock,
339+
);
340+
341+
// Find the staking output address in the previous staking transaction
342+
const previousScripts = previousStaking.buildScripts();
343+
const { outputAddress } = deriveStakingOutputInfo(previousScripts, this.network);
344+
345+
// Find the output index in the previous staking transaction that matches
346+
// the staking output address.
347+
const previousStakingOutputIndex = findMatchingTxOutputIndex(
348+
previousStakingTxInfo.stakingTx,
349+
outputAddress,
350+
this.network,
351+
);
352+
353+
// Create and return the PSBT for the staking expansion transaction
354+
// The PSBT will have two inputs:
355+
// 1. The previous staking output
356+
// 2. A funding UTXO from inputUTXOs (for additional funds)
357+
return stakingExpansionPsbt(
358+
this.network,
359+
stakingExpansionTx,
360+
{
361+
stakingTx: previousStakingTxInfo.stakingTx,
362+
outputIndex: previousStakingOutputIndex,
363+
},
364+
inputUTXOs,
365+
previousScripts,
366+
isTaproot(this.stakerInfo.address, this.network)
367+
? Buffer.from(this.stakerInfo.publicKeyNoCoordHex, "hex")
368+
: undefined,
369+
);
370+
}
371+
203372
/**
204373
* Create an unbonding transaction for staking.
205374
*

0 commit comments

Comments
 (0)