Skip to content

Commit 543ddf1

Browse files
committed
#75 Switch SMT inclusion verification from version 3o to 6a
1 parent 0a84705 commit 543ddf1

6 files changed

Lines changed: 90 additions & 56 deletions

File tree

src/main/java/org/unicitylabs/sdk/api/InclusionCertificate.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import org.unicitylabs.sdk.crypto.hash.HashAlgorithm;
66
import org.unicitylabs.sdk.smt.radix.FinalizedBranch;
77
import org.unicitylabs.sdk.smt.radix.FinalizedLeafBranch;
8+
import org.unicitylabs.sdk.smt.SparseMerkleTreePathUtils;
89
import org.unicitylabs.sdk.smt.radix.FinalizedNodeBranch;
910
import org.unicitylabs.sdk.util.BitString;
1011
import org.unicitylabs.sdk.util.HexConverter;
@@ -137,6 +138,7 @@ public boolean verify(StateId leafKey, DataHash leafValue, DataHash expectedRoot
137138
hash = new DataHasher(HashAlgorithm.SHA256)
138139
.update(new byte[]{0x01})
139140
.update(LongConverter.encode(depth))
141+
.update(SparseMerkleTreePathUtils.pathToRegion(keyPath, depth))
140142
.update(left)
141143
.update(right)
142144
.digest();
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package org.unicitylabs.sdk.smt;
2+
3+
import java.math.BigInteger;
4+
5+
/**
6+
* Path utilities for the radix sparse Merkle trees.
7+
*/
8+
public final class SparseMerkleTreePathUtils {
9+
10+
private SparseMerkleTreePathUtils() {
11+
}
12+
13+
/**
14+
* Region committed by an interior node: the node's {@code depth}-bit key prefix packed into 32
15+
* bytes least-significant-byte-first. {@code path} is the absolute node/key path (leading
16+
* sentinel bit at {@code depth}); its low {@code depth} bits are the prefix.
17+
*
18+
* @param path absolute node or key path
19+
* @param depth bifurcation depth of the node
20+
* @return 32-byte region
21+
*/
22+
public static byte[] pathToRegion(BigInteger path, int depth) {
23+
BigInteger bits = path.and(BigInteger.ONE.shiftLeft(depth).subtract(BigInteger.ONE));
24+
byte[] region = new byte[32];
25+
for (int j = 0; j * 8 < depth; j++) {
26+
region[j] = (byte) bits.shiftRight(8 * j).and(BigInteger.valueOf(0xff)).intValue();
27+
}
28+
return region;
29+
}
30+
}

src/main/java/org/unicitylabs/sdk/smt/radix/FinalizedNodeBranch.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import org.unicitylabs.sdk.crypto.hash.DataHash;
44
import org.unicitylabs.sdk.crypto.hash.DataHasher;
55
import org.unicitylabs.sdk.crypto.hash.HashAlgorithm;
6+
import org.unicitylabs.sdk.smt.SparseMerkleTreePathUtils;
67
import org.unicitylabs.sdk.util.LongConverter;
78

89
import java.math.BigInteger;
@@ -72,6 +73,7 @@ public static FinalizedNodeBranch fromPendingNode(HashAlgorithm hashAlgorithm, P
7273
DataHash hash = new DataHasher(hashAlgorithm)
7374
.update(new byte[]{0x01})
7475
.update(LongConverter.encode(node.getDepth()))
76+
.update(SparseMerkleTreePathUtils.pathToRegion(node.getPath(), node.getDepth()))
7577
.update(left.getHash().getData())
7678
.update(right.getHash().getData())
7779
.digest();

src/main/java/org/unicitylabs/sdk/smt/radix/SparseMerkleTree.java

Lines changed: 14 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ public synchronized void addLeaf(byte[] key, byte[] data)
5959
boolean isRight = path.testBit(0);
6060
Branch branch = isRight ? this.right : this.left;
6161
Branch result = branch != null
62-
? SparseMerkleTree.buildTree(branch, path, 0, key, data)
62+
? SparseMerkleTree.buildTree(branch, path, key, data)
6363
: new PendingLeafBranch(path, key, data);
6464

6565
if (isRight) {
@@ -83,13 +83,12 @@ public synchronized FinalizedNodeBranch calculateRoot() {
8383
return new PendingNodeBranch(BigInteger.ONE, 0, left, right).finalize(hashAlgorithm);
8484
}
8585

86-
private static Branch buildTree(Branch branch, BigInteger remainingPath, int depth, byte[] key,
87-
byte[] value) throws BranchExistsException, LeafOutOfBoundsException {
88-
CommonPath commonPath = CommonPath.create(remainingPath, branch.getPath());
89-
int commonPathLength = commonPath.getLength();
90-
boolean isRight = remainingPath.shiftRight(commonPathLength).testBit(0);
86+
private static Branch buildTree(Branch branch, BigInteger keyPath, byte[] key, byte[] value)
87+
throws BranchExistsException, LeafOutOfBoundsException {
88+
CommonPath commonPath = CommonPath.create(keyPath, branch.getPath());
89+
boolean isRight = keyPath.shiftRight(commonPath.getLength()).testBit(0);
9190

92-
if (commonPath.getPath().equals(remainingPath)) {
91+
if (commonPath.getPath().equals(keyPath)) {
9392
throw new BranchExistsException();
9493
}
9594

@@ -98,40 +97,28 @@ private static Branch buildTree(Branch branch, BigInteger remainingPath, int dep
9897
throw new LeafOutOfBoundsException();
9998
}
10099

101-
LeafBranch leafBranch = (LeafBranch) branch;
102-
103-
LeafBranch oldBranch = new PendingLeafBranch(
104-
branch.getPath().shiftRight(commonPathLength), leafBranch.getKey(),
105-
leafBranch.getValue());
106-
LeafBranch newBranch = new PendingLeafBranch(
107-
remainingPath.shiftRight(commonPathLength), key, value);
108-
return new PendingNodeBranch(commonPath.getPath(), depth + commonPathLength,
109-
isRight ? oldBranch : newBranch, isRight ? newBranch : oldBranch);
100+
LeafBranch newBranch = new PendingLeafBranch(keyPath, key, value);
101+
return new PendingNodeBranch(commonPath.getPath(), commonPath.getLength(),
102+
isRight ? branch : newBranch, isRight ? newBranch : branch);
110103
}
111104

112105
NodeBranch nodeBranch = (NodeBranch) branch;
113106

114107
// if node branch is split in the middle
115108
if (commonPath.getPath().compareTo(branch.getPath()) < 0) {
116-
LeafBranch newBranch = new PendingLeafBranch(
117-
remainingPath.shiftRight(commonPathLength), key, value);
118-
NodeBranch oldBranch = new PendingNodeBranch(
119-
branch.getPath().shiftRight(commonPathLength), nodeBranch.getDepth(),
120-
nodeBranch.getLeft(), nodeBranch.getRight());
121-
return new PendingNodeBranch(commonPath.getPath(), depth + commonPathLength,
122-
isRight ? oldBranch : newBranch, isRight ? newBranch : oldBranch);
109+
LeafBranch newBranch = new PendingLeafBranch(keyPath, key, value);
110+
return new PendingNodeBranch(commonPath.getPath(), commonPath.getLength(),
111+
isRight ? branch : newBranch, isRight ? newBranch : branch);
123112
}
124113

125114
if (isRight) {
126115
return new PendingNodeBranch(nodeBranch.getPath(), nodeBranch.getDepth(),
127116
nodeBranch.getLeft(),
128-
SparseMerkleTree.buildTree(nodeBranch.getRight(),
129-
remainingPath.shiftRight(commonPathLength), depth + commonPathLength, key, value));
117+
SparseMerkleTree.buildTree(nodeBranch.getRight(), keyPath, key, value));
130118
}
131119

132120
return new PendingNodeBranch(nodeBranch.getPath(), nodeBranch.getDepth(),
133-
SparseMerkleTree.buildTree(nodeBranch.getLeft(),
134-
remainingPath.shiftRight(commonPathLength), depth + commonPathLength, key, value),
121+
SparseMerkleTree.buildTree(nodeBranch.getLeft(), keyPath, key, value),
135122
nodeBranch.getRight());
136123
}
137124
}

src/main/java/org/unicitylabs/sdk/smt/radixsum/SparseMerkleSumTree.java

Lines changed: 14 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ public synchronized void addLeaf(byte[] key, byte[] data, BigInteger value)
6767
boolean isRight = path.testBit(0);
6868
Branch branch = isRight ? this.right : this.left;
6969
Branch result = branch != null
70-
? SparseMerkleSumTree.buildTree(branch, path, 0, key, data, value)
70+
? SparseMerkleSumTree.buildTree(branch, path, key, data, value)
7171
: new PendingLeafBranch(path, key, data, value);
7272

7373
if (isRight) {
@@ -91,14 +91,13 @@ public synchronized SparseMerkleSumTreeRootNode calculateRoot() {
9191
return SparseMerkleSumTreeRootNode.create(left, right, this.hashAlgorithm);
9292
}
9393

94-
private static Branch buildTree(Branch branch, BigInteger remainingPath, int depth, byte[] key,
95-
byte[] data, BigInteger value)
94+
private static Branch buildTree(Branch branch, BigInteger keyPath, byte[] key, byte[] data,
95+
BigInteger value)
9696
throws BranchExistsException, LeafOutOfBoundsException {
97-
CommonPath commonPath = CommonPath.create(remainingPath, branch.getPath());
98-
int commonPathLength = commonPath.getLength();
99-
boolean isRight = remainingPath.shiftRight(commonPathLength).testBit(0);
97+
CommonPath commonPath = CommonPath.create(keyPath, branch.getPath());
98+
boolean isRight = keyPath.shiftRight(commonPath.getLength()).testBit(0);
10099

101-
if (commonPath.getPath().equals(remainingPath)) {
100+
if (commonPath.getPath().equals(keyPath)) {
102101
throw new BranchExistsException();
103102
}
104103

@@ -107,42 +106,28 @@ private static Branch buildTree(Branch branch, BigInteger remainingPath, int dep
107106
throw new LeafOutOfBoundsException();
108107
}
109108

110-
LeafBranch leafBranch = (LeafBranch) branch;
111-
112-
LeafBranch oldBranch = new PendingLeafBranch(
113-
branch.getPath().shiftRight(commonPathLength), leafBranch.getKey(),
114-
leafBranch.getData(), leafBranch.getValue());
115-
LeafBranch newBranch = new PendingLeafBranch(
116-
remainingPath.shiftRight(commonPathLength), key, data, value);
117-
return new PendingNodeBranch(commonPath.getPath(), depth + commonPathLength,
118-
isRight ? oldBranch : newBranch, isRight ? newBranch : oldBranch);
109+
LeafBranch newBranch = new PendingLeafBranch(keyPath, key, data, value);
110+
return new PendingNodeBranch(commonPath.getPath(), commonPath.getLength(),
111+
isRight ? branch : newBranch, isRight ? newBranch : branch);
119112
}
120113

121114
NodeBranch nodeBranch = (NodeBranch) branch;
122115

123116
// if node branch is split in the middle
124117
if (commonPath.getPath().compareTo(branch.getPath()) < 0) {
125-
LeafBranch newBranch = new PendingLeafBranch(
126-
remainingPath.shiftRight(commonPathLength), key, data, value);
127-
NodeBranch oldBranch = new PendingNodeBranch(
128-
branch.getPath().shiftRight(commonPathLength), nodeBranch.getDepth(),
129-
nodeBranch.getLeft(), nodeBranch.getRight());
130-
return new PendingNodeBranch(commonPath.getPath(), depth + commonPathLength,
131-
isRight ? oldBranch : newBranch, isRight ? newBranch : oldBranch);
118+
LeafBranch newBranch = new PendingLeafBranch(keyPath, key, data, value);
119+
return new PendingNodeBranch(commonPath.getPath(), commonPath.getLength(),
120+
isRight ? branch : newBranch, isRight ? newBranch : branch);
132121
}
133122

134123
if (isRight) {
135124
return new PendingNodeBranch(nodeBranch.getPath(), nodeBranch.getDepth(),
136125
nodeBranch.getLeft(),
137-
SparseMerkleSumTree.buildTree(nodeBranch.getRight(),
138-
remainingPath.shiftRight(commonPathLength), depth + commonPathLength, key,
139-
data, value));
126+
SparseMerkleSumTree.buildTree(nodeBranch.getRight(), keyPath, key, data, value));
140127
}
141128

142129
return new PendingNodeBranch(nodeBranch.getPath(), nodeBranch.getDepth(),
143-
SparseMerkleSumTree.buildTree(nodeBranch.getLeft(),
144-
remainingPath.shiftRight(commonPathLength), depth + commonPathLength, key,
145-
data, value),
130+
SparseMerkleSumTree.buildTree(nodeBranch.getLeft(), keyPath, key, data, value),
146131
nodeBranch.getRight());
147132
}
148133
}

src/test/java/org/unicitylabs/sdk/smt/radix/SparseMerkleTreeTest.java

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,11 @@
22

33
import org.junit.jupiter.api.Assertions;
44
import org.junit.jupiter.api.Test;
5+
import org.unicitylabs.sdk.api.InclusionCertificate;
6+
import org.unicitylabs.sdk.api.StateId;
7+
import org.unicitylabs.sdk.crypto.hash.DataHash;
58
import org.unicitylabs.sdk.crypto.hash.HashAlgorithm;
9+
import org.unicitylabs.sdk.serializer.cbor.CborSerializer;
610

711
public class SparseMerkleTreeTest {
812

@@ -16,4 +20,28 @@ void addLeafRejectsNon32ByteKeyOrData() {
1620
Assertions.assertThrows(NullPointerException.class,
1721
() -> tree.addLeaf(null, new byte[32]));
1822
}
23+
24+
@Test
25+
void deepSplitAtDepth255VerifiesWithRegion() throws Exception {
26+
SparseMerkleTree tree = new SparseMerkleTree(HashAlgorithm.SHA256);
27+
28+
byte[] a = new byte[32];
29+
byte[] b = new byte[32];
30+
b[31] = (byte) 0x80;
31+
byte[] valueA = new byte[32];
32+
valueA[0] = 1;
33+
byte[] valueB = new byte[32];
34+
valueB[0] = 2;
35+
36+
tree.addLeaf(a, valueA);
37+
tree.addLeaf(b, valueB);
38+
FinalizedNodeBranch root = tree.calculateRoot();
39+
40+
for (byte[][] entry : new byte[][][]{{a, valueA}, {b, valueB}}) {
41+
InclusionCertificate certificate = InclusionCertificate.create(root, entry[0]);
42+
StateId key = StateId.fromCbor(CborSerializer.encodeByteString(entry[0]));
43+
DataHash value = new DataHash(HashAlgorithm.SHA256, entry[1]);
44+
Assertions.assertTrue(certificate.verify(key, value, root.getHash()));
45+
}
46+
}
1947
}

0 commit comments

Comments
 (0)