diff --git a/go.mod b/go.mod index 6c5067c0..730b4c1d 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,5 @@ module github.com/eigerco/strawberry go 1.22.5 + +require github.com/ChainSafe/gossamer v0.9.0 diff --git a/go.sum b/go.sum new file mode 100644 index 00000000..c25553e7 --- /dev/null +++ b/go.sum @@ -0,0 +1,12 @@ +github.com/ChainSafe/gossamer v0.9.0 h1:Xj2oRO+5JFIpE3qkC9L8pyR1fxTPv5fTGwvvD2BnmQQ= +github.com/ChainSafe/gossamer v0.9.0/go.mod h1:V/ePNNEpATpoP8IS65suyVT05waGwQT/EtyhMhqOTKk= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/internal/crypto/constants.go b/internal/crypto/constants.go index 1aeb4867..c7469f09 100644 --- a/internal/crypto/constants.go +++ b/internal/crypto/constants.go @@ -1,9 +1,11 @@ package crypto const ( - HashSize = 32 - BandersnatchSize = 32 - Ed25519PublicSize = 32 - Ed25519PrivateSize = 64 - BLSSize = 144 + HashSize = 32 + BandersnatchSize = 32 + Ed25519PublicSize = 32 + Ed25519PrivateSize = 64 + BLSSize = 144 + BandersnatchRingSize = 144 + MetadataSize = 128 ) diff --git a/internal/crypto/keys.go b/internal/crypto/keys.go index 27cc119f..943a2bb1 100644 --- a/internal/crypto/keys.go +++ b/internal/crypto/keys.go @@ -2,7 +2,13 @@ package crypto import ( "crypto/ed25519" + "github.com/eigerco/strawberry/internal/time" ) type Ed25519PublicKey ed25519.PublicKey type Ed25519PrivateKey ed25519.PrivateKey +type BlsKey [BLSSize]byte +type BandersnatchKey [BandersnatchSize]byte +type MetadataKey [MetadataSize]byte +type RingCommitment [BandersnatchRingSize]byte +type EpochKeys [time.TimeslotsPerEpoch]BandersnatchKey diff --git a/internal/safrole/constants.go b/internal/safrole/constants.go new file mode 100644 index 00000000..30ac67fa --- /dev/null +++ b/internal/safrole/constants.go @@ -0,0 +1,13 @@ +package safrole + +const ( + // Custom error codes as defined here https://github.com/w3f/jamtestvectors/blob/master/safrole/safrole.asn#L30 + + BadSlot CustomErrorCode = 0 // Timeslot value must be strictly monotonic. + UnexpectedTicket CustomErrorCode = 1 // Received a ticket while in epoch's tail. + BadTicketOrder CustomErrorCode = 2 // Tickets must be sorted. + BadTicketProof CustomErrorCode = 3 // Invalid ticket ring proof. + BadTicketAttempt CustomErrorCode = 4 // Invalid ticket attempt value. + Reserved CustomErrorCode = 5 // Reserved + DuplicateTicket CustomErrorCode = 6 // Found a ticket duplicate. +) diff --git a/internal/safrole/safrole.go b/internal/safrole/safrole.go new file mode 100644 index 00000000..9e0262c3 --- /dev/null +++ b/internal/safrole/safrole.go @@ -0,0 +1,134 @@ +package safrole + +import ( + "fmt" + "github.com/ChainSafe/gossamer/pkg/scale" + "github.com/eigerco/strawberry/internal/block" + "github.com/eigerco/strawberry/internal/crypto" + "github.com/eigerco/strawberry/internal/time" +) + +type TicketsBodies [time.TimeslotsPerEpoch]block.Ticket +type TicketsMark [time.TimeslotsPerEpoch]block.Ticket + +type CustomErrorCode int + +type Safrole struct { + Input Input + PreState State + Output OutputOrError + PostState State +} + +type Input struct { + Slot uint32 // Current slot. + Entropy crypto.Hash // Per block entropy (originated from block entropy source VRF). + Extrinsic []block.TicketProof // Safrole extrinsic. +} + +// TicketsOrKeys is enum +type TicketsOrKeys struct { + inner any +} + +type TicketsOrKeysValues interface { + crypto.EpochKeys | TicketsBodies +} + +func setTicketsOrKeys[Value TicketsOrKeysValues](tok *TicketsOrKeys, value Value) { + tok.inner = value +} + +func (tok *TicketsOrKeys) SetValue(value any) (err error) { + switch value := value.(type) { + case crypto.EpochKeys: + setTicketsOrKeys(tok, value) + return nil + case TicketsBodies: + setTicketsOrKeys(tok, value) + return nil + default: + return fmt.Errorf("unsupported type") + } +} + +func (tok TicketsOrKeys) IndexValue() (index uint, value any, err error) { + switch tok.inner.(type) { + case crypto.EpochKeys: + return 1, tok.inner, nil + case TicketsBodies: + return 2, tok.inner, nil + } + return 0, nil, scale.ErrUnsupportedVaryingDataTypeValue +} + +func (tok TicketsOrKeys) Value() (value any, err error) { + _, value, err = tok.IndexValue() + return +} + +func (tok TicketsOrKeys) ValueAt(index uint) (value any, err error) { + switch index { + case 1: + return crypto.EpochKeys{}, nil + case 2: + return TicketsBodies{}, nil + } + return nil, scale.ErrUnknownVaryingDataTypeValue +} + +// OutputOrError is the output from Safrole protocol +type OutputOrError struct { + inner any +} + +type OutputOrErrorValues interface { + OutputMarks | CustomErrorCode +} + +type OutputMarks struct { + EpochMark *block.EpochMarker + TicketsMark *TicketsMark +} + +func setOutputOrError[Value OutputOrErrorValues](oe *OutputOrError, value Value) { + oe.inner = value +} + +func (oe *OutputOrError) SetValue(value any) (err error) { + switch value := value.(type) { + case OutputMarks: + setOutputOrError(oe, value) + return nil + case CustomErrorCode: + setOutputOrError(oe, value) + return nil + default: + return fmt.Errorf("unsupported type") + } +} + +func (oe OutputOrError) IndexValue() (index uint, value any, err error) { + switch oe.inner.(type) { + case OutputMarks: + return 1, oe.inner, nil + case CustomErrorCode: + return 2, oe.inner, nil + } + return 0, nil, scale.ErrUnsupportedVaryingDataTypeValue +} + +func (oe OutputOrError) Value() (value any, err error) { + _, value, err = oe.IndexValue() + return +} + +func (oe OutputOrError) ValueAt(index uint) (value any, err error) { + switch index { + case 1: + return OutputMarks{}, nil + case 2: + return CustomErrorCode(0), nil + } + return nil, scale.ErrUnknownVaryingDataTypeValue +} diff --git a/internal/safrole/state.go b/internal/safrole/state.go new file mode 100644 index 00000000..6beb24a2 --- /dev/null +++ b/internal/safrole/state.go @@ -0,0 +1,27 @@ +package safrole + +import ( + "github.com/eigerco/strawberry/internal/block" + "github.com/eigerco/strawberry/internal/crypto" +) + +// State relevant to Safrole protocol +type State struct { + MostRecentTimeslot uint32 // (τ) Most recent block's timeslot. + EntropyAccumulator [4]crypto.Hash // (η) Entropy accumulator and epochal randomness. + PreviousValidators ValidatorsData // (λ) Validator keys and metadata which were active in the prior epoch. + CurrentValidators ValidatorsData // (κ) Validator keys and metadata currently active. + NextValidators ValidatorsData // (γk) Validator keys for the following epoch. + FutureValidators ValidatorsData // (ι) Validator keys and metadata to be drawn from next. + TicketAccumulator []block.Ticket // (γa) Sealing-key contest ticket accumulator. + SealingKeySeries TicketsOrKeys // (γs) Sealing-key series of the current epoch. + RingCommitment crypto.RingCommitment // (γz) Bandersnatch ring commitment. +} + +type ValidatorData struct { + Bandersnatch crypto.BandersnatchKey + Ed25519 crypto.Ed25519PublicKey + Bls crypto.BlsKey + Metadata crypto.MetadataKey +} +type ValidatorsData [block.NumberOfValidators]ValidatorData