Skip to content

Commit c56ca49

Browse files
committed
fixes/changes:
* proper implementation of MerkleHashBuilder, * make the tests match Java and Catapult, * bump aggregate versions and fix tests * exclude transaction padding from embedded transaction hash * bump catbuffer-typescript
1 parent 60a5675 commit c56ca49

9 files changed

+95
-50
lines changed

.gitmodules

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
[submodule "travis"]
2-
path = travis
3-
url = https://github.com/nemgrouplimited/travis-functions.git
1+
[submodule "catbuffer-generators"]
2+
path = catbuffer-generators
3+
url = git@github.com:symbol/sdk-java-catbuffer-generators.git

catbuffer-generators

Submodule catbuffer-generators added at 37b4a74

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@
9797
},
9898
"dependencies": {
9999
"@js-joda/core": "^3.2.0",
100-
"catbuffer-typescript": "^1.0.1",
100+
"catbuffer-typescript": "^1.0.2",
101101
"crypto-js": "^4.0.0",
102102
"futoin-hkdf": "^1.3.2",
103103
"js-sha256": "^0.9.0",

src/core/crypto/MerkleHashBuilder.ts

+4-2
Original file line numberDiff line numberDiff line change
@@ -72,12 +72,14 @@ export class MerkleHashBuilder {
7272
while (numRemainingHashes > 1) {
7373
for (let i = 0; i < numRemainingHashes; i += 2) {
7474
if (i + 1 < numRemainingHashes) {
75-
hashes.splice(i / 2, 0, this.hash([hashes[i], hashes[i + 1]]));
75+
const parentHash = this.hash([hashes[i], hashes[i + 1]]);
76+
hashes[Math.floor(i / 2)] = parentHash;
7677
continue;
7778
}
7879

7980
// if there is an odd number of hashes, duplicate the last one
80-
hashes.splice(i / 2, 0, this.hash([hashes[i], hashes[i]]));
81+
const parentHash = this.hash([hashes[i], hashes[i]]);
82+
hashes[Math.floor(i / 2)] = parentHash;
8183
++numRemainingHashes;
8284
}
8385
numRemainingHashes /= 2;

src/model/transaction/AggregateTransaction.ts

+1-3
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ import {
2020
AmountDto,
2121
CosignatureBuilder,
2222
EmbeddedTransactionBuilder,
23-
GeneratorUtils,
2423
Hash256Dto,
2524
PublicKeyDto,
2625
SignatureDto,
@@ -345,8 +344,7 @@ export class AggregateTransaction extends Transaction {
345344
hasher.reset();
346345

347346
const byte = transaction.toEmbeddedTransaction().serialize();
348-
const padding = new Uint8Array(GeneratorUtils.getPaddingSize(byte.length, 8));
349-
hasher.update(GeneratorUtils.concatTypedArrays(byte, padding));
347+
hasher.update(byte);
350348
hasher.finalize(entityHash);
351349

352350
// update merkle tree (add transaction hash)

src/model/transaction/TransactionVersion.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -65,12 +65,12 @@ export class TransactionVersion {
6565
* Aggregate complete transaction version.
6666
* @type {number}
6767
*/
68-
public static readonly AGGREGATE_COMPLETE = 1;
68+
public static readonly AGGREGATE_COMPLETE = 2;
6969

7070
/**
7171
* Aggregate bonded transaction version
7272
*/
73-
public static readonly AGGREGATE_BONDED = 1;
73+
public static readonly AGGREGATE_BONDED = 2;
7474

7575
/**
7676
* Lock transaction version

test/core/crypto/MerkleHashBuilder.spec.ts

+75-31
Original file line numberDiff line numberDiff line change
@@ -15,52 +15,96 @@
1515
*/
1616

1717
import { expect } from 'chai';
18-
import { MerkleHashBuilder } from '../../../src/core/crypto';
18+
import { Crypto, MerkleHashBuilder } from '../../../src/core/crypto';
1919
import { Convert } from '../../../src/core/format';
2020

21-
describe('MerkleHashBuilder should', () => {
22-
it('fill 0s for empty merkle tree', () => {
23-
// Arrange:
21+
describe('MerkleHashBuilder', () => {
22+
const calculateMerkleHash = (hashes) => {
2423
const builder = new MerkleHashBuilder(32);
24+
hashes.forEach((embeddedHash) => builder.update(embeddedHash));
25+
return builder.getRootHash();
26+
};
27+
28+
const assertMerkleHash = (expectedHashHex, hashesHex) => {
29+
// Act:
30+
const calculatedHash = calculateMerkleHash(hashesHex.map(Convert.hexToUint8));
2531

26-
const rootHash = builder.getRootHash();
32+
// Assert:
33+
expect(expectedHashHex).to.equal(Convert.uint8ToHex(calculatedHash));
34+
};
2735

28-
expect(Convert.uint8ToHex(rootHash)).equal('0000000000000000000000000000000000000000000000000000000000000000');
36+
it('fill 0s for empty merkle tree', () => {
37+
assertMerkleHash('0000000000000000000000000000000000000000000000000000000000000000', []);
2938
});
3039

3140
it('return first hash given single child', () => {
32-
// Arrange:
33-
const builder = new MerkleHashBuilder(32);
34-
35-
builder.update(Convert.hexToUint8('215B158F0BD416B596271BCE527CD9DC8E4A639CC271D896F9156AF6F441EEB9'));
41+
const randomHashHex = Convert.uint8ToHex(Crypto.randomBytes(32));
42+
assertMerkleHash(randomHashHex, [randomHashHex]);
43+
});
3644

37-
const rootHash = builder.getRootHash();
45+
it('can create from balanced tree', () => {
46+
assertMerkleHash('7D853079F5F9EE30BDAE49C4956AF20CDF989647AFE971C069AC263DA1FFDF7E', [
47+
'36C8213162CDBC78767CF43D4E06DDBE0D3367B6CEAEAEB577A50E2052441BC8',
48+
'8A316E48F35CDADD3F827663F7535E840289A16A43E7134B053A86773E474C28',
49+
'6D80E71F00DFB73B358B772AD453AEB652AE347D3E098AE269005A88DA0B84A7',
50+
'2AE2CA59B5BB29721BFB79FE113929B6E52891CAA29CBF562EBEDC46903FF681',
51+
'421D6B68A6DF8BB1D5C9ACF7ED44515E77945D42A491BECE68DA009B551EE6CE',
52+
'7A1711AF5C402CFEFF87F6DA4B9C738100A7AC3EDAD38D698DF36CA3FE883480',
53+
'1E6516B2CC617E919FAE0CF8472BEB2BFF598F19C7A7A7DC260BC6715382822C',
54+
'410330530D04A277A7C96C1E4F34184FDEB0FFDA63563EFD796C404D7A6E5A20',
55+
]);
56+
});
3857

39-
expect(Convert.uint8ToHex(rootHash)).equal('215B158F0BD416B596271BCE527CD9DC8E4A639CC271D896F9156AF6F441EEB9');
58+
it('can build from unbalanced tree', () => {
59+
assertMerkleHash('DEFB4BF7ACF2145500087A02C88F8D1FCF27B8DEF4E0FDABE09413D87A3F0D09', [
60+
'36C8213162CDBC78767CF43D4E06DDBE0D3367B6CEAEAEB577A50E2052441BC8',
61+
'8A316E48F35CDADD3F827663F7535E840289A16A43E7134B053A86773E474C28',
62+
'6D80E71F00DFB73B358B772AD453AEB652AE347D3E098AE269005A88DA0B84A7',
63+
'2AE2CA59B5BB29721BFB79FE113929B6E52891CAA29CBF562EBEDC46903FF681',
64+
'421D6B68A6DF8BB1D5C9ACF7ED44515E77945D42A491BECE68DA009B551EE6CE',
65+
]);
4066
});
4167

42-
it('create correct merkle hash given two children', () => {
68+
it('changing sub hash order changes merkle hash', () => {
4369
// Arrange:
44-
const builder = new MerkleHashBuilder(32);
45-
46-
builder.update(Convert.hexToUint8('215b158f0bd416b596271bce527cd9dc8e4a639cc271d896f9156af6f441eeb9'));
47-
builder.update(Convert.hexToUint8('976c5ce6bf3f797113e5a3a094c7801c885daf783c50563ffd3ca6a5ef580e25'));
48-
49-
const rootHash = builder.getRootHash();
50-
51-
expect(Convert.uint8ToHex(rootHash).toLocaleLowerCase()).equal('1c704e3ac99b124f92d2648649ec72c7a19ea4e2bb24f669b976180a295876fa');
70+
const seed1: Uint8Array[] = [];
71+
for (let i = 0; i < 8; ++i) {
72+
seed1.push(new Uint8Array(Crypto.randomBytes(32)));
73+
}
74+
75+
// - swap 5 and 3
76+
// eslint-disable-next-line prettier/prettier
77+
const seed2 = [
78+
seed1[0], seed1[1], seed1[2], seed1[5],
79+
seed1[4], seed1[3], seed1[6], seed1[7]
80+
];
81+
82+
// Act:
83+
const rootHash1 = calculateMerkleHash(seed1);
84+
const rootHash2 = calculateMerkleHash(seed2);
85+
86+
// Assert:
87+
expect(Convert.uint8ToHex(rootHash1)).not.to.equal(Convert.uint8ToHex(rootHash2));
5288
});
5389

54-
it('create correct merkle hash given three children', () => {
90+
it('changing sub hash changes merkle tree', () => {
5591
// Arrange:
56-
const builder = new MerkleHashBuilder(32);
57-
58-
builder.update(Convert.hexToUint8('215b158f0bd416b596271bce527cd9dc8e4a639cc271d896f9156af6f441eeb9'));
59-
builder.update(Convert.hexToUint8('976c5ce6bf3f797113e5a3a094c7801c885daf783c50563ffd3ca6a5ef580e25'));
60-
builder.update(Convert.hexToUint8('e926cc323886d47234bb0b49219c81e280e8a65748b437c2ae83b09b37a5aaf2'));
61-
62-
const rootHash = builder.getRootHash();
63-
64-
expect(Convert.uint8ToHex(rootHash).toLocaleLowerCase()).equal('5dc17b2409d50bcc7c1faa720d0ec8b79a1705d0c517bcc0bdbd316540974d5e');
92+
const seed1: Uint8Array[] = [];
93+
for (let i = 0; i < 8; ++i) {
94+
seed1.push(new Uint8Array(Crypto.randomBytes(32)));
95+
}
96+
97+
// eslint-disable-next-line prettier/prettier
98+
const seed2 = [
99+
seed1[0], seed1[1], seed1[2], seed1[3],
100+
new Uint8Array(Crypto.randomBytes(32)), seed1[5], seed1[6], seed1[7]
101+
];
102+
103+
// Act:
104+
const rootHash1 = calculateMerkleHash(seed1);
105+
const rootHash2 = calculateMerkleHash(seed2);
106+
107+
// Assert:
108+
expect(Convert.uint8ToHex(rootHash1)).not.to.equal(Convert.uint8ToHex(rootHash2));
65109
});
66110
});

0 commit comments

Comments
 (0)