Skip to content

Commit 69438d1

Browse files
authored
Merge pull request #572 from multiversx/TOOL-460-add-verify-message-and-verify-signature
Add verifyMessage and verifyTransaction on account
2 parents aa0dce0 + 4f315d6 commit 69438d1

File tree

12 files changed

+123
-79
lines changed

12 files changed

+123
-79
lines changed

src/accounts/account.spec.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,4 +91,42 @@ describe("test account methods", function () {
9191
"561bc58f1dc6b10de208b2d2c22c9a474ea5e8cabb59c3d3ce06bbda21cc46454aa71a85d5a60442bd7784effa2e062fcb8fb421c521f898abf7f5ec165e5d0f",
9292
);
9393
});
94+
95+
it("should verify message", async function () {
96+
const message = new Message({
97+
data: new Uint8Array(Buffer.from("hello")),
98+
});
99+
100+
const account = Account.newFromMnemonic(DUMMY_MNEMONIC);
101+
message.signature = await account.signMessage(message);
102+
const isVerified = await account.verifyMessageSignature(message, message.signature);
103+
104+
assert.isTrue(isVerified);
105+
});
106+
107+
it("should sign and verify transaction", async function () {
108+
const transaction = new Transaction({
109+
nonce: 89n,
110+
value: 0n,
111+
receiver: Address.newFromBech32("erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx"),
112+
sender: Address.newFromBech32("erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"),
113+
gasPrice: 1000000000n,
114+
gasLimit: 50000n,
115+
data: new Uint8Array(),
116+
chainID: "local-testnet",
117+
version: 1,
118+
options: 0,
119+
});
120+
121+
const account = Account.newFromMnemonic(DUMMY_MNEMONIC);
122+
transaction.signature = await account.signTransaction(transaction);
123+
124+
assert.equal(
125+
Buffer.from(transaction.signature).toString("hex"),
126+
"b56769014f2bdc5cf9fc4a05356807d71fcf8775c819b0f1b0964625b679c918ffa64862313bfef86f99b38cb84fcdb16fa33ad6eb565276616723405cd8f109",
127+
);
128+
129+
const isVerified = await account.verifyTransactionSignature(transaction, transaction.signature);
130+
assert.isTrue(isVerified);
131+
});
94132
});

src/accounts/account.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ export class Account implements IAccount {
9696
return this.secretKey.sign(data);
9797
}
9898

