diff --git a/cl/transition/impl/eth2/operations.go b/cl/transition/impl/eth2/operations.go index ccefc9b280f..47c50dcc8d9 100644 --- a/cl/transition/impl/eth2/operations.go +++ b/cl/transition/impl/eth2/operations.go @@ -17,7 +17,6 @@ package eth2 import ( - "bytes" "errors" "fmt" "slices" @@ -42,6 +41,10 @@ import ( "github.com/erigontech/erigon/cl/utils" ) +func (I *impl) FullValidate() bool { + return I.FullValidation +} + func (I *impl) ProcessProposerSlashing( s abstract.BeaconState, propSlashing *cltypes.ProposerSlashing, @@ -73,33 +76,6 @@ func (I *impl) ProcessProposerSlashing( return fmt.Errorf("proposer is not slashable: %v", proposer) } - for _, signedHeader := range []*cltypes.SignedBeaconBlockHeader{propSlashing.Header1, propSlashing.Header2} { - domain, err := s.GetDomain( - s.BeaconConfig().DomainBeaconProposer, - state.GetEpochAtSlot(s.BeaconConfig(), signedHeader.Header.Slot), - ) - if err != nil { - return fmt.Errorf("unable to get domain: %v", err) - } - signingRoot, err := fork.ComputeSigningRoot(signedHeader.Header, domain) - if err != nil { - return fmt.Errorf("unable to compute signing root: %v", err) - } - pk := proposer.PublicKey() - valid, err := bls.Verify(signedHeader.Signature[:], signingRoot[:], pk[:]) - if err != nil { - return fmt.Errorf("unable to verify signature: %v", err) - } - if !valid { - return fmt.Errorf( - "invalid signature: signature %v, root %v, pubkey %v", - signedHeader.Signature[:], - signingRoot[:], - pk, - ) - } - } - // Set whistleblower index to 0 so current proposer gets reward. pr, err := s.SlashValidator(h1.ProposerIndex, nil) if I.BlockRewardsCollector != nil { @@ -264,35 +240,7 @@ func (I *impl) ProcessVoluntaryExit( if err != nil { return err } - validator, err := s.ValidatorForValidatorIndex(int(voluntaryExit.ValidatorIndex)) - if err != nil { - return err - } - // We can skip it in some instances if we want to optimistically sync up. - if I.FullValidation { - var domain []byte - if s.Version() < clparams.DenebVersion { - domain, err = s.GetDomain(s.BeaconConfig().DomainVoluntaryExit, voluntaryExit.Epoch) - } else if s.Version() >= clparams.DenebVersion { - domain, err = fork.ComputeDomain(s.BeaconConfig().DomainVoluntaryExit[:], utils.Uint32ToBytes4(uint32(s.BeaconConfig().CapellaForkVersion)), s.GenesisValidatorsRoot()) - } - if err != nil { - return err - } - signingRoot, err := fork.ComputeSigningRoot(voluntaryExit, domain) - if err != nil { - return err - } - pk := validator.PublicKey() - valid, err := bls.Verify(signedVoluntaryExit.Signature[:], signingRoot[:], pk[:]) - if err != nil { - return err - } - if !valid { - return errors.New("ProcessVoluntaryExit: BLS verification failed") - } - } // Do the exit (same process in slashing). return s.InitiateValidatorExit(voluntaryExit.ValidatorIndex) } @@ -477,7 +425,6 @@ func (I *impl) ProcessBlsToExecutionChange( signedChange *cltypes.SignedBLSToExecutionChange, ) error { change := signedChange.Message - beaconConfig := s.BeaconConfig() validator, err := s.ValidatorForValidatorIndex(int(change.ValidatorIndex)) if err != nil { @@ -486,39 +433,6 @@ func (I *impl) ProcessBlsToExecutionChange( // Perform full validation if requested. wc := validator.WithdrawalCredentials() - if I.FullValidation { - // Check the validator's withdrawal credentials prefix. - if wc[0] != byte(beaconConfig.BLSWithdrawalPrefixByte) { - return errors.New("invalid withdrawal credentials prefix") - } - - // Check the validator's withdrawal credentials against the provided message. - hashedFrom := utils.Sha256(change.From[:]) - if !bytes.Equal(hashedFrom[1:], wc[1:]) { - return errors.New("invalid withdrawal credentials") - } - - // Compute the signing domain and verify the message signature. - domain, err := fork.ComputeDomain( - beaconConfig.DomainBLSToExecutionChange[:], - utils.Uint32ToBytes4(uint32(beaconConfig.GenesisForkVersion)), - s.GenesisValidatorsRoot(), - ) - if err != nil { - return err - } - signedRoot, err := fork.ComputeSigningRoot(change, domain) - if err != nil { - return err - } - valid, err := bls.Verify(signedChange.Signature[:], signedRoot[:], change.From[:]) - if err != nil { - return err - } - if !valid { - return errors.New("invalid signature") - } - } credentials := wc // Reset the validator's withdrawal credentials. credentials[0] = byte(beaconConfig.ETH1AddressWithdrawalPrefixByte) diff --git a/cl/transition/machine/block.go b/cl/transition/machine/block.go index 608206f6a27..981cdd59659 100644 --- a/cl/transition/machine/block.go +++ b/cl/transition/machine/block.go @@ -17,10 +17,14 @@ package machine import ( + "bytes" "fmt" + "github.com/Giulio2002/bls" "github.com/erigontech/erigon/cl/abstract" + "github.com/erigontech/erigon/cl/fork" "github.com/erigontech/erigon/cl/phase1/core/state" + "github.com/erigontech/erigon/cl/utils" "github.com/pkg/errors" "github.com/erigontech/erigon/cl/clparams" @@ -79,10 +83,23 @@ func ProcessBlock(impl BlockProcessor, s abstract.BeaconState, block cltypes.Gen if err := impl.ProcessEth1Data(s, body.GetEth1Data()); err != nil { return fmt.Errorf("processBlock: failed to process Eth1 data: %v", err) } + // Process block body operations. - if err := ProcessOperations(impl, s, body); err != nil { + signatures, messages, pubKeys, err := ProcessOperations(impl, s, body) + if err != nil { return fmt.Errorf("processBlock: failed to process block body operations: %v", err) + } + + // process signature validation + valid, err := bls.VerifyMultipleSignatures(signatures, messages, pubKeys) + if err != nil { + return err + } + if !valid { + return errors.New("block signature validation failed") + } + // Process sync aggregate in case of Altair version. if version >= clparams.AltairVersion { if err := impl.ProcessSyncAggregate(s, body.GetSyncAggregate()); err != nil { @@ -94,33 +111,21 @@ func ProcessBlock(impl BlockProcessor, s abstract.BeaconState, block cltypes.Gen } // ProcessOperations is called by ProcessBlock and prcesses the block body operations -func ProcessOperations(impl BlockOperationProcessor, s abstract.BeaconState, blockBody cltypes.GenericBeaconBody) error { +func ProcessOperations(impl BlockOperationProcessor, s abstract.BeaconState, blockBody cltypes.GenericBeaconBody) (signatures [][]byte, messages [][]byte, publicKeys [][]byte, err error) { if blockBody.GetDeposits().Len() != int(maximumDeposits(s)) { - return errors.New("outstanding deposits do not match maximum deposits") - } - // Process each proposer slashing - var err error - if err := solid.RangeErr[*cltypes.ProposerSlashing](blockBody.GetProposerSlashings(), func(index int, slashing *cltypes.ProposerSlashing, length int) error { - if err = impl.ProcessProposerSlashing(s, slashing); err != nil { - return fmt.Errorf("ProcessProposerSlashing: %s", err) - } - return nil - }); err != nil { - return err + return nil, nil, nil, errors.New("outstanding deposits do not match maximum deposits") } - if err := solid.RangeErr[*cltypes.AttesterSlashing](blockBody.GetAttesterSlashings(), func(index int, slashing *cltypes.AttesterSlashing, length int) error { - if err = impl.ProcessAttesterSlashing(s, slashing); err != nil { - return fmt.Errorf("ProcessAttesterSlashing: %s", err) - } - return nil - }); err != nil { - return err + // Process each proposer slashing + sigs, msgs, pubKeys, err := processProposerSlashings(impl, s, blockBody) + if err != nil { + return } + signatures, messages, publicKeys = append(signatures, sigs...), append(messages, msgs...), append(publicKeys, pubKeys...) // Process each attestations if err := impl.ProcessAttestations(s, blockBody.GetAttestations()); err != nil { - return fmt.Errorf("ProcessAttestation: %s", err) + return nil, nil, nil, fmt.Errorf("ProcessAttestation: %s", err) } // Process each deposit @@ -130,31 +135,148 @@ func ProcessOperations(impl BlockOperationProcessor, s abstract.BeaconState, blo } return nil }); err != nil { - return err + return nil, nil, nil, err } // Process each voluntary exit. - if err := solid.RangeErr[*cltypes.SignedVoluntaryExit](blockBody.GetVoluntaryExits(), func(index int, exit *cltypes.SignedVoluntaryExit, length int) error { + sigs, msgs, pubKeys, err = processVoluntaryExits(impl, s, blockBody) + if err != nil { + return nil, nil, nil, err + } + signatures, messages, publicKeys = append(signatures, sigs...), append(messages, msgs...), append(publicKeys, pubKeys...) + + if s.Version() < clparams.CapellaVersion { + return + } + + // Process each execution change. this will only have entries after the capella fork. + sigs, msgs, pubKeys, err = processBlsToExecutionChanges(impl, s, blockBody) + if err != nil { + return nil, nil, nil, err + } + signatures, messages, publicKeys = append(signatures, sigs...), append(messages, msgs...), append(publicKeys, pubKeys...) + + return +} + +func processProposerSlashings(impl BlockOperationProcessor, s abstract.BeaconState, blockBody cltypes.GenericBeaconBody) (sigs [][]byte, msgs [][]byte, pubKeys [][]byte, err error) { + // Process each proposer slashing + err = solid.RangeErr[*cltypes.ProposerSlashing](blockBody.GetProposerSlashings(), func(index int, propSlashing *cltypes.ProposerSlashing, length int) error { + for _, signedHeader := range []*cltypes.SignedBeaconBlockHeader{propSlashing.Header1, propSlashing.Header2} { + proposer, err := s.ValidatorForValidatorIndex(int(propSlashing.Header1.Header.ProposerIndex)) + if err != nil { + return err + } + + domain, err := s.GetDomain( + s.BeaconConfig().DomainBeaconProposer, + state.GetEpochAtSlot(s.BeaconConfig(), signedHeader.Header.Slot), + ) + if err != nil { + return fmt.Errorf("unable to get domain: %v", err) + } + signingRoot, err := fork.ComputeSigningRoot(signedHeader.Header, domain) + if err != nil { + return fmt.Errorf("unable to compute signing root: %v", err) + } + pk := proposer.PublicKey() + sigs, msgs, pubKeys = append(sigs, signedHeader.Signature[:]), append(msgs, signingRoot[:]), append(pubKeys, pk[:]) + } + + if err = impl.ProcessProposerSlashing(s, propSlashing); err != nil { + return fmt.Errorf("ProcessProposerSlashing: %s", err) + } + return nil + }) + + return +} + +func processVoluntaryExits(impl BlockOperationProcessor, s abstract.BeaconState, blockBody cltypes.GenericBeaconBody) (sigs [][]byte, msgs [][]byte, pubKeys [][]byte, err error) { + // Process each voluntary exit. + err = solid.RangeErr[*cltypes.SignedVoluntaryExit](blockBody.GetVoluntaryExits(), func(index int, exit *cltypes.SignedVoluntaryExit, length int) error { + voluntaryExit := exit.VoluntaryExit + validator, err := s.ValidatorForValidatorIndex(int(voluntaryExit.ValidatorIndex)) + if err != nil { + return err + } + + // We can skip it in some instances if we want to optimistically sync up. + if impl.FullValidate() { + var domain []byte + if s.Version() < clparams.DenebVersion { + domain, err = s.GetDomain(s.BeaconConfig().DomainVoluntaryExit, voluntaryExit.Epoch) + } else if s.Version() >= clparams.DenebVersion { + domain, err = fork.ComputeDomain(s.BeaconConfig().DomainVoluntaryExit[:], utils.Uint32ToBytes4(uint32(s.BeaconConfig().CapellaForkVersion)), s.GenesisValidatorsRoot()) + } + if err != nil { + return err + } + signingRoot, err := fork.ComputeSigningRoot(voluntaryExit, domain) + if err != nil { + return err + } + pk := validator.PublicKey() + sigs, msgs, pubKeys = append(sigs, exit.Signature[:]), append(msgs, signingRoot[:]), append(pubKeys, pk[:]) + } + if err = impl.ProcessVoluntaryExit(s, exit); err != nil { return fmt.Errorf("ProcessVoluntaryExit: %s", err) } return nil - }); err != nil { - return err - } - if s.Version() < clparams.CapellaVersion { - return nil - } + }) + + return +} + +func processBlsToExecutionChanges(impl BlockOperationProcessor, s abstract.BeaconState, blockBody cltypes.GenericBeaconBody) (sigs [][]byte, msgs [][]byte, pubKeys [][]byte, err error) { // Process each execution change. this will only have entries after the capella fork. - if err := solid.RangeErr[*cltypes.SignedBLSToExecutionChange](blockBody.GetExecutionChanges(), func(index int, addressChange *cltypes.SignedBLSToExecutionChange, length int) error { + err = solid.RangeErr[*cltypes.SignedBLSToExecutionChange](blockBody.GetExecutionChanges(), func(index int, addressChange *cltypes.SignedBLSToExecutionChange, length int) error { + change := addressChange.Message + + beaconConfig := s.BeaconConfig() + validator, err := s.ValidatorForValidatorIndex(int(change.ValidatorIndex)) + if err != nil { + return err + } + + // Perform full validation if requested. + wc := validator.WithdrawalCredentials() + if impl.FullValidate() { + // Check the validator's withdrawal credentials prefix. + if wc[0] != byte(beaconConfig.BLSWithdrawalPrefixByte) { + return errors.New("invalid withdrawal credentials prefix") + } + + // Check the validator's withdrawal credentials against the provided message. + hashedFrom := utils.Sha256(change.From[:]) + if !bytes.Equal(hashedFrom[1:], wc[1:]) { + return errors.New("invalid withdrawal credentials") + } + + // Compute the signing domain and verify the message signature. + domain, err := fork.ComputeDomain( + beaconConfig.DomainBLSToExecutionChange[:], + utils.Uint32ToBytes4(uint32(beaconConfig.GenesisForkVersion)), + s.GenesisValidatorsRoot(), + ) + if err != nil { + return err + } + signedRoot, err := fork.ComputeSigningRoot(change, domain) + if err != nil { + return err + } + sigs, msgs, pubKeys = append(sigs, addressChange.Signature[:]), append(msgs, signedRoot[:]), append(pubKeys, change.From[:]) + } + if err := impl.ProcessBlsToExecutionChange(s, addressChange); err != nil { return fmt.Errorf("ProcessBlsToExecutionChange: %s", err) } return nil - }); err != nil { - return err - } - return nil + }) + + return } func maximumDeposits(s abstract.BeaconState) (maxDeposits uint64) { diff --git a/cl/transition/machine/machine.go b/cl/transition/machine/machine.go index 888ecac95d4..e160ed18bf1 100644 --- a/cl/transition/machine/machine.go +++ b/cl/transition/machine/machine.go @@ -60,4 +60,5 @@ type BlockOperationProcessor interface { ProcessDeposit(s abstract.BeaconState, deposit *cltypes.Deposit) error ProcessVoluntaryExit(s abstract.BeaconState, signedVoluntaryExit *cltypes.SignedVoluntaryExit) error ProcessBlsToExecutionChange(state abstract.BeaconState, signedChange *cltypes.SignedBLSToExecutionChange) error + FullValidate() bool }