Skip to content

Commit

Permalink
Finalize SAFROLE consensus
Browse files Browse the repository at this point in the history
Add UpdateSafroleState function which passes all test vectors.
  • Loading branch information
greywolve committed Nov 19, 2024
1 parent 7603ea5 commit 12c0a32
Show file tree
Hide file tree
Showing 44 changed files with 9,663 additions and 261 deletions.
22 changes: 4 additions & 18 deletions bandersnatch/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -473,17 +473,10 @@ pub unsafe extern "C" fn new_ring_vrf_verifier(
let num_keys = public_keys_len / PUBLIC_KEY_LENGTH;

let padding_point = ring_context().padding_point();
let zero_chunk = [0u8; PUBLIC_KEY_LENGTH];
let ring: Vec<Public> = public_keys_slice
.chunks(PUBLIC_KEY_LENGTH)
.map(|chunk| {
// Replace any zero'd out public keys with a padding point.
if chunk == zero_chunk {
Public::from(padding_point)
} else {
Public::deserialize_compressed(chunk).unwrap()
}
})
// Invalid public keys become padding points.
.map(|chunk| Public::deserialize_compressed(chunk).unwrap_or(Public::from(padding_point)))
.collect();

if ring.len() != num_keys {
Expand Down Expand Up @@ -665,17 +658,10 @@ pub unsafe extern "C" fn new_ring_vrf_prover(
let num_keys = public_keys_len / PUBLIC_KEY_LENGTH;

let padding_point = ring_context().padding_point();
let zero_chunk = [0u8; PUBLIC_KEY_LENGTH];
let ring: Vec<Public> = public_keys_slice
.chunks(PUBLIC_KEY_LENGTH)
.map(|chunk| {
// Replace any zero'd out public keys with a padding point.
if chunk == zero_chunk {
Public::from(padding_point)
} else {
Public::deserialize_compressed(chunk).unwrap()
}
})
// Invalid public keys become padding points.
.map(|chunk| Public::deserialize_compressed(chunk).unwrap_or(Public::from(padding_point)))
.collect();

if ring.len() != num_keys {
Expand Down
16 changes: 8 additions & 8 deletions internal/block/block_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@ package block
import (
"crypto/ed25519"
"crypto/rand"
"github.com/eigerco/strawberry/internal/common"
"testing"

"github.com/eigerco/strawberry/internal/common"

"github.com/eigerco/strawberry/internal/crypto"
"github.com/eigerco/strawberry/internal/jamtime"
"github.com/eigerco/strawberry/internal/testutils"
"github.com/eigerco/strawberry/pkg/serialization"
"github.com/eigerco/strawberry/pkg/serialization/codec"
Expand All @@ -28,12 +28,12 @@ func Test_BlockEncodeDecode(t *testing.T) {
},
Entropy: testutils.RandomHash(t),
},
WinningTicketsMarker: &[jamtime.TimeslotsPerEpoch]Ticket{{
Identifier: testutils.RandomHash(t),
EntryIndex: 112,
},
{
Identifier: testutils.RandomHash(t),
WinningTicketsMarker: &WinningTicketMarker{
Ticket{
Identifier: testutils.RandomBandersnatchOutputHash(t),
EntryIndex: 112,
}, Ticket{
Identifier: testutils.RandomBandersnatchOutputHash(t),
EntryIndex: 222,
}},
OffendersMarkers: []ed25519.PublicKey{
Expand Down
23 changes: 13 additions & 10 deletions internal/block/header.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,24 @@ package block

import (
"crypto/ed25519"

"github.com/eigerco/strawberry/internal/common"
"github.com/eigerco/strawberry/internal/crypto"
"github.com/eigerco/strawberry/internal/jamtime"
)

// Header as defined in the section 5 in the paper
type Header struct {
ParentHash crypto.Hash // Hp
PriorStateRoot crypto.Hash // Hr
ExtrinsicHash crypto.Hash // Hx
TimeSlotIndex jamtime.Timeslot // Ht
EpochMarker *EpochMarker // He
WinningTicketsMarker *[jamtime.TimeslotsPerEpoch]Ticket // Hw
OffendersMarkers []ed25519.PublicKey // Ho, the culprit's and fault's public keys
BlockAuthorIndex uint16 // Hi
VRFSignature crypto.BandersnatchSignature // Hv
BlockSealSignature crypto.BandersnatchSignature // Hs
ParentHash crypto.Hash // Hp
PriorStateRoot crypto.Hash // Hr
ExtrinsicHash crypto.Hash // Hx
TimeSlotIndex jamtime.Timeslot // Ht
EpochMarker *EpochMarker // He
WinningTicketsMarker *WinningTicketMarker // Hw
OffendersMarkers []ed25519.PublicKey // Ho, the culprit's and fault's public keys
BlockAuthorIndex uint16 // Hi
VRFSignature crypto.BandersnatchSignature // Hv
BlockSealSignature crypto.BandersnatchSignature // Hs
}

// EpochMarker consists of epoch randomness and a sequence of
Expand All @@ -27,3 +28,5 @@ type EpochMarker struct {
Entropy crypto.Hash
Keys [common.NumberOfValidators]crypto.BandersnatchPublicKey
}

type WinningTicketMarker [jamtime.TimeslotsPerEpoch]Ticket
19 changes: 10 additions & 9 deletions internal/block/header_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ package block

import (
"crypto/ed25519"
"github.com/eigerco/strawberry/internal/common"
"testing"

"github.com/eigerco/strawberry/internal/jamtime"
"github.com/eigerco/strawberry/internal/common"

"github.com/eigerco/strawberry/internal/testutils"

"github.com/stretchr/testify/assert"
Expand All @@ -29,13 +29,14 @@ func Test_HeaderEncodeDecode(t *testing.T) {
},
Entropy: testutils.RandomHash(t),
},
WinningTicketsMarker: &[jamtime.TimeslotsPerEpoch]Ticket{{
Identifier: testutils.RandomHash(t),
EntryIndex: 111,
}, {
Identifier: testutils.RandomHash(t),
EntryIndex: 222,
}},
WinningTicketsMarker: &WinningTicketMarker{
Ticket{
Identifier: testutils.RandomBandersnatchOutputHash(t),
EntryIndex: 111,
}, Ticket{
Identifier: testutils.RandomBandersnatchOutputHash(t),
EntryIndex: 222,
}},
OffendersMarkers: []ed25519.PublicKey{
testutils.RandomED25519PublicKey(t),
},
Expand Down
16 changes: 2 additions & 14 deletions internal/block/ticket.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ const (

// Ticket represents a single ticket (C in equation 50)
type Ticket struct {
Identifier crypto.Hash // y ∈ H 32bytes hash
EntryIndex uint8 // r ∈ Nn (0, 1)
Identifier crypto.BandersnatchOutputHash // y ∈ H 32bytes hash
EntryIndex uint8 // r ∈ Nn (0, 1)
}

// TicketProof represents a proof of a valid ticket
Expand All @@ -23,15 +23,3 @@ type TicketProof struct {
type TicketExtrinsic struct {
TicketProofs []TicketProof
}

// TODO: Bandersnatch. This is just a mock.
func ExtractTicketFromProof(proofs []TicketProof) []Ticket {
result := make([]Ticket, len(proofs))
for i, proof := range proofs {
result[i] = Ticket{
EntryIndex: proof.EntryIndex,
Identifier: crypto.Hash(proof.Proof[:32]),
}
}
return result
}
1 change: 1 addition & 0 deletions internal/common/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@ const (
ValidatorsSuperMajority = (2 * NumberOfValidators / 3) + 1 // 2/3V + 1
WorkReportTimeoutPeriod = jamtime.Timeslot(5) // U = 5: The period in timeslots after which reported but unavailable work may be replaced.
ValidatorRotationPeriod = jamtime.Timeslot(10) // R = 10: The rotation period of validator-core assignments, in timeslots.
MaxTicketExtrinsicSize = 16 // The maximum number of tickets which may be submitted in a single extrinsic.
)
1 change: 1 addition & 0 deletions internal/common/constants_integration.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@ const (
ValidatorsSuperMajority = (2 * NumberOfValidators / 3) + 1
WorkReportTimeoutPeriod = jamtime.Timeslot(5)
ValidatorRotationPeriod = jamtime.Timeslot(10)
MaxTicketExtrinsicSize = 16
)
6 changes: 4 additions & 2 deletions internal/crypto/bandersnatch/bandersnatch.go
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,8 @@ func OutputHash(signature crypto.BandersnatchSignature) (outputHash crypto.Bande
type RingVrfVerifier struct{ ptr unsafe.Pointer }

// Creates a new RingVrfVerifier from a ring of bandersnatch public keys.
// Returns an opaque pointer from Rust FFI.
// Returns an opaque pointer from Rust FFI. Invalid bandersnatch public keys
// will become padding points on the ring.
func NewRingVerifier(publicKeys []crypto.BandersnatchPublicKey) (*RingVrfVerifier, error) {
flatKeys := flattenPublicKeys(publicKeys)
ptr := newRingVrfVerifier(flatKeys, C.size_t(len(flatKeys)))
Expand Down Expand Up @@ -285,7 +286,8 @@ func (r *RingVrfVerifier) Verify(
type RingVrfProver struct{ ptr unsafe.Pointer }

// Creates a new RingVrfProver using the given secret key, public key ring, and
// the position of the secret key's public key within the ring.
// the position of the secret key's public key within the ring. Invalid
// bandersnatch public keys will become padding points on the ring.
func NewRingProver(secret crypto.BandersnatchPrivateKey, publicKeys []crypto.BandersnatchPublicKey, proverIdx uint) (*RingVrfProver, error) {
flatKeys := flattenPublicKeys(publicKeys)
ptr := newRingVrfProver(secret[:], flatKeys, C.size_t(len(flatKeys)), C.size_t(proverIdx))
Expand Down
3 changes: 3 additions & 0 deletions internal/jamtime/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,7 @@ const (
// It is calculated by multiplying TimeslotsPerEpoch by TimeslotDuration,
// resulting in a duration of 1 hour per epoch.
EpochDuration = TimeslotsPerEpoch * TimeslotDuration

// The number of slots into an epoch at which ticket-submission ends.
TicketSubmissionTimeSlots = 500
)
9 changes: 5 additions & 4 deletions internal/jamtime/constants_integration.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@
package jamtime

const (
MinEpoch Epoch = 0
MaxEpoch Epoch = ^Epoch(0) / TimeslotsPerEpoch
TimeslotsPerEpoch = 12
EpochDuration = TimeslotsPerEpoch * TimeslotDuration
MinEpoch Epoch = 0
MaxEpoch Epoch = ^Epoch(0) / TimeslotsPerEpoch
TimeslotsPerEpoch = 12
EpochDuration = TimeslotsPerEpoch * TimeslotDuration
TicketSubmissionTimeSlots = 10
)
13 changes: 13 additions & 0 deletions internal/jamtime/timeslot.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,3 +100,16 @@ func ValidateTimeslot(ts Timeslot) error {
jamTime := FromTimeslot(ts)
return ValidateJamTime(jamTime.ToTime())
}

// IsTicketSubmissionPeriod checks if tickets can still be submitted in the given Timeslot
func (ts Timeslot) IsTicketSubmissionPeriod() bool {
return ts.TimeslotInEpoch() < TicketSubmissionTimeSlots
}

// IsWinningTicketMarkerPeriod checks if it's the first available Timeslot after
// the ticket submission lottery ends and where the winning ticket marker can be
// generated.
func (ts Timeslot) IsWinningTicketMarkerPeriod(previous Timeslot) bool {
return previous.TimeslotInEpoch() < TicketSubmissionTimeSlots &&
TicketSubmissionTimeSlots <= ts.TimeslotInEpoch()
}
77 changes: 77 additions & 0 deletions internal/jamtime/timeslot_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -246,3 +246,80 @@ func TestTimeSlot_ToEpoch(t *testing.T) {
assert.Equal(t, ts, want)
})
}

func TestTimeSlot_IsTicketSubmissionPeriod(t *testing.T) {
tests := []struct {
name string
timeslot Timeslot
want bool
}{
{
name: "First slot in epoch",
timeslot: 0,
want: true,
},
{
name: "Last submission slot",
timeslot: TicketSubmissionTimeSlots - 1,
want: true,
},
{
name: "First non-submission slot",
timeslot: TicketSubmissionTimeSlots,
want: false,
},
{
name: "Last slot in epoch",
timeslot: TimeslotsPerEpoch - 1,
want: false,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := tt.timeslot.IsTicketSubmissionPeriod()
assert.Equal(t, tt.want, got)
})
}
}

func TestIsWinningTicketMarkerPeriod(t *testing.T) {
tests := []struct {
name string
next Timeslot
previous Timeslot
want bool
}{
{
name: "First winning marker slot",
next: TicketSubmissionTimeSlots,
previous: TicketSubmissionTimeSlots - 1,
want: true,
},
{
name: "Winning marker slot, early submission slot, late end submission slot",
next: TicketSubmissionTimeSlots + 1,
previous: TicketSubmissionTimeSlots - 6,
want: true,
},
{
name: "Both slots in submission period",
next: TicketSubmissionTimeSlots - 2,
previous: TicketSubmissionTimeSlots - 3,
want: false,
},
{
name: "Both slots after submission period",
next: TicketSubmissionTimeSlots + 1,
previous: TicketSubmissionTimeSlots,
want: false,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := tt.next.IsWinningTicketMarkerPeriod(tt.previous)
assert.Equal(t, tt.want, got)
})
}
}
Loading

0 comments on commit 12c0a32

Please sign in to comment.