Skip to content

Commit ede3432

Browse files
committed
feat(MerkleTree): Consolidate MerkleTree repo into core kit
1 parent c508ee0 commit ede3432

6 files changed

Lines changed: 436 additions & 0 deletions

File tree

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
import Foundation
2+
3+
public extension MerkleTree {
4+
func getAuditTrail(for itemHash: String, leaves: [MerkleTree]) -> [PathHash] {
5+
guard let targetLeave = leaves.first(where: { $0.value.hash == itemHash }) else { return [] }
6+
var path = [PathHash]()
7+
8+
var currentParent: MerkleTree? = targetLeave.parent
9+
var siblingHash = itemHash
10+
while let parent = currentParent {
11+
if let leftHash = parent.children.left?.value.hash {
12+
if let rightHash = parent.children.right?.value.hash {
13+
if leftHash == siblingHash {
14+
path.append(PathHash(rightHash, leaf: .right))
15+
} else if rightHash == siblingHash {
16+
path.append(PathHash(leftHash, leaf: .left))
17+
}
18+
}
19+
}
20+
21+
siblingHash = parent.value.hash
22+
currentParent = parent.parent
23+
}
24+
return path
25+
}
26+
27+
private func createParent(times: Int) -> MerkleTree {
28+
assert(times >= 0, "times shouldn't be negative")
29+
var tree = self
30+
let hash = value.hash
31+
for _ in 0 ..< times {
32+
let parent = MerkleTree(hash: hash)
33+
parent.add(right: tree)
34+
tree.parent = parent
35+
tree = parent
36+
}
37+
return tree
38+
}
39+
40+
static func toPowersOfTwo(_ num: Int) -> [Int] {
41+
let binary = String(num, radix: 2)
42+
return binary
43+
.enumerated()
44+
.filter { $1 == "1" }
45+
.map { binary.count - $0.offset - 1 }
46+
}
47+
48+
static func build(fromBlobs: [Data]) -> MerkleTree {
49+
var leaves = ArraySlice(fromBlobs.map(MerkleTree.init(blob:)))
50+
var roots = [MerkleTree]()
51+
for power in toPowersOfTwo(leaves.count) {
52+
if power == 0, let last = leaves.last {
53+
roots.append(last)
54+
} else {
55+
guard !leaves.isEmpty else { continue }
56+
roots.append(merge(leaves.prefix(1 << power)))
57+
leaves.removeFirst(1 << power)
58+
}
59+
}
60+
61+
return merge(ArraySlice(roots))
62+
}
63+
64+
private static func merge(_ nodes: ArraySlice<MerkleTree>) -> MerkleTree {
65+
let count = nodes.count
66+
assert(count != 0, "at least one node should exist")
67+
if count == 1, let first = nodes.first { return first }
68+
if count == 2, let first = nodes.first, let last = nodes.last {
69+
return makeSiblings(first, last)
70+
} else {
71+
let half = (count / 2) + (count % 2)
72+
return makeSiblings(merge(nodes.prefix(half)), merge(nodes.suffix(count - half)))
73+
}
74+
}
75+
76+
func audit(itemHash: String, auditTrail: [PathHash]) -> Bool {
77+
var pathHashes = auditTrail
78+
var siblingHash = itemHash
79+
80+
while !pathHashes.isEmpty {
81+
let pathHash = pathHashes.removeFirst()
82+
let parentHashes = pathHash.leaf == .left ? (pathHash.hash + siblingHash) : (siblingHash + pathHash.hash)
83+
siblingHash = Data(parentHashes.utf8).doubleHashedHex
84+
}
85+
86+
return siblingHash == value.hash
87+
}
88+
89+
private static func makeSiblings(_ left: MerkleTree, _ right: MerkleTree) -> MerkleTree {
90+
let heightDifference = left.height - right.height
91+
92+
let leftHash = left.value.hash
93+
let rightHash = right.value.hash
94+
let parent = MerkleTree(hash: Data((leftHash + rightHash).utf8).doubleHashedHex)
95+
parent.add(left: left, right: right.createParent(times: heightDifference))
96+
return parent
97+
}
98+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import CryptoKit
2+
import Foundation
3+
4+
public typealias MerkleTree = TwoWayBinaryTree<MerkleNode>
5+
6+
public extension MerkleTree {
7+
convenience init(hash: String) {
8+
self.init(MerkleNode(hash: hash))
9+
}
10+
11+
convenience init(blob: Data) {
12+
self.init(MerkleNode(blob: blob))
13+
}
14+
15+
var height: Int {
16+
var sum = 1
17+
var rootChildren: TwoWayBinaryTree? = children.left
18+
while let left = rootChildren {
19+
sum += 1
20+
rootChildren = left.children.left
21+
}
22+
return sum
23+
}
24+
}
25+
26+
extension MerkleTree: Equatable where T: Equatable {
27+
public static func == (lhs: MerkleTree, rhs: MerkleTree) -> Bool {
28+
lhs.value == rhs.value
29+
}
30+
}
31+
32+
extension MerkleTree: Hashable where T: Hashable {
33+
public func hash(into hasher: inout Hasher) {
34+
hasher.combine(value)
35+
}
36+
}
37+
38+
extension Digest {
39+
private var bytes: [UInt8] { Array(makeIterator()) }
40+
var hexStr: String {
41+
bytes.map { String(format: "%02X", $0) }.joined()
42+
}
43+
}
44+
45+
public extension Data {
46+
private var hashedHex: String { SHA256.hash(data: self).hexStr }
47+
var doubleHashedHex: String { SHA256.hash(data: Data(hashedHex.utf8)).hexStr }
48+
}
49+
50+
public struct MerkleNode: Hashable {
51+
public let hash: String
52+
53+
public init(blob: Data) { hash = blob.doubleHashedHex }
54+
public init(hash: String) { self.hash = hash }
55+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
public struct PathHash: Hashable {
2+
public enum Leaf { case left, right }
3+
public let hash: String
4+
public let leaf: Leaf
5+
6+
public init(_ hash: String, leaf: Leaf) {
7+
self.hash = hash
8+
self.leaf = leaf
9+
}
10+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
public class TwoWayBinaryTree<T> {
2+
public var value: T
3+
4+
weak var parent: TwoWayBinaryTree<T>?
5+
public var children: (left: TwoWayBinaryTree<T>?, right: TwoWayBinaryTree<T>?)
6+
7+
public init(_ value: T, left: TwoWayBinaryTree<T>? = nil, right: TwoWayBinaryTree<T>? = nil) {
8+
self.value = value
9+
children = (left, right)
10+
}
11+
12+
func add(left: TwoWayBinaryTree<T>? = nil, right: TwoWayBinaryTree<T>? = nil) {
13+
children = (left, right)
14+
left?.parent = self
15+
right?.parent = self
16+
}
17+
}

0 commit comments

Comments
 (0)