Skip to content

Commit

Permalink
Safrole finish (#44)
Browse files Browse the repository at this point in the history
* fix typo

* add ring root

* real tickets verifier

* update bandersnatch

* wip

* fix

* fix

* fix

* wip

* full test ready

* remove serialze

* revert format

* remove format

* add consts

* use data

* fix

* fix
  • Loading branch information
qiweiii authored Aug 2, 2024
1 parent cf9bab1 commit 9a345e5
Show file tree
Hide file tree
Showing 12 changed files with 248 additions and 92 deletions.
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

0 comments on commit 9a345e5

Please sign in to comment.