Skip to content

Commit

Permalink
Second part for #137 (#147)
Browse files Browse the repository at this point in the history
The `FragmentTree` does not follow orignal code hashing.
A fragment leaf consists of `value`, `slot` and `byteLength`.
`byteLength` is the orignal length of any given bytes.
  • Loading branch information
pinkiebell authored Jul 11, 2019
1 parent 052dada commit f371581
Show file tree
Hide file tree
Showing 4 changed files with 107 additions and 3 deletions.
34 changes: 34 additions & 0 deletions test/utils/FragmentTree.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
const assert = require('assert');
const { randomFillSync } = require('crypto');

const { FragmentTree } = require('./../../utils');

function randomString (size) {
return `0x${randomFillSync(Buffer.alloc(size)).toString('hex')}`;
}

describe('FragmentTree', function () {
for (let i = 1; i < 1024; i++) {
it(`Loop #${i}`, () => {
const bytecode = randomString(i);
const byteLength = (bytecode.length - 2) / 2;
const tree = new FragmentTree().run(bytecode);
const slots = ~~((i + 31) / 32);

for (let x = 0; x < slots; x++) {
const leaf = tree.leaves[x];
const startOffset = (x * 64) + 2;
const value = bytecode.substring(startOffset, startOffset + 64).padEnd(64, '0');

assert.equal(leaf.slot, x, 'slot should match');
assert.equal(leaf.byteLength, byteLength, 'byteLength should match');
assert.equal(leaf.value, `0x${value}`, 'value should match');
}

assert.equal(tree.leaves.length, slots + slots % 2, 'number of leaves should match');

const proof = tree.calculateProof(slots - 1);
assert.ok(tree.verifyProof(tree.leaves[slots - 1], proof), 'verifyProof');
});
}
});
6 changes: 3 additions & 3 deletions utils/AbstractMerkleTree.js
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ module.exports = class AbstractMerkleTree {
let last = this.tree[level - 1];
let cur = [];

if (last.length === 1) {
if (last.length <= 1 && level > 1) {
// done
break;
}
Expand Down Expand Up @@ -125,8 +125,8 @@ module.exports = class AbstractMerkleTree {
for (let y = 0; y < row.length; y++) {
const e = row[y];
const h = e.hash.substring(2, 6);
const hl = e.left.hash.substring(2, 6);
const hr = e.right.hash.substring(2, 6);
const hl = e.left ? e.left.hash.substring(2, 6) : '?';
const hr = e.right ? e.right.hash.substring(2, 6) : '?';

res += ` [ ${h} (l:${hl} r:${hr}) ] `;
}
Expand Down
68 changes: 68 additions & 0 deletions utils/FragmentTree.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
const ethers = require('ethers');

const AbstractMerkleTree = require('./AbstractMerkleTree');

// We should support compressed proofs in the future,
// means re-using hashes if we construct a proof for more than 1 slot.
module.exports = class FragmentTree extends AbstractMerkleTree {
/// @dev return hash proof for `slot`,
/// `slot` is position in `this.leaves`.
/// @return proof - array of 32 bytes hex-string (hashes)
calculateProof (slot) {
const proof = [];
const len = this.depth - 1;

for (let i = 0; i < len; i++) {
proof.push(this.tree[i][slot ^ 1].hash);
slot >>= 1;
}
return proof;
}

/// @dev verify if given `proofs` and `leaf` match `this.root.hash`
verifyProof (leaf, proofs) {
const len = proofs.length;
let hash = leaf.hash;
let slot = leaf.slot;

for (let i = 0; i < len; i++) {
const proof = proofs[i];

if (slot % 2 === 0) {
hash = this.constructor.hash(hash, proof);
} else {
hash = this.constructor.hash(proof, hash);
}
slot >>= 1;
}

return hash === this.root.hash;
}

/// @notice build the tree for the given hex-string `data`.
/// @dev `data` will be splited into fragments and padded to one word (32 bytes).
run (data) {
data = data.replace('0x', '');

this.tree = [[]];

const leaves = this.tree[0];
const byteLength = data.length / 2;
const len = data.length;
let slot = 0;

for (let i = 0; i < len;) {
const value = '0x' + data.substring(i, i += 64).padEnd(64, '0');
const hash = ethers.utils.solidityKeccak256(
['bytes32', 'uint256', 'uint256'],
[value, slot, byteLength]
);

slot = leaves.push({ hash, value, slot, byteLength });
}

this.recal(0);

return this;
}
};
2 changes: 2 additions & 0 deletions utils/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ const OffchainStepper = require('./OffchainStepper.js');
const Merkelizer = require('./Merkelizer.js');
const ProofHelper = require('./ProofHelper.js');
const ExecutionPoker = require('./ExecutionPoker.js');
const FragmentTree = require('./FragmentTree.js');

module.exports = {
Constants,
Expand All @@ -12,4 +13,5 @@ module.exports = {
Merkelizer,
ProofHelper,
ExecutionPoker,
FragmentTree,
};

0 comments on commit f371581

Please sign in to comment.