Skip to content
Permalink
Browse files
feat: remove hash in encrypted transaction (#232)
  • Loading branch information
vrolland committed Jun 22, 2020
1 parent 969bebe commit d58f101f9f76e408671dd1edb0d67863d1c8abd5
Showing 11 changed files with 39 additions and 98 deletions.
@@ -59,7 +59,7 @@ function parseBlock(data: string): DataAccessTypes.IBlock {
}

// check the transactions format
if (!maybeBlock.transactions.every((tx: any) => tx.data || (tx.encryptedData && tx.hash))) {
if (!maybeBlock.transactions.every((tx: any) => tx.data || tx.encryptedData)) {
throw new Error(`Transactions do not follow the block standard`);
}
// check if channelIds are well formatted
@@ -121,10 +121,8 @@ function pushTransaction(
channelId: string,
topics: string[] = [],
): DataAccessTypes.IBlock {
if (transaction.data === undefined && !(transaction.encryptedData && transaction.hash)) {
throw new Error(
'The transaction is missing the data property or encryptedData and hash property',
);
if (transaction.data === undefined && !transaction.encryptedData) {
throw new Error('The transaction is missing the data property or encryptedData property');
}
// we don't want to modify the original block state
const copiedBlock: DataAccessTypes.IBlock = Utils.deepCopy(block);
@@ -568,7 +568,6 @@ describe('Request system', () => {

assert.exists(dataAccessData.result.transactions[0].transaction.encryptedData);
assert.exists(dataAccessData.result.transactions[0].transaction.encryptionMethod);
assert.exists(dataAccessData.result.transactions[0].transaction.hash);
assert.exists(dataAccessData.result.transactions[0].transaction.keys);
assert.exists(
dataAccessData.result.transactions[0].transaction.keys![
@@ -583,19 +582,15 @@ describe('Request system', () => {
assert.isUndefined(dataAccessData.result.transactions[0].transaction.data);

assert.exists(dataAccessData.result.transactions[1].transaction.encryptedData);
assert.exists(dataAccessData.result.transactions[1].transaction.hash);
assert.isUndefined(dataAccessData.result.transactions[1].transaction.data);

assert.exists(dataAccessData.result.transactions[2].transaction.encryptedData);
assert.exists(dataAccessData.result.transactions[2].transaction.hash);
assert.isUndefined(dataAccessData.result.transactions[2].transaction.data);

assert.exists(dataAccessData.result.transactions[3].transaction.encryptedData);
assert.exists(dataAccessData.result.transactions[3].transaction.hash);
assert.isUndefined(dataAccessData.result.transactions[3].transaction.data);

assert.exists(dataAccessData.result.transactions[4].transaction.encryptedData);
assert.exists(dataAccessData.result.transactions[4].transaction.hash);
assert.isUndefined(dataAccessData.result.transactions[4].transaction.data);
});
});
@@ -42,7 +42,7 @@ The encryption uses:
The data are first encrypted by AES-256 with a generated key.
This key is then encrypted for every other party with ECIES from their public key.
To not expose the public keys, the encrypted keys are indexed by the addresses.
The encrypted data, the encrypted keys and a hash of the data are pushed on chain.
The encrypted data, the encrypted keys and the encrypted method of the data are pushed on chain.

## Functions

@@ -51,7 +51,10 @@ The encrypted data, the encrypted keys and a hash of the data are pushed on chai
| Property | Type | Description |
| -------------------- | ------ | -------------------------------------------------------------------------------------------------------------------------------- |
| **encryptedData** | String | First encrypted data of the channel in base64 |
<<<<<<< HEAD
=======
| **hash** | String | Normalized Keccak256 hash of the message before encryption |
>>>>>>> master
| **keys** | Object | AES-256 key encrypted with ECIES from the parties public keys, encoded in base64 and indexed by their multi-formatted identities |
| **encryptionMethod** | String | Encryption method use for the channel _('ECIES-AES256-GCM' here)_ |
@@ -64,7 +67,6 @@ Example:
```JSON
{
"encryptedData": "JOz9aOV1aYatMSAx+3CD9EyjNI/FwLp6DeA+AYk5ERnTDwwaETY7zz2NemubnGW7GGDATjSVsnCVWXuM58cihq1Bhkon2aiPHhQdpteEugkrM2Zx/kWrVlvRY8kyseB30hU7NhyiDUSLGOJ/Pmq3PjANbBi2svgADLFZ6SdYgwFkjxaO1XkvW/qvjuraFqW55/4wCd53yjWcjMcLzMgLYcTLmSns642xAjx0hAvwVPQmTVI5xOFf6PjbEN9qfRPfdQkaOuuGG2AYsVhOkSK73BULdIvx6PArfqICCtL23xmt4kCeFgd6HYQvSzSFqszqAWT1kJdiRj3sZXRtf6xcpeXDelBacHN+xD2mzdZlroVhlsjZi5s0mhemBd+C",
"hash": "01865ea95812388a93162b560e01c5680f12966492dfbad8a9a104e1e79f6665fc",
"keys":
{
"20af083f77f1ffd54218d91491afd06c9296eac3ce": "aYOGYgtlt0JkBoKjxkMpoQJbE7GXtTT6JrjA+NF0Bd6BxDLyn5+hFIDvHltMkGS7rpzR3RyEnDl+SncDJ+cCxLo9Od7ntqGNVdin6n7EJqilmY0AmxJpAIAOnCwK5C46zH4RE0g7vBv/+3Gx2uFKw2Dfhpy7olQ5NL6Krsb2qEnmW32R3wmv85uCE88uxmcDlo/OrS36X+jzOye+/ZR+kOE=",
@@ -76,17 +78,22 @@ Example:

### Add encrypted data

<<<<<<< HEAD
| Property | Type | Description |
| ----------------- | ------ | ------------------------ |
| **encryptedData** | String | Encrypted data in base64 |
=======
| Property | Type | Description |
| ----------------- | ------ | ---------------------------------------------------------- |
| **encryptedData** | String | Encrypted data in base64 |
| **hash** | String | Normalized Keccak256 hash of the message before encryption |
>>>>>>> master
Example:

```JSON
{
"encryptedData": "mBVy2ENb0Edkego5c9QXcFxszKxe7iQVE22wUPHMbrC7bBm99S238BAyACa1TBDlI4SajbrWM+/MG8CkBoph4FLTvh4PsUjhnfazFI9BnMtIMhdqDAoxXUSHsvnwbEFhllqwhFCWn6pslLNu7X7UJSDgj7nQ0t1IHegBSV7ZRqdOYw3UoxAEAyVOoUwMhr/sitF2AlgMSvKas5YCD47YIm6rDNmzyBn9Ed/fAxNojMXcg386khrPs37P6Q==",
"hash": "018f94ee7e96fa65a761e8df9792af3f72fcf936f186fbb86881630f7d5333c8bb",
}
```

@@ -15,26 +15,20 @@ export default class EncryptedTransaction implements TransactionTypes.ITransacti
/** Persisted data */
private persistedData: TransactionTypes.ITransactionData;

/** hash given by the persisted transaction */
private hashFromPersistedTransaction: string;

/** channel key to decrypt the encrypted data */
private channelKey: EncryptionTypes.IDecryptionParameters;

/**
* Creates an instance of EncryptedTransaction.
* @param persistedData the encrypted data of the transaction
* @param hashFromPersistedTransaction the hash of the decrypted data (not checked)
* @param channelKey decryption parameters to decrypted the encrypted data
*/
constructor(
persistedData: TransactionTypes.ITransactionData,
hashFromPersistedTransaction: string,
channelKey: EncryptionTypes.IDecryptionParameters,
) {
this.persistedData = persistedData;
this.channelKey = channelKey;
this.hashFromPersistedTransaction = hashFromPersistedTransaction;
}

/**
@@ -78,13 +72,17 @@ export default class EncryptedTransaction implements TransactionTypes.ITransacti
* @returns a promise resolving a string of the error if any, otherwise an empty string
*/
public async getError(): Promise<string> {
let data = '';
try {
if ((await this.getHash()) !== this.hashFromPersistedTransaction) {
throw Error('The given hash does not match the hash of the decrypted data');
}
return '';
} catch (error) {
return error.message;
data = await this.getData();
} catch (e) {
return e.message;
}
try {
JSON.parse(data);
} catch (e) {
return 'Impossible to JSON parse the decrypted transaction data';
}
return '';
}
}
@@ -1,5 +1,5 @@
import MultiFormat from '@requestnetwork/multi-format';
import { EncryptionTypes, MultiFormatTypes, TransactionTypes } from '@requestnetwork/types';
import { EncryptionTypes, TransactionTypes } from '@requestnetwork/types';
import Utils from '@requestnetwork/utils';

/**
@@ -47,10 +47,8 @@ export default class TransactionsFactory {
method: EncryptionTypes.METHOD.AES256_GCM,
});

// Compute the hash of the data
let hash: MultiFormatTypes.HashTypes.IHash;
try {
hash = Utils.crypto.normalizeKeccak256Hash(JSON.parse(data));
JSON.parse(data);
} catch (error) {
throw new Error('Data not parsable');
}
@@ -105,9 +103,8 @@ export default class TransactionsFactory {
);

const encryptedDataSerialized: string = MultiFormat.serialize(encryptedData);
const hashSerialized: string = MultiFormat.serialize(hash);

return { encryptedData: encryptedDataSerialized, keys, hash: hashSerialized, encryptionMethod };
return { encryptedData: encryptedDataSerialized, keys, encryptionMethod };
}

/**
@@ -132,17 +129,14 @@ export default class TransactionsFactory {
channelKey,
);

// Compute the hash of the data
let hash: MultiFormatTypes.HashTypes.IHash;
try {
hash = Utils.crypto.normalizeKeccak256Hash(JSON.parse(data));
JSON.parse(data);
} catch (error) {
throw new Error('Data not parsable');
}

const encryptedDataSerialized: string = MultiFormat.serialize(encryptedData);
const hashSerialized: string = MultiFormat.serialize(hash);

return { encryptedData: encryptedDataSerialized, hash: hashSerialized };
return { encryptedData: encryptedDataSerialized };
}
}
@@ -44,7 +44,6 @@ export default class TransactionsParser {
if (
persistedTransaction.encryptedData ||
persistedTransaction.encryptionMethod ||
persistedTransaction.hash ||
persistedTransaction.keys
) {
throw new Error('only the property "data" is allowed for clear transaction');
@@ -57,9 +56,6 @@ export default class TransactionsParser {
if (channelType === TransactionTypes.ChannelType.CLEAR) {
throw new Error('Encrypted transactions are not allowed in clear channel');
}
if (!persistedTransaction.hash) {
throw new Error('the property "hash" is missing for the encrypted transaction');
}

// if we don't have the channel key we need to decrypt it
if (!channelKey) {
@@ -83,11 +79,7 @@ export default class TransactionsParser {
return {
channelKey,
encryptionMethod: persistedTransaction.encryptionMethod,
transaction: new EncryptedTransaction(
persistedTransaction.encryptedData,
persistedTransaction.hash,
channelKey,
),
transaction: new EncryptedTransaction(persistedTransaction.encryptedData, channelKey),
};
}

@@ -601,7 +601,6 @@ describe('index', () => {
data2,
[TestData.idRaw3.encryptionParams],
);
encryptedTxFakeHash.hash = channelId;

const encryptedTx = await TransactionsFactory.createEncryptedTransactionInNewChannel(data, [
TestData.idRaw1.encryptionParams,
@@ -652,7 +651,8 @@ describe('index', () => {
encryptionMethod: 'ecies-aes256-gcm',
ignoredTransactions: [
{
reason: 'The given hash does not match the hash of the decrypted data',
reason:
'as first transaction, the hash of the transaction do not match the channelId',
transaction: {
state: TransactionTypes.TransactionState.PENDING,
timestamp: 1,
@@ -24,14 +24,14 @@ const encryptedData =
describe('encryption-transaction', () => {
describe('getData', () => {
it('can getData()', async () => {
const tx = new EncryptedTransaction(encryptedData, hash, channelKey);
const tx = new EncryptedTransaction(encryptedData, channelKey);
expect(await tx.getData(), 'transaction not right').to.deep.equal(data);
});
});

describe('getHash', () => {
it('can get hash of the data', async () => {
const tx = new EncryptedTransaction(encryptedData, hash, channelKey);
const tx = new EncryptedTransaction(encryptedData, channelKey);

expect(await tx.getHash(), 'hash not right').to.deep.equal(hash);
});
@@ -43,14 +43,14 @@ describe('encryption-transaction', () => {
await Utils.encryption.encrypt('Not parsable', channelKey),
);

const tx = new EncryptedTransaction(encryptedDataNotParsable, hash, channelKey);
const tx = new EncryptedTransaction(encryptedDataNotParsable, channelKey);

expect(await tx.getError(), 'error not right').to.deep.equal(
'Impossible to JSON parse the decrypted transaction data',
);
});
it('can get error of a transaction impossible to decrypt', async () => {
const tx = new EncryptedTransaction(encryptedData, hash, {
const tx = new EncryptedTransaction(encryptedData, {
key: 'Corrupted',
method: EncryptionTypes.METHOD.AES256_CBC,
});
@@ -59,16 +59,8 @@ describe('encryption-transaction', () => {
'Impossible to decrypt the transaction',
);
});
it('can get error of a transaction with hash given not matching real hash', async () => {
const tx = new EncryptedTransaction(encryptedData, 'wrong hash', channelKey);

expect(await tx.getError(), 'error not right').to.deep.equal(
'The given hash does not match the hash of the decrypted data',
);
});
it('can get error of a transaction if no error', async () => {
const tx = new EncryptedTransaction(encryptedData, hash, channelKey);

const tx = new EncryptedTransaction(encryptedData, channelKey);
expect(await tx.getError(), 'error not right').to.deep.equal('');
});
});
@@ -7,7 +7,6 @@ const expect = chai.expect;

import MultiFormat from '@requestnetwork/multi-format';
import { EncryptionTypes, MultiFormatTypes } from '@requestnetwork/types';
import Utils from '@requestnetwork/utils';
import TransactionsFactory from '../../src/transactions-factory';
import * as TestData from './utils/test-data';

@@ -52,10 +51,6 @@ describe('transaction-factory', () => {
`${EncryptionTypes.METHOD.ECIES}-${EncryptionTypes.METHOD.AES256_GCM}`,
);

expect(encryptedTx.hash, 'hash not right').to.deep.equal(
MultiFormat.serialize(Utils.crypto.normalizeKeccak256Hash(JSON.parse(data))),
);

expect(Object.keys(encryptedTx.keys || {}).length, 'keys not right').to.deep.equal(3);
expect(Object.keys(encryptedTx.keys || {}), 'keys not right').to.deep.equal([
MultiFormat.serialize(TestData.idRaw1.identity),
@@ -120,10 +115,6 @@ describe('transaction-factory', () => {

expect(encryptedTx.encryptionMethod, 'encryptionMethod not right').to.be.undefined;

expect(encryptedTx.hash, 'hash not right').to.equal(
MultiFormat.serialize(Utils.crypto.normalizeKeccak256Hash(JSON.parse(data))),
);

expect(encryptedTx.keys, 'keys not right').to.be.undefined;
});

0 comments on commit d58f101

Please sign in to comment.