Skip to content

Commit

Permalink
Allow passing two staking transactions for signing
Browse files Browse the repository at this point in the history
Only allowed for retire + remove stake transactions
  • Loading branch information
sisou committed Feb 8, 2024
1 parent ab95e6c commit a268317
Show file tree
Hide file tree
Showing 4 changed files with 65 additions and 30 deletions.
8 changes: 4 additions & 4 deletions client/src/PublicRequest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@ export type SignTransactionRequest

export type SignStakingRequest = SimpleRequest & {
keyPath: string,
transaction: Uint8Array,
transaction: Uint8Array | Uint8Array[], // An array is only allowed for retire_stake + remove_stake transactions
senderLabel?: string,
recipientLabel?: string,
};
Expand Down Expand Up @@ -546,7 +546,7 @@ export type RedirectResult
| ExportResult
| KeyResult
| SignTransactionResult
| SignStakingResult
| SignStakingResult[]
| SignedBitcoinTransaction
| SignedPolygonTransaction
| SimpleResult
Expand All @@ -560,7 +560,7 @@ export type Result = RedirectResult | IFrameResult;

export type ResultType<T extends RedirectRequest> =
T extends Is<T, SignMessageRequest> | Is<T, SignTransactionRequest> ? SignatureResult :
T extends Is<T, SignStakingRequest> ? SignStakingResult :
T extends Is<T, SignStakingRequest> ? SignStakingResult[] :
T extends Is<T, DeriveAddressRequest> ? DerivedAddress[] :
T extends Is<T, CreateRequest> | Is<T, ImportRequest> | Is<T, ResetPasswordRequest> ? KeyResult :
T extends Is<T, ExportRequest> ? ExportResult :
Expand All @@ -574,7 +574,7 @@ export type ResultType<T extends RedirectRequest> =

export type ResultByCommand<T extends KeyguardCommand> =
T extends KeyguardCommand.SIGN_MESSAGE | KeyguardCommand.SIGN_TRANSACTION ? SignatureResult :
T extends KeyguardCommand.SIGN_STAKING ? SignStakingResult :
T extends KeyguardCommand.SIGN_STAKING ? SignStakingResult[] :
T extends KeyguardCommand.DERIVE_ADDRESS ? DerivedAddress[] :
T extends KeyguardCommand.CREATE | KeyguardCommand.IMPORT ? KeyResult :
T extends KeyguardCommand.EXPORT ? ExportResult :
Expand Down
29 changes: 19 additions & 10 deletions src/request/sign-staking/SignStaking.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

/**
* @callback SignStaking.resolve
* @param {KeyguardRequest.SignStakingResult} result
* @param {KeyguardRequest.SignStakingResult[]} result
*/

