Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add max-amount and auth-id params #1647

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
105 changes: 101 additions & 4 deletions packages/stacking/src/index.ts
Expand Up @@ -231,6 +231,10 @@ export interface LockStxOptions {
signerKey?: string;
/** hex-encoded signature `(buff 65)`, required for >= PoX-4 */
signerSignature?: string;
/** Maximum amount of STX that can be locked in this transaction */
maxAmount?: IntegerType;
/** Random integer to prevent re-use of signer signature */
authId?: number;
}

/**
Expand All @@ -247,6 +251,10 @@ export interface StackExtendOptions {
signerKey?: string;
/** hex-encoded signature `(buff 65)`, required for >= PoX-4 */
signerSignature?: string;
/** Maximum amount of STX that can be locked in this transaction */
maxAmount?: IntegerType;
/** Random integer to prevent re-use of signer signature */
authId?: number;
}

/**
Expand All @@ -257,6 +265,14 @@ export interface StackIncreaseOptions {
privateKey: string;
/** number of ustx to increase by */
increaseBy: IntegerType;
/** hex-encoded signer key `(buff 33)`, required for >= PoX-4 */
signerKey?: string;
/** hex-encoded signature `(buff 65)`, required for >= PoX-4 */
signerSignature?: string;
/** Maximum amount of STX that can be locked in this transaction */
maxAmount?: IntegerType;
/** Random integer to prevent re-use of signer signature */
authId?: number;
}

/**
Expand Down Expand Up @@ -331,6 +347,10 @@ export interface StackAggregationCommitOptions {
signerKey?: string;
/** hex-encoded signature `(buff 65)`, required for >= PoX-4 */
signerSignature?: string;
/** Maximum amount of STX that can be locked in this transaction */
maxAmount?: IntegerType;
/** Random integer to prevent re-use of signer signature */
authId?: number;
}

