Skip to content
This repository has been archived by the owner on Jun 11, 2024. It is now read-only.

Fix missing multisig signatures #9095

Merged
merged 4 commits into from
Oct 25, 2023
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions commander/src/bootstrapping/commands/transaction/sign.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
*/
import { Command, Flags as flagParser } from '@oclif/core';
import * as apiClient from '@liskhq/lisk-api-client';
import * as cryptography from '@liskhq/lisk-cryptography';
import {
Application,
blockHeaderSchema,
Expand Down Expand Up @@ -77,7 +76,7 @@ const signTransaction = async (

const chainIDBuffer = Buffer.from(chainID as string, 'hex');
const passphrase = flags.passphrase ?? (await getPassphraseFromPrompt('passphrase'));
const edKeys = cryptography.legacy.getPrivateAndPublicKeyFromPassphrase(passphrase);
const edKeys = await deriveKeypair(passphrase, flags['key-derivation-path']);

let signedTransaction: Record<string, unknown>;
if (flags['mandatory-keys'] || flags['optional-keys']) {
Expand Down
173 changes: 103 additions & 70 deletions commander/test/bootstrapping/commands/transaction/sign.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ import * as appUtils from '../../../../src/utils/application';
import * as readerUtils from '../../../../src/utils/reader';
import { SignCommand } from '../../../../src/bootstrapping/commands/transaction/sign';
import { getConfig } from '../../../helpers/config';
import { accountsForMultisignature } from '../../../helpers/account';
import { legacyAccounts, modernAccounts } from '../../../helpers/account';
import {
createIPCClientMock,
mockCommands,
Expand All @@ -40,26 +40,26 @@ import {
import { Awaited } from '../../../types';

describe('transaction:sign command', () => {
const senderPassphrase = accountsForMultisignature.targetAccount.passphrase;
const senderPassphrase = legacyAccounts.targetAccount.passphrase;

const mandatoryPassphrases = [
accountsForMultisignature.mandatoryOne.passphrase,
accountsForMultisignature.mandatoryTwo.passphrase,
legacyAccounts.mandatoryOne.passphrase,
legacyAccounts.mandatoryTwo.passphrase,
];

const optionalPassphrases = [
accountsForMultisignature.optionalOne.passphrase,
accountsForMultisignature.optionalTwo.passphrase,
legacyAccounts.optionalOne.passphrase,
legacyAccounts.optionalTwo.passphrase,
];

const mandatoryKeys = [
accountsForMultisignature.mandatoryOne.publicKey.toString('hex'),
accountsForMultisignature.mandatoryTwo.publicKey.toString('hex'),
legacyAccounts.mandatoryOne.publicKey.toString('hex'),
legacyAccounts.mandatoryTwo.publicKey.toString('hex'),
];

const optionalKeys = [
accountsForMultisignature.optionalOne.publicKey.toString('hex'),
accountsForMultisignature.optionalTwo.publicKey.toString('hex'),
legacyAccounts.optionalOne.publicKey.toString('hex'),
legacyAccounts.optionalTwo.publicKey.toString('hex'),
];

const signMultiSigCmdArgs = (unsignedTransaction: string, passphraseToSign: string): string[] => {
Expand Down Expand Up @@ -209,16 +209,16 @@ describe('transaction:sign command', () => {

describe('sign multi signature registration transaction', () => {
const messageForRegistration = {
address: accountsForMultisignature.targetAccount.publicKey,
address: legacyAccounts.targetAccount.address,
nonce: BigInt(2),
numberOfSignatures: 4,
mandatoryKeys: [
accountsForMultisignature.mandatoryOne.publicKey,
accountsForMultisignature.mandatoryTwo.publicKey,
legacyAccounts.mandatoryOne.publicKey,
legacyAccounts.mandatoryTwo.publicKey,
].sort((k1, k2) => k1.compare(k2)),
optionalKeys: [
accountsForMultisignature.optionalOne.publicKey,
accountsForMultisignature.optionalTwo.publicKey,
legacyAccounts.optionalOne.publicKey,
legacyAccounts.optionalTwo.publicKey,
].sort((k1, k2) => k1.compare(k2)),
};

Expand All @@ -238,31 +238,31 @@ describe('transaction:sign command', () => {
MESSAGE_TAG_MULTISIG_REG,
networkIdentifier,
messageBytes,
accountsForMultisignature.mandatoryTwo.privateKey,
legacyAccounts.mandatoryTwo.privateKey,
);
decodedParams.signatures.push(sign1);

const sign2 = ed.signData(
MESSAGE_TAG_MULTISIG_REG,
networkIdentifier,
messageBytes,
accountsForMultisignature.mandatoryOne.privateKey,
legacyAccounts.mandatoryOne.privateKey,
);
decodedParams.signatures.push(sign2);

const sign3 = ed.signData(
MESSAGE_TAG_MULTISIG_REG,
networkIdentifier,
messageBytes,
accountsForMultisignature.optionalOne.privateKey,
legacyAccounts.optionalOne.privateKey,
);
decodedParams.signatures.push(sign3);

const sign4 = ed.signData(
MESSAGE_TAG_MULTISIG_REG,
networkIdentifier,
messageBytes,
accountsForMultisignature.optionalTwo.privateKey,
legacyAccounts.optionalTwo.privateKey,
);
decodedParams.signatures.push(sign4);

Expand All @@ -271,35 +271,32 @@ describe('transaction:sign command', () => {
command: 'registerMultisignature',
nonce: BigInt('2'),
fee: BigInt('1500000000'),
senderPublicKey: accountsForMultisignature.targetAccount.publicKey,
senderPublicKey: legacyAccounts.targetAccount.publicKey,
params: codec.encode(registerMultisignatureParamsSchema, decodedParams),
signatures: [],
};

const unsignedMultiSigTransaction = codec.encode(transactionSchema, msTx);
const TAG_TRANSACTION = 'LSK_TX_';
const decodedBaseTransaction: any = codec.decode(
transactionSchema,
unsignedMultiSigTransaction,
);
const signatureSender = ed.signDataWithPrivateKey(
TAG_TRANSACTION,
networkIdentifier,
unsignedMultiSigTransaction,
accountsForMultisignature.targetAccount.privateKey,
legacyAccounts.targetAccount.privateKey,
);
const signedTransaction = codec.encode(transactionSchema, {
...decodedBaseTransaction,
...msTx,
signatures: [signatureSender],
});

it('should return signed transaction for sender account', async () => {
await SignCommandExtended.run(
[
unsignedMultiSigTransaction.toString('hex'),
`--passphrase=${accountsForMultisignature.targetAccount.passphrase}`,
`--passphrase=${legacyAccounts.targetAccount.passphrase}`,
`--chain-id=${chainIDStr}`,
'--offline',
'--key-derivation-path=legacy',
],
config,
);
Expand All @@ -313,46 +310,82 @@ describe('transaction:sign command', () => {
await SignCommandExtended.run(
[
unsignedMultiSigTransaction.toString('hex'),
`--passphrase=${accountsForMultisignature.targetAccount.passphrase}`,
`--passphrase=${legacyAccounts.targetAccount.passphrase}`,
`--chain-id=${chainIDStr}`,
'--offline',
'--json',
'--key-derivation-path=legacy',
],
config,
);
expect(SignCommandExtended.prototype.printJSON).toHaveBeenCalledTimes(2);
expect(SignCommandExtended.prototype.printJSON).toHaveBeenCalledWith(undefined, {
transaction: signedTransaction.toString('hex'),
});
});

it('should return a signed transaction when using accounts with modern key path derivation', async () => {
const params = {
numberOfSignatures: 3,
mandatoryKeys: [modernAccounts[0].publicKey, modernAccounts[1].publicKey].sort(
(key1, key2) => key1.compare(key2),
),
optionalKeys: [modernAccounts[2].publicKey],
signatures: [] as Buffer[],
};

const message = {
address: modernAccounts[0].address,
nonce: BigInt(3),
numberOfSignatures: params.numberOfSignatures,
mandatoryKeys: params.mandatoryKeys,
optionalKeys: params.optionalKeys,
};
const messageEncoded = codec.encode(multisigRegMsgSchema, message);

for (let i = 0; i < params.numberOfSignatures; i += 1) {
const messageSignature = ed.signData(
MESSAGE_TAG_MULTISIG_REG,
networkIdentifier,
messageEncoded,
modernAccounts[i].privateKey,
);
params.signatures.push(messageSignature);
}

const rawTx = {
module: 'auth',
command: 'registerMultisignature',
nonce: BigInt(3),
fee: BigInt('1000000000'),
senderPublicKey: modernAccounts[0].publicKey,
params: codec.encode(registerMultisignatureParamsSchema, params),
signatures: [] as Buffer[],
};
const unsignedTx = codec.encode(transactionSchema, rawTx);

const txSignature = ed.signDataWithPrivateKey(
TAG_TRANSACTION,
networkIdentifier,
unsignedTx,
modernAccounts[0].privateKey,
);
rawTx.signatures = [txSignature];

const signedTx = codec.encode(transactionSchema, rawTx);

await SignCommandExtended.run(
[
unsignedTx.toString('hex'),
`--passphrase=${modernAccounts[0].passphrase}`,
`--chain-id=${chainIDStr}`,
'--offline',
],
config,
);
expect(SignCommandExtended.prototype.printJSON).toHaveBeenCalledTimes(1);
expect(SignCommandExtended.prototype.printJSON).toHaveBeenCalledWith(undefined, {
transaction: {
id: expect.any(String),
module: 'auth',
command: 'registerMultisignature',
nonce: '2',
fee: '1500000000',
senderPublicKey: '0b211fce4b615083701cb8a8c99407e464b2f9aa4f367095322de1b77e5fcfbe',
params: {
numberOfSignatures: 4,
mandatoryKeys: [
accountsForMultisignature.mandatoryTwo.publicKey.toString('hex'),
accountsForMultisignature.mandatoryOne.publicKey.toString('hex'),
],
optionalKeys: [
accountsForMultisignature.optionalOne.publicKey.toString('hex'),
accountsForMultisignature.optionalTwo.publicKey.toString('hex'),
],
signatures: [
'b782005d5b55f390bf1e10ed88a076e448124a9be882414977876a3fd134a2047e14c6aeb8b58915c4bb51ba3490b7643710432019dac171467bad214e0ced07',
'082501104f42f0c8a47291b51630e6869aebb653fe224dca74a9027fc8494f5a2d6537bb1bc1b94d8b1a1af00d14fb50b9e0cd36e3bfb8ea3265c5e956064c0c',
'aa72f005da46a8782ff5edd5b2546baf6a8bb0d67284c4e7631abf6629feba65854203d961fb4c2a31c12c525c0b7c4f9c8a47118789f93cf6ebd158b294c707',
'6a3a179fbb076ca8e8c3e42e1e6d2ae447f39ae413df083e14685fd02a562ab3fac085080709387e3ecbf305b58b4f1957ea497e9e7834d55a8a71a946f5050d',
],
},
signatures: [
'06f78300e3fae75408e3526fd08de832e953e0d252f441f57003a1f4ad84f350edf0a20a641f43abe7fb81121306750e23effd31f6f04fbd1156a9088585440c',
],
},
transaction: signedTx.toString('hex'),
});
});
});
Expand Down Expand Up @@ -522,16 +555,16 @@ describe('transaction:sign command', () => {
// eslint-disable-next-line jest/no-disabled-tests
describe('sign multi signature registration transaction', () => {
const messageForRegistration = {
address: accountsForMultisignature.targetAccount.publicKey,
address: legacyAccounts.targetAccount.publicKey,
nonce: BigInt(2),
numberOfSignatures: 4,
mandatoryKeys: [
accountsForMultisignature.mandatoryOne.publicKey,
accountsForMultisignature.mandatoryTwo.publicKey,
legacyAccounts.mandatoryOne.publicKey,
legacyAccounts.mandatoryTwo.publicKey,
].sort((k1, k2) => k1.compare(k2)),
optionalKeys: [
accountsForMultisignature.optionalOne.publicKey,
accountsForMultisignature.optionalTwo.publicKey,
legacyAccounts.optionalOne.publicKey,
legacyAccounts.optionalTwo.publicKey,
].sort((k1, k2) => k1.compare(k2)),
};

Expand All @@ -551,31 +584,31 @@ describe('transaction:sign command', () => {
MESSAGE_TAG_MULTISIG_REG,
chainID,
messageBytes,
accountsForMultisignature.mandatoryTwo.privateKey,
legacyAccounts.mandatoryTwo.privateKey,
);
decodedParams.signatures.push(sign1);

const sign2 = ed.signData(
MESSAGE_TAG_MULTISIG_REG,
chainID,
messageBytes,
accountsForMultisignature.mandatoryOne.privateKey,
legacyAccounts.mandatoryOne.privateKey,
);
decodedParams.signatures.push(sign2);

const sign3 = ed.signData(
MESSAGE_TAG_MULTISIG_REG,
chainID,
messageBytes,
accountsForMultisignature.optionalOne.privateKey,
legacyAccounts.optionalOne.privateKey,
);
decodedParams.signatures.push(sign3);

const sign4 = ed.signData(
MESSAGE_TAG_MULTISIG_REG,
chainID,
messageBytes,
accountsForMultisignature.optionalTwo.privateKey,
legacyAccounts.optionalTwo.privateKey,
);
decodedParams.signatures.push(sign4);

Expand All @@ -584,7 +617,7 @@ describe('transaction:sign command', () => {
command: 'registerMultisignature',
nonce: BigInt('2'),
fee: BigInt('1500000000'),
senderPublicKey: accountsForMultisignature.targetAccount.publicKey,
senderPublicKey: legacyAccounts.targetAccount.publicKey,
params: codec.encode(registerMultisignatureParamsSchema, decodedParams),
signatures: [],
};
Expand All @@ -599,7 +632,7 @@ describe('transaction:sign command', () => {
command: 'registerMultisignature',
nonce: '2',
fee: '1500000000',
senderPublicKey: accountsForMultisignature.targetAccount.publicKey.toString('hex'),
senderPublicKey: legacyAccounts.targetAccount.publicKey.toString('hex'),
params: { ...decodedParamsJSON },
signatures: [],
};
Expand All @@ -614,7 +647,7 @@ describe('transaction:sign command', () => {
TAG_TRANSACTION,
chainID,
unsignedMultiSigTransaction,
accountsForMultisignature.targetAccount.privateKey,
legacyAccounts.targetAccount.privateKey,
);
const signedTransaction = codec.encode(transactionSchema, {
...decodedBaseTransaction,
Expand All @@ -632,7 +665,7 @@ describe('transaction:sign command', () => {
await SignCommandExtended.run(
[
unsignedMultiSigTransaction.toString('hex'),
`--passphrase=${accountsForMultisignature.targetAccount.passphrase}`,
`--passphrase=${legacyAccounts.targetAccount.passphrase}`,
],
config,
);
Expand All @@ -657,7 +690,7 @@ describe('transaction:sign command', () => {
await SignCommandExtended.run(
[
unsignedMultiSigTransaction.toString('hex'),
`--passphrase=${accountsForMultisignature.targetAccount.passphrase}`,
`--passphrase=${legacyAccounts.targetAccount.passphrase}`,
'--json',
],
config,
Expand Down
Loading