class SignStaking {
Expand All @@ -25,7 +25,7 @@ class SignStaking {
/** @type {HTMLElement} */
this.$el = (document.getElementById(SignStaking.Pages.CONFIRM_STAKING));

const transaction = request.plain;
const transaction = request.plain[request.plain.length - 1];

/** @type {HTMLElement} */
this.$accountDetails = (this.$el.querySelector('#account-details'));
Expand Down Expand Up @@ -150,15 +150,20 @@ class SignStaking {
const privateKey = Albatross.PrivateKey.unserialize(powPrivateKey.serialize());
const keyPair = Albatross.KeyPair.derive(privateKey);

request.transaction.sign(keyPair);
const results = request.transactions.map(transaction => {
transaction.sign(keyPair);

/** @type {KeyguardRequest.SignStakingResult} */
const result = {
publicKey: keyPair.publicKey.serialize(),
signature: request.transaction.proof.subarray(request.transaction.proof.length - 64),
transaction: request.transaction.serialize(),
};
resolve(result);
/** @type {KeyguardRequest.SignStakingResult} */
const result = {
publicKey: keyPair.publicKey.serialize(),
signature: transaction.proof.subarray(transaction.proof.length - 64),
transaction: transaction.serialize(),
};

return result;
});

resolve(results);
}

run() {
Expand Down Expand Up @@ -207,6 +212,10 @@ class SignStaking {
const { newActiveBalance } = plain.data;
return `Set active stake to ${newActiveBalance / 1e5} NIM`;
}
case 'retire-stake': {
const { retireStake } = plain.data;
return `Retire ${retireStake / 1e5} NIM stake`;
}
case 'create-validator': {
let text = `Create validator ${plain.sender}`;
const { rewardAddress } = plain.data;
Expand Down
54 changes: 40 additions & 14 deletions src/request/sign-staking/SignStakingApi.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,35 +24,61 @@ class SignStakingApi extends TopLevelApi { // eslint-disable-line no-unused-vars
parsedRequest.senderLabel = this.parseLabel(request.senderLabel);
parsedRequest.recipientLabel = this.parseLabel(request.recipientLabel);

parsedRequest.transaction = this.parseStakingTransaction(request.transaction);
parsedRequest.plain = parsedRequest.transaction.toPlain();
parsedRequest.transactions = this.parseStakingTransaction(request.transaction);
parsedRequest.plain = parsedRequest.transactions.map(tx => tx.toPlain());

if (parsedRequest.plain.length > 2) {
throw new Errors.InvalidRequestError('Only a maximum of two transactions are allowed in a single request');
}

if (parsedRequest.plain.length === 2) {
// Ensure the transactions are for stake retiring and removal, in this order
if (parsedRequest.plain[0].data.type !== 'retire-stake') {
throw new Errors.InvalidRequestError('First transaction must be a retire stake transaction');
}
if (parsedRequest.plain[1].senderData.type !== 'remove-stake') {
throw new Errors.InvalidRequestError('Second transaction must be a remove stake transaction');
}
}

return parsedRequest;
}

/**
* Checks that the given layout is valid
* @param {unknown} transaction
* @returns {Albatross.Transaction}
* @param {unknown} transactions
* @returns {Albatross.Transaction[]}
*/
parseStakingTransaction(transaction) {
if (!transaction) {
parseStakingTransaction(transactions) {
if (!transactions) {
throw new Errors.InvalidRequestError('transaction is required');
}

if (!(transaction instanceof Uint8Array)) {
throw new Errors.InvalidRequestError('transaction must be a Uint8Array');
if (!Array.isArray(transactions)) {
transactions = [transactions];
}
const tx = Albatross.Transaction.fromAny(Nimiq.BufferUtils.toHex(transaction));

if (tx.senderType !== Albatross.AccountType.Staking && tx.recipientType !== Albatross.AccountType.Staking) {
throw new Errors.InvalidRequestError('transaction must be a staking transaction');
if (/** @type {any[]} */ (transactions).length === 0) {
throw new Errors.InvalidRequestError('transaction must not be empty');
}

// Parsing the transaction does not validate any of it's fields.
// TODO: Validate all fields like tx.verify() would?
const txs = /** @type {any[]} */ (transactions).map(transaction => {
if (!(transaction instanceof Uint8Array)) {
throw new Errors.InvalidRequestError('transaction must be a Uint8Array');
}
const tx = Albatross.Transaction.fromAny(Nimiq.BufferUtils.toHex(transaction));

if (tx.senderType !== Albatross.AccountType.Staking && tx.recipientType !== Albatross.AccountType.Staking) {
throw new Errors.InvalidRequestError('transaction must be a staking transaction');
}

// Parsing the transaction does not validate any of it's fields.
// TODO: Validate all fields like tx.verify() would?

return tx;
});

return tx;
return txs;
}

get Handler() {
Expand Down
4 changes: 2 additions & 2 deletions types/Keyguard.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -241,8 +241,8 @@ type Parsed<T extends KeyguardRequest.Request> =
ConstructTransaction<KeyId2KeyInfo<KeyguardRequest.SignTransactionRequestCashlink>> :
T extends Is<T, KeyguardRequest.SignStakingRequest> ?
Transform<KeyId2KeyInfo<KeyguardRequest.SignStakingRequest> & {
plain: Albatross.PlainTransaction,
}, 'transaction', { transaction: Albatross.Transaction }> :
plain: Albatross.PlainTransaction[],
}, 'transaction', { transactions: Albatross.Transaction[] }> :
T extends Is<T, KeyguardRequest.SignMessageRequest> ?
Transform<
KeyId2KeyInfo<KeyguardRequest.SignMessageRequest>,
Expand Down

0 comments on commit a268317

Please sign in to comment.