PHP WebShell
Текущая директория: /opt/BitGoJS/modules/babylonlabs-io-btc-staking-ts/src/staking
Просмотр файла: manager.ts
import { networks, Psbt, Transaction } from "bitcoinjs-lib";
import { StakingParams, VersionedStakingParams } from "../types/params";
import { TransactionResult, UTXO } from "../types";
import { StakerInfo, Staking } from ".";
import {
btccheckpoint,
btcstaking,
btcstakingtx,
} from "@babylonlabs-io/babylon-proto-ts";
import {
BIP322Sig,
BTCSigType,
ProofOfPossessionBTC,
} from "@babylonlabs-io/babylon-proto-ts/dist/generated/babylon/btcstaking/v1/pop";
import { BABYLON_REGISTRY_TYPE_URLS } from "../constants/registry";
import { createCovenantWitness } from "./transactions";
import { getBabylonParamByBtcHeight, getBabylonParamByVersion } from "../utils/staking/param";
import { reverseBuffer } from "../utils";
import { deriveStakingOutputInfo } from "../utils/staking";
import { findMatchingTxOutputIndex } from "../utils/staking";
import { isValidBabylonAddress } from "../utils/babylon";
import { StakingError } from "../error";
import { StakingErrorCode } from "../error";
import { isNativeSegwit, isTaproot } from "../utils/btc";
export interface BtcProvider {
// Sign a PSBT
// Expecting the PSBT to be encoded in hex format.
signPsbt(signingStep: SigningStep, psbtHex: string): Promise<string>;
// Signs a message using either ECDSA or BIP-322, depending on the address type.
// - Taproot and Native Segwit addresses will use BIP-322.
// - Legacy addresses will use ECDSA.
// Expecting the message to be encoded in base64 format.
signMessage: (
signingStep: SigningStep, message: string, type: "ecdsa" | "bip322-simple"
) => Promise<string>;
}
export interface BabylonProvider {
/**
* Signs a Babylon chain transaction using the provided signing step.
* This is primarily used for signing MsgCreateBTCDelegation transactions
* which register the BTC delegation on the Babylon Genesis chain.
*
* @param {SigningStep} signingStep - The current signing step context
* @param {object} msg - The Cosmos SDK transaction message to sign
* @param {string} msg.typeUrl - The Protobuf type URL identifying the message type
* @param {T} msg.value - The transaction message data matching the typeUrl
* @returns {Promise<Uint8Array>} The signed transaction bytes
*/
signTransaction: <T extends object>(
signingStep: SigningStep,
msg: {
typeUrl: string;
value: T;
}
) => Promise<Uint8Array>
}
// Event types for the Signing event
export enum SigningStep {
STAKING_SLASHING = "staking-slashing",
UNBONDING_SLASHING = "unbonding-slashing",
PROOF_OF_POSSESSION = "proof-of-possession",
CREATE_BTC_DELEGATION_MSG = "create-btc-delegation-msg",
STAKING = "staking",
UNBONDING = "unbonding",
WITHDRAW_STAKING_EXPIRED = "withdraw-staking-expired",
WITHDRAW_EARLY_UNBONDED = "withdraw-early-unbonded",
WITHDRAW_SLASHING = "withdraw-slashing",
}
interface StakingInputs {
finalityProviderPkNoCoordHex: string;
stakingAmountSat: number;
stakingTimelock: number;
}
// Inclusion proof for a BTC staking transaction that is included in a BTC block
// This is used for post-staking registration on the Babylon chain
// You can refer to https://electrumx.readthedocs.io/en/latest/protocol-methods.html#blockchain-transaction-get-merkle
// for more information on the inclusion proof format.
interface InclusionProof {
// The 0-based index of the position of the transaction in the ordered list
// of transactions in the block.
pos: number;
// A list of transaction hashes the current hash is paired with, recursively,
// in order to trace up to obtain merkle root of the block, deepest pairing first.
merkle: string[];
// The block hash of the block that contains the transaction
blockHashHex: string;
}
export class BabylonBtcStakingManager {
protected stakingParams: VersionedStakingParams[];
protected btcProvider: BtcProvider;
protected network: networks.Network;
protected babylonProvider: BabylonProvider;
constructor(
network: networks.Network,
stakingParams: VersionedStakingParams[],
btcProvider: BtcProvider,
babylonProvider: BabylonProvider,
) {
this.network = network;
this.btcProvider = btcProvider;
this.babylonProvider = babylonProvider;
if (stakingParams.length === 0) {
throw new Error("No staking parameters provided");
}
this.stakingParams = stakingParams;
}
/**
* Creates a signed Pre-Staking Registration transaction that is ready to be
* sent to the Babylon chain.
* @param stakerBtcInfo - The staker BTC info which includes the BTC address
* and the no-coord public key in hex format.
* @param stakingInput - The staking inputs.
* @param babylonBtcTipHeight - The Babylon BTC tip height.
* @param inputUTXOs - The UTXOs that will be used to pay for the staking
* transaction.
* @param feeRate - The fee rate in satoshis per byte. Typical value for the
* fee rate is above 1. If the fee rate is too low, the transaction will not
* be included in a block.
* @param babylonAddress - The Babylon bech32 encoded address of the staker.
* @returns The signed babylon pre-staking registration transaction in base64
* format.
*/
async preStakeRegistrationBabylonTransaction(
stakerBtcInfo: StakerInfo,
stakingInput: StakingInputs,
babylonBtcTipHeight: number,
inputUTXOs: UTXO[],
feeRate: number,
babylonAddress: string,
): Promise<{
signedBabylonTx: Uint8Array;
stakingTx: Transaction;
}> {
if (babylonBtcTipHeight === 0) {
throw new Error("Babylon BTC tip height cannot be 0");
}
if (inputUTXOs.length === 0) {
throw new Error("No input UTXOs provided");
}
if (!isValidBabylonAddress(babylonAddress)) {
throw new Error("Invalid Babylon address");
}
// Get the Babylon params based on the BTC tip height from Babylon chain
const params = getBabylonParamByBtcHeight(
babylonBtcTipHeight,
this.stakingParams,
);
const staking = new Staking(
this.network,
stakerBtcInfo,
params,
stakingInput.finalityProviderPkNoCoordHex,
stakingInput.stakingTimelock,
);
// Create unsigned staking transaction
const { transaction } = staking.createStakingTransaction(
stakingInput.stakingAmountSat,
inputUTXOs,
feeRate,
);
// Create delegation message without including inclusion proof
const msg = await this.createBtcDelegationMsg(
staking,
stakingInput,
transaction,
babylonAddress,
stakerBtcInfo,
params,
);
return {
signedBabylonTx: await this.babylonProvider.signTransaction(
SigningStep.CREATE_BTC_DELEGATION_MSG,
msg,
),
stakingTx: transaction,
};
}
/**
* Creates a signed post-staking registration transaction that is ready to be
* sent to the Babylon chain. This is used when a staking transaction is
* already created and included in a BTC block and we want to register it on
* the Babylon chain.
* @param stakerBtcInfo - The staker BTC info which includes the BTC address
* and the no-coord public key in hex format.
* @param stakingTx - The staking transaction.
* @param stakingTxHeight - The BTC height in which the staking transaction
* is included.
* @param stakingInput - The staking inputs.
* @param inclusionProof - Merkle Proof of Inclusion: Verifies transaction
* inclusion in a Bitcoin block that is k-deep.
* @param babylonAddress - The Babylon bech32 encoded address of the staker.
* @returns The signed babylon transaction in base64 format.
*/
async postStakeRegistrationBabylonTransaction(
stakerBtcInfo: StakerInfo,
stakingTx: Transaction,
stakingTxHeight: number,
stakingInput: StakingInputs,
inclusionProof: InclusionProof,
babylonAddress: string,
): Promise<{
signedBabylonTx: Uint8Array;
}> {
// Get the Babylon params at the time of the staking transaction
const params = getBabylonParamByBtcHeight(stakingTxHeight, this.stakingParams);
if (!isValidBabylonAddress(babylonAddress)) {
throw new Error("Invalid Babylon address");
}
const stakingInstance = new Staking(
this.network,
stakerBtcInfo,
params,
stakingInput.finalityProviderPkNoCoordHex,
stakingInput.stakingTimelock,
);
// Validate if the stakingTx is valid based on the retrieved Babylon param
const scripts = stakingInstance.buildScripts();
const stakingOutputInfo = deriveStakingOutputInfo(scripts, this.network);
// Error will be thrown if the expected staking output address is not found
// in the stakingTx
findMatchingTxOutputIndex(
stakingTx,
stakingOutputInfo.outputAddress,
this.network,
)
// Create delegation message
const delegationMsg = await this.createBtcDelegationMsg(
stakingInstance,
stakingInput,
stakingTx,
babylonAddress,
stakerBtcInfo,
params,
this.getInclusionProof(inclusionProof),
);
return {
signedBabylonTx: await this.babylonProvider.signTransaction(
SigningStep.CREATE_BTC_DELEGATION_MSG,
delegationMsg,
),
};
}
/**
* Estimates the BTC fee required for staking.
* @param stakerBtcInfo - The staker BTC info which includes the BTC address
* and the no-coord public key in hex format.
* @param babylonBtcTipHeight - The BTC tip height recorded on the Babylon
* chain.
* @param stakingInput - The staking inputs.
* @param inputUTXOs - The UTXOs that will be used to pay for the staking
* transaction.
* @param feeRate - The fee rate in satoshis per byte. Typical value for the
* fee rate is above 1. If the fee rate is too low, the transaction will not
* be included in a block.
* @returns The estimated BTC fee in satoshis.
*/
estimateBtcStakingFee(
stakerBtcInfo: StakerInfo,
babylonBtcTipHeight: number,
stakingInput: StakingInputs,
inputUTXOs: UTXO[],
feeRate: number,
): number {
if (babylonBtcTipHeight === 0) {
throw new Error("Babylon BTC tip height cannot be 0");
}
// Get the param based on the tip height
const params = getBabylonParamByBtcHeight(
babylonBtcTipHeight,
this.stakingParams,
);
const staking = new Staking(
this.network,
stakerBtcInfo,
params,
stakingInput.finalityProviderPkNoCoordHex,
stakingInput.stakingTimelock,
);
const { fee: stakingFee } = staking.createStakingTransaction(
stakingInput.stakingAmountSat,
inputUTXOs,
feeRate,
);
return stakingFee;
}
/**
* Creates a signed staking transaction that is ready to be sent to the BTC
* network.
* @param stakerBtcInfo - The staker BTC info which includes the BTC address
* and the no-coord public key in hex format.
* @param stakingInput - The staking inputs.
* @param unsignedStakingTx - The unsigned staking transaction.
* @param inputUTXOs - The UTXOs that will be used to pay for the staking
* transaction.
* @param stakingParamsVersion - The params version that was used to create the
* delegation in Babylon chain
* @returns The signed staking transaction.
*/
async createSignedBtcStakingTransaction(
stakerBtcInfo: StakerInfo,
stakingInput: StakingInputs,
unsignedStakingTx: Transaction,
inputUTXOs: UTXO[],
stakingParamsVersion: number,
): Promise<Transaction> {
const params = getBabylonParamByVersion(stakingParamsVersion, this.stakingParams);
if (inputUTXOs.length === 0) {
throw new Error("No input UTXOs provided");
}
const staking = new Staking(
this.network,
stakerBtcInfo,
params,
stakingInput.finalityProviderPkNoCoordHex,
stakingInput.stakingTimelock,
);
const stakingPsbt = staking.toStakingPsbt(
unsignedStakingTx,
inputUTXOs,
);
const signedStakingPsbtHex = await this.btcProvider.signPsbt(
SigningStep.STAKING,
stakingPsbt.toHex()
);
return Psbt.fromHex(signedStakingPsbtHex).extractTransaction();
}
/**
* Creates a partial signed unbonding transaction that is only signed by the
* staker. In order to complete the unbonding transaction, the covenant
* unbonding signatures need to be added to the transaction before sending it
* to the BTC network.
* NOTE: This method should only be used for Babylon phase-1 unbonding.
* @param stakerBtcInfo - The staker BTC info which includes the BTC address
* and the no-coord public key in hex format.
* @param stakingInput - The staking inputs.
* @param stakingParamsVersion - The params version that was used to create the
* delegation in Babylon chain
* @param stakingTx - The staking transaction.
* @returns The partial signed unbonding transaction and its fee.
*/
async createPartialSignedBtcUnbondingTransaction(
stakerBtcInfo: StakerInfo,
stakingInput: StakingInputs,
stakingParamsVersion: number,
stakingTx: Transaction,
): Promise<TransactionResult> {
// Get the staking params at the time of the staking transaction
const params = getBabylonParamByVersion(
stakingParamsVersion,
this.stakingParams,
);
const staking = new Staking(
this.network,
stakerBtcInfo,
params,
stakingInput.finalityProviderPkNoCoordHex,
stakingInput.stakingTimelock,
);
const {
transaction: unbondingTx,
fee,
} = staking.createUnbondingTransaction(stakingTx);
const psbt = staking.toUnbondingPsbt(unbondingTx, stakingTx);
const signedUnbondingPsbtHex = await this.btcProvider.signPsbt(
SigningStep.UNBONDING,
psbt.toHex(),
);
const signedUnbondingTx = Psbt.fromHex(
signedUnbondingPsbtHex,
).extractTransaction();
return {
transaction: signedUnbondingTx,
fee,
};
}
/**
* Creates a signed unbonding transaction that is ready to be sent to the BTC
* network.
* @param stakerBtcInfo - The staker BTC info which includes the BTC address
* and the no-coord public key in hex format.
* @param stakingInput - The staking inputs.
* @param stakingParamsVersion - The params version that was used to create the
* delegation in Babylon chain
* @param stakingTx - The staking transaction.
* @param unsignedUnbondingTx - The unsigned unbonding transaction.
* @param covenantUnbondingSignatures - The covenant unbonding signatures.
* It can be retrieved from the Babylon chain or API.
* @returns The signed unbonding transaction and its fee.
*/
async createSignedBtcUnbondingTransaction(
stakerBtcInfo: StakerInfo,
stakingInput: StakingInputs,
stakingParamsVersion: number,
stakingTx: Transaction,
unsignedUnbondingTx: Transaction,
covenantUnbondingSignatures: {
btcPkHex: string;
sigHex: string;
}[],
): Promise<TransactionResult> {
// Get the staking params at the time of the staking transaction
const params = getBabylonParamByVersion(
stakingParamsVersion,
this.stakingParams,
);
const {
transaction: signedUnbondingTx,
fee,
} = await this.createPartialSignedBtcUnbondingTransaction(
stakerBtcInfo,
stakingInput,
stakingParamsVersion,
stakingTx,
);
// Check the computed txid of the signed unbonding transaction is the same as
// the txid of the unsigned unbonding transaction
if (signedUnbondingTx.getId() !== unsignedUnbondingTx.getId()) {
throw new Error(
"Unbonding transaction hash does not match the computed hash",
);
}
// Add covenant unbonding signatures
// Convert the params of covenants to buffer
const covenantBuffers = params.covenantNoCoordPks.map((covenant) =>
Buffer.from(covenant, "hex"),
);
const witness = createCovenantWitness(
// Since unbonding transactions always have a single input and output,
// we expect exactly one signature in TaprootScriptSpendSig when the
// signing is successful
signedUnbondingTx.ins[0].witness,
covenantBuffers,
covenantUnbondingSignatures,
params.covenantQuorum,
);
// Overwrite the witness to include the covenant unbonding signatures
signedUnbondingTx.ins[0].witness = witness;
return {
transaction: signedUnbondingTx,
fee,
};
}
/**
* Creates a signed withdrawal transaction on the unbodning output expiry path
* that is ready to be sent to the BTC network.
* @param stakingInput - The staking inputs.
* @param stakingParamsVersion - The params version that was used to create the
* delegation in Babylon chain
* @param earlyUnbondingTx - The early unbonding transaction.
* @param feeRate - The fee rate in satoshis per byte. Typical value for the
* fee rate is above 1. If the fee rate is too low, the transaction will not
* be included in a block.
* @returns The signed withdrawal transaction and its fee.
*/
async createSignedBtcWithdrawEarlyUnbondedTransaction(
stakerBtcInfo: StakerInfo,
stakingInput: StakingInputs,
stakingParamsVersion: number,
earlyUnbondingTx: Transaction,
feeRate: number,
): Promise<TransactionResult> {
const params = getBabylonParamByVersion(
stakingParamsVersion,
this.stakingParams,
);
const staking = new Staking(
this.network,
stakerBtcInfo,
params,
stakingInput.finalityProviderPkNoCoordHex,
stakingInput.stakingTimelock,
);
const { psbt: unbondingPsbt, fee } = staking.createWithdrawEarlyUnbondedTransaction(
earlyUnbondingTx,
feeRate,
);
const signedWithdrawalPsbtHex = await this.btcProvider.signPsbt(
SigningStep.WITHDRAW_EARLY_UNBONDED,
unbondingPsbt.toHex()
);
return {
transaction: Psbt.fromHex(signedWithdrawalPsbtHex).extractTransaction(),
fee,
};
}
/**
* Creates a signed withdrawal transaction on the staking output expiry path
* that is ready to be sent to the BTC network.
* @param stakerBtcInfo - The staker BTC info which includes the BTC address
* and the no-coord public key in hex format.
* @param stakingInput - The staking inputs.
* @param stakingParamsVersion - The params version that was used to create the
* delegation in Babylon chain
* @param stakingTx - The staking transaction.
* @param feeRate - The fee rate in satoshis per byte. Typical value for the
* fee rate is above 1. If the fee rate is too low, the transaction will not
* be included in a block.
* @returns The signed withdrawal transaction and its fee.
*/
async createSignedBtcWithdrawStakingExpiredTransaction(
stakerBtcInfo: StakerInfo,
stakingInput: StakingInputs,
stakingParamsVersion: number,
stakingTx: Transaction,
feeRate: number,
): Promise<TransactionResult> {
const params = getBabylonParamByVersion(
stakingParamsVersion,
this.stakingParams,
);
const staking = new Staking(
this.network,
stakerBtcInfo,
params,
stakingInput.finalityProviderPkNoCoordHex,
stakingInput.stakingTimelock,
);
const { psbt, fee } = staking.createWithdrawStakingExpiredPsbt(
stakingTx,
feeRate,
);
const signedWithdrawalPsbtHex = await this.btcProvider.signPsbt(
SigningStep.WITHDRAW_STAKING_EXPIRED,
psbt.toHex()
);
return {
transaction: Psbt.fromHex(signedWithdrawalPsbtHex).extractTransaction(),
fee,
};
}
/**
* Creates a signed withdrawal transaction for the expired slashing output that
* is ready to be sent to the BTC network.
* @param stakerBtcInfo - The staker BTC info which includes the BTC address
* and the no-coord public key in hex format.
* @param stakingInput - The staking inputs.
* @param stakingParamsVersion - The params version that was used to create the
* delegation in Babylon chain
* @param slashingTx - The slashing transaction.
* @param feeRate - The fee rate in satoshis per byte. Typical value for the
* fee rate is above 1. If the fee rate is too low, the transaction will not
* be included in a block.
* @returns The signed withdrawal transaction and its fee.
*/
async createSignedBtcWithdrawSlashingTransaction(
stakerBtcInfo: StakerInfo,
stakingInput: StakingInputs,
stakingParamsVersion: number,
slashingTx: Transaction,
feeRate: number,
): Promise<TransactionResult> {
const params = getBabylonParamByVersion(
stakingParamsVersion,
this.stakingParams,
);
const staking = new Staking(
this.network,
stakerBtcInfo,
params,
stakingInput.finalityProviderPkNoCoordHex,
stakingInput.stakingTimelock,
);
const { psbt, fee } = staking.createWithdrawSlashingPsbt(
slashingTx,
feeRate,
);
const signedSlashingPsbtHex = await this.btcProvider.signPsbt(
SigningStep.WITHDRAW_SLASHING,
psbt.toHex()
);
return {
transaction: Psbt.fromHex(signedSlashingPsbtHex).extractTransaction(),
fee,
};
}
/**
* Creates a proof of possession for the staker based on ECDSA signature.
* @param bech32Address - The staker's bech32 address on the babylon chain
* @param stakerBtcAddress - The staker's BTC address.
* @returns The proof of possession.
*/
async createProofOfPossession(
bech32Address: string,
stakerBtcAddress: string,
): Promise<ProofOfPossessionBTC> {
let sigType: BTCSigType = BTCSigType.ECDSA;
// For Taproot or Native SegWit addresses, use the BIP322 signature scheme
// in the proof of possession as it uses the same signature type as the regular
// input UTXO spend. For legacy addresses, use the ECDSA signature scheme.
if (
isTaproot(stakerBtcAddress, this.network)
|| isNativeSegwit(stakerBtcAddress, this.network)
) {
sigType = BTCSigType.BIP322;
}
const signedBabylonAddress = await this.btcProvider.signMessage(
SigningStep.PROOF_OF_POSSESSION,
bech32Address,
sigType === BTCSigType.BIP322 ? "bip322-simple" : "ecdsa",
);
let btcSig: Uint8Array;
if (sigType === BTCSigType.BIP322) {
const bip322Sig = BIP322Sig.fromPartial({
address: stakerBtcAddress,
sig: Buffer.from(signedBabylonAddress, "base64"),
});
// Encode the BIP322 protobuf message to a Uint8Array
btcSig = BIP322Sig.encode(bip322Sig).finish();
} else {
// Encode the ECDSA signature to a Uint8Array
btcSig = Buffer.from(signedBabylonAddress, "base64");
}
return {
btcSigType: sigType,
btcSig
};
}
/**
* Creates the unbonding, slashing, and unbonding slashing transactions and
* PSBTs.
* @param stakingInstance - The staking instance.
* @param stakingTx - The staking transaction.
* @returns The unbonding, slashing, and unbonding slashing transactions and
* PSBTs.
*/
private async createDelegationTransactionsAndPsbts(
stakingInstance: Staking,
stakingTx: Transaction,
) {
const { transaction: unbondingTx } =
stakingInstance.createUnbondingTransaction(stakingTx);
// Create slashing transactions and extract signatures
const { psbt: slashingPsbt } =
stakingInstance.createStakingOutputSlashingPsbt(stakingTx);
const { psbt: unbondingSlashingPsbt } =
stakingInstance.createUnbondingOutputSlashingPsbt(unbondingTx);
return {
unbondingTx,
slashingPsbt,
unbondingSlashingPsbt,
};
}
/**
* Creates a protobuf message for the BTC delegation.
* @param stakingInstance - The staking instance.
* @param stakingInput - The staking inputs.
* @param stakingTx - The staking transaction.
* @param bech32Address - The staker's babylon chain bech32 address
* @param stakerBtcInfo - The staker's BTC information such as address and
* public key
* @param params - The staking parameters.
* @param inclusionProof - The inclusion proof of the staking transaction.
* @returns The protobuf message.
*/
public async createBtcDelegationMsg(
stakingInstance: Staking,
stakingInput: StakingInputs,
stakingTx: Transaction,
bech32Address: string,
stakerBtcInfo: StakerInfo,
params: StakingParams,
inclusionProof?: btcstaking.InclusionProof,
) {
const {
unbondingTx,
slashingPsbt,
unbondingSlashingPsbt
} = await this.createDelegationTransactionsAndPsbts(
stakingInstance,
stakingTx,
);
// Sign the slashing PSBT
const signedSlashingPsbtHex = await this.btcProvider.signPsbt(
SigningStep.STAKING_SLASHING,
slashingPsbt.toHex(),
);
const signedSlashingTx = Psbt.fromHex(
signedSlashingPsbtHex,
).extractTransaction();
const slashingSig = extractFirstSchnorrSignatureFromTransaction(
signedSlashingTx
);
if (!slashingSig) {
throw new Error("No signature found in the staking output slashing PSBT");
}
// Sign the unbonding slashing PSBT
const signedUnbondingSlashingPsbtHex = await this.btcProvider.signPsbt(
SigningStep.UNBONDING_SLASHING,
unbondingSlashingPsbt.toHex(),
);
const signedUnbondingSlashingTx = Psbt.fromHex(
signedUnbondingSlashingPsbtHex,
).extractTransaction();
const unbondingSignatures = extractFirstSchnorrSignatureFromTransaction(
signedUnbondingSlashingTx,
);
if (!unbondingSignatures) {
throw new Error("No signature found in the unbonding output slashing PSBT");
}
// Create proof of possession
const proofOfPossession = await this.createProofOfPossession(
bech32Address,
stakerBtcInfo.address,
);
// Prepare the final protobuf message
const msg: btcstakingtx.MsgCreateBTCDelegation =
btcstakingtx.MsgCreateBTCDelegation.fromPartial({
stakerAddr: bech32Address,
pop: proofOfPossession,
btcPk: Uint8Array.from(
Buffer.from(stakerBtcInfo.publicKeyNoCoordHex, "hex"),
),
fpBtcPkList: [
Uint8Array.from(
Buffer.from(stakingInput.finalityProviderPkNoCoordHex, "hex"),
),
],
stakingTime: stakingInput.stakingTimelock,
stakingValue: stakingInput.stakingAmountSat,
stakingTx: Uint8Array.from(stakingTx.toBuffer()),
slashingTx: Uint8Array.from(
Buffer.from(clearTxSignatures(signedSlashingTx).toHex(), "hex"),
),
delegatorSlashingSig: Uint8Array.from(slashingSig),
unbondingTime: params.unbondingTime,
unbondingTx: Uint8Array.from(unbondingTx.toBuffer()),
unbondingValue: stakingInput.stakingAmountSat - params.unbondingFeeSat,
unbondingSlashingTx: Uint8Array.from(
Buffer.from(
clearTxSignatures(signedUnbondingSlashingTx).toHex(),
"hex",
),
),
delegatorUnbondingSlashingSig: Uint8Array.from(unbondingSignatures),
stakingTxInclusionProof: inclusionProof,
});
return {
typeUrl: BABYLON_REGISTRY_TYPE_URLS.MsgCreateBTCDelegation,
value: msg,
};
};
/**
* Gets the inclusion proof for the staking transaction.
* See the type `InclusionProof` for more information
* @param inclusionProof - The inclusion proof.
* @returns The inclusion proof.
*/
private getInclusionProof(
inclusionProof: InclusionProof,
): btcstaking.InclusionProof {
const {
pos,
merkle,
blockHashHex
} = inclusionProof;
const proofHex = deriveMerkleProof(merkle);
const hash = reverseBuffer(Uint8Array.from(Buffer.from(blockHashHex, "hex")));
const inclusionProofKey: btccheckpoint.TransactionKey =
btccheckpoint.TransactionKey.fromPartial({
index: pos,
hash,
});
return btcstaking.InclusionProof.fromPartial({
key: inclusionProofKey,
proof: Uint8Array.from(Buffer.from(proofHex, "hex")),
});
};
}
/**
* Extracts the first valid Schnorr signature from a signed transaction.
*
* Since we only handle transactions with a single input and request a signature
* for one public key, there can be at most one signature from the Bitcoin node.
* A valid Schnorr signature is exactly 64 bytes in length.
*
* @param singedTransaction - The signed Bitcoin transaction to extract the signature from
* @returns The first valid 64-byte Schnorr signature found in the transaction witness data,
* or undefined if no valid signature exists
*/
const extractFirstSchnorrSignatureFromTransaction = (
singedTransaction: Transaction,
): Buffer | undefined => {
// Loop through each input to extract the witness signature
for (const input of singedTransaction.ins) {
if (input.witness && input.witness.length > 0) {
const schnorrSignature = input.witness[0];
// Check that it's a 64-byte Schnorr signature
if (schnorrSignature.length === 64) {
return schnorrSignature; // Return the first valid signature found
}
}
}
return undefined;
};
/**
* Strips all signatures from a transaction by clearing both the script and
* witness data. This is due to the fact that we only need the raw unsigned
* transaction structure. The signatures are sent in a separate protobuf field
* when creating the delegation message in the Babylon.
* @param tx - The transaction to strip signatures from
* @returns A copy of the transaction with all signatures removed
*/
const clearTxSignatures = (tx: Transaction): Transaction => {
tx.ins.forEach((input) => {
input.script = Buffer.alloc(0);
input.witness = [];
});
return tx;
};
/**
* Derives the merkle proof from the list of hex strings. Note the
* sibling hashes are reversed from hex before concatenation.
* @param merkle - The merkle proof hex strings.
* @returns The merkle proof in hex string format.
*/
const deriveMerkleProof = (merkle: string[]) => {
const proofHex = merkle.reduce((acc: string, m: string) => {
return acc + Buffer.from(m, "hex").reverse().toString("hex");
}, "");
return proofHex;
};
/**
* Get the staker signature from the unbonding transaction
* This is used mostly for unbonding transactions from phase-1(Observable)
* @param unbondingTx - The unbonding transaction
* @returns The staker signature
*/
export const getUnbondingTxStakerSignature = (unbondingTx: Transaction): string => {
try {
// There is only one input and one output in the unbonding transaction
return unbondingTx.ins[0].witness[0].toString("hex");
} catch (error) {
throw StakingError.fromUnknown(
error, StakingErrorCode.INVALID_INPUT,
"Failed to get staker signature",
);
}
};Выполнить команду
Для локальной разработки. Не используйте в интернете!