Skip to content
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
27 changes: 8 additions & 19 deletions Blockchain/Sources/Blockchain/Validator/DevKeyStore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,31 +32,20 @@ public final class DevKeyStore: KeyStore {
}

public static func getDevKey(seed: UInt32) throws -> KeySet {
var seedData = Data(repeating: 0, count: 32)
let seedByte = seed.encode()
for i in 0 ..< 8 {
seedData[i * 4 ..< (i + 1) * 4] = seedByte
}
let seedData32 = Data32(seedData)!
let bandersnatch = try Bandersnatch.SecretKey(from: seedData32)
let ed25519 = try Ed25519.SecretKey(from: seedData32)
let bls = try BLS.SecretKey(from: seedData32)
let trivialSeed = JIP5SeedDerive.trivialSeed(seed)
let derivedSeeds = JIP5SeedDerive.deriveKeySeeds(from: trivialSeed)

let bandersnatch = try Bandersnatch.SecretKey(from: derivedSeeds.bandersnatch)
let ed25519 = try Ed25519.SecretKey(from: derivedSeeds.ed25519)
let bls = try BLS.SecretKey(from: trivialSeed)
return KeySet(bandersnatch: bandersnatch.publicKey, ed25519: ed25519.publicKey, bls: bls.publicKey)
}
}

extension KeyStore {
@discardableResult
public func addDevKeys(seed: UInt32) async throws -> KeySet {
var seedData = Data(repeating: 0, count: 32)
let seedByte = seed.encode()
for i in 0 ..< 8 {
seedData[i * 4 ..< (i + 1) * 4] = seedByte
}
let seedData32 = Data32(seedData)!
let bandersnatch = try await add(Bandersnatch.self, seed: seedData32)
let ed25519 = try await add(Ed25519.self, seed: seedData32)
let bls = try await add(BLS.self, seed: seedData32)
return KeySet(bandersnatch: bandersnatch.publicKey, ed25519: ed25519.publicKey, bls: bls.publicKey)
let trivialSeed = JIP5SeedDerive.trivialSeed(seed)
return try await generateKeys(from: trivialSeed)
}
}
11 changes: 7 additions & 4 deletions Blockchain/Sources/Blockchain/Validator/KeySet.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,13 @@ public struct KeySet: Codable, Sendable {
}

