PHP WebShell
Текущая директория: /opt/BitGoJS/modules/babylonlabs-io-btc-staking-ts/build/src/staking
Просмотр файла: stakingScript.js
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.StakingScriptData = exports.MAGIC_BYTES_LEN = void 0;
const bitcoinjs_lib_1 = require("bitcoinjs-lib");
const keys_1 = require("../constants/keys");
exports.MAGIC_BYTES_LEN = 4;
// StakingScriptData is a class that holds the data required for the BTC Staking Script
// and exposes methods for converting it into useful formats
class StakingScriptData {
constructor(
// The `stakerKey` is the public key of the staker without the coordinate bytes.
stakerKey,
// A list of public keys without the coordinate bytes corresponding to the finality providers
// the stake will be delegated to.
// Currently, Babylon does not support restaking, so this should contain only a single item.
finalityProviderKeys,
// A list of the public keys without the coordinate bytes corresponding to
// the covenant emulators.
// This is a parameter of the Babylon system and should be retrieved from there.
covenantKeys,
// The number of covenant emulator signatures required for a transaction
// to be valid.
// This is a parameter of the Babylon system and should be retrieved from there.
covenantThreshold,
// The staking period denoted as a number of BTC blocks.
stakingTimelock,
// The unbonding period denoted as a number of BTC blocks.
// This value should be more than equal than the minimum unbonding time of the
// Babylon system.
unbondingTimelock) {
if (!stakerKey ||
!finalityProviderKeys ||
!covenantKeys ||
!covenantThreshold ||
!stakingTimelock ||
!unbondingTimelock) {
throw new Error("Missing required input values");
}
this.stakerKey = stakerKey;
this.finalityProviderKeys = finalityProviderKeys;
this.covenantKeys = covenantKeys;
this.covenantThreshold = covenantThreshold;
this.stakingTimeLock = stakingTimelock;
this.unbondingTimeLock = unbondingTimelock;
// Run the validate method to check if the provided script data is valid
if (!this.validate()) {
throw new Error("Invalid script data provided");
}
}
/**
* Validates the staking script.
* @returns {boolean} Returns true if the staking script is valid, otherwise false.
*/
validate() {
// check that staker key is the correct length
if (this.stakerKey.length != keys_1.NO_COORD_PK_BYTE_LENGTH) {
return false;
}
// check that finalityProvider keys are the correct length
if (this.finalityProviderKeys.some((finalityProviderKey) => finalityProviderKey.length != keys_1.NO_COORD_PK_BYTE_LENGTH)) {
return false;
}
// check that covenant keys are the correct length
if (this.covenantKeys.some((covenantKey) => covenantKey.length != keys_1.NO_COORD_PK_BYTE_LENGTH)) {
return false;
}
// Check whether we have any duplicate keys
const allPks = [
this.stakerKey,
...this.finalityProviderKeys,
...this.covenantKeys,
];
const allPksSet = new Set(allPks);
if (allPks.length !== allPksSet.size) {
return false;
}
// check that the threshold is above 0 and less than or equal to
// the size of the covenant emulators set
if (this.covenantThreshold <= 0 ||
this.covenantThreshold > this.covenantKeys.length) {
return false;
}
// check that maximum value for staking time is not greater than uint16 and above 0
if (this.stakingTimeLock <= 0 || this.stakingTimeLock > 65535) {
return false;
}
// check that maximum value for unbonding time is not greater than uint16 and above 0
if (this.unbondingTimeLock <= 0 || this.unbondingTimeLock > 65535) {
return false;
}
return true;
}
// The staking script allows for multiple finality provider public keys
// to support (re)stake to multiple finality providers
// Covenant members are going to have multiple keys
/**
* Builds a timelock script.
* @param timelock - The timelock value to encode in the script.
* @returns {Buffer} containing the compiled timelock script.
*/
buildTimelockScript(timelock) {
return bitcoinjs_lib_1.script.compile([
this.stakerKey,
bitcoinjs_lib_1.opcodes.OP_CHECKSIGVERIFY,
bitcoinjs_lib_1.script.number.encode(timelock),
bitcoinjs_lib_1.opcodes.OP_CHECKSEQUENCEVERIFY,
]);
}
/**
* Builds the staking timelock script.
* Only holder of private key for given pubKey can spend after relative lock time
* Creates the timelock script in the form:
* <stakerPubKey>
* OP_CHECKSIGVERIFY
* <stakingTimeBlocks>
* OP_CHECKSEQUENCEVERIFY
* @returns {Buffer} The staking timelock script.
*/
buildStakingTimelockScript() {
return this.buildTimelockScript(this.stakingTimeLock);
}
/**
* Builds the unbonding timelock script.
* Creates the unbonding timelock script in the form:
* <stakerPubKey>
* OP_CHECKSIGVERIFY
* <unbondingTimeBlocks>
* OP_CHECKSEQUENCEVERIFY
* @returns {Buffer} The unbonding timelock script.
*/
buildUnbondingTimelockScript() {
return this.buildTimelockScript(this.unbondingTimeLock);
}
/**
* Builds the unbonding script in the form:
* buildSingleKeyScript(stakerPk, true) ||
* buildMultiKeyScript(covenantPks, covenantThreshold, false)
* || means combining the scripts
* @returns {Buffer} The unbonding script.
*/
buildUnbondingScript() {
return Buffer.concat([
this.buildSingleKeyScript(this.stakerKey, true),
this.buildMultiKeyScript(this.covenantKeys, this.covenantThreshold, false),
]);
}
/**
* Builds the slashing script for staking in the form:
* buildSingleKeyScript(stakerPk, true) ||
* buildMultiKeyScript(finalityProviderPKs, 1, true) ||
* buildMultiKeyScript(covenantPks, covenantThreshold, false)
* || means combining the scripts
* The slashing script is a combination of single-key and multi-key scripts.
* The single-key script is used for staker key verification.
* The multi-key script is used for finality provider key verification and covenant key verification.
* @returns {Buffer} The slashing script as a Buffer.
*/
buildSlashingScript() {
return Buffer.concat([
this.buildSingleKeyScript(this.stakerKey, true),
this.buildMultiKeyScript(this.finalityProviderKeys,
// The threshold is always 1 as we only need one
// finalityProvider signature to perform slashing
// (only one finalityProvider performs an offence)
1,
// OP_VERIFY/OP_CHECKSIGVERIFY is added at the end
true),
this.buildMultiKeyScript(this.covenantKeys, this.covenantThreshold,
// No need to add verify since covenants are at the end of the script
false),
]);
}
/**
* Builds the staking scripts.
* @returns {StakingScripts} The staking scripts.
*/
buildScripts() {
return {
timelockScript: this.buildStakingTimelockScript(),
unbondingScript: this.buildUnbondingScript(),
slashingScript: this.buildSlashingScript(),
unbondingTimelockScript: this.buildUnbondingTimelockScript(),
};
}
// buildSingleKeyScript and buildMultiKeyScript allow us to reuse functionality
// for creating Bitcoin scripts for the unbonding script and the slashing script
/**
* Builds a single key script in the form:
* buildSingleKeyScript creates a single key script
* <pk> OP_CHECKSIGVERIFY (if withVerify is true)
* <pk> OP_CHECKSIG (if withVerify is false)
* @param pk - The public key buffer.
* @param withVerify - A boolean indicating whether to include the OP_CHECKSIGVERIFY opcode.
* @returns The compiled script buffer.
*/
buildSingleKeyScript(pk, withVerify) {
// Check public key length
if (pk.length != keys_1.NO_COORD_PK_BYTE_LENGTH) {
throw new Error("Invalid key length");
}
return bitcoinjs_lib_1.script.compile([
pk,
withVerify ? bitcoinjs_lib_1.opcodes.OP_CHECKSIGVERIFY : bitcoinjs_lib_1.opcodes.OP_CHECKSIG,
]);
}
/**
* Builds a multi-key script in the form:
* <pk1> OP_CHEKCSIG <pk2> OP_CHECKSIGADD <pk3> OP_CHECKSIGADD ... <pkN> OP_CHECKSIGADD <threshold> OP_NUMEQUAL
* <withVerify -> OP_NUMEQUALVERIFY>
* It validates whether provided keys are unique and the threshold is not greater than number of keys
* If there is only one key provided it will return single key sig script
* @param pks - An array of public keys.
* @param threshold - The required number of valid signers.
* @param withVerify - A boolean indicating whether to include the OP_VERIFY opcode.
* @returns The compiled multi-key script as a Buffer.
* @throws {Error} If no keys are provided, if the required number of valid signers is greater than the number of provided keys, or if duplicate keys are provided.
*/
buildMultiKeyScript(pks, threshold, withVerify) {
// Verify that pks is not empty
if (!pks || pks.length === 0) {
throw new Error("No keys provided");
}
// Check buffer object have expected lengths like checking pks.length
if (pks.some((pk) => pk.length != keys_1.NO_COORD_PK_BYTE_LENGTH)) {
throw new Error("Invalid key length");
}
// Verify that threshold <= len(pks)
if (threshold > pks.length) {
throw new Error("Required number of valid signers is greater than number of provided keys");
}
if (pks.length === 1) {
return this.buildSingleKeyScript(pks[0], withVerify);
}
// keys must be sorted
const sortedPks = [...pks].sort(Buffer.compare);
// verify there are no duplicates
for (let i = 0; i < sortedPks.length - 1; ++i) {
if (sortedPks[i].equals(sortedPks[i + 1])) {
throw new Error("Duplicate keys provided");
}
}
const scriptElements = [sortedPks[0], bitcoinjs_lib_1.opcodes.OP_CHECKSIG];
for (let i = 1; i < sortedPks.length; i++) {
scriptElements.push(sortedPks[i]);
scriptElements.push(bitcoinjs_lib_1.opcodes.OP_CHECKSIGADD);
}
scriptElements.push(bitcoinjs_lib_1.script.number.encode(threshold));
if (withVerify) {
scriptElements.push(bitcoinjs_lib_1.opcodes.OP_NUMEQUALVERIFY);
}
else {
scriptElements.push(bitcoinjs_lib_1.opcodes.OP_NUMEQUAL);
}
return bitcoinjs_lib_1.script.compile(scriptElements);
}
}
exports.StakingScriptData = StakingScriptData;
Выполнить команду
Для локальной разработки. Не используйте в интернете!