Skip to content

Commit 5ff5d04

Browse files
authored
Merge pull request #88 from input-output-hk/slicedavltree-rework
New features and improvements in sliced authenticated AVL+ trees
2 parents f08152c + 40ab5fd commit 5ff5d04

File tree

9 files changed

+222
-71
lines changed

9 files changed

+222
-71
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ resolvers += "Sonatype Releases" at "https://oss.sonatype.org/content/repositori
1818

1919
You can use Scrypto in your sbt project by simply adding the following dependency to your build file:
2020
```scala
21-
libraryDependencies += "org.scorexfoundation" %% "scrypto" % "2.1.6"
21+
libraryDependencies += "org.scorexfoundation" %% "scrypto" % "2.2.0"
2222
```
2323

2424
### Hash functions

build.sbt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ libraryDependencies ++= Seq(
3535
"com.google.guava" % "guava" % "23.0",
3636
"com.typesafe.scala-logging" %% "scala-logging" % "3.9.2",
3737
"org.whispersystems" % "curve25519-java" % "0.5.0",
38-
"org.bouncycastle" % "bcprov-jdk15on" % "1.66",
38+
"org.bouncycastle" % "bcprov-jdk15to18" % "1.66",
3939
"org.scorexfoundation" %% "scorex-util" % "0.1.8"
4040
)
4141