extension KeyStore {
public func generateKeys() async throws -> KeySet {
let bandersnatch = try await generate(Bandersnatch.self)
let ed25519 = try await generate(Ed25519.self)
let bls = try await generate(BLS.self)
public func generateKeys(from seed: Data32) async throws -> KeySet {
let derivedSeeds = JIP5SeedDerive.deriveKeySeeds(from: seed)

let bandersnatch = try await add(Bandersnatch.self, seed: derivedSeeds.bandersnatch)
let ed25519 = try await add(Ed25519.self, seed: derivedSeeds.ed25519)
let bls = try await add(BLS.self, seed: seed)

return KeySet(bandersnatch: bandersnatch.publicKey, ed25519: ed25519.publicKey, bls: bls.publicKey)
}

Expand Down
38 changes: 38 additions & 0 deletions Utils/Sources/Utils/Crypto/JIP5SeedDerive.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import Foundation

/// This module implements the standard method for deriving validator secret key seeds
public enum JIP5SeedDerive {
/// Derives Ed25519 and Bandersnatch secret key seeds from a master seed
///
/// - Parameter seed: The 32-byte master seed
/// - Returns: A tuple containing (ed25519SecretSeed, bandersnatchSecretSeed)
public static func deriveKeySeeds(from seed: Data32) -> (ed25519: Data32, bandersnatch: Data32) {
let ed25519Prefix = "jam_val_key_ed25519"
let bandersnatchPrefix = "jam_val_key_bandersnatch"

let ed25519Input = Data(ed25519Prefix.utf8) + seed.data
let ed25519SecretSeed = ed25519Input.blake2b256hash()

let bandersnatchInput = Data(bandersnatchPrefix.utf8) + seed.data
let bandersnatchSecretSeed = bandersnatchInput.blake2b256hash()

return (ed25519: ed25519SecretSeed, bandersnatch: bandersnatchSecretSeed)
}

/// Creates a trivial seed from a 32-bit unsigned integer for testing purposes
///
/// Implements: trivial_seed(i) = repeat_8_times(encode_as_32bit_le(i))
///
/// - Parameter i: The 32-bit unsigned integer
/// - Returns: A 32-byte seed with the integer repeated 8 times in little-endian format
public static func trivialSeed(_ i: UInt32) -> Data32 {
var seedData = Data(capacity: 32)
let littleEndianBytes = i.littleEndian.encode()

for _ in 0 ..< 8 {
seedData.append(contentsOf: littleEndianBytes)
}

return Data32(seedData)!
}
}
81 changes: 81 additions & 0 deletions Utils/Tests/UtilsTests/Crypto/JIP5SeedDeriveTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import Foundation
import Testing
@testable import Utils

struct Vector {
let index: UInt32
let seedHex: String
let ed25519SeedHex: String
let ed25519PubHex: String
let bandersnatchSeedHex: String
let bandersnatchPubHex: String
}

// test vectors from JIP-5 spec
let vectors: [Vector] = [
Vector(
index: 0,
seedHex: "0000000000000000000000000000000000000000000000000000000000000000",
ed25519SeedHex: "996542becdf1e78278dc795679c825faca2e9ed2bf101bf3c4a236d3ed79cf59",
ed25519PubHex: "4418fb8c85bb3985394a8c2756d3643457ce614546202a2f50b093d762499ace",
bandersnatchSeedHex: "007596986419e027e65499cc87027a236bf4a78b5e8bd7f675759d73e7a9c799",
bandersnatchPubHex: "ff71c6c03ff88adb5ed52c9681de1629a54e702fc14729f6b50d2f0a76f185b3"
),
Vector(
index: 1,
seedHex: "0100000001000000010000000100000001000000010000000100000001000000",
ed25519SeedHex: "b81e308145d97464d2bc92d35d227a9e62241a16451af6da5053e309be4f91d7",
ed25519PubHex: "ad93247bd01307550ec7acd757ce6fb805fcf73db364063265b30a949e90d933",
bandersnatchSeedHex: "12ca375c9242101c99ad5fafe8997411f112ae10e0e5b7c4589e107c433700ac",
bandersnatchPubHex: "dee6d555b82024f1ccf8a1e37e60fa60fd40b1958c4bb3006af78647950e1b91"
),
Vector(
index: 2,
seedHex: "0200000002000000020000000200000002000000020000000200000002000000",
ed25519SeedHex: "0093c8c10a88ebbc99b35b72897a26d259313ee9bad97436a437d2e43aaafa0f",
ed25519PubHex: "cab2b9ff25c2410fbe9b8a717abb298c716a03983c98ceb4def2087500b8e341",
bandersnatchSeedHex: "3d71dc0ffd02d90524fda3e4a220e7ec514a258c59457d3077ce4d4f003fd98a",
bandersnatchPubHex: "9326edb21e5541717fde24ec085000b28709847b8aab1ac51f84e94b37ca1b66"
),
Vector(
index: 3,
seedHex: "0300000003000000030000000300000003000000030000000300000003000000",
ed25519SeedHex: "69b3a7031787e12bfbdcac1b7a737b3e5a9f9450c37e215f6d3b57730e21001a",
ed25519PubHex: "f30aa5444688b3cab47697b37d5cac5707bb3289e986b19b17db437206931a8d",
bandersnatchSeedHex: "107a9148b39a1099eeaee13ac0e3c6b9c256258b51c967747af0f8749398a276",
bandersnatchPubHex: "0746846d17469fb2f95ef365efcab9f4e22fa1feb53111c995376be8019981cc"
),
Vector(
index: 4,
seedHex: "0400000004000000040000000400000004000000040000000400000004000000",
ed25519SeedHex: "b4de9ebf8db5428930baa5a98d26679ab2a03eae7c791d582e6b75b7f018d0d4",
ed25519PubHex: "8b8c5d436f92ecf605421e873a99ec528761eb52a88a2f9a057b3b3003e6f32a",
bandersnatchSeedHex: "0bb36f5ba8e3ba602781bb714e67182410440ce18aa800c4cb4dd22525b70409",
bandersnatchPubHex: "151e5c8fe2b9d8a606966a79edd2f9e5db47e83947ce368ccba53bf6ba20a40b"
),
Vector(
index: 5,
seedHex: "0500000005000000050000000500000005000000050000000500000005000000",
ed25519SeedHex: "4a6482f8f479e3ba2b845f8cef284f4b3208ba3241ed82caa1b5ce9fc6281730",
ed25519PubHex: "ab0084d01534b31c1dd87c81645fd762482a90027754041ca1b56133d0466c06",
bandersnatchSeedHex: "75e73b8364bf4753c5802021c6aa6548cddb63fe668e3cacf7b48cdb6824bb09",
bandersnatchPubHex: "2105650944fcd101621fd5bb3124c9fd191d114b7ad936c1d79d734f9f21392e"
),
]

struct JIP5SeedDeriveTests {
@Test(arguments: vectors)
func testJIP5SeedDerivationAndPublicKeys(vector: Vector) throws {
let seed = JIP5SeedDerive.trivialSeed(vector.index)
#expect(seed.toHexString() == vector.seedHex)

let derived = JIP5SeedDerive.deriveKeySeeds(from: seed)
#expect(derived.ed25519.toHexString() == vector.ed25519SeedHex)
#expect(derived.bandersnatch.toHexString() == vector.bandersnatchSeedHex)

let ed25519Pub = try Ed25519.SecretKey(from: derived.ed25519).publicKey
let bandersnatchPub = try Bandersnatch.SecretKey(from: derived.bandersnatch).publicKey
#expect(ed25519Pub.data.toHexString() == vector.ed25519PubHex)
#expect(bandersnatchPub.data.toHexString() == vector.bandersnatchPubHex)
}
}