From 5caff36536569f3ff1d7746e8a3f2069ee9a5fbb Mon Sep 17 00:00:00 2001 From: Bryan Chen Date: Tue, 27 Aug 2024 11:20:30 +1200 Subject: [PATCH 1/7] move files and merklize --- Utils/Sources/Utils/Either.swift | 22 ++-- .../Utils/{ => Extensions}/Array+Utils.swift | 0 .../{ => Extensions}/Collection+Utils.swift | 0 .../Utils/{ => Extensions}/Data+Utils.swift | 0 .../Utils/{ => Extensions}/Result+Utils.swift | 0 Utils/Sources/Utils/Hashing/Blake2b256.swift | 2 +- Utils/Sources/Utils/Hashing/Hasher.swift | 7 -- Utils/Sources/Utils/Hashing/Hashing.swift | 44 +++++++ Utils/Sources/Utils/Hashing/Keccak.swift | 2 +- .../Utils/Merklization/Merklization.swift | 118 ++++++++++++++++++ .../StateMerklization.swift} | 0 11 files changed, 175 insertions(+), 20 deletions(-) rename Utils/Sources/Utils/{ => Extensions}/Array+Utils.swift (100%) rename Utils/Sources/Utils/{ => Extensions}/Collection+Utils.swift (100%) rename Utils/Sources/Utils/{ => Extensions}/Data+Utils.swift (100%) rename Utils/Sources/Utils/{ => Extensions}/Result+Utils.swift (100%) delete mode 100644 Utils/Sources/Utils/Hashing/Hasher.swift create mode 100644 Utils/Sources/Utils/Hashing/Hashing.swift create mode 100644 Utils/Sources/Utils/Merklization/Merklization.swift rename Utils/Sources/Utils/{merklization.swift => Merklization/StateMerklization.swift} (100%) diff --git a/Utils/Sources/Utils/Either.swift b/Utils/Sources/Utils/Either.swift index c1b362ba..a89f1616 100644 --- a/Utils/Sources/Utils/Either.swift +++ b/Utils/Sources/Utils/Either.swift @@ -1,13 +1,13 @@ -public enum Either { - case left(A) - case right(B) +public enum Either { + case left(Left) + case right(Right) } -extension Either: Equatable where A: Equatable, B: Equatable {} +extension Either: Equatable where Left: Equatable, Right: Equatable {} -extension Either: Sendable where A: Sendable, B: Sendable {} +extension Either: Sendable where Left: Sendable, Right: Sendable {} -extension Either: CustomStringConvertible where A: CustomStringConvertible, B: CustomStringConvertible { +extension Either: CustomStringConvertible where Left: CustomStringConvertible, Right: CustomStringConvertible { public var description: String { switch self { case let .left(a): @@ -18,7 +18,7 @@ extension Either: CustomStringConvertible where A: CustomStringConvertible, B: C } } -extension Either: Codable where A: Codable, B: Codable { +extension Either: Codable where Left: Codable, Right: Codable { enum CodingKeys: String, CodingKey { case left case right @@ -52,10 +52,10 @@ extension Either: Codable where A: Codable, B: Codable { let variant = try container.decode(UInt8.self) switch variant { case 0: - let a = try container.decode(A.self) + let a = try container.decode(Left.self) self = .left(a) case 1: - let b = try container.decode(B.self) + let b = try container.decode(Right.self) self = .right(b) default: throw DecodingError.dataCorrupted( @@ -68,10 +68,10 @@ extension Either: Codable where A: Codable, B: Codable { } else { let container = try decoder.container(keyedBy: CodingKeys.self) if container.contains(.left) { - let a = try container.decode(A.self, forKey: .left) + let a = try container.decode(Left.self, forKey: .left) self = .left(a) } else if container.contains(.right) { - let b = try container.decode(B.self, forKey: .right) + let b = try container.decode(Right.self, forKey: .right) self = .right(b) } else { throw DecodingError.dataCorrupted( diff --git a/Utils/Sources/Utils/Array+Utils.swift b/Utils/Sources/Utils/Extensions/Array+Utils.swift similarity index 100% rename from Utils/Sources/Utils/Array+Utils.swift rename to Utils/Sources/Utils/Extensions/Array+Utils.swift diff --git a/Utils/Sources/Utils/Collection+Utils.swift b/Utils/Sources/Utils/Extensions/Collection+Utils.swift similarity index 100% rename from Utils/Sources/Utils/Collection+Utils.swift rename to Utils/Sources/Utils/Extensions/Collection+Utils.swift diff --git a/Utils/Sources/Utils/Data+Utils.swift b/Utils/Sources/Utils/Extensions/Data+Utils.swift similarity index 100% rename from Utils/Sources/Utils/Data+Utils.swift rename to Utils/Sources/Utils/Extensions/Data+Utils.swift diff --git a/Utils/Sources/Utils/Result+Utils.swift b/Utils/Sources/Utils/Extensions/Result+Utils.swift similarity index 100% rename from Utils/Sources/Utils/Result+Utils.swift rename to Utils/Sources/Utils/Extensions/Result+Utils.swift diff --git a/Utils/Sources/Utils/Hashing/Blake2b256.swift b/Utils/Sources/Utils/Hashing/Blake2b256.swift index fcf5cc45..dad617bc 100644 --- a/Utils/Sources/Utils/Hashing/Blake2b256.swift +++ b/Utils/Sources/Utils/Hashing/Blake2b256.swift @@ -1,7 +1,7 @@ import Blake2 import Foundation -public struct Blake2b256: ~Copyable, Hashing { +public struct Blake2b256: /* ~Copyable, */ Hashing { private var hasher: Blake2b public init() { diff --git a/Utils/Sources/Utils/Hashing/Hasher.swift b/Utils/Sources/Utils/Hashing/Hasher.swift deleted file mode 100644 index 455ef1a0..00000000 --- a/Utils/Sources/Utils/Hashing/Hasher.swift +++ /dev/null @@ -1,7 +0,0 @@ -import Blake2 - -public protocol Hashing: ~Copyable { - init() - mutating func update(_ data: some DataPtrRepresentable) - consuming func finalize() -> Data32 -} diff --git a/Utils/Sources/Utils/Hashing/Hashing.swift b/Utils/Sources/Utils/Hashing/Hashing.swift new file mode 100644 index 00000000..b75dd5f9 --- /dev/null +++ b/Utils/Sources/Utils/Hashing/Hashing.swift @@ -0,0 +1,44 @@ +import Blake2 +import Foundation + +// Waiting for NoncopyableGenerics to be available +public protocol Hashing /*: ~Copyable */ { + init() + mutating func update(_ data: some DataPtrRepresentable) + consuming func finalize() -> Data32 +} + +extension Hashing { + public static func hash(data: some DataPtrRepresentable) -> Data32 { + var hasher = Self() + hasher.update(data) + return hasher.finalize() + } +} + +extension FixedSizeData: DataPtrRepresentable { + public typealias Ptr = UnsafeRawBufferPointer + + public func withPtr( + cb: (UnsafeRawBufferPointer) throws -> R + ) rethrows -> R { + try data.withUnsafeBytes(cb) + } +} + +extension Either: DataPtrRepresentable, PtrRepresentable where Left: DataPtrRepresentable, Right: DataPtrRepresentable, + Left.Ptr == Right.Ptr +{ + public typealias Ptr = Left.Ptr + + public func withPtr( + cb: (Left.Ptr) throws -> R + ) rethrows -> R { + switch self { + case let .left(left): + try left.withPtr(cb: cb) + case let .right(right): + try right.withPtr(cb: cb) + } + } +} diff --git a/Utils/Sources/Utils/Hashing/Keccak.swift b/Utils/Sources/Utils/Hashing/Keccak.swift index a71862de..595f5cb7 100644 --- a/Utils/Sources/Utils/Hashing/Keccak.swift +++ b/Utils/Sources/Utils/Hashing/Keccak.swift @@ -2,7 +2,7 @@ import Blake2 import Foundation import sha3_iuf -public struct Keccak: ~Copyable, Hashing { +public struct Keccak: /* ~Copyable, */ Hashing { private var ctx: sha3_context = .init() public init() { diff --git a/Utils/Sources/Utils/Merklization/Merklization.swift b/Utils/Sources/Utils/Merklization/Merklization.swift new file mode 100644 index 00000000..2be4f495 --- /dev/null +++ b/Utils/Sources/Utils/Merklization/Merklization.swift @@ -0,0 +1,118 @@ +import Foundation + +// TODO: add tests +public enum Merklization { + // roundup of half + private static func half(_ i: Int) -> Int { + (i + 1) / 2 + } + + private static func binaryMerklizeHelper>(_ nodes: T, + hasher: Hashing.Type = Blake2b256 + .self) -> Either + where T.Index == Int + { + switch nodes.count { + case 0: + return .right(Data32()) + case 1: + return .left(nodes.first!) + default: + let midIndex = nodes.startIndex + half(nodes.count) + let l = nodes[nodes.startIndex ..< midIndex] + let r = nodes[midIndex ..< nodes.endIndex] + var hash = hasher.init() + hash.update("node") + hash.update(binaryMerklizeHelper(l)) + hash.update(binaryMerklizeHelper(r)) + return .right(hash.finalize()) + } + } + + // well-balanced binary Merkle function defined in GP E.1.1 + public static func binaryMerklize>(_ nodes: T, hasher: Hashing.Type = Blake2b256.self) -> Data32 + where T.Index == Int + { + switch binaryMerklizeHelper(nodes, hasher: hasher) { + case let .left(data): + hasher.hash(data: data) + case let .right(data): + data + } + } + + private static func traceImpl>(_ nodes: T, index: T.Index, + hasher: Hashing.Type, output: (Either) -> Void) + where T.Index == Int + { + if nodes.count == 0 { + return + } + + func selectPart(left: Bool, nodes: T, index: T.Index) -> T.SubSequence { + let h = half(nodes.count) + if (index < h) == left { + return nodes[nodes.startIndex ..< nodes.startIndex + h] + } else { + return nodes[nodes.startIndex + h ..< nodes.endIndex] + } + } + + func selectIndex(nodes: T, index: T.Index) -> T.Index { + let h = half(nodes.count) + if index < h { + return 0 + } + return h + } + + let l = binaryMerklizeHelper(selectPart(left: true, nodes: nodes, index: index), hasher: hasher) + output(l) + traceImpl( + selectPart(left: false, nodes: nodes, index: index), + index: index - selectIndex(nodes: nodes, index: index), + hasher: hasher, + output: output + ) + } + + public static func trace>(_ nodes: T, hasher: Hashing.Type = Blake2b256.self) -> [Either] + where T.Index == Int + { + var res: [Either] = [] + traceImpl(nodes, index: nodes.count, hasher: hasher) { res.append($0) } + return res + } + + // return type should be [Data32] but binaryMerklizeHelper requires [Data] + private static func constancyPreprocessor>(_ nodes: T, hasher: Hashing.Type = Blake2b256.self) -> [Data] { + let length = UInt32(nodes.count) + // find the next power of two using bitwise logic + let nextPowerOfTwo = 1 << (32 - length.leadingZeroBitCount) + let newLength = nextPowerOfTwo == length ? length : nextPowerOfTwo * 2 + var res: [Data32] = [] + res.reserveCapacity(newLength) + for node in nodes { + var hash = hahser.init() + hash.update("leaf") + hash.update(node) + res.append(hash.finalize().data) + } + // fill the rest with zeros + for _ in nodes.count ..< newLength { + res.append(Data32().data) + } + return res + } + + // constant-depth binary merkle function defined in GP E.1.2 + public static func constantDepthMerklize>(_ nodes: T, hasher: Hashing.Type = Blake2b256.self) -> Data32 + where T.Index == Int { + switch binaryMerklizeHelper(constancyPreprocessor(nodes, hasher: hasher)) { + case let .left(data): + Data32(data)! // TODO somehow improve the typing so force unwrap is not needed + case let .right(data): + data + } + } +} diff --git a/Utils/Sources/Utils/merklization.swift b/Utils/Sources/Utils/Merklization/StateMerklization.swift similarity index 100% rename from Utils/Sources/Utils/merklization.swift rename to Utils/Sources/Utils/Merklization/StateMerklization.swift From 25571032ea585828f26b70dec1b95d1c4970a401 Mon Sep 17 00:00:00 2001 From: Bryan Chen Date: Tue, 27 Aug 2024 11:28:15 +1200 Subject: [PATCH 2/7] fix --- .../Utils/Merklization/Merklization.swift | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/Utils/Sources/Utils/Merklization/Merklization.swift b/Utils/Sources/Utils/Merklization/Merklization.swift index 2be4f495..2767b71a 100644 --- a/Utils/Sources/Utils/Merklization/Merklization.swift +++ b/Utils/Sources/Utils/Merklization/Merklization.swift @@ -85,15 +85,17 @@ public enum Merklization { } // return type should be [Data32] but binaryMerklizeHelper requires [Data] - private static func constancyPreprocessor>(_ nodes: T, hasher: Hashing.Type = Blake2b256.self) -> [Data] { + private static func constancyPreprocessor(_ nodes: some RandomAccessCollection, + hasher: Hashing.Type = Blake2b256.self) -> [Data] + { let length = UInt32(nodes.count) // find the next power of two using bitwise logic - let nextPowerOfTwo = 1 << (32 - length.leadingZeroBitCount) - let newLength = nextPowerOfTwo == length ? length : nextPowerOfTwo * 2 - var res: [Data32] = [] + let nextPowerOfTwo = UInt32(1 << (32 - length.leadingZeroBitCount)) + let newLength = Int(nextPowerOfTwo == length ? length : nextPowerOfTwo * 2) + var res: [Data] = [] res.reserveCapacity(newLength) for node in nodes { - var hash = hahser.init() + var hash = hasher.init() hash.update("leaf") hash.update(node) res.append(hash.finalize().data) @@ -107,10 +109,11 @@ public enum Merklization { // constant-depth binary merkle function defined in GP E.1.2 public static func constantDepthMerklize>(_ nodes: T, hasher: Hashing.Type = Blake2b256.self) -> Data32 - where T.Index == Int { + where T.Index == Int + { switch binaryMerklizeHelper(constancyPreprocessor(nodes, hasher: hasher)) { case let .left(data): - Data32(data)! // TODO somehow improve the typing so force unwrap is not needed + Data32(data)! // TODO: somehow improve the typing so force unwrap is not needed case let .right(data): data } From 284e141bc36e552e427f607462b2a18422ea2d3a Mon Sep 17 00:00:00 2001 From: Bryan Chen Date: Tue, 27 Aug 2024 15:30:10 +1200 Subject: [PATCH 3/7] generateJustification --- .../Extensions/UnsignedInteger+Utils.swift | 10 +++ Utils/Sources/Utils/Hashing/Hashing.swift | 2 + Utils/Sources/Utils/MaybeEither.swift | 27 ++++++++ .../Utils/Merklization/Merklization.swift | 67 ++++++++++--------- .../UtilsTests/FixedWidthIntegerTests.swift | 30 +++++++++ 5 files changed, 105 insertions(+), 31 deletions(-) create mode 100644 Utils/Sources/Utils/Extensions/UnsignedInteger+Utils.swift create mode 100644 Utils/Sources/Utils/MaybeEither.swift create mode 100644 Utils/Tests/UtilsTests/FixedWidthIntegerTests.swift diff --git a/Utils/Sources/Utils/Extensions/UnsignedInteger+Utils.swift b/Utils/Sources/Utils/Extensions/UnsignedInteger+Utils.swift new file mode 100644 index 00000000..3747d686 --- /dev/null +++ b/Utils/Sources/Utils/Extensions/UnsignedInteger+Utils.swift @@ -0,0 +1,10 @@ +extension FixedWidthInteger { + /// Return the next power of two that is equal or greater than self. + /// Returns nil if self is 0 or the next power of two is greater than `Self.max`. + public var nextPowerOfTwo: Self? { + guard self > 0 else { return nil } + let leadingZeroBitCount = (self - 1).leadingZeroBitCount + guard leadingZeroBitCount > 0 else { return nil } + return 1 << (bitWidth - leadingZeroBitCount) + } +} diff --git a/Utils/Sources/Utils/Hashing/Hashing.swift b/Utils/Sources/Utils/Hashing/Hashing.swift index b75dd5f9..0d0bbeea 100644 --- a/Utils/Sources/Utils/Hashing/Hashing.swift +++ b/Utils/Sources/Utils/Hashing/Hashing.swift @@ -1,6 +1,8 @@ import Blake2 import Foundation +public typealias DataPtrRepresentable = Blake2.DataPtrRepresentable + // Waiting for NoncopyableGenerics to be available public protocol Hashing /*: ~Copyable */ { init() diff --git a/Utils/Sources/Utils/MaybeEither.swift b/Utils/Sources/Utils/MaybeEither.swift new file mode 100644 index 00000000..3828182e --- /dev/null +++ b/Utils/Sources/Utils/MaybeEither.swift @@ -0,0 +1,27 @@ +public struct MaybeEither { + public var value: Either + + public init(_ value: Either) { + self.value = value + } + + public init(left value: Left) { + self.value = .left(value) + } + + public init(right value: Right) { + self.value = .right(value) + } +} + +extension MaybeEither where Left == Right { + typealias Unwrapped = Left + public var unwrapped: Left { + switch value { + case let .left(left): + left + case let .right(right): + right + } + } +} diff --git a/Utils/Sources/Utils/Merklization/Merklization.swift b/Utils/Sources/Utils/Merklization/Merklization.swift index 2767b71a..e756a51e 100644 --- a/Utils/Sources/Utils/Merklization/Merklization.swift +++ b/Utils/Sources/Utils/Merklization/Merklization.swift @@ -7,25 +7,25 @@ public enum Merklization { (i + 1) / 2 } - private static func binaryMerklizeHelper>(_ nodes: T, - hasher: Hashing.Type = Blake2b256 - .self) -> Either - where T.Index == Int + private static func binaryMerklizeHelper(_ nodes: T, + hasher: Hashing.Type = Blake2b256 + .self) -> MaybeEither + where T: RandomAccessCollection, T.Index == Int, U: DataPtrRepresentable { switch nodes.count { case 0: - return .right(Data32()) + return .init(right: Data32()) case 1: - return .left(nodes.first!) + return .init(left: nodes.first!) default: let midIndex = nodes.startIndex + half(nodes.count) let l = nodes[nodes.startIndex ..< midIndex] let r = nodes[midIndex ..< nodes.endIndex] var hash = hasher.init() hash.update("node") - hash.update(binaryMerklizeHelper(l)) - hash.update(binaryMerklizeHelper(r)) - return .right(hash.finalize()) + hash.update(binaryMerklizeHelper(l).value) + hash.update(binaryMerklizeHelper(r).value) + return .init(right: hash.finalize()) } } @@ -33,7 +33,7 @@ public enum Merklization { public static func binaryMerklize>(_ nodes: T, hasher: Hashing.Type = Blake2b256.self) -> Data32 where T.Index == Int { - switch binaryMerklizeHelper(nodes, hasher: hasher) { + switch binaryMerklizeHelper(nodes, hasher: hasher).value { case let .left(data): hasher.hash(data: data) case let .right(data): @@ -41,9 +41,9 @@ public enum Merklization { } } - private static func traceImpl>(_ nodes: T, index: T.Index, - hasher: Hashing.Type, output: (Either) -> Void) - where T.Index == Int + private static func traceImpl(_ nodes: T, index: T.Index, + hasher: Hashing.Type, output: (MaybeEither) -> Void) + where T: RandomAccessCollection, T.Index == Int, U: DataPtrRepresentable { if nodes.count == 0 { return @@ -76,33 +76,31 @@ public enum Merklization { ) } - public static func trace>(_ nodes: T, hasher: Hashing.Type = Blake2b256.self) -> [Either] - where T.Index == Int + public static func trace(_ nodes: T, index: T.Index, + hasher: Hashing.Type = Blake2b256.self) -> [Either] + where T: RandomAccessCollection, T.Index == Int, U: DataPtrRepresentable { - var res: [Either] = [] - traceImpl(nodes, index: nodes.count, hasher: hasher) { res.append($0) } + var res: [Either] = [] + traceImpl(nodes, index: index, hasher: hasher) { res.append($0.value) } return res } - // return type should be [Data32] but binaryMerklizeHelper requires [Data] private static func constancyPreprocessor(_ nodes: some RandomAccessCollection, - hasher: Hashing.Type = Blake2b256.self) -> [Data] + hasher: Hashing.Type = Blake2b256.self) -> [Data32] { let length = UInt32(nodes.count) - // find the next power of two using bitwise logic - let nextPowerOfTwo = UInt32(1 << (32 - length.leadingZeroBitCount)) - let newLength = Int(nextPowerOfTwo == length ? length : nextPowerOfTwo * 2) - var res: [Data] = [] + let newLength = Int(length.nextPowerOfTwo ?? 0) + var res: [Data32] = [] res.reserveCapacity(newLength) for node in nodes { var hash = hasher.init() hash.update("leaf") hash.update(node) - res.append(hash.finalize().data) + res.append(hash.finalize()) } // fill the rest with zeros for _ in nodes.count ..< newLength { - res.append(Data32().data) + res.append(Data32()) } return res } @@ -111,11 +109,18 @@ public enum Merklization { public static func constantDepthMerklize>(_ nodes: T, hasher: Hashing.Type = Blake2b256.self) -> Data32 where T.Index == Int { - switch binaryMerklizeHelper(constancyPreprocessor(nodes, hasher: hasher)) { - case let .left(data): - Data32(data)! // TODO: somehow improve the typing so force unwrap is not needed - case let .right(data): - data - } + binaryMerklizeHelper(constancyPreprocessor(nodes, hasher: hasher)).unwrapped + } + + public static func generateJustification( + _ nodes: T, + index: T.Index, + hasher: Hashing.Type = Blake2b256.self + ) -> [Data32] + where T: RandomAccessCollection, T.Index == Int + { + var res: [Data32] = [] + traceImpl(constancyPreprocessor(nodes, hasher: hasher), index: index, hasher: hasher) { res.append($0.unwrapped) } + return res } } diff --git a/Utils/Tests/UtilsTests/FixedWidthIntegerTests.swift b/Utils/Tests/UtilsTests/FixedWidthIntegerTests.swift new file mode 100644 index 00000000..7f2b9568 --- /dev/null +++ b/Utils/Tests/UtilsTests/FixedWidthIntegerTests.swift @@ -0,0 +1,30 @@ +import Testing + +@testable import Utils + +struct FixedWidthIntegerTests { + @Test func nextPowerOfTwo() throws { + #expect(UInt8(0).nextPowerOfTwo == nil) + #expect(UInt8(1).nextPowerOfTwo == UInt8(1)) + #expect(UInt8(2).nextPowerOfTwo == UInt8(2)) + #expect(UInt8(3).nextPowerOfTwo == UInt8(4)) + #expect(UInt8(4).nextPowerOfTwo == UInt8(4)) + #expect(UInt8(5).nextPowerOfTwo == UInt8(8)) + #expect(UInt8(8).nextPowerOfTwo == UInt8(8)) + #expect(UInt8(127).nextPowerOfTwo == UInt8(128)) + #expect(UInt8(128).nextPowerOfTwo == UInt8(128)) + #expect(UInt8(129).nextPowerOfTwo == nil) + #expect(UInt8(255).nextPowerOfTwo == nil) + + #expect(UInt32(0).nextPowerOfTwo == nil) + #expect(UInt32(1).nextPowerOfTwo == UInt32(1)) + #expect(UInt32(2).nextPowerOfTwo == UInt32(2)) + #expect(UInt32(511).nextPowerOfTwo == UInt32(512)) + #expect(UInt32(512).nextPowerOfTwo == UInt32(512)) + #expect(UInt32(513).nextPowerOfTwo == UInt32(1024)) + #expect(UInt32(0x7FFF_FFFF).nextPowerOfTwo == UInt32(0x8000_0000)) + #expect(UInt32(0xF000_0000).nextPowerOfTwo == nil) + #expect(UInt32(0xF000_0001).nextPowerOfTwo == nil) + #expect(UInt32.max.nextPowerOfTwo == nil) + } +} From 241e803c6c3bc14c4da25b0bbd0d291ad5464299 Mon Sep 17 00:00:00 2001 From: Bryan Chen Date: Tue, 27 Aug 2024 15:31:44 +1200 Subject: [PATCH 4/7] improve format --- .../Utils/Merklization/Merklization.swift | 29 ++++++++++++------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/Utils/Sources/Utils/Merklization/Merklization.swift b/Utils/Sources/Utils/Merklization/Merklization.swift index e756a51e..2f507395 100644 --- a/Utils/Sources/Utils/Merklization/Merklization.swift +++ b/Utils/Sources/Utils/Merklization/Merklization.swift @@ -7,9 +7,10 @@ public enum Merklization { (i + 1) / 2 } - private static func binaryMerklizeHelper(_ nodes: T, - hasher: Hashing.Type = Blake2b256 - .self) -> MaybeEither + private static func binaryMerklizeHelper( + _ nodes: T, + hasher: Hashing.Type = Blake2b256.self + ) -> MaybeEither where T: RandomAccessCollection, T.Index == Int, U: DataPtrRepresentable { switch nodes.count { @@ -41,8 +42,12 @@ public enum Merklization { } } - private static func traceImpl(_ nodes: T, index: T.Index, - hasher: Hashing.Type, output: (MaybeEither) -> Void) + private static func traceImpl( + _ nodes: T, + index: T.Index, + hasher: Hashing.Type, + output: (MaybeEither) -> Void + ) where T: RandomAccessCollection, T.Index == Int, U: DataPtrRepresentable { if nodes.count == 0 { @@ -76,8 +81,11 @@ public enum Merklization { ) } - public static func trace(_ nodes: T, index: T.Index, - hasher: Hashing.Type = Blake2b256.self) -> [Either] + public static func trace( + _ nodes: T, + index: T.Index, + hasher: Hashing.Type = Blake2b256.self + ) -> [Either] where T: RandomAccessCollection, T.Index == Int, U: DataPtrRepresentable { var res: [Either] = [] @@ -85,9 +93,10 @@ public enum Merklization { return res } - private static func constancyPreprocessor(_ nodes: some RandomAccessCollection, - hasher: Hashing.Type = Blake2b256.self) -> [Data32] - { + private static func constancyPreprocessor( + _ nodes: some RandomAccessCollection, + hasher: Hashing.Type = Blake2b256.self + ) -> [Data32] { let length = UInt32(nodes.count) let newLength = Int(length.nextPowerOfTwo ?? 0) var res: [Data32] = [] From fd7dacb007260891a38df5505010bef5c4f2b519 Mon Sep 17 00:00:00 2001 From: Bryan Chen Date: Tue, 27 Aug 2024 16:02:26 +1200 Subject: [PATCH 5/7] implements MMR --- Blockchain/Sources/Blockchain/Runtime.swift | 7 ++++- .../Blockchain/Types/RecentHistory.swift | 6 ++--- Utils/Sources/Utils/Merklization/MMR.swift | 26 +++++++++++++++++++ 3 files changed, 35 insertions(+), 4 deletions(-) create mode 100644 Utils/Sources/Utils/Merklization/MMR.swift diff --git a/Blockchain/Sources/Blockchain/Runtime.swift b/Blockchain/Sources/Blockchain/Runtime.swift index 091395ae..30779fb4 100644 --- a/Blockchain/Sources/Blockchain/Runtime.swift +++ b/Blockchain/Sources/Blockchain/Runtime.swift @@ -115,9 +115,14 @@ public final class Runtime { let workReportHashes = block.extrinsic.reports.guarantees.map(\.workReport.packageSpecification.workPackageHash) + let accumulationResult = Data32() // TODO: calculate accumulation result + + var mmr = history.items.last?.mmr ?? .init([]) + mmr.append(accumulationResult) + let newItem = try RecentHistory.HistoryItem( headerHash: block.header.parentHash, - mmrRoots: [], // TODO: update MMR roots + mmr: mmr, stateRoot: Data32(), // empty and will be updated upon next block workReportHashes: ConfigLimitedSizeArray(config: config, array: workReportHashes) ) diff --git a/Blockchain/Sources/Blockchain/Types/RecentHistory.swift b/Blockchain/Sources/Blockchain/Types/RecentHistory.swift index ae0b4843..fc76ac0f 100644 --- a/Blockchain/Sources/Blockchain/Types/RecentHistory.swift +++ b/Blockchain/Sources/Blockchain/Types/RecentHistory.swift @@ -7,7 +7,7 @@ public struct RecentHistory: Sendable, Equatable, Codable { public var headerHash: Data32 // b: accumulation-result mmr - public var mmrRoots: [Data32] + public var mmr: MMR // s public var stateRoot: Data32 @@ -17,12 +17,12 @@ public struct RecentHistory: Sendable, Equatable, Codable { public init( headerHash: Data32, - mmrRoots: [Data32], + mmr: MMR, stateRoot: Data32, workReportHashes: ConfigLimitedSizeArray ) { self.headerHash = headerHash - self.mmrRoots = mmrRoots + self.mmr = mmr self.stateRoot = stateRoot self.workReportHashes = workReportHashes } diff --git a/Utils/Sources/Utils/Merklization/MMR.swift b/Utils/Sources/Utils/Merklization/MMR.swift new file mode 100644 index 00000000..3ca17130 --- /dev/null +++ b/Utils/Sources/Utils/Merklization/MMR.swift @@ -0,0 +1,26 @@ +// Merkle Mountain Range +public struct MMR: Sendable, Equatable, Codable { + public var peaks: [Data32?] + + public init(_ peaks: [Data32?]) { + self.peaks = peaks + } + + public mutating func append(_ data: Data32, hasher: Hashing.Type = Blake2b256.self) { + append(data, at: 0, hasher: hasher) + } + + private mutating func append(_ data: Data32, at index: Int, hasher: Hashing.Type = Blake2b256.self) { + if index >= peaks.count { + peaks.append(data) + } else if let current = peaks[index] { + var hash = hasher.init() + hash.update(current) + hash.update(data) + peaks[index] = nil + append(hash.finalize(), at: index + 1, hasher: hasher) + } else { + peaks[index] = data + } + } +} From 2f821afaabf8251d91f9cc2e1b6bd6272dfce05a Mon Sep 17 00:00:00 2001 From: Bryan Chen Date: Tue, 27 Aug 2024 16:05:48 +1200 Subject: [PATCH 6/7] need tests --- Utils/Sources/Utils/Merklization/MMR.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Utils/Sources/Utils/Merklization/MMR.swift b/Utils/Sources/Utils/Merklization/MMR.swift index 3ca17130..109aa586 100644 --- a/Utils/Sources/Utils/Merklization/MMR.swift +++ b/Utils/Sources/Utils/Merklization/MMR.swift @@ -1,3 +1,4 @@ +// TODO: add tests // Merkle Mountain Range public struct MMR: Sendable, Equatable, Codable { public var peaks: [Data32?] From 26c20d715fe1d3d4c8d8d652683a3c8e9e954847 Mon Sep 17 00:00:00 2001 From: Bryan Chen Date: Tue, 27 Aug 2024 16:18:44 +1200 Subject: [PATCH 7/7] use keccak for mmr --- Blockchain/Sources/Blockchain/Runtime.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Blockchain/Sources/Blockchain/Runtime.swift b/Blockchain/Sources/Blockchain/Runtime.swift index 30779fb4..175080c9 100644 --- a/Blockchain/Sources/Blockchain/Runtime.swift +++ b/Blockchain/Sources/Blockchain/Runtime.swift @@ -118,7 +118,7 @@ public final class Runtime { let accumulationResult = Data32() // TODO: calculate accumulation result var mmr = history.items.last?.mmr ?? .init([]) - mmr.append(accumulationResult) + mmr.append(accumulationResult, hasher: Keccak.self) let newItem = try RecentHistory.HistoryItem( headerHash: block.header.parentHash,