|
| 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 | +} |
0 commit comments