99-
verify(data: Uint8Array, signature: Uint8Array): boolean {
99+
async verify(data: Uint8Array, signature: Uint8Array): Promise<boolean> {
100100
return this.publicKey.verify(data, signature);
101101
}
102102

@@ -106,12 +106,24 @@ export class Account implements IAccount {
106106
return this.secretKey.sign(serializedTransaction);
107107
}
108108

109+
async verifyTransactionSignature(transaction: Transaction, signature: Uint8Array): Promise<boolean> {
110+
const transactionComputer = new TransactionComputer();
111+
const serializedTransaction = transactionComputer.computeBytesForVerifying(transaction);
112+
return this.publicKey.verify(serializedTransaction, signature);
113+
}
114+
109115
async signMessage(message: Message): Promise<Uint8Array> {
110116
const messageComputer = new MessageComputer();
111117
const serializedMessage = messageComputer.computeBytesForSigning(message);
112118
return this.secretKey.sign(serializedMessage);
113119
}
114120

121+
async verifyMessageSignature(message: Message, signature: Uint8Array): Promise<boolean> {
122+
const messageComputer = new MessageComputer();
123+
const serializedMessage = messageComputer.computeBytesForVerifying(message);
124+
return this.publicKey.verify(serializedMessage, signature);
125+
}
126+
115127
getNonceThenIncrement(): bigint {
116128
let nonce = this.nonce;
117129
this.nonce = this.nonce + 1n;

src/accounts/interfaces.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
1+
import { Message, Transaction } from "../core";
12
import { Address } from "../core/address";
23

34
export interface IAccount {
45
readonly address: Address;
56

67
sign(data: Uint8Array): Promise<Uint8Array>;
8+
signTransaction(transaction: Transaction): Promise<Uint8Array>;
9+
verifyTransactionSignature(transaction: Transaction, signature: Uint8Array): Promise<boolean>;
10+
signMessage(message: Message): Promise<Uint8Array>;
11+
verifyMessageSignature(message: Message, signature: Uint8Array): Promise<boolean>;
712
}

src/core/message.spec.ts

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import { assert } from "chai";
22
import { Account } from "../accounts";
33
import { getTestWalletsPath } from "../testutils/utils";
4-
import { UserVerifier } from "../wallet";
54
import { DEFAULT_MESSAGE_VERSION, SDK_JS_SIGNER, UNKNOWN_SIGNER } from "./constants";
65
import { Message, MessageComputer } from "./message";
76

@@ -60,12 +59,8 @@ describe("test message", () => {
6059
assert.deepEqual(unpackedMessage.version, message.version);
6160
assert.deepEqual(unpackedMessage.signer, message.signer);
6261

63-
const verifier = UserVerifier.fromAddress(alice.address);
64-
const isValid = verifier.verify(
65-
Buffer.from(messageComputer.computeBytesForVerifying(unpackedMessage)),
66-
Buffer.from(unpackedMessage.signature!),
67-
);
68-
assert.equal(isValid, true);
62+
const isValid = await alice.verifyMessageSignature(unpackedMessage, Buffer.from(unpackedMessage.signature!));
63+
assert.isTrue(isValid);
6964
});
7065

7166
it("should unpack legacy message", async () => {

src/core/transaction.spec.ts

Lines changed: 4 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import { assert } from "chai";
33
import { Account } from "../accounts";
44
import { ProtoSerializer } from "../proto";
55
import { getTestWalletsPath } from "../testutils/utils";
6-
import { UserPublicKey, UserVerifier } from "../wallet";
76
import { Address } from "./address";
87
import { MIN_TRANSACTION_VERSION_THAT_SUPPORTS_OPTIONS, TRANSACTION_OPTIONS_DEFAULT } from "./constants";
98
import { INetworkConfig } from "./interface";
@@ -692,17 +691,9 @@ describe("test transaction", async () => {
692691

693692
transaction.signature = await alice.signTransaction(transaction);
694693

695-
const userVerifier = new UserVerifier(new UserPublicKey(alice.address.getPublicKey()));
696-
const isSignedByAlice = userVerifier.verify(
697-
transactionComputer.computeBytesForVerifying(transaction),
698-
transaction.signature,
699-
);
694+
const isSignedByAlice = await alice.verifyTransactionSignature(transaction, transaction.signature);
700695

701-
const wrongVerifier = new UserVerifier(new UserPublicKey(bob.address.getPublicKey()));
702-
const isSignedByBob = wrongVerifier.verify(
703-
transactionComputer.computeBytesForVerifying(transaction),
704-
transaction.signature,
705-
);
696+
const isSignedByBob = await bob.verifyTransactionSignature(transaction, transaction.signature);
706697

707698
assert.equal(isSignedByAlice, true);
708699
assert.equal(isSignedByBob, false);
@@ -721,17 +712,9 @@ describe("test transaction", async () => {
721712

722713
transaction.signature = await alice.sign(transactionComputer.computeHashForSigning(transaction));
723714

724-
const userVerifier = new UserVerifier(alice.publicKey);
725-
const isSignedByAlice = userVerifier.verify(
726-
transactionComputer.computeBytesForVerifying(transaction),
727-
transaction.signature,
728-
);
715+
const isSignedByAlice = await alice.verifyTransactionSignature(transaction, transaction.signature);
729716

730-
const wrongVerifier = new UserVerifier(new UserPublicKey(bob.address.getPublicKey()));
731-
const isSignedByBob = wrongVerifier.verify(
732-
transactionComputer.computeBytesForVerifying(transaction),
733-
transaction.signature,
734-
);
717+
const isSignedByBob = await bob.verifyTransactionSignature(transaction, transaction.signature);
735718
assert.equal(isSignedByAlice, true);
736719
assert.equal(isSignedByBob, false);
737720
});

src/entrypoints/entrypoints.ts

Lines changed: 7 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,8 @@ import { Account } from "../accounts";
44
import { IAccount } from "../accounts/interfaces";
55
import { Address } from "../core/address";
66
import { ErrInvalidNetworkProviderKind } from "../core/errors";
7-
import { Message, MessageComputer } from "../core/message";
7+
import { Message } from "../core/message";
88
import { Transaction } from "../core/transaction";
9-
import { TransactionComputer } from "../core/transactionComputer";
109
import { TransactionOnNetwork } from "../core/transactionOnNetwork";
1110
import { TransactionsFactoryConfig } from "../core/transactionsFactoryConfig";
1211
import { TransactionWatcher } from "../core/transactionWatcher";
@@ -20,7 +19,7 @@ import { SmartContractController } from "../smartContracts/smartContractControll
2019
import { TokenManagementController, TokenManagementTransactionsFactory } from "../tokenManagement";
2120
import { TransferTransactionsFactory } from "../transfers";
2221
import { TransfersController } from "../transfers/transfersControllers";
23-
import { UserSecretKey, UserVerifier } from "../wallet";
22+
import { UserSecretKey } from "../wallet";
2423
import { DevnetEntrypointConfig, MainnetEntrypointConfig, TestnetEntrypointConfig } from "./config";
2524

2625
class NetworkEntrypoint {
@@ -49,17 +48,14 @@ class NetworkEntrypoint {
4948
}
5049

5150
async signTransaction(transaction: Transaction, account: IAccount): Promise<void> {
52-
const txComputer = new TransactionComputer();
53-
transaction.signature = await account.sign(txComputer.computeBytesForSigning(transaction));
51+
transaction.signature = await account.signTransaction(transaction);
5452
}
5553

56-
verifyTransactionSignature(transaction: Transaction): boolean {
57-
const verifier = UserVerifier.fromAddress(transaction.sender);
58-
const txComputer = new TransactionComputer();
59-
return verifier.verify(txComputer.computeBytesForVerifying(transaction), transaction.signature);
54+
async verifyTransactionSignature(transaction: Transaction, account: IAccount): Promise<boolean> {
55+
return await account.verifyTransactionSignature(transaction, transaction.signature);
6056
}
6157

62-
verifyMessageSignature(message: Message): boolean {
58+
async verifyMessageSignature(message: Message, account: IAccount): Promise<boolean> {
6359
if (!message.address) {
6460
throw new Error("`address` property of Message is not set");
6561
}
@@ -68,9 +64,7 @@ class NetworkEntrypoint {
6864
throw new Error("`signature` property of Message is not set");
6965
}
7066

71-
const verifier = UserVerifier.fromAddress(message.address);
72-
const messageComputer = new MessageComputer();
73-
return verifier.verify(messageComputer.computeBytesForVerifying(message), message.signature);
67+
return await account.verifyMessageSignature(message, message.signature);
7468
}
7569

7670
async recallAccountNonce(address: Address): Promise<bigint> {

src/wallet/crypto/pubkeyDecryptor.ts

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,26 @@
11
import crypto from "crypto";
2-
import nacl from "tweetnacl";
32
import ed2curve from "ed2curve";
4-
import { X25519EncryptedData } from "./x25519EncryptedData";
3+
import nacl from "tweetnacl";
54
import { UserPublicKey, UserSecretKey } from "../userKeys";
5+
import { X25519EncryptedData } from "./x25519EncryptedData";
66

77
export class PubkeyDecryptor {
8-
static decrypt(data: X25519EncryptedData, decryptorSecretKey: UserSecretKey): Buffer {
9-
const ciphertext = Buffer.from(data.ciphertext, 'hex');
10-
const edhPubKey = Buffer.from(data.identities.ephemeralPubKey, 'hex');
11-
const originatorPubKeyBuffer = Buffer.from(data.identities.originatorPubKey, 'hex');
8+
static async decrypt(data: X25519EncryptedData, decryptorSecretKey: UserSecretKey): Promise<Buffer> {
9+
const ciphertext = Buffer.from(data.ciphertext, "hex");
10+
const edhPubKey = Buffer.from(data.identities.ephemeralPubKey, "hex");
11+
const originatorPubKeyBuffer = Buffer.from(data.identities.originatorPubKey, "hex");
1212
const originatorPubKey = new UserPublicKey(originatorPubKeyBuffer);
1313

14-
const authMessage = crypto.createHash('sha256').update(
15-
Buffer.concat([ciphertext, edhPubKey])
16-
).digest();
14+
const authMessage = crypto
15+
.createHash("sha256")
16+
.update(Buffer.concat([ciphertext, edhPubKey]))
17+
.digest();
1718

18-
if (!originatorPubKey.verify(authMessage, Buffer.from(data.mac, 'hex'))) {
19+
if (!(await originatorPubKey.verify(authMessage, Buffer.from(data.mac, "hex")))) {
1920
throw new Error("Invalid authentication for encrypted message originator");
2021
}
2122

22-
const nonce = Buffer.from(data.nonce, 'hex');
23+
const nonce = Buffer.from(data.nonce, "hex");
2324
const x25519Secret = ed2curve.convertSecretKey(decryptorSecretKey.valueOf());
2425
const x25519EdhPubKey = ed2curve.convertPublicKey(edhPubKey);
2526
if (x25519EdhPubKey === null) {

src/wallet/crypto/pubkeyEncrypt.spec.ts

Lines changed: 29 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -22,24 +22,39 @@ describe("test address", () => {
2222
);
2323
});
2424

25-
it("encrypts/decrypts", () => {
26-
const decryptedData = PubkeyDecryptor.decrypt(encryptedDataOfAliceForBob, new UserSecretKey(bob.secretKey));
25+
it("encrypts/decrypts", async function () {
26+
const decryptedData = await PubkeyDecryptor.decrypt(
27+
encryptedDataOfAliceForBob,
28+
new UserSecretKey(bob.secretKey),
29+
);
2730
assert.equal(sensitiveData.toString("hex"), decryptedData.toString("hex"));
2831
});
2932

30-
it("fails for different originator", () => {
31-
encryptedDataOfAliceForBob.identities.originatorPubKey = carol.address.hex();
32-
assert.throws(
33-
() => PubkeyDecryptor.decrypt(encryptedDataOfAliceForBob, new UserSecretKey(bob.secretKey)),
34-
"Invalid authentication for encrypted message originator",
35-
);
33+
it("fails for different originator", async function () {
34+
encryptedDataOfAliceForBob.identities.originatorPubKey = carol.address.toHex();
35+
36+
try {
37+
await PubkeyDecryptor.decrypt(encryptedDataOfAliceForBob, new UserSecretKey(bob.secretKey));
38+
assert.fail("Invalid authentication for encrypted message originator");
39+
} catch (error) {
40+
assert(
41+
error.message.includes("Invalid authentication for encrypted message originator"),
42+
`Unexpected error message: ${error.message}`,
43+
);
44+
}
3645
});
3746

38-
it("fails for different DH public key", () => {
39-
encryptedDataOfAliceForBob.identities.ephemeralPubKey = carol.address.hex();
40-
assert.throws(
41-
() => PubkeyDecryptor.decrypt(encryptedDataOfAliceForBob, new UserSecretKey(bob.secretKey)),
42-
"Invalid authentication for encrypted message originator",
43-
);
47+
it("fails for different DH public key", async function () {
48+
encryptedDataOfAliceForBob.identities.ephemeralPubKey = carol.address.toHex();
49+
50+
try {
51+
await PubkeyDecryptor.decrypt(encryptedDataOfAliceForBob, new UserSecretKey(bob.secretKey));
52+
assert.fail("Expected an error but none was thrown");
53+
} catch (error) {
54+
assert(
55+
error.message.includes("Invalid authentication for encrypted message originator"),
56+
`Unexpected error message: ${error.message}`,
57+
);
58+
}
4459
});
4560
});

src/wallet/userKeys.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ export class UserPublicKey {
6868
this.buffer = Buffer.from(buffer);
6969
}
7070

71-
verify(data: Buffer | Uint8Array, signature: Buffer | Uint8Array): boolean {
71+
async verify(data: Buffer | Uint8Array, signature: Buffer | Uint8Array): Promise<boolean> {
7272
try {
7373
const ok = ed.sync.verify(new Uint8Array(signature), new Uint8Array(data), new Uint8Array(this.buffer));
7474
return ok;

src/wallet/userVerifier.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ export class UserVerifier {
2222
* @param signature the signature to be verified
2323
* @returns true if the signature is valid, false otherwise
2424
*/
25-
verify(data: Buffer | Uint8Array, signature: Buffer | Uint8Array): boolean {
25+
async verify(data: Buffer | Uint8Array, signature: Buffer | Uint8Array): Promise<boolean> {
2626
return this.publicKey.verify(data, signature);
2727
}
2828
}

0 commit comments

Comments
 (0)