Skip to content

Commit

Permalink
Take a serialized transaction and return a serialized transaction for…
Browse files Browse the repository at this point in the history
… staking

Remove hard-coded staking data types and instead use the Albatross client's data parsing capabilities.
Requires nimiq/core-rs-albatross#2026

Enables signing of validator transactions (nimiq/hub#518).
  • Loading branch information
sisou committed Dec 1, 2023
1 parent f5f3b1d commit 376e8f1
Show file tree
Hide file tree
Showing 6 changed files with 132 additions and 216 deletions.
20 changes: 5 additions & 15 deletions client/src/PublicRequest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -188,19 +188,10 @@ export type SignTransactionRequest
| SignTransactionRequestCheckout
| SignTransactionRequestCashlink;

export type SignStakingRequest = SignTransactionRequestCommon & {
type: number, // See SignStakingApi for types

// For createStaker and updateStaker transactions
delegation?: string,

// For updateStaker transactions
reactivateAllStake?: boolean,

// For inactivateStake transactions
newInactiveBalance?: number,

// For unstake transactions
export type SignStakingRequest = SimpleRequest & {
keyPath: string,
transaction: Uint8Array,
senderLabel?: string,
recipientLabel?: string,
};

Expand Down Expand Up @@ -522,8 +513,7 @@ export type SignTransactionResult = SignatureResult & {
serializedTx: Uint8Array,
};
export type SignStakingResult = SignatureResult & {
data: Uint8Array,
serializedTx: Uint8Array,
transaction: Uint8Array,
};
export type SimpleResult = { success: boolean };
export type SignedBitcoinTransaction = {
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
},
"homepage": "https://github.com/nimiq/keyguard#readme",
"devDependencies": {
"@nimiq/albatross-wasm": "npm:@nimiq/core-web@next",
"@nimiq/albatross-wasm": "../core-rs-albatross/web-client/dist",
"@nimiq/core-web": "1.5.8",
"@nimiq/rpc": "^0.3.0",
"@nimiq/style": "^0.8.3",
Expand Down
204 changes: 100 additions & 104 deletions src/request/sign-staking/SignStaking.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
/* global Key */
/* global KeyStore */
/* global PasswordBox */
/* global SignStakingApi */
/* global Errors */
/* global Utf8Tools */
/* global TopLevelApi */
Expand All @@ -26,15 +25,15 @@ class SignStaking {
/** @type {HTMLElement} */
this.$el = (document.getElementById(SignStaking.Pages.CONFIRM_STAKING));

const transaction = request.transaction;
const transaction = request.plain;

/** @type {HTMLElement} */
this.$accountDetails = (this.$el.querySelector('#account-details'));

/** @type {HTMLLinkElement} */
const $sender = (this.$el.querySelector('.accounts .sender'));
this._senderAddressInfo = new AddressInfo({
userFriendlyAddress: transaction.sender.toUserFriendlyAddress(),
userFriendlyAddress: transaction.sender,
label: request.senderLabel || null,
imageUrl: null,
accountLabel: request.keyLabel || null,
Expand All @@ -46,13 +45,9 @@ class SignStaking {

/** @type {HTMLLinkElement} */
const $recipient = (this.$el.querySelector('.accounts .recipient'));
const recipientAddress = transaction.recipient.toUserFriendlyAddress();
const recipientLabel = 'recipientLabel' in request && !!request.recipientLabel
? request.recipientLabel
: null;
this._recipientAddressInfo = new AddressInfo({
userFriendlyAddress: recipientAddress,
label: recipientLabel,
userFriendlyAddress: transaction.recipient,
label: request.recipientLabel || null,
imageUrl: null,
accountLabel: null,
});
Expand Down Expand Up @@ -81,7 +76,7 @@ class SignStaking {
$feeSection.classList.remove('display-none');
}

if ($data && transaction.data.byteLength > 0) {
if ($data && transaction.data.raw.length) {
// Set transaction extra data.
$data.textContent = this._formatData(transaction);
/** @type {HTMLDivElement} */
Expand Down Expand Up @@ -155,70 +150,13 @@ class SignStaking {
const privateKey = Albatross.PrivateKey.unserialize(powPrivateKey.serialize());
const keyPair = Albatross.KeyPair.derive(privateKey);

/** @type {Albatross.Transaction} */
let tx;

switch (request.type) {
case SignStakingApi.IncomingStakingType.CREATE_STAKER:
tx = Albatross.TransactionBuilder.newCreateStaker(
keyPair.toAddress(),
Albatross.Address.fromString(/** @type {Nimiq.Address} */ (request.delegation).toHex()),
BigInt(request.transaction.value),
BigInt(request.transaction.fee),
request.transaction.validityStartHeight,
request.transaction.networkId,
);
break;
case SignStakingApi.IncomingStakingType.ADD_STAKE:
tx = Albatross.TransactionBuilder.newStake(
keyPair.toAddress(),
keyPair.toAddress(),
BigInt(request.transaction.value),
BigInt(request.transaction.fee),
request.transaction.validityStartHeight,
request.transaction.networkId,
);
break;
case SignStakingApi.IncomingStakingType.UPDATE_STAKER:
tx = Albatross.TransactionBuilder.newUpdateStaker(
keyPair.toAddress(),
Albatross.Address.fromString(/** @type {Nimiq.Address} */ (request.delegation).toHex()),
Boolean(request.reactivateAllStake),
BigInt(request.transaction.fee),
request.transaction.validityStartHeight,
request.transaction.networkId,
);
break;
case SignStakingApi.IncomingStakingType.SET_INACTIVE_STAKE:
tx = Albatross.TransactionBuilder.newSetInactiveStake(
keyPair.toAddress(),
BigInt(request.newInactiveBalance),
BigInt(request.transaction.fee),
request.transaction.validityStartHeight,
request.transaction.networkId,
);
break;
case SignStakingApi.IncomingStakingType.UNSTAKE:
tx = Albatross.TransactionBuilder.newUnstake(
keyPair.toAddress(),
BigInt(request.transaction.value),
BigInt(request.transaction.fee),
request.transaction.validityStartHeight,
request.transaction.networkId,
);
break;
default:
throw new Errors.KeyguardError('Unreachable');
}

tx.sign(keyPair);
request.transaction.sign(keyPair);

/** @type {KeyguardRequest.SignStakingResult} */
const result = {
publicKey: keyPair.publicKey.serialize(),
signature: tx.proof.subarray(tx.proof.length - 64),
data: request.transaction.data,
serializedTx: tx.serialize(),
signature: request.transaction.proof.subarray(request.transaction.proof.length - 64),
transaction: request.transaction.serialize(),
};
resolve(result);
}
Expand All @@ -229,47 +167,105 @@ class SignStaking {
}

/**
* @param {Nimiq.Transaction} transaction
* @param {Albatross.PlainTransaction} plain
* @returns {string}
*/
_formatData(transaction) {
const buf = new Nimiq.SerialBuffer(transaction.data);
const type = buf.readUint8();
switch (type) {
case SignStakingApi.IncomingStakingType.CREATE_STAKER: {
let text = 'Start staking';
const hasDelegation = buf.readUint8() === 1;
if (hasDelegation) {
const delegation = Nimiq.Address.unserialize(buf);
text += ` with validator ${delegation.toUserFriendlyAddress()}`;
} else {
text += ' with no validator';
_formatData(plain) {
// That either the recipient or the sender is a staking account type is validated in SignStakingApi
if (plain.recipientType === 'staking') {
switch (plain.data.type) {
case 'create-staker': {
let text = 'Start staking';
const { delegation } = plain.data;
if (delegation) {
text += ` with validator ${delegation}`;
} else {
text += ' with no validator';
}
return text;
}
return text;
}
case SignStakingApi.IncomingStakingType.UPDATE_STAKER: {
let text = 'Change validator';
const hasDelegation = buf.readUint8() === 1;
if (hasDelegation) {
const delegation = Nimiq.Address.unserialize(buf);
text += ` to validator ${delegation.toUserFriendlyAddress()}`;
} else {
text += ' to no validator';
case 'update-staker': {
let text = 'Change validator';
const { newDelegation, reactivateAllStake } = plain.data;
if (newDelegation) {
text += ` to validator ${newDelegation}`;
} else {
text += ' to no validator';
}
if (reactivateAllStake) {
text += ' and reactivate all stake';
}
return text;
}
if (buf.readUint8() === 1) {
text += ' and reactivate all stake';
case 'add-stake': {
const { staker } = plain.data;
return `Add stake to ${staker}`;
}
case 'set-inactive-stake': {
const { newInactiveBalance } = plain.data;
return `Set inactive stake to ${newInactiveBalance / 1e5} NIM`;
}
case 'create-validator': {
let text = `Create validator ${plain.sender}`;
const { rewardAddress } = plain.data;
if (rewardAddress !== plain.sender) {
text += ` with reward address ${rewardAddress}`;
}
// TODO: Somehow let users see validator key, signing key, and signal data that they are signing
return text;
}
case 'update-validator': {
let text = `Update validator ${plain.sender}`;
const {
newRewardAddress,
newVotingKey,
newSigningKey,
newSignalData,
} = plain.data;
text += ` ${plain.sender}`;
if (newRewardAddress) {
text += `, updating reward address to ${newRewardAddress}`;
}
if (newVotingKey) {
text += ', updating voting key';
}
if (newSigningKey) {
text += ', updating signing key';
}
if (newSignalData) {
text += ', updating signal data';
}
return text;
}
case 'deactivate-validator': {
const { validator } = plain.data;
return `Deactivate validator ${validator}`;
}
case 'reactivate-validator': {
const { validator } = plain.data;
return `Reactivate validator ${validator}`;
}
case 'retire-validator': {
return `Retire validator ${plain.sender}`;
}
default: {
return `Unrecognized in-staking data: ${plain.data.type} - ${plain.data.raw}`;
}
return text;
}
case SignStakingApi.IncomingStakingType.ADD_STAKE: {
const staker = Nimiq.Address.unserialize(buf);
return `Add stake for ${staker.toUserFriendlyAddress()}`;
}
case SignStakingApi.IncomingStakingType.SET_INACTIVE_STAKE: {
const inactiveBalance = buf.readUint64();
return `Set inactive stake to ${inactiveBalance / 1e5} NIM`;
} else {
switch (plain.senderData.type) {
case 'remove-stake': {
return 'Unstake';
}
case 'delete-validator': {
// Cannot show the validator address here, as the recipient can be any address and the validator
// address is the signer, which the Keyguard only knows after password entry.
return 'Delete validator';
}
default: {
return `Unrecognized out-staking data: ${plain.senderData.type} - ${plain.senderData.raw}`;
}
}
default: return Nimiq.BufferUtils.toHex(transaction.data);
}
}
}
Expand Down

0 comments on commit 376e8f1

Please sign in to comment.