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

Safrole finish #44

Merged
merged 18 commits into from
Aug 2, 2024
32 changes: 26 additions & 6 deletions Blockchain/Sources/Blockchain/Safrole.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ public enum SafroleError: Error {
case extrinsicsNotSorted
case extrinsicsTooLow
case extrinsicsNotUnique
case extrinsicsTooManyEntry
case hashingError
case bandersnatchError(BandersnatchError)
case decodingError
case unspecified
}
Expand Down Expand Up @@ -231,12 +233,15 @@ extension Safrole {
}

do {
let verifier = try Verifier(ring: nextValidators.map(\.bandersnatch))
let newVerifier = try Verifier(ring: validatorQueue.map(\.bandersnatch))

let (newNextValidators, newCurrentValidators, newPreviousValidators, newTicketsVerifier) = isEpochChange
? (
validatorQueue, // TODO: Φ filter out the one in the punishment set
nextValidators,
currentValidators,
ticketsVerifier // TODO: calculate the new ring root from the new validators
newVerifier.ringRoot
)
: (nextValidators, currentValidators, previousValidators, ticketsVerifier)

Expand All @@ -255,20 +260,27 @@ extension Safrole {
BandersnatchPublicKey,
ProtocolConfig.EpochLength
>
> = if newEpoch == currentEpoch + 1, newPhase >= ticketSubmissionEndSlot, ticketsAccumulator.count == config.value.epochLength {
> = if newEpoch == currentEpoch + 1,
currentPhase >= ticketSubmissionEndSlot,
ticketsAccumulator.count == config.value.epochLength
{
.left(ConfigFixedSizeArray(config: config, array: outsideInReorder(ticketsAccumulator.array)))
} else if newEpoch == currentEpoch {
ticketsOrKeys
} else {
try .right(ConfigFixedSizeArray(
config: config,
array: pickFallbackValidators(entropy: entropy, validators: newCurrentValidators, count: config.value.epochLength)
array: pickFallbackValidators(
entropy: newEntropyPool.2,
validators: newCurrentValidators,
count: config.value.epochLength
)
))
}

let epochMark = isEpochChange ? EpochMarker(
entropy: entropy,
validators: ConfigFixedSizeArray(config: config, array: newCurrentValidators.map(\.bandersnatch))
entropy: newEntropyPool.1,
validators: ConfigFixedSizeArray(config: config, array: newNextValidators.map(\.bandersnatch))
) : nil

let ticketsMark: ConfigFixedSizeArray<Ticket, ProtocolConfig.EpochLength>? =
Expand All @@ -283,11 +295,17 @@ extension Safrole {
nil
}

let newTickets = extrinsics.getTickets()
let newTickets = try extrinsics.getTickets(verifier: verifier, entropy: newEntropyPool.2)
guard newTickets.isSorted() else {
return .failure(.extrinsicsNotSorted)
}

for ticket in newTickets {
guard ticket.attempt < config.value.ticketEntriesPerValidator else {
return .failure(.extrinsicsTooManyEntry)
}
}

var newTicketsAccumulatorArr = if isEpochChange {
[Ticket]()
} else {
Expand Down Expand Up @@ -331,6 +349,8 @@ extension Safrole {
return .success((state: postState, epochMark: epochMark, ticketsMark: ticketsMark))
} catch let e as SafroleError {
return .failure(e)
} catch let e as BandersnatchError {
return .failure(.bandersnatchError(e))
} catch Blake2Error.hashingError {
return .failure(.hashingError)
} catch is DecodingError {
Expand Down
18 changes: 10 additions & 8 deletions Blockchain/Sources/Blockchain/Types/ExtrinsicTickets.swift
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import Foundation
import ScaleCodec
import Utils

public struct ExtrinsicTickets: Sendable, Equatable {
public struct TicketItem: Sendable, Equatable {
public var attempt: TicketIndex
public var signature: BandersnatchRintVRFProof
public var signature: BandersnatchRingVRFProof

public init(
attempt: TicketIndex,
signature: BandersnatchRintVRFProof
signature: BandersnatchRingVRFProof
) {
self.attempt = attempt
self.signature = signature
Expand Down Expand Up @@ -66,12 +67,13 @@ extension ExtrinsicTickets: ScaleCodec.Encodable {
}

extension ExtrinsicTickets {
public func getTickets() -> [Ticket] {
tickets.array.map {
// TODO: fix this
// this should be the Bandersnatch VRF output
let ticketId = Data32($0.signature.data[0 ..< 32])!
return Ticket(id: ticketId, attempt: $0.attempt)
public func getTickets(verifier: Verifier, entropy: Data32) throws -> [Ticket] {
try tickets.array.map {
var vrfInputData = SigningContext.ticketSeal
vrfInputData.append(entropy.data)
vrfInputData.append($0.attempt)
let ticketId = verifier.ringVRFVerify(vrfInputData: vrfInputData, auxData: Data(), signature: $0.signature.data)
return try Ticket(id: ticketId.get(), attempt: $0.attempt)
}
}
}
33 changes: 33 additions & 0 deletions Blockchain/Sources/Blockchain/Types/SigningContext.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import Foundation

public enum SigningContext {
/// XA = $jam_available: Ed25519 Availability assurances.
public static let available = Data("jam_available".utf8)

/// XB = $jam_beefy: bls Accumulate-result-root-mmr commitment.
public static let beefy = Data("jam_beefy".utf8)

/// XE = $jam_entropy: On-chain entropy generation.
public static let entropy = Data("jam_entropy".utf8)

/// XF = $jam_fallback_seal: Bandersnatch Fallback block seal.
public static let fallbackSeal = Data("jam_fallback_seal".utf8)

/// XG = $jam_guarantee: Ed25519 Guarantee statements.
public static let guarantee = Data("jam_guarantee".utf8)

/// XI = $jam_announce: Ed25519 Audit announcement statements.
public static let announce = Data("jam_announce".utf8)

/// XT = $jam_ticket_seal: Bandersnatch Ringvrf Ticket generation and regular block seal.
public static let ticketSeal = Data("jam_ticket_seal".utf8)

/// XU = $jam_audit: Bandersnatch Audit selection entropy.
public static let audit = Data("jam_audit".utf8)

/// X_top = $jam_valid: Ed25519 Judgements for valid work-reports.
public static let valid = Data("jam_valid".utf8)

/// X_bot = $jam_invalid: Ed25519 Judgements for invalid work-reports.
public static let invalid = Data("jam_invalid".utf8)
}
2 changes: 1 addition & 1 deletion Blockchain/Sources/Blockchain/Types/primitives.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,6 @@ public typealias Ed25519PublicKey = Data32
public typealias Ed25519Signature = Data64
public typealias BandersnatchPublicKey = Data32
public typealias BandersnatchSignature = Data64
public typealias BandersnatchRintVRFProof = Data784
public typealias BandersnatchRingVRFProof = Data784
public typealias BandersnatchRingVRFRoot = Data144
public typealias BLSKey = Data144
1 change: 1 addition & 0 deletions Database/Tests/DatabaseTests/RocksDBTests.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
// swiftlint:disable force_try
// swiftformat:disable hoistTry
import Foundation
import Testing

Expand Down
61 changes: 35 additions & 26 deletions JAMTests/Tests/JAMTests/SafroleTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,8 @@ enum SafroleTestVariants: String, CaseIterable {
var config = ProtocolConfigRef.mainnet.value
config.totalNumberOfValidators = 6
config.epochLength = 12
// 10 = 12 * 500/600, not sure what this should be for tiny, but this passes tests
config.ticketSubmissionEndSlot = 10
return Ref(config)
}()

Expand All @@ -242,33 +244,40 @@ struct SafroleTests {
}
}

@Test(arguments: try SafroleTests.loadTests(variant: .tiny))
func tinyTests(_ testcase: SafroleTestcase) throws {
withKnownIssue("not yet implemented", isIntermittent: true) {
let result = testcase.preState.updateSafrole(
slot: testcase.input.slot,
entropy: testcase.input.entropy,
extrinsics: testcase.input.extrinsics
)
switch result {
case let .success((state, epochMark, ticketsMark)):
switch testcase.output {
case let .ok(marks):
#expect(epochMark == marks.epochMark)
#expect(ticketsMark == marks.ticketsMark)
#expect(testcase.preState.mergeWith(postState: state) == testcase.postState)
case .err:
Issue.record("Expected error, got \(result)")
}
case .failure:
switch testcase.output {
case .ok:
Issue.record("Expected success, got \(result)")
case .err:
// ignore error code because it is unspecified
break
}
func safroleTests(_ testcase: SafroleTestcase) throws {
let result = testcase.preState.updateSafrole(
slot: testcase.input.slot,
entropy: testcase.input.entropy,
extrinsics: testcase.input.extrinsics
)
switch result {
case let .success((state, epochMark, ticketsMark)):
switch testcase.output {
case let .ok(marks):
#expect(epochMark == marks.epochMark)
#expect(ticketsMark == marks.ticketsMark)
#expect(testcase.preState.mergeWith(postState: state) == testcase.postState)
case .err:
Issue.record("Expected error, got \(result)")
}
case .failure:
switch testcase.output {
case .ok:
Issue.record("Expected success, got \(result)")
case .err:
// ignore error code because it is unspecified
break
}
}
}

@Test(arguments: try SafroleTests.loadTests(variant: .tiny))
func tinyTests(_ testcase: SafroleTestcase) throws {
try safroleTests(testcase)
}

@Test(arguments: try SafroleTests.loadTests(variant: .full))
func fullTests(_ testcase: SafroleTestcase) throws {
try safroleTests(testcase)
}
}
27 changes: 23 additions & 4 deletions Utils/Sources/Utils/Bandersnatch.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ public enum BandersnatchError: Error {
case ietfVRFSignFailed
case verifyRingVrfFailed
case verifyIetfVrfFailed
case wrongRingSize
}

extension Data {
Expand Down Expand Up @@ -50,6 +51,15 @@ extension CPublic {
}
}

extension RingSize {
init(_ size: UInt) throws {
guard size == 6 || size == 1023 else {
throw BandersnatchError.wrongRingSize
}
self = size == 6 ? Tiny : Full
}
}

public struct Bandersnatch {
public let secret: Data96
public let publicKey: Data32
Expand All @@ -76,10 +86,10 @@ public class Prover {
private var prover: OpaquePointer

/// init with a set of bandersnatch public keys and provider index
public init(ring: [Data32], proverIdx: UInt) throws {
public init(ring: [Data32], proverIdx: UInt, ringSize: UInt? = nil) throws {
var success = false
let cPublicArr = try ring.map { try CPublic(data32: $0) }
prover = prover_new(cPublicArr, UInt(ring.count), proverIdx, &success)
prover = try prover_new(cPublicArr, UInt(ring.count), RingSize(ringSize ?? UInt(ring.count)), proverIdx, &success)
if !success {
throw BandersnatchError.createProverFailed
}
Expand Down Expand Up @@ -123,14 +133,23 @@ public class Prover {

public class Verifier {
private var verifier: OpaquePointer
/// Bandersnatch ring root
public var ringRoot: Data144

public init(ring: [Data32]) throws {
public init(ring: [Data32], ringSize: UInt? = nil) throws {
var success = false
let cPublicArr = try ring.map { try CPublic(data32: $0) }
verifier = verifier_new(cPublicArr, UInt(ring.count), &success)
verifier = try verifier_new(cPublicArr, UInt(ring.count), RingSize(ringSize ?? UInt(ring.count)), &success)
if !success {
throw BandersnatchError.createVerifierFailed
}

var output = [UInt8](repeating: 0, count: 144)
success = verifier_commitment(&output, verifier)
if !success {
throw BandersnatchError.createVerifierFailed
}
ringRoot = Data144(Data(output))!
}

deinit {
Expand Down
Loading