diff --git a/internal/block/ticket.go b/internal/block/ticket.go index 6c8f8d86..24df4920 100644 --- a/internal/block/ticket.go +++ b/internal/block/ticket.go @@ -13,6 +13,8 @@ type Ticket struct { EntryIndex uint8 // r ∈ Nn (0, 1) } +func (t Ticket) TicketOrKeyType() {} + // TicketProof represents a proof of a valid ticket type TicketProof struct { EntryIndex uint8 // r ∈ Nn (0, 1) diff --git a/internal/crypto/keys.go b/internal/crypto/keys.go index e2c3f019..80b66175 100644 --- a/internal/crypto/keys.go +++ b/internal/crypto/keys.go @@ -10,13 +10,21 @@ type Ed25519Signature [Ed25519SignatureSize]byte type BlsKey [BLSSize]byte type BandersnatchSeedKey [BandersnatchSize]byte type BandersnatchPrivateKey [BandersnatchSize]byte + type BandersnatchPublicKey [BandersnatchSize]byte + +func (b BandersnatchPublicKey) TicketOrKeyType() {} + type BandersnatchSignature [96]byte type BandersnatchOutputHash [32]byte type RingVrfSignature [VrfProofSize]byte type MetadataKey [MetadataSize]byte type RingCommitment [BandersnatchRingSize]byte + type EpochKeys [jamtime.TimeslotsPerEpoch]BandersnatchPublicKey + +func (e EpochKeys) TicketsOrKeysType() {} + type ValidatorKey struct { Bandersnatch BandersnatchPublicKey Ed25519 ed25519.PublicKey diff --git a/internal/polkavm/host_call/accumulate_functions.go b/internal/polkavm/host_call/accumulate_functions.go index b00bae75..5cd271a4 100644 --- a/internal/polkavm/host_call/accumulate_functions.go +++ b/internal/polkavm/host_call/accumulate_functions.go @@ -1,6 +1,8 @@ package host_call import ( + "bytes" + "github.com/eigerco/strawberry/internal/block" "github.com/eigerco/strawberry/internal/common" "github.com/eigerco/strawberry/internal/crypto" @@ -8,6 +10,7 @@ import ( . "github.com/eigerco/strawberry/internal/polkavm" "github.com/eigerco/strawberry/internal/service" "github.com/eigerco/strawberry/internal/state" + "github.com/eigerco/strawberry/pkg/serialization/codec/jam" ) // Bless ΩB(ϱ, ω, μ, (x, y)) @@ -253,9 +256,63 @@ func Eject(gas Gas, regs Registers, mem Memory, ctxPair AccumulateContextPair, t } gas -= EjectCost - // TODO: implement method + d, o := regs[A0], regs[A1] - return gas, regs, mem, ctxPair, nil + // let h = μo..o+32 if Zo..o+32 ⊂ Vμ + h := make([]byte, 32) + if err := mem.Read(uint32(o), h); err != nil { + // otherwise ∇ + return gas, withCode(regs, OOB), mem, ctxPair, nil + } + + if block.ServiceId(d) == ctxPair.RegularCtx.ServiceId { + // d = x_s => WHO + return gas, withCode(regs, WHO), mem, ctxPair, nil + } + + // if d ∈ K((x_u)_d) + serviceAccount, ok := ctxPair.RegularCtx.AccumulationState.ServiceState[block.ServiceId(d)] + if !ok { + return gas, withCode(regs, WHO), mem, ctxPair, nil + } + + encodedXs, err := jam.Marshal(struct { + ServiceId block.ServiceId `jam:"length=32"` + }{ctxPair.RegularCtx.ServiceId}) + if err != nil || !bytes.Equal(serviceAccount.CodeHash[:], encodedXs) { + // d_c ≠ E32(x_s) => WHO + return gas, withCode(regs, WHO), mem, ctxPair, err + } + + if serviceAccount.TotalItems() != 2 { + // d_i ≠ 2 => HUH + return gas, withCode(regs, HUH), mem, ctxPair, nil + } + + // l = max(81, d_o) - 81 + l := max(81, len(serviceAccount.Code())) - 81 + + key := service.PreImageMetaKey{Hash: crypto.Hash(h), Length: service.PreimageLength(l)} + dL, ok := serviceAccount.PreimageMeta[key] + if !ok { + // (h, l) ∉ d_l => HUH + return gas, withCode(regs, HUH), mem, ctxPair, nil + } + + // if d_l[h, l] = [x, y], y < t − D => OK + if len(dL) == 2 && dL[1] < timeslot-jamtime.PreimageExpulsionPeriod { + xs := ctxPair.RegularCtx.ServiceAccount() + // s'_b = ((x_u)d)[x_s]b + d_b + xs.Balance += serviceAccount.Balance + + delete(ctxPair.RegularCtx.AccumulationState.ServiceState, block.ServiceId(d)) + ctxPair.RegularCtx.AccumulationState.ServiceState[ctxPair.RegularCtx.ServiceId] = xs + + return gas, withCode(regs, OK), mem, ctxPair, nil + } + + // otherwise => HUH + return gas, withCode(regs, HUH), mem, ctxPair, nil } // Query ΩQ(ϱ, ω, μ, (x, y)) diff --git a/internal/polkavm/host_call/accumulate_functions_test.go b/internal/polkavm/host_call/accumulate_functions_test.go index 89bfd8de..e7279649 100644 --- a/internal/polkavm/host_call/accumulate_functions_test.go +++ b/internal/polkavm/host_call/accumulate_functions_test.go @@ -282,6 +282,71 @@ func TestAccumulate(t *testing.T) { }}, }, }, + { + name: "eject", + fn: fnTms(Eject), + initialGas: 100, + timeslot: 200, + initialRegs: deltaRegs{ + A0: 999, + }, + alloc: alloc{ + A1: hash2bytes(randomHash), + }, + X: AccumulateContext{ + ServiceId: 222, + AccumulationState: state.AccumulationState{ + ServiceState: service.ServiceState{ + // d = 999 (the ejected service) + 999: func() service.ServiceAccount { + e32, err := jam.Marshal(struct { + ServiceId block.ServiceId `jam:"length=32"` + }{222}) + require.NoError(t, err) + + var codeHash crypto.Hash + copy(codeHash[:], e32) + + preImgLookup := map[crypto.Hash][]byte{ + codeHash: make([]byte, 81), + } + + preImgMeta := map[service.PreImageMetaKey]service.PreimageHistoricalTimeslots{ + {Hash: randomHash, Length: 0}: {50, 100}, + } + + return service.ServiceAccount{ + CodeHash: codeHash, + Balance: 100, // d_b + PreimageLookup: preImgLookup, + PreimageMeta: preImgMeta, + } + }(), + // x_s + 222: { + Balance: 1000, + }, + }, + }, + }, + expectedGas: 88, + expectedDeltaRegs: deltaRegs{ + A0: uint64(OK), + }, + // After success: + // - The ejected service 999 is removed + // - x_s(222).Balance = 1000+100=1100 + expectedX: AccumulateContext{ + ServiceId: 222, + AccumulationState: state.AccumulationState{ + ServiceState: service.ServiceState{ + 222: { + Balance: 1100, + }, + }, + }, + }, + }, { name: "solicit_out_of_gas", fn: fnTms(Solicit), diff --git a/internal/safrole/safrole.go b/internal/safrole/safrole.go index 45477bb9..7fc6c93b 100644 --- a/internal/safrole/safrole.go +++ b/internal/safrole/safrole.go @@ -2,6 +2,7 @@ package safrole import ( "fmt" + "github.com/eigerco/strawberry/pkg/serialization/codec/jam" "github.com/eigerco/strawberry/internal/block" @@ -11,48 +12,58 @@ import ( type TicketsBodies [jamtime.TimeslotsPerEpoch]block.Ticket -// TicketsOrKeys is enum -type TicketsOrKeys struct { - inner any +func (t TicketsBodies) TicketsOrKeysType() {} + +// TicketAccumulator is enum/union that represents ya. It should contain either +// TicketBodies which is an array of tickets, or in the fallback case +// crypto.EpochKeys, an array of bandersnatch public keys. +type TicketAccumulator struct { + inner TicketsOrKeys +} + +// TicketsOrKeys represents the union of either TicketBodies or +// crypto.EpochKeys. +type TicketsOrKeys interface { + TicketsOrKeysType() } -type TicketsOrKeysValues interface { - crypto.EpochKeys | TicketsBodies +func (ta *TicketAccumulator) Set(value TicketsOrKeys) { + ta.inner = value } -func setTicketsOrKeys[Value TicketsOrKeysValues](tok *TicketsOrKeys, value Value) { - tok.inner = value +func (ta *TicketAccumulator) Get() TicketsOrKeys { + return ta.inner } -func (tok *TicketsOrKeys) SetValue(value any) error { +func (ta *TicketAccumulator) SetValue(value any) error { switch value := value.(type) { case crypto.EpochKeys: - setTicketsOrKeys(tok, value) + ta.inner = value return nil case TicketsBodies: - setTicketsOrKeys(tok, value) + ta.inner = value return nil default: return fmt.Errorf(jam.ErrUnsupportedType, value) } } -func (tok TicketsOrKeys) IndexValue() (uint, any, error) { - switch tok.inner.(type) { +func (ta TicketAccumulator) IndexValue() (uint, any, error) { + switch ta.inner.(type) { case crypto.EpochKeys: - return 1, tok.inner, nil + return 1, ta.inner, nil case TicketsBodies: - return 0, tok.inner, nil + return 0, ta.inner, nil } return 0, nil, jam.ErrUnsupportedEnumTypeValue } -func (tok TicketsOrKeys) Value() (value any, err error) { - _, value, err = tok.IndexValue() +func (ta TicketAccumulator) Value() (value any, err error) { + _, value, err = ta.IndexValue() return } -func (tok TicketsOrKeys) ValueAt(index uint) (any, error) { +func (ta TicketAccumulator) ValueAt(index uint) (any, error) { switch index { case 1: return crypto.EpochKeys{}, nil diff --git a/internal/safrole/state.go b/internal/safrole/state.go index 63d8c764..1927b872 100644 --- a/internal/safrole/state.go +++ b/internal/safrole/state.go @@ -13,7 +13,7 @@ import ( type State struct { NextValidators ValidatorsData // (γk) Validator keys for the following epoch. TicketAccumulator []block.Ticket // (γa) Sealing-key contest ticket accumulator. - SealingKeySeries TicketsOrKeys // (γs) Sealing-key series of the current epoch. + SealingKeySeries TicketAccumulator // (γs) Sealing-key series of the current epoch. RingCommitment crypto.RingCommitment // (γz) Bandersnatch ring commitment. } diff --git a/internal/state/block_seal.go b/internal/state/block_seal.go index 84af2751..c12754bf 100644 --- a/internal/state/block_seal.go +++ b/internal/state/block_seal.go @@ -1,13 +1,14 @@ package state import ( + "errors" "fmt" + "github.com/eigerco/strawberry/pkg/serialization/codec/jam" "github.com/eigerco/strawberry/internal/block" "github.com/eigerco/strawberry/internal/crypto" "github.com/eigerco/strawberry/internal/crypto/bandersnatch" - "github.com/eigerco/strawberry/internal/jamtime" "github.com/eigerco/strawberry/internal/safrole" ) @@ -17,17 +18,21 @@ const ( EntropyContext = "jam_entropy" ) -// TODO currently unused, should be used in section 19 -// t ∈ {0, 1} 1 if ticket, 0 if fallback key -var T byte +var ( + ErrBlockSealInvalidAuthor = errors.New("invalid block seal author") +) -// Gets the winning ticket or key for the current timeslot -func getWinningTicketOrKey(header *block.Header, state *State) (interface{}, error) { - index := header.TimeSlotIndex % jamtime.TimeslotsPerEpoch - sealingKeys, err := state.ValidatorState.SafroleState.SealingKeySeries.Value() - if err != nil { - return nil, err - } +// This represents a union of either a block.Ticket or a +// crypto.BandersnatchPublic key as a fallback. +type TicketOrKey interface { + TicketOrKeyType() +} + +// Gets the winning ticket or key for the current timeslot. +// Implements part of equation 6.15 in the graypaper: γ′s[Ht]^↺ (v0.5.4) +func getWinningTicketOrKey(header *block.Header, state *State) (TicketOrKey, error) { + index := header.TimeSlotIndex.TimeslotInEpoch() + sealingKeys := state.ValidatorState.SafroleState.SealingKeySeries.Get() switch value := sealingKeys.(type) { case safrole.TicketsBodies: return value[index], nil @@ -38,150 +43,211 @@ func getWinningTicketOrKey(header *block.Header, state *State) (interface{}, err } } -// TODO: Bandersnatch implement this function. This is just a mock. -func isWinningKey(key crypto.BandersnatchPublicKey, header *block.Header, state *State) bool { +// Attempts to a seal a block and add a block seal signature (Hs) and a VRFS +// signature (Hv) to the header. Uses either a ticket in most cases but uses a +// bandersnatch public key as a fallback. Checks that the private key given was +// either used to generate the winning ticket or otherwise if it's public key +// matches the winning public key in the case of fallback. +// Implements equations 6.15-6.20 in the graypaper. (v0.5.4) +func SealBlock( + header *block.Header, + state *State, + privateKey crypto.BandersnatchPrivateKey, +) error { winningTicketOrKey, err := getWinningTicketOrKey(header, state) if err != nil { - return false + return err } - return key == winningTicketOrKey -} - -func encodeUnsealedHeader(header block.Header) ([]byte, error) { - header.BlockSealSignature = crypto.BandersnatchSignature{} - // Use the regular serialization from Appendix C - return jam.Marshal(header) -} -func buildSealContextForFallbackKeys(state *State) []byte { - // Equation 60: γ's ∈ ⟦HB⟧ - context := append([]byte(FallbackSealContext), state.EntropyPool[3][:]...) // η_3 - T = 0 - return context -} -func buildSealContextForTickets(state *State, ticket block.Ticket) []byte { - var context []byte - // Equation 59: γ's ∈ ⟦C⟧ - context = append([]byte(TicketSealContext), state.EntropyPool[3][:]...) // η_3 - context = append(context, byte(ticket.EntryIndex)) - T = 1 - return context -} + entropy := state.EntropyPool[3] // η_3 -func buildSealContext(header *block.Header, state *State) ([]byte, error) { - var context []byte + var ( + sealSignature crypto.BandersnatchSignature + vrfsSignature crypto.BandersnatchSignature + ) - winningTicketOrKey, err := getWinningTicketOrKey(header, state) + sealSignature, vrfsSignature, err = SignBlock(*header, winningTicketOrKey, privateKey, entropy) if err != nil { - return nil, err + return err } - // case switch if winningTicketOrKey is of type crypto.BandersnatchPublicKey or block.Ticket - switch tok := winningTicketOrKey.(type) { + + header.BlockSealSignature = sealSignature + header.VRFSignature = vrfsSignature + + return nil +} + +// Produces a seal signature and VRFS signature for the unsealed header bytes of +// the given header using either a winning ticket or a public key in the case of +// fallback. This will error if the private key can't be associated with the +// given ticket or public key in the case of fallback. +// Implements equations 6.15-6.20 in the graypaper. (v0.5.4) +func SignBlock( + header block.Header, + ticketOrKey TicketOrKey, + privateKey crypto.BandersnatchPrivateKey, // Ha + entropy crypto.Hash, // η′3 +) ( + sealSignature crypto.BandersnatchSignature, + vrfsSignature crypto.BandersnatchSignature, + err error, +) { + switch tok := ticketOrKey.(type) { case block.Ticket: - // Equation 59: γ's ∈ ⟦C⟧ - context = buildSealContextForTickets(state, tok) + return SignBlockWithTicket(header, tok, privateKey, entropy) case crypto.BandersnatchPublicKey: - // Equation 60: γ's ∈ ⟦HB⟧ - context = buildSealContextForFallbackKeys(state) + return SignBlockWithFallback(header, tok, privateKey, entropy) default: - return nil, fmt.Errorf("unknown sealing key type: %T", winningTicketOrKey) + return crypto.BandersnatchSignature{}, crypto.BandersnatchSignature{}, fmt.Errorf("unexpected type for ticketOrKey: %T", tok) } - return context, nil } -func createSealSignature(header *block.Header, state *State, privateKey crypto.BandersnatchPrivateKey) error { - context, err := buildSealContext(header, state) +// Produces a seal signature and VRFS signature for the unsealed header bytes of +// the given header using a winning ticket. This will error if the private key +// can't be associated with the given ticket. +// Implements equations 6.15 and 6.17-6.20 in the graypaper. (v0.5.4) +func SignBlockWithTicket( + header block.Header, + ticket block.Ticket, + privateKey crypto.BandersnatchPrivateKey, // Ha + entropy crypto.Hash, // η′3 +) ( + sealSignature crypto.BandersnatchSignature, + vrfsSignature crypto.BandersnatchSignature, + err error, +) { + unsealedHeader, err := encodeUnsealedHeader(header) if err != nil { - return err + return crypto.BandersnatchSignature{}, crypto.BandersnatchSignature{}, err } - unsealedHeader, err := encodeUnsealedHeader(*header) + // Build the context: XT ⌢ η′3 ++ ir + sealContext := buildTicketSealContext(entropy, ticket.EntryIndex) + + sealSignature, err = bandersnatch.Sign(privateKey, sealContext, unsealedHeader) if err != nil { - return err + return crypto.BandersnatchSignature{}, crypto.BandersnatchSignature{}, err } - // Hs(BlockSealSignaure) ∈ FEU(H)Ha⟨...⟩ - header.BlockSealSignature, err = bandersnatch.Sign(privateKey, context, unsealedHeader) - - return err -} -// Implements equation 61 Hv ∈ F[]Ha⟨XE ⌢ Y(Hs)⟩ -func createVRFSignature(header *block.Header, privateKey crypto.BandersnatchPrivateKey) error { - // XE is the constant context string - XE := []byte("jam_entropy") - // Generate Y(Hs) - sealOutputHash, err := bandersnatch.OutputHash(header.BlockSealSignature) + sealOutputHash, err := bandersnatch.OutputHash(sealSignature) if err != nil { - return err + return crypto.BandersnatchSignature{}, crypto.BandersnatchSignature{}, err + } + + // Extra safety check. See equation 6.29 in the graypaper. (v0.5.4) + // The VRF output hash of the seal signature should be the same as the VRF + // output hash of the ticket if the same private key was used to produce + // both. + if sealOutputHash != ticket.Identifier { + return crypto.BandersnatchSignature{}, crypto.BandersnatchSignature{}, ErrBlockSealInvalidAuthor } - // Construct the message: XE ⌢ Y(Hs) - vrfInputData := append(XE, sealOutputHash[:]...) - // Sign the constructed message to get Hv - signature, err := bandersnatch.Sign(privateKey, vrfInputData, []byte{}) + vrfsSignature, err = signBlockVRFS(sealOutputHash, privateKey) if err != nil { - return err + return crypto.BandersnatchSignature{}, crypto.BandersnatchSignature{}, err } - // Set the signature as Hv in the header - header.VRFSignature = signature - return nil + return sealSignature, vrfsSignature, nil } -// TODO: Bandersnatch Mock implementation of verifying the VRF proof -func ExtractVRFOutput(header block.Header) (crypto.BandersnatchOutputHash, error) { - return crypto.BandersnatchOutputHash{}, nil +// Helper to build the ticket sealing context. +func buildTicketSealContext(entropy crypto.Hash, ticketAttempt uint8) []byte { + // Build the context: XT ⌢ η′3 ++ ir + sealContext := append([]byte(TicketSealContext), entropy[:]...) + sealContext = append(sealContext, byte(ticketAttempt)) + return sealContext } -// Implements equations 66 and 67 -func updateEntropyAccumulator(header *block.Header, state *State) error { - outputHash, err := bandersnatch.OutputHash(header.VRFSignature) +// Produces a seal signature and VRFS signature for the unsealed header bytes of +// the given header using a winning public key. This is the fallback case. This +// will error if the private key can't be associated with the given public key. +// Implements equations 6.16 and 6.17-6.20 in the graypaper. (v0.5.4) +func SignBlockWithFallback( + header block.Header, + winningKey crypto.BandersnatchPublicKey, + privateKey crypto.BandersnatchPrivateKey, // Ha + entropy crypto.Hash, // // η′3 +) ( + sealSignature crypto.BandersnatchSignature, + vrfsSignature crypto.BandersnatchSignature, + err error, +) { + // Extra safety check. Ha's public key should match the winning public key. + ownerPublicKey, err := bandersnatch.Public(privateKey) if err != nil { - return err + return crypto.BandersnatchSignature{}, crypto.BandersnatchSignature{}, err + } + if ownerPublicKey != winningKey { + return crypto.BandersnatchSignature{}, crypto.BandersnatchSignature{}, ErrBlockSealInvalidAuthor + } + + unsealedHeader, err := encodeUnsealedHeader(header) + if err != nil { + return crypto.BandersnatchSignature{}, crypto.BandersnatchSignature{}, err } - // Equation 66: η'0 ≡ H(η0 ⌢ Y(Hv)) - newEntropy := crypto.HashData(append(state.EntropyPool[0][:], outputHash[:]...)) - entropyPool := EntropyPool{} + // Build the context: XF ⌢ η′3 + sealContext := buildTicketFallbackContext(entropy) - // Equation 67: Rotate entropy accumulators on epoch change - if header.TimeSlotIndex.IsFirstTimeslotInEpoch() { - entropyPool = RotateEntropyPool(state.EntropyPool) + sealSignature, err = bandersnatch.Sign(privateKey, sealContext, unsealedHeader) + if err != nil { + return crypto.BandersnatchSignature{}, crypto.BandersnatchSignature{}, err } - entropyPool[0] = newEntropy - state.EntropyPool = entropyPool - return nil + + sealOutputHash, err := bandersnatch.OutputHash(sealSignature) + if err != nil { + return crypto.BandersnatchSignature{}, crypto.BandersnatchSignature{}, err + } + + vrfsSignature, err = signBlockVRFS(sealOutputHash, privateKey) + if err != nil { + return crypto.BandersnatchSignature{}, crypto.BandersnatchSignature{}, err + } + + return sealSignature, vrfsSignature, nil + } -func RotateEntropyPool(pool EntropyPool) EntropyPool { - pool[3] = pool[2] - pool[2] = pool[1] - pool[1] = pool[0] - return pool +// Helper to build the fallback sealing context. +func buildTicketFallbackContext(entropy crypto.Hash) []byte { + // Build the context: XF ⌢ η′3 + return append([]byte(FallbackSealContext), entropy[:]...) } -// Should be called after a check if the validator has are winning key. -func sealBlockAndUpdateEntropy(header *block.Header, state *State, privateKey crypto.BandersnatchPrivateKey) error { - if err := createSealSignature(header, state, privateKey); err != nil { - return err - } - if err := createVRFSignature(header, privateKey); err != nil { - return err +// Helper to produce the VRFS signature. +// Implements equation 6.17 in the graypaper. (v0.5.4) +func signBlockVRFS( + sealOutputHash crypto.BandersnatchOutputHash, + privateKey crypto.BandersnatchPrivateKey, +) (crypto.BandersnatchSignature, error) { + // Construct the message: XE ⌢ Y(Hs) + vrfContext := buildVRFSContext(sealOutputHash) + + // Sign the constructed message to get Hv. + vrfSignature, err := bandersnatch.Sign(privateKey, vrfContext, []byte{}) + if err != nil { + return crypto.BandersnatchSignature{}, err } - return updateEntropyAccumulator(header, state) + + return vrfSignature, nil } -// Main function to implement all of section 6.4 -func AttemptBlockSealing(header *block.Header, state *State, privateKey crypto.BandersnatchPrivateKey) error { - publicKey, err := bandersnatch.Public(privateKey) +// Helper to build the fallback sealing context. +func buildVRFSContext(sealOutputHash crypto.BandersnatchOutputHash) []byte { + // Construct the message: XE ⌢ Y(Hs) + return append([]byte(EntropyContext), sealOutputHash[:]...) +} + +// Help to get unsealed header bytes. Essentially this strips off the header +// seal. +func encodeUnsealedHeader(header block.Header) ([]byte, error) { + // Use the regular serialization from Appendix C in the graypaper. + bytes, err := jam.Marshal(header) if err != nil { - return fmt.Errorf("failed to get public key: %w", err) - } - if !isWinningKey(publicKey, header, state) { - return fmt.Errorf("key does not have privilege to seal the block") - } - if err := sealBlockAndUpdateEntropy(header, state, privateKey); err != nil { - return fmt.Errorf("failed to seal block: %w", err) + return nil, err } - return nil + // Hs will be the last 96 zeros, so strip those off to get the unsealed + // header bytes. + // See equation C.19 in the graypaper. (v0.5.4) + return bytes[:len(bytes)-96], nil } diff --git a/internal/state/block_seal_test.go b/internal/state/block_seal_test.go index c6576a9e..3e7688e2 100644 --- a/internal/state/block_seal_test.go +++ b/internal/state/block_seal_test.go @@ -3,134 +3,197 @@ package state import ( "testing" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "github.com/eigerco/strawberry/internal/block" "github.com/eigerco/strawberry/internal/crypto" + "github.com/eigerco/strawberry/internal/crypto/bandersnatch" + "github.com/eigerco/strawberry/internal/jamtime" "github.com/eigerco/strawberry/internal/safrole" "github.com/eigerco/strawberry/internal/testutils" "github.com/eigerco/strawberry/internal/validator" - "github.com/eigerco/strawberry/pkg/serialization/codec/jam" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "golang.org/x/exp/rand" ) -func TestEncodeUnsealedHeader(t *testing.T) { - header := block.Header{ - ParentHash: testutils.RandomHash(t), - PriorStateRoot: testutils.RandomHash(t), - ExtrinsicHash: testutils.RandomHash(t), - TimeSlotIndex: 123, - VRFSignature: testutils.RandomBandersnatchSignature(t), - BlockSealSignature: testutils.RandomBandersnatchSignature(t), - } - encoded, err := encodeUnsealedHeader(header) - require.NoError(t, err) - require.NotNil(t, encoded) - decoded := &block.Header{} - err = jam.Unmarshal(encoded, decoded) - require.NoError(t, err) - assert.Empty(t, decoded.BlockSealSignature) - // Check that the rest of the header is the same - header.BlockSealSignature = crypto.BandersnatchSignature{} - assert.Equal(t, header, *decoded) -} -func TestBuildSealContextWithKey(t *testing.T) { - hash := testutils.RandomHash(t) +func TestSealBlockTicket(t *testing.T) { + entropy := testutils.RandomHash(t) + ticketBodies := randomTicketBodies(t, entropy) + + randomTimeslot := testutils.RandomUint32() % jamtime.TimeslotsPerEpoch + t.Logf("random timeslot: %d", randomTimeslot) + + // Replace one of the keys in the accumulator with our public key. This + // should later be selected as the winning key. + privateKey := testutils.RandomBandersnatchPrivateKey(t) + ticket := createTicket(t, privateKey, entropy, 0) + ticketBodies[randomTimeslot] = ticket + + ticketAccumulator := safrole.TicketAccumulator{} + ticketAccumulator.Set(ticketBodies) + header := &block.Header{ - TimeSlotIndex: 123, + ParentHash: testutils.RandomHash(t), + PriorStateRoot: testutils.RandomHash(t), + ExtrinsicHash: testutils.RandomHash(t), + TimeSlotIndex: jamtime.Timeslot(randomTimeslot), } - toks := safrole.TicketsOrKeys{} - var epochKeys crypto.EpochKeys - epochKeys[0] = testutils.RandomBandersnatchPublicKey(t) - err := toks.SetValue(epochKeys) - require.NoError(t, err) + state := &State{ EntropyPool: [4]crypto.Hash{ testutils.RandomHash(t), testutils.RandomHash(t), testutils.RandomHash(t), - hash, + entropy, + }, + ValidatorState: validator.ValidatorState{ + SafroleState: safrole.State{ + SealingKeySeries: ticketAccumulator, + }, }, } - validatorState := validator.SetupValidatorState(t) - state.ValidatorState = *validatorState - context, err := buildSealContext(header, state) + + err := SealBlock(header, state, privateKey) require.NoError(t, err) - fallbackSealContext := append([]byte(FallbackSealContext), hash[:]...) - assert.Equal(t, fallbackSealContext, context) - assert.Equal(t, byte(0), T) + assert.NotEmpty(t, header.BlockSealSignature) + assert.NotEmpty(t, header.VRFSignature) + + // Sanity checks that we did sign. + expectedTicketID, err := bandersnatch.OutputHash(header.BlockSealSignature) + require.NoError(t, err) + // Check that our seal produces the same output hash used by the ticket. + assert.Equal(t, expectedTicketID, ticket.Identifier) + publicKey, err := bandersnatch.Public(privateKey) + require.NoError(t, err) + // Check Hv. + ok, _ := bandersnatch.Verify( + publicKey, + buildVRFSContext(expectedTicketID), + []byte{}, + header.VRFSignature, + ) + require.True(t, ok) } -func TestBuildSealContextWithTicket(t *testing.T) { - hash := testutils.RandomHash(t) +func TestSealBlockFallback(t *testing.T) { + privateKey := testutils.RandomBandersnatchPrivateKey(t) + publicKey, err := bandersnatch.Public(privateKey) + require.NoError(t, err) + + var epochKeys = testutils.RandomEpochKeys(t) + + randomTimeslot := testutils.RandomUint32() % jamtime.TimeslotsPerEpoch + t.Logf("random timeslot: %d", randomTimeslot) + + // Replace one of the keys in the accumulator with our public key. This + // should later be selected as the winning key. + epochKeys[randomTimeslot] = publicKey + + ticketAccumulator := safrole.TicketAccumulator{} + ticketAccumulator.Set(epochKeys) + header := &block.Header{ - TimeSlotIndex: 123, + ParentHash: testutils.RandomHash(t), + PriorStateRoot: testutils.RandomHash(t), + ExtrinsicHash: testutils.RandomHash(t), + TimeSlotIndex: jamtime.Timeslot(randomTimeslot), } - toks := safrole.TicketsOrKeys{} - var tickets = safrole.TicketsBodies{} - tickets[0] = validator.RandomTicket(t) - err := toks.SetValue(tickets) + unsealedHeader, err := encodeUnsealedHeader(*header) require.NoError(t, err) + + entropy := testutils.RandomHash(t) state := &State{ EntropyPool: [4]crypto.Hash{ testutils.RandomHash(t), testutils.RandomHash(t), testutils.RandomHash(t), - hash, + entropy, + }, + ValidatorState: validator.ValidatorState{ + SafroleState: safrole.State{ + SealingKeySeries: ticketAccumulator, + }, }, } - validatorState := validator.SetupValidatorState(t) - validatorState.SafroleState.SealingKeySeries = toks - state.ValidatorState = *validatorState - context, err := buildSealContext(header, state) + err = SealBlock(header, state, privateKey) require.NoError(t, err) - expected := TicketSealContext + string(hash[:]) + string(rune(0)) - assert.Equal(t, []byte(expected), context) - assert.Equal(t, byte(1), T) -} + assert.NotEmpty(t, header.BlockSealSignature) + assert.NotEmpty(t, header.VRFSignature) -func TestSealBlockAndUpdateEntropy(t *testing.T) { - privateKey := testutils.RandomBandersnatchPrivateKey(t) - sealingKeySeries := safrole.TicketsOrKeys{} - var epochKeys = testutils.RandomEpochKeys(t) - err := sealingKeySeries.SetValue(epochKeys) + // Sanity checks that we did sign. + vrfInput := buildTicketFallbackContext(entropy) + require.NoError(t, err) + // Check Hs. + ok, _ := bandersnatch.Verify(publicKey, vrfInput, unsealedHeader, header.BlockSealSignature) + require.True(t, ok) + sealOutputHash, err := bandersnatch.OutputHash(header.BlockSealSignature) require.NoError(t, err) + // Check Hv. + ok, _ = bandersnatch.Verify( + publicKey, + buildVRFSContext(sealOutputHash), + []byte{}, + header.VRFSignature, + ) + require.True(t, ok) + +} + +func TestSealBlockInvalidAuthor(t *testing.T) { + entropy := testutils.RandomHash(t) + ticketBodies := randomTicketBodies(t, entropy) + + ticketAccumulator := safrole.TicketAccumulator{} + ticketAccumulator.Set(ticketBodies) + header := &block.Header{ ParentHash: testutils.RandomHash(t), PriorStateRoot: testutils.RandomHash(t), ExtrinsicHash: testutils.RandomHash(t), - TimeSlotIndex: 123, + TimeSlotIndex: testutils.RandomTimeslot(), } + state := &State{ EntropyPool: [4]crypto.Hash{ testutils.RandomHash(t), testutils.RandomHash(t), testutils.RandomHash(t), - testutils.RandomHash(t), + entropy, }, ValidatorState: validator.ValidatorState{ SafroleState: safrole.State{ - SealingKeySeries: sealingKeySeries, + SealingKeySeries: ticketAccumulator, }, }, } - err = sealBlockAndUpdateEntropy(header, state, privateKey) + privateKey := testutils.RandomBandersnatchPrivateKey(t) + err := SealBlock(header, state, privateKey) + require.ErrorIs(t, err, ErrBlockSealInvalidAuthor) +} + +func createTicket(t *testing.T, privateKey crypto.BandersnatchPrivateKey, entropy crypto.Hash, attempt uint8) block.Ticket { + vrfInput := buildTicketSealContext(entropy, attempt) + signature, err := bandersnatch.Sign(privateKey, vrfInput, []byte{}) require.NoError(t, err) - assert.NotEmpty(t, header.BlockSealSignature) - assert.NotEmpty(t, header.VRFSignature) - assert.NotEqual(t, state.EntropyPool[0], [32]byte{}) + + outputHash, err := bandersnatch.OutputHash(signature) + require.NoError(t, err) + + return block.Ticket{ + Identifier: outputHash, + EntryIndex: attempt, + } } -func TestRotateEntropyPool(t *testing.T) { - initialEntropyPool := [4]crypto.Hash{ - testutils.RandomHash(t), - testutils.RandomHash(t), - testutils.RandomHash(t), - testutils.RandomHash(t), + +func randomTicketBodies(t *testing.T, entropy crypto.Hash) safrole.TicketsBodies { + var ticketsBodies safrole.TicketsBodies + + for i := 0; i < jamtime.TimeslotsPerEpoch; i++ { + privateKey := testutils.RandomBandersnatchPrivateKey(t) + attempt := uint8(rand.Intn(256)) + ticketsBodies[i] = createTicket(t, privateKey, entropy, attempt) } - result := RotateEntropyPool(initialEntropyPool) - assert.Equal(t, initialEntropyPool[2], result[3]) - assert.Equal(t, initialEntropyPool[1], result[2]) - assert.Equal(t, initialEntropyPool[0], result[1]) + + return ticketsBodies } diff --git a/internal/state/merkle/helpers_test.go b/internal/state/merkle/helpers_test.go index f93fe4a2..8632602b 100644 --- a/internal/state/merkle/helpers_test.go +++ b/internal/state/merkle/helpers_test.go @@ -257,9 +257,8 @@ func RandomJudgements(t *testing.T) state.Judgements { } func RandomSafroleStateWithTicketBodies(t *testing.T) safrole.State { - sealingKeySeries := safrole.TicketsOrKeys{} - err := sealingKeySeries.SetValue(RandomTicketBodies(t)) - require.NoError(t, err) + sealingKeySeries := safrole.TicketAccumulator{} + sealingKeySeries.Set(RandomTicketBodies(t)) return safrole.State{ NextValidators: RandomValidatorsData(t), @@ -270,9 +269,8 @@ func RandomSafroleStateWithTicketBodies(t *testing.T) safrole.State { } func RandomSafroleStateWithEpochKeys(t *testing.T) safrole.State { - sealingKeySeries := safrole.TicketsOrKeys{} - err := sealingKeySeries.SetValue(RandomEpochKeys(t)) - require.NoError(t, err) + sealingKeySeries := safrole.TicketAccumulator{} + sealingKeySeries.Set(RandomEpochKeys(t)) return safrole.State{ NextValidators: RandomValidatorsData(t), diff --git a/internal/statetransition/state_transition.go b/internal/statetransition/state_transition.go index 34a37ea5..3b7b8290 100644 --- a/internal/statetransition/state_transition.go +++ b/internal/statetransition/state_transition.go @@ -636,20 +636,14 @@ func UpdateSafroleState( ticketAccumulatorFull { // Use tickets for sealing keys. Apply the Z function on the ticket accumulator. sealingTickets := safrole.OutsideInSequence(newValidatorState.SafroleState.TicketAccumulator) - err := newValidatorState.SafroleState.SealingKeySeries.SetValue(safrole.TicketsBodies(sealingTickets)) - if err != nil { - return entropyPool, validatorState, SafroleOutput{}, err - } + newValidatorState.SafroleState.SealingKeySeries.Set(safrole.TicketsBodies(sealingTickets)) } else { // Use bandersnatch keys for sealing keys. fallbackKeys, err := safrole.SelectFallbackKeys(newEntropyPool[2], newValidatorState.CurrentValidators) if err != nil { return entropyPool, validatorState, SafroleOutput{}, err } - err = newValidatorState.SafroleState.SealingKeySeries.SetValue(fallbackKeys) - if err != nil { - return entropyPool, validatorState, SafroleOutput{}, err - } + newValidatorState.SafroleState.SealingKeySeries.Set(fallbackKeys) } // Compute epoch marker (H_e). @@ -687,13 +681,20 @@ func calculateNewEntropyPool(currentTimeslot jamtime.Timeslot, newTimeslot jamti newEntropyPool := entropyPool if newTimeslot.ToEpoch() > currentTimeslot.ToEpoch() { - newEntropyPool = state.RotateEntropyPool(entropyPool) + newEntropyPool = rotateEntropyPool(entropyPool) } newEntropyPool[0] = crypto.HashData(append(entropyPool[0][:], entropyInput[:]...)) return newEntropyPool, nil } +func rotateEntropyPool(pool state.EntropyPool) state.EntropyPool { + pool[3] = pool[2] + pool[2] = pool[1] + pool[1] = pool[0] + return pool +} + // CalculateNewCoreAuthorizations implements equation 4.19: α' ≺ (H, EG, φ', α) . Graypaper 0.5.4 func CalculateNewCoreAuthorizations(header block.Header, guarantees block.GuaranteesExtrinsic, pendingAuthorizations state.PendingAuthorizersQueues, currentAuthorizations state.CoreAuthorizersPool) state.CoreAuthorizersPool { var newCoreAuthorizations state.CoreAuthorizersPool @@ -1798,7 +1799,7 @@ func CalculateWorkReportsAndAccumulate(header *block.Header, currentState *state slices.Concat( slices.Concat(currentState.AccumulationQueue[timeslotPerEpoch:]...), // ⋃(ϑm...) slices.Concat(currentState.AccumulationQueue[:timeslotPerEpoch]...), // ⋃(ϑ...m) - queuedWorkReports, // WQ + queuedWorkReports, // WQ ), getWorkPackageHashes(immediatelyAccWorkReports), // P(W!) ) @@ -2438,10 +2439,10 @@ func (a *Accumulator) ParallelDelta( workReports []block.WorkReport, privilegedGas map[block.ServiceId]uint64, // D⟨NS → NG⟩ ) ( - uint64, // total gas used - state.AccumulationState, // updated context + uint64, // total gas used + state.AccumulationState, // updated context []service.DeferredTransfer, // all transfers - ServiceHashPairs, // accumulation outputs + ServiceHashPairs, // accumulation outputs ) { // Get all unique service indices involved (s) // s = {rs | w ∈ w, r ∈ wr} ∪ K(f) @@ -2589,7 +2590,7 @@ func (a *Accumulator) Delta1( accumulationState state.AccumulationState, workReports []block.WorkReport, privilegedGas map[block.ServiceId]uint64, // D⟨NS → NG⟩ - serviceIndex block.ServiceId, // NS + serviceIndex block.ServiceId, // NS ) (state.AccumulationState, []service.DeferredTransfer, *crypto.Hash, uint64) { // Calculate gas limit (g) gasLimit := uint64(0) diff --git a/internal/validator/grid.go b/internal/validator/grid.go new file mode 100644 index 00000000..890b60b3 --- /dev/null +++ b/internal/validator/grid.go @@ -0,0 +1,221 @@ +package validator + +import ( + "crypto/ed25519" + "fmt" + "github.com/eigerco/strawberry/internal/common" + "github.com/eigerco/strawberry/internal/crypto" + "github.com/eigerco/strawberry/internal/safrole" + "math" +) + +// GridMapper manages the mapping between grid indices and validator information across epochs. +// It maintains data for three sets of validators: +// - Current validators: The active set in the current epoch +// - Archived validators: The set from the previous epoch +// - Queued validators: The set for the next epoch +// +// The grid structure arranges validators in a square-like grid where validators are considered +// neighbors if they share the same row or column. This structure is used primarily for block +// announcements and other network communications. +type GridMapper struct { + currentValidators safrole.ValidatorsData + archivedValidators safrole.ValidatorsData + queuedValidators safrole.ValidatorsData +} + +// NewGridMapper creates a new mapper instance using the provided validator state. +// The state must contain information about current, archived, and queued validators. +func NewGridMapper(state ValidatorState) GridMapper { + return GridMapper{ + currentValidators: state.CurrentValidators, + archivedValidators: state.ArchivedValidators, + queuedValidators: state.QueuedValidators, + } +} + +// GetAllEpochsNeighborValidators returns all neighbor validators across epochs for a given index. +// This includes: +// - All grid neighbors from the current epoch (same row or column) +// - The validator with the same index from the previous epoch +// - The validator with the same index from the next epoch +func (m GridMapper) GetAllEpochsNeighborValidators(index uint16) ([]*crypto.ValidatorKey, error) { + neighborsSameEpoch, err := m.GetCurrentEpochNeighborValidators(index) + if err != nil { + return nil, fmt.Errorf("failed to get current epoch neighbor validators: %w", err) + } + + // Initialize with capacity for same epoch neighbors plus potentially two more + neighbors := make([]*crypto.ValidatorKey, 0, len(neighborsSameEpoch)+2) + + // Add previous epoch validator if index exists + if index < uint16(len(m.archivedValidators)) { + neighbors = append(neighbors, m.archivedValidators[index]) + } + + // Add next epoch validator if index exists + if index < uint16(len(m.queuedValidators)) { + neighbors = append(neighbors, m.queuedValidators[index]) + } + + // Add current epoch neighbors + neighbors = append(neighbors, neighborsSameEpoch...) + + return neighbors, nil +} + +// GetCurrentEpochNeighborValidators returns all grid neighbors for a validator +// within the current epoch. Grid neighbors are validators that share either +// the same row or column in the grid structure. +func (m GridMapper) GetCurrentEpochNeighborValidators(index uint16) ([]*crypto.ValidatorKey, error) { + neighborIndices := getCurrentEpochNeighborIndices(index) + neighbors := make([]*crypto.ValidatorKey, 0, len(neighborIndices)) + + for _, idx := range neighborIndices { + neighbors = append(neighbors, m.currentValidators[idx]) + } + + return neighbors, nil +} + +// IsNeighbor determines if two validators are neighbors based on their public keys. +// Two validators are considered neighbors if either: +// - They are in the same epoch and share the same row or column in the grid +// - They are in different epochs but have the same grid index +// Parameters: +// - key1, key2: The Ed25519 public keys of the two validators +// - sameEpoch: Whether both validators are from the same epoch +func (m GridMapper) IsNeighbor(key1, key2 ed25519.PublicKey, sameEpoch bool) bool { + // Self-connections are not considered neighbors + if key1.Equal(key2) { + return false + } + + if sameEpoch { + idx1, found1 := m.FindValidatorIndex(key1) + idx2, found2 := m.FindValidatorIndex(key2) + + if !found1 || !found2 { + return false + } + return areGridNeighbors(idx1, idx2) + } + + // For different epochs, validators are neighbors if they have the same index + indices1 := m.getValidatorIndices(key1) + if len(indices1) == 0 { + return false // key1 not found in any epoch + } + + indices2 := m.getValidatorIndices(key2) + // Check if there are any matching indices between the two validators + for idx := range indices2 { + if indices1[idx] { + return true + } + } + + return false +} + +// FindValidatorIndex searches for a validator's grid index in the current validator set +// by their Ed25519 public key. Returns the index and true if found, or 0 and false if not found. +func (m GridMapper) FindValidatorIndex(key ed25519.PublicKey) (uint16, bool) { + return findValidatorIndexInSlice(m.currentValidators, key) +} + +// FindValidatorIndexInArchived searches for a validator's grid index in the previous epoch's +// validator set by their Ed25519 public key. Returns the index and true if found, +// or 0 and false if not found. +func (m GridMapper) FindValidatorIndexInArchived(key ed25519.PublicKey) (uint16, bool) { + return findValidatorIndexInSlice(m.archivedValidators, key) +} + +// FindValidatorIndexInQueued searches for a validator's grid index in the next epoch's +// validator set by their Ed25519 public key. Returns the index and true if found, +// or 0 and false if not found. +func (m GridMapper) FindValidatorIndexInQueued(key ed25519.PublicKey) (uint16, bool) { + return findValidatorIndexInSlice(m.queuedValidators, key) +} + +// getValidatorIndices finds all possible indices for a validator across epochs. +func (m GridMapper) getValidatorIndices(key ed25519.PublicKey) map[uint16]bool { + indices := make(map[uint16]bool) + + if idx, found := m.FindValidatorIndexInArchived(key); found { + indices[idx] = true + } + if idx, found := m.FindValidatorIndex(key); found { + indices[idx] = true + } + if idx, found := m.FindValidatorIndexInQueued(key); found { + indices[idx] = true + } + + return indices +} + +// findValidatorIndexInSlice searches for a validator's grid index +// in a given validator set by their Ed25519 public key. It returns the index and true if found, +// or 0 and false if not found. The index can be used to determine the validator's position +// in the grid structure. +func findValidatorIndexInSlice(validators safrole.ValidatorsData, key ed25519.PublicKey) (uint16, bool) { + for i, validator := range validators { + if validator != nil && ed25519.PublicKey.Equal(validator.Ed25519, key) { + return uint16(i), true + } + } + return 0, false +} + +// getCurrentEpochNeighborIndices returns all validator indices that are considered +// neighbors within the same epoch based on the grid structure. This includes all +// validators that share either: +// - The same row (index / gridWidth) +// - The same column (index % gridWidth) +// The returned slice excludes the input validatorIndex itself. +func getCurrentEpochNeighborIndices(validatorIndex uint16) []uint16 { + gridWidth := getGridWidth() + + // Pre-allocate with maximum possible capacity + // Maximum size is (gridWidth - 1) for row + (gridWidth - 1) for column + neighbors := make([]uint16, 0, 2*(gridWidth-1)) + + // Calculate row neighbors + rowStart := (validatorIndex / gridWidth) * gridWidth + rowEnd := min(rowStart+gridWidth, common.NumberOfValidators) + for i := rowStart; i < rowEnd; i++ { + if i != validatorIndex { + neighbors = append(neighbors, i) + } + } + + // Calculate column neighbors + for i := validatorIndex % gridWidth; i < common.NumberOfValidators; i += gridWidth { + if i != validatorIndex { + neighbors = append(neighbors, i) + } + } + + return neighbors +} + +// areGridNeighbors determines if two validators within the same epoch are neighbors +// in the grid structure by checking if they share the same row or column. +// The grid width is calculated as floor(sqrt(total_validators)). +func areGridNeighbors(validatorIndex1, validatorIndex2 uint16) bool { + gridWidth := getGridWidth() + row1, col1 := validatorIndex1/gridWidth, validatorIndex1%gridWidth + row2, col2 := validatorIndex2/gridWidth, validatorIndex2%gridWidth + + return row1 == row2 || col1 == col2 +} + +// getGridWidth calculates the width of the validator grid. +// The grid is arranged as a square-like structure with width = floor(sqrt(number_of_validators)). +// This ensures the grid dimensions are as close to square as possible while accommodating +// all validators. +func getGridWidth() uint16 { + // floor(sqrt(numValidators)) + return uint16(math.Floor(math.Sqrt(float64(common.NumberOfValidators)))) +} diff --git a/internal/validator/grid_test.go b/internal/validator/grid_test.go new file mode 100644 index 00000000..6222d924 --- /dev/null +++ b/internal/validator/grid_test.go @@ -0,0 +1,166 @@ +package validator + +import ( + "crypto/ed25519" + "math" + "testing" + + "github.com/eigerco/strawberry/internal/common" + "github.com/eigerco/strawberry/internal/crypto" + "github.com/eigerco/strawberry/internal/safrole" + "github.com/stretchr/testify/assert" +) + +func TestNewGridMapper(t *testing.T) { + state := ValidatorState{ + CurrentValidators: safrole.ValidatorsData{}, + ArchivedValidators: safrole.ValidatorsData{}, + QueuedValidators: safrole.ValidatorsData{}, + } + mapper := NewGridMapper(state) + + assert.Equal(t, state.CurrentValidators, mapper.currentValidators) + assert.Equal(t, state.ArchivedValidators, mapper.archivedValidators) + assert.Equal(t, state.QueuedValidators, mapper.queuedValidators) +} + +func TestGetAllEpochsNeighborValidators(t *testing.T) { + validators := safrole.ValidatorsData{} + mapper := GridMapper{ + currentValidators: validators, + archivedValidators: validators, + queuedValidators: validators, + } + + neighbors, err := mapper.GetAllEpochsNeighborValidators(0) + assert.NoError(t, err) + assert.Len(t, neighbors, 64) //62 neighbors + 1 archived + 1 queued +} + +func TestFindValidatorIndex(t *testing.T) { + key := ed25519.PublicKey("key") + validators := safrole.ValidatorsData{} + validators[42] = &crypto.ValidatorKey{Ed25519: key} + mapper := GridMapper{currentValidators: validators} + index, found := mapper.FindValidatorIndex(key) + assert.True(t, found) + assert.Equal(t, uint16(42), index) + + _, found = mapper.FindValidatorIndex(ed25519.PublicKey("missing")) + assert.False(t, found) +} + +func TestIsNeighbor(t *testing.T) { + // Setup basic validator data structures + currentValidators := safrole.ValidatorsData{} + archivedValidators := safrole.ValidatorsData{} + queuedValidators := safrole.ValidatorsData{} + + // Create test keys + key1 := ed25519.PublicKey("key1") + key2 := ed25519.PublicKey("key2") + key3 := ed25519.PublicKey("key3") + key4 := ed25519.PublicKey("key4") + key5 := ed25519.PublicKey("key5") + keyNotValidator := ed25519.PublicKey("notvalidator") + + // Calculate grid width based on total validator count + gridWidth := uint16(math.Floor(math.Sqrt(float64(common.NumberOfValidators)))) + + // Setup indices for different test scenarios + sameRowIdx1 := uint16(0) + sameRowIdx2 := uint16(1) + sameColIdx2 := gridWidth + differentIdx := gridWidth + 1 + crossEpochIdx := uint16(42) + + // Setup validators in current epoch + currentValidators[sameRowIdx1] = &crypto.ValidatorKey{Ed25519: key1} + currentValidators[sameRowIdx2] = &crypto.ValidatorKey{Ed25519: key2} + currentValidators[sameColIdx2] = &crypto.ValidatorKey{Ed25519: key3} + currentValidators[differentIdx] = &crypto.ValidatorKey{Ed25519: key4} + currentValidators[crossEpochIdx] = &crypto.ValidatorKey{Ed25519: key5} + + // Setup validators in archived and queued epochs + archivedValidators[crossEpochIdx] = &crypto.ValidatorKey{Ed25519: key1} + queuedValidators[crossEpochIdx] = &crypto.ValidatorKey{Ed25519: key2} + + mapper := GridMapper{ + currentValidators: currentValidators, + archivedValidators: archivedValidators, + queuedValidators: queuedValidators, + } + + tests := []struct { + name string + key1 ed25519.PublicKey + key2 ed25519.PublicKey + sameEpoch bool + want bool + reason string + }{ + { + name: "same epoch - same row", + key1: key1, + key2: key2, + sameEpoch: true, + want: true, + reason: "validators in same row should be neighbors", + }, + { + name: "same epoch - same column", + key1: key1, + key2: key3, + sameEpoch: true, + want: true, + reason: "validators in same column should be neighbors", + }, + { + name: "same epoch - different row and column", + key1: key1, + key2: key4, + sameEpoch: true, + want: false, + reason: "validators in different rows and columns should not be neighbors", + }, + { + name: "same epoch - self connection", + key1: key1, + key2: key1, + sameEpoch: true, + want: false, + reason: "validator should not be neighbor with itself", + }, + { + name: "different epochs - same index", + key1: key1, + key2: key2, + sameEpoch: false, + want: true, + reason: "validators with same index in different epochs should be neighbors", + }, + { + name: "different epochs - self connection", + key1: key1, + key2: key1, + sameEpoch: false, + want: false, + reason: "validator should not be neighbor with itself across epochs", + }, + { + name: "non-validator key", + key1: key1, + key2: keyNotValidator, + sameEpoch: true, + want: false, + reason: "non-validator key should not be neighbor with any validator", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := mapper.IsNeighbor(tt.key1, tt.key2, tt.sameEpoch) + assert.Equal(t, tt.want, got, tt.reason) + }) + } +} diff --git a/internal/validator/validator_test_utils.go b/internal/validator/validator_test_utils.go index 2bae9a26..f3f219d0 100644 --- a/internal/validator/validator_test_utils.go +++ b/internal/validator/validator_test_utils.go @@ -8,7 +8,6 @@ import ( "github.com/eigerco/strawberry/internal/jamtime" "github.com/eigerco/strawberry/internal/safrole" "github.com/eigerco/strawberry/internal/testutils" - "github.com/stretchr/testify/require" ) func SetupValidatorState(t *testing.T) *ValidatorState { @@ -22,14 +21,13 @@ func SetupValidatorState(t *testing.T) *ValidatorState { for i := 0; i < jamtime.TimeslotsPerEpoch; i++ { safroleState.TicketAccumulator[i] = RandomTicket(t) } - safroleState.SealingKeySeries = safrole.TicketsOrKeys{} + safroleState.SealingKeySeries = safrole.TicketAccumulator{} var epochKeys crypto.EpochKeys for i := 0; i < jamtime.TimeslotsPerEpoch; i++ { epochKeys[i] = testutils.RandomBandersnatchPublicKey(t) } - err := safroleState.SealingKeySeries.SetValue(epochKeys) + safroleState.SealingKeySeries.Set(epochKeys) validatorState.SafroleState = safroleState - require.NoError(t, err) return &validatorState } diff --git a/tests/integration/safrole_integration_test.go b/tests/integration/safrole_integration_test.go index ed4a0fe8..50899758 100644 --- a/tests/integration/safrole_integration_test.go +++ b/tests/integration/safrole_integration_test.go @@ -236,7 +236,7 @@ func toValidatorState(t *testing.T, s SafroleTestVectorState) validator.Validato } } - ticketOrKeys := safrole.TicketsOrKeys{} + ticketOrKeys := safrole.TicketAccumulator{} if len(s.GammaS.Keys) > 0 { keys := crypto.EpochKeys{} for i, k := range s.GammaS.Keys { diff --git a/tests/integration/vectors_community/sealing/0-0.json b/tests/integration/vectors_community/sealing/0-0.json new file mode 100644 index 00000000..b68302d1 --- /dev/null +++ b/tests/integration/vectors_community/sealing/0-0.json @@ -0,0 +1,15 @@ +{ + "bandersnatch_pub": "5e465beb01dbafe160ce8216047f2155dd0569f058afd52dcea601025a8d161d", + "bandersnatch_priv": "51c1537c18eea5c5969cb2ae45c1224cc245de5c5b8e6e25f48fb99f2786ee05", + "ticket_id": "", + "attempt": 0, + "c_for_H_s": "6a616d5f66616c6c6261636b5f7365616cd2d34655ebcad804c56d2fd5f932c575b6a5dbb3f5652c5202bcc75ab9c2cc95", + "m_for_H_s": "05972fdfcb50d912a5a163f8312f6dd56abcb8bcf521fe22f041b194d402f54ebc3920e20ba56486551e0200250a6368acb126b2b4581164510cdf87fe55d37aceff6a4b534f0ea94ece71c71561da44715d1f5d2a2c9f229d868b106af7bf2939d5530000000000004c9968e133a48e16c7edeb1df282de7d68705c99b2c18728d05893e64c4c7d119088ca51bd712615eb5c168d47ebf80c2e9bc01b9342a9f0e07775beb61f9e1b6f4bd0f56cf69d8e1cc37f1fdf36f6d7d13d74fbd5d78206dcde95ae7ce29c1b", + "H_s": "290cf0944d46ccf7b7c6e9dbe5eb2c9e4fef2f2c85f050b1b40571ed3a2c38129f0f85f76a44099160434b155f102a94ff22578ebc7188b8e4325a3bcecd6504d8062427fb00eb22f24645ae094d6debb802df03856025426af6806f1e215a00", + "c_for_H_v": "6a616d5f656e74726f7079cc99f285992311c507be6ecabe4786c505532034e5dfe6adb1092732635f4a56", + "m_for_H_v": "", + "H_v": "4c9968e133a48e16c7edeb1df282de7d68705c99b2c18728d05893e64c4c7d119088ca51bd712615eb5c168d47ebf80c2e9bc01b9342a9f0e07775beb61f9e1b6f4bd0f56cf69d8e1cc37f1fdf36f6d7d13d74fbd5d78206dcde95ae7ce29c1b", + "eta3": "d2d34655ebcad804c56d2fd5f932c575b6a5dbb3f5652c5202bcc75ab9c2cc95", + "T": 0, + "header_bytes": "05972fdfcb50d912a5a163f8312f6dd56abcb8bcf521fe22f041b194d402f54ebc3920e20ba56486551e0200250a6368acb126b2b4581164510cdf87fe55d37aceff6a4b534f0ea94ece71c71561da44715d1f5d2a2c9f229d868b106af7bf2939d5530000000000004c9968e133a48e16c7edeb1df282de7d68705c99b2c18728d05893e64c4c7d119088ca51bd712615eb5c168d47ebf80c2e9bc01b9342a9f0e07775beb61f9e1b6f4bd0f56cf69d8e1cc37f1fdf36f6d7d13d74fbd5d78206dcde95ae7ce29c1b290cf0944d46ccf7b7c6e9dbe5eb2c9e4fef2f2c85f050b1b40571ed3a2c38129f0f85f76a44099160434b155f102a94ff22578ebc7188b8e4325a3bcecd6504d8062427fb00eb22f24645ae094d6debb802df03856025426af6806f1e215a00" +} \ No newline at end of file diff --git a/tests/integration/vectors_community/sealing/0-1.json b/tests/integration/vectors_community/sealing/0-1.json new file mode 100644 index 00000000..312e7477 --- /dev/null +++ b/tests/integration/vectors_community/sealing/0-1.json @@ -0,0 +1,15 @@ +{ + "bandersnatch_pub": "3d5e5a51aab2b048f8686ecd79712a80e3265a114cc73f14bdb2a59233fb66d0", + "bandersnatch_priv": "9e84e7bb7c172ba7c0549f495b2412ce7d9d862719d5de4db97bacd97b60b505", + "ticket_id": "", + "attempt": 0, + "c_for_H_s": "6a616d5f66616c6c6261636b5f7365616cd2d34655ebcad804c56d2fd5f932c575b6a5dbb3f5652c5202bcc75ab9c2cc95", + "m_for_H_s": "a86ce58e12c56c24bf0780953eb4b2ffa527108e5b260d920cdec7935a2a0f91442efd52d3b55a7986d1a50d8d9a57262288afbf8560db12e253360564c93aa42d331708f0125013591dd6332db3c4df0833b3ab34ff94cde5039f4992480d4a3ed5530000010b7537993b0a700def26bb16e99ed0bfb530f616e4c13cf63ecb60bcbe83387d0271dd32fb8a1580b4aa3213c3616d8fbbcb9edc00467c4e4548ff8a1fd815811c021912baa74049a4cad89dc3f0646144459b691b926cf8b9c1c4a5bbfa1ee0c331016b5cc620ed50042cd517ec8267706c82482f07ebcb3c65bfb6288ef5984141a70122fdcfa858e5195e222174597d7d33bd66d97748c413b876f7a132134ce9baef00665df13fd353ffe92e9bd68ae952f4511681f04bd2ffb9a6da1b1f5f706c53ec0223bd628fd365a0f3ecd10db746dd04ec5efe61f96da19ae070c44b97d3c9a7b80239f7d99b86f90cada4aa3b08adfe310024813fca0bdcdff944873a2cc2e470740131d6a25525ff4bd6e47e611646d7b5835b94b5c0a69c225371b2b762c93095a20139e2d23807ff3788156eac40cc0a622a9fd23e9468bf962aebe48079c0fd2f1a0031e9b8070f42d7c9083eca5879e5528191259a395761b8fcc068dcdd36b06be40139120d5b82981c7f5aba8247925f358afb9539839b61602a0726f51efb35ef4c000001002f1fe0ca6b2b22211b877f7d6461873a4569cbd4a12d6ca1b1c85c1076eec8069d89baca103a4c42d52d21a19149815a0ef70c6c2517ddc4fbd927ff62da1011ce892c0edc10f1d3d5f1ce24ef678efc6d924bc446c9a4d334f1031a6f5ff908", + "H_s": "c7d288c6504d16221e95e01944d05688cb2c69678cafa72e03b7f1df16561701e2df6c3c38a6da42ecf5ba74d90e30a68d1ac850db2fc67ead8dc26440e8eb1209c4ea3198004a080719948274962c95cd9d767c127c6b8893719c1c2388900e", + "c_for_H_v": "6a616d5f656e74726f7079eb6b6e77c6c7f0f71edf541e37978e1bdfc50a74c27c13d385f0b26a25cf6979", + "m_for_H_v": "", + "H_v": "2f1fe0ca6b2b22211b877f7d6461873a4569cbd4a12d6ca1b1c85c1076eec8069d89baca103a4c42d52d21a19149815a0ef70c6c2517ddc4fbd927ff62da1011ce892c0edc10f1d3d5f1ce24ef678efc6d924bc446c9a4d334f1031a6f5ff908", + "eta3": "d2d34655ebcad804c56d2fd5f932c575b6a5dbb3f5652c5202bcc75ab9c2cc95", + "T": 0, + "header_bytes": "a86ce58e12c56c24bf0780953eb4b2ffa527108e5b260d920cdec7935a2a0f91442efd52d3b55a7986d1a50d8d9a57262288afbf8560db12e253360564c93aa42d331708f0125013591dd6332db3c4df0833b3ab34ff94cde5039f4992480d4a3ed5530000010b7537993b0a700def26bb16e99ed0bfb530f616e4c13cf63ecb60bcbe83387d0271dd32fb8a1580b4aa3213c3616d8fbbcb9edc00467c4e4548ff8a1fd815811c021912baa74049a4cad89dc3f0646144459b691b926cf8b9c1c4a5bbfa1ee0c331016b5cc620ed50042cd517ec8267706c82482f07ebcb3c65bfb6288ef5984141a70122fdcfa858e5195e222174597d7d33bd66d97748c413b876f7a132134ce9baef00665df13fd353ffe92e9bd68ae952f4511681f04bd2ffb9a6da1b1f5f706c53ec0223bd628fd365a0f3ecd10db746dd04ec5efe61f96da19ae070c44b97d3c9a7b80239f7d99b86f90cada4aa3b08adfe310024813fca0bdcdff944873a2cc2e470740131d6a25525ff4bd6e47e611646d7b5835b94b5c0a69c225371b2b762c93095a20139e2d23807ff3788156eac40cc0a622a9fd23e9468bf962aebe48079c0fd2f1a0031e9b8070f42d7c9083eca5879e5528191259a395761b8fcc068dcdd36b06be40139120d5b82981c7f5aba8247925f358afb9539839b61602a0726f51efb35ef4c000001002f1fe0ca6b2b22211b877f7d6461873a4569cbd4a12d6ca1b1c85c1076eec8069d89baca103a4c42d52d21a19149815a0ef70c6c2517ddc4fbd927ff62da1011ce892c0edc10f1d3d5f1ce24ef678efc6d924bc446c9a4d334f1031a6f5ff908c7d288c6504d16221e95e01944d05688cb2c69678cafa72e03b7f1df16561701e2df6c3c38a6da42ecf5ba74d90e30a68d1ac850db2fc67ead8dc26440e8eb1209c4ea3198004a080719948274962c95cd9d767c127c6b8893719c1c2388900e" +} \ No newline at end of file diff --git a/tests/integration/vectors_community/sealing/0-2.json b/tests/integration/vectors_community/sealing/0-2.json new file mode 100644 index 00000000..cf4e295b --- /dev/null +++ b/tests/integration/vectors_community/sealing/0-2.json @@ -0,0 +1,15 @@ +{ + "bandersnatch_pub": "aa2b95f7572875b0d0f186552ae745ba8222fc0b5bd456554bfe51c68938f8bc", + "bandersnatch_priv": "91ebd09c591e41858a7a2a45c671642708f546c163b76eef0991b755017e7412", + "ticket_id": "", + "attempt": 0, + "c_for_H_s": "6a616d5f66616c6c6261636b5f7365616cd2d34655ebcad804c56d2fd5f932c575b6a5dbb3f5652c5202bcc75ab9c2cc95", + "m_for_H_s": "5e2ee0bf241084db016f3f255f7346f316968b66100e58652af9214eb13aa1439e304d9b7f93686a9e0e12515e893966d63d6ebbd017655e0f90ac205d65ad0afa67b99f5cad6d886fd1017422dedb9ca466b61bdff6ac7a501afee61a5bcb893dd5530000000002001ef2d3fd951a6ca263cf407c86d5221437bfb8c23ccb39edf6f742c3145c2557490efd490bf82795f480d6242edec61333a46b9ca72e994cb0b4665723bba507b6bd807b7cb62d50af87d45177ddcb9171505b09f834d2bd8ddabb8d1c8e2803", + "H_s": "034c0e655f644fb109dbcd0050ca49180e2a5bd3b9a95b88626732ce05dbfcccc95a11bc000db6d56c358f5699e83118474d2e9f62ff35e27729e50e4d639911675e39fc966bfb3a86ea3240d53f94ecd301e9a1a99b01946cc2145426ce1201", + "c_for_H_v": "6a616d5f656e74726f70791e7790256687f89105875dd990b6f935266d9b8576e812e052809c5ea0241190", + "m_for_H_v": "", + "H_v": "1ef2d3fd951a6ca263cf407c86d5221437bfb8c23ccb39edf6f742c3145c2557490efd490bf82795f480d6242edec61333a46b9ca72e994cb0b4665723bba507b6bd807b7cb62d50af87d45177ddcb9171505b09f834d2bd8ddabb8d1c8e2803", + "eta3": "d2d34655ebcad804c56d2fd5f932c575b6a5dbb3f5652c5202bcc75ab9c2cc95", + "T": 0, + "header_bytes": "5e2ee0bf241084db016f3f255f7346f316968b66100e58652af9214eb13aa1439e304d9b7f93686a9e0e12515e893966d63d6ebbd017655e0f90ac205d65ad0afa67b99f5cad6d886fd1017422dedb9ca466b61bdff6ac7a501afee61a5bcb893dd5530000000002001ef2d3fd951a6ca263cf407c86d5221437bfb8c23ccb39edf6f742c3145c2557490efd490bf82795f480d6242edec61333a46b9ca72e994cb0b4665723bba507b6bd807b7cb62d50af87d45177ddcb9171505b09f834d2bd8ddabb8d1c8e2803034c0e655f644fb109dbcd0050ca49180e2a5bd3b9a95b88626732ce05dbfcccc95a11bc000db6d56c358f5699e83118474d2e9f62ff35e27729e50e4d639911675e39fc966bfb3a86ea3240d53f94ecd301e9a1a99b01946cc2145426ce1201" +} \ No newline at end of file diff --git a/tests/integration/vectors_community/sealing/0-4.json b/tests/integration/vectors_community/sealing/0-4.json new file mode 100644 index 00000000..e2affa49 --- /dev/null +++ b/tests/integration/vectors_community/sealing/0-4.json @@ -0,0 +1,15 @@ +{ + "bandersnatch_pub": "48e5fcdce10e0b64ec4eebd0d9211c7bac2f27ce54bca6f7776ff6fee86ab3e3", + "bandersnatch_priv": "0dea7844b6b937f8b00acea90a8ce9dfe07fdbd2a1e4ff09022340d9bb159911", + "ticket_id": "", + "attempt": 0, + "c_for_H_s": "6a616d5f66616c6c6261636b5f7365616cd2d34655ebcad804c56d2fd5f932c575b6a5dbb3f5652c5202bcc75ab9c2cc95", + "m_for_H_s": "f8087d9e57dd9031c3250aaca9b32077200ffb9d697a3d5d44641136faabd31be4a0385d94a923e928a5d94b71b6e2fbd1ffe2cf8292d5487feeb858dbe125afc2207ff68bcdb079047d9913f72ff9d636027d586669f67de2a1e3e5e70f7df53fd5530000000004004b213bfc74f65eb109896f1d57e78809d1a94c0c1b2e4543a9ee470eb6cfdfee96228bd01847dbe9e92c5c8c190fab85da4cb5ecd63cd3c758730b17b1247d1be6a5107ff246b08fbf8dcad39ba00b33e9ee4e2b934f62ee7e503e2a1eeaba11", + "H_s": "a060e079fdeefc27d1278b9a3d1922874c87e8d0dc7885d08443a29a460af827a8c70a845c6ca5528517e323ff251abdc152d465566fc2d01e4d1dc8c56ddb1cc9f0a94a989e5a5ba710f0c0c3530068d77ffa8ca1409aaf1e816e4e67983b07", + "c_for_H_v": "6a616d5f656e74726f70791286e739f65659311c7f3380ec9afad3560bc2971a0ea1d0acc961d79c0cf4c4", + "m_for_H_v": "", + "H_v": "4b213bfc74f65eb109896f1d57e78809d1a94c0c1b2e4543a9ee470eb6cfdfee96228bd01847dbe9e92c5c8c190fab85da4cb5ecd63cd3c758730b17b1247d1be6a5107ff246b08fbf8dcad39ba00b33e9ee4e2b934f62ee7e503e2a1eeaba11", + "eta3": "d2d34655ebcad804c56d2fd5f932c575b6a5dbb3f5652c5202bcc75ab9c2cc95", + "T": 0, + "header_bytes": "f8087d9e57dd9031c3250aaca9b32077200ffb9d697a3d5d44641136faabd31be4a0385d94a923e928a5d94b71b6e2fbd1ffe2cf8292d5487feeb858dbe125afc2207ff68bcdb079047d9913f72ff9d636027d586669f67de2a1e3e5e70f7df53fd5530000000004004b213bfc74f65eb109896f1d57e78809d1a94c0c1b2e4543a9ee470eb6cfdfee96228bd01847dbe9e92c5c8c190fab85da4cb5ecd63cd3c758730b17b1247d1be6a5107ff246b08fbf8dcad39ba00b33e9ee4e2b934f62ee7e503e2a1eeaba11a060e079fdeefc27d1278b9a3d1922874c87e8d0dc7885d08443a29a460af827a8c70a845c6ca5528517e323ff251abdc152d465566fc2d01e4d1dc8c56ddb1cc9f0a94a989e5a5ba710f0c0c3530068d77ffa8ca1409aaf1e816e4e67983b07" +} \ No newline at end of file diff --git a/tests/integration/vectors_community/sealing/0-5.json b/tests/integration/vectors_community/sealing/0-5.json new file mode 100644 index 00000000..49c75956 --- /dev/null +++ b/tests/integration/vectors_community/sealing/0-5.json @@ -0,0 +1,15 @@ +{ + "bandersnatch_pub": "f16e5352840afb47e206b5c89f560f2611835855cf2e6ebad1acc9520a72591d", + "bandersnatch_priv": "68f5494ec1c3d3cd8ff2a3cb285abf0e826b2c762d95fc2e953eaef666315403", + "ticket_id": "", + "attempt": 0, + "c_for_H_s": "6a616d5f66616c6c6261636b5f7365616cd2d34655ebcad804c56d2fd5f932c575b6a5dbb3f5652c5202bcc75ab9c2cc95", + "m_for_H_s": "76f7735094beca03f505943364ab334cf7e3478232bcb9e459d81758aecce1c50e95e29322af205aefb7c307ea38c89de0bef71b33bfe225aa09b8798626ff1067b5a7ca8605e0378fcb7e5adf80293ff4746f4d9aa97b15347db4d1f7e79f643ad5530000000005002e055be9247c579fe4d77fcc2286479bd2ea298da524910c0b061837e21f8d3d6b705fc48b6cf9c5592b3c284105577c89255035d26b33049e659d4ae37254049e9d1d9138c03fa43b294953b0f8ee58ea88ccf1182d6bac6964136efe8e750a", + "H_s": "efa769875579297f4257473e52e46425fedfa880f9b2e7bd50299a852c31f9c3af270626d39e4a8b9e48d9671280e8e89a94a7c48243cc667154a6b2e51dcf00376bd751bd8e544d0f3fcb4c9accfc8822e483f8f96ec92971576cf1af2ebb11", + "c_for_H_v": "6a616d5f656e74726f70790aa894a884b850fa0e85474fa2f1184468f78322c28f835b43c0ac336341e895", + "m_for_H_v": "", + "H_v": "2e055be9247c579fe4d77fcc2286479bd2ea298da524910c0b061837e21f8d3d6b705fc48b6cf9c5592b3c284105577c89255035d26b33049e659d4ae37254049e9d1d9138c03fa43b294953b0f8ee58ea88ccf1182d6bac6964136efe8e750a", + "eta3": "d2d34655ebcad804c56d2fd5f932c575b6a5dbb3f5652c5202bcc75ab9c2cc95", + "T": 0, + "header_bytes": "76f7735094beca03f505943364ab334cf7e3478232bcb9e459d81758aecce1c50e95e29322af205aefb7c307ea38c89de0bef71b33bfe225aa09b8798626ff1067b5a7ca8605e0378fcb7e5adf80293ff4746f4d9aa97b15347db4d1f7e79f643ad5530000000005002e055be9247c579fe4d77fcc2286479bd2ea298da524910c0b061837e21f8d3d6b705fc48b6cf9c5592b3c284105577c89255035d26b33049e659d4ae37254049e9d1d9138c03fa43b294953b0f8ee58ea88ccf1182d6bac6964136efe8e750aefa769875579297f4257473e52e46425fedfa880f9b2e7bd50299a852c31f9c3af270626d39e4a8b9e48d9671280e8e89a94a7c48243cc667154a6b2e51dcf00376bd751bd8e544d0f3fcb4c9accfc8822e483f8f96ec92971576cf1af2ebb11" +} \ No newline at end of file diff --git a/tests/integration/vectors_community/sealing/1-0.json b/tests/integration/vectors_community/sealing/1-0.json new file mode 100644 index 00000000..4f297825 --- /dev/null +++ b/tests/integration/vectors_community/sealing/1-0.json @@ -0,0 +1,15 @@ +{ + "bandersnatch_pub": "5e465beb01dbafe160ce8216047f2155dd0569f058afd52dcea601025a8d161d", + "bandersnatch_priv": "51c1537c18eea5c5969cb2ae45c1224cc245de5c5b8e6e25f48fb99f2786ee05", + "ticket_id": "0x8e05a5bd0756550a156418a51bacb7a1b67d29f76d6ca5861fa3120ae9eecffc", + "attempt": 1, + "c_for_H_s": "6a616d5f7469636b65745f7365616c6f6ad2224d7d58aec6573c623ab110700eaca20a48dc2965d535e466d524af2a01", + "m_for_H_s": "c55e6c8d35e024e44b6b1a03e9bbe5d37458584039193c90e1a17ee6b10c17099adea854e17f00a35331adaec9379a896094090e7f658c0ab22f2d03b6bd40128d9a95aac99649e9a76ec5ba9bc7bbd92fda3d6cb184f33d8998d6e934a548e24dd553000000000000023b7c0ccad82e20eb02002ed27ee17fb18e6d1df22799a4d119aa9be09bac2fb0623d626b81049378ab1bb7cb7eb459a6b05e2883b7be8a94287aebefe31c009864e70c7a6304ecdc0aa6130b2f17221b9571f7c7bbcde59d8095e27a79d50f", + "H_s": "389fde27d99986f4a3130fa4733f29f0a0ab13c125ef75f85471b11f15496068e6bc5a0a103ace2e1ee988ca214c44550f68853f483da542f643aea102edbc06f3f9fadb1621afbf76d45d7c20d7e3009a1dc083b8ca29f4bc4a29b95238c108", + "c_for_H_v": "6a616d5f656e74726f70798e05a5bd0756550a156418a51bacb7a1b67d29f76d6ca5861fa3120ae9eecffc", + "m_for_H_v": "", + "H_v": "023b7c0ccad82e20eb02002ed27ee17fb18e6d1df22799a4d119aa9be09bac2fb0623d626b81049378ab1bb7cb7eb459a6b05e2883b7be8a94287aebefe31c009864e70c7a6304ecdc0aa6130b2f17221b9571f7c7bbcde59d8095e27a79d50f", + "eta3": "6f6ad2224d7d58aec6573c623ab110700eaca20a48dc2965d535e466d524af2a", + "T": 1, + "header_bytes": "c55e6c8d35e024e44b6b1a03e9bbe5d37458584039193c90e1a17ee6b10c17099adea854e17f00a35331adaec9379a896094090e7f658c0ab22f2d03b6bd40128d9a95aac99649e9a76ec5ba9bc7bbd92fda3d6cb184f33d8998d6e934a548e24dd553000000000000023b7c0ccad82e20eb02002ed27ee17fb18e6d1df22799a4d119aa9be09bac2fb0623d626b81049378ab1bb7cb7eb459a6b05e2883b7be8a94287aebefe31c009864e70c7a6304ecdc0aa6130b2f17221b9571f7c7bbcde59d8095e27a79d50f389fde27d99986f4a3130fa4733f29f0a0ab13c125ef75f85471b11f15496068e6bc5a0a103ace2e1ee988ca214c44550f68853f483da542f643aea102edbc06f3f9fadb1621afbf76d45d7c20d7e3009a1dc083b8ca29f4bc4a29b95238c108" +} \ No newline at end of file diff --git a/tests/integration/vectors_community/sealing/1-1.json b/tests/integration/vectors_community/sealing/1-1.json new file mode 100644 index 00000000..84a35088 --- /dev/null +++ b/tests/integration/vectors_community/sealing/1-1.json @@ -0,0 +1,15 @@ +{ + "bandersnatch_pub": "3d5e5a51aab2b048f8686ecd79712a80e3265a114cc73f14bdb2a59233fb66d0", + "bandersnatch_priv": "9e84e7bb7c172ba7c0549f495b2412ce7d9d862719d5de4db97bacd97b60b505", + "ticket_id": "0x39e2d23807ff3788156eac40cc0a622a9fd23e9468bf962aebe48079c0fd2f1a", + "attempt": 0, + "c_for_H_s": "6a616d5f7469636b65745f7365616c835ac82bfa2ce8390bb50680d4b7a73dfa2a4cff6d8c30694b24a605f9574eaf00", + "m_for_H_s": "2ac6b444e1b8d69c07741bec9424985bedd9e1b05250245198dadab9ecbc26ea22e3d9b6f842692f06a25336bfb5cce3294c1dd36b876c92d74dc5d91589aa52593218a40935e051e65557a4822d474153737b2d5cdb631b3c7f86e0ff190fa649d55300000000010036e8eaca09372bd521b00095e75a04e19ba0cb6d9b3df429a5e304e9b69877225b4418285cca4741ec9fd6e1bea416cbc40cdcd804827e3acef32ccda278b50026280f00cbb1816b7478d78830a56b3df9b8f43eb6255b7a4ff16a4d2c077207", + "H_s": "926c00e70593a45318ddaec13a84262dbd56d8bf1e24982f221115e4575721455d093f458296560ffc25f82e24f76423e65026fcc500a143aca66fe14539201398e59658991ee46d30af330e7c751822e50944bafd347348be5a93c83bf14018", + "c_for_H_v": "6a616d5f656e74726f707939e2d23807ff3788156eac40cc0a622a9fd23e9468bf962aebe48079c0fd2f1a", + "m_for_H_v": "", + "H_v": "36e8eaca09372bd521b00095e75a04e19ba0cb6d9b3df429a5e304e9b69877225b4418285cca4741ec9fd6e1bea416cbc40cdcd804827e3acef32ccda278b50026280f00cbb1816b7478d78830a56b3df9b8f43eb6255b7a4ff16a4d2c077207", + "eta3": "835ac82bfa2ce8390bb50680d4b7a73dfa2a4cff6d8c30694b24a605f9574eaf", + "T": 1, + "header_bytes": "2ac6b444e1b8d69c07741bec9424985bedd9e1b05250245198dadab9ecbc26ea22e3d9b6f842692f06a25336bfb5cce3294c1dd36b876c92d74dc5d91589aa52593218a40935e051e65557a4822d474153737b2d5cdb631b3c7f86e0ff190fa649d55300000000010036e8eaca09372bd521b00095e75a04e19ba0cb6d9b3df429a5e304e9b69877225b4418285cca4741ec9fd6e1bea416cbc40cdcd804827e3acef32ccda278b50026280f00cbb1816b7478d78830a56b3df9b8f43eb6255b7a4ff16a4d2c077207926c00e70593a45318ddaec13a84262dbd56d8bf1e24982f221115e4575721455d093f458296560ffc25f82e24f76423e65026fcc500a143aca66fe14539201398e59658991ee46d30af330e7c751822e50944bafd347348be5a93c83bf14018" +} \ No newline at end of file diff --git a/tests/integration/vectors_community/sealing/1-2.json b/tests/integration/vectors_community/sealing/1-2.json new file mode 100644 index 00000000..75d5bb48 --- /dev/null +++ b/tests/integration/vectors_community/sealing/1-2.json @@ -0,0 +1,15 @@ +{ + "bandersnatch_pub": "aa2b95f7572875b0d0f186552ae745ba8222fc0b5bd456554bfe51c68938f8bc", + "bandersnatch_priv": "91ebd09c591e41858a7a2a45c671642708f546c163b76eef0991b755017e7412", + "ticket_id": "0x7aaf3347df9ea60a9a9977680a4c1bc59a1635a5d4525aff04be5b24d55c26e0", + "attempt": 1, + "c_for_H_s": "6a616d5f7469636b65745f7365616c6f6ad2224d7d58aec6573c623ab110700eaca20a48dc2965d535e466d524af2a01", + "m_for_H_s": "32077c68f5a8c7ddb861bc7c7f7729e0ed5f68259b7d2fa766540970f6e51bc7914d51b33223f0e5a38f529193603bf0eaee0609e45f83312db70a644c817650622dc68065b00bb763f4b47499fdb714e215610622651e009c9e4576c47ccb3d4fd553000000000200a81d92d83542ddfe0f34a81ce306ad3032740a75c14c6e99890690caa647986bf0b815a3ca5543ad24fe67d723a9180266ff65dcc3fc4824f9d688ab1e528301c76c5290d655cc1d1ad079ce696b5a9368241e2f0d16bc5cd413830f5c4c100a", + "H_s": "a70cd5b81c52bb33540d75a2c68ba482f54758f347d1ef97b6f4f9708199f51c65bf730390ac46c4dc41e7842da6130fa86fdc5f72c49280737ca1af3bdc4f033da227994bcad21b05b44bf955fc0dc84312bae771c4f9f85ef699fa5380520e", + "c_for_H_v": "6a616d5f656e74726f70797aaf3347df9ea60a9a9977680a4c1bc59a1635a5d4525aff04be5b24d55c26e0", + "m_for_H_v": "", + "H_v": "a81d92d83542ddfe0f34a81ce306ad3032740a75c14c6e99890690caa647986bf0b815a3ca5543ad24fe67d723a9180266ff65dcc3fc4824f9d688ab1e528301c76c5290d655cc1d1ad079ce696b5a9368241e2f0d16bc5cd413830f5c4c100a", + "eta3": "6f6ad2224d7d58aec6573c623ab110700eaca20a48dc2965d535e466d524af2a", + "T": 1, + "header_bytes": "32077c68f5a8c7ddb861bc7c7f7729e0ed5f68259b7d2fa766540970f6e51bc7914d51b33223f0e5a38f529193603bf0eaee0609e45f83312db70a644c817650622dc68065b00bb763f4b47499fdb714e215610622651e009c9e4576c47ccb3d4fd553000000000200a81d92d83542ddfe0f34a81ce306ad3032740a75c14c6e99890690caa647986bf0b815a3ca5543ad24fe67d723a9180266ff65dcc3fc4824f9d688ab1e528301c76c5290d655cc1d1ad079ce696b5a9368241e2f0d16bc5cd413830f5c4c100aa70cd5b81c52bb33540d75a2c68ba482f54758f347d1ef97b6f4f9708199f51c65bf730390ac46c4dc41e7842da6130fa86fdc5f72c49280737ca1af3bdc4f033da227994bcad21b05b44bf955fc0dc84312bae771c4f9f85ef699fa5380520e" +} \ No newline at end of file diff --git a/tests/integration/vectors_community/sealing/1-3.json b/tests/integration/vectors_community/sealing/1-3.json new file mode 100644 index 00000000..48871457 --- /dev/null +++ b/tests/integration/vectors_community/sealing/1-3.json @@ -0,0 +1,15 @@ +{ + "bandersnatch_pub": "7f6190116d118d643a98878e294ccf62b509e214299931aad8ff9764181a4e33", + "bandersnatch_priv": "40ad858dd0abe3016f7834831c93ae02764e0bb99ee204ffc6777b01c946ac0c", + "ticket_id": "0x39120d5b82981c7f5aba8247925f358afb9539839b61602a0726f51efb35ef4c", + "attempt": 0, + "c_for_H_s": "6a616d5f7469636b65745f7365616c835ac82bfa2ce8390bb50680d4b7a73dfa2a4cff6d8c30694b24a605f9574eaf00", + "m_for_H_s": "4b73a35b9d5aaa00d1f2c1c4ee9e72e600ccc35389b5eafacc1bdc7a23543a3ded158792da67fbce6337b1e164d8b321d6ffec50bee63c6b1d1c61a912d8749281823c1c98a422968cf9320172b1a8557559bd94c3e5ff417805917e01fdef684bd5530000000003007caaf1ed4574c2ee440d42747fb5bf70143466f2b84d23672a0b353dd6730e9fc880d82aee4e03586ae4a1f907c77582a5219a1a42612e079263d7567d91a71539a59653e2d858e6a77fe30c8533a0be67eab1efe79b16cdbacab523d172fb05", + "H_s": "ef3e2c81e818d8e07c05fc1af114c9102b46ca71f701eeecc0294e1db3934e958231f589b37e8ffe36aae7f03cf4191122407a471413f610d55959e76bf8da1ced1ea75f816146c2c247be471c656c6779788b8fc48e59f12c8f377ae232271b", + "c_for_H_v": "6a616d5f656e74726f707939120d5b82981c7f5aba8247925f358afb9539839b61602a0726f51efb35ef4c", + "m_for_H_v": "", + "H_v": "7caaf1ed4574c2ee440d42747fb5bf70143466f2b84d23672a0b353dd6730e9fc880d82aee4e03586ae4a1f907c77582a5219a1a42612e079263d7567d91a71539a59653e2d858e6a77fe30c8533a0be67eab1efe79b16cdbacab523d172fb05", + "eta3": "835ac82bfa2ce8390bb50680d4b7a73dfa2a4cff6d8c30694b24a605f9574eaf", + "T": 1, + "header_bytes": "4b73a35b9d5aaa00d1f2c1c4ee9e72e600ccc35389b5eafacc1bdc7a23543a3ded158792da67fbce6337b1e164d8b321d6ffec50bee63c6b1d1c61a912d8749281823c1c98a422968cf9320172b1a8557559bd94c3e5ff417805917e01fdef684bd5530000000003007caaf1ed4574c2ee440d42747fb5bf70143466f2b84d23672a0b353dd6730e9fc880d82aee4e03586ae4a1f907c77582a5219a1a42612e079263d7567d91a71539a59653e2d858e6a77fe30c8533a0be67eab1efe79b16cdbacab523d172fb05ef3e2c81e818d8e07c05fc1af114c9102b46ca71f701eeecc0294e1db3934e958231f589b37e8ffe36aae7f03cf4191122407a471413f610d55959e76bf8da1ced1ea75f816146c2c247be471c656c6779788b8fc48e59f12c8f377ae232271b" +} \ No newline at end of file diff --git a/tests/integration/vectors_community/sealing/1-4.json b/tests/integration/vectors_community/sealing/1-4.json new file mode 100644 index 00000000..a893a94e --- /dev/null +++ b/tests/integration/vectors_community/sealing/1-4.json @@ -0,0 +1,15 @@ +{ + "bandersnatch_pub": "48e5fcdce10e0b64ec4eebd0d9211c7bac2f27ce54bca6f7776ff6fee86ab3e3", + "bandersnatch_priv": "0dea7844b6b937f8b00acea90a8ce9dfe07fdbd2a1e4ff09022340d9bb159911", + "ticket_id": "0x1912baa74049a4cad89dc3f0646144459b691b926cf8b9c1c4a5bbfa1ee0c331", + "attempt": 1, + "c_for_H_s": "6a616d5f7469636b65745f7365616c835ac82bfa2ce8390bb50680d4b7a73dfa2a4cff6d8c30694b24a605f9574eaf01", + "m_for_H_s": "cf1ac703aadc427e46a798920d3342ea348e6d9b36a9300e12bf58620087cdf3abba70d4f0931a2df4c50e09cac7de2724c474f8c3ca5ecfc746ef323de9a16338cb8d47adc782a047e3ab0232062dbf6ac160d0eebd9a78de9f09af0d3cb74942d5530000000004009f9f647b5fe173545f735cfca7432b3edfb757f258e4b66980f672d2066b513863b8fcbab8533327586ae3adc6ed6ddbd5a5454f4bc3afc53e61d48a3fba15072f35e3ab005fcf3cb43471036d80f506f0410a65021738d4ca46e9d94afe2610", + "H_s": "3f0c489bcce6f070afa5d47151e75594eb9d84ba8b684f011addf723f2cb912f401782e8f60ed8e936cc13d7a7fdf7d4310fe55b4b6ee972084556352cc77c1470536efc5fbbc7f33eb57bd8ab9c44159b8dab044a1d19c857dac1fd8896c017", + "c_for_H_v": "6a616d5f656e74726f70791912baa74049a4cad89dc3f0646144459b691b926cf8b9c1c4a5bbfa1ee0c331", + "m_for_H_v": "", + "H_v": "9f9f647b5fe173545f735cfca7432b3edfb757f258e4b66980f672d2066b513863b8fcbab8533327586ae3adc6ed6ddbd5a5454f4bc3afc53e61d48a3fba15072f35e3ab005fcf3cb43471036d80f506f0410a65021738d4ca46e9d94afe2610", + "eta3": "835ac82bfa2ce8390bb50680d4b7a73dfa2a4cff6d8c30694b24a605f9574eaf", + "T": 1, + "header_bytes": "cf1ac703aadc427e46a798920d3342ea348e6d9b36a9300e12bf58620087cdf3abba70d4f0931a2df4c50e09cac7de2724c474f8c3ca5ecfc746ef323de9a16338cb8d47adc782a047e3ab0232062dbf6ac160d0eebd9a78de9f09af0d3cb74942d5530000000004009f9f647b5fe173545f735cfca7432b3edfb757f258e4b66980f672d2066b513863b8fcbab8533327586ae3adc6ed6ddbd5a5454f4bc3afc53e61d48a3fba15072f35e3ab005fcf3cb43471036d80f506f0410a65021738d4ca46e9d94afe26103f0c489bcce6f070afa5d47151e75594eb9d84ba8b684f011addf723f2cb912f401782e8f60ed8e936cc13d7a7fdf7d4310fe55b4b6ee972084556352cc77c1470536efc5fbbc7f33eb57bd8ab9c44159b8dab044a1d19c857dac1fd8896c017" +} \ No newline at end of file diff --git a/tests/integration/vectors_community/sealing/1-5.json b/tests/integration/vectors_community/sealing/1-5.json new file mode 100644 index 00000000..bb50c7aa --- /dev/null +++ b/tests/integration/vectors_community/sealing/1-5.json @@ -0,0 +1,15 @@ +{ + "bandersnatch_pub": "f16e5352840afb47e206b5c89f560f2611835855cf2e6ebad1acc9520a72591d", + "bandersnatch_priv": "68f5494ec1c3d3cd8ff2a3cb285abf0e826b2c762d95fc2e953eaef666315403", + "ticket_id": "0x265d1a74eb5e49a4e31a2c5de87477e9d59767c9e006892e5e765a7a41b931c4", + "attempt": 0, + "c_for_H_s": "6a616d5f7469636b65745f7365616c6f6ad2224d7d58aec6573c623ab110700eaca20a48dc2965d535e466d524af2a00", + "m_for_H_s": "05157a2b697ac9fd3f7829dc8d5784375f2246a8c20f38abbf4d486fcf1f42f2646b02154b7473f4dff8dfaa94caf75dce7d89c4d5072d0b4ba065f0b5260e664b96805a86b96cf230853fbd4ee16dff3f013a0780e5e6425ee088989ea49ee64ed5530000000005009b90b2678412ad35c8615762dac7bedbb23fbdc4b9248cb8dbfd4180f731753e9a327b0fd2a3991d7960019f6d9a23f26c4cfc54dcb898c7a821924198e99201a851e9149c1bfecf98fa688c91e7168f78fa24f2c9e14a08d4e7edaea58ea407", + "H_s": "30b6679a299999265b18c69a16c0702291b3a37447ec849b24b34fad03af5aa12de9b6070ebeb1d32e704f6d3ba2cb52c37705ad9fe0873e3a63fdb5e03a0d0ba8b3ea833d7c8a5853c8bea551cd39e52288d548fb714fa122ccd574974e070e", + "c_for_H_v": "6a616d5f656e74726f7079265d1a74eb5e49a4e31a2c5de87477e9d59767c9e006892e5e765a7a41b931c4", + "m_for_H_v": "", + "H_v": "9b90b2678412ad35c8615762dac7bedbb23fbdc4b9248cb8dbfd4180f731753e9a327b0fd2a3991d7960019f6d9a23f26c4cfc54dcb898c7a821924198e99201a851e9149c1bfecf98fa688c91e7168f78fa24f2c9e14a08d4e7edaea58ea407", + "eta3": "6f6ad2224d7d58aec6573c623ab110700eaca20a48dc2965d535e466d524af2a", + "T": 1, + "header_bytes": "05157a2b697ac9fd3f7829dc8d5784375f2246a8c20f38abbf4d486fcf1f42f2646b02154b7473f4dff8dfaa94caf75dce7d89c4d5072d0b4ba065f0b5260e664b96805a86b96cf230853fbd4ee16dff3f013a0780e5e6425ee088989ea49ee64ed5530000000005009b90b2678412ad35c8615762dac7bedbb23fbdc4b9248cb8dbfd4180f731753e9a327b0fd2a3991d7960019f6d9a23f26c4cfc54dcb898c7a821924198e99201a851e9149c1bfecf98fa688c91e7168f78fa24f2c9e14a08d4e7edaea58ea40730b6679a299999265b18c69a16c0702291b3a37447ec849b24b34fad03af5aa12de9b6070ebeb1d32e704f6d3ba2cb52c37705ad9fe0873e3a63fdb5e03a0d0ba8b3ea833d7c8a5853c8bea551cd39e52288d548fb714fa122ccd574974e070e" +} \ No newline at end of file