release-notes.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,27 @@
1+
**2.2.0**
2+
---------
3+
4+
* Rework of sliced AVL+ trees (BatchAVLProverManifest / BatchAVLProverSubtree)
5+
* Batch Merkle proof implementation
6+
* AuthenticationTreeOps.logError
7+
* Scala, scorex-util, Guava, BouncyCastle dependencies updated
8+
* example app in SparseMarkleTree reworked into tests (#38)
9+
* switch from sbt-git to sbt-dynver
10+
* migration from Travis to GA
11+
12+
13+
**2.1.8**
14+
---------
15+
16+
* Guava's comparator for byte arrays is used instead of custom old one ( #74 )
17+
* Scala 2.13 support ( #75 )
18+
19+
**2.1.7**
20+
---------
21+
22+
* remove logback dependency, add sbt-dependency-graph plugin
23+
* add git versioning, cross-build with scala 2.11, auto sonatype publishing(WIP); remove sbt-lock
24+
125
**2.1.6**
226
---------
327

src/main/scala/scorex/crypto/authds/avltree/batch/BatchNode.scala

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ sealed trait Node[D <: Digest] extends ToStringHelper {
1111

1212
protected var labelOpt: Option[D] = None
1313

14+
/**
15+
* Get digest of the node. If it was computed previously, read the digest from hash, otherwise, compute it.
16+
*/
1417
def label: D = labelOpt match {
1518
case None =>
1619
val l = computeLabel
@@ -34,11 +37,15 @@ class LabelOnlyNode[D <: Digest](l: D) extends VerifierNodes[D] {
3437
}
3538

3639
sealed trait InternalNode[D <: Digest] extends Node[D] {
40+
import InternalNode.InternalNodePrefix
41+
3742
protected var b: Balance
3843

3944
protected val hf: CryptographicHash[D]
4045

41-
protected def computeLabel: D = hf.prefixedHash(1: Byte, Array(b), left.label, right.label)
46+
override protected def computeLabel: D = {
47+
hf.hash(Array(InternalNodePrefix, b), left.label, right.label)
48+
}
4249

4350
def balance: Balance = b
4451

@@ -52,6 +59,10 @@ sealed trait InternalNode[D <: Digest] extends Node[D] {
5259
def getNewKey(newKey: ADKey): InternalNode[D]
5360
}
5461

62+
object InternalNode {
63+
val InternalNodePrefix: Byte = 1: Byte
64+
}
65+
5566
class InternalProverNode[D <: Digest](protected var k: ADKey,
5667
protected var l: ProverNodes[D],
5768
protected var r: ProverNodes[D],
@@ -120,23 +131,31 @@ class InternalVerifierNode[D <: Digest](protected var l: Node[D], protected var
120131
}
121132

122133
sealed trait Leaf[D <: Digest] extends Node[D] with KeyInVar {
134+
import Leaf.LeafPrefix
135+
123136
protected var nk: ADKey
124137
protected var v: ADValue
125138

126-
127139
def nextLeafKey: ADKey = nk
128140

129141
def value: ADValue = v
130142

131143
protected val hf: CryptographicHash[D] // TODO: Seems very wasteful to store hf in every node of the tree, when they are all the same. Is there a better way? Pass them in to label method from above? Same for InternalNode and for other, non-batch, trees
132144

133-
protected def computeLabel: D = hf.prefixedHash(0: Byte, k, v, nk)
145+
protected def computeLabel: D = {
146+
hf.prefixedHash(LeafPrefix, k, v, nk)
147+
}
134148

135149
def getNew(newKey: ADKey = k, newValue: ADValue = v, newNextLeafKey: ADKey = nk): Leaf[D]
136150

137151
override def toString: String = {
138152
s"${arrayToString(label)}: Leaf(${arrayToString(key)}, ${arrayToString(value)}, ${arrayToString(nextLeafKey)})"
139153
}
154+
155+
}
156+
157+
object Leaf {
158+
val LeafPrefix = 0: Byte
140159
}
141160

142161
class VerifierLeaf[D <: Digest](protected var k: ADKey, protected var v: ADValue, protected var nk: ADKey)
Lines changed: 39 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,46 @@
11
package scorex.crypto.authds.avltree.batch.serialization
22

3-
import scorex.crypto.authds.avltree.batch.ProverNodes
3+
import scorex.crypto.authds.avltree.batch.{InternalProverNode, ProverLeaf, ProverNodes}
44
import scorex.crypto.hash.Digest
55

6+
import scala.collection.mutable
7+
68

79
/**
8-
* Top subtree of AVL tree, starting from root node and ending with ProxyInternalNode
10+
* A subtree of AVL tree, which is starting from root node and ending at certain depth with nodes
11+
* having no children (ProxyInternalNode). The manifest commits to subtrees below the depth.
912
*/
10-
case class BatchAVLProverManifest[D <: Digest](keyLength: Int,
11-
valueLengthOpt: Option[Int],
12-
rootAndHeight: (ProverNodes[D], Int))
13+
case class BatchAVLProverManifest[D <: Digest](root: ProverNodes[D], rootHeight: Int) {
14+
15+
/**
16+
* Unique (and cryptographically strong) identifier of the manifest (digest of the root node)
17+
*/
18+
def id: D = root.label
19+
20+
/**
21+
* Verify that manifest corresponds to expected digest and height provided by a trusted party
22+
* (for blockchain protocols, it can be digest and height included by a miner)
23+
*/
24+
def verify(expectedDigest: D, expectedHeight: Int): Boolean = {
25+
id.sameElements(expectedDigest) && expectedHeight == rootHeight
26+
}
27+
28+
/**
29+
* Identifiers (digests) of subtrees below the manifest
30+
*/
31+
def subtreesIds: mutable.Buffer[D] = {
32+
def idCollector(node: ProverNodes[D], acc: mutable.Buffer[D]): mutable.Buffer[D] = {
33+
node match {
34+
case n: ProxyInternalNode[D] if n.isEmpty =>
35+
(acc += n.leftLabel) += n.rightLabel
36+
case i : InternalProverNode[D] =>
37+
idCollector(i.right, idCollector(i.left, acc))
38+
case _: ProverLeaf[D] =>
39+
acc
40+
}
41+
}
42+
43+
idCollector(root, mutable.Buffer.empty)
44+
}
45+
46+
}

src/main/scala/scorex/crypto/authds/avltree/batch/serialization/BatchAVLProverSerializer.scala

Lines changed: 30 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,6 @@ class BatchAVLProverSerializer[D <: Digest, HF <: CryptographicHash[D]](implicit
1313

1414
type SlicedTree = (BatchAVLProverManifest[D], Seq[BatchAVLProverSubtree[D]])
1515

16-
def slice(tree: BatchAVLProver[D, HF]): SlicedTree = slice(tree, tree.rootNodeHeight / 2)
17-
1816
/**
1917
* Slice AVL tree to top subtree tree (BatchAVLProverManifest) and
2018
* bottom subtrees (BatchAVLProverSubtree) with height `subtreeDepth`
@@ -31,87 +29,88 @@ class BatchAVLProverSerializer[D <: Digest, HF <: CryptographicHash[D]](implicit
3129
currentNode match {
3230
case n: InternalProverNode[D] if currentHeight > subtreeDepth =>
3331
val nextParent = ProxyInternalNode(n)
34-
parent.mutate(nextParent)
32+
parent.setChild(nextParent)
3533
val leftSubtrees = getSubtrees(n.left, currentHeight - 1, nextParent)
3634
val rightSubtrees = getSubtrees(n.right, currentHeight - 1, nextParent)
3735
leftSubtrees ++ rightSubtrees
3836
case n: InternalProverNode[D] =>
39-
parent.mutate(ProxyInternalNode(n))
37+
parent.setChild(ProxyInternalNode(n))
4038
Seq(BatchAVLProverSubtree(n.left), BatchAVLProverSubtree(n.right))
4139
case l: ProverLeaf[D] =>
42-
parent.mutate(l)
40+
parent.setChild(l)
4341
Seq(BatchAVLProverSubtree(l))
4442
}
4543
}
4644

4745
val subtrees = getSubtrees(tn.left, height - 1, rootProxyNode) ++ getSubtrees(tn.right, height - 1, rootProxyNode)
48-
val manifest = BatchAVLProverManifest[D](tree.keyLength, tree.valueLengthOpt, (rootProxyNode, height))
46+
val manifest = BatchAVLProverManifest[D](rootProxyNode, height)
4947
(manifest, subtrees)
5048
case l: ProverLeaf[D] =>
51-
(BatchAVLProverManifest[D](tree.keyLength, tree.valueLengthOpt, (l, tree.rootNodeHeight)), Seq.empty)
49+
(BatchAVLProverManifest[D](l, tree.rootNodeHeight), Seq.empty)
5250
}
5351

5452
/**
5553
* Combine tree pieces into one big tree
5654
*/
57-
def combine(sliced: SlicedTree): Try[BatchAVLProver[D, HF]] = Try {
58-
sliced._1.rootAndHeight._1 match {
55+
def combine(sliced: SlicedTree,
56+
keyLength: Int,
57+
valueLengthOpt: Option[Int]): Try[BatchAVLProver[D, HF]] = Try {
58+
val manifest = sliced._1
59+
manifest.root match {
5960
case tn: InternalProverNode[D] =>
61+
62+
// manifest being mutated here
6063
def mutateLoop(n: ProverNodes[D]): Unit = n match {
6164
case n: ProxyInternalNode[D] if n.isEmpty =>
62-
val left = sliced._2.find(_.subtreeTop.label sameElements n.leftLabel).get.subtreeTop
63-
val right = sliced._2.find(_.subtreeTop.label sameElements n.rightLabel).get.subtreeTop
64-
n.mutate(left)
65-
n.mutate(right)
65+
val left = sliced._2.find(_.id sameElements n.leftLabel).get.subtreeTop
66+
val right = sliced._2.find(_.id sameElements n.rightLabel).get.subtreeTop
67+
n.setChild(left)
68+
n.setChild(right)
6669
case n: InternalProverNode[D] =>
6770
mutateLoop(n.left)
6871
mutateLoop(n.right)
6972
case _ =>
7073
}
7174

7275
mutateLoop(tn)
73-
new BatchAVLProver[D, HF](sliced._1.keyLength, sliced._1.valueLengthOpt, Some(sliced._1.rootAndHeight))
7476
case _: ProverLeaf[D] =>
75-
new BatchAVLProver[D, HF](sliced._1.keyLength, sliced._1.valueLengthOpt, Some(sliced._1.rootAndHeight))
7677
}
78+
79+
new BatchAVLProver[D, HF](keyLength, valueLengthOpt, Some(manifest.root -> manifest.rootHeight))
7780
}
7881

7982
def manifestToBytes(manifest: BatchAVLProverManifest[D]): Array[Byte] = {
80-
Bytes.concat(Ints.toByteArray(manifest.keyLength),
81-
Ints.toByteArray(manifest.valueLengthOpt.getOrElse(-1)),
82-
Ints.toByteArray(manifest.rootAndHeight._2),
83-
nodesToBytes(manifest.rootAndHeight._1)
83+
Bytes.concat(
84+
Ints.toByteArray(manifest.rootHeight),
85+
nodesToBytes(manifest.root)
8486
)
8587
}
8688

87-
def manifestFromBytes(bytes: Array[Byte]): Try[BatchAVLProverManifest[D]] = Try {
88-
val keyLength = Ints.fromByteArray(bytes.slice(0, 4))
89-
val valueLength = Ints.fromByteArray(bytes.slice(4, 8))
90-
if (valueLength < -1) throw new Error(s"Wrong valueLength: $valueLength")
91-
val valueLengthOpt = if (valueLength == -1) None else Some(valueLength)
92-
val oldHeight = Ints.fromByteArray(bytes.slice(8, 12))
93-
val oldTop = nodesFromBytes(bytes.slice(12, bytes.length), keyLength).get
94-
BatchAVLProverManifest[D](keyLength, valueLengthOpt, (oldTop, oldHeight))
89+
def manifestFromBytes(bytes: Array[Byte],
90+
keyLength: Int): Try[BatchAVLProverManifest[D]] = Try {
91+
val oldHeight = Ints.fromByteArray(bytes.slice(0, 4))
92+
val oldTop = nodesFromBytes(bytes.slice(4, bytes.length), keyLength).get
93+
BatchAVLProverManifest[D](oldTop, oldHeight)
9594
}
9695

9796
def subtreeToBytes(t: BatchAVLProverSubtree[D]): Array[Byte] = nodesToBytes(t.subtreeTop)
9897

9998
def subtreeFromBytes(b: Array[Byte], kl: Int): Try[BatchAVLProverSubtree[D]] = nodesFromBytes(b, kl).
10099
map(topNode => BatchAVLProverSubtree[D](topNode))
101100

102-
def nodesToBytes(obj: ProverNodes[D]): Array[Byte] = {
101+
def nodesToBytes(rootNode: ProverNodes[D]): Array[Byte] = {
103102
def loop(currentNode: ProverNodes[D]): Array[Byte] = currentNode match {
104103
case l: ProverLeaf[D] =>
105104
Bytes.concat(Array(0.toByte), l.key, l.nextLeafKey, l.value)
106105
case n: ProxyInternalNode[D] if n.isEmpty =>
107-
Bytes.concat(Array(2.toByte, n.balance), n.key, n.leftLabel, n.rightLabel, n.label)
106+
Bytes.concat(Array(2.toByte, n.balance), n.key, n.leftLabel, n.rightLabel)
108107
case n: InternalProverNode[D] =>
109108
val leftBytes = loop(n.left)
110109
val rightBytes = loop(n.right)
111110
Bytes.concat(Array(1.toByte, n.balance), n.key, Ints.toByteArray(leftBytes.length), leftBytes, rightBytes)
112111
}
113112

114-
loop(obj)
113+
loop(rootNode)
115114
}
116115

117116
def nodesFromBytes(bytesIn: Array[Byte], keyLength: Int): Try[ProverNodes[D]] = Try {
@@ -135,8 +134,7 @@ class BatchAVLProverSerializer[D <: Digest, HF <: CryptographicHash[D]](implicit
135134
val key = ADKey @@ bytes.slice(2, keyLength + 2)
136135
val leftLabel = hf.byteArrayToDigest(bytes.slice(keyLength + 2, keyLength + 2 + labelLength)).get
137136
val rightLabel = hf.byteArrayToDigest(bytes.slice(keyLength + 2 + labelLength, keyLength + 2 + 2 * labelLength)).get
138-
val selfLabel = hf.byteArrayToDigest(bytes.slice(keyLength + 2 + 2 * labelLength, keyLength + 2 + 3 * labelLength)).get
139-
new ProxyInternalNode[D](key, Some(selfLabel), leftLabel, rightLabel, balance)
137+
new ProxyInternalNode[D](key, leftLabel, rightLabel, balance)
140138
case _ =>
141139
???
142140
}
Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,41 @@
11
package scorex.crypto.authds.avltree.batch.serialization
22

3-
import scorex.crypto.authds.avltree.batch.ProverNodes
3+
import scorex.crypto.authds.ADValue
4+
import scorex.crypto.authds.avltree.batch.{InternalProverNode, ProverLeaf, ProverNodes}
45
import scorex.crypto.hash.Digest
56

7+
import scala.collection.mutable
8+
69
/**
7-
* AVL subtree, starting from Manifests FinalInternalNode and ending with Leafs
10+
* AVL subtree, starting from a manifest's terminal internal node and ending with Leafs
811
*/
9-
case class BatchAVLProverSubtree[D <: Digest](subtreeTop: ProverNodes[D])
12+
case class BatchAVLProverSubtree[D <: Digest](subtreeTop: ProverNodes[D]) {
13+
/**
14+
* Unique (and cryptographically strong) identifier of the sub-tree
15+
*/
16+
def id: D = subtreeTop.label
17+
18+
/**
19+
* Verify that manifest corresponds to expected digest (e.g. got from a manifest)
20+
*/
21+
def verify(expectedDigest: D): Boolean = {
22+
subtreeTop.label.sameElements(expectedDigest)
23+
}
24+
25+
/**
26+
* @return leafs of the subtree
27+
*/
28+
def leafValues: mutable.Buffer[ADValue] = {
29+
def idCollector(node: ProverNodes[D], acc: mutable.Buffer[ADValue]): mutable.Buffer[ADValue] = {
30+
node match {
31+
case i : InternalProverNode[D] =>
32+
idCollector(i.right, idCollector(i.left, acc))
33+
case l: ProverLeaf[D] =>
34+
acc += l.value
35+
}
36+
}
37+
38+
idCollector(subtreeTop, mutable.Buffer.empty)
39+
}
40+
41+
}

0 commit comments

Comments
 (0)