Skip to content

Feature for non-included keys #4

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Nov 24, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 19 additions & 1 deletion contracts/implementation.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,10 @@ contract PartialMerkleTreeImplementation {
constructor () public {
}

function initialize (bytes32 initialRoot) public {
function initialize(bytes32 initialRoot) public {
tree.initialize(initialRoot);
}

function insert(bytes key, bytes value) public {
tree.insert(key, value);
}
Expand All @@ -20,6 +21,10 @@ contract PartialMerkleTreeImplementation {
return tree.commitBranch(key, value, branchMask, siblings);
}

function commitBranchOfNonInclusion(bytes key, bytes32 potentialSiblingLabel, bytes32 potentialSiblingValue, uint branchMask, bytes32[] siblings) public {
return tree.commitBranchOfNonInclusion(key, potentialSiblingLabel, potentialSiblingValue, branchMask, siblings);
}

function get(bytes key) public view returns (bytes) {
return tree.get(key);
}
Expand All @@ -44,7 +49,20 @@ contract PartialMerkleTreeImplementation {
return tree.getProof(key);
}

function getNonInclusionProof(bytes key) public view returns (
bytes32 leafLabel,
bytes32 leafNode,
uint branchMask,
bytes32[] _siblings
) {
return tree.getNonInclusionProof(key);
}

function verifyProof(bytes32 rootHash, bytes key, bytes value, uint branchMask, bytes32[] siblings) public pure {
PartialMerkleTree.verifyProof(rootHash, key, value, branchMask, siblings);
}

function verifyNonInclusionProof(bytes32 rootHash, bytes key, bytes32 leafLabel, bytes32 leafNode, uint branchMask, bytes32[] siblings) public pure {
PartialMerkleTree.verifyNonInclusionProof(rootHash, key, leafLabel, leafNode, branchMask, siblings);
}
}
121 changes: 121 additions & 0 deletions contracts/tree.sol
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,56 @@ library PartialMerkleTree {
tree.rootEdge = e;
}

function commitBranchOfNonInclusion(
Tree storage tree,
bytes key,
bytes32 potentialSiblingLabel,
bytes32 potentialSiblingValue,
uint branchMask,
bytes32[] siblings
) internal {
D.Label memory k = D.Label(keccak256(key), 256);
D.Edge memory e;
// e.node(0x083d)
for (uint i = 0; branchMask != 0; i++) {
// retrieve edge data with branch mask
uint bitSet = Utils.lowestBitSet(branchMask);
branchMask &= ~(uint(1) << bitSet);
(k, e.label) = Utils.splitAt(k, 255 - bitSet);
uint bit;
(bit, e.label) = Utils.chopFirstBit(e.label);

if (i == 0) {
e.label.length = bitSet;
e.label.data = potentialSiblingLabel;
e.node = potentialSiblingValue;
}

// find upper node with retrieved edge & sibling
bytes32[2] memory edgeHashes;
edgeHashes[bit] = edgeHash(e);
edgeHashes[1 - bit] = siblings[siblings.length - i - 1];
bytes32 upperNode = keccak256(abi.encode(edgeHashes[0], edgeHashes[1]));

// Update sibling information
D.Node storage parentNode = tree.nodes[upperNode];


// Put edge
parentNode.children[bit] = e;
// Put sibling edge if needed
if (parentNode.children[1 - bit].isEmpty()) {
parentNode.children[1 - bit].header = siblings[siblings.length - i - 1];
}
// go to upper edge
e.node = keccak256(abi.encode(edgeHashes[0], edgeHashes[1]));
}
e.label = k;
require(tree.root == edgeHash(e));
tree.root = edgeHash(e);
tree.rootEdge = e;
}

function insert(Tree storage tree, bytes key, bytes value) internal {
D.Label memory k = D.Label(keccak256(key), 256);
bytes32 valueHash = keccak256(value);
Expand Down Expand Up @@ -164,6 +214,54 @@ library PartialMerkleTree {
}
}

function getNonInclusionProof(Tree storage tree, bytes key) internal view returns (
bytes32 potentialSiblingLabel,
bytes32 potentialSiblingValue,
uint branchMask,
bytes32[] _siblings
){
uint length;
uint numSiblings;

// Start from root edge
D.Label memory label = D.Label(keccak256(key), 256);
D.Edge memory e = tree.rootEdge;
bytes32[256] memory siblings;

while (true) {
// Find at edge
require(label.length >= e.label.length);
D.Label memory prefix;
D.Label memory suffix;
(prefix, suffix) = Utils.splitCommonPrefix(label, e.label);

// suffix.length == 0 means that the key exists. Thus the length of the suffix should be not zero
require(suffix.length != 0);

if (prefix.length >= e.label.length) {
// Partial matched, keep finding
length += prefix.length;
branchMask |= uint(1) << (255 - length);
length += 1;
uint head;
(head, label) = Utils.chopFirstBit(suffix);
siblings[numSiblings++] = edgeHash(tree.nodes[e.node].children[1 - head]);
e = tree.nodes[e.node].children[head];
} else {
// Found the potential sibling. Set data to return
potentialSiblingLabel = e.label.data;
potentialSiblingValue = e.node;
break;
}
}
if (numSiblings > 0)
{
_siblings = new bytes32[](numSiblings);
for (uint i = 0; i < numSiblings; i++)
_siblings[i] = siblings[i];
}
}

function verifyProof(bytes32 rootHash, bytes key, bytes value, uint branchMask, bytes32[] siblings) public pure {
D.Label memory k = D.Label(keccak256(key), 256);
D.Edge memory e;
Expand All @@ -183,6 +281,29 @@ library PartialMerkleTree {
require(rootHash == edgeHash(e));
}

function verifyNonInclusionProof(bytes32 rootHash, bytes key, bytes32 potentialSiblingLabel, bytes32 potentialSiblingValue, uint branchMask, bytes32[] siblings) public pure {
D.Label memory k = D.Label(keccak256(key), 256);
D.Edge memory e;
for (uint i = 0; branchMask != 0; i++) {
uint bitSet = Utils.lowestBitSet(branchMask);
branchMask &= ~(uint(1) << bitSet);
(k, e.label) = Utils.splitAt(k, 255 - bitSet);
uint bit;
(bit, e.label) = Utils.chopFirstBit(e.label);
bytes32[2] memory edgeHashes;
if (i == 0) {
e.label.length = bitSet;
e.label.data = potentialSiblingLabel;
e.node = potentialSiblingValue;
}
edgeHashes[bit] = edgeHash(e);
edgeHashes[1 - bit] = siblings[siblings.length - i - 1];
e.node = keccak256(abi.encode(edgeHashes[0], edgeHashes[1]));
}
e.label = k;
require(rootHash == edgeHash(e));
}

function newEdge(bytes32 node, D.Label label) internal pure returns (D.Edge memory e){
e.node = node;
e.label = label;
Expand Down
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "solidity-partial-tree",
"version": "1.0.0",
"version": "1.1.0",
"description": "Solidity implementation of partial merkle tree",
"directories": {
"test": "test"
Expand Down
70 changes: 68 additions & 2 deletions test/PartialMerkleTree.Test.js
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,47 @@ contract('PartialMerkleTree', async ([_, primary, nonPrimary]) => {
assert.equal(await tree.doesInclude('fuz'), false)
})
})

describe('getNonInclusionProof()', async () => {
let items = { key1: 'value1', key2: 'value2', key3: 'value3' }
it('should return proof data when the key does not exist', async () => {
for (const key of Object.keys(items)) {
await tree.insert(key, items[key], { from: primary })
}
await tree.getNonInclusionProof('key4')
})
it('should not return data when the key does exist', async () => {
for (const key of Object.keys(items)) {
await tree.insert(key, items[key], { from: primary })
}
try {
await tree.getNonInclusionProof('key1')
assert.fail('Did not reverted')
} catch (e) {
assert.ok('Reverted successfully')
}
})
})

describe('verifyNonInclusionProof()', async () => {
it('should be passed when we use correct proof data', async () => {
let items = { key1: 'value1', key2: 'value2', key3: 'value3' }
for (const key of Object.keys(items)) {
await tree.insert(key, items[key], { from: primary })
}
let rootHash = await tree.getRootHash()
let [potentialSiblingLabel, potentialSiblingValue, branchMask, siblings] = await tree.getNonInclusionProof('key4')
await tree.verifyNonInclusionProof(rootHash, 'key4', potentialSiblingLabel, potentialSiblingValue, branchMask, siblings)
for (const key of Object.keys(items)) {
try {
await tree.verifyNonInclusionProof(rootHash, key, potentialSiblingLabel, potentialSiblingValue, branchMask, siblings)
assert.fail('Did not reverted')
} catch (e) {
assert.ok('Reverted successfully')
}
}
})
})
})

