Skip to content

Commit

Permalink
state merkl and trie test vectors (#60)
Browse files Browse the repository at this point in the history
* wip

* fix
  • Loading branch information
qiweiii authored Aug 14, 2024
1 parent 186f1d0 commit c14af2c
Show file tree
Hide file tree
Showing 3 changed files with 114 additions and 1 deletion.
37 changes: 37 additions & 0 deletions JAMTests/Tests/JAMTests/TrieTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import Foundation
import Testing
import Utils

@testable import JAMTests

struct TrieElement: Codable {
let input: [String: String]
let output: String
}

typealias TrieTestCase = [TrieElement]

struct TrieTests {
static func loadTests() throws -> [TrieTestCase] {
let tests = try TestLoader.getTestFiles(path: "trie", extension: "json")
return try tests.map {
let data = try Data(contentsOf: URL(fileURLWithPath: $0.path))
let decoder = JSONDecoder()
return try decoder.decode(TrieTestCase.self, from: data)
}
}

@Test(arguments: try loadTests())
func trieTests(_ testcase: TrieTestCase) throws {
for element in testcase {
let kv = element.input.reduce(into: [Data32: Data]()) { result, entry in
let keyData = Data(fromHexString: entry.key)
let valueData = Data(fromHexString: entry.value)
result[Data32(keyData!)!] = valueData
}

let result = try stateMerklize(kv: kv)
#expect(result.data.toHexString() == element.output)
}
}
}
7 changes: 6 additions & 1 deletion Utils/Sources/Utils/Data+Utils.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ import Foundation
extension Data {
public init?(fromHexString hexString: String) {
guard !hexString.isEmpty else {
return nil
self.init()
return
}

var data = Data()
Expand All @@ -23,4 +24,8 @@ extension Data {

self.init(data)
}

public func toHexString() -> String {
map { String(format: "%02x", $0) }.joined()
}
}
71 changes: 71 additions & 0 deletions Utils/Sources/Utils/merklization.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import Foundation

public enum MerklizeError: Error {
case invalidIndex
}

/// State Merklization function from GP D.2
///
/// Input is serialized state defined in the GP D.1
public func stateMerklize(kv: [Data32: Data], i: Int = 0) throws -> Data32 {
func branch(l: Data32, r: Data32) throws -> Data64 {
var data = l.data + r.data
data[0] = l.data[0] & 0xFE
return Data64(data)!
}

func embeddedLeaf(key: Data32, value: Data, size: UInt8) -> Data64 {
var data = Data()
data.reserveCapacity(64)
data.append(0b01 | (size << 2))
data += key.data[..<31]
data += value
data.append(contentsOf: repeatElement(0, count: 32 - Int(size)))
return Data64(data)!
}

func regularLeaf(key: Data32, value: Data) throws -> Data64 {
var data = Data()
data.reserveCapacity(64)
data.append(0b11)
data += key.data[..<31]
data += try blake2b256(value).data
return Data64(data)!
}

func leaf(key: Data32, value: Data) throws -> Data64 {
if value.count <= 32 {
return embeddedLeaf(key: key, value: value, size: UInt8(value.count))
} else {
return try regularLeaf(key: key, value: value)
}
}

/// bit at i, returns true if it is 1
func bit(_ data: Data, _ i: Int) throws -> Bool {
guard let byte = data[safe: i / 8] else {
throw MerklizeError.invalidIndex
}
return (byte & (1 << (i % 8))) != 0
}

if kv.isEmpty {
return Data32()
}

if kv.count == 1 {
return try blake2b256(leaf(key: kv.first!.key, value: kv.first!.value).data)
}

var l: [Data32: Data] = [:]
var r: [Data32: Data] = [:]
for (k, v) in kv {
if try bit(k.data, i) {
r[k] = v
} else {
l[k] = v
}
}

return try blake2b256(branch(l: stateMerklize(kv: l, i: i + 1), r: stateMerklize(kv: r, i: i + 1)).data)
}

0 comments on commit c14af2c

Please sign in to comment.