Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merkle functions #81

Merged
merged 7 commits into from
Aug 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion Blockchain/Sources/Blockchain/Runtime.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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, hasher: Keccak.self)

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)
)
Expand Down
6 changes: 3 additions & 3 deletions Blockchain/Sources/Blockchain/Types/RecentHistory.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -17,12 +17,12 @@ public struct RecentHistory: Sendable, Equatable, Codable {

public init(
headerHash: Data32,
mmrRoots: [Data32],
mmr: MMR,
stateRoot: Data32,
workReportHashes: ConfigLimitedSizeArray<Data32, ProtocolConfig.Int0, ProtocolConfig.TotalNumberOfCores>
) {
self.headerHash = headerHash
self.mmrRoots = mmrRoots
self.mmr = mmr
self.stateRoot = stateRoot
self.workReportHashes = workReportHashes
}
Expand Down
22 changes: 11 additions & 11 deletions Utils/Sources/Utils/Either.swift
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
public enum Either<A, B> {
case left(A)
case right(B)
public enum Either<Left, Right> {
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):
Expand All @@ -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
Expand Down Expand Up @@ -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(
Expand All @@ -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(
Expand Down
10 changes: 10 additions & 0 deletions Utils/Sources/Utils/Extensions/UnsignedInteger+Utils.swift
Original file line number Diff line number Diff line change
@@ -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)
}
}
2 changes: 1 addition & 1 deletion Utils/Sources/Utils/Hashing/Blake2b256.swift
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import Blake2
import Foundation

public struct Blake2b256: ~Copyable, Hashing {
public struct Blake2b256: /* ~Copyable, */ Hashing {
private var hasher: Blake2b

public init() {
Expand Down
7 changes: 0 additions & 7 deletions Utils/Sources/Utils/Hashing/Hasher.swift

This file was deleted.

46 changes: 46 additions & 0 deletions Utils/Sources/Utils/Hashing/Hashing.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import Blake2
import Foundation

public typealias DataPtrRepresentable = Blake2.DataPtrRepresentable

// 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<R>(
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<R>(
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)
}
}
}
2 changes: 1 addition & 1 deletion Utils/Sources/Utils/Hashing/Keccak.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down
27 changes: 27 additions & 0 deletions Utils/Sources/Utils/MaybeEither.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
public struct MaybeEither<Left, Right> {
public var value: Either<Left, Right>

public init(_ value: Either<Left, Right>) {
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
}
}
}
27 changes: 27 additions & 0 deletions Utils/Sources/Utils/Merklization/MMR.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// TODO: add tests
// 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
}
}
}
135 changes: 135 additions & 0 deletions Utils/Sources/Utils/Merklization/Merklization.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
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<T, U>(
_ nodes: T,
hasher: Hashing.Type = Blake2b256.self
) -> MaybeEither<U, Data32>
where T: RandomAccessCollection<U>, T.Index == Int, U: DataPtrRepresentable
{
switch nodes.count {
case 0:
return .init(right: Data32())
case 1:
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).value)
hash.update(binaryMerklizeHelper(r).value)
return .init(right: hash.finalize())
}
}

// well-balanced binary Merkle function defined in GP E.1.1
public static func binaryMerklize<T: RandomAccessCollection<Data>>(_ nodes: T, hasher: Hashing.Type = Blake2b256.self) -> Data32
where T.Index == Int
{
switch binaryMerklizeHelper(nodes, hasher: hasher).value {
case let .left(data):
hasher.hash(data: data)
case let .right(data):
data
}
}

private static func traceImpl<T, U>(
_ nodes: T,
index: T.Index,
hasher: Hashing.Type,
output: (MaybeEither<U, Data32>) -> Void
)
where T: RandomAccessCollection<U>, T.Index == Int, U: DataPtrRepresentable
{
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<T, U>(
_ nodes: T,
index: T.Index,
hasher: Hashing.Type = Blake2b256.self
) -> [Either<U, Data32>]
where T: RandomAccessCollection<U>, T.Index == Int, U: DataPtrRepresentable
{
var res: [Either<U, Data32>] = []
traceImpl(nodes, index: index, hasher: hasher) { res.append($0.value) }
return res
}

private static func constancyPreprocessor(
_ nodes: some RandomAccessCollection<Data>,
hasher: Hashing.Type = Blake2b256.self
) -> [Data32] {
let length = UInt32(nodes.count)
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())
}
// fill the rest with zeros
for _ in nodes.count ..< newLength {
res.append(Data32())
}
return res
}

// constant-depth binary merkle function defined in GP E.1.2
public static func constantDepthMerklize<T: RandomAccessCollection<Data>>(_ nodes: T, hasher: Hashing.Type = Blake2b256.self) -> Data32
where T.Index == Int
{
binaryMerklizeHelper(constancyPreprocessor(nodes, hasher: hasher)).unwrapped
}

public static func generateJustification<T>(
_ nodes: T,
index: T.Index,
hasher: Hashing.Type = Blake2b256.self
) -> [Data32]
where T: RandomAccessCollection<Data>, T.Index == Int
{
var res: [Data32] = []
traceImpl(constancyPreprocessor(nodes, hasher: hasher), index: index, hasher: hasher) { res.append($0.unwrapped) }
return res
}
}
Loading
Loading