diff --git a/package.json b/package.json index 9f1cc06fae..e4c6ce75a8 100644 --- a/package.json +++ b/package.json @@ -69,5 +69,6 @@ "semver": "https://github.com/RequestNetwork/requestNetwork/security/dependabot/197", "json-schema": "https://github.com/RequestNetwork/requestNetwork/security/dependabot/51", "json5": "https://github.com/RequestNetwork/requestNetwork/security/dependabot/165" - } + }, + "packageManager": "yarn@1.22.21+sha1.1959a18351b811cdeedbd484a8f86c3cc3bbaf72" } diff --git a/packages/payment-detection/package.json b/packages/payment-detection/package.json index d26cdb9215..1a2e7526ea 100644 --- a/packages/payment-detection/package.json +++ b/packages/payment-detection/package.json @@ -57,7 +57,7 @@ "@graphql-codegen/cli": "4.0.1", "@graphql-codegen/typescript": "4.0.1", "@graphql-codegen/typescript-document-nodes": "4.0.1", - "@graphql-codegen/typescript-graphql-request": "6.0.1", + "@graphql-codegen/typescript-graphql-request": "6.2.0", "@graphql-codegen/typescript-operations": "4.0.1", "@graphql-codegen/typescript-resolvers": "4.0.1", "@jridgewell/gen-mapping": "0.3.2", diff --git a/packages/payment-detection/src/thegraph/client.ts b/packages/payment-detection/src/thegraph/client.ts index 7b9d9b1968..3654c40ccd 100644 --- a/packages/payment-detection/src/thegraph/client.ts +++ b/packages/payment-detection/src/thegraph/client.ts @@ -2,9 +2,8 @@ import { CurrencyTypes } from '@requestnetwork/types'; import { NearChains } from '@requestnetwork/currency'; import { GraphQLClient } from 'graphql-request'; -import { Block_Height, Maybe, getSdk } from './generated/graphql'; +import { Block_Height, getSdk, Maybe } from './generated/graphql'; import { getSdk as getNearSdk } from './generated/graphql-near'; -import { RequestConfig } from 'graphql-request/src/types'; const THE_GRAPH_STUDIO_URL = 'https://api.studio.thegraph.com/query/67444/request-payments-$NETWORK/version/latest'; @@ -41,6 +40,8 @@ export type TheGraphQueryOptions = { blockFilter?: Maybe; }; +type RequestConfig = (typeof GraphQLClient.prototype)['requestConfig']; + export type TheGraphClientOptions = RequestConfig & { /** constraint to select indexers that have at least parsed this block */ minIndexedBlock?: number | undefined; diff --git a/packages/payment-detection/src/thegraph/superfluid.ts b/packages/payment-detection/src/thegraph/superfluid.ts index 941d2bc2b8..fae40e9b0c 100644 --- a/packages/payment-detection/src/thegraph/superfluid.ts +++ b/packages/payment-detection/src/thegraph/superfluid.ts @@ -1,6 +1,5 @@ import { GraphQLClient } from 'graphql-request'; import { getSdk } from './generated/graphql-superfluid'; -import { RequestConfig } from 'graphql-request/src/types'; const BASE_URL = `https://subgraph-endpoints.superfluid.dev`; const NETWORK_TO_URL: Record = { @@ -22,6 +21,7 @@ const NETWORK_TO_URL: Record = { /** * A GraphQL client to query Superfluid's subgraph. */ +type RequestConfig = (typeof GraphQLClient.prototype)['requestConfig']; export type TheGraphSuperfluidClient = ReturnType; export type TheGraphClientOptions = RequestConfig & { baseUrl?: string; diff --git a/packages/request-node/package.json b/packages/request-node/package.json index 214f63dbad..5126fe9c12 100644 --- a/packages/request-node/package.json +++ b/packages/request-node/package.json @@ -56,7 +56,7 @@ "@requestnetwork/utils": "0.54.0", "chalk": "4.1.0", "cors": "2.8.5", - "dotenv": "8.2.0", + "dotenv": "16.5.0", "ethers": "5.7.2", "express": "4.21.0", "graphql": "16.8.1", diff --git a/packages/smart-contracts/package.json b/packages/smart-contracts/package.json index fb90e4d163..72d33bf0b0 100644 --- a/packages/smart-contracts/package.json +++ b/packages/smart-contracts/package.json @@ -78,7 +78,7 @@ "@types/mocha": "8.2.3", "@types/node": "18.11.9", "chai": "4.3.4", - "dotenv": "10.0.0", + "dotenv": "16.5.0", "ethereum-waffle": "3.4.4", "ethers": "5.7.2", "ganache-cli": "6.12.0", diff --git a/packages/thegraph-data-access/package.json b/packages/thegraph-data-access/package.json index 79a5c2c415..8d740665fe 100644 --- a/packages/thegraph-data-access/package.json +++ b/packages/thegraph-data-access/package.json @@ -45,7 +45,7 @@ "@requestnetwork/types": "0.54.0", "@requestnetwork/utils": "0.54.0", "ethers": "5.7.2", - "graphql-request": "7.1.2", + "graphql-request": "6.1.0", "tslib": "2.5.0" }, "devDependencies": { diff --git a/packages/thegraph-data-access/src/subgraph-client.ts b/packages/thegraph-data-access/src/subgraph-client.ts index 322542153b..937fe16dbf 100644 --- a/packages/thegraph-data-access/src/subgraph-client.ts +++ b/packages/thegraph-data-access/src/subgraph-client.ts @@ -1,5 +1,5 @@ import { DataAccessTypes, StorageTypes } from '@requestnetwork/types'; -import { GraphQLClient } from 'graphql-request'; +import { GraphQLClient, Variables } from 'graphql-request'; import { GetBlockQuery, GetTransactionByDataHashQuery, @@ -10,12 +10,12 @@ import { Transaction, TransactionsBody, } from './queries'; -import { Variables } from 'graphql-request/build/cjs/types'; -import { RequestConfig } from 'graphql-request/build/legacy/helpers/types'; // Max Int value (as supported by grapqhl types) const MAX_INT_VALUE = 0x7fffffff; +type RequestConfig = (typeof GraphQLClient.prototype)['requestConfig']; + type ClientConfig = Omit & { headers?: Record }; export class SubgraphClient implements StorageTypes.IIndexer { diff --git a/packages/thegraph-data-access/src/types.ts b/packages/thegraph-data-access/src/types.ts index ffe0828a62..8bf0c32845 100644 --- a/packages/thegraph-data-access/src/types.ts +++ b/packages/thegraph-data-access/src/types.ts @@ -1,6 +1,8 @@ import { StorageTypes } from '@requestnetwork/types'; import { DataAccessBaseOptions } from '@requestnetwork/data-access'; -import { RequestConfig } from 'graphql-request/build/legacy/helpers/types'; +import { GraphQLClient } from 'graphql-request'; + +type RequestConfig = (typeof GraphQLClient.prototype)['requestConfig']; export type TheGraphDataAccessOptions = DataAccessBaseOptions & { graphql: { url: string } & Omit & { headers?: Record }; diff --git a/packages/utils/package.json b/packages/utils/package.json index 845c2efd10..fe8f7830a1 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -40,10 +40,12 @@ "test:watch": "yarn test --watch" }, "dependencies": { + "@ecies/ciphers": "0.2.3", + "@noble/curves": "1.8.1", + "@noble/hashes": "1.7.1", "@requestnetwork/types": "0.54.0", - "@toruslabs/eccrypto": "4.0.0", + "eciesjs": "0.4.14", "ethers": "5.7.2", - "secp256k1": "4.0.4", "tslib": "2.5.0" }, "devDependencies": { diff --git a/packages/utils/src/crypto.ts b/packages/utils/src/crypto.ts index bc99fa3769..e230eb0b60 100644 --- a/packages/utils/src/crypto.ts +++ b/packages/utils/src/crypto.ts @@ -10,10 +10,10 @@ import { import { ecDecrypt, ecEncrypt, - getAddressFromPrivateKey, - getAddressFromPublicKey, ecRecover, ecSign, + getAddressFromPrivateKey, + getAddressFromPublicKey, } from './crypto/ec-utils'; import { deepSort } from './utils'; diff --git a/packages/utils/src/crypto/crypto-wrapper.ts b/packages/utils/src/crypto/crypto-wrapper.ts index 50b0151868..68b31b91c7 100644 --- a/packages/utils/src/crypto/crypto-wrapper.ts +++ b/packages/utils/src/crypto/crypto-wrapper.ts @@ -1,7 +1,7 @@ import { createCipheriv, createDecipheriv, randomBytes as cryptoRandomBytes } from 'crypto'; /** - * Functions to manage native crypto functions of nodeJs + * Functions to manage native crypto functions of Node.js */ export { decryptWithAes256cbc, @@ -77,7 +77,7 @@ async function encryptWithAes256gcm(data: Buffer, key: Buffer): Promise /** * Decrypts an encrypted buffer using AES-256-cbc plus a random Initialization Vector (IV) * - * @param encrypted the data to decrypt + * @param encryptedAndIv the data to decrypt * @param key key of the encryption * * @returns Promise resolving a buffer containing the data decrypted diff --git a/packages/utils/src/crypto/ec-utils-legacy.ts b/packages/utils/src/crypto/ec-utils-legacy.ts new file mode 100644 index 0000000000..2bcec8e0c9 --- /dev/null +++ b/packages/utils/src/crypto/ec-utils-legacy.ts @@ -0,0 +1,81 @@ +import { PrivateKey, PublicKey } from 'eciesjs'; +import { secp256k1 } from '@noble/curves/secp256k1'; +import { sha256, sha512 } from '@noble/hashes/sha2'; +import { aes256cbc } from '@ecies/ciphers/aes'; +import { hmac } from '@noble/hashes/hmac'; + +/** + * Decrypt the `eccrypto` way: using ECIES with AES-CBC-MAC and SHA-512 derivation. + * Migrated from https://github.com/torusresearch/eccrypto/blob/923ebc03e5be016a7ee27a04d8c3b496ee949bfa/src/index.ts#L264 + * but using `@noble/curves` instead of `elliptics` + */ +export const ecDecryptLegacy = (privateKey: string, dataHex: string, padding = false): string => { + const { iv, ephemPublicKey, mac, ciphertext } = legacyAes256CbcMacSplit(dataHex); + const receiverPrivateKey = PrivateKey.fromHex(privateKey.replace(/^0x/, '')); + const sharedKey = deriveSharedKeyWithSha512(receiverPrivateKey, ephemPublicKey, padding); + const encryptionKey = sharedKey.subarray(0, 32); + const macKey = sharedKey.subarray(32); + const dataToMac = Buffer.concat([iv, ephemPublicKey.toBytes(false), ciphertext]); + const macGood = hmacSha256Verify(macKey, dataToMac, mac); + if (!macGood) { + if (!padding) { + return ecDecryptLegacy(privateKey, dataHex, true); + } + throw new Error('The encrypted data is not well formatted'); + } + const decrypted = aes256cbc(encryptionKey, iv).decrypt(ciphertext); + return Buffer.from(decrypted).toString(); +}; + +const hmacSha256Verify = (key: Uint8Array, msg: Uint8Array, sig: Uint8Array): boolean => { + const expectedSig = hmac(sha256, key, msg); + return equalConstTime(expectedSig, sig); +}; + +// Compare two buffers in constant time to prevent timing attacks. +const equalConstTime = (b1: Uint8Array, b2: Uint8Array): boolean => { + if (b1.length !== b2.length) { + return false; + } + let res = 0; + for (let i = 0; i < b1.length; i++) { + res |= b1[i] ^ b2[i]; + } + return res === 0; +}; + +const deriveSharedKeyWithSha512 = ( + privateKey: PrivateKey, + publicKey: PublicKey, + padding = false, +): Uint8Array => { + const sharedPoint = secp256k1.getSharedSecret(privateKey.secret, publicKey.toBytes()); + const paddedBytes = padding ? sharedPoint.subarray(1) : sharedPoint.subarray(2); + const hash = sha512.create().update(paddedBytes).digest(); + return new Uint8Array(hash); +}; + +/** + * Split a legacy-encrypted hex string to its AES-CBC-MAC params. + * See legacy way of generating an encrypted strings with the `@toruslabs/eccrypto` > `elliptic` library: + * https://github.com/RequestNetwork/requestNetwork/blob/4597d373b0284787273471cf306dd9b849c9f76a/packages/utils/src/crypto/ec-utils.ts#L141 + */ +const legacyAes256CbcMacSplit = (dataHex: string) => { + const buffer = Buffer.from(dataHex, 'hex'); + + const ivSize = 16; + const ephemPublicKeySize = 33; + const ephemPublicKeyEnd = ivSize + ephemPublicKeySize; + const macSize = 32; + const macEnd = ephemPublicKeyEnd + macSize; + + const ephemPublicKeyStr = buffer.subarray(ivSize, ephemPublicKeyEnd); + const ephemPublicKey = new PublicKey(ephemPublicKeyStr); + + return { + iv: buffer.subarray(0, ivSize), + ephemPublicKey, + mac: buffer.subarray(ephemPublicKeyEnd, macEnd), + ciphertext: buffer.subarray(macEnd), + }; +}; diff --git a/packages/utils/src/crypto/ec-utils.ts b/packages/utils/src/crypto/ec-utils.ts index 03c3419260..a4a2d620f7 100644 --- a/packages/utils/src/crypto/ec-utils.ts +++ b/packages/utils/src/crypto/ec-utils.ts @@ -1,6 +1,12 @@ -import { publicKeyConvert, ecdsaRecover } from 'secp256k1'; -import { ethers } from 'ethers'; -import { Ecies, decrypt, encrypt } from '@toruslabs/eccrypto'; +import { decrypt, ECIES_CONFIG, encrypt, PublicKey } from 'eciesjs'; +import { + computeAddress, + hexlify, + joinSignature, + recoverPublicKey, + SigningKey, +} from 'ethers/lib/utils'; +import { ecDecryptLegacy } from './ec-utils-legacy'; /** * Function to manage Elliptic-curve cryptography @@ -14,6 +20,11 @@ export { ecSign, }; +ECIES_CONFIG.ellipticCurve = 'secp256k1'; +ECIES_CONFIG.isEphemeralKeyCompressed = false; +ECIES_CONFIG.symmetricAlgorithm = 'aes-256-gcm'; +ECIES_CONFIG.symmetricNonceLength = 16; + /** * Function to derive the address from an EC private key * @@ -26,7 +37,7 @@ function getAddressFromPrivateKey(privateKey: string): string { if (!privateKey.match(/^0x/)) { privateKey = `0x` + privateKey; } - return ethers.utils.computeAddress(ethers.utils.hexlify(privateKey)); + return computeAddress(hexlify(privateKey)); } catch (e) { if ( e.message === 'private key length is invalid' || @@ -48,13 +59,10 @@ function getAddressFromPrivateKey(privateKey: string): string { */ function getAddressFromPublicKey(publicKey: string): string { try { - return ethers.utils.computeAddress(compressPublicKey(publicKey)); + const compressedKey = PublicKey.fromHex(publicKey).toHex(true); + return computeAddress(`0x${compressedKey}`); } catch (e) { - if ( - e.message === 'public key length is invalid' || - e.message === 'Expected public key to be an Uint8Array with length [33, 65]' || - e.code === 'INVALID_ARGUMENT' - ) { + if (e.code === 'INVALID_ARGUMENT' || e.message === 'second arg must be public key') { throw new Error('The public key must be a string representing 64 bytes'); } throw e; @@ -62,22 +70,19 @@ function getAddressFromPublicKey(publicKey: string): string { } /** - * Function ecSigndata with ECDSA + * Function ecSign data with ECDSA * + * @param privateKey the private key used to sign the message * @param data the data to sign * * @returns the signature */ function ecSign(privateKey: string, data: string): string { try { - const signingKey = new ethers.utils.SigningKey(privateKey); - return ethers.utils.joinSignature(signingKey.signDigest(data)); + const signingKey = new SigningKey(privateKey); + return joinSignature(signingKey.signDigest(data)); } catch (e) { - if ( - e.message === 'private key length is invalid' || - e.message === 'Expected private key to be an Uint8Array with length 32' || - e.code === 'INVALID_ARGUMENT' - ) { + if (e.code === 'INVALID_ARGUMENT') { throw new Error('The private key must be a string representing 32 bytes'); } throw e; @@ -94,30 +99,9 @@ function ecSign(privateKey: string, data: string): string { */ function ecRecover(signature: string, data: string): string { try { - signature = signature.replace(/^0x/, ''); - data = data.replace(/^0x/, ''); - // split into v-value and sig - const sigOnly = signature.substring(0, signature.length - 2); // all but last 2 chars - const vValue = signature.slice(-2); // last 2 chars - - const recoveryNumber = vValue === '1c' ? 1 : 0; - - return ethers.utils.computeAddress( - Buffer.from( - ecdsaRecover( - new Uint8Array(Buffer.from(sigOnly, 'hex')), - recoveryNumber, - new Uint8Array(Buffer.from(data, 'hex')), - false, - ), - ), - ); + return computeAddress(recoverPublicKey(data, signature)); } catch (e) { - if ( - e.message === 'signature length is invalid' || - e.message === 'Expected signature to be an Uint8Array with length 64' || - e.code === 'INVALID_ARGUMENT' - ) { + if (e.code === 'INVALID_ARGUMENT') { throw new Error('The signature must be a string representing 66 bytes'); } throw e; @@ -130,26 +114,13 @@ function ecRecover(signature: string, data: string): string { * @param publicKey the public key to encrypt with * @param data the data to encrypt * - * @returns the encrypted data + * @returns the encrypted data as a hex string */ -async function ecEncrypt(publicKey: string, data: string): Promise { +function ecEncrypt(publicKey: string, data: string): string { try { - // encrypts the data with the publicKey, returns the encrypted data with encryption parameters (such as IV..) - const compressed = compressPublicKey(publicKey); - const encrypted = await encrypt(Buffer.from(compressed), Buffer.from(data)); - - // Transforms the object with the encrypted data into a smaller string-representation. - return Buffer.concat([ - encrypted.iv, - publicKeyConvert(encrypted.ephemPublicKey), - encrypted.mac, - encrypted.ciphertext, - ]).toString('hex'); + return encrypt(publicKey, Buffer.from(data)).toString('hex'); } catch (e) { - if ( - e.message === 'public key length is invalid' || - e.message === 'Expected public key to be an Uint8Array with length [33, 65]' - ) { + if (e.message === 'second arg must be public key') { throw new Error('The public key must be a string representing 64 bytes'); } throw e; @@ -160,62 +131,23 @@ async function ecEncrypt(publicKey: string, data: string): Promise { * Function to decrypt data with a public key * * @param privateKey the private key to decrypt with - * @param data the data to decrypt + * @param dataHex the hex data to decrypt * * @returns the decrypted data */ -async function ecDecrypt(privateKey: string, data: string): Promise { +function ecDecrypt(privateKey: string, dataHex: string): string { try { - const buf = await decrypt(Buffer.from(privateKey.replace(/^0x/, ''), 'hex'), eciesSplit(data)); - return buf.toString(); + return decrypt(privateKey.replace(/^0x/, ''), Buffer.from(dataHex, 'hex')).toString(); } catch (e) { - if ( - e.message === 'Bad private key' || - e.message === 'Expected private key to be an Uint8Array with length 32' - ) { + if (e.message.startsWith('invalid Point')) { + return ecDecryptLegacy(privateKey, dataHex); + } + if (e.message === 'Invalid private key') { throw new Error('The private key must be a string representing 32 bytes'); } - if ( - e.message === 'public key length is invalid' || - e.message === 'Expected public key to be an Uint8Array with length [33, 65]' || - e.message === 'Bad MAC' || - e.message === 'bad MAC after trying padded' || - e.message === 'the public key could not be parsed or is invalid' || - e.message === 'Public Key could not be parsed' - ) { + if (e.message === 'second arg must be public key') { throw new Error('The encrypted data is not well formatted'); } throw e; } } - -/** - * Converts a public key to its compressed form. - */ -function compressPublicKey(publicKey: string): Uint8Array { - publicKey = publicKey.replace(/^0x/, ''); - // if there are more bytes than the key itself, it means there is already a prefix - if (publicKey.length % 32 === 0) { - publicKey = `04${publicKey}`; - } - return publicKeyConvert(Buffer.from(publicKey, 'hex')); -} - -/** - * Split an encrypted string to ECIES params - * inspired from https://github.com/pubkey/eth-crypto/blob/master/src/ecDecrypt-with-private-key.js - */ -const eciesSplit = (str: string): Ecies => { - const buf = Buffer.from(str, 'hex'); - - const ephemPublicKeyStr = buf.toString('hex', 16, 49); - - return { - iv: Buffer.from(buf.toString('hex', 0, 16), 'hex'), - mac: Buffer.from(buf.toString('hex', 49, 81), 'hex'), - ciphertext: Buffer.from(buf.toString('hex', 81, buf.length), 'hex'), - ephemPublicKey: Buffer.from( - publicKeyConvert(new Uint8Array(Buffer.from(ephemPublicKeyStr, 'hex')), false), - ), - }; -}; diff --git a/packages/utils/test/crypto/ec-utils.test.ts b/packages/utils/test/crypto/ec-utils.test.ts index 0b105f8a9b..958105b410 100644 --- a/packages/utils/test/crypto/ec-utils.test.ts +++ b/packages/utils/test/crypto/ec-utils.test.ts @@ -1,3 +1,4 @@ +import { randomBytes } from '@noble/hashes/utils'; import { ecDecrypt, ecEncrypt, @@ -6,8 +7,9 @@ import { getAddressFromPrivateKey, getAddressFromPublicKey, } from '../../src'; -import { eccryptoNativeData } from './data/crypto-native'; import { eccryptoBrowserData } from './data/crypto-browser'; +import { eccryptoNativeData } from './data/crypto-native'; +import { PrivateKey } from 'eciesjs'; const rawId = { address: '0x818B6337657A23F58581715Fc610577292e521D0', @@ -89,21 +91,21 @@ describe('Utils/EcUtils', () => { describe('encrypt', () => { it('can encrypt', async () => { - const encryptedData = await ecEncrypt(rawId.publicKey, anyData); - expect(encryptedData.length).toBe(226); - expect(await ecDecrypt(rawId.privateKey, encryptedData)).toBe(anyData); + const encryptedData = ecEncrypt(rawId.publicKey, anyData); + expect(encryptedData.length).toBe(228); + expect(ecDecrypt(rawId.privateKey, encryptedData)).toBe(anyData); }); it('can encrypt with other public key formats', async () => { - const encryptedData = await ecEncrypt( + const encryptedData = ecEncrypt( '0396212fc129c2f78771218b2e93da7a5aac63490a42bb41b97848c39c14fe65cd', anyData, ); - expect(encryptedData.length).toBe(226); + expect(encryptedData.length).toBe(228); }); it('cannot encrypt data with a wrong public key', async () => { - await expect(ecEncrypt('cf4a', anyData)).rejects.toThrowError( + expect(() => ecEncrypt('cf4a', anyData)).toThrow( 'The public key must be a string representing 64 bytes', ); }); @@ -111,55 +113,76 @@ describe('Utils/EcUtils', () => { describe('decrypt', () => { it('can decrypt', async () => { - const data = await ecDecrypt( + const data = ecDecrypt( rawId.privateKey, - '307bac038efaa5bf8a0ac8db53fd4de8024a0c0baf37283a9e6671589eba18edc12b3915ff0df66e6ffad862440228a65ead99e3320e50aa90008961e3d68acc35b314e98020e3280bf4ce4258419dbb775185e60b43e7b88038a776a9322ff7cb3e886b2d92060cff2951ef3beedcc70a', + '04f5ef23cfd828b7910d7c909eef047729b0cb986ebf4ba12ce877ec455863c6b9350e06f3f51479fa6b0a3feeb4c9f6fa808d4e6393d570627636642df35b1f85d59bb5bd78fdbaacaaa557c7d472ff7c5bbf8d8df59c0bd5d856831f1c0bcb77ad55bd82149a3bcb729f534fcdb62efa64', ); expect(data).toBe(anyData); }); - it('cannot decrypt data with a wrong private key', async () => { - await expect( - ecDecrypt( - '0xaa', - '307bac038efaa5bf8a0ac8db53fd4de8024a0c0baf37283a9e6671589eba18edc12b3915ff0df66e6ffad862440228a65ead99e3320e50aa90008961e3d68acc35b314e98020e3280bf4ce4258419dbb775185e60b43e7b88038a776a9322ff7cb3e886b2d92060cff2951ef3beedcc70a', - ), - ).rejects.toThrowError('The private key must be a string representing 32 bytes'); - }); - - it('cannot decrypt data with a wrong encrypted data: public key too short', async () => { - await expect(ecDecrypt(rawId.privateKey, 'aa')).rejects.toThrowError( - 'The encrypted data is not well formatted', - ); + it('can decrypt random data', async () => { + for (let i = 0; i < 10; i++) { + const privateKey = new PrivateKey(); + const data = Buffer.from(randomBytes(32)).toString('hex'); + const encrypted = ecEncrypt(privateKey.publicKey.toHex(Math.random() >= 0.5), data); + const decrypted = ecDecrypt(privateKey.secret.toString('hex'), encrypted); + expect(decrypted).toBe(data); + } }); - it('cannot decrypt data with a wrong encrypted data: public key not parsable', async () => { - await expect( - ecDecrypt( + describe('legacy dataset (AES-CBC-MAC)', () => { + it('can decrypt legacy format', async () => { + const data = ecDecrypt( rawId.privateKey, - 'e50aa90008961e3d68acc35b314e98020e3280bf4ce4258419dbb775185e60b43e7b88038a776a9322ff7cb3e886b2d92060cff2951ef3beedcc7', - ), - ).rejects.toThrowError('The encrypted data is not well formatted'); - }); + '307bac038efaa5bf8a0ac8db53fd4de8024a0c0baf37283a9e6671589eba18edc12b3915ff0df66e6ffad862440228a65ead99e3320e50aa90008961e3d68acc35b314e98020e3280bf4ce4258419dbb775185e60b43e7b88038a776a9322ff7cb3e886b2d92060cff2951ef3beedcc70a', + ); + expect(data).toBe(anyData); + }); - it('cannot decrypt data with a wrong encrypted data: bad MAC', async () => { - await expect( - ecDecrypt( - rawId.privateKey, - '307bac038efaa5bf8a0ac8db53fd4de8024a0c0baf37283a9e6671589eba18edc12b3915ff0df66e6ffad862440228a65ead99e3320e50aa90008961e3d68acc35b314e98020e3280bf4ce4258419dbb775185e60b43e7b88038a776a9322ff7cb3e886b2d92060cff2951ef3beedcc7', - ), - ).rejects.toThrowError('The encrypted data is not well formatted'); - }); + it('cannot decrypt data with a wrong private key', async () => { + expect(() => + ecDecrypt( + '0xaa', + '307bac038efaa5bf8a0ac8db53fd4de8024a0c0baf37283a9e6671589eba18edc12b3915ff0df66e6ffad862440228a65ead99e3320e50aa90008961e3d68acc35b314e98020e3280bf4ce4258419dbb775185e60b43e7b88038a776a9322ff7cb3e886b2d92060cff2951ef3beedcc70a', + ), + ).toThrow('The private key must be a string representing 32 bytes'); + }); - it.each([ - { type: 'native', array: eccryptoNativeData }, - { type: 'browser', array: eccryptoBrowserData }, - ])('should be compatible with legacy $type implementation of eccrypto', async ({ array }) => { - for (const row of array) { - const { data, key, encrypted } = row; - const decrypted = await ecDecrypt(key, encrypted); - expect(decrypted).toBe(data); - } + it('cannot decrypt data with a wrong encrypted data: public key too short', async () => { + expect(() => ecDecrypt(rawId.privateKey, 'aa')).toThrow( + 'The encrypted data is not well formatted', + ); + }); + + it('cannot decrypt data with a wrong encrypted data: public key not parsable', async () => { + expect(() => + ecDecrypt( + rawId.privateKey, + 'e50aa90008961e3d68acc35b314e98020e3280bf4ce4258419dbb775185e60b43e7b88038a776a9322ff7cb3e886b2d92060cff2951ef3beedcc7', + ), + ).toThrow('The encrypted data is not well formatted'); + }); + + it('cannot decrypt data with a wrong encrypted data: bad MAC', async () => { + expect(() => + ecDecrypt( + rawId.privateKey, + '307bac038efaa5bf8a0ac8db53fd4de8024a0c0baf37283a9e6671589eba18edc12b3915ff0df66e6ffad862440228a65ead99e3320e50aa90008961e3d68acc35b314e98020e3280bf4ce4258419dbb775185e60b43e7b88038a776a9322ff7cb3e886b2d92060cff2951ef3beedcc7', + ), + ).toThrow('The encrypted data is not well formatted'); + }); + + // test for https://github.com/RequestNetwork/requestNetwork/pull/1229 + it.each([ + { type: 'native', array: eccryptoNativeData }, + { type: 'browser', array: eccryptoBrowserData }, + ])('should be compatible with legacy $type implementation of eccrypto', async ({ array }) => { + for (const row of array) { + const { data, key, encrypted } = row; + const decrypted = ecDecrypt(key, encrypted); + expect(decrypted).toBe(data); + } + }); }); }); }); diff --git a/tsconfig.json b/tsconfig.json index 84638a12a9..84b8eff768 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -10,7 +10,7 @@ "strict": true, "baseUrl": "./packages", "skipLibCheck": true, - "moduleResolution": "node", + "moduleResolution": "nodenext", "resolveJsonModule": true, "useUnknownInCatchVariables": false, "lib": ["es2019"] diff --git a/yarn.lock b/yarn.lock index 445b3f98d6..fd0c149136 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1772,6 +1772,11 @@ is-absolute "^1.0.0" is-negated-glob "^1.0.0" +"@ecies/ciphers@0.2.3", "@ecies/ciphers@^0.2.2": + version "0.2.3" + resolved "https://registry.yarnpkg.com/@ecies/ciphers/-/ciphers-0.2.3.tgz#963805e46d07e646550098ac29cbcc5b132218ea" + integrity sha512-tapn6XhOueMwht3E2UzY0ZZjYokdaw9XtL9kEyjhQ/Fb9vL9xTFbOaI+fV0AWvTpYu4BNloC6getKW6NtSg4mA== + "@ensdomains/address-encoder@^0.1.7": version "0.1.9" resolved "https://registry.npmjs.org/@ensdomains/address-encoder/-/address-encoder-0.1.9.tgz" @@ -3411,10 +3416,10 @@ auto-bind "~4.0.0" tslib "~2.5.0" -"@graphql-codegen/typescript-graphql-request@6.0.1": - version "6.0.1" - resolved "https://registry.npmjs.org/@graphql-codegen/typescript-graphql-request/-/typescript-graphql-request-6.0.1.tgz" - integrity sha512-aScw7ICyscW7bYLh2HyjQU3geCAjvFy6sRIlzgdkeFvcKBdjCil69upkyZAyntnSno2C4ZoUv7sHOpyQ9hQmFQ== +"@graphql-codegen/typescript-graphql-request@6.2.0": + version "6.2.0" + resolved "https://registry.yarnpkg.com/@graphql-codegen/typescript-graphql-request/-/typescript-graphql-request-6.2.0.tgz#db3bd90cd9070d446b8039384476cc1029929617" + integrity sha512-nkp5tr4PrC/+2QkQqi+IB+bc7AavUnUvXPW8MC93HZRvwfMGy6m2Oo7b9JCPZ3vhNpqT2VDWOn/zIZXKz6zJAw== dependencies: "@graphql-codegen/plugin-helpers" "^3.0.0" "@graphql-codegen/visitor-plugin-common" "2.13.1" @@ -3423,7 +3428,7 @@ "@graphql-codegen/typescript-operations@4.0.1": version "4.0.1" - resolved "https://registry.npmjs.org/@graphql-codegen/typescript-operations/-/typescript-operations-4.0.1.tgz" + resolved "https://registry.yarnpkg.com/@graphql-codegen/typescript-operations/-/typescript-operations-4.0.1.tgz#930af3e2d2ae8ff06de696291be28fe7046a2fef" integrity sha512-GpUWWdBVUec/Zqo23aFLBMrXYxN2irypHqDcKjN78JclDPdreasAEPcIpMfqf4MClvpmvDLy4ql+djVAwmkjbw== dependencies: "@graphql-codegen/plugin-helpers" "^5.0.0" @@ -4969,6 +4974,11 @@ "@near-js/utils" "0.2.2" borsh "1.0.0" +"@noble/ciphers@^1.0.0": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@noble/ciphers/-/ciphers-1.2.1.tgz#3812b72c057a28b44ff0ad4aff5ca846e5b9cdc9" + integrity sha512-rONPWMC7PeExE077uLE4oqWrZ1IvAfz3oH9LibVAcVCopJiA9R62uavnbEzdkVmJYI6M6Zgkbeb07+tWjlq2XA== + "@noble/curves@1.1.0", "@noble/curves@~1.1.0": version "1.1.0" resolved "https://registry.npmjs.org/@noble/curves/-/curves-1.1.0.tgz" @@ -4983,6 +4993,13 @@ dependencies: "@noble/hashes" "1.3.2" +"@noble/curves@1.8.1", "@noble/curves@^1.6.0": + version "1.8.1" + resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.8.1.tgz#19bc3970e205c99e4bdb1c64a4785706bce497ff" + integrity sha512-warwspo+UYUPep0Q+vtdVB4Ugn8GGQj8iyB3gnRWsztmUHTI3S1nhdiWNsPUGL0vud7JlRRk1XEu7Lq1KGTnMQ== + dependencies: + "@noble/hashes" "1.7.1" + "@noble/hashes@1.2.0", "@noble/hashes@~1.2.0": version "1.2.0" resolved "https://registry.npmjs.org/@noble/hashes/-/hashes-1.2.0.tgz" @@ -5003,6 +5020,11 @@ resolved "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.3.tgz" integrity sha512-V7/fPHgl+jsVPXqqeOzT8egNj2iBIVt+ECeMMG8TdcnTikP3oaBtUVqpT/gYCR68aEBJSF+XbYUxStjbFMqIIA== +"@noble/hashes@1.7.1", "@noble/hashes@^1.5.0": + version "1.7.1" + resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.7.1.tgz#5738f6d765710921e7a751e00c20ae091ed8db0f" + integrity sha512-B8XBPsn4vT/KJAGqDzbwztd+6Yte3P4V7iafm24bxgDe/mlRuK6xmWPuCNrKt2vDafZ8MfJLlchDG/vYafQEjQ== + "@noble/hashes@^1.1.2": version "1.5.0" resolved "https://registry.npmjs.org/@noble/hashes/-/hashes-1.5.0.tgz" @@ -6350,13 +6372,6 @@ resolved "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz" integrity sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A== -"@toruslabs/eccrypto@4.0.0": - version "4.0.0" - resolved "https://registry.npmjs.org/@toruslabs/eccrypto/-/eccrypto-4.0.0.tgz" - integrity sha512-Z3EINkbsgJx1t6jCDVIJjLSUEGUtNIeDjhMWmeDGOWcP/+v/yQ1hEvd1wfxEz4q5WqIHhevacmPiVxiJ4DljGQ== - dependencies: - elliptic "^6.5.4" - "@truffle/abi-utils@^0.2.11": version "0.2.11" resolved "https://registry.npmjs.org/@truffle/abi-utils/-/abi-utils-0.2.11.tgz" @@ -11991,15 +12006,10 @@ dot@^1.1.3: resolved "https://registry.npmjs.org/dot/-/dot-1.1.3.tgz" integrity sha512-/nt74Rm+PcfnirXGEdhZleTwGC2LMnuKTeeTIlI82xb5loBBoXNYzr2ezCroPSMtilK8EZIfcNZwOcHN+ib1Lg== -dotenv@10.0.0, dotenv@~10.0.0: - version "10.0.0" - resolved "https://registry.npmjs.org/dotenv/-/dotenv-10.0.0.tgz" - integrity sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q== - -dotenv@8.2.0: - version "8.2.0" - resolved "https://registry.npmjs.org/dotenv/-/dotenv-8.2.0.tgz" - integrity sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw== +dotenv@16.5.0: + version "16.5.0" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.5.0.tgz#092b49f25f808f020050051d1ff258e404c78692" + integrity sha512-m/C+AwOAr9/W1UOIZUo232ejMNnJAJtYQjUbHoNTBNTJSvqzzDh7vnrei3o3r3m9blf6ZoDkvcw0VmozNRFJxg== dotenv@^16.0.0: version "16.3.1" @@ -12011,6 +12021,11 @@ dotenv@^16.4.5: resolved "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz" integrity sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg== +dotenv@~10.0.0: + version "10.0.0" + resolved "https://registry.npmjs.org/dotenv/-/dotenv-10.0.0.tgz" + integrity sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q== + dotignore@~0.1.2: version "0.1.2" resolved "https://registry.npmjs.org/dotignore/-/dotignore-0.1.2.tgz" @@ -12080,6 +12095,16 @@ ecc-jsbn@~0.1.1: jsbn "~0.1.0" safer-buffer "^2.1.0" +eciesjs@0.4.14: + version "0.4.14" + resolved "https://registry.yarnpkg.com/eciesjs/-/eciesjs-0.4.14.tgz#a48c527f7754b4390dfd7e863fe0166c1972be94" + integrity sha512-eJAgf9pdv214Hn98FlUzclRMYWF7WfoLlkS9nWMTm1qcCwn6Ad4EGD9lr9HXMBfSrZhYQujRE+p0adPRkctC6A== + dependencies: + "@ecies/ciphers" "^0.2.2" + "@noble/ciphers" "^1.0.0" + "@noble/curves" "^1.6.0" + "@noble/hashes" "^1.5.0" + ee-first@1.1.1: version "1.1.1" resolved "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz" @@ -15156,13 +15181,6 @@ graphql-request@6.1.0, graphql-request@^6.0.0: "@graphql-typed-document-node/core" "^3.2.0" cross-fetch "^3.1.5" -graphql-request@7.1.2: - version "7.1.2" - resolved "https://registry.npmjs.org/graphql-request/-/graphql-request-7.1.2.tgz" - integrity sha512-+XE3iuC55C2di5ZUrB4pjgwe+nIQBuXVIK9J98wrVwojzDW3GMdSBZfxUk8l4j9TieIpjpggclxhNEU9ebGF8w== - dependencies: - "@graphql-typed-document-node/core" "^3.2.0" - graphql-request@^4.3.0: version "4.3.0" resolved "https://registry.npmjs.org/graphql-request/-/graphql-request-4.3.0.tgz" @@ -22622,7 +22640,7 @@ scuid@^1.1.0: resolved "https://registry.npmjs.org/scuid/-/scuid-1.1.0.tgz" integrity sha512-MuCAyrGZcTLfQoH2XoBlQ8C6bzwN88XT/0slOGz0pn8+gIP85BOAfYa44ZXQUTOwRwPU0QvgU+V+OSajl/59Xg== -secp256k1@4.0.4, secp256k1@^4.0.1: +secp256k1@^4.0.1: version "4.0.4" resolved "https://registry.npmjs.org/secp256k1/-/secp256k1-4.0.4.tgz" integrity sha512-6JfvwvjUOn8F/jUoBY2Q1v5WY5XS+rj8qSe0v8Y4ezH4InLgTEeOOPQsRll9OV429Pvo6BCHGavIyJfr3TAhsw== @@ -23650,7 +23668,7 @@ string-length@^4.0.1: char-regex "^1.0.2" strip-ansi "^6.0.0" -"string-width-cjs@npm:string-width@^4.2.0": +"string-width-cjs@npm:string-width@^4.2.0", "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.2, string-width@^4.2.3: version "4.2.3" resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -23668,15 +23686,6 @@ string-width@^1.0.1: is-fullwidth-code-point "^1.0.0" strip-ansi "^3.0.0" -"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.2, string-width@^4.2.3: - version "4.2.3" - resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - string-width@^2.0.0, string-width@^2.1.0, string-width@^2.1.1: version "2.1.1" resolved "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz" @@ -23811,7 +23820,7 @@ stringify-object@^3.3.0: is-obj "^1.0.1" is-regexp "^1.0.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1": +"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -23846,13 +23855,6 @@ strip-ansi@^6.0.0: dependencies: ansi-regex "^5.0.0" -strip-ansi@^6.0.1: - version "6.0.1" - resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - strip-ansi@^7.0.1: version "7.1.0" resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz" @@ -27046,7 +27048,7 @@ workerpool@6.2.1: resolved "https://registry.npmjs.org/workerpool/-/workerpool-6.2.1.tgz" integrity sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw== -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== @@ -27081,15 +27083,6 @@ wrap-ansi@^6.0.1, wrap-ansi@^6.2.0: string-width "^4.1.0" strip-ansi "^6.0.0" -wrap-ansi@^7.0.0: - version "7.0.0" - resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - wrap-ansi@^8.1.0: version "8.1.0" resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz"