Skip to content

Commit 0760b43

Browse files
authored
Merge pull request #5 from commitground/1.1.0
1.1.0
2 parents 117dd2c + 98bd130 commit 0760b43

File tree

5 files changed

+257
-8
lines changed

5 files changed

+257
-8
lines changed

Diff for: contracts/implementation.sol

+27-1
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,10 @@ contract PartialMerkleTreeImplementation {
99
constructor () public {
1010
}
1111

12-
function initialize (bytes32 initialRoot) public {
12+
function initialize(bytes32 initialRoot) public {
1313
tree.initialize(initialRoot);
1414
}
15+
1516
function insert(bytes key, bytes value) public {
1617
tree.insert(key, value);
1718
}
@@ -20,10 +21,22 @@ contract PartialMerkleTreeImplementation {
2021
return tree.commitBranch(key, value, branchMask, siblings);
2122
}
2223

24+
function commitBranchOfNonInclusion(bytes key, bytes32 potentialSiblingLabel, bytes32 potentialSiblingValue, uint branchMask, bytes32[] siblings) public {
25+
return tree.commitBranchOfNonInclusion(key, potentialSiblingLabel, potentialSiblingValue, branchMask, siblings);
26+
}
27+
2328
function get(bytes key) public view returns (bytes) {
2429
return tree.get(key);
2530
}
2631

32+
function safeGet(bytes key) public view returns (bytes) {
33+
return tree.safeGet(key);
34+
}
35+
36+
function doesInclude(bytes key) public view returns (bool) {
37+
return tree.doesInclude(key);
38+
}
39+
2740
function getValue(bytes32 hash) public view returns (bytes) {
2841
return tree.values[hash];
2942
}
@@ -36,7 +49,20 @@ contract PartialMerkleTreeImplementation {
3649
return tree.getProof(key);
3750
}
3851

52+
function getNonInclusionProof(bytes key) public view returns (
53+
bytes32 leafLabel,
54+
bytes32 leafNode,
55+
uint branchMask,
56+
bytes32[] _siblings
57+
) {
58+
return tree.getNonInclusionProof(key);
59+
}
60+
3961
function verifyProof(bytes32 rootHash, bytes key, bytes value, uint branchMask, bytes32[] siblings) public pure {
4062
PartialMerkleTree.verifyProof(rootHash, key, value, branchMask, siblings);
4163
}
64+
65+
function verifyNonInclusionProof(bytes32 rootHash, bytes key, bytes32 leafLabel, bytes32 leafNode, uint branchMask, bytes32[] siblings) public pure {
66+
PartialMerkleTree.verifyNonInclusionProof(rootHash, key, leafLabel, leafNode, branchMask, siblings);
67+
}
4268
}

Diff for: contracts/tree.sol

+133
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,56 @@ library PartialMerkleTree {
6464
tree.rootEdge = e;
6565
}
6666

67+
function commitBranchOfNonInclusion(
68+
Tree storage tree,
69+
bytes key,
70+
bytes32 potentialSiblingLabel,
71+
bytes32 potentialSiblingValue,
72+
uint branchMask,
73+
bytes32[] siblings
74+
) internal {
75+
D.Label memory k = D.Label(keccak256(key), 256);
76+
D.Edge memory e;
77+
// e.node(0x083d)
78+
for (uint i = 0; branchMask != 0; i++) {
79+
// retrieve edge data with branch mask
80+
uint bitSet = Utils.lowestBitSet(branchMask);
81+
branchMask &= ~(uint(1) << bitSet);
82+
(k, e.label) = Utils.splitAt(k, 255 - bitSet);
83+
uint bit;
84+
(bit, e.label) = Utils.chopFirstBit(e.label);
85+
86+
if (i == 0) {
87+
e.label.length = bitSet;
88+
e.label.data = potentialSiblingLabel;
89+
e.node = potentialSiblingValue;
90+
}
91+
92+
// find upper node with retrieved edge & sibling
93+
bytes32[2] memory edgeHashes;
94+
edgeHashes[bit] = edgeHash(e);
95+
edgeHashes[1 - bit] = siblings[siblings.length - i - 1];
96+
bytes32 upperNode = keccak256(abi.encode(edgeHashes[0], edgeHashes[1]));
97+
98+
// Update sibling information
99+
D.Node storage parentNode = tree.nodes[upperNode];
100+
101+
102+
// Put edge
103+
parentNode.children[bit] = e;
104+
// Put sibling edge if needed
105+
if (parentNode.children[1 - bit].isEmpty()) {
106+
parentNode.children[1 - bit].header = siblings[siblings.length - i - 1];
107+
}
108+
// go to upper edge
109+
e.node = keccak256(abi.encode(edgeHashes[0], edgeHashes[1]));
110+
}
111+
e.label = k;
112+
require(tree.root == edgeHash(e));
113+
tree.root = edgeHash(e);
114+
tree.rootEdge = e;
115+
}
116+
67117
function insert(Tree storage tree, bytes key, bytes value) internal {
68118
D.Label memory k = D.Label(keccak256(key), 256);
69119
bytes32 valueHash = keccak256(value);
@@ -88,6 +138,18 @@ library PartialMerkleTree {
88138
return getValue(tree, _findNode(tree, key));
89139
}
90140

141+
function safeGet(Tree storage tree, bytes key) internal view returns (bytes value) {
142+
bytes32 valueHash = _findNode(tree, key);
143+
require(valueHash != bytes32(0));
144+
value = getValue(tree, valueHash);
145+
require(valueHash == keccak256(value));
146+
}
147+
148+
function doesInclude(Tree storage tree, bytes key) internal view returns (bool) {
149+
bytes32 valueHash = _findNode(tree, key);
150+
return (valueHash != bytes32(0));
151+
}
152+
91153
function getValue(Tree storage tree, bytes32 valueHash) internal view returns (bytes) {
92154
return tree.values[valueHash];
93155
}
@@ -152,6 +214,54 @@ library PartialMerkleTree {
152214
}
153215
}
154216

217+
function getNonInclusionProof(Tree storage tree, bytes key) internal view returns (
218+
bytes32 potentialSiblingLabel,
219+
bytes32 potentialSiblingValue,
220+
uint branchMask,
221+
bytes32[] _siblings
222+
){
223+
uint length;
224+
uint numSiblings;
225+
226+
// Start from root edge
227+
D.Label memory label = D.Label(keccak256(key), 256);
228+
D.Edge memory e = tree.rootEdge;
229+
bytes32[256] memory siblings;
230+
231+
while (true) {
232+
// Find at edge
233+
require(label.length >= e.label.length);
234+
D.Label memory prefix;
235+
D.Label memory suffix;
236+
(prefix, suffix) = Utils.splitCommonPrefix(label, e.label);
237+
238+
// suffix.length == 0 means that the key exists. Thus the length of the suffix should be not zero
239+
require(suffix.length != 0);
240+
241+
if (prefix.length >= e.label.length) {
242+
// Partial matched, keep finding
243+
length += prefix.length;
244+
branchMask |= uint(1) << (255 - length);
245+
length += 1;
246+
uint head;
247+
(head, label) = Utils.chopFirstBit(suffix);
248+
siblings[numSiblings++] = edgeHash(tree.nodes[e.node].children[1 - head]);
249+
e = tree.nodes[e.node].children[head];
250+
} else {
251+
// Found the potential sibling. Set data to return
252+
potentialSiblingLabel = e.label.data;
253+
potentialSiblingValue = e.node;
254+
break;
255+
}
256+
}
257+
if (numSiblings > 0)
258+
{
259+
_siblings = new bytes32[](numSiblings);
260+
for (uint i = 0; i < numSiblings; i++)
261+
_siblings[i] = siblings[i];
262+
}
263+
}
264+
155265
function verifyProof(bytes32 rootHash, bytes key, bytes value, uint branchMask, bytes32[] siblings) public pure {
156266
D.Label memory k = D.Label(keccak256(key), 256);
157267
D.Edge memory e;
@@ -171,6 +281,29 @@ library PartialMerkleTree {
171281
require(rootHash == edgeHash(e));
172282
}
173283

284+
function verifyNonInclusionProof(bytes32 rootHash, bytes key, bytes32 potentialSiblingLabel, bytes32 potentialSiblingValue, uint branchMask, bytes32[] siblings) public pure {
285+
D.Label memory k = D.Label(keccak256(key), 256);
286+
D.Edge memory e;
287+
for (uint i = 0; branchMask != 0; i++) {
288+
uint bitSet = Utils.lowestBitSet(branchMask);
289+
branchMask &= ~(uint(1) << bitSet);
290+
(k, e.label) = Utils.splitAt(k, 255 - bitSet);
291+
uint bit;
292+
(bit, e.label) = Utils.chopFirstBit(e.label);
293+
bytes32[2] memory edgeHashes;
294+
if (i == 0) {
295+
e.label.length = bitSet;
296+
e.label.data = potentialSiblingLabel;
297+
e.node = potentialSiblingValue;
298+
}
299+
edgeHashes[bit] = edgeHash(e);
300+
edgeHashes[1 - bit] = siblings[siblings.length - i - 1];
301+
e.node = keccak256(abi.encode(edgeHashes[0], edgeHashes[1]));
302+
}
303+
e.label = k;
304+
require(rootHash == edgeHash(e));
305+
}
306+
174307
function newEdge(bytes32 node, D.Label label) internal pure returns (D.Edge memory e){
175308
e.node = node;
176309
e.label = label;

Diff for: package-lock.json

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Diff for: package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "solidity-partial-tree",
3-
"version": "1.0.0",
3+
"version": "1.1.0",
44
"description": "Solidity implementation of partial merkle tree",
55
"directories": {
66
"test": "test"

Diff for: test/PartialMerkleTree.Test.js

+95-5
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,71 @@ contract('PartialMerkleTree', async ([_, primary, nonPrimary]) => {
142142
assert.equal(web3.toUtf8(await tree.get('foo')), 'bar')
143143
})
144144
})
145+
146+
describe('safeGet()', async () => {
147+
it('should return stored value for the given key', async () => {
148+
await tree.insert('foo', 'bar', { from: primary })
149+
assert.equal(web3.toUtf8(await tree.get('foo')), 'bar')
150+
})
151+
it('should throw if the given key is not included', async () => {
152+
await tree.insert('foo', 'bar', { from: primary })
153+
try {
154+
await tree.get('fuz')
155+
assert.fail('Did not reverted')
156+
} catch (e) {
157+
assert.ok('Reverted successfully')
158+
}
159+
})
160+
})
161+
162+
describe('doesInclude()', async () => {
163+
it('should return boolean whether the tree includes the given key or not', async () => {
164+
await tree.insert('foo', 'bar', { from: primary })
165+
assert.equal(await tree.doesInclude('foo'), true)
166+
assert.equal(await tree.doesInclude('fuz'), false)
167+
})
168+
})
169+
170+
describe('getNonInclusionProof()', async () => {
171+
let items = { key1: 'value1', key2: 'value2', key3: 'value3' }
172+
it('should return proof data when the key does not exist', async () => {
173+
for (const key of Object.keys(items)) {
174+
await tree.insert(key, items[key], { from: primary })
175+
}
176+
await tree.getNonInclusionProof('key4')
177+
})
178+
it('should not return data when the key does exist', async () => {
179+
for (const key of Object.keys(items)) {
180+
await tree.insert(key, items[key], { from: primary })
181+
}
182+
try {
183+
await tree.getNonInclusionProof('key1')
184+
assert.fail('Did not reverted')
185+
} catch (e) {
186+
assert.ok('Reverted successfully')
187+
}
188+
})
189+
})
190+
191+
describe('verifyNonInclusionProof()', async () => {
192+
it('should be passed when we use correct proof data', async () => {
193+
let items = { key1: 'value1', key2: 'value2', key3: 'value3' }
194+
for (const key of Object.keys(items)) {
195+
await tree.insert(key, items[key], { from: primary })
196+
}
197+
let rootHash = await tree.getRootHash()
198+
let [potentialSiblingLabel, potentialSiblingValue, branchMask, siblings] = await tree.getNonInclusionProof('key4')
199+
await tree.verifyNonInclusionProof(rootHash, 'key4', potentialSiblingLabel, potentialSiblingValue, branchMask, siblings)
200+
for (const key of Object.keys(items)) {
201+
try {
202+
await tree.verifyNonInclusionProof(rootHash, key, potentialSiblingLabel, potentialSiblingValue, branchMask, siblings)
203+
assert.fail('Did not reverted')
204+
} catch (e) {
205+
assert.ok('Reverted successfully')
206+
}
207+
}
208+
})
209+
})
145210
})
146211