export interface StackAggregationIncreaseOptions {
Expand Down Expand Up @@ -623,6 +643,8 @@ export class StackingClient {
burnBlockHeight,
signerKey,
signerSignature,
maxAmount,
authId,
...txOptions
}: LockStxOptions & BaseTxOptions): Promise<TxBroadcastResult> {
const poxInfo = await this.getPoxInfo();
Expand All @@ -631,7 +653,7 @@ export class StackingClient {
const contract = await this.getStackingContract(poxOperationInfo);

ensureLegacyBtcAddressForPox1({ contract, poxAddress });
ensureSignerArgsReadiness({ contract, signerKey, signerSignature });
ensureSignerArgsReadiness({ contract, signerKey, signerSignature, maxAmount, authId });

const callOptions = this.getStackOptions({
contract,
Expand All @@ -641,6 +663,8 @@ export class StackingClient {
burnBlockHeight,
signerKey,
signerSignature,
maxAmount,
authId,
});
const tx = await makeContractCall({
...callOptions,
Expand All @@ -663,20 +687,30 @@ export class StackingClient {
poxAddress,
signerKey,
signerSignature,
maxAmount,
authId,
...txOptions
}: StackExtendOptions & BaseTxOptions): Promise<TxBroadcastResult> {
const poxInfo = await this.getPoxInfo();
const poxOperationInfo = await this.getPoxOperationInfo(poxInfo);

ensurePox2Activated(poxOperationInfo);
ensureSignerArgsReadiness({ contract: poxInfo.contract_id, signerKey, signerSignature });
ensureSignerArgsReadiness({
contract: poxInfo.contract_id,
signerKey,
signerSignature,
maxAmount,
authId,
});

const callOptions = this.getStackExtendOptions({
contract: poxInfo.contract_id,
extendCycles,
poxAddress,
signerKey,
signerSignature,
maxAmount,
authId,
});
const tx = await makeContractCall({
...callOptions,
Expand All @@ -694,15 +728,30 @@ export class StackingClient {
*/
async stackIncrease({
increaseBy,
signerKey,
signerSignature,
maxAmount,
authId,
...txOptions
}: StackIncreaseOptions & BaseTxOptions): Promise<TxBroadcastResult> {
const poxInfo = await this.getPoxInfo();
const poxOperationInfo = await this.getPoxOperationInfo(poxInfo);
ensurePox2Activated(poxOperationInfo);
ensureSignerArgsReadiness({
contract: poxInfo.contract_id,
signerKey,
signerSignature,
maxAmount,
authId,
});

const callOptions = this.getStackIncreaseOptions({
contract: poxInfo.contract_id,
increaseBy,
signerKey,
signerSignature,
maxAmount,
authId,
});
const tx = await makeContractCall({
...callOptions,
Expand Down Expand Up @@ -857,6 +906,8 @@ export class StackingClient {
rewardCycle,
signerKey,
signerSignature,
maxAmount,
authId,
...txOptions
}: StackAggregationCommitOptions & BaseTxOptions): Promise<TxBroadcastResult> {
const contract = await this.getStackingContract();
Expand Down Expand Up @@ -988,6 +1039,8 @@ export class StackingClient {
burnBlockHeight,
signerKey,
signerSignature,
maxAmount,
authId,
}: {
cycles: number;
poxAddress: string;
Expand All @@ -996,6 +1049,8 @@ export class StackingClient {
burnBlockHeight: number;
signerKey?: string;
signerSignature?: string;
maxAmount?: IntegerType;
authId?: number;
}) {
const address = poxAddressToTuple(poxAddress);
const [contractAddress, contractName] = this.parseContractId(contract);
Expand All @@ -1009,6 +1064,8 @@ export class StackingClient {

if (signerSignature) functionArgs.push(someCV(bufferCV(hexToBytes(signerSignature))));
if (signerKey) functionArgs.push(bufferCV(hexToBytes(signerKey)));
if (maxAmount) functionArgs.push(uintCV(maxAmount));
if (authId) functionArgs.push(uintCV(authId));

const callOptions: ContractCallOptions = {
contractAddress,
Expand All @@ -1028,12 +1085,16 @@ export class StackingClient {
contract,
signerKey,
signerSignature,
maxAmount,
authId,
}: {
extendCycles: number;
poxAddress: string;
contract: string;
signerKey?: string;
signerSignature?: string;
maxAmount?: IntegerType;
authId?: number;
}) {
const address = poxAddressToTuple(poxAddress);
const [contractAddress, contractName] = this.parseContractId(contract);
Expand All @@ -1042,6 +1103,8 @@ export class StackingClient {

if (signerSignature) functionArgs.push(someCV(bufferCV(hexToBytes(signerSignature))));
if (signerKey) functionArgs.push(bufferCV(hexToBytes(signerKey)));
if (maxAmount) functionArgs.push(uintCV(maxAmount));
if (authId) functionArgs.push(uintCV(authId));

const callOptions: ContractCallOptions = {
contractAddress,
Expand All @@ -1055,13 +1118,35 @@ export class StackingClient {
return callOptions;
}

getStackIncreaseOptions({ increaseBy, contract }: { increaseBy: IntegerType; contract: string }) {
getStackIncreaseOptions({
increaseBy,
contract,
signerKey,
signerSignature,
maxAmount,
authId,
}: {
increaseBy: IntegerType;
contract: string;
signerKey?: string;
signerSignature?: string;
maxAmount?: IntegerType;
authId?: number;
}) {
const [contractAddress, contractName] = this.parseContractId(contract);

const functionArgs = [uintCV(increaseBy)] as ClarityValue[];

if (signerSignature) functionArgs.push(someCV(bufferCV(hexToBytes(signerSignature))));
if (signerKey) functionArgs.push(bufferCV(hexToBytes(signerKey)));
if (maxAmount) functionArgs.push(uintCV(maxAmount));
if (authId) functionArgs.push(uintCV(authId));

const callOptions: ContractCallOptions = {
contractAddress,
contractName,
functionName: 'stack-increase',
functionArgs: [uintCV(increaseBy)],
functionArgs,
validateWithAbi: true,
network: this.network,
anchorMode: AnchorMode.Any,
Expand Down Expand Up @@ -1197,12 +1282,16 @@ export class StackingClient {
rewardCycle,
signerKey,
signerSignature,
maxAmount,
authId,
}: {
contract: string;
poxAddress: string;
rewardCycle: number;
signerKey?: string;
signerSignature?: string;
maxAmount?: IntegerType;
authId?: number;
}) {
const address = poxAddressToTuple(poxAddress);
const [contractAddress, contractName] = this.parseContractId(contract);
Expand All @@ -1211,6 +1300,8 @@ export class StackingClient {

if (signerSignature) functionArgs.push(someCV(bufferCV(hexToBytes(signerSignature))));
if (signerKey) functionArgs.push(bufferCV(hexToBytes(signerKey)));
if (maxAmount) functionArgs.push(uintCV(maxAmount));
if (authId) functionArgs.push(uintCV(authId));

const callOptions: ContractCallOptions = {
contractAddress,
Expand Down Expand Up @@ -1452,12 +1543,16 @@ export class StackingClient {
rewardCycle,
period,
signerPrivateKey,
authId,
maxAmount,
}: {
topic: `${Pox4SignatureTopic}`;
poxAddress: string;
rewardCycle: number;
period: number;
signerPrivateKey: StacksPrivateKey;
maxAmount: IntegerType;
authId: number;
}) {
// todo: in the future add logic to determine if a later version of pox
// needs a different domain and thus use a different `signPox4SignatureHash`
Expand All @@ -1468,6 +1563,8 @@ export class StackingClient {
period,
network: this.network,
privateKey: signerPrivateKey,
maxAmount,
authId,
});
}
}
Expand Down
29 changes: 24 additions & 5 deletions packages/stacking/src/utils.ts
@@ -1,6 +1,6 @@
import { sha256 } from '@noble/hashes/sha256';
import { bech32, bech32m } from '@scure/base';
import { bigIntToBytes } from '@stacks/common';
import { IntegerType, bigIntToBytes } from '@stacks/common';
import {
base58CheckDecode,
base58CheckEncode,
Expand Down Expand Up @@ -368,19 +368,25 @@ export function ensureSignerArgsReadiness({
contract,
signerKey,
signerSignature,
maxAmount,
authId,
}: {
contract: string;
signerKey?: string;
signerSignature?: string;
maxAmount?: IntegerType;
authId?: number;
}) {
const hasMaxAmount = typeof maxAmount !== 'undefined';
const hasAuthId = typeof authId !== 'undefined';
if (/\.pox(-[2-3])?$/.test(contract)) {
// .pox, .pox-2 or .pox-3
if (signerKey || signerSignature) {
if (signerKey || signerSignature || hasMaxAmount || hasAuthId) {
throw new Error('PoX-1, PoX-2 and PoX-3 do not accept a signer-key or signer-sig');
}
} else {
// .pox-4 or later
if (!signerKey || !signerSignature) {
if (!signerKey || !signerSignature || !hasMaxAmount || !hasAuthId) {
throw new Error(
'PoX-4 or later requires a signer-key (buff 33) and signer-sig (buff 65) to stack'
);
Expand All @@ -392,6 +398,7 @@ export enum Pox4SignatureTopic {
StackStx = 'stack-stx',
AggregateCommit = 'agg-commit',
StackExtend = 'stack-extend',
StackIncrease = 'stack-increase',
}

export interface Pox4SignatureOptions {
Expand All @@ -403,6 +410,10 @@ export interface Pox4SignatureOptions {
/** lock period (in cycles) */
period: number;
network: StacksNetwork;
/** Maximum amount of uSTX that can be locked during this function call */
maxAmount: IntegerType;
/** Random integer to prevent signature re-use */
authId: number;
}

/**
Expand All @@ -415,9 +426,11 @@ export function signPox4SignatureHash({
period,
network,
privateKey,
maxAmount,
authId,
}: Pox4SignatureOptions & { privateKey: StacksPrivateKey }) {
return signStructuredData({
...pox4SignatureMessage({ topic, poxAddress, rewardCycle, period, network }),
...pox4SignatureMessage({ topic, poxAddress, rewardCycle, period, network, maxAmount, authId }),
privateKey,
}).data;
}
Expand All @@ -434,11 +447,13 @@ export function verifyPox4SignatureHash({
network,
publicKey,
signature,
maxAmount,
authId,
}: Pox4SignatureOptions & { publicKey: string; signature: string }) {
return verifyMessageSignatureRsv({
message: sha256(
encodeStructuredData(
pox4SignatureMessage({ topic, poxAddress, rewardCycle, period, network })
pox4SignatureMessage({ topic, poxAddress, rewardCycle, period, network, maxAmount, authId })
)
),
publicKey,
Expand All @@ -456,12 +471,16 @@ export function pox4SignatureMessage({
rewardCycle,
period: lockPeriod,
network,
maxAmount,
authId,
}: Pox4SignatureOptions) {
const message = tupleCV({
'pox-addr': poxAddressToTuple(poxAddress),
'reward-cycle': uintCV(rewardCycle),
topic: stringAsciiCV(topic),
period: uintCV(lockPeriod),
'max-amount': uintCV(maxAmount),
'auth-id': uintCV(authId),
});
const domain = tupleCV({
name: stringAsciiCV('pox-4-signer'),
Expand Down