From 2825500b0182bee75159922bcc1ad0eb72b2a631 Mon Sep 17 00:00:00 2001 From: AnthonyLaw Date: Mon, 6 Sep 2021 15:52:49 +0800 Subject: [PATCH 01/14] feat: add shared key hash on nacl-fast lib --- src/core/nem/external/nacl-fast.js | 66 ++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/src/core/nem/external/nacl-fast.js b/src/core/nem/external/nacl-fast.js index a6576ed..261411e 100644 --- a/src/core/nem/external/nacl-fast.js +++ b/src/core/nem/external/nacl-fast.js @@ -651,6 +651,22 @@ return 0; } + function crypto_shared_key_hash(shared, pk, sk, hash) { + var d = new Uint8Array(64); + var p = [gf(), gf(), gf(), gf()]; + + d = hash(sk); + + d[0] &= 248; + d[31] &= 127; + d[31] |= 64; + + var q = [gf(), gf(), gf(), gf()]; + unpack(q, pk); + scalarmult(p, q, d); + pack(shared, p); + } + function crypto_sign_hash(sm, keypair, data, hasher) { var privHash = new Uint8Array(64); var seededHash = new Uint8Array(64); @@ -811,6 +827,55 @@ return 0; } + function unpack(r, p) { + var t = gf(), chk = gf(), num = gf(), + den = gf(), den2 = gf(), den4 = gf(), + den6 = gf(); + + set25519(r[2], gf1); + unpack25519(r[1], p); + + // num = u = y^2 - 1 + // den = v = d * y^2 + 1 + S(num, r[1]); + M(den, num, D); + Z(num, num, r[2]); + A(den, r[2], den); + + // r[0] = x = sqrt(u / v) + S(den2, den); + S(den4, den2); + M(den6, den4, den2); + M(t, den6, num); + M(t, t, den); + + pow2523(t, t); + M(t, t, num); + M(t, t, den); + M(t, t, den); + M(r[0], t, den); + + S(chk, r[0]); + M(chk, chk, den); + if (neq25519(chk, num)) { + M(r[0], r[0], I); + } + + S(chk, r[0]); + M(chk, chk, den); + if (neq25519(chk, num)) { + console.log("not a valid Ed25519EncodedGroupElement."); + return -1; + } + + if (par25519(r[0]) !== (p[31]>>7)) { + Z(r[0], gf0, r[0]); + } + + M(r[3], r[0], r[1]); + return 0; + } + var crypto_scalarmult_BYTES = 32, crypto_scalarmult_SCALARBYTES = 32, crypto_sign_PUBLICKEYBYTES = 32, @@ -820,6 +885,7 @@ crypto_sign_keypair_hash: crypto_sign_keypair_hash, crypto_sign_hash: crypto_sign_hash, crypto_verify_hash: crypto_verify_hash, + crypto_shared_key_hash: crypto_shared_key_hash, crypto_sign_PUBLICKEYBYTES: crypto_sign_PUBLICKEYBYTES, crypto_sign_SECRETKEYBYTES: crypto_sign_SECRETKEYBYTES, From 0b865ba24a7fde9366c96a6268c3a0d53ef40636 Mon Sep 17 00:00:00 2001 From: AnthonyLaw Date: Mon, 6 Sep 2021 19:41:13 +0800 Subject: [PATCH 02/14] feat: message encode & decode cipher method --- src/core/nem/NemCrypto.ts | 105 ++++++++++++++++++++++++++++++++++++++ src/core/nem/index.ts | 1 + 2 files changed, 106 insertions(+) create mode 100644 src/core/nem/NemCrypto.ts diff --git a/src/core/nem/NemCrypto.ts b/src/core/nem/NemCrypto.ts new file mode 100644 index 0000000..7c5336a --- /dev/null +++ b/src/core/nem/NemCrypto.ts @@ -0,0 +1,105 @@ +/* + * Copyright 2021 SYMBOL + * + * 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 { Key } from '@core'; +import { keccakHash } from '@utils'; +import * as Crypto from 'crypto'; +import { keccak256 } from 'js-sha3'; + +/* eslint @typescript-eslint/no-var-requires: "off" */ +const Ed25519 = require('./external/nacl-fast.js').lowlevel; + +export class NemCrypto { + /** + * AES cipher algorithm (internal) + */ + private static algorithm = 'aes-256-cbc'; + + /** + * Key derive for cipher + * + * @param shared - shared secret + * @param salt - A salt + * @param privateKey - A private key + * @param publicKey - A public key + * + * @returns Keccak-256 hash + */ + private static key_derive(shared: Uint8Array, salt: Uint8Array, privateKey: Key, publicKey: Key): number[] { + Ed25519.crypto_shared_key_hash(shared, publicKey.toBytes(), [...privateKey.toBytes()].reverse(), keccakHash); + + for (let i = 0; i < salt.length; i++) { + shared[i] ^= salt[i]; + } + + return keccak256.digest(shared); + } + + /** + * Encode a message + * + * @param privateKey - A sender private key + * @param publicKey - A recipient public key + * @param message - A text message + * @param customIv - An initialization vector + * @param customSalt - A salt + * + * @returns The encoded message + */ + static encode(privateKey: Key, publicKey: Key, message: Uint8Array, customIv?: Uint8Array, customSalt?: Uint8Array): Uint8Array { + if (message.length > 32) throw new Error('Invalid message size!'); + + const iv = customIv ? customIv : Crypto.randomBytes(16); + const salt = customSalt ? customSalt : Crypto.randomBytes(32); + + const shared = new Uint8Array(32); + const key = this.key_derive(shared, salt, privateKey, publicKey); + + const cipher = Crypto.createCipheriv(this.algorithm, Buffer.from(key), iv); + const encrypted = Buffer.concat([cipher.update(message), cipher.final()]); + + // return Converter.uint8ToHex(salt) + Converter.uint8ToHex(iv) + encrypted.toString('hex').toUpperCase(); + return Buffer.concat([salt, iv, encrypted]); + } + + /** + * Decode an encrypted message payload + * + * @param privateKey - A recipient private key + * @param publicKey - A sender public key + * @param payload - An encrypted message payload + * + * @returns The decoded message + */ + static decode(privateKey: Key, publicKey: Key, payload: Uint8Array): Uint8Array { + if (payload.length !== 80) throw new Error('Invalid payload size!'); + + // 32 byte for salt + const salt = payload.slice(0, 32); + // 16 byte for iv + const iv = payload.slice(32, 48); + // 32 byte for payload + const message = payload.slice(48); + + const shared = new Uint8Array(32); + const key = this.key_derive(shared, salt, privateKey, publicKey); + + const decipher = Crypto.createDecipheriv(this.algorithm, Buffer.from(key), iv); + const decoded = Buffer.concat([decipher.update(message), decipher.final()]); + + return decoded; + } +} diff --git a/src/core/nem/index.ts b/src/core/nem/index.ts index cf0cc86..8fd94b9 100644 --- a/src/core/nem/index.ts +++ b/src/core/nem/index.ts @@ -1,4 +1,5 @@ export * from './NemAddress'; +export * from './NemCrypto'; export * from './NemEnums'; export * from './NemKeyPair'; export * from './NemNetwork'; From a1e7f5e99bb4b9be4f03bb1027de833c4b6fbc31 Mon Sep 17 00:00:00 2001 From: AnthonyLaw Date: Mon, 6 Sep 2021 23:00:46 +0800 Subject: [PATCH 03/14] test: encode & decode message --- test/core/nem/NemCrypto.spec.ts | 77 +++++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 test/core/nem/NemCrypto.spec.ts diff --git a/test/core/nem/NemCrypto.spec.ts b/test/core/nem/NemCrypto.spec.ts new file mode 100644 index 0000000..9b60528 --- /dev/null +++ b/test/core/nem/NemCrypto.spec.ts @@ -0,0 +1,77 @@ +import { Key, NemCrypto, NemKeyPair } from '@core'; +import { Converter } from '@utils'; +import { expect } from 'chai'; + +describe('Nem crypto cipher', () => { + // Arrange: + const deterministicSenderPrivateKey = Key.createFromHex('575DBB3062267EFF57C970A336EBBC8FBCFE12C5BD3ED7BC11EB0481D7704CED'); + const deterministicReceiverPrivateKey = Key.createFromHex('5B0E3FA5D3B49A79022D7C1E121BA1CBBF4DB5821F47AB8C708EF88DEFC29BFE'); + const sender = new NemKeyPair(deterministicSenderPrivateKey); + const recipient = new NemKeyPair(deterministicReceiverPrivateKey); + const message = 'Nem is awesome!'; + const encryptedHex = + '26A5EEBD8B959F664DB060DE9E8265BD4A4597D2BE9DAA2C481042879BEA9F995790143301428700938A0ABB7D8122499E16AE67FFD5E2E4143A58100CA0BD604EC054F82FFF719B26C3E2B68CB6EBB0'; + + describe('encode & decode message', () => { + it('Can encode message with sender private key', () => { + // Arrange: + const iv = Converter.hexToUint8('5790143301428700938A0ABB7D812249'); + const salt = Converter.hexToUint8('26A5EEBD8B959F664DB060DE9E8265BD4A4597D2BE9DAA2C481042879BEA9F99'); + + // Act: + const encoded = NemCrypto.encode(sender.privateKey, recipient.publicKey, Converter.utf8ToUint8(message), iv, salt); + + // Assert: + expect(encryptedHex).equal(Converter.uint8ToHex(encoded)); + }); + + it('Can decode message with recipient private key', () => { + // Act: + const decoded = NemCrypto.decode(recipient.privateKey, sender.publicKey, Converter.hexToUint8(encryptedHex)); + + // Assert: + expect(message).equal(Converter.uint8ToUtf8(decoded)); + }); + + it('Roundtrip decode encode', () => { + // Act: + const decrypted = NemCrypto.decode(recipient.privateKey, sender.publicKey, Converter.hexToUint8(encryptedHex)); + const encrypted = NemCrypto.encode(sender.privateKey, recipient.publicKey, decrypted); + + // Assert: + expect(Converter.uint8ToUtf8(decrypted)).equal(message); + expect(encrypted.length).equal(80); + }); + + it('Roundtrip encode decode', () => { + // Act: + const encrypted = NemCrypto.encode(sender.privateKey, recipient.publicKey, Converter.utf8ToUint8(message)); + const decrypted = NemCrypto.decode(recipient.privateKey, sender.publicKey, encrypted); + + // Assert: + expect(Converter.uint8ToUtf8(decrypted)).equal(message); + }); + + it('Encoding throw error if message exceed 32 btyes', () => { + // Arrange: + const exceedBtyesMessage = message.repeat(10); + + // Act: + const encoded = () => NemCrypto.encode(sender.privateKey, recipient.publicKey, Converter.utf8ToUint8(exceedBtyesMessage)); + + // Assert: + expect(encoded).to.throw(Error); + }); + + it('Decoding throw error if payload exceed 80 btyes', () => { + // Arrange: + const exceedBtyesPayload = encryptedHex.repeat(2); + + // Act: + const decoded = () => NemCrypto.decode(recipient.privateKey, sender.publicKey, Converter.hexToUint8(exceedBtyesPayload)); + + // Assert: + expect(decoded).to.throw(Error); + }); + }); +}); From bcd808d57329750b5f1ba6217c8321628737d43b Mon Sep 17 00:00:00 2001 From: AnthonyLaw Date: Tue, 7 Sep 2021 04:07:39 +0800 Subject: [PATCH 04/14] fix: add more validation --- src/core/nem/NemCrypto.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/core/nem/NemCrypto.ts b/src/core/nem/NemCrypto.ts index 7c5336a..b725393 100644 --- a/src/core/nem/NemCrypto.ts +++ b/src/core/nem/NemCrypto.ts @@ -53,14 +53,17 @@ export class NemCrypto { * * @param privateKey - A sender private key * @param publicKey - A recipient public key - * @param message - A text message + * @param message - A text message (max 976 bytes) * @param customIv - An initialization vector * @param customSalt - A salt * * @returns The encoded message */ static encode(privateKey: Key, publicKey: Key, message: Uint8Array, customIv?: Uint8Array, customSalt?: Uint8Array): Uint8Array { - if (message.length > 32) throw new Error('Invalid message size!'); + // Max payload size is 1024 bytes included iv and salt + if (message.length > 976) throw new Error('Invalid message size!'); + if (customIv && customIv.length !== 16) throw new Error('Invalid iv size!'); + if (customSalt && customSalt.length !== 32) throw new Error('Invalid salt size!'); const iv = customIv ? customIv : Crypto.randomBytes(16); const salt = customSalt ? customSalt : Crypto.randomBytes(32); @@ -71,7 +74,6 @@ export class NemCrypto { const cipher = Crypto.createCipheriv(this.algorithm, Buffer.from(key), iv); const encrypted = Buffer.concat([cipher.update(message), cipher.final()]); - // return Converter.uint8ToHex(salt) + Converter.uint8ToHex(iv) + encrypted.toString('hex').toUpperCase(); return Buffer.concat([salt, iv, encrypted]); } @@ -80,12 +82,12 @@ export class NemCrypto { * * @param privateKey - A recipient private key * @param publicKey - A sender public key - * @param payload - An encrypted message payload + * @param payload - An encrypted message payload (max 1024 bytes) * * @returns The decoded message */ static decode(privateKey: Key, publicKey: Key, payload: Uint8Array): Uint8Array { - if (payload.length !== 80) throw new Error('Invalid payload size!'); + if (payload.length > 1024) throw new Error('Invalid payload size!'); // 32 byte for salt const salt = payload.slice(0, 32); From ae54bb6d6f159977899485c38cff24a2f275c785 Mon Sep 17 00:00:00 2001 From: AnthonyLaw Date: Tue, 7 Sep 2021 04:08:54 +0800 Subject: [PATCH 05/14] feat: add more test case --- test/core/nem/NemCrypto.spec.ts | 41 ++++++++++++++++++++++++++------- 1 file changed, 33 insertions(+), 8 deletions(-) diff --git a/test/core/nem/NemCrypto.spec.ts b/test/core/nem/NemCrypto.spec.ts index 9b60528..e9f8ac1 100644 --- a/test/core/nem/NemCrypto.spec.ts +++ b/test/core/nem/NemCrypto.spec.ts @@ -10,7 +10,7 @@ describe('Nem crypto cipher', () => { const recipient = new NemKeyPair(deterministicReceiverPrivateKey); const message = 'Nem is awesome!'; const encryptedHex = - '26A5EEBD8B959F664DB060DE9E8265BD4A4597D2BE9DAA2C481042879BEA9F995790143301428700938A0ABB7D8122499E16AE67FFD5E2E4143A58100CA0BD604EC054F82FFF719B26C3E2B68CB6EBB0'; + '26A5EEBD8B959F664DB060DE9E8265BD4A4597D2BE9DAA2C481042879BEA9F995790143301428700938A0ABB7D81224996058BECCAB21970239EE94A5C587429'; describe('encode & decode message', () => { it('Can encode message with sender private key', () => { @@ -40,7 +40,7 @@ describe('Nem crypto cipher', () => { // Assert: expect(Converter.uint8ToUtf8(decrypted)).equal(message); - expect(encrypted.length).equal(80); + expect(encrypted.length).equal(Converter.hexToUint8(encryptedHex).length); }); it('Roundtrip encode decode', () => { @@ -52,23 +52,48 @@ describe('Nem crypto cipher', () => { expect(Converter.uint8ToUtf8(decrypted)).equal(message); }); - it('Encoding throw error if message exceed 32 btyes', () => { + it('Encoding throw error if message exceed 976 btyes', () => { // Arrange: - const exceedBtyesMessage = message.repeat(10); + const message = new Uint8Array(977); // Act: - const encoded = () => NemCrypto.encode(sender.privateKey, recipient.publicKey, Converter.utf8ToUint8(exceedBtyesMessage)); + const encoded = () => NemCrypto.encode(sender.privateKey, recipient.publicKey, message); // Assert: expect(encoded).to.throw(Error); }); - it('Decoding throw error if payload exceed 80 btyes', () => { + it('Encoding throw error if iv exceed 16 btyes', () => { // Arrange: - const exceedBtyesPayload = encryptedHex.repeat(2); + const message = new Uint8Array(32); + const customIv = new Uint8Array(17); // Act: - const decoded = () => NemCrypto.decode(recipient.privateKey, sender.publicKey, Converter.hexToUint8(exceedBtyesPayload)); + const encoded = () => NemCrypto.encode(sender.privateKey, recipient.publicKey, message, customIv); + + // Assert: + expect(encoded).to.throw(Error); + }); + + it('Encoding throw error if salt exceed 32 btyes', () => { + // Arrange: + const message = new Uint8Array(32); + const customIv = new Uint8Array(16); + const customSalt = new Uint8Array(33); + + // Act: + const encoded = () => NemCrypto.encode(sender.privateKey, recipient.publicKey, message, customIv, customSalt); + + // Assert: + expect(encoded).to.throw(Error); + }); + + it('Decoding throw error if payload exceed 1024 btyes', () => { + // Arrange: + const exceedBtyesPayload = new Uint8Array(1025); + + // Act: + const decoded = () => NemCrypto.decode(recipient.privateKey, sender.publicKey, exceedBtyesPayload); // Assert: expect(decoded).to.throw(Error); From 640e6c12e614166f6f7b29abb723a29c7f7a7928 Mon Sep 17 00:00:00 2001 From: AnthonyLaw Date: Wed, 8 Sep 2021 17:16:07 +0800 Subject: [PATCH 06/14] refactor: rename method --- src/core/nem/NemCrypto.ts | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/core/nem/NemCrypto.ts b/src/core/nem/NemCrypto.ts index b725393..0a55802 100644 --- a/src/core/nem/NemCrypto.ts +++ b/src/core/nem/NemCrypto.ts @@ -29,7 +29,7 @@ export class NemCrypto { private static algorithm = 'aes-256-cbc'; /** - * Key derive for cipher + * Derive key for cipher * * @param shared - shared secret * @param salt - A salt @@ -38,7 +38,7 @@ export class NemCrypto { * * @returns Keccak-256 hash */ - private static key_derive(shared: Uint8Array, salt: Uint8Array, privateKey: Key, publicKey: Key): number[] { + private static deriveKey(shared: Uint8Array, salt: Uint8Array, privateKey: Key, publicKey: Key): number[] { Ed25519.crypto_shared_key_hash(shared, publicKey.toBytes(), [...privateKey.toBytes()].reverse(), keccakHash); for (let i = 0; i < salt.length; i++) { @@ -69,7 +69,7 @@ export class NemCrypto { const salt = customSalt ? customSalt : Crypto.randomBytes(32); const shared = new Uint8Array(32); - const key = this.key_derive(shared, salt, privateKey, publicKey); + const key = this.deriveKey(shared, salt, privateKey, publicKey); const cipher = Crypto.createCipheriv(this.algorithm, Buffer.from(key), iv); const encrypted = Buffer.concat([cipher.update(message), cipher.final()]); @@ -97,11 +97,9 @@ export class NemCrypto { const message = payload.slice(48); const shared = new Uint8Array(32); - const key = this.key_derive(shared, salt, privateKey, publicKey); + const key = this.deriveKey(shared, salt, privateKey, publicKey); const decipher = Crypto.createDecipheriv(this.algorithm, Buffer.from(key), iv); - const decoded = Buffer.concat([decipher.update(message), decipher.final()]); - - return decoded; + return Buffer.concat([decipher.update(message), decipher.final()]); } } From 06f20b03a764033886d31075d90c0fb5ad29af24 Mon Sep 17 00:00:00 2001 From: AnthonyLaw Date: Wed, 8 Sep 2021 17:16:56 +0800 Subject: [PATCH 07/14] test: add message size check --- test/core/nem/NemCrypto.spec.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/test/core/nem/NemCrypto.spec.ts b/test/core/nem/NemCrypto.spec.ts index e9f8ac1..92329b9 100644 --- a/test/core/nem/NemCrypto.spec.ts +++ b/test/core/nem/NemCrypto.spec.ts @@ -52,6 +52,17 @@ describe('Nem crypto cipher', () => { expect(Converter.uint8ToUtf8(decrypted)).equal(message); }); + it('Can encode message in max size 976 btyes', () => { + // Arrange: + const message = new Uint8Array(976); + + // Act: + const encoded = () => NemCrypto.encode(sender.privateKey, recipient.publicKey, message); + + // Assert: + expect(encoded).to.not.throw(Error); + }); + it('Encoding throw error if message exceed 976 btyes', () => { // Arrange: const message = new Uint8Array(977); From 8b906f7005f5ad83ad5da9550db6b7d246e314a2 Mon Sep 17 00:00:00 2001 From: AnthonyLaw Date: Tue, 5 Oct 2021 23:19:53 +0800 Subject: [PATCH 08/14] feat: add crypto_shared_key interface --- src/core/nem/NemCrypto.ts | 4 +--- src/core/nem/external/index.ts | 10 +++++++++- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/core/nem/NemCrypto.ts b/src/core/nem/NemCrypto.ts index e883415..e8310b8 100644 --- a/src/core/nem/NemCrypto.ts +++ b/src/core/nem/NemCrypto.ts @@ -15,13 +15,11 @@ */ import { Key } from '@core'; +import Ed25519 from '@external'; import { keccakHash } from '@utils'; import * as Crypto from 'crypto'; import { keccak256 } from 'js-sha3'; -/* eslint @typescript-eslint/no-var-requires: "off" */ -const Ed25519 = require('./external/nacl-fast.js').lowlevel; - export class NemCrypto { /** * AES cipher algorithm (internal) diff --git a/src/core/nem/external/index.ts b/src/core/nem/external/index.ts index 62ebb45..e626d39 100644 --- a/src/core/nem/external/index.ts +++ b/src/core/nem/external/index.ts @@ -34,7 +34,15 @@ interface INacl { * @param data - The data to verify. * @param hasher - Hasher function example KeccakHasher. */ - crypto_verify_hash(signature: Uint8Array, publicKey: Uint8Array, data: Uint8Array, hasher: Hasher); + crypto_verify_hash(signature: Uint8Array, publicKey: Uint8Array, data: Uint8Array, hasher: Hasher): boolean; + /** + * Derive a shared key from private key based on the hash function. + * @param shared - shared secret. + * @param publicKey - The public key. + * @param privateKey - The private key. + * @param hashFunc - Hash function use to hash the private key. + */ + crypto_shared_key(shared: Uint8Array, publicKey: Uint8Array, privateKey: number[], hashFunc: (data: Uint8Array) => number[]): void; crypto_modL(r: Uint8Array, x: Float64Array): Uint8Array; /* number of public key bytes */ crypto_sign_PUBLICKEYBYTES: number; From 6dadb164a2288bb2b2fa2b935bb73b722a6ae89f Mon Sep 17 00:00:00 2001 From: AnthonyLaw Date: Wed, 6 Oct 2021 00:02:26 +0800 Subject: [PATCH 09/14] test: add cipher vector test in nem. --- test/BasicVectorTest.template.ts | 38 +++++++++++++++++++++------- test/test-vector | 2 +- test/vector-tests/AllTests.vector.ts | 5 ++++ 3 files changed, 35 insertions(+), 10 deletions(-) diff --git a/test/BasicVectorTest.template.ts b/test/BasicVectorTest.template.ts index d8dc6ff..5acd377 100644 --- a/test/BasicVectorTest.template.ts +++ b/test/BasicVectorTest.template.ts @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { deriveSharedKey, deriveSharedSecret, encode, Key, KeyPair, Network, SymbolIdGenerator } from '@core'; +import { decode, deriveSharedKey, deriveSharedSecret, encode, Key, KeyPair, NemCrypto, Network, SymbolIdGenerator } from '@core'; import { Converter } from '@utils'; import { toBufferLE } from 'bigint-buffer'; import { expect } from 'chai'; @@ -98,17 +98,37 @@ export const CipherVectorTester = (testCipherVectorFile: string): void => { describe('cipher - test vector', () => { tester.run( testCipherVectorFile, - (item: { privateKey: string; otherPublicKey: string; tag: string; iv: string; cipherText: string; clearText: string }) => { + (item: { + privateKey: string; + otherPublicKey: string; + tag: string; + salt: string; + iv: string; + cipherText: string; + clearText: string; + }) => { + // Arrange: + const privateKey = Key.createFromHex(item.privateKey); + const otherPublicKey = Key.createFromHex(item.otherPublicKey); + const clearText = Converter.hexToUint8(item.clearText); + const iv = Converter.hexToUint8(item.iv); + const salt = item.salt ? Converter.hexToUint8(item.salt) : undefined; + // Act: - const encoded = encode( - Key.createFromHex(item.privateKey), - Key.createFromHex(item.otherPublicKey), - Converter.hexToUint8(item.clearText), - Converter.hexToUint8(item.iv), - ); + const encoded = salt + ? NemCrypto.encode(privateKey, otherPublicKey, clearText, iv, salt) + : encode(privateKey, otherPublicKey, clearText, iv); + + const decoded = salt + ? NemCrypto.decode(privateKey, otherPublicKey, Converter.hexToUint8(item.salt + item.iv + item.cipherText)) + : decode(privateKey, otherPublicKey, encoded); + // Assert: const message = ` from ${item.clearText}`; - expect(Converter.uint8ToHex(encoded), `cipher ${message}`).equal(`${item.tag}${item.iv}${item.cipherText}`); + expect(Converter.uint8ToHex(encoded), `cipher encode ${message}`).equal( + `${salt ? item.salt : item.tag}${item.iv}${item.cipherText}`, + ); + expect(Converter.uint8ToHex(decoded), `cipher decoded ${message}`).equal(item.clearText); }, 'cipher test', ); diff --git a/test/test-vector b/test/test-vector index 4cd352e..d5ba677 160000 --- a/test/test-vector +++ b/test/test-vector @@ -1 +1 @@ -Subproject commit 4cd352ec2024d7b5f2f21edb6d6cbeb785717909 +Subproject commit d5ba677b520b1a318d2384a93a3dc9666a92d6b1 diff --git a/test/vector-tests/AllTests.vector.ts b/test/vector-tests/AllTests.vector.ts index 7006ea9..949ee9e 100644 --- a/test/vector-tests/AllTests.vector.ts +++ b/test/vector-tests/AllTests.vector.ts @@ -24,6 +24,11 @@ describe('Nem', () => { const networks = NemNetwork.list(); AddressMosaicIdTester(networks, vectorFile); }); + + describe('test-cipher vector', () => { + const vectorFile = path.join(__dirname, '../test-vector/nem/4.test-cipher.json'); + CipherVectorTester(vectorFile); + }); }); describe('Symbol', () => { From 285932d60a88c392d2e91e3760e19563870eab3b Mon Sep 17 00:00:00 2001 From: AnthonyLaw Date: Fri, 7 Jan 2022 14:56:49 +0800 Subject: [PATCH 10/14] fix: typo --- src/core/nem/NemCrypto.ts | 2 +- test/core/nem/NemCrypto.spec.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core/nem/NemCrypto.ts b/src/core/nem/NemCrypto.ts index e8310b8..6fac23a 100644 --- a/src/core/nem/NemCrypto.ts +++ b/src/core/nem/NemCrypto.ts @@ -91,7 +91,7 @@ export class NemCrypto { const salt = payload.slice(0, 32); // 16 byte for iv const iv = payload.slice(32, 48); - // 32 byte for payload + // 48 byte for payload const message = payload.slice(48); const shared = new Uint8Array(32); diff --git a/test/core/nem/NemCrypto.spec.ts b/test/core/nem/NemCrypto.spec.ts index 92329b9..f718c5a 100644 --- a/test/core/nem/NemCrypto.spec.ts +++ b/test/core/nem/NemCrypto.spec.ts @@ -63,7 +63,7 @@ describe('Nem crypto cipher', () => { expect(encoded).to.not.throw(Error); }); - it('Encoding throw error if message exceed 976 btyes', () => { + it('Encoding throw error if message exceed 976 bytes', () => { // Arrange: const message = new Uint8Array(977); From b63b6d1e2c04307280785b47c90117d41cf91507 Mon Sep 17 00:00:00 2001 From: AnthonyLaw Date: Sat, 8 Jan 2022 04:43:54 +0800 Subject: [PATCH 11/14] fix: split vector test template --- test/BasicVectorTest.template.ts | 57 +++++++++++++++++----------- test/vector-tests/AllTests.vector.ts | 9 +++-- 2 files changed, 39 insertions(+), 27 deletions(-) diff --git a/test/BasicVectorTest.template.ts b/test/BasicVectorTest.template.ts index 5acd377..ef4f705 100644 --- a/test/BasicVectorTest.template.ts +++ b/test/BasicVectorTest.template.ts @@ -94,43 +94,54 @@ export const AddressMosaicIdTester = ( }); }; -export const CipherVectorTester = (testCipherVectorFile: string): void => { - describe('cipher - test vector', () => { +export const NemCipherVectorTester = (testCipherVectorFile: string): void => { + describe('nem cipher - test vector', () => { tester.run( testCipherVectorFile, - (item: { - privateKey: string; - otherPublicKey: string; - tag: string; - salt: string; - iv: string; - cipherText: string; - clearText: string; - }) => { + (item: { privateKey: string; otherPublicKey: string; salt: string; iv: string; cipherText: string; clearText: string }) => { // Arrange: const privateKey = Key.createFromHex(item.privateKey); const otherPublicKey = Key.createFromHex(item.otherPublicKey); const clearText = Converter.hexToUint8(item.clearText); const iv = Converter.hexToUint8(item.iv); - const salt = item.salt ? Converter.hexToUint8(item.salt) : undefined; + const salt = Converter.hexToUint8(item.salt); // Act: - const encoded = salt - ? NemCrypto.encode(privateKey, otherPublicKey, clearText, iv, salt) - : encode(privateKey, otherPublicKey, clearText, iv); + const payload = item.salt + item.iv + item.cipherText; + const encoded = NemCrypto.encode(privateKey, otherPublicKey, clearText, iv, salt); + const decoded = NemCrypto.decode(privateKey, otherPublicKey, Converter.hexToUint8(payload)); - const decoded = salt - ? NemCrypto.decode(privateKey, otherPublicKey, Converter.hexToUint8(item.salt + item.iv + item.cipherText)) - : decode(privateKey, otherPublicKey, encoded); + // Assert: + const message = ` from ${item.clearText}`; + expect(Converter.uint8ToHex(encoded), `nem cipher encode ${message}`).equal(payload); + expect(Converter.uint8ToHex(decoded), `nem cipher decoded ${message}`).equal(item.clearText); + }, + 'nem cipher test', + ); + }); +}; + +export const SymbolCipherVectorTester = (testCipherVectorFile: string): void => { + describe('symbol cipher - test vector', () => { + tester.run( + testCipherVectorFile, + (item: { privateKey: string; otherPublicKey: string; tag: string; iv: string; cipherText: string; clearText: string }) => { + // Arrange: + const privateKey = Key.createFromHex(item.privateKey); + const otherPublicKey = Key.createFromHex(item.otherPublicKey); + const clearText = Converter.hexToUint8(item.clearText); + const iv = Converter.hexToUint8(item.iv); + + // Act: + const encoded = encode(privateKey, otherPublicKey, clearText, iv); + const decoded = decode(privateKey, otherPublicKey, encoded); // Assert: const message = ` from ${item.clearText}`; - expect(Converter.uint8ToHex(encoded), `cipher encode ${message}`).equal( - `${salt ? item.salt : item.tag}${item.iv}${item.cipherText}`, - ); - expect(Converter.uint8ToHex(decoded), `cipher decoded ${message}`).equal(item.clearText); + expect(Converter.uint8ToHex(encoded), `symbol cipher encode ${message}`).equal(`${item.tag}${item.iv}${item.cipherText}`); + expect(Converter.uint8ToHex(decoded), `symbol cipher decoded ${message}`).equal(item.clearText); }, - 'cipher test', + 'symbol cipher test', ); }); }; diff --git a/test/vector-tests/AllTests.vector.ts b/test/vector-tests/AllTests.vector.ts index 949ee9e..50bb357 100644 --- a/test/vector-tests/AllTests.vector.ts +++ b/test/vector-tests/AllTests.vector.ts @@ -1,11 +1,12 @@ import { NemKeyPair, NemNetwork, SymbolKeyPair, SymbolNetwork } from '@core'; import { AddressMosaicIdTester, - CipherVectorTester, DeriveVectorTester, KeyPairVectorTester, + NemCipherVectorTester, SignAndVerifyTester, -} from 'test/BasicVectorTest.template'; + SymbolCipherVectorTester, +} from '../BasicVectorTest.template'; import path = require('path'); describe('Nem', () => { @@ -27,7 +28,7 @@ describe('Nem', () => { describe('test-cipher vector', () => { const vectorFile = path.join(__dirname, '../test-vector/nem/4.test-cipher.json'); - CipherVectorTester(vectorFile); + NemCipherVectorTester(vectorFile); }); }); @@ -61,6 +62,6 @@ describe('Symbol', () => { describe('test-cipher vector', () => { const vectorFile = path.join(__dirname, '../test-vector/symbol/4.test-cipher.json'); - CipherVectorTester(vectorFile); + SymbolCipherVectorTester(vectorFile); }); }); From 102141db0a6594f27ff9bec745b200fb8697184c Mon Sep 17 00:00:00 2001 From: AnthonyLaw Date: Sat, 8 Jan 2022 04:44:03 +0800 Subject: [PATCH 12/14] fix: refactor unpack --- src/core/nem/external/nacl-fast.js | 55 ++++++------------------------ 1 file changed, 10 insertions(+), 45 deletions(-) diff --git a/src/core/nem/external/nacl-fast.js b/src/core/nem/external/nacl-fast.js index 286c26b..6265515 100644 --- a/src/core/nem/external/nacl-fast.js +++ b/src/core/nem/external/nacl-fast.js @@ -737,7 +737,7 @@ var p = [gf(), gf(), gf(), gf()]; var q = [gf(), gf(), gf(), gf()]; - if (unpackneg(q, pk)) return false; + if (unpack(q, pk, true)) return false; hasher.reset(); hasher.update(signature.subarray(0, 64/2)); @@ -789,60 +789,19 @@ modL(r, x); } - function unpackneg(r, p) { + function unpack(r, p, negate = false) { var t = gf(), chk = gf(), num = gf(), den = gf(), den2 = gf(), den4 = gf(), den6 = gf(); set25519(r[2], gf1); unpack25519(r[1], p); - S(num, r[1]); - M(den, num, D); - Z(num, num, r[2]); - A(den, r[2], den); - - S(den2, den); - S(den4, den2); - M(den6, den4, den2); - M(t, den6, num); - M(t, t, den); - - pow2523(t, t); - M(t, t, num); - M(t, t, den); - M(t, t, den); - M(r[0], t, den); - - S(chk, r[0]); - M(chk, chk, den); - if (neq25519(chk, num)) M(r[0], r[0], I); - - S(chk, r[0]); - M(chk, chk, den); - if (neq25519(chk, num)) return -1; - - if (par25519(r[0]) === (p[31]>>7)) Z(r[0], gf0, r[0]); - - M(r[3], r[0], r[1]); - return 0; - } - function unpack(r, p) { - var t = gf(), chk = gf(), num = gf(), - den = gf(), den2 = gf(), den4 = gf(), - den6 = gf(); - - set25519(r[2], gf1); - unpack25519(r[1], p); - - // num = u = y^2 - 1 - // den = v = d * y^2 + 1 S(num, r[1]); M(den, num, D); Z(num, num, r[2]); A(den, r[2], den); - // r[0] = x = sqrt(u / v) S(den2, den); S(den4, den2); M(den6, den4, den2); @@ -868,8 +827,14 @@ return -1; } - if (par25519(r[0]) !== (p[31]>>7)) { - Z(r[0], gf0, r[0]); + if (negate) { + if (par25519(r[0]) === (p[31]>>7)) { + Z(r[0], gf0, r[0]) + } + } else { + if (par25519(r[0]) !== (p[31]>>7)) { + Z(r[0], gf0, r[0]); + } } M(r[3], r[0], r[1]); From cb3dd4bf30bcb0683798e6fa8344a4fc0267ad1a Mon Sep 17 00:00:00 2001 From: AnthonyLaw Date: Mon, 10 Jan 2022 18:18:08 +0800 Subject: [PATCH 13/14] Revert "fix: refactor unpack" This reverts commit 102141db0a6594f27ff9bec745b200fb8697184c. --- src/core/nem/external/nacl-fast.js | 55 ++++++++++++++++++++++++------ 1 file changed, 45 insertions(+), 10 deletions(-) diff --git a/src/core/nem/external/nacl-fast.js b/src/core/nem/external/nacl-fast.js index 6265515..286c26b 100644 --- a/src/core/nem/external/nacl-fast.js +++ b/src/core/nem/external/nacl-fast.js @@ -737,7 +737,7 @@ var p = [gf(), gf(), gf(), gf()]; var q = [gf(), gf(), gf(), gf()]; - if (unpack(q, pk, true)) return false; + if (unpackneg(q, pk)) return false; hasher.reset(); hasher.update(signature.subarray(0, 64/2)); @@ -789,19 +789,60 @@ modL(r, x); } - function unpack(r, p, negate = false) { + function unpackneg(r, p) { var t = gf(), chk = gf(), num = gf(), den = gf(), den2 = gf(), den4 = gf(), den6 = gf(); set25519(r[2], gf1); unpack25519(r[1], p); + S(num, r[1]); + M(den, num, D); + Z(num, num, r[2]); + A(den, r[2], den); + + S(den2, den); + S(den4, den2); + M(den6, den4, den2); + M(t, den6, num); + M(t, t, den); + + pow2523(t, t); + M(t, t, num); + M(t, t, den); + M(t, t, den); + M(r[0], t, den); + + S(chk, r[0]); + M(chk, chk, den); + if (neq25519(chk, num)) M(r[0], r[0], I); + + S(chk, r[0]); + M(chk, chk, den); + if (neq25519(chk, num)) return -1; + + if (par25519(r[0]) === (p[31]>>7)) Z(r[0], gf0, r[0]); + + M(r[3], r[0], r[1]); + return 0; + } + function unpack(r, p) { + var t = gf(), chk = gf(), num = gf(), + den = gf(), den2 = gf(), den4 = gf(), + den6 = gf(); + + set25519(r[2], gf1); + unpack25519(r[1], p); + + // num = u = y^2 - 1 + // den = v = d * y^2 + 1 S(num, r[1]); M(den, num, D); Z(num, num, r[2]); A(den, r[2], den); + // r[0] = x = sqrt(u / v) S(den2, den); S(den4, den2); M(den6, den4, den2); @@ -827,14 +868,8 @@ return -1; } - if (negate) { - if (par25519(r[0]) === (p[31]>>7)) { - Z(r[0], gf0, r[0]) - } - } else { - if (par25519(r[0]) !== (p[31]>>7)) { - Z(r[0], gf0, r[0]); - } + if (par25519(r[0]) !== (p[31]>>7)) { + Z(r[0], gf0, r[0]); } M(r[3], r[0], r[1]); From aed84791743f28b72cc1bcbe1308e26bcee8f5eb Mon Sep 17 00:00:00 2001 From: AnthonyLaw Date: Mon, 10 Jan 2022 20:18:47 +0800 Subject: [PATCH 14/14] fix: refactor unpack --- src/core/nem/external/nacl-fast.js | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/src/core/nem/external/nacl-fast.js b/src/core/nem/external/nacl-fast.js index 286c26b..0b1dfe3 100644 --- a/src/core/nem/external/nacl-fast.js +++ b/src/core/nem/external/nacl-fast.js @@ -737,7 +737,7 @@ var p = [gf(), gf(), gf(), gf()]; var q = [gf(), gf(), gf(), gf()]; - if (unpackneg(q, pk)) return false; + if (unpack(q, pk, true)) return false; hasher.reset(); hasher.update(signature.subarray(0, 64/2)); @@ -827,22 +827,20 @@ return 0; } - function unpack(r, p) { + // A method modified from unpackneg. + // Support unpack and unpackneg. + function unpack(r, p, isNegate = false) { var t = gf(), chk = gf(), num = gf(), den = gf(), den2 = gf(), den4 = gf(), den6 = gf(); set25519(r[2], gf1); unpack25519(r[1], p); - - // num = u = y^2 - 1 - // den = v = d * y^2 + 1 S(num, r[1]); M(den, num, D); Z(num, num, r[2]); A(den, r[2], den); - // r[0] = x = sqrt(u / v) S(den2, den); S(den4, den2); M(den6, den4, den2); @@ -857,19 +855,20 @@ S(chk, r[0]); M(chk, chk, den); - if (neq25519(chk, num)) { - M(r[0], r[0], I); - } + if (neq25519(chk, num)) M(r[0], r[0], I); S(chk, r[0]); M(chk, chk, den); - if (neq25519(chk, num)) { - console.log("not a valid Ed25519EncodedGroupElement."); - return -1; - } + if (neq25519(chk, num)) return -1; - if (par25519(r[0]) !== (p[31]>>7)) { - Z(r[0], gf0, r[0]); + if (isNegate) { + if (par25519(r[0]) === (p[31]>>7)) { + Z(r[0], gf0, r[0]) + } + } else { + if (par25519(r[0]) !== (p[31]>>7)) { + Z(r[0], gf0, r[0]); + } } M(r[3], r[0], r[1]);