context('We can reenact merkle tree transformation by submitting only referred siblings instead of submitting all nodes', async () => {
Expand Down Expand Up @@ -207,12 +248,37 @@ contract('PartialMerkleTree', async ([_, primary, nonPrimary]) => {
await treeB.getProof('key1')
})

let secondPhaseOfTreeA
let secondPhaseOfTreeB
it('should have same root hash when we update key1', async () => {
await treeA.insert('key1', 'val4')
await treeB.insert('key1', 'val4')
let secondPhaseOfTreeA = await treeA.getRootHash()
let secondPhaseOfTreeB = await treeB.getRootHash()
secondPhaseOfTreeA = await treeA.getRootHash()
secondPhaseOfTreeB = await treeB.getRootHash()
assert.equal(secondPhaseOfTreeA, secondPhaseOfTreeB)
})

it('should revert before the branch data of non inclusion is committed', async () => {
try {
await treeB.insert('key4', 'val4')
assert.fail('Did not reverted')
} catch (e) {
assert.ok('Reverted successfully')
}
})

let thirdPhaseOfTreeA
let thirdPhaseOfTreeB
it('should be able to insert a non inclusion key-value pair after committting related branch data', async () => {
let [potentialSiblingLabel, potentialSiblingValue, branchMask, siblings] = await treeA.getNonInclusionProof('key4')
await treeB.commitBranchOfNonInclusion('key4', potentialSiblingLabel, potentialSiblingValue, branchMask, siblings)
assert.equal(await treeB.getRootHash(), secondPhaseOfTreeB)

await treeA.insert('key4', 'val4')
await treeB.insert('key4', 'val4')
thirdPhaseOfTreeA = await treeA.getRootHash()
thirdPhaseOfTreeB = await treeB.getRootHash()
assert.equal(thirdPhaseOfTreeA, thirdPhaseOfTreeB)
})
})
})