diff --git a/CHANGELOG.md b/CHANGELOG.md index 53102dbef2..ee3cbba2d6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ All notable changes to this project will be documented in this file. The changelog format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). -## [1.0.4] - NEXY +## [1.0.4] - NEXT **Milestone**: Symbol Mainnet Package | Version | Link @@ -15,6 +15,8 @@ Client Library | v1.0.3 | [symbol-openapi-typescript-fetch-client](https://www. - fix: Upgraded Node to 12.22.1. - fix: Upgraded typescript to 4.5.4. +- fix: Improved message API. +- fix: EncryptedMessage payload wasn't reproducible. - fix: Upgraded RXJS to 7.4.0. ## [1.0.3] - 16-Nov-2021 diff --git a/src/core/crypto/Crypto.ts b/src/core/crypto/Crypto.ts index 39ae091155..14354e4697 100644 --- a/src/core/crypto/Crypto.ts +++ b/src/core/crypto/Crypto.ts @@ -107,19 +107,25 @@ export class Crypto { /** * Encode a message using AES-GCM algorithm * - * @param {string} senderPriv - A sender private key - * @param {string} recipientPub - A recipient public key - * @param {string} msg - A text message - * @param {boolean} isHexString - Is payload string a hexadecimal string (default = false) + * @param senderPriv - A sender private key + * @param recipientPub - A recipient public key + * @param msg - A text message + * @param isHexString - Is payload string a hexadecimal string (default = false) + * @param iv - the iv for unit testing, otherwise a random 12 byte array. + * * @return {string} - The encoded message */ - public static encode = (senderPriv: string, recipientPub: string, msg: string, isHexString = false): string => { + public static encode = ( + senderPriv: string, + recipientPub: string, + msg: string, + isHexString = false, + iv = Crypto.randomBytes(12), + ): string => { // Errors if (!senderPriv || !recipientPub || !msg) { throw new Error('Missing argument !'); } - // Processing - const iv = Crypto.randomBytes(12); const encoded = Crypto._encode(senderPriv, recipientPub, isHexString ? msg : convert.utf8ToHex(msg), iv); // Result return encoded; @@ -171,9 +177,8 @@ export class Crypto { try { const decoded = Crypto._decode(recipientPrivate, senderPublic, payloadBuffer, tagAndIv); return decoded.toUpperCase(); - } catch { - // To return empty string rather than error throwing if authentication failed - return ''; + } catch (e) { + throw new Error(`Cannot decrypt payload. Error: ${e.message}`); } }; @@ -183,7 +188,7 @@ export class Crypto { * * @return {Uint8Array} */ - public static randomBytes = (length: number): any => { + public static randomBytes = (length: number): Buffer => { return crypto.randomBytes(length); }; } diff --git a/src/core/format/Convert.ts b/src/core/format/Convert.ts index a7301859b4..1e2cd801b8 100644 --- a/src/core/format/Convert.ts +++ b/src/core/format/Convert.ts @@ -181,9 +181,26 @@ export class Convert { * @return {string} */ public static utf8ToHex = (input: string): string => { - return Buffer.from(input, 'utf-8').toString('hex').toUpperCase(); + return Convert.utf8ToBuffer(input).toString('hex').toUpperCase(); }; + /** + * Convert hex to UTF-8 + * @param {string} hex - an hex string + * @return {string} An UTF-8 string + */ + public static hexToUtf8 = (hex: string): string => { + return Buffer.from(hex, 'hex').toString(); + }; + /** + * Convert UTF-8 to buffer + * @param input - An UTF-8 string + * @return the buffer + */ + public static utf8ToBuffer(input: string): Buffer { + return Buffer.from(input, 'utf-8'); + } + /** * Convert UTF-8 string to Uint8Array * @param {string} input - An string with UTF-8 encoding @@ -266,4 +283,19 @@ export class Convert { } return value >>> 0; } + /** + * It concats a list of Uint8Array into a new one. + * + * @param arrays - the Uint8Array to concat. + */ + public static concat(...arrays: Uint8Array[]): Uint8Array { + const totalLength = arrays.reduce((acc, value) => acc + value.length, 0); + const result = new Uint8Array(totalLength); + let length = 0; + for (const array of arrays) { + result.set(array, length); + length += array.length; + } + return result; + } } diff --git a/src/model/message/EncryptedMessage.ts b/src/model/message/EncryptedMessage.ts index 5558a7bea8..92eb8f2cca 100644 --- a/src/model/message/EncryptedMessage.ts +++ b/src/model/message/EncryptedMessage.ts @@ -14,6 +14,8 @@ * limitations under the License. */ +import { GeneratorUtils } from 'catbuffer-typescript'; +import { Convert } from '../../core'; import { Crypto } from '../../core/crypto'; import { PublicAccount } from '../account'; import { Message } from './Message'; @@ -23,12 +25,16 @@ import { PlainMessage } from './PlainMessage'; /** * Encrypted Message model */ -export class EncryptedMessage extends Message { - public readonly recipientPublicAccount?: PublicAccount; +export class EncryptedMessage implements Message { + public readonly type = MessageType.EncryptedMessage; + public readonly payload: string; - constructor(payload: string, recipientPublicAccount?: PublicAccount) { - super(MessageType.EncryptedMessage, payload); - this.recipientPublicAccount = recipientPublicAccount; + /** + * @internal + * @param buffer the buffer. + */ + constructor(private readonly buffer: Uint8Array) { + this.payload = EncryptedMessage.getPayload(buffer); } /** @@ -36,13 +42,23 @@ export class EncryptedMessage extends Message { * @param message - Plain message to be encrypted * @param recipientPublicAccount - Recipient public account * @param privateKey - Sender private key - * @return {EncryptedMessage} + * @param iv - iv for encoding, for unit tests. + * @return The encrypted message. + */ + public static create(message: string, recipientPublicAccount: PublicAccount, privateKey: string, iv?: Buffer): EncryptedMessage { + const encryptedHex = Crypto.encode(privateKey, recipientPublicAccount.publicKey, message, false, iv).toUpperCase(); + return new EncryptedMessage(EncryptedMessage.createBuffer(encryptedHex)); + } + + /** + * + * @param encryptMessage - Encrypted message to be decrypted + * @param privateKey - Recipient private key + * @param recipientPublicAccount - Sender public account + * @return {PlainMessage} */ - public static create(message: string, recipientPublicAccount: PublicAccount, privateKey: string): EncryptedMessage { - return new EncryptedMessage( - Crypto.encode(privateKey, recipientPublicAccount.publicKey, message).toUpperCase(), - recipientPublicAccount, - ); + public static decrypt(encryptMessage: EncryptedMessage, privateKey: string, recipientPublicAccount: PublicAccount): PlainMessage { + return PlainMessage.create(Convert.hexToUtf8(Crypto.decode(privateKey, recipientPublicAccount.publicKey, encryptMessage.payload))); } /** @@ -54,17 +70,44 @@ export class EncryptedMessage extends Message { * @param payload */ public static createFromPayload(payload: string): EncryptedMessage { - return new EncryptedMessage(this.decodeHex(payload)); + return new EncryptedMessage(EncryptedMessage.createBuffer(payload)); } /** * - * @param encryptMessage - Encrypted message to be decrypted - * @param privateKey - Recipient private key - * @param recipientPublicAccount - Sender public account - * @return {PlainMessage} + * It creates the Plain message from a payload hex with the 00 prefix. + * + * @internal + */ + public static createFromBuilder(builder: Uint8Array): EncryptedMessage { + return new EncryptedMessage(builder); + } + + /** + * Create DTO object */ - public static decrypt(encryptMessage: EncryptedMessage, privateKey, recipientPublicAccount: PublicAccount): PlainMessage { - return new PlainMessage(this.decodeHex(Crypto.decode(privateKey, recipientPublicAccount.publicKey, encryptMessage.payload))); + toDTO(): string { + return Convert.uint8ToHex(this.toBuffer()); + } + + toBuffer(): Uint8Array { + return this.buffer; + } + + public static createBuffer(payload: string): Uint8Array { + if (!payload) { + return Uint8Array.of(); + } + const message = Convert.utf8ToHex(payload); + const payloadBuffer = Convert.hexToUint8(message); + const typeBuffer = GeneratorUtils.uintToBuffer(MessageType.EncryptedMessage, 1); + return GeneratorUtils.concatTypedArrays(typeBuffer, payloadBuffer); + } + + public static getPayload(buffer: Uint8Array): string { + if (!buffer.length) { + return ''; + } + return Convert.uint8ToUtf8(buffer.slice(1)); } } diff --git a/src/model/message/Message.ts b/src/model/message/Message.ts index 8c3589240f..ea7c715628 100644 --- a/src/model/message/Message.ts +++ b/src/model/message/Message.ts @@ -15,51 +15,29 @@ import { Convert } from '../../core/format/Convert'; * limitations under the License. */ -import { Convert } from '../../core/format'; import { MessageType } from './MessageType'; /** * An abstract message class that serves as the base class of all message types. */ -export abstract class Message { +export interface Message { /** - * @internal - * @param hex - * @returns {string} + * The buffer to be used when serializing a transaction */ - public static decodeHex(hex: string): string { - return Buffer.from(hex, 'hex').toString(); - } + toBuffer(): Uint8Array; /** - * @internal - * @param type - * @param payload + * Create DTO object */ - constructor( - /** - * Message type - */ - public readonly type: MessageType, - /** - * Message payload, it could be the message hex, encryped text or plain text depending on the message type. - */ - public readonly payload: string, - ) {} + toDTO(): string; /** - * Create DTO object + * validate if the content is correct + */ + readonly type: MessageType; + + /** + * Payload without type prefix. */ - toDTO(): string { - if (!this.payload) { - return ''; - } - if (this.type === MessageType.PersistentHarvestingDelegationMessage) { - return this.payload; - } - if (this.type === MessageType.RawMessage) { - return this.payload; - } - return this.type.toString(16).padStart(2, '0').toUpperCase() + Convert.utf8ToHex(this.payload); - } + readonly payload: string; } diff --git a/src/model/message/MessageFactory.ts b/src/model/message/MessageFactory.ts index a7efbbab03..1d9357cfbd 100644 --- a/src/model/message/MessageFactory.ts +++ b/src/model/message/MessageFactory.ts @@ -34,31 +34,32 @@ export class MessageFactory { * @param payload the payload as byte array */ public static createMessageFromBuffer(payload?: Uint8Array): Message { - return this.createMessageFromHex(payload ? Convert.uint8ToHex(payload) : undefined); - } - /** - * It creates a message from the hex payload - * @param payload the payload as hex - */ - public static createMessageFromHex(payload?: string): Message { if (!payload || !payload.length) { - return new RawMessage(''); + return RawMessage.create(Uint8Array.of()); } - const upperCasePayload = payload.toUpperCase(); + const messageType = payload[0]; + const upperCasePayload = Convert.uint8ToHex(payload).toUpperCase(); if ( upperCasePayload.length == PersistentHarvestingDelegationMessage.HEX_PAYLOAD_SIZE && upperCasePayload.startsWith(MessageMarker.PersistentDelegationUnlock) ) { return PersistentHarvestingDelegationMessage.createFromPayload(upperCasePayload); } - const messageType = Convert.hexToUint8(upperCasePayload)[0]; + switch (messageType) { case MessageType.PlainMessage: - return PlainMessage.createFromPayload(upperCasePayload.substring(2)); + return PlainMessage.createFromBuilder(payload); case MessageType.EncryptedMessage: - return EncryptedMessage.createFromPayload(upperCasePayload.substring(2)); + return EncryptedMessage.createFromBuilder(payload); } - return new RawMessage(upperCasePayload); + return RawMessage.create(payload); + } + /** + * It creates a message from the hex payload + * @param payload the payload as hex + */ + public static createMessageFromHex(payload?: string): Message { + return MessageFactory.createMessageFromBuffer(payload ? Convert.hexToUint8(payload) : undefined); } } diff --git a/src/model/message/PersistentHarvestingDelegationMessage.ts b/src/model/message/PersistentHarvestingDelegationMessage.ts index 18c6740ef9..dbe93edde9 100644 --- a/src/model/message/PersistentHarvestingDelegationMessage.ts +++ b/src/model/message/PersistentHarvestingDelegationMessage.ts @@ -22,11 +22,15 @@ import { Message } from './Message'; import { MessageMarker } from './MessageMarker'; import { MessageType } from './MessageType'; -export class PersistentHarvestingDelegationMessage extends Message { +export class PersistentHarvestingDelegationMessage implements Message { + public type = MessageType.PersistentHarvestingDelegationMessage; public static readonly HEX_PAYLOAD_SIZE = 264; - constructor(payload: string) { - super(MessageType.PersistentHarvestingDelegationMessage, payload.toUpperCase()); + /** + * @internal + * @param payload + */ + constructor(public readonly payload: string) { if (!Convert.isHexString(payload)) { throw Error('Payload format is not valid hexadecimal string'); } @@ -86,4 +90,15 @@ export class PersistentHarvestingDelegationMessage extends Message { const decrypted = Crypto.decode(privateKey, ephemeralPublicKey, payload); return decrypted.toUpperCase(); } + + /** + * Create DTO object + */ + toDTO(): string { + return this.payload; + } + + toBuffer(): Uint8Array { + return Convert.hexToUint8(this.payload); + } } diff --git a/src/model/message/PlainMessage.ts b/src/model/message/PlainMessage.ts index 4611b7831f..79627fe0af 100644 --- a/src/model/message/PlainMessage.ts +++ b/src/model/message/PlainMessage.ts @@ -14,38 +14,47 @@ * limitations under the License. */ +import { Convert } from '../../core'; import { Message } from './Message'; import { MessageType } from './MessageType'; /** * The plain message model defines a plain string. When sending it to the network we transform the payload to hex-string. */ -export class PlainMessage extends Message { +export class PlainMessage implements Message { + public static readonly TYPE = MessageType.PlainMessage; + public readonly type = PlainMessage.TYPE; + public readonly payload: string; + + /** + * @internal + * @param builder + */ + constructor(private readonly builder: Uint8Array) { + this.payload = Convert.uint8ToUtf8(builder.slice(1)); + } /** * Create plain message object. * @returns PlainMessage */ public static create(message: string): PlainMessage { - return new PlainMessage(message); + return new PlainMessage(Convert.concat(Uint8Array.of(this.TYPE), Convert.utf8ToBuffer(message))); } /** * - * It creates the Plain message from a payload hex without the 00 prefix. - * - * The 00 prefix will be attached to the final payload. + * It creates the Plain message from a payload hex with the 00 prefix. * * @internal */ - public static createFromPayload(payload: string): PlainMessage { - return new PlainMessage(this.decodeHex(payload)); + public static createFromBuilder(builder: Uint8Array): PlainMessage { + return new PlainMessage(builder); } - /** - * @internal - * @param payload - */ - constructor(payload: string) { - super(MessageType.PlainMessage, payload); + toBuffer(): Uint8Array { + return this.builder; + } + toDTO(): string { + return Convert.uint8ToHex(this.builder); } } diff --git a/src/model/message/RawMessage.ts b/src/model/message/RawMessage.ts index e2264f087c..6772c97a2a 100644 --- a/src/model/message/RawMessage.ts +++ b/src/model/message/RawMessage.ts @@ -14,26 +14,35 @@ * limitations under the License. */ -import { Convert } from '../../core/format'; +import { Convert } from '../../core'; import { Message } from './Message'; import { MessageType } from './MessageType'; /** * The a raw message that doesn't assume any prefix. */ -export class RawMessage extends Message { +export class RawMessage implements Message { + public readonly type = MessageType.RawMessage; + public readonly payload: string; /** - * Create plain message object. - * @returns PlainMessage + * @internal + * @param buffer */ - public static create(payload: Uint8Array): RawMessage { - return new RawMessage(Convert.uint8ToHex(payload)); + private constructor(private readonly buffer: Uint8Array) { + this.payload = Convert.uint8ToHex(buffer); } /** - * @internal - * @param payload + * Create plain message object. + * @returns PlainMessage */ - constructor(payload: string) { - super(MessageType.RawMessage, payload); + public static create(buffer: Uint8Array): RawMessage { + return new RawMessage(buffer); + } + toBuffer(): Uint8Array { + return this.buffer; + } + + toDTO(): string { + return this.payload; } } diff --git a/src/model/transaction/TransferTransaction.ts b/src/model/transaction/TransferTransaction.ts index d25879a959..24106d87bc 100644 --- a/src/model/transaction/TransferTransaction.ts +++ b/src/model/transaction/TransferTransaction.ts @@ -18,7 +18,6 @@ import { AmountDto, EmbeddedTransactionBuilder, EmbeddedTransferTransactionBuilder, - GeneratorUtils, TimestampDto, TransactionBuilder, TransferTransactionBuilder, @@ -159,7 +158,7 @@ export class TransferTransaction extends Transaction { if (this.message?.type === MessageType.PersistentHarvestingDelegationMessage) { if (this.mosaics.length > 0) { throw new Error('PersistentDelegationRequestTransaction should be created without Mosaic'); - } else if (!/^[0-9a-fA-F]{264}$/.test(this.message.payload)) { + } else if (!/^[0-9a-fA-F]{264}$/.test(this.message.toDTO())) { throw new Error('PersistentDelegationRequestTransaction message is invalid'); } } @@ -199,18 +198,10 @@ export class TransferTransaction extends Transaction { * @returns {Uint8Array} */ public getMessageBuffer(): Uint8Array { - if (!this.message || !this.message.payload) { + if (!this.message) { return Uint8Array.of(); } - const messgeHex = - this.message.type === MessageType.PersistentHarvestingDelegationMessage - ? this.message.payload - : Convert.utf8ToHex(this.message.payload); - const payloadBuffer = Convert.hexToUint8(messgeHex); - const typeBuffer = GeneratorUtils.uintToBuffer(this.message.type, 1); - return this.message.type === MessageType.PersistentHarvestingDelegationMessage || !this.message.payload - ? payloadBuffer - : GeneratorUtils.concatTypedArrays(typeBuffer, payloadBuffer); + return this.message.toBuffer(); } /** diff --git a/test/core/crypto/crypto.spec.ts b/test/core/crypto/crypto.spec.ts index 0a0e2c9a32..8a6f4417ca 100644 --- a/test/core/crypto/crypto.spec.ts +++ b/test/core/crypto/crypto.spec.ts @@ -37,10 +37,10 @@ describe('crypto tests', () => { const encryptedMessage = Crypto.encode(Convert.uint8ToHex(sender.privateKey), Convert.uint8ToHex(recipient.publicKey), message); const senderPublic = '57F7DA205008026C776CB6AED843393F04CD458E0AA2D9F1D5F31A402072B2D6'; const recipientPriv = '57F7DA205008026C776CB6AED843393F04CD458E0AA2D9F1D5F31A402072B2D6'; - const expectedMessage = 'NEM is awesome !'; - const decrypted = Crypto.decode(recipientPriv, senderPublic, encryptedMessage); - expect(decrypted).not.equal(convert.utf8ToHex(expectedMessage)); + expect(() => Crypto.decode(recipientPriv, senderPublic, encryptedMessage)).to.throw( + 'Cannot decrypt payload. Error: Unsupported state or unable to authenticate data', + ); }); describe('Encode & decode message edge-cases', () => { diff --git a/test/core/format/Convert.spec.ts b/test/core/format/Convert.spec.ts index d4a78650d5..94dfb23ffe 100644 --- a/test/core/format/Convert.spec.ts +++ b/test/core/format/Convert.spec.ts @@ -428,6 +428,19 @@ describe('convert', () => { expect(uint.length).to.be.equal(actual.length); expect(convert.uint8ToHex(uint)).to.be.equal('74657374'); }); + it('should convert hex string to utf 8', () => { + const hex = '746573742D6D657373616765'; + const plainText = 'test-message'; + expect(convert.hexToUtf8(hex)).to.be.equal(plainText); + expect(convert.utf8ToHex(plainText)).eq(hex); + }); + + it('should convert hex string to utf 8 (emoji)', () => { + const hex = 'F09F9880E38193E38293E381ABE381A1E381AFF09F9880'; + const plainText = '๐Ÿ˜€ใ“ใ‚“ใซใกใฏ๐Ÿ˜€'; + expect(convert.hexToUtf8(hex)).to.be.equal(plainText); + expect(convert.utf8ToHex(plainText)).eq(hex); + }); }); describe('uint8ToUtf8', () => { diff --git a/test/core/utils/Hashes.spec.ts b/test/core/utils/Hashes.spec.ts index 957e34118a..6ec4457a99 100644 --- a/test/core/utils/Hashes.spec.ts +++ b/test/core/utils/Hashes.spec.ts @@ -41,7 +41,7 @@ describe('Hashes', () => { it('Op_Hash_160', () => { const secretSeed = Crypto.randomBytes(20); - const hash256 = sha256(Buffer.from(secretSeed, 'hex')); + const hash256 = sha256(Buffer.from(secretSeed)); const expected = new ripemd160().update(Buffer.from(hash256, 'hex')).digest('hex'); const hash = LockHashUtils.Op_Hash_160(secretSeed); @@ -55,7 +55,7 @@ describe('Hashes', () => { const hashSHA3 = LockHashUtils.Hash(LockHashAlgorithm.Op_Sha3_256, secretSeed); expect(expectedSHA3.toUpperCase()).to.be.equal(hashSHA3); - const h256 = sha256(Buffer.from(secretSeed, 'hex')); + const h256 = sha256(Buffer.from(secretSeed)); const expected256 = sha256(Buffer.from(h256, 'hex')); const hash256 = LockHashUtils.Hash(LockHashAlgorithm.Op_Hash_256, secretSeed); expect(expected256.toUpperCase()).to.be.equal(hash256); diff --git a/test/core/utils/TransactionMapping.spec.ts b/test/core/utils/TransactionMapping.spec.ts index 5759b3b7fb..dfe53eadc0 100644 --- a/test/core/utils/TransactionMapping.spec.ts +++ b/test/core/utils/TransactionMapping.spec.ts @@ -1019,7 +1019,7 @@ describe('TransactionMapping - createFromDTO (Transaction.toJSON() feed)', () => Deadline.createFromDTO('555'), Address.createFromRawAddress('TATNE7Q5BITMUTRRN6IB4I7FLSDRDWZA37JGO5Q'), [NetworkCurrencyLocal.createRelative(100)], - new EncryptedMessage('12324556'), + EncryptedMessage.createFromPayload('12324556'), TestNetworkType, ); diff --git a/test/model/message/EncryptedMessage.spec.ts b/test/model/message/EncryptedMessage.spec.ts index e9c0fac9bf..9d80a9c65c 100644 --- a/test/model/message/EncryptedMessage.spec.ts +++ b/test/model/message/EncryptedMessage.spec.ts @@ -15,6 +15,7 @@ */ import { expect } from 'chai'; +import { Convert, MessageType } from '../../../src'; import { Account } from '../../../src/model/account'; import { EncryptedMessage } from '../../../src/model/message'; import { NetworkType } from '../../../src/model/network'; @@ -39,45 +40,78 @@ describe('EncryptedMessage', () => { ); }); - it('should create a encrypted message from a DTO', () => { - const encryptedMessage = EncryptedMessage.createFromPayload('test transaction'); - expect(encryptedMessage.payload).not.to.be.equal('test transaction'); // As DTO returns Hexed payload + it('constructor', () => { + const message = EncryptedMessage.createFromBuilder(Convert.hexToUint8('013132333234353536')); + console.log(message.payload); }); - it('should return encrypted message dto', () => { - const encryptedMessage = sender.encryptMessage('test transaction', recipient.publicAccount); + const plainMessageText = 'test transaction'; + const encryptedMessage = sender.encryptMessage(plainMessageText, recipient.publicAccount); + expect(encryptedMessage.toBuffer()).deep.eq(Convert.hexToUint8(encryptedMessage.toDTO())); const plainMessage = recipient.decryptMessage(encryptedMessage, sender.publicAccount); - expect(plainMessage.payload).to.be.equal('test transaction'); + expect(plainMessage.payload).to.be.equal(plainMessageText); }); it('should decrypt message from raw encrypted message payload', () => { const encryptedMessage = sender.encryptMessage('Testing simple transfer', recipient.publicAccount); const payload = encryptedMessage.payload; - const plainMessage = recipient.decryptMessage(new EncryptedMessage(payload), sender.publicAccount); + const plainMessage = recipient.decryptMessage(EncryptedMessage.createFromPayload(payload), sender.publicAccount); expect(plainMessage.payload).to.be.equal('Testing simple transfer'); }); it('should return decrepted message reading from message payload', () => { const generationHash = '57F7DA205008026C776CB6AED843393F04CD458E0AA2D9F1D5F31A402072B2D6'; + const originalEncryptedMessage = sender.encryptMessage('Testing simple transfer', recipient.publicAccount); const transferTransaction = TransferTransaction.create( Deadline.create(epochAdjustment), recipient.address, [NetworkCurrencyLocal.createAbsolute(1)], - sender.encryptMessage('Testing simple transfer', recipient.publicAccount), + originalEncryptedMessage, NetworkType.TEST_NET, ); const signedTransaction = transferTransaction.signWith(sender, generationHash); - const encryptMessage = EncryptedMessage.createFromPayload( - signedTransaction.payload.substring(354, signedTransaction.payload.length), - ); - const plainMessage = recipient.decryptMessage(encryptMessage, sender.publicAccount); + const encryptMessage = (TransferTransaction.createFromPayload(signedTransaction.payload) as TransferTransaction).message; + expect(encryptMessage).deep.equal(originalEncryptedMessage); + expect(encryptMessage.type).equal(MessageType.EncryptedMessage); + const plainMessage = recipient.decryptMessage(encryptMessage as EncryptedMessage, sender.publicAccount); expect(plainMessage.payload).to.be.equal('Testing simple transfer'); }); it('should encrypt and decrypt message using NIS1 schema', () => { const encryptedMessage = sender_nis.encryptMessage('Testing simple transfer', recipient_nis.publicAccount); const payload = encryptedMessage.payload; - const plainMessage = recipient_nis.decryptMessage(new EncryptedMessage(payload), sender_nis.publicAccount); + const plainMessage = recipient_nis.decryptMessage(EncryptedMessage.createFromPayload(payload), sender_nis.publicAccount); expect(plainMessage.payload).to.be.equal('Testing simple transfer'); }); + + it('should be the same after deserializing ', () => { + const encryptedMessage1 = EncryptedMessage.create( + 'Testing simple transfer', + recipient_nis.publicAccount, + sender_nis.privateKey, + Buffer.alloc(12), + ); + const encryptedMessage2 = EncryptedMessage.create( + 'Testing simple transfer', + recipient_nis.publicAccount, + sender_nis.privateKey, + Buffer.alloc(12), + ); + expect(encryptedMessage1).to.be.deep.equal(encryptedMessage2); + expect(encryptedMessage1.toDTO()).to.be.deep.equal( + '01353834343746314432354132363741454341384344443935313035334135303730303030303030303030303030303030303030303030303038324546344339383231424637353130414237334346434337314641333339424344464235344538393432363941', + ); + expect(encryptedMessage1.payload).to.be.deep.equal( + '58447F1D25A267AECA8CDD951053A50700000000000000000000000082EF4C9821BF7510AB73CFCC71FA339BCDFB54E894269A', + ); + expect(encryptedMessage1).to.be.deep.equal(EncryptedMessage.createFromPayload(encryptedMessage1.payload)); + expect(encryptedMessage1).to.be.deep.equal(EncryptedMessage.createFromBuilder(encryptedMessage1.toBuffer())); + }); + + it('createBuffer and getPayload', () => { + const payload = 'Some text'; + const buffer = EncryptedMessage.createBuffer(payload); + expect(Convert.uint8ToHex(buffer)).deep.equal('01536F6D652074657874'); + expect(EncryptedMessage.getPayload(buffer)).be.eq(payload); + }); }); diff --git a/test/model/message/Message.spec.ts b/test/model/message/Message.spec.ts index d80eb6c7d4..805717a108 100644 --- a/test/model/message/Message.spec.ts +++ b/test/model/message/Message.spec.ts @@ -16,17 +16,17 @@ import { expect } from 'chai'; import { Convert } from '../../../src/core/format'; -import { EncryptedMessage, Message, PersistentHarvestingDelegationMessage, PlainMessage } from '../../../src/model/message'; +import { PersistentHarvestingDelegationMessage, PlainMessage } from '../../../src/model/message'; describe('Message', () => { it('should create an plain message dto object', () => { - const message = new PlainMessage('test'); + const message = PlainMessage.create('test'); expect(message.toDTO()).to.be.equal('00' + Convert.utf8ToHex('test')); }); - it('should create an encrypted message dto object', () => { - const message = new EncryptedMessage('test'); - expect(message.toDTO()).to.be.equal('01' + Convert.utf8ToHex('test')); + it('should create an plain message dto from buffer', () => { + const message = PlainMessage.createFromBuilder(PlainMessage.create('test').toBuffer()); + expect(message.toDTO()).to.be.equal('00' + Convert.utf8ToHex('test')); }); it('should throw exception on creating PersistentHarvestingDelegationMessage with wrong size', () => { @@ -40,14 +40,4 @@ describe('Message', () => { new PersistentHarvestingDelegationMessage('test'); }).to.throw(Error, 'Payload format is not valid hexadecimal string'); }); - - it('should decode hex string', () => { - const hex = '746573742D6D657373616765'; - expect(Message.decodeHex(hex)).to.be.equal('test-message'); - }); - - it('should decode hex string (emoji)', () => { - const hex = 'F09F9880E38193E38293E381ABE381A1E381AFF09F9880'; - expect(Message.decodeHex(hex)).to.be.equal('๐Ÿ˜€ใ“ใ‚“ใซใกใฏ๐Ÿ˜€'); - }); }); diff --git a/test/model/message/PlainMessage.spec.ts b/test/model/message/PlainMessage.spec.ts index be3bff04ad..5461ef9c93 100644 --- a/test/model/message/PlainMessage.spec.ts +++ b/test/model/message/PlainMessage.spec.ts @@ -19,26 +19,21 @@ import { EmptyMessage, PlainMessage } from '../../../src/model/message'; describe('PlainMessage', () => { it('should createComplete an empty message', () => { - expect(EmptyMessage.payload).to.be.equal(''); + expect(EmptyMessage.toDTO()).to.be.equal(''); + expect(EmptyMessage.toBuffer()).to.be.deep.equal(Uint8Array.of()); }); it('should createComplete message from payload with constructor', () => { const payload = 'test-message'; - const message = new PlainMessage(payload); + const message = PlainMessage.create(payload); expect(message.payload).to.be.equal(payload); expect(message.toDTO()).to.be.equal('00746573742D6D657373616765'); }); - it('should createComplete message from payload with static method', () => { - const payload = '746573742D6D657373616765'; - const message = PlainMessage.createFromPayload(payload); - expect(message.payload).to.be.equal('test-message'); - expect(message.toDTO()).to.be.equal('00746573742D6D657373616765'); - }); - - it('should decode hex message', () => { - const hexMessage = '746573742D6D657373616765'; - const decodedMessage = PlainMessage.decodeHex(hexMessage); - expect(decodedMessage).to.be.equal('test-message'); + it('should createComplete message from builder', () => { + const payload = 'test-message'; + const message = PlainMessage.create(payload); + expect(message).to.be.deep.equal(PlainMessage.create(message.payload)); + expect(message).to.be.deep.equal(PlainMessage.createFromBuilder(message.toBuffer())); }); }); diff --git a/test/model/message/RawMessage.spec.ts b/test/model/message/RawMessage.spec.ts new file mode 100644 index 0000000000..694be66559 --- /dev/null +++ b/test/model/message/RawMessage.spec.ts @@ -0,0 +1,38 @@ +/* + * (C) Symbol Contributors 2021 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { expect } from 'chai'; +import { MessageFactory } from '../../../src'; +import { RawMessage } from '../../../src/model/message/RawMessage'; + +describe('RawMessage', () => { + it('should create from raw payload', () => { + const buffer = Uint8Array.of(3, 1, 2, 3, 4); + const payload = '0301020304'; + const message = RawMessage.create(buffer); + expect(message.payload).to.be.equal(payload); + expect(message.toDTO()).to.be.equal(payload); + expect(message.toBuffer()).to.be.deep.equal(buffer); + }); + + it('should create from same raw message using factory', () => { + const buffer = Uint8Array.of(3, 1, 2, 3, 4); + const payload = '0301020304'; + const message = RawMessage.create(buffer); + expect(message).to.be.deep.equal(MessageFactory.createMessageFromHex(payload)); + expect(message).to.be.deep.equal(MessageFactory.createMessageFromBuffer(buffer)); + }); +});