147212
context('We can reenact merkle tree transformation by submitting only referred siblings instead of submitting all nodes', async () => {
@@ -166,29 +231,54 @@ contract('PartialMerkleTree', async ([_, primary, nonPrimary]) => {
166231
siblingsForKey1 = proof[1]
167232
})
168233

169-
it('should start with same root hash by initialization', async()=> {
234+
it('should start with same root hash by initialization', async () => {
170235
//initilaze with the first root hash
171236
await treeB.initialize(firstPhaseOfTreeA)
172237
assert.equal(await treeB.getRootHash(), firstPhaseOfTreeA)
173238
})
174239

175-
it('should not change root after committing branch data', async ()=> {
240+
it('should not change root after committing branch data', async () => {
176241
// commit branch data
177242
await treeB.commitBranch('key1', referredValueForKey1, branchMaskForKey1, siblingsForKey1)
178243
assert.equal(await treeB.getRootHash(), firstPhaseOfTreeA)
179244
})
180245

181-
it('should be able to return proof data', async ()=> {
246+
it('should be able to return proof data', async () => {
182247
// commit branch data
183248
await treeB.getProof('key1')
184249
})
185250

251+
let secondPhaseOfTreeA
252+
let secondPhaseOfTreeB
186253
it('should have same root hash when we update key1', async () => {
187254
await treeA.insert('key1', 'val4')
188255
await treeB.insert('key1', 'val4')
189-
let secondPhaseOfTreeA = await treeA.getRootHash()
190-
let secondPhaseOfTreeB = await treeB.getRootHash()
256+
secondPhaseOfTreeA = await treeA.getRootHash()
257+
secondPhaseOfTreeB = await treeB.getRootHash()
191258
assert.equal(secondPhaseOfTreeA, secondPhaseOfTreeB)
192259
})
260+
261+
it('should revert before the branch data of non inclusion is committed', async () => {
262+
try {
263+
await treeB.insert('key4', 'val4')
264+
assert.fail('Did not reverted')
265+
} catch (e) {
266+
assert.ok('Reverted successfully')
267+
}
268+
})
269+
270+
let thirdPhaseOfTreeA
271+
let thirdPhaseOfTreeB
272+
it('should be able to insert a non inclusion key-value pair after committting related branch data', async () => {
273+
let [potentialSiblingLabel, potentialSiblingValue, branchMask, siblings] = await treeA.getNonInclusionProof('key4')
274+
await treeB.commitBranchOfNonInclusion('key4', potentialSiblingLabel, potentialSiblingValue, branchMask, siblings)
275+
assert.equal(await treeB.getRootHash(), secondPhaseOfTreeB)
276+
277+
await treeA.insert('key4', 'val4')
278+
await treeB.insert('key4', 'val4')
279+
thirdPhaseOfTreeA = await treeA.getRootHash()
280+
thirdPhaseOfTreeB = await treeB.getRootHash()
281+
assert.equal(thirdPhaseOfTreeA, thirdPhaseOfTreeB)
282+
})
193283
})
194284
})

0 commit comments

Comments
 (0)